Support for zone manager?

Hi there, I was hoping to see if there could be support for zone manager for a pVE server that has PVP elements?

 

When entering the zone, it flags the player and if killed the player loses their inventory but not (server backpacks) via Backpasks.cs

 

this would be a great option for players, and if theres another way Im happy to learn how

Heres what i added to allow it to work:

using Facepunch;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using Rust;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using System.Text;
using UnityEngine;
using Debug = UnityEngine.Debug;

namespace Oxide.Plugins
{
[Info("Zone Manager", "k1lly0u", "3.1.10")]
[Description("An advanced management system for creating in-game zones")]
public class ZoneManager : RustPlugin
{
#region Fields
[PluginReference] Plugin Backpacks, PopupNotifications, Spawns;

private StoredData storedData;

private DynamicConfigFile data;

private readonly Hash<string, Zone> zones = new Hash<string, Zone>();
private readonly Hash<Plugin, HashSet<string>> temporaryZones = new Hash<Plugin, HashSet<string>>();

private static readonly Hash<ulong, EntityZones> zonedPlayers = new Hash<ulong, EntityZones>();

private static readonly Hash<NetworkableId, EntityZones> zonedEntities = new Hash<NetworkableId, EntityZones>();

private readonly Dictionary<ulong, string> lastPlayerZone = new Dictionary<ulong, string>();

private readonly ZoneFlags globalFlags = new ZoneFlags();

private readonly ZoneFlags adminBypass = new ZoneFlags();

private static readonly ZoneFlags tempFlags = new ZoneFlags();

private static readonly StringBuilder sb = new StringBuilder();

private bool zonesInitialized = false;

private static ZoneManager Instance { get; set; }

private const string PERMISSION_ZONE = "zonemanager.zone";

private const string PERMISSION_IGNORE_FLAG = "zonemanager.ignoreflag.";

private const int PLAYER_MASK = 131072;

private const int TARGET_LAYERS = ~(1 << 10 | 1 << 18 | 1 << 28 | 1 << 29);
#endregion

#region Oxide Hooks
private void Init()
{
Instance = this;

adminBypass.SetFlags(ZoneFlags.NoBuild, ZoneFlags.NoDeploy, ZoneFlags.NoCup, ZoneFlags.NoUpgrade, ZoneFlags.NoChat, ZoneFlags.NoVoice, ZoneFlags.KillSleepers, ZoneFlags.EjectSleepers, ZoneFlags.NoSignUpdates);

lang.RegisterMessages(Messages, this);

permission.RegisterPermission(PERMISSION_ZONE, this);

foreach (string flag in ZoneFlags.NameToIndex.Keys)
permission.RegisterPermission(PERMISSION_IGNORE_FLAG + flag.ToLower(), this);

LoadData();
}

private void OnServerInitialized() => InitializeZones();

private void OnTerrainInitialized() => InitializeZones();

private void OnPlayerConnected(BasePlayer player) => updateBehaviour.QueueUpdate(player);

private void OnPluginUnloaded(Plugin plugin)
{
if (!temporaryZones.TryGetValue(plugin, out HashSet<string> set))
return;

foreach (string zoneId in set)
{
if (!zones.TryGetValue(zoneId, out Zone zone))
continue;

if (zone.definition.Owner != plugin)
continue;

zones.Remove(zoneId);

UnityEngine.Object.DestroyImmediate(zone.gameObject);
Interface.CallHook("OnZoneErased", zoneId);
}

temporaryZones.Remove(plugin);
}

private void OnEntityKill(BaseEntity baseEntity)
{
if (!baseEntity || !baseEntity.IsValid() || baseEntity.IsDestroyed)
return;

if (!zonedEntities.TryGetValue(baseEntity.net.ID, out EntityZones entityZones))
return;

for (int i = entityZones.Zones.Count - 1; i >= 0; i--)
{
Zone zone = entityZones.Zones[i];
if (!zone)
continue;

zone.OnEntityExitZone(baseEntity, false, true);
}

zonedEntities.Remove(baseEntity.net.ID);
}

private void Unload()
{
DestroyUpdateBehaviour();

foreach (BasePlayer player in BasePlayer.activePlayerList)
CuiHelper.DestroyUi(player, ZMUI);

foreach (KeyValuePair<string, Zone> kvp in zones)
UnityEngine.Object.DestroyImmediate(kvp.Value.gameObject);

zones.Clear();
temporaryZones.Clear();
zonedPlayers.Clear();
zonedEntities.Clear();

Instance = null;
Configuration = null;
}
#endregion

#region UpdateQueue
private UpdateBehaviour m_UpdateBehaviour;

private UpdateBehaviour updateBehaviour
{
get
{
if (m_UpdateBehaviour)
return m_UpdateBehaviour;

m_UpdateBehaviour = new GameObject("ZoneManager.UpdateBehaviour").AddComponent<UpdateBehaviour>();

foreach (BasePlayer player in BasePlayer.activePlayerList)
m_UpdateBehaviour.QueueUpdate(player);

return m_UpdateBehaviour;
}
}

private void DestroyUpdateBehaviour()
{
if (updateBehaviour)
UnityEngine.Object.Destroy(updateBehaviour.gameObject);
}

// 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
// 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.
// Options are to either continually check and update players, or have every plugin that teleports players call the hook...
private class UpdateBehaviour : MonoBehaviour
{
private readonly System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();

private readonly Queue<BasePlayer> playerUpdateQueue = new Queue<BasePlayer>();

private const float MAX_MS = 0.25f;

private void OnDestroy()
{
playerUpdateQueue.Clear();
}

public void QueueUpdate(BasePlayer player)
{
if (!playerUpdateQueue.Contains(player))
playerUpdateQueue.Enqueue(player);
}

public void Reset() => playerUpdateQueue.Clear();

private void Update()
{
if (Time.frameCount % 10 != 0)
return;

sw.Reset();
sw.Start();

while (playerUpdateQueue.Count > 0)
{
if (sw.Elapsed.TotalMilliseconds >= MAX_MS)
{
sw.Stop();
return;
}

BasePlayer player = playerUpdateQueue.Dequeue();
if (!player || !player.IsConnected)
continue;

Instance?.UpdatePlayerZones(player);

InvokeHandler.Invoke(this, () => QueueUpdate(player), 2f);
}
}
}
#endregion

#region Flag Hooks
private void OnEntityBuilt(Planner planner, GameObject gameObject)
{
if (!planner || !gameObject)
return;

BasePlayer player = planner.GetOwnerPlayer();
if (!player)
return;

BaseEntity entity = gameObject.ToBaseEntity();
if (!entity)
return;

if (entity is BuildingBlock block)
{
if (!HasPlayerFlag(player, ZoneFlags.NoBuild))
return;

List<ItemAmount> list = block.BuildCost();

block.Invoke(() =>
{
for (int i = 0; i < list?.Count; i++)
{
ItemAmount itemAmount = list[i];
player.GiveItem(ItemManager.Create(itemAmount.itemDef, Mathf.Clamp(Mathf.RoundToInt(itemAmount.amount), 1, int.MaxValue)));
}

if (entity && !entity.IsDestroyed)
entity.Kill(BaseNetworkable.DestroyMode.Gib);
}, 0.1f);

SendMessage(player, Message("noBuild", player.UserIDString));
}
else if (entity is SimpleBuildingBlock)
{
if (!HasPlayerFlag(player, ZoneFlags.NoBuild))
return;

KillEntityAndReturnItem(player, entity, planner.GetItem());
SendMessage(player, Message("noBuild", player.UserIDString));
}
else
{
if (entity is BuildingPrivlidge)
{
if (!HasPlayerFlag(player, ZoneFlags.NoCup))
return;

KillEntityAndReturnItem(player, entity, planner.GetItem());
SendMessage(player, Message("noCup", player.UserIDString));
}
else
{
if (!HasPlayerFlag(player, ZoneFlags.NoDeploy))
return;

KillEntityAndReturnItem(player, entity, planner.GetItem());
SendMessage(player, Message("noDeploy", player.UserIDString));
}
}
}

private object OnStructureUpgrade(BuildingBlock buildingBlock, BasePlayer player, BuildingGrade.Enum grade)
{
if (HasPlayerFlag(player, ZoneFlags.NoUpgrade))
{
SendMessage(player, Message("noUpgrade", player.UserIDString));
return true;
}
return null;
}

private void OnItemDeployed(Deployer deployer, ItemModDeployable itemModDeployable, BaseEntity deployedEntity) // DoDeploy_Regular
{
BasePlayer player = deployer.GetOwnerPlayer();
if (!player)
return;

if (HasPlayerFlag(player, ZoneFlags.NoDeploy))
{
KillEntityAndReturnItem(player, deployedEntity, deployer.GetItem());
SendMessage(player, Message("noDeploy", player.UserIDString));
}
}

private void OnItemDeployed(Deployer deployer, BaseEntity parentEntity, BaseEntity deployedEntity) // DoDeploy_Slot
{
BasePlayer player = deployer.GetOwnerPlayer();
if (!player)
return;

if (HasPlayerFlag(player, ZoneFlags.NoDeploy))
{
KillEntityAndReturnItem(player, deployedEntity, deployer.GetItem());
SendMessage(player, Message("noDeploy", player.UserIDString));
}
}

private void KillEntityAndReturnItem(BasePlayer player, BaseEntity entity, Item item)
{
ItemDefinition itemDefinition = item?.info;
//int amount = item.amount;
ulong skin = item?.skin ?? 0UL;

entity.Invoke(() =>
{
if (entity && !entity.IsDestroyed)
entity.Kill(BaseNetworkable.DestroyMode.Gib);

if (itemDefinition)
player.GiveItem(ItemManager.Create(itemDefinition, 1, skin));

}, 0.1f);
}

private void OnItemUse(Item item, int amount)
{
BaseEntity entity = item?.parent?.entityOwner;
if (!entity)
return;

if (entity is FlameTurret or AutoTurret or GunTrap)
{
if (HasEntityFlag(entity, ZoneFlags.InfiniteTrapAmmo))
item.amount += amount;
return;
}

if (entity is not SearchLight)
return;

if (HasEntityFlag(entity, ZoneFlags.AlwaysLights))
{
item.amount += amount;
return;
}

if (!HasEntityFlag(entity, ZoneFlags.AutoLights))
return;

if (TOD_Sky.Instance.Cycle.Hour > Configuration.AutoLights.OnTime || TOD_Sky.Instance.Cycle.Hour < Configuration.AutoLights.OffTime)
item.amount += amount;
}

private object OnPlayerChat(BasePlayer player, string message, ConVar.Chat.ChatChannel channel)
{
if (!player)
return null;

if (!HasPlayerFlag(player, ZoneFlags.NoChat))
return null;

SendMessage(player, Message("noChat", player.UserIDString));
return true;
}

private object OnBetterChat(Oxide.Core.Libraries.Covalence.IPlayer iPlayer, string message)
{
BasePlayer player = iPlayer.Object as BasePlayer;
return OnPlayerChat(player, message, ConVar.Chat.ChatChannel.Global);
}

private object OnPlayerVoice(BasePlayer player, Byte[] data)
{
if (HasPlayerFlag(player, ZoneFlags.NoVoice))
{
SendMessage(player, Message("noVoice", player.UserIDString));
return true;
}
return null;
}

private object OnServerCommand(ConsoleSystem.Arg arg)
{
BasePlayer player = arg.Player();
if (!player || string.IsNullOrEmpty(arg.cmd?.Name))
return null;

if (arg.cmd.Name == "kill" && HasPlayerFlag(player, ZoneFlags.NoSuicide))
{
SendMessage(player, Message("noSuicide", player.UserIDString));
return true;
}
return null;
}

private void OnPlayerDisconnected(BasePlayer player)
{
if (!player)
return;

if (HasPlayerFlag(player, ZoneFlags.KillSleepers))
{
player.Die();
return;
}

if (HasPlayerFlag(player, ZoneFlags.EjectSleepers))
{
if (!zonedPlayers.TryGetValue(player.userID, out EntityZones entityZones) || entityZones.Count == 0)
return;

foreach (Zone zone in entityZones.Zones)
{
if (!zone)
continue;

if (HasFlag(zone, ZoneFlags.EjectSleepers))
{
EjectPlayer(player, zone);
return;
}
}
}
}

private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo hitinfo)
{
if (!entity || GetEntityComponent<ResourceDispenser>(entity))
return null;

BasePlayer attacker = hitinfo.InitiatorPlayer;
BasePlayer victim = entity as BasePlayer;

if (victim)
{
if (hitinfo.damageTypes.GetMajorityDamageType() == DamageType.Fall)
{
if (HasPlayerFlag(victim, ZoneFlags.NoFallDamage))
return true;
}

if (victim.IsSleeping() && HasPlayerFlag(victim, ZoneFlags.SleepGod))
return true;

if (attacker)
{
if (IsNpc(victim))
return null;

if (HasPlayerFlag(victim, ZoneFlags.PvpGod))
{
if (attacker == victim && hitinfo.damageTypes.GetMajorityDamageType() == DamageType.Suicide)
{
if (HasPlayerFlag(victim, ZoneFlags.NoSuicide))
return true;
return null;
}
if (IsNpc(attacker) && Configuration.NPCHurtPvpGod)
return null;

return true;
}

if (HasPlayerFlag(attacker, ZoneFlags.PvpGod) && !IsNpc(attacker))
return true;
}

else if (HasPlayerFlag(victim, ZoneFlags.PveGod) && !IsNpc(victim))
return true;

else if (hitinfo.Initiator is FireBall && HasPlayerFlag(victim, ZoneFlags.PvpGod))
return true;

return null;
}

BaseNpc baseNpc = entity as BaseNpc;
if (baseNpc)
{
if (HasEntityFlag(baseNpc, ZoneFlags.NoPve))
{
if (attacker && CanBypass(attacker, ZoneFlags.NoPve))
return null;
return true;
}
return null;
}

if (entity is BuildingBlock or SimpleBuildingBlock)
{
if (HasEntityFlag(entity, ZoneFlags.NoBuildingDamage))
{
if (attacker)
{
if (CanBypass(attacker, ZoneFlags.NoBuildingDamage))
return null;

if (HasPlayerFlag(attacker, ZoneFlags.NoBuildingDamage))
return true;
}

if (hitinfo.damageTypes.GetMajorityDamageType() == DamageType.Decay && Configuration.DecayDamageUndestr)
return null;

return true;
}
}

if (entity is not LootContainer && entity is not PatrolHelicopter)
{
if (HasEntityFlag(entity, ZoneFlags.UnDestr))
{
if (attacker)
{
if (CanBypass(attacker, ZoneFlags.UnDestr))
return null;

if (HasPlayerFlag(attacker, ZoneFlags.UnDestr))
return true;
}

if (hitinfo.damageTypes.GetMajorityDamageType() == DamageType.Decay && Configuration.DecayDamageUndestr)
return null;

return true;
}
}

return null;
}

private void OnEntitySpawned(BaseEntity baseEntity)
=> NextTick(() => CanSpawn(baseEntity));

private void CanSpawn(BaseEntity baseEntity)
{
if (!baseEntity.IsValid() || baseEntity.IsDestroyed)
return;

if (Interface.CallHook("CanSpawnInZone", baseEntity) != null)
return;

if (baseEntity is BaseCorpse corpse)
{
if (HasEntityFlag(corpse, ZoneFlags.NoCorpse) && !CanBypass(corpse.OwnerID, ZoneFlags.NoCorpse))
corpse.Invoke(() => baseEntity.Kill(BaseNetworkable.DestroyMode.None), 0.1f);
}
if (baseEntity is LootContainer or JunkPile)
{
if (HasEntityFlag(baseEntity, ZoneFlags.NoLootSpawns))
baseEntity.Invoke(() => baseEntity.Kill(BaseNetworkable.DestroyMode.None), 0.1f);
}
else if (baseEntity is BaseNpc or NPCPlayer)
{
if (HasEntityFlag(baseEntity, ZoneFlags.NoNPCSpawns))
baseEntity.Invoke(() => baseEntity.Kill(BaseNetworkable.DestroyMode.None), 0.1f);
}
else if (baseEntity is DroppedItem or WorldItem)
{
if (HasEntityFlag(baseEntity, ZoneFlags.NoDrop))
{
((WorldItem)baseEntity).item.Remove(0f);
baseEntity.Invoke(() => baseEntity.Kill(BaseNetworkable.DestroyMode.None), 0.1f);
}
}
else if (baseEntity is DroppedItemContainer)
{
if (HasEntityFlag(baseEntity, ZoneFlags.NoDrop))
baseEntity.Invoke(() => baseEntity.Kill(BaseNetworkable.DestroyMode.None), 0.1f);
}
}

private object CanBeWounded(BasePlayer player, HitInfo hitinfo) => HasPlayerFlag(player, ZoneFlags.NoWounded) ? (object)false : null;

private object CanUpdateSign(BasePlayer player, Signage sign)
{
if (HasPlayerFlag(player, ZoneFlags.NoSignUpdates))
{
SendMessage(player, Message("noSignUpdates", player.UserIDString));
return false;
}
return null;
}

private object OnOvenToggle(BaseOven oven, BasePlayer player)
{
if (HasPlayerFlag(player, ZoneFlags.NoOvenToggle))
{
SendMessage(player, Message("noOvenToggle", player.UserIDString));
return true;
}
return null;
}

private object CanUseVending(BasePlayer player, VendingMachine machine)
{
if (HasPlayerFlag(player, ZoneFlags.NoVending))
{
SendMessage(player, Message("noVending", player.UserIDString));
return false;
}
return null;
}

private object CanHideStash(BasePlayer player, StashContainer stash)
{
if (HasPlayerFlag(player, ZoneFlags.NoStash))
{
SendMessage(player, Message("noStash", player.UserIDString));
return false;
}
return null;
}

private object CanCraft(ItemCrafter itemCrafter, ItemBlueprint bp, int amount)
{
BasePlayer player = itemCrafter.GetComponent<BasePlayer>();
if (player && HasPlayerFlag(player, ZoneFlags.NoCraft))
{
SendMessage(player, Message("noCraft", player.UserIDString));
return false;
}
return null;
}

private void OnDoorOpened(Door door, BasePlayer player)
{
if (HasPlayerFlag(player, ZoneFlags.NoDoorAccess))
{
SendMessage(player, Message("noDoor", player.UserIDString));
door.CloseRequest();
}
}

private object OnSprayCreate(SprayCan sprayCan, Vector3 position, Quaternion rotation)
{
if (!sprayCan)
return null;

BasePlayer player = sprayCan.GetOwnerPlayer();
if (!player)
return null;

if (HasPlayerFlag(player, ZoneFlags.NoSprays))
{
SendMessage(player, Message("nosprays", player.UserIDString));
return false;
}

return null;
}

#region Looting Hooks
private object CanLootPlayer(BasePlayer target, BasePlayer looter) => OnLootPlayerInternal(looter, target);

private void OnLootPlayer(BasePlayer looter, BasePlayer target) => OnLootPlayerInternal(looter, target);

private object OnLootPlayerInternal(BasePlayer looter, BasePlayer target)
{
if (HasPlayerFlag(looter, ZoneFlags.NoPlayerLoot) || (target != null && HasPlayerFlag(target, ZoneFlags.NoPlayerLoot)))
{
if (looter == target && Backpacks != null)
{
object hookResult = Backpacks.Call("CanLootPlayer", target, looter);
if (hookResult is bool result && result)
return true;
}

SendMessage(looter, Message("noLoot", looter.UserIDString));
NextTick(looter.EndLooting);
return false;
}
return null;
}

private void OnLootEntity(BasePlayer player, BaseEntity entity)
{
if (entity is LootableCorpse corpse)
OnLootCorpse(corpse, player);
if (entity is DroppedItemContainer container)
OnLootContainer(container, player);
if (entity is StorageContainer)
OnLootInternal(player, ZoneFlags.NoBoxLoot);
}

private object CanLootEntity(BasePlayer player, LootableCorpse corpse)
{
if (corpse is NPCPlayerCorpse)
{
if (!HasPlayerFlag(player, ZoneFlags.NoNPCLoot))
return null;

SendMessage(player, Message("noLoot", player.UserIDString));
return false;
}

if (corpse.playerSteamID == player.userID && HasPlayerFlag(player, ZoneFlags.LootSelf))
return null;

return CanLootInternal(player, ZoneFlags.NoPlayerLoot);
}

private void OnLootCorpse(LootableCorpse corpse, BasePlayer player)
{
if (corpse is NPCPlayerCorpse)
{
if (!HasPlayerFlag(player, ZoneFlags.NoNPCLoot))
return;

SendMessage(player, Message("noLoot", player.UserIDString));
NextTick(player.EndLooting);
return;
}

if (corpse.playerSteamID == player.userID && HasPlayerFlag(player, ZoneFlags.LootSelf))
return;

OnLootInternal(player, ZoneFlags.NoPlayerLoot);
}

private void OnLootContainer(DroppedItemContainer container, BasePlayer player)
{
if (container.playerSteamID == player.userID && HasPlayerFlag(player, ZoneFlags.LootSelf))
return;

OnLootInternal(player, ZoneFlags.NoPlayerLoot);
}

private object CanLootEntity(BasePlayer player, DroppedItemContainer container)
{
if (container.playerSteamID == player.userID && HasPlayerFlag(player, ZoneFlags.LootSelf))
return null;

return CanLootInternal(player, ZoneFlags.NoPlayerLoot);
}

private object CanLootEntity(BasePlayer player, StorageContainer container) => CanLootInternal(player, ZoneFlags.NoBoxLoot);

private object CanLootInternal(BasePlayer player, int flag)
{
if (!player || !HasPlayerFlag(player, flag))
return null;

SendMessage(player, Message("noLoot", player.UserIDString));
return false;
}

private void OnLootInternal(BasePlayer player, int flag)
{
if (!player || !HasPlayerFlag(player, flag))
return;

SendMessage(player, Message("noLoot", player.UserIDString));
NextTick(player.EndLooting);
}
#endregion

#region Pickup Hooks
private object CanPickupEntity(BasePlayer player, BaseCombatEntity entity) => CanPickupInternal(player, ZoneFlags.NoEntityPickup);

private object CanPickupLock(BasePlayer player, BaseLock baseLock) => CanPickupInternal(player, ZoneFlags.NoEntityPickup);

private object OnItemPickup(Item item, BasePlayer player) => CanPickupInternal(player, ZoneFlags.NoPickup);

private object CanPickupInternal(BasePlayer player, int flag)
{
if (!HasPlayerFlag(player, flag))
return null;

SendMessage(player, Message("noPickup", player.UserIDString));
return false;
}
#endregion

#region Gather Hooks
private object CanLootEntity(ResourceContainer container, BasePlayer player) => OnGatherInternal(player);

private object OnCollectiblePickup(Item item, BasePlayer player) => OnGatherInternal(player);

private object OnGrowableGather(GrowableEntity plant, Item item, BasePlayer player) => OnGatherInternal(player);

private object OnDispenserGather(ResourceDispenser dispenser, BasePlayer player, Item item) => OnGatherInternal(player);

private object OnGatherInternal(BasePlayer player)
{
if (!player || !HasPlayerFlag(player, ZoneFlags.NoGather))
return null;

SendMessage(player, Message("noGather", player.UserIDString));
return true;

}
#endregion

#region Targeting Hooks
private object OnTurretTarget(AutoTurret turret, BasePlayer player) => OnTargetPlayerInternal(player, ZoneFlags.NoTurretTargeting);

private object CanBradleyApcTarget(BradleyAPC apc, BasePlayer player)
{
if (player && HasPlayerFlag(player, ZoneFlags.NoAPCTargeting))
return false;
return null;
}

private object CanHelicopterTarget(PatrolHelicopterAI heli, BasePlayer player)
{
if (!player || !HasPlayerFlag(player, ZoneFlags.NoHeliTargeting))
return null;

heli.interestZoneOrigin = heli.GetRandomPatrolDestination();
return false;
}

private object CanHelicopterStrafeTarget(PatrolHelicopterAI heli, BasePlayer player)
{
if (player && HasPlayerFlag(player, ZoneFlags.NoHeliTargeting))
return false;
return null;
}

private object OnHelicopterTarget(HelicopterTurret turret, BasePlayer player) => OnTargetPlayerInternal(player, ZoneFlags.NoHeliTargeting);

private object OnNpcTarget(BaseCombatEntity entity, BasePlayer player) => OnTargetPlayerInternal(player, ZoneFlags.NoNPCTargeting);

private object OnTargetPlayerInternal(BasePlayer player, int flag)
{
if (player && HasPlayerFlag(player, flag))
return true;
return null;
}
#endregion

#region Mounting Hooks
private object CanMountEntity(BasePlayer player, BaseMountable entity)
{
if (!player || !entity)
return null;

if (!entity.VehicleParent())
return null;

if (!HasPlayerFlag(player, ZoneFlags.NoVehicleMounting))
return null;

SendMessage(player, Message("novehiclemounting", player.UserIDString));
return false;
}

private object CanDismountEntity(BasePlayer player, BaseMountable entity)
{
if (!player || !entity)
return null;

if (!entity.VehicleParent())
return null;

if (!HasPlayerFlag(player, ZoneFlags.NoVehicleDismounting))
return null;

SendMessage(player, Message("novehicledismounting", player.UserIDString));
return false;

}
#endregion

#region Additional KillSleeper Checks
private void OnPlayerSleep(BasePlayer player)
{
if (!player)
return;

//player.Invoke(()=> UpdatePlayerZones(player), 1f); // Manually update the zones a player is in. Sleeping players don't trigger OnTriggerEnter or OnTriggerExit

timer.In(2f, () =>
{
if (!player || !player.IsSleeping())
return;

if (player.IsConnected)
return;

if (HasPlayerFlag(player, ZoneFlags.KillSleepers))
{
player.Invoke(() => KillSleepingPlayer(player), 3f);
return;
}

if (HasPlayerFlag(player, ZoneFlags.EjectSleepers))
{
player.Invoke(() =>
{
if (!player || !player.IsSleeping())
return;

if (!zonedPlayers.TryGetValue(player.userID, out EntityZones entityZones) || entityZones.Count == 0)
return;

foreach (Zone zone in entityZones.Zones)
{
if (!zone)
continue;

if (HasFlag(zone, ZoneFlags.EjectSleepers))
EjectPlayer(player, zone);
}
}, 3f);
}
});
}

private void OnPlayerSleepEnd(BasePlayer player) => updateBehaviour.QueueUpdate(player);

private void KillSleepingPlayer(BasePlayer player)
{
if (!player || !player.IsSleeping())
return;

if (!HasPlayerFlag(player, ZoneFlags.KillSleepers))
return;

if (player.IsConnected)
OnPlayerSleep(player);
else player.Die();
}

private void UpdatePlayerZones(BasePlayer player)
{
if (!player)
return;

if (zonedPlayers.TryGetValue(player.userID, out EntityZones entityZones))
{
List<Zone> list = Pool.Get<List<Zone>>();
list.AddRange(entityZones.Zones);

for (int i = list.Count - 1; i >= 0; i--)
{
Zone zone = list[i];
if (!zone || !zone.definition.Enabled)
continue;

if (zone.definition.Size != Vector3.zero)
{
if (!IsInsideBounds(zone, player.transform.position))
OnPlayerExitZone(player, zone);
}
else
{
if (Vector3.Distance(player.transform.position, zone.transform.position) > zone.definition.Radius)
OnPlayerExitZone(player, zone);
}
}

Pool.FreeUnmanaged(ref list);
}

foreach (Zone zone in zones.Values)
{
if (!zone)
continue;

if (entityZones != null && entityZones.Zones.Contains(zone))
continue;

if (zone.definition.Size != Vector3.zero)
{
if (IsInsideBounds(zone, player.transform.position))
OnPlayerEnterZone(player, zone);
}
else
{
if (Vector3.Distance(player.transform.position, zone.transform.position) <= zone.definition.Radius)
OnPlayerEnterZone(player, zone);
}
}

if (player.HasPlayerFlag(BasePlayer.PlayerFlags.SafeZone) && !player.InSafeZone())
player.SetPlayerFlag(BasePlayer.PlayerFlags.SafeZone, false);
}

private bool IsInsideBounds(Zone zone, Vector3 worldPos) => zone?.collider?.ClosestPoint(worldPos) == worldPos;
#endregion

private T GetEntityComponent<T>(BaseEntity entity) where T : EntityComponentBase
{
for (int i = 0; i < entity.Components.Count; i++)
{
EntityComponentBase component = entity.Components[i];
if (component is T t)
return t;
}

return null;
}
#endregion

#region Zone Functions
private void InitializeZones()
{
if (zonesInitialized)
return;

foreach (Zone.Definition definition in storedData.definitions)
{
Zone zone = new GameObject().AddComponent<Zone>();
zone.InitializeZone(definition);

zones.Add(definition.Id, zone);
}

foreach (Zone zone in zones.Values)
zone.FindZoneParent();

zonesInitialized = true;

UnsubscribeAll();
UpdateHookSubscriptions();
}

private static bool ReverseVelocity(BaseVehicle baseVehicle)
{
if (baseVehicle is BaseVehicleModule module)
baseVehicle = module.Vehicle;

if (!baseVehicle || !baseVehicle.IsVehicleRoot() || !baseVehicle.rigidBody)
return false;

if (baseVehicle.AnyMounted() && baseVehicle is BaseHelicopter)
{
baseVehicle.rigidBody.velocity *= -1f;
Vector3 euler = baseVehicle.transform.eulerAngles;
baseVehicle.transform.rotation = Quaternion.Euler(euler.x, euler.y - 180f, euler.z);
}
else
{
Vector3 force = (baseVehicle.rigidBody.velocity.normalized * -1f) * baseVehicle.rigidBody.mass * 4f;
baseVehicle.rigidBody.velocity = Vector3.zero;
baseVehicle.rigidBody.AddForce(force, ForceMode.Impulse);
}

return true;
}

private void EjectPlayer(BasePlayer player, Zone zone)
{
if (zone.keepInList.Contains(player.userID.Get()) || zone.whitelist.Contains(player.userID.Get()))
return;

if (!string.IsNullOrEmpty(zone.definition.Permission))
{
if (HasPermission(player, zone.definition.Permission))
return;
}

if (player.isMounted && ReverseVelocity(player.GetMountedVehicle()))
{
SendMessage(player, Message("eject", player.UserIDString));
return;
}

Vector3 position = Vector3.zero;
if (Spawns && !string.IsNullOrEmpty(zone.definition.EjectSpawns))
{
object success = Spawns.Call("GetRandomSpawn", zone.definition.EjectSpawns);
if (success is Vector3 vector3)
position = vector3;
}

if (position == Vector3.zero)
{
float distance = zone.definition.Size != Vector3.zero ? Mathf.Max(zone.definition.Size.x, zone.definition.Size.z) : zone.definition.Radius;

position = zone.transform.position + (((player.transform.position.XZ3D() - zone.transform.position.XZ3D()).normalized) * (distance + 10f));

if (Physics.Raycast(new Ray(new Vector3(position.x, position.y + 300, position.z), Vector3.down), out RaycastHit rayHit, 500, TARGET_LAYERS, QueryTriggerInteraction.Ignore))
position.y = rayHit.point.y;
else position.y = TerrainMeta.HeightMap.GetHeight(position);
}

player.MovePosition(position);
player.ClientRPCPlayer(null, player, "ForcePositionTo", player.transform.position);
player.SendNetworkUpdateImmediate();

SendMessage(player, Message("eject", player.UserIDString));
}

private void AttractPlayer(BasePlayer player, Zone zone)
{
if (player.isMounted && ReverseVelocity(player.GetMountedVehicle()))
{
SendMessage(player, Message("attract", player.UserIDString));
return;
}

float distance = zone.definition.Size != Vector3.zero ? Mathf.Max(zone.definition.Size.x, zone.definition.Size.z) : zone.definition.Radius;

Vector3 position = zone.transform.position + (player.transform.position - zone.transform.position).normalized * (distance - 5f);
position.y = TerrainMeta.HeightMap.GetHeight(position);

player.MovePosition(position);
player.ClientRPCPlayer(null, player, "ForcePositionTo", player.transform.position);
player.SendNetworkUpdateImmediate();

SendMessage(player, Message("attract", player.UserIDString));
}

private void ShowZone(BasePlayer player, string zoneId, float time = 30)
{
Zone zone = GetZoneByID(zoneId);
if (!zone)
return;

if (zone.definition.Size != Vector3.zero)
{
Vector3 center = zone.definition.Location;
Quaternion rotation = Quaternion.Euler(zone.definition.Rotation);
Vector3 size = zone.definition.Size / 2;
Vector3 point1 = RotatePointAroundPivot(new Vector3(center.x + size.x, center.y + size.y, center.z + size.z), center, rotation);
Vector3 point2 = RotatePointAroundPivot(new Vector3(center.x + size.x, center.y - size.y, center.z + size.z), center, rotation);
Vector3 point3 = RotatePointAroundPivot(new Vector3(center.x + size.x, center.y + size.y, center.z - size.z), center, rotation);
Vector3 point4 = RotatePointAroundPivot(new Vector3(center.x + size.x, center.y - size.y, center.z - size.z), center, rotation);
Vector3 point5 = RotatePointAroundPivot(new Vector3(center.x - size.x, center.y + size.y, center.z + size.z), center, rotation);
Vector3 point6 = RotatePointAroundPivot(new Vector3(center.x - size.x, center.y - size.y, center.z + size.z), center, rotation);
Vector3 point7 = RotatePointAroundPivot(new Vector3(center.x - size.x, center.y + size.y, center.z - size.z), center, rotation);
Vector3 point8 = RotatePointAroundPivot(new Vector3(center.x - size.x, center.y - size.y, center.z - size.z), center, rotation);

player.SendConsoleCommand("ddraw.line", time, Color.blue, point1, point2);
player.SendConsoleCommand("ddraw.line", time, Color.blue, point1, point3);
player.SendConsoleCommand("ddraw.line", time, Color.blue, point1, point5);
player.SendConsoleCommand("ddraw.line", time, Color.blue, point4, point2);
player.SendConsoleCommand("ddraw.line", time, Color.blue, point4, point3);
player.SendConsoleCommand("ddraw.line", time, Color.blue, point4, point8);

player.SendConsoleCommand("ddraw.line", time, Color.blue, point5, point6);
player.SendConsoleCommand("ddraw.line", time, Color.blue, point5, point7);
player.SendConsoleCommand("ddraw.line", time, Color.blue, point6, point2);
player.SendConsoleCommand("ddraw.line", time, Color.blue, point8, point6);
player.SendConsoleCommand("ddraw.line", time, Color.blue, point8, point7);
player.SendConsoleCommand("ddraw.line", time, Color.blue, point7, point3);
}
else player.SendConsoleCommand("ddraw.sphere", time, Color.blue, zone.definition.Location, zone.definition.Radius);
}

private Vector3 RotatePointAroundPivot(Vector3 point, Vector3 pivot, Quaternion rotation) => rotation * (point - pivot) + pivot;

#endregion

#region Component
public class Zone : MonoBehaviour
{
public Definition definition;

public ZoneFlags disabledFlags = new ZoneFlags();

public Zone parent;

public List<BasePlayer> players = Pool.Get<List<BasePlayer>>();

public List<BaseEntity> entities = Pool.Get<List<BaseEntity>>();

private List<IOEntity> ioEntities = Pool.Get<List<IOEntity>>();

public List<ulong> keepInList = Pool.Get<List<ulong>>();

public List<ulong> whitelist = Pool.Get<List<ulong>>();

public Hash<ulong, EntityZones> entityZones = new Hash<ulong, EntityZones>();

private Rigidbody rigidbody;

public Collider collider;

public Bounds colliderBounds;

private ChildSphereTrigger<TriggerRadiation> radiation;

private ChildSphereTrigger<TriggerComfort> comfort;

private ChildSphereTrigger<TriggerTemperature> temperature;

private ChildSphereTrigger<TriggerSafeZone> safeZone;

private readonly Hash<BaseVehicle, float> lastReversedTimes = new Hash<BaseVehicle, float>();

private int creationFrame;

private bool isTogglingLights = false;

private void Awake()
{
gameObject.layer = (int)Layer.Reserved1;
gameObject.name = "ZoneManager";
enabled = false;

creationFrame = Time.frameCount;
}

private void OnDestroy()
{
EmptyZone();

Pool.FreeUnmanaged(ref players);
Pool.FreeUnmanaged(ref entities);
Pool.FreeUnmanaged(ref ioEntities);
Pool.FreeUnmanaged(ref keepInList);
Pool.FreeUnmanaged(ref whitelist);

Interface.CallHook("OnZoneDestroyed", definition.Id);
}

private void EmptyZone()
{
RemovePlayersFromTriggers();

keepInList.Clear();

ioEntities.Clear();

for (int i = players.Count - 1; i >= 0; i--)
Instance?.OnPlayerExitZone(players[i], this);

for (int i = entities.Count - 1; i >= 0; i--)
Instance?.OnEntityExitZone(entities[i], this);
}

#region Zone Initialization
public void InitializeZone(Definition definition)
{
if (this.definition == null)
Interface.CallHook("OnZoneInitialize", definition.Id);

this.definition = definition;

transform.position = definition.Location;

transform.rotation = Quaternion.Euler(definition.Rotation);

if (definition.Enabled)
{
RegisterPermission();

InitializeCollider();

InitializeAutoLights();

InitializeRadiation();

InitializeSafeZone();

InitializeComfort();

InitializeTemperature();

RemovePlayersFromTriggers();

AddPlayersToTriggers();

OnZoneFlagsChanged();
}
else
{
InvokeHandler.CancelInvoke(this, CheckAlwaysLights);
InvokeHandler.CancelInvoke(this, CheckLights);

if (isLightsOn)
ServerMgr.Instance.StartCoroutine(ToggleLights(false));

EmptyZone();

if (collider)
DestroyImmediate(collider);

if (rigidbody)
DestroyImmediate(rigidbody);
}

enabled = definition.Enabled;
}

public void FindZoneParent()
{
if (string.IsNullOrEmpty(definition.ParentID))
return;

if (Instance == null)
{
Debug.LogError($"[ZoneManager] Zone attempted to find parent zone, but plugin instance is null...");
return;
}

Instance.zones.TryGetValue(definition.ParentID, out parent);
}

public void Reset()
{
InvokeHandler.CancelInvoke(this, CheckAlwaysLights);
InvokeHandler.CancelInvoke(this, CheckLights);

if (isLightsOn)
ServerMgr.Instance.StartCoroutine(ToggleLights(false));

EmptyZone();

InitializeZone(definition);
FindZoneParent();
}

public void OnZoneFlagsChanged()
{
if (HasFlag(ZoneFlags.PoweredSwitches))
{
if (!InvokeHandler.IsInvoking(this, IOTick))
InvokeHandler.InvokeRandomized(this, IOTick, 0f, 1f, 0.1f);
}
else InvokeHandler.CancelInvoke(this, IOTick);
}

private void RegisterPermission()
{
if (Instance == null)
{
Debug.LogError($"[ZoneManager] Zone attempted to register permission, but plugin instance is null...");
return;
}

if (!string.IsNullOrEmpty(definition.Permission) && !Instance.permission.PermissionExists(definition.Permission))
Instance.permission.RegisterPermission(definition.Permission, Instance);
}

private void InitializeCollider()
{
if (collider)
DestroyImmediate(collider);

if (rigidbody)
DestroyImmediate(rigidbody);

rigidbody = gameObject.AddComponent<Rigidbody>();
rigidbody.useGravity = false;
rigidbody.isKinematic = true;
rigidbody.detectCollisions = true;
rigidbody.collisionDetectionMode = CollisionDetectionMode.Discrete;

SphereCollider sphereCollider = gameObject.GetComponent<SphereCollider>();
BoxCollider boxCollider = gameObject.GetComponent<BoxCollider>();

if (definition.Size != Vector3.zero)
{
if (sphereCollider)
Destroy(sphereCollider);

if (!boxCollider)
{
boxCollider = gameObject.AddComponent<BoxCollider>();
boxCollider.isTrigger = true;
}
boxCollider.size = definition.Size;
colliderBounds = boxCollider.bounds;
collider = boxCollider;
}
else
{
if (boxCollider)
Destroy(boxCollider);

if (!sphereCollider)
{
sphereCollider = gameObject.AddComponent<SphereCollider>();
sphereCollider.isTrigger = true;
}
sphereCollider.radius = definition.Radius;
colliderBounds = sphereCollider.bounds;
collider = sphereCollider;
}
}
#endregion

#region Triggers
private void InitializeRadiation()
{
if (definition.Radiation > 0)
{
radiation ??= new ChildSphereTrigger<TriggerRadiation>(gameObject, "Radiation");
radiation.Trigger.RadiationAmountOverride = definition.Radiation;
radiation.Collider.radius = collider is SphereCollider ? definition.Radius : Mathf.Min(definition.Size.x, definition.Size.y, definition.Size.z) * 0.5f;
radiation.Trigger.enabled = this.enabled;
}
else radiation?.Destroy();
}

private void InitializeComfort()
{
if (definition.Comfort > 0)
{
comfort ??= new ChildSphereTrigger<TriggerComfort>(gameObject, "Comfort");
comfort.Trigger.baseComfort = definition.Comfort;
comfort.Trigger.triggerSize = comfort.Collider.radius = collider is SphereCollider ? definition.Radius : Mathf.Min(definition.Size.x, definition.Size.y, definition.Size.z) * 0.5f;
comfort.Trigger.enabled = this.enabled;
}
else comfort?.Destroy();
}

private void InitializeTemperature()
{
if (definition.Temperature != 0)
{
temperature ??= new ChildSphereTrigger<TriggerTemperature>(gameObject, "Temperature");
temperature.Trigger.Temperature = definition.Temperature;
temperature.Trigger.triggerSize = temperature.Collider.radius = collider is SphereCollider ? definition.Radius : Mathf.Min(definition.Size.x, definition.Size.y, definition.Size.z) * 0.5f;
temperature.Trigger.enabled = this.enabled;
}
else temperature?.Destroy();
}

private void InitializeSafeZone()
{
if (definition.SafeZone)
{
safeZone ??= new ChildSphereTrigger<TriggerSafeZone>(gameObject, "SafeZone");
}
else safeZone?.Destroy();
}

private void AddToTrigger(TriggerBase triggerBase, BasePlayer player)
{
if (!triggerBase || !player)
return;

triggerBase.entityContents ??= new HashSet<BaseEntity>();

if (!triggerBase.entityContents.Add(player))
return;

player.EnterTrigger(triggerBase);

if (triggerBase is not TriggerSafeZone)
return;

if (player.IsItemHoldRestricted(player.inventory.containerBelt.FindItemByUID(player.svActiveItemID)))
player.UpdateActiveItem(default(ItemId));

player.SetPlayerFlag(BasePlayer.PlayerFlags.SafeZone, true);
}

private void RemoveFromTrigger(TriggerBase triggerBase, BasePlayer player)
{
if (!triggerBase || !player)
return;

if (triggerBase.entityContents == null || !triggerBase.entityContents.Contains(player))
return;

triggerBase.entityContents.Remove(player);
player.LeaveTrigger(triggerBase);

if (triggerBase is not TriggerSafeZone)
return;

if (!player.InSafeZone())
player.SetPlayerFlag(BasePlayer.PlayerFlags.SafeZone, false);
}

private void AddPlayersToTriggers()
{
for (int i = 0; i < players.Count; i++)
{
BasePlayer player = players[i];

if (safeZone != null)
AddToTrigger(safeZone.Trigger, player);

if (radiation != null)
AddToTrigger(radiation.Trigger, player);

if (comfort != null)
AddToTrigger(comfort.Trigger, player);

if (temperature != null)
AddToTrigger(temperature.Trigger, player);
}
}

private void RemovePlayersFromTriggers()
{
for (int i = 0; i < players.Count; i++)
{
BasePlayer player = players[i];

if (safeZone != null)
RemoveFromTrigger(safeZone.Trigger, player);

if (radiation != null)
RemoveFromTrigger(radiation.Trigger, player);

if (comfort != null)
RemoveFromTrigger(comfort.Trigger, player);

if (temperature != null)
RemoveFromTrigger(temperature.Trigger, player);
}
}

private class ChildSphereTrigger<T> where T : TriggerBase
{
public GameObject Object { get; private set; }

public SphereCollider Collider { get; private set; }

public T Trigger { get; private set; }

public ChildSphereTrigger(GameObject parent, string name)
{
Object = parent.CreateChild();
Object.name = name;
Object.layer = (int)Layer.TransparentFX;

Collider = Object.AddComponent<SphereCollider>();
Collider.isTrigger = true;

Trigger = Object.AddComponent<T>();
Trigger.interestLayers = 0;
}

public void Destroy() => UnityEngine.Object.Destroy(Object);
}
#endregion

#region Autolights
private bool isLightsOn = false;

private void InitializeAutoLights()
{
if (HasFlag(ZoneFlags.AlwaysLights))
{
isLightsOn = true;

InvokeHandler.CancelInvoke(this, CheckAlwaysLights);
InvokeHandler.InvokeRandomized(this, CheckAlwaysLights, 5f, 60f, 10f);
}
else if (HasFlag(ZoneFlags.AutoLights))
{
InvokeHandler.CancelInvoke(this, CheckLights);
InvokeHandler.InvokeRandomized(this, CheckLights, 5f, 20f, 10f);
}
}

private void CheckAlwaysLights()
{
ServerMgr.Instance.StartCoroutine(ToggleLights(true));
}

private void CheckLights()
{
float currentTime = TOD_Sky.Instance.Cycle.Hour;

bool shouldBeActive = currentTime > Configuration.AutoLights.OnTime || currentTime < Configuration.AutoLights.OffTime;

if (shouldBeActive == isLightsOn)
return;

isLightsOn = shouldBeActive;
ServerMgr.Instance.StartCoroutine(ToggleLights(isLightsOn));
}

private IEnumerator ToggleLights(bool active)
{
while (isTogglingLights)
yield return null;

isTogglingLights = true;

bool requiresFuel = Configuration.AutoLights.RequiresFuel;

for (int i = 0; i < entities.Count; i++)
{
if (ToggleLight(entities[i], active, requiresFuel))
yield return CoroutineEx.waitForEndOfFrame;
}

isTogglingLights = false;
}

private bool ToggleLight(BaseEntity baseEntity, bool active, bool requiresFuel)
{
BaseOven baseOven = baseEntity as BaseOven;
if (baseOven)
{
if (active)
{
if (!baseOven.IsOn())
{
if ((requiresFuel && baseOven.FindBurnable() != null) || !requiresFuel)
baseOven.SetFlag(BaseEntity.Flags.On, true);
}
}
else
{
if (baseOven.IsOn())
baseOven.StopCooking();
}

return true;
}

SearchLight searchLight = baseEntity as SearchLight;
if (searchLight)
{
if (active)
{
if (!searchLight.IsOn())
searchLight.SetFlag(BaseEntity.Flags.On, true);
}
else
{
if (searchLight.IsOn())
searchLight.SetFlag(BaseEntity.Flags.On, false);
}

return true;
}

return false;
}
#endregion

#region Entity Detection
private void OnTriggerEnter(Collider col)
{
if (!definition.Enabled || !col || !col.gameObject)
return;

BaseEntity baseEntity = col.gameObject.ToBaseEntity();
if (!baseEntity || !baseEntity.IsValid())
return;

if (baseEntity is BasePlayer { IsNpc: false } player)
{
Instance?.OnPlayerEnterZone(player, this);

if (parent)
Instance?.UpdateZoneEntityFlags(this);

return;
}

Instance?.OnEntityEnterZone(baseEntity, this);
}

private void OnTriggerExit(Collider col)
{
if (!definition.Enabled || !col || !col.gameObject)
return;

BaseEntity baseEntity = col.gameObject.ToBaseEntity();
if (!baseEntity || !baseEntity.IsValid())
return;

if (baseEntity is BasePlayer { IsNpc: false } player)
{
Instance?.OnPlayerExitZone(player, this);

return;
}

Instance?.OnEntityExitZone(baseEntity, this);
}

public void OnPlayerEnterZone(BasePlayer player)
{
if (!players.Contains(player))
players.Add(player);

if (zonedPlayers.TryGetValue(player.userID, out EntityZones entityZone))
entityZones[player.userID] = entityZone;

if (safeZone != null)
AddToTrigger(safeZone.Trigger, player);

if (radiation != null)
AddToTrigger(radiation.Trigger, player);

if (comfort != null)
AddToTrigger(comfort.Trigger, player);

if (temperature != null)
AddToTrigger(temperature.Trigger, player);
}

public void OnPlayerExitZone(BasePlayer player)
{
players.Remove(player);
entityZones.Remove(player.userID);

if (safeZone != null)
RemoveFromTrigger(safeZone.Trigger, player);

if (radiation != null)
RemoveFromTrigger(radiation.Trigger, player);

if (comfort != null)
RemoveFromTrigger(comfort.Trigger, player);

if (temperature != null)
RemoveFromTrigger(temperature.Trigger, player);
}

public void OnEntityEnterZone(BaseEntity baseEntity)
{
entities.Add(baseEntity);

if (zonedEntities.TryGetValue(baseEntity.net.ID, out EntityZones entityZone))
entityZones[baseEntity.net.ID.Value] = entityZone;

if (HasFlag(ZoneFlags.NoDecay))
{
DecayEntity decayEntity = baseEntity.GetComponentInParent<DecayEntity>();
if (decayEntity)
{
decayEntity.decay = null;
}
}

if (HasFlag(ZoneFlags.NoStability))
{
if (baseEntity is StabilityEntity entity)
{
entity.grounded = true;
}
}

if (HasFlag(ZoneFlags.NpcFreeze) && baseEntity.IsNpc)
{
if (baseEntity is BaseAnimalNPC animalNpc)
{
animalNpc.brain.SetEnabled(false);
return;
}

if (baseEntity is global::HumanNPC humanNpc)
{
humanNpc.Brain.SetEnabled(false);
return;
}

if (baseEntity is ScarecrowNPC scarecrowNpc)
{
scarecrowNpc.Brain.SetEnabled(false);
return;
}

if (baseEntity is BaseNpc npc)
{
npc.CancelInvoke(npc.TickAi);
return;
}
}

if (baseEntity is SmartSwitch or ElectricSwitch or RFReceiver)
{
ioEntities.Add((IOEntity)baseEntity);

if (HasFlag(ZoneFlags.PoweredSwitches))
{
((IOEntity)baseEntity).SetFlag(BaseEntity.Flags.Reserved8, true);
((IOEntity)baseEntity).currentEnergy = int.MaxValue;
}
}

if (HasFlag(ZoneFlags.AlwaysLights) || (HasFlag(ZoneFlags.AutoLights) && isLightsOn))
{
if (baseEntity is BaseOven or SearchLight)
{
ToggleLight(baseEntity, true, Configuration.AutoLights.RequiresFuel);
}
}
}

public void OnEntityExitZone(BaseEntity baseEntity, bool resetDecay, bool isDead = false)
{
entities.Remove(baseEntity);

entityZones.Remove(baseEntity.net.ID.Value);

if (isDead)
return;

if (resetDecay)
{
if (HasFlag(ZoneFlags.NoDecay))
{
DecayEntity decayEntity = baseEntity.GetComponentInParent<DecayEntity>();
if (decayEntity)
{
decayEntity.decay = PrefabAttribute.server.Find<Decay>(decayEntity.prefabID);
}
}
}

if (HasFlag(ZoneFlags.NpcFreeze) && baseEntity.IsNpc)
{
if (baseEntity is BaseAnimalNPC animalNpc)
{
animalNpc.brain.SetEnabled(true);
return;
}

if (baseEntity is global::HumanNPC humanNpc)
{
humanNpc.Brain.SetEnabled(true);
return;
}

if (baseEntity is ScarecrowNPC scarecrowNpc)
{
scarecrowNpc.Brain.SetEnabled(true);
return;
}

if (baseEntity is BaseNpc npc)
{
npc.InvokeRandomized(npc.TickAi, 0.1f, 0.1f, 0.00500000035f);
return;
}
}

if (baseEntity is SmartSwitch or ElectricSwitch or RFReceiver)
{
IOEntity ioEntity = (IOEntity)baseEntity;
ioEntities.Remove(ioEntity);

if (HasFlag(ZoneFlags.PoweredSwitches))
{
ioEntity.SetFlag(BaseEntity.Flags.Reserved8, false);
ioEntity.currentEnergy = 0;

for (int i = 0; i < ioEntity.inputs.Length; i++)
{
IOEntity fromEntity = ioEntity.inputs[i].connectedTo.Get();
if (fromEntity)
fromEntity.MarkDirtyForceUpdateOutputs();
}
}
}

if (!HasFlag(ZoneFlags.AlwaysLights) && (!HasFlag(ZoneFlags.AutoLights) || !isLightsOn))
return;

if (baseEntity is BaseOven or SearchLight)
{
ToggleLight(baseEntity, false, false);
}
}
#endregion

#region IO Power
private void IOTick()
{
for (int i = 0; i < ioEntities.Count; i++)
{
IOEntity ioEntity = ioEntities[i];

if (!ioEntity || ioEntity.IsDestroyed)
continue;

ioEntity.SetFlag(BaseEntity.Flags.Reserved8, true);
ioEntity.currentEnergy = int.MaxValue;
}
}
#endregion

#region Vehicle Enter/Exit
public bool TryReverseVelocity(BaseEntity baseEntity)
{
if (Time.frameCount == creationFrame)
return false;

if (baseEntity is not BaseVehicle baseVehicle)
return false;

if (baseVehicle is BaseVehicleModule module)
baseVehicle = module.Vehicle;

if (!CanReverseVelocity(baseVehicle))
return false;

if (!ReverseVelocity(baseVehicle))
return false;

lastReversedTimes[baseVehicle] = Time.time;
return true;

}

private bool CanReverseVelocity(BaseVehicle baseVehicle)
{
if (lastReversedTimes.TryGetValue(baseVehicle, out float lastReversedTime))
return Time.time - lastReversedTime > 0.5f;

return true;
}
#endregion

#region Helpers
public bool HasPermission(BasePlayer player)
{
if (Instance == null)
{
Debug.LogError($"[ZoneManager] Zone attempted to check player permission, but plugin instance is null...");
return false;
}

return string.IsNullOrEmpty(definition.Permission) || Instance.permission.UserHasPermission(player.UserIDString, definition.Permission);
}

public bool CanLeaveZone(BasePlayer player) => !keepInList.Contains(player.userID.Get());

public bool CanEnterZone(BasePlayer player) => HasPermission(player) || !CanLeaveZone(player) || whitelist.Contains(player.userID.Get());
#endregion

#region Flags
public void AddFlag(int flag)
{
definition.Flags.AddFlag(flag);
OnZoneFlagsChanged();
}

public void RemoveFlag(int flag)
{
definition.Flags.RemoveFlag(flag);
OnZoneFlagsChanged();
}

public bool HasFlag(int flag) => definition.Flags.HasFlag(flag) && !disabledFlags.HasFlag(flag);

public bool HasDisabledFlag(int flag) => disabledFlags.HasFlag(flag);

public void AddDisabledFlag(int flag)
{
disabledFlags.AddFlag(flag);
OnZoneFlagsChanged();
}

public void RemoveDisabledFlag(int flag)
{
disabledFlags.AddFlag(flag);
OnZoneFlagsChanged();
}
#endregion

#region Zone Definition
public class Definition
{
public string Id { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Name { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
public float Radius { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
public float Radiation { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
public float Comfort { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
public float Temperature { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
public bool SafeZone { get; set; }

public Vector3 Location { get; set; }

public Vector3 Size { get; set; }

public Vector3 Rotation { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
public string ParentID { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
public string EnterMessage { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
public string LeaveMessage { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Permission { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
public string EjectSpawns { get; set; }

[DefaultValue(true)]
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
public bool Enabled { get; set; } = true;

[JsonIgnore]
public Plugin Owner { get; private set; }

[JsonIgnore]
public bool IsTemporary { get; private set; }

public ZoneFlags Flags { get; set; }

public Definition() { }

public Definition(Vector3 position)
{
Radius = 20f;
Location = position;

Flags = new ZoneFlags();
}

public void WithOwner(Plugin owner)
{
Owner = owner;
IsTemporary = true;
}
}
#endregion
}
#endregion

#region Entity Management
private void OnPlayerEnterZone(BasePlayer player, Zone zone)
{
if (!player || IsNpc(player))
return;

if (!zone.CanEnterZone(player))
{
EjectPlayer(player, zone);
return;
}

if (HasFlag(zone, ZoneFlags.Eject))
{
if (!CanBypass(player, ZoneFlags.Eject) && !IsAdmin(player))
{
EjectPlayer(player, zone);
return;
}
}

//if (HasFlag(zone, ZoneFlags.KeepVehiclesOut) && player.isMounted && ReverseVelocity(player.GetMountedVehicle()))
//{
// SendMessage(player, Message("novehiclesenter", player.UserIDString));
// return;
//}

if (player.IsSleeping() && !player.IsConnected)
{
if (HasFlag(zone, ZoneFlags.KillSleepers))
{
if (!CanBypass(player, ZoneFlags.KillSleepers) && !IsAdmin(player))
{
player.Die();
return;
}
}

if (HasFlag(zone, ZoneFlags.EjectSleepers))
{
if (!CanBypass(player, ZoneFlags.EjectSleepers) && !IsAdmin(player))
{
EjectPlayer(player, zone);
return;
}
}
}

if (HasFlag(zone, ZoneFlags.Kill))
{
if (!CanBypass(player, ZoneFlags.Kill) && !IsAdmin(player))
{
player.Die();
return;
}
}

if (!zonedPlayers.TryGetValue(player.userID, out EntityZones entityZones))
zonedPlayers[player.userID] = entityZones = new EntityZones();

if (!entityZones.EnterZone(zone))
return;

if (zone.parent)
entityZones.UpdateFlags();
else entityZones.AddFlags(zone.definition.Flags);

zone.OnPlayerEnterZone(player);

UpdateMetabolismForPlayer(player);

if (!string.IsNullOrEmpty(zone.definition.EnterMessage))
{
if (PopupNotifications != null && Configuration.Notifications.Popups)
PopupNotifications.Call("CreatePopupNotification", string.Format(zone.definition.EnterMessage, player.displayName), player);
else SendMessage(player, zone.definition.EnterMessage, player.displayName);
}

Interface.CallHook("OnEnterZone", zone.definition.Id, player);
}

private void OnPlayerExitZone(BasePlayer player, Zone zone)
{
if (!player || IsNpc(player))
return;

//if (HasFlag(zone, ZoneFlags.KeepVehiclesIn) && player.isMounted && ReverseVelocity(player.GetMountedVehicle()))
//{
// SendMessage(player, Message("novehiclesleave", player.UserIDString));
// return;
//}

if (!zone.CanLeaveZone(player))
{
AttractPlayer(player, zone);
return;
}

if (!zonedPlayers.TryGetValue(player.userID, out EntityZones entityZones))
return;

entityZones.LeaveZone(zone);

if (entityZones.ShouldRemove())
zonedPlayers.Remove(player.userID);
else entityZones.UpdateFlags();

zone.OnPlayerExitZone(player);

UpdateMetabolismForPlayer(player);

if (!string.IsNullOrEmpty(zone.definition.LeaveMessage))
{
if (PopupNotifications != null && Configuration.Notifications.Popups)
PopupNotifications.Call("CreatePopupNotification", string.Format(zone.definition.LeaveMessage, player.displayName), player);
else SendMessage(player, zone.definition.LeaveMessage, player.displayName);
}

Interface.CallHook("OnExitZone", zone.definition.Id, player);
}

private void UpdateMetabolismForPlayer(BasePlayer player)
{
if (!player)
return;

MetabolismAttribute bleeding = player.metabolism.bleeding;
if (HasPlayerFlag(player, ZoneFlags.NoBleed))
{
bleeding.value = 0f;
bleeding.max = 0f;
}
else bleeding.max = 1f;

MetabolismAttribute oxygen = player.metabolism.oxygen;
if (HasPlayerFlag(player, ZoneFlags.NoDrown))
{
oxygen.value = 1f;
oxygen.min = 1f;
}
else oxygen.min = 0f;

MetabolismAttribute poison = player.metabolism.poison;
if (HasPlayerFlag(player, ZoneFlags.NoPoison))
{
poison.value = 0f;
poison.max = 0f;
}
else poison.max = 100f;

MetabolismAttribute calories = player.metabolism.calories;
if (HasPlayerFlag(player, ZoneFlags

Merged post

I also modifed Restore upon death, but for some reason it isnt letting me post it here