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

FREE NightZombies : 3.4.3 3.4.3

Нет прав для скачивания
C#:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using Random = UnityEngine.Random;
using Physics = UnityEngine.Physics;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Core.Configuration;
using ConVar;
using Newtonsoft.Json;
namespace Oxide.Plugins {
    [Info("Night Zombies", "0x89A", "3.4.3")]
    [Description("Spawns and kills zombies at set times")]
    class NightZombies : RustPlugin {
        //private const string DeathSound = "assets/prefabs/npc/murderer/sound/death.prefab";
        private const string AdminPermission = "nightzombies.admin";
        private const string RemoveMeMethodName = nameof(DroppedItemContainer.RemoveMe);
        private const int GrenadeItemId = 1840822026;
        private static NightZombies _instance;
        private static Configuration _config;
        private DynamicConfigFile _dataFile;
        [PluginReference("Kits")]
        private Plugin _kits;
        [PluginReference("Vanish")]
        private Plugin _vanish;
        private SpawnController _spawnController;
#region -Init-
        private void Init() {
            _instance = this;
            permission.RegisterPermission(AdminPermission, this);
            RegisterCommands();
            _spawnController = new SpawnController();
            //Read saved number of days since last spawn
            _dataFile = Interface.Oxide.DataFileSystem.GetFile("NightZombies-daysSinceSpawn");
            try {
                _spawnController.DaysSinceLastSpawn = _dataFile.ReadObject<int>();
            }
            catch  //Default to 0 if error reading or data broken
            {
                PrintWarning("Failed to load saved days since last spawn, defaulting to 0");
                _spawnController.DaysSinceLastSpawn = 0;
            }
            if (_config.Behaviour.SentriesAttackZombies) {
                Unsubscribe(nameof(OnTurretTarget));
            }
            if (_config.Destroy.SpawnLoot) {
                Unsubscribe(nameof(OnCorpsePopulate));
            }
            if (_config.Behaviour.Ignored.Count == 0 && !_config.Behaviour.IgnoreHumanNpc && _config.Behaviour.AttackSleepers) {
                Unsubscribe(nameof(OnNpcTarget));
            }
        }
        private void OnServerInitialized() {
            //Warn if kits is not loaded
            if (!_kits?.IsLoaded ?? false) {
                PrintWarning("Kits is not loaded, custom kits will not work");
            }
            // Start time check even when always spawned so the initial spawn cycle still runs.
            if (_config.Spawn.SpawnTime >= 0 && _config.Spawn.DestroyTime >= 0) {
                TOD_Sky.Instance.Components.Time.OnMinute += _spawnController.TimeTick;
                TOD_Sky.Instance.Components.Time.OnDay += OnDay;
                _spawnController.StartWaveTimer();
                NextTick(_spawnController.TimeTick);
            }
        }
        private void Unload() {
            TOD_Sky.Instance.Components.Time.OnMinute -= _spawnController.TimeTick;
            TOD_Sky.Instance.Components.Time.OnDay -= OnDay;
            _dataFile.WriteObject(_spawnController.DaysSinceLastSpawn);
            _spawnController?.Shutdown();
            _config = null;
            _instance = null;
        }
        private void OnDay() => _spawnController.DaysSinceLastSpawn++;
#endregion
#region -Oxide Hooks-
        private void NightZombiesChatCommand(BasePlayer player, string command, string[] args) {
            if (!HasAdminAccess(player)) {
                SendReply(player, "You do not have permission to use this command.");
                return;
            }
            SendReply(player, HandleAdminCommand(command));
        }
        private void NightZombiesConsoleCommand(ConsoleSystem.Arg arg) {
            BasePlayer player = arg.Player();
            if (player != null && !HasAdminAccess(player)) {
                SendReply(player, "You do not have permission to use this command.");
                return;
            }
            string response = HandleAdminCommand(arg.cmd?.Name ?? string.Empty);
            if (player != null) {
                SendReply(player, response);
                return;
            }
            Puts(response);
        }
        private object OnNpcTarget(ScarecrowNPC npc, BaseEntity target) {
            return CanAttack(target);
        }
        private object OnNpcTarget(ScientistNPC npc, BaseEntity target) {
            return CanAttack(target);
        }
        private object OnTurretTarget(NPCAutoTurret turret, ScarecrowNPC entity) {
            if (entity == null) {
                return null;
            }
            return true;
        }
        private object OnTurretTarget(NPCAutoTurret turret, ScientistNPC entity) {
            if (entity == null) {
                return null;
            }
            return true;
        }
        private object OnPlayerDeath(ScarecrowNPC scarecrow, HitInfo info) {
            return HandleNpcDeath(scarecrow);
        }
        private object OnPlayerDeath(ScientistNPC scientist, HitInfo info) {
            return HandleNpcDeath(scientist);
        }
        private object HandleNpcDeath(BasePlayer npc) {
            //Effect.server.Run(DeathSound, npc.transform.position);
            _spawnController.NpcDied(npc);
            if (_config.Destroy.LeaveCorpseKilled) {
                return null;
            }
            NextTick(() => {
                if (npc == null || npc.IsDestroyed) {
                    return;
                }
                npc.AdminKill();
            }
            );
            return true;
        }
        private BaseCorpse OnCorpsePopulate(ScarecrowNPC npcPlayer, NPCPlayerCorpse corpse) {
            return corpse;
        }
        private BaseCorpse OnCorpsePopulate(ScientistNPC npcPlayer, NPCPlayerCorpse corpse) {
            return corpse;
        }
        private void OnEntitySpawned(NPCPlayerCorpse corpse) {
            if (string.Equals(corpse.playerName, "Scarecrow", StringComparison.OrdinalIgnoreCase)) {
                corpse.playerName = _config.Spawn.Zombies.DisplayName;
            }
            else if (string.Equals(corpse.playerName, "Scientist", StringComparison.OrdinalIgnoreCase)) {
                corpse.playerName = _config.Spawn.Scientists.DisplayName;
            }
        }
        private void OnEntitySpawned(DroppedItemContainer container) {
            if (!_config.Destroy.HalfBodybagDespawn) {
                return;
            }
            NextTick(() => {
                if (container == null) {
                    return;
                }
                if (container.playerName == _config.Spawn.Zombies.DisplayName || container.playerName == _config.Spawn.Scientists.DisplayName) {
                    container.CancelInvoke(RemoveMeMethodName);
                    container.Invoke(RemoveMeMethodName, container.CalculateRemovalTime() / 2);
                }
            }
            );
        }
#endregion
#region -Helpers-
        private void RegisterCommands() {
            cmd.AddChatCommand(_config.Commands.ChatSpawnCommand, this, nameof(NightZombiesChatCommand));
            cmd.AddChatCommand(_config.Commands.ChatRespawnCommand, this, nameof(NightZombiesChatCommand));
            cmd.AddChatCommand(_config.Commands.ChatClearCommand, this, nameof(NightZombiesChatCommand));
            cmd.AddChatCommand(_config.Commands.ChatStatusCommand, this, nameof(NightZombiesChatCommand));
            cmd.AddConsoleCommand(_config.Commands.ConsoleSpawnCommand, this, nameof(NightZombiesConsoleCommand));
            cmd.AddConsoleCommand(_config.Commands.ConsoleRespawnCommand, this, nameof(NightZombiesConsoleCommand));
            cmd.AddConsoleCommand(_config.Commands.ConsoleClearCommand, this, nameof(NightZombiesConsoleCommand));
            cmd.AddConsoleCommand(_config.Commands.ConsoleStatusCommand, this, nameof(NightZombiesConsoleCommand));
        }
        private bool HasAdminAccess(BasePlayer player) {
            return player != null && (player.IsAdmin || permission.UserHasPermission(player.UserIDString, AdminPermission));
        }
        private string HandleAdminCommand(string command) {
            string normalized = (command ?? string.Empty).Trim().TrimStart('/').ToLowerInvariant();
            if (string.IsNullOrEmpty(normalized)) {
                return GetAdminUsage();
            }
            if (normalized == _config.Commands.ChatSpawnCommand || normalized == _config.Commands.ConsoleSpawnCommand) {
                return _spawnController.ForceSpawnWave();
            }
            if (normalized == _config.Commands.ChatRespawnCommand || normalized == _config.Commands.ConsoleRespawnCommand) {
                return _spawnController.ForceRespawnWave();
            }
            if (normalized == _config.Commands.ChatClearCommand || normalized == _config.Commands.ConsoleClearCommand) {
                return _spawnController.ForceClearWave();
            }
            if (normalized == _config.Commands.ChatStatusCommand || normalized == _config.Commands.ConsoleStatusCommand) {
                return _spawnController.GetStatus();
            }
            return GetAdminUsage();
        }
        private string GetAdminUsage() {
            return $"Chat: /{_config.Commands.ChatSpawnCommand}, /{_config.Commands.ChatRespawnCommand}, /{_config.Commands.ChatClearCommand}, /{_config.Commands.ChatStatusCommand} | Console: {_config.Commands.ConsoleSpawnCommand}, {_config.Commands.ConsoleRespawnCommand}, {_config.Commands.ConsoleClearCommand}, {_config.Commands.ConsoleStatusCommand}";
        }
        private object CanAttack(BaseEntity target) {
            if (target is ScientistNPC || target is ScarecrowNPC) {
                return true;
            }
            if (_config.Behaviour.Ignored.Contains(target.ShortPrefabName) || (_config.Behaviour.IgnoreHumanNpc && HumanNPCCheck(target)) || (!_config.Behaviour.AttackSleepers && target is BasePlayer player && player.IsSleeping())) {
                return true;
            }
            return null;
        }
        private bool HumanNPCCheck(BaseEntity target) {
            return target is BasePlayer player && !player.userID.IsSteamId() && target is not ScientistNPC && target is not ScarecrowNPC;
        }
#endregion
#region -Classes-
        private class SpawnController {
            private const string ScarecrowPrefab = "assets/prefabs/npc/scarecrow/scarecrow.prefab";
            private const string ScientistPrefab = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_roam.prefab";
            private const BindingFlags ReflectionFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
            private readonly Configuration.SpawnSettings _spawnConfig;
            private readonly List<SpawnProfile> _profiles = new();
            private readonly int _spawnLayerMask = LayerMask.GetMask("Default", "Tree", "Construction", "World", "Vehicle_Detailed", "Deployed");
            private readonly WaitForSeconds _waitTenthSecond = new(0.1f);
            private bool IsSpawnTime => _spawnConfig.AlwaysSpawned || (_spawnTime > _destroyTime ? Env.time >= _spawnTime || Env.time < _destroyTime : Env.time <= _spawnTime || Env.time > _destroyTime);
            private bool IsDestroyTime => !_spawnConfig.AlwaysSpawned && (_spawnTime > _destroyTime ? Env.time >= _destroyTime && Env.time < _spawnTime : Env.time <= _destroyTime && Env.time > _spawnTime);
            public int DaysSinceLastSpawn;
            private readonly float _spawnTime;
            private readonly float _destroyTime;
            private readonly int _waveRespawnMinutes;
            private readonly bool _leaveCorpse;
            private bool _spawned;
            private bool _roadPointsInitialized;
            private bool _roadWarningShown;
            private DateTime _nextWaveActionTimeUtc = DateTime.MinValue;
            private Coroutine _currentCoroutine;
            private readonly Dictionary<BasePlayer, SpawnProfile> _npcs = new();
            private readonly List<Vector3> _roadPoints = new();
            private class SpawnProfile {
                public readonly string Prefab;
                public readonly string DefaultName;
                public readonly string DisplayName;
                public readonly int Population;
                public readonly float Health;
                public readonly List<string> Kits;
                public SpawnProfile(string prefab, string defaultName, string displayName, int population, float health, List<string> kits) {
                    Prefab = prefab;
                    DefaultName = defaultName;
                    DisplayName = displayName;
                    Population = population;
                    Health = health;
                    Kits = kits ?? new List<string>();
                }
            }
            public SpawnController() {
                _spawnConfig = _config.Spawn;
                _spawnTime = _spawnConfig.SpawnTime;
                _destroyTime = _spawnConfig.DestroyTime;
                _waveRespawnMinutes = _spawnConfig.WaveRespawnMinutes;
                // These might not be available after the plugin is unloaded, will cause NRE if trying to access in RemoveNpcs
                _leaveCorpse = _config.Destroy.LeaveCorpse;
                AddProfile(ScarecrowPrefab, "Scarecrow", _spawnConfig.Zombies.DisplayName, _spawnConfig.Zombies.Population, _spawnConfig.Zombies.Health, _spawnConfig.Zombies.Kits);
                AddProfile(ScientistPrefab, "Scientist", _spawnConfig.Scientists.DisplayName, _spawnConfig.Scientists.Population, _spawnConfig.Scientists.Health, _spawnConfig.Scientists.Kits);
            }
            private void AddProfile(string prefab, string defaultName, string displayName, int population, float health, List<string> kits) {
                if (population <= 0) {
                    return;
                }
                _profiles.Add(new SpawnProfile(prefab, defaultName, displayName, population, health, kits));
            }
            private IEnumerator SpawnNpcs(bool stopCurrent = true) {
                if (_profiles.Count == 0) {
                    yield break;
                }
                if (stopCurrent && _currentCoroutine != null) {
                    ServerMgr.Instance.StopCoroutine(_currentCoroutine);
                }
                if (_waveRespawnMinutes > 0) {
                    PushNextWaveActionWindow();
                }
                _spawned = false;
                int scientistCount = 0;
                int scarecrowCount = 0;
                foreach (SpawnProfile profile in _profiles) {
                    int amountToSpawn = profile.Population - GetPopulation(profile);
                    for (int i = 0; i < amountToSpawn; i++) {
                        if (SpawnNpc(profile)) {
                            _spawned = true;
                            if (profile.DefaultName == "Scientist") {
                                scientistCount++;
                            }
                            else {
                                scarecrowCount++;
                            }
                        }
                        yield return _waitTenthSecond;
                    }
                }
                if (_config.Broadcast.DoBroadcast && _spawned) {
                    if (scientistCount > 0 && scarecrowCount > 0) {
                        Broadcast("ChatBroadcastSeparate", scientistCount, scarecrowCount);
                    }
                    else {
                        Broadcast("ChatBroadcast", scientistCount + scarecrowCount);
                    }
                }
                if (_spawned) {
                    DaysSinceLastSpawn = 0;
                    if (_waveRespawnMinutes > 0) {
                        PushNextWaveActionWindow();
                    }
                }
                _currentCoroutine = null;
            }
            private IEnumerator RemoveNpcs(bool shuttingDown = false, bool stopCurrent = true) {
                CleanupNpcs();
                if (_npcs.Count == 0) {
                    yield break;
                }
                if (stopCurrent && _currentCoroutine != null) {
                    ServerMgr.Instance.StopCoroutine(_currentCoroutine);
                }
                List<BasePlayer> npcs = new List<BasePlayer>(_npcs.Keys);
                foreach (BasePlayer npc in npcs) {
                    if (npc == null || npc.IsDestroyed) {
                        continue;
                    }
                    if (_leaveCorpse && !shuttingDown) {
                        npc.Die();
                    }
                    else {
                        npc.AdminKill();
                    }
                    yield return !shuttingDown ? _waitTenthSecond : null;
                }
                _npcs.Clear();
                _spawned = false;
                _currentCoroutine = null;
            }
            private IEnumerator RespawnWave() {
                yield return RemoveNpcs(false, false);
                yield return _waitTenthSecond;
                yield return SpawnNpcs(false);
                _currentCoroutine = null;
            }
            public void TimeTick() {
                CleanupNpcs();
                if (_currentCoroutine != null) {
                    return;
                }
                if (_waveRespawnMinutes > 0) {
                    if (_npcs.Count == 0 && !_spawned) {
                        if (CanSpawn() && CanRunWaveAction()) {
                            _currentCoroutine = ServerMgr.Instance.StartCoroutine(SpawnNpcs());
                        }
                        return;
                    }
                    if (IsDestroyTime && _spawned) {
                        _currentCoroutine = ServerMgr.Instance.StartCoroutine(RemoveNpcs());
                        return;
                    }
                    return;
                }
                if (_npcs.Count == 0) {
                    _spawned = false;
                }
                if (CanSpawn()) {
                    _currentCoroutine = ServerMgr.Instance.StartCoroutine(SpawnNpcs());
                }
                else if (_npcs.Count > 0 && IsDestroyTime && _spawned) {
                    _currentCoroutine = ServerMgr.Instance.StartCoroutine(RemoveNpcs());
                }
            }
            public void NpcDied(BasePlayer npc) {
                if (npc == null) {
                    return;
                }
                SpawnProfile profile;
                if (!_npcs.TryGetValue(npc, out profile)) {
                    return;
                }
                _npcs.Remove(npc);
                if (!IsSpawnTime) {
                    return;
                }
                if (_waveRespawnMinutes > 0) {
                    return;
                }
                SpawnNpc(profile);
            }
            public string ForceSpawnWave() {
                CleanupNpcs();
                if (_currentCoroutine != null) {
                    return "Night Zombies is busy processing another spawn action.";
                }
                if (_waveRespawnMinutes > 0) {
                    PushNextWaveActionWindow();
                }
                _currentCoroutine = ServerMgr.Instance.StartCoroutine(SpawnNpcs());
                return "Night Zombies spawn wave started.";
            }
            public string ForceRespawnWave() {
                CleanupNpcs();
                if (_currentCoroutine != null) {
                    return "Night Zombies is busy processing another spawn action.";
                }
                if (_waveRespawnMinutes > 0) {
                    PushNextWaveActionWindow();
                }
                _currentCoroutine = ServerMgr.Instance.StartCoroutine(RespawnWave());
                return "Night Zombies respawn wave started.";
            }
            public string ForceClearWave() {
                CleanupNpcs();
                if (_npcs.Count == 0) {
                    _spawned = false;
                    return "Night Zombies has no active NPCs to clear.";
                }
                if (_currentCoroutine != null) {
                    return "Night Zombies is busy processing another spawn action.";
                }
                if (_waveRespawnMinutes > 0) {
                    PushNextWaveActionWindow();
                }
                _currentCoroutine = ServerMgr.Instance.StartCoroutine(RemoveNpcs());
                return "Night Zombies clear wave started.";
            }
            public string GetStatus() {
                CleanupNpcs();
                int scientistCount = 0;
                int scarecrowCount = 0;
                foreach (SpawnProfile profile in _npcs.Values) {
                    if (profile.DefaultName == "Scientist") {
                        scientistCount++;
                    }
                    else if (profile.DefaultName == "Scarecrow") {
                        scarecrowCount++;
                    }
                }
                string nextWaveStatus = "off";
                if (_waveRespawnMinutes > 0) {
                    TimeSpan remaining = _nextWaveActionTimeUtc - DateTime.UtcNow;
                    if (remaining < TimeSpan.Zero) {
                        remaining = TimeSpan.Zero;
                    }
                    nextWaveStatus = $"{remaining.Minutes:D2}:{remaining.Seconds:D2}";
                    if (remaining.TotalHours >= 1) {
                        nextWaveStatus = $"{(int)remaining.TotalHours:D2}:{remaining.Minutes:D2}:{remaining.Seconds:D2}";
                    }
                }
                return $"Night Zombies status: active={_npcs.Count}, scarecrows={scarecrowCount}, scientists={scientistCount}, busy={(_currentCoroutine != null ? "yes" : "no")}, nextWave={nextWaveStatus}.";
            }
            public void StartWaveTimer() {
                if (_waveRespawnMinutes <= 0) {
                    return;
                }
                _instance.timer.Every(_waveRespawnMinutes * 60f, RealTimeWaveTick);
            }
            private void RealTimeWaveTick() {
                CleanupNpcs();
                if (_currentCoroutine != null || !IsSpawnTime) {
                    return;
                }
                if (!CanRunWaveAction()) {
                    return;
                }
                if (_npcs.Count == 0) {
                    _spawned = false;
                    if (CanSpawn()) {
                        _currentCoroutine = ServerMgr.Instance.StartCoroutine(SpawnNpcs());
                    }
                    return;
                }
                if (_spawned) {
                    _currentCoroutine = ServerMgr.Instance.StartCoroutine(RespawnWave());
                }
            }
            private bool CanRunWaveAction() {
                return DateTime.UtcNow >= _nextWaveActionTimeUtc;
            }
            private void PushNextWaveActionWindow() {
                _nextWaveActionTimeUtc = DateTime.UtcNow.AddMinutes(_waveRespawnMinutes);
            }
#region -Util-
            private bool SpawnNpc(SpawnProfile profile) {
                if (GetPopulation(profile) >= profile.Population) {
                    return false;
                }
                Vector3 position;
                if (!TryGetSpawnPosition(out position)) {
                    return false;
                }
                BasePlayer npc = GameManager.server.CreateEntity(profile.Prefab, position) as BasePlayer;
                if (!npc) {
                    return false;
                }
                npc.Spawn();
                npc.displayName = profile.DisplayName;
                if (npc.TryGetComponent(out BaseNavigator navigator)) {
                    navigator.ForceToGround();
                    navigator.PlaceOnNavMesh(0);
                }
                npc.SetMaxHealth(profile.Health);
                npc.SetHealth(profile.Health);
                if (_instance._kits != null && profile.Kits.Count > 0) {
                    npc.inventory.containerWear.Clear();
                    ItemManager.DoRemoves();
                    _instance._kits.Call("GiveKit", npc, profile.Kits.GetRandom());
                }
                if (!_config.Behaviour.ThrowGrenades) {
                    List<Item> items = new List<Item>();
                    npc.inventory.FindItemsByItemID(items, GrenadeItemId);
                    foreach (Item item in items) {
                        item.Remove();
                    }
                    ItemManager.DoRemoves();
                }
                _npcs[npc] = profile;
                _instance.Puts($"Spawned {profile.DefaultName} '{profile.DisplayName}' at {position} ({GetPopulation(profile)}/{profile.Population})");
                return true;
            }
            private bool TryGetSpawnPosition(out Vector3 position) {
                if (_spawnConfig.SpawnNearPlayers && BasePlayer.activePlayerList.Count >= _spawnConfig.MinNearPlayers) {
                    if (TryGetPositionNearAnyPlayer(out position)) {
                        return true;
                    }
                    return false;
                }
                Vector3 roadPosition;
                if (TryGetRoadPosition(null, out roadPosition)) {
                    position = roadPosition;
                    return true;
                }
                return TryGetRandomPosition(out position);
            }
            private bool TryGetPositionNearAnyPlayer(out Vector3 position) {
                position = Vector3.zero;
                for (int i = 0; i < 12; i++) {
                    BasePlayer player;
                    if (!GetRandomPlayer(out player)) {
                        return false;
                    }
                    Vector3 nearRoadPosition;
                    if (TryGetRoadPosition(player, out nearRoadPosition)) {
                        position = nearRoadPosition;
                        return true;
                    }
                    if (TryGetRandomPositionAroundPlayer(player, out position)) {
                        return true;
                    }
                }
                return false;
            }
            private bool GetRandomPlayer(out BasePlayer player) {
                List<BasePlayer> players = new List<BasePlayer>();
                foreach (BasePlayer bplayer in BasePlayer.activePlayerList) {
                    if (bplayer.IsFlying || _instance._vanish?.Call<bool>("IsInvisible", bplayer) == true) {
                        continue;
                    }
                    players.Add(bplayer);
                }
                if (players.Count == 0) {
                    player = null;
                    return false;
                }
                player = players.GetRandom();
                return player != null;
            }
            private bool TryGetRandomPosition(out Vector3 position) {
                position = Vector3.zero;
                for (int i = 0; i < 24; i++) {
                    float x = Random.Range(-TerrainMeta.Size.x / 2, TerrainMeta.Size.x / 2), z = Random.Range(-TerrainMeta.Size.z / 2, TerrainMeta.Size.z / 2), y = TerrainMeta.HeightMap.GetHeight(new Vector3(x, 0, z));
                    Vector3 candidate = new Vector3(x, y + 0.5f, z);
                    if (TryGetNavigablePosition(candidate, out candidate) && IsValidPosition(candidate)) {
                        position = candidate;
                        return true;
                    }
                }
                return false;
            }
            private bool TryGetRandomPositionAroundPlayer(BasePlayer player, out Vector3 position) {
                Vector3 playerPos = player.transform.position;
                position = Vector3.zero;
                float maxDist = _spawnConfig.MaxDistance;
                for (int i = 0; i < 24; i++) {
                    Vector3 candidate = new Vector3(Random.Range(playerPos.x - maxDist, playerPos.x + maxDist), 0, Random.Range(playerPos.z - maxDist, playerPos.z + maxDist));
                    candidate.y = TerrainMeta.HeightMap.GetHeight(candidate) + 0.5f;
                    if (TryGetNavigablePosition(candidate, out candidate) && IsValidPosition(candidate) && Vector3.Distance(playerPos, candidate) > _spawnConfig.MinDistance) {
                        position = candidate;
                        return true;
                    }
                }
                return false;
            }
            private bool TryGetRoadPosition(BasePlayer player, out Vector3 position) {
                position = Vector3.zero;
                if (!_spawnConfig.Roads.PreferRoads || Random.Range(0f, 100f) > _spawnConfig.Roads.RoadChance) {
                    return false;
                }
                CacheRoadPoints();
                if (_roadPoints.Count == 0) {
                    return false;
                }
                for (int i = 0; i < 24; i++) {
                    Vector3 roadPoint = _roadPoints[Random.Range(0, _roadPoints.Count)];
                    if (TryGetValidRoadOffsetPosition(roadPoint, player, out position)) {
                        return true;
                    }
                }
                return false;
            }
            private bool TryGetValidRoadOffsetPosition(Vector3 roadPoint, BasePlayer player, out Vector3 position) {
                position = Vector3.zero;
                float maxRoadOffset = _spawnConfig.Roads.MaxDistanceFromRoad;
                for (int i = 0; i < 8; i++) {
                    Vector2 offset = Random.insideUnitCircle * maxRoadOffset;
                    Vector3 candidate = new Vector3(roadPoint.x + offset.x, 0f, roadPoint.z + offset.y);
                    candidate.y = TerrainMeta.HeightMap.GetHeight(candidate) + 0.5f;
                    if (!TryGetNavigablePosition(candidate, out candidate) || !IsValidPosition(candidate)) {
                        continue;
                    }
                    if (player != null) {
                        float distance = Vector3.Distance(player.transform.position, candidate);
                        if (distance < _spawnConfig.MinDistance || distance > _spawnConfig.MaxDistance) {
                            continue;
                        }
                    }
                    position = candidate;
                    return true;
                }
                return false;
            }
            private bool TryGetNavigablePosition(Vector3 sourcePosition, out Vector3 position) {
                UnityEngine.AI.NavMeshHit navMeshHit;
                if (UnityEngine.AI.NavMesh.SamplePosition(sourcePosition, out navMeshHit, 12f, UnityEngine.AI.NavMesh.AllAreas)) {
                    position = navMeshHit.position;
                    position.y += 0.1f;
                    return true;
                }
                if (UnityEngine.AI.NavMesh.SamplePosition(sourcePosition, out navMeshHit, 30f, UnityEngine.AI.NavMesh.AllAreas)) {
                    position = navMeshHit.position;
                    position.y += 0.1f;
                    return true;
                }
                position = Vector3.zero;
                return false;
            }
            private void CacheRoadPoints() {
                if (_roadPointsInitialized) {
                    return;
                }
                _roadPointsInitialized = true;
                try {
                    object terrainPath = GetMemberValue(typeof(TerrainMeta), "Path");
                    AddRoadPoints(GetMemberValue(terrainPath, "Roads") ?? GetMemberValue(terrainPath, "roads"));
                    if (_roadPoints.Count == 0) {
                        AddRoadPoints(terrainPath);
                    }
                }
                catch (Exception ex) {
                    if (!_roadWarningShown) {
                        _roadWarningShown = true;
                        _instance.PrintWarning($"Failed to resolve road points, falling back to generic spawn positions: {ex.Message}");
                    }
                }
                if (_roadPoints.Count == 0 && !_roadWarningShown) {
                    _roadWarningShown = true;
                    _instance.PrintWarning("Could not resolve road points in TerrainMeta.Path, falling back to generic spawn positions");
                }
            }
            private void AddRoadPoints(object source) {
                if (source == null) {
                    return;
                }
                if (TryAddVectorPoints(source)) {
                    return;
                }
                if (source is not IEnumerable enumerable) {
                    return;
                }
                foreach (object entry in enumerable) {
                    if (entry == null) {
                        continue;
                    }
                    if (entry is Vector3 point) {
                        _roadPoints.Add(point);
                        continue;
                    }
                    object nestedPoints = GetMemberValue(entry, "Points") ?? GetMemberValue(entry, "points") ?? GetMemberValue(entry, "Path") ?? GetMemberValue(entry, "path");
                    TryAddVectorPoints(nestedPoints);
                }
            }
            private bool TryAddVectorPoints(object source) {
                if (source is not IEnumerable enumerable) {
                    return false;
                }
                bool addedAny = false;
                foreach (object entry in enumerable) {
                    if (entry is Vector3 point) {
                        _roadPoints.Add(point);
                        addedAny = true;
                        continue;
                    }
                    object nestedPosition = GetMemberValue(entry, "Position") ?? GetMemberValue(entry, "position");
                    if (nestedPosition is Vector3 vector) {
                        _roadPoints.Add(vector);
                        addedAny = true;
                    }
                }
                return addedAny;
            }
            private static object GetMemberValue(object source, string name) {
                if (source == null) {
                    return null;
                }
                Type type = source as Type ?? source.GetType();
                object instance = source is Type ? null : source;
                PropertyInfo property = type.GetProperty(name, ReflectionFlags);
                if (property != null) {
                    return property.GetValue(instance, null);
                }
                FieldInfo field = type.GetField(name, ReflectionFlags);
                if (field != null) {
                    return field.GetValue(instance);
                }
                return null;
            }
            private int GetPopulation(SpawnProfile profile) {
                CleanupNpcs();
                int count = 0;
                foreach (SpawnProfile activeProfile in _npcs.Values) {
                    if (activeProfile == profile) {
                        count++;
                    }
                }
                return count;
            }
            private void CleanupNpcs() {
                List<BasePlayer> invalidNpcs = null;
                foreach (KeyValuePair<BasePlayer, SpawnProfile> entry in _npcs) {
                    if (entry.Key != null && !entry.Key.IsDestroyed) {
                        continue;
                    }
                    invalidNpcs ??= new List<BasePlayer>();
                    invalidNpcs.Add(entry.Key);
                }
                if (invalidNpcs == null) {
                    return;
                }
                foreach (BasePlayer invalidNpc in invalidNpcs) {
                    _npcs.Remove(invalidNpc);
                }
            }
            private bool CanSpawn() {
                return !_spawned && DaysSinceLastSpawn >= _spawnConfig.Chance.Days && Random.Range(0f, 100f) < _spawnConfig.Chance.Chance && IsSpawnTime;
            }
            private bool IsValidPosition(Vector3 position) {
                return !AntiHack.TestInsideTerrain(position) && !IsInObject(position) && !IsInOcean(position);
            }
            private bool IsInObject(Vector3 position) {
                return Physics.OverlapSphere(position, 0.5f, _spawnLayerMask).Length > 0;
            }
            private bool IsInOcean(Vector3 position) {
                return WaterLevel.GetWaterDepth(position, true, true) > 0.25f;
            }
            private void Broadcast(string key, params object[] values) {
                foreach (BasePlayer player in BasePlayer.activePlayerList) {
                    player.ChatMessage(string.Format(_instance.GetMessage(key, player.UserIDString), values));
                }
            }
#endregion
            public void Shutdown() {
                ServerMgr.Instance.StartCoroutine(RemoveNpcs(true));
            }
        }
#endregion
#region -Configuration-
        private class Configuration {
            [JsonProperty("Spawn Settings")]
            public SpawnSettings Spawn = new SpawnSettings();
            [JsonProperty("Destroy Settings")]
            public DestroySettings Destroy = new DestroySettings();
            [JsonProperty("Behaviour Settings")]
            public BehaviourSettings Behaviour = new BehaviourSettings();
            [JsonProperty("Command Settings")]
            public CommandSettings Commands = new CommandSettings();
            [JsonProperty("Broadcast Settings")]
            public ChatSettings Broadcast = new ChatSettings();
            public class SpawnSettings {
                [JsonProperty("Always Spawned")]
                public bool AlwaysSpawned = false;
                [JsonProperty("Spawn Time")]
                public float SpawnTime = 19.8f;
                [JsonProperty("Destroy Time")]
                public float DestroyTime = 7.3f;
                [JsonProperty("Spawn near players")]
                public bool SpawnNearPlayers = false;
                [JsonProperty("Min pop for near player spawn")]
                public int MinNearPlayers = 10;
                [JsonProperty("Min distance from player")]
                public float MinDistance = 40;
                [JsonProperty("Max distance from player")]
                public float MaxDistance = 80f;
                [JsonProperty("Wave respawn interval (minutes, 0 = off)")]
                public int WaveRespawnMinutes = 0;
                [JsonProperty("Zombie Settings")]
                public ZombieSettings Zombies = new ZombieSettings();
                [JsonProperty("Scientist Settings")]
                public ScientistSettings Scientists = new ScientistSettings();
                [JsonProperty("Road Settings")]
                public RoadSettings Roads = new RoadSettings();
                public class ZombieSettings {
                    [JsonProperty("Display Name")]
                    public string DisplayName = "Scarecrow";
                    [JsonProperty("Scarecrow Population (total amount)")]
                    public int Population = 50;
                    [JsonProperty("Scarecrow Health")]
                    public float Health = 200f;
                    [JsonProperty("Scarecrow Kits")]
                    public List<string> Kits = new List<string>();
                }
                public class ScientistSettings {
                    [JsonProperty("Display Name")]
                    public string DisplayName = "Scientist";
                    [JsonProperty("Scientist Population (total amount)")]
                    public int Population = 20;
                    [JsonProperty("Scientist Health")]
                    public float Health = 150f;
                    [JsonProperty("Scientist Kits")]
                    public List<string> Kits = new List<string>();
                }
                public class RoadSettings {
                    [JsonProperty("Prefer roads")]
                    public bool PreferRoads = true;
                    [JsonProperty("Road spawn chance")]
                    public float RoadChance = 80f;
                    [JsonProperty("Max distance from road")]
                    public float MaxDistanceFromRoad = 16f;
                }
                [JsonProperty("Chance Settings")]
                public ChanceSetings Chance = new ChanceSetings();
                public class ChanceSetings {
                    [JsonProperty("Chance per cycle")]
                    public float Chance = 100f;
                    [JsonProperty("Days betewen spawn")]
                    public int Days = 0;
                }
            }
            public class DestroySettings {
                [JsonProperty("Leave Corpse, when destroyed")]
                public bool LeaveCorpse = false;
                [JsonProperty("Leave Corpse, when killed by player")]
                public bool LeaveCorpseKilled = true;
                [JsonProperty("Spawn Loot")]
                public bool SpawnLoot = true;
                [JsonProperty("Half bodybag despawn time")]
                public bool HalfBodybagDespawn = true;
            }
            public class BehaviourSettings {
                [JsonProperty("Attack sleeping players")]
                public bool AttackSleepers = false;
                [JsonProperty("Zombies attacked by outpost sentries")]
                public bool SentriesAttackZombies = true;
                [JsonProperty("Throw Grenades")]
                public bool ThrowGrenades = true;
                [JsonProperty("Ignore Human NPCs")]
                public bool IgnoreHumanNpc = true;
                [JsonProperty("Ignored entities (full entity shortname)")]
                public List<string> Ignored = new List<string>();
            }
            public class ChatSettings {
                [JsonProperty("Broadcast spawn amount")]
                public bool DoBroadcast = false;
            }
            public class CommandSettings {
                [JsonProperty("Chat spawn command")]
                public string ChatSpawnCommand = "nzspawn";
                [JsonProperty("Chat respawn command")]
                public string ChatRespawnCommand = "nzrespawn";
                [JsonProperty("Chat clear command")]
                public string ChatClearCommand = "nzclear";
                [JsonProperty("Chat status command")]
                public string ChatStatusCommand = "nzstatus";
                [JsonProperty("Console spawn command")]
                public string ConsoleSpawnCommand = "nightzombies.spawn";
                [JsonProperty("Console respawn command")]
                public string ConsoleRespawnCommand = "nightzombies.respawn";
                [JsonProperty("Console clear command")]
                public string ConsoleClearCommand = "nightzombies.clear";
                [JsonProperty("Console status command")]
                public string ConsoleStatusCommand = "nightzombies.status";
            }
        }
        protected override void LoadConfig() {
            base.LoadConfig();
            try {
                _config = Config.ReadObject<Configuration>();
                if (_config == null) throw new Exception();
                _config.Commands.ChatSpawnCommand = SanitizeChatCommand(_config.Commands.ChatSpawnCommand, "nzspawn");
                _config.Commands.ChatRespawnCommand = SanitizeChatCommand(_config.Commands.ChatRespawnCommand, "nzrespawn");
                _config.Commands.ChatClearCommand = SanitizeChatCommand(_config.Commands.ChatClearCommand, "nzclear");
                _config.Commands.ChatStatusCommand = SanitizeChatCommand(_config.Commands.ChatStatusCommand, "nzstatus");
                _config.Commands.ConsoleSpawnCommand = SanitizeConsoleCommand(_config.Commands.ConsoleSpawnCommand, "nightzombies.spawn");
                _config.Commands.ConsoleRespawnCommand = SanitizeConsoleCommand(_config.Commands.ConsoleRespawnCommand, "nightzombies.respawn");
                _config.Commands.ConsoleClearCommand = SanitizeConsoleCommand(_config.Commands.ConsoleClearCommand, "nightzombies.clear");
                _config.Commands.ConsoleStatusCommand = SanitizeConsoleCommand(_config.Commands.ConsoleStatusCommand, "nightzombies.status");
                if (_config.Spawn.SpawnTime >= 0 && _config.Spawn.DestroyTime >= 0) {
                    if (_config.Spawn.SpawnTime > 24 || _config.Spawn.SpawnTime < 0) {
                        PrintWarning("Invalid spawn time (must be in 24 hour time)");
                        _config.Spawn.SpawnTime = 19.5f;
                    }
                    if (_config.Spawn.DestroyTime > 24 || _config.Spawn.DestroyTime < 0) {
                        PrintWarning("Invalid destroy time (must be in 24 hour time)");
                        _config.Spawn.DestroyTime = 7f;
                    }
                }
                _config.Spawn.Roads.RoadChance = Mathf.Clamp(_config.Spawn.Roads.RoadChance, 0f, 100f);
                if (_config.Spawn.WaveRespawnMinutes < 0) {
                    PrintWarning("Invalid wave respawn interval, defaulting to 0");
                    _config.Spawn.WaveRespawnMinutes = 0;
                }
                if (_config.Spawn.Roads.MaxDistanceFromRoad < 0) {
                    PrintWarning("Invalid max road distance, defaulting to 16");
                    _config.Spawn.Roads.MaxDistanceFromRoad = 16f;
                }
                SaveConfig();
            }
            catch {
                PrintError("Failed to load _config, using default values");
                LoadDefaultConfig();
            }
        }
        private string SanitizeChatCommand(string command, string fallback) {
            command = (command ?? string.Empty).Trim().TrimStart('/');
            if (string.IsNullOrEmpty(command)) {
                PrintWarning($"Invalid chat command, defaulting to '{fallback}'");
                return fallback;
            }
            return command.ToLowerInvariant();
        }
        private string SanitizeConsoleCommand(string command, string fallback) {
            command = (command ?? string.Empty).Trim();
            if (string.IsNullOrEmpty(command)) {
                PrintWarning($"Invalid console command, defaulting to '{fallback}'");
                return fallback;
            }
            return command.ToLowerInvariant();
        }
        protected override void LoadDefaultConfig() => _config = new Configuration {
            Behaviour = new Configuration.BehaviourSettings {
                Ignored = new List<string> {
                    "scientistjunkpile.prefab", "scarecrow.prefab"
                }

// ... [Code preview truncated due to length. Full source in attached file] ...

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