Guest User

ZoneManager

a guest
Aug 6th, 2026
151
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 163.08 KB | Gaming | 0 0
  1. using Facepunch;
  2. using Newtonsoft.Json;
  3. using Newtonsoft.Json.Converters;
  4. using Newtonsoft.Json.Linq;
  5. using Oxide.Core;
  6. using Oxide.Core.Configuration;
  7. using Oxide.Core.Plugins;
  8. using Oxide.Game.Rust.Cui;
  9. using Rust;
  10. using System;
  11. using System.Collections;
  12. using System.Collections.Generic;
  13. using System.ComponentModel;
  14. using System.Globalization;
  15. using System.Reflection;
  16. using System.Text;
  17. using Rust.Ai.Gen2;
  18. using UnityEngine;
  19. using Debug = UnityEngine.Debug;
  20.  
  21. namespace Oxide.Plugins
  22. {
  23.     [Info("Zone Manager", "k1lly0u", "3.1.12")]
  24.     [Description("An advanced management system for creating in-game zones")]
  25.     public class ZoneManager : RustPlugin
  26.     {
  27.         #region Fields
  28.         [PluginReference] Plugin Backpacks, PopupNotifications, Spawns;
  29.  
  30.         private StoredData storedData;
  31.  
  32.         private DynamicConfigFile data;
  33.  
  34.  
  35.         private readonly Hash<string, Zone> zones = new Hash<string, Zone>();
  36.         private readonly Hash<Plugin, HashSet<string>> temporaryZones = new Hash<Plugin, HashSet<string>>();
  37.  
  38.         private static readonly Hash<ulong, EntityZones> zonedPlayers = new Hash<ulong, EntityZones>();
  39.  
  40.         private static readonly Hash<NetworkableId, EntityZones> zonedEntities = new Hash<NetworkableId, EntityZones>();
  41.  
  42.         private readonly Dictionary<ulong, string> lastPlayerZone = new Dictionary<ulong, string>();
  43.  
  44.  
  45.         private readonly ZoneFlags globalFlags = new ZoneFlags();
  46.  
  47.         private readonly ZoneFlags adminBypass = new ZoneFlags();
  48.  
  49.  
  50.         private static readonly ZoneFlags tempFlags = new ZoneFlags();
  51.  
  52.         private static readonly StringBuilder sb = new StringBuilder();
  53.  
  54.  
  55.         private bool zonesInitialized = false;
  56.  
  57.  
  58.         private static ZoneManager Instance { get; set; }
  59.  
  60.         private const string PERMISSION_ZONE = "zonemanager.zone";
  61.  
  62.         private const string PERMISSION_IGNORE_FLAG = "zonemanager.ignoreflag.";
  63.  
  64.         private const int PLAYER_MASK = 131072;
  65.  
  66.         private const int TARGET_LAYERS = ~(1 << 10 | 1 << 18 | 1 << 28 | 1 << 29);
  67.         #endregion
  68.  
  69.         #region Oxide Hooks
  70.         private void Init()
  71.         {
  72.             Instance = this;
  73.  
  74.             adminBypass.SetFlags(ZoneFlags.NoBuild, ZoneFlags.NoDeploy, ZoneFlags.NoCup, ZoneFlags.NoUpgrade, ZoneFlags.NoChat, ZoneFlags.NoVoice, ZoneFlags.KillSleepers, ZoneFlags.EjectSleepers, ZoneFlags.NoSignUpdates);
  75.  
  76.             lang.RegisterMessages(Messages, this);
  77.  
  78.             permission.RegisterPermission(PERMISSION_ZONE, this);
  79.  
  80.             foreach (string flag in ZoneFlags.NameToIndex.Keys)
  81.                 permission.RegisterPermission(PERMISSION_IGNORE_FLAG + flag.ToLower(), this);
  82.  
  83.             LoadData();
  84.         }
  85.  
  86.         private void OnServerInitialized() => InitializeZones();
  87.  
  88.         private void OnTerrainInitialized() => InitializeZones();
  89.  
  90.         private void OnPlayerConnected(BasePlayer player) => updateBehaviour.QueueUpdate(player);
  91.  
  92.         private void OnPluginUnloaded(Plugin plugin)
  93.         {
  94.             if (!temporaryZones.TryGetValue(plugin, out HashSet<string> set))
  95.                 return;
  96.  
  97.             foreach (string zoneId in set)
  98.             {
  99.                 if (!zones.TryGetValue(zoneId, out Zone zone))
  100.                     continue;
  101.  
  102.                 if (zone.definition.Owner != plugin)
  103.                     continue;
  104.  
  105.                 zones.Remove(zoneId);
  106.  
  107.                 UnityEngine.Object.DestroyImmediate(zone.gameObject);
  108.                 Interface.CallHook("OnZoneErased", zoneId);
  109.             }
  110.  
  111.             temporaryZones.Remove(plugin);
  112.         }
  113.  
  114.         private void OnEntityKill(BaseEntity baseEntity)
  115.         {
  116.             if (!baseEntity || !baseEntity.IsValid() || baseEntity.IsDestroyed)
  117.                 return;
  118.  
  119.             if (!zonedEntities.TryGetValue(baseEntity.net.ID, out EntityZones entityZones))
  120.                 return;
  121.  
  122.             for (int i = entityZones.Zones.Count - 1; i >= 0; i--)
  123.             {
  124.                 Zone zone = entityZones.Zones[i];
  125.                 if (!zone)
  126.                     continue;
  127.  
  128.                 zone.OnEntityExitZone(baseEntity, false, true);
  129.             }
  130.  
  131.             zonedEntities.Remove(baseEntity.net.ID);
  132.         }
  133.  
  134.         private void Unload()
  135.         {
  136.             DestroyUpdateBehaviour();
  137.  
  138.             foreach (BasePlayer player in BasePlayer.activePlayerList)
  139.                 CuiHelper.DestroyUi(player, ZMUI);
  140.  
  141.             foreach (KeyValuePair<string, Zone> kvp in zones)
  142.                 UnityEngine.Object.DestroyImmediate(kvp.Value.gameObject);
  143.  
  144.             zones.Clear();
  145.             temporaryZones.Clear();
  146.             zonedPlayers.Clear();
  147.             zonedEntities.Clear();
  148.  
  149.             Instance = null;
  150.             Configuration = null;
  151.         }
  152.         #endregion
  153.  
  154.         #region UpdateQueue
  155.         private UpdateBehaviour m_UpdateBehaviour;
  156.  
  157.         private UpdateBehaviour updateBehaviour
  158.         {
  159.             get
  160.             {
  161.                 if (m_UpdateBehaviour)
  162.                     return m_UpdateBehaviour;
  163.  
  164.                 m_UpdateBehaviour = new GameObject("ZoneManager.UpdateBehaviour").AddComponent<UpdateBehaviour>();
  165.  
  166.                 foreach (BasePlayer player in BasePlayer.activePlayerList)
  167.                     m_UpdateBehaviour.QueueUpdate(player);
  168.  
  169.                 return m_UpdateBehaviour;
  170.             }
  171.         }
  172.  
  173.         private void DestroyUpdateBehaviour()
  174.         {
  175.             if (updateBehaviour)
  176.                 UnityEngine.Object.Destroy(updateBehaviour.gameObject);
  177.         }
  178.  
  179.         // Queue and check players for new zones and that they are still in old zones. Previously any plugin that put a player to sleep and teleports them out of a zone
  180.         // without calling the OnPlayerSleep hook would bypass a player zone update which would result in players being registered in zones they were no longer in.
  181.         // Options are to either continually check and update players, or have every plugin that teleports players call the hook...
  182.         private class UpdateBehaviour : MonoBehaviour
  183.         {
  184.             private readonly System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
  185.  
  186.             private readonly Queue<BasePlayer> playerUpdateQueue = new Queue<BasePlayer>();
  187.  
  188.             private const float MAX_MS = 0.25f;
  189.  
  190.             private void OnDestroy()
  191.             {
  192.                 playerUpdateQueue.Clear();
  193.             }
  194.  
  195.             public void QueueUpdate(BasePlayer player)
  196.             {
  197.                 if (!playerUpdateQueue.Contains(player))
  198.                     playerUpdateQueue.Enqueue(player);
  199.             }
  200.  
  201.             public void Reset() => playerUpdateQueue.Clear();
  202.  
  203.             private void Update()
  204.             {
  205.                 if (Time.frameCount % 10 != 0)
  206.                     return;
  207.  
  208.                 sw.Reset();
  209.                 sw.Start();
  210.  
  211.                 while (playerUpdateQueue.Count > 0)
  212.                 {
  213.                     if (sw.Elapsed.TotalMilliseconds >= MAX_MS)
  214.                     {
  215.                         sw.Stop();
  216.                         return;
  217.                     }
  218.  
  219.                     BasePlayer player = playerUpdateQueue.Dequeue();
  220.                     if (!player || !player.IsConnected)
  221.                         continue;
  222.  
  223.                     Instance?.UpdatePlayerZones(player);
  224.  
  225.                     InvokeHandler.Invoke(this, () => QueueUpdate(player), 2f);
  226.                 }
  227.             }
  228.         }
  229.         #endregion
  230.  
  231.         #region Flag Hooks
  232.         private void OnEntityBuilt(Planner planner, GameObject gameObject)
  233.         {
  234.             if (!planner || !gameObject)
  235.                 return;
  236.  
  237.             BasePlayer player = planner.GetOwnerPlayer();
  238.             if (!player)
  239.                 return;
  240.  
  241.             BaseEntity entity = gameObject.ToBaseEntity();
  242.             if (!entity)
  243.                 return;
  244.  
  245.             if (entity is BuildingBlock block)
  246.             {
  247.                 if (!HasPlayerFlag(player, ZoneFlags.NoBuild))
  248.                     return;
  249.  
  250.                 List<ItemAmount> list = block.BuildCost().Items;
  251.  
  252.                 block.Invoke(() =>
  253.                 {
  254.                     for (int i = 0; i < list?.Count; i++)
  255.                     {
  256.                         ItemAmount itemAmount = list[i];
  257.                         player.GiveItem(ItemManager.Create(itemAmount.itemDef, Mathf.Clamp(Mathf.RoundToInt(itemAmount.amount), 1, int.MaxValue)));
  258.                     }
  259.  
  260.                     if (entity && !entity.IsDestroyed)
  261.                         entity.Kill(BaseNetworkable.DestroyMode.Gib);
  262.                 }, 0.1f);
  263.  
  264.                 SendMessage(player, Message("noBuild", player.UserIDString));
  265.             }
  266.             else if (entity is SimpleBuildingBlock)
  267.             {
  268.                 if (!HasPlayerFlag(player, ZoneFlags.NoBuild))
  269.                     return;
  270.  
  271.                 KillEntityAndReturnItem(player, entity, planner.GetItem());
  272.                 SendMessage(player, Message("noBuild", player.UserIDString));
  273.             }
  274.             else
  275.             {
  276.                 if (entity is BuildingPrivlidge)
  277.                 {
  278.                     if (!HasPlayerFlag(player, ZoneFlags.NoCup))
  279.                         return;
  280.  
  281.                     KillEntityAndReturnItem(player, entity, planner.GetItem());
  282.                     SendMessage(player, Message("noCup", player.UserIDString));
  283.                 }
  284.                 else
  285.                 {
  286.                     if (!HasPlayerFlag(player, ZoneFlags.NoDeploy))
  287.                         return;
  288.  
  289.                     KillEntityAndReturnItem(player, entity, planner.GetItem());
  290.                     SendMessage(player, Message("noDeploy", player.UserIDString));
  291.                 }
  292.             }
  293.         }
  294.  
  295.         private object OnStructureUpgrade(BuildingBlock buildingBlock, BasePlayer player, BuildingGrade.Enum grade)
  296.         {
  297.             if (HasPlayerFlag(player, ZoneFlags.NoUpgrade))
  298.             {
  299.                 SendMessage(player, Message("noUpgrade", player.UserIDString));
  300.                 return true;
  301.             }
  302.             return null;
  303.         }
  304.  
  305.         private void OnItemDeployed(Deployer deployer, ItemModDeployable itemModDeployable, BaseEntity deployedEntity) // DoDeploy_Regular
  306.         {
  307.             BasePlayer player = deployer.GetOwnerPlayer();
  308.             if (!player)
  309.                 return;
  310.  
  311.             if (HasPlayerFlag(player, ZoneFlags.NoDeploy))
  312.             {
  313.                 KillEntityAndReturnItem(player, deployedEntity, deployer.GetItem());
  314.                 SendMessage(player, Message("noDeploy", player.UserIDString));
  315.             }
  316.         }
  317.  
  318.         private void OnItemDeployed(Deployer deployer, BaseEntity parentEntity, BaseEntity deployedEntity) // DoDeploy_Slot
  319.         {
  320.             BasePlayer player = deployer.GetOwnerPlayer();
  321.             if (!player)
  322.                 return;
  323.  
  324.             if (HasPlayerFlag(player, ZoneFlags.NoDeploy))
  325.             {
  326.                 KillEntityAndReturnItem(player, deployedEntity, deployer.GetItem());
  327.                 SendMessage(player, Message("noDeploy", player.UserIDString));
  328.             }
  329.         }
  330.  
  331.         private void KillEntityAndReturnItem(BasePlayer player, BaseEntity entity, Item item)
  332.         {
  333.             ItemDefinition itemDefinition = item?.info;
  334.             //int amount = item.amount;
  335.             ulong skin = item?.skin ?? 0UL;
  336.  
  337.             entity.Invoke(() =>
  338.             {
  339.                 if (entity && !entity.IsDestroyed)
  340.                     entity.Kill(BaseNetworkable.DestroyMode.Gib);
  341.  
  342.                 if (itemDefinition)
  343.                     player.GiveItem(ItemManager.Create(itemDefinition, 1, skin));
  344.  
  345.             }, 0.1f);
  346.         }
  347.  
  348.         private void OnItemUse(Item item, int amount)
  349.         {
  350.             BaseEntity entity = item?.parent?.entityOwner;
  351.             if (!entity)
  352.                 return;
  353.  
  354.             if (entity is FlameTurret or AutoTurret or GunTrap)
  355.             {
  356.                 if (HasEntityFlag(entity, ZoneFlags.InfiniteTrapAmmo))
  357.                     item.amount += amount;
  358.                 return;
  359.             }
  360.  
  361.             if (entity is not SearchLight)
  362.                 return;
  363.  
  364.             if (HasEntityFlag(entity, ZoneFlags.AlwaysLights))
  365.             {
  366.                 item.amount += amount;
  367.                 return;
  368.             }
  369.  
  370.             if (!HasEntityFlag(entity, ZoneFlags.AutoLights))
  371.                 return;
  372.  
  373.             if (TOD_Sky.Instance.Cycle.Hour > Configuration.AutoLights.OnTime || TOD_Sky.Instance.Cycle.Hour < Configuration.AutoLights.OffTime)
  374.                 item.amount += amount;
  375.         }
  376.  
  377.         private object OnPlayerChat(BasePlayer player, string message, ConVar.Chat.ChatChannel channel)
  378.         {
  379.             if (!player)
  380.                 return null;
  381.  
  382.             if (!HasPlayerFlag(player, ZoneFlags.NoChat))
  383.                 return null;
  384.  
  385.             SendMessage(player, Message("noChat", player.UserIDString));
  386.             return true;
  387.         }
  388.  
  389.         private object OnBetterChat(Oxide.Core.Libraries.Covalence.IPlayer iPlayer, string message)
  390.         {
  391.             BasePlayer player = iPlayer.Object as BasePlayer;
  392.             return OnPlayerChat(player, message, ConVar.Chat.ChatChannel.Global);
  393.         }
  394.  
  395.         private object OnPlayerVoice(BasePlayer player, Byte[] data)
  396.         {
  397.             if (HasPlayerFlag(player, ZoneFlags.NoVoice))
  398.             {
  399.                 SendMessage(player, Message("noVoice", player.UserIDString));
  400.                 return true;
  401.             }
  402.             return null;
  403.         }
  404.  
  405.         private object OnServerCommand(ConsoleSystem.Arg arg)
  406.         {
  407.             BasePlayer player = arg.Player();
  408.             if (!player || string.IsNullOrEmpty(arg.cmd?.Name))
  409.                 return null;
  410.  
  411.             if (arg.cmd.Name == "kill" && HasPlayerFlag(player, ZoneFlags.NoSuicide))
  412.             {
  413.                 SendMessage(player, Message("noSuicide", player.UserIDString));
  414.                 return true;
  415.             }
  416.             return null;
  417.         }
  418.  
  419.         private void OnPlayerDisconnected(BasePlayer player)
  420.         {
  421.             if (!player)
  422.                 return;
  423.  
  424.             if (HasPlayerFlag(player, ZoneFlags.KillSleepers))
  425.             {
  426.                 player.Die();
  427.                 return;
  428.             }
  429.  
  430.             if (HasPlayerFlag(player, ZoneFlags.EjectSleepers))
  431.             {
  432.                 if (!zonedPlayers.TryGetValue(player.userID, out EntityZones entityZones) || entityZones.Count == 0)
  433.                     return;
  434.  
  435.                 foreach (Zone zone in entityZones.Zones)
  436.                 {
  437.                     if (!zone)
  438.                         continue;
  439.  
  440.                     if (HasFlag(zone, ZoneFlags.EjectSleepers))
  441.                     {
  442.                         EjectPlayer(player, zone);
  443.                         return;
  444.                     }
  445.                 }
  446.             }
  447.         }
  448.  
  449.         private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo hitinfo)
  450.         {
  451.             if (!entity || GetEntityComponent<ResourceDispenser>(entity))
  452.                 return null;
  453.  
  454.             BasePlayer attacker = hitinfo.InitiatorPlayer;
  455.             BasePlayer victim = entity as BasePlayer;
  456.  
  457.             if (victim)
  458.             {
  459.                 if (hitinfo.damageTypes.GetMajorityDamageType() == DamageType.Fall)
  460.                 {
  461.                     if (HasPlayerFlag(victim, ZoneFlags.NoFallDamage))
  462.                         return true;
  463.                 }
  464.  
  465.                 if (victim.IsSleeping() && HasPlayerFlag(victim, ZoneFlags.SleepGod))
  466.                     return true;
  467.  
  468.                 if (attacker)
  469.                 {
  470.                     if (IsNpc(victim))
  471.                         return null;
  472.  
  473.                     if (HasPlayerFlag(victim, ZoneFlags.PvpGod))
  474.                     {
  475.                         if (attacker == victim && hitinfo.damageTypes.GetMajorityDamageType() == DamageType.Suicide)
  476.                         {
  477.                             if (HasPlayerFlag(victim, ZoneFlags.NoSuicide))
  478.                                 return true;
  479.                             return null;
  480.                         }
  481.                         if (IsNpc(attacker) && Configuration.NPCHurtPvpGod)
  482.                             return null;
  483.  
  484.                         return true;
  485.                     }
  486.  
  487.                     if (HasPlayerFlag(attacker, ZoneFlags.PvpGod) && !IsNpc(attacker))
  488.                         return true;
  489.                 }
  490.  
  491.                 else if (HasPlayerFlag(victim, ZoneFlags.PveGod) && !IsNpc(victim))
  492.                     return true;
  493.  
  494.                 else if (hitinfo.Initiator is FireBall && HasPlayerFlag(victim, ZoneFlags.PvpGod))
  495.                     return true;
  496.  
  497.                 return null;
  498.             }
  499.  
  500.             BaseNpc baseNpc = entity as BaseNpc;
  501.             if (baseNpc)
  502.             {
  503.                 if (HasEntityFlag(baseNpc, ZoneFlags.NoPve))
  504.                 {
  505.                     if (attacker && CanBypass(attacker, ZoneFlags.NoPve))
  506.                         return null;
  507.                     return true;
  508.                 }
  509.                 return null;
  510.             }
  511.  
  512.             if (entity is BuildingBlock or SimpleBuildingBlock)
  513.             {
  514.                 if (HasEntityFlag(entity, ZoneFlags.NoBuildingDamage))
  515.                 {
  516.                     if (attacker)
  517.                     {
  518.                         if (CanBypass(attacker, ZoneFlags.NoBuildingDamage))
  519.                             return null;
  520.  
  521.                         if (HasPlayerFlag(attacker, ZoneFlags.NoBuildingDamage))
  522.                             return true;
  523.                     }
  524.  
  525.                     if (hitinfo.damageTypes.GetMajorityDamageType() == DamageType.Decay && Configuration.DecayDamageUndestr)
  526.                         return null;
  527.  
  528.                     return true;
  529.                 }
  530.             }
  531.  
  532.             if (entity is not LootContainer && entity is not PatrolHelicopter)
  533.             {
  534.                 if (HasEntityFlag(entity, ZoneFlags.UnDestr))
  535.                 {
  536.                     if (attacker)
  537.                     {
  538.                         if (CanBypass(attacker, ZoneFlags.UnDestr))
  539.                             return null;
  540.  
  541.                         if (HasPlayerFlag(attacker, ZoneFlags.UnDestr))
  542.                             return true;
  543.                     }
  544.  
  545.                     if (hitinfo.damageTypes.GetMajorityDamageType() == DamageType.Decay && Configuration.DecayDamageUndestr)
  546.                         return null;
  547.  
  548.                     return true;
  549.                 }
  550.             }
  551.  
  552.             return null;
  553.         }
  554.  
  555.         private void OnEntitySpawned(BaseEntity baseEntity)
  556.             => NextTick(() => CanSpawn(baseEntity));
  557.  
  558.         private void CanSpawn(BaseEntity baseEntity)
  559.         {
  560.             if (!baseEntity.IsValid() || baseEntity.IsDestroyed)
  561.                 return;
  562.  
  563.             if (Interface.CallHook("CanSpawnInZone", baseEntity) != null)
  564.                 return;
  565.  
  566.             if (baseEntity is BaseCorpse corpse)
  567.             {
  568.                 if (HasEntityFlag(corpse, ZoneFlags.NoCorpse) && !CanBypass(corpse.OwnerID, ZoneFlags.NoCorpse))
  569.                     corpse.Invoke(() => baseEntity.Kill(BaseNetworkable.DestroyMode.None), 0.1f);
  570.             }
  571.             if (baseEntity is LootContainer or JunkPile)
  572.             {
  573.                 if (HasEntityFlag(baseEntity, ZoneFlags.NoLootSpawns))
  574.                     baseEntity.Invoke(() => baseEntity.Kill(BaseNetworkable.DestroyMode.None), 0.1f);
  575.             }
  576.             else if (baseEntity is BaseNpc or BaseNPC2 or NPCPlayer)
  577.             {
  578.                 if (HasEntityFlag(baseEntity, ZoneFlags.NoNPCSpawns))
  579.                     baseEntity.Invoke(() => baseEntity.Kill(BaseNetworkable.DestroyMode.None), 0.1f);
  580.             }
  581.             else if (baseEntity is DroppedItem or WorldItem)
  582.             {
  583.                 if (HasEntityFlag(baseEntity, ZoneFlags.NoDrop))
  584.                 {
  585.                     ((WorldItem)baseEntity).item.Remove(0f);
  586.                     baseEntity.Invoke(() => baseEntity.Kill(BaseNetworkable.DestroyMode.None), 0.1f);
  587.                 }
  588.             }
  589.             else if (baseEntity is DroppedItemContainer)
  590.             {
  591.                 if (HasEntityFlag(baseEntity, ZoneFlags.NoDrop))
  592.                     baseEntity.Invoke(() => baseEntity.Kill(BaseNetworkable.DestroyMode.None), 0.1f);
  593.             }
  594.         }
  595.  
  596.         private object CanBeWounded(BasePlayer player, HitInfo hitinfo) => HasPlayerFlag(player, ZoneFlags.NoWounded) ? (object)false : null;
  597.  
  598.         private object CanUpdateSign(BasePlayer player, Signage sign)
  599.         {
  600.             if (HasPlayerFlag(player, ZoneFlags.NoSignUpdates))
  601.             {
  602.                 SendMessage(player, Message("noSignUpdates", player.UserIDString));
  603.                 return false;
  604.             }
  605.             return null;
  606.         }
  607.  
  608.         private object OnOvenToggle(BaseOven oven, BasePlayer player)
  609.         {
  610.             if (HasPlayerFlag(player, ZoneFlags.NoOvenToggle))
  611.             {
  612.                 SendMessage(player, Message("noOvenToggle", player.UserIDString));
  613.                 return true;
  614.             }
  615.             return null;
  616.         }
  617.  
  618.         private object CanUseVending(BasePlayer player, VendingMachine machine)
  619.         {
  620.             if (HasPlayerFlag(player, ZoneFlags.NoVending))
  621.             {
  622.                 SendMessage(player, Message("noVending", player.UserIDString));
  623.                 return false;
  624.             }
  625.             return null;
  626.         }
  627.  
  628.         private object CanHideStash(BasePlayer player, StashContainer stash)
  629.         {
  630.             if (HasPlayerFlag(player, ZoneFlags.NoStash))
  631.             {
  632.                 SendMessage(player, Message("noStash", player.UserIDString));
  633.                 return false;
  634.             }
  635.             return null;
  636.         }
  637.  
  638.         private object CanCraft(ItemCrafter itemCrafter, ItemBlueprint bp, int amount)
  639.         {
  640.             BasePlayer player = itemCrafter.GetComponent<BasePlayer>();
  641.             if (player && HasPlayerFlag(player, ZoneFlags.NoCraft))
  642.             {
  643.                 SendMessage(player, Message("noCraft", player.UserIDString));
  644.                 return false;
  645.             }
  646.             return null;
  647.         }
  648.  
  649.         private void OnDoorOpened(Door door, BasePlayer player)
  650.         {
  651.             if (HasPlayerFlag(player, ZoneFlags.NoDoorAccess))
  652.             {
  653.                 SendMessage(player, Message("noDoor", player.UserIDString));
  654.                 door.CloseRequest();
  655.             }
  656.         }
  657.  
  658.         private object OnSprayCreate(SprayCan sprayCan, Vector3 position, Quaternion rotation)
  659.         {
  660.             if (!sprayCan)
  661.                 return null;
  662.  
  663.             BasePlayer player = sprayCan.GetOwnerPlayer();
  664.             if (!player)
  665.                 return null;
  666.  
  667.             if (HasPlayerFlag(player, ZoneFlags.NoSprays))
  668.             {
  669.                 SendMessage(player, Message("nosprays", player.UserIDString));
  670.                 return false;
  671.             }
  672.  
  673.             return null;
  674.         }
  675.  
  676.         #region Looting Hooks
  677.         private object CanLootPlayer(BasePlayer target, BasePlayer looter) => OnLootPlayerInternal(looter, target);
  678.  
  679.         private void OnLootPlayer(BasePlayer looter, BasePlayer target) => OnLootPlayerInternal(looter, target);
  680.  
  681.         private object OnLootPlayerInternal(BasePlayer looter, BasePlayer target)
  682.         {
  683.             if (HasPlayerFlag(looter, ZoneFlags.NoPlayerLoot) || (target != null && HasPlayerFlag(target, ZoneFlags.NoPlayerLoot)))
  684.             {
  685.                 if (looter == target && Backpacks != null)
  686.                 {
  687.                     object hookResult = Backpacks.Call("CanLootPlayer", target, looter);
  688.                     if (hookResult is bool result && result)
  689.                         return true;
  690.                 }
  691.  
  692.                 SendMessage(looter, Message("noLoot", looter.UserIDString));
  693.                 NextTick(looter.EndLooting);
  694.                 return false;
  695.             }
  696.             return null;
  697.         }
  698.  
  699.         private void OnLootEntity(BasePlayer player, BaseEntity entity)
  700.         {
  701.             if (entity is LootableCorpse corpse)
  702.                 OnLootCorpse(corpse, player);
  703.             if (entity is DroppedItemContainer container)
  704.                 OnLootContainer(container, player);
  705.             if (entity is StorageContainer)
  706.                 OnLootInternal(player, ZoneFlags.NoBoxLoot);
  707.         }
  708.  
  709.         private object CanLootEntity(BasePlayer player, LootableCorpse corpse)
  710.         {
  711.             if (corpse is NPCPlayerCorpse)
  712.             {
  713.                 if (!HasPlayerFlag(player, ZoneFlags.NoNPCLoot))
  714.                     return null;
  715.  
  716.                 SendMessage(player, Message("noLoot", player.UserIDString));
  717.                 return false;
  718.             }
  719.  
  720.             if (corpse.playerSteamID == player.userID && HasPlayerFlag(player, ZoneFlags.LootSelf))
  721.                 return null;
  722.  
  723.             return CanLootInternal(player, ZoneFlags.NoPlayerLoot);
  724.         }
  725.  
  726.         private void OnLootCorpse(LootableCorpse corpse, BasePlayer player)
  727.         {
  728.             if (corpse is NPCPlayerCorpse)
  729.             {
  730.                 if (!HasPlayerFlag(player, ZoneFlags.NoNPCLoot))
  731.                     return;
  732.  
  733.                 SendMessage(player, Message("noLoot", player.UserIDString));
  734.                 NextTick(player.EndLooting);
  735.                 return;
  736.             }
  737.  
  738.             if (corpse.playerSteamID == player.userID && HasPlayerFlag(player, ZoneFlags.LootSelf))
  739.                 return;
  740.  
  741.             OnLootInternal(player, ZoneFlags.NoPlayerLoot);
  742.         }
  743.  
  744.         private void OnLootContainer(DroppedItemContainer container, BasePlayer player)
  745.         {
  746.             if (container.playerSteamID == player.userID && HasPlayerFlag(player, ZoneFlags.LootSelf))
  747.                 return;
  748.  
  749.             OnLootInternal(player, ZoneFlags.NoPlayerLoot);
  750.         }
  751.  
  752.         private object CanLootEntity(BasePlayer player, DroppedItemContainer container)
  753.         {
  754.             if (container.playerSteamID == player.userID && HasPlayerFlag(player, ZoneFlags.LootSelf))
  755.                 return null;
  756.  
  757.             return CanLootInternal(player, ZoneFlags.NoPlayerLoot);
  758.         }
  759.  
  760.         private object CanLootEntity(BasePlayer player, StorageContainer container) => CanLootInternal(player, ZoneFlags.NoBoxLoot);
  761.  
  762.         private object CanLootInternal(BasePlayer player, int flag)
  763.         {
  764.             if (!player || !HasPlayerFlag(player, flag))
  765.                 return null;
  766.  
  767.             SendMessage(player, Message("noLoot", player.UserIDString));
  768.             return false;
  769.         }
  770.  
  771.         private void OnLootInternal(BasePlayer player, int flag)
  772.         {
  773.             if (!player || !HasPlayerFlag(player, flag))
  774.                 return;
  775.  
  776.             SendMessage(player, Message("noLoot", player.UserIDString));
  777.             NextTick(player.EndLooting);
  778.         }
  779.         #endregion
  780.  
  781.         #region Pickup Hooks
  782.         private object CanPickupEntity(BasePlayer player, BaseCombatEntity entity) => CanPickupInternal(player, ZoneFlags.NoEntityPickup);
  783.  
  784.         private object CanPickupLock(BasePlayer player, BaseLock baseLock) => CanPickupInternal(player, ZoneFlags.NoEntityPickup);
  785.  
  786.         private object OnItemPickup(Item item, BasePlayer player) => CanPickupInternal(player, ZoneFlags.NoPickup);
  787.  
  788.         private object CanPickupInternal(BasePlayer player, int flag)
  789.         {
  790.             if (!HasPlayerFlag(player, flag))
  791.                 return null;
  792.  
  793.             SendMessage(player, Message("noPickup", player.UserIDString));
  794.             return false;
  795.         }
  796.         #endregion
  797.  
  798.         #region Gather Hooks
  799.         private object CanLootEntity(ResourceContainer container, BasePlayer player) => OnGatherInternal(player);
  800.  
  801.         private object OnCollectiblePickup(Item item, BasePlayer player) => OnGatherInternal(player);
  802.  
  803.         private object OnGrowableGather(GrowableEntity plant, Item item, BasePlayer player) => OnGatherInternal(player);
  804.  
  805.         private object OnDispenserGather(ResourceDispenser dispenser, BasePlayer player, Item item) => OnGatherInternal(player);
  806.  
  807.         private object OnGatherInternal(BasePlayer player)
  808.         {
  809.             if (!player || !HasPlayerFlag(player, ZoneFlags.NoGather))
  810.                 return null;
  811.  
  812.             SendMessage(player, Message("noGather", player.UserIDString));
  813.             return true;
  814.  
  815.         }
  816.         #endregion
  817.  
  818.         #region Targeting Hooks
  819.         private object OnTurretTarget(AutoTurret turret, BasePlayer player) => OnTargetPlayerInternal(player, ZoneFlags.NoTurretTargeting);
  820.  
  821.         private object CanBradleyApcTarget(BradleyAPC apc, BasePlayer player)
  822.         {
  823.             if (player && HasPlayerFlag(player, ZoneFlags.NoAPCTargeting))
  824.                 return false;
  825.             return null;
  826.         }
  827.  
  828.         private object CanHelicopterTarget(PatrolHelicopterAI heli, BasePlayer player)
  829.         {
  830.             if (!player || !HasPlayerFlag(player, ZoneFlags.NoHeliTargeting))
  831.                 return null;
  832.  
  833.             heli.interestZoneOrigin = heli.GetRandomPatrolDestination();
  834.             return false;
  835.         }
  836.  
  837.         private object CanHelicopterStrafeTarget(PatrolHelicopterAI heli, BasePlayer player)
  838.         {
  839.             if (player && HasPlayerFlag(player, ZoneFlags.NoHeliTargeting))
  840.                 return false;
  841.             return null;
  842.         }
  843.  
  844.         private object OnHelicopterTarget(HelicopterTurret turret, BasePlayer player) => OnTargetPlayerInternal(player, ZoneFlags.NoHeliTargeting);
  845.  
  846.         private object OnNpcTarget(BaseCombatEntity entity, BasePlayer player) => OnTargetPlayerInternal(player, ZoneFlags.NoNPCTargeting);
  847.  
  848.         private object OnTargetPlayerInternal(BasePlayer player, int flag)
  849.         {
  850.             if (player && HasPlayerFlag(player, flag))
  851.                 return true;
  852.             return null;
  853.         }
  854.         #endregion
  855.  
  856.         #region Mounting Hooks
  857.         private object CanMountEntity(BasePlayer player, BaseMountable entity)
  858.         {
  859.             if (!player || !entity)
  860.                 return null;
  861.  
  862.             if (!entity.VehicleParent())
  863.                 return null;
  864.  
  865.             if (!HasPlayerFlag(player, ZoneFlags.NoVehicleMounting))
  866.                 return null;
  867.  
  868.             SendMessage(player, Message("novehiclemounting", player.UserIDString));
  869.             return false;
  870.         }
  871.  
  872.         private object CanDismountEntity(BasePlayer player, BaseMountable entity)
  873.         {
  874.             if (!player || !entity)
  875.                 return null;
  876.  
  877.             if (!entity.VehicleParent())
  878.                 return null;
  879.  
  880.             if (!HasPlayerFlag(player, ZoneFlags.NoVehicleDismounting))
  881.                 return null;
  882.  
  883.             SendMessage(player, Message("novehicledismounting", player.UserIDString));
  884.             return false;
  885.  
  886.         }
  887.         #endregion
  888.  
  889.         #region Additional KillSleeper Checks
  890.         private void OnPlayerSleep(BasePlayer player)
  891.         {
  892.             if (!player || player.IsNpc || !player.userID.IsSteamId())
  893.                 return;
  894.  
  895.             //player.Invoke(()=> UpdatePlayerZones(player), 1f); // Manually update the zones a player is in. Sleeping players don't trigger OnTriggerEnter or OnTriggerExit
  896.  
  897.             timer.In(2f, () =>
  898.             {
  899.                 if (!player || !player.IsSleeping())
  900.                     return;
  901.  
  902.                 if (player.IsConnected)
  903.                     return;
  904.  
  905.                 if (HasPlayerFlag(player, ZoneFlags.KillSleepers))
  906.                 {
  907.                     player.Invoke(() => KillSleepingPlayer(player), 3f);
  908.                     return;
  909.                 }
  910.  
  911.                 if (HasPlayerFlag(player, ZoneFlags.EjectSleepers))
  912.                 {
  913.                     player.Invoke(() =>
  914.                     {
  915.                         if (!player || !player.IsSleeping())
  916.                             return;
  917.  
  918.                         if (!zonedPlayers.TryGetValue(player.userID, out EntityZones entityZones) || entityZones.Count == 0)
  919.                             return;
  920.  
  921.                         foreach (Zone zone in entityZones.Zones)
  922.                         {
  923.                             if (!zone)
  924.                                 continue;
  925.  
  926.                             if (HasFlag(zone, ZoneFlags.EjectSleepers))
  927.                                 EjectPlayer(player, zone);
  928.                         }
  929.                     }, 3f);
  930.                 }
  931.             });
  932.         }
  933.  
  934.         private void OnPlayerSleepEnd(BasePlayer player) => updateBehaviour.QueueUpdate(player);
  935.  
  936.         private void KillSleepingPlayer(BasePlayer player)
  937.         {
  938.             if (!player || !player.IsSleeping())
  939.                 return;
  940.  
  941.             if (!HasPlayerFlag(player, ZoneFlags.KillSleepers))
  942.                 return;
  943.  
  944.             if (player.IsConnected)
  945.                 OnPlayerSleep(player);
  946.             else player.Die();
  947.         }
  948.  
  949.         private void UpdatePlayerZones(BasePlayer player)
  950.         {
  951.             if (!player)
  952.                 return;
  953.  
  954.             if (zonedPlayers.TryGetValue(player.userID, out EntityZones entityZones))
  955.             {
  956.                 List<Zone> list = Pool.Get<List<Zone>>();
  957.                 list.AddRange(entityZones.Zones);
  958.  
  959.                 for (int i = list.Count - 1; i >= 0; i--)
  960.                 {
  961.                     Zone zone = list[i];
  962.                     if (!zone || !zone.definition.Enabled)
  963.                         continue;
  964.  
  965.                     if (zone.definition.Size != Vector3.zero)
  966.                     {
  967.                         if (!IsInsideBounds(zone, player.transform.position))
  968.                             OnPlayerExitZone(player, zone);
  969.                     }
  970.                     else
  971.                     {
  972.                         if (Vector3.Distance(player.transform.position, zone.transform.position) > zone.definition.Radius)
  973.                             OnPlayerExitZone(player, zone);
  974.                     }
  975.                 }
  976.  
  977.                 Pool.FreeUnmanaged(ref list);
  978.             }
  979.  
  980.             foreach (Zone zone in zones.Values)
  981.             {
  982.                 if (!zone)
  983.                     continue;
  984.  
  985.                 if (entityZones != null && entityZones.Zones.Contains(zone))
  986.                     continue;
  987.  
  988.                 if (zone.definition.Size != Vector3.zero)
  989.                 {
  990.                     if (IsInsideBounds(zone, player.transform.position))
  991.                         OnPlayerEnterZone(player, zone);
  992.                 }
  993.                 else
  994.                 {
  995.                     if (Vector3.Distance(player.transform.position, zone.transform.position) <= zone.definition.Radius)
  996.                         OnPlayerEnterZone(player, zone);
  997.                 }
  998.             }
  999.  
  1000.             if (player.HasPlayerFlag(BasePlayer.PlayerFlags.SafeZone) && !player.InSafeZone())
  1001.                 player.SetPlayerFlag(BasePlayer.PlayerFlags.SafeZone, false);
  1002.         }
  1003.  
  1004.         private bool IsInsideBounds(Zone zone, Vector3 worldPos) => zone?.collider?.ClosestPoint(worldPos) == worldPos;
  1005.         #endregion
  1006.  
  1007.         private T GetEntityComponent<T>(BaseEntity entity) where T : EntityComponentBase
  1008.         {
  1009.             for (int i = 0; i < entity.Components.Count; i++)
  1010.             {
  1011.                 EntityComponentBase component = entity.Components[i];
  1012.                 if (component is T t)
  1013.                     return t;
  1014.             }
  1015.  
  1016.             return null;
  1017.         }
  1018.         #endregion
  1019.  
  1020.         #region Zone Functions
  1021.         private void InitializeZones()
  1022.         {
  1023.             if (zonesInitialized)
  1024.                 return;
  1025.  
  1026.             foreach (Zone.Definition definition in storedData.definitions)
  1027.             {
  1028.                 Zone zone = new GameObject().AddComponent<Zone>();
  1029.                 zone.InitializeZone(definition);
  1030.  
  1031.                 zones.Add(definition.Id, zone);
  1032.             }
  1033.  
  1034.             foreach (Zone zone in zones.Values)
  1035.                 zone.FindZoneParent();
  1036.  
  1037.             zonesInitialized = true;
  1038.  
  1039.             UnsubscribeAll();
  1040.             UpdateHookSubscriptions();
  1041.         }
  1042.  
  1043.         private static bool ReverseVelocity(BaseVehicle baseVehicle)
  1044.         {
  1045.             if (baseVehicle is BaseVehicleModule module)
  1046.                 baseVehicle = module.Vehicle;
  1047.  
  1048.             if (!baseVehicle || !baseVehicle.IsVehicleRoot() || !baseVehicle.rigidBody)
  1049.                 return false;
  1050.  
  1051.             if (baseVehicle.AnyMounted() && baseVehicle is BaseHelicopter)
  1052.             {
  1053.                 baseVehicle.rigidBody.velocity *= -1f;
  1054.                 Vector3 euler = baseVehicle.transform.eulerAngles;
  1055.                 baseVehicle.transform.rotation = Quaternion.Euler(euler.x, euler.y - 180f, euler.z);
  1056.             }
  1057.             else
  1058.             {
  1059.                 Vector3 force = (baseVehicle.rigidBody.velocity.normalized * -1f) * baseVehicle.rigidBody.mass * 4f;
  1060.                 baseVehicle.rigidBody.velocity = Vector3.zero;
  1061.                 baseVehicle.rigidBody.AddForce(force, ForceMode.Impulse);
  1062.             }
  1063.  
  1064.             return true;
  1065.         }
  1066.  
  1067.         private void EjectPlayer(BasePlayer player, Zone zone)
  1068.         {
  1069.             if (zone.keepInList.Contains(player.userID.Get()) || zone.whitelist.Contains(player.userID.Get()))
  1070.                 return;
  1071.  
  1072.             if (!string.IsNullOrEmpty(zone.definition.Permission))
  1073.             {
  1074.                 if (HasPermission(player, zone.definition.Permission))
  1075.                     return;
  1076.             }
  1077.  
  1078.             if (player.isMounted && ReverseVelocity(player.GetMountedVehicle()))
  1079.             {
  1080.                 SendMessage(player, Message("eject", player.UserIDString));
  1081.                 return;
  1082.             }
  1083.  
  1084.             Vector3 position = Vector3.zero;
  1085.             if (Spawns && !string.IsNullOrEmpty(zone.definition.EjectSpawns))
  1086.             {
  1087.                 object success = Spawns.Call("GetRandomSpawn", zone.definition.EjectSpawns);
  1088.                 if (success is Vector3 vector3)
  1089.                     position = vector3;
  1090.             }
  1091.  
  1092.             if (position == Vector3.zero)
  1093.             {
  1094.                 float distance = zone.definition.Size != Vector3.zero ? Mathf.Max(zone.definition.Size.x, zone.definition.Size.z) : zone.definition.Radius;
  1095.  
  1096.                 position = zone.transform.position + (((player.transform.position.XZ3D() - zone.transform.position.XZ3D()).normalized) * (distance + 10f));
  1097.  
  1098.                 if (Physics.Raycast(new Ray(new Vector3(position.x, position.y + 300, position.z), Vector3.down), out RaycastHit rayHit, 500, TARGET_LAYERS, QueryTriggerInteraction.Ignore))
  1099.                     position.y = rayHit.point.y;
  1100.                 else position.y = TerrainMeta.HeightMap.GetHeight(position);
  1101.             }
  1102.  
  1103.             player.MovePosition(position);
  1104.             //player.ClientRPCPlayer(null, player, "ForcePositionTo", player.transform.position);
  1105.             player.ClientRPC(RpcTarget.Player("ForcePositionTo", player), player.transform.position);
  1106.             player.SendNetworkUpdateImmediate();
  1107.  
  1108.             SendMessage(player, Message("eject", player.UserIDString));
  1109.         }
  1110.  
  1111.         private void AttractPlayer(BasePlayer player, Zone zone)
  1112.         {
  1113.             if (player.isMounted && ReverseVelocity(player.GetMountedVehicle()))
  1114.             {
  1115.                 SendMessage(player, Message("attract", player.UserIDString));
  1116.                 return;
  1117.             }
  1118.  
  1119.             float distance = zone.definition.Size != Vector3.zero ? Mathf.Max(zone.definition.Size.x, zone.definition.Size.z) : zone.definition.Radius;
  1120.  
  1121.             Vector3 position = zone.transform.position + (player.transform.position - zone.transform.position).normalized * (distance - 5f);
  1122.             position.y = TerrainMeta.HeightMap.GetHeight(position);
  1123.  
  1124.             player.MovePosition(position);
  1125.             //player.ClientRPCPlayer(null, player, "ForcePositionTo", player.transform.position);
  1126.             player.ClientRPC(RpcTarget.Player("ForcePositionTo", player), player.transform.position);
  1127.             player.SendNetworkUpdateImmediate();
  1128.  
  1129.             SendMessage(player, Message("attract", player.UserIDString));
  1130.         }
  1131.  
  1132.         private void ShowZone(BasePlayer player, string zoneId, float time = 30)
  1133.         {
  1134.             Zone zone = GetZoneByID(zoneId);
  1135.             if (!zone)
  1136.                 return;
  1137.  
  1138.             if (zone.definition.Size != Vector3.zero)
  1139.             {
  1140.                 Vector3 center = zone.definition.Location;
  1141.                 Quaternion rotation = Quaternion.Euler(zone.definition.Rotation);
  1142.                 Vector3 size = zone.definition.Size / 2;
  1143.                 Vector3 point1 = RotatePointAroundPivot(new Vector3(center.x + size.x, center.y + size.y, center.z + size.z), center, rotation);
  1144.                 Vector3 point2 = RotatePointAroundPivot(new Vector3(center.x + size.x, center.y - size.y, center.z + size.z), center, rotation);
  1145.                 Vector3 point3 = RotatePointAroundPivot(new Vector3(center.x + size.x, center.y + size.y, center.z - size.z), center, rotation);
  1146.                 Vector3 point4 = RotatePointAroundPivot(new Vector3(center.x + size.x, center.y - size.y, center.z - size.z), center, rotation);
  1147.                 Vector3 point5 = RotatePointAroundPivot(new Vector3(center.x - size.x, center.y + size.y, center.z + size.z), center, rotation);
  1148.                 Vector3 point6 = RotatePointAroundPivot(new Vector3(center.x - size.x, center.y - size.y, center.z + size.z), center, rotation);
  1149.                 Vector3 point7 = RotatePointAroundPivot(new Vector3(center.x - size.x, center.y + size.y, center.z - size.z), center, rotation);
  1150.                 Vector3 point8 = RotatePointAroundPivot(new Vector3(center.x - size.x, center.y - size.y, center.z - size.z), center, rotation);
  1151.  
  1152.                 player.SendConsoleCommand("ddraw.line", time, Color.blue, point1, point2);
  1153.                 player.SendConsoleCommand("ddraw.line", time, Color.blue, point1, point3);
  1154.                 player.SendConsoleCommand("ddraw.line", time, Color.blue, point1, point5);
  1155.                 player.SendConsoleCommand("ddraw.line", time, Color.blue, point4, point2);
  1156.                 player.SendConsoleCommand("ddraw.line", time, Color.blue, point4, point3);
  1157.                 player.SendConsoleCommand("ddraw.line", time, Color.blue, point4, point8);
  1158.  
  1159.                 player.SendConsoleCommand("ddraw.line", time, Color.blue, point5, point6);
  1160.                 player.SendConsoleCommand("ddraw.line", time, Color.blue, point5, point7);
  1161.                 player.SendConsoleCommand("ddraw.line", time, Color.blue, point6, point2);
  1162.                 player.SendConsoleCommand("ddraw.line", time, Color.blue, point8, point6);
  1163.                 player.SendConsoleCommand("ddraw.line", time, Color.blue, point8, point7);
  1164.                 player.SendConsoleCommand("ddraw.line", time, Color.blue, point7, point3);
  1165.             }
  1166.             else player.SendConsoleCommand("ddraw.sphere", time, Color.blue, zone.definition.Location, zone.definition.Radius);
  1167.         }
  1168.  
  1169.         private Vector3 RotatePointAroundPivot(Vector3 point, Vector3 pivot, Quaternion rotation) => rotation * (point - pivot) + pivot;
  1170.  
  1171.         #endregion
  1172.  
  1173.         #region Component
  1174.         public class Zone : MonoBehaviour
  1175.         {
  1176.             public Definition definition;
  1177.  
  1178.             public ZoneFlags disabledFlags = new ZoneFlags();
  1179.  
  1180.             public Zone parent;
  1181.  
  1182.  
  1183.             public List<BasePlayer> players = Pool.Get<List<BasePlayer>>();
  1184.  
  1185.             public List<BaseEntity> entities = Pool.Get<List<BaseEntity>>();
  1186.  
  1187.             private List<IOEntity> ioEntities = Pool.Get<List<IOEntity>>();
  1188.  
  1189.  
  1190.             public List<ulong> keepInList = Pool.Get<List<ulong>>();
  1191.  
  1192.             public List<ulong> whitelist = Pool.Get<List<ulong>>();
  1193.  
  1194.             public Hash<ulong, EntityZones> entityZones = new Hash<ulong, EntityZones>();
  1195.  
  1196.  
  1197.             private Rigidbody rigidbody;
  1198.  
  1199.             public Collider collider;
  1200.  
  1201.             public Bounds colliderBounds;
  1202.  
  1203.  
  1204.             private ChildSphereTrigger<TriggerRadiation> radiation;
  1205.  
  1206.             private ChildSphereTrigger<TriggerComfort> comfort;
  1207.  
  1208.             private ChildSphereTrigger<TriggerTemperature> temperature;
  1209.  
  1210.             private ChildSphereTrigger<TriggerSafeZone> safeZone;
  1211.  
  1212.  
  1213.             private readonly Hash<BaseVehicle, float> lastReversedTimes = new Hash<BaseVehicle, float>();
  1214.  
  1215.             private int creationFrame;
  1216.  
  1217.             private bool isTogglingLights = false;
  1218.  
  1219.             private void Awake()
  1220.             {
  1221.                 gameObject.layer = (int)Layer.Reserved1;
  1222.                 gameObject.name = "ZoneManager";
  1223.                 enabled = false;
  1224.  
  1225.                 creationFrame = Time.frameCount;
  1226.             }
  1227.  
  1228.             private void OnDestroy()
  1229.             {
  1230.                 EmptyZone();
  1231.  
  1232.                 Pool.FreeUnmanaged(ref players);
  1233.                 Pool.FreeUnmanaged(ref entities);
  1234.                 Pool.FreeUnmanaged(ref ioEntities);
  1235.                 Pool.FreeUnmanaged(ref keepInList);
  1236.                 Pool.FreeUnmanaged(ref whitelist);
  1237.  
  1238.                 Interface.CallHook("OnZoneDestroyed", definition.Id);
  1239.             }
  1240.  
  1241.             private void EmptyZone()
  1242.             {
  1243.                 RemovePlayersFromTriggers();
  1244.  
  1245.                 keepInList.Clear();
  1246.  
  1247.                 ioEntities.Clear();
  1248.  
  1249.                 for (int i = players.Count - 1; i >= 0; i--)
  1250.                     Instance?.OnPlayerExitZone(players[i], this);
  1251.  
  1252.                 for (int i = entities.Count - 1; i >= 0; i--)
  1253.                     Instance?.OnEntityExitZone(entities[i], this);
  1254.             }
  1255.  
  1256.             #region Zone Initialization
  1257.             public void InitializeZone(Definition definition)
  1258.             {
  1259.                 if (this.definition == null)
  1260.                     Interface.CallHook("OnZoneInitialize", definition.Id);
  1261.  
  1262.                 this.definition = definition;
  1263.  
  1264.                 transform.position = definition.Location;
  1265.  
  1266.                 transform.rotation = Quaternion.Euler(definition.Rotation);
  1267.  
  1268.                 if (definition.Enabled)
  1269.                 {
  1270.                     RegisterPermission();
  1271.  
  1272.                     InitializeCollider();
  1273.  
  1274.                     InitializeAutoLights();
  1275.  
  1276.                     InitializeRadiation();
  1277.  
  1278.                     InitializeSafeZone();
  1279.  
  1280.                     InitializeComfort();
  1281.  
  1282.                     InitializeTemperature();
  1283.  
  1284.                     RemovePlayersFromTriggers();
  1285.  
  1286.                     AddPlayersToTriggers();
  1287.  
  1288.                     OnZoneFlagsChanged();
  1289.                 }
  1290.                 else
  1291.                 {
  1292.                     InvokeHandler.CancelInvoke(this, CheckAlwaysLights);
  1293.                     InvokeHandler.CancelInvoke(this, CheckLights);
  1294.  
  1295.                     if (isLightsOn)
  1296.                         ServerMgr.Instance.StartCoroutine(ToggleLights(false));
  1297.  
  1298.                     EmptyZone();
  1299.  
  1300.                     if (collider)
  1301.                         DestroyImmediate(collider);
  1302.  
  1303.                     if (rigidbody)
  1304.                         DestroyImmediate(rigidbody);
  1305.                 }
  1306.  
  1307.                 enabled = definition.Enabled;
  1308.             }
  1309.  
  1310.             public void FindZoneParent()
  1311.             {
  1312.                 if (string.IsNullOrEmpty(definition.ParentID))
  1313.                     return;
  1314.  
  1315.                 if (Instance == null)
  1316.                 {
  1317.                     Debug.LogError($"[ZoneManager] Zone attempted to find parent zone, but plugin instance is null...");
  1318.                     return;
  1319.                 }
  1320.  
  1321.                 Instance.zones.TryGetValue(definition.ParentID, out parent);
  1322.             }
  1323.  
  1324.             public void Reset()
  1325.             {
  1326.                 InvokeHandler.CancelInvoke(this, CheckAlwaysLights);
  1327.                 InvokeHandler.CancelInvoke(this, CheckLights);
  1328.  
  1329.                 if (isLightsOn)
  1330.                     ServerMgr.Instance.StartCoroutine(ToggleLights(false));
  1331.  
  1332.                 EmptyZone();
  1333.  
  1334.                 InitializeZone(definition);
  1335.                 FindZoneParent();
  1336.             }
  1337.  
  1338.             public void OnZoneFlagsChanged()
  1339.             {
  1340.                 if (HasFlag(ZoneFlags.PoweredSwitches))
  1341.                 {
  1342.                     if (!InvokeHandler.IsInvoking(this, IOTick))
  1343.                         InvokeHandler.InvokeRandomized(this, IOTick, 0f, 1f, 0.1f);
  1344.                 }
  1345.                 else InvokeHandler.CancelInvoke(this, IOTick);
  1346.             }
  1347.  
  1348.             private void RegisterPermission()
  1349.             {
  1350.                 if (Instance == null)
  1351.                 {
  1352.                     Debug.LogError($"[ZoneManager] Zone attempted to register permission, but plugin instance is null...");
  1353.                     return;
  1354.                 }
  1355.  
  1356.                 if (!string.IsNullOrEmpty(definition.Permission) && !Instance.permission.PermissionExists(definition.Permission))
  1357.                     Instance.permission.RegisterPermission(definition.Permission, Instance);
  1358.             }
  1359.  
  1360.             private void InitializeCollider()
  1361.             {
  1362.                 if (collider)
  1363.                     DestroyImmediate(collider);
  1364.  
  1365.                 if (rigidbody)
  1366.                     DestroyImmediate(rigidbody);
  1367.  
  1368.                 rigidbody = gameObject.AddComponent<Rigidbody>();
  1369.                 rigidbody.useGravity = false;
  1370.                 rigidbody.isKinematic = true;
  1371.                 rigidbody.detectCollisions = true;
  1372.                 rigidbody.collisionDetectionMode = CollisionDetectionMode.Discrete;
  1373.  
  1374.                 SphereCollider sphereCollider = gameObject.GetComponent<SphereCollider>();
  1375.                 BoxCollider boxCollider = gameObject.GetComponent<BoxCollider>();
  1376.  
  1377.                 if (definition.Size != Vector3.zero)
  1378.                 {
  1379.                     if (sphereCollider)
  1380.                         Destroy(sphereCollider);
  1381.  
  1382.                     if (!boxCollider)
  1383.                     {
  1384.                         boxCollider = gameObject.AddComponent<BoxCollider>();
  1385.                         boxCollider.isTrigger = true;
  1386.                     }
  1387.                     boxCollider.size = definition.Size;
  1388.                     colliderBounds = boxCollider.bounds;
  1389.                     collider = boxCollider;
  1390.                 }
  1391.                 else
  1392.                 {
  1393.                     if (boxCollider)
  1394.                         Destroy(boxCollider);
  1395.  
  1396.                     if (!sphereCollider)
  1397.                     {
  1398.                         sphereCollider = gameObject.AddComponent<SphereCollider>();
  1399.                         sphereCollider.isTrigger = true;
  1400.                     }
  1401.                     sphereCollider.radius = definition.Radius;
  1402.                     colliderBounds = sphereCollider.bounds;
  1403.                     collider = sphereCollider;
  1404.                 }
  1405.             }
  1406.             #endregion
  1407.  
  1408.             #region Triggers
  1409.             private void InitializeRadiation()
  1410.             {
  1411.                 if (definition.Radiation > 0)
  1412.                 {
  1413.                     radiation ??= new ChildSphereTrigger<TriggerRadiation>(gameObject, "Radiation");
  1414.                     radiation.Trigger.RadiationAmountOverride = definition.Radiation;
  1415.                     radiation.Collider.radius = collider is SphereCollider ? definition.Radius : Mathf.Min(definition.Size.x, definition.Size.y, definition.Size.z) * 0.5f;
  1416.                     radiation.Trigger.enabled = this.enabled;
  1417.                 }
  1418.                 else radiation?.Destroy();
  1419.             }
  1420.  
  1421.             private void InitializeComfort()
  1422.             {
  1423.                 if (definition.Comfort > 0)
  1424.                 {
  1425.                     comfort ??= new ChildSphereTrigger<TriggerComfort>(gameObject, "Comfort");
  1426.                     comfort.Trigger.baseComfort = definition.Comfort;
  1427.                     comfort.Trigger.triggerSize = comfort.Collider.radius = collider is SphereCollider ? definition.Radius : Mathf.Min(definition.Size.x, definition.Size.y, definition.Size.z) * 0.5f;
  1428.                     comfort.Trigger.enabled = this.enabled;
  1429.                 }
  1430.                 else comfort?.Destroy();
  1431.             }
  1432.  
  1433.             private void InitializeTemperature()
  1434.             {
  1435.                 if (definition.Temperature != 0)
  1436.                 {
  1437.                     temperature ??= new ChildSphereTrigger<TriggerTemperature>(gameObject, "Temperature");
  1438.                     temperature.Trigger.Temperature = definition.Temperature;
  1439.                     temperature.Trigger.triggerSize = temperature.Collider.radius = collider is SphereCollider ? definition.Radius : Mathf.Min(definition.Size.x, definition.Size.y, definition.Size.z) * 0.5f;
  1440.                     temperature.Trigger.enabled = this.enabled;
  1441.                 }
  1442.                 else temperature?.Destroy();
  1443.             }
  1444.  
  1445.             private void InitializeSafeZone()
  1446.             {
  1447.                 if (definition.SafeZone)
  1448.                 {
  1449.                     safeZone ??= new ChildSphereTrigger<TriggerSafeZone>(gameObject, "SafeZone");
  1450.                 }
  1451.                 else safeZone?.Destroy();
  1452.             }
  1453.  
  1454.             private void AddToTrigger(TriggerBase triggerBase, BasePlayer player)
  1455.             {
  1456.                 if (!triggerBase || !player)
  1457.                     return;
  1458.  
  1459.                 triggerBase.entityContents ??= new HashSet<BaseEntity>();
  1460.  
  1461.                 if (!triggerBase.entityContents.Add(player))
  1462.                     return;
  1463.  
  1464.                 player.EnterTrigger(triggerBase);
  1465.  
  1466.                 if (triggerBase is not TriggerSafeZone)
  1467.                     return;
  1468.  
  1469.                 if (player.IsItemHoldRestricted(player.inventory.containerBelt.FindItemByUID(player.svActiveItemID)))
  1470.                     player.UpdateActiveItem(default(ItemId));
  1471.  
  1472.                 player.SetPlayerFlag(BasePlayer.PlayerFlags.SafeZone, true);
  1473.             }
  1474.  
  1475.             private void RemoveFromTrigger(TriggerBase triggerBase, BasePlayer player)
  1476.             {
  1477.                 if (!triggerBase || !player)
  1478.                     return;
  1479.  
  1480.                 if (triggerBase.entityContents == null || !triggerBase.entityContents.Contains(player))
  1481.                     return;
  1482.  
  1483.                 triggerBase.entityContents.Remove(player);
  1484.                 player.LeaveTrigger(triggerBase);
  1485.  
  1486.                 if (triggerBase is not TriggerSafeZone)
  1487.                     return;
  1488.  
  1489.                 if (!player.InSafeZone())
  1490.                     player.SetPlayerFlag(BasePlayer.PlayerFlags.SafeZone, false);
  1491.             }
  1492.  
  1493.             private void AddPlayersToTriggers()
  1494.             {
  1495.                 for (int i = 0; i < players.Count; i++)
  1496.                 {
  1497.                     BasePlayer player = players[i];
  1498.  
  1499.                     if (safeZone != null)
  1500.                         AddToTrigger(safeZone.Trigger, player);
  1501.  
  1502.                     if (radiation != null)
  1503.                         AddToTrigger(radiation.Trigger, player);
  1504.  
  1505.                     if (comfort != null)
  1506.                         AddToTrigger(comfort.Trigger, player);
  1507.  
  1508.                     if (temperature != null)
  1509.                         AddToTrigger(temperature.Trigger, player);
  1510.                 }
  1511.             }
  1512.  
  1513.             private void RemovePlayersFromTriggers()
  1514.             {
  1515.                 for (int i = 0; i < players.Count; i++)
  1516.                 {
  1517.                     BasePlayer player = players[i];
  1518.  
  1519.                     if (safeZone != null)
  1520.                         RemoveFromTrigger(safeZone.Trigger, player);
  1521.  
  1522.                     if (radiation != null)
  1523.                         RemoveFromTrigger(radiation.Trigger, player);
  1524.  
  1525.                     if (comfort != null)
  1526.                         RemoveFromTrigger(comfort.Trigger, player);
  1527.  
  1528.                     if (temperature != null)
  1529.                         RemoveFromTrigger(temperature.Trigger, player);
  1530.                 }
  1531.             }
  1532.  
  1533.             private class ChildSphereTrigger<T> where T : TriggerBase
  1534.             {
  1535.                 public GameObject Object { get; private set; }
  1536.  
  1537.                 public SphereCollider Collider { get; private set; }
  1538.  
  1539.                 public T Trigger { get; private set; }
  1540.  
  1541.                 public ChildSphereTrigger(GameObject parent, string name)
  1542.                 {
  1543.                     Object = parent.CreateChild();
  1544.                     Object.name = name;
  1545.                     Object.layer = (int)Layer.TransparentFX;
  1546.  
  1547.                     Collider = Object.AddComponent<SphereCollider>();
  1548.                     Collider.isTrigger = true;
  1549.  
  1550.                     Trigger = Object.AddComponent<T>();
  1551.                     Trigger.interestLayers = 0;
  1552.                 }
  1553.  
  1554.                 public void Destroy() => UnityEngine.Object.Destroy(Object);
  1555.             }
  1556.             #endregion
  1557.  
  1558.             #region Autolights
  1559.             private bool isLightsOn = false;
  1560.  
  1561.             private void InitializeAutoLights()
  1562.             {
  1563.                 if (HasFlag(ZoneFlags.AlwaysLights))
  1564.                 {
  1565.                     isLightsOn = true;
  1566.  
  1567.                     InvokeHandler.CancelInvoke(this, CheckAlwaysLights);
  1568.                     InvokeHandler.InvokeRandomized(this, CheckAlwaysLights, 5f, 60f, 10f);
  1569.                 }
  1570.                 else if (HasFlag(ZoneFlags.AutoLights))
  1571.                 {
  1572.                     InvokeHandler.CancelInvoke(this, CheckLights);
  1573.                     InvokeHandler.InvokeRandomized(this, CheckLights, 5f, 20f, 10f);
  1574.                 }
  1575.             }
  1576.  
  1577.             private void CheckAlwaysLights()
  1578.             {
  1579.                 ServerMgr.Instance.StartCoroutine(ToggleLights(true));
  1580.             }
  1581.  
  1582.             private void CheckLights()
  1583.             {
  1584.                 float currentTime = TOD_Sky.Instance.Cycle.Hour;
  1585.  
  1586.                 bool shouldBeActive = currentTime > Configuration.AutoLights.OnTime || currentTime < Configuration.AutoLights.OffTime;
  1587.  
  1588.                 if (shouldBeActive == isLightsOn)
  1589.                     return;
  1590.  
  1591.                 isLightsOn = shouldBeActive;
  1592.                 ServerMgr.Instance.StartCoroutine(ToggleLights(isLightsOn));
  1593.             }
  1594.  
  1595.             private IEnumerator ToggleLights(bool active)
  1596.             {
  1597.                 while (isTogglingLights)
  1598.                     yield return null;
  1599.  
  1600.                 isTogglingLights = true;
  1601.  
  1602.                 bool requiresFuel = Configuration.AutoLights.RequiresFuel;
  1603.  
  1604.                 for (int i = 0; i < entities.Count; i++)
  1605.                 {
  1606.                     if (ToggleLight(entities[i], active, requiresFuel))
  1607.                         yield return CoroutineEx.waitForEndOfFrame;
  1608.                 }
  1609.  
  1610.                 isTogglingLights = false;
  1611.             }
  1612.  
  1613.             private bool ToggleLight(BaseEntity baseEntity, bool active, bool requiresFuel)
  1614.             {
  1615.                 BaseOven baseOven = baseEntity as BaseOven;
  1616.                 if (baseOven)
  1617.                 {
  1618.                     if (active)
  1619.                     {
  1620.                         if (!baseOven.IsOn())
  1621.                         {
  1622.                             if ((requiresFuel && baseOven.FindBurnable() != null) || !requiresFuel)
  1623.                                 baseOven.SetFlag(BaseEntity.Flags.On, true);
  1624.                         }
  1625.                     }
  1626.                     else
  1627.                     {
  1628.                         if (baseOven.IsOn())
  1629.                             baseOven.StopCooking();
  1630.                     }
  1631.  
  1632.                     return true;
  1633.                 }
  1634.  
  1635.                 SearchLight searchLight = baseEntity as SearchLight;
  1636.                 if (searchLight)
  1637.                 {
  1638.                     if (active)
  1639.                     {
  1640.                         if (!searchLight.IsOn())
  1641.                             searchLight.SetFlag(BaseEntity.Flags.On, true);
  1642.                     }
  1643.                     else
  1644.                     {
  1645.                         if (searchLight.IsOn())
  1646.                             searchLight.SetFlag(BaseEntity.Flags.On, false);
  1647.                     }
  1648.  
  1649.                     return true;
  1650.                 }
  1651.  
  1652.                 return false;
  1653.             }
  1654.             #endregion
  1655.  
  1656.             #region Entity Detection
  1657.             private void OnTriggerEnter(Collider col)
  1658.             {
  1659.                 if (!definition.Enabled || !col || !col.gameObject)
  1660.                     return;
  1661.  
  1662.                 BaseEntity baseEntity = col.gameObject.ToBaseEntity();
  1663.                 if (!baseEntity || !baseEntity.IsValid())
  1664.                     return;
  1665.  
  1666.                 if (baseEntity is BasePlayer { IsNpc: false } player)
  1667.                 {
  1668.                     Instance?.OnPlayerEnterZone(player, this);
  1669.  
  1670.                     if (parent)
  1671.                         Instance?.UpdateZoneEntityFlags(this);
  1672.  
  1673.                     return;
  1674.                 }
  1675.  
  1676.                 Instance?.OnEntityEnterZone(baseEntity, this);
  1677.             }
  1678.  
  1679.             private void OnTriggerExit(Collider col)
  1680.             {
  1681.                 if (!definition.Enabled || !col || !col.gameObject)
  1682.                     return;
  1683.  
  1684.                 BaseEntity baseEntity = col.gameObject.ToBaseEntity();
  1685.                 if (!baseEntity || !baseEntity.IsValid())
  1686.                     return;
  1687.  
  1688.                 if (baseEntity is BasePlayer { IsNpc: false } player)
  1689.                 {
  1690.                     Instance?.OnPlayerExitZone(player, this);
  1691.  
  1692.                     return;
  1693.                 }
  1694.  
  1695.                 Instance?.OnEntityExitZone(baseEntity, this);
  1696.             }
  1697.  
  1698.             public void OnPlayerEnterZone(BasePlayer player)
  1699.             {
  1700.                 if (!players.Contains(player))
  1701.                     players.Add(player);
  1702.  
  1703.                 if (zonedPlayers.TryGetValue(player.userID, out EntityZones entityZone))
  1704.                     entityZones[player.userID] = entityZone;
  1705.  
  1706.                 if (safeZone != null)
  1707.                     AddToTrigger(safeZone.Trigger, player);
  1708.  
  1709.                 if (radiation != null)
  1710.                     AddToTrigger(radiation.Trigger, player);
  1711.  
  1712.                 if (comfort != null)
  1713.                     AddToTrigger(comfort.Trigger, player);
  1714.  
  1715.                 if (temperature != null)
  1716.                     AddToTrigger(temperature.Trigger, player);
  1717.             }
  1718.  
  1719.             public void OnPlayerExitZone(BasePlayer player)
  1720.             {
  1721.                 players.Remove(player);
  1722.                 entityZones.Remove(player.userID);
  1723.  
  1724.                 if (safeZone != null)
  1725.                     RemoveFromTrigger(safeZone.Trigger, player);
  1726.  
  1727.                 if (radiation != null)
  1728.                     RemoveFromTrigger(radiation.Trigger, player);
  1729.  
  1730.                 if (comfort != null)
  1731.                     RemoveFromTrigger(comfort.Trigger, player);
  1732.  
  1733.                 if (temperature != null)
  1734.                     RemoveFromTrigger(temperature.Trigger, player);
  1735.             }
  1736.  
  1737.             public void OnEntityEnterZone(BaseEntity baseEntity)
  1738.             {
  1739.                 entities.Add(baseEntity);
  1740.  
  1741.                 if (zonedEntities.TryGetValue(baseEntity.net.ID, out EntityZones entityZone))
  1742.                     entityZones[baseEntity.net.ID.Value] = entityZone;
  1743.  
  1744.                 if (HasFlag(ZoneFlags.NoDecay))
  1745.                 {
  1746.                     DecayEntity decayEntity = baseEntity.GetComponentInParent<DecayEntity>();
  1747.                     if (decayEntity)
  1748.                     {
  1749.                         decayEntity.decay = null;
  1750.                     }
  1751.                 }
  1752.  
  1753.                 if (HasFlag(ZoneFlags.NoStability))
  1754.                 {
  1755.                     if (baseEntity is StabilityEntity entity)
  1756.                     {
  1757.                         entity.grounded = true;
  1758.                     }
  1759.                 }
  1760.  
  1761.                 if (HasFlag(ZoneFlags.NpcFreeze) && baseEntity.IsNpc)
  1762.                 {
  1763.                     if (baseEntity is BaseAnimalNPC animalNpc)
  1764.                     {
  1765.                         animalNpc.brain.SetEnabled(false);
  1766.                         return;
  1767.                     }
  1768.  
  1769.                     if (baseEntity is global::HumanNPC humanNpc)
  1770.                     {
  1771.                         humanNpc.Brain.SetEnabled(false);
  1772.                         return;
  1773.                     }
  1774.  
  1775.                     if (baseEntity is ScarecrowNPC scarecrowNpc)
  1776.                     {
  1777.                         scarecrowNpc.Brain.SetEnabled(false);
  1778.                         return;
  1779.                     }
  1780.  
  1781.                     if (baseEntity is BaseNpc npc)
  1782.                     {
  1783.                         npc.CancelInvoke(npc.TickAi);
  1784.                         return;
  1785.                     }
  1786.  
  1787.                     if (baseEntity is BaseNPC2 npc2)
  1788.                     {
  1789.                         FSMComponent fsmComponent = npc2.GetComponent<FSMComponent>();
  1790.                         if (fsmComponent)
  1791.                             fsmComponent.SetFsmActive(false);
  1792.                     }
  1793.                 }
  1794.  
  1795.                 if (baseEntity is SmartSwitch or ElectricSwitch or RFReceiver)
  1796.                 {
  1797.                     ioEntities.Add((IOEntity)baseEntity);
  1798.  
  1799.                     if (HasFlag(ZoneFlags.PoweredSwitches))
  1800.                     {
  1801.                         ((IOEntity)baseEntity).SetFlag(BaseEntity.Flags.Reserved8, true);
  1802.                         ((IOEntity)baseEntity).currentEnergy = int.MaxValue;
  1803.                     }
  1804.                 }
  1805.  
  1806.                 if (HasFlag(ZoneFlags.AlwaysLights) || (HasFlag(ZoneFlags.AutoLights) && isLightsOn))
  1807.                 {
  1808.                     if (baseEntity is BaseOven or SearchLight)
  1809.                     {
  1810.                         ToggleLight(baseEntity, true, Configuration.AutoLights.RequiresFuel);
  1811.                     }
  1812.                 }
  1813.             }
  1814.  
  1815.             public void OnEntityExitZone(BaseEntity baseEntity, bool resetDecay, bool isDead = false)
  1816.             {
  1817.                 entities.Remove(baseEntity);
  1818.  
  1819.                 entityZones.Remove(baseEntity.net.ID.Value);
  1820.  
  1821.                 if (isDead)
  1822.                     return;
  1823.  
  1824.                 if (resetDecay)
  1825.                 {
  1826.                     if (HasFlag(ZoneFlags.NoDecay))
  1827.                     {
  1828.                         DecayEntity decayEntity = baseEntity.GetComponentInParent<DecayEntity>();
  1829.                         if (decayEntity)
  1830.                         {
  1831.                             decayEntity.decay = PrefabAttribute.server.Find<Decay>(decayEntity.prefabID);
  1832.                         }
  1833.                     }
  1834.                 }
  1835.  
  1836.                 if (HasFlag(ZoneFlags.NpcFreeze) && baseEntity.IsNpc)
  1837.                 {
  1838.                     if (baseEntity is BaseAnimalNPC animalNpc)
  1839.                     {
  1840.                         animalNpc.brain.SetEnabled(true);
  1841.                         return;
  1842.                     }
  1843.  
  1844.                     if (baseEntity is global::HumanNPC humanNpc)
  1845.                     {
  1846.                         humanNpc.Brain.SetEnabled(true);
  1847.                         return;
  1848.                     }
  1849.  
  1850.                     if (baseEntity is ScarecrowNPC scarecrowNpc)
  1851.                     {
  1852.                         scarecrowNpc.Brain.SetEnabled(true);
  1853.                         return;
  1854.                     }
  1855.  
  1856.                     if (baseEntity is BaseNpc npc)
  1857.                     {
  1858.                         npc.InvokeRandomized(npc.TickAi, 0.1f, 0.1f, 0.00500000035f);
  1859.                         return;
  1860.                     }
  1861.  
  1862.                     if (baseEntity is BaseNPC2 npc2)
  1863.                     {
  1864.                         FSMComponent fsmComponent = npc2.GetComponent<FSMComponent>();
  1865.                         if (fsmComponent)
  1866.                             fsmComponent.SetFsmActive(true);
  1867.                     }
  1868.                 }
  1869.  
  1870.                 if (baseEntity is SmartSwitch or ElectricSwitch or RFReceiver)
  1871.                 {
  1872.                     IOEntity ioEntity = (IOEntity)baseEntity;
  1873.                     ioEntities.Remove(ioEntity);
  1874.  
  1875.                     if (HasFlag(ZoneFlags.PoweredSwitches))
  1876.                     {
  1877.                         ioEntity.SetFlag(BaseEntity.Flags.Reserved8, false);
  1878.                         ioEntity.currentEnergy = 0;
  1879.  
  1880.                         for (int i = 0; i < ioEntity.inputs.Length; i++)
  1881.                         {
  1882.                             IOEntity fromEntity = ioEntity.inputs[i].connectedTo.Get();
  1883.                             if (fromEntity)
  1884.                                 fromEntity.MarkDirtyForceUpdateOutputs();
  1885.                         }
  1886.                     }
  1887.                 }
  1888.  
  1889.                 if (!HasFlag(ZoneFlags.AlwaysLights) && (!HasFlag(ZoneFlags.AutoLights) || !isLightsOn))
  1890.                     return;
  1891.  
  1892.                 if (baseEntity is BaseOven or SearchLight)
  1893.                 {
  1894.                     ToggleLight(baseEntity, false, false);
  1895.                 }
  1896.             }
  1897.             #endregion
  1898.  
  1899.             #region IO Power
  1900.             private void IOTick()
  1901.             {
  1902.                 for (int i = 0; i < ioEntities.Count; i++)
  1903.                 {
  1904.                     IOEntity ioEntity = ioEntities[i];
  1905.  
  1906.                     if (!ioEntity || ioEntity.IsDestroyed)
  1907.                         continue;
  1908.  
  1909.                     ioEntity.SetFlag(BaseEntity.Flags.Reserved8, true);
  1910.                     ioEntity.currentEnergy = int.MaxValue;
  1911.                 }
  1912.             }
  1913.             #endregion
  1914.  
  1915.             #region Vehicle Enter/Exit
  1916.             public bool TryReverseVelocity(BaseEntity baseEntity)
  1917.             {
  1918.                 if (Time.frameCount == creationFrame)
  1919.                     return false;
  1920.  
  1921.                 if (baseEntity is not BaseVehicle baseVehicle)
  1922.                     return false;
  1923.  
  1924.                 if (baseVehicle is BaseVehicleModule module)
  1925.                     baseVehicle = module.Vehicle;
  1926.  
  1927.                 if (!CanReverseVelocity(baseVehicle))
  1928.                     return false;
  1929.  
  1930.                 if (!ReverseVelocity(baseVehicle))
  1931.                     return false;
  1932.  
  1933.                 lastReversedTimes[baseVehicle] = Time.time;
  1934.                 return true;
  1935.  
  1936.             }
  1937.  
  1938.             private bool CanReverseVelocity(BaseVehicle baseVehicle)
  1939.             {
  1940.                 if (lastReversedTimes.TryGetValue(baseVehicle, out float lastReversedTime))
  1941.                     return Time.time - lastReversedTime > 0.5f;
  1942.  
  1943.                 return true;
  1944.             }
  1945.             #endregion
  1946.  
  1947.             #region Helpers
  1948.             public bool HasPermission(BasePlayer player)
  1949.             {
  1950.                 if (Instance == null)
  1951.                 {
  1952.                     Debug.LogError($"[ZoneManager] Zone attempted to check player permission, but plugin instance is null...");
  1953.                     return false;
  1954.                 }
  1955.  
  1956.                 return string.IsNullOrEmpty(definition.Permission) || Instance.permission.UserHasPermission(player.UserIDString, definition.Permission);
  1957.             }
  1958.  
  1959.             public bool CanLeaveZone(BasePlayer player) => !keepInList.Contains(player.userID.Get());
  1960.  
  1961.             public bool CanEnterZone(BasePlayer player) => HasPermission(player) || !CanLeaveZone(player) || whitelist.Contains(player.userID.Get());
  1962.             #endregion
  1963.  
  1964.             #region Flags
  1965.             public void AddFlag(int flag)
  1966.             {
  1967.                 definition.Flags.AddFlag(flag);
  1968.                 OnZoneFlagsChanged();
  1969.             }
  1970.  
  1971.             public void RemoveFlag(int flag)
  1972.             {
  1973.                 definition.Flags.RemoveFlag(flag);
  1974.                 OnZoneFlagsChanged();
  1975.             }
  1976.  
  1977.             public bool HasFlag(int flag) => definition.Flags.HasFlag(flag) && !disabledFlags.HasFlag(flag);
  1978.  
  1979.             public bool HasDisabledFlag(int flag) => disabledFlags.HasFlag(flag);
  1980.  
  1981.             public void AddDisabledFlag(int flag)
  1982.             {
  1983.                 disabledFlags.AddFlag(flag);
  1984.                 OnZoneFlagsChanged();
  1985.             }
  1986.  
  1987.             public void RemoveDisabledFlag(int flag)
  1988.             {
  1989.                 disabledFlags.AddFlag(flag);
  1990.                 OnZoneFlagsChanged();
  1991.             }
  1992.             #endregion
  1993.  
  1994.             #region Zone Definition
  1995.             public class Definition
  1996.             {
  1997.                 public string Id { get; set; }
  1998.  
  1999.                 [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
  2000.                 public string Name { get; set; }
  2001.  
  2002.                 [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
  2003.                 public float Radius { get; set; }
  2004.  
  2005.                 [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
  2006.                 public float Radiation { get; set; }
  2007.  
  2008.                 [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
  2009.                 public float Comfort { get; set; }
  2010.  
  2011.                 [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
  2012.                 public float Temperature { get; set; }
  2013.  
  2014.                 [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
  2015.                 public bool SafeZone { get; set; }
  2016.  
  2017.                 public Vector3 Location { get; set; }
  2018.  
  2019.                 public Vector3 Size { get; set; }
  2020.  
  2021.                 public Vector3 Rotation { get; set; }
  2022.  
  2023.                 [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
  2024.                 public string ParentID { get; set; }
  2025.  
  2026.                 [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
  2027.                 public string EnterMessage { get; set; }
  2028.  
  2029.                 [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
  2030.                 public string LeaveMessage { get; set; }
  2031.  
  2032.                 [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
  2033.                 public string Permission { get; set; }
  2034.  
  2035.                 [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
  2036.                 public string EjectSpawns { get; set; }
  2037.  
  2038.                 [DefaultValue(true)]
  2039.                 [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
  2040.                 public bool Enabled { get; set; } = true;
  2041.  
  2042.                 [JsonIgnore]
  2043.                 public Plugin Owner { get; private set; }
  2044.  
  2045.                 [JsonIgnore]
  2046.                 public bool IsTemporary { get; private set; }
  2047.  
  2048.                 public ZoneFlags Flags { get; set; }
  2049.  
  2050.                 public Definition() { }
  2051.  
  2052.                 public Definition(Vector3 position)
  2053.                 {
  2054.                     Radius = 20f;
  2055.                     Location = position;
  2056.  
  2057.                     Flags = new ZoneFlags();
  2058.                 }
  2059.  
  2060.                 public void WithOwner(Plugin owner)
  2061.                 {
  2062.                     Owner = owner;
  2063.                     IsTemporary = true;
  2064.                 }
  2065.             }
  2066.             #endregion
  2067.         }
  2068.         #endregion
  2069.  
  2070.         #region Entity Management
  2071.         private void OnPlayerEnterZone(BasePlayer player, Zone zone)
  2072.         {
  2073.             if (!player || IsNpc(player))
  2074.                 return;
  2075.  
  2076.             if (!zone.CanEnterZone(player))
  2077.             {
  2078.                 EjectPlayer(player, zone);
  2079.                 return;
  2080.             }
  2081.  
  2082.             if (HasFlag(zone, ZoneFlags.Eject))
  2083.             {
  2084.                 if (!CanBypass(player, ZoneFlags.Eject) && !IsAdmin(player))
  2085.                 {
  2086.                     EjectPlayer(player, zone);
  2087.                     return;
  2088.                 }
  2089.             }
  2090.  
  2091.             //if (HasFlag(zone, ZoneFlags.KeepVehiclesOut) && player.isMounted && ReverseVelocity(player.GetMountedVehicle()))
  2092.             //{
  2093.             //    SendMessage(player, Message("novehiclesenter", player.UserIDString));
  2094.             //    return;
  2095.             //}
  2096.  
  2097.             if (player.IsSleeping() && !player.IsConnected)
  2098.             {
  2099.                 if (HasFlag(zone, ZoneFlags.KillSleepers))
  2100.                 {
  2101.                     if (!CanBypass(player, ZoneFlags.KillSleepers) && !IsAdmin(player))
  2102.                     {
  2103.                         player.Die();
  2104.                         return;
  2105.                     }
  2106.                 }
  2107.  
  2108.                 if (HasFlag(zone, ZoneFlags.EjectSleepers))
  2109.                 {
  2110.                     if (!CanBypass(player, ZoneFlags.EjectSleepers) && !IsAdmin(player))
  2111.                     {
  2112.                         EjectPlayer(player, zone);
  2113.                         return;
  2114.                     }
  2115.                 }
  2116.             }
  2117.  
  2118.             if (HasFlag(zone, ZoneFlags.Kill))
  2119.             {
  2120.                 if (!CanBypass(player, ZoneFlags.Kill) && !IsAdmin(player))
  2121.                 {
  2122.                     player.Die();
  2123.                     return;
  2124.                 }
  2125.             }
  2126.  
  2127.             if (!zonedPlayers.TryGetValue(player.userID, out EntityZones entityZones))
  2128.                 zonedPlayers[player.userID] = entityZones = new EntityZones();
  2129.  
  2130.             if (!entityZones.EnterZone(zone))
  2131.                 return;
  2132.  
  2133.             if (zone.parent)
  2134.                 entityZones.UpdateFlags();
  2135.             else entityZones.AddFlags(zone.definition.Flags);
  2136.  
  2137.             zone.OnPlayerEnterZone(player);
  2138.  
  2139.             UpdateMetabolismForPlayer(player);
  2140.  
  2141.             if (!string.IsNullOrEmpty(zone.definition.EnterMessage))
  2142.             {
  2143.                 if (PopupNotifications != null && Configuration.Notifications.Popups)
  2144.                     PopupNotifications.Call("CreatePopupNotification", string.Format(zone.definition.EnterMessage, player.displayName), player);
  2145.                 else SendMessage(player, zone.definition.EnterMessage, player.displayName);
  2146.             }
  2147.  
  2148.             Interface.CallHook("OnEnterZone", zone.definition.Id, player);
  2149.         }
  2150.  
  2151.         private void OnPlayerExitZone(BasePlayer player, Zone zone)
  2152.         {
  2153.             if (!player || IsNpc(player))
  2154.                 return;
  2155.  
  2156.             //if (HasFlag(zone, ZoneFlags.KeepVehiclesIn) && player.isMounted && ReverseVelocity(player.GetMountedVehicle()))
  2157.             //{
  2158.             //    SendMessage(player, Message("novehiclesleave", player.UserIDString));
  2159.             //    return;
  2160.             //}
  2161.  
  2162.             if (!zone.CanLeaveZone(player))
  2163.             {
  2164.                 AttractPlayer(player, zone);
  2165.                 return;
  2166.             }
  2167.  
  2168.             if (!zonedPlayers.TryGetValue(player.userID, out EntityZones entityZones))
  2169.                 return;
  2170.  
  2171.             entityZones.LeaveZone(zone);
  2172.  
  2173.             if (entityZones.ShouldRemove())
  2174.                 zonedPlayers.Remove(player.userID);
  2175.             else entityZones.UpdateFlags();
  2176.  
  2177.             zone.OnPlayerExitZone(player);
  2178.  
  2179.             UpdateMetabolismForPlayer(player);
  2180.  
  2181.             if (!string.IsNullOrEmpty(zone.definition.LeaveMessage))
  2182.             {
  2183.                 if (PopupNotifications != null && Configuration.Notifications.Popups)
  2184.                     PopupNotifications.Call("CreatePopupNotification", string.Format(zone.definition.LeaveMessage, player.displayName), player);
  2185.                 else SendMessage(player, zone.definition.LeaveMessage, player.displayName);
  2186.             }
  2187.  
  2188.             Interface.CallHook("OnExitZone", zone.definition.Id, player);
  2189.         }
  2190.  
  2191.         private void UpdateMetabolismForPlayer(BasePlayer player)
  2192.         {
  2193.             if (!player)
  2194.                 return;
  2195.  
  2196.             MetabolismAttribute bleeding = player.metabolism.bleeding;
  2197.             if (HasPlayerFlag(player, ZoneFlags.NoBleed))
  2198.             {
  2199.                 bleeding.value = 0f;
  2200.                 bleeding.max = 0f;
  2201.             }
  2202.             else bleeding.max = 1f;
  2203.  
  2204.             MetabolismAttribute oxygen = player.metabolism.oxygen;
  2205.             if (HasPlayerFlag(player, ZoneFlags.NoDrown))
  2206.             {
  2207.                 oxygen.value = 1f;
  2208.                 oxygen.min = 1f;
  2209.             }
  2210.             else oxygen.min = 0f;
  2211.  
  2212.             MetabolismAttribute poison = player.metabolism.poison;
  2213.             if (HasPlayerFlag(player, ZoneFlags.NoPoison))
  2214.             {
  2215.                 poison.value = 0f;
  2216.                 poison.max = 0f;
  2217.             }
  2218.             else poison.max = 100f;
  2219.  
  2220.             MetabolismAttribute calories = player.metabolism.calories;
  2221.             if (HasPlayerFlag(player, ZoneFlags.NoStarvation))
  2222.             {
  2223.                 calories.value = Mathf.Max(calories.value, 50f);
  2224.                 calories.min = calories.value;
  2225.             }
  2226.             else calories.min = 0f;
  2227.  
  2228.             MetabolismAttribute hydration = player.metabolism.hydration;
  2229.             if (HasPlayerFlag(player, ZoneFlags.NoThirst))
  2230.             {
  2231.                 hydration.value = Mathf.Max(hydration.value, 50f);
  2232.                 hydration.min = hydration.value;
  2233.             }
  2234.             else hydration.min = 0f;
  2235.  
  2236.             MetabolismAttribute radiation_level = player.metabolism.radiation_level;
  2237.             MetabolismAttribute radiation_poison = player.metabolism.radiation_poison;
  2238.             if (HasPlayerFlag(player, ZoneFlags.NoRadiation))
  2239.             {
  2240.                 radiation_level.value = 0f;
  2241.                 radiation_level.max = 0f;
  2242.                 radiation_poison.value = 0f;
  2243.                 radiation_poison.max = 0f;
  2244.             }
  2245.             else
  2246.             {
  2247.                 radiation_level.max = 100f;
  2248.                 radiation_poison.max = 500f;
  2249.             }
  2250.  
  2251.             player.metabolism.SendChanges();
  2252.         }
  2253.  
  2254.         private void OnEntityEnterZone(BaseEntity baseEntity, Zone zone)
  2255.         {
  2256.             if (!baseEntity || !baseEntity.IsValid())
  2257.                 return;
  2258.  
  2259.             if (zone.HasFlag(ZoneFlags.KeepVehiclesOut) && !zone.entities.Contains(baseEntity) && zone.TryReverseVelocity(baseEntity))
  2260.             {
  2261.                 BasePlayer player = (baseEntity as BaseVehicle).GetDriver();
  2262.                 if (player)
  2263.                     SendMessage(player, Message("novehiclesenter", player.UserIDString));
  2264.                 return;
  2265.             }
  2266.  
  2267.             if (!zonedEntities.TryGetValue(baseEntity.net.ID, out EntityZones entityZones))
  2268.                 zonedEntities[baseEntity.net.ID] = entityZones = new EntityZones();
  2269.  
  2270.             if (!entityZones.EnterZone(zone))
  2271.                 return;
  2272.  
  2273.             if (zone.parent)
  2274.                 entityZones.UpdateFlags();
  2275.             else entityZones.AddFlags(zone.definition.Flags);
  2276.  
  2277.             zone.OnEntityEnterZone(baseEntity);
  2278.  
  2279.             Interface.CallHook("OnEntityEnterZone", zone.definition.Id, baseEntity);
  2280.         }
  2281.  
  2282.         private void OnEntityExitZone(BaseEntity baseEntity, Zone zone)
  2283.         {
  2284.             if (!baseEntity || !baseEntity.IsValid())
  2285.                 return;
  2286.  
  2287.             if (zone.HasFlag(ZoneFlags.KeepVehiclesOut) && zone.entities.Contains(baseEntity) && zone.TryReverseVelocity(baseEntity))
  2288.             {
  2289.                 BasePlayer player = (baseEntity as BaseVehicle).GetDriver();
  2290.                 if (player)
  2291.                     SendMessage(player, Message("novehiclesleave", player.UserIDString));
  2292.  
  2293.                 return;
  2294.             }
  2295.  
  2296.             if (!zonedEntities.TryGetValue(baseEntity.net.ID, out EntityZones entityZones))
  2297.                 return;
  2298.  
  2299.             entityZones.LeaveZone(zone);
  2300.  
  2301.             if (entityZones.ShouldRemove())
  2302.                 zonedEntities.Remove(baseEntity.net.ID);
  2303.             else entityZones.UpdateFlags();
  2304.  
  2305.             zone.OnEntityExitZone(baseEntity, !entityZones.HasFlag(ZoneFlags.NoDecay));
  2306.  
  2307.             Interface.CallHook("OnEntityExitZone", zone.definition.Id, baseEntity);
  2308.         }
  2309.         #endregion
  2310.  
  2311.         #region Helpers
  2312.         private bool IsAdmin(BasePlayer player) => player?.net?.connection?.authLevel > 0;
  2313.  
  2314.         private bool IsNpc(BasePlayer player) => player.IsNpc || player is NPCPlayer;
  2315.  
  2316.         private bool HasPermission(BasePlayer player, string perm) => IsAdmin(player) || permission.UserHasPermission(player.UserIDString, perm);
  2317.  
  2318.         private bool HasPermission(ConsoleSystem.Arg arg, string perm)
  2319.         {
  2320.             BasePlayer player = arg.Player();
  2321.             return !player || permission.UserHasPermission(player.UserIDString, perm);
  2322.         }
  2323.  
  2324.         private bool CanBypass(BasePlayer player, int flag) => CanBypass(player.UserIDString, flag);
  2325.  
  2326.         private bool CanBypass(ulong playerId, int flag) => CanBypass(playerId.ToString(), flag);
  2327.  
  2328.         private bool CanBypass(string playerId, int flag)
  2329.         {
  2330.             if (ZoneFlags.IndexToName.TryGetValue(flag, out string flagName))
  2331.                 return permission.UserHasPermission(playerId, PERMISSION_IGNORE_FLAG + flagName);
  2332.  
  2333.             Debug.LogError($"[ZoneManager] CanBypass called with invalid flag : {flag}");
  2334.             return false;
  2335.         }
  2336.  
  2337.         private void SendMessage(BasePlayer player, string message, params object[] args)
  2338.         {
  2339.             if (player)
  2340.             {
  2341.                 if (args.Length > 0)
  2342.                     message = string.Format(message, args);
  2343.                 SendReply(player, $"<color={Configuration.Notifications.Color}>{Configuration.Notifications.Prefix}</color> {message}");
  2344.             }
  2345.             else Puts(message);
  2346.         }
  2347.  
  2348.         private Zone GetZoneByID(string zoneId) => zones.ContainsKey(zoneId) ? zones[zoneId] : null;
  2349.  
  2350.         private void AddToKeepinlist(Zone zone, BasePlayer player)
  2351.         {
  2352.             zone.keepInList.Add(player.userID);
  2353.  
  2354.             if (!zonedPlayers.TryGetValue(player.userID, out EntityZones entityZones) || !entityZones.Zones.Contains(zone))
  2355.                 AttractPlayer(player, zone);
  2356.         }
  2357.  
  2358.         private void RemoveFromKeepinlist(Zone zone, BasePlayer player) => zone.keepInList.Remove(player.userID.Get());
  2359.  
  2360.         private void AddToWhitelist(Zone zone, BasePlayer player)
  2361.         {
  2362.             if (!zone.whitelist.Contains(player.userID.Get()))
  2363.                 zone.whitelist.Add(player.userID);
  2364.         }
  2365.  
  2366.         private void RemoveFromWhitelist(Zone zone, BasePlayer player) => zone.whitelist.Remove(player.userID.Get());
  2367.  
  2368.         private bool HasPlayerFlag(BasePlayer player, int flag)
  2369.         {
  2370.             if (!player)
  2371.                 return false;
  2372.  
  2373.             if (adminBypass.HasFlag(flag) && IsAdmin(player))
  2374.                 return false;
  2375.  
  2376.             if (CanBypass(player, flag))
  2377.                 return false;
  2378.  
  2379.             if (!zonedPlayers.TryGetValue(player.userID, out EntityZones entityZones))
  2380.                 return false;
  2381.  
  2382.             return entityZones.HasFlag(flag);
  2383.         }
  2384.  
  2385.         private bool HasEntityFlag(BaseEntity baseEntity, int flag)
  2386.         {
  2387.             if (!baseEntity.IsValid())
  2388.                 return false;
  2389.  
  2390.             if (!zonedEntities.TryGetValue(baseEntity.net.ID, out EntityZones entityZones))
  2391.                 return false;
  2392.  
  2393.             return entityZones.HasFlag(flag);
  2394.         }
  2395.         #endregion
  2396.  
  2397.         #region API
  2398.  
  2399.         #region Zone Management
  2400.  
  2401.         private void SetZoneStatus(string zoneId, bool active)
  2402.         {
  2403.             Zone zone = GetZoneByID(zoneId);
  2404.             if (zone)
  2405.             {
  2406.                 zone.definition.Enabled = active;
  2407.                 zone.InitializeZone(zone.definition);
  2408.             }
  2409.         }
  2410.  
  2411.         private Vector3 GetZoneLocation(string zoneId) => GetZoneByID(zoneId)?.definition.Location ?? Vector3.zero;
  2412.  
  2413.         private object GetZoneRadius(string zoneID) => GetZoneByID(zoneID)?.definition.Radius;
  2414.  
  2415.         private object GetZoneSize(string zoneID) => GetZoneByID(zoneID)?.definition.Size;
  2416.  
  2417.         private object GetZoneName(string zoneID) => GetZoneByID(zoneID)?.definition.Name;
  2418.  
  2419.         private object CheckZoneID(string zoneID) => GetZoneByID(zoneID)?.definition.Id;
  2420.  
  2421.         private object GetZoneIDs()
  2422.         {
  2423.             string[] array = new string[zones.Count];
  2424.             int i = 0;
  2425.             foreach (string zoneId in zones.Keys)
  2426.             {
  2427.                 array[i] = zoneId;
  2428.                 i++;
  2429.             }
  2430.  
  2431.             return array;
  2432.         }
  2433.  
  2434.         private void GetZoneIDsNoAlloc(List<string> list) => list.AddRange(zones.Keys);
  2435.  
  2436.         private bool IsPositionInZone(string zoneID, Vector3 position)
  2437.         {
  2438.             Zone zone = GetZoneByID(zoneID);
  2439.             if (!zone)
  2440.                 return false;
  2441.  
  2442.             if (zone.definition.Size != Vector3.zero)
  2443.                 return IsInsideBounds(zone, position);
  2444.             return Vector3.Distance(position, zone.transform.position) <= zone.definition.Radius;
  2445.         }
  2446.  
  2447.         private bool IsPositionInAnyZone(Vector3 position)
  2448.         {
  2449.             foreach (KeyValuePair<string, Zone> zone in zones)
  2450.             {
  2451.                 if (zone.Value.definition.Size != Vector3.zero && IsInsideBounds(zone.Value, position))
  2452.                     return true;
  2453.  
  2454.                 if (Vector3.Distance(position, zone.Value.transform.position) <= zone.Value.definition.Radius)
  2455.                     return true;
  2456.             }
  2457.  
  2458.             return false;
  2459.         }
  2460.  
  2461.         private void GetZonesAtPosition(Vector3 position, List<string> results)
  2462.         {
  2463.             foreach (KeyValuePair<string, Zone> zone in zones)
  2464.             {
  2465.                 if (zone.Value.definition.Size != Vector3.zero && IsInsideBounds(zone.Value, position))
  2466.                 {
  2467.                     results.Add(zone.Key);
  2468.                     continue;
  2469.                 }
  2470.  
  2471.                 if (Vector3.Distance(position, zone.Value.transform.position) <= zone.Value.definition.Radius)
  2472.                     results.Add(zone.Key);
  2473.             }
  2474.         }
  2475.  
  2476.         private List<BasePlayer> GetPlayersInZone(string zoneID)
  2477.         {
  2478.             Zone zone = GetZoneByID(zoneID);
  2479.             if (!zone)
  2480.                 return new List<BasePlayer>();
  2481.  
  2482.             return new List<BasePlayer>(zone.players);
  2483.         }
  2484.  
  2485.         private List<BaseEntity> GetEntitiesInZone(string zoneId)
  2486.         {
  2487.             Zone zone = GetZoneByID(zoneId);
  2488.             if (!zone)
  2489.                 return new List<BaseEntity>();
  2490.  
  2491.             return new List<BaseEntity>(zone.entities);
  2492.         }
  2493.  
  2494.         private void GetPlayersInZoneNoAlloc(string zoneID, List<BasePlayer> list)
  2495.         {
  2496.             Zone zone = GetZoneByID(zoneID);
  2497.             if (!zone)
  2498.                 return;
  2499.  
  2500.             list.AddRange(zone.players);
  2501.         }
  2502.  
  2503.         private void GetEntitiesInZoneNoAlloc(string zoneId, List<BaseEntity> list)
  2504.         {
  2505.             Zone zone = GetZoneByID(zoneId);
  2506.             if (!zone)
  2507.                 return;
  2508.  
  2509.             list.AddRange(zone.entities);
  2510.         }
  2511.  
  2512.         private bool isPlayerInZone(string zoneID, BasePlayer player) => IsPlayerInZone(zoneID, player);
  2513.  
  2514.         private bool isEntityInZone(string zoneID, BaseEntity entity) => IsEntityInZone(zoneID, entity);
  2515.  
  2516.         private bool IsPlayerInZone(string zoneID, BasePlayer player)
  2517.         {
  2518.             Zone zone = GetZoneByID(zoneID);
  2519.             if (!zone)
  2520.                 return false;
  2521.  
  2522.             return zone.players.Contains(player);
  2523.         }
  2524.  
  2525.         private bool IsEntityInZone(string zoneID, BaseEntity entity)
  2526.         {
  2527.             Zone zone = GetZoneByID(zoneID);
  2528.             return zone && zone.entities.Contains(entity);
  2529.         }
  2530.  
  2531.         private string[] GetPlayerZoneIDs(BasePlayer player)
  2532.         {
  2533.             if (!zonedPlayers.TryGetValue(player.userID, out EntityZones entityZones))
  2534.                 return Array.Empty<string>();
  2535.  
  2536.             string[] array = new string[entityZones.Zones.Count];
  2537.             for (int i = 0; i < entityZones.Zones.Count; i++)
  2538.                 array[i] = entityZones.Zones[i].definition.Id;
  2539.  
  2540.             return array;
  2541.         }
  2542.  
  2543.         private string[] GetEntityZoneIDs(BaseEntity entity)
  2544.         {
  2545.             if (!zonedEntities.TryGetValue(entity.net.ID, out EntityZones entityZones))
  2546.                 return Array.Empty<string>();
  2547.  
  2548.             string[] array = new string[entityZones.Zones.Count];
  2549.             for (int i = 0; i < entityZones.Zones.Count; i++)
  2550.                 array[i] = entityZones.Zones[i].definition.Id;
  2551.  
  2552.             return array;
  2553.         }
  2554.  
  2555.         private void GetPlayerZoneIDsNoAlloc(BasePlayer player, List<string> list)
  2556.         {
  2557.             if (!zonedPlayers.TryGetValue(player.userID, out EntityZones entityZones))
  2558.                 return;
  2559.  
  2560.             foreach (Zone zone in entityZones.Zones)
  2561.                 list.Add(zone.definition.Id);
  2562.         }
  2563.  
  2564.         private void GetEntityZoneIDsNoAlloc(BaseEntity entity, List<string> list)
  2565.         {
  2566.             if (!zonedEntities.TryGetValue(entity.net.ID, out EntityZones entityZones))
  2567.                 return;
  2568.  
  2569.             foreach (Zone zone in entityZones.Zones)
  2570.                 list.Add(zone.definition.Id);
  2571.         }
  2572.  
  2573.         private bool HasFlag(string zoneId, string flagName)
  2574.         {
  2575.             Zone zone = GetZoneByID(zoneId);
  2576.             if (!zone)
  2577.                 return false;
  2578.  
  2579.             if (!ZoneFlags.NameToIndex.TryGetValue(flagName, out int v))
  2580.             {
  2581.                 Debug.Log($"[ZoneManager] A plugin has call HasFlag with a invalid flag : {flagName}");
  2582.                 return false;
  2583.             }
  2584.  
  2585.             return zone.HasFlag(v);
  2586.         }
  2587.  
  2588.         private bool PositionHasFlag(Vector3 position, string flagName)
  2589.         {
  2590.             if (!ZoneFlags.NameToIndex.TryGetValue(flagName, out int v))
  2591.             {
  2592.                 Debug.Log($"[ZoneManager] A plugin has call PositionHasFlag with a invalid flag : {flagName}");
  2593.                 return false;
  2594.             }
  2595.  
  2596.             foreach (KeyValuePair<string, Zone> zone in zones)
  2597.             {
  2598.                 if (!zone.Value.HasFlag(v))
  2599.                     continue;
  2600.  
  2601.                 if (zone.Value.definition.Size != Vector3.zero && IsInsideBounds(zone.Value, position))
  2602.                     return true;
  2603.  
  2604.                 if (Vector3.Distance(position, zone.Value.transform.position) <= zone.Value.definition.Radius)
  2605.                     return true;
  2606.             }
  2607.  
  2608.             return false;
  2609.         }
  2610.  
  2611.         private void AddFlag(string zoneId, string flagName)
  2612.         {
  2613.             Zone zone = GetZoneByID(zoneId);
  2614.             if (!zone)
  2615.                 return;
  2616.  
  2617.             if (ZoneFlags.NameToIndex.TryGetValue(flagName, out int v))
  2618.                 zone.AddFlag(v);
  2619.             else Debug.Log($"[ZoneManager] A plugin has call AddFlag with a invalid flag : {flagName}");
  2620.         }
  2621.  
  2622.         private void RemoveFlag(string zoneId, string flagName)
  2623.         {
  2624.             Zone zone = GetZoneByID(zoneId);
  2625.             if (!zone)
  2626.                 return;
  2627.  
  2628.             if (ZoneFlags.NameToIndex.TryGetValue(flagName, out int v))
  2629.                 zone.RemoveFlag(v);
  2630.             else Debug.Log($"[ZoneManager] A plugin has call RemoveFlag with a invalid flag : {flagName}");
  2631.         }
  2632.  
  2633.         private bool HasDisabledFlag(string zoneId, string flagName)
  2634.         {
  2635.             Zone zone = GetZoneByID(zoneId);
  2636.             if (!zone)
  2637.                 return false;
  2638.  
  2639.             if (ZoneFlags.NameToIndex.TryGetValue(flagName, out int v))
  2640.                 return zone.HasDisabledFlag(v);
  2641.  
  2642.             Debug.Log($"[ZoneManager] A plugin has call HasDisabledFlag with a invalid flag : {flagName}");
  2643.             return false;
  2644.  
  2645.         }
  2646.  
  2647.         private void AddDisabledFlag(string zoneId, string flagName)
  2648.         {
  2649.             Zone zone = GetZoneByID(zoneId);
  2650.             if (!zone)
  2651.                 return;
  2652.  
  2653.             if (ZoneFlags.NameToIndex.TryGetValue(flagName, out int v))
  2654.                 zone.AddDisabledFlag(v);
  2655.             else Debug.Log($"[ZoneManager] A plugin has call AddDisabledFlag with a invalid flag : {flagName}");
  2656.         }
  2657.  
  2658.         private void RemoveDisabledFlag(string zoneId, string flagName)
  2659.         {
  2660.             Zone zone = GetZoneByID(zoneId);
  2661.             if (!zone)
  2662.                 return;
  2663.  
  2664.             if (ZoneFlags.NameToIndex.TryGetValue(flagName, out int v))
  2665.                 zone.RemoveDisabledFlag(v);
  2666.             else Debug.Log($"[ZoneManager] A plugin has call RemoveDisabledFlag with a invalid flag : {flagName}");
  2667.         }
  2668.  
  2669.         private bool CreateOrUpdateZone(string zoneId, string[] args, Vector3 position = default(Vector3))
  2670.         {
  2671.             bool result = CreateOrUpdateZoneInternal(zoneId, args, position);
  2672.             if (result)
  2673.                 SaveData();
  2674.             return result;
  2675.         }
  2676.  
  2677.         private void CreateOrUpdateZones(List<(string, string[], Vector3)> zones, List<bool> results = null)
  2678.         {
  2679.             bool any = false;
  2680.             foreach ((string zoneId, string[] args, Vector3 position) in zones)
  2681.             {
  2682.                 bool result = CreateOrUpdateZoneInternal(zoneId, args, position);
  2683.                 any |= result;
  2684.                 results?.Add(result);
  2685.             }
  2686.  
  2687.             if (any)
  2688.                 SaveData();
  2689.         }
  2690.  
  2691.         private bool CreateOrUpdateTemporaryZone(Plugin owner, string zoneId, string[] args, Vector3 position = default(Vector3)) =>
  2692.             CreateOrUpdateZoneInternal(zoneId, args, position, owner);
  2693.  
  2694.         private void CreateOrUpdateTemporaryZones(Plugin owner, List<(string, string[], Vector3)> zones, List<bool> results = null)
  2695.         {
  2696.             foreach ((string zoneId, string[] args, Vector3 position) in zones)
  2697.             {
  2698.                 bool result = CreateOrUpdateZoneInternal(zoneId, args, position, owner);
  2699.                 results?.Add(result);
  2700.             }
  2701.         }
  2702.  
  2703.         private bool CreateOrUpdateZoneInternal(string zoneId, string[] args, Vector3 position = default(Vector3), Plugin owner = null)
  2704.         {
  2705.             Zone.Definition definition;
  2706.             bool update = true;
  2707.             if (!zones.TryGetValue(zoneId, out Zone zone))
  2708.             {
  2709.                 zone = new GameObject().AddComponent<Zone>();
  2710.                 definition = new Zone.Definition { Id = zoneId, Radius = 20, Flags = new ZoneFlags() };
  2711.  
  2712.                 zones[zoneId] = zone;
  2713.  
  2714.                 if (owner)
  2715.                 {
  2716.                     definition.WithOwner(owner);
  2717.                     if (!temporaryZones.TryGetValue(owner, out HashSet<string> set))
  2718.                         temporaryZones[owner] = new HashSet<string>();
  2719.  
  2720.                     temporaryZones[owner].Add(zoneId);
  2721.                 }
  2722.  
  2723.                 zone.InitializeZone(definition);
  2724.                 update = false;
  2725.             }
  2726.             else definition = zone.definition;
  2727.  
  2728.             if (definition.Owner && owner != definition.Owner)
  2729.                 return false;
  2730.  
  2731.             UpdateZoneDefinition(zone, args);
  2732.  
  2733.             if (position != default(Vector3))
  2734.                 definition.Location = position;
  2735.  
  2736.             zone.definition = definition;
  2737.             zone.Reset();
  2738.  
  2739.             Interface.CallHook(update ? "OnZoneUpdated" : "OnZoneCreated", zoneId);
  2740.  
  2741.             return true;
  2742.         }
  2743.  
  2744.         private bool EraseZone(string zoneId)
  2745.         {
  2746.             bool result = EraseZoneInternal(zoneId);
  2747.             if (result)
  2748.                 SaveData();
  2749.  
  2750.             return result;
  2751.         }
  2752.  
  2753.         private void EraseZones(List<string> zoneIds, List<bool> results = null)
  2754.         {
  2755.             bool any = false;
  2756.             foreach (string zoneId in zoneIds)
  2757.             {
  2758.                 bool result = EraseZoneInternal(zoneId);
  2759.                 any |= result;
  2760.                 results?.Add(result);
  2761.             }
  2762.  
  2763.             if (any)
  2764.                 SaveData();
  2765.         }
  2766.  
  2767.         private bool EraseTemporaryZone(Plugin owner, string zoneId) => EraseZoneInternal(zoneId, owner);
  2768.  
  2769.         private void EraseTemporaryZones(Plugin owner, List<string> zoneIds, List<bool> results = null)
  2770.         {
  2771.             foreach (string zoneId in zoneIds)
  2772.             {
  2773.                 bool result = EraseZoneInternal(zoneId, owner);
  2774.                 results?.Add(result);
  2775.             }
  2776.         }
  2777.  
  2778.         private bool EraseZoneInternal(string zoneId, Plugin owner = null)
  2779.         {
  2780.             if (!zones.TryGetValue(zoneId, out Zone zone))
  2781.                 return false;
  2782.  
  2783.             Plugin zoneOwner = zone.definition.Owner;
  2784.  
  2785.             // Only compare zone owner if the owner param is provided so users can remove temporary zones without
  2786.             // needing to unload the plugin that created them
  2787.             if (owner && zoneOwner && owner != zoneOwner)
  2788.                 return false;
  2789.  
  2790.             zones.Remove(zoneId);
  2791.  
  2792.             if (zoneOwner && temporaryZones.TryGetValue(zoneOwner, out HashSet<string> set))
  2793.                 set.Remove(zoneId);
  2794.  
  2795.             UnityEngine.Object.DestroyImmediate(zone.gameObject);
  2796.             Interface.CallHook("OnZoneErased", zoneId);
  2797.             return true;
  2798.         }
  2799.  
  2800.         private List<string> ZoneFieldListRaw()
  2801.         {
  2802.             List<string> list = new List<string> { "name", "ID", "radius", "rotation", "size", "Location", "enter_message", "leave_message", "radiation", "comfort", "temperature" };
  2803.             list.AddRange(ZoneFlags.NameToIndex.Keys);
  2804.             return list;
  2805.         }
  2806.  
  2807.         private Dictionary<string, string> ZoneFieldList(string zoneId)
  2808.         {
  2809.             Zone zone = GetZoneByID(zoneId);
  2810.             if (!zone)
  2811.                 return null;
  2812.  
  2813.             Dictionary<string, string> fields = new Dictionary<string, string>
  2814.             {
  2815.                 { "name", zone.definition.Name },
  2816.                 { "ID", zone.definition.Id },
  2817.                 { "comfort", zone.definition.Comfort.ToString() },
  2818.                 { "temperature", zone.definition.Temperature.ToString() },
  2819.                 { "radiation", zone.definition.Radiation.ToString() },
  2820.                 { "safezone", zone.definition.SafeZone.ToString() },
  2821.                 { "radius", zone.definition.Radius.ToString() },
  2822.                 { "rotation", zone.definition.Rotation.ToString() },
  2823.                 { "size", zone.definition.Size.ToString() },
  2824.                 { "Location", zone.definition.Location.ToString() },
  2825.                 { "enter_message", zone.definition.EnterMessage },
  2826.                 { "leave_message", zone.definition.LeaveMessage },
  2827.                 { "permission", zone.definition.Permission },
  2828.                 { "ejectspawns", zone.definition.EjectSpawns }
  2829.             };
  2830.  
  2831.             foreach (KeyValuePair<string, int> kvp in ZoneFlags.NameToIndex)
  2832.                 fields[kvp.Key] = zone.HasFlag(kvp.Value).ToString();
  2833.  
  2834.             return fields;
  2835.         }
  2836.         #endregion
  2837.  
  2838.         #region Entity Management
  2839.         private bool AddPlayerToZoneKeepinlist(string zoneId, BasePlayer player)
  2840.         {
  2841.             Zone zone = GetZoneByID(zoneId);
  2842.             if (!zone)
  2843.                 return false;
  2844.  
  2845.             AddToKeepinlist(zone, player);
  2846.             return true;
  2847.         }
  2848.  
  2849.         private bool RemovePlayerFromZoneKeepinlist(string zoneId, BasePlayer player)
  2850.         {
  2851.             Zone zone = GetZoneByID(zoneId);
  2852.             if (!zone)
  2853.                 return false;
  2854.  
  2855.             RemoveFromKeepinlist(zone, player);
  2856.             return true;
  2857.         }
  2858.  
  2859.         private bool AddPlayerToZoneWhitelist(string zoneId, BasePlayer player)
  2860.         {
  2861.             Zone zone = GetZoneByID(zoneId);
  2862.             if (!zone)
  2863.                 return false;
  2864.  
  2865.             AddToWhitelist(zone, player);
  2866.             return true;
  2867.         }
  2868.  
  2869.         private bool RemovePlayerFromZoneWhitelist(string zoneId, BasePlayer player)
  2870.         {
  2871.             Zone zone = GetZoneByID(zoneId);
  2872.             if (!zone)
  2873.                 return false;
  2874.  
  2875.             RemoveFromWhitelist(zone, player);
  2876.             return true;
  2877.         }
  2878.  
  2879.         private bool EntityHasFlag(BaseEntity baseEntity, string flagName)
  2880.         {
  2881.             if (!baseEntity.IsValid())
  2882.                 return false;
  2883.  
  2884.             if (ZoneFlags.NameToIndex.TryGetValue(flagName, out int v))
  2885.                 return HasEntityFlag(baseEntity, v);
  2886.  
  2887.             Debug.LogError($"[ZoneManager] A plugin has called EntityHasFlag with a invalid flag : {flagName}");
  2888.             return false;
  2889.  
  2890.         }
  2891.  
  2892.         private bool PlayerHasFlag(BasePlayer player, string flagName)
  2893.         {
  2894.             if (!player)
  2895.                 return false;
  2896.  
  2897.             if (ZoneFlags.NameToIndex.TryGetValue(flagName, out int v))
  2898.                 return HasPlayerFlag(player, v);
  2899.  
  2900.             Debug.LogError($"[ZoneManager] A plugin has called EntityHasFlag with a invalid flag : {flagName}");
  2901.             return false;
  2902.  
  2903.         }
  2904.         #endregion
  2905.  
  2906.         #region Plugin Integration
  2907.         private object CanRedeemKit(BasePlayer player) => HasPlayerFlag(player, ZoneFlags.NoKits) ? "You may not redeem a kit inside this area" : null;
  2908.  
  2909.         private object CanTeleport(BasePlayer player) => HasPlayerFlag(player, ZoneFlags.NoTp) ? "You may not teleport in this area" : null;
  2910.  
  2911.         private object canRemove(BasePlayer player) => CanRemove(player);
  2912.  
  2913.         private object CanRemove(BasePlayer player) => HasPlayerFlag(player, ZoneFlags.NoRemove) ? "You may not use the remover tool in this area" : null;
  2914.  
  2915.         private bool CanChat(BasePlayer player) => HasPlayerFlag(player, ZoneFlags.NoChat) ? false : true;
  2916.  
  2917.         private object CanTrade(BasePlayer player) => HasPlayerFlag(player, ZoneFlags.NoTrade) ? "You may not trade in this area" : null;
  2918.  
  2919.         private object canShop(BasePlayer player) => CanShop(player);
  2920.  
  2921.         private object CanShop(BasePlayer player) => HasPlayerFlag(player, ZoneFlags.NoShop) ? "You may not use the store in this area" : null;
  2922.         #endregion
  2923.  
  2924.         #endregion
  2925.  
  2926.         #region Flags
  2927.         public class ZoneFlags
  2928.         {
  2929.             public static readonly int AutoLights;
  2930.             public static readonly int AlwaysLights;
  2931.  
  2932.             public static readonly int NoKits;
  2933.             public static readonly int NoTrade;
  2934.             public static readonly int NoShop;
  2935.             public static readonly int NoTp;
  2936.             public static readonly int NoRemove;
  2937.  
  2938.             public static readonly int NoPoison;
  2939.             public static readonly int NoStarvation;
  2940.             public static readonly int NoThirst;
  2941.             public static readonly int NoRadiation;
  2942.             public static readonly int NoDrown;
  2943.             public static readonly int NoBleed;
  2944.             public static readonly int NoWounded;
  2945.  
  2946.             public static readonly int Eject;
  2947.             public static readonly int EjectSleepers;
  2948.  
  2949.             public static readonly int PvpGod;
  2950.             public static readonly int PveGod;
  2951.             public static readonly int SleepGod;
  2952.             public static readonly int NoPve;
  2953.             public static readonly int NoFallDamage;
  2954.  
  2955.             public static readonly int UnDestr;
  2956.             public static readonly int NoBuildingDamage;
  2957.             public static readonly int NoDecay;
  2958.  
  2959.             public static readonly int NoBuild;
  2960.             public static readonly int NoStability;
  2961.             public static readonly int NoUpgrade;
  2962.  
  2963.             public static readonly int NoSprays;
  2964.             public static readonly int NoDeploy;
  2965.  
  2966.             public static readonly int PoweredSwitches;
  2967.  
  2968.             public static readonly int LootSelf;
  2969.             public static readonly int NoPlayerLoot;
  2970.             public static readonly int NoNPCLoot;
  2971.             public static readonly int NoBoxLoot;
  2972.             public static readonly int NoPickup;
  2973.             public static readonly int NoCollect;
  2974.             public static readonly int NoDrop;
  2975.             public static readonly int NoLootSpawns;
  2976.             public static readonly int NoEntityPickup;
  2977.             public static readonly int NoGather;
  2978.  
  2979.             public static readonly int NoHeliTargeting;
  2980.             public static readonly int NoTurretTargeting;
  2981.             public static readonly int NoAPCTargeting;
  2982.             public static readonly int NoNPCTargeting;
  2983.             public static readonly int NpcFreeze;
  2984.             public static readonly int NoNPCSpawns;
  2985.  
  2986.             public static readonly int KeepVehiclesIn;
  2987.             public static readonly int KeepVehiclesOut;
  2988.             public static readonly int NoVehicleMounting;
  2989.             public static readonly int NoVehicleDismounting;
  2990.  
  2991.             public static readonly int NoChat;
  2992.             public static readonly int NoVoice;
  2993.  
  2994.             public static readonly int NoCorpse;
  2995.             public static readonly int NoSuicide;
  2996.             public static readonly int KillSleepers;
  2997.  
  2998.             public static readonly int Kill;
  2999.             public static readonly int InfiniteTrapAmmo;
  3000.  
  3001.             public static readonly int NoCup;
  3002.             public static readonly int NoSignUpdates;
  3003.             public static readonly int NoOvenToggle;
  3004.             public static readonly int NoVending;
  3005.             public static readonly int NoStash;
  3006.             public static readonly int NoCraft;
  3007.             public static readonly int NoDoorAccess;
  3008.  
  3009.             public static readonly int Custom1;
  3010.             public static readonly int Custom2;
  3011.             public static readonly int Custom3;
  3012.             public static readonly int Custom4;
  3013.             public static readonly int Custom5;
  3014.  
  3015.             public static readonly Hash<string, int> NameToIndex;
  3016.             public static readonly Hash<int, string> IndexToName;
  3017.  
  3018.             private static readonly int Count;
  3019.  
  3020.             private readonly BitArray _bitArray;
  3021.  
  3022.             static ZoneFlags()
  3023.             {
  3024.                 NameToIndex = new Hash<string, int>(StringComparer.OrdinalIgnoreCase);
  3025.                 IndexToName = new Hash<int, string>();
  3026.  
  3027.                 FieldInfo[] fields = typeof(ZoneFlags).GetFields(BindingFlags.Public | BindingFlags.Static);
  3028.  
  3029.                 int index = 0;
  3030.                 for (int i = 0; i < fields.Length; i++)
  3031.                 {
  3032.                     FieldInfo fieldInfo = fields[i];
  3033.  
  3034.                     if (fieldInfo.FieldType != typeof(int))
  3035.                         continue;
  3036.  
  3037.                     fieldInfo.SetValue(null, index);
  3038.  
  3039.                     NameToIndex[fieldInfo.Name] = index;
  3040.                     IndexToName[index] = fieldInfo.Name;
  3041.  
  3042.                     index++;
  3043.                 }
  3044.  
  3045.                 Count = index;// NameToIndex.Values.Max() + 1;
  3046.             }
  3047.  
  3048.             public static bool Find(string flagName, out int index)
  3049.             {
  3050.                 foreach (KeyValuePair<string, int> kvp in NameToIndex)
  3051.                 {
  3052.                     if (!flagName.Equals(kvp.Key, StringComparison.OrdinalIgnoreCase))
  3053.                         continue;
  3054.  
  3055.                     index = kvp.Value;
  3056.                     return true;
  3057.                 }
  3058.  
  3059.                 index = 0;
  3060.                 return false;
  3061.             }
  3062.  
  3063.             public ZoneFlags()
  3064.             {
  3065.                 _bitArray = new BitArray(Count, false);
  3066.             }
  3067.  
  3068.             public bool this[int key]
  3069.             {
  3070.                 get => _bitArray[key];
  3071.                 set => _bitArray[key] = value;
  3072.             }
  3073.  
  3074.             public bool HasFlag(int flag) => this[flag];
  3075.  
  3076.             public void AddFlag(int flag) => this[flag] = true;
  3077.  
  3078.             public void RemoveFlag(int flag) => this[flag] = false;
  3079.  
  3080.             public void SetFlags(params int[] array)
  3081.             {
  3082.                 for (int i = 0; i < _bitArray.Length; i++)
  3083.                 {
  3084.                     this[i] = array.Contains(i);
  3085.                 }
  3086.             }
  3087.  
  3088.             public void Clear() => _bitArray.SetAll(false);
  3089.  
  3090.             public bool HasFlag(string flagName)
  3091.             {
  3092.                 if (!NameToIndex.TryGetValue(flagName, out int v))
  3093.                 {
  3094.                     Debug.LogError($"[ZoneManager] ZoneFlags.HasFlag used with invalid flag string : {flagName}");
  3095.                     return false;
  3096.                 }
  3097.                 return this[v];
  3098.             }
  3099.  
  3100.             public void AddFlag(string flagName)
  3101.             {
  3102.                 if (!NameToIndex.TryGetValue(flagName, out int v))
  3103.                     Debug.LogError($"[ZoneManager] ZoneFlags.AddFlag used with invalid flag string : {flagName}");
  3104.                 else this[v] = true;
  3105.             }
  3106.  
  3107.             public void RemoveFlag(string flagName)
  3108.             {
  3109.                 if (!NameToIndex.TryGetValue(flagName, out int v))
  3110.                     Debug.LogError($"[ZoneManager] ZoneFlags.RemoveFlag used with invalid flag string : {flagName}");
  3111.                 else this[v] = false;
  3112.             }
  3113.  
  3114.             public void SetFlags(params string[] array)
  3115.             {
  3116.                 List<int> list = Pool.Get<List<int>>();
  3117.                 for (int i = 0; i < array.Length; i++)
  3118.                 {
  3119.                     if (NameToIndex.TryGetValue(array[i], out int v))
  3120.                         Debug.Log($"[ZoneManager] ZoneFlags.SetFlags used with invalid flag string : {array[i]}");
  3121.                     else list.Add(v);
  3122.                 }
  3123.  
  3124.                 for (int i = 0; i < _bitArray.Length; i++)
  3125.                 {
  3126.                     this[i] = list.Contains(i);
  3127.                 }
  3128.                 Pool.FreeUnmanaged(ref list);
  3129.             }
  3130.  
  3131.             public void AddFlags(ZoneFlags zoneFlags)
  3132.             {
  3133.                 for (int i = 0; i < zoneFlags._bitArray.Length; i++)
  3134.                 {
  3135.                     if (zoneFlags._bitArray[i])
  3136.                         this[i] = true;
  3137.                 }
  3138.             }
  3139.  
  3140.             public void RemoveFlags(ZoneFlags zoneFlags)
  3141.             {
  3142.                 for (int i = 0; i < zoneFlags._bitArray.Length; i++)
  3143.                 {
  3144.                     if (zoneFlags._bitArray[i])
  3145.                         this[i] = false;
  3146.                 }
  3147.             }
  3148.  
  3149.             public bool CompareTo(ZoneFlags zoneFlags)
  3150.             {
  3151.                 for (int i = 0; i < _bitArray.Length; i++)
  3152.                 {
  3153.                     if (this[i] != zoneFlags[i])
  3154.                         return false;
  3155.                 }
  3156.  
  3157.                 return true;
  3158.             }
  3159.  
  3160.             public override string ToString()
  3161.             {
  3162.                 sb.Clear();
  3163.  
  3164.                 foreach (KeyValuePair<string, int> flag in NameToIndex)
  3165.                 {
  3166.                     if (HasFlag(flag.Value))
  3167.                     {
  3168.                         sb.Append(sb.Length == 0 ? flag.Key : ", " + flag.Key);
  3169.                     }
  3170.                 }
  3171.  
  3172.                 return sb.ToString();
  3173.             }
  3174.         }
  3175.  
  3176.         private void AddFlag(Zone zone, int flag)
  3177.         {
  3178.             zone.definition.Flags.AddFlag(flag);
  3179.  
  3180.             if (NeedsUpdateSubscriptions())
  3181.                 UpdateHookSubscriptions();
  3182.  
  3183.             zone.Reset();
  3184.         }
  3185.  
  3186.         private void RemoveFlag(Zone zone, int flag)
  3187.         {
  3188.             zone.definition.Flags.RemoveFlag(flag);
  3189.  
  3190.             if (NeedsUpdateSubscriptions())
  3191.             {
  3192.                 UnsubscribeAll();
  3193.                 UpdateHookSubscriptions();
  3194.             }
  3195.  
  3196.             zone.Reset();
  3197.         }
  3198.  
  3199.         private bool HasFlag(Zone zone, int flag) => zone.definition.Flags.HasFlag(flag) && ! zone.disabledFlags.HasFlag(flag);
  3200.  
  3201.         private void UpdateZoneEntityFlags(Zone zone)
  3202.         {
  3203.             foreach (EntityZones entityZones in zone.entityZones.Values)
  3204.                 entityZones?.UpdateFlags();
  3205.  
  3206.             /*for (int i = 0; i < zonedPlayers.Count; i++)
  3207.             {
  3208.                 EntityZones entityZones = zonedPlayers.ElementAt(i).Value;
  3209.  
  3210.                 if (entityZones.Zones.Contains(zone))
  3211.                 {
  3212.                     entityZones.UpdateFlags();
  3213.                 }
  3214.             }
  3215.  
  3216.             for (int i = 0; i < zonedEntities.Count; i++)
  3217.             {
  3218.                 EntityZones entityZones = zonedEntities.ElementAt(i).Value;
  3219.  
  3220.                 if (entityZones.Zones.Contains(zone))
  3221.                 {
  3222.                     entityZones.UpdateFlags();
  3223.                 }
  3224.             }*/
  3225.         }
  3226.         #endregion
  3227.  
  3228.         #region Hook Subscriptions
  3229.         private bool HasGlobalFlag(int flag) => globalFlags.HasFlag(flag);
  3230.  
  3231.         private void UpdateGlobalFlags()
  3232.         {
  3233.             globalFlags.Clear();
  3234.  
  3235.             foreach (Zone zone in zones.Values)
  3236.             {
  3237.                 if (!zone)
  3238.                     continue;
  3239.  
  3240.                 globalFlags.AddFlags(zone.definition.Flags);
  3241.             }
  3242.         }
  3243.  
  3244.         private bool NeedsUpdateSubscriptions()
  3245.         {
  3246.             tempFlags.Clear();
  3247.  
  3248.             foreach (Zone zone in zones.Values)
  3249.             {
  3250.                 if (!zone)
  3251.                     continue;
  3252.  
  3253.                 tempFlags.AddFlags(zone.definition.Flags);
  3254.             }
  3255.  
  3256.             bool isMatch = tempFlags.CompareTo(globalFlags);
  3257.  
  3258.             return !isMatch;
  3259.         }
  3260.  
  3261.         private void UpdateHookSubscriptions()
  3262.         {
  3263.             UpdateGlobalFlags();
  3264.  
  3265.             if (HasGlobalFlag(ZoneFlags.NoBuild) || HasGlobalFlag(ZoneFlags.NoCup) || HasGlobalFlag(ZoneFlags.NoDeploy))
  3266.                 Subscribe(nameof(OnEntityBuilt));
  3267.  
  3268.             if (HasGlobalFlag(ZoneFlags.NoUpgrade))
  3269.                 Subscribe(nameof(OnStructureUpgrade));
  3270.  
  3271.             if (HasGlobalFlag(ZoneFlags.NoDeploy))
  3272.                 Subscribe(nameof(OnItemDeployed));
  3273.  
  3274.             if (HasGlobalFlag(ZoneFlags.InfiniteTrapAmmo) || HasGlobalFlag(ZoneFlags.AlwaysLights) || HasGlobalFlag(ZoneFlags.AutoLights))
  3275.                 Subscribe(nameof(OnItemUse));
  3276.  
  3277.             if (HasGlobalFlag(ZoneFlags.NoChat))
  3278.                 Subscribe(nameof(OnPlayerChat));
  3279.  
  3280.             if (HasGlobalFlag(ZoneFlags.NoSuicide))
  3281.                 Subscribe(nameof(OnServerCommand));
  3282.  
  3283.             if (HasGlobalFlag(ZoneFlags.KillSleepers) || HasGlobalFlag(ZoneFlags.EjectSleepers))
  3284.                 Subscribe(nameof(OnPlayerDisconnected));
  3285.  
  3286.             if (HasGlobalFlag(ZoneFlags.NoFallDamage) || HasGlobalFlag(ZoneFlags.SleepGod) || HasGlobalFlag(ZoneFlags.PvpGod) || HasGlobalFlag(ZoneFlags.PveGod) || HasGlobalFlag(ZoneFlags.NoPve) || HasGlobalFlag(ZoneFlags.UnDestr) || HasGlobalFlag(ZoneFlags.NoBuildingDamage))
  3287.                 Subscribe(nameof(OnEntityTakeDamage));
  3288.  
  3289.             if (HasGlobalFlag(ZoneFlags.NoWounded))
  3290.                 Subscribe(nameof(CanBeWounded));
  3291.  
  3292.             if (HasGlobalFlag(ZoneFlags.NoSignUpdates))
  3293.                 Subscribe(nameof(CanUpdateSign));
  3294.  
  3295.             if (HasGlobalFlag(ZoneFlags.NoOvenToggle))
  3296.                 Subscribe(nameof(OnOvenToggle));
  3297.  
  3298.             if (HasGlobalFlag(ZoneFlags.NoVending))
  3299.                 Subscribe(nameof(CanUseVending));
  3300.  
  3301.             if (HasGlobalFlag(ZoneFlags.NoStash))
  3302.                 Subscribe(nameof(CanHideStash));
  3303.  
  3304.             if (HasGlobalFlag(ZoneFlags.NoCraft))
  3305.                 Subscribe(nameof(CanCraft));
  3306.  
  3307.             if (HasGlobalFlag(ZoneFlags.NoDoorAccess))
  3308.                 Subscribe(nameof(OnDoorOpened));
  3309.  
  3310.             if (HasGlobalFlag(ZoneFlags.NoVoice))
  3311.                 Subscribe(nameof(OnPlayerVoice));
  3312.  
  3313.             if (HasGlobalFlag(ZoneFlags.NoPlayerLoot))
  3314.             {
  3315.                 Subscribe(nameof(CanLootPlayer));
  3316.                 Subscribe(nameof(OnLootPlayer));
  3317.             }
  3318.  
  3319.             if (HasGlobalFlag(ZoneFlags.NoVehicleMounting))
  3320.                 Subscribe(nameof(CanMountEntity));
  3321.  
  3322.             if (HasGlobalFlag(ZoneFlags.NoVehicleDismounting))
  3323.                 Subscribe(nameof(CanDismountEntity));
  3324.  
  3325.             if (HasGlobalFlag(ZoneFlags.LootSelf) || HasGlobalFlag(ZoneFlags.NoPlayerLoot))
  3326.                 Subscribe(nameof(OnLootEntity));
  3327.  
  3328.             if (HasGlobalFlag(ZoneFlags.LootSelf) || HasGlobalFlag(ZoneFlags.NoPlayerLoot) || HasGlobalFlag(ZoneFlags.NoBoxLoot) || HasGlobalFlag(ZoneFlags.NoGather))
  3329.                 Subscribe(nameof(CanLootEntity));
  3330.  
  3331.             if (HasGlobalFlag(ZoneFlags.NoEntityPickup))
  3332.             {
  3333.                 Subscribe(nameof(CanPickupEntity));
  3334.                 Subscribe(nameof(CanPickupLock));
  3335.                 Subscribe(nameof(OnItemPickup));
  3336.             }
  3337.  
  3338.             if (HasGlobalFlag(ZoneFlags.NoGather))
  3339.             {
  3340.                 Subscribe(nameof(OnCollectiblePickup));
  3341.                 Subscribe(nameof(OnGrowableGather));
  3342.                 Subscribe(nameof(OnDispenserGather));
  3343.             }
  3344.  
  3345.             if (HasGlobalFlag(ZoneFlags.NoTurretTargeting))
  3346.                 Subscribe(nameof(OnTurretTarget));
  3347.  
  3348.             if (HasGlobalFlag(ZoneFlags.NoAPCTargeting))
  3349.                 Subscribe(nameof(CanBradleyApcTarget));
  3350.  
  3351.             if (HasGlobalFlag(ZoneFlags.NoHeliTargeting))
  3352.             {
  3353.                 Subscribe(nameof(CanHelicopterTarget));
  3354.                 Subscribe(nameof(CanHelicopterStrafeTarget));
  3355.                 Subscribe(nameof(OnHelicopterTarget));
  3356.             }
  3357.  
  3358.             if (HasGlobalFlag(ZoneFlags.NoNPCTargeting))
  3359.                 Subscribe(nameof(OnNpcTarget));
  3360.         }
  3361.  
  3362.         private void UnsubscribeAll()
  3363.         {
  3364.             Unsubscribe(nameof(OnEntityBuilt));
  3365.             Unsubscribe(nameof(OnStructureUpgrade));
  3366.             Unsubscribe(nameof(OnItemDeployed));
  3367.             Unsubscribe(nameof(OnItemUse));
  3368.             Unsubscribe(nameof(OnPlayerChat));
  3369.             Unsubscribe(nameof(OnServerCommand));
  3370.             Unsubscribe(nameof(OnPlayerDisconnected));
  3371.             Unsubscribe(nameof(OnEntityTakeDamage));
  3372.             Unsubscribe(nameof(CanBeWounded));
  3373.             Unsubscribe(nameof(CanUpdateSign));
  3374.             Unsubscribe(nameof(OnOvenToggle));
  3375.             Unsubscribe(nameof(CanUseVending));
  3376.             Unsubscribe(nameof(CanHideStash));
  3377.             Unsubscribe(nameof(CanCraft));
  3378.             Unsubscribe(nameof(OnDoorOpened));
  3379.             Unsubscribe(nameof(CanLootPlayer));
  3380.             Unsubscribe(nameof(OnLootPlayer));
  3381.             Unsubscribe(nameof(CanLootEntity));
  3382.             Unsubscribe(nameof(CanLootEntity));
  3383.             Unsubscribe(nameof(CanPickupEntity));
  3384.             Unsubscribe(nameof(CanPickupLock));
  3385.             Unsubscribe(nameof(OnItemPickup));
  3386.             Unsubscribe(nameof(CanLootEntity));
  3387.             Unsubscribe(nameof(OnCollectiblePickup));
  3388.             Unsubscribe(nameof(OnGrowableGather));
  3389.             Unsubscribe(nameof(OnDispenserGather));
  3390.             Unsubscribe(nameof(OnTurretTarget));
  3391.             Unsubscribe(nameof(CanBradleyApcTarget));
  3392.             Unsubscribe(nameof(CanHelicopterTarget));
  3393.             Unsubscribe(nameof(CanHelicopterStrafeTarget));
  3394.             Unsubscribe(nameof(OnHelicopterTarget));
  3395.             Unsubscribe(nameof(OnNpcTarget));
  3396.             Unsubscribe(nameof(OnPlayerVoice));
  3397.             Unsubscribe(nameof(CanMountEntity));
  3398.             Unsubscribe(nameof(CanDismountEntity));
  3399.         }
  3400.         #endregion
  3401.  
  3402.         #region Zone Creation
  3403.         private void UpdateZoneDefinition(Zone zone, string[] args, BasePlayer player = null)
  3404.         {
  3405.             for (int i = 0; i < args.Length; i += 2)
  3406.             {
  3407.                 string value;
  3408.                 switch (args[i].ToLower())
  3409.                 {
  3410.                     case "name":
  3411.                         value = zone.definition.Name = args[i + 1];
  3412.                         break;
  3413.  
  3414.                     case "id":
  3415.                         string newId = args[i + 1];
  3416.                         if (newId != zone.definition.Id && !zones.ContainsKey(newId))
  3417.                         {
  3418.                             string oldId = zone.definition.Id;
  3419.                             zones.Remove(oldId);
  3420.                             zone.definition.Id = newId;
  3421.                             zones.Add(newId, zone);
  3422.  
  3423.                             if (zone.definition.Owner && temporaryZones.TryGetValue(zone.definition.Owner, out HashSet<string> set))
  3424.                             {
  3425.                                 set.Remove(oldId);
  3426.                                 set.Add(newId);
  3427.                             }
  3428.                         }
  3429.                         value = newId;
  3430.                         break;
  3431.  
  3432.                     case "comfort":
  3433.                         if (float.TryParse(args[i + 1], out float comfort))
  3434.                             zone.definition.Comfort = comfort;
  3435.  
  3436.                         value = zone.definition.Comfort.ToString(CultureInfo.InvariantCulture);
  3437.                         break;
  3438.  
  3439.                     case "temperature":
  3440.                         if (float.TryParse(args[i + 1], out float temperature))
  3441.                             zone.definition.Temperature = temperature;
  3442.  
  3443.                         value = zone.definition.Temperature.ToString(CultureInfo.InvariantCulture);
  3444.                         break;
  3445.  
  3446.                     case "radiation":
  3447.                         if (float.TryParse(args[i + 1], out float radiation))
  3448.                             zone.definition.Radiation = radiation;
  3449.  
  3450.                         value = zone.definition.Radiation.ToString(CultureInfo.InvariantCulture);
  3451.                         break;
  3452.  
  3453.                     case "safezone":
  3454.                         if (bool.TryParse(args[i + 1], out bool safeZone))
  3455.                             zone.definition.SafeZone = safeZone;
  3456.  
  3457.                         value = zone.definition.SafeZone.ToString(CultureInfo.InvariantCulture);
  3458.                         break;
  3459.  
  3460.                     case "radius":
  3461.                         if (float.TryParse(args[i + 1], out float radius))
  3462.                         {
  3463.                             zone.definition.Radius = radius;
  3464.                             zone.definition.Size = Vector3.zero;
  3465.                         }
  3466.                         value = zone.definition.Radius.ToString(CultureInfo.InvariantCulture);
  3467.                         break;
  3468.  
  3469.                     case "rotation":
  3470.                         if (float.TryParse(args[i + 1], out float rotation))
  3471.                             zone.definition.Rotation = Quaternion.AngleAxis(rotation, Vector3.up).eulerAngles;
  3472.                         else if (player)
  3473.                             zone.definition.Rotation = new Vector3(0, player.GetNetworkRotation().eulerAngles.y, 0);
  3474.  
  3475.                         value = zone.definition.Rotation.ToString();
  3476.                         break;
  3477.  
  3478.                     case "location":
  3479.                         if (player && args[i + 1].Equals("here", StringComparison.OrdinalIgnoreCase))
  3480.                             zone.definition.Location = player.transform.position;
  3481.                         else
  3482.                         {
  3483.                             string[] location = args[i + 1].Trim().Split(' ');
  3484.                             if (location.Length == 3 && float.TryParse(location[0], out float lX) && float.TryParse(location[1], out float lY) && float.TryParse(location[2], out float lZ))
  3485.                                 zone.definition.Location = new Vector3(lX, lY, lZ);
  3486.                             else if (player)
  3487.                             {
  3488.                                 SendMessage(player, "Invalid location format. Correct syntax is \"/zone location \"x y z\"\" - or - \"/zone location here\"");
  3489.                                 continue;
  3490.                             }
  3491.                         }
  3492.  
  3493.                         value = zone.definition.Location.ToString();
  3494.                         break;
  3495.  
  3496.                     case "size":
  3497.                         string[] size = args[i + 1].Trim().Split(' ');
  3498.                         if (size.Length == 3 && float.TryParse(size[0], out float sX) && float.TryParse(size[1], out float sY) && float.TryParse(size[2], out float sZ))
  3499.                             zone.definition.Size = new Vector3(sX, sY, sZ);
  3500.                         else if (player)
  3501.                         {
  3502.                             SendMessage(player, "Invalid size format, Correct syntax is \"/zone size \"x y z\"\"");
  3503.                             continue;
  3504.                         }
  3505.  
  3506.                         value = zone.definition.Size.ToString();
  3507.                         break;
  3508.  
  3509.                     case "enter_message":
  3510.                         value = zone.definition.EnterMessage = args[i + 1];
  3511.                         break;
  3512.  
  3513.                     case "leave_message":
  3514.                         value = zone.definition.LeaveMessage = args[i + 1];
  3515.                         break;
  3516.  
  3517.                     case "parentid":
  3518.                         value = args[i + 1];
  3519.                         if (zones.TryGetValue((string)value, out Zone parent))
  3520.                         {
  3521.                             zone.definition.ParentID = (string)value;
  3522.                             zone.parent = parent;
  3523.  
  3524.                             UpdateZoneEntityFlags(zone);
  3525.                         }
  3526.                         else if (player)
  3527.                         {
  3528.                             SendMessage(player, $"Unable to find zone with ID {value}");
  3529.                             continue;
  3530.                         }
  3531.                         break;
  3532.  
  3533.                     case "permission":
  3534.                         string permission = args[i + 1];
  3535.  
  3536.                         if (!permission.StartsWith("zonemanager."))
  3537.                             permission = $"zonemanager.{permission}";
  3538.  
  3539.                         value = zone.definition.Permission = permission;
  3540.                         break;
  3541.  
  3542.                     case "ejectspawns":
  3543.                         value = zone.definition.EjectSpawns = args[i + 1];
  3544.                         break;
  3545.  
  3546.                     case "enabled":
  3547.                     case "enable":
  3548.                         if (bool.TryParse(args[i + 1], out bool enabled))
  3549.                             zone.definition.Enabled = enabled;
  3550.  
  3551.                         value = enabled.ToString();
  3552.                         break;
  3553.  
  3554.                     default:
  3555.                         if (!bool.TryParse(args[i + 1], out bool active))
  3556.                             active = false;
  3557.  
  3558.                         value = active.ToString();
  3559.  
  3560.                         if (ZoneFlags.Find(args[i], out int v))
  3561.                         {
  3562.                             if (active)
  3563.                                 zone.AddFlag(v);
  3564.                             else zone.RemoveFlag(v);
  3565.                         }
  3566.                         else if (player)
  3567.                             SendMessage(player, $"Invalid zone flag: {args[i]}");
  3568.                         break;
  3569.                 }
  3570.                 if (player)
  3571.                     SendMessage(player, $"{args[i]} set to {value}");
  3572.             }
  3573.         }
  3574.         #endregion
  3575.  
  3576.         #region Commands
  3577.  
  3578.         private StringBuilder _sb = new StringBuilder();
  3579.  
  3580.         [ChatCommand("zone_add")]
  3581.         private void cmdChatZoneAdd(BasePlayer player, string command, string[] args)
  3582.         {
  3583.             if (!HasPermission(player, PERMISSION_ZONE))
  3584.             {
  3585.                 SendMessage(player, "You don't have access to this command");
  3586.                 return;
  3587.             }
  3588.  
  3589.             string zoneId = GetRandomZoneID();
  3590.  
  3591.             if (!CreateOrUpdateZone(zoneId, Array.Empty<string>(), player.transform.position))
  3592.             {
  3593.                 SendMessage(player, $"Unable to create zone with ID {zoneId}");
  3594.                 return;
  3595.             }
  3596.  
  3597.             lastPlayerZone[player.userID] = zoneId;
  3598.  
  3599.             ShowZone(player, zoneId);
  3600.  
  3601.             SendMessage(player, "You have successfully created a new zone with ID : {0}!\nYou can edit it using the /zone_edit command", zoneId);
  3602.         }
  3603.  
  3604.         private string GetRandomZoneID()
  3605.         {
  3606.             string zoneId = UnityEngine.Random.Range(1, 99999999).ToString();
  3607.             if (zones.ContainsKey(zoneId))
  3608.                 return GetRandomZoneID();
  3609.  
  3610.             return zoneId;
  3611.         }
  3612.  
  3613.         [ChatCommand("zone_wipe")]
  3614.         private void cmdChatZoneReset(BasePlayer player, string command, string[] args)
  3615.         {
  3616.             if (!HasPermission(player, PERMISSION_ZONE))
  3617.             {
  3618.                 SendMessage(player, "You don't have access to this command");
  3619.                 return;
  3620.             }
  3621.  
  3622.             List<string> zoneIds = Pool.Get<List<string>>();
  3623.             foreach (KeyValuePair<string, Zone> kvp in zones)
  3624.             {
  3625.                 if (kvp.Value.definition.IsTemporary)
  3626.                     continue;
  3627.  
  3628.                 zoneIds.Add(kvp.Key);
  3629.             }
  3630.  
  3631.             EraseZones(zoneIds);
  3632.  
  3633.             Pool.FreeUnmanaged(ref zoneIds);
  3634.  
  3635.             updateBehaviour.Reset();
  3636.  
  3637.             SaveData();
  3638.  
  3639.             SendMessage(player, "Wiped zone data");
  3640.         }
  3641.  
  3642.         [ChatCommand("zone_remove")]
  3643.         private void cmdChatZoneRemove(BasePlayer player, string command, string[] args)
  3644.         {
  3645.             if (!HasPermission(player, PERMISSION_ZONE))
  3646.             {
  3647.                 SendMessage(player, "You don't have access to this command");
  3648.                 return;
  3649.             }
  3650.  
  3651.             if (args.Length == 0)
  3652.             {
  3653.                 SendMessage(player, "Invalid syntax! /zone_remove <zone ID>");
  3654.                 return;
  3655.             }
  3656.  
  3657.             bool result = EraseZone(args[0]);
  3658.             if (!result)
  3659.             {
  3660.                 SendMessage(player, "A zone with the specified ID does not exist");
  3661.                 return;
  3662.             }
  3663.  
  3664.             SendMessage(player, "Successfully removed zone : {0}", args[0]);
  3665.         }
  3666.  
  3667.         [ChatCommand("zone_stats")]
  3668.         private void cmdChatZoneStats(BasePlayer player, string command, string[] args)
  3669.         {
  3670.             if (!HasPermission(player, PERMISSION_ZONE))
  3671.             {
  3672.                 SendMessage(player, "You don't have access to this command");
  3673.                 return;
  3674.             }
  3675.  
  3676.             SendMessage(player, "Zones : {0}", zones.Count);
  3677.             SendMessage(player, "Players in Zones: {0}", zonedPlayers.Count);
  3678.             SendMessage(player, "Entities in Zones: {0}", zonedEntities.Count);
  3679.         }
  3680.  
  3681.         [ChatCommand("zone_edit")]
  3682.         private void cmdChatZoneEdit(BasePlayer player, string command, string[] args)
  3683.         {
  3684.             if (!HasPermission(player, PERMISSION_ZONE))
  3685.             {
  3686.                 SendMessage(player, "You don't have access to this command");
  3687.                 return;
  3688.             }
  3689.  
  3690.             string zoneId;
  3691.             if (args.Length == 0)
  3692.             {
  3693.                 if (!zonedPlayers.TryGetValue(player.userID, out EntityZones entityZones) || entityZones.Count != 1)
  3694.                 {
  3695.                     SendMessage(player, "You must enter a zone ID. /zone_edit <zone ID>");
  3696.                     return;
  3697.                 }
  3698.                 zoneId = entityZones.Zones[0].definition.Id;
  3699.             }
  3700.             else zoneId = args[0];
  3701.  
  3702.             if (!zones.ContainsKey(zoneId))
  3703.             {
  3704.                 SendMessage(player, "The specified zone does not exist");
  3705.                 return;
  3706.             }
  3707.  
  3708.             lastPlayerZone[player.userID] = zoneId;
  3709.  
  3710.             SendMessage(player, "You are now editing the zone with ID : {0}", zoneId);
  3711.             ShowZone(player, zoneId);
  3712.         }
  3713.  
  3714.         [ChatCommand("zone_list")]
  3715.         private void cmdChatZoneList(BasePlayer player, string command, string[] args)
  3716.         {
  3717.             if (!HasPermission(player, PERMISSION_ZONE))
  3718.             {
  3719.                 SendMessage(player, "You don't have access to this command");
  3720.                 return;
  3721.             }
  3722.  
  3723.             if (zones.Count == 0)
  3724.             {
  3725.                 SendMessage(player, "No zone have been created");
  3726.                 return;
  3727.             }
  3728.  
  3729.             _sb.Clear();
  3730.  
  3731.             void AppendLine(StringBuilder sb, Zone zone)
  3732.             {
  3733.                 sb.Append($"<color={Configuration.Notifications.Color}>ID:</color> {zone.definition.Id} ");
  3734.  
  3735.                 if (!string.IsNullOrEmpty(zone.definition.Name))
  3736.                     sb.Append($" (<color={Configuration.Notifications.Color}>{zone.definition.Name}</color>) ");
  3737.  
  3738.                 sb.Append($" - {zone.definition.Location} ");
  3739.  
  3740.                 sb.Append("\n");
  3741.             }
  3742.  
  3743.             _sb.AppendLine($"<size=13><color={Configuration.Notifications.Color}>Zones:</color>");
  3744.  
  3745.             foreach (Zone zone in zones.Values)
  3746.             {
  3747.                 if (zone.definition.IsTemporary)
  3748.                     continue;
  3749.  
  3750.                 AppendLine(_sb, zone);
  3751.             }
  3752.  
  3753.             foreach (KeyValuePair<Plugin, HashSet<string>> kvp in temporaryZones)
  3754.             {
  3755.                 _sb.AppendLine($"<color={Configuration.Notifications.Color}>{kvp.Key.Name} Temporary Zones:</color>");
  3756.                 foreach (string zoneId in kvp.Value)
  3757.                 {
  3758.                     if (zones.TryGetValue(zoneId, out Zone zone))
  3759.                         AppendLine(_sb, zone);
  3760.                 }
  3761.             }
  3762.  
  3763.             player.ChatMessage(_sb.ToString());
  3764.             //SendMessage(player, $"ID: {zone.Key} - {zone.Value.definition.Name} - {zone.Value.definition.Location}");
  3765.         }
  3766.  
  3767.         [ChatCommand("zone")]
  3768.         private void cmdChatZone(BasePlayer player, string command, string[] args)
  3769.         {
  3770.             if (!HasPermission(player, PERMISSION_ZONE))
  3771.             {
  3772.                 SendMessage(player, "You don't have access to this command");
  3773.                 return;
  3774.             }
  3775.  
  3776.             if (!lastPlayerZone.TryGetValue(player.userID, out string zoneId))
  3777.             {
  3778.                 SendMessage(player, "You must start editing a zone first. /zone_edit <zone ID>");
  3779.                 return;
  3780.             }
  3781.  
  3782.             if (!zones.TryGetValue(zoneId, out Zone zone))
  3783.             {
  3784.                 SendMessage(player, "Unable to find a zone with ID : {0}", zoneId);
  3785.                 return;
  3786.             }
  3787.  
  3788.             if (args.Length == 0)
  3789.             {
  3790.                 player.ChatMessage("/zone <option> <value>");
  3791.  
  3792.                 void AppendLine(StringBuilder sb, string field, object value)
  3793.                 {
  3794.                     sb.AppendLine($"<color={Configuration.Notifications.Color}>{field}:</color> {value}");
  3795.                 }
  3796.  
  3797.                 _sb.Clear();
  3798.                 AppendLine(_sb, "<size=13>ID", zone.definition.Id);
  3799.                 AppendLine(_sb, "Name", zone.definition.Name);
  3800.                 AppendLine(_sb, "Enabled", zone.definition.Enabled);
  3801.  
  3802.                 if (!string.IsNullOrEmpty(zone.definition.ParentID))
  3803.                     AppendLine(_sb, "Parent Zone ID", zone.definition.ParentID);
  3804.  
  3805.                 if (!string.IsNullOrEmpty(zone.definition.Permission))
  3806.                     AppendLine(_sb, "Permission", zone.definition.Permission);
  3807.  
  3808.                 AppendLine(_sb, "Location", zone.definition.Location);
  3809.                 if (zone.definition.Size == Vector3.zero)
  3810.                     AppendLine(_sb, "Radius", zone.definition.Radius);
  3811.                 else
  3812.                 {
  3813.                     AppendLine(_sb, "Size", zone.definition.Size);
  3814.                     AppendLine(_sb, "Rotation", zone.definition.Rotation);
  3815.                 }
  3816.  
  3817.                 if (!string.IsNullOrEmpty(zone.definition.EjectSpawns))
  3818.                     AppendLine(_sb, "Eject Spawns", zone.definition.EjectSpawns);
  3819.  
  3820.                 AppendLine(_sb, "Comfort", zone.definition.Comfort);
  3821.                 AppendLine(_sb, "Temperature", zone.definition.Temperature);
  3822.                 AppendLine(_sb, "Radiation", zone.definition.Radiation);
  3823.                 AppendLine(_sb, "Safe Zone", zone.definition.SafeZone);
  3824.  
  3825.                 AppendLine(_sb, "Enter Message", zone.definition.EnterMessage);
  3826.                 AppendLine(_sb, "Leave Message", zone.definition.LeaveMessage);
  3827.  
  3828.                 AppendLine(_sb, "Flags", zone.definition.Flags);
  3829.  
  3830.                 player.ChatMessage(_sb.ToString());
  3831.  
  3832.                 ShowZone(player, zoneId);
  3833.                 return;
  3834.             }
  3835.  
  3836.             if (args[0].ToLower() == "flags")
  3837.             {
  3838.                 OpenFlagEditor(player, zoneId);
  3839.                 return;
  3840.             }
  3841.  
  3842.             if (args.Length % 2 != 0)
  3843.             {
  3844.                 SendMessage(player, "Value missing. You must follow a option with a value");
  3845.                 return;
  3846.             }
  3847.             UpdateZoneDefinition(zone, args, player);
  3848.  
  3849.             zone.Reset();
  3850.  
  3851.             Interface.CallHook("OnZoneUpdated", zoneId);
  3852.  
  3853.             if (!zone.definition.IsTemporary)
  3854.                 SaveData();
  3855.  
  3856.             ShowZone(player, zoneId);
  3857.         }
  3858.  
  3859.         [ChatCommand("zone_flags")]
  3860.         private void cmdChatZoneFlags(BasePlayer player, string command, string[] args)
  3861.         {
  3862.             if (!HasPermission(player, PERMISSION_ZONE))
  3863.             {
  3864.                 SendMessage(player, "You don't have access to this command");
  3865.                 return;
  3866.             }
  3867.  
  3868.             if (!lastPlayerZone.TryGetValue(player.userID, out string zoneId))
  3869.             {
  3870.                 SendMessage(player, "You must start editing a zone first. /zone_edit <zone ID>");
  3871.                 return;
  3872.             }
  3873.  
  3874.             OpenFlagEditor(player, zoneId);
  3875.         }
  3876.  
  3877.         [ChatCommand("zone_player")]
  3878.         private void cmdChatZonePlayer(BasePlayer player, string command, string[] args)
  3879.         {
  3880.             if (!HasPermission(player, PERMISSION_ZONE))
  3881.             {
  3882.                 SendMessage(player, "You don't have access to this command");
  3883.                 return;
  3884.             }
  3885.  
  3886.             BasePlayer targetPlayer = player;
  3887.             if (args is { Length: > 0 })
  3888.             {
  3889.                 targetPlayer = BasePlayer.Find(args[0]);
  3890.                 if (!targetPlayer)
  3891.                 {
  3892.                     SendMessage(player, "Unable to find a player with the specified information");
  3893.                     return;
  3894.                 }
  3895.             }
  3896.  
  3897.             if (!zonedPlayers.TryGetValue(targetPlayer.userID, out EntityZones entityZones))
  3898.             {
  3899.                 SendReply(player, "The specified player is not in any zone");
  3900.                 return;
  3901.             }
  3902.  
  3903.             SendMessage(player, $"--- {targetPlayer.displayName} ---");
  3904.             SendMessage(player, $"Has Flags: {entityZones.Flags.ToString()}");
  3905.             SendMessage(player, "Is in zones:");
  3906.  
  3907.             foreach (Zone zone in entityZones.Zones)
  3908.                 SendMessage(player, $"{zone.definition.Id}: {zone.definition.Name} - {zone.definition.Location}");
  3909.         }
  3910.  
  3911.         private RaycastHit[] m_RaycastBuffer = new RaycastHit[64];
  3912.  
  3913.         [ChatCommand("zone_entity")]
  3914.         private void cmdChatZoneEntity(BasePlayer player, string command, string[] args)
  3915.         {
  3916.             if (!HasPermission(player, PERMISSION_ZONE))
  3917.             {
  3918.                 SendMessage(player, "You don't have access to this command");
  3919.                 return;
  3920.             }
  3921.  
  3922.             BaseEntity baseEntity = null;
  3923.             int count = Physics.RaycastNonAlloc(player.eyes.HeadRay(), m_RaycastBuffer, 3f);
  3924.  
  3925.             for (int i = 0; i < count; i++)
  3926.             {
  3927.                 RaycastHit raycastHit = m_RaycastBuffer[i];
  3928.                 BaseEntity entity = raycastHit.GetEntity();
  3929.                 if (entity && !entity.IsDestroyed)
  3930.                 {
  3931.                     baseEntity = entity;
  3932.                     break;
  3933.                 }
  3934.             }
  3935.  
  3936.             if (!baseEntity)
  3937.             {
  3938.                 SendMessage(player, "No entity found");
  3939.                 return;
  3940.             }
  3941.  
  3942.             if (!zonedEntities.TryGetValue(baseEntity.net.ID, out EntityZones entityZones))
  3943.             {
  3944.                 SendReply(player, "The specified entity is not in any zone");
  3945.                 return;
  3946.             }
  3947.  
  3948.             SendMessage(player, $"--- {baseEntity.ShortPrefabName} ({baseEntity.net.ID}) ---");
  3949.             SendMessage(player, $"Has Flags: {entityZones.Flags.ToString()}");
  3950.             SendMessage(player, "Is in zones:");
  3951.  
  3952.             foreach (Zone zone in entityZones.Zones)
  3953.                 SendMessage(player, $"{zone.definition.Id}: {zone.definition.Name} - {zone.definition.Location}");
  3954.         }
  3955.  
  3956.         [ConsoleCommand("zone")]
  3957.         private void ccmdZone(ConsoleSystem.Arg arg)
  3958.         {
  3959.             if (!HasPermission(arg, PERMISSION_ZONE))
  3960.             {
  3961.                 SendReply(arg, "You don't have access to this command");
  3962.                 return;
  3963.             }
  3964.  
  3965.             string zoneId = arg.GetString(0);
  3966.             if (!arg.HasArgs(3) || !zones.TryGetValue(zoneId, out Zone zone))
  3967.             {
  3968.                 SendReply(arg, "Zone ID not found or too few arguments supplied: zone <zoneid> <arg> <value>");
  3969.                 return;
  3970.             }
  3971.  
  3972.             string[] args = new string[arg.Args.Length - 1];
  3973.             Array.Copy(arg.Args, 1, args, 0, args.Length);
  3974.  
  3975.             UpdateZoneDefinition(zone, args, arg.Player());
  3976.             zone.Reset();
  3977.  
  3978.             Interface.CallHook("OnZoneUpdated", zoneId);
  3979.             if (!zone.definition.IsTemporary)
  3980.                 SaveData();
  3981.         }
  3982.  
  3983.         [ConsoleCommand("zone_list")]
  3984.         private void ccmdChatZoneList(ConsoleSystem.Arg arg)
  3985.         {
  3986.             if (!HasPermission(arg, PERMISSION_ZONE))
  3987.             {
  3988.                 SendReply(arg, "You don't have access to this command");
  3989.                 return;
  3990.             }
  3991.  
  3992.             SendReply(arg, "--- Zone list ---");
  3993.             if (zones.Count == 0)
  3994.             {
  3995.                 SendReply(arg, "None...");
  3996.                 return;
  3997.             }
  3998.  
  3999.             foreach (KeyValuePair<string, Zone> zone in zones)
  4000.                 SendReply(arg, $"ID: {zone.Key} - {zone.Value.definition.Name} - {zone.Value.definition.Location}");
  4001.         }
  4002.         #endregion
  4003.  
  4004.         #region UI
  4005.         const string ZMUI = "zmui.editor";
  4006.         #region Helper
  4007.         public static class UI
  4008.         {
  4009.             public static CuiElementContainer Container(string panel, string color, string min, string max, bool useCursor = false, string parent = "Overlay")
  4010.             {
  4011.                 CuiElementContainer container = new CuiElementContainer()
  4012.                 {
  4013.                     {
  4014.                         new CuiPanel
  4015.                         {
  4016.                             Image = {Color = color},
  4017.                             RectTransform = {AnchorMin = min, AnchorMax = max},
  4018.                             CursorEnabled = useCursor
  4019.                         },
  4020.                         new CuiElement().Parent = parent,
  4021.                         panel
  4022.                     }
  4023.                 };
  4024.                 return container;
  4025.             }
  4026.             public static void Panel(ref CuiElementContainer container, string panel, string color, string min, string max, bool cursor = false)
  4027.             {
  4028.                 container.Add(new CuiPanel
  4029.                 {
  4030.                     Image = { Color = color },
  4031.                     RectTransform = { AnchorMin = min, AnchorMax = max },
  4032.                     CursorEnabled = cursor
  4033.                 },
  4034.                 panel);
  4035.             }
  4036.             public static void Label(ref CuiElementContainer container, string panel, string text, int size, string min, string max, TextAnchor align = TextAnchor.MiddleCenter)
  4037.             {
  4038.                 container.Add(new CuiLabel
  4039.                 {
  4040.                     Text = { FontSize = size, Align = align, Text = text },
  4041.                     RectTransform = { AnchorMin = min, AnchorMax = max }
  4042.                 },
  4043.                 panel);
  4044.  
  4045.             }
  4046.             public static void Button(ref CuiElementContainer container, string panel, string color, string text, int size, string min, string max, string command, TextAnchor align = TextAnchor.MiddleCenter)
  4047.             {
  4048.                 container.Add(new CuiButton
  4049.                 {
  4050.                     Button = { Color = color, Command = command, FadeIn = 0f },
  4051.                     RectTransform = { AnchorMin = min, AnchorMax = max },
  4052.                     Text = { Text = text, FontSize = size, Align = align }
  4053.                 },
  4054.                 panel);
  4055.             }
  4056.             public static string Color(string hexColor, float alpha)
  4057.             {
  4058.                 if (hexColor.StartsWith("#"))
  4059.                     hexColor = hexColor.Substring(1);
  4060.                 int red = int.Parse(hexColor.Substring(0, 2), NumberStyles.AllowHexSpecifier);
  4061.                 int green = int.Parse(hexColor.Substring(2, 2), NumberStyles.AllowHexSpecifier);
  4062.                 int blue = int.Parse(hexColor.Substring(4, 2), NumberStyles.AllowHexSpecifier);
  4063.                 return $"{(double)red / 255} {(double)green / 255} {(double)blue / 255} {alpha}";
  4064.             }
  4065.         }
  4066.         #endregion
  4067.  
  4068.         #region Creation
  4069.         private const string COLOR1 = "0.168 0.168 0.168 0.9";
  4070.         private const string COLOR2 = "0.847 0.333 0.256 1";
  4071.         private const string COLOR3 = "0.447 0.898 0.447 1";
  4072.  
  4073.         private SortedDictionary<string, int> orderedZoneFlags;
  4074.  
  4075.         private void OpenFlagEditor(BasePlayer player, string zoneId)
  4076.         {
  4077.             Zone zone = GetZoneByID(zoneId);
  4078.             if (!zone)
  4079.             {
  4080.                 SendReply(player, $"Error getting zone object with ID: {zoneId}");
  4081.                 CuiHelper.DestroyUi(player, ZMUI);
  4082.             }
  4083.  
  4084.             orderedZoneFlags ??= new SortedDictionary<string, int>(ZoneFlags.NameToIndex);
  4085.  
  4086.             CuiElementContainer container = UI.Container(ZMUI, COLOR1, "0 0", "1 1", true);
  4087.             UI.Label(ref container, ZMUI, $"Zone Flag Editor", 18, "0 0.92", "1 1");
  4088.             UI.Label(ref container, ZMUI, $"Zone ID: {zoneId}\nName: {zone.definition.Name}\n{(zone.definition.Size != Vector3.zero ? $"Box Size: {zone.definition.Size}\nRotation: {zone.definition.Rotation}" : $"Radius: {zone.definition.Radius}\nSafe Zone: {zone.definition.SafeZone}")}", 13, "0.05 0.8", "1 0.92", TextAnchor.UpperLeft);
  4089.             UI.Label(ref container, ZMUI, $"Comfort: {zone.definition.Comfort}\nRadiation: {zone.definition.Radiation}\nTemperature: {zone.definition.Temperature}\nZone Enabled: {zone.definition.Enabled}", 13, "0.25 0.8", "1 0.92", TextAnchor.UpperLeft);
  4090.             UI.Label(ref container, ZMUI, $"Permission: {zone.definition.Permission}\nEject Spawnfile: {zone.definition.EjectSpawns}\nEnter Message: {zone.definition.EnterMessage}\nExit Message: {zone.definition.LeaveMessage}", 13, "0.5 0.8", "1 0.92", TextAnchor.UpperLeft);
  4091.             UI.Button(ref container, ZMUI, COLOR2, "Exit", 12, "0.95 0.96", "0.99 0.99", $"zmui.editflag {zoneId} exit");
  4092.  
  4093.             int count = 0;
  4094.  
  4095.             foreach(KeyValuePair<string, int> kvp in orderedZoneFlags)
  4096.             {
  4097.                 bool value = zone.definition.Flags.HasFlag(kvp.Value);
  4098.  
  4099.                 Vector4 position = GetButtonPosition(count);
  4100.  
  4101.                 UI.Label(ref container, ZMUI, kvp.Key, 12, $"{position[0]} {position[1]}", $"{position[0] + ((position[2] - position[0]) / 2)} {position[3]}");
  4102.                 UI.Button(ref container, ZMUI, value ? COLOR3 : COLOR2, value ? "Enabled" : "Disabled", 12, $"{position[0] + ((position[2] - position[0]) / 2)} {position[1]}", $"{position[2]} {position[3]}", $"zmui.editflag {zoneId} {kvp.Value} {!value}");
  4103.  
  4104.                 count++;
  4105.             }
  4106.  
  4107.             CuiHelper.DestroyUi(player, ZMUI);
  4108.             CuiHelper.AddUi(player, container);
  4109.         }
  4110.  
  4111.         private Vector4 GetButtonPosition(int i)
  4112.         {
  4113.             int column = i == 0 ? 0 : ColumnNumber(4, i);
  4114.             int row = i - (column * 4);
  4115.  
  4116.             float offsetX = 0.04f + ((0.01f + 0.21f) * row);
  4117.             float offsetY = (0.76f - (column * 0.04f));
  4118.  
  4119.             return new Vector4(offsetX, offsetY, offsetX + 0.21f, offsetY + 0.03f);
  4120.         }
  4121.  
  4122.         private int ColumnNumber(int max, int count) => Mathf.FloorToInt(count / max);
  4123.         #endregion
  4124.  
  4125.         #region Commands
  4126.         [ConsoleCommand("zmui.editflag")]
  4127.         private void ccmdEditFlag(ConsoleSystem.Arg arg)
  4128.         {
  4129.             if (arg.Connection == null)
  4130.                 return;
  4131.  
  4132.             BasePlayer player = arg.Connection.player as BasePlayer;
  4133.             if (!player)
  4134.                 return;
  4135.  
  4136.             string zoneId = arg.GetString(0);
  4137.             Zone zone = GetZoneByID(zoneId);
  4138.             if (!zone)
  4139.             {
  4140.                 SendReply(player, $"Error getting zone object with ID: {zoneId}");
  4141.                 CuiHelper.DestroyUi(player, ZMUI);
  4142.             }
  4143.  
  4144.             if (arg.GetString(1) == "exit")
  4145.             {
  4146.                 CuiHelper.DestroyUi(player, ZMUI);
  4147.  
  4148.                 Interface.CallHook("OnZoneUpdated", zoneId);
  4149.  
  4150.                 if (!zone.definition.IsTemporary)
  4151.                     SaveData();
  4152.  
  4153.                 NextTick(() =>
  4154.                 {
  4155.                     if (NeedsUpdateSubscriptions())
  4156.                     {
  4157.                         UnsubscribeAll();
  4158.                         UpdateHookSubscriptions();
  4159.                     }
  4160.  
  4161.                     UpdateZoneEntityFlags(zone);
  4162.                 });
  4163.             }
  4164.             else
  4165.             {
  4166.                 if (arg.GetBool(2))
  4167.                     zone.AddFlag(arg.GetInt(1));
  4168.                 else zone.RemoveFlag(arg.GetInt(1));
  4169.  
  4170.                 OpenFlagEditor(player, zoneId);
  4171.             }
  4172.         }
  4173.         #endregion
  4174.         #endregion
  4175.  
  4176.         #region Config
  4177.         private static ConfigData Configuration;
  4178.  
  4179.         private class ConfigData
  4180.         {
  4181.             [JsonProperty(PropertyName = "Autolight Options")]
  4182.             public AutoLightOptions AutoLights { get; set; }
  4183.  
  4184.             [JsonProperty(PropertyName = "Notification Options")]
  4185.             public NotificationOptions Notifications { get; set; }
  4186.  
  4187.             [JsonProperty(PropertyName = "NPC players can deal player damage in zones with PvpGod flag")]
  4188.             public bool NPCHurtPvpGod { get; set; }
  4189.  
  4190.             [JsonProperty(PropertyName = "Allow decay damage in zones with Undestr flag")]
  4191.             public bool DecayDamageUndestr { get; set; }
  4192.  
  4193.             public class AutoLightOptions
  4194.             {
  4195.                 [JsonProperty(PropertyName = "Time to turn lights on")]
  4196.                 public float OnTime { get; set; }
  4197.  
  4198.                 [JsonProperty(PropertyName = "Time to turn lights off")]
  4199.                 public float OffTime { get; set; }
  4200.  
  4201.                 [JsonProperty(PropertyName = "Lights require fuel to activate automatically")]
  4202.                 public bool RequiresFuel { get; set; }
  4203.             }
  4204.  
  4205.             public class NotificationOptions
  4206.             {
  4207.                 [JsonProperty(PropertyName = "Display notifications via PopupNotifications")]
  4208.                 public bool Popups { get; set; }
  4209.  
  4210.                 [JsonProperty(PropertyName = "Chat prefix")]
  4211.                 public string Prefix { get; set; }
  4212.  
  4213.                 [JsonProperty(PropertyName = "Chat color (hex)")]
  4214.                 public string Color { get; set; }
  4215.             }
  4216.  
  4217.             public Oxide.Core.VersionNumber Version { get; set; }
  4218.         }
  4219.  
  4220.         protected override void LoadConfig()
  4221.         {
  4222.             base.LoadConfig();
  4223.             Configuration = Config.ReadObject<ConfigData>();
  4224.  
  4225.             if (Configuration.Version < Version)
  4226.                 UpdateConfigValues();
  4227.  
  4228.             Config.WriteObject(Configuration, true);
  4229.         }
  4230.  
  4231.         protected override void LoadDefaultConfig() => Configuration = GetBaseConfig();
  4232.  
  4233.         private ConfigData GetBaseConfig()
  4234.         {
  4235.             return new ConfigData
  4236.             {
  4237.                 AutoLights = new ConfigData.AutoLightOptions
  4238.                 {
  4239.                     OnTime = 18f,
  4240.                     OffTime = 6f,
  4241.                     RequiresFuel = true
  4242.                 },
  4243.                 Notifications = new ConfigData.NotificationOptions
  4244.                 {
  4245.                     Color = "#d85540",
  4246.                     Popups = false,
  4247.                     Prefix = "[Zone Manager] :"
  4248.                 },
  4249.                 NPCHurtPvpGod = false,
  4250.                 DecayDamageUndestr = false,
  4251.                 Version = Version
  4252.             };
  4253.         }
  4254.  
  4255.         protected override void SaveConfig() => Config.WriteObject(Configuration, true);
  4256.  
  4257.         private void UpdateConfigValues()
  4258.         {
  4259.             PrintWarning("Config update detected! Updating config values...");
  4260.  
  4261.             ConfigData baseConfig = GetBaseConfig();
  4262.  
  4263.             if (Configuration.Version < new VersionNumber(3, 0, 0))
  4264.                 Configuration = baseConfig;
  4265.  
  4266.             Configuration.Version = Version;
  4267.             PrintWarning("Config update completed!");
  4268.         }
  4269.         #endregion
  4270.  
  4271.         #region Data Management
  4272.         private void SaveData()
  4273.         {
  4274.             storedData.definitions.Clear();
  4275.  
  4276.             foreach (KeyValuePair<string, Zone> zone in zones)
  4277.             {
  4278.                 if (zone.Value.definition.IsTemporary)
  4279.                     continue;
  4280.  
  4281.                 storedData.definitions.Add(zone.Value.definition);
  4282.             }
  4283.  
  4284.             data.WriteObject(storedData);
  4285.         }
  4286.  
  4287.         private void LoadData()
  4288.         {
  4289.             data = Interface.Oxide.DataFileSystem.GetFile("ZoneManager/zone_data");
  4290.             data.Settings.Converters = new JsonConverter[] { new StringEnumConverter(), new Vector3Converter(), new ZoneFlagsConverter() };
  4291.  
  4292.             storedData = data.ReadObject<StoredData>() ?? new StoredData();
  4293.         }
  4294.  
  4295.         private class StoredData
  4296.         {
  4297.             public HashSet<Zone.Definition> definitions = new HashSet<Zone.Definition>();
  4298.         }
  4299.  
  4300.         public class EntityZones
  4301.         {
  4302.             public ZoneFlags Flags { get; private set; }
  4303.  
  4304.             public List<Zone> Zones { get; private set; }
  4305.  
  4306.  
  4307.             public EntityZones()
  4308.             {
  4309.                 Zones = new List<Zone>();
  4310.                 Flags = new ZoneFlags();
  4311.             }
  4312.  
  4313.             public void AddFlags(ZoneFlags zoneFlags) => Flags.AddFlags(zoneFlags);
  4314.  
  4315.             public void RemoveFlags(ZoneFlags zoneFlags) => Flags.RemoveFlags(zoneFlags);
  4316.  
  4317.             public bool HasFlag(int flag) => Flags.HasFlag(flag);
  4318.  
  4319.             public void UpdateFlags()
  4320.             {
  4321.                 Flags.Clear();
  4322.  
  4323.                 foreach (Zone zone in Zones)
  4324.                 {
  4325.                     if (!zone)
  4326.                         continue;
  4327.  
  4328.                     tempFlags.Clear();
  4329.  
  4330.                     tempFlags.AddFlags(zone.definition.Flags);
  4331.                     tempFlags.RemoveFlags(zone.disabledFlags);
  4332.  
  4333.                     AddFlags(tempFlags);
  4334.                 }
  4335.  
  4336.                 foreach (Zone zone in Zones)
  4337.                 {
  4338.                     if (!zone)
  4339.                         continue;
  4340.  
  4341.                     if (zone.parent && Zones.Contains(zone.parent))
  4342.                         RemoveFlags(zone.parent.definition.Flags);
  4343.                 }
  4344.             }
  4345.  
  4346.             public bool EnterZone(Zone zone)
  4347.             {
  4348.                 if (Zones.Contains(zone))
  4349.                     return false;
  4350.  
  4351.                 Zones.Add(zone);
  4352.                 return true;
  4353.             }
  4354.  
  4355.             public bool LeaveZone(Zone zone) => Zones.Remove(zone);
  4356.  
  4357.             public bool IsInZone(Zone zone) => Zones.Contains(zone);
  4358.  
  4359.             public bool IsInZone(string zoneId)
  4360.             {
  4361.                 foreach (Zone zone in Zones)
  4362.                 {
  4363.                     if (zone && zone.definition.Id == zoneId)
  4364.                         return true;
  4365.                 }
  4366.  
  4367.                 return false;
  4368.             }
  4369.  
  4370.             public bool ShouldRemove() => Count == 0;
  4371.  
  4372.             public int Count => Zones.Count;
  4373.         }
  4374.  
  4375.         private class Vector3Converter : JsonConverter
  4376.         {
  4377.             public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
  4378.             {
  4379.                 Vector3 vector = (Vector3)value;
  4380.                 writer.WriteValue($"{vector.x} {vector.y} {vector.z}");
  4381.             }
  4382.  
  4383.             public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  4384.             {
  4385.                 if (reader.TokenType == JsonToken.String)
  4386.                 {
  4387.                     string[] values = reader.Value.ToString().Trim().Split(' ');
  4388.                     return new Vector3(Convert.ToSingle(values[0]), Convert.ToSingle(values[1]), Convert.ToSingle(values[2]));
  4389.                 }
  4390.                 JObject o = JObject.Load(reader);
  4391.                 return new Vector3(Convert.ToSingle(o["x"]), Convert.ToSingle(o["y"]), Convert.ToSingle(o["z"]));
  4392.             }
  4393.  
  4394.             public override bool CanConvert(Type objectType)
  4395.             {
  4396.                 return objectType == typeof(Vector3);
  4397.             }
  4398.         }
  4399.  
  4400.         private class ZoneFlagsConverter : JsonConverter
  4401.         {
  4402.             private const string SEPERATOR = ", ";
  4403.  
  4404.             public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
  4405.             {
  4406.                 ZoneFlags zoneFlags = (ZoneFlags)value;
  4407.  
  4408.                 sb.Clear();
  4409.  
  4410.                 foreach(KeyValuePair<string, int> flag in ZoneFlags.NameToIndex)
  4411.                 {
  4412.                     if (zoneFlags.HasFlag(flag.Value))
  4413.                     {
  4414.                         sb.Append(sb.Length == 0 ? flag.Key : SEPERATOR + flag.Key);
  4415.                     }
  4416.                 }
  4417.  
  4418.                 writer.WriteValue(sb.ToString());
  4419.             }
  4420.  
  4421.             public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  4422.             {
  4423.                 ZoneFlags zoneFlags = new ZoneFlags();
  4424.  
  4425.                 if (reader.TokenType == JsonToken.String)
  4426.                 {
  4427.                     string[] values = reader.Value.ToString().Split(',');
  4428.                     for (int i = 0; i < values.Length; i++)
  4429.                     {
  4430.                         string value = values[i].Trim();
  4431.  
  4432.                         if (ZoneFlags.NameToIndex.TryGetValue(value, out int v))
  4433.                             zoneFlags.AddFlag(v);
  4434.                     }
  4435.                 }
  4436.                 return zoneFlags;
  4437.             }
  4438.  
  4439.             public override bool CanConvert(Type objectType)
  4440.             {
  4441.                 return objectType == typeof(ZoneFlags);
  4442.             }
  4443.         }
  4444.         #endregion
  4445.  
  4446.         #region Localization
  4447.         private string Message(string key, string playerId = null) => lang.GetMessage(key, this, playerId);
  4448.  
  4449.         private readonly Dictionary<string, string> Messages = new Dictionary<string, string>
  4450.         {
  4451.             ["noBuild"] = "You are not allowed to build in this area!",
  4452.             ["noUpgrade"] = "You are not allowed to upgrade structures in this area!",
  4453.             ["noDeploy"] = "You are not allowed to deploy items in this area!",
  4454.             ["noCup"] = "You are not allowed to deploy cupboards in this area!",
  4455.             ["noChat"] = "You are not allowed to chat in this area!",
  4456.             ["noSuicide"] = "You are not allowed to suicide in this area!",
  4457.             ["noGather"] = "You are not allowed to gather in this area!",
  4458.             ["noLoot"] = "You are not allowed loot in this area!",
  4459.             ["noSignUpdates"] = "You can not update signs in this area!",
  4460.             ["noOvenToggle"] = "You can not toggle ovens and lights in this area!",
  4461.             ["noPickup"] = "You can not pick up objects in this area!",
  4462.             ["noVending"] = "You can not use vending machines in this area!",
  4463.             ["noStash"] = "You can not hide a stash in this area!",
  4464.             ["noCraft"] = "You can not craft in this area!",
  4465.             ["eject"] = "You are not allowed in this area!",
  4466.             ["attract"] = "You are not allowed to leave this area!",
  4467.             ["kill"] = "Access to this area is restricted!",
  4468.             ["noVoice"] = "You are not allowed to voice chat in this area!",
  4469.             ["novehiclesenter"] = "Vehicles are not allowed in this area!",
  4470.             ["novehiclesleave"] = "Vehicles are not allowed to leave this area!",
  4471.             ["novehiclemounting"] = "You are not allowed to mount vehicles in this area!",
  4472.             ["novehicledismounting"] = "You are not allowed to dismount vehicles in this area!",
  4473.             ["nosprays"] = "You are not allowed to spray paint in this area!"
  4474.         };
  4475.         #endregion
  4476.     }
  4477. }
  4478.  
Advertisement
Add Comment
Please, Sign In to add comment