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

FREE BombTrucks : 0.8.7 0.8.7

Нет прав для скачивания
C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Rust.Modular;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Oxide.Core.Plugins;

namespace Oxide.Plugins
{
    [Info("Bomb Trucks", "WhiteThunder", "0.8.7")]
    [Description("Allow players to spawn bomb trucks.")]
    internal class BombTrucks : CovalencePlugin
    {
        #region Fields

        [PluginReference]
        private readonly Plugin NoEngineParts, NoEscape, SpawnModularCar, ZoneManager;

        private const int RfReservedRangeMin = 4760;
        private const int RfReservedRangeMax = 4790;

        private const string DefaultTruckConfigName = "default";

        private const string PermissionSpawnFormat = "bombtrucks.spawn.{0}";
        private const string PermissionGiveBombTruck = "bombtrucks.give";
        private const string PermissionFreeDetonator = "bombtrucks.freedetonator";

        private const string PrefabExplosiveRocket = "assets/prefabs/ammo/rocket/rocket_basic.prefab";
        private const string PrefabRfReceiver = "assets/prefabs/deployable/playerioents/gates/rfreceiver/rfreceiver.prefab";

        private const int DetonatorItemId = 596469572;
        private const int InvalidFrequency = -1;

        private readonly object False = false;

        private readonly Vector3 RfReceiverPosition = new Vector3(0, -0.1f, 0);
        private readonly Quaternion RfReceiverRotation = Quaternion.Euler(0, 180, 0);

        private readonly RFReceiverManager _receiverManager;
        private readonly BombTruckTracker _bombTruckTracker;
        private readonly Dictionary<ulong, float> _dismountTimeByPlayer = new Dictionary<ulong, float>();

        private StoredData _pluginData;
        private Configuration _pluginConfig;
        private ProtectionProperties _immortalProtection;
        private bool _pluginUnloaded;

        public BombTrucks()
        {
            _receiverManager = new RFReceiverManager(this);
            _bombTruckTracker = new BombTruckTracker(this);
        }

        #endregion

        #region Hooks

        private void Init()
        {
            _pluginData = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name);

            foreach (var truckConfig in _pluginConfig.BombTrucks)
            {
                permission.RegisterPermission(GetSpawnPermission(truckConfig.Name), this);
            }

            permission.RegisterPermission(PermissionGiveBombTruck, this);
            permission.RegisterPermission(PermissionFreeDetonator, this);
        }

        private void OnServerInitialized()
        {
            _immortalProtection = ScriptableObject.CreateInstance<ProtectionProperties>();
            _immortalProtection.name = "BombTrucksProtection";
            _immortalProtection.Add(1);

            VerifyDependencies();
            CleanStaleTruckData();
            InitializeBombTrucks();
        }

        private void Unload()
        {
            UnityEngine.Object.Destroy(_immortalProtection);

            _bombTruckTracker.Unload();

            // This is used to signal coroutines to stop early (simpler than keeping track of them)._pluginUnloaded = true;
        }

        private void OnNewSave() => ClearData();

        private void OnEntityDeath(ModularCar car)
        {
            if (IsBombTruck(car) && car.OwnerID != 0)
            {
                DetonateBombTruck(car);
            }
        }

        private void OnEntityMounted(ModularCarSeat seat, BasePlayer player)
        {
            var car = (seat.GetParentEntity() as BaseVehicleModule)?.GetParentEntity() as ModularCar;
            if (car == null || !IsBombTruck(car))
                return;

            var fuelContainer = car.GetFuelSystem().GetFuelContainer();
            fuelContainer.inventory.AddItem(fuelContainer.allowedItem, fuelContainer.allowedItem.stackable);
        }

        private void OnEntityDismounted(BaseMountable mountable, BasePlayer player)
        {
            _dismountTimeByPlayer[player.userID] = Time.time;
        }

        private object CanLootEntity(BasePlayer player, ModularCarGarage carLift)
        {
            if (!carLift.PlatformIsOccupied)
                return null;

            var car = carLift.carOccupant;
            if (car != null && IsBombTruck(car))
            {
                ChatMessage(player, "Lift.Edit.Error");
                return False;
            }

            return null;
        }

        // This hook is exposed by the deprecated plugin Modular Car Code Locks (CarCodeLocks).private object CanDeployCarCodeLock(ModularCar car, BasePlayer player) =>
            CanLockVehicle(car, player);

        private object CanDeployVehicleCodeLock(ModularCar car, BasePlayer player) =>
            CanLockVehicle(car, player);

        private object CanDeployVehicleKeyLock(ModularCar car, BasePlayer player) =>
            CanLockVehicle(car, player);

        private object CanLockVehicle(ModularCar car, BasePlayer player)
        {
            if (!IsBombTruck(car))
                return null;

            if (player != null)
            {
                player.ChatMessage(GetMessage(player.IPlayer, "Lock.Deploy.Error"));
            }

            return False;
        }

        private void OnRfBroadcasterAdded(IRFObject obj, int frequency) =>
            _receiverManager.DetonateFrequency(frequency);

