C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Facepunch;
using Facepunch.Math;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Plugins.TruePVEExtensionMethods;
using Rust;
using Rust.Ai.Gen2;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
namespace Oxide.Plugins {
[Info("TruePVE", "nivex", "2.2.4")]
[Description("Improvement of the default Rust PVE behavior")]
// Thanks to the original author, ignignokt84.internal class TruePVE : RustPlugin
{
#region Variables
private static TruePVE Instance;
// config/data container
private Configuration config = new();
[PluginReference]
Plugin ZoneManager, LiteZones, Clans, Friends, AbandonedBases, RaidableBases;
public string usageString;
private enum Command {
def, sched, trace, usage, enable, sleepers
}
;
[Flags]
public enum RuleFlags : ulong {
None = 0, AdminsHurtSleepers = 1uL << 1, AnimalsIgnoreSleepers = 1uL << 2, AuthorizedDamage = 1uL << 3, AuthorizedDamageRequiresOwnership = 1uL << 4, CupboardOwnership = 1uL << 5, FriendlyFire = 1uL << 6, HeliDamageLocked = 1uL << 7, HumanNPCDamage = 1uL << 8, LockedBoxesImmortal = 1uL << 9, LockedDoorsImmortal = 1uL << 10, NoPlayerDamageToCar = 1uL << 11, NoPlayerDamageToMini = 1uL << 12, NoPlayerDamageToScrap = 1uL << 13, NoHeliDamage = 1uL << 14, NoHeliDamagePlayer = 1uL << 15, NoHeliDamageQuarry = 1uL << 16, NoHeliDamageRidableHorses = 1uL << 17, NoHeliDamageSleepers = 1uL << 18, NoMLRSDamage = 1uL << 19, NpcsCanHurtAnything = 1uL << 20, PlayerSamSitesIgnorePlayers = 1uL << 21, ProtectedSleepers = 1uL << 22, TrapsIgnorePlayers = 1uL << 23, TrapsIgnoreScientist = 1uL << 24, TurretsIgnorePlayers = 1uL << 25, TurretsIgnoreScientist = 1uL << 26, TwigDamage = 1uL << 27, TwigDamageRequiresOwnership = 1uL << 28, VehiclesTakeCollisionDamageWithoutDriver = 1uL << 29, SamSitesIgnoreMLRS = 1uL << 30, SelfDamage = 1uL << 31, StaticSamSitesIgnorePlayers = 1uL << 32, StaticTurretsIgnorePlayers = 1uL << 33, SafeZoneTurretsIgnorePlayers = 1uL << 34, SuicideBlocked = 1uL << 35, NoHeliDamageBuildings = 1uL << 36, WoodenDamage = 1uL << 37, WoodenDamageRequiresOwnership = 1uL << 38, AuthorizedDamageCheckPrivilege = 1uL << 39, ExcludeTugboatFromImmortalFlags = 1uL << 40,
}
public static RuleFlags GetRuleFlag(string value) {
return Enum.TryParse(value, out RuleFlags flag) ? flag : RuleFlags.None;
}
private Timer scheduleUpdateTimer;
// timer to check for schedule updates
private bool shareRedirectEnabled;
// undocumented.UAYOR.private RuleSet dudRuleSet; // dud ruleset when no locations are shared
private RuleSet currentRuleSet;
// current ruleset
private string currentBroadcastMessage;
// current broadcast message
private bool useZones;
// internal useZones flag
private const string Any = "any";
// constant "any" string for rules
private const string AllZones = "allzones";
// constant "allzones" string for mappings
private const string PermCanMap = "truepve.canmap";
// permission for mapping command
private bool animalsIgnoreSleepers;
// toggle flag to protect sleepers
private bool trace = false;
// trace flag
private const string traceFile = "ruletrace";
// tracefile name
private const float traceTimeout = 300f;
// auto-disable trace after 300s (5m)
private Timer traceTimer;
// trace timeout timer
private bool tpveEnabled = true;
// toggle flag for damage handling
private List<DamageType> damageTypes = new() {
DamageType.Arrow, DamageType.Blunt, DamageType.Bullet, DamageType.Explosion, DamageType.Cold, DamageType.Heat, DamageType.Generic, DamageType.Slash, DamageType.Stab,
}
;
#endregion
#region Loading/Unloading
private void Unload() {
Instance = null;
scheduleUpdateTimer?.Destroy();
SaveData();
}
private void OnPluginLoaded(Plugin plugin) {
if (plugin.Name == "ZoneManager") ZoneManager = plugin;
if (plugin.Name == "LiteZones") LiteZones = plugin;
if (ZoneManager != null || LiteZones != null) useZones = config == null || config.options.useZones;
}
private void OnPluginUnloaded(Plugin plugin) {
if (plugin.Name == "ZoneManager") ZoneManager = null;
if (plugin.Name == "LiteZones") LiteZones = null;
if (ZoneManager == null && LiteZones == null) useZones = false;
traceTimer?.Destroy();
}
private void Init() {
Unsubscribe(nameof(OnEntityMarkHostile));
Unsubscribe(nameof(OnEntitySpawned));
Unsubscribe(nameof(OnEntityEnter));
Unsubscribe(nameof(OnTurretTarget));
Unsubscribe(nameof(OnEntityTakeDamage));
Unsubscribe(nameof(OnPlayerConnected));
Unsubscribe(nameof(OnSamSiteTarget));
Unsubscribe(nameof(OnTrapTrigger));
Unsubscribe(nameof(OnNpcTarget));
Unsubscribe(nameof(OnMlrsFire));
Instance = this;
// register console commands automagically
foreach (Command command in Enum.GetValues(typeof(Command))) {
AddCovalenceCommand($"tpve.{command}", nameof(CommandDelegator));
}
// register chat commands
AddCovalenceCommand("tpve_prod", nameof(CommandDelegator));
AddCovalenceCommand("tpve_enable", nameof(CommandDelegator));
AddCovalenceCommand("tpve", nameof(CommandDelegator));
permission.RegisterPermission(PermCanMap, this);
// build usage string for console (without sizing)
usageString = WrapColor("orange", GetMessage("Header_Usage")) + $" - {Version}{Environment.NewLine}" + WrapColor("cyan", $"tpve.{Command.def}") + $" - {GetMessage("Cmd_Usage_def")}{Environment.NewLine}" + WrapColor("cyan", $"tpve.{Command.trace}") + $" - {GetMessage("Cmd_Usage_trace")}{Environment.NewLine}" + WrapColor("cyan", $"tpve.{Command.sched} [enable|disable]") + $" - {GetMessage("Cmd_Usage_sched")}{Environment.NewLine}" + WrapColor("cyan", $"/tpve_prod") + $" - {GetMessage("Cmd_Usage_prod")}{Environment.NewLine}" + WrapColor("cyan", $"/tpve map") + $" - {GetMessage("Cmd_Usage_map")}";
LoadData();
}
private void OnServerInitialized(bool isStartup) {
// check for server pve setting
if (ConVar.Server.pve) WarnPve();
// load configuration
config.Init();
currentRuleSet = config.GetDefaultRuleSet();
dudRuleSet = config.GetDudRuleSet();
if (currentRuleSet == null) PrintWarning(GetMessage("Warning_NoRuleSet"), config.defaultRuleSet);
useZones = config.options.useZones && (LiteZones != null || ZoneManager != null);
if (useZones && config.mappings.Count == 1 && config.mappings.FirstOrDefault().Key.Equals(config.defaultRuleSet)) useZones = false;
if (config.schedule.enabled) TimerLoop(true);
if (config.ruleSets.Exists(ruleSet => ruleSet.HasFlag(RuleFlags.AnimalsIgnoreSleepers))) Subscribe(nameof(OnNpcTarget));
if (currentRuleSet == null) return;
if (config.ruleSets.Exists(ruleSet => (ruleSet._flags & (RuleFlags.SafeZoneTurretsIgnorePlayers | RuleFlags.StaticTurretsIgnorePlayers | RuleFlags.TrapsIgnorePlayers | RuleFlags.TrapsIgnoreScientist | RuleFlags.TurretsIgnorePlayers | RuleFlags.TurretsIgnoreScientist)) != 0)) {
Subscribe(nameof(OnEntityEnter));
Subscribe(nameof(OnTurretTarget));
}
if (config.ruleSets.Exists(ruleSet => (ruleSet._flags & (RuleFlags.SamSitesIgnoreMLRS | RuleFlags.PlayerSamSitesIgnorePlayers | RuleFlags.StaticSamSitesIgnorePlayers)) != 0)) {
Subscribe(nameof(OnSamSiteTarget));
}
if (config.ruleSets.Exists(ruleSet => (ruleSet._flags & (RuleFlags.TrapsIgnorePlayers | RuleFlags.TrapsIgnoreScientist)) != 0)) {
Subscribe(nameof(OnTrapTrigger));
}
if (config.schedule.enabled && config.schedule.broadcast && !string.IsNullOrEmpty(currentBroadcastMessage)) {
Subscribe(nameof(OnPlayerConnected));
}
if (config.options.disableBaseOvenSplash) {
BaseNetworkable.serverEntities.OfType<BaseOven>().ForEach(oven => oven.disabledBySplash = false);
}
if (config.options.disableHostility) {
Subscribe(nameof(OnEntityMarkHostile));
}
Subscribe(nameof(OnEntitySpawned));
Subscribe(nameof(OnEntityTakeDamage));
Subscribe(nameof(OnMlrsFire));
}
#endregion
#region Data
private class StoredData {
public Dictionary<ulong, int> LastSeen = new();
public string LastRunTime {
get;
set;
}
= DateTime.MinValue.ToString();
}
private StoredData data = new();
private void LoadData() {
try {
data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name);
}
catch (Exception ex) {
Puts(ex.ToString());
}
data ??= new();
data.LastSeen ??= new();
if (data.LastRunTime != DateTime.MinValue.ToString() && DateTime.TryParse(data.LastRunTime, out var lastDate) && DateTime.Now.Subtract(lastDate).TotalHours >= 24) {
data = new();
data.LastRunTime = DateTime.Now.ToString();
Puts("Last seen data wiped due to plugin not being loaded for {0} day(s).", DateTime.Now.Subtract(lastDate).Days);
}
if (config.AllowKillingSleepersHoursOffline <= 0f) {
if (data.LastSeen.Count > 0) {
data.LastSeen.Clear();
SaveData();
}
return;
}
timer.Every(60f, UpdateLastSeen);
UpdateLastSeen();
}
private void SaveData() {
data.LastRunTime = DateTime.Now.ToString();
Interface.Oxide.DataFileSystem.WriteObject(Name, data);
}
public void UpdateLastSeen() {
bool changed = false;
foreach (var sleeper in BasePlayer.sleepingPlayerList) {
if (!sleeper || !sleeper.userID.IsSteamId()) {
continue;
}
if (!data.LastSeen.ContainsKey(sleeper.userID)) {
data.LastSeen[sleeper.userID] = Epoch.Current;
changed = true;
}
}
foreach (var player in BasePlayer.activePlayerList) {
if (data.LastSeen.Remove(player.userID)) {
changed = true;
}
}
if (changed) {
SaveData();
}
}
public bool CanKillOfflinePlayer(BasePlayer player) {
if (config.AllowKillingSleepersHoursOffline <= 0f) {
return false;
}
if (player.IsConnected || !player.IsSleeping()) {
data.LastSeen.Remove(player.userID);
return false;
}
if (!data.LastSeen.TryGetValue(player.userID, out var lastSeen)) {
return false;
}
return Epoch.Current - lastSeen > config.AllowKillingSleepersHoursOffline * 3600f;
}
#endregion Data
#region Command Handling
// delegation method for commands
private void CommandDelegator(IPlayer user, string command, string[] args) {
// return if user doesn't have access to run console command
if (!user.IsServer && !(user.Object as BasePlayer).IsAdmin) return;
if (args.Length > 0 && args[0] == "map" && user.HasPermission(PermCanMap)) {
CommandMap(user, command, args);
return;
}
if (args.Contains("pvp")) {
if (!currentRuleSet.rules.Remove("players cannot hurt players")) {
currentRuleSet.rules.Add("players cannot hurt players");
}
Puts("PVP toggled {0}", currentRuleSet.rules.Contains("players cannot hurt players") ? "on" : "off");
SaveConfig();
return;
}
if (command == "tpve_prod") {
HandleProd(user);
return;
}
if (command == "tpve_enable") {
Message(user, "Enable", tpveEnabled = !tpveEnabled);
return;
}
if (command == "tpve" && args.Length != 0) command = args[0];
else command = command.Replace("tpve.", string.Empty);
if (!Enum.TryParse(command, out TruePVE.Command @enum)) {
user.Reply($"Invalid argument: {command}");
return;
}
switch (@enum) {
case Command.sleepers : HandleSleepers(user);
return;
case Command.def : HandleDef(user);
return;
case Command.sched : HandleScheduleSet(user, args);
return;
case Command.trace : if (!IsTraceEnabled(user)) {
return;
}
if (user.IsServer) {
traceDistance = 0f;
}
else traceDistance = config.options.MaxTraceDistance;
trace = !trace;
if (!trace) {
tracePlayer = null;
traceEntity = null;
}
else tracePlayer = user.Object as BasePlayer;
Message(user, "Notify_TraceToggle", new object[] {
trace ? "on" : "off"
}
);
if (trace) {
traceTimer = timer.In(traceTimeout, () => trace = false);
}
else traceTimer?.Destroy();
return;
case Command.enable : Message(user, "Enable", tpveEnabled = !tpveEnabled);
return;
case Command.usage : default : ShowUsage(user);
return;
}
}
private bool IsTraceEnabled(IPlayer user) {
if (config.options.PlayerConsole || config.options.ServerConsole) {
return true;
}
Message(user, "`Trace To Player Console` or `Trace To Server Console` must be enabled in the config!");
return false;
}
private void HandleSleepers(IPlayer user) {
if (animalsIgnoreSleepers) {
animalsIgnoreSleepers = false;
if (!config.ruleSets.Exists(ruleSet => ruleSet.HasFlag(RuleFlags.AnimalsIgnoreSleepers))) Unsubscribe(nameof(OnNpcTarget));
user.Reply("Sleepers are no longer protected from animals.");
}
else {
animalsIgnoreSleepers = true;
Subscribe(nameof(OnNpcTarget));
user.Reply("Sleepers are now protected from animals.");
}
}
// handle setting defaults
private void HandleDef(IPlayer user) {
config.options = new();
Message(user, "Notify_DefConfigLoad");
LoadDefaultData();
Message(user, "Notify_DefDataLoad");
SaveConfig();
}
// handle prod command (raycast to determine what player is looking at)
private void HandleProd(IPlayer user) {
var player = user.Object as BasePlayer;
if (!player || !player.IsAdmin) {
Message(user, "Error_NoPermission");
return;
}
if (!GetRaycastTarget(player, out var entity)) {
SendReply(player, WrapSize(12, WrapColor("red", GetMessage("Error_NoEntityFound", player.UserIDString))));
return;
}
Message(player, "Notify_ProdResult", new object[] {
entity.GetType(), entity.ShortPrefabName
}
);
}
private void CommandMap(IPlayer user, string command, string[] args) {
// assume args[0] is the command (beyond /tpve)
if (args.Length > 0) command = args[0];
// shift arguments
args = args.Length > 1 ? args.Skip(1) : new string[0];
if (command != "map") {
Message(user, "Error_InvalidCommand");
}
else if (args.Length == 0) {
Message(user, "Error_InvalidParamForCmd", command);
}
else {
string from = args[0];
// mapping name
string to = args.Length == 2 ? args[1] : null;
// target ruleSet/exclude, otherwise delete mapping
if (to != null) {
if (to != "exclude" && !config.ruleSets.Exists(r => r.name == to)) {
// target ruleset must exist, or be "exclude"
Message(user, "Error_InvalidMapping", from, to);
return;
}
if (config.HasMapping(from)) {
string old = config.mappings[from];
Message(user, "Notify_MappingUpdated", from, old, to);
// update existing mapping
}
else Message(user, "Notify_MappingCreated", from, to);
// add new mapping
config.mappings[from] = to;
SaveConfig();
}
else {
if (config.HasMapping(from)) {
Message(user, "Notify_MappingDeleted", from, config.mappings[from]);
config.mappings.Remove(from);
// remove mapping
SaveConfig();
}
else Message(user, "Error_NoMappingToDelete", from);
}
}
}
// handles schedule enable/disable
private void HandleScheduleSet(IPlayer user, string[] args) {
if (args.Length == 0) {
Message(user, "Error_InvalidParamForCmd");
return;
}
if (!config.schedule.valid) {
Message(user, "Notify_InvalidSchedule");
}
else if (args[0] == "enable") {
if (config.schedule.enabled) return;
config.schedule.enabled = true;
TimerLoop();
Message(user, "Notify_SchedSetEnabled");
}
else if (args[0] == "disable") {
if (!config.schedule.enabled) return;
config.schedule.enabled = false;
if (scheduleUpdateTimer != null) scheduleUpdateTimer.Destroy();
Message(user, "Notify_SchedSetDisabled");
}
else {
Message(user, "Error_InvalidParameter", args[0]);
}
}
#endregion
#region Configuration/Data
// load config
protected override void LoadConfig() {
base.LoadConfig();
try {
config = Config.ReadObject<Configuration>();
if (config == null) LoadDefaultConfig();
CheckData();
SaveConfig();
}
catch (Exception ex) {
canSaveConfig = false;
Puts("{0}", ex);
LoadDefaultConfig();
}
}
protected override void LoadDefaultConfig() {
config = new Configuration {
configVersion = Version.ToString(), options = new ConfigurationOptions()
}
;
LoadDefaultData();
}
private bool canSaveConfig = true;
// save data
protected override void SaveConfig() {
if (canSaveConfig) {
Config.WriteObject(config);
}
}
// check rulesets and groups
private void CheckData() {
if (config.ruleSets.IsNullOrEmpty() || config.groups.IsNullOrEmpty()) {
LoadDefaultData();
}
if (config.schedule == null) {
config.schedule = new Schedule();
}
// check config version, update version to current version
if (config.configVersion != Version.ToString()) {
config.configVersion = Version.ToString();
}
CheckMappings();
}
// rebuild mappings
private bool CheckMappings() {
bool dirty = false;
foreach (RuleSet rs in config.ruleSets) {
if (!config.mappings.ContainsValue(rs.name)) {
config.mappings[rs.name] = rs.name;
dirty = true;
}
}
return dirty;
}
// load default data to mappings, rulesets, and groups
private bool LoadDefaultData() {
config.mappings.Clear();
config.ruleSets.Clear();
config.groups.Clear();
config.schedule = new();
config.defaultRuleSet = "default";
// build groups first
config.groups.Add(new("barricades") {
members = "door_barricade_a, door_barricade_a_large, door_barricade_b, door_barricade_dbl_a, door_barricade_dbl_a_large, door_barricade_dbl_b, door_barricade_dbl_b_large, gingerbread_barricades_house, gingerbread_barricades_snowman, gingerbread_barricades_tree, wooden_crate_gingerbread, icewall, GraveYardFence", exclusions = "barricade.concrete, barricade.sandbags, barricade.stone"
}
);
config.groups.Add(new("barricades2") {
members = "spikes_static, barricade.metal, barricade.wood, barricade.woodwire, spikes.floor",
}
);
config.groups.Add(new("dispensers") {
members = "BaseCorpse, HelicopterDebris, PlayerCorpse, NPCPlayerCorpse, HorseCorpse, SkyLantern, Pinata"
}
);
config.groups.Add(new("fire") {
members = "FireBall, FlameExplosive, FlameThrower, BaseOven, FlameTurret, rocket_heli_napalm, napalm, oilfireball2"
}
);
config.groups.Add(new("guards") {
members = "bandit_guard, scientistpeacekeeper, sentry.scientist.static"
}
);
config.groups.Add(new("heli") {
members = "PatrolHelicopter"
}
);
config.groups.Add(new("highwalls") {
members = "SimpleBuildingBlock, wall.external.high.ice, gates.external.high.stone, gates.external.high.wood"
}
);
config.groups.Add(new("ridablehorses") {
members = "RidableHorse"
}
);
config.groups.Add(new("cars") {
members = "BasicCar, ModularCar, BaseModularVehicle, BaseVehicleModule, VehicleModuleEngine, VehicleModuleSeating, VehicleModuleStorage, VehicleModuleTaxi, ModularCarSeat, Bike"
}
);
config.groups.Add(new("mini") {
members = "minicopter.entity"
}
);
config.groups.Add(new("scrapheli") {
members = "ScrapTransportHelicopter"
}
);
config.groups.Add(new("ch47") {
members = "ch47.entity"
}
);
config.groups.Add(new("npcs") {
members = "ch47scientists.entity, BradleyAPC, CustomScientistNpc, ScarecrowNPC, HumanNPC, NPCPlayer, ScientistNPC, TunnelDweller, SimpleShark, UnderwaterDweller, ZombieNPC"
}
);
config.groups.Add(new("players") {
members = "BasePlayer, FrankensteinPet"
}
);
config.groups.Add(new("resources") {
members = "ResourceEntity, TreeEntity, OreResourceEntity, LootContainer", exclusions = "hobobarrel.deployed"
}
);
config.groups.Add(new("snowmobiles") {
members = "snowmobile, tomahasnowmobile"
}
);
config.groups.Add(new("traps") {
members = "AutoTurret, BearTrap, FlameTurret, Landmine, GunTrap, ReactiveTarget, TeslaCoil, spikes.floor"
}
);
config.groups.Add(new("junkyard") {
members = "magnetcrane.entity, carshredder.entity"
}
);
config.groups.Add(new("tugboats") {
members = "Tugboat"
}
);
config.groups.Add(new("heliturrets") {
members = "turret_attackheli"
}
);
// create default ruleset
RuleSet defaultRuleSet = new(config.defaultRuleSet) {
_flags = RuleFlags.HumanNPCDamage | RuleFlags.LockedBoxesImmortal | RuleFlags.LockedDoorsImmortal | RuleFlags.PlayerSamSitesIgnorePlayers | RuleFlags.TrapsIgnorePlayers | RuleFlags.TurretsIgnorePlayers, flags = "HumanNPCDamage, LockedBoxesImmortal, LockedDoorsImmortal, PlayerSamSitesIgnorePlayers, TrapsIgnorePlayers, TurretsIgnorePlayers"
}
;
// create rules and add to ruleset
defaultRuleSet.AddRule("anything can hurt dispensers");
defaultRuleSet.AddRule("anything can hurt resources");
defaultRuleSet.AddRule("anything can hurt barricades");
defaultRuleSet.AddRule("anything can hurt traps");
defaultRuleSet.AddRule("anything can hurt heli");
defaultRuleSet.AddRule("anything can hurt npcs");
defaultRuleSet.AddRule("anything can hurt players");
defaultRuleSet.AddRule("nothing can hurt ch47");
defaultRuleSet.AddRule("nothing can hurt cars");
defaultRuleSet.AddRule("nothing can hurt mini");
defaultRuleSet.AddRule("nothing can hurt snowmobiles");
//defaultRuleSet.AddRule("nothing can hurt guards");
defaultRuleSet.AddRule("nothing can hurt ridablehorses");
defaultRuleSet.AddRule("cars cannot hurt anything");
defaultRuleSet.AddRule("mini cannot hurt anything");
defaultRuleSet.AddRule("ch47 cannot hurt anything");
defaultRuleSet.AddRule("scrapheli cannot hurt anything");
defaultRuleSet.AddRule("players cannot hurt players");
defaultRuleSet.AddRule("players cannot hurt traps");
defaultRuleSet.AddRule("guards cannot hurt players");
defaultRuleSet.AddRule("fire cannot hurt players");
defaultRuleSet.AddRule("traps cannot hurt players");
defaultRuleSet.AddRule("highwalls cannot hurt players");
defaultRuleSet.AddRule("barricades cannot hurt players");
defaultRuleSet.AddRule("barricades2 cannot hurt players");
defaultRuleSet.AddRule("mini cannot hurt mini");
defaultRuleSet.AddRule("npcs can hurt players");
defaultRuleSet.AddRule("junkyard cannot hurt anything");
defaultRuleSet.AddRule("junkyard can hurt cars");
defaultRuleSet.AddRule("players cannot hurt tugboats");
defaultRuleSet.AddRule("heliturrets cannot hurt players");
config.ruleSets.Add(defaultRuleSet);
// add ruleset to rulesets list
config.mappings[config.defaultRuleSet] = config.defaultRuleSet;
// create mapping for ruleset
return true;
}
private bool ResetRules(string key) {
if (string.IsNullOrEmpty(key) || config == null) {
return false;
}
string old = config.defaultRuleSet;
config.defaultRuleSet = key;
currentRuleSet = config.GetDefaultRuleSet();
if (currentRuleSet == null) {
config.defaultRuleSet = old;
currentRuleSet = config.GetDefaultRuleSet();
return false;
}
return true;
}
#endregion
#region Trace
private StringBuilder _tsb = new();
private BaseEntity traceEntity;
private BasePlayer tracePlayer;
private float traceDistance;
private void Trace(string message, int indentation = 0) {
if (!traceEntity || traceEntity.IsDestroyed) {
return;
}
bool playerInRange = tracePlayer != null && !tracePlayer.IsDestroyed && tracePlayer.Distance(traceEntity) <= traceDistance;
if ((config.options.PlayerConsole && playerInRange) || (config.options.ServerConsole && (traceDistance == 0 || playerInRange))) {
_tsb.AppendLine(string.Empty.PadLeft(indentation, ' ') + message);
}
}
private void LogTrace() {
var text = _tsb.ToString();
traceEntity = null;
_tsb.Length = 0;
try {
if (!string.IsNullOrEmpty(text)) {
if (config.options.ServerConsole) {
Puts(text);
}
if (config.options.PlayerConsole && tracePlayer.IsOnline()) {
tracePlayer.ConsoleMessage(text);
}
if (config.options.LogToFile) {
LogToFile(traceFile, text, this);
}
}
}
catch (IOException) {
timer.Once(1f, () => LogToFile(traceFile, text, this));
}
}
#endregion Trace
#region Hooks/Handler Procedures
private void OnPlayerConnected(BasePlayer player) {
if (config.schedule.broadcast && !string.IsNullOrEmpty(currentBroadcastMessage)) {
SendReply(player, GetMessage("Prefix") + currentBroadcastMessage);
}
}
private string CurrentRuleSetName() => currentRuleSet?.name;
private bool IsEnabled() => tpveEnabled;
private class PlayerExclusion : Pool.IPooled {
public Plugin plugin;
public float time;
public bool IsExpired => Time.time > time;
public void EnterPool() {
plugin = null;
time = 0f;
}
public void LeavePool() {
plugin = null;
time = 0f;
}
}
private Dictionary<ulong, List<PlayerExclusion>> playerDelayExclusions = new();
private void ExcludePlayer(ulong userid, float maxDelayLength, Plugin plugin) {
if (plugin == null) {
return;
}
if (!playerDelayExclusions.TryGetValue(userid, out var exclusions)) {
playerDelayExclusions[userid] = exclusions = Pool.Get<List<PlayerExclusion>>();
}
var exclusion = exclusions.Find(x => x.plugin == plugin);
if (maxDelayLength <= 0f) {
if (exclusion != null) {
exclusions.Remove(exclusion);
exclusion.plugin = null;
exclusion.time = 0f;
Pool.Free(ref exclusion);
}
if (exclusions.Count == 0) {
playerDelayExclusions.Remove(userid);
Pool.FreeUnmanaged(ref exclusions);
}
}
else {
if (exclusion == null) {
exclusion = Pool.Get<PlayerExclusion>();
exclusion.plugin = plugin;
exclusions.Add(exclusion);
}
exclusion.time = Time.time + maxDelayLength;
}
}
private bool HasDelayExclusion(ulong userid) {
if (playerDelayExclusions.TryGetValue(userid, out var exclusions)) {
for (int i = 0; i < exclusions.Count; i++) {
var exclusion = exclusions[i];
if (!exclusion.IsExpired) {
return true;
}
exclusions.RemoveAt(i);
exclusion.plugin = null;
exclusion.time = 0f;
Pool.Free(ref exclusion);
i--;
}
if (exclusions.Count == 0) {
playerDelayExclusions.Remove(userid);
Pool.Free(ref exclusions);
}
}
return false;
}
// handle damage - if another mod must override TruePVE damages or take priority,
// set handleDamage to false and reference HandleDamage from the other mod(s)
private object OnEntityTakeDamage(ResourceEntity entity, HitInfo hitInfo) {
// if default global is not enabled, return true (allow all damage)
if (!IsEnabled() || hitInfo == null || currentRuleSet == null || currentRuleSet.IsEmpty() || !currentRuleSet.enabled) {
return null;
}
// get entity and initiator locations (zones)
List<string> entityLocations = GetLocationKeys(entity);
List<string> initiatorLocations = GetLocationKeys(hitInfo.Initiator);
// check for exclusion zones (zones with no rules mapped)
if (CheckExclusion(entityLocations, initiatorLocations, trace)) {
if (trace) Trace("Exclusion found; allow and return", 1);
return null;
}
if (trace) Trace("No exclusion found - looking up RuleSet...", 1);
// process location rules
RuleSet ruleSet = GetRuleSet(entityLocations, initiatorLocations);
return EvaluateRules(entity, hitInfo, ruleSet) ? (object)null : true;
}
private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo hitInfo) {
if (entity.IsNull() || entity.IsDestroyed || hitInfo == null || AllowKillingSleepers(entity, hitInfo)) {
return null;
}
if (Interface.CallHook("CanEntityTakeDamage", new object[] {
entity, hitInfo
}
) is bool val) {
if (val) {
return null;
}
CancelHit(hitInfo);
return true;
}
if (!IsEnabled() || !config.options.handleDamage) {
return null;
}
var damageAmount = hitInfo.damageTypes.Total();
if (damageAmount <= 0f) {
return null;
}
var victim = entity as BasePlayer;
if (config.scrap) {
if (hitInfo.Initiator is ScrapTransportHelicopter && victim) {
hitInfo.damageTypes.Clear();
return null;
}
if (hitInfo.Initiator is BasePlayer driver && (hitInfo.WeaponPrefab is ScrapTransportHelicopter || driver.GetMountedVehicle() is ScrapTransportHelicopter)) {
hitInfo.damageTypes.Clear();
return null;
}
}
var damageType = hitInfo.damageTypes.GetMajorityDamageType();
if (damageType == DamageType.Decay || damageType == DamageType.Fall || damageType == DamageType.Radiation) {
return null;
}
if (config.igniter && entity is Igniter && entity.OwnerID != 0) {
hitInfo.damageTypes.Clear();
return null;
}
var weapon = hitInfo.Initiator ?? hitInfo.WeaponPrefab ?? hitInfo.Weapon;
if ((damageType == DamageType.Cold || damageType == DamageType.Heat) && IsMetabolismDamage(victim, damageType, damageAmount)) {
if (trace) Trace($"Initiator is {damageType} metabolism damage; {((damageType == DamageType.Cold ? config.options.Cold : config.options.Heat) ? "allow and return" : "block and return")}", 1);
if ((damageType == DamageType.Cold && !config.options.Cold) || (damageType == DamageType.Heat && !config.options.Heat)) hitInfo.damageTypes.Clear();
if (trace) LogTrace();
return null;
}
if (!AllowDamage(weapon, victim, entity, hitInfo, damageType, damageAmount)) {
if (trace) LogTrace();
CancelHit(hitInfo);
return true;
}
if (trace) LogTrace();
return null;
}
private bool IsTrap(BaseEntity entity) => entity is BaseTrap || entity is HBHFSensor || entity is GunTrap;
private bool IsTurret(BaseEntity entity) => entity is FlameTurret || entity is AutoTurret;
private bool ContainsTopology(TerrainTopology.Enum mask, Vector3 position, float radius) => (TerrainMeta.TopologyMap.GetTopology(position, radius) & (int)mask) != 0;
private bool CanPlayerTriggerTurretOrTrap(BasePlayer victim, BaseEntity entity, BaseEntity weapon) {
if (weapon != null && weapon.OwnerID == 0uL && victim != null && victim.userID.IsSteamId() && (config.PlayersTriggerTraps && IsTrap(weapon) || config.PlayersTriggerTurrets && IsTurret(weapon))) {
return ContainsTopology(TerrainTopology.Enum.Monument, weapon.transform.position, 5f);
}
return false;
}
private bool CanPlayerHurtTurretOrTrap(BasePlayer victim, BaseEntity entity, BaseEntity weapon) {
if (entity.OwnerID == 0uL && weapon is BasePlayer attacker && attacker.userID.IsSteamId() && (config.PlayersHurtTraps && IsTrap(entity) || config.PlayersHurtTurrets && IsTurret(entity))) {
return ContainsTopology(TerrainTopology.Enum.Monument, weapon.transform.position, 5f);
}
return false;
}
private void CancelHit(HitInfo hitInfo) {
hitInfo.damageTypes = new();
hitInfo.DidHit = false;
hitInfo.DoHitEffects = false;
}
private bool AllowKillingSleepers(BaseCombatEntity entity, HitInfo hitInfo) {
if (entity.ShortPrefabName == "player" && (config.AllowKillingSleepersAlly || config.AllowKillingSleepers || config.AllowKillingSleepersAuthorization)) {
var victim = entity.ToPlayer();
if (!victim.IsSleeping()) {
return false;
}
if (config.AllowKillingSleepersAuthorization && hitInfo.Initiator is BasePlayer attacker && AllowAuthorizationDamage(victim, attacker)) {
return true;
}
if (config.AllowKillingSleepersAlly && hitInfo.Initiator is BasePlayer attacker2) {
return IsAlly(victim.userID, attacker2.userID);
}
return config.AllowKillingSleepers;
}
return false;
}
private bool IsAuthed(DecayEntity entity, BasePlayer attacker) {
if (entity is LegacyShelter || entity is LegacyShelterDoor) {
return entity.GetEntityBuildingPrivilege() is EntityPrivilege entityPriv && entityPriv.AnyAuthed() && entityPriv.IsAuthed(attacker);
}
return entity.GetBuilding() is BuildingManager.Building building && building.GetDominatingBuildingPrivilege() is BuildingPrivlidge priv && priv.AnyAuthed() && priv.IsAuthed(attacker);
}
public bool IsAuthed(Tugboat tugboat, BasePlayer attacker) {
return !tugboat.children.IsNullOrEmpty() && tugboat.children.Exists(child => child is VehiclePrivilege vehiclePrivilege && vehiclePrivilege.AnyAuthed() && vehiclePrivilege.IsAuthed(attacker));
}
public bool IsAuthed(BaseHelicopter heli, BasePlayer attacker) {
return attacker.GetBuildingPrivilege(heli.WorldSpaceBounds()) is BuildingPrivlidge priv && priv.AnyAuthed() && priv.IsAuthed(attacker);
}
private bool AllowAuthorizationDamage(BasePlayer victim, BasePlayer attacker) {
if (!attacker.userID.IsSteamId()) {
return false;
}
if (victim.GetParentEntity() is Tugboat tugboat && tugboat.IsAuthed(attacker)) {
return true;
}
if (victim.GetBuildingPrivilege() is BuildingPrivlidge priv && priv.IsAuthed(attacker)) {
return true;
}
return false;
}
// determines if an entity is "allowed" to take damage
private bool AllowDamage(BaseEntity weapon, BasePlayer victim, BaseCombatEntity entity, HitInfo hitInfo, DamageType damageType, float damageAmount) {
if (trace) {
traceEntity = entity;
_tsb.Length = 0;
}
// if default global is not enabled or entity is npc, allow all damage
if (currentRuleSet == null || currentRuleSet.IsEmpty() || !currentRuleSet.enabled) {
return true;
}
if (entity is BaseNpc || entity is BaseNPC2) {
if (trace) Trace("Target is animal; allow and return", 1);
return true;
}
// allow damage to door barricades and covers
if (entity.PrefabName.Contains("trainbarricade") || entity is Barricade && (entity.ShortPrefabName.Contains("door_barricade") || entity.ShortPrefabName.Contains("cover"))) {
if (trace) Trace($"Target is {entity.ShortPrefabName}; allow and return", 1);
return true;
}
// if entity is a barrel, trash can, or giftbox, allow damage (exclude water and hobo barrels)
if (!entity.ShortPrefabName.Equals("waterbarrel") && entity.prefabID != 1748062128 && ((entity.ShortPrefabName.Contains("barrel") && entity is LootContainer) || entity.ShortPrefabName.Equals("loot_trash") || entity.ShortPrefabName.Equals("giftbox_loot"))) {
if (trace) Trace("Target is barrel; allow and return", 1);
return true;
}
TrySetInitiator(hitInfo);
if (trace) {
// Sometimes the initiator is not the attacker (turrets)
Trace("======================" + Environment.NewLine + "== STARTING TRACE ==" + Environment.NewLine + "== " + DateTime.Now.ToString("HH:mm:ss.fffff") + " ==" + Environment.NewLine + "======================");
Trace($"From: {weapon?.GetType().Name ?? "Unknown_Weapon"}, {weapon?.ShortPrefabName ?? "Unknown_Prefab"}", 1);
Trace($"To: {entity.GetType().Name}, {entity.ShortPrefabName}", 1);
}
// get entity and initiator locations (zones)
List<string> entityLocations = GetLocationKeys(entity);
List<string> initiatorLocations = GetLocationKeys(weapon);
// check for exclusion zones (zones with no rules mapped)
if (CheckExclusion(entityLocations, initiatorLocations, trace)) {
if (trace) Trace("Exclusion found; allow and return", 1);
return true;
}
if ((config.PlayersHurtTraps || config.PlayersHurtTurrets) && CanPlayerHurtTurretOrTrap(victim, entity, weapon)) {
if (trace) Trace($"Initiator is player; Target is turret or trap in monument topology; allow and return", 1);
return true;
}
if ((config.PlayersTriggerTraps || config.PlayersTriggerTurrets) && CanPlayerTriggerTurretOrTrap(victim, entity, weapon)) {
if (trace) Trace($"Initiator is turret or trap in monument topology; Target is player; allow and return", 1);
return true;
}
if (trace) Trace("No exclusion found - looking up RuleSet...", 1);
// process location rules
RuleSet ruleSet = GetRuleSet(entityLocations, initiatorLocations);
if (trace) Trace($"Using RuleSet \"{ruleSet.name}\"", 1);
var attacker = hitInfo.Initiator as BasePlayer;
var isAttacker = !attacker.IsKilled();
var isVictim = !victim.IsKilled();
if (isVictim) {
if (isAttacker) {
if (CanKillOfflinePlayer(victim)) {
if (trace) Trace($"Initiator ({attacker}) and target ({victim} exceeds Allow Killing Sleepers offline time); allow and return", 1);
return true;
}
bool atkCondition = HasDelayExclusion(attacker.userID) || (config.options.Aboveworld < 5000f && attacker.transform.position.y >= config.options.Aboveworld) || (config.options.Underworld > -500f && attacker.transform.position.y <= config.options.Underworld) || (!initiatorLocations.IsNullOrEmpty() && initiatorLocations.Exists(loc => config.mappings.TryGetValue(loc, out var mapping) && mapping.Equals("exclude")));
if (atkCondition) {
bool vicCondition = HasDelayExclusion(victim.userID) || (config.options.Aboveworld < 5000f && victim.transform.position.y >= config.options.Aboveworld) || (config.options.Underworld > -500f && victim.transform.position.y <= config.options.Underworld) || (!entityLocations.IsNullOrEmpty() && entityLocations.Exists(loc => config.mappings.TryGetValue(loc, out var mapping) && mapping.Equals("exclude")));
if (vicCondition) {
if (trace) Trace($"Initiator ({attacker}) and target ({victim}) meet exclusion conditions; allow and return", 1);
return true;
}
}
}
if (config.options.UnderworldOther > -500f && (!isAttacker || !attacker.userID.IsSteamId()) && victim.transform.position.y <= config.options.UnderworldOther) {
if (trace) Trace($"Initiator is {weapon} under world; Target is player; allow and return", 1);
return true;
}
if (config.options.AboveworldOther < 5000f && (!isAttacker || !attacker.userID.IsSteamId()) && victim.transform.position.y >= config.options.AboveworldOther) {
if (trace) Trace($"Initiator is {weapon} above world; Target is player; allow and return", 1);
// ... [Code preview truncated due to length. Full source in attached file] ...