﻿using System;
using System.Collections.Generic;
using UnityEngine;
using System.Globalization;
using Rust;
using Facepunch;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using Rust.Ai.Gen2;
using System.Text;

namespace Oxide.Plugins
{
    [Info("Tank Commander", "k1lly0u", "2.0.0")]
    [Description("Drive tanks, shoot stuff")]
    class TankCommander : RustPlugin
    {
        #region Fields
        
        private static TankCommander _instance;
        
        [PluginReference] private Plugin Friends, Clans, Godmode;

        private const string ApcPrefab = "assets/prefabs/npc/m2bradley/bradleyapc.prefab";
        
        private const string UIHealth = "tcui.health";
        private const string UIAmmoMachineGun = "tcui.ammo.mg";
        private const string UIAmmoRocket = "tcui.ammo.rocket";

        private const string PermissionAdmin = "tankcommander.admin";
        private const string PermissionUse = "tankcommander.use";

        #endregion

        #region Oxide Hooks
        
        private void Loaded()
        {
            _instance = this;
            
            permission.RegisterPermission(PermissionAdmin, this);
            permission.RegisterPermission(PermissionUse, this);
            
            ControlScheme.Initialize();
        }

        protected override void LoadDefaultMessages() => lang.RegisterMessages(_messages, this);

        private void OnServerInitialized()
        {
            ValidateAmmoItem(_configData.Weapons.Cannon, "Cannon");
            ValidateAmmoItem(_configData.Weapons.Coax, "Coax");
            ValidateAmmoItem(_configData.Weapons.Mg, "Machine Gun");
            return;

            void ValidateAmmoItem(ConfigData.WeaponOptions.WeaponSystem system, string name)
            {
                if (system.RequireAmmo)
                {
                    ItemDefinition ammoDefinition = ItemManager.FindItemDefinition(system.Type);
                    if (!ammoDefinition)
                        Debug.LogWarning($"[TankCommander] Invalid weapon ammo item shortname in config ({name}): {system.Type}");
                    else
                    {
                        system.AmmoItemID = ammoDefinition.itemid;
                        system.AmmoItemDisplayName = ammoDefinition.displayName.english;
                    }
                }
            }
        }

        private void OnEntityTakeDamage(BradleyAPC bradleyApc, HitInfo info)
        {
            if (!bradleyApc)
                return;
            
            ApcController controller = bradleyApc.GetComponent<ApcController>();
            if (controller)
                controller.HandleDamageReceiving(info);
        }
        
        private object OnEntityTakeDamage(BasePlayer player, HitInfo info)
        {
            if (!player)
                return null;
            
            if (IsOnboardAPC(player))
                return true;

            if (info.Initiator is not BradleyAPC bradleyApc) 
                return null;
            
            ApcController controller = bradleyApc.GetComponent<ApcController>();
            if (!controller) 
                return null;
                
            if (!controller.HasCommander)
                return true;

            return null;
        }
        
        private object OnEntityTakeDamage(BaseMountable baseMountable, HitInfo info) => 
            baseMountable && baseMountable.GetComponentInParent<ApcController>() ? (object)true : null;

        private void OnPlayerInput(BasePlayer player, InputState input)
        {
            if (!player || IsOnboardAPC(player))
                return;

            if (_configData.Inventory.Enabled && ControlScheme.WasJustPressed(input, ControlScheme.Enum.Inventory))
            {
                if (!HasPermission(player, PermissionUse))
                    return;
                
                if (!Physics.SphereCast(player.eyes.position, 0.5f, Quaternion.Euler(player.serverInput.current.aimAngles) * Vector3.forward, out RaycastHit hit, 3f))
                    return;

                BaseEntity entity = hit.GetEntity();
                if (!entity)
                    return;

                ApcController controller = entity.GetComponent<ApcController>();
                if (controller && !controller.HasCommander)
                    OpenTankInventory(player, controller);

                return;
            }

            if (ControlScheme.WasJustPressed(input, ControlScheme.Enum.EnterExit))
            {
                if (!HasPermission(player, PermissionUse))
                    return;
                
                if (!Physics.SphereCast(player.eyes.position, 0.5f, Quaternion.Euler(player.serverInput.current.aimAngles) * Vector3.forward, out RaycastHit hit, 3f))
                    return;

                BaseEntity entity = hit.GetEntity();
                if (!entity)
                    return;

                ApcController controller = entity.GetComponent<ApcController>();
                if (!controller) 
                    return;
                
                if (!controller.HasCommander)
                {
                    controller.MountPlayer(player);
                }
                else
                {
                    if (_configData.Passengers.Enabled && controller.CanMountPlayer())
                    {
                        BasePlayer commander = controller.Commander;

                        if (!_configData.Passengers.UseFriends && !_configData.Passengers.UseClans)
                        {
                            controller.MountPlayer(player);
                            return;
                        }

                        if (_configData.Passengers.UseFriends && AreFriends(commander.userID, player.userID))
                        {
                            controller.MountPlayer(player);
                            return;
                        }

                        if (_configData.Passengers.UseClans && IsClanmate(commander.userID, player.userID))
                        {
                            controller.MountPlayer(player);
                            return;
                        }

                        player.ChatMessage(TranslatedMessage("not_friend", player.UserIDString));
                    }
                    else player.ChatMessage(TranslatedMessage("in_use", player.UserIDString));
                }
            }
        }

        private object CanDismountEntity(BasePlayer player, BaseMountable baseMountable)
        {
            if (!baseMountable)
                return null;
            
            ApcController controller = baseMountable.GetComponentInParent<ApcController>();
            if (!controller) 
                return null;
            
            controller.DismountPlayer(player);
            return false;
        }

        private void OnPlayerDisconnected(BasePlayer player)
        {
            if (IsOnboardAPC(player, out ApcController controller))
                controller.DismountPlayer(player);
        }

        private object OnRunPlayerMetabolism(PlayerMetabolism metabolism, BasePlayer player)
        {
            if (!player || !IsOnboardAPC(player))
                return null;

            if (Godmode && (bool)Godmode.Call("IsGod", player.UserIDString))
                return null;

            return true;
        }

        private void Unload()
        {
            foreach (ApcController controller in ApcController.All)
                UnityEngine.Object.Destroy(controller);

            ApcController.All.Clear();

            _configData = null;
            _instance = null;
        }
        
        #endregion

        #region Functions
        
        private static bool IsOnboardAPC(BasePlayer player)
        {
            if (!player)
                return false;

            BaseMountable baseMountable = player.GetMounted();
            if (!baseMountable)
                return false;
            
            foreach (ApcController apcController in ApcController.All)
            {
                if (apcController.IsMountPoint(baseMountable))
                    return true;
            }

            return false;
        }

        private static bool IsOnboardAPC(BasePlayer player, out ApcController controller)
        {
            controller = null;
            
            if (!player)
                return false;

            BaseMountable baseMountable = player.GetMounted();
            if (!baseMountable)
                return false;

            foreach (ApcController apcController in ApcController.All)
            {
                if (apcController.IsMountPoint(baseMountable))
                {
                    controller = apcController;
                    return true;
                }
            }
            
            return false;
        }

        private static T ParseType<T>(string type) => (T)Enum.Parse(typeof(T), type, true);

        private bool HasPermission(BasePlayer player, string perm) => permission.UserHasPermission(player.UserIDString, perm) || permission.UserHasPermission(player.UserIDString, PermissionAdmin);
        
        private static void OpenTankInventory(BasePlayer player, ApcController controller)
        {
            player.inventory.loot.Clear();
            player.inventory.loot.entitySource = controller.Entity;
            player.inventory.loot.itemSource = null;
            player.inventory.loot.AddContainer(controller.Inventory);
            player.inventory.loot.SendImmediate();
            player.ClientRPCPlayer(null, player, "RPC_OpenLootPanel", "generic");
            player.SendNetworkUpdate();
        }
        
        #endregion

        #region Component
        
        private class ApcController : MonoBehaviour
        {
            private Rigidbody _rb;
            private BaseMountable[] _mountPoints;

