using System;
using System.Collections.Generic;
using System.Linq;
using Facepunch;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Configuration;
using Rust;
using UnityEngine;
using Random = UnityEngine.Random;
using Time = UnityEngine.Time;

namespace Oxide.Plugins
{
    [Info("Dungeons", "Marte6", "1.0.0")]
    public class Dungeons : RustPlugin
    {
        private ConfigData _configData;
        private DynamicConfigFile _dataFile;
        private DungeonData _dungeonData;
        private Timer _autoSpawnTimer;
        private Timer _dungeonCheckTimer;
        private Dictionary<DungeonTierConfig, string> _tierNameMap;

        #region Initialization

        private void Init()
        {
            _tierNameMap = new Dictionary<DungeonTierConfig, string>
            {
                { _configData.Tiers.Easy, "Easy" },
                { _configData.Tiers.Normal, "Normal" },
                { _configData.Tiers.Medium, "Medium" },
                { _configData.Tiers.Hard, "Hard" },
                { _configData.Tiers.Nightmare, "Nightmare" },
            };

            LoadData();
            RemoveAllExistingDungeons();

            if (_configData.DungeonSpawn.EnableAutoSpawn)
            {
                StartAutoSpawnTimer();
            }

            StartDungeonCheckTimer();
        }

        private void LoadData()
        {
            _dataFile = Interface.Oxide.DataFileSystem.GetFile("Dungeons_Data");

            try
            {
                _dungeonData = _dataFile.ReadObject<DungeonData>() ?? new DungeonData();
            }
            catch
            {
                _dungeonData = new DungeonData();
            }
        }

        private void SaveData()
        {
            _dataFile.WriteObject(_dungeonData);
        }

        private void RemoveAllExistingDungeons()
        {
            foreach (var dungeon in _dungeonData.ActiveDungeons.ToList())
            {
                var basePortal = BaseNetworkable.serverEntities.Find(new NetworkableId(dungeon.PortalId)) as BasePortal;
                if (basePortal != null)
                {
                    RemoveDungeonEntities(dungeon);
                    basePortal.Kill(BaseNetworkable.DestroyMode.None);
                }
                RemoveDungeonMarkers(dungeon);
            }

            _dungeonData.ActiveDungeons.Clear();
            SaveData();
        }

        #endregion

        #region Hooks

        private object CanEntityTakeDamage(BaseCombatEntity entity, HitInfo hitInfo)
        {
            return IsDungeonCreatedEntity(entity) ? (object)true : null;
        }

        private object OnEntityTakeDamage(BaseCombatEntity victim, HitInfo info)
        {
            if (victim == null || info == null || info.Initiator == null || !IsDungeonCreatedEntity(victim))
                return null;

            if (IsDungeonCreatedEntity(info.Initiator))
            {
                return false;
            }

            return null;
        }

        private bool IsDungeonCreatedEntity(BaseEntity entity)
        {
            return entity != null && _dungeonData.ActiveDungeons.Any(dungeon => dungeon.EntityIds.Contains(entity.net.ID.Value));
        }

        private void OnServerSave() => SaveData();

        #endregion

        #region Dungeon Management

        [ChatCommand("createdungeon")]
        private void AutoSpawnDungeonCommand(BasePlayer player, string command, string[] args)
        {
            if (player != null && player.IsAdmin)
            {
                player.ChatMessage(Msg(player.UserIDString, "AttemptSpawnDungeons"));

                bool success = AutoSpawnDungeon();
                if (!success)
                {
                    player.ChatMessage(Msg(player.UserIDString, "DungeonSpawnFailed"));
                }
            }
        }

        [ChatCommand("removedungeons")]
        private void RemoveDungeonCommand(BasePlayer player, string command, string[] args)
        {
            if (player != null && player.IsAdmin)
            {
                RemoveAllExistingDungeons();
                player.ChatMessage(Msg(player.UserIDString, "AllDungeonsRemoved"));
            }
        }

        [ChatCommand("removeinactivedungeons")]
        private void RemoveInactiveDungeonCommand(BasePlayer player, string command, string[] args)
        {
            if (player != null && player.IsAdmin)
            {
                CheckDungeonsExistence();
                player.ChatMessage(Msg(player.UserIDString, "AllInactiveDungeonsRemoved"));
            }
        }

        private void StartAutoSpawnTimer()
        {
            _autoSpawnTimer = timer.Every(
                _configData.DungeonSpawn.AutoSpawnCycleInterval,
                () =>
                {
                    bool success = AutoSpawnDungeon();
                    if (!success)
                    {
                        Puts("Could not find a valid location to spawn a new dungeon.");
                    }
                }
            );
        }

        private void StartDungeonCheckTimer()
        {
            _dungeonCheckTimer = timer.Every(60f, CheckDungeonsExistence);
        }

        private bool AutoSpawnDungeon()
        {
            int activeDungeons = _dungeonData.ActiveDungeons.Count;

            if (activeDungeons < _configData.DungeonSpawn.MaxTotalActiveDungeons)
            {
                if (TryFindDungeonSpawnPoint(out var position, out var rotation))
                {
                    var selectedTier = CreateDungeon(position, rotation);
                    if (selectedTier != null)
                    {
                        _tierNameMap.TryGetValue(selectedTier, out string tierName);

                        CreateDungeonMarkers(position, tierName);

                        NotifyPlayersOfDungeonLocation(position, tierName);

                        return true;
                    }
                }
            }

            return false;
        }

        private void NotifyPlayersOfDungeonLocation(Vector3 position, string tierName)
        {
            string gridPosition = PhoneController.PositionToGridCoord(position);
            List<BasePlayer> players = new List<BasePlayer>(BasePlayer.activePlayerList);

            foreach (var player in players)
            {
                string message = Msg(player.UserIDString, "DungeonAppeared", tierName, gridPosition);
                player.ChatMessage(message);
                player.ShowToast(GameTip.Styles.Blue_Normal, message);
            }
        }

        private void CreateDungeonMarkers(Vector3 position, string tierName)
        {
            VendingMachineMapMarker dungeonVendingMarker = GameManager.server.CreateEntity("assets/prefabs/deployable/vendingmachine/vending_mapmarker.prefab", position) as VendingMachineMapMarker;
            dungeonVendingMarker.Spawn();
            dungeonVendingMarker.markerShopName = $"Dungeon: {tierName}";
            dungeonVendingMarker.SendNetworkUpdate();

            MapMarkerGenericRadius dungeonRadiusMarker = GameManager.server.CreateEntity("assets/prefabs/tools/map/genericradiusmarker.prefab", position) as MapMarkerGenericRadius;
            dungeonRadiusMarker.Spawn();
            dungeonRadiusMarker.alpha = 0.75f;
            dungeonRadiusMarker.radius = 0.5f;
            dungeonRadiusMarker.color2 = GetMarkerColorByTier(tierName);
            dungeonRadiusMarker.SendUpdate();
            dungeonRadiusMarker.SendNetworkUpdate();

            var marker = new DungeonMarker
            {
                VendingMarker = dungeonVendingMarker.net.ID.Value,
                RadiusMarker = dungeonRadiusMarker.net.ID.Value,
                Position = position,
                Tier = tierName,
            };

            _dungeonData.ActiveDungeonMarkers.Add(marker);
        }

