﻿using Facepunch;
using Network;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Game.Rust;
using Oxide.Game.Rust.Cui;
using Oxide.Game.Rust.Libraries;
using Rust;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;

namespace Oxide.Plugins
{
    [Info("Raidable Bases", "nivex", "2.4.1")]
    [Description("Create fully automated raidable bases with npcs.")]
    class RaidableBases : RustPlugin
    {
        [PluginReference]
        private Plugin DangerousTreasures, Vanish, ZoneManager, Economics, ServerRewards, GUIAnnouncements, CopyPaste, Friends, Clans, Kits, TruePVE, Spawns, NightLantern, Wizardry, NextGenPVE, Imperium, Backpacks, BaseRepair, Notify;

        public enum AlliedType
        {
            All,
            Clan,
            Friend,
            Team
        }

        public enum CacheType
        {
            Delete,
            Generic,
            Temporary,
            Privilege,
            Submerged,
            None
        }

        public enum RaidableMode
        {
            Disabled = -1,
            Normal = 0,
            Random = 9999
        }

        public enum RaidableType
        {
            None,
            Manual,
            Scheduled,
            Maintained,
            Grid
        }

        private enum SpawnResult
        {
            Failure,
            Transfer,
            Success,
            Skipped
        }

        private const int targetLayer = Layers.Mask.Default | Layers.Mask.Water | Layers.Solid;
        private const int visibleLayer = Layers.Mask.Deployed | Layers.Mask.Default | Layers.Mask.Construction | Layers.Mask.World | Layers.Mask.Terrain;

        private Dictionary<Vector3, string> _rocks { get; set; } = new Dictionary<Vector3, string>();

        private readonly List<string> assets = new List<string>
        {
            "/props/", "/structures/", "/building/", "train_", "powerline_", "dune", "candy-cane", "assets/content/nature/", "walkway", "invisible_collider"
        };

        private List<string> arguments { get; set; } = new List<string> { "add", "remove", "list", "clean" };
        public Dictionary<int, List<BaseEntity>> Bases { get; } = new Dictionary<int, List<BaseEntity>>();
        private static List<string> blockedcolliders;
        public Dictionary<ulong, RaidableBase> Npcs { get; set; } = new Dictionary<ulong, RaidableBase>();
        public Dictionary<BaseEntity, MountInfo> MountEntities { get; } = new Dictionary<BaseEntity, MountInfo>();
        public Dictionary<int, RaidableBase> Raids { get; } = new Dictionary<int, RaidableBase>();
        public List<TreasureItem> BaseLootList { get; set; } = new List<TreasureItem>();
        private static Dictionary<string, ItemDefinition> _shortnames { get; set; }
        public static StoredData data { get; set; } = new StoredData();
        private Dictionary<uint, BMGELEVATOR> _elevators = new Dictionary<uint, BMGELEVATOR>();
        protected static SingletonBackbone Backbone { get; set; }
        protected static RaidableBases Instance;
        protected RotationCycle Cycle { get; set; } = new RotationCycle();
        private bool DEBUG_DRAWINGS { get; set; }
        protected Coroutine gridCoroutine { get; set; }
        protected Coroutine maintainCoroutine { get; set; }
        protected Dictionary<ulong, DelaySettings> PvpDelay { get; } = new Dictionary<ulong, DelaySettings>();
        protected Dictionary<RaidableType, RaidableSpawns> raidSpawns { get; set; } = new Dictionary<RaidableType, RaidableSpawns>();
        public Dictionary<ulong, HumanoidBrain> HumanoidBrains { get; set; } = new Dictionary<ulong, HumanoidBrain>();
        protected Coroutine scheduleCoroutine { get; set; }
        private static BuildingTables Buildings { get; set; } = new BuildingTables();
        private static Dictionary<Vector3, float> LoadingTimes { get; set; } = new Dictionary<Vector3, float>();
        private int _maxOnce { get; set; }
        private StringBuilder _sb { get; } = new StringBuilder();
        private List<uint> BBQs { get; set; } = new List<uint> { 2409469892, 128449714 };
        private List<string> Boots { get; set; } = new List<string> { "boots.frog", "shoes.boots", "burlap.shoes", "attire.hide.boots" };
        private bool debugMode { get; set; }
        private List<uint> Furnaces { get; set; } = new List<uint> { 1374462671, 2931042549, 1402456403 };
        private List<string> Gloves { get; set; } = new List<string> { "burlap.gloves.new", "burlap.gloves", "roadsign.gloves", "tactical.gloves" };
        private Stopwatch gridStopwatch { get; } = new Stopwatch();
        private float gridTime { get; set; }
        private List<string> Helms { get; set; } = new List<string> { "hat.wolf", "attire.hide.helterneck", "hat.beenie", "hat.boonie", "bucket.helmet", "burlap.headwrap", "hat.candle", "hat.cap", "clatter.helmet", "coffeecan.helmet", "deer.skull.mask", "heavy.plate.helmet", "hat.miner", "partyhat", "riot.helmet", "wood.armor.helmet", "mask.balaclava", "mask.bandana", "metal.facemask", "nightvisiongoggles", "hat.dragonmask", "hat.ratmask", "attire.nesthat" };
        private bool IsUnloading { get; set; }
        private List<uint> Lanterns { get; set; } = new List<uint> { 3887352222, 1889323056, 630866573, 4027991414, 1392608348 };
        private float lastSpawnRequestTime { get; set; }
        private List<string> Legs { get; set; } = new List<string> { "burlap.trousers", "heavy.plate.pants", "attire.hide.pants", "pants.shorts", "wood.armor.pants", "pants" };
        private bool maintainEnabled { get; set; }
        private bool AnyFileExists { get; set; }
        private List<ZoneInfo> managedZones { get; set; } = new List<ZoneInfo>();
        private List<MonumentInfoEx> Monuments { get; set; } = new List<MonumentInfoEx>();
        private List<string> Other { get; set; } = new List<string> { "movembermoustachecard", "movembermoustache", "sunglasses02black", "sunglasses02camo", "sunglasses02red", "sunglasses03black", "sunglasses03chrome", "sunglasses03gold", "sunglasses", "twitchsunglasses", "gloweyes", "attire.bunnyears" };
        private List<uint> Refineries { get; set; } = new List<uint> { 1057236622, 919097516 };
        private bool scheduleEnabled { get; set; }
        private List<string> Shirts { get; set; } = new List<string> { "hoodie", "burlap.shirt", "shirt.collared", "attire.hide.vest", "shirt.tanktop", "tshirt.long", "tshirt" };
        private Dictionary<string, SkinInfo> Skins { get; } = new Dictionary<string, SkinInfo>();
        private List<string> Vests { get; set; } = new List<string> { "bone.armor.suit", "heavy.plate.jacket", "jacket.snow", "jacket", "wood.armor.jacket", "attire.hide.poncho", "metal.plate.torso", "roadsign.jacket" };
        private bool wiped { get; set; }
        //campfire: 4160694184, campfire_static: 1339281147, cursedcauldron.deployed: 1348425051, fireplace.deployed: 110576239, hobobarrel_static: 754638672, skull_fire_pit: 1906669538

        private List<uint> ExcludedMounts { get; set; } = new List<uint> { 3552983236, 4218596772, 1845856065, 1992774774, 629849447, 4267988016, 1418740895, 2493676858, 3814928951, 1980628900, 703403829, 3061223907, 113644298, 3691382632, 3858860623, 286221745, 2230162530, 51176708, 3363531184, 3224878175 };

        private struct Constants
        {
            public const float CELL_SIZE = 12.5f;
            public const uint COFFIN_STORAGE = 4080262419;
            public const uint ID_CODELOCK = 3518824735;
            public const uint ID_EXPLOSIONMARKER = 4060989661;
            public const uint ID_HIGHEXTERNALSTONEWALL = 1585379529;
            public const uint ID_HIGHEXTERNALWOODENWALL = 1745077396;
            public const uint ID_LADDER = 2150203378;
            public const uint ID_RADIUSMARKER = 2849728229;
            public const uint ID_SCIENTIST = 1536035819;
            public const uint ID_SPHERE = 3211242734;
            public const uint ID_VENDINGMACHINE = 186002280;
            public const uint ID_VENDINGMARKER = 3459945130;
            public const uint LARGE_WOODEN_BOX = 2206646561;
            public const float RADIUS = 25f;
            public const uint SMALL_WOODEN_BOX = 1560881570;
        }

        public class BackpackData
        {
            public DroppedItemContainer backpack;
            public BasePlayer player;
            public ulong userID;
        }

        public class BaseProfile
        {
            public BaseProfile()
            {

            }

            public BaseProfile(BuildingOptions options, string name)
            {
                Options = options;
                Name = name;
            }
            
            public BuildingOptions Options { get; set; } = new BuildingOptions();
            public string Name { get; set; }

            public static BaseProfile Clone(BaseProfile profile)
            {
                return profile.MemberwiseClone() as BaseProfile;
            }
        }

        public class DelaySettings
        {
            public bool AllowPVP;
            public RaidableBase RaidableBase;
            public Timer Timer;
        }

        public class Elevation
        {
            public float Max { get; set; }
            public float Min { get; set; }
        }

        public class BMGELEVATOR : FacepunchBehaviour // credits: bmgjet
        {
            internal const string ElevatorPanelName = "RB_UI_Elevator";

            internal Elevator _elevator;
            internal RaycastHit hit;
            internal BaseEntity hitEntity;
            internal RaidableBase raid;
            internal BuildingOptionsElevators options;
            internal HashSet<BasePlayer> _UI = new HashSet<BasePlayer>();
            internal bool HasButton;
            internal uint uid;
            internal int CurrentFloor;
            internal int returnDelay = 60;
            internal float Floors;
            internal float lastPermissionTime;
            internal float _LiftSpeedPerMetre = 3f;

            private void Awake()
            {
                _elevator = GetComponent<Elevator>();
                _elevator.LiftSpeedPerMetre = _LiftSpeedPerMetre;
            }

            private void OnDestroy()
            {
                if (_elevator != null && !_elevator.IsDestroyed) _elevator.Kill();
                _UI.ToList().ForEach(DestroyUi);
                Instance?._elevators.Remove(uid);
                CancelInvoke();
            }

            public void DestroyMe()
            {
                Destroy(this);
            }

            public Vector3 ServerPosition => _elevator.liftEntity.transform.position;

            private Vector3 GetWorldSpaceFloorPosition(int targetFloor)
            {
                int num = _elevator.Floor - targetFloor;
                Vector3 b = Vector3.up * ((float)num * _elevator.FloorHeight);
                b.y -= 1f;
                return base.transform.position - b;
            }

            public void GoToFloor(Elevator.Direction Direction = Elevator.Direction.Down, bool FullTravel = false, int forcedFloor = -1)
            {
                if (_elevator.HasFlag(BaseEntity.Flags.Busy))
                {
                    return;
                }
                int maxFloors = (int)(Floors / 3f);
                if (forcedFloor != -1)
                {
                    int targetFloor = Mathf.RoundToInt((forcedFloor - ServerPosition.y) / 3);
                    if (targetFloor == 0 && CurrentFloor == 0) targetFloor = maxFloors;
                    else if (targetFloor == 0 && CurrentFloor == maxFloors) targetFloor = -maxFloors;
                    CurrentFloor += targetFloor;
                    if (CurrentFloor > maxFloors) CurrentFloor = maxFloors;
                    if (CurrentFloor < 0) CurrentFloor = 0;
                }
                else
                {
                    if (Direction == Elevator.Direction.Up)
                    {
                        CurrentFloor++;
                        if (FullTravel) CurrentFloor = (int)(Floors / _elevator.FloorHeight);
                        if ((CurrentFloor * 3) > Floors) CurrentFloor = (int)(Floors / _elevator.FloorHeight);
                    }
                    else
                    {
                        if (GamePhysics.CheckSphere(ServerPosition - new Vector3(0, 1f, 0), 0.5f, Layers.Mask.Construction | Layers.Server.Deployed, QueryTriggerInteraction.Ignore))
                        {
                            _elevator.Invoke(Retry, returnDelay);
                            return;
                        }

                        CurrentFloor--;
                        if (CurrentFloor < 0 || FullTravel) CurrentFloor = 0;
                    }
                }
                Vector3 worldSpaceFloorPosition = GetWorldSpaceFloorPosition(CurrentFloor);
                if (!GamePhysics.LineOfSight(ServerPosition, worldSpaceFloorPosition, 2097152))
                {
                    if (Direction == Elevator.Direction.Up)
                    {
                        if (!Physics.Raycast(ServerPosition, Vector3.up, out hit, 21f) || (hitEntity = hit.GetEntity()) == null)
                        {
                            return;
                        }
                        CurrentFloor = (int)(Vector3.Distance(hitEntity.transform.position, _elevator.transform.position) / 3);
                        worldSpaceFloorPosition = GetWorldSpaceFloorPosition(CurrentFloor);
                    }
                    else
                    {
                        if (!Physics.Raycast(ServerPosition - new Vector3(0, 2.9f, 0), Vector3.down, out hit, 21f) || (hitEntity = hit.GetEntity()) == null || hitEntity.ShortPrefabName == "foundation" || hitEntity.ShortPrefabName == "elevator.static")
                        {
                            _elevator.Invoke(Retry, returnDelay);
                            return;
                        }
                        CurrentFloor = (int)(Vector3.Distance(hitEntity.transform.position, _elevator.transform.position) / 3) + 1;
                        worldSpaceFloorPosition = GetWorldSpaceFloorPosition(CurrentFloor);
                    }
                }
                Vector3 vector = transform.InverseTransformPoint(worldSpaceFloorPosition);
                float timeToTravel = _elevator.TimeToTravelDistance(Mathf.Abs(_elevator.liftEntity.transform.localPosition.y - vector.y));
                LeanTween.moveLocalY(_elevator.liftEntity.gameObject, vector.y, timeToTravel);
                _elevator.SetFlag(BaseEntity.Flags.Busy, true, false, true);
                _elevator.liftEntity.ToggleHurtTrigger(true);
                _elevator.Invoke(_elevator.ClearBusy, timeToTravel);
                _elevator.CancelInvoke(ElevatorToGround);
                _elevator.Invoke(ElevatorToGround, timeToTravel + returnDelay);
            }

            private void Retry()
            {
                GoToFloor(Elevator.Direction.Down, true);
            }

            private void ElevatorToGround()
            {
                if (CurrentFloor != 0)
                {
                    if (_elevator.HasFlag(BaseEntity.Flags.Busy))
                    {
                        _elevator.Invoke(ElevatorToGround, 5f);
                        return;
                    }
                    GoToFloor(Elevator.Direction.Down, true);
                }
            }

            public void Init(RaidableBase raid)
            {
                this.raid = raid;
                options = raid.Options.Elevators;
                _elevator._maxHealth = options.ElevatorHealth;
                _elevator.InitializeHealth(options.ElevatorHealth, options.ElevatorHealth);

                if (options.Enabled)
                {
                    InvokeRepeating(ShowHealthUI, 10, 1);
                }

                if (HasButton)
                {
                    Instance.Subscribe(nameof(OnButtonPress));
                }
            }

            private void ShowHealthUI()
            {
                var players = raid.intruders.Where(player => player != null && player.IsConnected && player.Distance(ServerPosition) <= 3f);
                foreach (var player in _UI.ToList())
                {
                    if (!players.Contains(player)) // || !GamePhysics.LineOfSight(ServerPosition, player.transform.position, 2097152))
                    {
                        DestroyUi(player);
                        _UI.Remove(player);
                    }
                }
                foreach (var player in players)
                {
                    if (!player.IsSleeping()) // && GamePhysics.LineOfSight(ServerPosition, player.transform.position, 2097152))
                    {
                        var translated = Backbone.GetMessageEx("Elevator Health", player.UserIDString);
                        var color = UI.Color(options.PanelColor, options.PanelAlpha);
                        var elements = UI.CreateElementContainer(ElevatorPanelName, color, options.Min, options.Max);
                        var text = $"{translated} {_elevator._health:#.##}/{_elevator._maxHealth}";
                        UI.CreateLabel(ref elements, ElevatorPanelName, "1 1 1 1", text, 16, "0 0", "1 1");
                        DestroyUi(player);
                        CuiHelper.AddUi(player, elements);
                        _UI.Add(player);
                    }
                }
            }

            public static void DestroyUi(BasePlayer player) => CuiHelper.DestroyUi(player, ElevatorPanelName);

            public static Dictionary<Elevator, BMGELEVATOR> FixElevators(List<BaseEntity> pastedEntities)
            {
                var elevators = new List<BaseEntity>();
                var bmgs = new Dictionary<Elevator, BMGELEVATOR>();
                bool hasButton = false;

                foreach (BaseEntity entity in pastedEntities.ToList())
                {
                    if (entity is Elevator || entity is ElevatorLift)
                    {
                        elevators.Add(entity);
                        pastedEntities.Remove(entity);
                    }
                    else if (entity is PressButton)
                    {
                        hasButton = true;
                    }
                }

                foreach (var list in SplitElevators(elevators))
                {
                    BMGELEVATOR bmgELEVATOR;
                    Elevator elevator = FixElevators(list, out bmgELEVATOR);
                    if (elevator != null)
                    {
                        bmgELEVATOR.HasButton = hasButton;
                        bmgs[elevator] = bmgELEVATOR;
                    }
                }

                return bmgs;
            }

            public static Elevator FixElevators(List<BaseEntity> elevators, out BMGELEVATOR bmgELEVATOR)
            {
                bmgELEVATOR = null;
                if (elevators.Count > 0)
                {
                    Elevator e = FixElevator(elevators, out bmgELEVATOR);
                    if (e != null)
                    {
                        elevators.Add(e);
                        return e;
                    }
                }
                return null;
            }

            private static void CleanElevatorKill(BaseEntity entity)
            {
                if (!entity.IsDestroyed)
                {
                    entity.transform.position = new Vector3(0, -100f, 0);
                    Instance.NextFrame(entity.KillMessage);
                }
            }

            public static Elevator FixElevator(List<BaseEntity> elevators, out BMGELEVATOR bmgELEVATOR)
            {
                bmgELEVATOR = null;
                if (elevators.Count == 1)
                {
                    CleanElevatorKill(elevators[0]);
                    return null;
                }
                Vector3 bottom = new Vector3(999f, 999f, 999f);
                Vector3 top = new Vector3(-999f, -999f, -999f);
                Quaternion rot = elevators[0].transform.rotation;
                foreach (BaseEntity entity in elevators)
                {
                    if (entity.transform.position.y < bottom.y) bottom = entity.transform.position;
                    if (entity.transform.position.y > top.y) top = entity.transform.position;
                    CleanElevatorKill(entity);
                }
                Elevator elevator = GameManager.server.CreateEntity("assets/prefabs/deployable/elevator/static/elevator.static.prefab", bottom, rot, true) as Elevator;
                elevator.enableSaving = false;
                elevator.transform.rotation = rot;
                elevator.transform.position = bottom;
                elevator.transform.localPosition += new Vector3(0f, 0.25f, 0f);
                bmgELEVATOR = elevator.gameObject.AddComponent<BMGELEVATOR>();
                elevator.Spawn();
                bmgELEVATOR.Floors = top.y - bottom.y;
                Interface.Oxide.NextTick(() =>
                {
                    if (elevator == null || elevator.IsDestroyed) return;
                    RemoveImmortality(elevator.baseProtection, 0.3f, 1.0f, 1.0f, 0.5f, 0.9f, 0.9f, 0.3f, 0.9f, 0.9f, 1.0f, 0.9f, 0.9f);
                    RemoveImmortality(elevator.liftEntity.baseProtection, 0.3f, 1.0f, 1.0f, 0.5f, 0.9f, 0.9f, 0.3f, 0.9f, 0.9f, 1.0f, 0.9f, 0.9f);
                });
                elevator.SetFlag(BaseEntity.Flags.Reserved1, true, false, true);
                elevator.SetFlag(Elevator.Flag_HasPower, true);
                elevator.SendNetworkUpdateImmediate();
                bmgELEVATOR.uid = elevator.net.ID;
                Instance._elevators[elevator.net.ID] = bmgELEVATOR;
                Instance.Subscribe(nameof(OnElevatorButtonPress));
                return elevator;
            }

            private static void RemoveImmortality(ProtectionProperties baseProtection, float antivehicle, float arrow, float bite, float bullet, float blunt, float collision, float explosion, float generic, float heat, float radiation, float slash, float stab)
            {
                baseProtection.amounts[(int)DamageType.AntiVehicle] = antivehicle;
                baseProtection.amounts[(int)DamageType.Arrow] = arrow;
                baseProtection.amounts[(int)DamageType.Bite] = bite;
                baseProtection.amounts[(int)DamageType.Bullet] = bullet;
                baseProtection.amounts[(int)DamageType.Blunt] = blunt;
                baseProtection.amounts[(int)DamageType.Collision] = collision;
                baseProtection.amounts[(int)DamageType.Explosion] = explosion;
                baseProtection.amounts[(int)DamageType.Generic] = generic;
                baseProtection.amounts[(int)DamageType.Heat] = heat;
                baseProtection.amounts[(int)DamageType.Radiation] = radiation;
                baseProtection.amounts[(int)DamageType.Slash] = slash;
                baseProtection.amounts[(int)DamageType.Stab] = stab;
            }

            public static List<List<BaseEntity>> SplitElevators(List<BaseEntity> source)
            {
                var result = new List<List<BaseEntity>>();
                List<int> Elevators = new List<int>();
                foreach (BaseEntity entity in source)
                {
                    int distance = (int)(entity.transform.position.x + entity.transform.position.x);
                    if (!Elevators.Contains(distance))
                    {
                        Elevators.Add(distance);
                        result.Add(new List<BaseEntity>
                        {
                            entity
                        });
                    }
                    else
                    {
                        int index = Elevators.IndexOf(distance);
                        result[index].Add(entity);
                    }
                }
                return result;
            }

            private HashSet<ulong> _granted = new HashSet<ulong>();

            public bool HasCardPermission(BasePlayer player)
            {
                if (_granted.Contains(player.userID) || options.RequiredAccessLevel == 0 || Instance.HasPermission(player, "raidablebases.elevators.bypass.card"))
                {
                    return true;
                }

                string shortname = options.RequiredAccessLevel == 1 ? "keycard_green" : options.RequiredAccessLevel == 2 ? "keycard_blue" : "keycard_red";

                Item item = player.inventory.FindItemID(shortname);

                if (item == null || item.skin != options.SkinID)
                {
                    raid.TryMessage(player, options.RequiredAccessLevel == 1 ? "Elevator Green Card" : options.RequiredAccessLevel == 2 ? "Elevator Blue Card" : options.RequiredAccessLevel == 3 ? "Elevator Red Card" : "Elevator Special Card");
                    return false;
                }

                Keycard keycard = item.GetHeldEntity() as Keycard;

                if (keycard?.accessLevel == options.RequiredAccessLevel)
                {
                    if (options.RequiredAccessLevelOnce)
                    {
                        _granted.Add(player.userID);
                    }

                    return true;
                }

                raid.TryMessage(player, options.RequiredAccessLevel == 1 ? "Elevator Green Card" : options.RequiredAccessLevel == 2 ? "Elevator Blue Card" : options.RequiredAccessLevel == 3 ? "Elevator Red Card" : "Elevator Special Card");
                return false;
            }

            public bool HasBuildingPermission(BasePlayer player)
            {
                if (!options.RequiresBuildingPermission || Instance.HasPermission(player, "raidablebases.elevators.bypass.building"))
                {
                    return true;
                }

                if (Time.time < lastPermissionTime)
                {
                    return false;
                }

                lastPermissionTime = Time.time + 1f;

                if (player.IsBuildingBlocked())
                {
                    raid.TryMessage(player, "Elevator Privileges");
                    return false;
                }

                return true;
            }
        }

        public class HumanoidBrain : BaseAIBrain<global::HumanNPC>
        {
            internal enum AttackType
            {
                BaseProjectile,
                FlameThrower,
                Melee,
                Water,
                Syringe,
                None
            }

            internal ScientistNPC npc;
            private AttackEntity _attackEntity;
            private FlameThrower flameThrower;
            private LiquidWeapon liquidWeapon;
            private MedicalTool syringe;
            private BaseMelee baseMelee;
            private BasePlayer AttackTarget;
            internal RaidableBase raid;
            internal NpcSettings Settings;
            private List<Vector3> positions;
            internal Vector3 DestinationOverride;
            private ulong uid;
            private float lastWarpTime;
            internal float softLimitSenseRange;
            private float nextAttackTime;
            private float attackRange;
            private float attackCooldown;
            internal AttackType attackType = AttackType.None;
            private BaseNavigator.NavigationSpeed CurrentSpeed = BaseNavigator.NavigationSpeed.Normal;

            internal Vector3 AttackPosition => AttackTarget.ServerPosition;

            internal Vector3 ServerPosition => npc.ServerPosition;

            internal AttackEntity AttackEntity
            {
                get
                {
                    if (_attackEntity == null)
                    {
                        IdentifyWeapon();
                    }

                    return _attackEntity;
                }
            }

            internal void IdentifyWeapon()
            {
                _attackEntity = GetEntity().GetAttackEntity();

                attackRange = 0f;
                attackCooldown = 99999f;
                attackType = AttackType.None;
                baseMelee = null;
                flameThrower = null;
                liquidWeapon = null;
                syringe = null;

                if (_attackEntity == null)
                {
                    return;
                }

                switch (_attackEntity.ShortPrefabName)
                {
                    case "ak47u.entity":
                    case "bolt_rifle.entity":
                    case "double_shotgun.entity":
                    case "l96.entity":
                    case "lr300.entity":
                    case "m249.entity":
                    case "m39.entity":
                    case "m92.entity":
                    case "mp5.entity":
                    case "nailgun.entity":
                    case "pistol_eoka.entity":
                    case "pistol_revolver.entity":
                    case "pistol_semiauto.entity":
                    case "python.entity":
                    case "semi_auto_rifle.entity":
                    case "shotgun_pump.entity":
                    case "shotgun_waterpipe.entity":
                    case "spas12.entity":
                    case "thompson.entity":
                    case "smg.entity":
                        SetAttackRestrictions(AttackType.BaseProjectile, 300f, 0f, 150f);
                        break;
                    case "snowballgun.entity":
                        SetAttackRestrictions(AttackType.BaseProjectile, 15f, 0.1f, 15f);
                        break;
                    case "chainsaw.entity":
                    case "jackhammer.entity":
                        baseMelee = _attackEntity as BaseMelee;
                        SetAttackRestrictions(AttackType.Melee, 2.5f, (_attackEntity.animationDelay + _attackEntity.deployDelay) * 2f);
                        break;
                    case "axe_salvaged.entity":
                    case "bone_club.entity":
                    case "butcherknife.entity":
                    case "candy_cane.entity":
                    case "hammer_salvaged.entity":
                    case "hatchet.entity":
                    case "icepick_salvaged.entity":
                    case "knife.combat.entity":
                    case "knife_bone.entity":
                    case "longsword.entity":
                    case "mace.entity":
                    case "machete.weapon":
                    case "pickaxe.entity":
                    case "pitchfork.entity":
                    case "salvaged_cleaver.entity":
                    case "salvaged_sword.entity":
                    case "sickle.entity":
                    case "spear_stone.entity":
                    case "spear_wooden.entity":
                    case "stone_pickaxe.entity":
                    case "stonehatchet.entity":
                        baseMelee = _attackEntity as BaseMelee;
                        SetAttackRestrictions(AttackType.Melee, 2.5f, _attackEntity.animationDelay + _attackEntity.deployDelay);
                        break;
                    case "flamethrower.entity":
                        flameThrower = _attackEntity as FlameThrower;
                        SetAttackRestrictions(AttackType.FlameThrower, 10f, (_attackEntity.animationDelay + _attackEntity.deployDelay) * 2f);
                        break;
                    case "compound_bow.entity":
                    case "crossbow.entity":
                    case "speargun.entity":
                        SetAttackRestrictions(AttackType.BaseProjectile, 200f, (_attackEntity.animationDelay + _attackEntity.deployDelay) * 1.25f, 150f);
                        break;
                    case "watergun.entity":
                    case "waterpistol.entity":
                        liquidWeapon = _attackEntity as LiquidWeapon;
                        liquidWeapon.AutoPump = true;
                        SetAttackRestrictions(AttackType.Water, 10f, 2f);
                        break;
                    case "syringe_medical.entity":
                        syringe = _attackEntity as MedicalTool;
                        SetAttackRestrictions(AttackType.Syringe, 2.5f, 4f);
                        break;
                    default: _attackEntity = null; break;
                }
            }

            private void SetAttackRestrictions(AttackType attackType, float attackRange, float attackCooldown, float effectiveRange = 0f)
            {
                if (effectiveRange != 0f)
                {
                    _attackEntity.effectiveRange = effectiveRange;
                }

                this.attackType = attackType;
                this.attackRange = attackRange;
                this.attackCooldown = attackCooldown;
            }

            public bool ValidTarget
            {
                get
                {
                    if (AttackTarget == null || AttackTarget.IsDestroyed || ShouldForgetTarget(AttackTarget))
                    {
                        return false;
                    }

                    return true;
                }
            }

            public override void OnDestroy()
            {
                if (!Rust.Application.isQuitting)
                {
                    BaseEntity.Query.Server.RemoveBrain(GetEntity());
                    Instance?.HumanoidBrains?.Remove(uid);
                    CancelInvoke();
                    LeaveGroup();
                }
            }

            public override void InitializeAI()
            {
                base.InitializeAI();
                base.ForceSetAge(0f);

                Pet = false;
                sleeping = false;
                UseAIDesign = true;
                AllowedToSleep = false;
                HostileTargetsOnly = false;
                AttackRangeMultiplier = 2f;
                MaxGroupSize = 0;

                Senses.Init(
                    owner: GetEntity(),
                    memoryDuration: 5f,
                    range: 50f,
                    targetLostRange: 75f,
                    visionCone: -1f,
                    checkVision: false,
                    checkLOS: true,
                    ignoreNonVisionSneakers: true,
                    listenRange: 15f,
                    hostileTargetsOnly: false,
                    senseFriendlies: false,
                    ignoreSafeZonePlayers: false,
                    senseTypes: EntityType.Player,
                    refreshKnownLOS: true
                );
            }

            public override void AddStates()
            {
                base.AddStates();

                states[AIState.Attack] = new AttackState(this);
            }

            public class AttackState : BaseAttackState
            {
                private new HumanoidBrain brain;

                private IAIAttack attack => brain.Senses.ownerAttack;

                public AttackState(HumanoidBrain humanoidBrain)
                {
                    base.brain = brain = humanoidBrain;
                    base.AgrresiveState = true;
                }

                public new global::HumanNPC GetEntity()
                {
                    return brain.baseEntity;
                }

                public override void StateEnter()
                {
                    if (brain.ValidTarget)
                    {
                        if (InAttackRange())
                        {
                            StartAttacking();
                        }
                        else
                        {
                            StopAttacking();
                        }
                        if (brain.Navigator.CanUseNavMesh)
                        {
                            brain.Navigator.SetDestination(brain.DestinationOverride, BaseNavigator.NavigationSpeed.Fast, 0f, 0f);
                        }
                    }
                }

                public override void StateLeave()
                {
                    StopAttacking();
                }

                private void StopAttacking()
                {
                    if (attack != null)
                    {
                        attack.StopAttacking();
                        brain.Navigator.ClearFacingDirectionOverride();
                    }
                }

                public override StateStatus StateThink(float delta)
                {
                    if (attack == null)
                    {
                        return StateStatus.Error;
                    }
                    if (!brain.ValidTarget)
                    {
                        StopAttacking();

                        return StateStatus.Finished;
                    }
                    if (brain.Senses.ignoreSafeZonePlayers && brain.AttackTarget.InSafeZone())
                    {
                        return StateStatus.Error;
                    }
                    if (brain.Navigator.CanUseNavMesh && !brain.Navigator.SetDestination(brain.DestinationOverride, BaseNavigator.NavigationSpeed.Fast, 0f, 0f))
                    {
                        return StateStatus.Error;
                    }
                    if (!brain.CanLeave(brain.AttackPosition) || !brain.CanShoot())
                    {
                        brain.Forget();

                        StopAttacking();

                        return StateStatus.Finished;
                    }
                    if (InAttackRange())
                    {
                        StartAttacking();
                    }
                    else
                    {
                        StopAttacking();
                    }
                    return StateStatus.Running;
                }

                private bool InAttackRange()
                {
                    return attack.CanAttack(brain.AttackTarget) && brain.IsInAttackRange() && brain.CanSeeTarget(brain.AttackTarget);
                }

                private void StartAttacking()
                {
                    brain.SetAimDirection();

                    if (!brain.CanShoot() || brain.IsAttackOnCooldown())
                    {
                        return;
                    }

                    if (brain.attackType == AttackType.BaseProjectile)
                    {
                        GetEntity().ShotTest(Vector3.Distance(brain.AttackPosition, brain.ServerPosition));
                    }
                    else if (brain.attackType == AttackType.FlameThrower)
                    {
                        brain.UseFlameThrower();
                    }
                    else if (brain.attackType == AttackType.Water)
                    {
                        brain.UseWaterGun();
                    }
                    else if (brain.CanUseMedicalTool())
                    {
                        brain.UseSyringe(true);
                    }
                    else brain.MeleeAttack();
                }
            }

            private bool init;

            public void Init()
            {
                if (init) return;
                init = true;
                lastWarpTime = Time.time;
                npc.spawnPos = raid.Location;
                npc.AdditionalLosBlockingLayer = visibleLayer;
                Instance.HumanoidBrains[uid = npc.userID] = this;

                IdentifyWeapon();

                SetupNavigator(GetEntity(), GetComponent<BaseNavigator>(), raid.ProtectionRadius);
            }

            private void Converge()
            {
                foreach (var brain in Instance.HumanoidBrains.Values)
                {
                    if (brain != this && brain.attackType == attackType && brain.CanConverge(npc) && CanLeave(AttackPosition))
                    {
                        brain.SetTarget(AttackTarget, false);
                        brain.TryToAttack(AttackTarget);
                    }
                }
            }

            public void Forget()
            {
                Senses.Players.Clear();
                Senses.Memory.All.Clear();
                Senses.Memory.Threats.Clear();
                Senses.Memory.Targets.Clear();
                Senses.Memory.Players.Clear();
                Navigator.ClearFacingDirectionOverride();

                DestinationOverride = GetRandomRoamPosition();
                SenseRange = ListenRange = Settings.AggressionRange;
                TargetLostRange = SenseRange * 1.25f;
                AttackTarget = null;

                TryReturnHome();
            }

            private void RandomMove(float radius)
            {
                var to = AttackPosition + UnityEngine.Random.onUnitSphere * radius;

                to.y = TerrainMeta.HeightMap.GetHeight(to);

                SetDestination(to);
            }

            public void SetupNavigator(BaseCombatEntity owner, BaseNavigator navigator, float distance)
            {
                navigator.CanUseNavMesh = !Rust.Ai.AiManager.nav_disable;
                navigator.MaxRoamDistanceFromHome = navigator.BestMovementPointMaxDistance = navigator.BestRoamPointMaxDistance = distance * 0.85f;
                navigator.DefaultArea = "Walkable";
                navigator.topologyPreference = ((TerrainTopology.Enum)TerrainTopology.EVERYTHING);
                navigator.Agent.agentTypeID = -1372625422;
                navigator.MaxWaterDepth = config.Settings.Management.WaterDepth;
                if (navigator.CanUseNavMesh) navigator.Init(owner, navigator.Agent);
            }

            private void SetAimDirection()
            {
                Navigator.SetFacingDirectionEntity(AttackTarget);
            }

            private void SetDestination()
            {
                SetDestination(GetRandomRoamPosition());
            }

            private void SetDestination(Vector3 destination)
            {
                if (!CanLeave(destination))
                {
                    if (attackType != AttackType.BaseProjectile)
                    {
                        destination = ((destination.XZ3D() - raid.Location.XZ3D()).normalized * (raid.ProtectionRadius * 0.75f)) + raid.Location;

                        destination += UnityEngine.Random.onUnitSphere * (raid.ProtectionRadius * 0.2f);
                    }
                    else
                    {
                        destination = GetRandomRoamPosition();
                    }

                    CurrentSpeed = BaseNavigator.NavigationSpeed.Normal;
                }

                if (destination != DestinationOverride)
                {
                    destination.y = TerrainMeta.HeightMap.GetHeight(destination);

                    DestinationOverride = destination;
                }

                Navigator.SetCurrentSpeed(CurrentSpeed);

                if (Navigator.CurrentNavigationType == BaseNavigator.NavigationType.None && !Rust.Ai.AiManager.ai_dormant && !Rust.Ai.AiManager.nav_disable)
                {
                    Navigator.SetCurrentNavigationType(BaseNavigator.NavigationType.NavMesh);
                }

                if (Navigator.Agent == null || !Navigator.Agent.enabled || !Navigator.Agent.isOnNavMesh || !Navigator.SetDestination(destination, CurrentSpeed, 0f, 0f))
                {
                    Navigator.Destination = destination;
                    npc.finalDestination = destination;
                }
            }

            public void SetTarget(BasePlayer player, bool converge = true)
            {
                if (AttackTarget == player)
                {
                    return;
                }

                Senses.Memory.SetKnown(player, npc, null);
                npc.lastAttacker = player;
                AttackTarget = player;

                if (!IsInSenseRange(player.transform.position))
                {
                    SenseRange = ListenRange = Settings.AggressionRange + Vector3.Distance(player.transform.position, ServerPosition);
                    TargetLostRange = SenseRange + (SenseRange * 0.25f);
                }
                else
                {
                    SenseRange = ListenRange = softLimitSenseRange;
                    TargetLostRange = softLimitSenseRange * 1.25f;
                }

                if (converge)
                {
                    Converge();
                }
            }

            private void TryReturnHome()
            {
                if (Settings.CanLeave && !IsInHomeRange())
                {
                    CurrentSpeed = BaseNavigator.NavigationSpeed.Normal;

                    Warp();
                }
            }

            private void TryToAttack() => TryToAttack(null);

            private void TryToAttack(BasePlayer attacker)
            {
                if (attacker == null)
                {
                    attacker = GetBestTarget();
                }

                if (attacker == null)
                {
                    return;
                }

                if (ShouldForgetTarget(attacker))
                {
                    Forget();

                    return;
                }

                SetTarget(attacker);

                if (!CanSeeTarget(attacker))
                {
                    return;
                }

                if (attackType == AttackType.BaseProjectile)
                {
                    TryScientistActions();
                }
                else
                {
                    TryMurdererActions();
                }

                SwitchToState(AIState.Attack, -1);
            }

            private void TryMurdererActions()
            {
                CurrentSpeed = BaseNavigator.NavigationSpeed.Fast;

                if (!IsInReachableRange())
                {
                    RandomMove(15f);
                }
                else if (!IsInAttackRange())
                {
                    if (attackType == AttackType.FlameThrower)
                    {
                        RandomMove(attackRange);
                    }
                    else
                    {
                        SetDestination(AttackPosition);
                    }
                }
            }

            private void TryScientistActions()
            {
                CurrentSpeed = BaseNavigator.NavigationSpeed.Fast;

                SetDestination();
            }

            public void SetupMovement(List<Vector3> positions)
            {
                this.positions = positions;

                InvokeRepeating(TryToAttack, 1f, 1f);
                InvokeRepeating(TryToRoam, 0f, 7.5f);
            }

            private void TryToRoam()
            {
                if (ValidTarget)
                {
                    return;
                }

                if (npc.IsSwimming())
                {
                    npc.Kill();
                    Destroy(this);
                    return;
                }

                CurrentSpeed = BaseNavigator.NavigationSpeed.Normal;

                SetDestination();
            }

            public void Warp()
            {
                if (Time.time < lastWarpTime)
                {
                    return;
                }

                lastWarpTime = Time.time + 1f;

                DestinationOverride = GetRandomRoamPosition();

                Navigator.Warp(DestinationOverride);
            }

            private void UseFlameThrower()
            {
                if (flameThrower.ammo < flameThrower.maxAmmo * 0.25)
                {
                    flameThrower.SetFlameState(false);
                    flameThrower.ServerReload();
                    return;
                }
                npc.triggerEndTime = Time.time + attackCooldown;
                flameThrower.SetFlameState(true);
                flameThrower.Invoke(() => flameThrower.SetFlameState(false), 2f);
            }

            private void UseWaterGun()
            {
                RaycastHit hit;
                Physics.Raycast(npc.eyes.BodyRay(), out hit, 10f, 1218652417);
                List<DamageTypeEntry> damage = new List<DamageTypeEntry>();
                WaterBall.DoSplash(hit.point, 2f, ItemManager.FindItemDefinition("water"), 10);
                DamageUtil.RadiusDamage(npc, liquidWeapon.LookupPrefab(), hit.point, 0.15f, 0.15f, damage, 131072, true);
            }

            public bool CanUseMedicalTool()
            {
                return npc.inventory.containerBelt.itemList.Exists(item => item.info.shortname == "syringe.medical");
            }

            private void UseSyringe(bool now = false)
            {
                raid.EquipWeapon(npc, this, now);
                IdentifyWeapon();
                if (syringe == null) return;
                syringe.ServerUse();
                Invoke(() => UseSyringe(), 4f);
            }

            private void UseChainsaw()
            {
                AttackEntity.TopUpAmmo();
                AttackEntity.ServerUse();
                AttackTarget.Hurt(10f * AttackEntity.npcDamageScale, DamageType.Bleeding, npc);
            }

            private void MeleeAttack()
            {
                if (baseMelee == null)
                {
                    return;
                }

                if (AttackEntity is Chainsaw)
                {
                    UseChainsaw();
                    return;
                }

                Vector3 position = AttackPosition;
                AttackEntity.StartAttackCooldown(AttackEntity.repeatDelay * 2f);
                npc.SignalBroadcast(BaseEntity.Signal.Attack, string.Empty, null);
                if (baseMelee.swingEffect.isValid)
                {
                    Effect.server.Run(baseMelee.swingEffect.resourcePath, position, Vector3.forward, npc.Connection, false);
                }
                HitInfo hitInfo = new HitInfo
                {
                    damageTypes = new DamageTypeList(),
                    DidHit = true,
                    Initiator = npc,
                    HitEntity = AttackTarget,
                    HitPositionWorld = position,
                    HitPositionLocal = AttackTarget.transform.InverseTransformPoint(position),
                    HitNormalWorld = npc.eyes.BodyForward(),
                    HitMaterial = StringPool.Get("Flesh"),
                    PointStart = ServerPosition,
                    PointEnd = position,
                    Weapon = AttackEntity,
                    WeaponPrefab = AttackEntity
                };
                hitInfo.damageTypes.Set(DamageType.Slash, 30f * AttackEntity.npcDamageScale);
                Effect.server.ImpactEffect(hitInfo);
                AttackTarget.OnAttacked(hitInfo);
            }
            
            private bool CanConverge(global::HumanNPC other)
            {
                if (ValidTarget && !ShouldForgetTarget(AttackTarget)) return false;
                if (other == null || other.IsDestroyed || other.IsDead()) return false;
                return IsInTargetRange(other.transform.position);
            }

            private bool CanLeave(Vector3 destination)
            {
                return Settings.CanLeave || IsInLeaveRange(destination);
            }

            private bool CanSeeTarget(BasePlayer target)
            {
                if (Navigator.CurrentNavigationType == BaseNavigator.NavigationType.None && (attackType == AttackType.FlameThrower || attackType == AttackType.Melee))
                {
                    return true;
                }

                if (Vector3.Distance(ServerPosition, target.ServerPosition) < 10f || Senses.Memory.IsLOS(target))
                {
                    return true;
                }

                nextAttackTime = Time.realtimeSinceStartup + 1f;

                return false;
            }

            public bool CanRoam(Vector3 destination)
            {
                return destination == DestinationOverride && IsInSenseRange(destination);
            }

            private bool CanShoot()
            {
                if (attackType == AttackType.None)
                {
                    return false;
                }

                return Settings.CanShoot || attackType != AttackType.BaseProjectile || IsInLeaveRange(AttackPosition);
            }

            public BasePlayer GetBestTarget()
            {
                if (npc.IsWounded())
                {
                    return null;
                }
                float delta = -1f;
                BasePlayer target = null;
                foreach (var player in Senses.Memory.Targets.OfType<BasePlayer>())
                {
                    if (player == null || player.IsNpc || !player.userID.IsSteamId() || player.health <= 0f || player.IsDead()) continue;
                    float dist = Vector3.Distance(player.transform.position, npc.transform.position);
                    float rangeDelta = 1f - Mathf.InverseLerp(1f, SenseRange, dist);
                    rangeDelta += (CanSeeTarget(player) ? 2f : 0f);
                    if (rangeDelta <= delta) continue;
                    target = player;
                    delta = rangeDelta;
                }
                return target;
            }

            private Vector3 GetRandomRoamPosition()
            {
                return positions.GetRandom();
            }

            private bool IsAttackOnCooldown()
            {
                if (attackType == AttackType.None || Time.realtimeSinceStartup < nextAttackTime)
                {
                    return true;
                }

                if (attackCooldown > 0f)
                {
                    nextAttackTime = Time.realtimeSinceStartup + attackCooldown;
                }

                return false;
            }

            private bool IsInAttackRange(float range = 0f)
            {
                return InRange(ServerPosition, AttackPosition, range == 0f ? attackRange : range, false);
            }

            private bool IsInHomeRange()
            {
                return InRange(ServerPosition, raid.Location, Mathf.Max(raid.ProtectionRadius, TargetLostRange), false);
            }

            private bool IsInLeaveRange(Vector3 destination)
            {
                return InRange(raid.Location, destination, raid.ProtectionRadius, false);
            }

            private bool IsInReachableRange()
            {
                if (AttackPosition.y - ServerPosition.y > attackRange)
                {
                    return false;
                }

                return attackType != AttackType.Melee || InRange(AttackPosition, ServerPosition, 15f, false);
            }

            private bool IsInSenseRange(Vector3 destination)
            {
                return InRange(raid.Location, destination, SenseRange);
            }

            private bool IsInTargetRange(Vector3 destination)
            {
                return InRange(raid.Location, destination, TargetLostRange);
            }

            private bool ShouldForgetTarget(BasePlayer target)
            {
                return target.health <= 0f || target.limitNetworking || !IsInTargetRange(target.transform.position);
            }
        }

        public class MountInfo
        {
            public Vector3 position;
            public float radius;
            public BaseMountable mountable;
        }

        public class PlayerInfo
        {
            public PlayerInfo() { }

            public int Raids { get; set; }
            public int TotalRaids { get; set; }
        }

        public class PlayerInputEx : FacepunchBehaviour
        {
            public BasePlayer player { get; set; }
            private InputState input { get; set; }
            private RaidableBase raid { get; set; }

            public static bool IsLadderNear(Vector3 target)
            {
                var list = Pool.GetList<BaseLadder>();

                Vis.Entities(target, 0.3f, list, Layers.Mask.Deployed, QueryTriggerInteraction.Ignore);

                bool result = list.Count > 0;

                Pool.FreeList(ref list);

                return result;
            }

            public void Restart()
            {
                CancelInvoke(Repeater);
                InvokeRepeating(Repeater, 0.1f, 0.1f);
            }

            public void Setup(BasePlayer player, RaidableBase raid)
            {
                this.player = player;
                this.raid = raid;
                raid.Inputs[player] = this;
                input = player.serverInput;
                InvokeRepeating(Repeater, 0.1f, 0.1f);
            }
            public bool TryPlaceLadder(BasePlayer player, RaidableBase raid)
            {
                if (player.svActiveItemID == 0)
                {
                    return false;
                }

                var input = player.serverInput;

                if (!input.WasJustReleased(BUTTON.FIRE_PRIMARY) && !input.IsDown(BUTTON.FIRE_PRIMARY))
                {
                    return false;
                }

                Item item = player.GetActiveItem();

                if (item?.info.shortname != "ladder.wooden.wall")
                {
                    return false;
                }

                if (raid.Options.RequiresCupboardAccessLadders && !player.CanBuild())
                {
                    Backbone.Message(player, "Ladders Require Building Privilege!");
                    return false;
                }

                RaycastHit hit;
                if (!Physics.Raycast(player.eyes.HeadRay(), out hit, 4f, Layers.Mask.Construction, QueryTriggerInteraction.Ignore))
                {
                    return false;
                }

                var block = hit.GetEntity();

                if (!block.IsValid() || block.OwnerID != 0 || !Backbone.Path.Blocks.Contains(block.prefabID)) // walls and foundations
                {
                    return false;
                }

                int amount = item.amount;
                var action = new Action(() =>
                {
                    if (raid == null || item == null || item.amount != amount || IsLadderNear(hit.point))
                    {
                        return;
                    }

                    var rot = Quaternion.LookRotation(hit.normal, Vector3.up);
                    var e = GameManager.server.CreateEntity(Backbone.Path.Ladder, hit.point, rot, true);

                    if (e == null)
                    {
                        return;
                    }

                    e.gameObject.SendMessage("SetDeployedBy", player, SendMessageOptions.DontRequireReceiver);
                    e.OwnerID = 0;
                    e.Spawn();
                    item.UseItem(1);

                    var planner = item.GetHeldEntity() as Planner;

                    if (planner != null)
                    {
                        var deployable = planner?.GetDeployable();

                        if (deployable != null && deployable.setSocketParent && block.SupportsChildDeployables())
                        {
                            e.SetParent(block, true, false);
                        }
                    }

                    raid.Entities.Add(e);
                    raid.BuiltList.Add(e.net.ID);
                });

                player.Invoke(action, 0.1f);
                return true;
            }

            private void OnDestroy()
            {
                CancelInvoke();
                raid?.Inputs?.Remove(player);
                Destroy(this);
            }

            private void Repeater()
            {
                if (raid == null)
                {
                    Destroy(this);
                    return;
                }

                if (!player || !player.IsConnected)
                {
                    raid.TryInvokeResetOwner();
                    Destroy(this);
                    return;
                }

                TryPlaceLadder(player, raid);
            }
        }

        public class RandomBase
        {
            public string BaseName;
            public Vector3 Position;
            public BaseProfile Profile;
            public RaidableType Type;
        }

        public class RaidableSpawnLocation
        {
            public List<Vector3> Surroundings = new List<Vector3>();
            public Elevation Elevation = new Elevation();
            public Vector3 Location = Vector3.zero;
            public float WaterHeight;
            public float TerrainHeight;
            public float SpawnHeight;
            public float Radius;
            public bool AutoHeight;

            public RaidableSpawnLocation(Vector3 Location)
            {
                this.Location = Location;
            }
        }

        public class RaidableSpawns
        {
            public readonly HashSet<RaidableSpawnLocation> Spawns = new HashSet<RaidableSpawnLocation>();
            private readonly Dictionary<CacheType, HashSet<RaidableSpawnLocation>> Cached = new Dictionary<CacheType, HashSet<RaidableSpawnLocation>>();
            private float lastTryTime;

            public bool IsCustomSpawn { get; set; }

            public int Count => Spawns.Count;

            public HashSet<RaidableSpawnLocation> Active => Spawns;

            public HashSet<RaidableSpawnLocation> Inactive(CacheType cacheType) => Get(cacheType);

            public RaidableSpawns(HashSet<RaidableSpawnLocation> spawns)
            {
                Spawns = spawns;
                IsCustomSpawn = true;
            }

            public RaidableSpawns() { }

            public void Add(RaidableSpawnLocation rsl, CacheType cacheType)
            {
                switch (cacheType)
                {
                    case CacheType.Submerged:
                    {
                        rsl.WaterHeight = TerrainMeta.WaterMap.GetHeight(rsl.Location);
                        rsl.Surroundings.Clear();
                    }
                    break;
                    case CacheType.Generic:
                    {
                        if (Instance.EventTerritory(rsl.Location))
                        {
                            return;
                        }
                    }
                    break;
                }

                Spawns.Add(rsl);
            }

            public void Check()
            {
                if (lastTryTime == 0f || Time.time - lastTryTime > 600f)
                {
                    TryAddRange(CacheType.Temporary, true);
                    TryAddRange(CacheType.Privilege, true);
                    lastTryTime = Time.time;
                }

                if (Spawns.Count == 0)
                {
                    TryAddRange();
                }
            }

            public void TryAddRange(CacheType cacheType = CacheType.Generic, bool forced = false)
            {
                HashSet<RaidableSpawnLocation> cache = Get(cacheType);

                foreach (var rsl in cache)
                {
                    if (forced)
                    {
                        Spawns.Add(rsl);
                    }
                    else Add(rsl, cacheType);
                }

                cache.RemoveWhere(rsl => Spawns.Contains(rsl));
            }

            public RaidableSpawnLocation GetRandom(BuildingWaterOptions options)
            {
                options.SpawnOnSeabed = false;

                if (Instance.Seabed.Count > 0 && UnityEngine.Random.Range(0f, 100f) >= options.Seabed)
                {
                    var rsl = Instance.Seabed.ElementAt(UnityEngine.Random.Range(0, Instance.Seabed.Count));

                    Remove(rsl, CacheType.Generic);

                    options.SpawnOnSeabed = true;

                    return rsl;
                }
                else
                {
                    var rsl = Spawns.ElementAt(UnityEngine.Random.Range(0, Spawns.Count));

                    Remove(rsl, CacheType.Generic);

                    return rsl;
                }
            }

            public HashSet<RaidableSpawnLocation> Get(CacheType cacheType)
            {
                HashSet<RaidableSpawnLocation> cache;
                if (!Cached.TryGetValue(cacheType, out cache))
                {
                    Cached[cacheType] = cache = new HashSet<RaidableSpawnLocation>();
                }

                return cache;
            }

            public void AddNear(Vector3 target, float radius, CacheType cacheType, bool delayed)
            {
                if (delayed)
                {
                    Instance.timer.Once(1200f, () => AddNear(target, radius, cacheType, false));
                    return;
                }

                HashSet<RaidableSpawnLocation> cache = Get(cacheType);

                AddNear(cache, target, radius);
            }

            private void AddNear(HashSet<RaidableSpawnLocation> cache, Vector3 target, float radius)
            {
                foreach (var b in cache)
                {
                    if (InRange(target, b.Location, radius))
                    {
                        Spawns.Add(b);
                    }
                }

                cache.RemoveWhere(Spawns.Contains);
            }

            public void Remove(RaidableSpawnLocation a, CacheType cacheType)
            {
                Get(cacheType).Add(a);
                Spawns.Remove(a);
            }

            public float RemoveNear(Vector3 target, float radius, CacheType cacheType, RaidableType type)
            {
                if (cacheType == CacheType.Generic)
                {
                    radius = Mathf.Max(GetDistance(type), radius);
                }

                var cache = Get(cacheType);

                foreach (var b in Spawns)
                {
                    if (InRange(target, b.Location, radius))
                    {
                        cache.Add(b);
                    }
                }

                Spawns.RemoveWhere(x => cache.Contains(x));
                return radius;
            }
        }

        public class ResourcePath
        {
            public ResourcePath()
            {
                Blocks = new List<uint> { 803699375, 2194854973, 919059809, 3531096400, 310235277, 2326657495, 3234260181, 72949757, 1745077396, 1585379529, 995542180, 372561515 };
                TrueDamage = new List<uint> { 976279966, 3824663394, 1202834203, 4254045167, 1745077396, 1585379529, 1585379529, 921229511 };
                CodeLock = StringPool.Get(Constants.ID_CODELOCK);
                ExplosionMarker = StringPool.Get(Constants.ID_EXPLOSIONMARKER);
                HighExternalStoneWall = StringPool.Get(Constants.ID_HIGHEXTERNALSTONEWALL);
                HighExternalWoodenWall = StringPool.Get(Constants.ID_HIGHEXTERNALWOODENWALL);
                Ladder = StringPool.Get(Constants.ID_LADDER);
                RadiusMarker = StringPool.Get(Constants.ID_RADIUSMARKER);
                Scientist = StringPool.Get(Constants.ID_SCIENTIST);
                Sphere = StringPool.Get(Constants.ID_SPHERE);
                VendingMarker = StringPool.Get(Constants.ID_VENDINGMARKER);
            }

            public List<uint> Blocks { get; }
            public string CodeLock { get; }
            public string ExplosionMarker { get; }
            public string HighExternalStoneWall { get; }
            public string HighExternalWoodenWall { get; }
            public string Ladder { get; }
            public string RadiusMarker { get; }
            public string Scientist { get; }
            public string Sphere { get; }
            public List<uint> TrueDamage { get; }
            public string VendingMarker { get; }
        }

        public class RotationCycle
        {
            private Dictionary<RaidableMode, List<string>> _buildings = new Dictionary<RaidableMode, List<string>>();

            public void Add(RaidableType type, RaidableMode mode, string key)
            {
                if (!config.Settings.Management.RequireAllSpawned || type == RaidableType.Grid || type == RaidableType.Manual)
                {
                    return;
                }

                List<string> keyList;
                if (!_buildings.TryGetValue(mode, out keyList))
                {
                    _buildings[mode] = keyList = new List<string>();
                }

                if (!keyList.Contains(key))
                {
                    keyList.Add(key);
                }
            }

            public bool CanSpawn(RaidableType type, RaidableMode mode, string key)
            {
                if (mode == RaidableMode.Disabled)
                {
                    return false;
                }

                if (!config.Settings.Management.RequireAllSpawned || mode == RaidableMode.Random || type == RaidableType.Grid || type == RaidableType.Manual)
                {
                    return true;
                }

                List<string> keyList;
                if (!_buildings.TryGetValue(mode, out keyList))
                {
                    return true;
                }

                return TryClear(type, mode, keyList) || !keyList.Contains(key);
            }

            public bool TryClear(RaidableType type, RaidableMode mode, List<string> keyList)
            {
                foreach (var profile in Buildings.Profiles)
                {
                    if (MustExclude(type, profile.Value.Options.AllowPVP))
                    {
                        continue;
                    }

                    if (!keyList.Contains(profile.Key) && FileExists(profile.Key))
                    {
                        return false;
                    }

                    foreach (var kvp in profile.Value.Options.AdditionalBases)
                    {
                        if (!keyList.Contains(kvp.Key) && FileExists(kvp.Key))
                        {
                            return false;
                        }
                    }
                }

                keyList.Clear();
                return true;
            }
        }

        public class SingletonBackbone : SingletonComponent<SingletonBackbone>
        {
            public SingletonBackbone(RaidableBases plugin)
            {
                Plugin = plugin;
                Path = new ResourcePath();
                OceanLevel = WaterSystem.OceanLevel;
                InvokeRepeating(CheckOceanLevel, 60f, 60f);
            }

            public Oxide.Core.Libraries.Lang lang => Plugin.lang;
            public float OceanLevel { get; set; }
            public ResourcePath Path { get; private set; }
            public RaidableBases Plugin { get; private set; }
            private StringBuilder sb => Plugin._sb;
            public void Destroy()
            {
                Path = null;
                Plugin = null;
                DestroyImmediate(Instance);
            }

            public string GetMessageEx(string key, string id = null, params object[] args)
            {
                sb.Length = 0;

                sb.Append(id == "server_console" || id == null ? RemoveFormatting(lang.GetMessage(key, Plugin, id)) : lang.GetMessage(key, Plugin, id));

                return args.Length > 0 ? string.Format(sb.ToString(), args) : sb.ToString();
            }

            public string GetMessage(string key, string id = null, params object[] args)
            {
                sb.Length = 0;

                if (config.EventMessages.Prefix)
                {
                    sb.Append(lang.GetMessage("Prefix", Plugin, id));
                }

                sb.Append(id == "server_console" || id == null ? RemoveFormatting(lang.GetMessage(key, Plugin, id)) : lang.GetMessage(key, Plugin, id));

                return args.Length > 0 ? string.Format(sb.ToString(), args) : sb.ToString();
            }

            public void InitializeSkins()
            {
                foreach (var def in ItemManager.GetItemDefinitions())
                {
                    ItemModDeployable imd;
                    if (def.TryGetComponent(out imd))
                    {
                        _shortnames[imd.entityPrefab.resourcePath] = def;
                    }
                }
            }

            public void Message(BasePlayer player, string key, params object[] args)
            {
                if (player.IsValid())
                {
                    Plugin.Player.Message(player, GetMessage(key, player.UserIDString, args), config.Settings.ChatID);
                }
            }

            public string RemoveFormatting(string source) => source.Contains(">") ? Regex.Replace(source, "<.*?>", string.Empty) : source;

            public Timer Timer(float seconds, Action action) => Plugin.timer.Once(seconds, action);

            private void CheckOceanLevel()
            {
                if (OceanLevel != WaterSystem.OceanLevel)
                {
                    OceanLevel = WaterSystem.OceanLevel;

                    RaidableSpawns rs;
                    if (!Plugin.raidSpawns.TryGetValue(RaidableType.Grid, out rs))
                    {
                        return;
                    }

                    rs.TryAddRange(CacheType.Submerged);
                }
            }
        }

        public class SkinInfo
        {
            public List<ulong> skins = new List<ulong>();
            public List<ulong> workshopSkins = new List<ulong>();
            public List<ulong> allSkins = new List<ulong>();
        }

        public class StoredData
        {
            public StoredData() { }

            public Dictionary<string, PlayerInfo> Players { get; set; } = new Dictionary<string, PlayerInfo>();
            public string RaidTime { get; set; } = DateTime.MinValue.ToString();
            public int TotalEvents { get; set; }
            public Dictionary<string, UI.Info> UI { get; set; } = new Dictionary<string, UI.Info>();
            public static void Load(string name)
            {
                try { data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(name); } catch { }

                if (data == null) data = new StoredData();
                if (data.UI == null) data.UI = new Dictionary<string, UI.Info>();
                if (data.Players == null) data.Players = new Dictionary<string, PlayerInfo>();
            }
            public void Save(string name = "RaidableBases")
            {
                GarbageController.RemoveAll(data.Players, ((userid, playerInfo) => playerInfo.TotalRaids == 0)); 
                GarbageController.RemoveAll(data.UI, ((userid, uii) => uii.All()));
                Interface.Oxide.DataFileSystem.WriteObject(name, this);
            }
        }

        public class MonumentInfoEx
        {
            public float size;
            public Bounds bounds;
            public string translated;
            public Transform transform;
            public MonumentInfo monument;

            public MonumentInfoEx(MonumentInfo monument)
            {
                this.monument = monument;
                transform = monument.transform;
                translated = Translated();
                size = Size();
                bounds = new Bounds(monument.Bounds.center, new Vector3(size, size, size));

                if (config.Settings.Management.MonumentDistance > 0f)
                {
                    bounds.Expand(config.Settings.Management.MonumentDistance);
                }
            }

            public bool IsInBounds(Vector3 target)
            {
                return monument.IsInBounds(target) || new OBB(monument.transform.position, monument.transform.rotation, bounds).Contains(target);
            }

            private float Size()
            {
                if (string.IsNullOrEmpty(monument.displayPhrase.translated.TrimEnd()))
                {
                    return monument.name.Contains("power_sub") ? 50f : monument.name.Contains("cave") ? 75f : monument.name.Contains("OilrigAI") ? 225f : Mathf.Max(monument.Bounds.size.Max(), 75f);
                }

                switch (monument.displayPhrase.translated.TrimEnd())
                {
                    case "power_sub_small_1": case "power_sub_small_2": case "power_sub_big_1": case "power_sub_big_2": case "Stone Quarry": case "Sulfur Quarry": case "Water Well": case "Wild Swamp": case "HQM Quarry": return 50f;
                    case "Abandoned Cabins": case "Abandoned Supermarket": case "Fishing Village": case "Large Fishing Village": case "Lighthouse": case "Mining Outpost": case "Ice Lake": case "The Dome": case "Oxum's Gas Station": return 100f;
                    case "Military Tunnel": case "Sewer Branch": case "Satellite Dish": case "Junkyard": case "Underwater Lab": return 125f;
                    case "Harbor": case "Train Yard": case "Power Plant": return 150f;
                    case "Barn": case "Large Barn": case "Ranch": return 200f;
                    case "Airfield": case "Oil Rig": case "Large Oil Rig": case "Water Treatment Plant": case "Giant Excavator Pit": return 225f;
                    case "Bandit Camp": case "Outpost": return 350f;
                    case "Launch Site": return 300f;
                }

                return monument.Bounds.size.Max() > 0f ? monument.Bounds.size.Max() : 100f;
            }

            private string Translated()
            {
                if (!string.IsNullOrEmpty(monument.displayPhrase.translated.TrimEnd()))
                {
                    return monument.displayPhrase.translated.TrimEnd();
                }
                else if (monument.name.Contains("Oilrig"))
                {
                    return "Oil Rig";
                }
                else if (monument.name.Contains("cave"))
                {
                    return "Cave";
                }
                else if (monument.name.Contains("power_sub"))
                {
                    return "Power Sub Station";
                }

                return "Unknown Monument";
            }
        }

        public class ZoneInfo
        {
            public Vector3 Position;
            public Vector3 Size;
            public float Distance;
            public OBB OBB;

            public ZoneInfo(object location, object radius, object size)
            {
                Position = (Vector3)location;

                if (radius is float)
                {
                    Distance = (float)radius + Constants.RADIUS + 25f;
                }

                if (size is Vector3)
                {
                    Size = (Vector3)size;
                }

                var bounds = new Bounds(Position, Size);

                if (config.Settings.ZoneDistance > 0f)
                {
                    bounds.Expand(config.Settings.ZoneDistance);
                }

                OBB = new OBB(Position, default(Quaternion), bounds);
            }
        }

        private class BuildingTables
        {
            public List<TreasureItem> Default { get; set; } = new List<TreasureItem>();
            public List<TreasureItem> Normal { get; set; } = new List<TreasureItem>();
            public Dictionary<string, BaseProfile> Profiles { get; set; } = new Dictionary<string, BaseProfile>();
            public Dictionary<DayOfWeek, List<TreasureItem>> Weekday { get; set; } = new Dictionary<DayOfWeek, List<TreasureItem>>();
        }

        public class RaidableBase : FacepunchBehaviour
        {
            private const float Radius = Constants.RADIUS;
            private List<string> messagesSent = new List<string>();
            public List<StorageContainer> _allcontainers { get; set; } = Pool.GetList<StorageContainer>();
            public List<StorageContainer> _containers { get; set; } = Pool.GetList<StorageContainer>();
            public bool AllowPVP { get; set; }
            public int BaseIndex { get; set; } = -1;
            public string BaseName { get; set; }
            public uint BuildingID { get; set; }
            public List<uint> BuiltList { get; set; } = new List<uint>();
            public Hash<uint, float> conditions { get; set; } = Pool.Get<Hash<uint, float>>();
            public Dictionary<uint, BackpackData> backpacks { get; set; } = Pool.Get<Dictionary<uint, BackpackData>>();
            public float loadTime { get; set; }
            public float despawnTime { get; set; }
            public string DifficultyMode { get; set; }
            private bool _undoStructures { get; set; }
            private bool _undoDeployables { get; set; }
            private bool _undoMounts { get; set; }
            private bool _undoTeleport { get; set; }
            private int _undoLimit { get; set; }
            private Dictionary<Elevator, BMGELEVATOR> elevators { get; set; }
            public List<BaseEntity> Entities { get; set; } = new List<BaseEntity>();
            public List<BasePlayer> friends { get; set; } = Pool.GetList<BasePlayer>();
            public string ID { get; set; } = "0";
            public List<string> ids { get; set; } = Pool.GetList<string>();
            public Dictionary<BasePlayer, PlayerInputEx> Inputs { get; set; } = Pool.Get<Dictionary<BasePlayer, PlayerInputEx>>();
            public List<BasePlayer> intruders { get; set; } = Pool.GetList<BasePlayer>();
            public bool HasDroppedItems { get; set; }
            public bool IsAuthed { get; set; }
            public bool isAuthorized { get; set; }
            public bool IsCompleted { get; set; }
            public bool IsDespawning { get; set; }
            public bool IsEngaged { get; set; }
            public bool IsLoading => setupRoutine != null;
            public bool IsLooted => CanUndo();
            public bool IsOpened { get; set; } = true;
            public bool IsUnloading { get; set; }
            public bool killed { get; set; }
            private static bool isSpawning { get; set; }
            private static float isSpawningTime { get; set; }
            public Dictionary<string, float> lastActive { get; set; } = Pool.Get<Dictionary<string, float>>();
            public float ProtectionRadius => Options.ProtectionRadius;
            private GameObject go;
            public Vector3 Location { get; set; }
            public string markerName { get; set; }
            public uint NetworkID { get; set; } = uint.MaxValue;
            public string NoMode { get; set; }
            public Color NoneColor { get; set; }
            public int npcMaxAmount { get; set; }
            public List<ScientistNPC> npcs { get; set; } = Pool.GetList<ScientistNPC>();
            public BuildingOptions Options { get; set; }
            public BasePlayer owner { get; set; }
            public bool ownerFlag { get; set; }
            public ulong ownerId { get; set; }
            public Vector3 PastedLocation { get; set; }
            public BuildingPrivlidge priv { get; set; }
            public string ProfileName { get; set; }
            public Dictionary<ulong, BasePlayer> raiders { get; set; } = Pool.Get<Dictionary<ulong, BasePlayer>>();
            public Dictionary<uint, BasePlayer> records { get; set; } = Pool.Get<Dictionary<uint, BasePlayer>>();
            public float RemoveNearDistance { get; set; }
            public RaidableSpawns spawns { get; set; }
            public float spawnTime { get; set; }
            public List<AutoTurret> turrets { get; set; } = Pool.GetList<AutoTurret>();
            public RaidableType Type { get; set; }
            public int uid { get; set; }
            private List<CustomDoorManipulator> doorControllers { get; set; } = Pool.GetList<CustomDoorManipulator>();
            private List<Door> doors { get; set; } = Pool.GetList<Door>();
            private MapMarkerExplosion explosionMarker { get; set; }
            public List<Vector3> foundations { get; set; } = Pool.GetList<Vector3>();
            private MapMarkerGenericRadius genericMarker { get; set; }
            private bool IsInvokingCanFinish { get; set; }
            private int itemAmountSpawned { get; set; }
            private List<BaseEntity> lights { get; set; } = Pool.GetList<BaseEntity>();
            private bool lightsOn { get; set; }
            private List<Locker> lockers { get; set; } = Pool.GetList<Locker>();
            private ItemDefinition lowgradefuel { get; set; } = ItemManager.FindItemDefinition("lowgradefuel");
            private bool markerCreated { get; set; }
            private List<BaseMountable> mountables { get; set; } = Pool.GetList<BaseMountable>();
            private Dictionary<string, List<string>> npcKits { get; set; }
            private List<BaseOven> ovens { get; set; } = Pool.GetList<BaseOven>();
            private bool privSpawned { get; set; }
            private Coroutine setupRoutine { get; set; } = null;
            private Dictionary<uint, ulong> skinIds { get; set; } = Pool.Get<Dictionary<uint, ulong>>();
            private Dictionary<string, ulong> skins { get; set; } = Pool.Get<Dictionary<string, ulong>>();
            private List<SphereEntity> spheres { get; set; } = Pool.GetList<SphereEntity>();
            private Dictionary<TriggerBase, BaseEntity> triggers { get; set; } = Pool.Get<Dictionary<TriggerBase, BaseEntity>>();
            private Dictionary<SleepingBag, ulong> _bags { get; set; } = Pool.Get<Dictionary<SleepingBag, ulong>>();
            private VendingMachineMapMarker vendingMarker { get; set; }
            private float _lastInvokeUpdate { get; set; } = Time.time;
            private List<TreasureItem> BaseLoot { get; set; } = new List<TreasureItem>();
            private List<TreasureItem> BaseLootPermanent { get; set; } = new List<TreasureItem>();
            private List<TreasureItem> DefaultLoot { get; set; } = new List<TreasureItem>();
            private List<TreasureItem> DifficultyLoot { get; set; } = new List<TreasureItem>();
            private List<TreasureItem> Loot { get; set; } = new List<TreasureItem>();
            private RaycastHit _hit;
            private NavMeshHit _navHit;

            public bool IsProtectedWeapon(BaseEntity e, bool checkBuiltList = false)
            {
                if (!e.IsValid() || checkBuiltList && BuiltList.Contains(e.net.ID))
                {
                    return false;
                }

                return e is GunTrap || e is FlameTurret || e is FogMachine || e is SamSite || e is AutoTurret;
            }

            public static void Unload(bool isKilled)
            {
                foreach (var raid in Backbone.Plugin.Raids.Values)
                {
                    if (raid.setupRoutine != null)
                    {
                        raid.StopCoroutine(raid.setupRoutine);
                    }

                    if (isKilled)
                    {
                        raid.killed = true;
                    }

                    raid.IsUnloading = true;
                    TryInvokeMethod(raid.CancelInvokes);
                    TryInvokeMethod(raid.DestroyNpcs);
                }

                IsSpawning = false;
            }

            public bool AddLooter(BasePlayer looter, HitInfo hitInfo = null)
            {
                if (!IsAlly(looter))
                {
                    return false;
                }

                if (looter.IsFlying || Backbone.Plugin.IsInvisible(looter))
                {
                    return true;
                }

                UpdateStatus(looter);

                if (IsHogging(looter, false))
                {
                    NullifyDamage(hitInfo);
                    return false;
                }
                
                if (!raiders.ContainsKey(looter.userID))
                {
                    raiders.Add(looter.userID, looter);
                }

                return true;
            }

            public void AwardRaiders()
            {
                var players = new List<BasePlayer>();
                var sb = new StringBuilder();

                foreach (var raider in raiders)
                {
                    var player = raider.Value;

                    if (player == null || player.IsFlying || !IsPlayerActive(player.userID))
                    {
                        continue;
                    }

                    if (config.Settings.RemoveAdminRaiders && player.IsAdmin && Type != RaidableType.None)
                    {
                        continue;
                    }

                    sb.Append(player.displayName).Append(", ");
                    players.Add(player);
                }

                if (players.Count == 0)
                {
                    return;
                }

                Interface.CallHook("OnRaidableBaseCompleted", hookObjects);

                if (Options.Levels.Level2 && npcMaxAmount > 0)
                {
                    SpawnNpcs();
                }

                if (IsCompleted)
                {
                    HandleAwards(players);
                }

                sb.Length -= 2;
                string thieves = sb.ToString();
                string posStr = FormatGridReference(Location);

                Puts(Backbone.GetMessageEx("Thief", null, posStr, thieves));

                if (Backbone.Plugin.debugMode)
                {
                    _containers.ForEach(container =>
                    {
                        if (IsBox(container, false) || container is BuildingPrivlidge)
                        {
                            Puts("{0} : {1}", container.ShortPrefabName, string.Join(", ", container.inventory.itemList.Select(item => string.Format("{0} ({1})", item.info.displayName.english, item.amount))));
                        }
                    });
                }

                if (config.EventMessages.AnnounceThief)
                {
                    foreach (var target in BasePlayer.activePlayerList)
                    {
                        SendNotification(target, _("Thief", target.UserIDString, posStr, thieves));
                    }
                }

                data.Save();
            }

            public bool CanUndo()
            {
                if (IsLoading)
                {
                    return false;
                }

                if (EndWhenCupboardIsDestroyed())
                {
                    return true;
                }

                if (config.Settings.Management.RequireCupboardLooted && privSpawned)
                {
                    if (priv.IsValid() && !priv.IsDestroyed && !priv.inventory.IsEmpty())
                    {
                        return false;
                    }
                }

                foreach (var container in _containers)
                {
                    if (container.IsValid() && !container.IsDestroyed && !container.inventory.IsEmpty() && IsBox(container, true))
                    {
                        return false;
                    }
                }

                foreach (string value in config.Settings.Management.Inherit)
                {
                    foreach (var container in _allcontainers)
                    {
                        if (container == null || container.IsDestroyed || !container.ShortPrefabName.Contains(value, CompareOptions.OrdinalIgnoreCase))
                        {
                            continue;
                        }

                        if (!container.inventory.IsEmpty())
                        {
                            return false;
                        }
                    }
                }

                return IsCompleted = true;
            }

            public void CheckDespawn()
            {
                if (IsDespawning || config.Settings.Management.DespawnMinutesInactive <= 0)
                {
                    return;
                }

                if (config.Settings.Management.Engaged && !IsEngaged)
                {
                    return;
                }

                if (IsInvoking(Despawn))
                {
                    CancelInvoke(Despawn);
                }

                if (!config.Settings.Management.DespawnMinutesInactiveReset && despawnTime != 0)
                {
                    return;
                }

                float time = config.Settings.Management.DespawnMinutesInactive * 60f;
                despawnTime = Time.realtimeSinceStartup + time;
                Invoke(Despawn, time);
            }

            public void Despawn()
            {
                if (!CanDespawn())
                {
                    return;
                }

                TryInvokeMethod(StopAllCoroutines);
                TryInvokeMethod(CancelInvoke);
                TryInvokeMethod(SetNoDrops);
                TryInvokeMethod(UpdateUI);
                TryInvokeMethod(DestroyNpcs);
                TryInvokeMethod(DestroyInputs);
                TryInvokeMethod(DestroySpheres);
                TryInvokeMethod(DestroyMapMarkers);
                TryInvokeMethod(DestroyEntities);
                TryInvokeMethod(ResetSleepingBags);
                TryInvokeMethod(ResetToPool);
                TryInvokeMethod(CheckSubscribe);

                Interface.CallHook("OnRaidableBaseEnded", hookObjects);

                Destroy(this);
            }

            private void CheckSubscribe()
            {
                if (Instance == null)
                {
                    return;
                }

                Instance.Bases.Remove(BaseIndex);
                Instance.Raids.Remove(uid);

                if (Instance.Raids.Count == 0 || Instance.Bases.Count == 0)
                {
                    if (IsUnloading)
                    {
                        UnsetStatics();
                    }
                    else
                    {
                        Instance.UnsubscribeHooks();
                        spawns?.AddNear(Location, RemoveNearDistance, CacheType.Generic, HasDroppedItems);
                    }
                }
            }

            private bool CanDespawn()
            {
                if (IsDespawning)
                {
                    return false;
                }

                Interface.CallHook("OnRaidableBaseDespawn", hookObjects);

                IsDespawning = true;
                IsOpened = false;

                Instance?.Locations?.RemoveAll(e => e.Position == PastedLocation);

                return true;
            }

            private void DestroyEntities()
            {
                if (elevators != null && elevators.Count > 0)
                {
                    TryInvokeMethod(RemoveParentFromEntitiesOnElevators); 
                    elevators.ToList().ForEach(element => Destroy(element.Value));
                }

                UndoSettings undo = new UndoSettings
                {
                    Structures = _undoStructures,
                    Deployables = _undoDeployables,
                    Mounts = _undoMounts,
                    Teleport = _undoTeleport,
                    Limit = _undoLimit,
                    Entities = Entities,
                    hookObjects = hookObjects
                };

                GarbageController.UndoLoop(undo);
            }

            private void RemoveParentFromEntitiesOnElevators()
            {
                int hits = Physics.OverlapSphereNonAlloc(Location, ProtectionRadius, Vis.colBuffer, -1, QueryTriggerInteraction.Ignore);

                for (int i = 0; i < hits; i++)
                {
                    var e = Vis.colBuffer[i].ToBaseEntity();

                    if (e is PlayerCorpse || e is DroppedItemContainer)
                    {
                        if (e.HasParent())
                        {
                            e.SetParent(null, false, true);
                        }
                    }

                    Vis.colBuffer[i] = null;
                }
            }

            private static void TryInvokeMethod(Action action)
            {
                try
                {
                    action.Invoke();
                }
                catch (Exception ex)
                {
                    Puts("{0} ERROR: {1}", action.Method.Name, ex);
                }
            }

            public void UndoInit()
            {
                _undoStructures = config.Settings.Management.DoNotDestroyStructures;
                _undoDeployables = config.Settings.Management.DoNotDestroyDeployables;
                _undoMounts = config.Settings.Management.DespawnMounts;
                _undoTeleport = Options.Setup.TeleportEntities;
                _undoLimit = Mathf.Clamp(Options.Setup.DespawnLimit, 1, 500);
            }

            private object[] hookObjects
            {
                get
                {
                    return new object[] { Location, 512, AllowPVP, ID, spawnTime, despawnTime, loadTime, ownerId, GetOwner(), GetRaiders(), GetIntruders(), Entities };
                }
            }

            public void DestroyInputs()
            {
                if (Inputs.Count > 0)
                {
                    Inputs.ToList().ForEach(input => Destroy(input.Value));
                    Inputs.Clear();
                }
            }

            public void UpdateUI()
            {
                if (elevators != null)
                {
                    elevators.ToList().ForEach(element => element.Value.DestroyMe());
                }

                foreach (var player in intruders)
                {
                    UI.DestroyStatusUI(player);
                }
            }

            public bool EndWhenCupboardIsDestroyed()
            {
                if (config.Settings.Management.EndWhenCupboardIsDestroyed && privSpawned)
                {
                    if (priv == null || priv.IsDestroyed)
                    {
                        return IsCompleted = true;
                    }
                }

                return false;
            }

            public void ResetToPool()
            {
                ResetToPool(conditions);
                ResetToPool(Inputs);
                ResetToPool(records);
                ResetToPool(raiders);
                ResetToPool(backpacks);
                ResetToPool(lastActive);
                ResetToPool(mountables);
                ResetToPool(_containers);
                ResetToPool(_allcontainers);
                ResetToPool(npcs);
                ResetToPool(friends);
                ResetToPool(intruders);
                ResetToPool(foundations);
                ResetToPool(spheres);
                ResetToPool(lights);
                ResetToPool(ovens);
                ResetToPool(turrets);
                ResetToPool(doors);
                ResetToPool(doorControllers);
                ResetToPool(ids);
                ResetToPool(lockers);
                ResetToPool(skins);
                ResetToPool(skinIds);
                ResetToPool(triggers);
                ResetToPool(_bags);
            }

            public string Mode()
            {
                if (owner.IsValid())
                {
                    return string.Format("{0} {1}", owner.displayName, DifficultyMode.SentenceCase());
                }

                return DifficultyMode.SentenceCase();
            }

            public void OnEnterRaid(BasePlayer player)
            {
                if (!IsValid(player) || !IsHuman(player))
                {
                    return;
                }

                if (Type != RaidableType.None && (IsBanned(player) || Teleported(player)) && RemovePlayer(player))
                {
                    return;
                }

                if (!intruders.Contains(player))
                {
                    intruders.Add(player);
                }

                Protector();

                if (!intruders.Contains(player))
                {
                    return;
                }

                PlayerInputEx component;
                if (Inputs.TryGetValue(player, out component))
                {
                    Destroy(component);
                }

                if (config.Settings.Management.AllowLadders)
                {
                    player.gameObject.AddComponent<PlayerInputEx>().Setup(player, this);
                }

                StopUsingWand(player);

                if (config.EventMessages.AnnounceEnterExit)
                {
                    SendNotification(player, _(AllowPVP ? "OnPlayerEntered" : "OnPlayerEnteredPVE", player.UserIDString));
                }

                UI.UpdateStatusUI(player);
                Interface.CallHook("OnPlayerEnteredRaidableBase", player, Location, AllowPVP, 512);

                if (config.Settings.Management.PVPDelay > 0)
                {
                    Interface.CallHook("OnPlayerPvpDelayEntry", player, 512);
                }
            }

            public void OnLootEntityInternal(BasePlayer player, BaseEntity e)
            {
                UpdateStatus(player);

                if (e.OwnerID == player.userID || e is BaseMountable)
                {
                    return;
                }

                if (IsBlacklisted(e.ShortPrefabName))
                {
                    player.Invoke(player.EndLooting, 0.1f);
                    return;
                }

                if (e.HasParent() && e.GetParentEntity() is BaseMountable)
                {
                    return;
                }

                if (!CanBeLooted(player, e))
                {
                    if (player.IPlayer == null || !player.IPlayer.HasPermission("raidablebases.admin.loot") || !player.IsAdmin)
                    {
                        player.Invoke(player.EndLooting, 0.01f);
                        return;
                    }
                }

                if (e is LootableCorpse || e is DroppedItemContainer)
                {
                    return;
                }

                if (player.GetMounted())
                {
                    Backbone.Message(player, "CannotBeMounted");
                    player.Invoke(player.EndLooting, 0.1f);
                    return;
                }

                if (Options.RequiresCupboardAccess && !player.CanBuild()) //player.IsBuildingBlocked())
                {
                    Backbone.Message(player, "MustBeAuthorized");
                    player.Invoke(player.EndLooting, 0.1f);
                    return;
                }

                if (!IsAlly(player))
                {
                    Backbone.Message(player, "OwnerLocked");
                    player.Invoke(player.EndLooting, 0.1f);
                    return;
                }

                if (raiders.Count > 0 && Type != RaidableType.None)
                {
                    CheckDespawn();
                }

                AddLooter(player);

                if (IsBox(e, true) || e is BuildingPrivlidge)
                {
                    StartTryToEnd();
                }
            }

            public void OnPlayerExit(BasePlayer p, bool skipDelay = true)
            {
                UI.DestroyStatusUI(p);

                PlayerInputEx component;
                if (Inputs.TryGetValue(p, out component))
                {
                    Destroy(component);
                }

                if (!intruders.Contains(p))
                {
                    return;
                }

                intruders.Remove(p);
                Interface.CallHook("OnPlayerExitedRaidableBase", p, Location, AllowPVP, 512);

                if (config.Settings.Management.PVPDelay > 0)
                {
                    if (skipDelay || !Backbone.Plugin.IsPVE() || !AllowPVP)
                    {
                        goto enterExit;
                    }

                    if (config.EventMessages.AnnounceEnterExit)
                    {
                        string arg = Backbone.GetMessageEx("PVPFlag", p.UserIDString).Replace("[", string.Empty).Replace("] ", string.Empty);
                        SendNotification(p, _("DoomAndGloom", p.UserIDString, arg, config.Settings.Management.PVPDelay));
                    }

                    ulong id = p.userID;
                    DelaySettings ds;
                    if (!Backbone.Plugin.PvpDelay.TryGetValue(id, out ds))
                    {
                        Backbone.Plugin.PvpDelay[id] = ds = new DelaySettings
                        {
                            Timer = Backbone.Timer(config.Settings.Management.PVPDelay, () =>
                            {
                                Interface.CallHook("OnPlayerPvpDelayExpired", p, 512);
                                Backbone.Plugin.PvpDelay.Remove(id);
                            }),
                            AllowPVP = AllowPVP,
                            RaidableBase = this
                        };

                        return;
                    }

                    ds.Timer.Reset();

                    return;
                }

            enterExit:
                if (config.EventMessages.AnnounceEnterExit)
                {
                    SendNotification(p, _(AllowPVP ? "OnPlayerExit" : "OnPlayerExitPVE", p.UserIDString));
                }
            }

            public void SetEntities(int baseIndex, List<BaseEntity> entities, Dictionary<Elevator, BMGELEVATOR> elevators = null)
            {
                if (!IsLoading)
                {
                    Entities = entities;
                    BaseIndex = baseIndex;
                    setupRoutine = ServerMgr.Instance.StartCoroutine(EntitySetup());
                    TryInvokeMethod(() => SetupElevators(elevators));
                    TryInvokeMethod(SetupCollider);
                }
            }

            private void SetupElevators(Dictionary<Elevator, BMGELEVATOR> elevators)
            {
                if (elevators == null || elevators.Count == 0)
                {
                    return;
                }

                this.elevators = elevators;

                elevators.ToList().ForEach(element => element.Value.Init(this));
            }

            public void SetOwner(BasePlayer player)
            {
                TryInvokeResetOwner();
                UpdateStatus(player);
                owner = player;
                ownerId = player.userID;
                UpdateMarker();
                ClearEnemies();
            }

            public void StartTryToEnd()
            {
                if (!IsInvokingCanFinish && !IsLoading)
                {
                    IsInvokingCanFinish = true;
                    InvokeRepeating(TryToEnd, 0f, 1f);
                }
            }

            public void TrySetOwner(BasePlayer attacker, BaseEntity entity, HitInfo hitInfo)
            {
                UpdateStatus(attacker);

                if (!config.Settings.Management.UseOwners || !IsOpened || ownerId.IsSteamId() || IsOwner(attacker)) // || !InRange(attacker.transform.position, Location, 250f))
                {
                    return;
                }

                if (config.Settings.Management.BypassUseOwnersForPVP && AllowPVP || config.Settings.Management.BypassUseOwnersForPVE && !AllowPVP)
                {
                    return;
                }

                if (IsHogging(attacker))
                {
                    NullifyDamage(hitInfo);
                    return;
                }

                if (entity is ScientistNPC)
                {
                    SetOwner(attacker);
                    return;
                }

                if (!(entity is BuildingBlock) && !(entity is Door) && !(entity is SimpleBuildingBlock))
                {
                    return;
                }

                if (InRange(attacker.transform.position, Location, Options.ProtectionRadius) || IsLootingWeapon(hitInfo))
                {
                    SetOwner(attacker);
                }
            }

            public void TryToEnd()
            {
                if (IsOpened && IsLooted && !IsLoading)
                {
                    CancelInvoke(TryToEnd);
                    AwardRaiders();
                    Undo();
                }
            }

            private void ShowAnnouncement(BasePlayer target, string message)
            {
                if (config.GUIAnnouncement.Enabled && Backbone.Plugin.GUIAnnouncements != null && Backbone.Plugin.GUIAnnouncements.IsLoaded)
                {
                    Backbone.Plugin.GUIAnnouncements?.Call("CreateAnnouncement", message, config.GUIAnnouncement.TintColor, config.GUIAnnouncement.TextColor, target);
                }
            }

            private void AddContainer(StorageContainer container)
            {
                if (!Entities.Contains(container))
                {
                    Entities.Add(container);
                }

                if (!_allcontainers.Contains(container))
                {
                    _allcontainers.Add(container);
                }

                if (!_containers.Contains(container) && (IsBox(container, true) || container is BuildingPrivlidge))
                {
                    _containers.Add(container);
                }
            }

            private void Awake()
            {
                markerName = config.Settings.Markers.MarkerName;
                spawnTime = Time.realtimeSinceStartup;
                go = gameObject;
            }

            private bool CanBeLooted(BasePlayer player, BaseEntity e)
            {
                if (IsProtectedWeapon(e, true))
                {
                    return config.Settings.Management.LootableTraps;
                }

                if (e is NPCPlayerCorpse)
                {
                    return true;
                }

                if (e is LootableCorpse)
                {
                    if (CanBypass(player))
                    {
                        return true;
                    }

                    var corpse = e as LootableCorpse;

                    if (!corpse.playerSteamID.IsSteamId() || corpse.playerSteamID == player.userID || corpse.playerName == player.displayName)
                    {
                        return true;
                    }

                    return CanPlayerBeLooted();
                }
                else if (e is DroppedItemContainer)
                {
                    if (CanBypass(player))
                    {
                        return true;
                    }

                    var container = e as DroppedItemContainer;

                    if (!container.playerSteamID.IsSteamId() || container.playerSteamID == player.userID || container.playerName == player.displayName)
                    {
                        return true;
                    }

                    return CanPlayerBeLooted();
                }

                return true;
            }

            private bool CanBypass(BasePlayer player)
            {
                return Backbone.Plugin.HasPermission(player, canBypassPermission) || player.IsFlying;
            }

            private bool CanPlayerBeLooted()
            {
                if (!config.Settings.Management.PlayersLootableInPVE && !AllowPVP || !config.Settings.Management.PlayersLootableInPVP && AllowPVP)
                {
                    return false;
                }

                return true;
            }

            private void ChangeTier(BuildingBlock block)
            {
                if (Options.Tiers.HQM && block.grade != BuildingGrade.Enum.TopTier)
                {
                    SetGrade(block, BuildingGrade.Enum.TopTier);
                }
                else if (Options.Tiers.Metal && block.grade != BuildingGrade.Enum.Metal)
                {
                    SetGrade(block, BuildingGrade.Enum.Metal);
                }
                else if (Options.Tiers.Stone && block.grade != BuildingGrade.Enum.Stone)
                {
                    SetGrade(block, BuildingGrade.Enum.Stone);
                }
                else if (Options.Tiers.Wooden && block.grade != BuildingGrade.Enum.Wood)
                {
                    SetGrade(block, BuildingGrade.Enum.Wood);
                }
            }

            private void CheckBackpacks(bool bypass = false)
            {
                var keys = Pool.GetList<uint>();

                foreach (var data in backpacks)
                {
                    if (EjectBackpack(data.Key, data.Value, bypass))
                    {
                        keys.Add(data.Key);
                    }
                }

                foreach (uint key in keys)
                {
                    backpacks.Remove(key);
                }

                Pool.FreeList(ref keys);
            }

            private void ClearEnemies()
            {
                if (raiders.Count == 0)
                {
                    return;
                }

                var list = Pool.GetList<ulong>();

                foreach (var raider in raiders)
                {
                    var target = raider.Value;

                    if (target == null || !target.IsConnected || !IsAlly(target))
                    {
                        list.Add(raider.Key);
                    }
                }

                foreach (var targetId in list)
                {
                    raiders.Remove(targetId);
                }

                Pool.FreeList(ref list);
            }

            private void CreateSpheres()
            {
                if (Options.SphereAmount <= 0 || Options.Silent)
                {
                    return;
                }

                for (int i = 0; i < Options.SphereAmount; i++)
                {
                    var sphere = GameManager.server.CreateEntity(Backbone.Path.Sphere, Location, default(Quaternion), true) as SphereEntity;

                    if (sphere == null)
                    {
                        break;
                    }

                    sphere.currentRadius = 1f;
                    sphere.Spawn();
                    sphere.LerpRadiusTo(Options.ProtectionRadius * 2f, Options.ProtectionRadius * 0.75f);
                    spheres.Add(sphere);
                }
            }

            private void CreateZoneWalls()
            {
                if (!Options.ArenaWalls.Enabled)
                {
                    return;
                }

                int stacks = 0;
                float maxDistance = 48f;

                if (Options.Setup.ForcedHeight > 0)
                {
                    maxDistance += Options.Setup.ForcedHeight;
                    Options.ArenaWalls.Stone = true;
                    Options.ArenaWalls.Ice = false;
                    stacks = Mathf.CeilToInt(Options.Setup.ForcedHeight / 6f);
                }

                float invokeTime = 0f;
                var center = new Vector3(Location.x, Location.y, Location.z);
                string prefab = Options.ArenaWalls.Ice ? StringPool.Get(921229511) : Options.ArenaWalls.Stone ? StringPool.Get(1585379529) : StringPool.Get(1745077396);
                float maxHeight = -999f;
                float minHeight = 999f;
                int next1 = Mathf.CeilToInt(360 / Options.ArenaWalls.Radius * 0.1375f);

                foreach (var position in GetCircumferencePositions(center, Options.ArenaWalls.Radius, next1, false, 1f))
                {
                    float y = GetSpawnHeight(position, false, true);
                    maxHeight = Mathf.Max(y, maxHeight, TerrainMeta.WaterMap.GetHeight(position));
                    minHeight = Mathf.Min(y, minHeight);
                    center.y = minHeight;
                }

                float gap = Options.ArenaWalls.Stone || Options.ArenaWalls.Ice ? 0.3f : 0.5f;
                stacks += Mathf.CeilToInt((maxHeight - minHeight) / 6f) + Options.ArenaWalls.Stacks;
                float next2 = 360 / Options.ArenaWalls.Radius - gap;
                float j = Options.ArenaWalls.Stacks * 6f + 6f;
                var list = new List<int>();

                for (int i = 0; i < stacks; i++)
                {
                    if (Options.Setup.ForcedHeight != -1f && i < stacks * 0.75)
                    {
                        center.y += 6f;
                        continue;
                    }

                    foreach (var position in GetCircumferencePositions(center, Options.ArenaWalls.Radius, next2, false, center.y))
                    {
                        if (Mathf.Abs(Location.y - position.y) > maxDistance)
                        {
                            continue;
                        }

                        var groundHeight = TerrainMeta.HeightMap.GetHeight(new Vector3(position.x, position.y + 6f, position.z));

                        if (i == 0 && position.y + 8.018669f < groundHeight)
                        {
                            continue;
                        }

                        if (groundHeight > position.y + 6.5f)
                        {
                            continue;
                        }

                        if (Options.ArenaWalls.LeastAmount)
                        {
                            float h = TerrainMeta.HeightMap.GetHeight(position);

                            if (position.y - groundHeight > j && position.y < h)
                            {
                                continue;
                            }
                        }

                        var _center = center;

                        Invoke(() =>
                        {
                            var e = GameManager.server.CreateEntity(prefab, position, default(Quaternion), false);

                            if (e == null)
                            {
                                return;
                            }

                            e.OwnerID = 0;
                            e.transform.LookAt(_center, Vector3.up);

                            if (Options.ArenaWalls.UseUFOWalls)
                            {
                                e.transform.Rotate(-67.5f, 0f, 0f);
                            }

                            e.enableSaving = false;
                            e.Spawn();

                            e.gameObject.SetActive(true);

                            if (CanSetupEntity(e))
                            {
                                Entities.Add(e);
                                GarbageController.RaidEntities[e] = this;
                            }
                        }, invokeTime += 0.01f);

                        if (stacks == i - 1)
                        {
                            RaycastHit hit;
                            if (Physics.Raycast(new Vector3(position.x, position.y + 6.5f, position.z), Vector3.down, out hit, 13f, Layers.Mask.World | Layers.Mask.Default | Layers.Mask.Terrain))
                            {
                                if (hit.collider.name.Contains("rock_") || hit.collider.name.Contains("formation_", CompareOptions.OrdinalIgnoreCase))
                                {
                                    stacks++;
                                }
                            }
                        }
                    }

                    center.y += 6f;
                }
            }

            private IEnumerator EntitySetup()
            {
                int checks = 0;
                var list = new List<BaseEntity>(Entities);
                int limit = Mathf.Clamp(Options.Setup.SpawnLimit, 1, 500);

                foreach (var e in list)
                {
                    if (!CanSetupEntity(e))
                    {
                        if (++checks >= limit)
                        {
                            checks = 0;
                            yield return CoroutineEx.waitForFixedUpdate;
                        }
                        continue;
                    }

                    GarbageController.RaidEntities[e] = this;

                    if (e.net.ID < NetworkID)
                    {
                        NetworkID = e.net.ID;
                    }

                    e.OwnerID = 0;

                    if (!Options.AllowPickup && e is BaseCombatEntity)
                    {
                        SetupPickup(e as BaseCombatEntity);
                    }

                    if (e is IOEntity)
                    {
                        if (e is ContainerIOEntity)
                        {
                            SetupIO(e as ContainerIOEntity);
                        }

                        if (e is AutoTurret)
                        {
                            SetupTurret(e as AutoTurret);
                        }
                        else if (e is Igniter)
                        {
                            SetupIgniter(e as Igniter);
                        }
                        else if (e is SamSite)
                        {
                            SetupSamSite(e as SamSite);
                        }
                        else if (e is TeslaCoil)
                        {
                            SetupTeslaCoil(e as TeslaCoil);
                        }
                        else if (e is SearchLight)
                        {
                            SetupSearchLight(e as SearchLight);
                        }
                        else if (e is CustomDoorManipulator)
                        {
                            doorControllers.Add(e as CustomDoorManipulator);
                        }
                        else if (e is HBHFSensor)
                        {
                            SetupHBHFSensor(e as HBHFSensor);
                        }
                        else if (e.prefabID == 1216081662 || e.prefabID == 1331920001)
                        {
                            SetupGenerator(e as ElectricGenerator);
                        }
                        else if (e is PressButton)
                        {
                            SetupButton(e as PressButton);
                        }
                    }
                    else if (e is StorageContainer)
                    {
                        SetupContainer(e as StorageContainer);

                        if (e is BaseOven)
                        {
                            ovens.Add(e as BaseOven);
                        }
                        else if (e is FogMachine)
                        {
                            SetupFogMachine(e as FogMachine);
                        }
                        else if (e is FlameTurret)
                        {
                            SetupFlameTurret(e as FlameTurret);
                        }
                        else if (e is VendingMachine)
                        {
                            SetupVendingMachine(e as VendingMachine);
                        }
                        else if (e is BuildingPrivlidge)
                        {
                            SetupBuildingPriviledge(e as BuildingPrivlidge);
                        }
                        else if (config.Settings.Management.Lockers && e is Locker)
                        {
                            lockers.Add(e as Locker);
                        }
                        else if (e is GunTrap)
                        {
                            SetupGunTrap(e as GunTrap);
                        }
                    }
                    else if (e is BuildingBlock)
                    {
                        SetupBuildingBlock(e as BuildingBlock);
                    }
                    else if (e is Door)
                    {
                        doors.Add(e as Door);
                    }
                    else if (e is BaseLock)
                    {
                        SetupLock(e);
                    }
                    else if (e is SleepingBag)
                    {
                        SetupSleepingBag(e as SleepingBag);
                    }
                    else if (e is BaseMountable)
                    {
                        SetupMountable(e as BaseMountable);
                    }

                    if (e is DecayEntity)
                    {
                        SetupDecayEntity(e as DecayEntity);
                    }

                    SetupSkin(e);

                    if (++checks >= limit)
                    {
                        checks = 0;
                        yield return CoroutineEx.waitForFixedUpdate;
                    }
                }

                yield return CoroutineEx.waitForSeconds(2f);

                TryInvokeMethod(SetupLoot);
                TryInvokeMethod(Subscribe);
                TryInvokeMethod(CreateGenericMarker);
                TryInvokeMethod(UpdateMarker);
                TryInvokeMethod(EjectSleepers);
                TryInvokeMethod(KillClutter);
                TryInvokeMethod(CreateZoneWalls);
                TryInvokeMethod(CreateSpheres);
                TryInvokeMethod(SetupLights);
                TryInvokeMethod(SetupDoorControllers);
                TryInvokeMethod(SetupDoors);
                TryInvokeMethod(CheckDespawn);
                TryInvokeMethod(SetupContainers);
                TryInvokeMethod(MakeAnnouncements);
                TryInvokeMethod(SetLoadTime);
                InvokeRepeating(Protector, 1f, 1f);

                IsSpawning = false;
                setupRoutine = null;
                Interface.CallHook("OnRaidableBaseStarted", hookObjects);
            }
            
            private void FillAmmoFlameTurret(FlameTurret ft)
            {
                if (IsUnloading || isAuthorized || ft.IsDestroyed || lowgradefuel == null)
                {
                    return;
                }

                ft.inventory.AddItem(lowgradefuel, config.Weapons.Ammo.FlameTurret);
            }

            private void FillAmmoFogMachine(FogMachine fm)
            {
                if (IsUnloading || isAuthorized || lowgradefuel == null || fm.IsDestroyed)
                {
                    return;
                }

                fm.inventory.AddItem(lowgradefuel, config.Weapons.Ammo.FogMachine);
            }

            private void FillAmmoGunTrap(GunTrap gt)
            {
                if (IsUnloading || isAuthorized || gt.IsDestroyed)
                {
                    return;
                }

                if (gt.ammoType == null)
                {
                    gt.ammoType = ItemManager.FindItemDefinition("ammo.handmade.shell");
                }

                var ammo = gt.inventory.GetSlot(0);

                if (ammo == null)
                {
                    gt.inventory.AddItem(gt.ammoType, config.Weapons.Ammo.GunTrap);
                }
                else ammo.amount = config.Weapons.Ammo.GunTrap;
            }

            private void FillAmmoSamSite(SamSite ss)
            {
                if (IsUnloading || isAuthorized || ss.IsDestroyed)
                {
                    return;
                }

                if (!ss.HasAmmo())
                {
                    Item item = ItemManager.Create(ss.ammoType, config.Weapons.Ammo.SamSite);

                    if (!item.MoveToContainer(ss.inventory))
                    {
                        item.Remove();
                    }
                    else ss.ammoItem = item;
                }
                else if (ss.ammoItem != null && ss.ammoItem.amount < config.Weapons.Ammo.SamSite)
                {
                    ss.ammoItem.amount = config.Weapons.Ammo.SamSite;
                }
            }

            private void FillAmmoTurret(AutoTurret turret)
            {
                if (IsUnloading || isAuthorized || turret.IsDestroyed)
                {
                    return;
                }

                foreach (var id in turret.authorizedPlayers)
                {
                    if (id.userid.IsSteamId())
                    {
                        isAuthorized = true;
                        return;
                    }
                }

                var attachedWeapon = turret.GetAttachedWeapon();

                if (attachedWeapon == null)
                {
                    turret.Invoke(() => FillAmmoTurret(turret), 0.2f);
                    return;
                }

                int p = Math.Max(config.Weapons.Ammo.AutoTurret, attachedWeapon.primaryMagazine.capacity);
                turret.inventory.AddItem(attachedWeapon.primaryMagazine.ammoType, p, 0uL);
                attachedWeapon.primaryMagazine.contents = attachedWeapon.primaryMagazine.capacity;
                attachedWeapon.SendNetworkUpdate(BasePlayer.NetworkQueue.Update);
                turret.Invoke(turret.UpdateTotalAmmo, 0.25f);
            }

            private Vector3 GetCenterFromMultiplePoints()
            {
                if (foundations.Count <= 1)
                {
                    return PastedLocation;
                }

                float x = 0f;
                float z = 0f;

                foreach (var position in foundations)
                {
                    x += position.x;
                    z += position.z;
                }

                var vector = new Vector3(x / foundations.Count, 0f, z / foundations.Count);

                if (Options.Setup.ForcedHeight != -1)
                {
                    vector.y = Options.Setup.ForcedHeight;
                }
                else vector.y = GetSpawnHeight(vector);

                return vector;
            }

            private void HandleAwards(List<BasePlayer> players)
            {
                foreach (var raider in players)
                {
                    if (config.RankedLadder.Enabled)
                    {
                        PlayerInfo playerInfo;
                        if (!data.Players.TryGetValue(raider.UserIDString, out playerInfo))
                        {
                            data.Players[raider.UserIDString] = playerInfo = new PlayerInfo();
                        }

                        playerInfo.TotalRaids++;
                        playerInfo.Raids++;
                    }

                    if (Options.Rewards.Money > 0 && Backbone.Plugin.Economics != null && Backbone.Plugin.Economics.IsLoaded)
                    {
                        double money = config.Settings.Management.DivideRewards ? Options.Rewards.Money / players.Count : Options.Rewards.Money;
                        Backbone.Plugin.Economics?.Call("Deposit", raider.UserIDString, money);
                        Backbone.Message(raider, "EconomicsDeposit", money);
                    }

                    if (Options.Rewards.Points > 0 && Backbone.Plugin.ServerRewards != null && Backbone.Plugin.ServerRewards.IsLoaded)
                    {
                        int points = config.Settings.Management.DivideRewards ? Options.Rewards.Points / players.Count : Options.Rewards.Points;
                        Backbone.Plugin.ServerRewards?.Call("AddPoints", raider.userID, points);
                        Backbone.Message(raider, "ServerRewardPoints", points);
                    }
                }
            }

            public bool IsScavenging(BasePlayer player)
            {
                if (IsOpened || !ownerId.IsSteamId() || CanBypass(player))
                {
                    return false;
                }

                return !Any(player.userID) && !IsAlly(player) && RemovePlayer(player, 8);
            }

            public bool RemoveFauxAdmin(BasePlayer player)
            {
                if (player.IsDeveloper && player.IPlayer != null && player.IPlayer.HasPermission("fauxadmin.allowed") && player.IPlayer.HasPermission("raidablebases.block.fauxadmin"))
                {
                    if (player.IsGod() || player.IsFlying || player.metabolism.calories.min == 500)
                    {
                        RemovePlayer(player, 6);
                        Backbone.Message(player, "NoFauxAdmin");
                        OnPlayerExit(player, false);
                        return true;
                    }
                }

                return false;
            }

            private bool IsBanned(BasePlayer player)
            {
                if (Backbone.Plugin.HasPermission(player, banPermission))
                {
                    TryMessage(player, "Banned");

                    return true;
                }

                return false;
            }

            private bool IsHogging(BasePlayer player, bool shouldMessage = true)
            {
                if (!config.Settings.Management.PreventHogging || !player.IsValid() || CanBypass(player) || Backbone.Plugin.HasPermission(player, bypassBlockPermission))
                {
                    return false;
                }

                foreach (var raid in Backbone.Plugin.Raids.Values)
                {
                    if (raid.IsOpened && raid.BaseIndex != BaseIndex && raid.Any(player.userID, false))
                    {
                        if (raid.AllowPVP && config.Settings.Management.BypassUseOwnersForPVP)
                        {
                            continue;
                        }

                        if (!raid.AllowPVP && config.Settings.Management.BypassUseOwnersForPVE)
                        {
                            continue;
                        }

                        if (shouldMessage)
                        {
                            TryMessage(player, "HoggingFinishYourRaid", PositionToGrid(raid.Location));
                        }

                        return true;
                    }
                }

                return false;
            }

            private bool IsPlayerActive(ulong playerId)
            {
                if (config.Settings.Management.LockTime <= 0f)
                {
                    return true;
                }

                float time;
                if (!lastActive.TryGetValue(playerId.ToString(), out time))
                {
                    return true;
                }

                return Time.realtimeSinceStartup - time <= config.Settings.Management.LockTime * 60f;
            }

            private bool IsPriority(TreasureItem a)
            {
                if (Options.Prioritize)
                {
                    foreach (var b in BaseLootPermanent)
                    {
                        if (a.shortname == b.shortname)
                        {
                            return true;
                        }
                    }
                }

                return false;
            }

            private void KillClutter()
            {
                BaseEntity e;
                int hits = Physics.OverlapSphereNonAlloc(Location, Constants.RADIUS, Vis.colBuffer, -1, QueryTriggerInteraction.Ignore);
                for (int i = 0; i < hits; i++)
                {
                    e = Vis.colBuffer[i].ToBaseEntity();

                    if (e is TreeEntity)
                    {
                        e.Kill();
                    }
                    else if (e is SleepingBag)
                    {
                        SleepingBagHandler(e as SleepingBag);
                    }

                    Vis.colBuffer[i] = null;
                }
            }

            private void SleepingBagHandler(SleepingBag bag)
            {
                if (Entities.Contains(bag))
                {
                    return;
                }

                _bags[bag] = bag.deployerUserID;

                bag.deployerUserID = 0uL;
                bag.unlockTime = UnityEngine.Time.realtimeSinceStartup + 99999f;
            }

            private void ResetSleepingBags()
            {
                foreach (var element in _bags)
                {
                    if (element.Key == null || element.Key.IsDestroyed) continue;
                    element.Key.deployerUserID = element.Value;
                    element.Key.unlockTime = UnityEngine.Time.realtimeSinceStartup;
                }
            }

            private void OnDestroy()
            {
                Despawn();
                Destroy(go);
                Destroy(this);
            }

            private void OnItemAddedRemoved(Item item, bool bAdded)
            {
                if (!bAdded)
                {
                    StartTryToEnd();
                }
            }

            private static BaseEntity GetEntity(Collider collider)
            {
                var entity = collider.ToBaseEntity();

                while (entity != null && entity.HasParent() && !(entity is BaseMountable) && !(entity is BasePlayer))
                {
                    entity = entity.GetParentEntity();
                }

                return entity;
            }

            private void OnTriggerEnter(Collider collider)
            {
                if (collider == null || collider.name == "ZoneManager")
                {
                    return;
                }

                var entity = GetEntity(collider);

                if (entity == null)
                {
                    return;
                }

                var player = entity as BasePlayer;
                var m = entity as BaseMountable;
                var targets = new List<BasePlayer>();

                if (player.IsValid())
                {
                    m = player.GetMounted();

                    targets.Add(player);
                }

                if (m.IsValid() && TryRemoveMountable(m, targets = GetMountedPlayers(m)))
                {
                    return;
                }

                targets.RemoveAll(target => !IsHuman(target) || intruders.Contains(target) || IsUnderground(target.transform.position));

                foreach (var target in targets)
                {
                    if (IsLoading && !CanBypass(target))
                    {
                        RemovePlayer(target);
                        continue;
                    }

                    if (!target.IsConnected && Time.time - target.sleepStartTime < 2f)
                    {
                        continue;
                    }

                    if (RemoveFauxAdmin(player) || IsScavenging(player))
                    {
                        return;
                    }

                    OnEnterRaid(target);
                }
            }

            private void OnTriggerExit(Collider collider)
            {
                if (collider == null || collider.name == "ZoneManager")
                {
                    return;
                }

                var entity = GetEntity(collider);

                if (entity is BasePlayer)
                {
                    var player = entity as BasePlayer;

                    OnPlayerExit(player, player.IsDead());
                }
                else if (entity is BaseMountable)
                {
                    var m = entity as BaseMountable;

                    GetMountedPlayers(m).ForEach(player =>
                    OnPlayerExit(player, player.IsDead()));
                }
            }

            private void OnWeaponItemPreRemove(Item item)
            {
                if (isAuthorized || IsUnloading)
                {
                    return;
                }

                if (priv != null && !priv.IsDestroyed)
                {
                    foreach (var id in priv.authorizedPlayers)
                    {
                        if (id.userid.IsSteamId())
                        {
                            isAuthorized = true;
                            return;
                        }
                    }
                }
                else if (privSpawned && (priv == null || priv.IsDestroyed))
                {
                    isAuthorized = true;
                    return;
                }

                var weapon = item.parent?.entityOwner;

                if (weapon is AutoTurret)
                {
                    weapon.Invoke(() => FillAmmoTurret(weapon as AutoTurret), 0.1f);
                }
                else if (weapon is GunTrap)
                {
                    weapon.Invoke(() => FillAmmoGunTrap(weapon as GunTrap), 0.1f);
                }
                else if (weapon is SamSite)
                {
                    weapon.Invoke(() => FillAmmoSamSite(weapon as SamSite), 0.1f);
                }
            }

            private void PopulateLoot(bool unique) // rewrite this.
            {
                if (unique)
                {
                    if (!config.Treasure.UniqueBaseLoot && BaseLoot.Count > 0)
                    {
                        AddToLoot(BaseLoot);
                    }

                    if (!config.Treasure.UniqueDifficultyLoot && DifficultyLoot.Count > 0)
                    {
                        AddToLoot(DifficultyLoot);
                    }

                    if (!config.Treasure.UniqueDefaultLoot && DefaultLoot.Count > 0)
                    {
                        AddToLoot(DefaultLoot);
                    }
                }
                else
                {
                    if (BaseLoot.Count > 0)
                    {
                        AddToLoot(BaseLoot);
                    }

                    if (DifficultyLoot.Count > 0)
                    {
                        AddToLoot(DifficultyLoot);
                    }

                    if (DefaultLoot.Count > 0)
                    {
                        AddToLoot(DefaultLoot);
                    }
                }
            }

            public bool IsBlacklisted(string name)
            {
                foreach (var value in Options.BlacklistedPickupItems)
                {
                    if (!string.IsNullOrEmpty(value) && name.Contains(value, CompareOptions.OrdinalIgnoreCase))
                    {
                        return true;
                    }
                }

                return false;
            }

            private void Protector()
            {
                if (backpacks.Count > 0)
                {
                    CheckBackpacks(!AllowPVP && Options.EjectBackpacksPVE);
                }

                if (Type == RaidableType.None || intruders.Count == 0)
                {
                    return;
                }

                var targets = new List<BasePlayer>(intruders);

                foreach (var target in targets)
                {
                    if (target == null || target == owner || friends.Contains(target) || CanBypass(target) || RemoveFauxAdmin(target))
                    {
                        continue;
                    }

                    if (CanEject(target))
                    {
                        UI.DestroyStatusUI(target);
                        intruders.Remove(target);
                        RemovePlayer(target);
                    }
                    else if (ownerId.IsSteamId())
                    {
                        friends.Add(target);
                    }
                }
            }

            public static bool IsSpawning
            {
                get
                {
                    if (Time.realtimeSinceStartup - isSpawningTime > 180f)
                    {
                        isSpawning = false;
                    }

                    return isSpawning;
                }
                set
                {
                    isSpawningTime = Time.realtimeSinceStartup;
                    isSpawning = value;
                }
            }

            private void ResetToPool<T>(ICollection<T> collection)
            {
                collection.Clear();

                Pool.Free(ref collection);
            }
            private void SetGrade(BuildingBlock block, BuildingGrade.Enum grade)
            {
                block.SetGrade(grade);
                block.SetHealthToMax();
                block.SendNetworkUpdate();
                block.UpdateSkin();
            }

            private void SetNoDrops()
            {
                foreach (var container in _allcontainers)
                {
                    if (!container.IsValid() || container.IsDestroyed) continue;
                    container.dropsLoot = false;
                    container.inventory.Clear();
                }
            }

            public void SetAllowPVP(RaidableType type, bool flag)
            {
                Type = type;

                if (type == RaidableType.Maintained && config.Settings.Maintained.Chance > 0)
                {
                    AllowPVP = Core.Random.Range(0, 101) <= config.Settings.Maintained.Chance;
                }
                else if (type == RaidableType.Scheduled && config.Settings.Schedule.Chance > 0)
                {
                    AllowPVP = Core.Random.Range(0, 101) <= config.Settings.Schedule.Chance;
                }
                else if (type == RaidableType.Maintained && config.Settings.Maintained.ConvertPVP)
                {
                    AllowPVP = false;
                }
                else if (type == RaidableType.Scheduled && config.Settings.Schedule.ConvertPVP)
                {
                    AllowPVP = false;
                }
                else if (type == RaidableType.Manual && config.Settings.Manual.ConvertPVP)
                {
                    AllowPVP = false;
                }
                else if (type == RaidableType.Maintained && config.Settings.Maintained.ConvertPVE)
                {
                    AllowPVP = true;
                }
                else if (type == RaidableType.Scheduled && config.Settings.Schedule.ConvertPVE)
                {
                    AllowPVP = true;
                }
                else if (type == RaidableType.Manual && config.Settings.Manual.ConvertPVE)
                {
                    AllowPVP = true;
                }
                else AllowPVP = flag;
            }

            private void SetLoadTime()
            {
                float time;
                if (LoadingTimes.TryGetValue(PastedLocation, out time))
                {
                    loadTime = Time.time - time;
                    LoadingTimes.Remove(PastedLocation);
                }
            }

            private void SetupBuildingBlock(BuildingBlock block)
            {
                if (!IsValid(block))
                {
                    return;
                }

                if (Options.Tiers.Any())
                {
                    ChangeTier(block);
                }

                block.StopBeingDemolishable();
                block.StopBeingRotatable();
            }

            private void SetupCollider()
            {
                foreach (var e in Entities)
                {
                    if (e == null || e.IsDestroyed)
                    {
                        continue;
                    }
                    if (e.ShortPrefabName == "foundation.triangle" || e.ShortPrefabName == "foundation")
                    {
                        foundations.Add(e.transform.position);
                    }
                }

                Location = GetCenterFromMultiplePoints();

                var collider = go.AddComponent<SphereCollider>();
                collider.radius = ProtectionRadius;
                collider.isTrigger = true;
                collider.center = Vector3.zero;
                go.layer = (int)Layer.Trigger;
                go.transform.position = Location;
            }

            private void SetupContainer(StorageContainer container)
            {
                if (container.inventory == null)
                {
                    container.inventory = new ItemContainer();
                    container.inventory.ServerInitialize(null, 30);
                    container.inventory.GiveUID();
                    container.inventory.entityOwner = container;
                }

                if (Options.LockBoxes && IsBox(container, false))
                {
                    CreateLock(container);
                }

                if (Options.EmptyAll && Type != RaidableType.None)
                {
                    container.inventory.Clear();
                    ItemManager.DoRemoves();
                }

                AddContainer(container);
                SetupBoxSkin(container);

                if (Type == RaidableType.None && container.inventory.itemList.Count > 0)
                {
                    return;
                }

                container.dropFloats = true;
                container.dropsLoot = false;

                if (container is BuildingPrivlidge)
                {
                    container.dropsLoot = config.Settings.Management.AllowCupboardLoot;
                }
                else if (!IsProtectedWeapon(container) && !(container is VendingMachine))
                {
                    container.dropsLoot = true;
                }

                if (IsBox(container, true) || container is BuildingPrivlidge)
                {
                    container.inventory.SetFlag(ItemContainer.Flag.NoItemInput, true);
                }
            }

            private void SetupContainers()
            {
                foreach (var container in _containers)
                {
                    container.inventory.onItemAddedRemoved += new Action<Item, bool>(OnItemAddedRemoved);
                    if (container.prefabID != Constants.LARGE_WOODEN_BOX) continue;
                    container.SendNetworkUpdate();
                }
            }

            private void SetupFlameTurret(FlameTurret ft)
            {
                triggers[ft.trigger] = ft;
                ft.InitializeHealth(Options.FlameTurretHealth, Options.FlameTurretHealth);

                if (config.Weapons.Ammo.FlameTurret > 0)
                {
                    FillAmmoFlameTurret(ft);
                }

                if (config.Weapons.InfiniteAmmo.FlameTurret)
                {
                    ft.fuelPerSec = 0f;
                }
            }

            private void SetupFogMachine(FogMachine fm)
            {
                if (config.Weapons.Ammo.FogMachine > 0)
                {
                    FillAmmoFogMachine(fm);
                }

                if (config.Weapons.InfiniteAmmo.FogMachine)
                {
                    fm.fuelPerSec = 0f;
                }

                if (config.Weapons.FogMotion)
                {
                    fm.SetFlag(BaseEntity.Flags.Reserved7, true, false, true);
                }

                if (!config.Weapons.FogRequiresPower)
                {
                    fm.CancelInvoke(fm.CheckTrigger);
                    fm.SetFlag(BaseEntity.Flags.Reserved6, true, false, true);
                    fm.SetFlag(BaseEntity.Flags.Reserved8, true, false, true);
                    fm.SetFlag(BaseEntity.Flags.On, true, false, true);
                }
            }

            private void SetupGunTrap(GunTrap gt)
            {
                if (config.Weapons.Ammo.GunTrap > 0)
                {
                    FillAmmoGunTrap(gt);
                }

                if (config.Weapons.InfiniteAmmo.GunTrap)
                {
                    gt.inventory.onPreItemRemove += new Action<Item>(OnWeaponItemPreRemove);
                }

                triggers[gt.trigger] = gt;
            }

            private void SetupHBHFSensor(HBHFSensor sensor)
            {
                triggers[sensor.myTrigger] = sensor;
            }

            private void SetupGenerator(ElectricGenerator generator)
            {
                generator.electricAmount = config.Weapons.TestGeneratorPower;
            }

            private void SetupButton(PressButton button)
            {
                button._maxHealth = Options.Elevators.ButtonHealth;
                button.InitializeHealth(Options.Elevators.ButtonHealth, Options.Elevators.ButtonHealth);
            }

            private void SetupIgniter(Igniter igniter)
            {
                igniter.SelfDamagePerIgnite = 0f;
            }

            private void SetupIO(ContainerIOEntity io)
            {
                io.dropFloats = true;
                io.dropsLoot = !IsProtectedWeapon(io);
                io.inventory.SetFlag(ItemContainer.Flag.NoItemInput, true);
            }

            private void SetupIO(IOEntity io)
            {
                io.SetFlag(BaseEntity.Flags.Reserved8, true, false, true);
            }

            private void SetupLights()
            {
                if (Backbone.Plugin.NightLantern == null)
                {
                    if (config.Settings.Management.Lights)
                    {
                        InvokeRepeating(ToggleLights, 1f, 1f);
                    }
                    else if (config.Settings.Management.AlwaysLights)
                    {
                        ToggleLights();
                    }
                }
            }

            private void SetupLock(BaseEntity e, bool justCreated = false)
            {
                if (!Entities.Contains(e))
                {
                    Entities.Add(e);
                }

                if (Type == RaidableType.None)
                {
                    return;
                }

                if (e is CodeLock)
                {
                    var codeLock = e as CodeLock;

                    if (config.Settings.Management.RandomCodes || justCreated)
                    {
                        codeLock.code = UnityEngine.Random.Range(1000, 9999).ToString();
                        codeLock.hasCode = true;
                    }

                    codeLock.OwnerID = 0;
                    codeLock.guestCode = string.Empty;
                    codeLock.hasGuestCode = false;
                    codeLock.guestPlayers.Clear();
                    codeLock.whitelistPlayers.Clear();
                    codeLock.SetFlag(BaseEntity.Flags.Locked, true);
                }
                else if (e is KeyLock)
                {
                    var keyLock = e as KeyLock;

                    if (config.Settings.Management.RandomCodes)
                    {
                        keyLock.keyCode = UnityEngine.Random.Range(1, 100000);
                    }

                    keyLock.OwnerID = 0;
                    keyLock.firstKeyCreated = true;
                    keyLock.SetFlag(BaseEntity.Flags.Locked, true);
                }
            }

            private void SetupLoot()
            {
                _containers.RemoveAll(x => !IsValid(x));

                if (_containers.Count == 0)
                {
                    Puts(Backbone.GetMessageEx("NoContainersFound", null, BaseName, PositionToGrid(Location)));
                    return;
                }

                CheckExpansionSettings();

                int treasureAmount = Options.MinTreasure > 0 ? UnityEngine.Random.Range(Options.MinTreasure, Options.MaxTreasure) : Options.MaxTreasure;

                if (Options.SkipTreasureLoot || treasureAmount <= 0)
                {
                    return;
                }

                var containers = Pool.GetList<StorageContainer>();

                foreach (var container in _containers)
                {
                    if (IsBox(container, false))
                    {
                        containers.Add(container);
                    }
                }

                if (Options.IgnoreContainedLoot)
                {
                    containers.RemoveAll(x => !x.inventory.IsEmpty());
                    lockers.RemoveAll(x => !x.inventory.IsEmpty());
                }

                if (containers.Count == 0)
                {
                    Pool.FreeList(ref containers);
                    Puts(Backbone.GetMessageEx("NoBoxesFound", null, BaseName, PositionToGrid(Location)));
                    return;
                }

                var baseLoot = new List<TreasureItem>(Backbone.Plugin.BaseLootList);

                TakeLootFrom(baseLoot, BaseLoot);

                BaseLootPermanent = BaseLoot.ToList();

                if (BaseLoot.Count < treasureAmount && Buildings.Normal.Count > 0)
                {
                    TakeLootFrom(new List<TreasureItem>(Buildings.Normal), DifficultyLoot);
                }

                if (BaseLoot.Count + DifficultyLoot.Count < treasureAmount)
                {
                    TakeLootFrom(TreasureLoot, DefaultLoot);
                }

                PopulateLoot(true);

                if (Options.AllowDuplicates && Loot.Count > 0 && Loot.Count < treasureAmount)
                {
                    var copy = Loot.ToList();

                    while (Loot.Count < treasureAmount && copy.Count > 0)
                    {
                        var ti = copy.GetRandom();

                        if (IsUnique(ti))
                        {
                            copy.Remove(ti);
                            continue;
                        }

                        Loot.Add(ti);
                    }
                }

                PopulateLoot(false);

                if (Loot.Count == 0)
                {
                    Pool.FreeList(ref containers);
                    Puts(Backbone.GetMessageEx("NoConfiguredLoot"));
                    return;
                }

                if (!Options.AllowDuplicates)
                {
                    var newLoot = new List<TreasureItem>();
                    var shortNames = new List<string>();

                    foreach (var ti in Loot)
                    {
                        if (ti.modified || !shortNames.Contains(ti.shortname) || IsPriority(ti))
                        {
                            newLoot.Add(ti);
                            shortNames.Add(ti.shortname);
                        }
                    }

                    Loot = newLoot;
                }

                Shuffle(Loot);

                if (Loot.Count > treasureAmount)
                {
                    int index = Loot.Count;

                    while (Loot.Count > treasureAmount && --index >= 0)
                    {
                        if (IsPriority(Loot[index]))
                        {
                            continue;
                        }

                        Loot.RemoveAt(index);
                    }
                }

                if (Options.DivideLoot)
                {
                    DivideLoot(containers, treasureAmount);
                }
                else
                {
                    SpawnLoot(containers, treasureAmount);
                }

                if (itemAmountSpawned == 0)
                {
                    Puts(Backbone.GetMessageEx("NoLootSpawned"));
                }

                Pool.FreeList(ref containers);
            }

            private bool IsUnique(TreasureItem ti)
            {
                if (config.Treasure.UniqueBaseLoot)
                {
                    foreach (var x in BaseLootPermanent)
                    {
                        if (x.shortname == ti.shortname && x.amount == ti.amount && x.skin == ti.skin)
                        {
                            return true;
                        }
                    }
                }

                if (config.Treasure.UniqueDifficultyLoot)
                {
                    foreach (var x in DifficultyLoot)
                    {
                        if (x.shortname == ti.shortname && x.amount == ti.amount && x.skin == ti.skin)
                        {
                            return true;
                        }
                    }
                }

                if (config.Treasure.UniqueDefaultLoot)
                {
                    foreach (var x in DefaultLoot)
                    {
                        if (x.shortname == ti.shortname && x.amount == ti.amount && x.skin == ti.skin)
                        {
                            return true;
                        }
                    }
                }

                return false;
            }

            private void SetupPickup(BaseCombatEntity e)
            {
                e.pickup.enabled = false;
            }

            private void SetupSearchLight(SearchLight light)
            {
                if (!config.Settings.Management.Lights && !config.Settings.Management.AlwaysLights)
                {
                    return;
                }

                lights.Add(light);

                light.enabled = false;
            }

            private void SetupTeslaCoil(TeslaCoil tc)
            {
                if (!config.Weapons.TeslaCoil.RequiresPower)
                {
                    tc.UpdateFromInput(25, 0);
                    tc.SetFlag(IOEntity.Flag_HasPower, true, false, true);
                }

                tc.maxDischargeSelfDamageSeconds = Mathf.Clamp(config.Weapons.TeslaCoil.MaxDischargeSelfDamageSeconds, 0f, 9999f);
                tc.maxDamageOutput = Mathf.Clamp(config.Weapons.TeslaCoil.MaxDamageOutput, 0f, 9999f);
            }

            private void SetupTurret(AutoTurret turret)
            {
                SetupIO(turret as IOEntity);

                if (Type != RaidableType.None)
                {
                    turret.authorizedPlayers.Clear();
                }

                triggers[turret.targetTrigger] = turret;
                turret.InitializeHealth(Options.AutoTurret.Health, Options.AutoTurret.Health);
                turret.sightRange = Options.AutoTurret.SightRange;
                turret.aimCone = Options.AutoTurret.AimCone;
                turrets.Add(turret);

                if (Options.AutoTurret.RemoveWeapon)
                {
                    turret.AttachedWeapon = null;
                    Item slot = turret.inventory.GetSlot(0);

                    if (slot != null && (slot.info.category == ItemCategory.Weapon || slot.info.category == ItemCategory.Fun))
                    {
                        slot.RemoveFromContainer();
                        slot.Remove();
                    }
                }

                Options.AutoTurret.Shortnames.RemoveAll(value => value == "fun.trumpet");

                if (Options.AutoTurret.Shortnames.Count > 0)
                {
                    if (turret.AttachedWeapon == null)
                    {
                        var shortname = Options.AutoTurret.Shortnames.GetRandom();
                        var itemToCreate = ItemManager.FindItemDefinition(shortname);

                        if (itemToCreate != null)
                        {
                            Item item = ItemManager.Create(itemToCreate, 1, (ulong)itemToCreate.skins.GetRandom().id);

                            if (!item.MoveToContainer(turret.inventory, 0, false))
                            {
                                item.Remove();
                            }
                        }
                    }
                }

                turret.Invoke(turret.UpdateAttachedWeapon, 1f);

                if (Backbone.Plugin.debugMode)
                {
                    Backbone.Timer(2.5f, () =>
                    {
                        if (turret == null)
                        {
                            return;
                        }

                        FillAmmoTurret(turret);
                    });
                }
                else turret.Invoke(() => FillAmmoTurret(turret), 2.5f);

                if (Options.AutoTurret.Hostile)
                {
                    turret.SetPeacekeepermode(false);
                }

                if (!Options.AutoTurret.RequiresPower)
                {
                    turret.Invoke(turret.InitiateStartup, 3f);
                }

                if (config.Weapons.InfiniteAmmo.AutoTurret)
                {
                    turret.inventory.onPreItemRemove += new Action<Item>(OnWeaponItemPreRemove);
                }
            }

            private void SetupVendingMachine(VendingMachine vm)
            {
                if (config.Settings.Management.AllowBroadcasting)
                {
                    return;
                }

                vm.SetFlag(BaseEntity.Flags.Reserved4, false, false, true);
                vm.UpdateMapMarker();
            }

            private bool Teleported(BasePlayer p)
            {
                if (!config.Settings.Management.AllowTeleport && p.IsConnected && !CanBypass(p) && Interface.CallHook("OnBlockRaidableBasesTeleport", p, Location) == null)
                {
                    if (NearFoundation(p.transform.position)) // if (InRange(p.transform.position, Location, Options.ProtectionRadius * 0.9f, false))
                    {
                        TryMessage(p, "CannotTeleport");

                        return true;
                    }
                }

                return false;
            }

            public bool TryMessage(BasePlayer player, string key, params object[] args)
            {
                if (player == null || messagesSent.Contains(player.UserIDString))
                {
                    return false;
                }

                string userid = player.UserIDString;

                messagesSent.Add(userid);
                Instance.timer.Once(10f, () => messagesSent.Remove(userid));
                Backbone.Message(player, key, args);

                return true;
            }

            private Vector3 RandomPosition(float radius)
            {
                return RandomWanderPositions(Options.ArenaWalls.Radius * 0.9f).FirstOrDefault();
            }

            private List<Vector3> RandomWanderPositions(float radius)
            {
                var list = new List<Vector3>();

                for (int i = 0; i < 10; i++)
                {
                    var target = GetRandomPoint(radius);
                    var vector = FindPointOnNavmesh(target, radius);

                    if (vector != Vector3.zero)
                    {
                        list.Add(vector);
                    }
                }

                return list;
            }

            public static BaseEntity Get(TriggerBase triggerBase)
            {
                foreach (var raid in Backbone.Plugin.Raids.Values)
                {
                    if (raid.triggers.ContainsKey(triggerBase))
                    {
                        return raid.triggers[triggerBase];
                    }
                }

                return null;
            }

            public static int Get(RaidableType type)
            {
                int amount = 0;

                foreach (var value in Instance.Locations)
                {
                    if (value.Type == type)
                    {
                        amount++;
                    }
                }

                return amount;
            }

            public static int Get(RaidableMode mode)
            {
                return Backbone.Plugin.Raids.Values.Count;
            }

            public static RaidableBase Get(ulong userID)
            {
                RaidableBase raid;
                if (Backbone.Plugin.Npcs.TryGetValue(userID, out raid))
                {
                    return raid;
                }

                return null;
            }

            public static RaidableBase Get(Vector3 target, float f = 0f)
            {
                foreach (var raid in Backbone.Plugin.Raids.Values)
                {
                    if (InRange(raid.Location, target, raid.Options.ProtectionRadius + f))
                    {
                        return raid;
                    }
                }

                return null;
            }

            public static RaidableBase Get(BasePlayer victim, HitInfo hitInfo = null)
            {
                if (Has(victim.userID))
                {
                    return Get(victim.userID);
                }

                DelaySettings ds;
                if (Instance.PvpDelay.TryGetValue(victim.userID, out ds) && ds.RaidableBase != null)
                {
                    return ds.RaidableBase;
                }

                return hitInfo == null ? null : Get(hitInfo.PointStart) ?? Get(hitInfo.PointEnd);
            }

            public static RaidableBase Get(PlayerCorpse corpse)
            {
                if (!corpse.playerSteamID.IsSteamId())
                {
                    return Get(corpse.playerSteamID);
                }

                DelaySettings ds;
                if (Backbone.Plugin.PvpDelay.TryGetValue(corpse.playerSteamID, out ds))
                {
                    return ds.RaidableBase;
                }

                return Get(corpse.transform.position);
            }

            public static RaidableBase Get(BaseEntity entity)
            {
                if (GarbageController.RaidEntities.ContainsKey(entity))
                {
                    return GarbageController.RaidEntities[entity];
                }

                return null;
            }

            public static RaidableBase Get(List<BaseEntity> entities)
            {
                foreach (var raid in Backbone.Plugin.Raids.Values)
                {
                    foreach (var e in entities)
                    {
                        if (InRange(raid.PastedLocation, e.transform.position, Radius))
                        {
                            return raid;
                        }
                    }
                }

                return null;
            }

            private BasePlayer Record(BasePlayer attacker, BaseCombatEntity victim)
            {
                attacker.lastDealtDamageTime = Time.time;
                records[victim.net.ID] = attacker;
                victim.lastAttacker = attacker;

                return attacker;
            }

            public BasePlayer GetInitiatorPlayer(HitInfo hitInfo, BaseCombatEntity victim)
            {
                BasePlayer attacker;

                if (hitInfo.Initiator is BasePlayer)
                {
                    return Record(hitInfo.Initiator as BasePlayer, victim);
                }

                if (!hitInfo.damageTypes.Has(DamageType.Heat))
                {
                    return null;
                }

                foreach (var intruder in intruders)
                {
                    if (!intruder.IsValid() || Time.time - intruder.lastDealtDamageTime > 1f || !IsUsingProjectile(intruder))
                    {
                        continue;
                    }

                    return Record(intruder, victim);
                }

                if (records.TryGetValue(victim.net.ID, out attacker) && attacker.IsValid())
                {
                    return Record(attacker, victim);
                }

                if (victim.lastAttacker is BasePlayer)
                {
                    return Record(victim.lastAttacker as BasePlayer, victim);
                }

                return null;
            }

            private bool IsUsingProjectile(BasePlayer player)
            {
                if (player == null || player.svActiveItemID == 0)
                {
                    return false;
                }

                Item item = player.GetActiveItem();

                if (item == null)
                {
                    return false;
                }

                return item.info.shortname == "flamethrower" || item.GetHeldEntity() is BaseProjectile;
            }

            public ulong GetItemSkin(ItemDefinition def, ulong defaultSkin, bool unique)
            {
                ulong skin = defaultSkin;

                if (def.shortname != "explosive.satchel" && def.shortname != "grenade.f1")
                {
                    if (!skins.TryGetValue(def.shortname, out skin)) // apply same skin once randomly chosen so items with skins can stack properly
                    {
                        skin = defaultSkin;
                    }

                    if (!unique || skin == 0)
                    {
                        var si = GetItemSkins(def);
                        var random = new List<ulong>();

                        if (config.Skins.Loot.RandomWorkshopSkins && si.workshopSkins.Count > 0)
                        {
                            random.Add(si.workshopSkins.GetRandom());
                        }

                        if (config.Skins.Loot.RandomSkins && si.skins.Count > 0)
                        {
                            random.Add(si.skins.GetRandom());
                        }

                        if (random.Count != 0)
                        {
                            skins[def.shortname] = skin = random.GetRandom();
                        }
                    }
                }

                return skin;
            }

            public static SkinInfo GetItemSkins(ItemDefinition def)
            {
                SkinInfo si;
                if (!Backbone.Plugin.Skins.TryGetValue(def.shortname, out si))
                {
                    Backbone.Plugin.Skins[def.shortname] = si = new SkinInfo();

                    foreach (var skin in def.skins)
                    {
                        if (IsBlacklistedSkin(def, skin.id))
                        {
                            continue;
                        }

                        var id = Convert.ToUInt64(skin.id);

                        si.skins.Add(id);
                        si.allSkins.Add(id);
                    }

                    if (def.skins2 == null)
                    {
                        return si;
                    }

                    foreach (var skin in def.skins2)
                    {
                        if (IsBlacklistedSkin(def, (int)skin.WorkshopId))
                        {
                            continue;
                        }

                        if (!si.workshopSkins.Contains(skin.WorkshopId))
                        {
                            si.workshopSkins.Add(skin.WorkshopId);
                            si.allSkins.Add(skin.WorkshopId);
                        }
                    }
                }

                return si;
            }

            public static bool Has(ulong userID)
            {
                return Backbone.Plugin.Npcs.ContainsKey(userID);
            }

            public static bool Has(TriggerBase triggerBase)
            {
                foreach (var raid in Backbone.Plugin.Raids.Values)
                {
                    if (raid.triggers.ContainsKey(triggerBase))
                    {
                        return true;
                    }
                }

                return false;
            }

            public static bool Has(BaseEntity entity)
            {
                return GarbageController.RaidEntities.ContainsKey(entity);
            }

            public static bool IsOwner(BasePlayer player)
            {
                foreach (var raid in Backbone.Plugin.Raids.Values)
                {
                    if (raid.ownerId.IsSteamId() && raid.ownerId == player.userID && raid.IsOpened)
                    {
                        return true;
                    }
                }

                return false;
            }

            public static bool IsTooClose(Vector3 target, float radius)
            {
                return Instance.Locations.Exists(value => InRange(value.Position, target, radius));
            }

            public static void StopUsingWand(BasePlayer player)
            {
                if (!config.Settings.NoWizardry || Backbone.Plugin.Wizardry == null || !Backbone.Plugin.Wizardry.IsLoaded)
                {
                    return;
                }

                if (player.svActiveItemID == 0)
                {
                    return;
                }

                Item item = player.GetActiveItem();

                if (item?.info.shortname != "knife.bone")
                {
                    return;
                }

                if (!item.MoveToContainer(player.inventory.containerMain))
                {
                    item.DropAndTossUpwards(player.GetDropPosition() + player.transform.forward, 2f);
                    Backbone.Message(player, "TooPowerfulDrop");
                }
                else Backbone.Message(player, "TooPowerful");
            }

            public static void UpdateAllMarkers()
            {
                foreach (var raid in Backbone.Plugin.Raids.Values)
                {
                    raid.UpdateMarker();
                }
            }

            public BackpackData AddCorpse(DroppedItemContainer backpack, BasePlayer player)
            {
                BackpackData data;
                if (!backpacks.TryGetValue(backpack.net.ID, out data))
                {
                    backpacks[backpack.net.ID] = data = new BackpackData
                    {
                        backpack = backpack,
                        player = player,
                        userID = backpack.playerSteamID
                    };
                }

                return data;
            }

            public bool Any(ulong targetId, bool checkFriends = true)
            {
                if (ownerId == targetId)
                {
                    return true;
                }

                foreach (var x in raiders)
                {
                    if (x.Key == targetId)
                    {
                        return true;
                    }
                }

                if (checkFriends)
                {
                    foreach (var x in friends)
                    {
                        if (x?.userID == targetId)
                        {
                            return true;
                        }
                    }
                }

                return false;
            }

            public bool CanEject()
            {
                if (AllowPVP && Options.EjectLockedPVP && ownerId.IsSteamId())
                {
                    return true;
                }

                if (!AllowPVP && Options.EjectLockedPVE && ownerId.IsSteamId())
                {
                    return true;
                }

                return false;
            }

            public bool CanSetupEntity(BaseEntity e)
            {
                BaseEntity.saveList.Remove(e);

                if (e == null || e.IsDestroyed)
                {
                    Entities.Remove(e);
                    return false;
                }

                if (e.net == null)
                {
                    e.net = Net.sv.CreateNetworkable();
                }

                if (e is StaticInstrument)
                {
                    Entities.Remove(e);
                    e.Invoke(e.KillMessage, 0.01f);
                    return false;
                }

                e.enableSaving = false;
                return true;
            }

            public bool EjectBackpack(uint key, BackpackData data, bool bypass)
            {
                if (data.backpack == null || data.backpack.IsDestroyed)
                {
                    return true;
                }

                if (!bypass && (!ownerId.IsSteamId() || Any(data.userID) || data.player.IsValid() && IsAlly(data.player)))
                {
                    return false;
                }

                var position = GetEjectLocation(data.backpack.transform.position, 5f, Location, ProtectionRadius);

                position.y = Mathf.Max(position.y, TerrainMeta.WaterMap.GetHeight(position));
                data.backpack.transform.position = position;
                data.backpack.TransformChanged();

                var player = data.player;

                if (player.IsValid() && player.IsConnected)
                {
                    if (config.Settings.Management.DrawTime <= 0)
                    {
                        Backbone.Message(player, "YourCorpse");
                        return true;
                    }

                    bool isAdmin = player.IsAdmin;

                    Backbone.Message(player, "YourCorpse");

                    try
                    {
                        ToggleAdminFlag(player, isAdmin, true);

                        string message = Backbone.GetMessageEx("YourCorpse", player.UserIDString);

                        player.SendConsoleCommand("ddraw.text", config.Settings.Management.DrawTime, Color.red, data.backpack.transform.position, message);
                    }
                    catch (Exception ex)
                    {
                        Puts("{0}", ex);
                    }
                    finally
                    {
                        ToggleAdminFlag(player, isAdmin, false);
                    }
                }

                Interface.CallHook("OnRaidableBaseBackpackEjected", new object[] { data.player, data.userID, data.backpack, Location, AllowPVP, (int)Options.Mode, GetOwner(), GetRaiders() });

                return true;
            }

            public BasePlayer GetOwner()
            {
                if (owner.IsValid())
                {
                    return owner;
                }

                foreach (var raider in raiders)
                {
                    if (raider.Value.IsValid())
                    {
                        return raider.Value;
                    }
                }

                return null;
            }

            public List<BasePlayer> GetIntruders()
            {
                return intruders.Where(intruder => intruder.IsValid()).ToList();
            }

            public List<BasePlayer> GetRaiders()
            {
                return raiders.Where(raider => raider.Value.IsValid()).Select(raider => raider.Value).ToList();
            }

            public void EnsureDismounted(BaseMountable mountable)
            {
                if (mountable == null)
                {
                    return;
                }

                if (mountable is BaseVehicle)
                {
                    var vehicle = mountable as BaseVehicle;

                    vehicle.DismountAllPlayers();
                }
                else mountable.DismountAllPlayers();
            }

            public static Vector3 GetEjectLocation(Vector3 a, float distance, Vector3 target, float radius)
            {
                var position = ((a.XZ3D() - target.XZ3D()).normalized * (radius + distance)) + target; // credits ZoneManager
                float y = TerrainMeta.HighestPoint.y + 250f;

                RaycastHit hit;
                if (Physics.Raycast(position + new Vector3(0f, y, 0f), Vector3.down, out hit, Mathf.Infinity, targetLayer, QueryTriggerInteraction.Ignore))
                {
                    position.y = hit.point.y + 0.75f;
                }
                else position.y = Mathf.Max(TerrainMeta.HeightMap.GetHeight(position), TerrainMeta.WaterMap.GetHeight(position)) + 0.75f;

                return position;
            }

            public bool IsAlly(ulong playerId, ulong targetId, AlliedType type = AlliedType.All)
            {
                if (type == AlliedType.All || type == AlliedType.Team)
                {
                    RelationshipManager.PlayerTeam team;
                    if (RelationshipManager.ServerInstance.playerToTeam.TryGetValue(playerId, out team) && team.members.Contains(targetId))
                    {
                        return true;
                    }
                }

                if ((type == AlliedType.All || type == AlliedType.Clan) && Convert.ToBoolean(Backbone.Plugin.Clans?.Call("IsMemberOrAlly", playerId.ToString(), targetId.ToString())))
                {
                    return true;
                }

                if ((type == AlliedType.All || type == AlliedType.Friend) && Convert.ToBoolean(Backbone.Plugin.Friends?.Call("AreFriends", playerId.ToString(), targetId.ToString())))
                {
                    return true;
                }

                return false;
            }

            public bool IsAlly(BasePlayer player)
            {
                if (!ownerId.IsSteamId() || CanBypass(player) || player.userID == ownerId || friends.Contains(player))
                {
                    return true;
                }

                if (IsAlly(player.userID, ownerId))
                {
                    friends.Add(player);
                    return true;
                }

                return false;
            }

            public bool IsControlledMount(BaseMountable m)
            {
                if (!config.Settings.Management.Mounts.ControlledMounts)
                {
                    return false;
                }

                if (m is BaseChair)
                {
                    m.DismountAllPlayers();

                    return true;
                }

                var parentEntity = m.GetParentEntity();

                if (parentEntity == null || parentEntity is RidableHorse)
                {
                    return false;
                }

                if (parentEntity.GetType().Name.Contains("Controller"))
                {
                    m.DismountAllPlayers();

                    return true;
                }

                return false;
            }

            public bool IsDayTime() => TOD_Sky.Instance?.Cycle.DateTime.Hour >= 8 && TOD_Sky.Instance?.Cycle.DateTime.Hour < 20;

            public void DestroyMapMarkers()
            {
                if (explosionMarker != null && !explosionMarker.IsDestroyed)
                {
                    explosionMarker.CancelInvoke(explosionMarker.DelayedDestroy);
                    explosionMarker.Kill();
                }

                if (genericMarker != null && !genericMarker.IsDestroyed)
                {
                    genericMarker.Kill();
                }

                if (vendingMarker != null && !vendingMarker.IsDestroyed)
                {
                    vendingMarker.Kill();
                }
            }

            public bool RemovePlayer(BasePlayer player, int index = 0)
            {
                if (!IsHuman(player) || Type == RaidableType.None && !player.IsSleeping())
                {
                    return false;
                }

                var m = player.GetMounted();

                if (m.IsValid())
                {
                    var players = GetMountedPlayers(m);

                    players.RemoveAll(x => x == null || !IsHuman(x));

                    if (RemoveMountable(m, players))
                    {
                        return true;
                    }
                }

                var position = GetEjectLocation(player.transform.position, 10f, Location, ProtectionRadius);

                if (player.IsFlying)
                {
                    position.y = player.transform.position.y;
                }

                player.Teleport(position);
                player.SendNetworkUpdateImmediate();

                return true;
            }


            public void ResetOwner()
            {
                if (!IsOpened || IsPlayerActive(ownerId))
                {
                    TryInvokeResetOwner();
                    return;
                }

                owner = null;
                ownerId = 0;
                friends.Clear();
                UpdateMarker();
            }

            public void RespawnNpcNow()
            {
                if (npcs.Count >= npcMaxAmount)
                {
                    return;
                }

                var npc = SpawnNpc(Options.NPC.SpawnScientistsOnly ? false : Options.NPC.SpawnBoth ? UnityEngine.Random.value > 0.5f : Options.NPC.SpawnMurderers);

                if (npc == null || npcs.Count >= npcMaxAmount)
                {
                    return;
                }

                TryRespawnNpc();
            }

            public void SpawnNpcs()
            {
                if (!Options.NPC.Enabled || (Options.NPC.UseExpansionNpcs && config.Settings.ExpansionMode && Backbone.Plugin.DangerousTreasures != null && Backbone.Plugin.DangerousTreasures.IsLoaded))
                {
                    return;
                }

                for (int i = 0; i < npcMaxAmount; i++)
                {
                    if (npcs.Count >= npcMaxAmount)
                    {
                        break;
                    }

                    SpawnNpc(!Options.NPC.SpawnScientistsOnly && (Options.NPC.SpawnBoth ? UnityEngine.Random.value >= 0.5f : Options.NPC.SpawnMurderers));
                }
            }

            public void TryInvokeResetOwner()
            {
                if (config.Settings.Management.LockTime > 0f)
                {
                    if (IsInvoking(ResetOwner)) CancelInvoke(ResetOwner);
                    Invoke(ResetOwner, config.Settings.Management.LockTime * 60f);
                }
            }

            public void TryRespawnNpc()
            {
                if ((!IsOpened && !Options.Levels.Level2) || IsInvoking(RespawnNpcNow))
                {
                    return;
                }

                if (Options.RespawnRateMin > 0)
                {
                    Invoke(RespawnNpcNow, UnityEngine.Random.Range(Options.RespawnRateMin, Options.RespawnRateMax));
                }
                else Invoke(RespawnNpcNow, Options.RespawnRateMax);
            }

            public void Undo()
            {
                if (IsOpened)
                {
                    float time = config.Settings.Management.DespawnMinutes > 0 ? config.Settings.Management.DespawnMinutes * 60f : 0f;

                    IsOpened = false;
                    CancelInvoke(ResetOwner);

                    if (time > 0f)
                    {
                        despawnTime = Time.realtimeSinceStartup + time;

                        if (config.EventMessages.ShowWarning)
                        {
                            var grid = FormatGridReference(Location);

                            foreach (var target in BasePlayer.activePlayerList)
                            {
                                SendNotification(target, _("DestroyingBaseAt", target.UserIDString, grid, config.Settings.Management.DespawnMinutes));
                            }
                        }

                        var go = gameObject;

                        Backbone.Timer(time, () => Destroy(go));
                    }
                    else Destroy(gameObject);
                }
            }

            public void UpdateMarker()
            {
                if (IsLoading)
                {
                    Invoke(UpdateMarker, 1f);
                    return;
                }

                if (genericMarker != null && !genericMarker.IsDestroyed)
                {
                    genericMarker.SendUpdate();
                }

                if (explosionMarker != null && !explosionMarker.IsDestroyed)
                {
                    explosionMarker.transform.position = Location;
                    explosionMarker.SendNetworkUpdate();
                }

                if (vendingMarker != null && !vendingMarker.IsDestroyed)
                {
                    vendingMarker.transform.position = Location;
                    float seconds = despawnTime - Time.realtimeSinceStartup;
                    string despawnText = config.Settings.Management.DespawnMinutesInactive > 0 && seconds > 0 ? string.Format(" [{0}m]", Math.Floor(TimeSpan.FromSeconds(seconds).TotalMinutes)) : null;
                    string flag = Backbone.GetMessageEx(AllowPVP ? "PVPFlag" : "PVEFlag");
                    string markerShopName = markerName == config.Settings.Markers.MarkerName ? _("MapMarkerOrderWithMode", null, flag, Mode(), markerName, despawnText) : string.Format("{0} {1}", flag, markerName);

                    vendingMarker.markerShopName = markerShopName.Trim();
                    vendingMarker.SendNetworkUpdate();
                }

                if (markerCreated || !IsMarkerAllowed())
                {
                    return;
                }

                if (config.Settings.Markers.UseExplosionMarker)
                {
                    explosionMarker = GameManager.server.CreateEntity(Backbone.Path.ExplosionMarker, Location) as MapMarkerExplosion;

                    if (explosionMarker != null)
                    {
                        explosionMarker.Spawn();
                        explosionMarker.Invoke(() => explosionMarker.CancelInvoke(explosionMarker.DelayedDestroy), 1f);
                    }
                }
                else if (config.Settings.Markers.UseVendingMarker)
                {
                    vendingMarker = GameManager.server.CreateEntity(Backbone.Path.VendingMarker, Location) as VendingMachineMapMarker;

                    if (vendingMarker != null)
                    {
                        string flag = Backbone.GetMessageEx(AllowPVP ? "PVPFlag" : "PVEFlag");
                        string despawnText = config.Settings.Management.DespawnMinutesInactive > 0 ? string.Format(" [{0}m]", config.Settings.Management.DespawnMinutesInactive.ToString()) : null;
                        string markerShopName;

                        if (markerName == config.Settings.Markers.MarkerName)
                        {
                            markerShopName = _("MapMarkerOrderWithMode", null, flag, Mode(), markerName, despawnText);
                        }
                        else markerShopName = _("MapMarkerOrderWithoutMode", null, flag, markerName, despawnText); 
                        
                        vendingMarker.enabled = false;
                        vendingMarker.markerShopName = markerShopName;
                        vendingMarker.Spawn();
                    }
                }

                markerCreated = true;
            }

            private ScientistNPC InstantiateEntity(Vector3 position, out HumanoidBrain humanoidBrain)
            {
                var prefabName = StringPool.Get(1536035819);
                var prefab = GameManager.server.FindPrefab(prefabName);
                var go = Facepunch.Instantiate.GameObject(prefab, position, Quaternion.identity);

                go.SetActive(false);

                go.name = prefabName;

                ScientistBrain scientistBrain = go.GetComponent<ScientistBrain>();
                ScientistNPC npc = go.GetComponent<ScientistNPC>();

                npc.enableSaving = false;
                humanoidBrain = go.AddComponent<HumanoidBrain>();
                humanoidBrain.DestinationOverride = position;
                humanoidBrain.CheckLOS = humanoidBrain.RefreshKnownLOS = true;
                humanoidBrain.SenseRange = Options.NPC.AggressionRange;
                humanoidBrain.softLimitSenseRange = humanoidBrain.SenseRange + (humanoidBrain.SenseRange * 0.25f);
                humanoidBrain.TargetLostRange = humanoidBrain.SenseRange * 1.25f;
                humanoidBrain.Settings = Options.NPC;
                humanoidBrain.UseAIDesign = false;
                humanoidBrain._baseEntity = npc;
                humanoidBrain.raid = this;
                humanoidBrain.npc = npc;

                DestroyImmediate(scientistBrain, true);

                SceneManager.MoveGameObjectToScene(go, Rust.Server.EntityScene);

                go.AwakeFromInstantiate();

                return npc;
            }

            private static bool IsBlacklistedSkin(ItemDefinition def, int num)
            {
                var skinId = ItemDefinition.FindSkin(def.isRedirectOf?.itemid ?? def.itemid, num);
                var dirSkin = def.isRedirectOf == null ? def.skins.FirstOrDefault(x => (ulong)x.id == skinId) : def.isRedirectOf.skins.FirstOrDefault(x => (ulong)x.id == skinId);
                var itemSkin = (dirSkin.id == 0) ? null : (dirSkin.invItem as ItemSkin);

                if (itemSkin?.Redirect != null || def.isRedirectOf != null)
                {
                    return true;
                }

                return false;
            }

            private void AddToLoot(List<TreasureItem> source)
            {
                foreach (var ti in source)
                {
                    bool isBlueprint = ti.shortname.EndsWith(".bp");
                    string shortname = isBlueprint ? ti.shortname.Replace(".bp", string.Empty) : ti.shortname;
                    bool isModified = false;

                    if (shortname.Contains("_") && ItemManager.FindItemDefinition(shortname) == null)
                    {
                        shortname = shortname.Substring(shortname.IndexOf("_") + 1);
                        isModified = true;
                    }

                    if (ti.definition == null)
                    {
                        Puts("Invalid shortname in config: {0} -> {1}", ti.shortname, shortname);
                        continue;
                    }

                    ti.isBlueprint = isBlueprint;

                    int amount = ti.amount;

                    if (ti.amountMin < ti.amount)
                    {
                        amount = Core.Random.Range(ti.amountMin, ti.amount + 1); // get a random number min (inclusive) and max (exclusive)
                    }

                    if (amount <= 0)
                    {
                        continue; // skip item and pick another since the min amount was 0 while the max amount was greater than 0 and random number equaled 0
                    }

                    if (config.Treasure.UseStackSizeLimit)
                    {
                        var stacks = GetStacks(amount, ti.definition.stackable);
                        isModified = amount > ti.definition.stackable;

                        foreach (int stack in stacks)
                        {
                            Loot.Add(new TreasureItem
                            {
                                amount = stack,
                                shortname = shortname,
                                skin = ti.skin,
                                modified = isModified,
                                isBlueprint = isBlueprint
                            });
                        }
                    }
                    else
                    {
                        Loot.Add(new TreasureItem
                        {
                            amount = amount,
                            shortname = shortname,
                            skin = ti.skin,
                            isBlueprint = isBlueprint,
                            modified = isModified
                        });
                    }
                }

                source.Clear();
            }

            private bool CanEject(List<BasePlayer> players)
            {
                foreach (var player in players)
                {
                    if (CanEject(player))
                    {
                        return true;
                    }
                }

                return false;
            }

            private bool CanEject(BasePlayer target)
            {
                if (target == null || target == owner)
                {
                    return false;
                }

                if (IsBanned(target) || IsHogging(target))
                {
                    return true;
                }
                else if (CanEject() && !IsAlly(target))
                {
                    TryMessage(target, "OnPlayerEntryRejected");

                    return true;
                }

                return false;
            }

            private void CheckExpansionSettings()
            {
                if (!config.Settings.ExpansionMode || Backbone.Plugin.DangerousTreasures == null)
                {
                    return;
                }

                var boxes = Pool.GetList<StorageContainer>();

                foreach (var x in _containers)
                {
                    if (x.prefabID == 2206646561)
                    {
                        boxes.Add(x);
                    }
                }

                if (boxes.Count > 0)
                {
                    Backbone.Plugin.DangerousTreasures?.Call("API_SetContainer", boxes.GetRandom(), Radius, !Options.NPC.Enabled || Options.NPC.UseExpansionNpcs);
                }

                Pool.FreeList(ref boxes);
            }

            private void CreateCodeLock(BaseEntity entity)
            {
                var codeLock = GameManager.server.CreateEntity(Backbone.Path.CodeLock) as CodeLock;

                if (codeLock == null)
                {
                    return;
                }

                codeLock.gameObject.Identity();
                codeLock.SetParent(entity, entity.GetSlotAnchorName(BaseEntity.Slot.Lock));
                codeLock.Spawn();
                entity.SetSlot(BaseEntity.Slot.Lock, codeLock);

                SetupLock(codeLock, true);
            }

            private void CreateGenericMarker()
            {
                if (config.Settings.Markers.UseExplosionMarker || config.Settings.Markers.UseVendingMarker)
                {
                    if (!IsMarkerAllowed())
                    {
                        return;
                    }

                    genericMarker = GameManager.server.CreateEntity(Backbone.Path.RadiusMarker, Location) as MapMarkerGenericRadius;

                    if (genericMarker != null)
                    {
                        genericMarker.alpha = 0.75f;
                        genericMarker.color1 = GetMarkerColor1();
                        genericMarker.color2 = GetMarkerColor2();
                        genericMarker.radius = Mathf.Min(2.5f, config.Settings.Markers.Radius);
                        genericMarker.Spawn();
                        genericMarker.SendUpdate();
                    }
                }
            }

            private void CreateLock(BaseEntity entity)
            {
                if (Type == RaidableType.None || entity == null || entity.IsDestroyed)
                {
                    return;
                }

                var slot = entity.GetSlot(BaseEntity.Slot.Lock) as BaseLock;

                if (slot == null)
                {
                    CreateCodeLock(entity);
                    return;
                }

                var keyLock = slot.GetComponent<KeyLock>();

                if (keyLock.IsValid() && !keyLock.IsDestroyed)
                {
                    keyLock.SetParent(null);
                    keyLock.Kill();
                }

                CreateCodeLock(entity);
            }

            private void DivideLoot(List<StorageContainer> containers, int treasureAmount)
            {
                while (Loot.Count > 0 && containers.Count > 0 && itemAmountSpawned < treasureAmount)
                {
                    var lootItem = Loot.GetRandom();

                    if (containers.Count > 1)
                    {
                        var lastContainer = containers[0];

                        containers.Remove(lastContainer);

                        SpawnItem(lootItem, containers);

                        containers.Insert(containers.Count, lastContainer);
                    }
                    else SpawnItem(lootItem, containers);

                    Loot.Remove(lootItem);

                    containers.RemoveAll(container => container.inventory.IsFull());
                }
            }

            private bool IsFlying(BasePlayer player)
            {
                return player?.modelState?.onground == false && TerrainMeta.HeightMap.GetHeight(player.transform.position) < player.transform.position.y - 1f;
            }

            private bool EjectMountable(BaseMountable m, float distance, List<BasePlayer> players)
            {
                var j = TerrainMeta.HeightMap.GetHeight(m.transform.position) - m.transform.position.y;

                if (j > 5f)
                {
                    distance += j;
                }

                var position = ((m.transform.position.XZ3D() - Location.XZ3D()).normalized * (ProtectionRadius + distance)) + Location;
                var e = m.transform.eulerAngles;

                if (m is MiniCopter || m is CH47Helicopter || players.Exists(player => IsFlying(player)))
                {
                    position.y = Mathf.Max(m.transform.position.y + 5f, GetSpawnHeight(position) + 1f);
                }
                else
                {
                    position.y = GetSpawnHeight(position) + 1f;
                }

                m.transform.rotation = Quaternion.Euler(e.x, e.y - 180f, e.z);

                Rigidbody rigidbody;
                if (m.TryGetComponent(out rigidbody))
                {
                    rigidbody.velocity *= -1f;
                }

                if (m.mountAnchor != null && m.mountAnchor.transform != null)
                {
                    m.transform.position = m.mountAnchor.transform.position = position;
                    m.mountAnchor.Rotate(m.transform.eulerAngles);
                }
                else m.transform.position = position;

                m.TransformChanged();

                return true;
            }

            // credits ZoneManager
            private void EjectSleepers()
            {
                if (!config.Settings.Management.EjectSleepers || Type == RaidableType.None)
                {
                    return;
                }

                var players = Pool.GetList<BasePlayer>();
                Vis.Entities(Location, Options.ProtectionRadius, players, Layers.Mask.Player_Server, QueryTriggerInteraction.Ignore);

                foreach (var player in players)
                {
                    if (player.IsSleeping() && !player.IsBuildingAuthed())
                    {
                        RemovePlayer(player);
                    }
                }

                Pool.FreeList(ref players);
            }

            private Vector3 FindPointOnNavmesh(Vector3 target, float radius)
            {
                int tries = 0;

                while (++tries < 100)
                {
                    if (NavMesh.SamplePosition(target, out _navHit, radius, 1))
                    {
                        if (NearFoundation(_navHit.position))
                        {
                            continue;
                        }

                        float y = TerrainMeta.HeightMap.GetHeight(_navHit.position);

                        if (TestInsideRock(_navHit.position) || _navHit.position.y < y)
                        {
                            continue;
                        }

                        if (TerrainMeta.WaterMap.GetHeight(_navHit.position) - y > 1f)
                        {
                            continue;
                        }

                        if ((_navHit.position - Location).magnitude > Mathf.Max(radius * 2f, Options.ProtectionRadius) - 2.5f)
                        {
                            continue;
                        }

                        return _navHit.position;
                    }
                }

                return Vector3.zero;
            }

            private bool TryParseHtmlString(string value, out Color color)
            {
                if (!value.StartsWith("#"))
                {
                    value = $"#{value}";
                }

                if (ColorUtility.TryParseHtmlString(value, out color))
                {
                    return true;
                }

                return false;
            }

            private Color GetMarkerColor1()
            {
                if (Type == RaidableType.None)
                {
                    return Color.clear;
                }

                Color color;

                if (TryParseHtmlString(config.Settings.Management.Colors1.Normal, out color))
                {
                    return color;
                }

                return Color.green;
            }

            private Color GetMarkerColor2()
            {
                if (Type == RaidableType.None)
                {
                    return NoneColor;
                }

                Color color;

                if (TryParseHtmlString(config.Settings.Management.Colors2.Normal, out color))
                {
                    return color;
                }

                return Color.green;
            }

            private Vector3 GetRandomPoint(float radius)
            {
                var vector = Location + UnityEngine.Random.onUnitSphere * radius;

                if (Options.Setup.ForcedHeight == -1)
                {
                    vector.y = TerrainMeta.HeightMap.GetHeight(vector);
                }

                return vector;
            }

            private List<int> GetStacks(int amount, int maxStack)
            {
                var list = new List<int>();

                while (amount > maxStack)
                {
                    amount -= maxStack;
                    list.Add(maxStack);
                }

                list.Add(amount);

                return list;
            }

            private bool HasOccupants(BaseMountable m)
            {
                if (m is BaseVehicle)
                {
                    var vehicle = m as BaseVehicle;

                    foreach (var mp in vehicle.mountPoints)
                    {
                        if (mp.mountable.IsValid() && mp.mountable.GetMounted().IsValid())
                        {
                            return true;
                        }
                    }
                }

                return m.GetMounted().IsValid();
            }

            private bool HasSpace(StorageContainer container, int amount)
            {
                return container.inventory.itemList.Count + amount < container.inventory.capacity;
            }

            private bool IsCookable(ItemDefinition def)
            {
                if (def.shortname.EndsWith(".cooked") || def.shortname.EndsWith(".burned") || def.shortname.EndsWith(".spoiled") || def.shortname == "lowgradefuel")
                {
                    return false;
                }

                return def.GetComponent<ItemModCookable>() || def.shortname == "wood";
            }

            private bool IsHealthy(Item item)
            {
                if (item.info.category == ItemCategory.Food || item.info.category == ItemCategory.Medical)
                {
                    if (item.info.shortname.Contains(".spoiled") || item.info.shortname.Contains(".raw") || item.info.shortname.Contains(".burned"))
                    {
                        return false;
                    }

                    return item.info.GetComponent<ItemModConsumable>() != null;
                }

                return false;
            }

            private bool IsKit(string kit)
            {
                var success = Backbone.Plugin.Kits?.Call("isKit", kit);

                if (success == null || !(success is bool))
                {
                    return false;
                }

                return (bool)success;
            }

            private bool IsMarkerAllowed()
            {
                if (Options.Silent)
                {
                    return false;
                }

                switch (Type)
                {
                    case RaidableType.Grid:
                    case RaidableType.Manual:
                    case RaidableType.None:
                        {
                            return config.Settings.Markers.Manual;
                        }
                    case RaidableType.Maintained:
                        {
                            return config.Settings.Markers.Maintained;
                        }
                    case RaidableType.Scheduled:
                        {
                            return config.Settings.Markers.Scheduled;
                        }
                }

                return true;
            }

            private bool IsRangedWeapon(Item item)
            {
                return item.info.category == ItemCategory.Weapon && item.info.GetComponent<ItemModProjectile>() != null;
            }

            private void CancelInvokes()
            {
                try { CancelInvoke(); } catch { }
            }

            private void DestroyNpcs()
            {
                foreach (var npc in new List<ScientistNPC>(npcs))
                {
                    if (npc?.IsDestroyed == false)
                    {
                        try { npc.Brain.Navigator.CanUseNavMesh = false; } catch { }
                        npc.Kill();
                    }
                }
            }

            private void ToggleLights()
            {
                if (Instance == null || IsUnloading || this == null)
                {
                    return;
                }

                if (lights.Count == 0 && ovens.Count == 0 && npcs.Count == 0)
                {
                    CancelInvoke(ToggleLights);
                    return;
                }

                if (config.Settings.Management.AlwaysLights || (!lightsOn && !IsDayTime()))
                {
                    lights.RemoveAll(e => e == null || e.IsDestroyed);
                    ovens.RemoveAll(e => e == null || e.IsDestroyed);

                    var list = new List<BaseEntity>();

                    list.AddRange(lights);
                    list.AddRange(ovens);

                    foreach (var e in list)
                    {
                        if (e == null || e.IsDestroyed) continue;
                        if (!e.IsOn())
                        {
                            if (Backbone.Plugin.Furnaces.Contains(e.prefabID) && ((e as BaseOven).inventory.IsEmpty()))
                            {
                                continue;
                            }

                            e.SetFlag(BaseEntity.Flags.On, true, false, true);
                        }
                    }

                    foreach (var npc in npcs)
                    {
                        if (npc == null || npc.IsDestroyed) continue;
                        ToggleNpcMinerHat(npc, true);
                    }

                    lightsOn = true;
                }
                else if (lightsOn && IsDayTime())
                {
                    lights.RemoveAll(e => e == null || e.IsDestroyed);
                    ovens.RemoveAll(e => e == null || e.IsDestroyed);

                    var list = new List<BaseEntity>();

                    list.AddRange(lights);
                    list.AddRange(ovens);

                    foreach (var e in list)
                    {
                        if (e.prefabID == 2931042549 || e.prefabID == 4160694184 || e.prefabID == 1374462671 || e.prefabID == 2162666837 || Backbone.Plugin.BBQs.Contains(e.prefabID))
                        {
                            continue;
                        }

                        if (e.IsOn())
                        {
                            e.SetFlag(BaseEntity.Flags.On, false);
                        }
                    }

                    foreach (var npc in npcs)
                    {
                        ToggleNpcMinerHat(npc, false);
                    }

                    lightsOn = false;
                }
            }

            private void MakeAnnouncements()
            {
                if (Type == RaidableType.None)
                {
                    itemAmountSpawned = 0;

                    foreach (var x in _allcontainers)
                    {
                        if (x == null || x.IsDestroyed)
                        {
                            continue;
                        }

                        itemAmountSpawned += x.inventory.itemList.Count;
                    }
                }

                var posStr = FormatGridReference(Location);

                Puts("{0} @ {1} : {2} items", BaseName, posStr, itemAmountSpawned);

                if (Options.Silent)
                {
                    return;
                }

                foreach (var target in BasePlayer.activePlayerList)
                {
                    float distance = Mathf.Floor((target.transform.position - Location).magnitude);
                    string flag = Backbone.GetMessageEx(AllowPVP ? "PVPFlag" : "PVEFlag", target.UserIDString).Replace("[", string.Empty).Replace("] ", string.Empty);
                    string api = Backbone.GetMessageEx("RaidOpenMessage", target.UserIDString, DifficultyMode, posStr, distance, flag);
                    if (Type == RaidableType.None) api = api.Replace(DifficultyMode, NoMode);
                    string message = owner.IsValid() ? string.Format("{0}[Owner: {1}]", api, owner.displayName) : api;

                    if (config.EventMessages.Opened)
                    {
                        SendNotification(target, message);
                    }

                    if (distance <= config.GUIAnnouncement.Distance)
                    {
                        ShowAnnouncement(target, message);
                    }
                }
            }

            private bool MoveToBBQ(Item item)
            {
                if (!config.Settings.Management.Food || ovens.Count == 0 || item.info.category != ItemCategory.Food || !IsCookable(item.info))
                {
                    return false;
                }

                if (ovens.Count > 1)
                {
                    Shuffle(ovens);
                }

                foreach (var oven in ovens)
                {
                    if (oven == null || oven.IsDestroyed)
                    {
                        continue;
                    }

                    if (Backbone.Plugin.BBQs.Contains(oven.prefabID) && item.MoveToContainer(oven.inventory, -1, true))
                    {
                        return true;
                    }
                }

                return false;
            }

            private bool MoveToContainer(ItemContainer container, Item item, params int[] positions)
            {
                foreach (int position in positions)
                {
                    if (item.MoveToContainer(container, position, false))
                    {
                        return true;
                    }
                }

                return false;
            }

            private bool MoveToCupboard(Item item)
            {
                if (!config.Settings.Management.Cupboard || !privSpawned || item.info.category != ItemCategory.Resources || config.Treasure.ExcludeFromCupboard.Contains(item.info.shortname))
                {
                    return false;
                }

                if (config.Settings.Management.Cook && item.info.shortname.EndsWith(".ore") && MoveToOven(item))
                {
                    return true;
                }

                if (priv.IsValid() && !priv.IsDestroyed)
                {
                    return item.MoveToContainer(priv.inventory, -1, true);
                }

                return false;
            }

            private bool MoveToFridge(Item item)
            {
                if (!config.Settings.Management.Food || _allcontainers.Count == 0 || item.info.category != ItemCategory.Food)
                {
                    return false;
                }

                if (_allcontainers.Count > 1)
                {
                    Shuffle(_allcontainers);
                }

                foreach (var x in _allcontainers)
                {
                    if (x == null || x.IsDestroyed)
                    {
                        continue;
                    }

                    if (x.prefabID == 1844023509 && item.MoveToContainer(x.inventory, -1, true))
                    {
                        return true;
                    }
                }

                return false;
            }

            private bool MoveToLocker(Item item)
            {
                if (!config.Settings.Management.Lockers || lockers.Count == 0)
                {
                    return false;
                }

                if (lockers.Count > 0)
                {
                    Shuffle(lockers);
                }

                foreach (var locker in lockers)
                {
                    if (Backbone.Plugin.Helms.Contains(item.info.shortname))
                    {
                        if (MoveToContainer(locker.inventory, item, 0, 13, 26))
                        {
                            return true;
                        }
                    }
                    else if (Backbone.Plugin.Boots.Contains(item.info.shortname))
                    {
                        if (MoveToContainer(locker.inventory, item, 1, 14, 27))
                        {
                            return true;
                        }
                    }
                    else if (Backbone.Plugin.Gloves.Contains(item.info.shortname))
                    {
                        if (MoveToContainer(locker.inventory, item, 2, 15, 28))
                        {
                            return true;
                        }
                    }
                    else if (Backbone.Plugin.Vests.Contains(item.info.shortname))
                    {
                        if (MoveToContainer(locker.inventory, item, 3, 16, 29))
                        {
                            return true;
                        }
                    }
                    else if (Backbone.Plugin.Legs.Contains(item.info.shortname))
                    {
                        if (MoveToContainer(locker.inventory, item, 4, 17, 30))
                        {
                            return true;
                        }
                    }
                    else if (Backbone.Plugin.Shirts.Contains(item.info.shortname))
                    {
                        if (MoveToContainer(locker.inventory, item, 5, 18, 31))
                        {
                            return true;
                        }
                    }
                    else if (Backbone.Plugin.Other.Contains(item.info.shortname))
                    {
                        if (MoveToContainer(locker.inventory, item, 6, 19, 32))
                        {
                            return true;
                        }
                    }
                    else if (IsRangedWeapon(item))
                    {
                        if (MoveToContainer(locker.inventory, item, 7, 8, 20, 21, 33, 34))
                        {
                            return true;
                        }
                    }
                    else if (item.info.category == ItemCategory.Ammunition)
                    {
                        if (MoveToContainer(locker.inventory, item, 9, 10, 22, 23, 35, 36))
                        {
                            return true;
                        }
                    }
                    else if (IsHealthy(item))
                    {
                        if (MoveToContainer(locker.inventory, item, 11, 12, 24, 25, 37, 38))
                        {
                            return true;
                        }
                    }
                }

                return false;
            }

            private bool MoveToOven(Item item)
            {
                if (!config.Settings.Management.Cook || ovens.Count == 0 || !IsCookable(item.info))
                {
                    return false;
                }

                if (ovens.Count > 1)
                {
                    Shuffle(ovens);
                }

                foreach (var oven in ovens)
                {
                    if (oven == null || oven.IsDestroyed || Backbone.Plugin.BBQs.Contains(oven.prefabID))
                    {
                        continue;
                    }

                    if (item.info.shortname.EndsWith(".ore") && !Backbone.Plugin.Furnaces.Contains(oven.prefabID))
                    {
                        continue;
                    }

                    if (item.info.shortname == "lowgradefuel" && !Backbone.Plugin.Lanterns.Contains(oven.prefabID))
                    {
                        continue;
                    }

                    if (item.info.shortname == "crude.oil" && !Backbone.Plugin.Refineries.Contains(oven.prefabID))
                    {
                        continue;
                    }

                    if (item.MoveToContainer(oven.inventory, -1, true))
                    {
                        if (!oven.IsOn() && oven.FindBurnable() != null)
                        {
                            oven.SetFlag(BaseEntity.Flags.On, true, false, true);
                        }

                        if (oven.IsOn() && !item.HasFlag(global::Item.Flag.OnFire))
                        {
                            item.SetFlag(global::Item.Flag.OnFire, true);
                            item.MarkDirty();
                        }

                        return true;
                    }
                }

                return false;
            }

            private bool NearFoundation(Vector3 position)
            {
                foreach (var a in foundations)
                {
                    if (InRange(a, position, 5f))
                    {
                        return true;
                    }
                }

                return false;
            }

            private bool RemoveMountable(BaseMountable m, List<BasePlayer> players)
            {
                if (players.Count == 0)
                {
                    return EjectMountable(m, 10f, players);
                }

                BaseVehicle vehicle = m.HasParent() ? m.VehicleParent() : m as BaseVehicle;

                if (vehicle != null && !vehicle.IsDestroyed && vehicle.transform != null)
                {
                    Vector3 forward = (vehicle.transform.position.XZ3D() - Location.XZ3D()).normalized;
                    Vector3 direction = Quaternion.LookRotation(forward) * Quaternion.Euler(180f, 0f, 0f) * -Vector3.forward;

                    vehicle.transform.rotation = Quaternion.LookRotation(direction);

                    if (vehicle.rigidBody != null)
                    {
                        vehicle.rigidBody.velocity *= -1f;
                    }

                    return true;
                }

                return EjectMountable(m, 2f, players);
            }

            private void DestroySpheres()
            {
                if (spheres.Count > 0)
                {
                    foreach (var sphere in spheres)
                    {
                        if (sphere != null && !sphere.IsDestroyed)
                        {
                            sphere.Kill();
                        }
                    }

                    spheres.Clear();
                }
            }

            private void SetupBoxSkin(StorageContainer container)
            {
                if (!IsBox(container, false))
                {
                    return;
                }

                ItemDefinition def;
                if (!_shortnames.TryGetValue(container.gameObject.name, out def))
                {
                    return;
                }

                var si = GetItemSkins(def);

                if (si.allSkins.Count == 0)
                {
                    return;
                }

                if (!skinIds.ContainsKey(container.prefabID))
                {
                    if (config.Skins.Boxes.PresetSkin == 0uL || !si.allSkins.Contains(config.Skins.Boxes.PresetSkin))
                    {
                        var random = new List<ulong>();

                        if (config.Skins.Boxes.RandomWorkshopSkins)
                        {
                            random.Add(si.workshopSkins.GetRandom());
                        }

                        if (config.Skins.Boxes.RandomSkins)
                        {
                            random.Add(si.skins.GetRandom());
                        }

                        if (random.Count == 0)
                        {
                            skinIds[container.prefabID] = container.skinID;
                        }
                        else skinIds[container.prefabID] = random.GetRandom();
                    }
                    else skinIds[container.prefabID] = config.Skins.Boxes.PresetSkin;
                }

                if (config.Skins.Boxes.PresetSkin != 0uL || Options.SetSkins)
                {
                    container.skinID = skinIds[container.prefabID];
                }
                else if (config.Skins.Boxes.RandomWorkshopSkins)
                {
                    container.skinID = si.workshopSkins.GetRandom();
                }
                else if (config.Skins.Boxes.RandomSkins)
                {
                    container.skinID = si.skins.GetRandom();
                }
            }

            private void SetupBuildingPriviledge(BuildingPrivlidge priv)
            {
                if (Type != RaidableType.None)
                {
                    priv.authorizedPlayers.Clear();
                    priv.SendNetworkUpdate(BasePlayer.NetworkQueue.Update);
                }

                if (Options.LockPrivilege)
                {
                    CreateLock(priv);
                }

                this.priv = priv;
                privSpawned = true;
            }

            private void SetupDecayEntity(DecayEntity decayEntity)
            {
                if (BuildingID == 0)
                {
                    BuildingID = BuildingManager.server.NewBuildingID();
                }

                decayEntity.AttachToBuilding(BuildingID);
                decayEntity.decay = null;
                decayEntity.upkeepTimer = float.MinValue;
            }

            private void SetupDoor(Door door)
            {
                if (Options.DoorLock)
                {
                    CreateLock(door);
                }

                if (Options.CloseOpenDoors)
                {
                    door.SetOpen(false, true);
                }
            }

            private void SetupDoorControllers()
            {
                doorControllers.RemoveAll(x => x == null || x.IsDestroyed);

                foreach (var cdm in doorControllers)
                {
                    SetupIO(cdm);

                    if (cdm.IsPaired())
                    {
                        doors.Remove(cdm.targetDoor);
                        continue;
                    }

                    var door = cdm.FindDoor(true);

                    if (door.IsValid())
                    {
                        cdm.SetTargetDoor(door);
                        doors.Remove(door);

                        if (Options.DoorLock)
                        {
                            CreateLock(door);
                        }
                    }
                }

                doorControllers.Clear();
            }

            private void SetupDoors()
            {
                doors.RemoveAll(x => x == null || x.IsDestroyed);

                foreach (var door in doors)
                {
                    SetupDoor(door);
                }
            }

            private void SetupMountable(BaseMountable mountable)
            {
                if (!config.Settings.Management.DespawnMounts)
                {
                    Backbone.Plugin.MountEntities[mountable] = new MountInfo
                    {
                        position = Location,
                        radius = Options.ProtectionRadius,
                        mountable = mountable
                    };
                }

                if (mountable is BaseVehicle)
                {
                    return;
                }

                mountables.Add(mountable);
            }

            private void SetupNpcKits()
            {
                var murdererKits = new List<string>();
                var scientistKits = new List<string>();

                foreach (string kit in Options.NPC.MurdererKits)
                {
                    if (IsKit(kit))
                    {
                        murdererKits.Add(kit);
                    }
                }

                foreach (string kit in Options.NPC.ScientistKits)
                {
                    if (IsKit(kit))
                    {
                        scientistKits.Add(kit);
                    }
                }

                npcKits = new Dictionary<string, List<string>>
                {
                    { "murderer", murdererKits },
                    { "scientist", scientistKits }
                };
            }

            private void SetupSamSite(SamSite ss)
            {
                if (config.Weapons.SamSiteRepair > 0f)
                {
                    ss.staticRespawn = true;
                    ss.InvokeRepeating(ss.SelfHeal, config.Weapons.SamSiteRepair * 60f, config.Weapons.SamSiteRepair * 60f);
                }

                SetupIO(ss as IOEntity);

                if (config.Weapons.SamSiteRange > 0f)
                {
                    ss.missileScanRadius = ss.vehicleScanRadius = config.Weapons.SamSiteRange;
                }

                if (config.Weapons.Ammo.SamSite > 0)
                {
                    FillAmmoSamSite(ss);
                }

                if (config.Weapons.InfiniteAmmo.SamSite)
                {
                    ss.inventory.onPreItemRemove += new Action<Item>(OnWeaponItemPreRemove);
                }
            }

            private void SetupSkin(BaseEntity entity)
            {
                if (IsBox(entity, false) || config.Skins.IgnoreSkinned && entity.skinID != 0uL)
                {
                    return;
                }

                if (!config.Skins.Deployables.Everything && !config.Skins.Deployables.Names.Exists(entity.name.Contains))
                {
                    return;
                }

                ItemDefinition def;
                if (!_shortnames.TryGetValue(entity.gameObject.name, out def))
                {
                    return;
                }

                var si = GetItemSkins(def);
                var random = new List<ulong>();

                if (config.Skins.Deployables.RandomWorkshopSkins && si.workshopSkins.Count > 0)
                {
                    random.Add(si.workshopSkins.GetRandom());
                }

                if (config.Skins.Deployables.RandomSkins && si.skins.Count > 0)
                {
                    random.Add(si.skins.GetRandom());
                }

                if (random.Count > 0)
                {
                    entity.skinID = random.GetRandom();
                    entity.SendNetworkUpdate();
                }
            }

            private void SetupSleepingBag(SleepingBag bag)
            {
                if (Type == RaidableType.None)
                {
                    return;
                }

                bag.deployerUserID = 0uL;
            }

            private SpawnResult SpawnItem(TreasureItem ti, List<StorageContainer> containers)
            {
                Item item = CreateItem(ti);

                if (item == null)
                {
                    return SpawnResult.Skipped;
                }

                foreach (var container in containers)
                {
                    if (MoveToCupboard(item) || MoveToBBQ(item) || MoveToOven(item) || MoveToFridge(item) || MoveToLocker(item))
                    {
                        itemAmountSpawned++;
                        return SpawnResult.Transfer;
                    }
                    else if (item.MoveToContainer(container.inventory, -1, false))
                    {
                        itemAmountSpawned++;
                        return SpawnResult.Success;
                    }
                }

                item.Remove();
                return SpawnResult.Failure;
            }


            private Item CreateItem(TreasureItem ti)
            {
                if (ti.amount <= 0)
                {
                    return null;
                }

                if (ti.definition == null)
                {
                    Puts("Invalid shortname in config: {0}", ti.shortname);
                    return null;
                }

                var def = ti.definition;
                ulong skin = GetItemSkin(def, ti.skin, true);

                Item item;
                if (ti.isBlueprint)
                {
                    item = ItemManager.Create(Workbench.GetBlueprintTemplate());
                    item.blueprintTarget = def.itemid;
                    item.amount = ti.amount;
                }
                else item = ItemManager.Create(def, ti.amount, skin);

                var e = item.GetHeldEntity();

                if (e.IsValid())
                {
                    e.skinID = skin;
                    e.SendNetworkUpdate();
                }

                return item;
            }

            private void SpawnLoot(List<StorageContainer> containers, int treasureAmount)
            {
                StorageContainer container = null;

                foreach (var x in containers)
                {
                    if (HasSpace(x, treasureAmount))
                    {
                        container = x;
                        break;
                    }
                }

                if (container == null)
                {
                    container = containers.GetRandom();
                    container.inventory.Clear();
                    ItemManager.DoRemoves();
                }

                SpawnLoot(container, treasureAmount, Loot);
            }

            private void SpawnLoot(StorageContainer container, int total, List<TreasureItem> source)
            {
                if (total > container.inventory.capacity)
                {
                    total = container.inventory.capacity;
                }

                for (int j = 0; j < total; j++)
                {
                    if (Loot.Count == 0 || source.Count == 0)
                    {
                        break;
                    }

                    var lootItem = source.GetRandom();

                    Loot.Remove(lootItem);

                    SpawnItem(lootItem, new List<StorageContainer> { container });
                }
            }

            private ScientistNPC SpawnNpc(bool isMurderer)
            {
                var positions = RandomWanderPositions(ProtectionRadius * 0.9f);

                if (positions.Count == 0)
                {
                    return null;
                }

                var position = RandomPosition(Options.ArenaWalls.Radius * 0.9f);

                if (position == Vector3.zero)
                {
                    return null;
                }

                HumanoidBrain brain;
                ScientistNPC npc = InstantiateEntity(position, out brain);

                if (npc == null)
                {
                    return null;
                }

                npc.userID = (ulong)UnityEngine.Random.Range(0, 10000000);
                npc.UserIDString = npc.userID.ToString();
                npc.displayName = GetUsername(npc);

                var loadout = GetLoadout(npc, brain, isMurderer);

                if (loadout.belt.Count > 0 || loadout.main.Count > 0 || loadout.wear.Count > 0)
                {
                    npc.loadouts = new PlayerInventoryProperties[1];
                    npc.loadouts[0] = loadout;
                }
                else npc.loadouts = new PlayerInventoryProperties[0];

                BasePlayer.bots.Add(npc);

                Instance.Npcs[npc.userID] = this;

                npc.enableSaving = false;
                npc.Spawn();
                npc.CancelInvoke(npc.EquipTest);

                npcs.Add(npc);

                SetupNpc(npc, brain, isMurderer, positions);

                return npc;
            }

            public class Loadout
            {
                public List<PlayerInventoryProperties.ItemAmountSkinned> belt = new List<PlayerInventoryProperties.ItemAmountSkinned>();
                public List<PlayerInventoryProperties.ItemAmountSkinned> main = new List<PlayerInventoryProperties.ItemAmountSkinned>();
                public List<PlayerInventoryProperties.ItemAmountSkinned> wear = new List<PlayerInventoryProperties.ItemAmountSkinned>();
            }

            private PlayerInventoryProperties GetLoadout(ScientistNPC npc, HumanoidBrain brain, bool isMurderer)
            {
                var loadout = CreateLoadout(npc, brain, isMurderer);
                var pip = ScriptableObject.CreateInstance<PlayerInventoryProperties>();

                pip.belt = loadout.belt;
                pip.main = loadout.main;
                pip.wear = loadout.wear;

                return pip;
            }

            private Loadout CreateLoadout(ScientistNPC npc, HumanoidBrain brain, bool isMurderer)
            {
                var loadout = new Loadout();

                switch (isMurderer)
                {
                    case true:
                        AddItemAmountSkinned(loadout.wear, Options.NPC.MurdererItems.Boots);
                        AddItemAmountSkinned(loadout.wear, Options.NPC.MurdererItems.Gloves);
                        AddItemAmountSkinned(loadout.wear, Options.NPC.MurdererItems.Helm);
                        AddItemAmountSkinned(loadout.wear, Options.NPC.MurdererItems.Pants);
                        AddItemAmountSkinned(loadout.wear, Options.NPC.MurdererItems.Shirt);
                        AddItemAmountSkinned(loadout.wear, Options.NPC.MurdererItems.Torso);
                        if (!Options.NPC.MurdererItems.Torso.Exists(v => v.Contains("suit")))
                        {
                            AddItemAmountSkinned(loadout.wear, Options.NPC.MurdererItems.Kilts);
                        }
                        AddItemAmountSkinned(loadout.belt, Options.NPC.MurdererItems.Weapon);
                        break;
                    case false:
                        AddItemAmountSkinned(loadout.wear, Options.NPC.ScientistItems.Boots);
                        AddItemAmountSkinned(loadout.wear, Options.NPC.ScientistItems.Gloves);
                        AddItemAmountSkinned(loadout.wear, Options.NPC.ScientistItems.Helm);
                        AddItemAmountSkinned(loadout.wear, Options.NPC.ScientistItems.Pants);
                        AddItemAmountSkinned(loadout.wear, Options.NPC.ScientistItems.Shirt);
                        AddItemAmountSkinned(loadout.wear, Options.NPC.ScientistItems.Torso);
                        if (!Options.NPC.ScientistItems.Torso.Exists(v => v.Contains("suit")))
                        {
                            AddItemAmountSkinned(loadout.wear, Options.NPC.ScientistItems.Kilts);
                        }
                        AddItemAmountSkinned(loadout.belt, Options.NPC.ScientistItems.Weapon);
                        break;
                }

                return loadout;
            }

            private List<string> NotSupportedWeapons = new List<string>
            {
                "explosive.satchel",
                "explosive.timed",
                "grenade.beancan",
                "grenade.f1",
                "grenade.smoke",
                "multiplegrenadelauncher",
                "rocket.launcher",
                "snowballgun",
                "speargun",
                "watergun",
                "waterpistol"
            };

            private void AddItemAmountSkinned(List<PlayerInventoryProperties.ItemAmountSkinned> source, List<string> shortnames)
            {
                if (shortnames.Count == 0)
                {
                    return;
                }

                string shortname = shortnames.GetRandom();

                if (shortname == "bow.hunting")
                {
                    shortname = "bow.compound";
                }
                else if (NotSupportedWeapons.Contains(shortname))
                {
                    Puts("Unsupported weapon for npc in {0}/{1} profile: {2}", ProfileName, BaseName, shortname);
                    shortname = "bow.compound";
                }

                ItemDefinition def = ItemManager.FindItemDefinition(shortname);

                if (def == null)
                {
                    Puts("Invalid item shortname for npc in {0}/{1} profile: {2}", ProfileName, BaseName, shortname);
                    return;
                }

                ulong skin = 0uL;
                if (config.Skins.Npcs)
                {
                    skin = GetItemSkin(def, 0uL, config.Skins.UniqueNpcs);
                }

                source.Add(new PlayerInventoryProperties.ItemAmountSkinned
                {
                    amount = 1,
                    itemDef = def,
                    skinOverride = skin,
                    startAmount = 1
                });
            }

            private void SetupNpc(ScientistNPC npc, HumanoidBrain brain, bool isMurderer, List<Vector3> positions)
            {
                if (Options.NPC.DespawnInventory)
                {
                    npc.LootSpawnSlots = new LootContainer.LootSpawnSlot[0];
                }

                npc.CancelInvoke(npc.PlayRadioChatter);
                npc.DeathEffects = new GameObjectRef[0];
                npc.RadioChatterEffects = new GameObjectRef[0];
                npc.radioChatterType = ScientistNPC.RadioChatterType.NONE;
                npc.startHealth = isMurderer ? Options.NPC.MurdererHealth : Options.NPC.ScientistHealth;
                npc.InitializeHealth(npc.startHealth, npc.startHealth);
                npc.Invoke(() => UpdateItems(npc, brain, isMurderer), 0.2f);
                npc.Invoke(() => brain.SetupMovement(positions), 0.3f);
            }

            private void UpdateItems(ScientistNPC npc, HumanoidBrain brain, bool isMurderer)
            {
                List<string> kits;
                if (npcKits.TryGetValue(isMurderer ? "murderer" : "scientist", out kits) && kits.Count > 0)
                {
                    npc.inventory.Strip();

                    Instance.Kits?.Call("GiveKit", npc, kits.GetRandom());
                }

                EquipWeapon(npc, brain, false);

                if (!ToggleNpcMinerHat(npc, !IsDayTime()))
                {
                    npc.inventory.ServerUpdate(0f);
                }
            }

            private void UpdateWeapon(ScientistNPC npc, HumanoidBrain brain, AttackEntity attackEntity, Item item)
            {
                npc.UpdateActiveItem(item.uid);

                if (attackEntity is Chainsaw)
                {
                    (attackEntity as Chainsaw).ServerNPCStart();
                }

                npc.damageScale = 1f;

                attackEntity.TopUpAmmo();
                attackEntity.SetHeld(true);
                brain.Init();
            }

            public void EquipWeapon(ScientistNPC npc, HumanoidBrain brain, bool allowSyringe)
            {
                foreach (Item item in npc.inventory.AllItems())
                {
                    var e = item.GetHeldEntity() as HeldEntity;

                    if (e.IsValid())
                    {
                        if (item.skin != 0)
                        {
                            e.skinID = item.skin;
                            e.SendNetworkUpdate();
                        }

                        if (brain.AttackEntity != null || !allowSyringe && item.info.shortname == "syringe.medical")
                        {
                            continue;
                        }

                        var weapon = e as BaseProjectile;

                        if (weapon.IsValid())
                        {
                            weapon.primaryMagazine.contents = weapon.primaryMagazine.capacity;
                            weapon.SendNetworkUpdateImmediate();
                        }

                        if (e is AttackEntity && item.GetRootContainer() == npc.inventory.containerBelt)
                        {
                            var attackEntity = e as AttackEntity;

                            if (attackEntity.hostile || item.info.shortname == "syringe.medical")
                            {
                                UpdateWeapon(npc, brain, attackEntity, item);
                            }
                        }
                    }

                    item.MarkDirty();
                }
            }

            private string GetUsername(ScientistNPC npc)
            {
                return Options.NPC.RandomNames.Count > 0 ? Options.NPC.RandomNames.GetRandom() : RandomUsernames.Get(npc.userID);
            }

            private void Subscribe()
            {
                if (IsUnloading)
                {
                    return;
                }

                if (Backbone.Plugin.BaseRepair != null)
                {
                    Subscribe(nameof(OnBaseRepair));
                }

                if (Options.EnforceDurability)
                {
                    Subscribe(nameof(OnLoseCondition));
                }

                if (config.Weapons.SamSiteRange > 0f)
                {
                    Subscribe(nameof(CanSamSiteShoot));
                }

                Subscribe(nameof(CanPickupEntity));

                if (Options.NPC.SpawnAmount < 1)
                {
                    Options.NPC.Enabled = false;
                }

                if (Options.NPC.Enabled)
                {
                    Options.NPC.SpawnAmount = Mathf.Clamp(Options.NPC.SpawnAmount, 0, 25);
                    Options.NPC.SpawnMinAmount = Mathf.Clamp(Options.NPC.SpawnMinAmount, 0, Options.NPC.SpawnAmount);
                    Options.NPC.ScientistHealth = Mathf.Clamp(Options.NPC.ScientistHealth, 100, 5000);
                    Options.NPC.MurdererHealth = Mathf.Clamp(Options.NPC.MurdererHealth, 100, 5000);
                    npcMaxAmount = Options.NPC.SpawnRandomAmount && Options.NPC.SpawnAmount > 1 ? UnityEngine.Random.Range(Options.NPC.SpawnMinAmount, Options.NPC.SpawnAmount) : Options.NPC.SpawnAmount;

                    if (npcMaxAmount > 0)
                    {
                        if (config.Settings.Management.BlockNpcKits)
                        {
                            Subscribe(nameof(OnNpcKits));
                        }

                        Subscribe(nameof(OnEntityEnter));
                        Subscribe(nameof(OnNpcDuck));
                        Subscribe(nameof(OnNpcDestinationSet));
                        Subscribe(nameof(OnNpcKits));
                        SetupNpcKits();
                        Invoke(SpawnNpcs, 1f);
                    }
                }
                
                if (privSpawned)
                {
                    Subscribe(nameof(OnCupboardProtectionCalculated));
                }

                Subscribe(nameof(OnPlayerDropActiveItem));
                Subscribe(nameof(OnPlayerDeath));
                Subscribe(nameof(OnEntityDeath));
                Subscribe(nameof(OnEntityKill));
                Subscribe(nameof(CanBGrade));

                if (config.Settings.Management.PreventFallDamage)
                {
                    Subscribe(nameof(OnPlayerLand));
                }

                if (!config.Settings.Management.AllowTeleport)
                {
                    Subscribe(nameof(CanTeleport));
                    Subscribe(nameof(canTeleport));
                }

                if (config.Settings.Management.BlockRestorePVP && AllowPVP || config.Settings.Management.BlockRestorePVE && !AllowPVP)
                {
                    Subscribe(nameof(OnRestoreUponDeath));
                }

                if (Options.DropTimeAfterLooting > 0 || config.UI.Containers)
                {
                    Subscribe(nameof(OnLootEntityEnd));
                }

                if (!config.Settings.Management.BackpacksOpenPVP || !config.Settings.Management.BackpacksOpenPVE)
                {
                    Subscribe(nameof(CanOpenBackpack));
                }

                if (config.Settings.Management.PreventFireFromSpreading)
                {
                    Subscribe(nameof(OnFireBallSpread));
                }

                if (Backbone.Plugin.IsPVE())
                {
                    Subscribe(nameof(CanEntityBeTargeted));
                    Subscribe(nameof(CanEntityTrapTrigger));
                }
                else
                {
                    Subscribe(nameof(OnTrapTrigger));
                }

                if (Options.BuildingRestrictions.Any() || !config.Settings.Management.AllowUpgrade)
                {
                    Subscribe(nameof(OnStructureUpgrade));
                }

                Subscribe(nameof(CanBePenalized));                
                Subscribe(nameof(OnEntityGroundMissing));
                Subscribe(nameof(OnLootEntity));
                Subscribe(nameof(OnEntityBuilt));
                Subscribe(nameof(OnCupboardAuthorize));
            }

            private void Subscribe(string hook) => Backbone.Plugin.Subscribe(hook);

            private void TakeLootFrom(List<TreasureItem> source, List<TreasureItem> to)
            {
                if (source.Count == 0)
                {
                    return;
                }

                var from = new List<TreasureItem>();

                foreach (var ti in source)
                {
                    if (ti == null || ti.amount <= 0 || ti.amountMin < 0)
                    {
                        continue;
                    }

                    from.Add(ti.Clone());
                }

                if (from.Count == 0)
                {
                    return;
                }

                Shuffle(from);
                to.AddRange(from);

                if (Options.Multiplier == 1f)
                {
                    return;
                }

                var m = Mathf.Clamp(Options.Multiplier, 0f, 999f);

                foreach (var ti in to)
                {
                    if (ti.amount == 1)
                    {
                        continue;
                    }

                    ti.amount = Mathf.CeilToInt(ti.amount * m);

                    if (ti.amountMin == 0)
                    {
                        continue;
                    }

                    ti.amountMin = Mathf.CeilToInt(ti.amountMin * m);
                }
            }

            private bool TestInsideRock(Vector3 position)
            {
                Physics.queriesHitBackfaces = true;

                bool flag = IsInside(position);

                Physics.queriesHitBackfaces = false;

                return flag;
            }

            private bool IsInside(Vector3 point) => Physics.Raycast(point, Vector3.up, out _hit, 50f, Layers.Mask.World | Layers.Mask.Terrain | Layers.Mask.Default, QueryTriggerInteraction.Ignore) && IsRock(_hit.collider.name) && _hit.collider.bounds.Contains(point);

            private bool IsRock(string name) => _prefabs.Any(value => name.Contains(value, CompareOptions.OrdinalIgnoreCase));

            private List<string> _prefabs = new List<string> { "rock_", "formation_", "cliff" };

            private bool ToggleNpcMinerHat(ScientistNPC npc, bool state)
            {
                if (npc == null || npc.inventory == null || npc.IsDead())
                {
                    return false;
                }

                var slot = npc.inventory.FindItemID("hat.miner");

                if (slot == null)
                {
                    return false;
                }

                if (state && slot.contents != null)
                {
                    slot.contents.AddItem(ItemManager.FindItemDefinition("lowgradefuel"), 50);
                }

                slot.SwitchOnOff(state);
                npc.inventory.ServerUpdate(0f);
                return true;
            }

            private bool TryRemoveMountable(BaseMountable m, List<BasePlayer> players)
            {
                if (m == null || Type == RaidableType.None || m.GetParentEntity() is BaseTrain || IsControlledMount(m))
                {
                    return false;
                }

                if (players.Count == 0 && !m.OwnerID.IsSteamId())
                {
                    return false;
                }

                if (CanEject(players))
                {
                    return RemoveMountable(m, players);
                }

                if (config.Settings.Management.Mounts.Boats && m is BaseBoat)
                {
                    return RemoveMountable(m, players);
                }
                else if (config.Settings.Management.Mounts.BasicCars && m is BasicCar)
                {
                    return RemoveMountable(m, players);
                }
                else if (config.Settings.Management.Mounts.ModularCars && m is ModularCar)
                {
                    return RemoveMountable(m, players);
                }
                else if (config.Settings.Management.Mounts.CH47 && m is CH47Helicopter)
                {
                    return RemoveMountable(m, players);
                }
                else if (config.Settings.Management.Mounts.Horses && m is RidableHorse)
                {
                    return RemoveMountable(m, players);
                }
                else if (config.Settings.Management.Mounts.Scrap && m is ScrapTransportHelicopter)
                {
                    return RemoveMountable(m, players);
                }
                else if (config.Settings.Management.Mounts.MiniCopters && m is MiniCopter && !(m is ScrapTransportHelicopter))
                {
                    return RemoveMountable(m, players);
                }
                else if (config.Settings.Management.Mounts.Pianos && m is StaticInstrument)
                {
                    return RemoveMountable(m, players);
                }

                return false;
            }

            private void UpdateStatus(BasePlayer player)
            {
                if (IsOpened)
                {
                    lastActive[player.UserIDString] = Time.realtimeSinceStartup;
                }

                if (ownerId == player.userID && Time.time - _lastInvokeUpdate > 1f)
                {
                    _lastInvokeUpdate = Time.time;
                    TryInvokeResetOwner();
                }
            }
        }

        public class UndoSettings
        {
            public List<BaseEntity> Entities;
            public object[] hookObjects;
            public bool Structures;
            public bool Deployables;
            public bool Mounts;
            public bool Teleport;
            public int Limit;
        }

        public static class GarbageController
        {
            internal static Coroutine despawnCoroutine { get; set; }
            internal static Dictionary<BaseEntity, MountInfo> Mounts { get; set; } = new Dictionary<BaseEntity, MountInfo>();
            internal static Dictionary<BaseEntity, RaidableBase> RaidEntities { get; set; } = new Dictionary<BaseEntity, RaidableBase>();

            public static void UndoLoop(UndoSettings undo, int count = 0)
            {
                undo.Entities.RemoveAll(e => e == null || e.IsDestroyed);

                undo.Entities.Sort((x, y) => (x is BuildingBlock).CompareTo(y is BuildingBlock));

                undo.Entities.Take(undo.Limit).ToList().ForEach(entity =>
                {
                    undo.Entities.Remove(entity);

                    if (entity == null || entity.IsDestroyed || KeepEntity(entity, undo))
                    {
                        return;
                    }

                    if (entity is IOEntity)
                    {
                        var io = entity as IOEntity;

                        io.ClearConnections();

                        if (entity is SamSite)
                        {
                            var ss = entity as SamSite;

                            ss.staticRespawn = false;
                        }
                    }

                    if (undo.Teleport)
                    {
                        entity.transform.position = new Vector3(0f, -500f, 0f);
                        Interface.Oxide.NextTick(() => SafelyKill(entity));
                    }
                    else entity.Kill(BaseNetworkable.DestroyMode.None);
                });

                if (count != 0 && undo.Entities.Count != 0 && undo.Entities.Count == count)
                {
                    goto done;
                }

                if (undo.Entities.Count > 0)
                {
                    Interface.Oxide.NextTick(() => UndoLoop(undo, undo.Entities.Count));
                    return;
                }

done:
                RemoveAll(GarbageController.RaidEntities, ((e, raid) => e == null || e.IsDestroyed));

                if (GarbageController.RaidEntities.Count == 0)
                {
                    GarbageController.Mounts.Clear();
                }

                if (undo.hookObjects != null)
                {
                    Interface.CallHook("OnRaidableBaseDespawned", undo.hookObjects);
                }
            }

            public static void SafelyKill(BaseNetworkable entity)
            {
                if (entity == null || entity.IsDestroyed)
                {
                    return;
                }

                entity.Kill(BaseNetworkable.DestroyMode.None);
            }

            public static int RemoveAll<TKey, TValue>(IDictionary<TKey, TValue> source, Func<TKey, TValue, bool> predicate)
            {
                var result = new List<TKey>();

                using (var enumerator = source.GetEnumerator())
                {
                    while (enumerator.MoveNext())
                    {
                        if (predicate(enumerator.Current.Key, enumerator.Current.Value))
                        {
                            result.Add(enumerator.Current.Key);
                        }
                    }
                }

                result.ForEach(element => source.Remove(element));

                return result.Count;
            }

            private static bool KeepEntity(BaseEntity entity, UndoSettings undo)
            {
                if (!undo.Mounts && KeepMountable(entity) || entity.OwnerID.IsSteamId() && KeepPlayerEntity(entity, undo))
                {
                    return true;
                }

                RaidEntities.Remove(entity);

                return false;
            }

            private static bool KeepPlayerEntity(BaseEntity entity, UndoSettings undo)
            {
                if (entity.PrefabName.Contains("assets/prefabs/deployable/"))
                {
                    if (!undo.Deployables)
                    {
                        if (entity is IItemContainerEntity)
                        {
                            var ice = entity as IItemContainerEntity;

                            if (ice != null)
                            {
                                DropUtil.DropItems(ice.inventory, entity.transform.position + Vector3.up);
                            }
                        }

                        return false;
                    }
                }
                else if (!undo.Structures)
                {
                    return false;
                }

                RaidEntities.Remove(entity);

                return true;
            }

            private static bool KeepMountable(BaseEntity entity)
            {
                MountInfo mi;
                if (!Mounts.TryGetValue(entity, out mi) || !Mounts.Remove(entity))
                {
                    return false;
                }

                return mi.mountable.GetMounted() != null || !InRange(entity.transform.position, mi.position, mi.radius);
            }

            public static void RemoveHeldEntities()
            {
                foreach (var element in RaidEntities.ToList())
                {
                    if (element.Key is IItemContainerEntity)
                    {
                        var ice = element.Key as IItemContainerEntity;

                        if (ice == null || ice.inventory == null)
                        {
                            continue;
                        }

                        foreach (Item item in ice.inventory.itemList)
                        {
                            var e = item.GetHeldEntity();

                            if (e.IsValid())
                            {
                                e.enableSaving = false;
                                BaseEntity.saveList.Remove(e);
                            }
                        }

                        ice.inventory.Clear();
                    }
                }

                ItemManager.DoRemoves();
            }

            public static void StopCoroutine()
            {
                if (despawnCoroutine != null)
                {
                    ServerMgr.Instance.StopCoroutine(despawnCoroutine);
                    despawnCoroutine = null;
                }
            }

            public static void StartDespawn(bool inactiveOnly)
            {
                if (Instance.Raids.Count == 0)
                {
                    return;
                }

                if (despawnCoroutine != null)
                {
                    Instance.timer.Once(0.1f, () => StartDespawn(inactiveOnly));
                    return;
                }

                despawnCoroutine = ServerMgr.Instance.StartCoroutine(DespawnCoroutine(inactiveOnly));
            }

            private static IEnumerator DespawnCoroutine(bool inactiveOnly)
            {
                foreach (var raid in Instance.Raids.Values.ToList())
                {
                    if (raid == null || inactiveOnly && (raid.intruders.Count > 0 || raid.ownerId.IsSteamId()))
                    {
                        continue;
                    }

                    var baseIndex = raid.BaseIndex;

                    Instance.Raids.Remove(raid.uid);

                    raid.Despawn();

                    yield return new WaitWhile(() => Instance.Bases.ContainsKey(baseIndex));
                    yield return CoroutineEx.waitForSeconds(0.1f);
                }

                despawnCoroutine = null;
            }
        }

        #region Hooks

        private object CanBePenalized(BasePlayer player)
        {
            var raid = RaidableBase.Get(player);

            if (raid == null)
            {
                return null;
            }

            if (raid.Type == RaidableType.None || raid.AllowPVP && !raid.Options.PenalizePVP || !raid.AllowPVP && !raid.Options.PenalizePVE)
            {
                return false;
            }

            return null;
        }

        private object CanBGrade(BasePlayer player, int playerGrade, BuildingBlock block, Planner planner)
        {

            if (player.IsValid() && (EventTerritory(player.transform.position) || PvpDelay.ContainsKey(player.userID)))
            {
                return 0;
            }

            return null;
        }

        private object CanBuild(Planner planner, Construction construction, Construction.Target target)
        {
            var buildPos = target.entity && target.entity.transform && target.socket ? target.GetWorldPosition() : target.position;
            var raid = RaidableBase.Get(buildPos);

            if (raid == null)
            {
                return null;
            }

            if (!raid.Options.AllowBuildingPriviledges && construction.prefabID == 2476970476)
            {
                Backbone.Message(target.player, "Cupboards are blocked!");
                return false;
            }
            else if (construction.prefabID == 2150203378)
            {
                if (config.Settings.Management.AllowLadders)
                {
                    PlayerInputEx input;
                    if (raid.Inputs.TryGetValue(target.player, out input))
                    {
                        input.Restart();
                        input.TryPlaceLadder(target.player, raid);
                    }
                }
                else
                {
                    target.player.ChatMessage(lang.GetMessage("Ladders are blocked!", this, target.player.UserIDString));
                    return false;
                }
            }
            else if (!config.Settings.Management.AllowBuilding)
            {
                target.player.ChatMessage(lang.GetMessage("Building is blocked!", this, target.player.UserIDString));
                return false;
            }

            return null;
        }

        private bool CanDropPlayerBackpack(BasePlayer player, RaidableBase raid)
        {
            DelaySettings ds;
            if (PvpDelay.TryGetValue(player.userID, out ds) && (ds.AllowPVP && config.Settings.Management.BackpacksPVP || !ds.AllowPVP && config.Settings.Management.BackpacksPVE))
            {
                return true;
            }

            if (raid == null)
            {
                return false;
            }

            return raid.AllowPVP && config.Settings.Management.BackpacksPVP || !raid.AllowPVP && config.Settings.Management.BackpacksPVE;
        }

        private object CanEntityBeTargeted(BasePlayer player, BaseEntity entity)
        {
            if (player == null || entity == null || player.limitNetworking)
            {
                return null;
            }

            if (PvpDelay.ContainsKey(player.userID))
            {
                return true;
            }

            var raid = RaidableBase.Get(player.transform.position) ?? RaidableBase.Get(entity.transform.position);

            if (raid == null)
            {
                return null;
            }

            if (RaidableBase.Has(player.userID))
            {
                return entity.OwnerID.IsSteamId() && !raid.Options.NPC.IgnoreTrapsTurrets;
            }

            if (!raid.AllowPVP)
            {
                return raid.Entities.Contains(entity) && !raid.BuiltList.Contains(entity.net.ID);
            }

            return raid.Entities.Contains(entity) || raid.BuiltList.Contains(entity.net.ID) ? true : (object)null;
        }

        private object CanEntityTakeDamage(BaseCombatEntity entity, HitInfo hitInfo)
        {
            if (hitInfo == null || hitInfo.damageTypes == null || !IsValid(entity))
            {
                return null;
            }

            var success = entity is BasePlayer ? HandlePlayerDamage(entity as BasePlayer, hitInfo) : HandleEntityDamage(entity, hitInfo);

            if (success is bool && !(bool)success)
            {
                NullifyDamage(hitInfo);
                return false;
            }

            return success;
        }

        private object CanEntityTrapTrigger(BaseTrap trap, BasePlayer player)
        {
            if (!IsValid(player) || IsInvisible(player))
            {
                return null;
            }

            if (RaidableBase.Has(player.userID))
            {
                return false;
            }

            return EventTerritory(player.transform.position) ? true : (object)null;
        }

        private void CanOpenBackpack(BasePlayer looter, ulong backpackOwnerID)
        {
            var raid = RaidableBase.Get(looter.transform.position);

            if (raid == null)
            {
                return;
            }

            if (!raid.AllowPVP && !config.Settings.Management.BackpacksOpenPVE || raid.AllowPVP && !config.Settings.Management.BackpacksOpenPVP)
            {
                looter.Invoke(looter.EndLooting, 0.1f);
                Player.Message(looter, lang.GetMessage("NotAllowed", this, looter.UserIDString));
            }
        }

        private object CanPickupEntity(BasePlayer player, BaseCombatEntity entity)
        {
            var raid = RaidableBase.Get(entity);

            if (raid == null)
            {
                return null;
            }

            if (player.IsValid() && !raid.AddLooter(player))
            {
                return false;
            }

            if (raid.IsBlacklisted(entity.ShortPrefabName))
            {
                return false;
            }

            return !raid.Options.AllowPickup && entity.OwnerID == 0 ? false : (object)null;
        }

        private object canTeleport(BasePlayer player)
        {
            return !player.IsFlying && (EventTerritory(player.transform.position) || PvpDelay.ContainsKey(player.userID)) ? Backbone.GetMessage("CannotTeleport", player.UserIDString) : null;
        }

        private object CanTeleport(BasePlayer player, Vector3 to)
        {
            return !player.IsFlying && (EventTerritory(to) || EventTerritory(player.transform.position) || PvpDelay.ContainsKey(player.userID)) ? Backbone.GetMessage("CannotTeleport", player.UserIDString) : null;
        }

        private void CheckForWipe()
        {
            if (!wiped && BuildingManager.server.buildingDictionary.Count == 0)
            {
                foreach (var pi in data.Players.Values)
                {
                    if (pi.Raids > 0)
                    {
                        wiped = true;
                        break;
                    }
                }
            }

            if (wiped)
            {
                var raids = new List<int>();
                var dict = new Dictionary<string, PlayerInfo>(data.Players);

                if (data.Players.Count > 0)
                {
                    if (AssignTreasureHunters())
                    {
                        foreach (var entry in dict)
                        {
                            if (entry.Value.Raids > 0)
                            {
                                raids.Add(entry.Value.Raids);
                            }

                            data.Players[entry.Key].Raids = 0;
                        }
                    }
                }

                if (raids.Count > 0)
                {
                    var average = raids.Average();

                    foreach (var entry in dict)
                    {
                        if (entry.Value.TotalRaids < average)
                        {
                            data.Players.Remove(entry.Key);
                        }
                    }
                }

                wiped = false;
                data.Save();
            }
        }

        private void Init()
        {
            Instance = this;
            IsUnloading = false;
            _shortnames = new Dictionary<string, ItemDefinition>();
            blockedcolliders = new List<string> { "powerline", "invisible", "TopCol" };
            permission.CreateGroup(rankLadderGroup, rankLadderGroup, 0);
            permission.GrantGroupPermission(rankLadderGroup, rankLadderPermission, this);
            permission.RegisterPermission(adminPermission, this);
            permission.RegisterPermission(rankLadderPermission, this);
            permission.RegisterPermission(drawPermission, this);
            permission.RegisterPermission(mapPermission, this);
            permission.RegisterPermission(canBypassPermission, this);
            permission.RegisterPermission(bypassBlockPermission, this);
            permission.RegisterPermission(banPermission, this);
            permission.RegisterPermission(vipPermission, this);
            permission.RegisterPermission(losePermission, this);
            permission.RegisterPermission(notitlePermission, this);
            permission.RegisterPermission("raidablebases.block.fauxadmin", this);
            permission.RegisterPermission("raidablebases.admin.loot", this);
            permission.RegisterPermission("raidablebases.elevators.bypass.building", this);
            permission.RegisterPermission("raidablebases.elevators.bypass.card", this);
            lastSpawnRequestTime = Time.realtimeSinceStartup;
            Backbone = new SingletonBackbone(this);
            Unsubscribe(nameof(OnMapMarkerAdded));
            Unsubscribe(nameof(OnPlayerSleepEnded));
            UnsubscribeHooks();
            maintainEnabled = config.Settings.Maintained.Enabled;
            scheduleEnabled = config.Settings.Schedule.Enabled;
        }
        
        private void OnActiveItemChanged(BasePlayer player, Item oldItem, Item newItem)
        {
            if (!IsHuman(player) || !EventTerritory(player.transform.position))
            {
                return;
            }

            RaidableBase.StopUsingWand(player);
        }

        private object OnBaseRepair(BuildingManager.Building building, BasePlayer player)
        {
            if (EventTerritory(player.transform.position))
            {
                return false;
            }

            return null;
        }

        private void OnElevatorButtonPress(ElevatorLift e, BasePlayer player, Elevator.Direction Direction, bool FullTravel)
        {
            BMGELEVATOR bmgELEVATOR;
            if (_elevators.TryGetValue(e.GetParentEntity().net.ID, out bmgELEVATOR))
            {
                if (bmgELEVATOR.HasCardPermission(player) && bmgELEVATOR.HasBuildingPermission(player))
                {
                    bmgELEVATOR.GoToFloor(Direction, FullTravel);
                }
            }
        }

        private void OnButtonPress(PressButton button, BasePlayer player)
        {
            if (button.OwnerID == 0 && RaidableBase.Has(button))
            {
                foreach (var e in _elevators)
                {
                    if (Vector3Ex.Distance2D(button.ServerPosition, e.Value.ServerPosition) <= 3f)
                    {
                        e.Value.GoToFloor(Elevator.Direction.Up, false, Mathf.CeilToInt(button.transform.position.y));
                    }
                }
            }
        }

        private object CanSamSiteShoot(SamSite ss)
        {
            if (!ss.HasValidTarget() || !EventTerritory(ss.transform.position) && !RaidableBase.Has(ss))
            {
                return null;
            }

            if (InRange(ss.currentTarget.CenterPoint(), ss.transform.position, config.Weapons.SamSiteRange, false))
            {
                return null;
            }

            return true;
        }

        private void OnCupboardAuthorize(BuildingPrivlidge priv, BasePlayer player)
        {
            foreach (var raid in Raids.Values)
            {
                if (raid.priv == priv && raid.Options.RequiresCupboardAccess && !raid.IsAuthed)
                {
                    raid.IsAuthed = true;

                    if (raid.Options.RequiresCupboardAccess && config.EventMessages.AnnounceRaidUnlock)
                    {
                        foreach (var p in BasePlayer.activePlayerList)
                        {
                            SendNotification(p, _("OnRaidFinished", p.UserIDString, FormatGridReference(raid.Location)));
                        }
                    }

                    break;
                }
            }

            foreach (var raid in Raids.Values)
            {
                if (!raid.IsAuthed)
                {
                    return;
                }
            }

            Unsubscribe(nameof(OnCupboardAuthorize));
        }

        private void OnCupboardProtectionCalculated(BuildingPrivlidge priv, float cachedProtectedMinutes)
        {
            if (RaidableBase.Has(priv))
            {
                priv.cachedProtectedMinutes = 0;
            }
        }

        private void OnEntityBuilt(Planner planner, GameObject go)
        {
            var e = go.ToBaseEntity();

            if (e == null)
            {
                return;
            }

            var raid = RaidableBase.Get(e.transform.position);

            if (raid == null)
            {
                return;
            }

            var player = planner.GetOwnerPlayer();

            if (player == null)
            {
                return;
            }

            if (raid.Options.BuildingRestrictions.Any() && e is BuildingBlock)
            {
                var block = e as BuildingBlock;
                var grade = block.grade;

                block.Invoke(() =>
                {
                    if (block.IsDestroyed || block.grade == grade || OnStructureUpgrade(block, player, block.grade) == null)
                    {
                        return;
                    }

                    foreach (var ia in block.BuildCost())
                    {
                        player.GiveItem(ItemManager.Create(ia.itemDef, (int)ia.amount));
                    }

                    block.Kill();
                }, 0.1f);
            }

            if (!raid.intruders.Contains(player))
            {
                e.Invoke(e.KillMessage, 0.1f);
                return;
            }

            var decayEntity = e as DecayEntity;

            if (decayEntity.IsValid())
            {
                if (e.prefabID == 3234260181 || e.prefabID == 72949757)
                {
                    if (decayEntity.buildingID == raid.BuildingID)
                    {
                        raid.TryMessage(player, "TooCloseToABuilding");

                        e.Invoke(e.KillMessage, 0.1f);
                        return;
                    }
                }
            }

            AddEntity(e, raid);
        }

        private void OnEntityDeath(AutoTurret turret, HitInfo hitInfo)
        {
            if (!config.Settings.Management.DropLootTraps)
            {
                return;
            }

            var raid = RaidableBase.Get(turret);

            if (raid == null || !raid.IsOpened || raid.killed)
            {
                return;
            }

            if (turret.inventory.itemList.Count > 0)
            {
                float y = turret.transform.position.y + turret.bounds.size.y + 0.015f;

                turret.inventory.Drop(StringPool.Get(545786656), turret.transform.position.WithY(y), turret.transform.rotation);
            }

            raid.turrets.Remove(turret);
        }

        private void OnEntityDeath(BuildingPrivlidge priv, HitInfo hitInfo)
        {
            var raid = RaidableBase.Get(priv);

            if (raid == null)
            {
                return;
            }

            if (hitInfo?.Initiator == null && !raid.IsOpened)
            {
                priv.inventory.Clear();
            }

            if (raid.Options.RequiresCupboardAccess)
            {
                OnCupboardAuthorize(priv, null);
            }

            if (raid.IsOpened && raid.EndWhenCupboardIsDestroyed())
            {
                raid.CancelInvoke(raid.TryToEnd);
                raid.AwardRaiders();
                raid.Undo();
            }
        }

        private void OnEntityDeath(StorageContainer container, HitInfo hitInfo) => EntityHandler(container, hitInfo);

        private void OnEntityDeath(Door door, HitInfo hitInfo) => BlockHandler(door, hitInfo);

        private void OnEntityDeath(BuildingBlock block, HitInfo hitInfo) => BlockHandler(block, hitInfo);

        private void OnEntityDeath(SimpleBuildingBlock block, HitInfo hitInfo) => BlockHandler(block, hitInfo);

        private object OnEntityEnter(TriggerBase trigger, BasePlayer player) // TargetTrigger, PlayerDetectionTrigger // Prevent npcs from triggering HBHFSensor, GunTrap, FlameTurret, AutoTurret
        {
            if (RaidableBase.Has(player.userID))
            {
                if (RaidableBase.Has(trigger))
                {
                    return false;
                }

                var raid = RaidableBase.Get(player.userID);

                if (raid.Options.NPC.IgnoreTrapsTurrets)
                {
                    return false;
                }
            }

            return config.Settings.Management.IgnoreFlying && player.IsFlying && EventTerritory(player.transform.position) ? false : (object)null;
        }

        private object OnStructureUpgrade(BuildingBlock block, BasePlayer player, BuildingGrade.Enum grade)
        {
            var raid = RaidableBase.Get(block.transform.position);

            if (raid == null || !raid.Options.BuildingRestrictions.Any())
            {
                return null;
            }

            if (!config.Settings.Management.AllowUpgrade && RaidableBase.Has(block))
            {
                return true;
            }

            switch (grade)
            {
                case BuildingGrade.Enum.Metal:
                    return raid.Options.BuildingRestrictions.Metal ? true : (object)null;
                case BuildingGrade.Enum.Stone:
                    return raid.Options.BuildingRestrictions.Stone ? true : (object)null;
                case BuildingGrade.Enum.TopTier:
                    return raid.Options.BuildingRestrictions.HQM ? true : (object)null;
                case BuildingGrade.Enum.Wood:
                    return raid.Options.BuildingRestrictions.Wooden ? true : (object)null;
            }

            return null;
        }

        private object OnEntityGroundMissing(StorageContainer container)
        {
            if (IsBox(container, true))
            {
                var raid = RaidableBase.Get(container);

                if (raid != null && raid.Options.Invulnerable)
                {
                    return true;
                }
            }

            EntityHandler(container, null);
            return null;
        }

        private void OnEntityKill(StorageContainer container)
        {
            if (container is BuildingPrivlidge)
            {
                OnEntityDeath(container as BuildingPrivlidge, null);
            }

            EntityHandler(container, null);
        }

        private void OnEntitySpawned(DroppedItemContainer backpack)
        {
            NextTick(() =>
            {
                if (backpack == null || backpack.IsDestroyed || !backpack.playerSteamID.IsSteamId())
                {
                    return;
                }

                DelaySettings ds;
                if (PvpDelay.TryGetValue(backpack.playerSteamID, out ds) && (ds.AllowPVP && config.Settings.Management.BackpacksPVP || !ds.AllowPVP && config.Settings.Management.BackpacksPVE))
                {
                    backpack.playerSteamID = 0;
                    return;
                }

                var raid = RaidableBase.Get(backpack.transform.position);

                if (raid == null)
                {
                    return;
                }

                if (backpack.ShortPrefabName == "item_drop")
                {
                    raid.HasDroppedItems = true;
                }

                if (raid.AllowPVP && config.Settings.Management.BackpacksPVP || !raid.AllowPVP && config.Settings.Management.BackpacksPVE)
                {
                    backpack.playerSteamID = 0;
                }
            });
        }

        private void OnEntitySpawned(BaseLock entity)
        {
            var parent = entity.GetParentEntity();

            foreach (var raid in Raids.Values)
            {
                if (raid.IsLoading)
                {
                    continue;
                }

                foreach (var container in raid._containers)
                {
                    if (parent == container)
                    {
                        entity.Invoke(entity.KillMessage, 0.1f);
                        break;
                    }
                }
            }
        }

        private void OnEntitySpawned(PlayerCorpse corpse)
        {
            if (corpse == null)
            {
                return;
            }

            var raid = RaidableBase.Get(corpse);

            if (raid == null)
            {
                return;
            }

            if (corpse.playerSteamID.IsSteamId())
            {
                var playerSteamID = corpse.playerSteamID;

                if (raid.Options.EjectCorpses)
                {
                    if (corpse.containers == null)
                    {
                        if (config.Settings.Management.PlayersLootableInPVE && !raid.AllowPVP || config.Settings.Management.PlayersLootableInPVP && raid.AllowPVP)
                        {
                            corpse.playerSteamID = 0;
                        }

                        return;
                    }

                    var container = ItemContainer.Drop(StringPool.Get(1519640547), corpse.transform.position, Quaternion.identity, corpse.containers);

                    if (!container.IsValid())
                    {
                        if (config.Settings.Management.PlayersLootableInPVE && !raid.AllowPVP || config.Settings.Management.PlayersLootableInPVP && raid.AllowPVP)
                        {
                            corpse.playerSteamID = 0;
                        }

                        return;
                    }

                    container.playerName = corpse.playerName;
                    container.playerSteamID = corpse.playerSteamID;

                    for (int i = 0; i < corpse.containers.Length; i++)
                    {
                        corpse.containers[i].Kill();
                    }

                    corpse.containers = null;
                    corpse.Kill();

                    var player = RustCore.FindPlayerById(playerSteamID);
                    var data = raid.AddCorpse(container, player);

                    if (raid.EjectBackpack(container.net.ID, data, false))
                    {
                        raid.backpacks.Remove(container.net.ID);
                    }
                    else Interface.CallHook("OnRaidablePlayerCorpse", player, container, 512);

                    if (config.Settings.Management.PlayersLootableInPVE && !raid.AllowPVP || config.Settings.Management.PlayersLootableInPVP && raid.AllowPVP)
                    {
                        container.playerSteamID = 0;
                    }

                    return;
                }

                if (config.Settings.Management.PlayersLootableInPVE && !raid.AllowPVP || config.Settings.Management.PlayersLootableInPVP && raid.AllowPVP)
                {
                    corpse.playerSteamID = 0;
                }
            }
            else if (raid.npcs.RemoveAll(npc => npc == null || npc.userID == corpse.playerSteamID) > 0)
            {
                if (raid.Options.NPC.DespawnInventory)
                {
                    corpse.Invoke(corpse.KillMessage, 30f);
                }

                Npcs.Remove(corpse.playerSteamID);

                if (raid.Options.RespawnRateMax > 0f)
                {
                    raid.TryRespawnNpc();
                    return;
                }

                if (!AnyNpcs())
                {
                    Unsubscribe(nameof(OnNpcDuck));
                    Unsubscribe(nameof(OnNpcDestinationSet));
                }
            }
        }

        private void OnEntityTakeDamage(BaseCombatEntity entity, HitInfo hitInfo) => CanEntityTakeDamage(entity, hitInfo);

        private void OnFireBallSpread(FireBall ball, BaseEntity fire)
        {
            if (EventTerritory(fire.transform.position))
            {
                NextTick(() =>
                {
                    if (fire == null || fire.IsDestroyed)
                    {
                        return;
                    }

                    fire.Kill();
                });
            }
        }

        private void OnLootEntity(BasePlayer player, BaseEntity entity)
        {
            if (!IsHuman(player))
            {
                return;
            }

            var raid = RaidableBase.Get(entity.transform.position);

            if (raid == null)
            {
                return;
            }

            raid.OnLootEntityInternal(player, entity);
        }

        private void OnLootEntityEnd(BasePlayer player, StorageContainer container)
        {
            if (container?.inventory == null || container.OwnerID.IsSteamId() || IsInvisible(player))
            {
                return;
            }

            var raid = RaidableBase.Get(container);

            if (raid == null)
            {
                return;
            }

            if (IsBox(container, true) || container is BuildingPrivlidge)
            {
                UI.UpdateStatusUI(raid);
            }

            if (raid.Options.DropTimeAfterLooting <= 0 || (raid.Options.DropOnlyBoxesAndPrivileges && !IsBox(container, true) && !(container is BuildingPrivlidge)))
            {
                return;
            }

            if (container.inventory.IsEmpty() && (container.prefabID == Constants.LARGE_WOODEN_BOX || container.prefabID == Constants.SMALL_WOODEN_BOX || container.prefabID == Constants.COFFIN_STORAGE))
            {
                container.Invoke(container.KillMessage, 0.1f);
            }
            else container.Invoke(() => DropOrRemoveItems(container, raid.IsProtectedWeapon(container)), raid.Options.DropTimeAfterLooting);
        }

        private object OnLoseCondition(Item item, float amount)
        {
            var player = GetOwnerPlayer(item);

            if (!IsValid(player) || permission.UserHasPermission(player.UserIDString, losePermission))
            {
                return null;
            }

            var raid = RaidableBase.Get(player.transform.position);

            if (raid == null || !raid.Options.EnforceDurability)
            {
                return null;
            }

            uint uid = item.uid;
            float condition;
            if (!raid.conditions.TryGetValue(uid, out condition))
            {
                raid.conditions[uid] = condition = item.condition;
            }

            NextTick(() =>
            {
                if (raid == null)
                {
                    return;
                }

                if (!IsValid(item))
                {
                    raid.conditions.Remove(uid);
                    return;
                }

                item.condition = condition - amount;

                if (item.condition <= 0f && item.condition < condition)
                {
                    item.OnBroken();
                    raid.conditions.Remove(uid);
                }
                else raid.conditions[uid] = item.condition;
            });

            return true;
        }

        private void OnMapMarkerAdded(BasePlayer player, ProtoBuf.MapNote note)
        {
            if (player.IsValid() && player.IsConnected && note != null && HasPermission(player, mapPermission))
            {
                player.Teleport(new Vector3(note.worldPosition.x, GetSpawnHeight(note.worldPosition), note.worldPosition.z));
            }
        }

        private void OnNewSave(string filename) => wiped = true;

        private object OnNpcDuck(ScientistNPC npc) => RaidableBase.Has(npc.userID) ? true : (object)null;

        private object OnNpcDestinationSet(ScientistNPC npc, Vector3 newDestination)
        {
            if (npc == null || npc.NavAgent == null || !npc.NavAgent.enabled || !npc.NavAgent.isOnNavMesh)
            {
                return true;
            }

            HumanoidBrain brain;
            if (!HumanoidBrains.TryGetValue(npc.userID, out brain) || brain.CanRoam(newDestination))
            {
                return null;
            }

            return true;
        }

        private object OnNpcKits(ulong targetId)
        {
            return RaidableBase.Get(targetId) == null ? (object)null : true;
        }

        private object OnPlayerCommand(BasePlayer player, string command, string[] args)
        {
            if (player.IsValid() && EventTerritory(player.transform.position))
            {
                command = command.Replace("/", string.Empty);

                foreach (var value in config.Settings.BlacklistedCommands)
                {
                    if (command.Equals(value.Replace("/", string.Empty), StringComparison.OrdinalIgnoreCase))
                    {
                        Backbone.Message(player, "CommandNotAllowed");
                        return true;
                    }
                }
            }

            return null;
        }

        private void OnPlayerDeath(BasePlayer player, HitInfo hitInfo)
        {
            var raid = RaidableBase.Get(player);

            if (raid == null)
            {
                return;
            }

            if (!IsHuman(player))
            {
                if (!RaidableBase.Has(player.userID))
                {
                    return;
                }

                if (config.Settings.Management.UseOwners && hitInfo != null && hitInfo.Initiator is BasePlayer)
                {
                    var attacker = hitInfo.Initiator as BasePlayer;

                    if (IsHuman(attacker) && raid.AddLooter(attacker))
                    {
                        raid.TrySetOwner(attacker, player, hitInfo);
                    }
                }

                if (raid.Options.NPC.DespawnInventory)
                {
                    player.inventory.Strip();
                }

                raid.CheckDespawn();
            }
            else
            {
                if (CanDropPlayerBackpack(player, raid))
                {
                    Backpacks?.Call("API_DropBackpack", player);
                }

                raid.OnPlayerExit(player);
            }
        }

        private object OnPlayerDropActiveItem(BasePlayer player, Item item)
        {
            if (EventTerritory(player.transform.position))
            {
                return true;
            }

            return null;
        }

        private object OnPlayerLand(BasePlayer player, float amount)
        {
            var raid = RaidableBase.Get(player.transform.position);

            return raid == null || !raid.IsDespawning ? (object)null : true;
        }

        private void OnPlayerSleepEnded(BasePlayer player)
        {
            NextTick(() =>
            {
                if (player == null || player.IsDestroyed || !IsHuman(player))
                {
                    return;
                }

                DelaySettings ds;
                if (PvpDelay.TryGetValue(player.userID, out ds))
                {
                    if (ds.Timer != null && !ds.Timer.Destroyed)
                    {
                        ds.Timer.Callback.Invoke();
                        ds.Timer.Destroy();
                    }

                    PvpDelay.Remove(player.userID);
                }

                if (config.Settings.Management.AllowTeleport)
                {
                    return;
                }

                var raid = RaidableBase.Get(player.transform.position, 5f); // 1.5.1 sleeping bag exploit fix

                if (raid == null || raid.intruders.Contains(player))
                {
                    return;
                }

                if (InRange(player.transform.position, raid.Location, raid.Options.ProtectionRadius))
                {
                    if (raid.RemoveFauxAdmin(player) || raid.IsScavenging(player))
                    {
                        return;
                    }

                    raid.OnEnterRaid(player);
                }
                else raid.RemovePlayer(player);
            });
        }

        private object OnRestoreUponDeath(BasePlayer player)
        {
            var raid = RaidableBase.Get(player.transform.position);

            if (raid == null)
            {
                return null;
            }

            return config.Settings.Management.BlockRestorePVE && !raid.AllowPVP || config.Settings.Management.BlockRestorePVP && raid.AllowPVP ? true : (object)null;
        }

        private object OnServerCommand(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();

            if (player.IsValid() && EventTerritory(player.transform.position))
            {
                string command = arg.cmd.FullName.Replace("/", string.Empty);

                foreach (var value in config.Settings.BlacklistedCommands)
                {
                    if (command.Equals(value.Replace("/", string.Empty), StringComparison.OrdinalIgnoreCase))
                    {
                        Backbone.Message(player, "CommandNotAllowed");
                        return true;
                    }
                }
            }

            return null;
        }

        private void OnServerInitialized(bool isStartup)
        {
            timer.Repeat(30f, 0, () => RaidableBase.UpdateAllMarkers());

            LoadData();
            SetupMonuments();
            RegisterCommands();
            CheckForWipe();
            Initialize();
            Puts("Free version initialized.");
        }

        private object OnTrapTrigger(BaseTrap trap, GameObject go)
        {
            var player = go.GetComponent<BasePlayer>();
            var result = CanEntityTrapTrigger(trap, player);

            if (result is bool)
            {
                return (bool)result ? (object)null : true;
            }

            return null;
        }

        private void OnServerShutdown()
        {
            IsUnloading = true;

            RaidableBase.Unload(true);
            StopAllCoroutines();
            data.Save();
            DestroyAll();
        }

        private void Unload()
        {
            if (IsUnloading)
            {
                return;
            }

            IsUnloading = true;

            data.Save();
            RaidableBase.Unload(false);
            StopAllCoroutines();
            DestroyComponents();
            
            if (Raids.Count > 0 || Bases.Count > 0)
            {
                DespawnAllBasesNow(false);
                return;
            }

            UnsetStatics();
        }

        #endregion Hooks

        #region Spawn

        public bool TryOpenEvent(RaidableType type, Vector3 position, int uid, string BaseName, BaseProfile profile, out RaidableBase raid)
        {
            if (IsUnloading)
            {
                raid = null;
                return false;
            }

            raid = new GameObject().AddComponent<RaidableBase>();
            raid.name = Name;

            raid.SetAllowPVP(type, profile.Options.AllowPVP);
            raid.DifficultyMode = Backbone.GetMessageEx("Normal").ToLower();
            raid.PastedLocation = position;
            raid.Location = position;
            raid.Options = profile.Options;
            raid.BaseName = BaseName;
            raid.ProfileName = profile.Name;
            raid.uid = uid;

            Cycle.Add(type, RaidableMode.Normal, BaseName);

            if (config.Settings.NoWizardry && Wizardry != null)
            {
                Subscribe(nameof(OnActiveItemChanged));
            }

            if (config.Settings.BlacklistedCommands.Count > 0)
            {
                Subscribe(nameof(OnPlayerCommand));
                Subscribe(nameof(OnServerCommand));
            }

            if (!IsPVE())
            {
                Subscribe(nameof(OnEntityTakeDamage));
            }

            Subscribe(nameof(CanEntityTakeDamage));
            Subscribe(nameof(OnEntityEnter));
            Subscribe(nameof(OnEntitySpawned));
            Subscribe(nameof(CanBuild));

            data.TotalEvents++;
            raid.UndoInit();

            Raids[uid] = raid;
            return true;
        }

        protected void LoadSpawns()
        {
            raidSpawns.Clear();
            raidSpawns.Add(RaidableType.Grid, new RaidableSpawns());

            if (SpawnsFileValid(config.Settings.Manual.SpawnsFile))
            {
                var spawns = GetSpawnsLocations(config.Settings.Manual.SpawnsFile);

                if (spawns?.Count > 0)
                {
                    Puts(Backbone.GetMessageEx("LoadedManual", null, spawns.Count));
                    raidSpawns[RaidableType.Manual] = new RaidableSpawns(spawns);
                }
            }

            if (SpawnsFileValid(config.Settings.Schedule.SpawnsFile))
            {
                var spawns = GetSpawnsLocations(config.Settings.Schedule.SpawnsFile);

                if (spawns?.Count > 0)
                {
                    Puts(Backbone.GetMessageEx("LoadedScheduled", null, spawns.Count));
                    raidSpawns[RaidableType.Scheduled] = new RaidableSpawns(spawns);
                }
            }

            if (SpawnsFileValid(config.Settings.Maintained.SpawnsFile))
            {
                var spawns = GetSpawnsLocations(config.Settings.Maintained.SpawnsFile);

                if (spawns?.Count > 0)
                {
                    Puts(Backbone.GetMessageEx("LoadedMaintained", null, spawns.Count));
                    raidSpawns[RaidableType.Maintained] = new RaidableSpawns(spawns);
                }
            }
        }

        protected void StopAllCoroutines()
        {
            StopScheduleCoroutine();
            StopMaintainCoroutine();
            StopGridCoroutine();
            GarbageController.StopCoroutine();
        }

        protected void SetupGrid()
        {
            if (raidSpawns.Count >= 5)
            {
                StartAutomation();
                return;
            }

            StopGridCoroutine();

            NextTick(() =>
            {
                gridStopwatch.Start();
                gridTime = Time.realtimeSinceStartup;
                gridCoroutine = ServerMgr.Instance.StartCoroutine(GenerateGrid());
            });
        }

        private static bool ContainsTopology(TerrainTopology.Enum mask, Vector3 position)
        {
            return (TerrainMeta.TopologyMap.GetTopology(position) & (int)mask) != 0;
        }

        private static bool ContainsTopology(TerrainTopology.Enum mask, Vector3 position, float radius)
        {
            return (TerrainMeta.TopologyMap.GetTopology(position, radius) & (int)mask) != 0;
        }
        
        public static float GetSpawnHeight(Vector3 target, bool flag = true, bool draw = false)
        {
            float y = TerrainMeta.HeightMap.GetHeight(target);
            float w = TerrainMeta.WaterMap.GetHeight(target);
            float p = TerrainMeta.HighestPoint.y + 250f;
            RaycastHit hit;

            if (Physics.Raycast(target.WithY(p), Vector3.down, out hit, ++p, Layers.Mask.World | Layers.Mask.Terrain, QueryTriggerInteraction.Ignore))
            {
                if (!blockedcolliders.Exists(hit.collider.name.Contains))
                {
                    y = Mathf.Max(y, hit.point.y);
                }
            }

            return flag ? Mathf.Max(y, w) : y;
        }

        private static Vector3 GetBuildingPrivilege(Vector3 target, float radius)
        {
            var vector = Vector3.zero;
            var list = Pool.GetList<BuildingPrivlidge>();
            Vis.Entities(target, radius, list);
            foreach (var tc in list)
            {
                if (tc.IsValid() && !tc.IsDestroyed && !RaidableBase.Has(tc))
                {
                    vector = tc.transform.position;
                    break;
                }
            }
            Pool.FreeList(ref list);
            return vector;
        }

        private static void Shuffle<T>(IList<T> list) // Fisher-Yates shuffle
        {
            int count = list.Count;
            int n = count;
            while (n-- > 0)
            {
                int k = UnityEngine.Random.Range(0, count);
                int j = UnityEngine.Random.Range(0, count);
                T value = list[k];
                list[k] = list[j];
                list[j] = value;
            }
        }

        private void draw(Vector3 pos, string text)
        {
            if (!DEBUG_DRAWINGS) return;
            foreach (var player in BasePlayer.activePlayerList)
            {
                if (!player.IsAdmin || !InRange(player.transform.position, pos, 100f))
                    continue;

                player.SendConsoleCommand("ddraw.text", 60f, Color.yellow, pos, text);
            }
        }

        public void ExtractLocation(RaidableSpawns spawns, Vector3 position, float e, float m, float p, float w, bool s)
        {
            if (IsValidLocation(position, Constants.CELL_SIZE, m, s))
            {
                var elevation = GetTerrainElevation(position, 20f);

                if (IsFlatTerrain(position, elevation, e))
                {
                    var rsl = new RaidableSpawnLocation(position)
                    {
                        Elevation = elevation,
                        WaterHeight = TerrainMeta.WaterMap.GetHeight(position),
                        TerrainHeight = TerrainMeta.HeightMap.GetHeight(position),
                        SpawnHeight = GetSpawnHeight(position, false),
                        Radius = p,
                        AutoHeight = true
                    };

                    if (s)
                    {
                        if (InDeepWater(position, w))
                        {
                            Seabed.Add(rsl);
                        }
                        else
                        {
                            spawns.Spawns.Add(rsl);
                        }
                    }
                    else spawns.Spawns.Add(rsl);
                }
            }
        }

        private HashSet<RaidableSpawnLocation> Seabed { get; set; } = new HashSet<RaidableSpawnLocation>();

        private IEnumerator GenerateGrid()
        {
            var gridStopwatch = new Stopwatch();

            gridStopwatch.Start();

            RaidableSpawns spawns = raidSpawns[RaidableType.Grid] = new RaidableSpawns();

            gridTime = Time.realtimeSinceStartup;

            float waterDepthMax = Buildings.Profiles.Any(x => x.Value.Options.Water.AllowSubmerged) ? 3f : 0f;
            float monumentDist = Constants.RADIUS * 2f + config.Settings.Management.MonumentDistance;
            bool seabed = Buildings.Profiles.Any(x => x.Value.Options.Water.Seabed > 0f);
            int minPos = (int)(World.Size / -2f);
            int maxPos = (int)(World.Size / 2f);
            float protectionRadius = 50f;
            float elevation = 0.5f;
            int checks = 0;

            foreach (var profile in Buildings.Profiles.Values)
            {
                protectionRadius = Mathf.Max(profile.Options.ProtectionRadius, profile.Options.ArenaWalls.Radius, protectionRadius);

                waterDepthMax = Mathf.Max(profile.Options.Water.WaterDepth, seabed ? waterDepthMax : 40f);

                elevation = Mathf.Max(profile.Options.Elevation, elevation);
            }

            for (float x = minPos; x < maxPos; x += Constants.CELL_SIZE) // Credits to Jake_Rich for creating this for me!
            {
                for (float z = minPos; z < maxPos; z += Constants.CELL_SIZE)
                {
                    var position = new Vector3(x, 0f, z);

                    position.y = GetSpawnHeight(position);

                    ExtractLocation(spawns, position, elevation, monumentDist, protectionRadius, waterDepthMax, seabed);

                    if (++checks >= 75)
                    {
                        checks = 0;
                        yield return CoroutineEx.waitForSeconds(0.025f);
                    }
                }
            }

            gridCoroutine = null;
            gridStopwatch.Stop();
            StartAutomation();

            Puts(Backbone.GetMessageEx("InitializedGrid", null, gridStopwatch.Elapsed.Seconds, gridStopwatch.Elapsed.Milliseconds, World.Size, spawns.Count));
            if (Seabed.Count > 0) Puts(Backbone.GetMessageEx("InitializedGridSea", null, Seabed.Count));
        }

        private HashSet<RaidableSpawnLocation> GetSpawnsLocations(string spawnsFile)
        {
            object success = Spawns?.Call("LoadSpawnFile", spawnsFile);

            if (success == null)
            {
                return null;
            }

            var list = (List<Vector3>)success;
            var locations = new HashSet<RaidableSpawnLocation>();

            foreach (var pos in list)
            {
                locations.Add(new RaidableSpawnLocation(pos));
            }

            return locations;
        }

        private static bool InDeepWater(Vector3 vector, float depth)
        {
            vector.y = TerrainMeta.HeightMap.GetHeight(vector);

            return WaterLevel.GetWaterDepth(vector, true, null) >= depth;
        }

        private static float GetRockHeight(Vector3 a)
        {
            RaycastHit hit;
            if (Physics.Raycast(a + new Vector3(0f, 50f, 0f), Vector3.down, out hit, a.y + 51f, Layers.Mask.World, QueryTriggerInteraction.Ignore))
            {
                return Mathf.Abs(hit.point.y - a.y);
            }

            return 0f;
        }

        public static bool IsAreaSafe(Vector3 position, float radius, int layers, bool? isCustomSpawn, out CacheType cacheType, out string message, RaidableType type = RaidableType.None)
        {
            var colliders = Pool.GetList<Collider>();

            Vis.Colliders(position, radius, colliders, layers, QueryTriggerInteraction.Collide);

            cacheType = CacheType.Generic;
            message = string.Empty;

            foreach (var collider in colliders)
            {
                if (collider.name == "ZoneManager" || collider.name.Contains("xmas"))
                {
                    continue;
                }

                if (collider.name.Contains("SafeZone"))
                {
                    message = $"Safe Zone at {collider.transform.position}";
                    cacheType = CacheType.Delete;
                    break;
                }

                var e = collider.ToBaseEntity();
                var assets = new List<string> { "/props/", "/structures/", "/building/", "train_", "powerline_", "dune", "candy-cane", "assets/content/nature/", "walkway", "invisible_collider" };

                if (assets.Exists(collider.name.Contains) && (e == null || e.name.Contains("/treessource/")))
                {
                    message = $"Blocked by a map prefab {collider.transform.position} {collider.name}";
                    cacheType = CacheType.Delete;
                    break;
                }

                if (e.IsValid())
                {
                    if (e.PrefabName.Contains("xmas")) continue;

                    bool isSteamId = e.OwnerID.IsSteamId();

                    if (e is BasePlayer)
                    {
                        var player = e as BasePlayer;

                        if (!IsHuman(player) || player.IsFlying || config.Settings.Management.EjectSleepers && player.IsSleeping())
                        {
                            continue;
                        }
                        else
                        {
                            message = $"A player is too close {e.transform.position}";
                            cacheType = CacheType.Temporary;
                            break;
                        }
                    }
                    else if (isSteamId && e is SleepingBag)
                    {
                        continue;
                    }
                    else if (isSteamId && config.Settings.Schedule.Skip && type == RaidableType.Scheduled)
                    {
                        continue;
                    }
                    else if (isSteamId && config.Settings.Maintained.Skip && type == RaidableType.Maintained)
                    {
                        continue;
                    }
                    else if (RaidableBase.Has(e))
                    {
                        message = $"Already occupied by a raidable base {e.transform.position}";
                        cacheType = CacheType.Temporary;
                        break;
                    }
                    else if (e.IsNpc || e is SleepingBag)
                    {
                        continue;
                    }
                    else if (e is BaseOven)
                    {
                        if (e.bounds.size.Max() > 1.6f)
                        {
                            message = $"An oven is too close {e.transform.position}";
                            cacheType = CacheType.Temporary;
                            break;
                        }
                    }
                    else if (e is PlayerCorpse)
                    {
                        var corpse = e as PlayerCorpse;

                        if (corpse.playerSteamID == 0 || corpse.playerSteamID.IsSteamId())
                        {
                            message = $"A player's corpse is too close {e.transform.position}";
                            cacheType = CacheType.Temporary;
                            break;
                        }
                    }
                    else if (e is DroppedItemContainer && e.ShortPrefabName != "item_drop")
                    {
                        var backpack = e as DroppedItemContainer;

                        if (backpack.playerSteamID == 0 || backpack.playerSteamID.IsSteamId())
                        {
                            message = $"A player's backpack is too close {e.transform.position}";
                            cacheType = CacheType.Temporary;
                            break;
                        }
                    }
                    else if (e.OwnerID == 0)
                    {
                        if (e is BuildingBlock)
                        {
                            message = $"{e.ShortPrefabName} is too close {e.transform.position}";
                            cacheType = CacheType.Temporary;
                            break;
                        }
                        else if (e is MiningQuarry)
                        {
                            message = $"{e.ShortPrefabName} is too close {e.transform.position}";
                            cacheType = CacheType.Delete;
                            break;
                        }
                    }
                    else
                    {
                        message = $"Blocked by {e.ShortPrefabName} {e.transform.position}";
                        cacheType = CacheType.Temporary;
                        break;
                    }
                }
                else if (collider.gameObject.layer == (int)Layer.World)
                {
                    if (collider.name.Contains("rock_") || collider.name.Contains("formation_", CompareOptions.OrdinalIgnoreCase))
                    {
                        float height = GetRockHeight(collider.transform.position);

                        if (height > 2f)
                        {
                            message = $"Rock is too large {collider.transform.position}";
                            cacheType = CacheType.Delete;
                            break;
                        }
                    }
                    else if (!config.Settings.Management.AllowOnRoads && collider.name.StartsWith("road_"))
                    {
                        message = $"Not allowed on roads {collider.transform.position}";
                        cacheType = CacheType.Delete;
                        break;
                    }
                    else if (collider.name.StartsWith("ice_sheet"))
                    {
                        message = $"Not allowed on ice sheets {collider.transform.position}";
                        cacheType = CacheType.Delete;
                        break;
                    }
                }
                else if (collider.gameObject.layer == (int)Layer.Water)
                {
                    if (!config.Settings.Management.AllowOnRivers && collider.name.StartsWith("River Mesh"))
                    {
                        message = $"Not allowed on rivers {collider.transform.position}";
                        cacheType = CacheType.Delete;
                        break;
                    }
                }
            }

            Pool.FreeList(ref colliders);

            return string.IsNullOrEmpty(message);
        }

        private bool IsAsset(string value)
        {
            foreach (var asset in assets)
            {
                if (value.Contains(asset))
                {
                    return true;
                }
            }

            return false;
        }

        private bool IsInsideBounds(OBB obb, Vector3 worldPos)
        {
            return obb.ClosestPoint(worldPos) == worldPos;
        }

        public static bool IsMonumentPosition(Vector3 target)
        {
            return Instance.Monuments.Any(monument => monument.IsInBounds(target));
        }

        public static bool IsSubmerged(BuildingWaterOptions options, RaidableSpawnLocation rsl)
        {
            if (rsl.WaterHeight - rsl.TerrainHeight > options.WaterDepth)
            {
                if (!options.AllowSubmerged)
                {
                    return true;
                }

                rsl.Location.y = rsl.WaterHeight;
            }

            return !options.AllowSubmerged && options.SubmergedAreaCheck && IsSubmerged(options, rsl, rsl.Radius);
        }

        private static bool IsSubmerged(BuildingWaterOptions options, RaidableSpawnLocation rsl, float radius)
        {
            if (rsl.Surroundings.Count == 0)
            {
                rsl.Surroundings = GetCircumferencePositions(rsl.Location, radius, 90f, false, 1f);
            }

            foreach (var vector in rsl.Surroundings)
            {
                float w = TerrainMeta.WaterMap.GetHeight(vector);
                float h = TerrainMeta.HeightMap.GetHeight(vector);

                if (w - h > options.WaterDepth)
                {
                    return true;
                }
            }

            return false;
        }

        public static bool IsInBounds(OBB obb, Vector3 worldPos)
        {
            return obb.ClosestPoint(worldPos) == worldPos;
        }

        private static bool IsValidLocation(Vector3 vector, float radius, float md, bool seabed)
        {
            CacheType cacheType;
            string message;
            if (!IsAreaSafe(vector, radius, Layers.Mask.World | Layers.Mask.Deployed | Layers.Mask.Trigger, null, out cacheType, out message))
            {
                return false;
            }

            foreach (var zone in Instance.managedZones)
            {
                if (zone.Size != Vector3.zero)
                {
                    if (IsInBounds(zone.OBB, vector))
                    {
                        return false;
                    }
                }
                else if (InRange(zone.Position, vector, zone.Distance))
                {
                    return false;
                }
            }

            if (!seabed && InDeepWater(vector, 5f))
            {
                return false;
            }

            if (IsMonumentPosition(vector) || ContainsTopology(TerrainTopology.Enum.Monument, vector, md))
            {
                return false;
            }

            if (!config.Settings.Management.AllowOnBuildingTopology && ContainsTopology(TerrainTopology.Enum.Building, vector, Constants.RADIUS))
            {
                return false;
            }

            if (!config.Settings.Management.AllowOnRivers && ContainsTopology(TerrainTopology.Enum.River | TerrainTopology.Enum.Riverside, vector, Constants.RADIUS))
            {
                return false;
            }

            if (!config.Settings.Management.AllowOnRoads && ContainsTopology(TerrainTopology.Enum.Road | TerrainTopology.Enum.Roadside, vector, Constants.RADIUS))
            {
                return false;
            }

            return true;
        }

        private bool SpawnsFileValid(string spawnsFile)
        {
            if (Spawns == null || !Spawns.IsLoaded || string.IsNullOrEmpty(spawnsFile))
            {
                return false;
            }

            if (!FileExists($"SpawnsDatabase{Path.DirectorySeparatorChar}{spawnsFile}"))
            {
                return false;
            }

            return Spawns?.Call("GetSpawnsCount", spawnsFile) is int;
        }

        private void StartAutomation()
        {
            if (scheduleEnabled)
            {
                if (data.RaidTime != DateTime.MinValue.ToString() && GetRaidTime() > config.Settings.Schedule.IntervalMax) // Allows users to lower max event time
                {
                    data.RaidTime = DateTime.MinValue.ToString();
                    data.Save();
                }

                StartScheduleCoroutine();
            }

            StartMaintainCoroutine();
        }

        private void StopGridCoroutine()
        {
            if (gridCoroutine != null)
            {
                ServerMgr.Instance.StopCoroutine(gridCoroutine);
                gridCoroutine = null;
            }
        }
        #endregion

        #region Paste

        private List<RandomBase> Locations { get; set; } = new List<RandomBase>();

        protected bool IsGridLoading
        {
            get
            {
                return gridCoroutine != null;
            }
        }

        protected bool IsPasteAvailable
        {
            get
            {
                foreach (var raid in Raids.Values)
                {
                    if (raid.IsLoading)
                    {
                        return false;
                    }
                }

                return true;
            }
        }

        private static bool FileExists(string file)
        {
            if (!file.Contains(Path.DirectorySeparatorChar))
            {
                bool exists = Interface.Oxide.DataFileSystem.ExistsDatafile($"copypaste{Path.DirectorySeparatorChar}{file}");

                if (exists)
                {
                    Backbone.Plugin.AnyFileExists = true;
                }

                return exists;
            }

            return Interface.Oxide.DataFileSystem.ExistsDatafile(file);
        }

        private static List<Vector3> GetCircumferencePositions(Vector3 center, float radius, float next, bool spawnHeight, float y = 0f)
        {
            var positions = new List<Vector3>();

            if (next < 1f)
            {
                next = 1f;
            }

            float angle = 0f;
            float angleInRadians = 2 * (float)Math.PI;

            while (angle < 360)
            {
                float radian = (angleInRadians / 360) * angle;
                float x = center.x + radius * (float)Math.Cos(radian);
                float z = center.z + radius * (float)Math.Sin(radian);
                var a = new Vector3(x, 0f, z);

                a.y = y == 0f ? spawnHeight ? GetSpawnHeight(a) : TerrainMeta.HeightMap.GetHeight(a) : y;

                if (a.y < -48f)
                {
                    a.y = -48f;
                }

                positions.Add(a);
                angle += next;
            }

            return positions;
        }

        private static bool IsBuildingAllowed(RaidableType type, RaidableMode requestedMode, RaidableMode buildingMode, bool allowPVP)
        {
            if (requestedMode != RaidableMode.Random && buildingMode != requestedMode)
            {
                return false;
            }

            return true;
        }

        private static bool IsProfileValid(KeyValuePair<string, BaseProfile> profile)
        {
            if (string.IsNullOrEmpty(profile.Key) || profile.Value == null || profile.Value.Options == null)
            {
                return false;
            }

            return true;
        }

        private float DegreeToRadian(float angle)
        {
            return angle.Equals(0f) ? 0f : (float)(Math.PI * angle / 180.0f);
        }

        private KeyValuePair<string, BaseProfile> GetBuilding(RaidableType type, RaidableMode mode, string baseName)
        {
            var list = new List<KeyValuePair<string, BaseProfile>>();
            bool isBaseNull = string.IsNullOrEmpty(baseName);
            string last = "Start of selection";

            foreach (var profile in Buildings.Profiles)
            {
                if (MustExclude(type, profile.Value.Options.AllowPVP) || !IsBuildingAllowed(type, mode, RaidableMode.Normal, profile.Value.Options.AllowPVP))
                {
                    last = "Profile excluded or building not allowed";
                    continue;
                }

                if (FileExists(profile.Key) && Cycle.CanSpawn(type, mode, profile.Key))
                {
                    if (isBaseNull)
                    {
                        list.Add(profile);
                    }
                    else if (profile.Key.Equals(baseName, StringComparison.OrdinalIgnoreCase))
                    {
                        return profile;
                    }
                }
                else last = $"Profile {profile.Key} either does not exist, or cannot be spawned again yet.";

                foreach (var extra in profile.Value.Options.AdditionalBases)
                {
                    if (!FileExists(extra.Key) || !Cycle.CanSpawn(type, mode, extra.Key))
                    {
                        continue;
                    }
                    else last = $"Additional Base {extra.Key} of {profile.Key} profile either does not exist, or cannot be spawned again yet.";

                    var clone = BaseProfile.Clone(profile.Value);
                    var kvp = new KeyValuePair<string, BaseProfile>(extra.Key, clone);

                    kvp.Value.Options.PasteOptions = new List<PasteOption>(extra.Value);

                    if (isBaseNull)
                    {
                        list.Add(kvp);
                    }
                    else if (extra.Key.Equals(baseName, StringComparison.OrdinalIgnoreCase))
                    {
                        return kvp;
                    }
                }
            }

            if (list.Count == 0)
            {
                if (!AnyFileExists)
                {
                    PrintDebugMessage("No copypaste file in any profile exists?");
                }
                else PrintDebugMessage($"No building was available for random selection of {mode} difficulty for {type} event.");

                PrintDebugMessage($"Last message: {last}");

                return default(KeyValuePair<string, BaseProfile>);
            }

            var random = list.GetRandom();

            return random;
        }

        private string GetDebugMessage(RaidableMode mode, bool validProfile, bool isAdmin, string id, string baseName, BuildingOptions options, string message)
        {
            if (options != null)
            {
                if (!options.Enabled)
                {
                    return Backbone.GetMessageEx("Profile Not Enabled", id, baseName);
                }
            }

            if (!validProfile)
            {
                if (!string.IsNullOrEmpty(baseName))
                {
                    if (!FileExists(baseName))
                    {
                        return Backbone.GetMessageEx("FileDoesNotExist", id);
                    }
                    else if (!Buildings.Profiles.ContainsKey(baseName))
                    {
                        return Backbone.GetMessageEx("BuildingNotConfigured", id);
                    }
                }

                if (!string.IsNullOrEmpty(message))
                {
                    return message;
                }
                else return Backbone.GetMessageEx("NoBuildingsConfigured", id);
            }

            return Backbone.GetMessageEx("CannotFindPosition", id);
        }

        private Vector3 GetEventPosition(BuildingOptions options, bool checkTerrain, RaidableSpawns spawns, RaidableType type, out string message)
        {
            spawns.Check();

            message = null;

            int attempts = 1000;
            float typeDistance = GetDistance(type);
            float protectionRadius = options.ProtectionRadius;
            float safeRadius = Mathf.Max(options.ArenaWalls.Radius, protectionRadius);
            float buildRadius = Mathf.Max(config.Settings.Management.CupboardDetectionRadius, options.ArenaWalls.Radius, protectionRadius) + 5f;
            int layers = Layers.Mask.Player_Server | Layers.Mask.Construction | Layers.Mask.Deployed | Layers.Mask.Ragdoll;

            CacheType cacheType;

            while (spawns.Count > 0 && --attempts > 0)
            {
                var rsl = spawns.GetRandom(options.Water);

                var vector = rsl.Location;

                if (options.Setup.ForcedHeight != -1)
                {
                    vector.y = options.Setup.ForcedHeight;
                }
                else vector.y += options.Setup.PasteHeightAdjustment;

                if (typeDistance > 0 && RaidableBase.IsTooClose(vector, typeDistance))
                {
                    message = "Too close to another raidable base";
                    continue;
                }

                if (type == RaidableType.Maintained && spawns.IsCustomSpawn && (config.Settings.Maintained.Ignore || config.Settings.Maintained.SafeRadius > 0f))
                {
                    if (config.Settings.Maintained.SafeRadius <= 0f)
                    {
                        message = $"Ignoring safe checks enabled for maintained events; returning {vector}";
                        return vector;
                    }
                    else safeRadius = config.Settings.Maintained.SafeRadius;
                }

                if (type == RaidableType.Scheduled && spawns.IsCustomSpawn && (config.Settings.Schedule.Ignore || config.Settings.Schedule.SafeRadius > 0f))
                {
                    if (config.Settings.Schedule.SafeRadius <= 0f)
                    {
                        message = $"Ignoring safe checks enabled for scheduled events; returning {vector}";
                        return vector;
                    }
                    else safeRadius = config.Settings.Schedule.SafeRadius;
                }

                if (!spawns.IsCustomSpawn && options.Setup.ForcedHeight == -1f && options.Water.Seabed <= 0f && IsSubmerged(options.Water, rsl))
                {
                    continue;
                }

                if (!spawns.IsCustomSpawn && GetBuildingPrivilege(vector, buildRadius) != Vector3.zero)
                {
                    spawns.RemoveNear(vector, buildRadius, CacheType.Privilege, type);

                    continue;
                }

                if (!IsAreaSafe(vector, safeRadius, layers, spawns.IsCustomSpawn, out cacheType, out message, type))
                {
                    if (cacheType == CacheType.Delete)
                    {
                        spawns.Remove(rsl, cacheType);
                    }
                    else spawns.RemoveNear(vector, safeRadius / 2f, cacheType, type);

                    if (string.IsNullOrEmpty(message))
                    {
                        message = "Failed safe check; trying again...";
                    }

                    continue;
                }

                if (!spawns.IsCustomSpawn && IsObstructed(vector, protectionRadius, options.Elevation, options.Setup.ForcedHeight))
                {
                    spawns.RemoveNear(vector, protectionRadius / 2f, CacheType.Temporary, type);

                    continue;
                }

                return vector;
            }

            spawns.TryAddRange();

            if (message == null)
            {
                message = Backbone.GetMessageEx("CannotFindPosition");
            }

            return Vector3.zero;
        }

        public static bool IsObstructed(Vector3 target, float radius, float elevation, float forcedHeight, BasePlayer player = null)
        {
            float f = radius * 0.2f;
            int n = 5;
            bool flag = false;

            if (forcedHeight != -1)
            {
                elevation += forcedHeight;
            }

            while (n-- > 0)
            {
                float step = f * n;
                float next = 360f / step;

                foreach (var a in GetCircumferencePositions(target, step, next, true, 0f))
                {
                    if (Mathf.Abs(a.y - target.y) > elevation)
                    {
                        if (player.IsValid()) player.SendConsoleCommand("ddraw.text", 15f, Color.red, a, "X");
                        flag = true;
                    }
                }
            }

            return flag;
        }

        private List<string> GetListedOptions(List<PasteOption> options)
        {
            var list = new List<string>();
            bool flag1 = false, flag2 = false, flag3 = false, flag4 = false, flag5 = false;

            for (int i = 0; i < options.Count; i++)
            {
                string key = options[i].Key.ToLower();
                string value = options[i].Value.ToLower();

                if (key == "stability")
                {
                    flag1 = true;
                    value = "false";
                }
                if (key == "autoheight")
                {
                    flag2 = true;
                }
                if (key == "height")
                {
                    flag3 = true;
                }
                if (key == "entityowner")
                {
                    flag4 = true;
                    value = "false";
                }
                if (key == "auth")
                {
                    flag5 = true;
                    value = "false";
                }

                list.Add(key);
                list.Add(value);
            }

            if (!flag1)
            {
                list.Add("stability");
                list.Add("false");
            }

            if (!flag2)
            {
                list.Add("autoheight");
                list.Add("false");
            }

            if (!flag3)
            {
                list.Add("height");
                list.Add("1.0");
            }

            if (!flag4)
            {
                list.Add("entityowner");
                list.Add("false");
            }

            if (!flag5)
            {
                list.Add("auth");
                list.Add("false");
            }

            return list;
        }

        private RaidableSpawns GetSpawns(RaidableType type, out bool checkTerrain)
        {
            RaidableSpawns spawns;
            checkTerrain = false;

            switch (type)
            {
                case RaidableType.Maintained:
                {
                    if (raidSpawns.TryGetValue(RaidableType.Maintained, out spawns))
                    {
                        return spawns;
                    }
                    break;
                }
                case RaidableType.Manual:
                {
                    if (raidSpawns.TryGetValue(RaidableType.Manual, out spawns))
                    {
                        return spawns;
                    }
                    break;
                }
                case RaidableType.Scheduled:
                {
                    if (raidSpawns.TryGetValue(RaidableType.Scheduled, out spawns))
                    {
                        return spawns;
                    }
                    break;
                }
            }

            checkTerrain = true;
            return raidSpawns.TryGetValue(RaidableType.Grid, out spawns) ? spawns : null;
        }


        private Elevation GetTerrainElevation(Vector3 center, float radius)
        {
            float maxY = -1000;
            float minY = 1000;

            foreach (var position in GetCircumferencePositions(center, radius, 30f, true, 0f)) // 70 to 30 in 1.5.1
            {
                if (position.y > maxY) maxY = position.y;
                if (position.y < minY) minY = position.y;
            }

            return new Elevation
            {
                Min = minY,
                Max = maxY
            };
        }

        private bool IsFlatTerrain(Vector3 center, Elevation elevation, float value)
        {
            return elevation.Max - elevation.Min <= value && elevation.Max - center.y <= value;
        }

        private void OnPasteFinished(List<BaseEntity> pastedEntities, string fileName)
        {
            if (pastedEntities == null || pastedEntities.Count == 0 || !Locations.Exists(x => x.BaseName == fileName))
            {
                return;
            }

            var bmgs = BMGELEVATOR.FixElevators(pastedEntities);
            Timer t = null;
            int repeat = 120;

            t = timer.Repeat(1f, 0, () =>
            {
                if (--repeat <= 0)
                {
                    RaidableBase.IsSpawning = false;
                    return;
                }

                if (IsUnloading)
                {
                    return;
                }

                pastedEntities.RemoveAll(e => e == null || e.IsDestroyed);

                var raid = RaidableBase.Get(pastedEntities);

                if (raid == null)
                {
                    return;
                }

                int baseIndex = UnityEngine.Random.Range(1, 9999999);

                while (Bases.ContainsKey(baseIndex))
                {
                    baseIndex = UnityEngine.Random.Range(1, 9999999);
                }

                Bases[baseIndex] = pastedEntities;
                raid.SetEntities(baseIndex, pastedEntities, bmgs);
                t.Destroy();
            });
        }

        private bool PasteBuilding(RaidableType type, Vector3 position, KeyValuePair<string, BaseProfile> profile, RaidableSpawns spawns, out string message)
        {
            if (profile.Value.Options.Water.Seabed > 0f && profile.Value.Options.Water.SpawnOnSeabed)
            {
                var h = TerrainMeta.HeightMap.GetHeight(position);

                if (TerrainMeta.WaterMap.GetHeight(position) > h)
                {
                    position.y = h;
                }
            }
            else if (profile.Value.Options.Setup.ForcedHeight != -1)
            {
                position.y = profile.Value.Options.Setup.ForcedHeight;
            }
            else if (profile.Value.Options.Setup.Recalculate)
            {
                int pasteLayer = LayerMask.GetMask("Construction", "Deployed", "Tree", "Terrain", "World", "Water", "Prevent Building");
                RaycastHit hit;
                if (Physics.Raycast(position + new Vector3(0f, 200f, 0f), Vector3.down, out hit, 500f, pasteLayer))
                {
                    position = hit.point;
                }
            }

            position.y += profile.Value.Options.Setup.PasteHeightAdjustment;

            if (Locations.Exists(x => x.Position == position))
            {
                message = $"{position} has a base already.";
                return false;
            }

            LoadingTimes[position] = Time.time;

            int uid;

            do
            {
                uid = UnityEngine.Random.Range(1000, 100000);
            } while (Raids.ContainsKey(uid));

            var distance = spawns == null ? profile.Value.Options.ProtectionRadius : spawns.RemoveNear(position, profile.Value.Options.ProtectionRadius, CacheType.Generic, type);

            var callback = new Action(() =>
            {
                RaidableBase raid;
                if (TryOpenEvent(type, position, uid, profile.Key, profile.Value, out raid))
                {
                    Cycle.Add(type, RaidableMode.Normal, profile.Key);

                    raid.spawns = spawns;
                    raid.RemoveNearDistance = distance;
                }
                else
                {
                    Locations.RemoveAll(e => e.Position == position);
                    RaidableBase.IsSpawning = false;

                    if (spawns == null)
                    {
                        return;
                    }

                    spawns.AddNear(position, distance, CacheType.Generic, false);
                }
            });

            List<PasteOption> options = profile.Value.Options.PasteOptions;

            foreach (var kvp in profile.Value.Options.AdditionalBases)
            {
                if (kvp.Key.Equals(profile.Key, StringComparison.OrdinalIgnoreCase))
                {
                    options = kvp.Value;
                    break;
                }
            }

            var list = GetListedOptions(options);

            Locations.Add(new RandomBase
            {
                Position = position,
                Type = type,
                BaseName = profile.Key,
                Profile = profile.Value,
            });

            Subscribe(nameof(OnEntitySpawned));

            CopyPaste.Call("TryPasteFromVector3", position, 0f, profile.Key, list.ToArray(), callback);

            message = $"{profile.Key} trying to paste at {position}";

            return true;
        }

        private bool PrintDebugMessage(string message)
        {
            if (debugMode && !string.IsNullOrEmpty(message))
            {
                Puts("DEBUG: {0}", message);

                return true;
            }

            return false;
        }

        private RandomBase SpawnRandomBase(out string message, RaidableType type, RaidableMode mode, string baseName = null, bool isAdmin = false, string userid = null)
        {
            lastSpawnRequestTime = Time.realtimeSinceStartup;

            if (RaidableBase.IsSpawning)
            {
                message = "Base is spawning already.";
                return null;
            }

            message = string.Empty;

            var profile = GetBuilding(type, mode, baseName);
            bool checkTerrain, validProfile = IsProfileValid(profile);
            var spawns = GetSpawns(type, out checkTerrain);

            if (validProfile && spawns != null)
            {
                var eventPos = GetEventPosition(profile.Value.Options, checkTerrain, spawns, type, out message);

                if (eventPos != Vector3.zero && PasteBuilding(type, eventPos, profile, spawns, out message))
                {
                    RaidableBase.IsSpawning = true;
                    message = $"Pasting building {profile.Key} at {eventPos}";

                    return new RandomBase
                    {
                        BaseName = profile.Key,
                        Profile = profile.Value,
                        Position = eventPos,
                        Type = type
                    };
                }
            }

            if (type == RaidableType.Maintained || type == RaidableType.Scheduled)
            {
                PrintDebugMessage(message);

                return null;
            }

            var debug = GetDebugMessage(mode, validProfile, isAdmin, userid, baseName, profile.Value?.Options, message);

            if (!string.IsNullOrEmpty(message) && debug != message)
            {
                message = $"{message} : {debug}";
            }
            else message = debug;

            return null;
        }

        #endregion

        #region Commands

        protected void DrawRaidLocations(BasePlayer player, bool hasPerm)
        {
            foreach (var raid in Raids.Values)
            {
                if (InRange(raid.Location, player.transform.position, 100f))
                {
                    Player.Message(player, string.Format("{0} @ {1} ({2})", raid.BaseName, raid.Location, PositionToGrid(raid.Location)));
                }
            }

            if (!hasPerm)
            {
                return;
            }

            bool isAdmin = player.IsAdmin;

            try
            {
                ToggleAdminFlag(player, isAdmin, true);

                foreach (var raid in Raids.Values)
                {
                    int num = 0;

                    foreach (var t in BasePlayer.activePlayerList)
                    {
                        if (IsValid(t) && t.Distance(raid.Location) <= raid.Options.ProtectionRadius * 3f)
                        {
                            num++;
                        }
                    }

                    int distance = Mathf.CeilToInt(Vector3.Distance(player.transform.position, raid.Location));
                    string message = string.Format(lang.GetMessage("RaidMessage", this, player.UserIDString), distance, num);
                    string flag = Backbone.GetMessageEx(raid.AllowPVP ? "PVPFlag" : "PVEFlag", player.UserIDString);

                    player.SendConsoleCommand("ddraw.text", 15f, Color.yellow, raid.Location, string.Format("{0} : {1}{2} {3}", raid.BaseName, flag, raid.Mode(), message));

                    foreach (var target in raid.friends)
                    {
                        if (IsValid(target))
                        {
                            player.SendConsoleCommand("ddraw.text", 15f, Color.yellow, target.transform.position, "Ally");
                        }
                    }

                    if (IsValid(raid.owner))
                    {
                        player.SendConsoleCommand("ddraw.text", 15f, Color.yellow, raid.owner.transform.position, "Owner");
                    }
                }
            }
            catch (Exception ex)
            {
                Puts(ex.StackTrace);
                Puts(ex.Message);
            }
            finally
            {
                ToggleAdminFlag(player, isAdmin, false);
            }
        }

        protected List<KeyValuePair<string, int>> GetLadder(string arg)
        {
            var ladder = new List<KeyValuePair<string, int>>();
            bool isLadder = arg.ToLower() == "ladder";

            foreach (var entry in data.Players)
            {
                int value = isLadder ? entry.Value.Raids : entry.Value.TotalRaids;

                if (value > 0)
                {
                    ladder.Add(new KeyValuePair<string, int>(entry.Key, value));
                }
            }

            return ladder;
        }

        protected void ProcessConsoleCommand(IPlayer user, BasePlayer player, bool isAllowed, string[] args) // rbevent
        {
            if (IsGridLoading && !user.IsAdmin)
            {
                int count = raidSpawns.ContainsKey(RaidableType.Grid) ? raidSpawns[RaidableType.Grid].Count : 0;
                user.Reply(Backbone.GetMessageEx("GridIsLoadingFormatted", user.Id, (Time.realtimeSinceStartup - gridTime).ToString("N02"), count));
            }

            string message;
            string baseName = args.FirstOrDefault(value => FileExists(value));
            RaidableMode mode = RaidableMode.Normal;
            RandomBase randomBase = SpawnRandomBase(out message, RaidableType.Manual, mode, baseName, isAllowed, null);

            if (randomBase == null)
            {
                user.Reply(message);
            }
            else if (isAllowed && user.IsConnected)
            {
                user.Teleport(randomBase.Position.x, randomBase.Position.y, randomBase.Position.z);
            }
        }

        protected void ProcessEventCommand(BasePlayer player, IPlayer p, string[] args, bool isAllowed) // rbe
        {
            if (!isAllowed || !player.IsValid())
            {
                return;
            }

            string baseName = null;

            if (args.Length > 0)
            {
                for (int i = 0; i < args.Length; i++)
                {
                    if (string.IsNullOrEmpty(baseName) && FileExists(args[i])) baseName = args[i];
                }
            }

            var profile = GetBuilding(RaidableType.Manual, RaidableMode.Normal, baseName);
            string message = null;

            if (IsProfileValid(profile))
            {
                RaycastHit hit;
                int layers = Layers.Mask.Construction | Layers.Mask.Default | Layers.Mask.Deployed | Layers.Mask.Tree | Layers.Mask.Terrain | Layers.Mask.Water | Layers.Mask.World;
                if (Physics.Raycast(player.eyes.HeadRay(), out hit, isAllowed ? Mathf.Infinity : 100f, layers, QueryTriggerInteraction.Ignore))
                {
                    CacheType cacheType;                    
                    var position = hit.point;
                    int layers2 = Layers.Mask.Player_Server | Layers.Mask.Construction | Layers.Mask.Deployed;
                    var safe = player.IsAdmin || IsAreaSafe(position, Mathf.Max(Constants.RADIUS * 2f, profile.Value.Options.ArenaWalls.Radius), layers2, null, out cacheType, out message, RaidableType.Manual);

                    if (!safe && !player.IsFlying && InRange(player.transform.position, position, 50f, false))
                    {
                        p.Reply(Backbone.GetMessage("PasteIsBlockedStandAway", p.Id));
                        return;
                    }

                    if (safe && (isAllowed || !IsMonumentPosition(position)))
                    {
                        var spawns = raidSpawns.FirstOrDefault(x => x.Value.Spawns.Any(y => InRange(y.Location, hit.point, Constants.RADIUS))).Value;
                        if (PasteBuilding(RaidableType.Manual, hit.point, profile, spawns, out message))
                        {
                            if (player.IsAdmin)
                            {
                                player.SendConsoleCommand("ddraw.text", 10f, Color.red, hit.point, "XXX");
                            }
                        }
                    }
                    else p.Reply(Backbone.GetMessage("PasteIsBlocked", p.Id));

                    if (!string.IsNullOrEmpty(message))
                    {
                        p.Reply(message);
                    }
                }
                else p.Reply(Backbone.GetMessage("LookElsewhere", p.Id));
            }
            else
            {
                if (profile.Value == null)
                {
                    p.Reply(Backbone.GetMessage("BuildingNotConfigured", p.Id));
                }
                else p.Reply(GetDebugMessage(RaidableMode.Normal, false, true, p.Id, profile.Key, profile.Value.Options, message));
            }
        }

        protected void ShowGrid(BasePlayer player)
        {
            bool isAdmin = player.IsAdmin;

            try
            {
                RaidableSpawns spawns;
                if (!raidSpawns.TryGetValue(RaidableType.Grid, out spawns))
                {
                    return;
                }

                ToggleAdminFlag(player, isAdmin, true);

                foreach (var rsl in spawns.Active)
                {
                    if (InRange(rsl.Location, player.transform.position, 1000f))
                    {
                        player.SendConsoleCommand("ddraw.text", 30f, Color.green, rsl.Location, "X");
                    }
                }

                foreach (CacheType cacheType in Enum.GetValues(typeof(CacheType)))
                {
                    var color = cacheType == CacheType.Generic ? Color.red : cacheType == CacheType.Temporary ? Color.cyan : cacheType == CacheType.Privilege ? Color.yellow : Color.blue;
                    var text = cacheType == CacheType.Generic ? "X" : cacheType == CacheType.Temporary ? "C" : cacheType == CacheType.Privilege ? "TC" : "W";

                    foreach (var rsl in spawns.Inactive(cacheType))
                    {
                        if (InRange(rsl.Location, player.transform.position, 1000f))
                        {
                            player.SendConsoleCommand("ddraw.text", 30f, color, rsl.Location, text);
                        }
                    }
                }

                foreach (var monument in Instance.Monuments)
                {
                    player.SendConsoleCommand("ddraw.sphere", 30f, Color.blue, monument.transform.position, monument.bounds.size.Max());
                    player.SendConsoleCommand("ddraw.text", 30f, Color.cyan, monument.transform.position, monument.translated);
                }
            }
            catch (Exception ex)
            {
                Puts("{0}", ex);
            }
            finally
            {
                ToggleAdminFlag(player, isAdmin, false);
            }
        }

        private static void ToggleAdminFlag(BasePlayer player, bool isAdmin, bool state)
        {
            if (!isAdmin)
            {
                player.SetPlayerFlag(BasePlayer.PlayerFlags.IsAdmin, state);
                player.SendNetworkUpdateImmediate();
            }
        }

        protected void Prodigy(BasePlayer player, string[] args)
        {
            if (!player.IsValid() || !(player.IsAdmin || permission.UserHasPermission(player.UserIDString, adminPermission)))
            {
                Backbone.Message(player, "No Permission");
                return;
            }

            RaycastHit hit;
            if (!Physics.Raycast(player.eyes.HeadRay(), out hit, 5f, -1, QueryTriggerInteraction.Ignore))
            {
                Backbone.Message(player, "LookElsewhere");
                return;
            }

            var entity = hit.GetEntity();

            if (entity == null)
            {
                Backbone.Message(player, "LookElsewhere");
                return;
            }

            _sb.Length = 0;
            _sb.AppendLine(string.Format("Owner ID: {0}", entity.OwnerID));
            _sb.AppendLine(string.Format("RaidEntities: {0}", GarbageController.RaidEntities.ContainsKey(entity)));
            _sb.AppendLine(string.Format("EventTerritory: {0}", EventTerritory(entity.transform.position)));

            var raid = Raids.Values.FirstOrDefault(x => x.Entities.Contains(entity));

            if (raid == null)
            {
                _sb.AppendLine("Unknown entity");
            }
            else
            {
                if (raid.BuiltList.Contains(entity.net.ID))
                {
                    _sb.AppendLine("Entity was built by a player after the base spawned.");
                }

                _sb.AppendLine(string.Format("Distance From Raid: {0}m", (raid.Location - entity.transform.position).magnitude));
            }

            Player.Message(player, _sb.ToString());
            _sb.Length = 0;
        }

        protected void ShowLadder(IPlayer p, string[] args)
        {
            if (!config.RankedLadder.Enabled || config.RankedLadder.Top < 1) 
            {
                return;
            }

            if (data.Players.Count == 0)
            {
                p.Reply(Backbone.GetMessage("Ladder Insufficient Players", p.Id));
                return;
            }

            if (args.Length == 2 && args[1].ToLower() == "resetme" && data.Players.ContainsKey(p.Id))
            {
                data.Players[p.Id].Raids = 0;
                return;
            }

            string key = args[0].ToLower();
            var ladder = GetLadder(key);
            int rank = 0;

            ladder.Sort((x, y) => y.Value.CompareTo(x.Value));

            p.Reply(Backbone.GetMessage(key == "ladder" ? "RankedLadder" : "RankedTotal", p.Id, config.RankedLadder.Top));

            foreach (var kvp in ladder.Take(config.RankedLadder.Top))
            {
                NotifyPlayer(p, rank, kvp);
            }

            ladder.Clear();
        }

        protected void ShowNextScheduledEvent(IPlayer p)
        {
            string message;
            double time = GetRaidTime();

            if (BasePlayer.activePlayerList.Count < config.Settings.Schedule.PlayerLimit)
            {
                message = Backbone.GetMessage("Not Enough Online", p.Id, config.Settings.Schedule.PlayerLimit);
            }
            else message = FormatTime(time);

            p.Reply(Backbone.GetMessage("Next", p.Id, message));
        }

        private bool CanCommandContinue(BasePlayer player, IPlayer p, string[] args, bool isAllowed)
        {
            if (HandledCommandArguments(player, p, isAllowed, args))
            {
                return false;
            }

            if (!IsCopyPasteLoaded(player))
            {
                return false;
            }

            if (!isAllowed && RaidableBase.Get(RaidableType.Manual) >= config.Settings.Manual.Max)
            {
                p.Reply(Backbone.GetMessage("Max Manual Events", p.Id, config.Settings.Manual.Max));
                return false;
            }

            if (!IsPasteAvailable && !p.IsAdmin)
            {
                p.Reply(Backbone.GetMessage("PasteOnCooldown", p.Id));
                return false;
            }

            if (!p.IsAdmin && IsSpawnOnCooldown())
            {
                p.Reply(Backbone.GetMessage("SpawnOnCooldown", p.Id));
                return false;
            }

            if (!isAllowed && BaseNetworkable.serverEntities.Count > 300000)
            {
                p.Reply(lang.GetMessage("EntityCountMax", this, p.Id));
                return false;
            }

            return true;
        }

        private void CommandConfig(IPlayer p, string command, string[] args)
        {
            if (!IsValid(args))
            {
                p.Reply(string.Format(lang.GetMessage("ConfigUseFormat", this, p.Id), string.Join("|", arguments.ToArray())));
                return;
            }

            switch (args[0].ToLower())
            {
                case "add":
                    {
                        ConfigAddBase(p, args);
                        return;
                    }
                case "remove":
                case "clean":
                    {
                        ConfigRemoveBase(p, args);
                        return;
                    }
                case "list":
                    {
                        ConfigListBases(p);
                        return;
                    }
            }
        }

        private void CommandPopulate(IPlayer p, string command, string[] args)
        {
            var lootList = new List<TreasureItem>();

            foreach (var def in ItemManager.GetItemDefinitions())
            {
                lootList.Add(new TreasureItem
                {
                    shortname = def.shortname
                });
            }

            lootList.Sort((x, y) => x.shortname.CompareTo(y.shortname));

            p.Reply("Created Editable_Lists/Normal.json");

            string file = $"{Name}{Path.DirectorySeparatorChar}Editable_Lists{Path.DirectorySeparatorChar}Normal";
            Interface.Oxide.DataFileSystem.WriteObject(file, lootList);

            SaveConfig();
        }

        private void CommandRaidBase(IPlayer user, string command, string[] args)
        {
            var player = user.Object as BasePlayer;
            bool isAllowed = user.IsServer || player.IsAdmin || user.HasPermission(adminPermission);

            if (!CanCommandContinue(player, user, args, isAllowed))
            {
                return;
            }

            if (command == config.Settings.EventCommand) // rbe
            {
                ProcessEventCommand(player, user, args, isAllowed);
            }
            else if (command == config.Settings.ConsoleCommand) // rbevent
            {
                ProcessConsoleCommand(user, player, isAllowed, args);
            }
        }

        private void CommandRaidHunter(IPlayer p, string command, string[] args)
        {
            var player = p.Object as BasePlayer;
            bool isAdmin = p.IsServer || player.IsAdmin;
            string arg = args.Length >= 1 ? args[0].ToLower() : string.Empty;

            switch (arg)
            {
                case "version":
                    p.Reply(Version.ToString());
                    return;
                case "resettotal":
                    if (isAdmin)
                    {
                        foreach (var entry in data.Players)
                        {
                            entry.Value.TotalRaids = 0;
                        }

                        data.Save();
                    }

                    return;
                case "resettime":
                    if (isAdmin)
                    {
                        data.RaidTime = DateTime.MinValue.ToString();
                        data.Save();
                    }

                    return;
                case "wipe":
                    if (isAdmin)
                    {
                        wiped = true;
                        CheckForWipe();
                    }

                    return;
                case "grid?":
                    if (player.IsValid())
                    {
                        p.Reply(PositionToGrid(player.transform.position));
                    }

                    return;
                case "grid":
                    if (player.IsValid() && (isAdmin || HasPermission(player, drawPermission)))
                    {
                        ShowGrid(player);
                    }

                    return;
                case "savefix":
                    if (p.IsAdmin)
                    {
                        if (SaveRestore.IsSaving)
                        {
                            SaveRestore.IsSaving = false;
                            p.Reply("Server save has been canceled. You must type server.save again.");
                        }
                        else p.Reply("Server is not saving.");
                    }

                    return;
                case "ui":
                    CommandUI(p, command, args.Skip(1).ToArray());
                    return;
                case "ladder":
                case "lifetime":
                    ShowLadder(p, args);
                    return;
                case "prod":
                    Prodigy(player, args);
                    return;
            }

            if (config.UI.Enabled)
            {
                p.Reply(Backbone.GetMessage("UIHelpText", p.Id, command));
            }

            if (config.RankedLadder.Enabled)
            {
                p.Reply(Backbone.GetMessage("Wins", p.Id, data.Players.ContainsKey(p.Id) ? data.Players[p.Id].Raids : 0, config.Settings.HunterCommand));
            }

            if (Raids.Count == 0 && scheduleEnabled)
            {
                ShowNextScheduledEvent(p);
                return;
            }

            if (!player.IsValid())
            {
                return;
            }

            DrawRaidLocations(player, isAdmin || HasPermission(player, drawPermission));
        }

        private void CommandReloadConfig(IPlayer p, string command, string[] args)
        {
            if (p.IsServer || (p.Object as BasePlayer).IsAdmin)
            {
                if (!IsPasteAvailable)
                {
                    p.Reply(Backbone.GetMessage("PasteOnCooldown", p.Id));
                    return;
                }

                p.Reply(Backbone.GetMessage("ReloadConfig", p.Id));
                LoadConfig();
                maintainEnabled = config.Settings.Maintained.Enabled;
                scheduleEnabled = config.Settings.Schedule.Enabled;

                if (maintainCoroutine != null)
                {
                    StopMaintainCoroutine();
                    p.Reply(Backbone.GetMessage("ReloadMaintainCo", p.Id));
                }

                if (scheduleCoroutine != null)
                {
                    StopScheduleCoroutine();
                    p.Reply(Backbone.GetMessage("ReloadScheduleCo", p.Id));
                }

                p.Reply(Backbone.GetMessage("ReloadInit", p.Id));
                Initialize();
            }
        }

        private void CommandToggle(IPlayer p, string command, string[] args)
        {
            if (config.Settings.Maintained.Enabled)
            {
                maintainEnabled = !maintainEnabled;
                p.Reply($"Toggled maintained events {(maintainEnabled ? "on" : "off")}");
            }

            if (config.Settings.Schedule.Enabled)
            {
                scheduleEnabled = !scheduleEnabled;
                p.Reply($"Toggled scheduled events {(scheduleEnabled ? "on" : "off")}");
            }
        }

        private bool HandledCommandArguments(BasePlayer player, IPlayer p, bool isAllowed, string[] args)
        {
            if (args.Length == 0)
            {
                return false;
            }

            if (player.IsValid())
            {
                if (args[0].ToLower() == "despawn")
                {
                    if (isAllowed)
                    {
                        bool success = DespawnBase(player);
                        Backbone.Message(player, success ? "DespawnBaseSuccess" : "DespawnBaseNoneAvailable");
                        if (success) Puts(Backbone.GetMessageEx("DespawnedAt", null, player.displayName, FormatGridReference(player.transform.position)));
                        return true;
                    }
                }

                if (!HasPermission(player, drawPermission) && !isAllowed)
                {
                    return false;
                }

                if (args[0].ToLower() == "draw")
                {
                    bool isAdmin = player.IsAdmin;

                    try
                    {
                        ToggleAdminFlag(player, isAdmin, true);

                        foreach (var raid in Raids.Values)
                        {
                            player.SendConsoleCommand("ddraw.sphere", 30f, Color.blue, raid.Location, raid.Options.ProtectionRadius);
                        }
                    }
                    catch (Exception ex)
                    {
                        Puts(ex.StackTrace);
                        Puts(ex.Message);
                    }
                    finally
                    {
                        ToggleAdminFlag(player, isAdmin, false);
                    }

                    return true;
                }
            }

            if (!isAllowed)
            {
                return false;
            }

            switch (args[0].ToLower())
            {
                case "debug":
                    {
                        debugMode = !debugMode;
                        p.Reply(string.Format("Debug mode: {0}", debugMode));
                        p.Reply(string.Format("Scheduled Events Running: {0}", scheduleCoroutine != null));
                        p.Reply(string.Format("Maintained Events Running: {0}", maintainCoroutine != null));
                        return true;
                    }
                case "kill_cleanup":
                {
                    BaseNetworkable.serverEntities.OfType<BaseEntity>().ToList().ForEach(entity =>
                    {
                        if (entity.OwnerID != 0 || entity.Distance(player) > 100f) return;
                        if (entity.name.Contains("building") || entity.name.Contains("deploy")) entity.Kill();
                    });

                    return true;
                }
                case "despawnall":
                case "despawn_inactive":
                    {
                        if (Raids.Count > 0)
                        {
                            DespawnAllBasesNow(args[0].ToLower() == "despawn_inactive");
                            Puts(Backbone.GetMessageEx("DespawnedAll", null, player?.displayName ?? p.Id));
                        }

                        return true;
                    }
                case "active":
                    {
                        int count = 0;

                        foreach (var raid in Raids.Values)
                        {
                            if (raid.intruders.Count > 0 || raid.ownerId.IsSteamId())
                            {
                                Puts("Active raid at {0} in {1}", raid.Location, FormatGridReference(raid.Location));
                                count++;
                            }
                        }

                        Puts("{0} active raids", count);
                        return true;
                    }
                case "setowner":
                case "lockraid":
                    {
                        if (args.Length >= 2)
                        {
                            var target = RustCore.FindPlayer(args[1]);

                            if (IsValid(target))
                            {
                                var raid = GetNearestBase(target.transform.position);

                                if (raid == null)
                                {
                                    p.Reply(Backbone.GetMessage("TargetTooFar", p.Id));
                                }
                                else
                                {
                                    raid.SetOwner(target);
                                    p.Reply(Backbone.GetMessage("RaidLockedTo", p.Id, target.displayName));
                                }
                            }
                            else p.Reply(Backbone.GetMessage("TargetNotFoundId", p.Id, args[1]));
                        }

                        return true;
                    }
                case "clearowner":
                    {
                        if (!player.IsValid()) return true;

                        var raid = GetNearestBase(player.transform.position);

                        if (raid == null)
                        {
                            p.Reply(Backbone.GetMessage("TooFar", p.Id));
                        }
                        else
                        {
                            raid.owner = null;
                            raid.ownerId = 0;
                            raid.friends.Clear();
                            raid.UpdateMarker();
                            p.Reply(Backbone.GetMessage("RaidOwnerCleared", p.Id));
                        }

                        return true;
                    }
            }

            return false;
        }

        private void Initialize()
        {
            Reinitialize();
            BlockZoneManagerZones();
            LoadSpawns();
            SetupGrid();
            CreateDefaultFiles();
            LoadTables();
            LoadProfiles();
            Backbone.InitializeSkins();
        }
        private void NotifyPlayer(IPlayer p, int rank, KeyValuePair<string, int> kvp)
        {
            string name = covalence.Players.FindPlayerById(kvp.Key)?.Name ?? kvp.Key;
            string value = kvp.Value.ToString("N0");
            string message = lang.GetMessage("NotifyPlayerMessageFormat", this, p.Id);

            message = message.Replace("{rank}", rank.ToString());
            message = message.Replace("{name}", name);
            message = message.Replace("{value}", value);

            p.Reply(message);
        }
        #endregion Commands

        #region Helpers

        private void AddEntity(BaseEntity e, RaidableBase raid)
        {
            raid.BuiltList.Add(e.net.ID);

            GarbageController.RaidEntities[e] = raid;

            if (e.name.Contains("assets/prefabs/deployable/"))
            {
                if (config.Settings.Management.DoNotDestroyDeployables)
                {
                    UnityEngine.Object.Destroy(e.GetComponent<DestroyOnGroundMissing>());
                    UnityEngine.Object.Destroy(e.GetComponent<GroundWatch>());
                }
                else raid.Entities.Add(e);
            }
            else if (!config.Settings.Management.DoNotDestroyStructures)
            {
                raid.Entities.Add(e);
            }
        }

        private void BlockHandler(BaseEntity entity, HitInfo hitInfo)
        {
            if (hitInfo == null)
            {
                return;
            }

            var raid = RaidableBase.Get(entity.transform.position);

            if (raid == null || raid.killed)
            {
                return;
            }

            var player = hitInfo.Initiator as BasePlayer;

            if (!player.IsValid())
            {
                return;
            }

            if (raid.AddLooter(player))
            {
                raid.TrySetOwner(player, entity, hitInfo);
            }

            raid.CheckDespawn();
        }

        public void BlockZoneManagerZones()
        {
            if (ZoneManager == null)
            {
                return;
            }

            var zoneIds = ZoneManager?.Call("GetZoneIDs") as string[];

            if (zoneIds == null)
            {
                return;
            }

            managedZones.Clear();

            foreach (string zoneId in zoneIds)
            {
                var zoneLoc = ZoneManager.Call("GetZoneLocation", zoneId);

                if (!(zoneLoc is Vector3))
                {
                    continue;
                }

                var zoneName = Convert.ToString(ZoneManager.Call("GetZoneName", zoneId));

                if (config.Settings.Inclusions.Exists(zone => zone == "*" || zone == zoneId || !string.IsNullOrEmpty(zoneName) && zoneName.Contains(zone, CompareOptions.OrdinalIgnoreCase)))
                {
                    continue;
                }

                var radius = ZoneManager.Call("GetZoneRadius", zoneId);
                var size = ZoneManager.Call("GetZoneSize", zoneId);

                managedZones.Add(new ZoneInfo(zoneLoc, radius, size));
            }

            if (managedZones.Count > 0)
            {
                Puts(Backbone.GetMessageEx("BlockedZones", null, managedZones.Count));
            }
        }

        protected new static void Puts(string format, params object[] args)
        {
            Interface.Oxide.LogInfo("[{0}] {1}", "Raidable Bases", (args.Length != 0) ? string.Format(format, args) : format);
        }

        private void EntityHandler(StorageContainer container, HitInfo hitInfo)
        {
            var raid = RaidableBase.Get(container);

            if (raid == null || !raid.IsOpened)
            {
                return;
            }

            if (IsLootingWeapon(hitInfo))
            {
                var player = raid.GetInitiatorPlayer(hitInfo, container);

                if (player.IsValid())
                {
                    raid.AddLooter(player);
                }
            }

            DropOrRemoveItems(container, raid.IsProtectedWeapon(container, true));

            raid._containers.Remove(container);

            if (IsBox(container, true) || container is BuildingPrivlidge)
            {
                raid.StartTryToEnd();
                UI.UpdateStatusUI(raid);
            }

            foreach (var x in Raids.Values)
            {
                if (x._containers.Count > 0)
                {
                    return;
                }
            }

            Unsubscribe(nameof(OnEntityKill));
            Unsubscribe(nameof(OnEntityGroundMissing));
        }

        public static string FormatGridReference(Vector3 position)
        {
            if (config.Settings.ShowXZ)
            {
                return string.Format("{0} ({1} {2})", PositionToGrid(position), position.x.ToString("N2"), position.z.ToString("N2"));
            }

            return PositionToGrid(position);
        }

        protected bool DespawnBase(BasePlayer player)
        {
            var raid = GetNearestBase(player.transform.position);

            if (raid == null)
            {
                return false;
            }

            raid.Despawn();

            return true;
        }

        private static string FormatTime(double seconds, string id = null)
        {
            if (seconds < 0)
            {
                return "0s";
            }

            var ts = TimeSpan.FromSeconds(seconds);
            string format = Backbone.GetMessageEx("TimeFormat", id);

            if (format == "TimeFormat")
            {
                format = "{0:D2}h {1:D2}m {2:D2}s";
            }

            return string.Format(format, ts.Hours, ts.Minutes, ts.Seconds);
        }

        public static float GetDistance(RaidableType type)
        {
            float distance;

            switch (type)
            {
                case RaidableType.Maintained:
                    {
                        distance = config.Settings.Maintained.Distance;
                        break;
                    }
                case RaidableType.Scheduled:
                    {
                        distance = config.Settings.Schedule.Distance;
                        break;
                    }
                case RaidableType.Manual:
                    {
                        distance = config.Settings.Manual.Distance;
                        break;
                    }
                default:
                    {
                        distance = 100f;
                        break;
                    }
            }

            return distance;
        }

        private static List<BasePlayer> GetMountedPlayers(BaseMountable m)
        {
            BaseVehicle vehicle = m.HasParent() ? m.VehicleParent() : m as BaseVehicle;

            if (vehicle.IsValid())
            {
                return GetMountedPlayers(vehicle);
            }

            List<BasePlayer> players = new List<BasePlayer>();

            var player = m.GetMounted();

            if (player.IsValid() && IsHuman(player))
            {
                players.Add(player);
            }

            return players;
        }

        private static List<BasePlayer> GetMountedPlayers(BaseVehicle vehicle)
        {
            List<BasePlayer> players = new List<BasePlayer>();

            if (!vehicle.HasMountPoints())
            {
                var player = vehicle.GetMounted();

                if (player.IsValid() && IsHuman(player))
                {
                    players.Add(player);
                }

                return players;
            }

            for (int i = 0; i < vehicle.mountPoints.Count; i++)
            {
                var mountPoint = vehicle.mountPoints[i];

                if (mountPoint.mountable == null)
                {
                    continue;
                }

                var player = mountPoint.mountable.GetMounted();

                if (player.IsValid() && IsHuman(player))
                {
                    players.Add(player);
                }
            }

            return players;
        }

        private BasePlayer GetOwnerPlayer(Item item)
        {
            if (item.parentItem == null)
            {
                return item.GetOwnerPlayer();
            }

            return item.parentItem.GetOwnerPlayer();
        }

        private object HandleEntityDamage(BaseCombatEntity entity, HitInfo hitInfo)
        {
            var raid = RaidableBase.Get(entity.transform.position);

            if (raid == null || raid.killed)
            {
                return null;
            }

            if (raid.Options.Setup.FoundationsImmune && raid.Options.Setup.ForcedHeight != -1f)
            {
                if (raid.foundations.Count > 0 && entity.ShortPrefabName.StartsWith("foundation"))
                {
                    return false;
                }

                if (raid.foundations.Count == 0 && entity.ShortPrefabName.StartsWith("floor") && entity.transform.position.y - raid.Location.y <= 3f)
                {
                    return false;
                }
            }

            if (entity.IsNpc || entity is PlayerCorpse)
            {
                return true;
            }

            if (hitInfo.damageTypes.GetMajorityDamageType() == DamageType.Decay)
            {
                return false;
            }

            if (entity is BaseMountable || entity.name.Contains("modularcar"))
            {
                if (hitInfo.Initiator is SamSite)
                {
                    return config.Settings.Management.MountDamageFromSamSites;
                }

                if (!config.Settings.Management.MountDamageFromPlayers && !ExcludedMounts.Contains(entity.prefabID) && hitInfo.Initiator is BasePlayer)
                {
                    return false;
                }
            }

            if (!RaidableBase.Has(entity) && !raid.BuiltList.Contains(entity.net.ID))
            {
                bool attached = entity is DecayEntity && (entity as DecayEntity).buildingID == raid.BuildingID;

                if (!attached)
                {
                    return null;
                }
            }

            if (hitInfo.Initiator == entity && entity is AutoTurret)
            {
                return false;
            }

            var attacker = raid.GetInitiatorPlayer(hitInfo, entity);

            if (!IsValid(attacker))
            {
                return null;
            }

            if (!IsHuman(attacker))
            {
                return true;
            }

            entity.lastAttacker = attacker;
            attacker.lastDealtDamageTime = Time.time;

            if (config.Settings.Management.BlockMounts && attacker.GetMounted())
            {
                return false;
            }

            if (CanBlockOutsideDamage(raid, attacker, raid.Options.BlockOutsideDamageToBaseInside))
            {
                return false;
            }

            if (raid.ID.IsSteamId() && IsBox(entity, true) && raid.IsAlly(attacker.userID, Convert.ToUInt64(raid.ID)))
            {
                return false;
            }

            if (raid.ownerId.IsSteamId() && !raid.IsAlly(attacker))
            {
                return false;
            }

            if (raid.Options.AutoTurret.AutoAdjust && raid.turrets.Count > 0 && entity is AutoTurret)
            {
                var turret = entity as AutoTurret;

                if (raid.turrets.Contains(turret) && turret.sightRange <= raid.Options.AutoTurret.SightRange)
                {
                    turret.sightRange = raid.Options.AutoTurret.SightRange * 2f;
                }
            }

            if (!raid.Options.ExplosionModifier.Equals(100) && hitInfo.damageTypes.Has(DamageType.Explosion))
            {
                float m = Mathf.Clamp(raid.Options.ExplosionModifier, 0f, 999f);

                hitInfo.damageTypes.Scale(DamageType.Explosion, m.Equals(0f) ? 0f : m / 100f);
            }

            if (raid.BuiltList.Contains(entity.net.ID))
            {
                return true;
            }

            if (raid.Type != RaidableType.None)
            {
                raid.IsEngaged = true;
                raid.CheckDespawn();
            }

            if (raid.IsOpened && IsLootingWeapon(hitInfo))
            {
                if (raid.AddLooter(attacker, hitInfo))
                {
                    raid.TrySetOwner(attacker, entity, hitInfo);
                }
                else return false;
            }

                if (raid.Options.BlocksImmune && entity is BuildingBlock)
            {
                return false;
            }

            if (raid.Options.Invulnerable && IsBox(entity, true))
            {
                return false;
            }

            return true;
        }

        private object HandlePlayerDamage(BasePlayer victim, HitInfo hitInfo)
        {
            var raid = RaidableBase.Get(victim, hitInfo);

            if (raid == null || raid.IsDespawning)
            {
                return null;
            }

            var weapon = hitInfo.Initiator;

            if (IsTrueDamage(weapon, raid.IsProtectedWeapon(weapon)))
            {
                return HandleTrueDamage(raid, hitInfo, weapon, victim);
            }

            var attacker = raid.GetInitiatorPlayer(hitInfo, victim);

            if (IsValid(attacker))
            {
                return HandleAttacker(attacker, victim, hitInfo, raid);
            }
            else if (RaidableBase.Has(victim.userID))
            {
                return false;
            }

            return null;
        }

        private object HandleTrueDamage(RaidableBase raid, HitInfo hitInfo, BaseEntity weapon, BasePlayer victim)
        {
            if (weapon is AutoTurret)
            {
                if (RaidableBase.Has(victim.userID) && (raid.Options.NPC.IgnoreTrapsTurrets || RaidableBase.Has(weapon)))
                {
                    return false;
                }

                if (weapon.OwnerID.IsSteamId() && (!raid.AllowPVP || !InRange(weapon.transform.position, raid.Location, raid.ProtectionRadius)))
                {
                    return false;
                }

                hitInfo.damageTypes.Scale(DamageType.Bullet, UnityEngine.Random.Range(raid.Options.AutoTurret.Min, raid.Options.AutoTurret.Max));
            }

            return true;
        }

        private object HandleAttacker(BasePlayer attacker, BasePlayer victim, HitInfo hitInfo, RaidableBase raid)
        {
            if (RaidableBase.Has(attacker.userID) && RaidableBase.Has(victim.userID))
            {
                return false;
            }

            if (attacker.userID == victim.userID)
            {
                return true;
            }

            if (PvpDelay.ContainsKey(victim.userID))
            {
                if (EventTerritory(attacker.transform.position))
                {
                    return true;
                }

                if (config.Settings.Management.PVPDelayAnywhere && PvpDelay.ContainsKey(attacker.userID))
                {
                    return true;
                }
            }

            if (config.Settings.Management.PVPDelayDamageInside && PvpDelay.ContainsKey(attacker.userID) && InRange(raid.Location, victim.transform.position, raid.ProtectionRadius))
            {
                return true;
            }

            if (!IsHuman(victim) && IsHuman(attacker))
            {
                return HandleNpcVictim(raid, victim, attacker);
            }
            else if (IsHuman(victim) && IsHuman(attacker))
            {
                return HandlePVPDamage(raid, victim, attacker);
            }
            else if (RaidableBase.Has(attacker.userID))
            {
                return HandleNpcAttacker(raid, victim, attacker, hitInfo);
            }

            return null;
        }

        private object HandleNpcVictim(RaidableBase raid, BasePlayer victim, BasePlayer attacker)
        {
            HumanoidBrain brain;
            if (!HumanoidBrains.TryGetValue(victim.userID, out brain))
            {
                return true;
            }

            if (config.Settings.Management.BlockMounts && attacker.GetMounted() || raid.ownerId.IsSteamId() && !raid.IsAlly(attacker))
            {
                return false;
            }

            if (CanBlockOutsideDamage(raid, attacker, raid.Options.NPC.BlockOutsideDamageToNpcsInside))
            {
                return false;
            }

            var e = attacker.HasParent() ? attacker.GetParentEntity() : null;

            if (!(e == null) && (e is ScrapTransportHelicopter || e is HotAirBalloon || e is CH47Helicopter))
            {
                return false;
            }

            if (!raid.Options.NPC.CanLeave && raid.Options.NPC.BlockOutsideDamageOnLeave && !InRange(attacker.transform.position, raid.Location, raid.ProtectionRadius, false))
            {
                brain.Forget();

                return false;
            }

            brain.SetTarget(attacker);

            return true;
        }

        private object HandlePVPDamage(RaidableBase raid, BasePlayer victim, BasePlayer attacker)
        {
            if (!raid.AllowPVP || (!raid.Options.AllowFriendlyFire && raid.IsAlly(victim.userID, attacker.userID)))
            {
                return false;
            }

            if (CanBlockOutsideDamage(raid, attacker, raid.Options.BlockOutsideDamageToPlayersInside))
            {
                return false;
            }

            if (IsPVE())
            {
                if (!InRange(attacker.transform.position, raid.Location, raid.Options.ProtectionRadius, false))
                {
                    return false;
                }

                return InRange(victim.transform.position, raid.Location, raid.Options.ProtectionRadius, false);
            }

            return true;
        }

        private object HandleNpcAttacker(RaidableBase raid, BasePlayer victim, BasePlayer attacker, HitInfo hitInfo)
        {
            HumanoidBrain brain;
            if (!HumanoidBrains.TryGetValue(attacker.userID, out brain))
            {
                return true;
            }

            if (RaidableBase.Has(victim.userID) || (InRange(attacker.transform.position, raid.Location, raid.ProtectionRadius) && CanBlockOutsideDamage(raid, victim, raid.Options.BlockNpcDamageToPlayersOutside)))
            {
                return false;
            }

            if (brain.SenseRange <= brain.softLimitSenseRange && hitInfo.IsProjectile() && UnityEngine.Random.Range(0f, 100f) > raid.Options.NPC.Accuracy)
            {
                return false;
            }

            float multiplier = 1f;
            if (brain.attackType == HumanoidBrain.AttackType.BaseProjectile)
            {
                multiplier = raid.Options.NPC.Multipliers.ProjectileDamageMultiplier;
            }
            else if (brain.attackType == HumanoidBrain.AttackType.Melee)
            {
                multiplier = raid.Options.NPC.Multipliers.MeleeDamageMultiplier;
            }

            if (multiplier != 1f)
            {
                hitInfo.damageTypes.ScaleAll(multiplier);
            }

            return true;
        }

        private static bool InRange(Vector3 a, Vector3 b, float distance, bool ex = true)
        {
            if (!ex)
            {
                return (a - b).sqrMagnitude <= distance * distance;
            }

            return (new Vector3(a.x, 0f, a.z) - new Vector3(b.x, 0f, b.z)).sqrMagnitude <= distance * distance;
        }

        private bool IsCopyPasteLoaded(BasePlayer player)
        {
            if (CopyPaste == null || !CopyPaste.IsLoaded)
            {
                if (player.IsValid())
                {
                    Player.Message(player, Backbone.GetMessage("InstallCopyPastePlugin", player.UserIDString), config.Settings.ChatID);
                }
                else Puts(Backbone.GetMessageEx("InstallCopyPastePlugin"));

                return false;
            }

            if (CopyPaste.Version < new VersionNumber(4, 1, 31))
            {
                if (player.IsValid())
                {
                    Player.Message(player, Backbone.GetMessage("LoadSupportedCopyPasteVersion", player.UserIDString), config.Settings.ChatID);
                }
                else Puts(Backbone.GetMessageEx("LoadSupportedCopyPasteVersion"));

                return false;
            }

            return true;
        }

        private static bool IsLootingWeapon(HitInfo hitInfo)
        {
            if (hitInfo == null || hitInfo.damageTypes == null)
            {
                return false;
            }

            return hitInfo.damageTypes.Has(DamageType.Explosion) || hitInfo.damageTypes.Has(DamageType.Heat) || hitInfo.damageTypes.IsMeleeType();
        }

        private static bool IsBox(BaseEntity entity, bool inherit)
        {
            if (entity.prefabID == Constants.LARGE_WOODEN_BOX || entity.prefabID == Constants.SMALL_WOODEN_BOX || entity.prefabID == Constants.COFFIN_STORAGE)
            {
                return true;
            }

            return inherit && config.Settings.Management.Inherit.Any(entity.ShortPrefabName.Contains);
        }

        private static bool IsModeValid(RaidableMode mode) => mode != RaidableMode.Disabled && mode != RaidableMode.Random;

        [HookMethod("IsPremium")]
        private bool IsPremium() => false;

        private static bool IsValid(BaseEntity e)
        {
            if (e == null || e.net == null || e.IsDestroyed || e.transform == null)
            {
                return false;
            }

            return true;
        }

        public static bool IsHuman(BasePlayer player)
        {
            if (player.IsNpc || !player.userID.IsSteamId())
            {
                return false;
            }

            return true;
        }

        public static bool IsUnderground(Vector3 a) => GamePhysics.CheckSphere<TerrainCollisionTrigger>(a, 5f, 262144, QueryTriggerInteraction.Collide);

        private bool KeepMountable(BaseEntity entity)
        {
            if (config.Settings.Management.DespawnMounts)
            {
                return false;
            }

            MountInfo mi;
            if (!MountEntities.TryGetValue(entity, out mi) || !MountEntities.Remove(entity))
            {
                return false;
            }

            var mountable = entity as BaseMountable;

            return mountable.GetMounted() != null || !InRange(mountable.transform.position, mi.position, mi.radius);
        }

        public static bool MustExclude(RaidableType type, bool allowPVP)
        {
            if (!config.Settings.Maintained.IncludePVE && type == RaidableType.Maintained && !allowPVP)
            {
                return true;
            }

            if (!config.Settings.Maintained.IncludePVP && type == RaidableType.Maintained && allowPVP)
            {
                return true;
            }

            if (!config.Settings.Schedule.IncludePVE && type == RaidableType.Scheduled && !allowPVP)
            {
                return true;
            }

            if (!config.Settings.Schedule.IncludePVP && type == RaidableType.Scheduled && allowPVP)
            {
                return true;
            }

            return false;
        }

        private static void NullifyDamage(HitInfo hitInfo)
        {
            if (hitInfo == null)
            {
                return;
            }

            hitInfo.damageTypes = new DamageTypeList();
            hitInfo.DidHit = false;
            hitInfo.DoHitEffects = false;
            hitInfo.HitEntity = null;
        }

        private static string PositionToGrid(Vector3 position) => PhoneController.PositionToGridCoord(position);

        private bool AnyNpcs()
        {
            foreach (var x in Raids.Values)
            {
                if (x.npcs.Count > 0)
                {
                    return true;
                }
            }

            return false;
        }

        private bool AssignTreasureHunters()
        {
            foreach (var target in covalence.Players.All)
            {
                if (target == null || string.IsNullOrEmpty(target.Id))
                    continue;

                if (permission.UserHasPermission(target.Id, rankLadderPermission))
                    permission.RevokeUserPermission(target.Id, rankLadderPermission);

                if (permission.UserHasGroup(target.Id, rankLadderGroup))
                    permission.RemoveUserGroup(target.Id, rankLadderGroup);
            }

            if (!config.RankedLadder.Enabled || config.RankedLadder.Amount <= 0)
            {
                return true;
            }

            var ladder = new List<KeyValuePair<string, int>>();

            foreach (var entry in data.Players)
            {
                if (entry.Value.Raids > 0)
                {
                    if (!entry.Key.IsSteamId() || !IsNormalUser(entry.Key)) continue;
                    ladder.Add(new KeyValuePair<string, int>(entry.Key, entry.Value.Raids));
                }
            }

            ladder.Sort((x, y) => y.Value.CompareTo(x.Value));

            foreach (var kvp in ladder.Take(config.RankedLadder.Amount))
            {
                var p = covalence.Players.FindPlayerById(kvp.Key);

                if (p == null)
                {
                    continue;
                }

                permission.GrantUserPermission(p.Id, rankLadderPermission, this);
                permission.AddUserGroup(p.Id, rankLadderGroup);

                string message = Backbone.GetMessageEx("Log Stolen", null, p.Name, p.Id, kvp.Value);

                LogToFile("treasurehunters", $"{DateTime.Now} : {message}", this, true);
                Puts(Backbone.GetMessageEx("Log Granted", null, p.Name, p.Id, rankLadderPermission, rankLadderGroup));
            }

            Puts(Backbone.GetMessageEx("Log Saved", null, "treasurehunters"));

            return true;
        }

        private bool IsNormalUser(string userid)
        {
            if (permission.UserHasPermission(userid, notitlePermission))
            {
                return false;
            }

            var player = covalence.Players.FindPlayerById(userid);

            if (player == null || player.IsBanned)
            {
                return false;
            }

            return true;
        }

        private bool CanBlockOutsideDamage(BasePlayer victim, HitInfo hitInfo)
        {
            if (!IsHuman(victim) || !hitInfo.Initiator.IsValid() || !hitInfo.Initiator.IsNpc || !(hitInfo.Initiator is ScientistNPC))
            {
                return false;
            }

            var npc = hitInfo.Initiator as ScientistNPC;
            var raid = RaidableBase.Get(npc.userID);

            if (raid == null || !CanBlockOutsideDamage(raid, npc, raid.Options.BlockOutsideDamageToPlayersInside))
            {
                return false;
            }

            return true;
        }

        private bool CanBlockOutsideDamage(RaidableBase raid, BasePlayer attacker, bool isEnabled)
        {
            if (isEnabled)
            {
                float radius = Mathf.Max(raid.Options.ProtectionRadius, raid.Options.ArenaWalls.Radius, Constants.RADIUS);

                return !InRange(attacker.transform.position, raid.Location, radius, false);
            }

            return false;
        }

        private bool CanMaintainOpenEvent()
        {
            if (!IsPasteAvailable)
            {
                var vector = Raids.Values.FirstOrDefault(raid => raid.IsLoading)?.Location;
                PrintDebugMessage($"Paste not available; a base is currently loading at {vector}");
                return false;
            }

            if (RaidableBase.IsSpawning)
            {
                PrintDebugMessage($"Paste not available; a base is currently spawning.");
                return false;
            }

            if (IsGridLoading)
            {
                PrintDebugMessage($"Grid is loading.");
                return false;
            }

            if (config.Settings.Maintained.Max > 0 && RaidableBase.Get(RaidableType.Maintained) >= config.Settings.Maintained.Max)
            {
                PrintDebugMessage($"The max amount of maintained events are spawned.");
                return false;
            }

            if (BasePlayer.activePlayerList.Count < config.Settings.Maintained.PlayerLimit)
            {
                PrintDebugMessage($"Insufficient amount of players online {BasePlayer.activePlayerList.Count}/{config.Settings.Maintained.PlayerLimit}");
                return false;
            }

            return true;
        }

        private bool CanScheduleOpenEvent()
        {
            if (!IsPasteAvailable)
            {
                var vector = Raids.Values.FirstOrDefault(raid => raid.IsLoading)?.Location;
                PrintDebugMessage($"Scheduled: Paste not available; a base is currently loading at {vector}");
                return false;
            }

            if (RaidableBase.IsSpawning)
            {
                PrintDebugMessage($"Paste not available; a base is currently spawning.");
                return false;
            }

            if (IsGridLoading)
            {
                PrintDebugMessage($"Scheduled: Grid is loading.");
                return false;
            }

            if (config.Settings.Schedule.Max > 0 && RaidableBase.Get(RaidableType.Scheduled) >= config.Settings.Schedule.Max)
            {
                PrintDebugMessage($"Scheduled: The max amount of scheduled events are spawned.");
                return false;
            }

            if (BasePlayer.activePlayerList.Count < config.Settings.Schedule.PlayerLimit)
            {
                PrintDebugMessage($"Scheduled: Insufficient amount of players online {BasePlayer.activePlayerList.Count}/{config.Settings.Schedule.PlayerLimit}");
                return false;
            }

            if (GetRaidTime() > 0)
            {
                PrintDebugMessage($"{FormatTime(GetRaidTime())} before next event.");
                return false;
            }

            return true;
        }

        private void DespawnAllBasesNow(bool inactiveOnly)
        {
            RaidableBase.IsSpawning = false;

            if (!IsUnloading)
            {
                GarbageController.StartDespawn(inactiveOnly);
                return;
            }

            if (Interface.Oxide.IsShuttingDown)
            {
                GarbageController.RemoveHeldEntities();
                return;
            }

            DestroyAll();
        }

        private void DestroyAll()
        {
            foreach (var raid in Raids.Values.ToList())
            {
                Puts(lang.GetMessage("Destroyed Raid", this), $"{PositionToGrid(raid.Location)} {raid.Location}");
                if (raid.IsOpened) raid.AwardRaiders();
                raid.Despawn();
            }
        }

        private void DestroyComponents()
        {
            foreach (var raid in Raids.Values)
            {
                raid.DestroyInputs();
            }

            UI.DestroyAllStatusUI();
        }

        private static void DropOrRemoveItems(StorageContainer container, bool isProtectedWeapon)
        {
            if (!config.Settings.Management.DropLootTraps && isProtectedWeapon || !config.Settings.Management.AllowCupboardLoot && container.OwnerID == 0 && container is BuildingPrivlidge)
            {
                container.inventory.Clear();
            }
            else if (container.inventory.itemList.Count > 0)
            {
                float y = container.transform.position.y + Mathf.Max(0.81158f, container.bounds.size.y);

                container.inventory.Drop(StringPool.Get(545786656), container.transform.position.WithY(y), container.transform.rotation);
            }

            container.Invoke(container.KillMessage, 0.1f);
        }

        private bool EventTerritory(Vector3 position)
        {
            foreach (var raid in Raids.Values)
            {
                if (InRange(raid.Location, position, raid.Options.ProtectionRadius))
                {
                    return true;
                }
            }

            return false;
        }

        private Vector3 GetCenterLocation(Vector3 position)
        {
            foreach (var raid in Raids.Values)
            {
                if (InRange(raid.Location, position, raid.Options.ProtectionRadius))
                {
                    return raid.Location;
                }
            }

            return Vector3.zero;
        }

        private RaidableBase GetNearestBase(Vector3 target, float radius = 100f)
        {
            var values = new List<RaidableBase>();

            foreach (var x in Raids.Values)
            {
                if (InRange(x.Location, target, radius))
                {
                    values.Add(x);
                }
            }

            int count = values.Count;

            if (count == 0)
            {
                return null;
            }

            if (count > 1)
            {
                values.Sort((a, b) => (a.Location - target).sqrMagnitude.CompareTo((b.Location - target).sqrMagnitude));
            }

            return values[0];
        }

        private double GetRaidTime() => DateTime.Parse(data.RaidTime).Subtract(DateTime.Now).TotalSeconds;

        private bool HasPermission(BasePlayer player, string perm)
        {
            return permission.UserHasPermission(player.UserIDString, perm);
        }

        private bool HasPVPDelay(ulong playerId)
        {
            return PvpDelay.ContainsKey(playerId);
        }

        private bool IsInvisible(BasePlayer player)
        {
            return Vanish != null && Convert.ToBoolean(Vanish?.Call("IsInvisible", player));
        }

        private bool IsPVE() => TruePVE != null || NextGenPVE != null || Imperium != null;
        private bool IsSpawnOnCooldown()
        {
            if (Time.realtimeSinceStartup - lastSpawnRequestTime < 2f)
            {
                return true;
            }

            lastSpawnRequestTime = Time.realtimeSinceStartup;
            return false;
        }

        private bool IsTrueDamage(BaseEntity entity, bool isProtectedWeapon)
        {
            if (entity == null)
            {
                return false;
            }

            return isProtectedWeapon || entity.skinID == 1587601905 || TrueDamage.Contains(entity.ShortPrefabName) || entity is TeslaCoil || entity is BaseTrap;
        }

        private List<string> TrueDamage { get; set; } = new List<string> { "spikes.floor", "barricade.metal", "barricade.woodwire", "barricade.wood", "wall.external.high.wood", "wall.external.high.stone", "wall.external.high.ice" };

        private bool IsValid(Item item)
        {
            if (item == null || !item.IsValid() || item.isBroken)
            {
                return false;
            }

            return true;
        }

        private bool IsValid(string[] args)
        {
            return args.Length > 0 && arguments.Contains(args[0]);
        }

        private IEnumerator MaintainCoroutine()
        {
            string message;
            RandomBase randomBase;

            while (!IsUnloading)
            {
                if (CanMaintainOpenEvent())
                {
                    if (!IsCopyPasteLoaded(null))
                    {
                        yield return CoroutineEx.waitForSeconds(60f);
                    }
                    else if (!maintainEnabled || SaveRestore.IsSaving)
                    {
                        PrintDebugMessage(maintainEnabled ? "Server saving" : "Maintained events not enabled");

                        yield return CoroutineEx.waitForSeconds(15f);
                    }
                    else if ((randomBase = SpawnRandomBase(out message, RaidableType.Maintained, RaidableMode.Normal)) != null)
                    {
                        RaidableBase.IsSpawning = true;

                        PrintDebugMessage($"Waiting for base to be setup by the plugin.");
                        yield return new WaitWhile(() => RaidableBase.IsSpawning);
                        PrintDebugMessage($"Base has been setup by the plugin.");

                        if (config.Settings.Maintained.Time > 0)
                        {
                            PrintDebugMessage($"Waiting {config.Settings.Maintained.Time} seconds.");
                            yield return CoroutineEx.waitForSeconds(config.Settings.Maintained.Time);
                        }
                    }
                    else PrintDebugMessage(message);
                }

                PrintDebugMessage("Maintained coroutine is waiting for 1 second.");
                yield return CoroutineEx.waitForSeconds(1f);
            }

            PrintDebugMessage("Maintained coroutine has been cancelled!");
            maintainCoroutine = null;
        }

        private bool MaxOnce()
        {
            return config.Settings.Schedule.MaxOnce <= 0 || _maxOnce < config.Settings.Schedule.MaxOnce;
        }

        private void RegisterCommands()
        {
            AddCovalenceCommand(config.Settings.EventCommand, nameof(CommandRaidBase));
            AddCovalenceCommand(config.Settings.HunterCommand, nameof(CommandRaidHunter));
            AddCovalenceCommand(config.Settings.ConsoleCommand, nameof(CommandRaidBase));
            AddCovalenceCommand("rb.reloadconfig", nameof(CommandReloadConfig));
            AddCovalenceCommand("rb.config", nameof(CommandConfig), "raidablebases.config");
            AddCovalenceCommand("rb.populate", nameof(CommandPopulate), "raidablebases.config");
            AddCovalenceCommand("rb.toggle", nameof(CommandToggle), "raidablebases.config");
        }

        private void Reinitialize()
        {
            Backbone.Plugin.Skins.Clear();

            if (config.Settings.TeleportMarker)
            {
                Subscribe(nameof(OnMapMarkerAdded));
            }

            Subscribe(nameof(OnPlayerSleepEnded));
        }

        private static void SetupMonuments()
        {
            List<MonumentInfo> monuments;
            if (TerrainMeta.Path == null)
            {
                monuments = UnityEngine.Object.FindObjectsOfType<MonumentInfo>().ToList();
            }
            else monuments = TerrainMeta.Path.Monuments;

            foreach (var monument in monuments)
            {
                MonumentInfoEx mi = new MonumentInfoEx(monument);

                Instance.Monuments.Add(mi);
            }
        }

        private IEnumerator ScheduleCoroutine()
        {
            string message;
            RandomBase randomBase;

            while (!IsUnloading)
            {
                if (CanScheduleOpenEvent())
                {
                    while (RaidableBase.Get(RaidableType.Scheduled) < config.Settings.Schedule.Max && MaxOnce())
                    {
                        if (!IsCopyPasteLoaded(null))
                        {
                            yield return CoroutineEx.waitForSeconds(60f);
                        }
                        else if (!scheduleEnabled || SaveRestore.IsSaving)
                        {
                            PrintDebugMessage(scheduleEnabled ? "Server saving" : "Scheduled events not enabled");
                            yield return CoroutineEx.waitForSeconds(15f);
                        }
                        else if ((randomBase = SpawnRandomBase(out message, RaidableType.Scheduled, RaidableMode.Normal)) != null)
                        {
                            RaidableBase.IsSpawning = true;
                            _maxOnce++;

                            PrintDebugMessage($"Waiting for base to be setup by the plugin.");
                            yield return new WaitWhile(() => RaidableBase.IsSpawning);
                            PrintDebugMessage($"Base has been setup by the plugin.");

                            if (config.Settings.Schedule.Time > 0)
                            {
                                PrintDebugMessage($"Waiting {config.Settings.Schedule.Time} seconds.");
                                yield return CoroutineEx.waitForSeconds(config.Settings.Schedule.Time);
                            }
                        }
                        else PrintDebugMessage(message);

                        PrintDebugMessage("Scheduled coroutine is waiting for 1 second.");
                        yield return CoroutineEx.waitForSeconds(1f);
                    }

                    PrintDebugMessage("Scheduling next automated event.");
                    ScheduleNextAutomatedEvent();
                }

                yield return CoroutineEx.waitForSeconds(1f);
            }

            PrintDebugMessage("Scheduled coroutine has been cancelled!");
            scheduleCoroutine = null;
        }

        private void ScheduleNextAutomatedEvent()
        {
            var raidInterval = Core.Random.Range(config.Settings.Schedule.IntervalMin, config.Settings.Schedule.IntervalMax + 1);

            data.RaidTime = DateTime.Now.AddSeconds(raidInterval).ToString();
            _maxOnce = 0;

            Puts(Backbone.GetMessageEx("Next Automated Raid", null, FormatTime(raidInterval), data.RaidTime));
            data.Save();
        }
        
        private void StartMaintainCoroutine()
        {
            if (!maintainEnabled || config.Settings.Maintained.Max <= 0)
            {
                return;
            }

            if (IsGridLoading)
            {
                timer.Once(1f, () => StartMaintainCoroutine());
                return;
            }

            StopMaintainCoroutine();

            timer.Once(0.2f, () =>
            {
                maintainCoroutine = ServerMgr.Instance.StartCoroutine(MaintainCoroutine());
            });
        }

        private void StartScheduleCoroutine()
        {
            if (!scheduleEnabled || config.Settings.Schedule.Max <= 0)
            {
                return;
            }

            if (IsGridLoading)
            {
                timer.Once(1f, () => StartScheduleCoroutine());
                return;
            }

            StopScheduleCoroutine();

            if (data.RaidTime == DateTime.MinValue.ToString())
            {
                ScheduleNextAutomatedEvent();
            }

            timer.Once(0.2f, () =>
            {
                scheduleCoroutine = ServerMgr.Instance.StartCoroutine(ScheduleCoroutine());
            });
        }

        private void StopMaintainCoroutine()
        {
            if (maintainCoroutine != null)
            {
                ServerMgr.Instance.StopCoroutine(maintainCoroutine);
                maintainCoroutine = null;
            }
        }
        private void StopScheduleCoroutine()
        {
            if (scheduleCoroutine != null)
            {
                ServerMgr.Instance.StopCoroutine(scheduleCoroutine);
                scheduleCoroutine = null;
            }
        }

        private static void UnsetStatics()
        {
            UI.Players.Clear();
            UI.InvokeTimers.Clear();
            LoadingTimes.Clear();
            Instance.Locations.Clear();
            Backbone.Destroy();
            Backbone = null;
            config = null;
            Buildings.Default.Clear();
            Buildings.Normal.Clear();
            Buildings.Profiles.Clear();
            Buildings.Weekday.Clear();
            _shortnames = null;
            blockedcolliders = null;
            Instance = null;
            data = null;
        }

        private void UnsubscribeHooks()
        {
            if (IsUnloading)
            {
                return;
            }

            Unsubscribe(nameof(CanBGrade));
            Unsubscribe(nameof(OnRestoreUponDeath));
            Unsubscribe(nameof(OnNpcKits));
            Unsubscribe(nameof(CanTeleport));
            Unsubscribe(nameof(canTeleport));
            Unsubscribe(nameof(CanEntityBeTargeted));
            Unsubscribe(nameof(CanEntityTrapTrigger));
            Unsubscribe(nameof(CanEntityTakeDamage));
            Unsubscribe(nameof(CanOpenBackpack));
            Unsubscribe(nameof(CanBePenalized));

            Unsubscribe(nameof(OnButtonPress));
            Unsubscribe(nameof(OnElevatorButtonPress));
            Unsubscribe(nameof(CanSamSiteShoot));
            Unsubscribe(nameof(OnStructureUpgrade));
            Unsubscribe(nameof(OnPlayerCommand));
            Unsubscribe(nameof(OnServerCommand));
            Unsubscribe(nameof(OnTrapTrigger));
            Unsubscribe(nameof(OnPlayerLand));
            Unsubscribe(nameof(OnEntityBuilt));
            Unsubscribe(nameof(OnEntityGroundMissing));
            Unsubscribe(nameof(OnEntityKill));
            Unsubscribe(nameof(OnEntityTakeDamage));
            Unsubscribe(nameof(OnLootEntity));
            Unsubscribe(nameof(OnLootEntityEnd));
            Unsubscribe(nameof(OnEntityDeath));
            Unsubscribe(nameof(CanPickupEntity));
            Unsubscribe(nameof(OnPlayerDeath));
            Unsubscribe(nameof(OnPlayerDropActiveItem));
            Unsubscribe(nameof(OnEntityEnter));
            Unsubscribe(nameof(OnNpcDuck));
            Unsubscribe(nameof(OnNpcDestinationSet));
            Unsubscribe(nameof(OnEntitySpawned));
            Unsubscribe(nameof(OnCupboardAuthorize));
            Unsubscribe(nameof(OnActiveItemChanged));
            Unsubscribe(nameof(OnLoseCondition));
            Unsubscribe(nameof(OnFireBallSpread));
            Unsubscribe(nameof(CanBuild));
            Unsubscribe(nameof(OnBaseRepair));
            Unsubscribe(nameof(OnCupboardProtectionCalculated));
        }

        #endregion

        #region Data files

        private bool _createdBackup;

        private void LoadData()
        {
            StoredData.Load(Name);
            CheckForWipe();
        }

        private void ConfigAddBase(IPlayer p, string[] args)
        {
            if (args.Length < 2)
            {
                p.Reply(lang.GetMessage("ConfigAddBaseSyntax", this, p.Id));
                return;
            }

            _sb.Length = 0;
            var values = new List<string>(args);
            values.RemoveAt(0);
            string value = values[0];

            p.Reply(string.Format(lang.GetMessage("Adding", this, p.Id), string.Join(" ", values.ToArray())));

            BaseProfile profile;
            if (!Buildings.Profiles.TryGetValue(value, out profile))
            {
                Buildings.Profiles[value] = profile = new BaseProfile();
                _sb.AppendLine(string.Format(lang.GetMessage("AddedPrimaryBase", this, p.Id), value));
                profile.Options.AdditionalBases = new Dictionary<string, List<PasteOption>>();
            }

            if (args.Length >= 3)
            {
                values.RemoveAt(0);

                foreach (string ab in values)
                {
                    if (!profile.Options.AdditionalBases.ContainsKey(ab))
                    {
                        profile.Options.AdditionalBases.Add(ab, DefaultPasteOptions);
                        _sb.AppendLine(string.Format(lang.GetMessage("AddedAdditionalBase", this, p.Id), ab));
                    }
                }
            }

            if (_sb.Length > 0)
            {
                p.Reply(_sb.ToString());
                _sb.Length = 0;
                profile.Options.Enabled = true;
                SaveProfile(value, profile.Options);
                Buildings.Profiles[value] = profile;
            }
            else p.Reply(lang.GetMessage("EntryAlreadyExists", this, p.Id));

            values.Clear();
        }

        private void ConfigListBases(IPlayer p)
        {
            _sb.Length = 0;
            _sb.Append(lang.GetMessage("ListingAll", this, p.Id));
            _sb.AppendLine();

            bool validBase = false;

            foreach (var entry in Buildings.Profiles)
            {
                _sb.AppendLine(lang.GetMessage("PrimaryBase", this, p.Id));

                if (FileExists(entry.Key))
                {
                    _sb.AppendLine(entry.Key);
                    validBase = true;
                }
                else _sb.Append(entry.Key).Append(lang.GetMessage("IsProfile", this, p.Id));

                if (entry.Value.Options.AdditionalBases.Count > 0)
                {
                    _sb.AppendLine(lang.GetMessage("AdditionalBase", this, p.Id));

                    foreach (var ab in entry.Value.Options.AdditionalBases)
                    {
                        if (FileExists(ab.Key))
                        {
                            _sb.AppendLine(ab.Key);
                            validBase = true;
                        }
                        else _sb.Append(ab.Key).Append((lang.GetMessage("FileDoesNotExist", this, p.Id)));
                    }
                }
            }

            if (!validBase)
            {
                _sb.AppendLine(lang.GetMessage("NoBuildingsConfigured", this, p.Id));
            }

            p.Reply(_sb.ToString());
            _sb.Length = 0;
        }

        private void ConfigRemoveBase(IPlayer p, string[] args)
        {
            string arg = args[0].ToLower();

            if (arg == "clean")
            {
                var clean = new List<string>();

                foreach (var x in Buildings.Profiles)
                {
                    foreach (var y in x.Value.Options.AdditionalBases)
                    {
                        if (!FileExists(y.Key))
                        {
                            clean.Add(y.Key);
                        }
                    }
                }

                args = clean.ToArray();
            }

            if (args.Length < 2)
            {
                p.Reply(lang.GetMessage("RemoveSyntax", this, p.Id));
                return;
            }

            int num = 0;
            var profiles = new Dictionary<string, BaseProfile>(Buildings.Profiles);
            var files = string.Join(" ", arg == "remove" ? args.Skip(1) : args);
            files = files.Replace(", ", " ");

            _sb.Length = 0;
            _sb.AppendLine(string.Format(lang.GetMessage("RemovingAllBasesFor", this, p.Id), string.Join(" ", files)));

            foreach (var profile in profiles)
            {
                var list = new List<KeyValuePair<string, List<PasteOption>>>(profile.Value.Options.AdditionalBases);

                foreach (string value in files.Split(' '))
                {
                    foreach (var ab in list)
                    {
                        if (ab.Key == value || profile.Key == value)
                        {
                            _sb.AppendLine(string.Format(lang.GetMessage("RemovedAdditionalBase", this, p.Id), ab.Key, profile.Key));
                            profile.Value.Options.AdditionalBases.Remove(ab.Key);
                            num++;
                            SaveProfile(profile.Key, profile.Value.Options);
                        }
                    }

                    if (profile.Key == value)
                    {
                        _sb.AppendLine(string.Format(lang.GetMessage("RemovedPrimaryBase", this, p.Id), value));
                        Buildings.Profiles.Remove(profile.Key);
                        profile.Value.Options.Enabled = false;
                        num++;
                        SaveProfile(profile.Key, profile.Value.Options);
                    }
                }

                list.Clear();
            }

            _sb.AppendLine(string.Format(lang.GetMessage("RemovedEntries", this, p.Id), num));
            p.Reply(_sb.ToString());
            _sb.Length = 0;
        }

        protected void ConvertFromConfig(List<TreasureItem> lootList, string key)
        {
            if (lootList?.Count > 0)
            {
                CreateBackup();
                Interface.Oxide.DataFileSystem.WriteObject($"{Name}{Path.DirectorySeparatorChar}{key}", lootList);
            }
        }

        protected void ConvertProfilesFromConfig()
        {
            if (config.RaidableBases?.Buildings?.Count > 0)
            {
                CreateBackup();

                foreach (var building in config.RaidableBases.Buildings)
                {
                    SaveProfile(building.Key, building.Value);
                }

                config.RaidableBases.Buildings.Clear();
            }

            config.RaidableBases.Buildings = null;
            config.RaidableBases = null;
            SaveConfig();
        }

        protected void ConvertTablesFromConfig()
        {
            if (config.RaidableBases?.Buildings != null)
            {
                foreach (var building in config.RaidableBases.Buildings)
                {
                    ConvertFromConfig(building.Value.Loot, $"Base_Loot{Path.DirectorySeparatorChar}{building.Key}");
                    building.Value.Loot = null;
                }
            }

            ConvertFromConfig(config.Treasure.Loot, "Default_Loot");
            ConvertFromConfig(config.Treasure.LootEasy, $"Difficulty_Loot{Path.DirectorySeparatorChar}Easy");
            ConvertFromConfig(config.Treasure.LootMedium, $"Difficulty_Loot{Path.DirectorySeparatorChar}Medium");
            ConvertFromConfig(config.Treasure.LootHard, $"Difficulty_Loot{Path.DirectorySeparatorChar}Hard");
            ConvertFromConfig(config.Treasure.LootExpert, $"Difficulty_Loot{Path.DirectorySeparatorChar}Expert");
            ConvertFromConfig(config.Treasure.LootNightmare, $"Difficulty_Loot{Path.DirectorySeparatorChar}Nightmare");
            ConvertFromConfig(config.Treasure.DOWL_Monday, $"Weekday_Loot{Path.DirectorySeparatorChar}Monday");
            ConvertFromConfig(config.Treasure.DOWL_Tuesday, $"Weekday_Loot{Path.DirectorySeparatorChar}Tuesday");
            ConvertFromConfig(config.Treasure.DOWL_Wednesday, $"Weekday_Loot{Path.DirectorySeparatorChar}Wednesday");
            ConvertFromConfig(config.Treasure.DOWL_Thursday, $"Weekday_Loot{Path.DirectorySeparatorChar}Thursday");
            ConvertFromConfig(config.Treasure.DOWL_Friday, $"Weekday_Loot{Path.DirectorySeparatorChar}Friday");
            ConvertFromConfig(config.Treasure.DOWL_Saturday, $"Weekday_Loot{Path.DirectorySeparatorChar}Saturday");
            ConvertFromConfig(config.Treasure.DOWL_Sunday, $"Weekday_Loot{Path.DirectorySeparatorChar}Sunday");

            EnsureDeserialized();
            SaveConfig();
        }

        protected void EnsureDeserialized()
        {
            config.Treasure.Loot = null;
            config.Treasure.LootEasy = null;
            config.Treasure.LootMedium = null;
            config.Treasure.LootHard = null;
            config.Treasure.LootExpert = null;
            config.Treasure.LootNightmare = null;
            config.Treasure.DOWL_Monday = null;
            config.Treasure.DOWL_Tuesday = null;
            config.Treasure.DOWL_Wednesday = null;
            config.Treasure.DOWL_Thursday = null;
            config.Treasure.DOWL_Friday = null;
            config.Treasure.DOWL_Saturday = null;
            config.Treasure.DOWL_Sunday = null;
        }

        private Dictionary<string, string> GetFiles(string path)
        {
            string[] files;

            try
            {
                files = Interface.Oxide.DataFileSystem.GetFiles(path);
            }
            catch (UnauthorizedAccessException ex)
            {
                LogError(ex.Message);

                return null;
            }

            var dict = new Dictionary<string, string>();

            foreach (string file in files)
            {
                if (file.EndsWith("_emptyfile.json") || file.EndsWith("_empty_file.json"))
                {
                    continue;
                }

                int index = file.LastIndexOf(Path.DirectorySeparatorChar) + 1;
                string fileName = file.Substring(index, file.Length - index - 5);

                dict[fileName] = file;
            }

            return dict;
        }

        protected void LoadProfiles()
        {
            string folder = $"{Name}{Path.DirectorySeparatorChar}Profiles";

            ConvertProfilesFromConfig();

            foreach (var entry in GetFiles(folder))
            {
                string profileName = entry.Key;

                try
                {
                    if (entry.Value.EndsWith("_emptyfile.json") || entry.Value.EndsWith("_empty_file.json"))
                    {
                        continue;
                    }

                    string fullName = $"{folder}{Path.DirectorySeparatorChar}{profileName}";
                    var options = Interface.Oxide.DataFileSystem.ReadObject<BuildingOptions>(fullName);

                    if (options == null || !options.Enabled)
                    {
                        continue;
                    }

                    if (options.AdditionalBases == null)
                    {
                        options.AdditionalBases = new Dictionary<string, List<PasteOption>>();
                    }

                    options.Loot = null;

                    if (options.ProtectionRadius < Constants.CELL_SIZE)
                    {
                        options.ProtectionRadius = Constants.CELL_SIZE;
                    }

                    Buildings.Profiles[profileName] = new BaseProfile(options, fullName);

                    Puts("Loaded {0} profile", profileName);
                }
                catch (Exception ex)
                {
                    Puts(entry.Value);
                    Puts("{0}", ex);
                }
            }

            foreach (var profile in Buildings.Profiles)
            {
                SaveProfile(profile.Key, profile.Value.Options);
            }

            LoadBaseTables();
            VerifyProfiles();
        }

        protected void VerifyProfiles()
        {
            bool allowPVP = Buildings.Profiles.Values.Any(profile => profile.Options.AllowPVP);
            bool allowPVE = Buildings.Profiles.Values.Any(profile => !profile.Options.AllowPVP);

            if (config.Settings.Maintained.Enabled)
            {
                if (allowPVP && !config.Settings.Maintained.IncludePVP && !allowPVE)
                {
                    Puts("Invalid configuration detected: Maintained Events -> Include PVP Bases is set false, and all profiles have Allow PVP enabled. Therefore no bases can spawn for Maintained Events. The ideal configuration is for Include PVP Bases to be set true, and Convert PVP To PVE to be set true.");
                }

                if (allowPVE && !config.Settings.Maintained.IncludePVE && !allowPVP)
                {
                    Puts("Invalid configuration detected: Maintained Events -> Include PVE Bases is set false, and all profiles have Allow PVP disabled. Therefore no bases can spawn for Maintained Events. The ideal configuration is for Include PVE Bases to be set true, and Convert PVE To PVP to be set true.");
                }
            }

            if (config.Settings.Schedule.Enabled)
            {
                if (allowPVP && !config.Settings.Schedule.IncludePVP && !allowPVE)
                {
                    Puts("Invalid configuration detected: Scheduled Events -> Include PVP Bases is set false, and all profiles have Allow PVP enabled. Therefore no bases can spawn for Scheduled Events. The ideal configuration is for Include PVP Bases to be set true, and Convert PVP To PVE to be set true.");
                }

                if (allowPVE && !config.Settings.Schedule.IncludePVE && !allowPVP)
                {
                    Puts("Invalid configuration detected: Scheduled Events -> Include PVE Bases is set false, and all profiles have Allow PVP disabled. Therefore no bases can spawn for Scheduled Events. The ideal configuration is for Include PVE Bases to be set true, and Convert PVE To PVP to be set true.");
                }
            }
        }

        private void LogError(string message)
        {
            Puts(message);
            Puts("ERROR: Issue with the machines host permission configuration");
            Puts("Read/write access permissions are not being set properly on this machine.");
            Puts("Manually change the file permissions to 777 using your hosts File Manager.");
        }

        protected void LoadTables()
        {
            ConvertTablesFromConfig();
            Buildings = new BuildingTables();
            _sb.Length = 0;

            List<TreasureItem> defaultList;

            Buildings.Default = defaultList = GetTable($"{Name}{Path.DirectorySeparatorChar}Default_Loot");

            defaultList.RemoveAll(ti => ti.amount == 0 && ti.amountMin == 0);

            if (defaultList.Count > 0)
            {
                _sb.AppendLine($"Loaded {defaultList.Count} items from RaidableBases/Default_Loot");

                defaultList.ToList().ForEach(ti =>
                {
                    if (ti != null && ti.amount < ti.amountMin)
                    {
                        int min = ti.amountMin;

                        ti.amountMin = ti.amount;
                        ti.amount = min;
                    }
                });
            }

            List<TreasureItem> normalList;

            Buildings.Normal = normalList = GetTable($"{Name}{Path.DirectorySeparatorChar}Difficulty_Loot{Path.DirectorySeparatorChar}Normal");

            normalList.RemoveAll(ti => ti.amount == 0 && ti.amountMin == 0);

            if (normalList.Count > 0)
            {
                _sb.AppendLine($"Loaded {normalList.Count} items from RaidableBases{Path.DirectorySeparatorChar}Difficulty_Loot{Path.DirectorySeparatorChar}Normal");

                normalList.ForEach(ti =>
                {
                    if (ti.amount < ti.amountMin)
                    {
                        _sb.AppendLine(string.Format("Error >>> {0} has {1} amount / {2} amountMin in {3}.json", ti.shortname, ti.amount, ti.amountMin, "Normal"));
                    }
                });
            }

            foreach (DayOfWeek day in Enum.GetValues(typeof(DayOfWeek)))
            {
                string file = $"{Name}{Path.DirectorySeparatorChar}Weekday_Loot{Path.DirectorySeparatorChar}{day}";
                List<TreasureItem> lootList;

                Buildings.Weekday[day] = lootList = GetTable(file);

                lootList.RemoveAll(ti => ti.amount == 0 && ti.amountMin == 0);

                if (lootList.Count > 0)
                {
                    _sb.AppendLine($"Loaded {lootList.Count} items from {file}");

                    lootList.ForEach(ti =>
                    {
                        if (ti.amount < ti.amountMin)
                        {
                            _sb.AppendLine(string.Format("Error >>> {0} has {1} amount / {2} amountMin in {3}.json", ti.shortname, ti.amount, ti.amountMin, file));
                        }
                    });
                }
            }

            Puts(_sb.ToString());
            _sb.Clear();
        }

        protected void SaveProfile(string key, BuildingOptions options)
        {
            Interface.Oxide.DataFileSystem.WriteObject($"{Name}{Path.DirectorySeparatorChar}Profiles{Path.DirectorySeparatorChar}{key}", options);
        }

        private void CreateBackup()
        {
            if (!_createdBackup)
            {
                string file = $"{Interface.Oxide.ConfigDirectory}{Path.DirectorySeparatorChar}{Name}.backup_old_system.{DateTime.Now:yyyy-MM-dd hh-mm-ss}.json";
                Config.WriteObject(config, false, file);
                Puts("Created config backup of old system: {0}", file);
                _createdBackup = true;
            }
        }

        private void CreateDefaultFiles()
        {
            string folder = $"{Name}{Path.DirectorySeparatorChar}Profiles";
            string empty = $"{folder}{Path.DirectorySeparatorChar}_emptyfile";

            if (Interface.Oxide.DataFileSystem.ExistsDatafile(empty))
            {
                return;
            }

            Interface.Oxide.DataFileSystem.GetDatafile(empty);

            foreach (var building in DefaultBuildingOptions)
            {
                string filename = $"{Name}{Path.DirectorySeparatorChar}Profiles{Path.DirectorySeparatorChar}{building.Key}";

                if (!Interface.Oxide.DataFileSystem.ExistsDatafile(filename))
                {
                    SaveProfile(building.Key, building.Value);
                }
            }

            string lootFile = $"{Name}{Path.DirectorySeparatorChar}Default_Loot";

            if (!Interface.Oxide.DataFileSystem.ExistsDatafile(lootFile))
            {
                Interface.Oxide.DataFileSystem.WriteObject(lootFile, DefaultLoot);
            }
        }

        private List<TreasureItem> GetTable(string file)
        {
            var lootList = new List<TreasureItem>();

            try
            {
                lootList = Interface.Oxide.DataFileSystem.ReadObject<List<TreasureItem>>(file);
            }
            catch (JsonReaderException ex)
            {
                Puts("{0}", ex);
            }

            if (lootList == null)
            {
                return new List<TreasureItem>();
            }

            return lootList;
        }

        private void LoadBaseTables()
        {
            var key = string.Empty;

            foreach (var entry in Buildings.Profiles)
            {
                string file = $"{Name}{Path.DirectorySeparatorChar}Base_Loot{Path.DirectorySeparatorChar}{entry.Key}";

                var lootTable = GetTable(file);

                if (!lootTable.Any(ti => ti != null && ti.amount != 0))
                {
                    continue;
                }

                LoadTable(file, BaseLootList = lootTable);

                key = entry.Key;
            }

            if (BaseLootList.Count > 0)
            {
                Puts("Loaded {0} items from {1}", BaseLootList.Count, key);
            }
        }

        private void LoadTable(string file, List<TreasureItem> lootList)
        {
            if (lootList.Count == 0)
            {
                return;
            }

            Interface.Oxide.DataFileSystem.WriteObject(file, lootList);

            lootList.RemoveAll(ti => ti == null || ti.amount == 0 && ti.amountMin == 0);

            lootList.ToList().ForEach(ti =>
            {
                if (ti != null && ti.amount < ti.amountMin)
                {
                    int min = ti.amountMin;

                    ti.amountMin = ti.amount;
                    ti.amount = min;
                }
            });
        }

        #endregion

        #region Configuration

        private Dictionary<string, Dictionary<string, string>> GetMessages()
        {
            return new Dictionary<string, Dictionary<string, string>>
            {
                {"No Permission", new Dictionary<string, string>() {
                    {"en", "You do not have permission to use this command."},
                }},
                {"Building is blocked!", new Dictionary<string, string>() {
                    {"en", "<color=#FF0000>Building is blocked near raidable bases!</color>"},
                }},
                {"Ladders are blocked!", new Dictionary<string, string>() {
                    {"en", "<color=#FF0000>Ladders are blocked in raidable bases!</color>"},
                }},
                {"Cupboards are blocked!", new Dictionary<string, string>() {
                    {"en", "<color=#FF0000>Tool cupboards are blocked in raidable bases!</color>"},
                }},
                {"Ladders Require Building Privilege!", new Dictionary<string, string>() {
                    {"en", "<color=#FF0000>You need building privilege to place ladders!</color>"},
                }},
                {"Profile Not Enabled", new Dictionary<string, string>() {
                    {"en", "This profile is not enabled: <color=#FF0000>{0}</color>."},
                }},
                {"Max Manual Events", new Dictionary<string, string>() {
                    {"en", "Maximum number of manual events <color=#FF0000>{0}</color> has been reached!"},
                }},
                {"Manual Event Failed", new Dictionary<string, string>() {
                    {"en", "Event failed to start! Unable to obtain a valid position. Please try again."},
                }},
                {"Help", new Dictionary<string, string>() {
                    {"en", "/{0} <tp> - start a manual event, and teleport to the position if TP argument is specified and you are an admin."},
                }},
                {"RaidOpenMessage", new Dictionary<string, string>() {
                    {"en", "<color=#C0C0C0>A {0} raidable base event has opened at <color=#FFFF00>{1}</color>! You are <color=#FFA500>{2}m</color> away. [{3}]</color>"},
                }},
                {"Next", new Dictionary<string, string>() {
                    {"en", "<color=#C0C0C0>No events are open. Next event in <color=#FFFF00>{0}</color></color>"},
                }},
                {"Wins", new Dictionary<string, string>()
                {
                    {"en", "<color=#C0C0C0>You have looted <color=#FFFF00>{0}</color> raid bases! View the ladder using <color=#FFA500>/{1} ladder</color> or <color=#FFA500>/{1} lifetime</color></color>"},
                }},
                {"RaidMessage", new Dictionary<string, string>() {
                    {"en", "Raidable Base {0}m [{1} players]"},
                }},
                {"RankedLadder", new Dictionary<string, string>()
                {
                    {"en", "<color=#FFFF00>[ Top {0} (This Wipe) ]</color>:"},
                }},
                {"RankedTotal", new Dictionary<string, string>()
                {
                    {"en", "<color=#FFFF00>[ Top {0} (Lifetime) ]</color>:"},
                }},
                {"Ladder Insufficient Players", new Dictionary<string, string>()
                {
                    {"en", "<color=#FFFF00>No players are on the ladder yet!</color>"},
                }},
                {"Next Automated Raid", new Dictionary<string, string>() {
                    {"en", "Next automated raid in {0} at {1}"},
                }},
                {"Not Enough Online", new Dictionary<string, string>() {
                    {"en", "Not enough players online ({0} minimum)"},
                }},
                {"Raid Base Distance", new Dictionary<string, string>() {
                    {"en", "<color=#C0C0C0>Raidable Base <color=#FFA500>{0}m</color>"},
                }},
                {"Destroyed Raid", new Dictionary<string, string>() {
                    {"en", "Destroyed a left over raid base at {0}"},
                }},
                {"Indestructible", new Dictionary<string, string>() {
                    {"en", "<color=#FF0000>Treasure chests are indestructible!</color>"},
                }},
                {"View Config", new Dictionary<string, string>() {
                    {"en", "Please view the config if you haven't already."},
                }},
                {"Log Stolen", new Dictionary<string, string>() {
                    {"en", "{0} ({1}) Raids {2}"},
                }},
                {"Log Granted", new Dictionary<string, string>() {
                    {"en", "Granted {0} ({1}) permission {2} for group {3}"},
                }},
                {"Log Saved", new Dictionary<string, string>() {
                    {"en", "Raid Hunters have been logged to: {0}"},
                }},
                {"Prefix", new Dictionary<string, string>() {
                    {"en", "[ <color=#406B35>Raidable Bases</color> ] "},
                }},
                {"RestartDetected", new Dictionary<string, string>()
                {
                    {"en", "Restart detected. Next event in {0} minutes."},
                }},
                {"EconomicsDeposit", new Dictionary<string, string>()
                {
                    {"en", "You have received <color=#FFFF00>${0}</color> for stealing the treasure!"},
                }},
                {"ServerRewardPoints", new Dictionary<string, string>()
                {
                    {"en", "You have received <color=#FFFF00>{0} RP</color> for stealing the treasure!"},
                }},
                {"InvalidItem", new Dictionary<string, string>()
                {
                    {"en", "Invalid item shortname: {0}. Use /{1} additem <shortname> <amount> [skin]"},
                }},
                {"AddedItem", new Dictionary<string, string>()
                {
                    {"en", "Added item: {0} amount: {1}, skin: {2}"},
                }},
                {"CustomPositionSet", new Dictionary<string, string>()
                {
                    {"en", "Custom event spawn location set to: {0}"},
                }},
                {"CustomPositionRemoved", new Dictionary<string, string>()
                {
                    {"en", "Custom event spawn location removed."},
                }},
                {"OpenedEvents", new Dictionary<string, string>()
                {
                    {"en", "Opened {0}/{1} events."},
                }},
                {"OnPlayerEntered", new Dictionary<string, string>()
                {
                    {"en", "<color=#FF0000>You have entered a raidable PVP base!</color>"},
                }},
                {"OnPlayerEnteredPVE", new Dictionary<string, string>()
                {
                    {"en", "<color=#FF0000>You have entered a raidable PVE base!</color>"},
                }},
                {"OnPlayerEntryRejected", new Dictionary<string, string>()
                {
                    {"en", "<color=#FF0000>You cannot enter an event that does not belong to you!</color>"},
                }},
                {"OnFirstPlayerEntered", new Dictionary<string, string>()
                {
                    {"en", "<color=#FFFF00>{0}</color> is the first to enter the raidable base at <color=#FFFF00>{1}</color>"},
                }},
                {"OnChestOpened", new Dictionary<string, string>() {
                    {"en", "<color=#FFFF00>{0}</color> is the first to see the treasures at <color=#FFFF00>{1}</color>!</color>"},
                }},
                {"OnRaidFinished", new Dictionary<string, string>() {
                    {"en", "The raid at <color=#FFFF00>{0}</color> has been unlocked!"},
                }},
                {"CannotBeMounted", new Dictionary<string, string>() {
                    {"en", "You cannot loot the treasure while mounted!"},
                }},
                {"CannotTeleport", new Dictionary<string, string>() {
                    {"en", "You are not allowed to teleport from this event."},
                }},
                {"MustBeAuthorized", new Dictionary<string, string>() {
                    {"en", "You must have building privilege to access this treasure!"},
                }},
                {"OwnerLocked", new Dictionary<string, string>() {
                    {"en", "This treasure belongs to someone else!"},
                }},
                {"CannotFindPosition", new Dictionary<string, string>() {
                    {"en", "Could not find a random position!"},
                }},
                {"PasteOnCooldown", new Dictionary<string, string>() {
                    {"en", "Paste is on cooldown!"},
                }},
                {"SpawnOnCooldown", new Dictionary<string, string>() {
                    {"en", "Try again, a manual spawn was already requested."},
                }},
                {"Thief", new Dictionary<string, string>() {
                    {"en", "<color=#FFFF00>The base at <color=#FFFF00>{0}</color> has been raided by <color=#FFFF00>{1}</color>!</color>"},
                }},
                {"TargetNotFoundId", new Dictionary<string, string>() {
                    {"en", "<color=#FFFF00>Target {0} not found, or not online.</color>"},
                }},
                {"TargetNotFoundNoId", new Dictionary<string, string>() {
                    {"en", "<color=#FFFF00>No steamid provided.</color>"},
                }},
                {"DestroyingBaseAt", new Dictionary<string, string>() {
                    {"en", "<color=#C0C0C0>Destroying raid base at <color=#FFFF00>{0}</color> in <color=#FFFF00>{1}</color> minutes!</color>"},
                }},
                {"PasteIsBlocked", new Dictionary<string, string>() {
                    {"en", "You cannot start a raid base event there!"},
                }},
                {"LookElsewhere", new Dictionary<string, string>() {
                    {"en", "Unable to find a position; look elsewhere."},
                }},
                {"BuildingNotConfigured", new Dictionary<string, string>() {
                    {"en", "You cannot spawn a base that is not configured."},
                }},
                {"NoBuildingsConfigured", new Dictionary<string, string>() {
                    {"en", "No valid buildings have been configured."},
                }},
                {"DespawnBaseSuccess", new Dictionary<string, string>() {
                    {"en", "<color=#C0C0C0>Despawning the nearest raid base to you!</color>"},
                }},
                {"DespawnedAt", new Dictionary<string, string>() {
                    {"en", "{0} despawned a base manually at {1}"},
                }},
                {"DespawnedAll", new Dictionary<string, string>() {
                    {"en", "{0} despawned all bases manually"},
                }},
                {"ModeLevel", new Dictionary<string, string>() {
                    {"en", "level"},
                }},
                {"DespawnBaseNoneAvailable", new Dictionary<string, string>() {
                    {"en", "<color=#C0C0C0>You must be within 100m of a raid base to despawn it.</color>"},
                }},
                {"GridIsLoading", new Dictionary<string, string>() {
                    {"en", "The grid is loading; please wait until it has finished."},
                }},
                {"GridIsLoadingFormatted", new Dictionary<string, string>() {
                    {"en", "Grid is loading. The process has taken {0} seconds so far with {1} locations added on the grid."},
                }},
                {"TooPowerful", new Dictionary<string, string>() {
                    {"en", "<color=#FF0000>This place is guarded by a powerful spirit. You sheath your wand in fear!</color>"},
                }},
                {"TooPowerfulDrop", new Dictionary<string, string>() {
                    {"en", "<color=#FF0000>This place is guarded by a powerful spirit. You drop your wand in fear!</color>"},
                }},
                {"InstallCopyPastePlugin", new Dictionary<string, string>() {
                    {"en", "CopyPaste is not installed, and is required to use this plugin."},
                }},
                {"LoadSupportedCopyPasteVersion", new Dictionary<string, string>() {
                    {"en", "You must update your version of CopyPaste to 4.1.31 or higher!"},
                }},
                {"DoomAndGloom", new Dictionary<string, string>() {
                    {"en", "<color=#FF0000>You have left a {0} zone and can be attacked for another {1} seconds!</color>"},
                }},
                {"MaintainCoroutineFailedToday", new Dictionary<string, string>() {
                    {"en", "<color=#FF0000>Failed to start maintain coroutine; no difficulties are available today.</color>"},
                }},
                {"ScheduleCoroutineFailedToday", new Dictionary<string, string>() {
                    {"en", "<color=#FF0000>Failed to start scheduled coroutine; no difficulties are available today.</color>"},
                }},
                {"NoConfiguredLoot", new Dictionary<string, string>() {
                    {"en", "Error: No loot found in the config!"},
                }},
                {"NoContainersFound", new Dictionary<string, string>() {
                    {"en", "Error: No usable containers found for {0} @ {1}!"},
                }},
                {"NoBoxesFound", new Dictionary<string, string>() {
                    {"en", "Error: No usable boxes found for {0} @ {1}!"},
                }},
                {"NoLootSpawned", new Dictionary<string, string>() {
                    {"en", "Error: No loot was spawned!"},
                }},
                {"LoadedManual", new Dictionary<string, string>() {
                    {"en", "Loaded {0} manual spawns."},
                }},
                {"LoadedMaintained", new Dictionary<string, string>() {
                    {"en", "Loaded {0} maintained spawns."},
                }},
                {"LoadedScheduled", new Dictionary<string, string>() {
                    {"en", "Loaded {0} scheduled spawns."},
                }},
                {"InitializedGrid", new Dictionary<string, string>() {
                    {"en", "Grid initialization completed in {0} seconds and {1} milliseconds on a {2} size map. {3} locations are on the grid."},
                }},
                {"EntityCountMax", new Dictionary<string, string>() {
                    {"en", "Command disabled due to entity count being greater than 300k"},
                }},
                {"NotifyPlayerMessageFormat", new Dictionary<string, string>() {
                    {"en", "<color=#ADD8E6>{rank}</color>. <color=#C0C0C0>{name}</color> (<color=#FFFF00>{value}</color>)"},
                }},
                {"ConfigUseFormat", new Dictionary<string, string>() {
                    {"en", "Use: rb.config <{0}> [base] [subset]"},
                }},
                {"ConfigAddBaseSyntax", new Dictionary<string, string>() {
                    {"en", "Use: rb.config add nivex1 nivex4 nivex5 nivex6"},
                }},
                {"FileDoesNotExist", new Dictionary<string, string>() {
                    {"en", " > This file does not exist\n"},
                }},
                {"IsProfile", new Dictionary<string, string>() {
                    {"en", " > Profile\n"},
                }},
                {"ListingAll", new Dictionary<string, string>() {
                    {"en", "Listing all primary bases and their subsets:"},
                }},
                {"PrimaryBase", new Dictionary<string, string>() {
                    {"en", "Primary Base: "},
                }},
                {"AdditionalBase", new Dictionary<string, string>() {
                    {"en", "Additional Base: "},
                }},
                {"NoValidBuilingsWarning", new Dictionary<string, string>() {
                    {"en", "No valid buildings are configured with a valid file that exists. Did you configure valid files and reload the plugin?"},
                }},
                {"Adding", new Dictionary<string, string>() {
                    {"en", "Adding: {0}"},
                }},
                {"AddedPrimaryBase", new Dictionary<string, string>() {
                    {"en", "Added Primary Base: {0}"},
                }},
                {"AddedAdditionalBase", new Dictionary<string, string>() {
                    {"en", "Added Additional Base: {0}"},
                }},
                {"AddUnauthorizedAccessException", new Dictionary<string, string>() {
                    {"en", "You cannot save to profiles that have read/write errors! You must fix the file permissions first."},
                }},
                {"AddJsonException", new Dictionary<string, string>() {
                    {"en", "You cannot save to profiles that have json errors! You must fix the json error first. The server console message explains exactly where the error is, and how to fix it."},
                }},
                {"AddException", new Dictionary<string, string>() {
                    {"en", "You cannot save to profiles that have errors! You must fix the error first by reading the error message in the server console."},
                }},
                {"EntryAlreadyExists", new Dictionary<string, string>() {
                    {"en", "That entry already exists."},
                }},
                {"RemoveSyntax", new Dictionary<string, string>() {
                    {"en", "Use: rb.config remove nivex1"},
                }},
                {"RemovingAllBasesFor", new Dictionary<string, string>() {
                    {"en", "\nRemoving all bases for: {0}"},
                }},
                {"RemovedPrimaryBase", new Dictionary<string, string>() {
                    {"en", "Removed primary base: {0}"},
                }},
                {"RemovedAdditionalBase", new Dictionary<string, string>() {
                    {"en", "Removed additional base {0} from primary base {1}"},
                }},
                {"RemovedEntries", new Dictionary<string, string>() {
                    {"en", "Removed {0} entries"},
                }},
                {"LockedOut", new Dictionary<string, string>() {
                    {"en", "You are locked out from {0} raids for {1}"},
                }},
                {"PVPFlag", new Dictionary<string, string>() {
                    {"en", "[<color=#FF0000>PVP</color>] "},
                }},
                {"PVEFlag", new Dictionary<string, string>() {
                    {"en", "[<color=#008000>PVE</color>] "},
                }},
                {"PVP ZONE", new Dictionary<string, string>() {
                    {"en", "PVP ZONE"},
                }},
                {"PVE ZONE", new Dictionary<string, string>() {
                    {"en", "PVE ZONE"},
                }},
                {"OnPlayerExit", new Dictionary<string, string>()
                {
                    {"en", "<color=#FF0000>You have left a raidable PVP base!</color>"},
                }},
                {"OnPlayerExitPVE", new Dictionary<string, string>()
                {
                    {"en", "<color=#FF0000>You have left a raidable PVE base!</color>"},
                }},
                {"PasteIsBlockedStandAway", new Dictionary<string, string>() {
                    {"en", "You cannot start a raid base event there because you are too close to the spawn. Either move or use noclip."},
                }},
                {"ReloadConfig", new Dictionary<string, string>() {
                    {"en", "Reloading config..."},
                }},
                {"ReloadMaintainCo", new Dictionary<string, string>() {
                    {"en", "Stopped maintain coroutine."},
                }},
                {"ReloadScheduleCo", new Dictionary<string, string>() {
                    {"en", "Stopped schedule coroutine."},
                }},
                {"ReloadInit", new Dictionary<string, string>() {
                    {"en", "Initializing..."},
                }},
                {"YourCorpse", new Dictionary<string, string>() {
                    {"en", "Your Corpse"},
                }},
                {"NotAllowed", new Dictionary<string, string>() {
                    {"en", "<color=#FF0000>That action is not allowed in this zone.</color>"},
                }},
                {"BlockedZones", new Dictionary<string, string>() {
                    {"en", "Blocked spawn points in {0} zones."},
                }},
                {"UIFormat", new Dictionary<string, string>() {
                    {"en", "{0} C:{1} [{2}m]"},
                }},
                {"UIFormatContainers", new Dictionary<string, string>() {
                    {"en", "{0} C:{1}"},
                }},
                {"UIFormatMinutes", new Dictionary<string, string>() {
                    {"en", "{0} [{1}m]"},
                }},
                {"UIHelpText", new Dictionary<string, string>() {
                    {"en", "<color=#C0C0C0>You can toggle the UI by using <color=#FFA500>/{0} ui</color></color>"},
                }},
                {"HoggingFinishYourRaid", new Dictionary<string, string>() {
                    {"en", "<color=#FF0000>You must finish your last raid at {0} before joining another.</color>"},
                }},
                {"TimeFormat", new Dictionary<string, string>() {
                    {"en", "{0:D2}h {1:D2}m {2:D2}s"},
                }},
                {"TargetTooFar", new Dictionary<string, string>() {
                    {"en", "Your target is not close enough to a raid."},
                }},
                {"TooFar", new Dictionary<string, string>() {
                    {"en", "You are not close enough to a raid."},
                }},
                {"RaidLockedTo", new Dictionary<string, string>() {
                    {"en", "Raid has been locked to: {0}"},
                }},
                {"RaidOwnerCleared", new Dictionary<string, string>() {
                    {"en", "Raid owner has been cleared."},
                }},
                {"TooCloseToABuilding", new Dictionary<string, string>() {
                    {"en", "Too close to another building"},
                }},
                {"CommandNotAllowed", new Dictionary<string, string>() {
                    {"en", "You are not allowed to use this command right now."},
                }},
                {"Normal", new Dictionary<string, string>() {
                    {"en", "normal"},
                }},
                {"MapMarkerOrderWithMode", new Dictionary<string, string>() {
                    {"en", "{0}{1} {2}{3}"},
                }},
                {"MapMarkerOrderWithoutMode", new Dictionary<string, string>() {
                    {"en", "{0}{1}{2}"},
                }},
                {"InitializedGridSea", new Dictionary<string, string>() {
                    {"en", "{0} locations are on the seabed grid."},
                }},
                {"Elevator Health", new Dictionary<string, string>() {
                    {"en", "Elevator Health"},
                }},
                {"Elevator Green Card", new Dictionary<string, string>() {
                    {"en", "Elevator access requires a green access card!"},
                }},
                {"Elevator Blue Card", new Dictionary<string, string>() {
                    {"en", "Elevator access requires a blue access card!"},
                }},
                {"Elevator Red Card", new Dictionary<string, string>() {
                    {"en", "Elevator access requires a red access card!"},
                }},
                {"Elevator Special Card", new Dictionary<string, string>() {
                    {"en", "Elevator access requires a special access card!"},
                }},
                {"Elevator Privileges", new Dictionary<string, string>() {
                    {"en", "Elevator access requires building privileges!"},
                }},
            };
        }

        protected override void LoadDefaultMessages()
        {
            var compiledLangs = new Dictionary<string, Dictionary<string, string>>();

            foreach (var line in GetMessages())
            {
                foreach (var translate in line.Value)
                {
                    if (!compiledLangs.ContainsKey(translate.Key))
                        compiledLangs[translate.Key] = new Dictionary<string, string>();

                    compiledLangs[translate.Key][line.Key] = translate.Value;
                }
            }

            foreach (var cLangs in compiledLangs)
                lang.RegisterMessages(cLangs.Value, this, cLangs.Key);
        }

        public static string _(string key, string id = null, params object[] args)
        {
            string message = Backbone.Plugin.lang.GetMessage(key, Backbone.Plugin, id);

            return args.Length > 0 ? string.Format(message, args) : message;
        }

        private static void SendNotification(BasePlayer player, string message)
        {
            if (string.IsNullOrEmpty(message))
            {
                return;
            }

            if (config.EventMessages.Message)
            {
                Backbone.Plugin.Player.Message(player, message, config.Settings.ChatID);
            }

            if (config.EventMessages.NotifyType == -1 || Backbone.Plugin.Notify == null)
            {
                return;
            }

            Backbone.Plugin.Notify?.Call("SendNotify", player, config.EventMessages.NotifyType, message);
        }

        private static Configuration config;

        private static List<PasteOption> DefaultPasteOptions
        {
            get
            {
                return new List<PasteOption>
                {
                    new PasteOption() { Key = "stability", Value = "false" },
                    new PasteOption() { Key = "autoheight", Value = "false" },
                    new PasteOption() { Key = "height", Value = "1.0" }
                };
            }
        }

        private static Dictionary<string, BuildingOptions> DefaultBuildingOptions
        {
            get
            {
                return new Dictionary<string, BuildingOptions>()
                {
                    ["RaidBases"] = new BuildingOptions
                    {
                        AdditionalBases = new Dictionary<string, List<PasteOption>>
                        {
                            ["RaidBase1"] = DefaultPasteOptions,
                            ["RaidBase2"] = DefaultPasteOptions,
                            ["RaidBase3"] = DefaultPasteOptions,
                            ["RaidBase4"] = DefaultPasteOptions,
                            ["RaidBase5"] = DefaultPasteOptions
                        },
                        PasteOptions = DefaultPasteOptions,
                        NPC = new NpcSettings
                        {
                            Accuracy = 25f,
                            SpawnAmount = 8
                        }
                    }
                };
            }
        }

        private static List<TreasureItem> DefaultLoot
        {
            get
            {
                return new List<TreasureItem>
                {
                    new TreasureItem { shortname = "ammo.pistol", amount = 40, skin = 0, amountMin = 40 },
                    new TreasureItem { shortname = "ammo.pistol.fire", amount = 40, skin = 0, amountMin = 40 },
                    new TreasureItem { shortname = "ammo.pistol.hv", amount = 40, skin = 0, amountMin = 40 },
                    new TreasureItem { shortname = "ammo.rifle", amount = 60, skin = 0, amountMin = 60 },
                    new TreasureItem { shortname = "ammo.rifle.explosive", amount = 60, skin = 0, amountMin = 60 },
                    new TreasureItem { shortname = "ammo.rifle.hv", amount = 60, skin = 0, amountMin = 60 },
                    new TreasureItem { shortname = "ammo.rifle.incendiary", amount = 60, skin = 0, amountMin = 60 },
                    new TreasureItem { shortname = "ammo.shotgun", amount = 24, skin = 0, amountMin = 24 },
                    new TreasureItem { shortname = "ammo.shotgun.slug", amount = 40, skin = 0, amountMin = 40 },
                    new TreasureItem { shortname = "surveycharge", amount = 20, skin = 0, amountMin = 20 },
                    new TreasureItem { shortname = "bucket.helmet", amount = 1, skin = 0, amountMin = 1 },
                    new TreasureItem { shortname = "cctv.camera", amount = 1, skin = 0, amountMin = 1 },
                    new TreasureItem { shortname = "coffeecan.helmet", amount = 1, skin = 0, amountMin = 1 },
                    new TreasureItem { shortname = "explosive.timed", amount = 1, skin = 0, amountMin = 1 },
                    new TreasureItem { shortname = "metal.facemask", amount = 1, skin = 0, amountMin = 1 },
                    new TreasureItem { shortname = "metal.plate.torso", amount = 1, skin = 0, amountMin = 1 },
                    new TreasureItem { shortname = "mining.quarry", amount = 1, skin = 0, amountMin = 1 },
                    new TreasureItem { shortname = "pistol.m92", amount = 1, skin = 0, amountMin = 1 },
                    new TreasureItem { shortname = "rifle.ak", amount = 1, skin = 0, amountMin = 1 },
                    new TreasureItem { shortname = "rifle.bolt", amount = 1, skin = 0, amountMin = 1 },
                    new TreasureItem { shortname = "rifle.lr300", amount = 1, skin = 0, amountMin = 1 },
                    new TreasureItem { shortname = "shotgun.pump", amount = 1, skin = 0, amountMin = 1 },
                    new TreasureItem { shortname = "shotgun.spas12", amount = 1, skin = 0, amountMin = 1 },
                    new TreasureItem { shortname = "smg.2", amount = 1, skin = 0, amountMin = 1 },
                    new TreasureItem { shortname = "smg.mp5", amount = 1, skin = 0, amountMin = 1 },
                    new TreasureItem { shortname = "smg.thompson", amount = 1, skin = 0, amountMin = 1 },
                    new TreasureItem { shortname = "supply.signal", amount = 1, skin = 0, amountMin = 1 },
                    new TreasureItem { shortname = "targeting.computer", amount = 1, skin = 0, amountMin = 1 },
                    new TreasureItem { shortname = "metal.refined", amount = 150, skin = 0, amountMin = 150 },
                    new TreasureItem { shortname = "stones", amount = 15000, skin = 0, amountMin = 7500 },
                    new TreasureItem { shortname = "sulfur", amount = 7500, skin = 0, amountMin = 2500 },
                    new TreasureItem { shortname = "metal.fragments", amount = 7500, skin = 0, amountMin = 2500 },
                    new TreasureItem { shortname = "charcoal", amount = 5000, skin = 0, amountMin = 1000 },
                    new TreasureItem { shortname = "gunpowder", amount = 3500, skin = 0, amountMin = 1000 },
                    new TreasureItem { shortname = "scrap", amount = 150, skin = 0, amountMin = 100 },
                };
            }
        }

        public class PluginSettingsBaseBlocks
        {
            [JsonProperty(PropertyName = "Block Clans From Owning More Than One Raid")]
            public bool BlockClans { get; set; }

            [JsonProperty(PropertyName = "Block Friends From Owning More Than One Raid")]
            public bool BlockFriends { get; set; }

            [JsonProperty(PropertyName = "Block Teams From Owning More Than One Raid")]
            public bool BlockTeams { get; set; }

            public bool IsBlocking() => BlockClans || BlockFriends || BlockTeams;
        }

        public class PluginSettingsColors1
        {
            [JsonProperty(PropertyName = "Normal")]
            public string Normal { get; set; } = "000000";
        }

        public class PluginSettingsColors2
        {
            [JsonProperty(PropertyName = "Normal")]
            public string Normal { get; set; } = "00FF00";
        }

        public class PluginSettingsBaseManagementMountables
        {
            [JsonProperty(PropertyName = "All Controlled Mounts")]
            public bool ControlledMounts { get; set; }

            [JsonProperty(PropertyName = "Boats")]
            public bool Boats { get; set; }

            [JsonProperty(PropertyName = "Cars (Basic)")]
            public bool BasicCars { get; set; }

            [JsonProperty(PropertyName = "Cars (Modular)")]
            public bool ModularCars { get; set; }

            [JsonProperty(PropertyName = "Chinook")]
            public bool CH47 { get; set; }

            [JsonProperty(PropertyName = "Horses")]
            public bool Horses { get; set; }

            [JsonProperty(PropertyName = "MiniCopters")]
            public bool MiniCopters { get; set; }

            [JsonProperty(PropertyName = "Pianos")]
            public bool Pianos { get; set; } = true;

            [JsonProperty(PropertyName = "Scrap Transport Helicopters")]
            public bool Scrap { get; set; }
        }

        public class PluginSettingsBaseManagement
        {
            [JsonProperty(PropertyName = "Eject Mounts")]
            public PluginSettingsBaseManagementMountables Mounts { get; set; } = new PluginSettingsBaseManagementMountables();
            
            [JsonProperty(PropertyName = "Additional Containers To Include As Boxes", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> Inherit { get; set; } = new List<string>();

            [JsonProperty(PropertyName = "Difficulty Colors (Border)", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public PluginSettingsColors1 Colors1 { get; set; } = new PluginSettingsColors1();

            [JsonProperty(PropertyName = "Difficulty Colors (Inner)", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public PluginSettingsColors2 Colors2 { get; set; } = new PluginSettingsColors2();

            [JsonProperty(PropertyName = "Anti Terrain Clipping Distance (Advanced Users Only)")]
            public float Anti { get; set; } = 50f;

            [JsonProperty(PropertyName = "Allow Teleport")]
            public bool AllowTeleport { get; set; }

            [JsonProperty(PropertyName = "Allow Cupboard Loot To Drop")]
            public bool AllowCupboardLoot { get; set; } = true;

            [JsonProperty(PropertyName = "Allow Players To Build")]
            public bool AllowBuilding { get; set; } = true;

            [JsonProperty(PropertyName = "Allow Players To Use Ladders")]
            public bool AllowLadders { get; set; } = true;

            [JsonProperty(PropertyName = "Allow Players To Upgrade Event Buildings")]
            public bool AllowUpgrade { get; set; }

            [JsonProperty(PropertyName = "Allow Player Bags To Be Lootable At PVP Bases")]
            public bool PlayersLootableInPVP { get; set; } = true;

            [JsonProperty(PropertyName = "Allow Player Bags To Be Lootable At PVE Bases")]
            public bool PlayersLootableInPVE { get; set; } = true;

            [JsonProperty(PropertyName = "Allow Traps To Drop Loot")]
            public bool DropLootTraps { get; set; }

            [JsonProperty(PropertyName = "Allow Players To Loot Traps")]
            public bool LootableTraps { get; set; }

            [JsonProperty(PropertyName = "Allow Raid Bases On Roads")]
            public bool AllowOnRoads { get; set; } = true;

            [JsonProperty(PropertyName = "Allow Raid Bases On Rivers")]
            public bool AllowOnRivers { get; set; } = true;

            [JsonProperty(PropertyName = "Allow Raid Bases On Building Topology")]
            public bool AllowOnBuildingTopology { get; set; } = true;

            [JsonProperty(PropertyName = "Allow Vending Machines To Broadcast")]
            public bool AllowBroadcasting { get; set; }

            [JsonProperty(PropertyName = "Block Npc Kits Plugin")]
            public bool BlockNpcKits { get; set; }

            [JsonProperty(PropertyName = "Prevent Bases From Floating Above Water By Also Checking Surrounding Area")]
            public bool SubmergedAreaCheck { get; set; }

            [JsonProperty(PropertyName = "Maximum Water Depth Level Used For Float Above Water Option")]
            public float WaterDepth { get; set; } = 1f;

            [JsonProperty(PropertyName = "Backpacks Can Be Opened At PVE Bases")]
            public bool BackpacksOpenPVE { get; set; } = true;

            [JsonProperty(PropertyName = "Backpacks Can Be Opened At PVP Bases")]
            public bool BackpacksOpenPVP { get; set; } = true;

            [JsonProperty(PropertyName = "Backpacks Drop At PVE Bases")]
            public bool BackpacksPVE { get; set; }

            [JsonProperty(PropertyName = "Backpacks Drop At PVP Bases")]
            public bool BackpacksPVP { get; set; }

            [JsonProperty(PropertyName = "Block Mounted Damage To Bases And Players")]
            public bool BlockMounts { get; set; }

            [JsonProperty(PropertyName = "Block RestoreUponDeath Plugin For PVP Bases")]
            public bool BlockRestorePVP { get; set; }

            [JsonProperty(PropertyName = "Block RestoreUponDeath Plugin For PVE Bases")]
            public bool BlockRestorePVE { get; set; }

            [JsonProperty(PropertyName = "Bypass Lock Treasure To First Attacker For PVE Bases")]
            public bool BypassUseOwnersForPVE { get; set; }

            [JsonProperty(PropertyName = "Bypass Lock Treasure To First Attacker For PVP Bases")]
            public bool BypassUseOwnersForPVP { get; set; }

            [JsonProperty(PropertyName = "Despawn Spawned Mounts")]
            public bool DespawnMounts { get; set; } = true;

            [JsonProperty(PropertyName = "Do Not Destroy Player Built Deployables")]
            public bool DoNotDestroyDeployables { get; set; } = true;

            [JsonProperty(PropertyName = "Do Not Destroy Player Built Structures")]
            public bool DoNotDestroyStructures { get; set; } = true;

            [JsonProperty(PropertyName = "Divide Rewards Among All Raiders")]
            public bool DivideRewards { get; set; } = true;

            [JsonProperty(PropertyName = "Draw Corpse Time (Seconds)")]
            public float DrawTime { get; set; } = 300f;

            [JsonProperty(PropertyName = "Eject Sleepers Before Spawning Base")]
            public bool EjectSleepers { get; set; } = true;

            [JsonProperty(PropertyName = "Extra Distance To Spawn From Monuments")]
            public float MonumentDistance { get; set; }

            [JsonProperty(PropertyName = "Move Cookables Into Ovens")]
            public bool Cook { get; set; } = true;

            [JsonProperty(PropertyName = "Move Food Into BBQ Or Fridge")]
            public bool Food { get; set; } = true;

            [JsonProperty(PropertyName = "Move Resources Into Tool Cupboard")]
            public bool Cupboard { get; set; } = true;

            [JsonProperty(PropertyName = "Move Items Into Lockers")]
            public bool Lockers { get; set; } = true;

            [JsonProperty(PropertyName = "Lock Treasure To First Attacker")]
            public bool UseOwners { get; set; } = true;

            [JsonProperty(PropertyName = "Lock Treasure Max Inactive Time (Minutes)")]
            public float LockTime { get; set; } = 10f;

            [JsonProperty(PropertyName = "Minutes Until Despawn After Looting (min: 1)")]
            public int DespawnMinutes { get; set; } = 15;

            [JsonProperty(PropertyName = "Minutes Until Despawn After Inactive (0 = disabled)")]
            public int DespawnMinutesInactive { get; set; } = 45;

            [JsonProperty(PropertyName = "Minutes Until Despawn After Inactive Resets When Damaged")]
            public bool DespawnMinutesInactiveReset { get; set; } = true;

            [JsonProperty(PropertyName = "Mounts Can Take Damage From Players")]
            public bool MountDamageFromPlayers { get; set; }

            [JsonProperty(PropertyName = "Mounts Can Take Damage From SamSites")]
            public bool MountDamageFromSamSites { get; set; } = true;

            [JsonProperty(PropertyName = "Player Cupboard Detection Radius")]
            public float CupboardDetectionRadius { get; set; } = 75f;

            [JsonProperty(PropertyName = "Players With PVP Delay Can Damage Anything Inside Zone")]
            public bool PVPDelayDamageInside { get; set; }

            [JsonProperty(PropertyName = "Players With PVP Delay Can Damage Other Players With PVP Delay Anywhere")]
            public bool PVPDelayAnywhere { get; set; }

            [JsonProperty(PropertyName = "PVP Delay Between Zone Hopping")]
            public float PVPDelay { get; set; } = 10f;

            [JsonProperty(PropertyName = "Prevent Fire From Spreading")]
            public bool PreventFireFromSpreading { get; set; } = true;

            [JsonProperty(PropertyName = "Prevent Players From Hogging Raids")]
            public bool PreventHogging { get; set; } = true;

            [JsonProperty(PropertyName = "Prevent Fall Damage When Base Despawns")]
            public bool PreventFallDamage { get; set; }

            [JsonProperty(PropertyName = "Require Cupboard To Be Looted Before Despawning")]
            public bool RequireCupboardLooted { get; set; }

            [JsonProperty(PropertyName = "Destroying The Cupboard Completes The Raid")]
            public bool EndWhenCupboardIsDestroyed { get; set; }

            [JsonProperty(PropertyName = "Require All Bases To Spawn Before Respawning An Existing Base")]
            public bool RequireAllSpawned { get; set; }

            [JsonProperty(PropertyName = "Turn Lights On At Night")]
            public bool Lights { get; set; } = true;

            [JsonProperty(PropertyName = "Turn Lights On Indefinitely")]
            public bool AlwaysLights { get; set; }

            [JsonProperty(PropertyName = "Traps And Turrets Ignore Users Using NOCLIP")]
            public bool IgnoreFlying { get; set; }

            [JsonProperty(PropertyName = "Use Random Codes On Code Locks")]
            public bool RandomCodes { get; set; } = true;

            [JsonProperty(PropertyName = "Wait To Start Despawn Timer When Base Takes Damage From Player")]
            public bool Engaged { get; set; }
        }

        public class PluginSettingsMapMarkers
        {
            [JsonProperty(PropertyName = "Marker Name")]
            public string MarkerName { get; set; } = "Raidable Base Event";

            [JsonProperty(PropertyName = "Radius")]
            public float Radius { get; set; } = 0.25f;

            [JsonProperty(PropertyName = "Use Vending Map Marker")]
            public bool UseVendingMarker { get; set; } = true;

            [JsonProperty(PropertyName = "Use Explosion Map Marker")]
            public bool UseExplosionMarker { get; set; }

            [JsonProperty(PropertyName = "Create Markers For Maintained Events")]
            public bool Maintained { get; set; } = true;

            [JsonProperty(PropertyName = "Create Markers For Scheduled Events")]
            public bool Scheduled { get; set; } = true;

            [JsonProperty(PropertyName = "Create Markers For Manual Events")]
            public bool Manual { get; set; } = true;
        }

        public class PluginSettings
        {
            [JsonProperty(PropertyName = "Raid Management")]
            public PluginSettingsBaseManagement Management { get; set; } = new PluginSettingsBaseManagement();

            [JsonProperty(PropertyName = "Map Markers")]
            public PluginSettingsMapMarkers Markers { get; set; } = new PluginSettingsMapMarkers();

            [JsonProperty(PropertyName = "Maintained Events")]
            public RaidableBaseSettingsMaintained Maintained { get; set; } = new RaidableBaseSettingsMaintained();

            [JsonProperty(PropertyName = "Manual Events")]
            public RaidableBaseSettingsManual Manual { get; set; } = new RaidableBaseSettingsManual();

            [JsonProperty(PropertyName = "Scheduled Events")]
            public RaidableBaseSettingsScheduled Schedule { get; set; } = new RaidableBaseSettingsScheduled();

            [JsonProperty(PropertyName = "Allowed Zone Manager Zones", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> Inclusions { get; set; } = new List<string> { "pvp", "99999999" };

            [JsonProperty(PropertyName = "Blacklisted Commands", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> BlacklistedCommands { get; set; } = new List<string>();

            [JsonProperty(PropertyName = "Automatically Teleport Admins To Their Map Marker Positions")]
            public bool TeleportMarker { get; set; } = true;

            [JsonProperty(PropertyName = "Block Wizardry Plugin At Events")]
            public bool NoWizardry { get; set; }

            [JsonProperty(PropertyName = "Chat Steam64ID")]
            public ulong ChatID { get; set; }

            [JsonProperty(PropertyName = "Expansion Mode (Dangerous Treasures)")]
            public bool ExpansionMode { get; set; }

            [JsonProperty(PropertyName = "Remove Admins From Raiders List")]
            public bool RemoveAdminRaiders { get; set; }

            [JsonProperty(PropertyName = "Show X Z Coordinates")]
            public bool ShowXZ { get; set; }

            [JsonProperty(PropertyName = "Event Command")]
            public string EventCommand { get; set; } = "rbe";

            [JsonProperty(PropertyName = "Hunter Command")]
            public string HunterCommand { get; set; } = "rb";

            [JsonProperty(PropertyName = "Server Console Command")]
            public string ConsoleCommand { get; set; } = "rbevent";

            [JsonProperty(PropertyName = "Extended Distance To Spawn Away From Zone Manager Zones")]
            public float ZoneDistance { get; set; } = 25f;
        }

        public class EventMessageSettings
        {
            [JsonProperty(PropertyName = "Announce Raid Unlocked")]
            public bool AnnounceRaidUnlock { get; set; }

            [JsonProperty(PropertyName = "Announce Thief Message")]
            public bool AnnounceThief { get; set; } = true;

            [JsonProperty(PropertyName = "Announce PVE/PVP Enter/Exit Messages")]
            public bool AnnounceEnterExit { get; set; } = true;

            [JsonProperty(PropertyName = "Show Destroy Warning")]
            public bool ShowWarning { get; set; } = true;

            [JsonProperty(PropertyName = "Show Opened Message")]
            public bool Opened { get; set; } = true;

            [JsonProperty(PropertyName = "Show Prefix")]
            public bool Prefix { get; set; } = true;

            [JsonProperty(PropertyName = "Notify Plugin - Type (-1 = disabled)")]
            public int NotifyType { get; set; } = -1;

            [JsonProperty(PropertyName = "Send Messages To Player")]
            public bool Message { get; set; } = true;
        }

        public class GUIAnnouncementSettings
        {
            [JsonProperty(PropertyName = "Enabled")]
            public bool Enabled { get; set; }

            [JsonProperty(PropertyName = "Banner Tint Color")]
            public string TintColor { get; set; } = "Grey";

            [JsonProperty(PropertyName = "Maximum Distance")]
            public float Distance { get; set; } = 300f;

            [JsonProperty(PropertyName = "Text Color")]
            public string TextColor { get; set; } = "White";
        }

        public class LustyMapSettings
        {
            [JsonProperty(PropertyName = "Enabled")]
            public bool Enabled { get; set; }

            [JsonProperty(PropertyName = "Icon File")]
            public string IconFile { get; set; } = "http://i.imgur.com/XoEMTJj.png";

            [JsonProperty(PropertyName = "Icon Name")]
            public string IconName { get; set; } = "rbevent";

            [JsonProperty(PropertyName = "Icon Rotation")]
            public float IconRotation { get; set; }
        }

        public class MurdererKitSettings
        {
            [JsonProperty(PropertyName = "Helm", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> Helm { get; set; } = new List<string> { "metal.facemask" };

            [JsonProperty(PropertyName = "Torso", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> Torso { get; set; } = new List<string> { "metal.plate.torso" };

            [JsonProperty(PropertyName = "Pants", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> Pants { get; set; } = new List<string> { "pants" };

            [JsonProperty(PropertyName = "Gloves", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> Gloves { get; set; } = new List<string> { "tactical.gloves" };

            [JsonProperty(PropertyName = "Boots", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> Boots { get; set; } = new List<string> { "boots.frog" };

            [JsonProperty(PropertyName = "Shirt", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> Shirt { get; set; } = new List<string> { "tshirt" };

            [JsonProperty(PropertyName = "Kilts", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> Kilts { get; set; } = new List<string>();

            [JsonProperty(PropertyName = "Weapon", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> Weapon { get; set; } = new List<string> { "machete" };
        }

        public class ScientistKitSettings
        {
            [JsonProperty(PropertyName = "Helm", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> Helm { get; set; } = new List<string>();

            [JsonProperty(PropertyName = "Torso", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> Torso { get; set; } = new List<string> { "hazmatsuit_scientist_peacekeeper" };

            [JsonProperty(PropertyName = "Pants", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> Pants { get; set; } = new List<string>();

            [JsonProperty(PropertyName = "Gloves", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> Gloves { get; set; } = new List<string>();

            [JsonProperty(PropertyName = "Boots", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> Boots { get; set; } = new List<string>();

            [JsonProperty(PropertyName = "Shirt", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> Shirt { get; set; } = new List<string>();

            [JsonProperty(PropertyName = "Kilts", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> Kilts { get; set; } = new List<string>();

            [JsonProperty(PropertyName = "Weapon", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> Weapon { get; set; } = new List<string> { "rifle.ak" };
        }

        public class NpcMultiplierSettings
        {
            [JsonProperty(PropertyName = "Gun Damage Multiplier")]
            public float ProjectileDamageMultiplier { get; set; } = 1f;

            [JsonProperty(PropertyName = "Melee Damage Multiplier")]
            public float MeleeDamageMultiplier { get; set; } = 1f;
        }

        public class NpcSettings
        {
            [JsonProperty(PropertyName = "Damage Multipliers")]
            public NpcMultiplierSettings Multipliers { get; set; } = new NpcMultiplierSettings();

            [JsonProperty(PropertyName = "Murderer (Items)")]
            public MurdererKitSettings MurdererItems { get; set; } = new MurdererKitSettings();

            [JsonProperty(PropertyName = "Scientist (Items)")]
            public ScientistKitSettings ScientistItems { get; set; } = new ScientistKitSettings();

            [JsonProperty(PropertyName = "Murderer Kits", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> MurdererKits { get; set; } = new List<string> { "murderer_kit_1", "murderer_kit_2" };

            [JsonProperty(PropertyName = "Scientist Kits", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> ScientistKits { get; set; } = new List<string> { "scientist_kit_1", "scientist_kit_2" };

            [JsonProperty(PropertyName = "Random Names", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> RandomNames { get; set; } = new List<string>();

            [JsonProperty(PropertyName = "Allow Npcs To Leave Dome When Attacking")]
            public bool CanLeave { get; set; } = true;

            [JsonProperty(PropertyName = "Allow Npcs To Shoot Players Outside Of The Dome")]
            public bool CanShoot { get; set; } = true;

            [JsonProperty(PropertyName = "Aggression Range")]
            public float AggressionRange { get; set; } = 70f;

            [JsonProperty(PropertyName = "Block Damage Outside To Npcs When Not Allowed To Leave Dome")]
            public bool BlockOutsideDamageOnLeave { get; set; } = true;

            [JsonProperty(PropertyName = "Block Damage Outside Of The Dome To Npcs Inside")]
            public bool BlockOutsideDamageToNpcsInside { get; set; }

            [JsonProperty(PropertyName = "Despawn Inventory On Death")]
            public bool DespawnInventory { get; set; } = true;

            [JsonProperty(PropertyName = "Enabled")]
            public bool Enabled { get; set; } = true;

            [JsonProperty(PropertyName = "Health For Murderers (100 min, 5000 max)")]
            public float MurdererHealth { get; set; } = 150f;

            [JsonProperty(PropertyName = "Health For Scientists (100 min, 5000 max)")]
            public float ScientistHealth { get; set; } = 150f;

            [JsonProperty(PropertyName = "Amount To Spawn")]
            public int SpawnAmount { get; set; } = 3;
            
            [JsonProperty(PropertyName = "Minimum Amount To Spawn")]
            public int SpawnMinAmount { get; set; } = 1;

            [JsonProperty(PropertyName = "Use Dangerous Treasures NPCs")]
            public bool UseExpansionNpcs { get; set; }

            [JsonProperty(PropertyName = "Spawn Murderers And Scientists")]
            public bool SpawnBoth { get; set; } = true;

            [JsonProperty(PropertyName = "Scientist Weapon Accuracy (0 - 100)")]
            public float Accuracy { get; set; } = 30f;

            [JsonProperty(PropertyName = "Spawn Murderers")]
            public bool SpawnMurderers { get; set; }

            [JsonProperty(PropertyName = "Spawn Random Amount")]
            public bool SpawnRandomAmount { get; set; }

            [JsonProperty(PropertyName = "Spawn Scientists Only")]
            public bool SpawnScientistsOnly { get; set; }

            [JsonProperty(PropertyName = "Player Traps And Turrets Ignore Npcs")]
            public bool IgnoreTrapsTurrets { get; set; }
        }

        public class PasteOption
        {
            [JsonProperty(PropertyName = "Option")]
            public string Key { get; set; }

            [JsonProperty(PropertyName = "Value")]
            public string Value { get; set; }
        }

        public class BuildingLevelOne
        {
            [JsonProperty(PropertyName = "Amount (0 = disabled)")]
            public int Amount { get; set; }

            [JsonProperty(PropertyName = "Chance To Play")]
            public float Chance { get; set; } = 0.5f;
        }

        public class BuildingLevels
        {
            [JsonProperty(PropertyName = "Level 2 - Final Death")]
            public bool Level2 { get; set; }
        }

        public class BuildingGradeLevels
        {
            [JsonProperty(PropertyName = "Wooden")]
            public bool Wooden { get; set; }

            [JsonProperty(PropertyName = "Stone")]
            public bool Stone { get; set; }

            [JsonProperty(PropertyName = "Metal")]
            public bool Metal { get; set; }

            [JsonProperty(PropertyName = "HQM")]
            public bool HQM { get; set; }

            public bool Any() => Wooden || Stone || Metal || HQM;
        }

        public class BuildingOptionsAutoTurrets
        {
            [JsonProperty(PropertyName = "Aim Cone")]
            public float AimCone { get; set; } = 5f;

            [JsonProperty(PropertyName = "Minimum Damage Modifier")]
            public float Min { get; set; } = 1f;

            [JsonProperty(PropertyName = "Maximum Damage Modifier")]
            public float Max { get; set; } = 1f;

            [JsonProperty(PropertyName = "Start Health")]
            public float Health { get; set; } = 1000f;

            [JsonProperty(PropertyName = "Sight Range")]
            public float SightRange { get; set; } = 30f;

            [JsonProperty(PropertyName = "Double Sight Range When Shot")]
            public bool AutoAdjust { get; set; }

            [JsonProperty(PropertyName = "Set Hostile (False = Do Not Set Any Mode)")]
            public bool Hostile { get; set; } = true;

            [JsonProperty(PropertyName = "Requires Power Source")]
            public bool RequiresPower { get; set; }

            [JsonProperty(PropertyName = "Remove Equipped Weapon")]
            public bool RemoveWeapon { get; set; }

            [JsonProperty(PropertyName = "Random Weapons To Equip When Unequipped", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> Shortnames { get; set; } = new List<string> { "rifle.ak" };
        }

        public class BuildingOptionsSetupSettings
        {
            [JsonProperty(PropertyName = "Amount Of Entities To Spawn Per Batch")]
            public int SpawnLimit { get; set; } = 1;

            [JsonProperty(PropertyName = "Amount Of Entities To Despawn Per Batch")]
            public int DespawnLimit { get; set; } = 10;

            [JsonProperty(PropertyName = "Height Adjustment Applied To This Paste")]
            public float PasteHeightAdjustment { get; set; }

            [JsonProperty(PropertyName = "Force All Bases To Spawn At Height Level (0 = Water)")]
            public float ForcedHeight { get; set; } = -1f;

            [JsonProperty(PropertyName = "Foundations Immune To Damage When Forced Height Is Applied")]
            public bool FoundationsImmune { get; set; }

            [JsonProperty(PropertyName = "Recalculate Spawn Position Before Paste")]
            public bool Recalculate { get; set; }

            [JsonProperty(PropertyName = "Teleport Entities Underworld Before Despawning")]
            public bool TeleportEntities { get; set; }
        }

        public class BuildingWaterOptions
        {
            [JsonProperty(PropertyName = "Allow Bases To Float Above Water")]
            public bool AllowSubmerged { get; set; }

            [JsonProperty(PropertyName = "Chance For Underwater Bases To Spawn (0-100) (BETA - WORK IN PROGRESS)")]
            public float Seabed { get; set; }

            [JsonProperty(PropertyName = "Prevent Bases From Floating Above Water By Also Checking Surrounding Area")]
            public bool SubmergedAreaCheck { get; set; }

            [JsonProperty(PropertyName = "Maximum Water Depth Level Used For Float Above Water Option")]
            public float WaterDepth { get; set; } = 1f;

            [JsonIgnore]
            public bool SpawnOnSeabed;
        }

        public class BuildingOptionsElevators
        {
            [JsonProperty(PropertyName = "UI Enabled")]
            public bool Enabled { get; set; } = true;

            [JsonProperty(PropertyName = "Anchor Min")]
            public string Min { get; set; } = "0.406 0.915";

            [JsonProperty(PropertyName = "Anchor Max")]
            public string Max { get; set; } = "0.59 0.949";

            [JsonProperty(PropertyName = "Panel Color")]
            public string PanelColor { get; set; } = "#000000";

            [JsonProperty(PropertyName = "Panel Alpha")]
            public float PanelAlpha { get; set; } = 0f;

            [JsonProperty(PropertyName = "Required Access Level")]
            public int RequiredAccessLevel { get; set; }

            [JsonProperty(PropertyName = "Required Access Level Grants Permanent Use")]
            public bool RequiredAccessLevelOnce { get; set; }

            [JsonProperty(PropertyName = "Required Keycard Skin ID")]
            public ulong SkinID { get; set; } = 2690554489;

            [JsonProperty(PropertyName = "Requires Building Permission")]
            public bool RequiresBuildingPermission { get; set; }

            [JsonProperty(PropertyName = "Button Health")]
            public float ButtonHealth { get; set; } = 1000f;

            [JsonProperty(PropertyName = "Elevator Health")]
            public float ElevatorHealth { get; set; } = 600f;
        }

        public class BuildingOptions
        {
            [JsonProperty(PropertyName = "Difficulty (0 = easy, 1 = medium, 2 = hard, 3 = expert, 4 = nightmare)", NullValueHandling = NullValueHandling.Ignore)]
            public int? Mode { get; set; } = null;

            [JsonProperty(PropertyName = "Advanced Setup Settings")]
            public BuildingOptionsSetupSettings Setup { get; set; } = new BuildingOptionsSetupSettings();

            [JsonProperty(PropertyName = "Water Settings")]
            public BuildingWaterOptions Water { get; set; } = new BuildingWaterOptions();

            [JsonProperty(PropertyName = "Elevators")]
            public BuildingOptionsElevators Elevators { get; set; } = new BuildingOptionsElevators();

            [JsonProperty(PropertyName = "Entities Not Allowed To Be Picked Up", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> BlacklistedPickupItems { get; set; } = new List<string> { "generator.small", "generator.static", "autoturret_deployed" };
            
            [JsonProperty(PropertyName = "Additional Bases For This Difficulty", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public Dictionary<string, List<PasteOption>> AdditionalBases { get; set; } = new Dictionary<string, List<PasteOption>>();

            [JsonProperty(PropertyName = "Paste Options", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<PasteOption> PasteOptions { get; set; } = new List<PasteOption>();

            [JsonProperty(PropertyName = "Arena Walls")]
            public RaidableBaseWallOptions ArenaWalls { get; set; } = new RaidableBaseWallOptions();

            [JsonProperty(PropertyName = "NPC Levels")]
            public BuildingLevels Levels { get; set; } = new BuildingLevels();

            [JsonProperty(PropertyName = "NPCs")]
            public NpcSettings NPC { get; set; } = new NpcSettings();

            [JsonProperty(PropertyName = "Rewards")]
            public RewardSettings Rewards { get; set; } = new RewardSettings();

            [JsonProperty(PropertyName = "Change Building Material Tier To")]
            public BuildingGradeLevels Tiers { get; set; } = new BuildingGradeLevels();

            [JsonProperty(PropertyName = "Auto Turrets")]
            public BuildingOptionsAutoTurrets AutoTurret { get; set; } = new BuildingOptionsAutoTurrets();

            [JsonProperty(PropertyName = "Player Building Restrictions")]
            public BuildingGradeLevels BuildingRestrictions { get; set; } = new BuildingGradeLevels();

            [JsonProperty(PropertyName = "Loot (Empty List = Use Treasure Loot)", NullValueHandling = NullValueHandling.Ignore)]
            public List<TreasureItem> Loot { get; set; } = new List<TreasureItem>();

            [JsonProperty(PropertyName = "Profile Enabled")]
            public bool Enabled { get; set; } = true;

            [JsonProperty(PropertyName = "Maximum Elevation Level")]
            public float Elevation { get; set; } = 2.5f;

            [JsonProperty(PropertyName = "Add Code Lock To Unlocked Or KeyLocked Doors")]
            public bool DoorLock { get; set; } = true;

            [JsonProperty(PropertyName = "Add Code Lock To Tool Cupboards")]
            public bool LockPrivilege { get; set; }

            [JsonProperty(PropertyName = "Add Code Lock To Boxes")]
            public bool LockBoxes { get; set; }

            [JsonProperty(PropertyName = "Close Open Doors With No Door Controller Installed")]
            public bool CloseOpenDoors { get; set; } = true;

            [JsonProperty(PropertyName = "Allow Duplicate Items")]
            public bool AllowDuplicates { get; set; }

            [JsonProperty(PropertyName = "Allow Players To Pickup Deployables")]
            public bool AllowPickup { get; set; }

            [JsonProperty(PropertyName = "Allow Players To Deploy A Cupboard")]
            public bool AllowBuildingPriviledges { get; set; } = true;

            [JsonProperty(PropertyName = "Allow PVP")]
            public bool AllowPVP { get; set; } = true;

            [JsonProperty(PropertyName = "Allow Friendly Fire (Teams)")]
            public bool AllowFriendlyFire { get; set; } = true;

            [JsonProperty(PropertyName = "Minimum Amount Of Items To Spawn (0 = Use Max Value)")]
            public int MinTreasure { get; set; }

            [JsonProperty(PropertyName = "Amount Of Items To Spawn")]
            public int MaxTreasure { get; set; } = 30;

            [JsonProperty(PropertyName = "Flame Turret Health")]
            public float FlameTurretHealth { get; set; } = 300f;

            [JsonProperty(PropertyName = "Block Plugins Which Prevent Item Durability Loss")]
            public bool EnforceDurability { get; set; }

            [JsonProperty(PropertyName = "Block Damage Outside Of The Dome To Players Inside")]
            public bool BlockOutsideDamageToPlayersInside { get; set; }

            [JsonProperty(PropertyName = "Block Damage Outside Of The Dome To Bases Inside")]
            public bool BlockOutsideDamageToBaseInside { get; set; }

            [JsonProperty(PropertyName = "Block Damage Inside From Npcs To Players Outside")]
            public bool BlockNpcDamageToPlayersOutside { get; set; }

            [JsonProperty(PropertyName = "Building Blocks Are Immune To Damage")]
            public bool BlocksImmune { get; set; }

            [JsonProperty(PropertyName = "Boxes Are Invulnerable")]
            public bool Invulnerable { get; set; }

            [JsonProperty(PropertyName = "Spawn Silently (No Notifcation, No Dome, No Map Marker)")]
            public bool Silent { get; set; }

            [JsonProperty(PropertyName = "Divide Loot Into All Containers")]
            public bool DivideLoot { get; set; } = true;

            [JsonProperty(PropertyName = "Drop Container Loot X Seconds After It Is Looted")]
            public float DropTimeAfterLooting { get; set; }

            [JsonProperty(PropertyName = "Drop Container Loot Applies Only To Boxes And Cupboards")]
            public bool DropOnlyBoxesAndPrivileges { get; set; } = true;

            [JsonProperty(PropertyName = "Create Dome Around Event Using Spheres (0 = disabled, recommended = 5)")]
            public int SphereAmount { get; set; } = 5;

            [JsonProperty(PropertyName = "Empty All Containers Before Spawning Loot")]
            public bool EmptyAll { get; set; } = true;

            [JsonProperty(PropertyName = "Eject Corpses From Enemy Raids (Advanced Users Only)")]
            public bool EjectCorpses { get; set; } = true;

            [JsonProperty(PropertyName = "Eject Corpses From PVE Instantly (Advanced Users Only)")]
            public bool EjectBackpacksPVE { get; set; }

            [JsonProperty(PropertyName = "Eject Enemies From Locked PVE Raids")]
            public bool EjectLockedPVE { get; set; } = true;

            [JsonProperty(PropertyName = "Eject Enemies From Locked PVP Raids")]
            public bool EjectLockedPVP { get; set; }

            [JsonProperty(PropertyName = "Explosion Damage Modifier (0-999)")]
            public float ExplosionModifier { get; set; } = 100f;

            [JsonProperty(PropertyName = "Force All Boxes To Have Same Skin")]
            public bool SetSkins { get; set; } = true;

            [JsonProperty(PropertyName = "Ignore Containers That Spawn With Loot Already")]
            public bool IgnoreContainedLoot { get; set; }

            [JsonProperty(PropertyName = "Loot Amount Multiplier")]
            public float Multiplier { get; set; } = 1f;

            [JsonProperty(PropertyName = "Maximum Respawn Npc X Seconds After Death")]
            public float RespawnRateMax { get; set; }

            [JsonProperty(PropertyName = "Minimum Respawn Npc X Seconds After Death")]
            public float RespawnRateMin { get; set; }

            [JsonProperty(PropertyName = "Protection Radius")]
            public float ProtectionRadius { get; set; } = 50f;

            [JsonProperty(PropertyName = "Penalize Players On Death In PVE (ZLevels)")]
            public bool PenalizePVE { get; set; } = true;

            [JsonProperty(PropertyName = "Penalize Players On Death In PVP (ZLevels)")]
            public bool PenalizePVP { get; set; } = true;

            [JsonProperty(PropertyName = "Require Cupboard Access To Loot")]
            public bool RequiresCupboardAccess { get; set; }

            [JsonProperty(PropertyName = "Require Cupboard Access To Place Ladders")]
            public bool RequiresCupboardAccessLadders { get; set; }

            [JsonProperty(PropertyName = "Skip Treasure Loot And Use Loot In Base Only")]
            public bool SkipTreasureLoot { get; set; }

            [JsonProperty(PropertyName = "Always Spawn Base Loot Table")]
            public bool Prioritize { get; set; }

            public static BuildingOptions Clone(BuildingOptions options)
            {
                return options.MemberwiseClone() as BuildingOptions;
            }
        }

        public class RaidableBaseSettingsScheduled
        {
            [JsonProperty(PropertyName = "Enabled")]
            public bool Enabled { get; set; }

            [JsonProperty(PropertyName = "Chance To Randomly Spawn PVP Bases (0 = Ignore Setting)")]
            public double Chance { get; set; }

            [JsonProperty(PropertyName = "Convert PVE To PVP")]
            public bool ConvertPVE { get; set; }

            [JsonProperty(PropertyName = "Convert PVP To PVE")]
            public bool ConvertPVP { get; set; }

            [JsonProperty(PropertyName = "Every Min Seconds")]
            public double IntervalMin { get; set; } = 3600f;

            [JsonProperty(PropertyName = "Every Max Seconds")]
            public double IntervalMax { get; set; } = 7200f;

            [JsonProperty(PropertyName = "Include PVE Bases")]
            public bool IncludePVE { get; set; } = true;

            [JsonProperty(PropertyName = "Include PVP Bases")]
            public bool IncludePVP { get; set; } = true;

            [JsonProperty(PropertyName = "Ignore Safe Checks")]
            public bool Ignore { get; set; }

            [JsonProperty(PropertyName = "Ignore Safe Checks In X Radius Only")]
            public float SafeRadius { get; set; }

            [JsonProperty(PropertyName = "Ignore Player Entities At Custom Spawn Locations")]
            public bool Skip { get; set; }

            [JsonProperty(PropertyName = "Max Scheduled Events")]
            public int Max { get; set; } = 1;

            [JsonProperty(PropertyName = "Max To Spawn At Once (0 = Use Max Scheduled Events Amount)")]
            public int MaxOnce { get; set; }

            [JsonProperty(PropertyName = "Minimum Required Players Online")]
            public int PlayerLimit { get; set; } = 1;

            [JsonProperty(PropertyName = "Spawn Bases X Distance Apart")]
            public float Distance { get; set; } = 100f;

            [JsonProperty(PropertyName = "Spawns Database File (Optional)")]
            public string SpawnsFile { get; set; } = "none";

            [JsonProperty(PropertyName = "Time To Wait Between Spawns")]
            public float Time { get; set; } = 15f;
        }

        public class RaidableBaseSettingsMaintained
        {
            [JsonProperty(PropertyName = "Always Maintain Max Events")]
            public bool Enabled { get; set; }

            [JsonProperty(PropertyName = "Chance To Randomly Spawn PVP Bases (0 = Ignore Setting)")]
            public double Chance { get; set; }

            [JsonProperty(PropertyName = "Convert PVE To PVP")]
            public bool ConvertPVE { get; set; }

            [JsonProperty(PropertyName = "Convert PVP To PVE")]
            public bool ConvertPVP { get; set; }

            [JsonProperty(PropertyName = "Include PVE Bases")]
            public bool IncludePVE { get; set; } = true;

            [JsonProperty(PropertyName = "Include PVP Bases")]
            public bool IncludePVP { get; set; } = true;

            [JsonProperty(PropertyName = "Ignore Safe Checks")]
            public bool Ignore { get; set; }

            [JsonProperty(PropertyName = "Ignore Safe Checks In X Radius Only")]
            public float SafeRadius { get; set; }

            [JsonProperty(PropertyName = "Ignore Player Entities At Custom Spawn Locations")]
            public bool Skip { get; set; }

            [JsonProperty(PropertyName = "Minimum Required Players Online")]
            public int PlayerLimit { get; set; } = 1;

            [JsonProperty(PropertyName = "Max Maintained Events")]
            public int Max { get; set; } = 1;

            [JsonProperty(PropertyName = "Spawn Bases X Distance Apart")]
            public float Distance { get; set; } = 100f;

            [JsonProperty(PropertyName = "Spawns Database File (Optional)")]
            public string SpawnsFile { get; set; } = "none";

            [JsonProperty(PropertyName = "Time To Wait Between Spawns")]
            public float Time { get; set; } = 15f;
        }

        public class RaidableBaseSettingsManual
        {
            [JsonProperty(PropertyName = "Convert PVE To PVP")]
            public bool ConvertPVE { get; set; }

            [JsonProperty(PropertyName = "Convert PVP To PVE")]
            public bool ConvertPVP { get; set; }

            [JsonProperty(PropertyName = "Max Manual Events")]
            public int Max { get; set; } = 1;

            [JsonProperty(PropertyName = "Spawn Bases X Distance Apart")]
            public float Distance { get; set; } = 100f;

            [JsonProperty(PropertyName = "Spawns Database File (Optional)")]
            public string SpawnsFile { get; set; } = "none";
        }

        public class RaidableBaseSettings
        {
            [JsonProperty(PropertyName = "Buildings", NullValueHandling = NullValueHandling.Ignore)]
            public Dictionary<string, BuildingOptions> Buildings { get; set; }
        }

        public class RaidableBaseWallOptions
        {
            [JsonProperty(PropertyName = "Enabled")]
            public bool Enabled { get; set; } = true;

            [JsonProperty(PropertyName = "Extra Stacks")]
            public int Stacks { get; set; } = 1;

            [JsonProperty(PropertyName = "Use Stone Walls")]
            public bool Stone { get; set; } = true;

            [JsonProperty(PropertyName = "Use Iced Walls")]
            public bool Ice { get; set; }

            [JsonProperty(PropertyName = "Use Least Amount Of Walls")]
            public bool LeastAmount { get; set; } = true;

            [JsonProperty(PropertyName = "Use UFO Walls")]
            public bool UseUFOWalls { get; set; }

            [JsonProperty(PropertyName = "Radius")]
            public float Radius { get; set; } = 25f;
        }

        public class RaidableBaseEconomicsOptions
        {
            [JsonProperty(PropertyName = "Amount")]
            public double Amount { get; set; }

            [JsonIgnore]
            public bool Any
            {
                get
                {
                    return Amount > 0;
                }
            }
        }

        public class RaidableBaseServerRewardsOptions
        {
            [JsonProperty(PropertyName = "Amount")]
            public int Amount { get; set; }

            [JsonIgnore]
            public bool Any
            {
                get
                {
                    return Amount > 0;
                }
            }
        }

        public class RankedLadderSettings
        {
            [JsonProperty(PropertyName = "Award Top X Players On Wipe")]
            public int Amount { get; set; } = 3;

            [JsonProperty(PropertyName = "Enabled")]
            public bool Enabled { get; set; } = true;

            [JsonProperty(PropertyName = "Show Top X Ladder")]
            public int Top { get; set; } = 10;
        }

        public class RewardSettings
        {
            [JsonProperty(PropertyName = "Economics Money")]
            public double Money { get; set; }

            [JsonProperty(PropertyName = "ServerRewards Points")]
            public int Points { get; set; }
        }

        public class SkinSettingsDefault
        {
            [JsonProperty(PropertyName = "Include Workshop Skins")]
            public bool RandomWorkshopSkins { get; set; } = true;

            [JsonProperty(PropertyName = "Preset Skin")]
            public ulong PresetSkin { get; set; }

            [JsonProperty(PropertyName = "Use Random Skin")]
            public bool RandomSkins { get; set; } = true;
        }

        public class SkinSettingsLoot
        {
            [JsonProperty(PropertyName = "Include Workshop Skins")]
            public bool RandomWorkshopSkins { get; set; } = true;

            [JsonProperty(PropertyName = "Use Random Skin")]
            public bool RandomSkins { get; set; } = true;
        }

        public class SkinSettingsDeployables
        {
            [JsonProperty(PropertyName = "Partial Names", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> Names { get; set; } = new List<string>
            {
                "door", "barricade", "chair", "fridge", "furnace", "locker", "reactivetarget", "rug", "sleepingbag", "table", "vendingmachine", "waterpurifier", "skullspikes", "skulltrophy", "summer_dlc", "sled"
            };

            [JsonProperty(PropertyName = "Include Workshop Skins")]
            public bool RandomWorkshopSkins { get; set; } = true;

            [JsonProperty(PropertyName = "Use Random Skin")]
            public bool RandomSkins { get; set; } = true;

            [JsonProperty(PropertyName = "Skin Everything")]
            public bool Everything { get; set; } = true;
        }

        public class SkinSettings
        {
            [JsonProperty(PropertyName = "Boxes")]
            public SkinSettingsDefault Boxes { get; set; } = new SkinSettingsDefault();

            [JsonProperty(PropertyName = "Loot Items")]
            public SkinSettingsLoot Loot { get; set; } = new SkinSettingsLoot();

            [JsonProperty(PropertyName = "Deployables")]
            public SkinSettingsDeployables Deployables { get; set; } = new SkinSettingsDeployables();

            [JsonProperty(PropertyName = "Randomize Npc Item Skins")]
            public bool Npcs { get; set; } = true;

            [JsonProperty(PropertyName = "Use Identical Skins For All Npcs")]
            public bool UniqueNpcs { get; set; } = true;

            [JsonProperty(PropertyName = "Ignore If Skinned Already")]
            public bool IgnoreSkinned { get; set; } = true;
        }

        public class TreasureItem  : IEquatable<TreasureItem>
        {
            [JsonProperty(PropertyName = "shortname")]
            public string shortname { get; set; }

            [JsonProperty(PropertyName = "amount")]
            public int amount { get; set; }

            [JsonProperty(PropertyName = "skin")]
            public ulong skin { get; set; }

            [JsonProperty(PropertyName = "amountMin")]
            public int amountMin { get; set; }

            [JsonIgnore]
            private ItemDefinition _def { get; set; }

            [JsonIgnore]
            public ItemDefinition definition
            {
                get
                {
                    if (_def == null)
                    {
                        string _shortname = shortname.EndsWith(".bp") ? shortname.Replace(".bp", string.Empty) : shortname;

                        if (shortname.Contains("_") && ItemManager.FindItemDefinition(_shortname) == null)
                        {
                            _shortname = _shortname.Substring(_shortname.IndexOf("_") + 1);
                        }

                        _def = ItemManager.FindItemDefinition(_shortname);
                    }

                    return _def;
                }
            }

            [JsonIgnore]
            public bool isBlueprint { get; set; }

            [JsonIgnore]
            public bool modified { get; set; }

            public TreasureItem Clone()
            {
                var ti = MemberwiseClone() as TreasureItem;

                ti.isBlueprint = isBlueprint;
                ti.modified = modified;

                return ti;
            }

            public bool Equals(TreasureItem other)
            {
                return shortname == other.shortname && amount == other.amount && skin == other.skin && amountMin == other.amountMin;
            }
        }

        public class TreasureSettings
        {
            [JsonProperty(PropertyName = "Resources Not Moved To Cupboards", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> ExcludeFromCupboard { get; set; } = new List<string>
            {
                "skull.human", "battery.small", "bone.fragments", "can.beans.empty", "can.tuna.empty", "water.salt", "water", "skull.wolf"
            };

            [JsonProperty(PropertyName = "Use Day Of Week Loot")]
            public bool UseDOWL { get; set; }

            [JsonProperty(PropertyName = "Day Of Week Loot Monday", NullValueHandling = NullValueHandling.Ignore)]
            public List<TreasureItem> DOWL_Monday { get; set; } = new List<TreasureItem>();

            [JsonProperty(PropertyName = "Day Of Week Loot Tuesday", NullValueHandling = NullValueHandling.Ignore)]
            public List<TreasureItem> DOWL_Tuesday { get; set; } = new List<TreasureItem>();

            [JsonProperty(PropertyName = "Day Of Week Loot Wednesday", NullValueHandling = NullValueHandling.Ignore)]
            public List<TreasureItem> DOWL_Wednesday { get; set; } = new List<TreasureItem>();

            [JsonProperty(PropertyName = "Day Of Week Loot Thursday", NullValueHandling = NullValueHandling.Ignore)]
            public List<TreasureItem> DOWL_Thursday { get; set; } = new List<TreasureItem>();

            [JsonProperty(PropertyName = "Day Of Week Loot Friday", NullValueHandling = NullValueHandling.Ignore)]
            public List<TreasureItem> DOWL_Friday { get; set; } = new List<TreasureItem>();

            [JsonProperty(PropertyName = "Day Of Week Loot Saturday", NullValueHandling = NullValueHandling.Ignore)]
            public List<TreasureItem> DOWL_Saturday { get; set; } = new List<TreasureItem>();

            [JsonProperty(PropertyName = "Day Of Week Loot Sunday", NullValueHandling = NullValueHandling.Ignore)]
            public List<TreasureItem> DOWL_Sunday { get; set; } = new List<TreasureItem>();

            [JsonProperty(PropertyName = "Loot (Easy Difficulty)", NullValueHandling = NullValueHandling.Ignore)]
            public List<TreasureItem> LootEasy { get; set; } = new List<TreasureItem>();

            [JsonProperty(PropertyName = "Loot (Medium Difficulty)", NullValueHandling = NullValueHandling.Ignore)]
            public List<TreasureItem> LootMedium { get; set; } = new List<TreasureItem>();

            [JsonProperty(PropertyName = "Loot (Hard Difficulty)", NullValueHandling = NullValueHandling.Ignore)]
            public List<TreasureItem> LootHard { get; set; } = new List<TreasureItem>();

            [JsonProperty(PropertyName = "Loot (Expert Difficulty)", NullValueHandling = NullValueHandling.Ignore)]
            public List<TreasureItem> LootExpert { get; set; } = new List<TreasureItem>();

            [JsonProperty(PropertyName = "Loot (Nightmare Difficulty)", NullValueHandling = NullValueHandling.Ignore)]
            public List<TreasureItem> LootNightmare { get; set; } = new List<TreasureItem>();

            [JsonProperty(PropertyName = "Loot", NullValueHandling = NullValueHandling.Ignore)]
            public List<TreasureItem> Loot { get; set; } = new List<TreasureItem>();

            [JsonProperty(PropertyName = "Do Not Duplicate Base Loot")]
            public bool UniqueBaseLoot { get; set; }

            [JsonProperty(PropertyName = "Do Not Duplicate Difficulty Loot")]
            public bool UniqueDifficultyLoot { get; set; }

            [JsonProperty(PropertyName = "Do Not Duplicate Default Loot")]
            public bool UniqueDefaultLoot { get; set; }

            [JsonProperty(PropertyName = "Use Stack Size Limit For Spawning Items")]
            public bool UseStackSizeLimit { get; set; }
        }

        public class UISettings
        {
            [JsonProperty(PropertyName = "Enabled")]
            public bool Enabled { get; set; } = true;

            [JsonProperty(PropertyName = "Anchor Min")]
            public string AnchorMin { get; set; } = "0.838 0.249";

            [JsonProperty(PropertyName = "Anchor Max")]
            public string AnchorMax { get; set; } = "0.986 0.284";

            [JsonProperty(PropertyName = "Font Size")]
            public int FontSize { get; set; } = 18;

            [JsonProperty(PropertyName = "Panel Alpha")]
            public float Alpha { get; set; } = 1f;

            [JsonProperty(PropertyName = "Panel Color")]
            public string PanelColor { get; set; } = "#000000";

            [JsonProperty(PropertyName = "PVP Color")]
            public string ColorPVP { get; set; } = "#FF0000";

            [JsonProperty(PropertyName = "PVE Color")]
            public string ColorPVE { get; set; } = "#008000";

            [JsonProperty(PropertyName = "Show Containers Left")]
            public bool Containers { get; set; }

            [JsonProperty(PropertyName = "Show Time Left")]
            public bool Time { get; set; } = true;
        }

        public class WeaponTypeStateSettings
        {
            [JsonProperty(PropertyName = "AutoTurret")]
            public bool AutoTurret { get; set; } = true;

            [JsonProperty(PropertyName = "FlameTurret")]
            public bool FlameTurret { get; set; } = true;

            [JsonProperty(PropertyName = "FogMachine")]
            public bool FogMachine { get; set; } = true;

            [JsonProperty(PropertyName = "GunTrap")]
            public bool GunTrap { get; set; } = true;

            [JsonProperty(PropertyName = "SamSite")]
            public bool SamSite { get; set; } = true;
        }

        public class WeaponTypeAmountSettings
        {
            [JsonProperty(PropertyName = "AutoTurret")]
            public int AutoTurret { get; set; } = 256;

            [JsonProperty(PropertyName = "FlameTurret")]
            public int FlameTurret { get; set; } = 256;

            [JsonProperty(PropertyName = "FogMachine")]
            public int FogMachine { get; set; } = 5;

            [JsonProperty(PropertyName = "GunTrap")]
            public int GunTrap { get; set; } = 128;

            [JsonProperty(PropertyName = "SamSite")]
            public int SamSite { get; set; } = 24;
        }

        public class WeaponSettingsTeslaCoil
        {
            [JsonProperty(PropertyName = "Requires A Power Source")]
            public bool RequiresPower { get; set; } = true;

            [JsonProperty(PropertyName = "Max Discharge Self Damage Seconds (0 = None, 120 = Rust default)")]
            public float MaxDischargeSelfDamageSeconds { get; set; }

            [JsonProperty(PropertyName = "Max Damage Output")]
            public float MaxDamageOutput { get; set; } = 35f;
        }

        public class WeaponSettings
        {
            [JsonProperty(PropertyName = "Infinite Ammo")]
            public WeaponTypeStateSettings InfiniteAmmo { get; set; } = new WeaponTypeStateSettings();

            [JsonProperty(PropertyName = "Ammo")]
            public WeaponTypeAmountSettings Ammo { get; set; } = new WeaponTypeAmountSettings();

            [JsonProperty(PropertyName = "Tesla Coil")]
            public WeaponSettingsTeslaCoil TeslaCoil { get; set; } = new WeaponSettingsTeslaCoil();

            [JsonProperty(PropertyName = "Fog Machine Allows Motion Toggle")]
            public bool FogMotion { get; set; } = true;

            [JsonProperty(PropertyName = "Fog Machine Requires A Power Source")]
            public bool FogRequiresPower { get; set; } = true;

            [JsonProperty(PropertyName = "SamSite Repairs Every X Minutes (0.0 = disabled)")]
            public float SamSiteRepair { get; set; } = 5f;

            [JsonProperty(PropertyName = "SamSite Range (350.0 = Rust default)")]
            public float SamSiteRange { get; set; } = 75f;

            [JsonProperty(PropertyName = "Test Generator Power")]
            public float TestGeneratorPower { get; set; } = 100f;
        }

        public class Configuration
        {
            [JsonProperty(PropertyName = "Settings")]
            public PluginSettings Settings = new PluginSettings();

            [JsonProperty(PropertyName = "Event Messages")]
            public EventMessageSettings EventMessages = new EventMessageSettings();

            [JsonProperty(PropertyName = "GUIAnnouncements")]
            public GUIAnnouncementSettings GUIAnnouncement = new GUIAnnouncementSettings();

            [JsonProperty(PropertyName = "Lusty Map")]
            public LustyMapSettings LustyMap = new LustyMapSettings();

            [JsonProperty(PropertyName = "Raidable Bases", NullValueHandling = NullValueHandling.Ignore)]
            public RaidableBaseSettings RaidableBases = new RaidableBaseSettings();

            [JsonProperty(PropertyName = "Ranked Ladder")]
            public RankedLadderSettings RankedLadder = new RankedLadderSettings();

            [JsonProperty(PropertyName = "Skins")]
            public SkinSettings Skins = new SkinSettings();

            [JsonProperty(PropertyName = "Treasure")]
            public TreasureSettings Treasure = new TreasureSettings();

            [JsonProperty(PropertyName = "UI")]
            public UISettings UI = new UISettings();

            [JsonProperty(PropertyName = "Weapons")]
            public WeaponSettings Weapons = new WeaponSettings();
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            
            try
            {
                config = Config.ReadObject<Configuration>();
                if (config == null) LoadDefaultConfig();
                SaveConfig();
            }
            catch (JsonException ex)
            {
                UnityEngine.Debug.Log(ex);
                LoadDefaultConfig();
            }

            if (string.IsNullOrEmpty(config.LustyMap.IconFile) || string.IsNullOrEmpty(config.LustyMap.IconName))
            {
                config.LustyMap.Enabled = false;
            }

            if (config.GUIAnnouncement.TintColor.ToLower() == "black")
            {
                config.GUIAnnouncement.TintColor = "grey";
            }
        }

        private const string rankLadderPermission = "raidablebases.th";
        private const string rankLadderGroup = "raidhunter";
        private const string adminPermission = "raidablebases.allow";
        private const string drawPermission = "raidablebases.ddraw";
        private const string mapPermission = "raidablebases.mapteleport";
        private const string canBypassPermission = "raidablebases.canbypass";
        private const string bypassBlockPermission = "raidablebases.blockbypass";
        private const string banPermission = "raidablebases.banned";
        private const string vipPermission = "raidablebases.vipcooldown";
        private const string losePermission = "raidablebases.durabilitybypass";
        private const string notitlePermission = "raidablebases.notitle";

        public static List<TreasureItem> TreasureLoot
        {
            get
            {
                List<TreasureItem> lootList;

                if (config.Treasure.UseDOWL && Buildings.Weekday.TryGetValue(DateTime.Now.DayOfWeek, out lootList) && lootList.Count > 0)
                {
                    return new List<TreasureItem>(lootList);
                }

                return new List<TreasureItem>(Buildings.Default);
            }
        }

        protected override void SaveConfig() => Config.WriteObject(config);

        protected override void LoadDefaultConfig()
        {
            config = new Configuration();
            Puts("Loaded default configuration file");
        }

        #endregion

        #region UI

        public class UI // Credits: Absolut & k1lly0u
        {
            public static CuiElementContainer CreateElementContainer(string panelName, string color, string aMin, string aMax, bool cursor = false, string parent = "Overlay")
            {
                var NewElement = new CuiElementContainer
                {
                    {
                        new CuiPanel
                        {
                            Image =
                            {
                                Color = color
                            },
                            RectTransform =
                            {
                                AnchorMin = aMin,
                                AnchorMax = aMax
                            },
                            CursorEnabled = cursor
                        },
                        new CuiElement().Parent = parent,
                        panelName
                    }
                };
                return NewElement;
            }

            public static void CreateButton(ref CuiElementContainer container, string panel, string color, string text, int size, string aMin, string aMax, string command, TextAnchor align = TextAnchor.MiddleCenter, string labelColor = "")
            {
                container.Add(new CuiButton
                {
                    Button =
                    {
                        Color = color,
                        Command = command,
                        FadeIn = 1.0f
                    },
                    RectTransform =
                    {
                        AnchorMin = aMin,
                        AnchorMax = aMax
                    },
                    Text =
                    {
                        Text = text,
                        FontSize = size,
                        Align = align,
                        Color = labelColor
                    }
                }, panel);
            }

            public static void CreateLabel(ref CuiElementContainer container, string panel, string color, string text, int size, string aMin, string aMax, TextAnchor align = TextAnchor.MiddleCenter)
            {
                container.Add(new CuiLabel
                {
                    Text =
                    {
                        Color = color,
                        FontSize = size,
                        Align = align,
                        FadeIn = 0f,
                        Text = text
                    },
                    RectTransform =
                    {
                        AnchorMin = aMin,
                        AnchorMax = aMax
                    }
                }, panel);
            }

            private static string GetContrast(string hexColor)
            {
                hexColor = hexColor.TrimStart('#');
                int r = int.Parse(hexColor.Substring(0, 2), NumberStyles.AllowHexSpecifier);
                int g = int.Parse(hexColor.Substring(2, 2), NumberStyles.AllowHexSpecifier);
                int b = int.Parse(hexColor.Substring(4, 2), NumberStyles.AllowHexSpecifier);
                var color = ((r * 299) + (g * 587) + (b * 114)) / 1000 >= 128 ? "0 0 0 1" : "1 1 1 1";
                return color;

            }

            public static string Color(string hexColor, float a = 1.0f)
            {
                a = Mathf.Clamp(a, 0f, 1f);
                hexColor = hexColor.TrimStart('#');
                int r = int.Parse(hexColor.Substring(0, 2), NumberStyles.AllowHexSpecifier);
                int g = int.Parse(hexColor.Substring(2, 2), NumberStyles.AllowHexSpecifier);
                int b = int.Parse(hexColor.Substring(4, 2), NumberStyles.AllowHexSpecifier);
                return $"{(double)r / 255} {(double)g / 255} {(double)b / 255} {a}";
            }

            public static void DestroyAllStatusUI()
            {
                Players.RemoveAll(x => x == null || !x.IsConnected);

                foreach (var player in Players)
                {
                    CuiHelper.DestroyUi(player, StatusPanelName);
                }

                Players.Clear();
            }

            public static void DestroyStatusUI(BasePlayer player)
            {
                if (player.IsValid() && player.IsConnected && Players.Contains(player))
                {
                    CuiHelper.DestroyUi(player, StatusPanelName);
                    Players.Remove(player);
                    DestroyStatusUpdate(player);
                }
            }

            private static void Create(BasePlayer player, RaidableBase raid, string panelName, string text, string color, string panelColor, string aMin, string aMax)
            {
                var element = CreateElementContainer(panelName, panelColor, aMin, aMax, false, "Hud");

                CreateLabel(ref element, panelName, Color(color), text, config.UI.FontSize, "0 0", "1 1");
                CuiHelper.AddUi(player, element);

                if (!Players.Contains(player))
                {
                    Players.Add(player);
                }
            }

            private static void Create(BasePlayer player, string panelName, string text, string color, string panelColor, string aMin, string aMax)
            {
                var element = CreateElementContainer(panelName, panelColor, aMin, aMax, false, "Hud");

                CreateLabel(ref element, panelName, Color(color), text, config.UI.FontSize, "0 0", "1 1");
                CuiHelper.AddUi(player, element);
            }

            private static void ShowStatus(BasePlayer player)
            {
                var raid = RaidableBase.Get(player.transform.position);

                if (raid == null)
                {
                    return;
                }

                string zone = raid.AllowPVP ? Backbone.GetMessageEx("PVP ZONE") : Backbone.GetMessageEx("PVE ZONE");
                int lootAmount = 0;
                float seconds = raid.despawnTime - Time.realtimeSinceStartup;
                string despawnText = config.Settings.Management.DespawnMinutesInactive > 0 && seconds > 0 ? Math.Floor(TimeSpan.FromSeconds(seconds).TotalMinutes).ToString() : null;
                string text;

                foreach (var x in raid._containers)
                {
                    if (x == null || x.IsDestroyed || raid.IsProtectedWeapon(x))
                    {
                        continue;
                    }

                    lootAmount += x.inventory.itemList.Count;
                }

                if (config.UI.Containers && config.UI.Time && !string.IsNullOrEmpty(despawnText))
                {
                    text = Backbone.GetMessageEx("UIFormat", null, zone, lootAmount, despawnText);
                }
                else if (config.UI.Containers)
                {
                    text = Backbone.GetMessageEx("UIFormatContainers", null, zone, lootAmount);
                }
                else if (config.UI.Time && !string.IsNullOrEmpty(despawnText))
                {
                    text = Backbone.GetMessageEx("UIFormatMinutes", null, zone, despawnText);
                }
                else text = zone;

                Create(player, raid, StatusPanelName, text, raid.AllowPVP ? config.UI.ColorPVP : config.UI.ColorPVE, Color(config.UI.PanelColor, config.UI.Alpha), config.UI.AnchorMin, config.UI.AnchorMax);
            }

            public static void UpdateStatusUI(RaidableBase raid)
            {
                foreach (var p in raid.intruders)
                {
                    UI.UpdateStatusUI(p);
                }
            }

            public static void UpdateStatusUI(BasePlayer player)
            {
                Players.RemoveAll(x => x == null || !x.IsConnected);

                if (player == null || !player.IsConnected)
                {
                    return;
                }

                DestroyStatusUI(player);

                if (config == null || !config.UI.Enabled)
                {
                    return;
                }

                var uii = GetSettings(player.UserIDString);

                if (!uii.Enabled || !uii.Status)
                {
                    return;
                }

                ShowStatus(player);
                SetStatusUpdate(player);
            }

            private static void SetStatusUpdate(BasePlayer player)
            {
                var raid = RaidableBase.Get(player.transform.position);

                if (raid == null || raid.killed)
                {
                    return;
                }

                Timers timers;
                if (!InvokeTimers.TryGetValue(player.userID, out timers))
                {
                    InvokeTimers[player.userID] = timers = new Timers();
                }

                if (timers.Status == null || timers.Status.Destroyed)
                {
                    timers.Status = Backbone.Timer(60f, () => UpdateStatusUI(player));
                }
                else timers.Status.Reset();
            }

            public static void DestroyStatusUpdate(BasePlayer player)
            {
                Timers timers;
                if (!InvokeTimers.TryGetValue(player.userID, out timers))
                {
                    return;
                }

                if (timers.Status == null || timers.Status.Destroyed)
                {
                    return;
                }

                timers.Status.Destroy();
            }

            public static Info GetSettings(string playerId)
            {
                Info uii;
                if (!data.UI.TryGetValue(playerId, out uii))
                {
                    data.UI[playerId] = uii = new UI.Info();
                }

                return uii;
            }

            private const string StatusPanelName = "RB_UI_Status";
            private const string DifficultyPanelName = "RB_UI_Default";

            public static List<BasePlayer> Players { get; set; } = new List<BasePlayer>();
            public static Dictionary<ulong, Timers> InvokeTimers { get; set; } = new Dictionary<ulong, Timers>();

            public class Timers
            {
                public Timer Status;
            }

            public class Info
            {
                public bool Enabled { get; set; } = true;
                public bool Status { get; set; } = true;
                public bool All() => Enabled && Status;
            }
        }

        private void CommandUI(IPlayer p, string command, string[] args)
        {
            if (p.IsServer)
            {
                return;
            }

            var uii = UI.GetSettings(p.Id);
            var player = p.Object as BasePlayer;

            if (args.Length == 0)
            {
                uii.Enabled = !uii.Enabled;

                if (!uii.Enabled)
                {
                    UI.DestroyStatusUI(player);
                }
                else
                {
                    UI.UpdateStatusUI(player);
                }

                return;
            }

            switch (args[0].ToLower())
            {
                case "status":
                    {
                        uii.Status = !uii.Status;
                        UI.UpdateStatusUI(player);
                        return;
                    }
            }
        }

        #endregion UI        
    }
}