            private WheelCollider[] _leftWheels;
            private WheelCollider[] _rightWheels;

            private float _accelTimeTaken;

            private float _nextFireCannon;
            private float _nextFireCoax;
            private float _nextFireMg;

            private bool _needsHealthUpdate = false;
            
            private bool _isDying = false;

            private Vector3 _localTurretRotation;
            private Vector3 _mouseInput;
            
            private Vector3 _aimVector = Vector3.forward;
            private Vector3 _aimVectorTop = Vector3.forward;

            public static readonly List<ApcController> All = new List<ApcController>();
            
            private static readonly StringBuilder ControlInfo = new StringBuilder();

            private const float DDrawUpdateTime = 0.02f;

            public bool HasCommander => _mountPoints != null && _mountPoints[0].GetMounted();

            public BradleyAPC Entity { get; private set; }
            
            public ItemContainer Inventory { get; private set; }

            public BasePlayer Commander { get; private set; }
            
            
            private void Awake()
            {
                Entity = GetComponent<BradleyAPC>();
                
                Entity.enabled = false;

                Entity.CancelInvoke(Entity.UpdateTargetList);
                Entity.CancelInvoke(Entity.UpdateTargetVisibilities);

                _localTurretRotation = Entity.mainTurret.localEulerAngles;
                
                _rb = Entity.myRigidBody;
                _leftWheels = Entity.leftWheels;
                _rightWheels = Entity.rightWheels;

                if (_configData.Inventory.Enabled)
                {
                    Inventory = new ItemContainer();
                    Inventory.ServerInitialize(null, _configData.Inventory.Size);
                    
                    if (!Inventory.uid.IsValid)
                        Inventory.GiveUID();
                }
                
                CreateMountPoints();
                
                All.Add(this);
            }

            private void OnDestroy()
            {
                Entity.CancelInvoke(DrawTargeting);
                
                DismountAll();

                for (int i = 0; i < _mountPoints.Length; i++)
                {
                    BaseMountable baseMountable = _mountPoints[i];
                    if (baseMountable && !baseMountable.IsDestroyed)
                        baseMountable.Kill();
                }                

                if (Entity && !Entity.IsDestroyed)
                    Entity.Kill();
                
                All.Remove(this);
            }

            private void Update()
            {
                if (!Commander) 
                    return;
                
                UpdateHealthUI();
                
                MoveVehicleInput();
                AimWeaponInput();
                FireWeaponInput();

                Entity.SendNetworkUpdateImmediate();
            }

            private void OnCollisionEnter(Collision collision)
            {
                if (!HasCommander) 
                    return;

                BaseEntity baseEntity = collision.gameObject ? collision.gameObject.GetComponentInParent<BaseEntity>() : null;
                if (!baseEntity)
                    return;

                ConfigData.CrushableTypes crushables = _configData.Crushables;
                BaseCombatEntity baseCombatEntity = baseEntity as BaseCombatEntity;

                if (crushables.Players && baseCombatEntity is BasePlayer triggerPlayer)
                {
                    if (!IsPassenger(triggerPlayer))
                    {
                        baseCombatEntity.Die(new HitInfo(Commander, baseCombatEntity, DamageType.Blunt, 200f));
                        return;
                    }
                }

                if (crushables.Animals && baseCombatEntity is BaseNpc or BaseNPC2)
                {
                    baseCombatEntity.Die(new HitInfo(Commander, baseCombatEntity, DamageType.Blunt, 200f));
                    return;
                }


                if (crushables.Buildings && baseCombatEntity)
                {
                    float impactForce = CalculateImpactForce(collision);
                    
                    BuildingBlock buildingBlock = baseCombatEntity as BuildingBlock;
                    if (buildingBlock && impactForce >= crushables.GradeForce[buildingBlock.grade.ToString()])
                    {
                        baseCombatEntity.Die(new HitInfo(Commander, baseCombatEntity, DamageType.Blunt, 1000f));
                        return;
                    }

                    SimpleBuildingBlock simpleBlock = baseCombatEntity as SimpleBuildingBlock;
                    if (simpleBlock && impactForce >= crushables.WallForce)
                    {
                        baseCombatEntity.Die(new HitInfo(Commander, baseCombatEntity, DamageType.Blunt, 1500));
                        return;
                    }
                }

                if (crushables.Loot && baseCombatEntity is LootContainer)
                {
                    baseCombatEntity.Die(new HitInfo(Commander, baseCombatEntity, DamageType.Blunt, 200f));
                    return;
                }

                if (crushables.Resources && baseEntity is ResourceEntity)
                {
                    float impactForce = CalculateImpactForce(collision);
                    if (impactForce >= crushables.ResourceForce)
                    {
                        baseEntity.Kill(BaseNetworkable.DestroyMode.None);
                        return;
                    }
                }
            }

            private float CalculateImpactForce(Collision col)
            {
                float impactVelocityX = _rb.velocity.x;
                impactVelocityX *= Mathf.Sign(impactVelocityX);

                float impactVelocityY = _rb.velocity.y;
                impactVelocityY *= Mathf.Sign(impactVelocityY);

                float impactVelocity = impactVelocityX + impactVelocityY;
                float impactForce = impactVelocity * _rb.mass;
                impactForce *= Mathf.Sign(impactForce);

                return impactForce;
            }
            
            #region Mounting
            
            private static readonly (Vector3, Vector3)[] MountOffsets = new []
            {
                (new Vector3(0.36f, -0.3f, 0.05f), Vector3.zero),
                (new Vector3(-0.6f, 0.4f, -1.3f), new Vector3(0f, 90f, 0f)),
                (new Vector3(-0.6f, 0.4f, -2f), new Vector3(0f, 90f, 0f)),
                (new Vector3(0.5f, 0.4f, -1.3f), new Vector3(0f, -90f, 0f)),
                (new Vector3(0.5f, 0.4f, -2f), new Vector3(0f, -90f, 0f)),
            };

            private void CreateMountPoints()
            {
                int passengers = Mathf.Clamp(_configData.Passengers.Max, 0, 4) + 1;
                _mountPoints = new BaseMountable[passengers];

                for (int i = 0; i < passengers; i++)
                    CreateMountPoint(i);
            }

            private void CreateMountPoint(int index)
            {
                const string CHAIR_PREFAB = "assets/prefabs/vehicle/seats/miniheliseat.prefab";

                (Vector3 position, Vector3 rotation) = MountOffsets[index];

                BaseMountable baseMountable = GameManager.server.CreateEntity(CHAIR_PREFAB, Entity.transform.position) as BaseMountable;
                baseMountable.enableSaving = false;                
                baseMountable.Spawn();

                Destroy(baseMountable.GetComponent<DestroyOnGroundMissing>());
                Destroy(baseMountable.GetComponent<GroundWatch>());

                baseMountable.SetParent(Entity, index == 0 ? StringPool.Get("turret") : 0, false, true);
                baseMountable.transform.localPosition = position;
                baseMountable.transform.localRotation = Quaternion.Euler(rotation);
                
                GameObject tr = new GameObject("Seat Transform");
                tr.transform.SetParent(baseMountable.transform);
                tr.transform.localPosition = index > 0 ? new Vector3(0, 0, -3f) : new Vector3(3f, 0, 0);

                baseMountable.dismountPositions = new [] { tr.transform };

                _mountPoints[index] = baseMountable;
            }

            public bool CanMountPlayer()
            {
                for (int i = 0; i < _mountPoints.Length; i++)
                {
                    BaseMountable baseMountable = _mountPoints[i];
                    if (!baseMountable.IsMounted())
                        return true;
                }

                return false;
            }