        private Color GetMarkerColorByTier(string tierName)
        {
            return tierName switch
            {
                "Easy" => Color.green,
                "Normal" => Color.yellow,
                "Medium" => Color.yellow,
                "Hard" => Color.red,
                "Nightmare" => Color.black,
                _ => Color.white,
            };
        }

        private DungeonTierConfig CreateDungeon(Vector3 position, Quaternion rotation)
        {
            string prefabPath = RandomlySelectDungeonPrefab();
            if (string.IsNullOrEmpty(prefabPath))
            {
                return null;
            }

            var dungeon = GameManager.server.CreateEntity(prefabPath, position, rotation) as BasePortal;

            if (dungeon == null)
            {
                return null;
            }

            dungeon.Spawn();

            var proceduralDungeon = (dungeon as XmasDungeon)?.dungeonInstance.Get(true) ?? (dungeon as HalloweenDungeon)?.dungeonInstance.Get(true);

            if (proceduralDungeon == null || proceduralDungeon.spawnedCells.Count < 1)
            {
                dungeon.Kill(BaseNetworkable.DestroyMode.None);
                return null;
            }

            var selectedTier = DetermineDungeonTier(proceduralDungeon.spawnedCells.Count);

            var activeDungeon = new ActiveDungeon
            {
                PortalId = dungeon.net.ID.Value,
                Position = position,
                TierName = _tierNameMap[selectedTier],
                EntityIds = new List<ulong>(),
            };

            NextTick(() =>
            {
                IntegrateEntitiesIntoDungeon(dungeon, selectedTier, activeDungeon);
            });

            _dungeonData.ActiveDungeons.Add(activeDungeon);

            return selectedTier;
        }

        private object OnPortalUse(BasePlayer player, BasePortal portal)
        {
            if (_dungeonData.ActiveDungeons.All(d => d.PortalId != portal.net.ID.Value))
            {
                return null;
            }

            var proceduralDungeon = (portal as XmasDungeon)?.dungeonInstance.Get(true) ?? (portal as HalloweenDungeon)?.dungeonInstance.Get(true);
            if (proceduralDungeon == null)
            {
                return null;
            }

            foreach (var cell in proceduralDungeon.spawnedCells)
            {
                foreach (var group in cell.spawnGroups)
                {
                    for (int num = group.spawnInstances.Count - 1; num >= 0; num--)
                    {
                        SpawnPointInstance spawnPointInstance = group.spawnInstances[num];
                        BaseEntity entity = spawnPointInstance?.gameObject?.ToBaseEntity();

                        if (entity != null && !IsDungeonCreatedEntity(entity) && (entity is NPCDwelling || entity is ScarecrowNPC || entity is LootContainer))
                        {
                            entity.Kill();
                        }
                    }
                }
            }
            return null;
        }

        private DungeonTierConfig DetermineDungeonTier(int cellCount)
        {
            return cellCount switch
            {
                < 5 => _configData.Tiers.Easy,
                < 8 => _configData.Tiers.Normal,
                < 12 => _configData.Tiers.Medium,
                < 16 => _configData.Tiers.Hard,
                _ => _configData.Tiers.Nightmare,
            };
        }

        private void RemoveDungeonEntities(ActiveDungeon activeDungeon)
        {
            foreach (var entityId in activeDungeon.EntityIds)
            {
                var entity = BaseNetworkable.serverEntities.Find(new NetworkableId(entityId)) as BaseEntity;
                if (entity != null && !entity.IsDestroyed)
                {
                    entity.Kill(BaseNetworkable.DestroyMode.None);
                }
            }

            activeDungeon.EntityIds.Clear();
        }

        private void RemoveDungeonMarkers(ActiveDungeon dungeon)
        {
            foreach (var marker in _dungeonData.ActiveDungeonMarkers.Where(m => m.Position == dungeon.Position).ToList())
            {
                var vendingMarker = BaseNetworkable.serverEntities.Find(new NetworkableId(marker.VendingMarker)) as BaseEntity;
                var radiusMarker = BaseNetworkable.serverEntities.Find(new NetworkableId(marker.RadiusMarker)) as BaseEntity;

                vendingMarker?.Kill(BaseNetworkable.DestroyMode.None);
                radiusMarker?.Kill(BaseNetworkable.DestroyMode.None);

                _dungeonData.ActiveDungeonMarkers.Remove(marker);
            }
        }

        private void CheckDungeonsExistence()
        {
            foreach (var dungeon in _dungeonData.ActiveDungeons.ToList())
            {
                if (DungeonCanBeDeleted(dungeon))
                {
                    RemoveDungeon(dungeon);
                }
            }
        }

        private bool DungeonCanBeDeleted(ActiveDungeon dungeon)
        {
            bool allNpcsEliminated = CheckAllNpcEliminated(dungeon);
            bool noPlayersInside = CheckNoPlayersInside(dungeon.Position);

            return allNpcsEliminated && noPlayersInside;
        }

        private bool CheckAllNpcEliminated(ActiveDungeon dungeon)
        {
            foreach (var npcId in dungeon.NpcIds)
            {
                var npc = BaseNetworkable.serverEntities.Find(new NetworkableId(npcId)) as BaseEntity;
                if (npc != null && !npc.IsDestroyed)
                {
                    return false;
                }
            }
            return true;
        }

        private bool CheckNoPlayersInside(Vector3 dungeonPosition)
        {
            float checkRadius = 100f;
            var playersNearby = BasePlayer.activePlayerList.Where(player => Vector3.Distance(player.transform.position, dungeonPosition) <= checkRadius);
            return !playersNearby.Any();
        }

        private void RemoveDungeon(ActiveDungeon dungeon)
        {
            RemoveDungeonEntities(dungeon);
            RemoveDungeonMarkers(dungeon);

            _dungeonData.ActiveDungeons.Remove(dungeon);
            SaveData();
        }

