Иконка ресурса

FREE ZoneManager : 3.1.10 3.1.10

Нет прав для скачивания
C#:
// PATCHED by penguin-rust-base: fixed removed Oxide APIs (arg.Player()→arg.Connection?.player, userID.Get()→userID)
// PATCHED by penguin-rust-base: performance opts: sqrMagnitude, HashSet<Zone> EntityZones, ZoneGrid spatial index, HashSet queue dedup, bounds pre-check, cast not GetComponent, List capacity hints
// PATCHED by penguin-rust-base: CanCraft ItemCrafter as BasePlayer->GetComponent<BasePlayer>() (no inheritance relationship)
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;
        // OPT3: spatial grid for proximity culling in UpdatePlayerZones
        private readonly ZoneGrid _zoneGrid = new ZoneGrid();
        private readonly HashSet<Zone> _gridCandidates = new HashSet<Zone>();
        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);
                _zoneGrid.RemoveZone(zone);
                // OPT3: remove from spatial grid
                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;
            // OPT2: Zones is now HashSet — copy to list for safe reverse iteration while calling exit hooks
            var exitList = Pool.Get<List<Zone>>();
            exitList.AddRange(entityZones.Zones);
            for (int i = exitList.Count - 1; i >= 0; i--) {
                Zone zone = exitList[i];
                if (!zone) continue;
                zone.OnEntityExitZone(baseEntity, false, true);
            }
            Pool.FreeUnmanaged(ref exitList);
            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();
            _zoneGrid.Clear();
            // OPT3: clear spatial grid
            _gridCandidates.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>();
            // OPT4: O(1) membership test replacing O(n) Queue.Contains
            private readonly HashSet<BasePlayer> _queuedPlayers = new HashSet<BasePlayer>();
            private const float MAX_MS = 0.25f;
            private void OnDestroy() {
                playerUpdateQueue.Clear();
                _queuedPlayers.Clear();
            }
            public void QueueUpdate(BasePlayer player) {
                // OPT4: HashSet.Add returns false if already present — O(1) vs old O(n) Contains
                if (_queuedPlayers.Add(player)) playerUpdateQueue.Enqueue(player);
            }
            public void Reset() {
                playerUpdateQueue.Clear();
                _queuedPlayers.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();
                    _queuedPlayers.Remove(player);
                    // OPT4: keep set in sync
                    if (!player || !player.IsConnected) continue;
                    Instance?.UpdatePlayerZones(player);
                    var p = player;
                    // OPT6: local copy avoids closure capturing a stale variable
                    InvokeHandler.Invoke(this, () => QueueUpdate(p), 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?.ShortPrefabName == "cupboard.tool.deployed") {
                    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, int 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, 0);
            // 0 = 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.Connection?.player as BasePlayer;
            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;
            Vector3 playerPos = player.transform.position;
            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, playerPos)) OnPlayerExitZone(player, zone);
                    }
                    else {
                        // OPT1: sqrMagnitude avoids sqrt in Vector3.Distance
                        float r = zone.definition.Radius;
                        if ((playerPos - zone.transform.position).sqrMagnitude > r * r) OnPlayerExitZone(player, zone);
                    }
                }
                Pool.FreeUnmanaged(ref list);
            }
            // OPT3: use spatial grid to cull candidate zones instead of iterating all zones
            _zoneGrid.GetCandidates(playerPos, 200f, _gridCandidates);
            foreach (Zone zone in _gridCandidates) {
                if (!zone) continue;
                if (entityZones != null && entityZones.Zones.Contains(zone)) continue;
                if (zone.definition.Size != Vector3.zero) {
                    if (IsInsideBounds(zone, playerPos)) OnPlayerEnterZone(player, zone);
                }
                else {
                    // OPT1: sqrMagnitude avoids sqrt in Vector3.Distance
                    float r = zone.definition.Radius;
                    if ((playerPos - zone.transform.position).sqrMagnitude <= r * r) OnPlayerEnterZone(player, zone);
                }
            }
            if (player.HasPlayerFlag(BasePlayer.PlayerFlags.SafeZone) && !player.InSafeZone()) player.SetPlayerFlag(BasePlayer.PlayerFlags.SafeZone, false);
        }
        // OPT5: AABB bounds.Contains is a fast early-reject; ClosestPoint only called when inside the bounding box
        private bool IsInsideBounds(Zone zone, Vector3 worldPos) {
            if (zone?.collider == null) return false;
            return zone.collider.bounds.Contains(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);
                _zoneGrid.AddZone(zone);
                // OPT3: register in spatial grid
            }
            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) || zone.whitelist.Contains(player.userID)) 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;
                    }
                  
// ... [Code preview truncated due to length. Full source in attached file] ...

Автор
15FPS
Скачиваний
0
Просмотры
1
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок
Сверху