            public void MountPlayer(BasePlayer player)
            {
                for (int i = 0; i < _mountPoints.Length; i++)
                {                    
                    BaseMountable baseMountable = _mountPoints[i];
                    if (baseMountable.IsMounted())
                        continue;

                    player.EnsureDismounted();
                    baseMountable._mounted = player;
                    
                    player.SetMounted(baseMountable);
                    player.MovePosition(baseMountable.mountAnchor.transform.position);
                    player.transform.rotation = baseMountable.mountAnchor.transform.rotation;
                    player.ServerRotation = baseMountable.mountAnchor.transform.rotation;
                    player.OverrideViewAngles(baseMountable.mountAnchor.transform.rotation.eulerAngles);
                    player.eyes.NetworkUpdate(baseMountable.mountAnchor.transform.rotation);
                    player.SendNetworkUpdateImmediate();
                    baseMountable.OnPlayerMounted();
                    
                    if (baseMountable.allowedGestures == BaseMountable.MountGestureType.None && player.InGesture)
                        player.Server_CancelGesture();
                    else if (baseMountable.allowedGestures == BaseMountable.MountGestureType.UpperBody && player.InGesture && player.CurrentGestureIsFullBody)
                        player.Server_CancelGesture();

                    if (i > 0)
                        player.SetPlayerFlag(BasePlayer.PlayerFlags.ThirdPersonViewmode, true);

                    OnEntityMounted(player, i == 0);
                    return;
                }
            }

            public void DismountPlayer(BasePlayer player)
            {
                if (!player)
                    return;

                BaseMountable baseMountable = player.GetMounted();
                if (!baseMountable)
                    return;

                player.PauseFlyHackDetection(1f);

                Vector3 dismountPosition = baseMountable.dismountPositions?.Length > 0 ? baseMountable.dismountPositions[0].position : baseMountable.transform.position + (baseMountable.transform.right * 3f);
                baseMountable._mounted.DismountObject();
                baseMountable._mounted.transform.rotation = Quaternion.LookRotation(Vector3.forward, Vector3.up);
                baseMountable._mounted.MovePosition(dismountPosition);
                baseMountable._mounted.SendNetworkUpdateImmediate();
                baseMountable._mounted = null;
                baseMountable.SetFlag(BaseEntity.Flags.Busy, false, false, true);

                player.ForceUpdateTriggers(true, true, true);
                player.ClientRPCPlayer<Vector3>(null, player, "ForcePositionTo", dismountPosition);

                if (player == Commander)
                {
                    Entity.CancelInvoke(DrawTargeting);
                    Commander = null;
                }

                DestroyUI(player);
            }

            private void DismountAll()
            {
                for (int i = 0; i < _mountPoints.Length; i++)
                {
                    BasePlayer player = _mountPoints[i].GetMounted();
                    if (!player)
                        continue;
                    
                    DismountPlayer(player);
                }

                Commander = null;
            }
            
            private bool IsPassenger(BasePlayer player)
            {
                if (!player)
                    return false;
                
                BaseMountable baseMountable = player.GetMounted();
                return baseMountable && _mountPoints.Contains(baseMountable);
            }

            public bool IsMountPoint(BaseMountable baseMountable) => baseMountable && _mountPoints.Contains(baseMountable);
            
            private void OnEntityMounted(BasePlayer player, bool isOperator)
            {
                ControlInfo.Clear();
                
                if (isOperator)
                {
                    ControlInfo.AppendLine(TranslatedMessage("controls", player.UserIDString));
                    if (_configData.Weapons.Cannon.Enabled)
                        ControlInfo.AppendLine(string.Format(TranslatedMessage("fire_cannon", player.UserIDString), _configData.Buttons.Cannon));
                    
                    if (_configData.Weapons.Coax.Enabled)
                        ControlInfo.AppendLine(string.Format(TranslatedMessage("fire_coax", player.UserIDString), _configData.Buttons.Coax));
                    
                    if (_configData.Weapons.Mg.Enabled)
                        ControlInfo.AppendLine(string.Format(TranslatedMessage("fire_mg", player.UserIDString), _configData.Buttons.MachineGun));
                    
                    ControlInfo.AppendLine(string.Format(TranslatedMessage("speed_boost", player.UserIDString), _configData.Buttons.Boost));
                    ControlInfo.AppendLine(string.Format(TranslatedMessage("enter_exit", player.UserIDString), _configData.Buttons.Enter));
                    ControlInfo.AppendLine(string.Format(TranslatedMessage("toggle_lights", player.UserIDString), _configData.Buttons.Lights));
                    
                    if (_configData.Inventory.Enabled)
                        ControlInfo.AppendLine(string.Format(TranslatedMessage("access_inventory", player.UserIDString), _configData.Buttons.Inventory));
                    
                    player.ChatMessage(ControlInfo.ToString());

                    CreateHealthUI(player, this);
                    CreateMachineGunAmmoUI(player, this);
                    CreateRocketAmmoUI(player, this);

                    Commander = player;
                    Entity.InvokeRepeating(DrawTargeting, DDrawUpdateTime, DDrawUpdateTime);
                }
                else
                {
                    player.ChatMessage(string.Format(TranslatedMessage("enter_exit", player.UserIDString), _configData.Buttons.Enter));
                    
                    if (_configData.Inventory.Enabled)
                        player.ChatMessage(string.Format(TranslatedMessage("access_inventory", player.UserIDString), _configData.Buttons.Inventory));
                    
                    CreateHealthUI(player, this);
                }
            }
            
            #endregion

            #region Weapons
            
            private void FireWeaponInput()
            {                
                if (ControlScheme.WasJustPressed(Commander.serverInput, ControlScheme.Enum.Lights))
                    ToggleLights();

                if (ControlScheme.WasJustPressed(Commander.serverInput, ControlScheme.Enum.Cannon))
                    FireCannon();

                if (ControlScheme.IsDownOrPressed(Commander.serverInput, ControlScheme.Enum.MachineGun))
                    FireMachineGun();

                if (ControlScheme.IsDownOrPressed(Commander.serverInput, ControlScheme.Enum.Coax))
                    FireCoax();
            }
            
            private void AimWeaponInput()
            {            
                _mouseInput = Commander.tickMouseDelta;
                _mouseInput.x = Mathf.Clamp(_mouseInput.x, -5f, 5f) * 3f;
                _mouseInput.y = Mathf.Clamp(_mouseInput.y, -5f, 5f) * 2f;

                _localTurretRotation.y = Mathf.Lerp(_localTurretRotation.y, _localTurretRotation.y + _mouseInput.x, Time.deltaTime * 12f) % 360;
                _localTurretRotation.x = Mathf.Lerp(_localTurretRotation.x, Mathf.Clamp(_localTurretRotation.x - _mouseInput.y, -40f, 7f), Time.deltaTime * 12f);
                
                Vector3 up = Entity.mainTurret.up;
    
                Vector3 yawDirection = Quaternion.Euler(0, _localTurretRotation.y, 0) * Vector3.forward;
    
                Vector3 pitchAxis = Vector3.Cross(up, yawDirection).normalized;
    
                Vector3 levelDirection = Vector3.ProjectOnPlane(yawDirection, up).normalized;
    
                Vector3 direction = Quaternion.AngleAxis(_localTurretRotation.x, pitchAxis) * levelDirection;
                
                Ray ray = new Ray(Commander.eyes.position + (direction * 4f), direction);
                
                Vector3 hitPoint = Physics.Raycast(ray, out RaycastHit raycastHit, 2000f, 1220225809) ? raycastHit.point : ray.GetPoint(100);
                
                Vector3 desiredAim = (hitPoint - Entity.CannonMuzzle.position).normalized;
                Vector3 desiredAimTop = (hitPoint - Entity.topTurretEyePos.position).normalized;

                _aimVector = Entity.turretAimVector = desiredAim;
                _aimVectorTop = Entity.topTurretAimVector = desiredAimTop;

                Entity.AimWeaponAt(Entity.mainTurret, Entity.coaxPitch, _aimVector, -40f, 7f, 360f, null);
                Entity.AimWeaponAt(Entity.mainTurret, Entity.CannonPitch, _aimVector, -40f, 7f, 360f, null);

                Entity.AimWeaponAt(Entity.topTurretYaw, Entity.topTurretPitch, _aimVectorTop, -360f, 360f, 360f, Entity.mainTurret);
            }
            