        private string RandomlySelectDungeonPrefab()
        {
            var availablePrefabs = new List<string>();

            if (_configData.DungeonSpawn.EnableXmasDungeon)
                availablePrefabs.Add("assets/prefabs/missions/portal/xmasportalentry.prefab");

            if (_configData.DungeonSpawn.EnableHalloweenDungeon)
                availablePrefabs.Add("assets/prefabs/missions/portal/halloweenportalentry.prefab");

            return availablePrefabs.Count == 0 ? null : availablePrefabs[Random.Range(0, availablePrefabs.Count)];
        }

        private void IntegrateEntitiesIntoDungeon(BasePortal dungeon, DungeonTierConfig tierConfig, ActiveDungeon activeDungeon)
        {
            var proceduralDungeon = (dungeon as XmasDungeon)?.dungeonInstance.Get(true) ?? (dungeon as HalloweenDungeon)?.dungeonInstance.Get(true);

            if (proceduralDungeon == null)
            {
                return;
            }

            var allSpawnEntries = GatherAllSpawnEntries(tierConfig);
            int totalEntities = allSpawnEntries.Count;
            int cellCount = proceduralDungeon.spawnedCells.Count;

            if (totalEntities == 0 || cellCount == 0)
            {
                return;
            }

            float exactRatio = (float)totalEntities / cellCount;
            int[] entitiesPerCell = new int[cellCount];
            int assignedEntities = 0;

            for (int i = 0; i < cellCount; i++)
            {
                entitiesPerCell[i] = (int)exactRatio;
                assignedEntities += entitiesPerCell[i];
            }

            int remainingEntities = totalEntities - assignedEntities;
            var random = new System.Random();
            while (remainingEntities > 0)
            {
                int cellIndex = random.Next(cellCount);
                entitiesPerCell[cellIndex]++;
                remainingEntities--;
            }

            int entryIndex = 0;
            for (int i = 0; i < cellCount; i++)
            {
                int entitiesInThisCell = entitiesPerCell[i];
                if (entitiesInThisCell > 0 && entryIndex + entitiesInThisCell <= totalEntities)
                {
                    var entriesForCell = allSpawnEntries.GetRange(entryIndex, entitiesInThisCell);
                    SetupSpawnGroup(proceduralDungeon.spawnedCells[i], entriesForCell, tierConfig, entitiesInThisCell, activeDungeon);
                    entryIndex += entitiesInThisCell;
                }
            }
        }

        private List<SpawnGroup.SpawnEntry> GatherAllSpawnEntries(DungeonTierConfig tierConfig)
        {
            var allSpawnEntries = new List<SpawnGroup.SpawnEntry>();

            foreach (var config in tierConfig.NpcSpawnConfigs)
            {
                if (GameManifest.pathToGuid.TryGetValue(config.PrefabName, out var guid))
                {
                    for (int i = 0; i < config.Total; i++)
                    {
                        allSpawnEntries.Add(
                            new SpawnGroup.SpawnEntry
                            {
                                prefab = new GameObjectRef { guid = guid },
                                weight = 1,
                                mobile = true,
                            }
                        );
                    }
                }
            }

            for (int i = 0; i < tierConfig.AutoTurretConfig.Total; i++)
            {
                if (GameManifest.pathToGuid.TryGetValue("assets/prefabs/npc/autoturret/autoturret_deployed.prefab", out var turretGuid))
                {
                    allSpawnEntries.Add(
                        new SpawnGroup.SpawnEntry
                        {
                            prefab = new GameObjectRef { guid = turretGuid },
                            weight = 1,
                            mobile = false,
                        }
                    );
                }
            }

            for (int i = 0; i < tierConfig.TotalLootBoxes; i++)
            {
                if (GameManifest.pathToGuid.TryGetValue("assets/prefabs/deployable/woodenbox/woodbox_deployed.prefab", out var boxGuid))
                {
                    allSpawnEntries.Add(
                        new SpawnGroup.SpawnEntry
                        {
                            prefab = new GameObjectRef { guid = boxGuid },
                            weight = 1,
                            mobile = false,
                        }
                    );
                }
            }

            return allSpawnEntries.OrderBy(x => Random.value).ToList();
        }

        private void SetupSpawnGroup(ProceduralDungeonCell cell, List<SpawnGroup.SpawnEntry> entries, DungeonTierConfig tierConfig, int population, ActiveDungeon activeDungeon)
        {
            var spawnGroup = cell.gameObject.AddComponent<SpawnGroup>();
            spawnGroup.prefabs = entries;
            spawnGroup.maxPopulation = population;
            spawnGroup.numToSpawnPerTickMin = 0;
            spawnGroup.numToSpawnPerTickMax = 0;
            spawnGroup.fillOnSpawn = true;
            spawnGroup.wantsInitialSpawn = true;
            spawnGroup.SpawnInitial();

            foreach (var instance in spawnGroup.spawnInstances)
            {
                var entity = instance.GetComponent<BaseEntity>();
                if (entity != null && !activeDungeon.EntityIds.Contains(entity.net.ID.Value))
                {
                    activeDungeon.EntityIds.Add(entity.net.ID.Value);

                    if (entity is NPCPlayer)
                    {
                        activeDungeon.NpcIds.Add(entity.net.ID.Value);
                    }

                    if (entity is AutoTurret autoTurret)
                    {
                        ConfigureAutoTurret(autoTurret, tierConfig.AutoTurretConfig);
                        autoTurret.gameObject.AddComponent<TurretBehaviour>();
                    }

                    if (entity is StorageContainer box)
                    {
                        ConfigureLootBox(box);
                    }
                }
            }
        }

        private void ConfigureLootBox(StorageContainer box)
        {
            box.skinID = _configData.LootBoxConfig.SmallWoodBoxSkinID;
            AddLockToBox(box);
            FillLootBox(box.inventory, _configData.LootBoxConfig.LootItems);
        }

        private void AddLockToBox(StorageContainer box)
        {
            var codeLock = GameManager.server.CreateEntity("assets/prefabs/locks/keypad/lock.code.prefab") as CodeLock;
            if (codeLock != null)
            {
                codeLock.SetParent(box, box.GetSlotAnchorName(BaseEntity.Slot.Lock));
                codeLock.Spawn();
                codeLock.code = Random.Range(1000, 9999).ToString();
                codeLock.hasCode = true;
                codeLock.guestCode = string.Empty;
                codeLock.hasGuestCode = false;
                codeLock.guestPlayers.Clear();
                codeLock.whitelistPlayers.Clear();
                codeLock.SetFlag(BaseEntity.Flags.Locked, true);
            }
        }

