﻿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 Oxide.Plugins.RaidableBasesExtensionMethods;
using Rust;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;

namespace Oxide.Plugins
{
    [Info("RaidableBases", "nivex", "2.4.4")]
    [Description("Create fully automated raidable bases with npcs.")]
    class RaidableBases : RustPlugin
    {
        private new const string Name = "RaidableBases";

        [PluginReference] Plugin DangerousTreasures, ZoneManager, IQEconomic, Economics, ServerRewards, GUIAnnouncements, CopyPaste, Friends, Clans, Kits, TruePVE, Spawns, NightLantern, Wizardry, NextGenPVE, Imperium, Backpacks, BaseRepair, Notify, AdvancedAlerts, SkillTree;

        public enum AlliedType { All, Clan, Friend, Team }
        public enum CacheType { Close, 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 }
        public enum ConstructionType { Barricade, Ladder, Any }
        public enum EjectCode { Awaken, Banned, Enter, FauxAdmin, Hogging, Mounted, Protector, Scavenging, Sleeper, Spawning, Teleported, None }

        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" };
        private static List<string> blockedcolliders { get; set; }
        public Dictionary<ulong, RaidableBase> Npcs { get; set; } = new Dictionary<ulong, RaidableBase>();
        public Dictionary<int, RaidableBase> Raids { get; } = new Dictionary<int, RaidableBase>();
        public List<LootItem> BaseLootList { get; set; } = new List<LootItem>();
        private static Dictionary<string, ItemDefinition> _definitions { get; set; }
        public static StoredData data { get; set; } = new StoredData();
        private Dictionary<uint, BMGELEVATOR> _elevators = new Dictionary<uint, BMGELEVATOR>();
        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 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 List<string> Blocks { get; set; } = new List<string> { "wall.doorway", "wall", "wall.frame", "wall.half", "wall.low", "wall.window", "foundation.triangle", "foundation", "wall.external.high.wood", "wall.external.high.stone", "wall.external.high.ice", "floor.triangle.frame", "floor.triangle", "floor.frame" };
        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 float OceanLevel { get; set; }
        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 static StringBuilder _sb { get; set; }
        private static StringBuilder _sb2 { get; set; }
        private const float M_RADIUS = 25f;
        private const float CELL_SIZE = M_RADIUS / 2f;

        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 DamageRecord
        {
            public BasePlayer player;
            public ulong userid;
            public DamageRecord() { }
            public DamageRecord(BasePlayer player)
            {
                this.player = player;
                userid = player.userID;
            }
        }

        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()
            {
                _elevator.SafelyKill();
                _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)(hitEntity.transform.position.Distance(_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)(hitEntity.transform.position.Distance(_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.GetIntruders().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 = mx("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.IsKilled())
                {
                    entity.transform.position = new Vector3(0, -100f, 0);
                    Instance.NextFrame(entity.SafelyKill);
                }
            }

            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.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.IsKilled()) 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 : ScientistBrain
        {
            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 "double_shotgun.entity":
                    case "shotgun_pump.entity":
                    case "shotgun_waterpipe.entity":
                    case "spas12.entity":
                        SetAttackRestrictions(AttackType.BaseProjectile, 30f, 0f, 30f);
                        break;
                    case "ak47u.entity":
                    case "bolt_rifle.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 "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.IsKilled() || ShouldForgetTarget(AttackTarget))
                    {
                        return false;
                    }

                    return true;
                }
            }

            public override void OnDestroy()
            {
                if (!Rust.Application.isQuitting)
                {
                    BaseEntity.Query.Server.RemoveBrain(GetEntity());
                    Instance?.HumanoidBrains?.Remove(uid);
                    LeaveGroup();
                }

                CancelInvoke();
            }

            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
                );