            private void DrawTargeting()
            {
                if (!_configData.Weapons.EnableCrosshair || !Commander)
                    return;

                Vector3 start = Entity.CannonMuzzle.position;
                Vector3 end;

                if (Physics.Raycast(start, Entity.CannonMuzzle.forward, out RaycastHit rayHit, 300, 1220225809, QueryTriggerInteraction.Ignore))                
                    end = rayHit.point;                
                else end = start + (Entity.CannonMuzzle.forward * 300);
                
                if (Commander.IsAdmin)
                {
                    Commander.SendConsoleCommand("ddraw.text", DDrawUpdateTime + 0.01f, _configData.Weapons.CrosshairColor.Color, end, $"<size={_configData.Weapons.CrosshairSize}>⊕</size>");
                }
                else
                {
                    Commander.SetPlayerFlag(BasePlayer.PlayerFlags.IsAdmin, true);
                    Commander.SendNetworkUpdateImmediate();
                    Commander.SendConsoleCommand("ddraw.text", DDrawUpdateTime + 0.01f, _configData.Weapons.CrosshairColor.Color, end, $"<size={_configData.Weapons.CrosshairSize}>⊕</size>");
                    Commander.SetPlayerFlag(BasePlayer.PlayerFlags.IsAdmin, false);
                }            
            }

            private bool HasAmmoForWeapon(int itemId, string displayName, string key, out Item item)
            {
                item = null;
                
                if (itemId == 0)
                    return false;
                
                for (int i = 0; i < Inventory.itemList.Count; i++)
                {
                    item = Inventory.itemList[i];
                    if (item != null && item.info.itemid == itemId && item.amount > 0)
                        return true;
                }

                Commander.ChatMessage(string.Format(TranslatedMessage(key, Commander.UserIDString), displayName));
                return false;
            }
            
            private void FireCannon()
            {
                if (Time.realtimeSinceStartup < _nextFireCannon)
                    return;
                
                ConfigData.WeaponOptions.WeaponSystem config = _configData.Weapons.Cannon;

                Item ammoItem = null;
                if (config.RequireAmmo && !HasAmmoForWeapon(config.AmmoItemID, config.AmmoItemDisplayName, "no_ammo_cannon", out ammoItem))
                    return;
                
                Vector3 modifiedAimConeDirection = AimConeUtil.GetModifiedAimConeDirection(config.Accuracy, Entity.CannonMuzzle.rotation * Vector3.forward, true);
                Vector3 cannonPitch = (Entity.CannonPitch.transform.rotation * Vector3.back) + (Entity.transform.up * -1f);

                Entity.myRigidBody.AddForceAtPosition(cannonPitch.normalized * Entity.recoilScale, Entity.CannonPitch.transform.position, ForceMode.Impulse);

                Effect.server.Run(Entity.mainCannonMuzzleFlash.resourcePath, Entity, StringPool.Get(Entity.CannonMuzzle.gameObject.name), Vector3.zero, Vector3.zero, null, false);

                BaseEntity rocket = GameManager.server.CreateEntity(Entity.mainCannonProjectile.resourcePath, Entity.CannonMuzzle.transform.position, Quaternion.LookRotation(modifiedAimConeDirection), true);
                ServerProjectile projectile = rocket.GetComponent<ServerProjectile>();
                projectile.InitializeVelocity(modifiedAimConeDirection.normalized * projectile.speed);
                rocket.Spawn();

                TimedExplosive explosive = rocket.GetComponent<TimedExplosive>();
                if (explosive)
                    explosive.damageTypes.Add(new DamageTypeEntry { amount = config.Damage, type = DamageType.Explosion });

                _nextFireCannon = Time.realtimeSinceStartup + config.Interval;

                if (config.RequireAmmo && ammoItem != null)
                    ammoItem.UseItem(1);
                
                CreateRocketAmmoUI(Commander, this);
            }

            private void FireCoax()
            {
                if (Time.realtimeSinceStartup < _nextFireCoax)
                    return;
                
                ConfigData.WeaponOptions.WeaponSystem config = _configData.Weapons.Coax;
                
                Item ammoItem = null;
                if (config.RequireAmmo && !HasAmmoForWeapon(config.AmmoItemID, config.AmmoItemDisplayName, "no_ammo_coax", out ammoItem))
                    return;
                
                Vector3 vector3 = Entity.coaxMuzzle.transform.position - (Entity.coaxMuzzle.forward * 0.25f);

                Vector3 modifiedAimConeDirection = AimConeUtil.GetModifiedAimConeDirection(config.Accuracy, Entity.coaxMuzzle.transform.forward, true);
                Vector3 targetPos = vector3 + (modifiedAimConeDirection * 300f);

                List<RaycastHit> list = Pool.Get<List<RaycastHit>>();
                GamePhysics.TraceAll(new Ray(vector3, modifiedAimConeDirection), 0f, list, 300f, 1219701521, QueryTriggerInteraction.UseGlobal);
                for (int i = 0; i < list.Count; i++)
                {
                    RaycastHit hit = list[i];
                    BaseEntity hitEntity = hit.GetEntity();
                    if (!hitEntity || hitEntity == Entity) 
                        continue;
                    
                    BaseCombatEntity baseCombatEntity = hitEntity as BaseCombatEntity;
                    if (baseCombatEntity)
                        ApplyDamage(baseCombatEntity, config.Damage, hit.point, modifiedAimConeDirection);

                    if (!hitEntity.ShouldBlockProjectiles()) 
                        continue;
                    
                    targetPos = hit.point;
                    break;
                }

                Entity.ClientRPC(null, "CLIENT_FireGun", true, targetPos);
                Pool.FreeUnmanaged<RaycastHit>(ref list);

                _nextFireCoax = Time.realtimeSinceStartup + config.Interval;

                if (config.RequireAmmo && ammoItem != null)
                    ammoItem.UseItem(1);
                
                CreateMachineGunAmmoUI(Commander, this);
            }

            private void FireMachineGun()
            {
                if (Time.realtimeSinceStartup < _nextFireMg)
                    return;
                
                ConfigData.WeaponOptions.WeaponSystem config = _configData.Weapons.Mg;

                Item ammoItem = null;
                if (config.RequireAmmo && !HasAmmoForWeapon(config.AmmoItemID, config.AmmoItemDisplayName, "no_ammo_mg", out ammoItem))
                    return;
                
                Vector3 firePosition = Entity.topTurretMuzzle.transform.position - (Entity.topTurretMuzzle.forward * 0.25f);

                Vector3 modifiedAimConeDirection = AimConeUtil.GetModifiedAimConeDirection(config.Accuracy, Entity.topTurretMuzzle.transform.forward, true);
                Vector3 targetPos = firePosition + modifiedAimConeDirection;

                List<RaycastHit> list = Pool.Get<List<RaycastHit>>();
                GamePhysics.TraceAll(new Ray(firePosition, modifiedAimConeDirection), 0f, list, 300f, 1219701521, QueryTriggerInteraction.UseGlobal);
                for (int i = 0; i < list.Count; i++)
                {
                    RaycastHit hit = list[i];
                    BaseEntity hitEntity = hit.GetEntity();
                    
                    if (!hitEntity || hitEntity == Entity) 
                        continue;
                    
                    BaseCombatEntity baseCombatEntity = hitEntity as BaseCombatEntity;
                    if (baseCombatEntity)
                        ApplyDamage(baseCombatEntity, config.Damage, hit.point, modifiedAimConeDirection);

                    if (!hitEntity.ShouldBlockProjectiles()) 
                        continue;
                    
                    targetPos = hit.point;
                    break;
                }

                Entity.ClientRPC(null, "CLIENT_FireGun", false, targetPos);
                Pool.FreeUnmanaged<RaycastHit>(ref list);

                _nextFireMg = Time.realtimeSinceStartup + config.Interval;

                if (config.RequireAmmo)
                    Inventory.itemList.Find(x => x.info.shortname == config.Type)?.UseItem(1);
                
                CreateMachineGunAmmoUI(Commander, this);
            }

