C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Facepunch;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Rust;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("TruePVE", "nivex", "2.0.6")]
[Description("Improvement of the default Rust PVE behavior")]
// Thanks to the original author, ignignokt84.class TruePVE : RustPlugin
{
#region Variables
private static TruePVE Instance;
// config/data container
private Configuration config = new Configuration();
[PluginReference]
Plugin ZoneManager, LiteZones, Clans, Friends;
// usage information string with formatting
public string usageString;
// valid commands
private enum Command { def, sched, trace, usage, enable, sleepers };
// default values array
// flags for RuleSets
[Flags]
private enum RuleFlags
{
None = 0,
SuicideBlocked = 1,
AuthorizedDamage = 1 << 1,
NoHeliDamage = 1 << 2,
HeliDamageLocked = 1 << 3,
NoHeliDamagePlayer = 1 << 4,
HumanNPCDamage = 1 << 5,
LockedBoxesImmortal = 1 << 6,
LockedDoorsImmortal = 1 << 7,
AdminsHurtSleepers = 1 << 8,
ProtectedSleepers = 1 << 9,
CupboardOwnership = 1 << 10,
SelfDamage = 1 << 11,
TwigDamage = 1 << 12,
NoHeliDamageQuarry = 1 << 13,
TrapsIgnorePlayers = 1 << 14,
TurretsIgnorePlayers = 1 << 15,
TurretsIgnoreScientist = 1 << 16,
TrapsIgnoreScientist = 1 << 17,
SamSitesIgnorePlayers = 1 << 18,
TwigDamageRequiresOwnership = 1 << 19,
AuthorizedDamageRequiresOwnership = 1 << 20,
VehiclesTakeCollisionDamageWithoutDriver = 1 << 21,
FriendlyFire = 1 << 22,
AnimalsIgnoreSleepers = 1 << 23,
NoHeliDamageRidableHorses = 1 << 24,
NoHeliDamageSleepers = 1 << 25,
SamSitesIgnoreMLRS = 1 << 26,
}
private Timer scheduleUpdateTimer; // timer to check for schedule updates
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 HashSet<string> _deployables = new HashSet<string>();
private List<DamageType> damageTypes = new List<DamageType>
{
DamageType.Explosion,
DamageType.Bullet,
DamageType.Slash,
DamageType.Stab,
DamageType.Blunt
};
#endregion
#region Loading/Unloading
private void Loaded()
{
Instance = this;
LoadDefaultMessages();
// register console commands automagically
foreach (Command command in Enum.GetValues(typeof(Command)))
{
AddCovalenceCommand($"tpve.{command}", nameof(CommandDelegator));
}
// register chat commands
cmd.AddChatCommand("tpve_prod", this, nameof(HandleProd));
cmd.AddChatCommand("tpve_enable", this, nameof(EnableToggle));
cmd.AddChatCommand("tpve", this, nameof(ChatCommandDelegator));
// build usage string for console (without sizing)
usageString = WrapColor("orange", GetMessage("Header_Usage")) + "\n" +
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")}";
permission.RegisterPermission(PermCanMap, this);
}
private void Unload()
{
if (scheduleUpdateTimer != null)
scheduleUpdateTimer.Destroy();
Instance = null;
}
private void OnPluginLoaded(Plugin plugin)
{
if (plugin.Name == "ZoneManager")
ZoneManager = plugin;
if (plugin.Name == "LiteZones")
LiteZones = plugin;
if (ZoneManager != null || LiteZones != null)
useZones = config?.options.useZones ?? true;
}
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(CanBeTargeted));
Unsubscribe(nameof(OnEntityTakeDamage));
Unsubscribe(nameof(OnPlayerConnected));
//Unsubscribe(nameof(OnSamSiteTargetScan));
Unsubscribe(nameof(OnSamSiteTarget));
Unsubscribe(nameof(OnTrapTrigger));
Unsubscribe(nameof(OnNpcTarget));
}
private void OnServerInitialized(bool isStartup)
{
// check for server pve setting
if (ConVar.Server.pve) WarnPve();
// load configuration
config.Init();
currentRuleSet = config.GetDefaultRuleSet();
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.First().Key.Equals(config.defaultRuleSet))
useZones = false;
if (config.schedule.enabled)
TimerLoop(true);
if (config.ruleSets.Any(ruleSet => ruleSet.HasFlag(RuleFlags.AnimalsIgnoreSleepers))) Subscribe(nameof(OnNpcTarget));
if (currentRuleSet == null) return;
InitializeDeployables();
Subscribe(nameof(CanBeTargeted));
Subscribe(nameof(OnEntityTakeDamage));
Subscribe(nameof(OnPlayerConnected));
//Subscribe(nameof(OnSamSiteTargetScan));
Subscribe(nameof(OnSamSiteTarget));
Subscribe(nameof(OnTrapTrigger));
}
#endregion
#region Command Handling
// delegation method for console 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;
switch ((Command)Enum.Parse(typeof(Command), command.Replace("tpve.", string.Empty)))
{
case Command.sleepers:
HandleSleepers(user);
return;
case Command.def:
HandleDef(user);
return;
case Command.sched:
HandleScheduleSet(user, args);
return;
case Command.trace:
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:
tpveEnabled = !tpveEnabled;
Message(user, "Enable", tpveEnabled);
return;
case Command.usage:
default:
ShowUsage(user);
return;
}
}
private void HandleSleepers(IPlayer user)
{
if (animalsIgnoreSleepers)
{
animalsIgnoreSleepers = false;
if (!config.ruleSets.Any(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.");
}
}
private void EnableToggle(BasePlayer player, string command, string[] args)
{
if (player.IsAdmin)
{
tpveEnabled = !tpveEnabled;
Message(player, "Enable", new object[] { tpveEnabled.ToString() });
}
}
// handle setting defaults
private void HandleDef(IPlayer user)
{
config.options = new ConfigurationOptions();
Message(user, "Notify_DefConfigLoad");
LoadDefaultData();
Message(user, "Notify_DefDataLoad");
SaveConfig();
}
// handle prod command (raycast to determine what player is looking at)
private void HandleProd(BasePlayer player, string command, string[] args)
{
if (!player.IsAdmin)
{
Message(player, "Error_NoPermission");
return;
}
object entity;
if (!GetRaycastTarget(player, out entity) || entity == null)
{
SendReply(player, WrapSize(12, WrapColor("red", GetMessage("Error_NoEntityFound", player.UserIDString))));
return;
}
Message(player, "Notify_ProdResult", new object[] { entity.GetType(), (entity as BaseEntity).ShortPrefabName });
}
// delegation method for chat commands
private void ChatCommandDelegator(BasePlayer player, string command, string[] args)
{
if (!player.IsAdmin && !permission.UserHasPermission(player.UserIDString, PermCanMap))
{
Message(player, "Error_NoPermission");
return;
}
// assume args[0] is the command (beyond /tpve)
if (args.Length > 0) command = args[0];
// shift arguments
args = args.Length > 1 ? args.Skip(1).ToArray() : new string[0];
if (command != "map")
{
Message(player, "Error_InvalidCommand");
}
else if (args.Length == 0)
{
Message(player, "Error_InvalidParamForCmd", command);
}
else
{
// args[0] should be mapping name
// args[1] if exists should be target ruleset or "exclude"
// if args[1] is empty, delete mapping
string from = args[0];
string to = args.Length == 2 ? args[1] : null;
if (to != null && !config.ruleSets.Select(r => r.name).Contains(to) && to != "exclude")
{
// target ruleset must exist, or be "exclude"
Message(player, "Error_InvalidMapping", from, to);
}
else
{
bool dirty = false;
if (to != null)
{
dirty = true;
if (config.HasMapping(from))
{
// update existing mapping
string old = config.mappings[from];
config.mappings[from] = to;
Message(player, "Notify_MappingUpdated", from, old, to);
}
else
{
// add new mapping
config.mappings.Add(from, to);
Message(player, "Notify_MappingCreated", from, to);
}
}
else
{
if (config.HasMapping(from))
{
dirty = true;
// remove mapping
string old = config.mappings[from];
config.mappings.Remove(from);
Message(player, "Notify_MappingDeleted", from, old);
}
else
{
Message(player, "Error_NoMappingToDelete", from);
}
}
if (dirty)
{
SaveConfig(); // save changes to config file
}
}
}
}
// 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)
{
Puts(ex.Message);
LoadDefaultConfig();
return;
}
// check config version, update version to current version
if (config.configVersion == null || !config.configVersion.Equals(Version.ToString()))
{
config.configVersion = Version.ToString();
}
}
protected override void LoadDefaultConfig()
{
config = new Configuration
{
configVersion = Version.ToString(),
options = new ConfigurationOptions()
};
LoadDefaultData();
}
// save data
protected override void SaveConfig()
{
Config.WriteObject(config);
}
// check rulesets and groups
private bool CheckData()
{
bool dirty = false;
if ((config.ruleSets == null || config.ruleSets.Count == 0) || (config.groups == null || config.groups.Count == 0))
{
dirty = LoadDefaultData();
}
if (config.schedule == null)
{
config.schedule = new Schedule();
dirty = true;
}
dirty |= CheckMappings();
return dirty;
}
// 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 Schedule();
config.defaultRuleSet = "default";
// build groups first
config.groups.Add(new EntityGroup("barricades")
{
members = "Barricade, icewall, GraveYardFence", // "barricade.cover.wood, 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",
exclusions = "barricade.concrete, barricade.sandbags, barricade.metal, barricade.stone, barricade.wood, barricade.woodwire"
});
config.groups.Add(new EntityGroup("dispensers")
{
members = "BaseCorpse, HelicopterDebris, PlayerCorpse, NPCPlayerCorpse, HorseCorpse"
});
config.groups.Add(new EntityGroup("fire")
{
members = "FireBall, FlameExplosive, FlameThrower, BaseOven, FlameTurret, rocket_heli_napalm, napalm, oilfireball2"
});
config.groups.Add(new EntityGroup("guards")
{
members = "bandit_guard, scientistpeacekeeper, sentry.scientist.static"
});
config.groups.Add(new EntityGroup("heli")
{
members = "BaseHelicopter"
});
config.groups.Add(new EntityGroup("highwalls")
{
members = "SimpleBuildingBlock, wall.external.high.ice, gates.external.high.stone, gates.external.high.wood"
});
config.groups.Add(new EntityGroup("ridablehorses")
{
members = "RidableHorse"
});
config.groups.Add(new EntityGroup("cars")
{
members = "BasicCar, ModularCar, BaseModularVehicle, BaseVehicleModule, VehicleModuleEngine, VehicleModuleSeating, VehicleModuleStorage, VehicleModuleTaxi, ModularCarSeat"
});
config.groups.Add(new EntityGroup("mini")
{
members = "minicopter.entity"
});
config.groups.Add(new EntityGroup("scrapheli")
{
members = "ScrapTransportHelicopter"
});
config.groups.Add(new EntityGroup("ch47")
{
members = "ch47.entity"
});
config.groups.Add(new EntityGroup("npcs")
{
members = "ch47scientists.entity, BradleyAPC, HumanNPC, NPCPlayer, ScientistNPC, TunnelDweller, SimpleShark, UnderwaterDweller, Zombie, ZombieNPC"
});
config.groups.Add(new EntityGroup("players")
{
members = "BasePlayer, FrankensteinPet"
});
config.groups.Add(new EntityGroup("resources")
{
members = "ResourceEntity, TreeEntity, OreResourceEntity, LootContainer",
exclusions = "hobobarrel.deployed"
});
config.groups.Add(new EntityGroup("samsites")
{
members = "sam_site_turret_deployed",
exclusions = "sam_static"
});
config.groups.Add(new EntityGroup("traps")
{
members = "AutoTurret, BearTrap, FlameTurret, Landmine, GunTrap, ReactiveTarget, TeslaCoil, spikes.floor"
});
config.groups.Add(new EntityGroup("junkyard")
{
members = "magnetcrane.entity, carshredder.entity"
});
// create default ruleset
RuleSet defaultRuleSet = new RuleSet(config.defaultRuleSet)
{
_flags = RuleFlags.HumanNPCDamage | RuleFlags.LockedBoxesImmortal | RuleFlags.LockedDoorsImmortal | RuleFlags.SamSitesIgnorePlayers | RuleFlags.TrapsIgnorePlayers | RuleFlags.TurretsIgnorePlayers,
flags = "HumanNPCDamage, LockedBoxesImmortal, LockedDoorsImmortal, SamSitesIgnorePlayers, 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 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("mini cannot hurt mini");
defaultRuleSet.AddRule("npcs can hurt players");
defaultRuleSet.AddRule("junkyard cannot hurt anything");
defaultRuleSet.AddRule("junkyard can hurt cars");
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 BaseEntity traceEntity;
private BasePlayer tracePlayer;
private void Trace(string message, int indentation = 0)
{
if (config.options.PlayerConsole || config.options.ServerConsole)
{
if (traceEntity != null && !traceEntity.IsDestroyed && tracePlayer != null && tracePlayer.IsConnected)
{
if (config.options.MaxTraceDistance == 0 || tracePlayer.Distance(traceEntity) <= config.options.MaxTraceDistance)
{
if (config.options.PlayerConsole)
{
tracePlayer.ConsoleMessage(message);
}
if (config.options.ServerConsole)
{
Puts(message);
}
_tsb.AppendLine(string.Empty.PadLeft(indentation, ' ') + message);
}
}
}
else _tsb.AppendLine(string.Empty.PadLeft(indentation, ' ') + message);
}
private void LogTrace()
{
var text = _tsb.ToString();
traceEntity = null;
try
{
LogToFile(traceFile, text, this);
}
catch (IOException)
{
timer.Once(1f, () => LogToFile(traceFile, text, this));
return;
}
_tsb.Length = 0;
}
private StringBuilder _tsb = new StringBuilder();
#endregion Trace
#region Hooks/Handler Procedures
private void OnPlayerConnected(BasePlayer player)
{
if (config.schedule.enabled && config.schedule.broadcast && currentBroadcastMessage != null)
{
SendReply(player, GetMessage("Prefix") + currentBroadcastMessage);
}
}
private string CurrentRuleSetName() => currentRuleSet.name;
private bool IsEnabled() => tpveEnabled;
// 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 (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 == null || hitInfo == null || AllowKillingSleepers(entity, hitInfo))
{
return null;
}
object extCanTakeDamage = Interface.CallHook("CanEntityTakeDamage", new object[] { entity, hitInfo });
if (extCanTakeDamage is bool)
{
if ((bool)extCanTakeDamage)
{
return null;
}
CancelHit(hitInfo);
return true;
}
if (!config.options.handleDamage)
{
return null;
}
var majority = hitInfo.damageTypes.GetMajorityDamageType();
if (majority == DamageType.Decay || majority == DamageType.Fall)
{
return null;
}
if (!AllowDamage(entity, hitInfo))
{
if (trace) LogTrace();
CancelHit(hitInfo);
return true;
}
if (trace) LogTrace();
return null;
}
private void CancelHit(HitInfo hitInfo)
{
hitInfo.damageTypes = new DamageTypeList();
hitInfo.DidHit = false;
hitInfo.DoHitEffects = false;
}
private bool AllowKillingSleepers(BaseCombatEntity entity, HitInfo hitInfo)
{
if (animalsIgnoreSleepers && hitInfo.Initiator is BaseNpc) return false;
return config.AllowKillingSleepers && entity is BasePlayer && entity.ToPlayer().IsSleeping();
}
// determines if an entity is "allowed" to take damage
private bool AllowDamage(BaseCombatEntity entity, HitInfo hitInfo)
{
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 || entity is BaseNpc || entity is BaseHelicopter)
{
return true;
}
// allow damage to door barricades and covers
if (entity is Barricade && (entity.ShortPrefabName.Contains("door_barricade") || entity.ShortPrefabName.Contains("cover")))
{
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)
{
if (entity.ShortPrefabName.Contains("barrel") || entity.ShortPrefabName.Equals("loot_trash") || entity.ShortPrefabName.Equals("giftbox_loot"))
{
return true;
}
}
var weapon = hitInfo.Initiator ?? hitInfo.Weapon ?? hitInfo.WeaponPrefab;
//hitInfo.ProjectilePrefab.sourceWeaponPrefab
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 (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);
if (weapon?.ShortPrefabName == "maincannonshell" || weapon is BradleyAPC)
{
if (trace) Trace("Initiator is BradleyAPC; evaluating RuleSet rules...", 1);
return EvaluateRules(entity, weapon, ruleSet);
}
if (ruleSet.HasFlag(RuleFlags.VehiclesTakeCollisionDamageWithoutDriver) && entity is BaseMountable && weapon == entity)
{
BaseVehicle vehicle = entity.HasParent() ? (entity as BaseMountable).VehicleParent() : entity as BaseVehicle;
if (vehicle.IsValid())
{
var player = vehicle.GetDriver();
if (trace) Trace($"Vehicle collision: { (player == null ? "No driver; allow and return" : "Has driver; continue checks") }", 1);
if (player == null)
{
return true;
}
}
}
// Check storage containers and doors for locks
if (ruleSet.HasFlag(RuleFlags.LockedBoxesImmortal) && entity is StorageContainer || ruleSet.HasFlag(RuleFlags.LockedDoorsImmortal) && entity is Door)
{
if (!(entity is LootContainer))
{
// check for lock
object hurt = CheckLock(ruleSet, entity, hitInfo);
if (trace) Trace($"Door/StorageContainer detected with immortal flag; lock check results: { (hurt == null ? "null (no lock or unlocked); continue checks" : (bool)hurt ? "allow and return" : "block and return") }", 1);
if (hurt is bool)
{
return (bool)hurt;
}
}
}
// check heli and turret
object heli = CheckHeliInitiator(ruleSet, hitInfo);
bool isVictim = entity is BasePlayer;
if (heli is bool)
{
return HandleHelicopter(ruleSet, entity, heli, isVictim);
}
if (weapon is BaseProjectile && hitInfo.Initiator == null)
{
var projectile = weapon as BaseProjectile;
hitInfo.Initiator = projectile.GetOwnerPlayer();
}
// after heli check, return true if initiator is null
if (hitInfo.Initiator == null)
{
if (hitInfo.damageTypes.types.Any(type => damageTypes.Contains((DamageType)type)))
{
if (trace) Trace($"Initiator empty for player damage; block and return", 1);
return false;
}
if (trace) Trace($"Initiator empty; allow and return", 1);
return true;
}
if (ruleSet.HasFlag(RuleFlags.SamSitesIgnorePlayers) && hitInfo.Initiator is SamSite && !entity.IsNpc)
{
var player = isVictim ? entity as BasePlayer : GetMountedPlayer(entity as BaseMountable);
if (player.IsValid() && player.userID.IsSteamId())
{
// check for exclusions in entity groups
bool hasExclusion = CheckExclusion(hitInfo.Initiator);
if (trace) Trace($"Initiator is samsite, and target is player; {(hasExclusion ? "exclusion found; allow and return" : "no exclusion; block and return")}", 1);
return hasExclusion;
}
}
var victim = entity as BasePlayer;
// handle suicide
if (isVictim && !entity.IsNpc && victim.userID.IsSteamId() && hitInfo.damageTypes?.Get(DamageType.Suicide) > 0)
{
if (trace) Trace($"DamageType is suicide; blocked? { (ruleSet.HasFlag(RuleFlags.SuicideBlocked) ? "true; block and return" : "false; allow and return") }", 1);
if (ruleSet.HasFlag(RuleFlags.SuicideBlocked))
{
Message(entity as BasePlayer, "Error_NoSuicide");
return false;
}
return true;
}
// allow players to hurt themselves
if (isVictim && ruleSet.HasFlag(RuleFlags.SelfDamage) && !entity.IsNpc && victim.userID.IsSteamId() && hitInfo.Initiator == entity)
{
if (trace) Trace($"SelfDamage flag; player inflicted damage to self; allow and return", 1);
return true;
}
if (hitInfo.Initiator is MiniCopter && entity is BuildingBlock)
{
if (trace) Trace("Initiator is minicopter, target is building; evaluate and return", 1);
return EvaluateRules(entity, hitInfo, ruleSet);
}
if (hitInfo.Initiator is BaseNpc)
{
// check for sleeper protection - return false if sleeper protection is on (true)
if (isVictim && ruleSet.HasFlag(RuleFlags.ProtectedSleepers) && (entity as BasePlayer).IsSleeping())
{
if (trace) Trace("Target is sleeping player, with ProtectedSleepers flag set; block and return", 1);
return false;
}
if (trace) Trace("Initiator is NPC animal; allow and return", 1);
return true; // allow NPC damage to other entities if sleeper protection is off
}
var attacker = hitInfo.Initiator as BasePlayer;
if (attacker.IsValid())
{
if (attacker.isMounted)
{
if (!EvaluateRules(entity, attacker.GetMounted(), ruleSet, false))
{
return false;
}
}
if (entity is AdvancedChristmasLights)
{
if (entity.OwnerID == 0)
{
return attacker.CanBuild();
}
}
else if (entity is GrowableEntity)
{
if (attacker.CanBuild())
{
return true;
}
var ge = entity as GrowableEntity;
var planter = ge.GetPlanter();
return planter == null || !planter.OwnerID.IsSteamId() || planter.OwnerID == attacker.userID;
}
else if (entity is BuildingBlock)
{
var block = entity as BuildingBlock;
if (block.grade == BuildingGrade.Enum.Twigs)
{
if (ruleSet.HasFlag(RuleFlags.TwigDamageRequiresOwnership)) // Allow twig damage by owner or anyone authed if ruleset flag is set
{
if (entity.OwnerID == attacker.userID)
{
return true;
}
return attacker.IsBuildingAuthed(entity.transform.position, entity.transform.rotation, entity.bounds);
}
if (ruleSet.HasFlag(RuleFlags.TwigDamage)) // Allow twig damage by anyone if ruleset flag is set
{
return true;
}
}
}
if (isVictim)
{
if (ruleSet.HasFlag(RuleFlags.FriendlyFire) && victim.userID.IsSteamId() && AreAllies(attacker, victim))
{
if (trace) Trace("Initiator and target are allied players, with FriendlyFire flag set; allow and return", 1);
return true;
}
// allow sleeper damage by admins if configured
if (ruleSet.HasFlag(RuleFlags.AdminsHurtSleepers) && attacker.IsAdmin && victim.IsSleeping())
{
if (trace) Trace("Initiator is admin player and target is sleeping player, with AdminsHurtSleepers flag set; allow and return", 1);
return true;
}
// allow Human NPC damage if configured
if (ruleSet.HasFlag(RuleFlags.HumanNPCDamage) && IsHumanNPC(attacker, victim))
{
if (trace) Trace("Initiator or target is HumanNPC, with HumanNPCDamage flag set; allow and return", 1);
return true;
}
}
else if (ruleSet.HasFlag(RuleFlags.AuthorizedDamage) && !entity.IsNpc) // ignore checks if authorized damage enabled (except for players and npcs)
{
if (ruleSet.HasFlag(RuleFlags.AuthorizedDamageRequiresOwnership) && entity.OwnerID != attacker.userID && CanAuthorize(entity, attacker, ruleSet))
{
if (trace) Trace("Initiator is player who does not own non-player target; block and return", 1);
return false;
}
if (CheckAuthorized(entity, attacker, ruleSet))
{
if (entity is SamSite)
{
if (trace) Trace("Target is SamSite; evaluate and return", 1);
return EvaluateRules(entity, hitInfo, ruleSet);
}
if (trace) Trace("Initiator is player with authorization over non-player target; allow and return", 1);
return true;
}
}
}
if (trace) Trace("No match in pre-checks; evaluating RuleSet rules...", 1);
return EvaluateRules(entity, hitInfo, ruleSet);
}
private bool HandleHelicopter(RuleSet ruleSet, BaseCombatEntity entity, object heli, bool isVictim)
{
if (isVictim)
{
var victim = entity as BasePlayer;
if (ruleSet.HasFlag(RuleFlags.NoHeliDamageSleepers))
{
if (trace) Trace($"Initiator is heli, and target is player; flag check results: { (victim.IsSleeping() ? "victim is sleeping; block and return" : "victim is not sleeping; continue checks") }", 1);
if (victim.IsSleeping()) return false;
}
if (trace) Trace($"Initiator is heli, and target is player; flag check results: { (ruleSet.HasFlag(RuleFlags.NoHeliDamagePlayer) ? "flag set; block and return" : "flag not set; block and return") }", 1);
return !ruleSet.HasFlag(RuleFlags.NoHeliDamagePlayer);
}
if (entity is MiningQuarry)
{
if (trace) Trace($"Initiator is heli, and target is quarry; flag check results: { (ruleSet.HasFlag(RuleFlags.NoHeliDamageQuarry) ? "flag set; block and return" : "flag not set; allow and return") }", 1);
return !ruleSet.HasFlag(RuleFlags.NoHeliDamageQuarry);
}
if (entity is RidableHorse)
{
if (trace) Trace($"Initiator is heli, and target is ridablehorse; flag check results: { (ruleSet.HasFlag(RuleFlags.NoHeliDamageRidableHorses) ? "flag set; block and return" : "flag not set; allow and return") }", 1);
return !ruleSet.HasFlag(RuleFlags.NoHeliDamageRidableHorses);
}
if (trace) Trace($"Initiator is heli, target is non-player; results: { ((bool)heli ? "allow and return" : "block and return") }", 1);
//return EvaluateRules(entity, weapon, ruleSet);
return (bool)heli;
}
public bool AreAllies(BasePlayer attacker, BasePlayer victim)
{
if (attacker.currentTeam != 0uL && attacker.Team.members.Contains(victim.userID))
{
return true;
}
if (Clans != null && Convert.ToBoolean(Clans?.Call("IsMemberOrAlly", attacker.userID, victim.userID)))
{
return true;
}
if (Friends != null && Convert.ToBoolean(Friends?.Call("AreFriends", attacker.UserIDString, victim.UserIDString)))
{
return true;
}
return false;
}
private const uint WALL_LOW_JPIPE = 310235277;
private bool CanAuthorize(BaseEntity entity, BasePlayer attacker, RuleSet ruleSet)
{
if (entity is BaseVehicle && !EvaluateRules(entity, attacker, ruleSet, false))
{
return false;
}
if (entity.OwnerID == 0)
{
return entity is MiniCopter || entity.prefabID == WALL_LOW_JPIPE && !entity.enableSaving;
}
return entity is BuildingBlock || entity.PrefabName.Contains("building") || _deployables.Contains(entity.PrefabName);
}
private void InitializeDeployables()
{
foreach (var def in ItemManager.GetItemDefinitions())
{
var imd = def.GetComponent<ItemModDeployable>();
if (imd == null || _deployables.Contains(imd.entityPrefab.resourcePath)) continue;
_deployables.Add(imd.entityPrefab.resourcePath);
}
}
// process rules to determine whether to allow damage
private bool EvaluateRules(BaseEntity entity, BaseEntity attacker, RuleSet ruleSet, bool returnDefaultValue = true)
{
List<string> e0Groups = config.ResolveEntityGroups(attacker);
List<string> e1Groups = config.ResolveEntityGroups(entity);
if (trace)
{
Trace($"Initator EntityGroup matches: { (e0Groups == null || e0Groups.Count == 0 ? "none" : string.Join(", ", e0Groups.ToArray())) }", 2);
Trace($"Target EntityGroup matches: { (e1Groups == null || e1Groups.Count == 0 ? "none" : string.Join(", ", e1Groups.ToArray())) }", 2);
}
return ruleSet.Evaluate(e0Groups, e1Groups, attacker, returnDefaultValue);
}
private bool EvaluateRules(BaseEntity entity, HitInfo hitInfo, RuleSet ruleSet)
{
return EvaluateRules(entity, hitInfo.Initiator ?? hitInfo.WeaponPrefab, ruleSet);
}
// checks an entity to see if it has a lock
private object CheckLock(RuleSet ruleSet, BaseEntity entity, HitInfo hitInfo)
{
var slot = entity.GetSlot(BaseEntity.Slot.Lock); // check for lock
if (slot == null || !slot.IsLocked())
{
return null; // no lock or unlocked, continue checks
}
// if HeliDamageLocked flag is false or NoHeliDamage flag, all damage is cancelled from immortal flag
if (!ruleSet.HasFlag(RuleFlags.HeliDamageLocked) || ruleSet.HasFlag(RuleFlags.NoHeliDamage))
{
return false;
}
object heli = CheckHeliInitiator(ruleSet, hitInfo);
return Convert.ToBoolean(heli); // cancel damage except from heli
// ... [Code preview truncated due to length. Full source in attached file] ...