        private void FillLootBox(ItemContainer container, List<ItemConfig> lootItems)
        {
            var shuffledItems = lootItems.OrderBy(_ => Random.value).ToList();
            int maxDifferentItems = _configData.LootBoxConfig.MaxDifferentItemsPerBox;
            int differentItemsCount = 0;

            foreach (var itemInfo in shuffledItems)
            {
                if (differentItemsCount >= maxDifferentItems)
                    break;

                if (Random.Range(0f, 100f) <= itemInfo.InclusionChancePercentage)
                {
                    var itemDefinition = ItemManager.FindItemDefinition(itemInfo.ShortName);
                    if (itemDefinition != null)
                    {
                        int amount = Random.Range(itemInfo.MinimumAmount, itemInfo.MaximumAmount + 1);

                        if (container.itemList.Count < container.capacity)
                        {
                            var item = ItemManager.Create(itemDefinition, amount);
                            item.MoveToContainer(container);
                            differentItemsCount++;
                        }
                    }
                }
            }
        }

        private void ConfigureAutoTurret(AutoTurret autoTurret, TurretConfig turretConfig)
        {
            autoTurret.health = turretConfig.Health;
            autoTurret.SetMaxHealth(autoTurret.health);

            var weapon = ItemManager.CreateByName(turretConfig.WeaponShortName);
            if (weapon != null)
            {
                weapon.MoveToContainer(autoTurret.inventory, 0);

                var laserSight = ItemManager.CreateByName("weapon.mod.lasersight");
                laserSight?.MoveToContainer(weapon.contents);
                autoTurret.UpdateAttachedWeapon();
            }
        }

        #endregion

        #region Utility and Cleanup

        private bool TryFindDungeonSpawnPoint(out Vector3 suitablePosition, out Quaternion suitableRotation)
        {
            float mapSize = World.Size;

            for (int attempt = 0; attempt < 100; attempt++)
            {
                Vector3 candidatePosition = new Vector3(Random.Range(-mapSize / 2, mapSize / 2), 0, Random.Range(-mapSize / 2, mapSize / 2));

                if (IsValidSpawnPosition(candidatePosition, out suitablePosition, out suitableRotation))
                {
                    bool tooClose = false;

                    foreach (var dungeon in _dungeonData.ActiveDungeons)
                    {
                        if (Vector3.Distance(suitablePosition, dungeon.Position) < _configData.DungeonSpawn.MinDistanceBetweenDungeons)
                        {
                            tooClose = true;
                            break;
                        }
                    }

                    if (!tooClose)
                        return true;
                }
            }

            suitablePosition = Vector3.zero;
            suitableRotation = Quaternion.identity;
            return false;
        }

        private bool IsValidSpawnPosition(Vector3 position, out Vector3 suitablePosition, out Quaternion suitableRotation)
        {
            suitablePosition = Vector3.zero;
            suitableRotation = Quaternion.identity;

            if (TerrainUtilities.InsideRock(position, _configData.DungeonSpawn.RocksAvoidanceRadius))
                return false;

            if (TerrainUtilities.InRadTown(position) || TerrainUtilities.HasEntityNearby(position, _configData.DungeonSpawn.NearbyEntitiesAvoidanceRadius, Layers.Mask.Construction))
                return false;

            if (TerrainUtilities.InWater(position) || TerrainUtilities.OnRoadOrRail(position))
                return false;

            if (TerrainUtilities.InNoBuildZone(position, _configData.DungeonSpawn.DistanceFromNoBuildZones))
                return false;

            if (TerrainUtilities.GetTerrainInfo(position, out var hitInfo))
            {
                suitablePosition = hitInfo.point;
                suitableRotation = Quaternion.FromToRotation(Vector3.up, hitInfo.normal);
                return true;
            }

            return false;
        }

        private void Unload()
        {
            SaveData();
            RemoveAllExistingDungeons();
            _autoSpawnTimer?.Destroy();
            _dungeonCheckTimer?.Destroy();
        }

        #endregion

        #region Terrain Utilities

        public static class TerrainUtilities
        {
            private const int WorldLayer = Layers.Mask.World;
            private const int TerrainLayer = Layers.Mask.Terrain;
            private const int PreventBuildingLayer = Layers.Mask.Prevent_Building;
            private const int ConstructionLayer = Layers.Mask.Construction;

            public static Vector3 GetRandomPosition(float minX, float maxX, float minZ, float maxZ)
            {
                float randomX = Random.Range(minX, maxX);
                float randomZ = Random.Range(minZ, maxZ);
                float y = TerrainMeta.HeightMap.GetHeight(new Vector3(randomX, 0, randomZ));

                return new Vector3(randomX, y, randomZ);
            }

            public static bool InsideRock(Vector3 position, float radius)
            {
                List<Collider> colliders = Pool.GetList<Collider>();
                Vis.Colliders(position, radius, colliders, WorldLayer, QueryTriggerInteraction.Ignore);

                bool isInsideRock = colliders.Any(collider =>
                    collider.name.Contains("rock", StringComparison.OrdinalIgnoreCase)
                    || collider.name.Contains("cliff", StringComparison.OrdinalIgnoreCase)
                    || collider.name.Contains("formation", StringComparison.OrdinalIgnoreCase)
                );

                Pool.FreeList(ref colliders);
                return isInsideRock;
            }

            public static bool InRadTown(Vector3 position)
            {
                return TerrainMeta.Path.Monuments.Any(monument => monument.IsInBounds(position) && monument.shouldDisplayOnMap)
                    || (TerrainMeta.TopologyMap.GetTopology(position) & (int)TerrainTopology.Enum.Monument) != 0;
            }

            public static bool HasEntityNearby(Vector3 position, float radius, int mask, string prefabName = null)
            {
                List<Collider> hitColliders = Pool.GetList<Collider>();
                GamePhysics.OverlapSphere(position, radius, hitColliders, mask, QueryTriggerInteraction.Ignore);

                bool result = hitColliders.Any(collider =>
                {
                    BaseEntity entity = collider.gameObject.ToBaseEntity();
                    return entity != null && (prefabName == null || entity.PrefabName == prefabName);
                });

                Pool.FreeList(ref hitColliders);
                return result;
            }

            public static bool InWater(Vector3 position)
            {
                return WaterLevel.Test(position, false, false);
            }

            public static bool OnRoadOrRail(Vector3 position)
            {
                int topology = TerrainMeta.TopologyMap.GetTopology(position);
                return (topology & (int)(TerrainTopology.Enum.Road | TerrainTopology.Enum.Roadside | TerrainTopology.Enum.Rail | TerrainTopology.Enum.Railside)) != 0;
            }

            public static bool GetTerrainInfo(Vector3 startPosition, out RaycastHit hitInfo, float range = 1f, LayerMask mask = default)
            {
                mask = mask == default ? TerrainLayer : mask;
                return Physics.Linecast(startPosition + Vector3.up * range, startPosition - Vector3.up * range, out hitInfo, mask);
            }