            private void ApplyDamage(BaseCombatEntity hitEntity, float damage, Vector3 point, Vector3 normal)
            {
                float single = damage * UnityEngine.Random.Range(0.9f, 1.1f);
                hitEntity.OnAttacked(new HitInfo(Entity, hitEntity, DamageType.Bullet, single, point));
                if (hitEntity is BasePlayer or BaseNpc)
                {
                    HitInfo hitInfo = new HitInfo()
                    {
                        HitPositionWorld = point,
                        HitNormalWorld = -normal,
                        HitMaterial = StringPool.Get("Flesh")
                    };
                    Effect.server.ImpactEffect(hitInfo);
                }
            }
            
            #endregion

            #region Movement
            
            private void MoveVehicleInput()
            {
                float accelerate = Commander.serverInput.IsDown(BUTTON.FORWARD) ? 1f : Commander.serverInput.IsDown(BUTTON.BACKWARD) ? -1f : 0f;
                float steer = Commander.serverInput.IsDown(BUTTON.RIGHT) ? 1f : Commander.serverInput.IsDown(BUTTON.LEFT) ? -1f : 0f;

                bool boost = ControlScheme.IsDown(Commander.serverInput, ControlScheme.Enum.Boost);

                SetThrottleSpeed(accelerate, steer, boost);
            }

            private void SetThrottleSpeed(float acceleration, float steering, bool boost)
            {
                ConfigData.MovementSettings movement = _configData.Movement;
                
                if (acceleration == 0 && steering == 0)
                {
                    ApplyBrakes(movement, 0.5f);

                    if (_accelTimeTaken > 0)
                        _accelTimeTaken = Mathf.Clamp(_accelTimeTaken -= (Time.deltaTime * 2), 0, movement.Acceleration);
                }
                else
                {
                    ApplyBrakes(movement, 0f);

                    _accelTimeTaken += Time.deltaTime;
                    float engineRpm = Mathf.InverseLerp(0f, movement.Acceleration, _accelTimeTaken);

                    float throttle = Mathf.InverseLerp(0f, 1f, engineRpm);

                    float leftTrack = 0;
                    float rightTrack = 0;
                    float torque = 0;

                    if (acceleration != 0)
                    {
                        torque = movement.ForwardTorque;
                        leftTrack = acceleration > 0 ? 1f : -1f;
                        rightTrack = acceleration > 0 ? 1f : -1f;
                    }

                    if (steering != 0)
                    {
                        if (acceleration == 0)
                        {
                            torque = movement.TurnTorque;
                            leftTrack = steering > 0 ? 1f : -1f;
                            rightTrack = steering > 0 ? -1f : 1f;
                        }
                        else
                        {
                            torque = Mathf.Lerp(movement.ForwardTorque, movement.TurnTorque, 0.33f);
                            rightTrack *= steering > 0 ? 0.33f : 1f;
                            leftTrack *= steering < 0 ? 0.33f : 1f;
                        }
                    }
                    
                    if (boost && torque != 0)
                        torque += torque > 0 ? movement.BoostTorque : -movement.BoostTorque;

                    float sidewaysVelocity = Mathf.InverseLerp(5f, 1.5f, _rb.velocity.magnitude * Mathf.Abs(Vector3.Dot(_rb.velocity.normalized, Entity.transform.forward)));
                    Entity.ScaleSidewaysFriction(1f - sidewaysVelocity);

                    ApplyMotorTorque(Mathf.Clamp(leftTrack * throttle, -1f, 1f) * torque, false);
                    ApplyMotorTorque(Mathf.Clamp(rightTrack * throttle, -1f, 1f) * torque, true);
                }
            }

            private void ApplyBrakes(ConfigData.MovementSettings movement, float amount)
            {
                amount = Mathf.Clamp(movement.BrakeTorque * amount, 0, movement.BrakeTorque);
                ApplyBrakeTorque(movement, amount, true);
                ApplyBrakeTorque(movement, amount, false);
            }

            private void ApplyBrakeTorque(ConfigData.MovementSettings movement, float amount, bool rightSide)
            {
                WheelCollider[] wheelColliderArray = (!rightSide ? _leftWheels : _rightWheels);

                for (int i = 0; i < wheelColliderArray.Length; i++)
                    wheelColliderArray[i].brakeTorque = movement.BrakeTorque * amount;
            }

            private void ApplyMotorTorque(float torque, bool rightSide)
            {
                WheelCollider[] wheelColliderArray = (!rightSide ? _leftWheels : _rightWheels);

                for (int i = 0; i < wheelColliderArray.Length; i++)
                    wheelColliderArray[i].motorTorque = torque;
            }
            #endregion
            
            private void ToggleLights() => Entity.SetFlag(BaseEntity.Flags.Reserved5, !Entity.HasFlag(BaseEntity.Flags.Reserved5), false);

            public void HandleDamageReceiving(HitInfo info)
            {
                if (_isDying)
                    return;

                if (info.damageTypes.Total() >= Entity.health)
                {
                    info.damageTypes = new DamageTypeList();
                    info.HitEntity = null;
                    info.HitMaterial = 0;
                    info.PointStart = Vector3.zero;

                    OnDeath();
                }
                else _needsHealthUpdate = true;
            }

            private void UpdateHealthUI()
            {
                if (!_needsHealthUpdate)
                    return;
                
                _needsHealthUpdate = false;
                
                if (Commander)
                    CreateHealthUI(Commander, this);

                for (int i = 1; i < _mountPoints.Length; i++)
                {
                    BaseMountable baseMountable = _mountPoints[i];
                    if (!baseMountable)
                        continue;
                            
                    BasePlayer player = baseMountable.GetMounted();
                    if (player)
                        CreateHealthUI(player, this);
                }
            }

            private void OnDeath()
            {
                _isDying = true;

                DismountAll();

                Effect.server.Run(Entity.explosionEffect.resourcePath, Entity.transform.position, Vector3.up, null, true);

                List<ServerGib> serverGibs = ServerGib.CreateGibs(Entity.servergibs.resourcePath, Entity.gameObject, Entity.servergibs.Get().GetComponent<ServerGib>()._gibSource, Vector3.zero, 3f);
                for (int i = 0; i < 12 - Entity.maxCratesToSpawn; i++)
                {
                    BaseEntity fireBall = GameManager.server.CreateEntity(Entity.fireBall.resourcePath, Entity.transform.position, Entity.transform.rotation, true);
                    if (fireBall)
                    {
                        Vector3 onSphere = UnityEngine.Random.onUnitSphere;
                        fireBall.transform.position = (Entity.transform.position + new Vector3(0f, 1.5f, 0f)) + (onSphere * UnityEngine.Random.Range(-4f, 4f));
                        Collider collider = fireBall.GetComponent<Collider>();
                        fireBall.Spawn();
                        fireBall.SetVelocity(Vector3.zero + (onSphere * UnityEngine.Random.Range(3, 10)));
                        foreach (ServerGib serverGib in serverGibs)
                            Physics.IgnoreCollision(collider, serverGib.GetCollider(), true);
                    }
                }

                if (_configData.Inventory.DropInv)
                {
                    Inventory.Drop("assets/prefabs/misc/item drop/item_drop.prefab", (Entity.transform.position + new Vector3(0f, 1.5f, 0f)) + (UnityEngine.Random.onUnitSphere * UnityEngine.Random.Range(2f, 3f)), Quaternion.identity, 0f);
                }

                if (_configData.Inventory.DropLoot)
                {
                    for (int j = 0; j < Entity.maxCratesToSpawn; j++)
                    {
                        Vector3 onSphere = UnityEngine.Random.onUnitSphere;
                        BaseEntity lootCrate = GameManager.server.CreateEntity(Entity.crateToDrop.resourcePath, (Entity.transform.position + new Vector3(0f, 1.5f, 0f)) + (onSphere * UnityEngine.Random.Range(2f, 3f)), Quaternion.LookRotation(onSphere), true);
                        lootCrate.Spawn();

                        LootContainer lootContainer = lootCrate as LootContainer;
                        if (lootContainer)
                            lootContainer.Invoke(lootContainer.RemoveMe, 1800f);

                        Collider collider = lootCrate.GetComponent<Collider>();
                        Rigidbody rigidbody = lootCrate.gameObject.AddComponent<Rigidbody>();
                        rigidbody.useGravity = true;
                        rigidbody.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
                        rigidbody.mass = 2f;
                        rigidbody.interpolation = RigidbodyInterpolation.Interpolate;
                        rigidbody.velocity = Vector3.zero + (onSphere * UnityEngine.Random.Range(1f, 3f));
                        rigidbody.angularVelocity = Vector3Ex.Range(-1.75f, 1.75f);
                        rigidbody.drag = 0.5f * (rigidbody.mass / 5f);
                        rigidbody.angularDrag = 0.2f * (rigidbody.mass / 5f);

                        FireBall fireBall = GameManager.server.CreateEntity(Entity.fireBall.resourcePath, lootCrate.transform.position, new Quaternion(), true) as FireBall;
                        if (fireBall)
                        {
                            fireBall.transform.position = lootCrate.transform.position;
                            fireBall.Spawn();
                            fireBall.GetComponent<Rigidbody>().isKinematic = true;
                            fireBall.GetComponent<Collider>().enabled = false;
                            fireBall.transform.parent = lootCrate.transform;
                        }
                        lootCrate.SendMessage("SetLockingEnt", fireBall.gameObject, SendMessageOptions.DontRequireReceiver);

                        foreach (ServerGib serverGib1 in serverGibs)
                            Physics.IgnoreCollision(collider, serverGib1.GetCollider(), true);
                    }
                }
                if (Entity && !Entity.IsDestroyed)
                    Entity.Kill(BaseNetworkable.DestroyMode.Gib);
            }
        }
        