        private void OnRfListenerAdded(IRFObject obj, int frequency)
        {
            var receiver = obj as RFReceiver;
            if (receiver != null)
            {
                var frequency2 = frequency;
                var receiver2 = receiver;

                // Need to delay checking for the car since the receiver is spawned unparented to mitigate rendering bug.NextTick(() =>
                {
                    var car = GetReceiverCar(receiver2);
                    if (car == null || !IsBombTruck(car))
                        return;

                    _receiverManager.AddReceiver(frequency2, receiver2);
                });
            }
        }

        private void OnRfListenerRemoved(IRFObject obj, int frequency)
        {
            var receiver = obj as RFReceiver;
            if (receiver == null)
                return;

            var car = GetReceiverCar(receiver);
            if (car == null || !IsBombTruck(car))
                return;

            _receiverManager.RemoveReceiver(frequency, receiver);
        }

        // This hook is exposed by Claim Vehicle Ownership (ClaimVehicle).private object OnVehicleUnclaim(BasePlayer player, ModularCar car)
        {
            if (car == null || !IsBombTruck(car))
                return null;

            ChatMessage(player, "Unclaim.Error");
            return False;
        }

        // This hook is exposed by Modular Car Turrets (CarTurrets).private object OnCarAutoTurretDeploy(BaseVehicleModule module, BasePlayer player, bool automatedDeployment)
        {
            if (module == null)
                return null;

            var car = module.Vehicle as ModularCar;
            if (car == null || !IsBombTruck(car))
                return null;

            if (player != null && !automatedDeployment)
            {
                ChatMessage(player, "AutoTurret.Deploy.Error");
            }

            return False;
        }

        // This hook is exposed by No Engine Parts (NoEngineParts).private object OnEngineLoadoutOverride(EngineStorage engineStorage)
        {
            var car = engineStorage.GetEngineModule()?.Vehicle as ModularCar;
            if (car == null || !IsBombTruck(car))
                return null;

            return False;
        }

        // This hook is exposed by Engine Parts Durability (EnginePartsDurability).private object OnEngineDamageMultiplierChange(EngineStorage engineStorage, float desiredMultiplier)
        {
            var car = engineStorage.GetEngineModule()?.Vehicle as ModularCar;
            if (car == null || !IsBombTruck(car))
                return null;

            return False;
        }

        // This hook is exposed by Auto Engine Parts (AutoEngineParts).private object OnEngineStorageFill(EngineStorage engineStorage, int enginePartsTier)
        {
            var car = engineStorage.GetEngineModule()?.Vehicle as ModularCar;
            if (car == null || !IsBombTruck(car))
                return null;

            return False;
        }

        #endregion

        #region Commands

        [Command("bombtruck", "bt", "boomer")]
        private void SpawnBombTruckCommand(IPlayer player, string cmd, string[] args)
        {
            if (player.IsServer)
                return;

            var basePlayer = player.Object as BasePlayer;
            if (!basePlayer.CanInteract())
                return;

            if (args.Length == 0)
            {
                SubCommand_SpawnBombTruck(player, args);
                return;
            }

            switch (args[0].ToLower())
            {
                case "help":
                    SubCommand_Help(player, args.Skip(1).ToArray());
                    return;

                default:
                    SubCommand_SpawnBombTruck(player, args);
                    return;
            }
        }

        private void SubCommand_Help(IPlayer player, string[] args)
        {
            var allowedTruckConfigs = _pluginConfig.BombTrucks.Where(config => permission.UserHasPermission(player.Id, GetSpawnPermission(config.Name))).OrderBy(truckConfig => truckConfig.Name, Comparer<string>.Create(SortTruckNames)).ToList();

            if (allowedTruckConfigs.Count == 0)
            {
                ReplyToPlayer(player, "Generic.Error.NoPermission");
                return;
            }

            var messages = new List<string> { GetMessage(player, "Command.Help") };

            messages.AddRange(allowedTruckConfigs.Select(truckConfig =>
            {
                var message = (truckConfig.Name == DefaultTruckConfigName) ?
                    GetMessage(player, "Command.Help.Spawn.Default") :
                    GetMessage(player, "Command.Help.Spawn.Named", truckConfig.Name);

                var limitUsage = GetPlayerData(player.Id).GetTruckCount(truckConfig.Name);
                message += " - " + GetMessage(player, "Command.Help.LimitUsage", limitUsage, truckConfig.SpawnLimit);

                var remainingCooldown = GetPlayerRemainingCooldownSeconds(player.Id, truckConfig);
                if (remainingCooldown > 0)
                    message += " - " + GetMessage(player, "Command.Help.RemainingCooldown", FormatTime(remainingCooldown));

                return message;
            }));

            player.Reply(string.Join("\n", messages));
        }

        private void SubCommand_SpawnBombTruck(IPlayer player, string[] args)
        {
            var truckName = DefaultTruckConfigName;

            if (args.Length > 0)
                truckName = args[0];

            var basePlayer = player.Object as BasePlayer;

            TruckConfig truckConfig;
            if (!VerifyTruckConfigDefined(player, truckName, out truckConfig) ||
                !VerifyHasPermission(player, GetSpawnPermission(truckName)) ||
                !VerifyOffCooldown(player, truckConfig) ||
                !VerifyBelowTruckLimit(player, truckConfig) ||
                !VerifyNotInSafeZone(player, basePlayer) ||
                !VerifyNotBuildingBlocked(player, basePlayer) ||
                !VerifyNotMounted(player, basePlayer) ||
                !VerifyNotRecentlyDismounted(player, basePlayer) ||
                !VerifyOnGround(player, basePlayer) ||
                !VerifyNotParented(player, basePlayer) ||
                !VerifyNotRaidOrCombatBlocked(basePlayer) ||
                !VerifyNotInZone(basePlayer) ||
                SpawnWasBlocked(basePlayer))
                return;

            SpawnBombTruck(basePlayer, truckConfig, shouldTrack: true);
        }

        [Command("givebombtruck")]
        private void GiveBombTruckCommand(IPlayer player, string cmd, string[] args)
        {
            if (!player.IsServer && !VerifyHasPermission(player, PermissionGiveBombTruck))
                return;

            if (args.Length < 1)
            {
                ReplyToPlayer(player, "Command.Give.Error.Syntax");
                return;
            }

            var playerNameOrIdArg = args[0];

            var truckName = DefaultTruckConfigName;
            if (args.Length > 1)
                truckName = args[1];

            var targetPlayer = BasePlayer.Find(playerNameOrIdArg);
            if (targetPlayer == null)
            {
                ReplyToPlayer(player, "Command.Give.Error.PlayerNotFound", playerNameOrIdArg);
                return;
            }

            TruckConfig truckConfig;
            if (!VerifyTruckConfigDefined(player, truckName, out truckConfig))
                return;

            SpawnBombTruck(targetPlayer, truckConfig, shouldTrack: false);
        }

        #endregion

        #region Helper Methods - Command Checks

        private static bool SpawnWasBlocked(BasePlayer player)
        {
            var hookResult = Interface.CallHook("CanSpawnBombTruck", player);
            return hookResult is bool && (bool)hookResult == false;
        }

        private bool VerifyNotRaidOrCombatBlocked(BasePlayer player)
        {
            if (!_pluginConfig.NoEscapeSettings.CanSpawnWhileRaidBlocked && IsRaidBlocked(player))
            {
                ChatMessage(player, "Command.Spawn.Error.RaidBlocked");
                return false;
            }

            if (!_pluginConfig.NoEscapeSettings.CanSpawnWhileCombatBlocked && IsCombatBlocked(player))
            {
                ChatMessage(player, "Command.Spawn.Error.CombatBlocked");
                return false;
            }

            return true;
        }

        private bool VerifyNotInZone(BasePlayer player)
        {
            if (!_pluginConfig.ZoneManagerSettings.CanSpawnInZones && InZone(player))
            {
                ChatMessage(player, "Command.Spawn.Error.InZone");
                return false;
            }

            return true;
        }
        
        private bool InZone(BasePlayer player) => 
            ZoneManager != null && ZoneManager.IsLoaded && (bool)ZoneManager?.Call("PlayerHasFlag", player, "keepvehiclesout");
        

        private bool IsRaidBlocked(BasePlayer player) =>
            NoEscape != null && (bool)NoEscape.Call("IsRaidBlocked", player);

        private bool IsCombatBlocked(BasePlayer player) =>
            NoEscape != null && (bool)NoEscape.Call("IsCombatBlocked", player);

        private bool VerifyHasPermission(IPlayer player, string perm)
        {
            if (player.HasPermission(perm))
                return true;

            ReplyToPlayer(player, "Generic.Error.NoPermission");
            return false;
        }

        private bool VerifyTruckConfigDefined(IPlayer player, string truckName, out TruckConfig truckConfig)
        {
            truckConfig = GetTruckConfig(truckName);
            if (truckConfig == null)
            {
                ReplyToPlayer(player, "Command.Spawn.Error.NotFound", truckName);
                return false;
            }
            return true;
        }

        private bool VerifyNotBuildingBlocked(IPlayer player, BasePlayer basePlayer)
        {
            if (basePlayer.IsBuildingBlocked())
            {
                ReplyToPlayer(player, "Generic.Error.BuildingBlocked");
                return false;
            }
            return true;
        }

        private bool VerifyNotInSafeZone(IPlayer player, BasePlayer basePlayer)
        {
            if (!basePlayer.InSafeZone())
                return true;

            ReplyToPlayer(player, "Command.Spawn.Error.Generic");
            return false;
        }

        private bool VerifyOffCooldown(IPlayer player, TruckConfig truckConfig)
        {
            var secondsRemaining = GetPlayerRemainingCooldownSeconds(player.Id, truckConfig);
            if (secondsRemaining > 0)
            {
                ReplyToPlayer(player, "Generic.Error.Cooldown", FormatTime(secondsRemaining));
                return false;
            }
            return true;
        }

        private bool VerifyNotMounted(IPlayer player, BasePlayer basePlayer)
        {
            if (basePlayer.isMounted)
            {
                ReplyToPlayer(player, "Command.Spawn.Error.Mounted");
                return false;
            }
            return true;
        }

        private bool VerifyNotRecentlyDismounted(IPlayer player, BasePlayer basePlayer)
        {
            float dismountTime;
            if (_dismountTimeByPlayer.TryGetValue(basePlayer.userID, out dismountTime) && dismountTime + 0.2f > Time.time)
            {
                ReplyToPlayer(player, "Command.Spawn.Error.Generic");
                return false;
            }
            return true;
        }

        private bool VerifyOnGround(IPlayer player, BasePlayer basePlayer)
        {
            if (!basePlayer.IsOnGround())
            {
                ReplyToPlayer(player, "Command.Spawn.Error.NotOnGround");
                return false;
            }
            return true;
        }

        private bool VerifyNotParented(IPlayer player, BasePlayer basePlayer)
        {
            if (basePlayer.HasParent())
            {
                ReplyToPlayer(player, "Command.Spawn.Error.Generic");
                return false;
            }
            return true;
        }

        private bool VerifyBelowTruckLimit(IPlayer player, TruckConfig truckConfig)
        {
            if (GetPlayerData(player.Id).GetTruckCount(truckConfig.Name) >= truckConfig.SpawnLimit)
            {
                ReplyToPlayer(player, "Command.Spawn.Error.TooManyOfType", truckConfig.SpawnLimit);
                return false;
            }
            return true;
        }

        #endregion

        #region Helper Methods - Misc

        private static void DisableEnginePartDamage(ModularCar car)
        {
            foreach (var module in car.AttachedModuleEntities)
            {
                var engineStorage = GetEngineStorage(module);
                if (engineStorage == null)
                    continue;

                engineStorage.internalDamageMultiplier = 0;
            }
        }

        private static EngineStorage GetEngineStorage(BaseVehicleModule module)
        {
            var engineModule = module as VehicleModuleEngine;
            if (engineModule == null)
                return null;

            return engineModule.GetContainer() as EngineStorage;
        }

        private static string GetSpawnPermission(string truckName) =>
            string.Format(PermissionSpawnFormat, truckName);

        private static int SortTruckNames(string a, string b) =>
            a.ToLower() == DefaultTruckConfigName ? -1 :
            b.ToLower() == DefaultTruckConfigName ? 1 :
            string.Compare(a, b, StringComparison.Ordinal);

        private static ModularCar GetReceiverCar(RFReceiver receiver) =>
            (receiver.GetParentEntity() as VehicleModuleSeating)?.Vehicle as ModularCar;

        private static RFReceiver GetBombTruckReceiver(ModularCar car)
        {
            var driverModule = FindFirstDriverModule(car);
            if (driverModule == null)
                return null;

            return GetChildOfType<RFReceiver>(driverModule);
        }

        private static T GetChildOfType<T>(BaseEntity entity) where T : BaseEntity
        {
            foreach (var child in entity.children)
            {
                var childOfType = child as T;
                if (childOfType != null)
                    return childOfType;
            }
            return null;
        }

        private static void RemoveProblemComponents(BaseEntity entity)
        {
            foreach (var meshCollider in entity.GetComponentsInChildren<MeshCollider>())
                UnityEngine.Object.DestroyImmediate(meshCollider);

            UnityEngine.Object.DestroyImmediate(entity.GetComponent<DestroyOnGroundMissing>());
            UnityEngine.Object.DestroyImmediate(entity.GetComponent<GroundWatch>());
        }

        private static VehicleModuleSeating FindFirstDriverModule(ModularCar car)
        {
            for (var socketIndex = 0; socketIndex < car.TotalSockets; socketIndex++)
            {
                BaseVehicleModule module;
                if (car.TryGetModuleAt(socketIndex, out module))
                {
                    var seatingModule = module as VehicleModuleSeating;
                    if (seatingModule != null && seatingModule.HasADriverSeat())
                        return seatingModule;
                }
            }
            return null;
        }

        private static bool HasExistingDetonator(ItemContainer container, out int frequency)
        {
            var hasDetonator = false;
            frequency = InvalidFrequency;

            for (var slot = 0; slot < container.capacity; slot++)
            {
                var item = container.GetSlot(slot);
                if (item == null || item.info.itemid != DetonatorItemId)
                    continue;

                hasDetonator = true;

                frequency = item.instanceData?.dataInt ?? InvalidFrequency;
                if (frequency != InvalidFrequency)
                {
                    // Only exit early if the detonator has a valid frequency.// Otherwise, keep searching for detonators with a valid frequency.return true;
                }
            }

            return hasDetonator;
        }

        private static bool HasExistingDetonator(BasePlayer player, out int frequency)
        {
            var hasDetonator = false;
            frequency = -1;

            var activeDetonator = player.GetActiveItem();
            if (activeDetonator != null && activeDetonator.info.itemid == DetonatorItemId)
            {
                frequency = activeDetonator.instanceData?.dataInt ?? -1;
                hasDetonator = true;
            }

            // Only exit early if one of the belt detonators had a valid frequency.// Otherwise, keep searching for a detonator with a valid frequency.if (hasDetonator && frequency != InvalidFrequency)
                return true;

            if (HasExistingDetonator(player.inventory.containerBelt, out frequency))
                hasDetonator = true;

            if (hasDetonator && frequency != InvalidFrequency)
                return true;

            if (HasExistingDetonator(player.inventory.containerMain, out frequency))
                hasDetonator = true;

            return hasDetonator;
        }

        private static Item CreateRFTransmitter(int frequency)
        {
            var detonatorItem = ItemManager.CreateByItemID(DetonatorItemId);
            if (detonatorItem == null)
                return null;

            if (detonatorItem.instanceData == null)
            {
                detonatorItem.instanceData = new ProtoBuf.Item.InstanceData { ShouldPool = false };
            }

            detonatorItem.instanceData.dataInt = frequency;

            var detonator = detonatorItem.GetHeldEntity() as Detonator;
            if (detonator != null)
                detonator.frequency = frequency;

            return detonatorItem;
        }

        private static int GenerateRandomFrequency()
        {
            var frequency = Core.Random.Range(RFManager.minFreq, RFManager.maxFreq);
            return frequency >= RfReservedRangeMin && frequency <= RfReservedRangeMax
                ? RfReservedRangeMin - 1
                : frequency;
        }

        private static string FormatTime(double seconds) => TimeSpan.FromSeconds(seconds).ToString("g");

        private bool VerifyDependencies()
        {
            if (SpawnModularCar == null)
            {
                LogError("SpawnModularCar is not loaded, get it at https://umod.org");
                return false;
            }

            var requiredVersion = new VersionNumber(5, 0, 1);
            if (SpawnModularCar.Version < requiredVersion)
            {
                LogError($"SpawnModularCar {requiredVersion} or newer is required, get it at https://umod.org");
                return false;
            }

            return true;
        }

        private void SetupReceiver(RFReceiver receiver)
        {
            receiver.pickup.enabled = false;
            receiver.baseProtection = _immortalProtection;
            RemoveProblemComponents(receiver);
        }

        private void InitializeBombTrucks()
        {
            foreach (var entity in BaseNetworkable.serverEntities)
            {
                var car = entity as ModularCar;
                if (car == null || !IsBombTruck(car))
                    continue;

                _bombTruckTracker.TrackBombTruck(car);

                DisableEnginePartDamage(car);
                var receiver = GetBombTruckReceiver(car);
                if (receiver != null)
                {
                    _receiverManager.AddReceiver(receiver.GetFrequency(), receiver);
                    SetupReceiver(receiver);
                }
            }
        }

        private bool IsBombTruck(ModularCar car) =>
            _pluginData.PlayerData.Any(item => item.Value.BombTrucks.Any(data => data.ID == car.net.ID.Value));

        private ModularCar SpawnBombTruck(BasePlayer player, TruckConfig truckConfig, bool shouldTrack = false)
        {
            if (!VerifyDependencies())
                return null;

            var car = SpawnModularCar.Call("API_SpawnPreset", new Dictionary<string, object>
            {
                ["EnginePartsTier"] = truckConfig.EnginePartsTier,
                ["FuelAmount"] = -1,
                ["Modules"] = truckConfig.Modules
            }, player) as ModularCar;

            if (car == null)
                return null;

            _bombTruckTracker.TrackBombTruck(car);

            car.GetFuelSystem().GetFuelContainer().SetFlag(BaseEntity.Flags.Locked, true);

            foreach (var module in car.AttachedModuleEntities)
            {
                var engineStorage = GetEngineStorage(module);
                if (engineStorage != null)
                {
                    engineStorage.inventory.SetLocked(true);
                    engineStorage.internalDamageMultiplier = 0;
                }
            }

            var message = GetMessage(player.IPlayer, "Command.Spawn.Success");

            if (truckConfig.AttachRFReceiver)
            {
                int frequency;
                var hasDetonator = HasExistingDetonator(player, out frequency);

                var receiver = AttachRFReceiver(car, frequency);
                if (receiver != null)
                {
                    // Get the current frequency of the RF receiver, in case a new one was generated.frequency = receiver.GetFrequency();

                    if (!hasDetonator && permission.UserHasPermission(player.UserIDString, PermissionFreeDetonator))
                    {
                        var detonatorItem = CreateRFTransmitter(frequency);
                        if (detonatorItem != null)
                            player.GiveItem(detonatorItem);
                    }

                    message += " " + GetMessage(player.IPlayer, "Command.Spawn.Success.Frequency", frequency);
                }
            }

            player.IPlayer.Reply(message);

            if (shouldTrack)
            {
                UpdatePlayerCooldown(player.UserIDString, truckConfig.Name);
            }

            GetPlayerData(player.UserIDString).BombTrucks.Add(new PlayerTruckData
            {
                Name = truckConfig.Name,
                ID = car.net.ID.Value,
                Tracked = shouldTrack
            });

            SaveData();

            if (NoEngineParts != null)
            {
                // Refresh engine stats on next tick to override NoEngineParts.// This has to be done on the next tick after the bomb truck id has been registered.NextTick(() =>
                {
                    if (car == null || car.IsDestroyed)
                        return;

                    foreach (var module in car.AttachedModuleEntities)
                    {
                        var engineStorage = GetEngineStorage(module);
                        if (engineStorage != null)
                        {
                            engineStorage.RefreshLoadoutData();
                        }
                    }
                });
            }

            return car;
        }

        private RFReceiver AttachRFReceiver(ModularCar car, int frequency = -1)
        {
            var module = FindFirstDriverModule(car);
            if (module == null)
                return null;

            var receiver = GameManager.server.CreateEntity(PrefabRfReceiver, module.transform.TransformPoint(RfReceiverPosition), module.transform.rotation * RfReceiverRotation) as RFReceiver;
            if (receiver == null)
                return null;

            if (frequency == -1)
                frequency = GenerateRandomFrequency();

            receiver.frequency = frequency;

            SetupReceiver(receiver);
            receiver.Spawn();
            receiver.SetParent(module, worldPositionStays: true);

            return receiver;
        }

        private double GetPlayerRemainingCooldownSeconds(string userID, TruckConfig truckConfig)
        {
            var playerCooldowns = GetPlayerData(userID).Cooldowns;
            if (!playerCooldowns.ContainsKey(truckConfig.Name))
                return 0;

            var lastUsed = playerCooldowns[truckConfig.Name];
            var cooldownDuration = truckConfig.CooldownSeconds;
            var currentTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();

            return lastUsed + cooldownDuration - currentTime;
        }

        private void UpdatePlayerCooldown(string userID, string truckName) =>
            GetPlayerData(userID).UpdateCooldown(truckName, DateTimeOffset.UtcNow.ToUnixTimeSeconds());

        #endregion

        #region Unity Components

        private class BombTruckComponent : FacepunchBehaviour
        {
            public static BombTruckComponent AddToCar(BombTruckTracker tracker, ModularCar car)
            {
                var component = car.gameObject.AddComponent<BombTruckComponent>();
                component._tracker = tracker;
                component.Car = car;
                component.NetId = car.net.ID;
                component.OwnerId = car.OwnerID;
                return component;
            }

            public ModularCar Car { get; private set; }
            public NetworkableId NetId { get; private set; }
            public ulong OwnerId { get; private set; }
            private BombTruckTracker _tracker;

            private void OnDestroy()
            {
                _tracker.HandleBombTruckDestroyed(this);
            }
        }

        private class BombTruckTracker
        {
            private BombTrucks _plugin;
            private HashSet<Component> _bombTruckComponents = new HashSet<Component>();

            public BombTruckTracker(BombTrucks plugin)
            {
                _plugin = plugin;
            }

            public void TrackBombTruck(ModularCar car)
            {
                _bombTruckComponents.Add(BombTruckComponent.AddToCar(this, car));
            }

            public void HandleBombTruckDestroyed(BombTruckComponent component)
            {
                _bombTruckComponents.Remove(component);

                if (component.Car == null || component.Car.IsDestroyed)
                {
                    _plugin.GetPlayerData(component.OwnerId.ToString()).RemoveTruck(component.NetId.Value);
                    _plugin.SaveData();
                }
            }

            public void Unload()
            {
                foreach (var component in _bombTruckComponents.ToArray())
                {
                    UnityEngine.Object.DestroyImmediate(component);
                }
            }
        }

        #endregion

        #region RF Receiver Manager

        private class RFReceiverManager
        {
            private BombTrucks _plugin;
            private readonly Dictionary<int, List<RFReceiver>> Receivers = new Dictionary<int, List<RFReceiver>>();

            public RFReceiverManager(BombTrucks plugin)
            {
                _plugin = plugin;
            }

            public void AddReceiver(int frequency, RFReceiver receiver)
            {
                List<RFReceiver> receiverList;
                if (Receivers.TryGetValue(frequency, out receiverList))
                {
                    receiverList.Add(receiver);
                }
                else
                {
                    Receivers.Add(frequency, new List<RFReceiver> { receiver });
                }
            }

            public void RemoveReceiver(int frequency, RFReceiver receiver)
            {
                List<RFReceiver> receiverList;
                if (Receivers.TryGetValue(frequency, out receiverList))
                {
                    receiverList.Remove(receiver);
                }
            }

            public void DetonateFrequency(int frequency)
            {
                List<RFReceiver> receiverList;
                if (Receivers.TryGetValue(frequency, out receiverList))
                {
                    for (var i = receiverList.Count - 1; i >= 0; i--)
                    {
                        var receiver = receiverList[i];
                        var car = GetReceiverCar(receiver);
                        if (car != null)
                        {
                            _plugin.DetonateBombTruck(car);
                        }
                    }
                }
            }
        }

        #endregion

        #region Explosions

        private static void FireRocket(string rocketPrefab, Vector3 origin, Vector3 direction, float time, float damageRadiusMult = 1.0f, float damageMult = 1.0f)
        {
            var rocket = GameManager.server.CreateEntity(rocketPrefab, origin);
            var rocketProjectile = rocket.GetComponent<ServerProjectile>();
            var rocketExplosion = rocket.GetComponent<TimedExplosive>();

            rocketProjectile.gravityModifier = 0;
            rocketExplosion.explosionRadius *= damageRadiusMult;
            rocketExplosion.timerAmountMin = time;
            rocketExplosion.timerAmountMax = time;

            for (var i = 0; i < rocketExplosion.damageTypes.Count; i++)
            {
                rocketExplosion.damageTypes[i].amount *= damageMult;
            }

            rocket.SendMessage("InitializeVelocity", direction);
            rocket.Spawn();
        }

        private void DetonateBombTruck(ModularCar car)
        {
            var playerConfig = GetPlayerData(car.OwnerID.ToString());

            var netID = car.net.ID.Value;
            var truckName = playerConfig.FindTruck(netID)?.Name;
            if (truckName == null)
            {
                LogError("Unable to determine truck name.");
                return;
            }

            var truckConfig = GetTruckConfig(truckName);
            if (truckConfig == null)
            {
                LogError("Unable to detonate '{0}' truck because its configuration is missing.", truckName);
                return;
            }

            // Remove the engine parts.foreach (var module in car.AttachedModuleEntities)
            {
                var engineStorage = GetEngineStorage(module);
                if (engineStorage != null)
                {
                    engineStorage.inventory.Kill();
                }
            }

            var carPosition = car.CenterPoint();
            car.Kill();
            DetonateExplosion(truckConfig.ExplosionSpec, carPosition);
        }

        private void DetonateExplosion(ExplosionSpec spec, Vector3 origin) =>
            ServerMgr.Instance.StartCoroutine(ExplosionCoroutine(spec, origin));

        private IEnumerator ExplosionCoroutine(ExplosionSpec spec, Vector3 origin)
        {
            float rocketTravelTime = 0.3f;
            double totalTime = spec.Radius / spec.Speed;
            int numExplosions = (int)Math.Ceiling(spec.DensityCoefficient * Math.Pow(spec.Radius, spec.DensityExponent));

            float timeElapsed = 0;
            double prevDistance = 0;

            FireRocket(PrefabExplosiveRocket, origin, Vector3.forward, 0, spec.BlastRadiusMult, spec.DamageMult);

            for (var i = 1; i <= numExplosions; i++)
            {
                if (_pluginUnloaded)
                    yield break;

                double timeFraction = timeElapsed / totalTime;
                double stepDistance = spec.Radius * timeFraction;

                double stepStartDistance = prevDistance;
                double stepEndDistance = stepDistance;

                double rocketDistance = Core.Random.Range(stepStartDistance, stepEndDistance);
                double rocketSpeed = rocketDistance / rocketTravelTime;

                Vector3 rocketVector = MakeRandomDomeVector();

                // Skip over some space to reduce the frequency of rockets colliding with each other.Vector3 skipDistance = rocketVector;

                rocketVector *= Convert.ToSingle(rocketSpeed);
                FireRocket(PrefabExplosiveRocket, origin + skipDistance, rocketVector + skipDistance, rocketTravelTime, spec.BlastRadiusMult, spec.DamageMult);

                float timeToNext = Convert.ToSingle(Math.Pow(i / spec.DensityCoefficient, 1.0 / spec.DensityExponent) / spec.Speed - timeElapsed);

                yield return CoroutineEx.waitForSeconds(timeToNext);
                prevDistance = stepDistance;
                timeElapsed += timeToNext;
            }
        }

        private Vector3 MakeRandomDomeVector() =>
            new Vector3(Core.Random.Range(-1f, 1f), Core.Random.Range(0, 1f), Core.Random.Range(-1f, 1f)).normalized;

        #endregion

        #region Data Management

        private PlayerData GetPlayerData(string userID)
        {
            if (!_pluginData.PlayerData.ContainsKey(userID))
                _pluginData.PlayerData.Add(userID, new PlayerData());

            return _pluginData.PlayerData[userID];
        }

        private void CleanStaleTruckData()
        {
            var cleanedCount = 0;

            // Clean up any stale truck IDs in case of a data file desync.foreach (var playerData in _pluginData.PlayerData.Values)
            {
                cleanedCount += playerData.BombTrucks.RemoveAll(truckData =>
                    (BaseNetworkable.serverEntities.Find(new NetworkableId(truckData.ID)) as ModularCar) == null);
            }

            if (cleanedCount > 0)
            {
                SaveData();
            }
        }

        private void SaveData() => Interface.Oxide.DataFileSystem.WriteObject(Name, _pluginData);

        private void ClearData() => Interface.Oxide.DataFileSystem.WriteObject(Name, new StoredData());

        private class StoredData
        {
            [JsonProperty("PlayerData")]
            public Dictionary<string, PlayerData> PlayerData = new Dictionary<string, PlayerData>();
        }

        private class PlayerData
        {
            [JsonProperty("BombTrucks")]
            public List<PlayerTruckData> BombTrucks = new List<PlayerTruckData>();

            [JsonProperty("Cooldowns")]
            public Dictionary<string, long> Cooldowns = new Dictionary<string, long>();

            public void UpdateCooldown(string truckName, long time)
            {
                if (Cooldowns.ContainsKey(truckName))
                {
                    Cooldowns[truckName] = time;
                }
                else
                {
                    Cooldowns.Add(truckName, time);
                }
            }

            public int GetTruckCount(string truckName) =>
                BombTrucks.Count(truckData => truckData.Tracked && truckData.Name == truckName);

            public PlayerTruckData FindTruck(ulong netID) =>
                BombTrucks.FirstOrDefault(truckData => truckData.ID == netID);

            public void RemoveTruck(ulong netID)
            {
                BombTrucks.RemoveAll(truckData => truckData.ID == netID);
            }
        }

        private class PlayerTruckData
        {
            [JsonProperty("ID")]
            public ulong ID;

            [JsonProperty("Name")]
            public string Name;

            [JsonProperty("Tracked")]
            public bool Tracked = true;
        }

        #endregion

        #region Configuration

        private TruckConfig GetTruckConfig(string truckName) =>
            _pluginConfig.BombTrucks.FirstOrDefault(truckConfig => truckConfig.Name.ToLower() == truckName.ToLower());

        private class Configuration : SerializableConfiguration
        {
            [JsonProperty("BombTrucks")]
            public TruckConfig[] BombTrucks = new TruckConfig[0];

            [JsonProperty("NoEscapeSettings")]
            public NoEscapeSettings NoEscapeSettings = new NoEscapeSettings();
            
            [JsonProperty("ZoneManagerSettings")]
            public ZoneManagerSettings ZoneManagerSettings = new ZoneManagerSettings();
        }

        private class TruckConfig
        {
            [JsonProperty("Name")]
            public string Name;

            [JsonProperty("CooldownSeconds")]
            public long CooldownSeconds;

            [JsonProperty("SpawnLimitPerPlayer")]
            public int SpawnLimit = 1;

            [JsonProperty("AttachRFReceiver")]
            public bool AttachRFReceiver = true;

            private int _enginePartsTier = 1;

            [JsonProperty("EnginePartsTier")]
            public int EnginePartsTier
            {
                get { return _enginePartsTier; }
                set { _enginePartsTier = Math.Min(Math.Max(value, 1), 3); }
            }

            [JsonProperty("Modules")]
            public object[] Modules =
            {
                "vehicle.1mod.cockpit.with.engine",
                "vehicle.2mod.fuel.tank"
            };

            [JsonProperty("ExplosionSettings")]
            public ExplosionSpec ExplosionSpec = new ExplosionSpec();
        }

        private Configuration GetDefaultConfig()
        {
            return new Configuration
            {
                BombTrucks = new[]
                {
                    new TruckConfig
                    {
                        Name = DefaultTruckConfigName,
                        EnginePartsTier = 3,
                        CooldownSeconds = 3600,
                        SpawnLimit = 3,
                        ExplosionSpec = new ExplosionSpec
                        {
                            BlastRadiusMult = 1,
                            DamageMult = 4.0f,
                            DensityCoefficient = 1,
                            DensityExponent = Math.Round(1.8f * 100) / 100,
                            Radius = 5,
                            Speed = 10,
                        }
                    },
                    new TruckConfig
                    {
                        Name = "Nuke",
                        EnginePartsTier = 1,
                        CooldownSeconds = 10800,
                        SpawnLimit = 1,
                        Modules = new object[]
                        {
                            "vehicle.1mod.engine",
                            "vehicle.1mod.cockpit.armored",
                            "vehicle.2mod.fuel.tank"
                        },
                        ExplosionSpec = new ExplosionSpec
                        {
                            BlastRadiusMult = 1,
                            DamageMult = 6.0f,
                            DensityCoefficient = 1,
                            DensityExponent = Math.Round(1.6f * 100) / 100,
                            Radius = 15,
                            Speed = 10,
                        }
                    }
                }
            };
        }

        private class ExplosionSpec
        {
            private double _speed = 10;
            private double _densityCoefficient = 1;
            private double _densityExponent = 2;

            [JsonProperty("Radius")]
            public double Radius = 10;

            [JsonProperty("DensityCoefficient")]
            public double DensityCoefficient
            {
                get { return _densityCoefficient; }
                set { _densityCoefficient = Math.Max(value, 0.01); }
            }

            [JsonProperty("DensityExponent")]
            public double DensityExponent
            {
                get { return _densityExponent; }
                set { _densityExponent = Math.Min(Math.Max(value, 1), 3); }
            }

            [JsonProperty("Speed")]
            public double Speed
            {
                get { return _speed; }
                set { _speed = Math.Max(value, 0.1); }
            }

            [JsonProperty("BlastRadiusMult")]
            public float BlastRadiusMult = 1;

            [JsonProperty("DamageMult")]
            public float DamageMult = 1;
        }

        private class NoEscapeSettings
        {
            [JsonProperty("CanSpawnWhileRaidBlocked")]
            public bool CanSpawnWhileRaidBlocked = true;

            [JsonProperty("CanSpawnWhileCombatBlocked")]
            public bool CanSpawnWhileCombatBlocked = true;
        }

        private class ZoneManagerSettings
        {
            [JsonProperty("CanSpawnInZones")]
            public bool CanSpawnInZones = false;
        }

        #endregion

        #region Configuration Boilerplate

        private class SerializableConfiguration
        {
            public string ToJson() => JsonConvert.SerializeObject(this);

            public Dictionary<string, object> ToDictionary() => JsonHelper.Deserialize(ToJson()) as Dictionary<string, object>;
        }

        private static class JsonHelper
        {
            public static object Deserialize(string json) => ToObject(JToken.Parse(json));

            private static object ToObject(JToken token)
            {
                switch (token.Type)
                {
                    case JTokenType.Object:
                        return token.Children<JProperty>().ToDictionary(prop => prop.Name,
                                                  prop => ToObject(prop.Value));

                    case JTokenType.Array:
                        return token.Select(ToObject).ToList();

                    default:
                        return ((JValue)token).Value;
                }
            }
        }

        private bool MaybeUpdateConfig(SerializableConfiguration config)
        {
            var currentWithDefaults = config.ToDictionary();
            var currentRaw = Config.ToDictionary(x => x.Key, x => x.Value);
            return MaybeUpdateConfigDict(currentWithDefaults, currentRaw);
        }

        private bool MaybeUpdateConfigDict(Dictionary<string, object> currentWithDefaults, Dictionary<string, object> currentRaw)
        {
            bool changed = false;

            foreach (var key in currentWithDefaults.Keys)
            {
                object currentRawValue;
                if (currentRaw.TryGetValue(key, out currentRawValue))
                {
                    var defaultDictValue = currentWithDefaults[key] as Dictionary<string, object>;
                    var cu
// ... [Code preview truncated due to length. Full source in attached file] ...

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