            public static bool InNoBuildZone(Vector3 position, float radius)
            {
                return Physics.CheckSphere(position, radius, PreventBuildingLayer, QueryTriggerInteraction.Ignore);
            }
        }
        #endregion

        #region Configuration

        private class ConfigData
        {
            [JsonProperty(PropertyName = "Dungeon Spawn Settings")]
            public DungeonSpawnSettings DungeonSpawn { get; set; } = new DungeonSpawnSettings();

            [JsonProperty(PropertyName = "Tiers")]
            public DungeonTiers Tiers { get; set; } = new DungeonTiers();

            [JsonProperty(PropertyName = "Loot Box Config")]
            public LootBoxConfig LootBoxConfig { get; set; } = new LootBoxConfig();

            [JsonProperty(PropertyName = "Version")]
            public VersionNumber Version { get; set; }
        }

        private class DungeonTiers
        {
            [JsonProperty(PropertyName = "Easy")]
            public DungeonTierConfig Easy { get; set; } =
                new DungeonTierConfig
                {
                    TotalLootBoxes = 1,
                    NpcSpawnConfigs = new List<NpcSpawnConfig>
                    {
                        new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_roamtethered.prefab", Total = 1 },
                    },
                    AutoTurretConfig = new TurretConfig
                    {
                        Total = 1,
                        Health = 300,
                        WeaponShortName = "pistol.revolver",
                    },
                };

            [JsonProperty(PropertyName = "Normal")]
            public DungeonTierConfig Normal { get; set; } =
                new DungeonTierConfig
                {
                    TotalLootBoxes = 2,
                    NpcSpawnConfigs = new List<NpcSpawnConfig>
                    {
                        new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_roamtethered.prefab", Total = 3 },
                    },
                    AutoTurretConfig = new TurretConfig
                    {
                        Total = 2,
                        Health = 600,
                        WeaponShortName = "smg.2",
                    },
                };

            [JsonProperty(PropertyName = "Medium")]
            public DungeonTierConfig Medium { get; set; } =
                new DungeonTierConfig
                {
                    TotalLootBoxes = 4,
                    NpcSpawnConfigs = new List<NpcSpawnConfig>
                    {
                        new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_roamtethered.prefab", Total = 4 },
                    },
                    AutoTurretConfig = new TurretConfig
                    {
                        Total = 3,
                        Health = 1000,
                        WeaponShortName = "smg.mp5",
                    },
                };

            [JsonProperty(PropertyName = "Hard")]
            public DungeonTierConfig Hard { get; set; } =
                new DungeonTierConfig
                {
                    TotalLootBoxes = 5,
                    NpcSpawnConfigs = new List<NpcSpawnConfig>
                    {
                        new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_roamtethered.prefab", Total = 4 },
                        new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_roamtethered.prefab", Total = 3 },
                    },
                    AutoTurretConfig = new TurretConfig
                    {
                        Total = 4,
                        Health = 1500,
                        WeaponShortName = "rifle.ak",
                    },
                };

            [JsonProperty(PropertyName = "Nightmare")]
            public DungeonTierConfig Nightmare { get; set; } =
                new DungeonTierConfig
                {
                    TotalLootBoxes = 6,
                    NpcSpawnConfigs = new List<NpcSpawnConfig>
                    {
                        new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_roamtethered.prefab", Total = 5 },
                        new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_roamtethered.prefab", Total = 5 },
                    },
                    AutoTurretConfig = new TurretConfig
                    {
                        Total = 6,
                        Health = 2000,
                        WeaponShortName = "rifle.ak",
                    },
                };
        }

        private class DungeonTierConfig
        {
            [JsonProperty(PropertyName = "Maximum Loot Boxes")]
            public int TotalLootBoxes { get; set; }