        private class ControlScheme
        {
            private static BUTTON _enterExit;
            private static BUTTON _lights;
            private static BUTTON _inventory;
            private static BUTTON _boost;
            private static BUTTON _cannon;
            private static BUTTON _coax;
            private static BUTTON _machineGun;

            public static void Initialize()
            {
                _enterExit = ParseType<BUTTON>(_configData.Buttons.Enter);
                _lights = ParseType<BUTTON>(_configData.Buttons.Lights);
                _inventory = ParseType<BUTTON>(_configData.Buttons.Inventory);
                _boost = ParseType<BUTTON>(_configData.Buttons.Boost);
                _cannon = ParseType<BUTTON>(_configData.Buttons.Cannon);
                _coax = ParseType<BUTTON>(_configData.Buttons.Coax);
                _machineGun = ParseType<BUTTON>(_configData.Buttons.MachineGun);
            }

            private static BUTTON EnumToButton(Enum value)
            {
                return value switch
                {
                    Enum.EnterExit => _enterExit,
                    Enum.Lights => _lights,
                    Enum.Inventory => _inventory,
                    Enum.Boost => _boost,
                    Enum.Cannon => _cannon,
                    Enum.Coax => _coax,
                    Enum.MachineGun => _machineGun,
                    _ => throw new ArgumentOutOfRangeException(nameof(value), value, null)
                };
            }

            public static bool WasJustPressed(InputState inputState, Enum value) => inputState.WasJustPressed(EnumToButton(value));
            
            public static bool IsDown(InputState inputState, Enum value) => inputState.IsDown(EnumToButton(value));

            public static bool IsDownOrPressed(InputState inputState, Enum value)
            {
                BUTTON button = EnumToButton(value);
                return inputState.WasJustPressed(button) || inputState.IsDown(button);
            }
            
            public enum Enum { EnterExit, Lights, Inventory, Boost, Cannon, Coax, MachineGun }
        }

        #endregion

        #region Commands
        
        [ChatCommand("spawntank")]
        private void CommandSpawnTank(BasePlayer player, string command, string[] args)
        {
            if (!permission.UserHasPermission(player.UserIDString, PermissionAdmin)) return;

            Vector3 position = player.eyes.position + (player.eyes.MovementForward() * 5f);

            BaseEntity entity = GameManager.server.CreateEntity(ApcPrefab, position, Quaternion.Euler(0, player.eyes.rotation.eulerAngles.y - 90f, 0));
            entity.enableSaving = false;
            entity.Spawn();

            entity.gameObject.AddComponent<ApcController>();
        }

        [ConsoleCommand("spawntank")]
        private void ConsoleSpawnTank(ConsoleSystem.Arg arg)
        {
            if (arg.Connection != null || arg.Args == null)
                return;

            if (arg.Args.Length == 1)
            {
                BasePlayer player = arg.Player();
                if (!player) 
                    return;
                
                Vector3 position = player.eyes.position + (player.eyes.MovementForward() * 5f);

                BaseEntity entity = GameManager.server.CreateEntity(ApcPrefab, position, Quaternion.Euler(0, player.eyes.rotation.eulerAngles.y - 90f, 0));
                entity.enableSaving = false;
                entity.Spawn();

                entity.gameObject.AddComponent<ApcController>();
                return;
            }
            
            if (arg.Args.Length == 3)
            {
                if (float.TryParse(arg.GetString(0), out float x) && float.TryParse(arg.GetString(1), out float y) && float.TryParse(arg.GetString(2), out float z))
                {
                    BaseEntity entity = GameManager.server.CreateEntity(ApcPrefab, new Vector3(x, y, z));
                    entity.enableSaving = false;
                    entity.Spawn();

                    entity.gameObject.AddComponent<ApcController>();
                    return;
                }
                
                PrintError($"Invalid arguments supplied to spawn a tank at position : (x = {arg.GetString(0)}, y = {arg.GetString(1)}, z = {arg.GetString(2)})");
            }
        }
        
        #endregion

        #region Friends
        
        private bool AreFriends(ulong playerId, ulong friendId)
        {
            if (Friends && _configData.Passengers.UseFriends)
                return (bool)Friends?.Call("AreFriendsS", playerId.ToString(), friendId.ToString());
            return true;
        }
        
        private bool IsClanmate(ulong playerId, ulong friendId)
        {
            if (!Clans || !_configData.Passengers.UseClans) 
                return true;
            
            object playerClanObj = Clans?.Call("GetClanOf", playerId);
            object friendClanObj = Clans?.Call("GetClanOf", friendId);
            
            if (playerClanObj is string playerTag && friendClanObj is string friendTag)
            {
                if (!string.IsNullOrEmpty(playerTag) && !string.IsNullOrEmpty(friendTag) && playerTag == friendTag)
                    return true;
            }
            return false;
        }
        
        #endregion

        #region UI
        
        private static class UI
        {
            public static CuiElementContainer Container(string panelName, string color, Anchor anchor, bool cursor = false, string parent = "Overlay")
            {
                CuiElementContainer container = new();
            
                CuiImageComponent image = new();
                image.Color = color;
            
                CuiRectTransformComponent rect = new();
                rect.AnchorMin = anchor.Min;
                rect.AnchorMax = anchor.Max;
                
                CuiElement element = new();
                element.Name = string.IsNullOrEmpty(panelName) ? CuiHelper.GetGuid() : panelName;
                element.Parent = parent;
                element.DestroyUi = panelName;
                element.Components.Add(image);
                element.Components.Add(rect);

                if (cursor)
                    element.Components.Add(new CuiNeedsCursorComponent());

                container.Add(element);
                return container;
            }
            
            public static void Panel(ref CuiElementContainer container, string panel, string color, Anchor anchor)
            {
                CuiImageComponent image = new();
                image.Color = color;
            
                CuiRectTransformComponent rect = new();
                rect.AnchorMin = anchor.Min;
                rect.AnchorMax = anchor.Max;
                
                CuiElement element = new();
                element.Name = CuiHelper.GetGuid();
                element.Parent = panel;
                element.Components.Add(image);
                element.Components.Add(rect);
            
                container.Add(element);
            }
            
