C#:
using Facepunch;
using Facepunch.Math;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Rust;
using Rust.Ai.Gen2;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
/*
Fixed issue where game does not assign `creatorPlayer` to bee grenades, bee catapult bombs, and supply signals when dropped or catapulted
Fixed issues with the TwigDamage flag options and included in documentation
Fixed `Prevent hackable crate timer from resetting when attacked`
Added `Allow Raiding In Deep Sea` (false) to include looting containers, can still be blocked by other plugins. Does not enable PVP, enable both options for that.
Added additional functionality to `Allow PVP Damage In Deep Sea` to allow looting players, corpses and backpacks, as well as allowing traps and turrets to target and kill players. You must enable the raiding option for raiding to be allowed. Other plugins can block this behavior.
Added `Use Clans` (true) - several features depend on the functionality of these options to determine if a player is an ally of another player.
Added `Use Friends` (true)
Added `Use Teams` (true)
Added `ExcludePlayerBoatFromImmortalFlags` flag (boat station will not be included until a performance friendly implementation is possible)
Added support for player-made boats to several existing options
Renamed `Require Owner Online` to `Block Damage When Owner Is Online` retroactively for clarity
Renamed "Apply To Twig" retroactively
Added `Vehicles can hurt NPC players (true = ignore this option)` and stopped applying vehicle rules to mounted players which prevented players from hurting npcs by hitting them with a vehicle
Reimplemented `Allow Killing Sleepers (TC Auth Only)` with new options
Updated `Allow Killing Sleepers (TC Auth Only)` to support player-made boats
*/
namespace Oxide.Plugins {
[Info("TruePVE", "nivex", "2.3.721")]
[Description("Improvement of the default Rust PVE behavior")]
// Thanks to the original author, ignignokt84.
internal class TruePVE : RustPlugin {
#region Variables
// config/data container
private Configuration config = new();
[PluginReference]
Plugin ZoneManager, LiteZones, Clans, Friends, AbandonedBases, RaidableBases;
public string usageString;
public enum Command {
def, sched, trace, usage, enable, sleepers, map, unmap
}
;
public enum DamageResult {
None, Allow, Block
}
[Flags]
public enum RuleFlags : ulong {
None = 0, AdminsHurtSleepers = 1uL << 1, AdminsHurtPlayers = 1uL << 2, AnimalsIgnoreSleepers = 1uL << 3, AuthorizedDamage = 1uL << 4, AuthorizedDamageRequiresOwnership = 1uL << 5, CupboardOwnership = 1uL << 6, FriendlyFire = 1uL << 7, HeliDamageLocked = 1uL << 8, HumanNPCDamage = 1uL << 9, LockedBoxesImmortal = 1uL << 10, LockedDoorsImmortal = 1uL << 11, NoPlayerDamageToCar = 1uL << 12, NoPlayerDamageToMini = 1uL << 13, NoPlayerDamageToScrap = 1uL << 14, NoHeliDamage = 1uL << 15, NoHeliDamagePlayer = 1uL << 16, NoHeliDamageQuarry = 1uL << 17, NoHeliDamageRidableHorses = 1uL << 18, NoHeliDamageSleepers = 1uL << 19, NoMLRSDamage = 1uL << 20, NpcsCanHurtAnything = 1uL << 21, PlayerSamSitesIgnorePlayers = 1uL << 22, ProtectedSleepers = 1uL << 23, TrapsIgnorePlayers = 1uL << 24, TrapsIgnoreScientist = 1uL << 25, TurretsIgnorePlayers = 1uL << 26, TurretsIgnoreScientist = 1uL << 27, StaticTurretsIgnoreScientist = 1uL << 28, TwigDamage = 1uL << 29, TwigDamageRequiresOwnership = 1uL << 30, VehiclesTakeCollisionDamageWithoutDriver = 1uL << 31, SamSitesIgnoreMLRS = 1uL << 32, SelfDamage = 1uL << 33, StaticSamSitesIgnorePlayers = 1uL << 34, StaticTurretsIgnorePlayers = 1uL << 35, SafeZoneTurretsIgnorePlayers = 1uL << 36, SuicideBlocked = 1uL << 37, NoHeliDamageBuildings = 1uL << 38, WoodenDamage = 1uL << 39, WoodenDamageRequiresOwnership = 1uL << 40, AuthorizedDamageCheckPrivilege = 1uL << 41, ExcludeTugboatFromImmortalFlags = 1uL << 42, ExcludePlayerBoatFromImmortalFlags = 1uL << 43, LockedVehiclesImmortal = 1uL << 44, TurretsIgnoreBradley = 1uL << 45, AuthorizedFarmableDamage = 1uL << 46, HopperCannotTargetEnemyLoot = 1uL << 47, VehiclesTakeCollisionDamage = 1uL << 48,
}
private bool IsUnloading;
private Timer scheduleUpdateTimer;
// timer to check for schedule updates
private bool shareRedirectDudEnabled;
// 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;
// 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, DamageType.Cannon
}
;
private uint maincannonshell = 3032863244;
private uint trainbarricade = 1221760186;
private uint trainbarricadeheavy = 1363243026;
private uint loot_trash = 3279100614;
private uint giftbox_loot = 2216891097;
private uint campfire = 4160694184;
private uint oilfireballsmall = 3550347674;
private uint heli_napalm = 184893264;
private uint rocket_heli_napalm = 200672762;
private uint rocket_heli = 129320027;
private bool excludeAllZones;
private readonly List<ulong> _waiting = new();
private readonly HashSet<string> _deployables = new();
private readonly HashSet<string> exclusionLocationsSet = new(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<ulong, List<PlayerExclusion>> playerDelayExclusions = new();
private readonly Dictionary<string, RuleSet> ruleSetByNameDictionary = new(StringComparer.OrdinalIgnoreCase);
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() {
}
}
#endregion
#region Loading/Unloading
protected new static void Puts(string format, params object[] args) {
if (!string.IsNullOrEmpty(format)) {
Interface.Oxide.LogInfo("[{0}] {1}", "TruePVE", (args.Length != 0) ? string.Format(format, args) : format);
}
}
private void Unload() {
bool save = false;
if (_removeMappingTimer is {
Destroyed : false
}
) {
_removeMappingTimer.Destroy();
SaveConfig();
save = true;
}
if (_auMappingTimer is {
Destroyed : false
}
) {
_auMappingTimer.Destroy();
if (!save) SaveConfig();
}
IsUnloading = true;
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) SetUseZones();
}
private void OnPluginUnloaded(Plugin plugin) {
if (plugin.Name == "ZoneManager") ZoneManager = null;
if (plugin.Name == "LiteZones") LiteZones = null;
if (ZoneManager == null && LiteZones == null) useZones = false;
}
private void OnCreatedDynamicPVP() => SetUseZones();
private void OnDeletedDynamicPVP() => SetUseZones();
protected void SetUseZones() {
useZones = config != null && config.mappings != null && config.options != null && config.options.useZones && (LiteZones != null || ZoneManager != null);
if (useZones && config.mappings.Count == 1) {
foreach (var mapping in config.mappings) {
useZones = !mapping.Key.Equals(config.defaultRuleSet);
}
}
}
private void Init() {
if (config.AllowKillingSleepersHoursOffline <= 0f) {
Unsubscribe(nameof(OnPlayerSleep));
Unsubscribe(nameof(OnPlayerSleepEnded));
}
if (!config.laptop) {
Unsubscribe(nameof(OnCrateLaptopAttack));
}
if (!config.options.Loot.NoShieldDrop) {
Unsubscribe(nameof(OnPlayerActiveShieldDrop));
}
if (!config.options.Loot.NoActiveItemDrop) {
Unsubscribe(nameof(OnPlayerDropActiveItem));
}
if (!config.options.Loot.NoRustBackpackDrop) {
Unsubscribe(nameof(OnBackpackDrop));
}
if (!config.BlockSprayCanInSafeZones) {
Unsubscribe(nameof(OnSprayCreate));
}
Unsubscribe(nameof(CanLootEntity));
Unsubscribe(nameof(OnCodeEntered));
Unsubscribe(nameof(CanChangeCode));
Unsubscribe(nameof(OnCupboardAuthorize));
Unsubscribe(nameof(CanHelicopterStrafeTarget));
Unsubscribe(nameof(CanWaterBallSplash));
Unsubscribe(nameof(OnEntityMarkHostile));
Unsubscribe(nameof(OnEntitySpawned));
Unsubscribe(nameof(OnEntityEnter));
Unsubscribe(nameof(OnTurretTarget));
Unsubscribe(nameof(OnTimedExplosiveExplode));
Unsubscribe(nameof(OnWallpaperRemove));
Unsubscribe(nameof(OnEntityTakeDamage));
Unsubscribe(nameof(OnPlayerConnected));
Unsubscribe(nameof(OnSamSiteTarget));
Unsubscribe(nameof(OnTrapTrigger));
Unsubscribe(nameof(OnMlrsFire));
// 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.map}") + $" - {GetMessage("Cmd_Usage_mapzone")}{Environment.NewLine}" + WrapColor("cyan", $"tpve.{Command.unmap}") + $" - {GetMessage("Cmd_Usage_unmapzone")}{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 bool IsAnimalsIgnoringSleepers() => animalsIgnoreSleepers || config.ruleSets.Exists(ruleSet => ruleSet.HasFlag(RuleFlags.AnimalsIgnoreSleepers));
private bool IsAnimalsIgnoringSleepers(RuleSet ruleSet) => animalsIgnoreSleepers || ruleSet.HasFlag(RuleFlags.AnimalsIgnoreSleepers);
private void OnServerInitialized(bool isStartup) {
isServerStartingUp = false;
// check for server pve setting
if (ConVar.Server.pve) WarnPve();
// load configuration
config.Init(this);
currentRuleSet = config.GetDefaultRuleSet();
dudRuleSet = config.GetDudRuleSet();
if (currentRuleSet == null) Puts(GetMessage("Warning_NoRuleSet"), config.defaultRuleSet);
SetUseZones();
if (config.schedule.enabled) {
TimerLoop(true);
}
if (!IsAnimalsIgnoringSleepers()) {
Unsubscribe(nameof(OnNpcTarget));
}
if (config.PreventSafeZoneStrafing) {
Subscribe(nameof(CanHelicopterStrafeTarget));
}
if (config.PreventThrowingWaterInFreezingBiome || config.BlockRadioactiveWaterDamage) {
Subscribe(nameof(CanWaterBallSplash));
}
if (currentRuleSet == null) {
return;
}
if (config.ruleSets.Exists(ruleSet => (ruleSet._flags & (RuleFlags.SafeZoneTurretsIgnorePlayers | RuleFlags.StaticTurretsIgnorePlayers | RuleFlags.StaticTurretsIgnoreScientist | RuleFlags.TrapsIgnorePlayers | RuleFlags.TrapsIgnoreScientist | RuleFlags.TurretsIgnorePlayers | RuleFlags.TurretsIgnoreScientist | RuleFlags.TurretsIgnoreBradley)) != 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) {
ServerMgr.Instance.StartCoroutine(OvenCo());
}
if (config.options.disableHostility) {
Subscribe(nameof(OnEntityMarkHostile));
}
RuleSet ruleSet = currentRuleSet;
if (config.options.handleDamage && ruleSet != null && !ruleSet.IsEmpty() && ruleSet.enabled) {
Subscribe(nameof(OnEntityTakeDamage));
tpveEnabled = true;
}
if (config.wallpaper) {
Subscribe(nameof(OnWallpaperRemove));
Subscribe(nameof(OnTimedExplosiveExplode));
}
if (config.options.Loot.ProtectTC) {
Subscribe(nameof(OnCupboardAuthorize));
Subscribe(nameof(CanLootEntity));
Subscribe(nameof(CanChangeCode));
}
else if (config.options.Loot.Lifts) {
Subscribe(nameof(CanLootEntity));
}
else if (config.options.Loot.Sleepers) {
Subscribe(nameof(CanLootEntity));
}
else if (config.options.Loot.Corpses) {
Subscribe(nameof(CanLootEntity));
}
else if (config.options.Loot.Backpacks) {
Subscribe(nameof(CanLootEntity));
}
Subscribe(nameof(OnEntitySpawned));
Subscribe(nameof(OnMlrsFire));
BuildPrefabIds();
AllowLocksOnContainers();
RemoveTemporaryZones();
InitDeepSea();
}
private IEnumerator OvenCo() {
int checks = 0;
foreach (var ent in BaseNetworkable.serverEntities) {
if (++checks > 500) {
checks = 0;
yield return null;
}
if (ent is BaseOven oven) {
oven.disabledBySplash = false;
}
}
}
private void BuildPrefabIds() {
if (StringPool.toNumber.TryGetValue("assets/prefabs/npc/m2bradley/maincannonshell.prefab", out var prefab1)) maincannonshell = prefab1;
if (StringPool.toNumber.TryGetValue("assets/content/props/train_tunnels/trainbarricade.prefab", out var prefab2)) trainbarricade = prefab2;
if (StringPool.toNumber.TryGetValue("assets/content/props/train_tunnels/trainbarricadeheavy.prefab", out var prefab3)) trainbarricadeheavy = prefab3;
if (StringPool.toNumber.TryGetValue("assets/bundled/prefabs/radtown/loot_trash.prefab", out var prefab4)) loot_trash = prefab4;
if (StringPool.toNumber.TryGetValue("assets/prefabs/misc/xmas/giftbox/giftbox_loot.prefab", out var prefab5)) giftbox_loot = prefab5;
if (StringPool.toNumber.TryGetValue("assets/prefabs/deployable/campfire/campfire.prefab", out var prefab6)) campfire = prefab6;
if (StringPool.toNumber.TryGetValue("assets/bundled/prefabs/oilfireballsmall.prefab", out var prefab7)) oilfireballsmall = prefab7;
if (StringPool.toNumber.TryGetValue("assets/bundled/prefabs/napalm.prefab", out var prefab8)) heli_napalm = prefab8;
if (StringPool.toNumber.TryGetValue("assets/prefabs/npc/patrol helicopter/rocket_heli_napalm.prefab", out var prefab9)) rocket_heli_napalm = prefab9;
if (StringPool.toNumber.TryGetValue("assets/prefabs/npc/patrol helicopter/rocket_heli.prefab", out var prefab10)) rocket_heli = prefab10;
}
#endregion
#region Data
private void OnNewSave() {
data = new();
SaveData();
}
private class StoredData {
public Dictionary<ulong, int> LastSeen = new();
public DateTime LastRunTime = DateTime.MinValue;
}
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 && DateTime.Now.Subtract(data.LastRunTime).TotalHours >= 24) {
if (config.AllowKillingSleepersHoursOffline > 0f && data.LastSeen.Count > 0) {
Puts("Last seen data wiped due to plugin not being loaded for {0} day(s).", DateTime.Now.Subtract(data.LastRunTime).Days);
}
data = new();
data.LastRunTime = DateTime.Now;
}
if (config.AllowKillingSleepersHoursOffline <= 0f) {
if (data.LastSeen.Count > 0) {
data.LastSeen.Clear();
SaveData();
}
}
}
private void SaveData() {
data.LastRunTime = DateTime.Now;
Interface.Oxide.DataFileSystem.WriteObject(Name, data);
}
private void OnPlayerSleep(BasePlayer player) {
if (player.IsConnected) {
data.LastSeen.Remove(player.userID);
}
else if (!player.IsBuildingAuthed(true)) {
data.LastSeen[player.userID] = Epoch.Current;
}
}
private void OnPlayerSleepEnded(BasePlayer player) {
data.LastSeen.Remove(player.userID);
}
public bool CanKillOfflinePlayer(BasePlayer player, out double timeLeft) {
timeLeft = 0;
if (player.IsConnected || !player.IsSleeping()) {
data.LastSeen.Remove(player.userID);
return false;
}
if (!data.LastSeen.TryGetValue(player.userID, out var lastSeen)) {
return false;
}
if (player.IsBuildingAuthed(true)) {
data.LastSeen.Remove(player.userID);
return false;
}
double timeOffline = Epoch.Current - lastSeen;
double allowedOfflineTime = config.AllowKillingSleepersHoursOffline * 3600.0;
timeLeft = (allowedOfflineTime - timeOffline) / 3600.0;
return timeOffline > allowedOfflineTime;
}
#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.IsAdmin) return;
if (args.Length > 0 && (args[0] == "map" || args[0] == "unmap")) {
if (user.HasPermission(PermCanMap)) {
CommandMap(user, command, args);
}
else user.Reply("You do not have the required truepve.canmap permission.");
return;
}
if (args.Contains("pvp")) {
if (currentRuleSet.rules.Remove("players cannot hurt players")) {
currentRuleSet.rules.Add("players can hurt players");
}
else if (currentRuleSet.rules.Remove("player can hurt players")) {
currentRuleSet.rules.Add("player cannot hurt players");
}
Puts("PVP toggled {0}", currentRuleSet.rules.Contains("players can hurt players") ? "on" : "off");
SaveConfig();
return;
}
if (command == "tpve_prod") {
HandleProd(user);
return;
}
if (command == "tpve_enable") {
tpveEnabled = !tpveEnabled;
ValidateCurrentDamageHook();
Message(user, "Enable", tpveEnabled);
return;
}
if (command == "tpve" && args.Length != 0) command = args[0];
else command = command.Replace("tpve.", string.Empty);
if (!Enum.TryParse(command, out 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 : HandleTrace(user);
return;
case Command.enable : tpveEnabled = !tpveEnabled;
ValidateCurrentDamageHook();
Message(user, "Enable", tpveEnabled);
return;
case Command.map : case Command.unmap : if (user.HasPermission(PermCanMap)) {
CommandMap(user, command, args);
}
else user.Reply("You do not have the required truepve.canmap permission.");
return;
case Command.usage : default : ShowUsage(user);
return;
}
}
protected void HandleTrace(IPlayer user) {
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"
}
);
traceTimer?.Destroy();
if (trace) {
traceTimer = timer.In(traceTimeout, () => trace = false);
}
}
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 (!IsAnimalsIgnoringSleepers()) {
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");
CheckData();
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 == null || !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", 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[1..] : Array.Empty<string>();
if (command != "map" && command != "unmap") {
Message(user, "Error_InvalidCommand");
}
else if (args.Length == 0) // ideally this would just update from/to for the next if statement instead of reusing duplicated code
{
BasePlayer player = user.Object as BasePlayer;
if (player != null) {
using var locs = GetLocationKeys(player);
if (locs.Count > 0) {
string from = locs[0];
// mapping name
string to = "exclude";
if (command == "map") {
if (config.mappings.TryGetValue(from, out string old)) {
Message(user, "Notify_MappingUpdated", from, old, to);
// update existing mapping
}
else Message(user, "Notify_MappingCreated", from, to);
// add new mapping
config.mappings[from] = to;
}
else if (command == "unmap") {
if (config.mappings.Remove(from, out to)) {
Message(user, "Notify_MappingDeleted", from, to);
config.mappings.Remove(from);
}
else {
Message(user, "Error_InvalidMapping", from, to);
return;
}
}
SaveConfig();
TryBuildExclusionMappings();
return;
}
}
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.mappings.TryGetValue(from, out string old)) {
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();
TryBuildExclusionMappings();
}
else {
if (config.HasMapping(from)) {
Message(user, "Notify_MappingDeleted", from, config.mappings[from]);
config.mappings.Remove(from);
// remove mapping
SaveConfig();
TryBuildExclusionMappings();
}
else Message(user, "Error_NoMappingToDelete", from);
}
}
}
// handles schedule enable/disable
private void HandleScheduleSet(IPlayer user, string[] args) {
if (args.Length < 2) {
Message(user, "Error_InvalidParamForCmd", $"tpve.{args[0]}");
Message(user, $"Syntax: tpve.{args[0]} <enable|disable>");
return;
}
if (!config.schedule.valid) {
Message(user, "Notify_InvalidSchedule");
}
else if (args[1] == "enable") {
if (config.schedule.enabled) return;
config.schedule.enabled = true;
TimerLoop();
Message(user, "Notify_SchedSetEnabled");
}
else if (args[1] == "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[1]);
Message(user, $"Syntax: tpve.{args[0]} <enable|disable>");
}
}
#endregion
#region Configuration/Data
private bool _playersTriggerOption, _playersHurtOption, _canKillOfflinePlayerEnabled, _pvpReflectionEnabled, _allowKillingSleepersEnabled, _buildingBlockHandlerEnabled;
// load config
protected override void LoadConfig() {
base.LoadConfig();
canSaveConfig = false;
try {
config = Config.ReadObject<Configuration>();
if (config == null) LoadDefaultConfig();
canSaveConfig = true;
CheckData();
SaveConfig();
}
catch (Exception ex) {
Puts(ex.ToString());
LoadDefaultConfig();
}
}
protected override void LoadDefaultConfig() {
config = new() {
configVersion = Version.ToString(), options = new()
}
;
LoadDefaultData();
Puts("Loaded default config.");
}
private bool canSaveConfig = true;
// save data
protected override void SaveConfig() {
if (canSaveConfig) {
Config.WriteObject(config);
}
}
// check rulesets and groups
private void CheckData() {
if (string.IsNullOrEmpty(config.defaultRuleSet)) {
config.defaultRuleSet = "default";
Puts("Loaded default ruleset (no default ruleset was configured)");
}
if (config.mappings.IsNullOrEmpty()) {
BuildDefaultMappings();
Puts("Loaded default mappings (no mappings were configured)");
}
if (config.schedule == null) {
Puts("Loaded default schedule (schedule was null)");
BuildDefaultSchedule();
}
if (config.groups.IsNullOrEmpty()) {
BuildDefaultGroups();
Puts("Loaded default entity groups (no entity groups were configured)");
}
if (config.ruleSets.IsNullOrEmpty()) {
BuildDefaultRuleset();
Puts("Loaded default rulesets (no rulesets were configured)");
}
if (config.options.BlockHandler._Online != null) {
config.options.BlockHandler.BlockWhenOnline = config.options.BlockHandler._Online.Value;
config.options.BlockHandler._Online = null;
}
if (config.options.BlockHandler._Twig != null) {
config.options.BlockHandler.Twig = config.options.BlockHandler._Twig.Value;
config.options.BlockHandler._Twig = null;
}
TryUpdateConfig();
config.configVersion = Version.ToString();
CheckMappings();
BuildRuleSetDictionary();
BuildExclusionMappings();
_allowKillingSleepersEnabled = config.AllowKillingSleepersAlly || config.AllowKillingSleepers || config.EntityCountRequired || config.AllowKillingSleepersIds.Exists(x => x.IsSteamId());
_buildingBlockHandlerEnabled = config.options.BlockHandler.Any;
_pvpReflectionEnabled = config.options.Reflect.Any;
_canKillOfflinePlayerEnabled = config.AllowKillingSleepersHoursOffline > 0;
_playersTriggerOption = config.PlayersTriggerTraps || config.PlayersTriggerTurrets;
_playersHurtOption = config.PlayersHurtTraps || config.PlayersHurtTurrets;
}
private void TryUpdateConfig() {
if (!TryParseVersionNumber(config.configVersion, out var vn) || vn >= Version) return;
Dictionary<string, string> updates = new(StringComparer.OrdinalIgnoreCase) {
["npcs"]
= "SnakeHazard", ["npcs"] = "ScientistNPC2", ["dispensers"] = "VineSwingingTree"
}
;
for (int i = 0; i < config.groups.Count; i++) {
var group = config.groups[i];
if (string.IsNullOrWhiteSpace(group.members)) {
continue;
}
if (string.IsNullOrEmpty(group.name)) {
group.name = $"group{i}";
continue;
}
if (group.name == "ridablehorses" && group.members.Contains("RidableHorse2")) {
group.members = ReplaceMember(group.members, "RidableHorse2", "RidableHorse");
continue;
}
if (group.members.Contains("heli_napalm")) {
group.members = ReplaceMember(group.members, "heli_napalm", "napalm");
var fire = config.groups.Find(x => x.name == "fire");
if (fire != null && fire.members.Contains("FireBall") && !fire.exclusions.Contains("napalm")) {
fire.exclusions = fire.exclusions.Length == 0 ? "napalm" : fire.exclusions + ", napalm";
}
}
if (updates.TryGetValue(group.name, out var update) && !ContainedInGroups(update)) {
group.members = $"{group.members.TrimEnd(',', ' ')}{", "}{update}";
continue;
}
}
}
private static string ReplaceMember(string members, string oldValue, string newValue) {
if (string.IsNullOrWhiteSpace(members)) return members;
string[] parts = members.Split(',');
for (int i = 0; i < parts.Length; i++) {
string member = parts[i].Trim();
if (member == oldValue) {
parts[i] = newValue;
}
else {
parts[i] = member;
}
}
return string.Join(",", parts);
}
private bool ContainedInGroups(string member) => config.groups.Exists(g => g.members.Contains(member, CompareOptions.OrdinalIgnoreCase) || g.exclusions.Contains(member, CompareOptions.OrdinalIgnoreCase));
private bool TryParseVersionNumber(string input, out VersionNumber version) {
version = default;
if (string.IsNullOrWhiteSpace(input)) return false;
var parts = input.Split('.');
if (parts.Length != 3) return false;
if (int.TryParse(parts[0], out int major) && int.TryParse(parts[1], out int minor) && int.TryParse(parts[2], out int patch)) {
version = new(major, minor, patch);
return true;
}
return false;
}
// rebuild mappings
private bool CheckMappings() {
bool dirty = false;
foreach (RuleSet ruleSet in config.ruleSets) {
if (!config.mappings.ContainsValue(ruleSet.name)) {
config.mappings[ruleSet.name] = ruleSet.name;
dirty = true;
}
}
return dirty;
}
protected void BuildRuleSetDictionary() {
ruleSetByNameDictionary.Clear();
foreach (RuleSet ruleSet in config.ruleSets) {
if (ruleSet.enabled) {
ruleSetByNameDictionary[ruleSet.name] = ruleSet;
}
}
}
protected void TryBuildExclusionMappings() {
if (!config.mappings.TryGetValue(AllZones, out var val) || !val.Equals("exclude", StringComparison.OrdinalIgnoreCase)) {
BuildExclusionMappings();
}
}
protected void BuildExclusionMappings() {
excludeAllZones = false;
exclusionLocationsSet.Clear();
if (config.mappings.TryGetValue(AllZones, out var val) && val.Equals("exclude", StringComparison.OrdinalIgnoreCase)) {
excludeAllZones = true;
return;
}
foreach (var (key, value) in config.mappings) {
if (!value.Equals("exclude", StringComparison.OrdinalIgnoreCase)) {
foreach (var ruleSet in config.ruleSets) {
if (ruleSet.name.Equals(value, StringComparison.OrdinalIgnoreCase) && ruleSet.IsEmpty()) {
exclusionLocationsSet.Add(key);
}
}
}
else {
exclusionLocationsSet.Add(key);
}
}
foreach (var group in config.groups) {
foreach (var exclusion in group._exclusionSet) {
if (!string.IsNullOrWhiteSpace(exclusion)) {
exclusionLocationsSet.Add(exclusion.Trim());
}
}
}
}
// load default data to mappings, rulesets, and groups
protected bool LoadDefaultData() {
BuildDefaultMappings();
BuildDefaultSchedule();
BuildDefaultGroups();
BuildDefaultRuleset();
return true;
}
protected void BuildDefaultSchedule() {
config.schedule = new();
}
protected void BuildDefaultMappings() {
config.mappings ??= new();
config.mappings.Clear();
config.defaultRuleSet = "default";
config.mappings[config.defaultRuleSet] = config.defaultRuleSet;
// create mapping for ruleset
}
protected void BuildDefaultGroups() {
config.groups ??= new();
config.groups.Clear();
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", exclusions = "barricade.concrete, barricade.sandbags, barricade.stone"
}
);
config.groups.Add(new("barricades2") {
members = "spikes_static, barricade.metal, barricade.wood, barricade.woodwire, spikes.floor, icewall, GraveyardFence",
}
);
config.groups.Add(new("dispensers") {
members = "BaseCorpse, HelicopterDebris, PlayerCorpse, NPCPlayerCorpse, HorseCorpse, SkyLantern, Pinata"
}
);
config.groups.Add(new("fire") {
members = "FireBall, FlameExplosive, FlameThrower, BaseOven, FlameTurret, oilfireball2", exclusions = "napalm"
}
);
config.groups.Add(new("guards") {
members = "bandit_guard, scientistpeacekeeper, sentry.scientist.static, sentry.bandit.static"
}
);
config.groups.Add(new("heli") {
members = "PatrolHelicopter, oilfireballsmall, rocket_heli, rocket_heli_napalm, napalm"
}
);
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, maincannonshell, CustomScientistNpc, SnakeHazard, ScarecrowNPC, HumanNPC, NPCPlayer, ScientistNPC, ScientistNPC2, TunnelDweller, SimpleShark, UnderwaterDweller, ZombieNPC"
}
);
config.groups.Add(new("players") {
members = "BasePlayer, FrankensteinPet, BeeGrenade"
}
);
config.groups.Add(new("resources") {
members = "ResourceEntity, TreeEntity, OreResourceEntity, LootContainer, NaturalBeehive, VineSwingingTree", 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"
}
);
config.groups.Add(new("ramhead") {
members = "BatteringRamHead"
}
);
config.groups.Add(new("siege") {
members = "SiegeTower, Catapult, Ballista, Cannon, BallistaGun, BatteringRam, ConstructableEntity"
}
);
config.groups.Add(new("bees") {
members = "BeeSwarmAI, Beehive, BeeSwarmMaster, NaturalBeehive"
}
);
config.groups.Add(new("farm") {
members = "simplechicken.entity, FarmableAnimal, ChickenCoop"
}
);
}
protected void BuildDefaultRuleset() {
config.ruleSets ??= new();
config.ruleSets.Clear();
// create default ruleset
RuleSet defaultRuleSet = new(config.defaultRuleSet) {
_flags = RuleFlags.AuthorizedDamage | RuleFlags.AuthorizedDamageRequiresOwnership | RuleFlags.AuthorizedFarmableDamage | RuleFlags.HopperCannotTargetEnemyLoot | RuleFlags.HumanNPCDamage | RuleFlags.LockedBoxesImmortal | RuleFlags.LockedDoorsImmortal | RuleFlags.PlayerSamSitesIgnorePlayers | RuleFlags.TrapsIgnorePlayers | RuleFlags.TurretsIgnorePlayers, flags = "AuthorizedDamage, AuthorizedDamageRequiresOwnership, AuthorizedFarmableDamage, HopperCannotTargetEnemyLoot, HumanNPCDamage, LockedBoxesImmortal, LockedDoorsImmortal, PlayerSamSitesIgnorePlayers, TrapsIgnorePlayers, TurretsIgnorePlayers"
}
;
// create rules and add to ruleset
defaultRuleSet.AddRule(this, "anything can hurt dispensers");
defaultRuleSet.AddRule(this, "anything can hurt resources");
defaultRuleSet.AddRule(this, "anything can hurt barricades");
defaultRuleSet.AddRule(this, "anything can hurt traps");
defaultRuleSet.AddRule(this, "anything can hurt heli");
defaultRuleSet.AddRule(this, "anything can hurt npcs");
defaultRuleSet.AddRule(this, "anything can hurt players");
defaultRuleSet.AddRule(this, "nothing can hurt ch47");
defaultRuleSet.AddRule(this, "nothing can hurt cars");
defaultRuleSet.AddRule(this, "nothing can hurt mini");
defaultRuleSet.AddRule(this, "not
// ... [Code preview truncated due to length. Full source in attached file] ...