                CanUseHealingItems = true;
            }

            public override void AddStates()
            {
                base.AddStates();

                states[AIState.Attack] = new AttackState(this);
            }

            public class AttackState : BaseAttackState
            {
                private new HumanoidBrain brain;
                private global::HumanNPC npc;

                private IAIAttack attack => brain.Senses.ownerAttack;

                public AttackState(HumanoidBrain humanoidBrain)
                {
                    base.brain = brain = humanoidBrain;
                    base.AgrresiveState = true;
                    npc = brain.GetBrainBaseEntity() as global::HumanNPC;
                }

                public override void StateEnter(BaseAIBrain _brain, BaseEntity _entity)
                {
                    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(BaseAIBrain brain, BaseEntity entity)
                {
                    StopAttacking();
                }

                private void StopAttacking()
                {
                    if (attack != null)
                    {
                        attack.StopAttacking();
                        brain.Navigator.ClearFacingDirectionOverride();
                    }
                }

                public override StateStatus StateThink(float delta, BaseAIBrain _brain, BaseEntity _entity)
                {
                    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)
                    {
                        npc.ShotTest(brain.AttackPosition.Distance(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 + player.transform.position.Distance(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.IsKilled() || 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 (ServerPosition.Distance(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.IsNull() || player.health <= 0f || player.IsDead() || player.limitNetworking) continue;
                    if (!player.IsHuman() && !config.Settings.Management.TargetNpcs) continue;
                    float dist = player.transform.position.Distance(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 static PlayerInfo Get(string userid)
            {
                PlayerInfo playerInfo;
                if (!data.Players.TryGetValue(userid, out playerInfo))
                {
                    data.Players[userid] = playerInfo = new PlayerInfo();
                }
                return playerInfo;
            }
        }

        public class Raider
        {
            public ulong userid;
            public string id;
            public string displayName;
            public bool reward = true;
            public bool IsRaider;
            public bool IsAlly;
            public bool IsAllowed;
            public bool PreEnter = true;
            public bool LockedOnEnter;
            public BasePlayer player;
            public PlayerInputEx Input;
            public bool IsParticipant
            {
                get
                {
                    return IsRaider || LockedOnEnter;
                }
                set
                {
                    IsRaider = value;
                    LockedOnEnter = value;
                }
            }
            public Raider(BasePlayer target)
            {
                player = target;
                userid = target.userID;
                id = target.userID.ToString();
                displayName = target.displayName;
            }
            public void DestroyInput()
            {
                if (Input != null)
                {
                    UnityEngine.Object.Destroy(Input);
                }
            }
            public void Reset()
            {
                DestroyInput();
                IsAllowed = false;
                IsParticipant = false;
            }
        }

        public class PlayerInputEx : FacepunchBehaviour
        {
            public BasePlayer player { get; set; }
            private InputState input { get; set; }
            private RaidableBase raid { get; set; }
            private ulong userID { get; set; }

            private void Awake()
            {
                player = GetComponent<BasePlayer>();
                input = player.serverInput;
                userID = player.userID;
            }

            private void OnDestroy()
            {
                CancelInvoke();
                Destroy(this);
            }

            public void Setup(RaidableBase raid)
            {
                this.raid = raid;
                raid.GetRaider(player).Input = this;

                InvokeRepeating(Repeater, 0f, 0.1f);
            }

            public void Restart()
            {
                CancelInvoke(Repeater);
                InvokeRepeating(Repeater, 0.1f, 0.1f);
            }

            private void Repeater()
            {
                if (raid == null)
                {
                    Destroy(this);
                }
                else TryPlace(ConstructionType.Any);
            }

            public bool TryPlace(ConstructionType constructionType)
            {
                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 == null)
                {
                    return false;
                }

                RaycastHit hit;
                if (!IsConstructionType(item.info.shortname, ref constructionType, out hit))
                {
                    return false;
                }

                int amount = item.amount;

                var action = new Action(() =>
                {
                    if (raid == null || item == null || item.amount != amount || IsConstructionNear(constructionType, hit.point))
                    {
                        return;
                    }

                    Quaternion rot;
                    if (constructionType == ConstructionType.Barricade)
                    {
                        rot = Quaternion.LookRotation((player.transform.position.WithY(0f) - hit.point.WithY(0f)).normalized);
                    }
                    else rot = Quaternion.LookRotation(hit.normal, Vector3.up);

                    var prefab = GetConstructionPrefab(item.info);
                    var e = GameManager.server.CreateEntity(prefab, hit.point, rot, true);

                    if (e.IsNull())
                    {
                        return;
                    }

                    e.gameObject.SendMessage("SetDeployedBy", player, SendMessageOptions.DontRequireReceiver);
                    e.OwnerID = 0;
                    e.Spawn();
                    item.UseItem(1);

                    if (constructionType == ConstructionType.Ladder)
                    {
                        e.SetParent(hit.GetEntity(), true, false);
                    }

                    raid.BuiltList[e] = hit.point;
                    raid.AddEntity(e);
                });

                player.Invoke(action, 0.1f);
                return true;
            }

            public bool IsConstructionType(string shortname, ref ConstructionType constructionType, out RaycastHit hit)
            {
                hit = default(RaycastHit);

                if (constructionType == ConstructionType.Any || constructionType == ConstructionType.Ladder)
                {
                    if (shortname == "ladder.wooden.wall")
                    {
                        constructionType = ConstructionType.Ladder;

                        if (raid.Options.RequiresCupboardAccessLadders && !player.CanBuild())
                        {
                            Message(player, "Ladders Require Building Privilege!");
                            return false;
                        }

                        if (!Physics.Raycast(player.eyes.HeadRay(), out hit, 4f, Layers.Mask.Construction, QueryTriggerInteraction.Ignore))
                        {
                            return false;
                        }

                        var entity = hit.GetEntity();

                        if (entity.IsNull() || entity.OwnerID != 0 || !Instance.Blocks.Contains(entity.ShortPrefabName)) // walls and foundations
                        {
                            return false;
                        }

                        return true;
                    }
                }

                if (constructionType == ConstructionType.Any || constructionType == ConstructionType.Barricade)
                {
                    if (shortname.StartsWith("barricade."))
                    {
                        constructionType = ConstructionType.Barricade;

                        if (!Physics.Raycast(player.eyes.HeadRay(), out hit, 5f, Layers.Solid, QueryTriggerInteraction.Ignore))
                        {
                            return false;
                        }

                        return hit.GetEntity().IsNull();
                    }
                }

                return false;
            }

            private bool IsConstructionNear(ConstructionType constructionType, Vector3 target)
            {
                float radius = constructionType == ConstructionType.Barricade ? 1f : 0.3f;
                int layerMask = constructionType == ConstructionType.Barricade ? -1 : Layers.Mask.Deployed;
                var entities = FindEntitiesOfType<BaseEntity>(target, radius, layerMask);
                bool result;
                if (constructionType == ConstructionType.Barricade)
                {
                    result = entities.Count > 0;
                }
                else result = entities.Exists(e => e is BaseLadder);
                return result;
            }

            private string GetConstructionPrefab(ItemDefinition def)
            {
                switch (def.shortname)
                {
                    case "ladder.wooden.wall": return StringPool.Get(2150203378);
                    case "barricade.woodwire": return StringPool.Get(1202834203);
                    case "barricade.concrete": return StringPool.Get(2057881102);
                    case "barricade.metal": return StringPool.Get(3824663394);
                    case "barricade.sandbags": return StringPool.Get(2335812770);
                    case "barricade.stone": return StringPool.Get(1206527181);
                    case "barricade.wood": return StringPool.Get(4254045167);
                    case "barricade.cover.wood": return StringPool.Get(1581233281);
                }

                return def.GetComponent<ItemModDeployable>().entityPrefab.Get().GetComponent<BaseEntity>().PrefabName;
            }
        }

        public class RandomBase
        {
            public string BaseName;
            public Vector3 Position;
            public BaseProfile Profile;
            public RaidableType Type;
            public bool hasSpawned;
            public int BaseIndex => Position.GetHashCode();
        }

        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 HashSet<RaidableSpawnLocation> Spawns { get; set; } = new HashSet<RaidableSpawnLocation>();
            private Dictionary<CacheType, HashSet<RaidableSpawnLocation>> Cached { get; set; } = 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 bool Add(RaidableSpawnLocation rsl, CacheType cacheType, HashSet<RaidableSpawnLocation> cache)
            {
                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 false;
                        }
                        break;
                    case CacheType.Close:
                        if (RaidableBase.IsTooClose(rsl.Location, GetDistance(RaidableType.None)))
                        {
                            return false;
                        }
                        break;
                }

                return 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);
                List<RaidableSpawnLocation> remove = new List<RaidableSpawnLocation>();

                foreach (var rsl in cache)
                {
                    if (forced && Spawns.Add(rsl) || Add(rsl, cacheType, cache))
                    {
                        remove.Add(rsl);
                    }

                }

                remove.ForEach(rsl => cache.Remove(rsl));
            }

            public RaidableSpawnLocation GetRandom(BuildingWaterOptions options)
            {
                RaidableSpawnLocation rsl;

                if (Instance.Seabed.Count > 0 && UnityEngine.Random.Range(0f, 100f) >= options.Seabed)
                {
                    rsl = Instance.Seabed.GetRandom();



                    options.SpawnOnSeabed = true;


                }
                else
                {
                    rsl = Spawns.GetRandom();
                    options.SpawnOnSeabed = false;
                }

                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)
            {
                List<RaidableSpawnLocation> remove = new List<RaidableSpawnLocation>();

                foreach (var rsl in cache)
                {
                    if (InRange(target, rsl.Location, radius) && Spawns.Add(rsl))
                    {
                        remove.Add(rsl);
                    }
                }

                remove.ForEach(rsl => cache.Remove(rsl));
            }

            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);
                }

                HashSet<RaidableSpawnLocation> cache = Get(cacheType);
                List<RaidableSpawnLocation> remove = new List<RaidableSpawnLocation>();

                foreach (var rsl in Spawns)
                {
                    if (InRange(target, rsl.Location, radius) && cache.Add(rsl))
                    {
                        remove.Add(rsl);
                    }
                }

                remove.ForEach(rsl => Spawns.Remove(rsl));

                return radius;
            }
        }

        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) || !keyList.Contains(key))
                {
                    return true;
                }

                return TryClear(type, mode, keyList);
            }

            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;
                    }

                    if (profile.Value.Options.AdditionalBases.Exists(kvp => !keyList.Contains(kvp.Key) && FileExists(kvp.Key)))
                    {
                        return false;
                    }
                }

                keyList.Clear();
                return true;
            }
        }

        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 List<uint> TemporaryUID { get; set; } = new List<uint>();
            public string RaidTime { get; set; } = DateTime.MinValue.ToString();
            public int TotalEvents { get; set; }
            public static void Load(PluginTimers timer)
            {
                try { data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name); } catch { }

                if (data == null) data = new StoredData();
                if (data.Players == null) data.Players = new Dictionary<string, PlayerInfo>();
                if (data.TemporaryUID.Count > 0)
                {
                    var uids = data.TemporaryUID.ToList();
                    timer.Once(30f, () => CleanupTemporaryFiles(uids));
                }
            }
            public void Save()
            {
                data.Players.RemoveAll((userid, playerInfo) => playerInfo.TotalRaids == 0);
                Interface.Oxide.DataFileSystem.WriteObject(Name, data);
            }
            private static void CleanupTemporaryFiles(List<uint> uids)
            {
                var entities = new List<BaseEntity>();
                foreach (uint uid in uids)
                {
                    data.TemporaryUID.Remove(uid);
                    var entity = BaseNetworkable.serverEntities.Find(uid) as BaseEntity;
                    if (entity.IsKilled()) continue;
                    entities.Add(entity);
                }
                if (entities.Count == 0) return;
                var undo = new UndoSettings(entities, 1, true);
                Instance.UndoLoop(undo, 0, 1, Time.time);
            }
        }

        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": case "Arctic Research Base": 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 + M_RADIUS + config.Settings.ZoneDistance;
                }

                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<LootItem> Default { get; set; } = new List<LootItem>();
            public List<LootItem> Normal { get; set; } = new List<LootItem>();
            public Dictionary<string, BaseProfile> Profiles { get; set; } = new Dictionary<string, BaseProfile>();
            public Dictionary<DayOfWeek, List<LootItem>> Weekday { get; set; } = new Dictionary<DayOfWeek, List<LootItem>>();
        }

        public class RaidableBase : FacepunchBehaviour
        {
            private const float Radius = M_RADIUS;
            private List<string> messagesSent = new List<string>();
            public HashSet<StorageContainer> _allcontainers { get; set; } = new HashSet<StorageContainer>();
            public HashSet<StorageContainer> _containers { get; set; } = new HashSet<StorageContainer>();
            public bool AllowPVP { get; set; }
            public string BaseName { get; set; }
            public int BaseIndex { get; set; }
            public uint BuildingID { get; set; }
            public Dictionary<BaseEntity, Vector3> BuiltList { get; set; } = new Dictionary<BaseEntity, Vector3>();
            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; }
            private Timer despawnTimer { 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; }
            public RaidableBases MyInstance { get; set; }
            private Dictionary<Elevator, BMGELEVATOR> elevators { get; set; }
            public HashSet<BaseEntity> Entities { get; set; } = new HashSet<BaseEntity>();
            public string ID { get; set; } = "0";
            public List<string> ids { get; set; } = Pool.GetList<string>();
            public List<ulong> intruders { get; set; } = Pool.GetList<ulong>();
            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 ulong permOwnerId { get; set; }
            public ulong ownerId { get; set; }
            public Raider riOwner { get; set; }
            public Vector3 PastedLocation { get; set; }
            public BuildingPrivlidge priv { get; set; }
            public string ProfileName { get; set; }
            public Dictionary<ulong, Raider> raiders { get; set; } = Pool.Get<Dictionary<ulong, Raider>>();
            public Dictionary<uint, DamageRecord> records { get; set; } = Pool.Get<Dictionary<uint, DamageRecord>>();
            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>();
            public List<BaseEntity> locks { get; set; } = Pool.GetList<BaseEntity>();
            private MapMarkerGenericRadius genericMarker { get; set; }
            private bool IsInvokingCanFinish { get; set; }
            private int itemAmountSpawned { get; set; }
            private int treasureAmount { get; set; }
            private List<IOEntity> lights { get; set; } = Pool.GetList<IOEntity>();
            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<LootItem> Collective { get; set; } = new List<LootItem>();
            private List<LootItem> BaseLoot { get; set; } = new List<LootItem>();
            private List<LootItem> BaseLootPermanent { get; set; } = new List<LootItem>();
            private List<LootItem> DefaultLoot { get; set; } = new List<LootItem>();
            private List<LootItem> DifficultyLoot { get; set; } = new List<LootItem>();
            private List<LootItem> Loot { get; set; } = new List<LootItem>();
            private HashSet<string> LootNames { get; set; } = new HashSet<string>();
            private RaycastHit _hit;
            private NavMeshHit _navHit;

            public bool IsProtectedWeapon(BaseEntity e, bool checkBuiltList = false)
            {
                if (!e.IsReallyValid() || checkBuiltList && BuiltList.ContainsKey(e))
                {
                    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 Instance.Raids.Values.ToList())
                {
                    if (raid.setupRoutine != null)
                    {
                        raid.StopCoroutine(raid.setupRoutine);
                    }

                    if (isKilled)
                    {
                        raid.killed = true;
                    }

                    raid.IsUnloading = true;
                    TryInvokeMethod(raid.DestroyLocks);
                    TryInvokeMethod(raid.DestroyNpcs);
                    TryInvokeMethod(raid.CancelInvokes);
                }

                IsSpawning = false;
            }

            private List<string> unityEngineScripts { get; set; } = new List<string> { "saddletest", "fuel_storage" };

            private void AddEntities(List<BaseEntity> entities)
            {
                foreach (var e in entities.ToList())
                {
                    if (e.IsKilled() || unityEngineScripts.Contains(e.ShortPrefabName))
                    {
                        entities.Remove(e);
                        e.SafelyKill();
                        continue;
                    }
                    if (e.ShortPrefabName == "foundation.triangle" || e.ShortPrefabName == "foundation" || e.skinID == 1337424001 && e is CollectibleEntity)
                    {
                        foundations.Add(e.transform.position);
                    }
                    AddEntity(e);
                }
            }

            public void AddEntity(BaseEntity entity)
            {
                if (!entity.IsValid())
                {
                    return;
                }
                if (!data.TemporaryUID.Contains(entity.net.ID))
                {
                    data.TemporaryUID.Add(entity.net.ID);
                }
                Entities.Add(entity);
            }

            public bool AddLooter(BasePlayer looter, HitInfo hitInfo = null)
            {
                if (!IsAlly(looter))
                {
                    return false;
                }

                if (looter.IsFlying || looter.limitNetworking)
                {
                    return true;
                }

                UpdateStatus(looter);

                if (IsHogging(looter, false))
                {
                    NullifyDamage(hitInfo);
                    return false;
                }

                GetRaider(looter).IsRaider = true;

                return true;
            }

            private bool CancelOnServerRestart()
            {
                return config.Settings.Management.Restart && Interface.Oxide.IsShuttingDown;
            }

            public void AwardRaiders()
            {
                _sb.Length = 0;

                foreach (var ri in raiders.Values)
                {
                    if (ri.player.IsNull() || CancelOnServerRestart())
                    {
                        ri.reward = false;
                        continue;
                    }

                    if (ri.player.IsFlying)
                    {
                        if (config.EventMessages.Rewards.Flying) Message(ri.player, "No Reward: Flying");
                        ri.reward = false;
                        continue;
                    }

                    if (ri.player._limitedNetworking)
                    {
                        if (config.EventMessages.Rewards.Vanished) Message(ri.player, "No Reward: Vanished");
                        ri.reward = false;
                        continue;
                    }

                    if (!IsPlayerActive(ri.id))
                    {
                        if (config.EventMessages.Rewards.Inactive) Message(ri.player, "No Reward: Inactive");
                        ri.reward = false;
                        continue;
                    }

                    if (config.Settings.RemoveAdminRaiders && ri.player.IsAdmin && Type != RaidableType.None)
                    {
                        if (config.EventMessages.Rewards.RemoveAdmin) Message(ri.player, "No Reward: Admin");
                        ri.reward = false;
                        continue;
                    }

                    if (config.Settings.Management.OnlyAwardOwner && ri.player.userID != ownerId && ownerId.IsSteamId())
                    {
                        if (config.EventMessages.Rewards.NotOwner) Message(ri.player, "No Reward: Not Owner");
                        ri.reward = false;
                    }

                    if (!ri.IsParticipant)
                    {
                        if (config.EventMessages.Rewards.NotParticipant) Message(ri.player, "No Reward: Not A Participant");
                        ri.reward = false;
                        continue;
                    }

                    if (config.Settings.Management.OnlyAwardAllies && ri.player.userID != ownerId && !IsAlly(ri.userid, ownerId))
                    {
                        if (config.EventMessages.Rewards.NotAlly) Message(ri.player, "No Reward: Not Ally");
                        ri.reward = false;
                    }

                    _sb.Append(ri.displayName).Append(", ");
                }

                Interface.CallHook("OnRaidableBaseCompleted", hookObjects);

                if (_sb.Length == 0)
                {
                    return;
                }

                Interface.CallHook("OnRaidableBaseCompleted", hookObjects);

                if (Options.Levels.Level2 && npcMaxAmount > 0)
                {
                    SpawnNpcs();
                }

                if (IsCompleted)
                {
                    HandleAwards();
                }

                _sb.Length -= 2;
                string thieves = _sb.ToString();
                string posStr = FormatGridReference(Location);

                Puts(mx("Thief", null, posStr, thieves));

                if (config.EventMessages.AnnounceThief)
                {
                    foreach (var target in BasePlayer.activePlayerList)
                    {
                        QueueNotification(target, "Thief", posStr, thieves);
                    }
                }

                data.Save();
            }

            public bool CanUndo()
            {
                if (IsLoading)
                {
                    return false;
                }

                if (EndWhenCupboardIsDestroyed())
                {
                    return true;
                }

                if (config.Settings.Management.RequireCupboardLooted && privSpawned)
                {
                    if (!priv.IsKilled() && !priv.inventory.IsEmpty())
                    {
                        return false;
                    }
                }

                foreach (var container in _containers)
                {
                    if (!container.IsKilled() && !container.inventory.IsEmpty() && !(container is BuildingPrivlidge))
                    {
                        return false;
                    }
                }

                foreach (var container in _allcontainers)
                {
                    if (container.IsKilled() || !config.Settings.Management.Inherit.Exists(value => container.ShortPrefabName.Contains(value, CompareOptions.OrdinalIgnoreCase)))
                    {
                        continue;
                    }

                    if (!container.inventory.IsEmpty())
                    {
                        return false;
                    }
                }

                return IsCompleted = true;
            }

            public void CheckDespawn()
            {
                if (!IsOpened)
                {
                    TryResetDespawn();
                    return;
                }

                if (IsDespawning || config.Settings.Management.DespawnMinutesInactive <= 0 || 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.time + time;
                Invoke(Despawn, time);
            }

            public void Despawn() => Despawn(true);

            public void Despawn(bool destroyEntities)
            {
                if (!CanDespawn())
                {
                    return;
                }

                TryInvokeMethod(RemoveAllFromEvent);
                TryInvokeMethod(StopAllCoroutines);
                TryInvokeMethod(CancelInvoke);
                TryInvokeMethod(SetNoDrops);
                TryInvokeMethod(DestroyUI);
                TryInvokeMethod(DestroyNpcs);
                TryInvokeMethod(DestroyInputs);
                TryInvokeMethod(DestroySpheres);
                TryInvokeMethod(DestroyMapMarkers);
                TryInvokeMethod(DestroyElevators);
                if (destroyEntities)
                {
                    TryInvokeMethod(DestroyEntities);
                }
                TryInvokeMethod(ResetSleepingBags);
                TryInvokeMethod(ResetToPool);
                TryInvokeMethod(CheckSubscribe);

                Interface.CallHook("OnRaidableBaseEnded", hookObjects);

                Destroy(this);
            }

            private bool CanDespawn()
            {
                if (IsDespawning)
                {
                    return false;
                }

                Interface.CallHook("OnRaidableBaseDespawn", hookObjects);

                IsDespawning = true;
                IsOpened = false;

                MyInstance?.Locations?.RemoveAll(e => e.BaseIndex == BaseIndex);

                return true;
            }

            private void RemoveAllFromEvent()
            {
                GetIntruders().ToList().ForEach(player => OnPlayerExit(player));
            }

            private void CheckSubscribe()
            {
                if (Instance == null)
                {
                    return;
                }

                Instance.Raids.Remove(uid);

                if (Instance.Raids.Count == 0)
                {
                    if (IsUnloading)
                    {
                        UnsetStatics();
                    }
                    else
                    {
                        Instance.UnsubscribeHooks();
                        spawns?.AddNear(Location, RemoveNearDistance, CacheType.Generic, HasDroppedItems);
                    }
                }
            }

            private void DestroyElevators()
            {
                if (elevators?.Count > 0)
                {
                    TryInvokeMethod(RemoveParentFromEntitiesOnElevators);
                    elevators.ToList().ForEach(element => Destroy(element.Value));
                }

            }

            private void DestroyEntities()
            {
                MyInstance.UndoLoop(
                    new UndoSettings(
                    Entities.ToList(),
                    _undoLimit,
                    _undoMounts,
                    _undoStructures,
                    _undoDeployables,
                    _undoTeleport), 0, 3, spawnTime);
            }

            private void RemoveParentFromEntitiesOnElevators()
            {
                var entities = FindEntitiesOfType<BaseEntity>(Location, ProtectionRadius);
                for (int i = 0; i < entities.Count; i++)
                {
                    var e = entities[i];
                    if ((e is PlayerCorpse || e is DroppedItemContainer) && e.HasParent())
                    {
                        e.SetParent(null, false, true);
                    }
                }
            }

            private static void TryInvokeMethod(Action action)
            {
                try
                {
                    action.Invoke();
                }
                catch (Exception ex)
                {
                    Puts("{0} U_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.ToList(), BaseName };
                }
            }

            public void DestroyInputs()
            {
                raiders.Values.ToList().ForEach(ri => ri.DestroyInput());
            }

            public void DestroyUI()
            {
                TryInvokeMethod(DestroyElevators);
                GetIntruders().ForEach(UI.DestroyStatusUI);
            }

            public bool EndWhenCupboardIsDestroyed()
            {
                if (config.Settings.Management.EndWhenCupboardIsDestroyed && privSpawned && priv.IsKilled())
                {
                    return IsCompleted = true;
                }

                return false;
            }

            public Raider GetRaider(BasePlayer player)
            {
                Raider ri;
                if (!raiders.TryGetValue(player.userID, out ri))
                {
                    raiders[player.userID] = ri = new Raider(player);
                }
                return ri;
            }

            public void ResetToPool()
            {
                ResetToPool(intruders);
                ResetToPool(raiders);
                ResetToPool(conditions);
                ResetToPool(records);
                ResetToPool(backpacks);
                ResetToPool(lastActive);
                ResetToPool(locks);
                ResetToPool(npcs);
                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);
                ResetToPool(mountables);
            }

            public string Mode(string userid = null, bool forceShowName = false)
            {
                var difficultyMode = mx("Normal", userid);

                if (owner.IsReallyValid())
                {
                    return string.Format("{0} {1}",
                        (config.Settings.Markers.ShowOwnersName || forceShowName) ?
                        owner.displayName :
                        mx("Claimed", userid), difficultyMode.SentenceCase());
                }

                return difficultyMode.SentenceCase();
            }

            private bool CannotEnter(BasePlayer target, bool justEntered)
            {
                if (GetRaider(target).IsAllowed)
                {
                    return IsBanned(target);
                }

                EjectCode code = EjectCode.None;

                if (IsBanned(target)) code = EjectCode.Banned;
                else if (IsHogging(target)) code = EjectCode.Hogging;
                else if (justEntered && Teleported(target)) code = EjectCode.Teleported;

                if (code != EjectCode.None)
                {
                    return RemovePlayer(target, code, Location, ProtectionRadius, Type);
                }

                return false;
            }

            public void OnEnterRaid(BasePlayer target)
            {
                if (!target.IsReallyValid() || !target.IsHuman())
                {
                    return;
                }

                if (Type != RaidableType.None && CannotEnter(target, true))
                {
                    return;
                }

                if (!intruders.Contains(target.userID))
                {
                    intruders.Add(target.userID);
                }

                Protector();

                if (!intruders.Contains(target.userID))
                {
                    return;
                }

                Raider ri = GetRaider(target);

                ri.DestroyInput();

                if (config.Settings.Management.AllowLadders)
                {
                    target.gameObject.AddComponent<PlayerInputEx>().Setup(this);
                }

                StopUsingWand(target);

                if (config.EventMessages.AnnounceEnterExit)
                {
                    QueueNotification(target, AllowPVP ? "OnPlayerEntered" : "OnPlayerEnteredPVE");
                }

                UI.UpdateStatusUI(target);

                Interface.CallHook("OnPlayerEnteredRaidableBase", new object[] { target, Location, AllowPVP, 512, ID, spawnTime, despawnTime, loadTime, ownerId, BaseName });

                if (config.Settings.Management.PVPDelay > 0)
                {
                    Interface.CallHook("OnPlayerPvpDelayEntry", new object[] { target, 512, Location, AllowPVP, ID, spawnTime, despawnTime, loadTime, ownerId, BaseName });
                }

                foreach (var brain in Instance.HumanoidBrains.Values)
                {
                    if (InRange(brain.DestinationOverride, Location, brain.SenseRange))
                    {
                        brain.SwitchToState(AIState.Attack, -1);
                    }
                }

                ri.PreEnter = false;
            }

            public void OnLootEntityInternal(BasePlayer player, BaseEntity entity)
            {
                if (!entity.OwnerID.IsSteamId() && !RaidableBase.Has(entity))
                {
                    return;
                }

                UpdateStatus(player);

                if (entity.ShortPrefabName == "coffinstorage" && Mathf.Approximately(entity.transform.position.Distance(new Vector3(0f, -50f, 0f)), 0f))
                {
                    return;
                }

                if (entity.OwnerID == player.userID || entity is BaseMountable)
                {
                    return;
                }

                if (IsBlacklisted(entity.ShortPrefabName))
                {
                    player.Invoke(player.EndLooting, 0.01f);
                    return;
                }

                if (entity.HasParent() && entity.GetParentEntity() is BaseMountable)
                {
                    return;
                }

                if (!CanBeLooted(player, entity))
                {
                    if (!player.HasPermission("raidablebases.admin.loot") || !player.IsAdmin)
                    {
                        player.Invoke(player.EndLooting, 0.01f);
                        return;
                    }
                }

                if (entity is LootableCorpse || entity is DroppedItemContainer)
                {
                    return;
                }

                if (player.GetMounted())
                {
                    Message(player, "CannotBeMounted");
                    player.Invoke(player.EndLooting, 0.01f);
                    return;
                }

                if (Options.RequiresCupboardAccess && !player.CanBuild())
                {
                    Message(player, "MustBeAuthorized");
                    player.Invoke(player.EndLooting, 0.01f);
                    return;
                }

                if (!IsAlly(player))
                {
                    Message(player, "OwnerLocked");
                    player.Invoke(player.EndLooting, 0.01f);
                    return;
                }

                if (Type != RaidableType.None && raiders.Exists(ri => ri.Value.IsParticipant))
                {
                    CheckDespawn();
                }

                AddLooter(player);

                if (IsBox(entity, true) || entity is BuildingPrivlidge)
                {
                    StartTryToEnd();
                }
            }

            public void OnPlayerExit(BasePlayer target, bool skipDelay = true)
            {
                if (!target.IsHuman())
                {
                    return;
                }

                UI.DestroyStatusUI(target);

                if (!intruders.Contains(target.userID))
                {
                    return;
                }

                Raider ri = GetRaider(target);

                ri.DestroyInput();
                intruders.Remove(target.userID);

                if (ri.PreEnter)
                {
                    return;
                }

                Interface.CallHook("OnPlayerExitedRaidableBase", new object[] { target, Location, AllowPVP, 512, ID, spawnTime, despawnTime, loadTime, ownerId, BaseName });

                if (config.Settings.Management.PVPDelay > 0)
                {
                    if (skipDelay || !Instance.IsPVE() || !AllowPVP)
                    {
                        goto enterExit;
                    }

                    if (config.EventMessages.AnnounceEnterExit)
                    {
                        string arg = mx("PVPFlag", target.UserIDString).Replace("[", string.Empty).Replace("] ", string.Empty);
                        QueueNotification(target, "DoomAndGloom", arg, config.Settings.Management.PVPDelay);
                    }

                    ulong id = target.userID;
                    DelaySettings ds;
                    if (!Instance.PvpDelay.TryGetValue(id, out ds))
                    {
                        Instance.PvpDelay[id] = ds = new DelaySettings
                        {
                            Timer = MyInstance.timer.Once(config.Settings.Management.PVPDelay, () =>
                            {
                                Interface.CallHook("OnPlayerPvpDelayExpired", new object[] { target, 512, Location, AllowPVP, ID, spawnTime, despawnTime, loadTime, ownerId, BaseName });
                                Instance.PvpDelay.Remove(id);
                            }),
                            AllowPVP = AllowPVP,
                            RaidableBase = this
                        };
                    }
                    else ds.Timer.Reset();

                    return;
                }

            enterExit:
                if (config.EventMessages.AnnounceEnterExit)
                {
                    QueueNotification(target, AllowPVP ? "OnPlayerExit" : "OnPlayerExitPVE");
                }
            }

            public void OnBuildingPrivilegeDestroyed()
            {
                Interface.CallHook("OnRaidableBasePrivilegeDestroyed", hookObjects);
            }

            public void SetEntities(int baseIndex, List<BaseEntity> entities, Dictionary<Elevator, BMGELEVATOR> elevators = null)
            {
                BaseIndex = baseIndex;

                if (!IsLoading)
                {
                    TryInvokeMethod(() => AddEntities(entities));
                    Interface.Oxide.NextTick(() =>
                    {
                        TryInvokeMethod(SetupCollider);
                        TryInvokeMethod(() => SetupElevators(elevators));
                        setupRoutine = ServerMgr.Instance.StartCoroutine(EntitySetup());
                    });
                }
            }

            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 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 && Instance.GUIAnnouncements != null && Instance.GUIAnnouncements.IsLoaded)
                {
                    Instance.GUIAnnouncements?.Call("CreateAnnouncement", message, config.GUIAnnouncement.TintColor, config.GUIAnnouncement.TextColor, target);
                }
            }

            private void AddContainer(StorageContainer container)
            {
                if (IsBox(container, true) || container is BuildingPrivlidge)
                {
                    _containers.Add(container);
                }
                _allcontainers.Add(container);
                Entities.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))
                {
                    if (config.Settings.Management.LootableTraps)
                    {
                        isAuthorized = true;
                        return true;
                    }
                    return false;
                }

                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 Instance.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()
            {
                raiders.RemoveAll((uid, ri) => !ri.player.IsReallyConnected() || !IsAlly(ri.player));
            }

            private void CreateSpheres()
            {
                if (Options.SphereAmount <= 0 || Options.Silent)
                {
                    return;
                }

                for (int i = 0; i < Options.SphereAmount; i++)
                {
                    var sphere = GameManager.server.CreateEntity(StringPool.Get(3211242734), Location) 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.Spawn();

                            e.gameObject.SetActive(true);

                            if (CanSetupEntity(e))
                            {
                                SetupEntity(e);
                                data.TemporaryUID.Add(e.net.ID);
                            }
                        }, 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()
            {
                TryInvokeMethod(KillClutter);

                int checks = 0;
                float invokeTime = 0f;
                var list = Entities.ToList();
                int limit = Mathf.Clamp(Options.Setup.SpawnLimit, 1, 500);

                foreach (var e in list)
                {


                    if (++checks >= limit)
                    {
                        checks = 0;
                        yield return CoroutineEx.waitForFixedUpdate;
                    }
                }

                foreach (var e in Entities.ToList())
                {
                    if (Instance.IsNull())
                    {
                        yield break;
                    }

                    TryInvokeMethod(() => TrySetupEntity(e, ref invokeTime));

                    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(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 TrySetupEntity(BaseEntity e, ref float invokeTime)
            {
                if (!CanSetupEntity(e))
                {
                    return;
                }

                MyInstance.RaidEntities[e] = this;

                if (e.net.ID < NetworkID)
                {
                    NetworkID = e.net.ID;
                }

                e.OwnerID = 0;

                if (e.skinID == 1337424001 && e is CollectibleEntity)
                {
                    (e as CollectibleEntity).itemList = null; // WaterBases compatibility
                }

                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);
            }

            private void FillAmmoFlameTurret(FlameTurret ft)
            {
                if (IsUnloading || isAuthorized || ft.IsKilled() || lowgradefuel == null)
                {
                    return;
                }

                ft.inventory.AddItem(lowgradefuel, config.Weapons.Ammo.FlameTurret);
            }

            private void FillAmmoFogMachine(FogMachine fm)
            {
                if (IsUnloading || isAuthorized || lowgradefuel == null || fm.IsKilled())
                {
                    return;
                }

                fm.inventory.AddItem(lowgradefuel, config.Weapons.Ammo.FogMachine);
            }

            private void FillAmmoGunTrap(GunTrap gt)
            {
                if (IsUnloading || isAuthorized || gt.IsKilled())
                {
                    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.IsKilled())
                {
                    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.IsKilled())
                {
                    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 && Instance.Economics != null && Instance.Economics.IsLoaded)
                    {
                        double money = config.Settings.Management.DivideRewards ? Options.Rewards.Money / players.Count : Options.Rewards.Money;
                        Instance.Economics?.Call("Deposit", raider.UserIDString, money);
                        Message(raider, "EconomicsDeposit", money);
                    }

                    if (Options.Rewards.Points > 0 && Instance.ServerRewards != null && Instance.ServerRewards.IsLoaded)
                    {
                        int points = config.Settings.Management.DivideRewards ? Options.Rewards.Points / players.Count : Options.Rewards.Points;
                        Instance.ServerRewards?.Call("AddPoints", raider.userID, points);
                        Message(raider, "ServerRewardPoints", points);
                    }
                }
            }

            private void HandleAwards()
            {
                foreach (var ri in raiders.Values)
                {
                    if (!ri.IsParticipant || !ri.reward)
                    {
                        continue;
                    }

                    if (config.RankedLadder.Enabled)
                    {
                        PlayerInfo playerInfo = PlayerInfo.Get(ri.id);

                        playerInfo.TotalRaids++;
                        playerInfo.Raids++;
                    }

                    int total = raiders.Values.ToList().Sum(x => x.IsParticipant ? 1 : 0);

                    if (Options.Rewards.Money > 0 && Instance.Economics.CanCall())
                    {
                        double money = config.Settings.Management.DivideRewards ? Options.Rewards.Money / total : Options.Rewards.Money;
                        if (Convert.ToBoolean(Instance.Economics?.Call("Deposit", ri.id, money)))
                        {
                            QueueNotification(ri.player, "EconomicsDeposit", money);
                        }
                    }

                    if (Options.Rewards.Money > 0 && Instance.IQEconomic.CanCall())
                    {
                        int money = Convert.ToInt32(config.Settings.Management.DivideRewards ? Options.Rewards.Money / total : Options.Rewards.Money);
                        Instance.IQEconomic?.Call("API_SET_BALANCE", ri.userid, money);
                        QueueNotification(ri.player, "EconomicsDeposit", money);
                    }

                    if (Options.Rewards.Points > 0 && Instance.ServerRewards.CanCall())
                    {
                        int points = config.Settings.Management.DivideRewards ? Options.Rewards.Points / total : Options.Rewards.Points;
                        Instance.ServerRewards?.Call("AddPoints", ri.userid, points);
                        QueueNotification(ri.player, "ServerRewardPoints", points);
                    }

                    if (Options.Rewards.XP > 0 && Instance.SkillTree.CanCall())
                    {
                        double xp = config.Settings.Management.DivideRewards ? Options.Rewards.XP / total : Options.Rewards.XP;
                        QueueNotification(ri.player, "SkillTreeXP", xp);
                        Instance.SkillTree?.Call("AwardXP", ri.player, xp);
                    }
                }
            }

            private bool IsScavenging(BasePlayer player)
            {
                if (IsOpened || !config.Settings.Management.EjectScavengers || !ownerId.IsSteamId() || CanBypass(player))
                {
                    return false;
                }

                return !Any(player.userID) && !IsAlly(player) && RemovePlayer(player, EjectCode.Scavenging, Location, ProtectionRadius, Type);
            }

            private bool RemoveFauxAdmin(BasePlayer player)
            {
                if (player.IsReallyValid() && player.IsDeveloper && player.HasPermission("fauxadmin.allowed") && player.HasPermission("raidablebases.block.fauxadmin") && player.IsCheating())
                {
                    RemovePlayer(player, EjectCode.FauxAdmin, Location, ProtectionRadius, Type);
                    QueueNotification(player, "NoFauxAdmin");
                    OnPlayerExit(player, false);
                    return true;
                }

                return false;
            }

            private bool IsBanned(BasePlayer player)
            {
                if (Instance.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) || Instance.HasPermission(player, bypassBlockPermission))
                {
                    return false;
                }

                foreach (var raid in Instance.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(string playerId)
            {
                if (config.Settings.Management.LockTime <= 0f)
                {
                    return true;
                }

                float time;
                if (!lastActive.TryGetValue(playerId, out time))
                {
                    return true;
                }

                float secondsInactive = Time.realtimeSinceStartup - time;
                float secondsBeforeInactive = config.Settings.Management.LockTime * 60f;

                return secondsInactive < secondsBeforeInactive;
            }

            private void KillClutter()
            {
                var entities = FindEntitiesOfType<BaseEntity>(Location, ProtectionRadius);
                for (int i = 0; i < entities.Count; i++)
                {
                    var e = entities[i];
                    if (e is TreeEntity) // && NearFoundation(e.transform.position, 10f))
                    {
                        e.SafelyKill();
                    }
                    else if (e is SleepingBag)
                    {
                        SleepingBagHandler(e as SleepingBag);
                    }
                }
            }

            private void SleepingBagHandler(SleepingBag bag)
            {
                if (Entities.Contains(bag))
                {
                    return;
                }

                if (KillSleepingBags())
                {
                    bag.SafelyKill();
                }
                else
                {
                    _bags[bag] = bag.deployerUserID;

                    bag.deployerUserID = 0uL;
                    bag.unlockTime = UnityEngine.Time.realtimeSinceStartup + 99999f;
                }
            }

            private bool KillSleepingBags()
            {
                if (spawns?.IsCustomSpawn != true) return false;
                if (config.Settings.Maintained.KillSleepingBags && Type == RaidableType.Maintained) return true;
                if (config.Settings.Schedule.KillSleepingBags && Type == RaidableType.Scheduled) return true;
                return false;
            }

            private void ResetSleepingBags()
            {
                foreach (var element in _bags)
                {
                    if (element.Key == null || element.Key.IsKilled()) 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.ObjectName() == "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 => !target.IsHuman() || intruders.Contains(target.userID) || IsUnderground(target.transform.position));

                foreach (var target in targets)
                {
                    if (IsLoading && !CanBypass(target))
                    {
                        RemovePlayer(target, EjectCode.Enter, Location, Options.ProtectionRadius, Type);
                        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.ObjectName() == "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.IsKilled())
                {
                    foreach (var id in priv.authorizedPlayers)
                    {
                        if (id.userid.IsSteamId())
                        {
                            isAuthorized = true;
                            return;
                        }
                    }
                }
                else if (privSpawned && priv.IsKilled())
                {
                    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);
                }
            }

            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;
                }

                foreach (var ri in raiders.Values.ToList())
                {
                    if (ri.player.IsNull() || RemoveFauxAdmin(ri.player) || ri.player == owner || ri.IsAllowed || CanBypass(ri.player) || !intruders.Contains(ri.userid))
                    {
                        continue;
                    }

                    if (CanEject(ri.player))
                    {
                        ri.Reset();
                        UI.DestroyStatusUI(ri.player);
                        RemovePlayer(ri.player, EjectCode.Protector, Location, ProtectionRadius, Type);
                        continue;
                    }

                    if (config.Settings.Management.LockToRaidOnEnter && !ri.LockedOnEnter)
                    {
                        QueueNotification(ri.player, "OnLockedToRaid");

                        ri.LockedOnEnter = true;
                    }

                    ri.IsAllowed = true;
                }
            }

            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.IsKilled()) continue;
                    container.dropsLoot = false;
                    ClearInventory(container.inventory);
                }
            }

            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 (block.IsKilled())
                {
                    return;
                }

                if (Options.Tiers.Any())
                {
                    ChangeTier(block);
                }

                block.StopBeingDemolishable();
                block.StopBeingRotatable();
            }

            private void SetupCollider()
            {
                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)
                {
                    ClearInventory(container.inventory);
                    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.ShortPrefabName != "box.wooden.large") continue;
                    container.SendNetworkUpdate();
                }
            }

            public void SetupEntity(BaseEntity e, bool skipCheck = true)
            {
                if (skipCheck)
                {
                    AddEntity(e);
                }

                MyInstance.RaidEntities[e] = this;
            }

            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 SetupHeldEntity(Item item)
            {
                var e = item.GetHeldEntity();
                if (!e.IsReallyValid()) return;
                data.TemporaryUID.Add(e.net.ID);
            }

            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 (Instance.NightLantern.CanCall())
                {
                    return;
                }

                if (config.Settings.Management.Lights || config.Settings.Management.AlwaysLights)
                {
                    ToggleLights();
                }
            }

            private void SetupLock(BaseEntity e, bool justCreated = false)
            {
                AddEntity(e);
                locks.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.RemoveWhere(x => x.IsKilled());

                treasureAmount = Options.MinTreasure > 0 ? UnityEngine.Random.Range(Options.MinTreasure, Options.MaxTreasure + 1) : Options.MaxTreasure;

                if (Options.SkipTreasureLoot || treasureAmount <= 0)
                {
                    return;
                }

                var containers = new List<StorageContainer>();

                if (!SetupLootContainers(containers))
                {
                    return;
                }

                SetTreasureLootLimit(containers);
                TakeLootFromBaseLoot();
                TakeLootFromDifficultyLoot();
                TakeLootFromDefaultLoot();
                PopulateLoot(true);
                TryAddDuplicates();
                PopulateLoot(false);

                if (Loot.Count == 0)
                {
                    Puts(mx("NoConfiguredLoot"));
                    return;
                }

                TryRemoveDuplicates();
                VerifyLootAmount();
                SpawnLoot(containers);
            }

            private bool SetupLootContainers(List<StorageContainer> containers)
            {
                if (_containers.Count == 0)
                {
                    Puts(mx(Entities.Exists() ? "NoContainersFound" : "NoEntitiesFound", null, BaseName, PositionToGrid(Location)));
                    return false;
                }

                CheckExpansionSettings();

                foreach (var container in _containers)
                {
                    if (!IsBox(container, true) || Options.IgnoreContainedLoot && !container.inventory.IsEmpty())
                    {
                        continue;
                    }

                    containers.Add(container);
                }

                if (Options.IgnoreContainedLoot)
                {
                    lockers.RemoveAll(x => !x.inventory.IsEmpty());
                }

                if (containers.Count == 0)
                {
                    Puts(mx("NoBoxesFound", null, BaseName, PositionToGrid(Location)));
                    return false;
                }

                return true;
            }

            private void CheckExpansionSettings()
            {
                if (!config.Settings.ExpansionMode || !Instance.DangerousTreasures.CanCall())
                {
                    return;
                }

                var boxes = Pool.GetList<StorageContainer>();

                foreach (var x in _containers)
                {
                    if (x.ShortPrefabName == "box.wooden.large")
                    {
                        boxes.Add(x);
                    }
                }

                if (boxes.Count > 0)
                {
                    Instance.DangerousTreasures?.Call("API_SetContainer", boxes.GetRandom(), Radius, !Options.NPC.Enabled || Options.NPC.UseExpansionNpcs);
                }

                Pool.FreeList(ref boxes);
            }

            private void SetTreasureLootLimit(List<StorageContainer> containers)
            {
                if (treasureAmount <= 400)
                {
                    return;
                }

                int availableSpace = privSpawned && config.Settings.Management.Cupboard ? 24 : 0;

                foreach (var container in containers)
                {
                    availableSpace += container.InventorySlots();
                }

                if (config.Settings.Management.Lockers)
                {
                    foreach (var container in lockers)
                    {
                        availableSpace += container.InventorySlots();
                    }
                }

                if (config.Settings.Management.Cook)
                {
                    foreach (var container in ovens)
                    {
                        availableSpace += container.InventorySlots();
                    }
                }

                if (config.Settings.Management.Food)
                {
                    foreach (var container in _allcontainers)
                    {
                        if (container?.ShortPrefabName != "fridge.deployed") continue;
                        availableSpace += container.InventorySlots();
                    }

                    foreach (var container in ovens)
                    {
                        if (!Instance.BBQs.Contains(container.prefabID)) continue;
                        availableSpace += container.InventorySlots();
                    }
                }

                if (treasureAmount > availableSpace)
                {
                    treasureAmount = availableSpace;
                }
            }

            private void TakeLootFromBaseLoot()
            {
                TakeLootFrom(Instance.BaseLootList.ToList(), BaseLoot, config.Treasure.UniqueBaseLoot);

                BaseLootPermanent = BaseLoot.ToList();
            }

            private void TakeLootFromDifficultyLoot()
            {
                if (BaseLoot.Count < treasureAmount && Buildings.Normal.Count > 0)
                {
                    TakeLootFrom(Buildings.Normal.ToList(), DifficultyLoot, config.Treasure.UniqueDifficultyLoot);
                }
            }

            private void TakeLootFromDefaultLoot()
            {
                if (BaseLoot.Count + DifficultyLoot.Count < treasureAmount)
                {
                    TakeLootFrom(TreasureLoot.ToList(), DefaultLoot, config.Treasure.UniqueDefaultLoot);
                }
            }

            private void TakeLootFrom(List<LootItem> source, List<LootItem> to, bool isUnique)
            {
                if (source.Count == 0)
                {
                    return;
                }

                var from = new List<LootItem>();

                foreach (var ti in source)
                {
                    if (ti == null || ti.amount <= 0)
                    {
                        continue;
                    }

                    Collective.Add(ti.Clone());
                    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)
                    {
                        ti.amount = Mathf.CeilToInt(ti.amount * m);
                        ti.amountMin = Mathf.CeilToInt(ti.amountMin * m);
                    }
                }
            }

            private void PopulateLoot(bool unique)
            {
                if (!unique || !config.Treasure.UniqueBaseLoot)
                {
                    AddToLoot(BaseLoot);
                }

                if (!unique || !config.Treasure.UniqueDifficultyLoot)
                {
                    AddToLoot(DifficultyLoot);
                }

                if (!unique || !config.Treasure.UniqueDefaultLoot)
                {
                    AddToLoot(DefaultLoot);
                }
            }

            private void TryAddDuplicates()
            {
                if (Options.AllowDuplicates && Loot.Count > 0 && Loot.Count < treasureAmount)
                {
                    int attempts = treasureAmount;

                    while (Loot.Count < treasureAmount && Collective.Count > 0 && --attempts > 0)
                    {
                        var ti = Collective.GetRandom();

                        if (IsUnique(ti))
                        {
                            Collective.Remove(ti);
                            continue;
                        }

                        AddToLoot(ti);
                    }
                }
            }

            private void TryRemoveDuplicates()
            {
                if (!Options.AllowDuplicates)
                {
                    var newLoot = new List<LootItem>();

                    foreach (var ti in Loot)
                    {
                        if (ti.modified || !LootNames.Contains(ti.shortname) || IsPriority(ti))
                        {
                            LootNames.Add(ti.shortname);
                            newLoot.Add(ti);
                        }
                    }

                    Loot = newLoot;
                }

                foreach (var ti in Loot)
                {
                    LootNames.Add(ti.shortname);
                }
            }

            private bool IsPriority(LootItem a)
            {
                return Options.AlwaysSpawnBaseLoot && BaseLootPermanent.Exists(b => a.shortname == b.shortname);
            }

            private bool IsUnique(LootItem ti)
            {
                if (!Options.AllowDuplicates && Loot.Exists(x => x.shortname == ti.shortname))
                {
                    return true;
                }

                if (config.Treasure.UniqueBaseLoot && BaseLootPermanent.Exists(x => x.Equals(ti)))
                {
                    return true;
                }

                if (config.Treasure.UniqueDifficultyLoot && DifficultyLoot.Exists(x => x.Equals(ti)))
                {
                    return true;
                }

                if (config.Treasure.UniqueDefaultLoot && DefaultLoot.Exists(x => x.Equals(ti)))
                {
                    return true;
                }

                return false;
            }

            private void VerifyLootAmount()
            {
                if (Loot.Count > treasureAmount)
                {
                    Shuffle(Loot);

                    int index = Loot.Count;

                    while (Loot.Count > treasureAmount && --index >= 0)
                    {
                        if (!IsPriority(Loot[index]))
                        {
                            Loot.RemoveAt(index);
                        }
                    }
                }
                else
                {
                    int retries = treasureAmount - Loot.Count;

                    while (Loot.Count < treasureAmount && Collective.Count > 0 && --retries > 0)
                    {
                        var ti = Collective.GetRandom();

                        if (IsUnique(ti))
                        {
                            Collective.Remove(ti);
                            continue;
                        }

                        LootNames.Add(ti.shortname);
                        AddToLoot(ti);
                        retries++;
                    }
                }
            }

            private void ClearLoot()
            {
                BaseLoot.Clear();
                BaseLootPermanent.Clear();
                DifficultyLoot.Clear();
                DefaultLoot.Clear();
                Collective.Clear();
                Loot.Clear();
                LootNames.Clear();
            }

            private void SpawnLoot(List<StorageContainer> containers)
            {
                if (Options.DivideLoot)
                {
                    DivideLoot(containers);
                }
                else SpawnLoot(containers, treasureAmount);

                if (itemAmountSpawned == 0)
                {
                    Puts(mx("NoLootSpawned"));
                }

                Interface.Oxide.NextTick(ClearLoot);
            }

            private void SpawnLoot(List<StorageContainer> containers, int amount)
            {
                StorageContainer container = containers.FirstOrDefault(x => x.inventory.itemList.Count + amount <= x.inventory.capacity);

                if (container == null)
                {
                    container = containers.GetRandom();
                    ClearInventory(container.inventory);
                    ItemManager.DoRemoves();
                }

                SpawnLoot(container, amount);
            }

            private void SpawnLoot(StorageContainer container, int amount)
            {
                if (amount > container.inventory.capacity)
                {
                    amount = container.inventory.capacity;
                }

                for (int j = 0; j < amount; j++)
                {
                    if (Loot.Count == 0)
                    {
                        break;
                    }

                    var lootItem = GetRandomLootItem();

                    Loot.Remove(lootItem);

                    SpawnItem(lootItem, new List<StorageContainer> { container });
                }
            }

            private LootItem GetRandomLootItem()
            {
                for (int i = 0; i < Loot.Count; i++)
                {
                    if (IsPriority(Loot[i]) || Loot[i].modified)
                    {
                        return Loot[i];
                    }
                }

                return Loot.GetRandom();
            }

            private void DivideLoot(List<StorageContainer> containers)
            {
                while (Loot.Count > 0 && containers.Count > 0 && itemAmountSpawned < treasureAmount)
                {
                    var lootItem = GetRandomLootItem();

                    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 void SpawnItem(LootItem ti, ItemContainer[] containers)
            {
                Item item = CreateItem(ti);

                if (item == null || item.MoveToContainer(containers[0]) || item.MoveToContainer(containers[1]) || item.MoveToContainer(containers[2]))
                {
                    return;
                }

                item.Remove();
            }

            private SpawnResult SpawnItem(LootItem 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 (container is BaseOven && !IsCookable(item.info))
                    {
                        continue;
                    }
                    else if (item.MoveToContainer(container.inventory, -1, false))
                    {
                        itemAmountSpawned++;
                        return SpawnResult.Success;
                    }
                }

                item.Remove();
                return SpawnResult.Failure;
            }

            private Item CreateItem(LootItem 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.IsReallyValid())
                {
                    e.skinID = skin;
                    e.SendNetworkUpdate();
                }

                return item;
            }

            private void AddToLoot(List<LootItem> source)
            {
                foreach (var ti in source)
                {
                    AddToLoot(ti);
                }

                source.Clear();
            }

            private void AddToLoot(LootItem lootItem)
            {
                LootItem ti = lootItem.Clone();
                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)
                {
                    isModified = true;
                    shortname = shortname.Substring(shortname.IndexOf("_") + 1);
                }

                if (ti.definition == null)
                {
                    Puts("Invalid shortname in config: {0} -> {1}", ti.shortname, shortname);
                    return;
                }

                ti.isBlueprint = isBlueprint;

                int amount = ti.amount;

                if (ti.amountMin < ti.amount)
                {
                    amount = Core.Random.Range(ti.amountMin, ti.amount + 1);
                }

                if (amount <= 0)
                {
                    Loot.RemoveAll(x => lootItem.Equals(x));
                    Collective.RemoveAll(x => lootItem.Equals(x));
                    return;
                }

                if (config.Treasure.UseStackSizeLimit)
                {
                    var stacks = GetStacks(amount, ti.definition.stackable);
                    if (!isModified) isModified = amount > ti.definition.stackable;

                    foreach (int stack in stacks)
                    {
                        Loot.Add(new LootItem
                        {
                            amount = stack,
                            shortname = shortname,
                            skin = ti.skin,
                            modified = isModified,
                            isBlueprint = isBlueprint
                        });
                    }
                }
                else
                {
                    Loot.Add(new LootItem
                    {
                        amount = amount,
                        shortname = shortname,
                        skin = ti.skin,
                        isBlueprint = isBlueprint,
                        modified = isModified
                    });
                }
            }

            private List<int> GetStacks(int amount, int maxStack)
            {
                var stacks = new List<int>();

                while (amount > maxStack && stacks.Count < 100)
                {
                    amount -= maxStack;
                    stacks.Add(maxStack);
                }

                if (amount > 0)
                {
                    stacks.Add(amount);
                }

                return stacks;
            }

            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();
                            }
                            else SetupHeldEntity(item);
                        }
                    }
                }

                turret.Invoke(turret.UpdateAttachedWeapon, 1f);

                if (MyInstance.debugMode)
                {
                    MyInstance.timer.Once(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 player)
            {
                if (!config.Settings.Management.AllowTeleport && player.IsConnected && !CanBypass(player) && NearFoundation(player.transform.position) && Interface.CallHook("OnBlockRaidableBasesTeleport", player, Location) == null)
                {
                    TryMessage(player, "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);
                MyInstance.timer.Once(10f, () => messagesSent.Remove(userid));
                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 Instance.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 Instance.Raids.Values.Count;
            }

            public static RaidableBase Get(ulong userID)
            {
                RaidableBase raid;
                if (Instance.Npcs.TryGetValue(userID, out raid))
                {
                    return raid;
                }

                return null;
            }

            public static RaidableBase Get(Vector3 target, float f = 0f)
            {
                foreach (var raid in Instance.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 (Instance.PvpDelay.TryGetValue(corpse.playerSteamID, out ds))
                {
                    return ds.RaidableBase;
                }

                return Get(corpse.transform.position);
            }

            public static RaidableBase Get(BaseEntity entity)
            {
                if (Instance.RaidEntities.ContainsKey(entity))
                {
                    return Instance.RaidEntities[entity];
                }

                return null;
            }

            public static RaidableBase Get(List<BaseEntity> entities)
            {
                foreach (var raid in Instance.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)
            {
                if (victim.IsReallyValid()) records[victim.net.ID] = new DamageRecord(attacker);
                attacker.lastDealtDamageTime = Time.time;
                victim.lastAttacker = attacker;

                return attacker;
            }

            public BasePlayer GetInitiatorPlayer(HitInfo hitInfo, BaseCombatEntity victim)
            {
                if (hitInfo.Initiator is BasePlayer)
                {
                    return Record(hitInfo.Initiator as BasePlayer, victim);
                }

                if (!hitInfo.damageTypes.Has(DamageType.Heat))
                {
                    return null;
                }

                foreach (var intruder in GetIntruders().Where(x => !(Time.time - x.lastDealtDamageTime > 1f || !IsUsingProjectile(x))))
                {
                    return Record(intruder, victim);
                }

                DamageRecord history;
                if (victim.IsReallyValid() && records.TryGetValue(victim.net.ID, out history) && history.player.IsReallyValid())
                {
                    history.player.lastDealtDamageTime = Time.time;
                    victim.lastAttacker = history.player;
                    return history.player;
                }

                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 (!Instance.Skins.TryGetValue(def.shortname, out si))
                {
                    Instance.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 Instance.Npcs.ContainsKey(userID);
            }

            public static bool Has(TriggerBase triggerBase)
            {
                foreach (var raid in Instance.Raids.Values)
                {
                    if (raid.triggers.ContainsKey(triggerBase))
                    {
                        return true;
                    }
                }

                return false;
            }

            public static bool Has(BaseEntity entity)
            {
                return Instance.RaidEntities.ContainsKey(entity);
            }

            public static bool IsOwner(BasePlayer player)
            {
                return Instance.Raids.Values.Exists(raid => raid.ownerId == player.userID && (raid.IsOpened || raid.IsDespawning || raid.despawnTimer != null));
            }

            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 || Instance.Wizardry == null || !Instance.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);
                    Message(player, "TooPowerfulDrop");
                }
                else Message(player, "TooPowerful");
            }

            public static void UpdateAllMarkers()
            {
                foreach (var raid in Instance.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 userid, bool checkAllies = true)
            {
                if (ownerId == userid) return true;
                Raider ri;
                if (!raiders.TryGetValue(userid, out ri)) return false;
                return ri.IsParticipant || checkAllies && ri.IsAlly;
            }

            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)
            {
                if (e.IsKilled())
                {
                    Entities.Remove(e);
                    return false;
                }

                if (e.net == null)
                {
                    e.net = Net.sv.CreateNetworkable();
                }

                if (e is StaticInstrument)
                {
                    data.TemporaryUID.Remove(e.net.ID);
                    Entities.Remove(e);
                    Interface.Oxide.NextTick(e.SafelyKill);
                    return false;
                }

                return true;
            }


            public bool EjectBackpack(uint key, BackpackData data, bool bypass)
            {
                if (data.backpack.IsKilled())
                {
                    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)
                    {
                        Message(player, "YourCorpse");
                        return true;
                    }

                    bool isAdmin = player.IsAdmin;

                    Message(player, "YourCorpse");

                    try
                    {
                        ToggleAdminFlag(player, isAdmin, true);

                        string message = mx("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, 512, GetOwner(), GetRaiders(), Entities.ToList(), BaseName });

                return true;
            }

            public BasePlayer GetOwner()
            {
                if (!owner.IsNull())
                {
                    return owner;
                }

                foreach (var player in GetRaiders())
                {
                    return player;
                }

                return null;
            }

            public List<BasePlayer> GetIntruders()
            {
                return raiders.Values.Where(x => intruders.Contains(x.userid) && x.player.IsReallyValid()).Select(x => x.player).ToList();
            }

            public List<BasePlayer> GetRaiders()
            {
                return raiders.Values.Where(x => x.player.IsReallyValid() && x.IsParticipant).Select(x => x.player).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(Instance.Clans?.Call("IsMemberOrAlly", playerId.ToString(), targetId.ToString())))
                {
                    return true;
                }

                if ((type == AlliedType.All || type == AlliedType.Friend) && Convert.ToBoolean(Instance.Friends?.Call("AreFriends", playerId.ToString(), targetId.ToString())))
                {
                    return true;
                }

                return false;
            }

            public bool IsAlly(BasePlayer player)
            {
                if (!ownerId.IsSteamId() || CanBypass(player) || player.userID == ownerId)
                {
                    return true;
                }

                Raider ri = GetRaider(player);

                if (ri.IsAlly || IsAlly(player.userID, ownerId))
                {
                    ri.IsAlly = true;
                    return true;
                }

                return false;
            }

            public void DismountAllPlayers(BaseMountable m)
            {
                foreach (var target in GetMountedPlayers(m))
                {
                    if (target.IsNull()) continue;

                    m.DismountPlayer(target, false);

                    target.EnsureDismounted();
                }
            }

            public bool IsControlledMount(BaseEntity m)
            {
                if (!config.Settings.Management.Mounts.ControlledMounts)
                {
                    return false;
                }

                if (m is BaseChair)
                {
                    DismountAllPlayers(m as BaseMountable);

                    return true;
                }

                var parentEntity = m.GetParentEntity();

                if (parentEntity.IsNull() || parentEntity is RidableHorse)
                {
                    return false;
                }

                if (parentEntity.GetType().Name.Contains("Controller"))
                {
                    DismountAllPlayers(m as BaseMountable);

                    return true;
                }

                return false;
            }

            private bool IsBlockingCampers(ModularCar car)
            {
                if (!config.Settings.Management.Mounts.Campers || car.AttachedModuleEntities == null)
                {
                    return false;
                }

                foreach (var module in car.AttachedModuleEntities)
                {
                    if (module.ShortPrefabName.Contains("module_camper"))
                    {
                        return true;
                    }
                }

                return false;
            }

            public void DestroyMapMarkers()
            {
                if (!explosionMarker.IsKilled())
                {
                    explosionMarker.CancelInvoke(explosionMarker.DelayedDestroy);
                    explosionMarker.Kill();
                }

                genericMarker.SafelyKill();
                vendingMarker.SafelyKill();
            }

            public static bool RemovePlayer(BasePlayer player, EjectCode code, Vector3 a, float radius, RaidableType type)
            {
                if (!player.IsHuman() || type == RaidableType.None && !player.IsSleeping())
                {
                    return false;
                }

                var m = player.GetMounted();

                if (m.IsReallyValid())
                {
                    var players = GetMountedPlayers(m);

                    players.RemoveAll(x => x.IsNull() || !x.IsHuman());

                    return EjectMountable(m, code, players, a, radius);
                }

                var position = GetEjectLocation(player.transform.position, 10f, a, radius);

                if (player.IsFlying)
                {
                    position.y = player.transform.position.y;
                }

                player.Teleport(position);
                player.SendNetworkUpdateImmediate();

                PrintDebugMessage($"Ejected {player.displayName} ({player.userID}) with code {code} at {a} in {PositionToGrid(a)}");

                return true;
            }

            public void ResetOwner()
            {
                if (!IsOpened || !ownerId.IsSteamId() || IsPlayerActive(ownerId.ToString()))
                {
                    TryInvokeResetOwner();
                    return;
                }

                riOwner = null;
                owner = null;
                ownerId = 0;
                UpdateMarker();
            }

            public void SetOwner(BasePlayer player)
            {
                riOwner = GetRaider(player);
                owner = player;
                ownerId = player.userID;
                permOwnerId = ownerId;
                riOwner.IsAlly = true;
                riOwner.IsAllowed = true;
                TryInvokeResetOwner();
                UpdateStatus(player);
                ClearEnemies();
                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 && Instance.DangerousTreasures != null && Instance.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);
            }

            private void TryResetDespawn()
            {
                if (config.Settings.Management.DespawnMinutesReset && despawnTimer != null)
                {
                    float time = config.Settings.Management.DespawnMinutes * 60f;
                    despawnTime = Time.time + time;
                    despawnTimer.Reset();
                }
            }

            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)
                            {
                                QueueNotification(target, "DestroyingBaseAt", grid, config.Settings.Management.DespawnMinutes);
                            }
                        }

                        var go = gameObject;

                        despawnTimer = MyInstance.timer.Once(time, () => Destroy(go));
                    }
                    else Destroy(gameObject);
                }
            }

            public void UpdateMarker()
            {
                if (IsLoading)
                {
                    Invoke(UpdateMarker, 1f);
                    return;
                }

                if (!genericMarker.IsKilled())
                {
                    genericMarker.SendUpdate();
                }

                if (!explosionMarker.IsKilled())
                {
                    explosionMarker.transform.position = Location;
                    explosionMarker.SendNetworkUpdate();
                }

                if (!vendingMarker.IsKilled())
                {
                    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 = mx(AllowPVP ? "PVPFlag" : "PVEFlag");
                    string markerShopName = markerName == config.Settings.Markers.MarkerName ? mx("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(StringPool.Get(4060989661), 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(StringPool.Get(3459945130), Location) as VendingMachineMapMarker;

                    if (vendingMarker != null)
                    {
                        string flag = mx(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 = mx("MapMarkerOrderWithMode", null, flag, Mode(), markerName, despawnText);
                        }
                        else markerShopName = mx("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>();

                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 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 CreateCodeLock(BaseEntity entity)
            {
                var codeLock = GameManager.server.CreateEntity(StringPool.Get(3518824735)) 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(StringPool.Get(2849728229), 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.IsKilled())
                {
                    return;
                }

                var slot = entity.GetSlot(BaseEntity.Slot.Lock) as BaseLock;

                if (slot == null)
                {
                    CreateCodeLock(entity);
                    return;
                }

                var keyLock = slot.GetComponent<KeyLock>();

                if (!keyLock.IsNull())
                {
                    keyLock.SetParent(null);
                    keyLock.SafelyKill();
                }

                CreateCodeLock(entity);
            }

            private static bool IsFlying(BasePlayer player)
            {
                return !player.IsKilled() && !player.modelState.onground && TerrainMeta.HeightMap.GetHeight(player.transform.position) < player.transform.position.y - 1f;
            }

            private static BaseMountable GetParentMountableEntity(BaseMountable m)
            {
                while (m != null && m.HasParent())
                {
                    var parent = m.GetParentEntity() as BaseMountable;
                    if (parent == null) break;
                    m = parent;
                }

                return m;
            }

            public static bool EjectMountable(HotAirBalloon m, EjectCode code, List<BasePlayer> players, Vector3 position, float radius)
            {
                var j = TerrainMeta.HeightMap.GetHeight(m.transform.position) - m.transform.position.y;
                var target = ((m.transform.position.XZ3D() - position.XZ3D()).normalized * (radius + 10f)) + position;

                target.y = Mathf.Max(m.transform.position.y, GetSpawnHeight(target)) + 5f;

                m.transform.position = target;

                return true;
            }

            public static bool EjectMountable(BaseMountable m, EjectCode code, List<BasePlayer> players, Vector3 position, float radius)
            {
                m = GetParentMountableEntity(m);

                var j = TerrainMeta.HeightMap.GetHeight(m.transform.position) - m.transform.position.y;
                float distance = m is RidableHorse ? 2f : 10f;

                if (j > 5f)
                {
                    distance += j;
                }

                var target = ((m.transform.position.XZ3D() - position.XZ3D()).normalized * (radius + distance)) + position;

                if (m is MiniCopter || m is CH47Helicopter || players.Exists(player => IsFlying(player)))
                {
                    target.y = Mathf.Max(m.transform.position.y, GetSpawnHeight(target)) + 5f;
                }
                else target.y = GetSpawnHeight(target) + 0.1f;

                BaseVehicle vehicle = m.HasParent() ? m.VehicleParent() : m as BaseVehicle;

                if (m is RidableHorse || InRange(m.transform.position, position, radius + 1f))
                {
                    m.transform.position = target;
                }
                else if (!vehicle.IsNull())
                {
                    TryPushMountable(vehicle, target);
                }
                else m.transform.position = target;

                return true;
            }

            private static void TryPushMountable(BaseVehicle vehicle, Vector3 target)
            {
                if (vehicle is MiniCopter || vehicle is CH47Helicopter || vehicle.name.Contains("modularcar"))
                {
                    var body = vehicle.rigidBody ?? vehicle.GetComponent<Rigidbody>();
                    Vector3 normalized = Vector3.ProjectOnPlane(vehicle.transform.position - target, vehicle.transform.up).normalized;
                    float d2 = body.mass * 4f;
                    body.AddForce(-normalized * d2, ForceMode.Impulse);
                }
                else
                {
                    var e = vehicle.transform.eulerAngles; // credits k1lly0u

                    vehicle.transform.rotation = Quaternion.Euler(e.x, e.y - 180f, e.z);
                    var body = vehicle.rigidBody ?? vehicle.GetComponent<Rigidbody>();
                    if (body != null)
                    {
                        body.velocity *= -1f;
                    }
                }
            }

            private void EjectSleepers()
            {
                if (!config.Settings.Management.EjectSleepers || Type == RaidableType.None)
                {
                    return;
                }

                foreach (var player in FindEntitiesOfType<BasePlayer>(Location, ProtectionRadius, Layers.Mask.Player_Server))
                {
                    if (player.IsSleeping() && !player.IsBuildingAuthed())
                    {
                        RemovePlayer(player, EjectCode.Sleeper, Location, ProtectionRadius, Type);
                    }
                }
            }

            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.Distance(Location) > 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 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 = Instance.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 DestroyLocks()
            {
                locks.ForEach(SafelyKill);
            }

            private void DestroyNpcs()
            {
                npcs.ForEach(SafelyKill);
            }

            private void DestroySpheres()
            {
                spheres.ForEach(SafelyKill);
            }

            public void ToggleLights()
            {
                bool state = config.Settings.Management.AlwaysLights || TOD_Sky.Instance?.IsDay == false;

                if (lights?.Count > 0)
                {
                    foreach (var io in lights)
                    {
                        if (io.IsKilled()) continue;
                        io.UpdateHasPower(state ? 25 : 0, 1);
                        io.SetFlag(BaseEntity.Flags.On, state);
                        io.SendNetworkUpdateImmediate();
                    }
                }

                if (ovens?.Count > 0)
                {
                    foreach (var oven in ovens.Where(oven => !oven.IsKilled()))
                    {
                        if (state && (Instance.Furnaces.Contains(oven.prefabID) && oven is BaseOven && (oven as BaseOven).inventory.IsEmpty())) continue;
                        oven.SetFlag(BaseEntity.Flags.On, state);
                    }
                }

                if (npcs?.Count > 0)
                {
                    foreach (var npc in npcs.Where(npc => !npc.IsKilled()))
                    {
                        ToggleNpcMinerHat(npc, state);
                    }
                }

                lightsOn = state;
            }

            private void MakeAnnouncements()
            {
                if (Type == RaidableType.None)
                {
                    itemAmountSpawned = 0;

                    foreach (var x in _allcontainers)
                    {
                        if (x.IsKilled()) 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.Distance(Location));
                    string flag = mx(AllowPVP ? "PVPFlag" : "PVEFlag", target.UserIDString).Replace("[", string.Empty).Replace("] ", string.Empty);
                    string api = mx("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)
                    {
                        QueueNotification(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.IsKilled() && Instance.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.IsKilled())
                {
                    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;
                }

                var fridges = _allcontainers.Where(x => !x.IsKilled() && x.prefabID == 1844023509);

                if (fridges.Count > 1)
                {
                    Shuffle(fridges);
                }

                if (fridges.Count > 1)
                {
                    Shuffle(fridges);
                }

                foreach (var x in fridges)
                {
                    if (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 (Instance.Helms.Contains(item.info.shortname))
                    {
                        if (MoveToContainer(locker.inventory, item, 0, 13, 26))
                        {
                            return true;
                        }
                    }
                    else if (Instance.Boots.Contains(item.info.shortname))
                    {
                        if (MoveToContainer(locker.inventory, item, 1, 14, 27))
                        {
                            return true;
                        }
                    }
                    else if (Instance.Gloves.Contains(item.info.shortname))
                    {
                        if (MoveToContainer(locker.inventory, item, 2, 15, 28))
                        {
                            return true;
                        }
                    }
                    else if (Instance.Vests.Contains(item.info.shortname))
                    {
                        if (MoveToContainer(locker.inventory, item, 3, 16, 29))
                        {
                            return true;
                        }
                    }
                    else if (Instance.Legs.Contains(item.info.shortname))
                    {
                        if (MoveToContainer(locker.inventory, item, 4, 17, 30))
                        {
                            return true;
                        }
                    }
                    else if (Instance.Shirts.Contains(item.info.shortname))
                    {
                        if (MoveToContainer(locker.inventory, item, 5, 18, 31))
                        {
                            return true;
                        }
                    }
                    else if (Instance.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.IsKilled() || Instance.BBQs.Contains(oven.prefabID))
                    {
                        continue;
                    }

                    if (item.info.shortname.EndsWith(".ore") && !Instance.Furnaces.Contains(oven.prefabID))
                    {
                        continue;
                    }

                    if (item.info.shortname == "lowgradefuel" && !Instance.Lanterns.Contains(oven.prefabID))
                    {
                        continue;
                    }

                    if (item.info.shortname == "crude.oil" && !Instance.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 void SetupBoxSkin(StorageContainer container)
            {
                if (!IsBox(container, false))
                {
                    return;
                }

                ItemDefinition def;
                if (!_definitions.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.IsKilled());

                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.IsKilled());

                foreach (var door in doors)
                {
                    SetupDoor(door);
                }
            }

            private void SetupMountable(BaseMountable mountable)
            {
                if (!config.Settings.Management.DespawnMounts)
                {
                    MyInstance.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 (!_definitions.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 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);

                MyInstance.Npcs[npc.userID] = this;

                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();

                    MyInstance.Kits?.Call("GiveKit", npc, kits.GetRandom());
                }

                EquipWeapon(npc, brain, false);

                if (!ToggleNpcMinerHat(npc, TOD_Sky.Instance?.IsNight == true))
                {
                    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())
                    {
                        data.TemporaryUID.Add(e.net.ID);

                        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 (Instance.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 (Instance.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) => Instance.Subscribe(hook);

            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.Exists(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(BaseEntity m, List<BasePlayer> players)
            {
                if (m.IsNull() || Type == RaidableType.None || m.GetParentEntity() is TrainCar || IsControlledMount(m) || Entities.Contains(m))
                {
                    return false;
                }

                bool shouldEject = config.Settings.Management.Mounts.Other;

                if (CanEject(players))
                {
                    shouldEject = true;
                }
                else if (m is BaseBoat)
                {
                    shouldEject = config.Settings.Management.Mounts.Boats;
                }
                else if (m is BasicCar)
                {
                    shouldEject = config.Settings.Management.Mounts.BasicCars;
                }
                else if (m is ModularCar)
                {
                    shouldEject = config.Settings.Management.Mounts.ModularCars || IsBlockingCampers(m as ModularCar);
                }
                else if (m is CH47Helicopter)
                {
                    shouldEject = config.Settings.Management.Mounts.CH47;
                }
                else if (m is RidableHorse)
                {
                    shouldEject = config.Settings.Management.Mounts.Horses;
                }
                else if (m is ScrapTransportHelicopter)
                {
                    shouldEject = config.Settings.Management.Mounts.Scrap;
                }
                else if (m is MiniCopter && !(m is ScrapTransportHelicopter))
                {
                    shouldEject = config.Settings.Management.Mounts.MiniCopters;
                }
                else if (m is Snowmobile)
                {
                    shouldEject = config.Settings.Management.Mounts.Snowmobile;
                }
                else if (m is StaticInstrument)
                {
                    shouldEject = config.Settings.Management.Mounts.Pianos;
                }
                else if (m is HotAirBalloon)
                {
                    if (config.Settings.Management.Mounts.HotAirBalloon)
                    {
                        return EjectMountable(m as HotAirBalloon, EjectCode.Mounted, players, Location, ProtectionRadius);
                    }
                }
                else if (IsJetpack(m))
                {
                    shouldEject = config.Settings.Management.Mounts.Jetpacks;
                }

                if (shouldEject && m is BaseMountable)
                {
                    return EjectMountable(m as BaseMountable, EjectCode.Mounted, players, Location, ProtectionRadius);
                }

                return false;
            }

            private bool IsJetpack(BaseEntity m)
            {
                return m.ShortPrefabName == "standingdriver" && m.HasParent() && m.GetParentEntity() is DroppedItemContainer;
            }

            private void UpdateStatus(BasePlayer player)
            {
                if (IsOpened)
                {
                    lastActive[player.UserIDString] = Time.realtimeSinceStartup;
                }

                if (ownerId == player.userID && Time.time - _lastInvokeUpdate > 1f)
                {
                    _lastInvokeUpdate = Time.time;
                    TryInvokeResetOwner();
                }
            }
        }

        #region Garbage

        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 UndoSettings(List<BaseEntity> entities, int limit = 1, bool mounts = false, bool structures = false, bool deployables = false, bool teleport = false, object[] ho = null)
            {
                Entities = entities;
                Limit = limit;
                Structures = structures;
                Deployables = deployables;
                Mounts = mounts;
                Teleport = teleport;
                hookObjects = ho;
            }
        }

        internal Dictionary<BaseEntity, MountInfo> MountEntities { get; set; } = new Dictionary<BaseEntity, MountInfo>();
        internal Dictionary<BaseEntity, RaidableBase> RaidEntities { get; set; } = new Dictionary<BaseEntity, RaidableBase>();

        private 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 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 bool KeepMountable(BaseEntity entity)
        {
            MountInfo mi;
            if (!MountEntities.TryGetValue(entity, out mi) || !MountEntities.Remove(entity))
            {
                return false;
            }

            return !mi.mountable.GetMounted().IsNull() || !InRange(entity.transform.position, mi.position, mi.radius);
        }

        public void RemoveHeldEntities()
        {
            foreach (var element in RaidEntities.ToList())
            {
                if (element.Key is IItemContainerEntity)
                {
                    var ice = element.Key as IItemContainerEntity;

                    if (ice.IsNull() || ice.inventory.IsNull())
                    {
                        continue;
                    }

                    ClearInventory(ice.inventory);
                }
            }

            ItemManager.DoRemoves();
        }

        public void DespawnAll(List<RaidableBase> raids, bool inactiveOnly)
        {
            var entities = new List<BaseEntity>();
            bool teleportEntities = false;
            int limit = 1;

            for (int i = 0; i < raids.Count; i++)
            {
                var raid = raids[i];

                if (raid.IsNull() || inactiveOnly && (raid.intruders.Count > 0 || raid.ownerId.IsSteamId()))
                {
                    continue;
                }

                entities.AddRange(raid.Entities);

                if (raid.Options.Setup.TeleportEntities)
                {
                    teleportEntities = true;
                }

                limit = Math.Max(limit, raid.Options.Setup.DespawnLimit);

                raid.Despawn(false);
            }

            UndoLoop(new UndoSettings(
                entities,
                limit,
                config.Settings.Management.DespawnMounts,
                config.Settings.Management.DoNotDestroyStructures,
                config.Settings.Management.DoNotDestroyDeployables,
                teleportEntities), 0, 2, Time.realtimeSinceStartup + 5.33333f);
        }

        public static int Evaluate(BaseEntity entity)
        {
            if (entity is BuildingBlock)
            {
                return 2;
            }
            if (!entity.HasParent())
            {
                return 1;
            }
            return 0;
        }

        public void UndoLoop(UndoSettings undo, int count, int index, float spawnTime)
        {
            undo.Entities.RemoveAll(e => e.IsKilled() || KeepEntity(e, undo));

            undo.Entities.Sort((x, y) => Evaluate(x).CompareTo(Evaluate(y)));

            undo.Entities.Take(undo.Limit).ToList().ForEach(entity =>
            {
                undo.Entities.Remove(entity);

                if (entity.IsKilled() || entity.prefabID == 1519640547)
                {
                    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 (data != null && entity.net != null)
                {
                    data.TemporaryUID.Remove(entity.net.ID);
                }

                if (undo.Teleport)
                {
                    entity.transform.position = new Vector3(0f, -500f, 0f);
                    Interface.Oxide.NextTick(entity.SafelyKill);
                }
                else entity.Kill(BaseNetworkable.DestroyMode.None);
            });

            if (count != 0 && undo.Entities.Count != 0 && undo.Entities.Count == count)
            {
                goto done;
            }

            if (undo.Entities.Count > 0)
            {
                var action = new Action(() => UndoLoop(undo, undo.Entities.Count, index, spawnTime));
                Interface.Oxide.NextTick(action);
                return;
            }

        done:
            RaidEntities.RemoveAll((e, raid) => e.IsKilled());

            if (RaidEntities.Count == 0)
            {
                MountEntities.Clear();
            }

            if (undo.hookObjects != null)
            {
                Interface.CallHook("OnRaidableBaseDespawned", undo.hookObjects);
            }
        }

        #endregion Garbage

        #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) // TC
            {
                Message(target.player, "Cupboards are blocked!");
                return false;
            }
            else if (construction.prefabID == 2150203378) // LADDER
            {
                if (CanBuildLadders(target.player, raid))
                {
                    Raider ri;
                    if (raid.raiders.TryGetValue(target.player.userID, out ri) && ri.Input != null)
                    {
                        ri.Input.Restart();
                        ri.Input.TryPlace(ConstructionType.Ladder);
                    }
                }
                else
                {
                    Message(target.player, "Ladders are blocked!");
                    return false;
                }
            }
            else if (construction.fullName.Contains("/barricades/barricade."))
            {
                if (raid.Options.Barricades)
                {
                    Raider ri;
                    if (raid.raiders.TryGetValue(target.player.userID, out ri) && ri.Input != null)
                    {
                        ri.Input.Restart();
                        ri.Input.TryPlace(ConstructionType.Barricade);
                    }
                }
                else
                {
                    Message(target.player, "Barricades are blocked!");
                    return false;
                }
            }
            else if (!config.Settings.Management.AllowBuilding)
            {
                Message(target.player, "Building is blocked!");
                return false;
            }

            return null;
        }

        private bool CanBuildLadders(BasePlayer player, RaidableBase raid)
        {
            if (!config.Settings.Management.AllowLadders) return false;
            if (raid.Options.RequiresCupboardAccessLadders) return player.CanBuild();
            return true;
        }

        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.IsNull() || entity.IsNull() || player.limitNetworking)
            {
                return null;
            }

            if (entity is NPCAutoTurret && !player.userID.IsSteamId())
            {
                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 !raid.Options.NPC.IgnoreTrapsTurrets && (entity.OwnerID.IsSteamId() || !raid.Entities.Contains(entity));
            }
            else if (player is ScientistNPC)
            {
                return InRange((player as ScientistNPC).spawnPos, raid.Location, raid.ProtectionRadius) ? !raid.Options.NPC.IgnoreTrapsTurrets : (object)null;
            }

            if (!raid.AllowPVP)
            {
                return raid.Entities.Contains(entity) && !raid.BuiltList.ContainsKey(entity);
            }

            return raid.Entities.Contains(entity) || raid.BuiltList.ContainsKey(entity) ? true : (object)null;
        }

        private object CanEntityTakeDamage(BaseCombatEntity entity, HitInfo hitInfo)
        {
            if (hitInfo == null || hitInfo.damageTypes == null || !entity.IsReallyValid() || entity.OwnerID == 1337422)
            {
                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 (!player.IsReallyValid() || player.limitNetworking)
            {
                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);
                Message(looter, "NotAllowed");
            }
        }

        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)) ? m("CannotTeleport", player.UserIDString) : null;
        }

        private object CanTeleport(BasePlayer player, Vector3 to)
        {
            return !player.IsFlying && (EventTerritory(to) || EventTerritory(player.transform.position) || PvpDelay.ContainsKey(player.userID)) ? m("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>();

                if (data.Players.Count > 0)
                {
                    if (AssignTreasureHunters())
                    {
                        foreach (var entry in data.Players.ToList())
                        {
                            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 data.Players.ToList())
                    {
                        if (entry.Value.TotalRaids < average)
                        {
                            data.Players.Remove(entry.Key);
                        }
                    }
                }

                wiped = false;
                data.Save();
            }
        }

        private void Init()
        {
            Instance = this;
            IsUnloading = false;
            _sb = new StringBuilder();
            _sb2 = new StringBuilder();
            _definitions = 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;
            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 (!player.IsHuman() || !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 (InRange(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)
                        {
                            QueueNotification(p, "OnRaidFinished", 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.IsKilled() || 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.userID))
            {
                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)
            {
                ClearInventory(priv.inventory);
            }

            if (raid.Options.RequiresCupboardAccess)
            {
                OnCupboardAuthorize(priv, null);
            }

            if (raid.IsOpened && raid.EndWhenCupboardIsDestroyed())
            {
                raid.CancelInvoke(raid.TryToEnd);
                raid.AwardRaiders();
                raid.Undo();
            }

            raid.OnBuildingPrivilegeDestroyed();
        }

        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.IsKilled() || !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(fire.SafelyKill);
            }
        }

        private void OnLootEntity(BasePlayer player, BaseEntity entity)
        {
            RaidableBase.Get(entity.transform.position)?.OnLootEntityInternal(player, entity);
        }

        private void OnLootEntityEnd(BasePlayer player, StorageContainer container)
        {
            if (container?.inventory == null || container.OwnerID.IsSteamId() || player.limitNetworking)
            {
                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.ShortPrefabName == "box.wooden.large" || container.ShortPrefabName == "woodbox_deployed" || container.ShortPrefabName == "coffinstorage"))
            {
                container.Invoke(container.SafelyKill, 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 (player.IsKilled() || 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))
                    {
                        Message(player, "CommandNotAllowed");
                        return true;
                    }
                }
            }

            return null;
        }

        private void OnPlayerDeath(BasePlayer player, HitInfo hitInfo)
        {
            var raid = RaidableBase.Get(player);

            if (raid == null)
            {
                return;
            }

            if (!player.IsHuman())
            {
                if (!RaidableBase.Has(player.userID))
                {
                    return;
                }

                if (config.Settings.Management.UseOwners && hitInfo != null && hitInfo.Initiator is BasePlayer)
                {
                    var attacker = hitInfo.Initiator as BasePlayer;

                    if (attacker.IsHuman() && 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.IsKilled() || !player.IsHuman())
                {
                    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.userID))
                {
                    return;
                }

                if (InRange(player.transform.position, raid.Location, raid.ProtectionRadius))
                {
                    raid.OnEnterRaid(player);
                }
                else RaidableBase.RemovePlayer(player, EjectCode.Awaken, raid.Location, raid.ProtectionRadius, raid.Type);
            });
        }

        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))
                    {
                        Message(player, "CommandNotAllowed");
                        return true;
                    }
                }
            }

            return null;
        }

        private void OnServerInitialized(bool isStartup)
        {
            timer.Repeat(30f, 0, () => RaidableBase.UpdateAllMarkers());

            LoadData();
            SetupMonuments();
            RegisterCommands();
            CheckForWipe();
            Initialize();
            OceanLevel = WaterSystem.OceanLevel;
            timer.Repeat(60f, 0, CheckOceanLevel);
            Puts("Free version initialized.");
        }

        private void OnSunrise()
        {
            Raids.Values.ToList().ForEach(raid => raid.ToggleLights());
        }

        private void OnSunset()
        {
            Raids.Values.ToList().ForEach(raid => raid.ToggleLights());
        }

        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;
            data.Save();
            RaidableBase.Unload(true);
            StopAllCoroutines();
            DestroyAll();
        }

        private void Unload()
        {
            if (IsUnloading)
            {
                return;
            }

            IsUnloading = true;
            SetOnSun(false);
            data.Save();
            RaidableBase.Unload(false);
            StopAllCoroutines();
            DestroyComponents();

            if (Raids.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.MyInstance = this;
            raid.name = Name;

            raid.SetAllowPVP(type, profile.Options.AllowPVP);
            raid.DifficultyMode = mx("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(mx("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(mx("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(mx("LoadedMaintained", null, spawns.Count));
                    raidSpawns[RaidableType.Maintained] = new RaidableSpawns(spawns);
                }
            }
        }

        protected void StopAllCoroutines()
        {
            StopScheduleCoroutine();
            StopMaintainCoroutine();
            StopGridCoroutine();
        }

        protected void SetupGrid()
        {
            if (raidSpawns.Count >= 5)
            {
                StartAutomation();
                return;
            }

            StopGridCoroutine();

            NextTick(() =>
            {
                gridStopwatch.Start();
                gridTime = Time.realtimeSinceStartup;
                gridCoroutine = ServerMgr.Instance.StartCoroutine(GenerateGrid());
            });
        }

        public static void ClearInventory(ItemContainer container)
        {
            Item[] array = container.itemList.ToArray();
            for (int i = 0; i < array.Length; i++)
            {
                array[i].GetHeldEntity().SafelyKill();
                array[i].Remove(0f);
            }
        }

        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.IsKilled() && !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, 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.Exists(x => x.Value.Options.Water.AllowSubmerged) ? 3f : 0f;
            float monumentDist = M_RADIUS * 2f + config.Settings.Management.MonumentDistance;
            bool seabed = Buildings.Profiles.Exists(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 += CELL_SIZE) // Credits to Jake_Rich for creating this for me!
            {
                for (float z = minPos; z < maxPos; z += 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(mx("InitializedGrid", null, gridStopwatch.Elapsed.Seconds, gridStopwatch.Elapsed.Milliseconds, World.Size, spawns.Count));
            if (Seabed.Count > 0) Puts(mx("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;
        }

        private static float GetRadius(TriggerSafeZone triggerSafeZone)
        {
            try
            {
                return triggerSafeZone.triggerCollider.GetRadius(triggerSafeZone.transform.localScale);
            }
            catch
            {
                return 150f;
            }
        }

        private static bool IsSafeZone(Vector3 a)
        {
            return TriggerSafeZone.allSafeZones.Exists(triggerSafeZone => InRange(triggerSafeZone.transform.position, a, GetRadius(triggerSafeZone) + config.Settings.Management.MonumentDistance));
        }

        public static bool IsAreaSafe(Vector3 position, float radius, int layers, bool? isCustomSpawn, out CacheType cacheType, out string message, RaidableType type = RaidableType.None)
        {
            if (IsSafeZone(position))
            {
                message = $"Safe Zone at {position}";
                cacheType = CacheType.Delete;
                return false;
            }

            var colliders = Pool.GetList<Collider>();

            Vis.Colliders(position, radius, colliders, layers, QueryTriggerInteraction.Collide);

            cacheType = CacheType.Generic;
            message = string.Empty;

            foreach (var collider in colliders)
            {
                var colName = collider.ObjectName();
                var colPosition = collider.GetPosition();

                if (colName == "ZoneManager" || colName.Contains("xmas") || colPosition == Vector3.zero)
                {
                    continue;
                }

                if (colName.Contains("SafeZone"))
                {
                    message = $"Safe Zone at {colPosition}";
                    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(colName.Contains) && (e == null || e.name.Contains("/treessource/")))
                {
                    message = $"Blocked by a map prefab {colPosition} {colName}";
                    cacheType = CacheType.Delete;
                    break;
                }

                if (e.IsValid())
                {
                    string entityName = e.ObjectName();

                    if (e.PrefabName.Contains("xmas") || entityName.StartsWith("assets/prefabs/plants")) continue;

                    bool isSteamId = e.OwnerID.IsSteamId();

                    if (e is BasePlayer)
                    {
                        var player = e as BasePlayer;

                        if (!player.IsHuman() || 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 {colPosition}";
                        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 {colPosition}";
                            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 {colPosition}";
                            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 {colPosition}";
                            cacheType = CacheType.Temporary;
                            break;
                        }
                    }
                    else if (e.OwnerID == 0)
                    {
                        if (e is BuildingBlock)
                        {
                            message = $"{e.ShortPrefabName} is too close {colPosition}";
                            cacheType = CacheType.Temporary;
                            break;
                        }
                        else if (e is MiningQuarry)
                        {
                            message = $"{e.ShortPrefabName} is too close {colPosition}";
                            cacheType = CacheType.Delete;
                            break;
                        }
                    }
                    else
                    {
                        message = $"Blocked by {e.ShortPrefabName} {colPosition}";
                        cacheType = CacheType.Temporary;
                        break;
                    }
                }
                else if (collider.gameObject.layer == (int)Layer.World)
                {
                    if (colName.Contains("rock_") || colName.Contains("formation_", CompareOptions.OrdinalIgnoreCase))
                    {
                        float height = GetRockHeight(colPosition);

                        if (height > 2f)
                        {
                            message = $"Rock is too large {colPosition}";
                            cacheType = CacheType.Delete;
                            break;
                        }
                    }
                    else if (!config.Settings.Management.AllowOnRoads && colName.StartsWith("road_"))
                    {
                        message = $"Not allowed on roads {colPosition}";
                        cacheType = CacheType.Delete;
                        break;
                    }
                    else if (colName.StartsWith("ice_sheet"))
                    {
                        message = $"Not allowed on ice sheets {colPosition}";
                        cacheType = CacheType.Delete;
                        break;
                    }
                }
                else if (collider.gameObject.layer == (int)Layer.Water)
                {
                    if (!config.Settings.Management.AllowOnRivers && colName.StartsWith("River Mesh"))
                    {
                        message = $"Not allowed on rivers {colPosition}";
                        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.Exists(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 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;
            }

            string topology;
            return TopologyChecks(vector, M_RADIUS, out topology);
        }

        private bool TopologyChecks(Vector3 vector, float radius, out string topology)
        {
            if (ContainsTopology(TerrainTopology.Enum.Rail | TerrainTopology.Enum.Railside, vector, radius) || HasPointOnRail(vector))
            {
                topology = "Railroad";
                return false;
            }

            if (!config.Settings.Management.AllowOnBuildingTopology && ContainsTopology(TerrainTopology.Enum.Building, vector, radius))
            {
                topology = "Building";
                return false;
            }

            if (!config.Settings.Management.AllowOnRivers && ContainsTopology(TerrainTopology.Enum.River | TerrainTopology.Enum.Riverside, vector, radius))
            {
                topology = "River";
                return false;
            }

            if (!config.Settings.Management.AllowOnRoads && ContainsTopology(TerrainTopology.Enum.Road | TerrainTopology.Enum.Roadside, vector, radius))
            {
                topology = "Road";
                return false;
            }

            topology = "";
            return true;
        }

        private bool HasPointOnRail(Vector3 a)
        {
            if (TerrainMeta.Path?.Rails?.Count > 0)
            {
                foreach (PathList pathList in TerrainMeta.Path.Rails)
                {
                    if (pathList?.Path?.Points?.Exists(b => InRange(a, b, M_RADIUS * 2f)) == true)
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        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.ToString()))
            {
                bool exists = Interface.Oxide.DataFileSystem.ExistsDatafile($"copypaste{Path.DirectorySeparatorChar}{file}");

                if (exists)
                {
                    Instance.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 = extra.Value.ToList();

                    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 mx("Profile Not Enabled", id, baseName);
                }
            }

            if (!validProfile)
            {
                if (!string.IsNullOrEmpty(baseName))
                {
                    if (!FileExists(baseName))
                    {
                        return mx("FileDoesNotExist", id);
                    }
                    else if (!Buildings.Profiles.ContainsKey(baseName))
                    {
                        return mx("BuildingNotConfigured", id);
                    }
                }

                if (!string.IsNullOrEmpty(message))
                {
                    return message;
                }
                else return mx("NoBuildingsConfigured", id);
            }

            return mx("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;

                string topology;
                if (!TopologyChecks(vector, buildRadius, out topology))
                {
                    message = $"Spawn is on blocked topology ({topology})";
                    continue;
                }

                if (options.Setup.ForcedHeight != -1)
                {
                    vector.y = options.Setup.ForcedHeight;
                }
                else vector.y += options.Setup.PasteHeightAdjustment;

                if (typeDistance > 0 && RaidableBase.IsTooClose(vector, typeDistance))
                {
                    spawns.RemoveNear(vector, options.ProtectionRadius, CacheType.Close, type);
                    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 = mx("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)
        {
            var rb = Locations.FirstOrDefault(x => x.BaseName == fileName && !x.hasSpawned);

            if (rb == null || pastedEntities == null || pastedEntities.Count == 0)
            {
                return;
            }

            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.IsKilled());

                var raid = RaidableBase.Get(pastedEntities);

                if (raid == null)
                {
                    return;
                }

                raid.SetEntities(rb.BaseIndex, pastedEntities, bmgs);
                t.Destroy();

                rb.hasSpawned = true;
            });
        }

        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 List<string> _messages = new List<string>();

        private static void PrintDebugMessage(string message)
        {
            if (!string.IsNullOrEmpty(message) && Instance != null)
            {
                Instance._messages.Add(message);

                if (Instance._messages.Count > 10)
                {
                    Instance._messages.RemoveAt(0);
                }

                if (Instance.debugMode)
                {
                    Puts("DEBUG: {0}", message);
                    Instance.LogToFile("debug", message, Instance, true);
                }
            }
        }

        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 = BasePlayer.activePlayerList.Sum(x => x.IsReallyValid() && x.Distance(raid.Location) <= raid.ProtectionRadius * 3f ? 1 : 0);
                    int distance = Mathf.CeilToInt(player.transform.position.Distance(raid.Location));
                    string message = mx("RaidMessage", player.UserIDString, distance, num);
                    string flag = mx(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(player.UserIDString, true), message));

                    foreach (var ri in raid.raiders.Values.Where(x => x.IsAlly && x.player.IsReallyValid()))
                    {
                        player.SendConsoleCommand("ddraw.text", 15f, Color.yellow, ri.player.transform.position, mx("Ally", player.UserIDString).Replace(":", string.Empty));
                    }

                    if (raid.owner.IsReallyValid())
                    {
                        player.SendConsoleCommand("ddraw.text", 15f, Color.yellow, raid.owner.transform.position, mx("Owner", player.UserIDString).Replace(":", string.Empty));
                    }
                }
            }
            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(mx("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(M_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(m("PasteIsBlockedStandAway", p.Id));
                        return;
                    }

                    if (safe && (isAllowed || !IsMonumentPosition(position)))
                    {
                        var spawns = raidSpawns.FirstOrDefault(x => x.Value.Spawns.Exists(y => InRange(y.Location, hit.point, M_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(m("PasteIsBlocked", p.Id));

                    if (!string.IsNullOrEmpty(message))
                    {
                        p.Reply(message);
                    }
                }
                else p.Reply(m("LookElsewhere", p.Id));
            }
            else
            {
                if (profile.Value == null)
                {
                    p.Reply(m("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)))
            {
                Message(player, "No Permission");
                return;
            }

            RaycastHit hit;
            if (!Physics.Raycast(player.eyes.HeadRay(), out hit, 5f, -1, QueryTriggerInteraction.Ignore))
            {
                Message(player, "LookElsewhere");
                return;
            }

            var entity = hit.GetEntity();

            if (entity == null)
            {
                Message(player, "LookElsewhere");
                return;
            }

            _sb.Length = 0;
            _sb.AppendLine(string.Format("Owner ID: {0}", entity.OwnerID));
            _sb.AppendLine(string.Format("RaidEntities: {0}", 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.ContainsKey(entity))
                {
                    _sb.AppendLine("Entity was built by a player after the base spawned.");
                }

                _sb.AppendLine(string.Format("Distance From Raid: {0}m", raid.Location.Distance(entity.transform.position)));
            }

            Player.Message(player, _sb.ToString());
        }

        protected void ShowLadder(IPlayer p, string[] args)
        {
            if (!config.RankedLadder.Enabled || config.RankedLadder.Top < 1)
            {
                return;
            }

            if (data.Players.Count == 0)
            {
                p.Reply(m("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(m(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 = m("Not Enough Online", p.Id, config.Settings.Schedule.PlayerLimit);
            }
            else message = FormatTime(time);

            p.Reply(m("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(mx("Max Manual Events", p.Id, config.Settings.Manual.Max));
                return false;
            }

            if (!IsPasteAvailable && !p.IsAdmin)
            {
                p.Reply(mx("PasteOnCooldown", p.Id));
                return false;
            }

            if (!p.IsAdmin && IsSpawnOnCooldown())
            {
                p.Reply(mx("SpawnOnCooldown", p.Id));
                return false;
            }

            if (!isAllowed && BaseNetworkable.serverEntities.Count > 300000)
            {
                p.Reply(mx("EntityCountMax", p.Id));
                return false;
            }

            return true;
        }

        private void CommandConfig(IPlayer p, string command, string[] args)
        {
            if (!IsValid(args))
            {
                p.Reply(mx("ConfigUseFormat", 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<LootItem>();

            foreach (var def in ItemManager.GetItemDefinitions())
            {
                lootList.Add(new LootItem
                {
                    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 user, string command, string[] args)
        {
            var player = user.Object as BasePlayer;
            bool isAdmin = user.IsServer || player.IsAdmin;
            string arg = args.Length >= 1 ? args[0].ToLower() : string.Empty;

            switch (arg)
            {
                case "version":
                    user.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())
                    {
                        user.Reply(PositionToGrid(player.transform.position));
                    }

                    return;
                case "grid":
                    if (player.IsValid() && (isAdmin || HasPermission(player, drawPermission)))
                    {
                        ShowGrid(player);
                    }

                    return;
                case "savefix":
                    if (user.IsAdmin)
                    {
                        if (SaveRestore.IsSaving)
                        {
                            SaveRestore.IsSaving = false;
                            user.Reply("Server save has been canceled. You must type server.save again.");
                        }
                        else user.Reply("Server is not saving.");
                    }

                    return;
                case "ladder":
                case "lifetime":
                    ShowLadder(user, args);
                    return;
                case "prod":
                    Prodigy(player, args);
                    return;
            }

            if (config.RankedLadder.Enabled)
            {
                user.Reply(m("Wins", user.Id, PlayerInfo.Get(user.Id).Raids, config.Settings.HunterCommand));
            }

            if (Raids.Count == 0 && scheduleEnabled)
            {
                ShowNextScheduledEvent(user);
                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(m("PasteOnCooldown", p.Id));
                    return;
                }

                p.Reply(m("ReloadConfig", p.Id));
                LoadConfig();
                SetOnSun(false);
                maintainEnabled = config.Settings.Maintained.Enabled;
                scheduleEnabled = config.Settings.Schedule.Enabled;

                if (maintainCoroutine != null)
                {
                    StopMaintainCoroutine();
                    p.Reply(m("ReloadMaintainCo", p.Id));
                }

                if (scheduleCoroutine != null)
                {
                    StopScheduleCoroutine();
                    p.Reply(m("ReloadScheduleCo", p.Id));
                }

                p.Reply(m("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 user, bool isAllowed, string[] args)
        {
            if (args.Length == 0)
            {
                return false;
            }

            if (player.IsValid())
            {
                if (args[0].ToLower() == "despawn")
                {
                    if (isAllowed)
                    {
                        bool success = DespawnBase(player);
                        Message(player, success ? "DespawnBaseSuccess" : "DespawnBaseNoneAvailable");
                        if (success) Puts(mx("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;
                        user.Reply(string.Format("Debug mode: {0}", debugMode));
                        user.Reply(string.Format("Scheduled Events Running: {0}", scheduleCoroutine != null));
                        user.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(mx("DespawnedAll", null, player?.displayName ?? user.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 (!target.IsKilled())
                            {
                                var raid = GetNearestBase(target.transform.position);

                                if (raid == null)
                                {
                                    user.Reply(m("TargetTooFar", user.Id));
                                }
                                else
                                {
                                    raid.SetOwner(target);
                                    user.Reply(m("RaidLockedTo", user.Id, target.displayName));
                                }
                            }
                            else user.Reply(m("TargetNotFoundId", user.Id, args[1]));
                        }

                        return true;
                    }
                case "clearowner":
                    {
                        if (player.IsReallyValid() && (isAllowed || user.HasPermission("raidablebases.setowner")))
                        {
                            var raid = GetNearestBase(player.transform.position);

                            if (raid == null)
                            {
                                user.Reply(m("TooFar", user.Id));
                            }
                            else
                            {
                                raid.riOwner = null;
                                raid.owner = null;
                                raid.ownerId = 0;
                                raid.raiders.Clear();
                                raid.UpdateMarker();
                                user.Reply(m("RaidOwnerCleared", user.Id));
                            }
                        }

                        return true;
                    }
            }

            return false;
        }

        private void Initialize()
        {
            Reinitialize();
            BlockZoneManagerZones();
            LoadSpawns();
            SetupGrid();
            CreateDefaultFiles();
            LoadTables();
            LoadProfiles();
            InitializeSkins();
            SetOnSun(true);
        }

        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 = mx("NotifyPlayerMessageFormat", 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[e] = e.transform.position;
            raid.SetupEntity(e, false);

            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.AddEntity(e);
                }
            }
            else if (!config.Settings.Management.DoNotDestroyStructures)
            {
                raid.AddEntity(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(mx("BlockedZones", null, managedZones.Count));
            }
        }

        private void CheckOceanLevel()
        {
            if (OceanLevel != WaterSystem.OceanLevel)
            {
                OceanLevel = WaterSystem.OceanLevel;

                RaidableSpawns rs;
                if (raidSpawns.TryGetValue(RaidableType.Grid, out rs))
                {
                    rs.TryAddRange(CacheType.Submerged);
                }
            }
        }

        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));
        }

        private static List<T> FindEntitiesOfType<T>(Vector3 a, float n, int m = -1) where T : BaseEntity
        {
            int hits = Physics.OverlapSphereNonAlloc(a, n, Vis.colBuffer, m, QueryTriggerInteraction.Collide);
            List<T> entities = new List<T>();
            for (int i = 0; i < hits; i++)
            {
                var entity = Vis.colBuffer[i]?.ToBaseEntity();
                if (entity is T) entities.Add(entity as T);
                Vis.colBuffer[i] = null;
            }

            return entities;
        }

        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 = mx("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() && player.IsHuman())
            {
                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() && player.IsHuman())
                {
                    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() && player.IsHuman())
                {
                    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 || entity is DroppedItemContainer)
            {
                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.ContainsKey(entity))
            {
                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 (attacker.IsKilled())
            {
                return null;
            }

            if (!attacker.IsHuman())
            {
                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.ContainsKey(entity))
            {
                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 (!attacker.IsKilled())
            {
                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 (victim is ScientistNPC && !RaidableBase.Has(victim.userID))
            {
                return null;
            }

            if (weapon is AutoTurret)
            {
                hitInfo.damageTypes.Scale(DamageType.Bullet, UnityEngine.Random.Range(raid.Options.AutoTurret.Min, raid.Options.AutoTurret.Max));

                if (RaidableBase.Has(victim.userID) && (raid.Options.NPC.IgnoreTrapsTurrets || RaidableBase.Has(weapon) && !raid.BuiltList.ContainsKey(weapon)))
                {
                    return false;
                }

                if (weapon.OwnerID.IsSteamId())
                {
                    if (InRange(weapon.transform.position, raid.Location, raid.ProtectionRadius))
                    {
                        return victim.IsHuman() ? raid.AllowPVP : true;
                    }

                    return raid.AllowPVP || !victim.IsHuman();
                }
            }

            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 (!victim.IsHuman() && attacker.IsHuman())
            {
                return HandleNpcVictim(raid, victim, attacker);
            }
            else if (victim.IsHuman() && attacker.IsHuman())
            {
                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 bool HasEventEntity(BaseEntity entity)
        {
            if (entity.IsKilled()) return false;
            if (RaidableBase.Has(entity)) return true;
            if (entity is BasePlayer) return RaidableBase.Has(((BasePlayer)entity).userID);
            return false;
        }

        public void InitializeSkins()
        {
            foreach (var def in ItemManager.GetItemDefinitions())
            {
                ItemModDeployable imd;
                if (def.TryGetComponent(out imd))
                {
                    _definitions[imd.entityPrefab.resourcePath] = def;
                }
            }
        }

        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())
                {
                    Message(player, "InstallCopyPastePlugin");
                }
                else Puts(mx("InstallCopyPastePlugin"));

                return false;
            }

            if (CopyPaste.Version < new VersionNumber(4, 1, 31))
            {
                if (player.IsValid())
                {
                    Message(player, "LoadSupportedCopyPasteVersion");
                }
                else Puts(mx("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.ShortPrefabName == "box.wooden.large" || entity.ShortPrefabName == "woodbox_deployed" || entity.ShortPrefabName == "coffinstorage")
            {
                return true;
            }

            return inherit && config.Settings.Management.Inherit.Exists(entity.ShortPrefabName.Contains);
        }

        private static bool IsModeValid(RaidableMode mode) => mode != RaidableMode.Disabled && mode != RaidableMode.Random;

        [HookMethod("IsPremium")]
        private bool IsPremium() => false;

        public static bool IsUnderground(Vector3 a) => GamePhysics.CheckSphere<TerrainCollisionTrigger>(a, 5f, 262144, QueryTriggerInteraction.Collide);

        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 = mx("Log Stolen", null, p.Name, p.Id, kvp.Value);

                LogToFile("treasurehunters", $"{DateTime.Now} : {message}", this, true);
                Puts(mx("Log Granted", null, p.Name, p.Id, rankLadderPermission, rankLadderGroup));
            }

            Puts(mx("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 (!victim.IsHuman() || !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, M_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)
            {
                DespawnAll(Raids.Values.ToList(), inactiveOnly);
                return;
            }

            if (Interface.Oxide.IsShuttingDown)
            {
                RemoveHeldEntities();
                return;
            }

            DestroyAll();
        }

        private void DestroyAll()
        {
            foreach (var raid in Raids.Values.ToList())
            {
                Puts(mx("Destroyed Raid", null, $"{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)
            {
                ClearInventory(container.inventory);
            }
            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 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()
        {
            Instance.Skins.Clear();

            if (config.Settings.TeleportMarker)
            {
                Subscribe(nameof(OnMapMarkerAdded));
            }

            Subscribe(nameof(OnPlayerSleepEnded));
        }

        private static void SafelyKill(BaseEntity entity) => entity.SafelyKill();

        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 void SetOnSun(bool state)
        {
            if (!config.Settings.Management.Lights)
            {
                return;
            }

            if (state)
            {
                TOD_Sky.Instance.Components.Time.OnSunrise += OnSunrise;
                TOD_Sky.Instance.Components.Time.OnSunset += OnSunset;
            }
            else
            {
                TOD_Sky.Instance.Components.Time.OnSunrise -= OnSunrise;
                TOD_Sky.Instance.Components.Time.OnSunset -= OnSunset;
            }
        }

        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(mx("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();
            config = null;
            Buildings.Default.Clear();
            Buildings.Normal.Clear();
            Buildings.Profiles.Clear();
            Buildings.Weekday.Clear();
            _definitions = 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(timer);
            CheckForWipe();
        }

        private void ConfigAddBase(IPlayer p, string[] args)
        {
            if (args.Length < 2)
            {
                p.Reply(mx("ConfigAddBaseSyntax", p.Id));
                return;
            }

            _sb.Length = 0;
            var values = args.ToList();
            values.RemoveAt(0);
            string value = values[0];

            p.Reply(mx("Adding", p.Id, string.Join(" ", values.ToArray())));

            BaseProfile profile;
            if (!Buildings.Profiles.TryGetValue(value, out profile))
            {
                Buildings.Profiles[value] = profile = new BaseProfile();
                _sb.AppendLine(mx("AddedPrimaryBase", 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(mx("AddedAdditionalBase", p.Id, ab));
                    }
                }
            }

            if (_sb.Length > 0)
            {
                p.Reply(_sb.ToString());
                profile.Options.Enabled = true;
                SaveProfile(value, profile.Options);
                Buildings.Profiles[value] = profile;
            }
            else p.Reply(mx("EntryAlreadyExists", p.Id));
        }

        private void ConfigListBases(IPlayer p)
        {
            _sb.Length = 0;
            _sb.Append(mx("ListingAll", p.Id));
            _sb.AppendLine();

            bool validBase = false;

            foreach (var entry in Buildings.Profiles)
            {
                _sb.AppendLine(mx("PrimaryBase", p.Id));

                if (FileExists(entry.Key))
                {
                    _sb.AppendLine(entry.Key);
                    validBase = true;
                }
                else _sb.Append(entry.Key).Append(mx("IsProfile", p.Id));

                if (entry.Value.Options.AdditionalBases.Count > 0)
                {
                    _sb.AppendLine(mx("AdditionalBase", 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(mx("FileDoesNotExist", p.Id));
                    }
                }
            }

            if (!validBase)
            {
                _sb.AppendLine(mx("NoBuildingsConfigured", p.Id));
            }

            p.Reply(_sb.ToString());
        }

        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(mx("RemoveSyntax", 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(mx("RemovingAllBasesFor", p.Id, string.Join(" ", files)));

            foreach (var profile in profiles)
            {
                foreach (string value in files.Split(' '))
                {
                    foreach (var ab in profile.Value.Options.AdditionalBases.ToList())
                    {
                        if (ab.Key == value || profile.Key == value)
                        {
                            _sb.AppendLine(mx("RemovedAdditionalBase", 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(mx("RemovedPrimaryBase", p.Id, value));
                        Buildings.Profiles.Remove(profile.Key);
                        profile.Value.Options.Enabled = false;
                        num++;
                        SaveProfile(profile.Key, profile.Value.Options);
                    }
                }
            }

            _sb.AppendLine(mx("RemovedEntries", p.Id, num));
            p.Reply(_sb.ToString());
        }

        protected void ConvertFromConfig(List<LootItem> 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 < CELL_SIZE)
                    {
                        options.ProtectionRadius = 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.Exists(profile => profile.Options.AllowPVP);
            bool allowPVE = Buildings.Profiles.Values.Exists(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<LootItem> 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<LootItem> 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 != null && ti.amount < ti.amountMin)
                    {
                        int min = ti.amountMin;

                        ti.amountMin = ti.amount;
                        ti.amount = min;
                    }
                });
            }

            foreach (DayOfWeek day in Enum.GetValues(typeof(DayOfWeek)))
            {
                string file = $"{Name}{Path.DirectorySeparatorChar}Weekday_Loot{Path.DirectorySeparatorChar}{day}";
                List<LootItem> 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 != null && ti.amount < ti.amountMin)
                        {
                            int min = ti.amountMin;

                            ti.amountMin = ti.amount;
                            ti.amount = min;
                        }
                    });

                }
            }

            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 bool ProfilesExists()
        {
            try
            {
                Interface.Oxide.DataFileSystem.GetFiles($"{Name}{Path.DirectorySeparatorChar}Profiles");
            }
            catch
            {
                return false;
            }

            return true;
        }

        private void CreateDefaultFiles()
        {
            if (ProfilesExists())
            {
                return;
            }

            Interface.Oxide.DataFileSystem.GetDatafile($"{Name}{Path.DirectorySeparatorChar}Profiles{Path.DirectorySeparatorChar}_emptyfile");

            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<LootItem> GetTable(string file)
        {
            var lootList = new List<LootItem>();

            try
            {
                lootList = Interface.Oxide.DataFileSystem.ReadObject<List<LootItem>>(file);
            }
            catch (JsonReaderException ex)
            {
                Puts("{0}", ex);
            }

            if (lootList == null)
            {
                return new List<LootItem>();
            }

            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.Exists(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<LootItem> 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

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                {"No Permission", "You do not have permission to use this command."},
                {"Building is blocked!", "<color=#FF0000>Building is blocked near raidable bases!</color>"},
                {"Barricades are blocked!", "<color=#FF0000>Barricades are blocked in raidable bases!</color>"},
                {"Ladders are blocked!", "<color=#FF0000>Ladders are blocked in raidable bases!</color>"},
                {"Cupboards are blocked!", "<color=#FF0000>Tool cupboards are blocked in raidable bases!</color>"},
                {"Ladders Require Building Privilege!", "<color=#FF0000>You need building privilege to place ladders!</color>"},
                {"Profile Not Enabled", "This profile is not enabled: <color=#FF0000>{0}</color>."},
                {"Max Manual Events", "Maximum number of manual events <color=#FF0000>{0}</color> has been reached!"},
                {"Manual Event Failed", "Event failed to start! Unable to obtain a valid position. Please try again."},
                {"Help", "/{0} <tp> - start a manual event, and teleport to the position if TP argument is specified and you are an admin."},
                {"RaidOpenMessage", "<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", "<color=#C0C0C0>No events are open. Next event in <color=#FFFF00>{0}</color></color>"},
                {"Wins", "<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", "Raidable Base {0}m [{1} players]"},
                {"RankedLadder", "<color=#FFFF00>[ Top {0} (This Wipe) ]</color>:"},
                {"RankedTotal", "<color=#FFFF00>[ Top {0} (Lifetime) ]</color>:"},
                {"Ladder Insufficient Players", "<color=#FFFF00>No players are on the ladder yet!</color>"},
                {"Next Automated Raid", "Next automated raid in {0} at {1}"},
                {"Not Enough Online", "Not enough players online ({0} minimum)"},
                {"Raid Base Distance", "<color=#C0C0C0>Raidable Base <color=#FFA500>{0}m</color>"},
                {"Destroyed Raid", "Destroyed a left over raid base at {0}"},
                {"Indestructible", "<color=#FF0000>Treasure chests are indestructible!</color>"},
                {"View Config", "Please view the config if you haven't already."},
                {"Log Stolen", "{0} ({1}) Raids {2}"},
                {"Log Granted", "Granted {0} ({1}) permission {2} for group {3}"},
                {"Log Saved", "Raid Hunters have been logged to: {0}"},
                {"Prefix", "[ <color=#406B35>Raidable Bases</color> ] "},
                {"RestartDetected", "Restart detected. Next event in {0} minutes."},
                {"SkillTreeXP", "You have received <color=#FFFF00>{0} XP</color> for stealing the treasure!"},
                {"EconomicsDeposit", "You have received <color=#FFFF00>${0}</color> for stealing the treasure!"},
                {"ServerRewardPoints", "You have received <color=#FFFF00>{0} RP</color> for stealing the treasure!"},
                {"InvalidItem", "Invalid item shortname: {0}. Use /{1} additem <shortname> <amount> [skin]"},
                {"AddedItem", "Added item: {0} amount: {1}, skin: {2}"},
                {"CustomPositionSet", "Custom event spawn location set to: {0}"},
                {"CustomPositionRemoved", "Custom event spawn location removed."},
                {"OpenedEvents", "Opened {0}/{1} events."},
                {"OnPlayerEntered", "<color=#FF0000>You have entered a raidable PVP base!</color>"},
                {"OnPlayerEnteredPVE", "<color=#FF0000>You have entered a raidable PVE base!</color>"},
                {"OnPlayerEntryRejected", "<color=#FF0000>You cannot enter an event that does not belong to you!</color>"},
                {"OnFirstPlayerEntered", "<color=#FFFF00>{0}</color> is the first to enter the raidable base at <color=#FFFF00>{1}</color>"},
                {"OnChestOpened", "<color=#FFFF00>{0}</color> is the first to see the treasures at <color=#FFFF00>{1}</color>!</color>"},
                {"OnRaidFinished", "The raid at <color=#FFFF00>{0}</color> has been unlocked!"},
                {"CannotBeMounted", "You cannot loot the treasure while mounted!"},
                {"CannotTeleport", "You are not allowed to teleport from this event."},
                {"MustBeAuthorized", "You must have building privilege to access this treasure!"},
                {"OwnerLocked", "This treasure belongs to someone else!"},
                {"CannotFindPosition", "Could not find a random position!"},
                {"PasteOnCooldown", "Paste is on cooldown!"},
                {"SpawnOnCooldown", "Try again, a manual spawn was already requested."},
                {"Thief", "<color=#FFFF00>The base at <color=#FFFF00>{0}</color> has been raided by <color=#FFFF00>{1}</color>!</color>"},
                {"TargetNotFoundId", "<color=#FFFF00>Target {0} not found, or not online.</color>"},
                {"TargetNotFoundNoId", "<color=#FFFF00>No steamid provided.</color>"},
                {"DestroyingBaseAt", "<color=#C0C0C0>Destroying raid base at <color=#FFFF00>{0}</color> in <color=#FFFF00>{1}</color> minutes!</color>"},
                {"PasteIsBlocked", "You cannot start a raid base event there!"},
                {"LookElsewhere", "Unable to find a position; look elsewhere."},
                {"BuildingNotConfigured", "You cannot spawn a base that is not configured."},
                {"NoBuildingsConfigured", "No valid buildings have been configured."},
                {"DespawnBaseSuccess", "<color=#C0C0C0>Despawning the nearest raid base to you!</color>"},
                {"DespawnedAt", "{0} despawned a base manually at {1}"},
                {"DespawnedAll", "{0} despawned all bases manually"},
                {"ModeLevel", "level"},
                {"DespawnBaseNoneAvailable", "<color=#C0C0C0>You must be within 100m of a raid base to despawn it.</color>"},
                {"GridIsLoading", "The grid is loading; please wait until it has finished."},
                {"GridIsLoadingFormatted", "Grid is loading. The process has taken {0} seconds so far with {1} locations added on the grid."},
                {"TooPowerful", "<color=#FF0000>This place is guarded by a powerful spirit. You sheath your wand in fear!</color>"},
                {"TooPowerfulDrop", "<color=#FF0000>This place is guarded by a powerful spirit. You drop your wand in fear!</color>"},
                {"InstallCopyPastePlugin", "CopyPaste is not installed, and is required to use this plugin."},
                {"LoadSupportedCopyPasteVersion", "You must update your version of CopyPaste to 4.1.31 or higher!"},
                {"DoomAndGloom", "<color=#FF0000>You have left a {0} zone and can be attacked for another {1} seconds!</color>"},
                {"MaintainCoroutineFailedToday", "<color=#FF0000>Failed to start maintain coroutine; no difficulties are available today.</color>"},
                {"ScheduleCoroutineFailedToday", "<color=#FF0000>Failed to start scheduled coroutine; no difficulties are available today.</color>"},
                {"NoConfiguredLoot", "Error: No loot found in the config!"},
                {"NoContainersFound", "Error: No usable containers found for {0} @ {1}!"},
                {"NoBoxesFound", "Error: No usable boxes found for {0} @ {1}!"},
                {"NoLootSpawned", "Error: No loot was spawned!"},
                {"LoadedManual", "Loaded {0} manual spawns."},
                {"LoadedMaintained", "Loaded {0} maintained spawns."},
                {"LoadedScheduled", "Loaded {0} scheduled spawns."},
                {"InitializedGrid", "Grid initialization completed in {0} seconds and {1} milliseconds on a {2} size map. {3} locations are on the grid."},
                {"EntityCountMax", "Command disabled due to entity count being greater than 300k"},
                {"NotifyPlayerMessageFormat", "<color=#ADD8E6>{rank}</color>. <color=#C0C0C0>{name}</color> (<color=#FFFF00>{value}</color>)"},
                {"ConfigUseFormat", "Use: rb.config <{0}> [base] [subset]"},
                {"ConfigAddBaseSyntax", "Use: rb.config add nivex1 nivex4 nivex5 nivex6"},
                {"FileDoesNotExist", " > This file does not exist\n"},
                {"IsProfile", " > Profile\n"},
                {"ListingAll", "Listing all primary bases and their subsets:"},
                {"PrimaryBase", "Primary Base: "},
                {"AdditionalBase", "Additional Base: "},
                {"NoValidBuilingsWarning", "No valid buildings are configured with a valid file that exists. Did you configure valid files and reload the plugin?"},
                {"Adding", "Adding: {0}"},
                {"AddedPrimaryBase", "Added Primary Base: {0}"},
                {"AddedAdditionalBase", "Added Additional Base: {0}"},
                {"AddUnauthorizedAccessException", "You cannot save to profiles that have read/write errors! You must fix the file permissions first."},
                {"AddJsonException", "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", "You cannot save to profiles that have errors! You must fix the error first by reading the error message in the server console."},
                {"EntryAlreadyExists", "That entry already exists."},
                {"RemoveSyntax", "Use: rb.config remove nivex1"},
                {"RemovingAllBasesFor", "\nRemoving all bases for: {0}"},
                {"RemovedPrimaryBase", "Removed primary base: {0}"},
                {"RemovedAdditionalBase", "Removed additional base {0} from primary base {1}"},
                {"RemovedEntries", "Removed {0} entries"},
                {"LockedOut", "You are locked out from {0} raids for {1}"},
                {"PVPFlag", "[<color=#FF0000>PVP</color>] "},
                {"PVEFlag", "[<color=#008000>PVE</color>] "},
                {"PVP ZONE", "PVP ZONE"},
                {"PVE ZONE", "PVE ZONE"},
                {"OnPlayerExit", "<color=#FF0000>You have left a raidable PVP base!</color>"},
                {"OnPlayerExitPVE", "<color=#FF0000>You have left a raidable PVE base!</color>"},
                {"PasteIsBlockedStandAway", "You cannot start a raid base event there because you are too close to the spawn. Either move or use noclip."},
                {"ReloadConfig", "Reloading config..."},
                {"ReloadMaintainCo", "Stopped maintain coroutine."},
                {"ReloadScheduleCo", "Stopped schedule coroutine."},
                {"ReloadInit", "Initializing..."},
                {"YourCorpse", "Your Corpse"},
                {"NotAllowed", "<color=#FF0000>That action is not allowed in this zone.</color>"},
                {"BlockedZones", "Blocked spawn points in {0} zones."},
                {"UIFormat", "{0} C:{1} [{2}m]"},
                {"UIFormatContainers", "{0} C:{1}"},
                {"UIFormatMinutes", "{0} [{1}m]"},
                {"HoggingFinishYourRaid", "<color=#FF0000>You must finish your last raid at {0} before joining another.</color>"},
                {"TimeFormat", "{0:D2}h {1:D2}m {2:D2}s"},
                {"TargetTooFar", "Your target is not close enough to a raid."},
                {"TooFar", "You are not close enough to a raid."},
                {"RaidLockedTo", "Raid has been locked to: {0}"},
                {"RaidOwnerCleared", "Raid owner has been cleared."},
                {"TooCloseToABuilding", "Too close to another building"},
                {"CommandNotAllowed", "You are not allowed to use this command right now."},
                {"Normal", "normal"},
                {"MapMarkerOrderWithMode", "{0}{1} {2}{3}"},
                {"MapMarkerOrderWithoutMode", "{0}{1}{2}"},
                {"InitializedGridSea", "{0} locations are on the seabed grid."},
                {"Ally", "Ally"},
                {"Owner", "Owner:"},
                {"Owner:", "OWNER: <color={0}>{1}</color>  "},
                {"Active", "Active"},
                {"Inactive", "Inactive"},
                {"InactiveTimeLeft", " [Inactive in {0} mins]"},
                {"Status:", "YOUR STATUS: <color={0}>{1}</color>{2}"},
                {"Claimed", "(Claimed)"},
                { "Elevator Health", "Elevator Health"},
                {"Elevator Green Card", "Elevator access requires a green access card!"},
                {"Elevator Blue Card", "Elevator access requires a blue access card!"},
                {"Elevator Red Card", "Elevator access requires a red access card!"},
                {"Elevator Special Card", "Elevator access requires a special access card!"},
                {"Elevator Privileges", "Elevator access requires building privileges!"},
                {"No Reward: Flying", "You cannot earn a reward while flying."},
                {"No Reward: Vanished", "You cannot earn a reward while vanished."},
                {"No Reward: Inactive", "You cannot earn a reward while inactive."},
                {"No Reward: Admin", "Administrators cannot earn rewards."},
                {"No Reward: Not Owner", "You must be the owner of the raid to be eligible."},
                {"No Reward: Not Ally", "You must be the owner or an ally of the raid to be eligible."},
                {"No Reward: Not A Participant", "You must be a participant of the raid to be eligible."},
            }, this, "en");
        }

        public static void Message(BasePlayer player, string key, params object[] args)
        {
            if (player.IsReallyValid())
            {
                QueueNotification(player, key, args);
            }
        }

        public static string m(string key, string id = null, params object[] args)
        {
            _sb2.Length = 0;

            if (config.EventMessages.Prefix)
            {
                _sb2.Append(Instance.lang.GetMessage("Prefix", Instance, id));
            }

            string message = Instance.lang.GetMessage(key, Instance, id);

            _sb2.Append(id == null || id == "server_console" ? rf(message) : message);

            return args.Length > 0 ? string.Format(_sb2.ToString(), args) : _sb2.ToString();
        }

        public static string mx(string key, string id = null, params object[] args)
        {
            _sb2.Length = 0;

            string message = Instance.lang.GetMessage(key, Instance, id);

            _sb2.Append(id == null || id == "server_console" ? rf(message) : message);

            return args.Length > 0 ? string.Format(_sb2.ToString(), args) : _sb2.ToString();
        }

        public class Notification
        {
            public BasePlayer player;
            public string messageEx;
        }

        private Dictionary<ulong, List<Notification>> _notifications = new Dictionary<ulong, List<Notification>>();

        private void CheckNotifications()
        {
            if (_notifications.Count > 0)
            {
                foreach (var entry in _notifications.ToList())
                {
                    var notification = entry.Value.ElementAt(0);

                    SendNotification(notification);

                    entry.Value.Remove(notification);

                    if (entry.Value.Count == 0)
                    {
                        _notifications.Remove(entry.Key);
                    }
                }
            }
        }

        private static void QueueNotification(BasePlayer player, string key, params object[] args)
        {
            if (!player.IsReallyConnected())
            {
                return;
            }

            string message = m(key, player.UserIDString, args);

            if (string.IsNullOrEmpty(message))
            {
                return;
            }

            if (config.EventMessages.Message)
            {
                Instance.Player.Message(player, message, config.Settings.ChatID);
            }

            if (config.GUIAnnouncement.Enabled || config.UI.AA.Enabled || config.EventMessages.NotifyType != -1)
            {
                string messageEx = mx(key, player.UserIDString, args);

                List<Notification> notifications;
                if (!Instance._notifications.TryGetValue(player.userID, out notifications))
                {
                    Instance._notifications[player.userID] = notifications = new List<Notification>();
                }

                notifications.Add(new Notification
                {
                    player = player,
                    messageEx = messageEx
                });
            }
        }

        private static void SendNotification(Notification notification)
        {
            if (!notification.player.IsReallyConnected())
            {
                return;
            }

            if (config.GUIAnnouncement.Enabled && Instance.GUIAnnouncements.CanCall())
            {
                Instance.GUIAnnouncements?.Call("CreateAnnouncement", notification.messageEx, config.GUIAnnouncement.TintColor, config.GUIAnnouncement.TextColor, notification.player);
            }

            if (config.UI.AA.Enabled && Instance.AdvancedAlerts.CanCall())
            {
                Instance.AdvancedAlerts?.Call("SpawnAlert", notification.player, "hook", notification.messageEx, config.UI.AA.AnchorMin, config.UI.AA.AnchorMax, config.UI.AA.Time);
            }

            if (config.EventMessages.NotifyType != -1 && Instance.Notify.CanCall())
            {
                Instance.Notify?.Call("SendNotify", notification.player, config.EventMessages.NotifyType, notification.messageEx);
            }
        }

        public static string rf(string source) => source.Contains(">") ? Regex.Replace(source, "<.*?>", string.Empty) : source;

        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<LootItem> DefaultLoot
        {
            get
            {
                return new List<LootItem>
                {
                    new LootItem { shortname = "ammo.pistol", amount = 40, skin = 0, amountMin = 40 },
                    new LootItem { shortname = "ammo.pistol.fire", amount = 40, skin = 0, amountMin = 40 },
                    new LootItem { shortname = "ammo.pistol.hv", amount = 40, skin = 0, amountMin = 40 },
                    new LootItem { shortname = "ammo.rifle", amount = 60, skin = 0, amountMin = 60 },
                    new LootItem { shortname = "ammo.rifle.explosive", amount = 60, skin = 0, amountMin = 60 },
                    new LootItem { shortname = "ammo.rifle.hv", amount = 60, skin = 0, amountMin = 60 },
                    new LootItem { shortname = "ammo.rifle.incendiary", amount = 60, skin = 0, amountMin = 60 },
                    new LootItem { shortname = "ammo.shotgun", amount = 24, skin = 0, amountMin = 24 },
                    new LootItem { shortname = "ammo.shotgun.slug", amount = 40, skin = 0, amountMin = 40 },
                    new LootItem { shortname = "surveycharge", amount = 20, skin = 0, amountMin = 20 },
                    new LootItem { shortname = "bucket.helmet", amount = 1, skin = 0, amountMin = 1 },
                    new LootItem { shortname = "cctv.camera", amount = 1, skin = 0, amountMin = 1 },
                    new LootItem { shortname = "coffeecan.helmet", amount = 1, skin = 0, amountMin = 1 },
                    new LootItem { shortname = "explosive.timed", amount = 1, skin = 0, amountMin = 1 },
                    new LootItem { shortname = "metal.facemask", amount = 1, skin = 0, amountMin = 1 },
                    new LootItem { shortname = "metal.plate.torso", amount = 1, skin = 0, amountMin = 1 },
                    new LootItem { shortname = "mining.quarry", amount = 1, skin = 0, amountMin = 1 },
                    new LootItem { shortname = "pistol.m92", amount = 1, skin = 0, amountMin = 1 },
                    new LootItem { shortname = "rifle.ak", amount = 1, skin = 0, amountMin = 1 },
                    new LootItem { shortname = "rifle.bolt", amount = 1, skin = 0, amountMin = 1 },
                    new LootItem { shortname = "rifle.lr300", amount = 1, skin = 0, amountMin = 1 },
                    new LootItem { shortname = "shotgun.pump", amount = 1, skin = 0, amountMin = 1 },
                    new LootItem { shortname = "shotgun.spas12", amount = 1, skin = 0, amountMin = 1 },
                    new LootItem { shortname = "smg.2", amount = 1, skin = 0, amountMin = 1 },
                    new LootItem { shortname = "smg.mp5", amount = 1, skin = 0, amountMin = 1 },
                    new LootItem { shortname = "smg.thompson", amount = 1, skin = 0, amountMin = 1 },
                    new LootItem { shortname = "supply.signal", amount = 1, skin = 0, amountMin = 1 },
                    new LootItem { shortname = "targeting.computer", amount = 1, skin = 0, amountMin = 1 },
                    new LootItem { shortname = "metal.refined", amount = 150, skin = 0, amountMin = 150 },
                    new LootItem { shortname = "stones", amount = 15000, skin = 0, amountMin = 7500 },
                    new LootItem { shortname = "sulfur", amount = 7500, skin = 0, amountMin = 2500 },
                    new LootItem { shortname = "metal.fragments", amount = 7500, skin = 0, amountMin = 2500 },
                    new LootItem { shortname = "charcoal", amount = 5000, skin = 0, amountMin = 1000 },
                    new LootItem { shortname = "gunpowder", amount = 3500, skin = 0, amountMin = 1000 },
                    new LootItem { 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 = "All Other Mounts")]
            public bool Other { get; set; }

            [JsonProperty(PropertyName = "Boats")]
            public bool Boats { get; set; }

            [JsonProperty(PropertyName = "Campers")]
            public bool Campers { get; set; } = true;

            [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 = "HotAirBalloon")]
            public bool HotAirBalloon { get; set; } = true;

            [JsonProperty(PropertyName = "Jetpacks")]
            public bool Jetpacks { 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; }

            [JsonProperty(PropertyName = "Snowmobiles")]
            public bool Snowmobile { 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 Npcs To Target Other Npcs")]
            public bool TargetNpcs { 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 = "Block Rewards During Server Restart")]
            public bool Restart { 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 = "Eject Scavengers When Raid Is Completed")]
            public bool EjectScavengers { get; set; }

            [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 Players To Raid Base After Entering Zone")]
            public bool LockToRaidOnEnter { get; set; }

            [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 Looting Resets When Damaged")]
            public bool DespawnMinutesReset { get; set; }

            [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 = "Only Award First Attacker and Allies")]
            public bool OnlyAwardAllies { get; set; }

            [JsonProperty(PropertyName = "Only Award Owner Of Raid")]
            public bool OnlyAwardOwner { get; set; }

            [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 = "Show Owners Name On Map Marker")]
            public bool ShowOwnersName { 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 EventMessageRewardSettings
        {
            [JsonProperty(PropertyName = "Flying")]
            public bool Flying { get; set; }

            [JsonProperty(PropertyName = "Vanished")]
            public bool Vanished { get; set; }

            [JsonProperty(PropertyName = "Inactive")]
            public bool Inactive { get; set; } = true;

            [JsonProperty(PropertyName = "Not An Ally")]
            public bool NotAlly { get; set; } = true;

            [JsonProperty(PropertyName = "Not The Owner")]
            public bool NotOwner { get; set; } = true;

            [JsonProperty(PropertyName = "Not A Participant")]
            public bool NotParticipant { get; set; } = true;

            [JsonProperty(PropertyName = "Remove Admins From Raiders List")]
            public bool RemoveAdmin { get; set; }
        }

        public class EventMessageSettings
        {
            [JsonProperty(PropertyName = "Ineligible For Rewards")]
            public EventMessageRewardSettings Rewards { get; set; } = new EventMessageRewardSettings();

            [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 = "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<LootItem> Loot { get; set; } = new List<LootItem>();

            [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 Players To Deploy Barricades")]
            public bool Barricades { 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 AlwaysSpawnBaseLoot { 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;

            [JsonProperty(PropertyName = "Kill Sleeping Bags At Spawns Database Locations")]
            public bool KillSleepingBags { get; set; } = true;
        }

        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;

            [JsonProperty(PropertyName = "Kill Sleeping Bags At Spawns Database Locations")]
            public bool KillSleepingBags { get; set; } = true;
        }

        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; }

            [JsonProperty(PropertyName = "SkillTree XP")]
            public double XP { 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 LootItem : IEquatable<LootItem>
        {
            [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 LootItem Clone()
            {
                var ti = MemberwiseClone() as LootItem;

                ti.isBlueprint = isBlueprint;
                ti.modified = modified;

                return ti;
            }

            public bool Equals(LootItem 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<LootItem> DOWL_Monday { get; set; } = new List<LootItem>();

            [JsonProperty(PropertyName = "Day Of Week Loot Tuesday", NullValueHandling = NullValueHandling.Ignore)]
            public List<LootItem> DOWL_Tuesday { get; set; } = new List<LootItem>();

            [JsonProperty(PropertyName = "Day Of Week Loot Wednesday", NullValueHandling = NullValueHandling.Ignore)]
            public List<LootItem> DOWL_Wednesday { get; set; } = new List<LootItem>();

            [JsonProperty(PropertyName = "Day Of Week Loot Thursday", NullValueHandling = NullValueHandling.Ignore)]
            public List<LootItem> DOWL_Thursday { get; set; } = new List<LootItem>();

            [JsonProperty(PropertyName = "Day Of Week Loot Friday", NullValueHandling = NullValueHandling.Ignore)]
            public List<LootItem> DOWL_Friday { get; set; } = new List<LootItem>();

            [JsonProperty(PropertyName = "Day Of Week Loot Saturday", NullValueHandling = NullValueHandling.Ignore)]
            public List<LootItem> DOWL_Saturday { get; set; } = new List<LootItem>();

            [JsonProperty(PropertyName = "Day Of Week Loot Sunday", NullValueHandling = NullValueHandling.Ignore)]
            public List<LootItem> DOWL_Sunday { get; set; } = new List<LootItem>();

            [JsonProperty(PropertyName = "Loot (Easy Difficulty)", NullValueHandling = NullValueHandling.Ignore)]
            public List<LootItem> LootEasy { get; set; } = new List<LootItem>();

            [JsonProperty(PropertyName = "Loot (Medium Difficulty)", NullValueHandling = NullValueHandling.Ignore)]
            public List<LootItem> LootMedium { get; set; } = new List<LootItem>();

            [JsonProperty(PropertyName = "Loot (Hard Difficulty)", NullValueHandling = NullValueHandling.Ignore)]
            public List<LootItem> LootHard { get; set; } = new List<LootItem>();

            [JsonProperty(PropertyName = "Loot (Expert Difficulty)", NullValueHandling = NullValueHandling.Ignore)]
            public List<LootItem> LootExpert { get; set; } = new List<LootItem>();

            [JsonProperty(PropertyName = "Loot (Nightmare Difficulty)", NullValueHandling = NullValueHandling.Ignore)]
            public List<LootItem> LootNightmare { get; set; } = new List<LootItem>();

            [JsonProperty(PropertyName = "Loot", NullValueHandling = NullValueHandling.Ignore)]
            public List<LootItem> Loot { get; set; } = new List<LootItem>();

            [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 UIAdvancedAlertSettings
        {
            [JsonProperty(PropertyName = "Enabled")]
            public bool Enabled { get; set; } = true;

            [JsonProperty(PropertyName = "Anchor Min")]
            public string AnchorMin { get; set; } = "0.35 0.85";

            [JsonProperty(PropertyName = "Anchor Max")]
            public string AnchorMax { get; set; } = "0.65 0.95";

            [JsonProperty(PropertyName = "Time Shown")]
            public float Time { get; set; } = 5f;
        }

        public class UISettings
        {
            [JsonProperty(PropertyName = "Advanced Alerts UI")]
            public UIAdvancedAlertSettings AA { get; set; } = new UIAdvancedAlertSettings();

            [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<LootItem> TreasureLoot
        {
            get
            {
                List<LootItem> lootList;

                if (config.Treasure.UseDOWL && Buildings.Weekday.TryGetValue(DateTime.Now.DayOfWeek, out lootList) && lootList.Count > 0)
                {
                    return lootList.ToList();
                }

                return Buildings.Default.ToList();
            }
        }

        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 ? mx("PVP ZONE", player.UserIDString) : mx("PVE ZONE", player.UserIDString);
                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.IsKilled() || raid.IsProtectedWeapon(x))
                    {
                        continue;
                    }

                    lootAmount += x.inventory.itemList.Count;
                }

                if (config.UI.Containers && config.UI.Time && !string.IsNullOrEmpty(despawnText))
                {
                    text = mx("UIFormat", null, zone, lootAmount, despawnText);
                }
                else if (config.UI.Containers)
                {
                    text = mx("UIFormatContainers", null, zone, lootAmount);
                }
                else if (config.UI.Time && !string.IsNullOrEmpty(despawnText))
                {
                    text = mx("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 x in raid.raiders) UpdateStatusUI(x.Value.player);
            }

            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;
                }
                
                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 = Instance.timer.Once(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();
            }

            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;
            }
        }
        #endregion UI        
    }
}

namespace Oxide.Plugins.RaidableBasesExtensionMethods
{
    public static class ExtensionMethods
    {
        internal static Core.Libraries.Permission p;
        public static int Average(this IList<int> a) { if (a.Count == 0) { return 0; } int b = 0; for (int i = 0; i < a.Count; i++) { b += a[i]; } return b / a.Count; }
        public static T ElementAt<T>(this IEnumerable<T> a, int b) { using (var c = a.GetEnumerator()) { while (c.MoveNext()) { if (b == 0) { return c.Current; } b--; } } return default(T); }
        public static bool Exists<T>(this HashSet<T> a) where T : BaseEntity { foreach (var b in a) { if (!b.IsKilled()) { return true; } } return false; }
        public static bool Exists<T>(this IEnumerable<T> a, Func<T, bool> b = null) { using (var c = a.GetEnumerator()) { while (c.MoveNext()) { if (b == null || b(c.Current)) { return true; } } } return false; }
        public static T FirstOrDefault<T>(this IEnumerable<T> a, Func<T, bool> b = null) { using (var c = a.GetEnumerator()) { while (c.MoveNext()) { if (b == null || b(c.Current)) { return c.Current; } } } return default(T); }
        public static int RemoveAll<TKey, TValue>(this IDictionary<TKey, TValue> c, Func<TKey, TValue, bool> d) { int a = 0; foreach (var b in c.ToList()) { if (d(b.Key, b.Value)) { c.Remove(b.Key); a++; } } return a; }
        public static IEnumerable<V> Select<T, V>(this IEnumerable<T> a, Func<T, V> b) { var c = new List<V>(); using (var d = a.GetEnumerator()) { while (d.MoveNext()) { c.Add(b(d.Current)); } } return c; }
        public static string[] Skip(this string[] a, int b) { if (a.Length == 0) { return Array.Empty<string>(); } string[] c = new string[a.Length - b]; int n = 0; for (int i = 0; i < a.Length; i++) { if (i < b) continue; c[n] = a[i]; n++; } return c; }
        public static List<T> Take<T>(this IList<T> a, int b) { var c = new List<T>(); for (int i = 0; i < a.Count; i++) { if (c.Count == b) { break; } c.Add(a[i]); } return c; }
        public static Dictionary<T, V> ToDictionary<S, T, V>(this IEnumerable<S> a, Func<S, T> b, Func<S, V> c) { var d = new Dictionary<T, V>(); using (var e = a.GetEnumerator()) { while (e.MoveNext()) { d[b(e.Current)] = c(e.Current); } } return d; }
        public static List<T> ToList<T>(this IEnumerable<T> a) { var b = new List<T>(); if (a == null) { return b; } using (var c = a.GetEnumerator()) { while (c.MoveNext()) { b.Add(c.Current); } } return b; }
        public static List<T> Where<T>(this IEnumerable<T> a, Func<T, bool> b) { var c = new List<T>(); using (var d = a.GetEnumerator()) { while (d.MoveNext()) { if (b(d.Current)) { c.Add(d.Current); } } } return c; }
        public static List<T> OfType<T>(this IEnumerable<BaseNetworkable> a) where T : BaseEntity { var b = new List<T>(); using (var c = a.GetEnumerator()) { while (c.MoveNext()) { if (c.Current is T) { b.Add(c.Current as T); } } } return b; }
        public static int Sum<T>(this IList<T> a, Func<T, int> b) { int c = 0; for (int i = 0; i < a.Count; i++) { var d = b(a[i]); if (!float.IsNaN(d)) { c += d; } } return c; }
        public static bool HasPermission(this string a, string b) { if (p == null) { p = Interface.Oxide.GetLibrary<Core.Libraries.Permission>(null); } return !string.IsNullOrEmpty(a) && p.UserHasPermission(a, b); }
        public static bool HasPermission(this BasePlayer a, string b) { return a.UserIDString.HasPermission(b); }
        public static bool HasPermission(this ulong a, string b) { return a.ToString().HasPermission(b); }
        public static bool IsReallyConnected(this BasePlayer a) { return a.IsReallyValid() && a.net.connection != null; }
        public static bool IsKilled(this BaseNetworkable a) { return (object)a == null || a.IsDestroyed; }
        public static bool IsNull<T>(this T a) where T : class { return a == null; }
        public static bool IsNull(this BasePlayer a) { return (object)a == null; }
        public static bool IsReallyValid(this BaseNetworkable a) { return !((object)a == null || a.IsDestroyed || (object)a.net == null); }
        public static void SafelyKill(this BaseNetworkable a) { if (a.IsKilled()) { return; } a.Kill(BaseNetworkable.DestroyMode.None); }
        public static bool CanCall(this Plugin o) { return (object)o != null && o.IsLoaded; }
        public static bool IsHuman(this BasePlayer a) { return !(a.IsNpc || !a.userID.IsSteamId()); }
        public static bool IsCheating(this BasePlayer a) { return a._limitedNetworking || a.IsFlying || a.UsedAdminCheat(30f) || a.IsGod() || a.metabolism?.calories?.min == 500; }
        public static string ObjectName(this Collider collider) { try { return collider.gameObject?.name ?? string.Empty; } catch { return string.Empty; } }
        public static Vector3 GetPosition(this Collider collider) { try { return collider.transform?.position ?? Vector3.zero; } catch { return Vector3.zero; } }
        public static string ObjectName(this BaseEntity entity) { try { return entity.name ?? string.Empty; } catch { return string.Empty; } }
        public static T GetRandom<T>(this HashSet<T> h) { if (h == null || h.Count == 0) { return default(T); } return h.ElementAt(UnityEngine.Random.Range(0, h.Count)); }
        public static int InventorySlots(this StorageContainer a) { if (a.IsKilled() || a.inventory == null) return 0; return a.inventorySlots; }
        public static float Distance(this Vector3 a, Vector3 b) => (a - b).magnitude;
    }
}