            public static void Label(ref CuiElementContainer container, string panel, string text, int size, Anchor anchor, TextAnchor align = TextAnchor.MiddleCenter)
            {
                const string FONT = "droidsansmono.ttf";
                
                CuiTextComponent textComponent = new();
                textComponent.FontSize = size;
                textComponent.Align = align;
                textComponent.Text = text;
                textComponent.Font = FONT;
            
                CuiRectTransformComponent rect = new();
                rect.AnchorMin = anchor.Min;
                rect.AnchorMax = anchor.Max;
            
                CuiElement element = new();
                element.Name = CuiHelper.GetGuid();
                element.Parent = panel;
                element.Components.Add(textComponent);
                element.Components.Add(rect);
            
                container.Add(element);
            }
        }
        
        private readonly struct Anchor
        {
            private readonly float _xMin;
            private readonly float _yMin;
            private readonly float _xMax;
            private readonly float _yMax;

            public Anchor(float xMin, float yMin, float xMax, float yMax)
            {
                _xMin = xMin;
                _yMin = yMin;
                _xMax = xMax;
                _yMax = yMax;
            }
            
            public string Min => $"{_xMin} {_yMin}";
            
            public string Max => $"{_xMax} {_yMax}";
        }
        
        private static void CreateHealthUI(BasePlayer player, ApcController controller)
        {
            if (!player)
                return;

            CuiElementContainer container = UI.Container(UIHealth, "0.95 0.95 0.95 0.05", new Anchor(0.69f, 0.1f, 0.83f, 0.135f));
            UI.Label(ref container, UIHealth, "HLTH: ", 12, new Anchor(0.03f, 0, 1, 1), TextAnchor.MiddleLeft);
            
            float percentHealth = controller.Entity.health / controller.Entity.MaxHealth();
            float yMaxHealth = 0.25f + (0.73f * percentHealth);
            UI.Panel(ref container, UIHealth, "0.8 0.25 0.17 0.6", new Anchor(0.25f, 0.1f, yMaxHealth, 0.9f));
            
            CuiHelper.AddUi(player, container);
        }

        private static void CreateMachineGunAmmoUI(BasePlayer player, ApcController controller)
        {
            if (!player)
                return;

            if (!_configData.Weapons.Mg.Enabled || !_configData.Weapons.Mg.RequireAmmo || _configData.Weapons.Mg.AmmoItemID == 0) 
                return;
            
            int amount = controller.Inventory.GetAmount(_configData.Weapons.Mg.AmmoItemID, false);
                
            CuiElementContainer container = UI.Container(UIAmmoMachineGun, "0.95 0.95 0.95 0.05", new Anchor(0.69f, 0.060f, 0.83f, 0.096f));
            UI.Label(ref container, UIAmmoMachineGun, $"MGUN: <color=#ce422b>{amount}</color>", 12, new Anchor(0.03f, 0, 1, 1), TextAnchor.MiddleLeft);

            CuiHelper.AddUi(player, container);
        }

        private static void CreateRocketAmmoUI(BasePlayer player, ApcController controller)
        {
            if (!player)
                return;

            if (!_configData.Weapons.Cannon.Enabled || !_configData.Weapons.Cannon.RequireAmmo || _configData.Weapons.Cannon.AmmoItemID == 0) 
                return;
            
            int amount = controller.Inventory.GetAmount(_configData.Weapons.Cannon.AmmoItemID, false);
                
            CuiElementContainer container = UI.Container(UIAmmoRocket, "0.95 0.95 0.95 0.05", new Anchor(0.69f, 0.021f, 0.83f, 0.056f));
            UI.Label(ref container, UIAmmoRocket, $"CNON: <color=#ce422b>{amount}</color>", 12, new Anchor(0.03f, 0, 1, 1), TextAnchor.MiddleLeft);

            CuiHelper.AddUi(player, container);
        }

        private static void DestroyUI(BasePlayer player, string panel) => CuiHelper.DestroyUi(player, panel);

        private static void DestroyUI(BasePlayer player)
        {
            DestroyUI(player, UIHealth);
            DestroyUI(player, UIAmmoMachineGun);
            DestroyUI(player, UIAmmoRocket);
        }
        
        #endregion

        #region Config
        
        private static ConfigData _configData;

        private class ConfigData
        {
            [JsonProperty(PropertyName = "Movement Settings")]
            public MovementSettings Movement { get; set; }

            [JsonProperty(PropertyName = "Button Configuration")]
            public ButtonConfiguration Buttons { get; set; }

            [JsonProperty(PropertyName = "Crushable Types")]
            public CrushableTypes Crushables { get; set; }

            [JsonProperty(PropertyName = "Passenger Options")]
            public PassengerOptions Passengers { get; set; }

            [JsonProperty(PropertyName = "Inventory Options")]
            public InventoryOptions Inventory { get; set; }

            [JsonProperty(PropertyName = "Weapon Options")]
            public WeaponOptions Weapons { get; set; }

            public class CrushableTypes
            {
                [JsonProperty(PropertyName = "Can crush buildings")]
                public bool Buildings { get; set; }

                [JsonProperty(PropertyName = "Can crush resources")]
                public bool Resources { get; set; }

                [JsonProperty(PropertyName = "Can crush loot containers")]
                public bool Loot { get; set; }

                [JsonProperty(PropertyName = "Can crush animals")]
                public bool Animals { get; set; }

                [JsonProperty(PropertyName = "Can crush players")]
                public bool Players { get; set; }

                [JsonProperty(PropertyName = "Amount of force required to crush various building grades")]
                public Dictionary<string, float> GradeForce { get; set; }

                [JsonProperty(PropertyName = "Amount of force required to crush external walls")]
                public float WallForce { get; set; }

                [JsonProperty(PropertyName = "Amount of force required to crush resources")]
                public float ResourceForce { get; set; }
            }

            public class ButtonConfiguration
            {
                [JsonProperty(PropertyName = "Enter/Exit vehicle")]
                public string Enter { get; set; }

                [JsonProperty(PropertyName = "Toggle light")]
                public string Lights { get; set; }

                [JsonProperty(PropertyName = "Open inventory")]
                public string Inventory { get; set; }

                [JsonProperty(PropertyName = "Speed boost")]
                public string Boost { get; set; }

                [JsonProperty(PropertyName = "Fire Cannon")]
                public string Cannon { get; set; }

                [JsonProperty(PropertyName = "Fire Coaxial Gun")]
                public string Coax { get; set; }

                [JsonProperty(PropertyName = "Fire MG")]
                public string MachineGun { get; set; }
            }
            
            public class MovementSettings
            {
                [JsonProperty(PropertyName = "Forward torque (nm)")]
                public float ForwardTorque { get; set; }

                [JsonProperty(PropertyName = "Rotation torque (nm)")]
                public float TurnTorque { get; set; }

                [JsonProperty(PropertyName = "Brake torque (nm)")]
                public float BrakeTorque { get; set; }

                [JsonProperty(PropertyName = "Time to reach maximum acceleration (seconds)")]
                public float Acceleration { get; set; }

                [JsonProperty(PropertyName = "Boost torque (nm)")]
                public float BoostTorque { get; set; }
            }
            public class PassengerOptions
            {
                [JsonProperty(PropertyName = "Allow passengers")]
                public bool Enabled { get; set; }

                [JsonProperty(PropertyName = "Number of allowed passengers (Max 4)")]
                public int Max { get; set; }

                [JsonProperty(PropertyName = "Require passenger to be a friend (FriendsAPI)")]
                public bool UseFriends { get; set; }

                [JsonProperty(PropertyName = "Require passenger to be a clan mate (Clans)")]
                public bool UseClans { get; set; }
            }

            public class InventoryOptions
            {
                [JsonProperty(PropertyName = "Enable inventory system")]
                public bool Enabled { get; set; }

                [JsonProperty(PropertyName = "Drop inventory on death")]
                public bool DropInv { get; set; }

                [JsonProperty(PropertyName = "Drop loot on death")]
                public bool DropLoot { get; set; }

                [JsonProperty(PropertyName = "Inventory size (max 36)")]
                public int Size { get; set; }
            }

            public class WeaponOptions
            {
                [JsonProperty(PropertyName = "Cannon")]
                public WeaponSystem Cannon { get; set; }