            [JsonProperty(PropertyName = "NPC Spawn Configs", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<NpcSpawnConfig> NpcSpawnConfigs { get; set; }

            [JsonProperty(PropertyName = "Auto Turret Config")]
            public TurretConfig AutoTurretConfig { get; set; } = new TurretConfig();
        }

        public class LootBoxConfig
        {
            [JsonProperty(PropertyName = "Small Wood Box Skin ID")]
            public ulong SmallWoodBoxSkinID { get; set; } = 2998755525;

            [JsonProperty(PropertyName = "Max Different Items Per Box")]
            public int MaxDifferentItemsPerBox { get; set; } = 6;

            [JsonProperty(PropertyName = "Loot Items", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<ItemConfig> LootItems { get; set; } =
                new List<ItemConfig>
                {
                    new ItemConfig
                    {
                        ShortName = "ammo.rifle",
                        InclusionChancePercentage = 15,
                        MinimumAmount = 100,
                        MaximumAmount = 300,
                    },
                    new ItemConfig
                    {
                        ShortName = "sulfur.ore",
                        InclusionChancePercentage = 15,
                        MinimumAmount = 100,
                        MaximumAmount = 300,
                    },
                    new ItemConfig
                    {
                        ShortName = "scrap",
                        InclusionChancePercentage = 70,
                        MinimumAmount = 500,
                        MaximumAmount = 2000,
                    },
                    new ItemConfig
                    {
                        ShortName = "rifle.ak",
                        InclusionChancePercentage = 5,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "rocket.launcher.dragon",
                        InclusionChancePercentage = 5,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "grenade.f1",
                        InclusionChancePercentage = 10,
                        MinimumAmount = 1,
                        MaximumAmount = 5,
                    },
                    new ItemConfig
                    {
                        ShortName = "crude.oil",
                        InclusionChancePercentage = 40,
                        MinimumAmount = 10,
                        MaximumAmount = 100,
                    },
                    new ItemConfig
                    {
                        ShortName = "diesel_barrel",
                        InclusionChancePercentage = 30,
                        MinimumAmount = 1,
                        MaximumAmount = 5,
                    },
                    new ItemConfig
                    {
                        ShortName = "cctv.camera",
                        InclusionChancePercentage = 20,
                        MinimumAmount = 5,
                        MaximumAmount = 20,
                    },
                    new ItemConfig
                    {
                        ShortName = "gingerbreadsuit",
                        InclusionChancePercentage = 5,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "gears",
                        InclusionChancePercentage = 20,
                        MinimumAmount = 1,
                        MaximumAmount = 20,
                    },
                    new ItemConfig
                    {
                        ShortName = "metal.refined",
                        InclusionChancePercentage = 20,
                        MinimumAmount = 10,
                        MaximumAmount = 80,
                    },
                    new ItemConfig
                    {
                        ShortName = "electric.furnace",
                        InclusionChancePercentage = 20,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "fuse",
                        InclusionChancePercentage = 20,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "xmas.door.garland",
                        InclusionChancePercentage = 10,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "gunpowder",
                        InclusionChancePercentage = 50,
                        MinimumAmount = 50,
                        MaximumAmount = 150,
                    },
                    new ItemConfig
                    {
                        ShortName = "handcuffs",
                        InclusionChancePercentage = 10,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "hazmatsuit",
                        InclusionChancePercentage = 10,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "ammo.rocket.hv",
                        InclusionChancePercentage = 10,
                        MinimumAmount = 1,
                        MaximumAmount = 20,
                    },
                    new ItemConfig
                    {
                        ShortName = "ammo.rifle.hv",
                        InclusionChancePercentage = 10,
                        MinimumAmount = 100,
                        MaximumAmount = 300,
                    },
                    new ItemConfig
                    {
                        ShortName = "metal.facemask",
                        InclusionChancePercentage = 25,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "ammo.rocket.fire",
                        InclusionChancePercentage = 10,
                        MinimumAmount = 5,
                        MaximumAmount = 10,
                    },
                    new ItemConfig
                    {
                        ShortName = "jackhammer",
                        InclusionChancePercentage = 30,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "rifle.l96",
                        InclusionChancePercentage = 5,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "furnace.large",
                        InclusionChancePercentage = 50,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "largemedkit",
                        InclusionChancePercentage = 50,
                        MinimumAmount = 1,
                        MaximumAmount = 10,
                    },
                    new ItemConfig
                    {
                        ShortName = "xmas.present.large",
                        InclusionChancePercentage = 40,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "locker",
                        InclusionChancePercentage = 40,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "lowgradefuel",
                        InclusionChancePercentage = 70,
                        MinimumAmount = 50,
                        MaximumAmount = 200,
                    },
                    new ItemConfig
                    {
                        ShortName = "rifle.lr300",
                        InclusionChancePercentage = 5,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "rifle.m39",
                        InclusionChancePercentage = 5,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "pistol.m92",
                        InclusionChancePercentage = 10,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "lmg.m249",
                        InclusionChancePercentage = 2,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "syringe.medical",
                        InclusionChancePercentage = 70,
                        MinimumAmount = 10,
                        MaximumAmount = 30,
                    },
                    new ItemConfig
                    {
                        ShortName = "metal.ore",
                        InclusionChancePercentage = 80,
                        MinimumAmount = 100,
                        MaximumAmount = 300,
                    },
                    new ItemConfig
                    {
                        ShortName = "metalpipe",
                        InclusionChancePercentage = 60,
                        MinimumAmount = 1,
                        MaximumAmount = 5,
                    },
                    new ItemConfig
                    {
                        ShortName = "metalspring",
                        InclusionChancePercentage = 60,
                        MinimumAmount = 1,
                        MaximumAmount = 5,
                    },
                    new ItemConfig
                    {
                        ShortName = "minigun",
                        InclusionChancePercentage = 2,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "grenade.molotov",
                        InclusionChancePercentage = 10,
                        MinimumAmount = 1,
                        MaximumAmount = 5,
                    },
                    new ItemConfig
                    {
                        ShortName = "smg.mp5",
                        InclusionChancePercentage = 4,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "multiplegrenadelauncher",
                        InclusionChancePercentage = 15,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "mummymask",
                        InclusionChancePercentage = 20,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "mushroom",
                        InclusionChancePercentage = 80,
                        MinimumAmount = 20,
                        MaximumAmount = 40,
                    },
                    new ItemConfig
                    {
                        ShortName = "weapon.mod.muzzleboost",
                        InclusionChancePercentage = 35,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "weapon.mod.muzzlebrake",
                        InclusionChancePercentage = 35,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "nightvisiongoggles",
                        InclusionChancePercentage = 20,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "newyeargong",
                        InclusionChancePercentage = 25,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "hazmatsuit.nomadsuit",
                        InclusionChancePercentage = 20,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "firework.boomer.pattern",
                        InclusionChancePercentage = 40,
                        MinimumAmount = 1,
                        MaximumAmount = 5,
                    },
                    new ItemConfig
                    {
                        ShortName = "jar.pickle",
                        InclusionChancePercentage = 60,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "ammo.pistol",
                        InclusionChancePercentage = 70,
                        MinimumAmount = 100,
                        MaximumAmount = 500,
                    },
                    new ItemConfig
                    {
                        ShortName = "pookie.bear",
                        InclusionChancePercentage = 60,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "potato",
                        InclusionChancePercentage = 70,
                        MinimumAmount = 100,
                        MaximumAmount = 100,
                    },
                    new ItemConfig
                    {
                        ShortName = "pistol.revolver",
                        InclusionChancePercentage = 50,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "riflebody",
                        InclusionChancePercentage = 60,
                        MinimumAmount = 5,
                        MaximumAmount = 10,
                    },
                    new ItemConfig
                    {
                        ShortName = "roadsign.gloves",
                        InclusionChancePercentage = 40,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "roadsign.kilt",
                        InclusionChancePercentage = 40,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "ammo.rocket.basic",
                        InclusionChancePercentage = 30,
                        MinimumAmount = 1,
                        MaximumAmount = 5,
                    },
                    new ItemConfig
                    {
                        ShortName = "rocket.launcher",
                        InclusionChancePercentage = 5,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "rope",
                        InclusionChancePercentage = 60,
                        MinimumAmount = 5,
                        MaximumAmount = 10,
                    },
                    new ItemConfig
                    {
                        ShortName = "icepick.salvaged",
                        InclusionChancePercentage = 30,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "explosive.satchel",
                        InclusionChancePercentage = 25,
                        MinimumAmount = 1,
                        MaximumAmount = 5,
                    },
                    new ItemConfig
                    {
                        ShortName = "electric.seismicsensor",
                        InclusionChancePercentage = 20,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "semibody",
                        InclusionChancePercentage = 60,
                        MinimumAmount = 5,
                        MaximumAmount = 10,
                    },
                    new ItemConfig
                    {
                        ShortName = "pistol.semiauto",
                        InclusionChancePercentage = 10,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "rifle.semiauto",
                        InclusionChancePercentage = 5,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "sheetmetal",
                        InclusionChancePercentage = 70,
                        MinimumAmount = 1,
                        MaximumAmount = 9,
                    },
                    new ItemConfig
                    {
                        ShortName = "sewingkit",
                        InclusionChancePercentage = 65,
                        MinimumAmount = 1,
                        MaximumAmount = 10,
                    },
                    new ItemConfig
                    {
                        ShortName = "weapon.mod.silencer",
                        InclusionChancePercentage = 10,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "weapon.mod.simplesight",
                        InclusionChancePercentage = 50,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "knife.skinning",
                        InclusionChancePercentage = 50,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "rifle.sks",
                        InclusionChancePercentage = 7,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "xmas.present.small",
                        InclusionChancePercentage = 40,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "smgbody",
                        InclusionChancePercentage = 60,
                        MinimumAmount = 5,
                        MaximumAmount = 9,
                    },
                    new ItemConfig
                    {
                        ShortName = "sofa",
                        InclusionChancePercentage = 30,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "hazmatsuit.spacesuit",
                        InclusionChancePercentage = 10,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "spookyspeaker",
                        InclusionChancePercentage = 25,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "stones",
                        InclusionChancePercentage = 80,
                        MinimumAmount = 100,
                        MaximumAmount = 600,
                    },
                    new ItemConfig
                    {
                        ShortName = "sulfur",
                        InclusionChancePercentage = 80,
                        MinimumAmount = 100,
                        MaximumAmount = 600,
                    },
                    new ItemConfig
                    {
                        ShortName = "tactical.gloves",
                        InclusionChancePercentage = 35,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "tarp",
                        InclusionChancePercentage = 60,
                        MinimumAmount = 1,
                        MaximumAmount = 5,
                    },
                    new ItemConfig
                    {
                        ShortName = "vampire.stake",
                        InclusionChancePercentage = 20,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "vending.machine",
                        InclusionChancePercentage = 15,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "bottle.vodka",
                        InclusionChancePercentage = 9,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "gun.water",
                        InclusionChancePercentage = 9,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "piano",
                        InclusionChancePercentage = 9,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "firework.volcano",
                        InclusionChancePercentage = 30,
                        MinimumAmount = 1,
                        MaximumAmount = 5,
                    },
                    new ItemConfig
                    {
                        ShortName = "hat.wolf",
                        InclusionChancePercentage = 50,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                    new ItemConfig
                    {
                        ShortName = "worm",
                        InclusionChancePercentage = 70,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                    },
                };
        }

        private class NpcSpawnConfig
        {
            [JsonProperty(PropertyName = "Prefab")]
            public string PrefabName { get; set; }

            [JsonProperty(PropertyName = "Maximum")]
            public int Total { get; set; }
        }

        private class TurretConfig
        {
            [JsonProperty(PropertyName = "Maximum")]
            public int Total { get; set; } = 3;

            [JsonProperty(PropertyName = "Health")]
            public float Health { get; set; } = 1000f;

            [JsonProperty(PropertyName = "Weapon Short Name")]
            public string WeaponShortName { get; set; } = "rifle.ak";
        }

        private class DungeonSpawnSettings
        {
            [JsonProperty(PropertyName = "Enable Xmas Dungeon")]
            public bool EnableXmasDungeon { get; set; } = true;

            [JsonProperty(PropertyName = "Enable Halloween Dungeon")]
            public bool EnableHalloweenDungeon { get; set; } = true;

            [JsonProperty(PropertyName = "Max Total Active Dungeons")]
            public int MaxTotalActiveDungeons { get; set; } = 4;

            [JsonProperty(PropertyName = "Min Distance Between Dungeons")]
            public float MinDistanceBetweenDungeons { get; set; } = 50f;

            [JsonProperty(PropertyName = "Dungeon Spawn Interval")]
            public float DungeonSpawnInterval { get; set; } = 3f;

            [JsonProperty(PropertyName = "Auto Spawn Cycle Interval")]
            public float AutoSpawnCycleInterval { get; set; } = 60f;

            [JsonProperty(PropertyName = "Enable Auto Spawn")]
            public bool EnableAutoSpawn { get; set; } = true;

            [JsonProperty(PropertyName = "Nearby Entities Avoidance Radius")]
            public float NearbyEntitiesAvoidanceRadius { get; set; } = 10f;

            [JsonProperty(PropertyName = "Rocks Avoidance Radius")]
            public float RocksAvoidanceRadius { get; set; } = 10f;

            [JsonProperty(PropertyName = "Distance From No Build Zones")]
            public float DistanceFromNoBuildZones { get; set; } = 10f;
        }

        public class ItemConfig
        {
            [JsonProperty(PropertyName = "ShortName")]
            public string ShortName { get; set; }

            [JsonProperty(PropertyName = "Inclusion Chance Percentage")]
            public float InclusionChancePercentage { get; set; }

            [JsonProperty(PropertyName = "Minimum Amount")]
            public int MinimumAmount { get; set; }

            [JsonProperty(PropertyName = "Maximum Amount")]
            public int MaximumAmount { get; set; }
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                _configData = Config.ReadObject<ConfigData>();
                if (_configData == null)
                {
                    LoadDefaultConfig();
                }
            }
            catch (Exception ex)
            {
                PrintError($"The configuration file is corrupted: {ex}");
                LoadDefaultConfig();
            }
            SaveConfig();
        }

        protected override void LoadDefaultConfig()
        {
            PrintWarning("Creating a new configuration file");
            _configData = new ConfigData();
            _configData.Version = Version;
        }

        protected override void SaveConfig()
        {
            Config.WriteObject(_configData, true);
        }

        #endregion

        #region Localization

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(
                new Dictionary<string, string>
                {
                    ["AllDungeonsRemoved"] = "All dungeons removed.",
                    ["AllInactiveDungeonsRemoved"] = "All inactive dungeons removed.",
                    ["AttemptSpawnDungeons"] = "Attempting to spawn dungeons.",
                    ["DungeonAppeared"] = "A {0} Dungeon has appeared at {1}!",
                    ["DungeonSpawnFailed"] = "Could not find a valid location to spawn a new dungeon.",
                    ["DungeonEntityCreationFailed"] = "Failed to create dungeon entity.",
                    ["DungeonRemoved"] = "Dungeon at {0} no longer exists. Cleaning up.",
                    ["DungeonSpawning"] = "Spawning dungeon...",
                    ["LootBoxFilled"] = "Loot box filled with items.",
                },
                this
            );
        }

        private string Msg(string userId, string key, params object[] args)
        {
            return string.Format(lang.GetMessage(key, this, userId), args);
        }

        #endregion

        #region Data Classes

        private class DungeonData
        {
            public List<ActiveDungeon> ActiveDungeons { get; set; } = new();
            public List<DungeonMarker> ActiveDungeonMarkers { get; set; } = new();
        }

        private class ActiveDungeon
        {
            public ulong PortalId { get; set; }
            public Vector3 Position { get; set; }
            public string TierName { get; set; }
            public List<ulong> EntityIds { get; set; } = new();
            public List<ulong> NpcIds { get; set; } = new();
        }

        private class DungeonMarker
        {
            public ulong VendingMarker { get; set; }
            public ulong RadiusMarker { get; set; }
            public Vector3 Position { get; set; }
            public string Tier { get; set; }
        }

        #endregion

        #region Turret Behaviour Class

        public class TurretBehaviour : MonoBehaviour
        {
            private AutoTurret _turret;
            private const float SearchDistance = 15f;
            private const int MaxReserveAmmo = 1000;

            private void Awake()
            {
                InitializeTurret();
            }

            private void InitializeTurret()
            {
                _turret = GetComponent<AutoTurret>();
                var triggerCollider = _turret.targetTrigger.GetComponent<SphereCollider>();
                triggerCollider.enabled = false;

                _turret.SetPeacekeepermode(false);
                _turret.InitiateStartup();
                _turret.SetIsOnline(true);
                _turret.CancelInvoke(_turret.ServerTick);
                _turret.SetTarget(null);
                _turret.InvokeRepeating(PerformTurretCycle, 1f, 0.01f);
                _turret.InvokeRepeating(ScanForTargets, 2f, 1f);
                _turret.isLootable = false;
                _turret.dropFloats = false;
                _turret.dropsLoot = false;

                _turret.SendNetworkUpdateImmediate();
                InvokeRepeating(nameof(RefillAmmo), 2f, 30f);
            }

            private void RefillAmmo()
            {
                if (_turret.AttachedWeapon is not BaseProjectile baseProjectile || baseProjectile.primaryMagazine?.ammoType == null)
                    return;

                var ammoType = baseProjectile.primaryMagazine.ammoType;
                int currentAmmoCount = CalculateCurrentAmmoCount(ammoType);

                if (currentAmmoCount < MaxReserveAmmo)
                {
                    int ammoNeeded = MaxReserveAmmo - currentAmmoCount;
                    Item ammoItem = ItemManager.Create(ammoType, ammoNeeded);
                    ammoItem?.MoveToContainer(_turret.inventory);

                    _turret.UpdateTotalAmmo();
                    _turret.EnsureReloaded();
                    _turret.SendNetworkUpdateImmediate();
                }
            }

            private int CalculateCurrentAmmoCount(ItemDefinition ammoType)
            {
                return _turret.inventory.itemList.Where(item => item.info == ammoType).Sum(item => item.amount);
            }

            private void ScanForTargets()
            {
                var entityContents = _turret.targetTrigger.entityContents ??= new HashSet<BaseEntity>();
                entityContents.Clear();

                int foundTargets = BaseEntity.Query.Server.GetPlayersInSphereFast(transform.position, SearchDistance, AIBrainSenses.playerQueryResults, IsTargetValid);

                if (foundTargets == 0)
                    return;

                _turret.authDirty = true;

                for (int i = 0; i < foundTargets; i++)
                {
                    var player = AIBrainSenses.playerQueryResults[i];
                    if (Interface.CallHook("OnEntityEnter", _turret.targetTrigger, player) != null || player.IsSleeping() || (player.InSafeZone() && !player.IsHostile()))
                        continue;

                    entityContents.Add(player);
                }
            }

            private bool IsTargetValid(BasePlayer player) => player != null && !player.IsNpc;

            private void PerformTurretCycle()
            {
                if (_turret.isClient || _turret.IsDestroyed)
                    return;

                float deltaTime = (float)_turret.timeSinceLastServerTick;
                _turret.timeSinceLastServerTick = 0f;

                if (_turret.IsOnline() && !_turret.IsBeingControlled)
                {
                    if (!_turret.HasTarget())
                    {
                        _turret.IdleTick(deltaTime);
                    }
                    else
                    {
                        ExecuteTargetEngagement();
                    }
                }

                _turret.UpdateFacingToTarget(deltaTime);
                UpdateAmmoStatus();
            }

            private void ExecuteTargetEngagement()
            {
                if (Time.realtimeSinceStartup >= _turret.nextVisCheck)
                {
                    _turret.nextVisCheck = Time.realtimeSinceStartup + UnityEngine.Random.Range(0.2f, 0.3f);
                    _turret.targetVisible = _turret.ObjectVisible(_turret.target);

                    if (_turret.targetVisible)
                        _turret.lastTargetSeenTime = Time.realtimeSinceStartup;
                }

                _turret.EnsureReloaded();
                if (ShouldFireAtTarget())
                {
                    var weapon = _turret.GetAttachedWeapon();
                    FireWeapon(weapon);
                }

                ValidateTargetEngagement();
            }

            private bool ShouldFireAtTarget()
            {
                return Time.time >= _turret.nextShotTime
                    && _turret.targetVisible
                    && Mathf.Abs(_turret.AngleToTarget(_turret.target, _turret.currentAmmoGravity != 0f)) < _turret.GetMaxAngleForEngagement();
            }

            private void FireWeapon(BaseProjectile weapon)
            {
                if (weapon == null)
                {
                    _turret.nextShotTime = Time.time + 1f;
                    return;
                }

                if (weapon.primaryMagazine.contents > 0)
                {
                    _turret.FireAttachedGun(_turret.AimOffset(_turret.target), _turret.aimCone, null, _turret.PeacekeeperMode() ? _turret.target : null);
                    float delay = weapon.isSemiAuto ? weapon.repeatDelay * 1.5f : weapon.repeatDelay;
                    delay = weapon.ScaleRepeatDelay(delay);
                    _turret.nextShotTime = Time.time + delay;
                }
                else
                {
                    _turret.nextShotTime = Time.time + 5f;
                }
            }

            private void ValidateTargetEngagement()
            {
                var targetPlayer = _turret.target as BasePlayer;
                if (_turret.target != null && (!IsValidSteamPlayer(targetPlayer) || _turret.target.IsDead() || TimeExceedsLastSeen() || DistanceExceedsSightRange() || ShouldTargetBeReset()))
                {
                    _turret.SetTarget(null);
                }
            }

            private static bool IsValidSteamPlayer(BasePlayer targetPlayer) => targetPlayer != null && targetPlayer.userID.IsSteamId();

            private bool TimeExceedsLastSeen() => Time.realtimeSinceStartup - _turret.lastTargetSeenTime > 3f;

            private bool DistanceExceedsSightRange() => Vector3.Distance(_turret.transform.position, _turret.target.transform.position) > _turret.sightRange;

            private bool ShouldTargetBeReset() => _turret.PeacekeeperMode() && !_turret.IsEntityHostile(_turret.target);

            private void UpdateAmmoStatus()
            {
                if (_turret.totalAmmoDirty && Time.time > _turret.nextAmmoCheckTime)
                {
                    _turret.UpdateTotalAmmo();
                    _turret.totalAmmoDirty = false;
                    _turret.nextAmmoCheckTime = Time.time + 0.5f;
                }
            }

            private void OnDestroy()
            {
                CancelInvoke(nameof(RefillAmmo));
            }
        }

        #endregion
    }
}