                [JsonProperty(PropertyName = "Coaxial")]
                public WeaponSystem Coax { get; set; }

                [JsonProperty(PropertyName = "Machine Gun")]
                public WeaponSystem Mg { get; set; }

                [JsonProperty(PropertyName = "Enable Crosshair")]
                public bool EnableCrosshair { get; set; }

                [JsonProperty(PropertyName = "Crosshair Color")]
                public SerializedColor CrosshairColor { get; set; }

                [JsonProperty(PropertyName = "Crosshair Size")]
                public int CrosshairSize { get; set; }

                public class SerializedColor
                {
                    public float R { get; set; }
                    public float G { get; set; }
                    public float B { get; set; }
                    public float A { get; set; }

                    private Color _color;
                    private bool _isInit;

                    public SerializedColor(float r, float g, float b, float a)
                    {
                        R = r;
                        G = g;
                        B = b;
                        A = a;
                    }

                    [JsonIgnore]
                    public Color Color
                    {
                        get
                        {
                            if (!_isInit)
                            {
                                _color = new Color(R, G, B, A);
                                _isInit = true;
                            }
                            return _color;
                        }
                    }
                }

                public class WeaponSystem
                {
                    [JsonProperty(PropertyName = "Enable weapon system")]
                    public bool Enabled { get; set; }

                    [JsonProperty(PropertyName = "Require ammunition in inventory")]
                    public bool RequireAmmo { get; set; }

                    [JsonProperty(PropertyName = "Ammunition type (item shortname)")]
                    public string Type { get; set; }

                    [JsonProperty(PropertyName = "Fire rate (seconds)")]
                    public float Interval { get; set; }

                    [JsonProperty(PropertyName = "Aim cone (smaller number is more accurate)")]
                    public float Accuracy { get; set; }

                    [JsonProperty(PropertyName = "Damage")]
                    public float Damage { get; set; }
                    
                    [JsonIgnore]
                    public int AmmoItemID { get; set; }
                    
                    [JsonIgnore]
                    public string AmmoItemDisplayName { get; set; }
                }
            }

            public VersionNumber Version { get; set; }
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            _configData = Config.ReadObject<ConfigData>();

            if (_configData.Version < Version)
                UpdateConfigValues();

            Config.WriteObject(_configData, true);
        }

        protected override void LoadDefaultConfig() => _configData = GetBaseConfig();

        private ConfigData GetBaseConfig()
        {
            return new ConfigData
            {
                Buttons = new ConfigData.ButtonConfiguration
                {
                    Enter = "USE",
                    Lights = "RELOAD",
                    Inventory = "RELOAD",
                    Boost = "SPRINT",
                    Cannon = "FIRE_PRIMARY",
                    Coax = "FIRE_SECONDARY",
                    MachineGun = "FIRE_THIRD"
                },
                Crushables = new ConfigData.CrushableTypes
                {
                    Animals = true,
                    Buildings = true,
                    Loot = true,
                    Players = true,
                    Resources = true,
                    GradeForce = new Dictionary<string, float>
                    {
                        [nameof(BuildingGrade.Enum.Twigs)] = 1000f,
                        [nameof(BuildingGrade.Enum.Wood)] = 2000f,
                        [nameof(BuildingGrade.Enum.Stone)] = 3000f,
                        [nameof(BuildingGrade.Enum.Metal)] = 5000f,
                        [nameof(BuildingGrade.Enum.TopTier)] = 7000f,
                    },
                    ResourceForce = 1500f,
                    WallForce = 3000f
                },
                Movement = new ConfigData.MovementSettings
                {
                    Acceleration = 3f,
                    BrakeTorque = 50f,
                    ForwardTorque = 1500f,
                    TurnTorque = 1800f,
                    BoostTorque = 300f
                },
                Passengers = new ConfigData.PassengerOptions
                {
                    Enabled = true,
                    Max = 4,
                    UseClans = true,
                    UseFriends = true
                },
                Inventory = new ConfigData.InventoryOptions
                {
                    Enabled = true,
                    Size = 36,
                    DropInv = true,
                    DropLoot = false
                },
                Weapons = new ConfigData.WeaponOptions
                {
                    EnableCrosshair = true,
                    CrosshairColor = new ConfigData.WeaponOptions.SerializedColor(0.75f, 0.75f, 0.75f, 0.75f),
                    CrosshairSize = 40,
                    Cannon = new ConfigData.WeaponOptions.WeaponSystem
                    {
                        Accuracy = 0.025f,
                        Damage = 90f,
                        Enabled = true,
                        Interval = 1.75f,
                        RequireAmmo = false,
                        Type = "ammo.rocket.hv"
                    },
                    Coax = new ConfigData.WeaponOptions.WeaponSystem
                    {
                        Accuracy = 0.75f,
                        Damage = 10f,
                        Enabled = true,
                        Interval = 0.06667f,
                        RequireAmmo = false,
                        Type = "ammo.rifle.hv"
                    },
                    Mg = new ConfigData.WeaponOptions.WeaponSystem
                    {
                        Accuracy = 1.25f,
                        Damage = 10f,
                        Enabled = true,
                        Interval = 0.1f,
                        RequireAmmo = false,
                        Type = "ammo.rifle.hv"
                    }
                },
                Version = Version
            };
        }

        protected override void SaveConfig() => Config.WriteObject(_configData, true);

        private void UpdateConfigValues()
        {
            PrintWarning("Config update detected! Updating config values...");

            ConfigData baseConfig = GetBaseConfig();

            if (_configData.Version < new VersionNumber(0, 2, 0))
                _configData = baseConfig;

            if (_configData.Version < new VersionNumber(0, 2, 2))
            {
                _configData.Weapons.EnableCrosshair = true;
                _configData.Weapons.CrosshairColor = new ConfigData.WeaponOptions.SerializedColor(0.75f, 0.75f, 0.75f, 0.75f);
                _configData.Weapons.CrosshairSize = 40;
            }

            _configData.Version = Version;
            PrintWarning("Config update completed!");
        }
        
        #endregion

        #region Localization
        
        private static string TranslatedMessage(string key, string playerId = null) => _instance.lang.GetMessage(key, _instance, playerId);

        private readonly Dictionary<string, string> _messages = new Dictionary<string, string>
        {
            ["in_use"] = "<color=#D3D3D3>This tank is already in use</color>",
            ["not_friend"] = "<color=#D3D3D3>You must be a friend or clanmate with the operator</color>",
            ["passenger_enter"] = "<color=#D3D3D3>You have entered the tank as a passenger</color>",
            ["controls"] = "<color=#ce422b>Tank Controls:</color>",
            ["fire_cannon"] = "<color=#D3D3D3>Fire Cannon </color><color=#ce422b>{0}</color>",
            ["fire_coax"] = "<color=#D3D3D3>Fire Coaxial Gun </color><color=#ce422b>{0}</color>",
            ["fire_mg"] = "<color=#D3D3D3>Fire MG </color><color=#ce422b>{0}</color>",
            ["speed_boost"] = "<color=#D3D3D3>Speed Boost </color><color=#ce422b>{0}</color>",
            ["enter_exit"] = "<color=#D3D3D3>Enter/Exit Vehicle </color><color=#ce422b>{0}</color>",
            ["toggle_lights"] = "<color=#D3D3D3>Toggle Lights </color><color=#ce422b>{0}</color>",
            ["access_inventory"] = "<color=#D3D3D3>Access Inventory (from outside of the vehicle) </color><color=#ce422b>{0}</color>",
            ["no_ammo_cannon"] = "<color=#D3D3D3>You do not have ammunition to fire the cannon. It requires </color><color=#ce422b>{0}</color>",
            ["no_ammo_mg"] = "<color=#D3D3D3>You do not have ammunition to fire the machine gun. It requires </color><color=#ce422b>{0}</color>",
            ["no_ammo_coax"] = "<color=#D3D3D3>You do not have ammunition to fire the coaxial gun. It requires </color><color=#ce422b>{0}</color>",
        };
        
        #endregion
    }
}