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

FREE VehicleMines : 1.0.23 1.0.23

Нет прав для скачивания
C#:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using Rust;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("Vehicle Mines", "Form23 Guy", "1.0.23")]
    [Description("Loot-only anti-vehicle mines, sea mines, and depth charges with custom placement, triggering, salvage, and crate injection.")]
    public class VehicleMines : RustPlugin
    {
        [PluginReference]
        private Plugin AlphaLoot, BetterLoot, MagicLoot, ImageLibrary;

        private const string PermissionAdmin = "vehiclemines.admin";
        private const string AntiVehiclePrefab = "assets/prefabs/deployable/landmine/landmine.prefab";
        private const string DepthChargeUiName = "VehicleMines.DepthCharge";
        private const string MineDataFileName = "VehicleMines/Mines";
        private const string AntiVehicleIconImageName = "vehiclemines.icon.antivehicle";
        private const string SeaMineIconImageName = "vehiclemines.icon.sea";
        private const string DepthChargeIconImageName = "vehiclemines.icon.depthcharge";
        private const float DefaultAutomaticMineDecaySeconds = 8f * 60f * 60f;

        private PluginConfig _config;
        private MineDatabase _mineDatabase = new MineDatabase();
        private readonly HashSet<MineController> _controllers = new HashSet<MineController>();
        private readonly List<MineController> _controllerSchedule = new List<MineController>();
        private readonly Dictionary<ulong, MineController> _controllersByNetworkId = new Dictionary<ulong, MineController>();
        private readonly Dictionary<ulong, MineRecord> _mineRecordsByNetworkId = new Dictionary<ulong, MineRecord>();
        private readonly Dictionary<ulong, ulong> _mineOwnersByNetworkId = new Dictionary<ulong, ulong>();
        private readonly List<ulong> _mineDatabaseRecordIdBuffer = new List<ulong>();
        private readonly List<ulong> _mineDatabaseCleanupIdBuffer = new List<ulong>();
        private readonly List<ulong> _mineDatabaseCleanupOwnerBuffer = new List<ulong>();
        private readonly List<ulong> _mineDatabaseEmptyOwnerBuffer = new List<ulong>();
        private readonly List<BaseEntity> _mineRemovalEntityBuffer = new List<BaseEntity>();
        private readonly HashSet<ulong> _mineRemovalNetworkIdBuffer = new HashSet<ulong>();
        private readonly Dictionary<ulong, float> _placementCooldowns = new Dictionary<ulong, float>();
        private readonly Dictionary<ulong, float> _adminCommandCooldowns = new Dictionary<ulong, float>();
        private readonly Dictionary<ulong, PendingDepthChargePlacement> _pendingDepthChargePlacements = new Dictionary<ulong, PendingDepthChargePlacement>();
        private readonly Dictionary<ulong, int> _lootInjectionSequences = new Dictionary<ulong, int>();
        private readonly HashSet<ulong> _lootSpawnObservedForRescan = new HashSet<ulong>();
        private readonly HashSet<BaseCombatEntity> _damagedCharacterBuffer = new HashSet<BaseCombatEntity>();
        private readonly List<BaseEntity> _resolveEntityChain = new List<BaseEntity>(32);
        private readonly HashSet<BaseEntity> _resolveVisited = new HashSet<BaseEntity>();
        private readonly List<BaseEntity> _resolveTransformParents = new List<BaseEntity>(16);
        private readonly List<BaseEntity> _resolveChildren = new List<BaseEntity>(32);
        private readonly List<BasePlayer> _mountedPlayerBuffer = new List<BasePlayer>(8);
        private readonly Dictionary<uint, string> _entityMatchTextCache = new Dictionary<uint, string>();
        private Collider[] _triggerColliderBuffer = new Collider[128];
        private Collider[] _characterColliderBuffer = new Collider[128];
        private Timer _mineScanTimer;
        private Timer _mineDecayTimer;
        private Timer _mineDataSaveTimer;
        private Timer _mineDatabaseIntegrityTimer;
        private Timer _queuedMineDataSaveTimer;
        private int _nextControllerScanIndex;
        private int _nextLootInjectionSequence;
        private int _nextDepthChargePlacementSequence;
        private int _lootModRescanSequence;
        private bool _trackLootSpawnsForRescan;
        private bool _mineDatabaseDirty;
        private bool _mineDatabaseIntegrityCheckRunning;
        private bool _mineRuntimeHooksSubscribed;
        private bool _isUnloading;
        private bool _isRemovingAllMines;
        private bool _triggerColliderBufferWarningIssued;
        private bool _characterColliderBufferWarningIssued;
        private bool _reflectionFreeConfigWarningIssued;
        private string[] _antiVehicleTokens = new string[0];
        private string[] _seaVehicleTokens = new string[0];
        private string[] _depthChargeTokens = new string[0];
        private string[] _npcVehicleTokens = new string[0];
        private ItemDefinition _antiVehicleItemDefinition;
        private ItemDefinition _seaItemDefinition;
        private ItemDefinition _depthChargeItemDefinition;

        private enum MineKind
        {
            AntiVehicle,
            Sea,
            DepthCharge
        }

        private enum WaterBodyKind
        {
            Unknown,
            Ocean,
            Inland
        }

        private class PendingDepthChargePlacement
        {
            public int Sequence;
            public Vector3 SurfacePoint;
            public Vector3 PlayerStartPosition;
            public float WaterSurfaceY;
            public float SeabedY;
            public float WaterDepth;
            public float MinimumDepth;
            public float MaximumDepth;
            public float SelectedDepth;
            public float YawDegrees;
            public float ExpiresAt;
        }

        private class SafetySettings
        {
            [JsonProperty("VMGive Maximum Amount Per Command")]
            public int MaximumGiveAmount = 100;

            [JsonProperty("VMGive Command Cooldown Seconds")]
            public float GiveCommandCooldownSeconds = 1f;

            [JsonProperty("Maximum Active Mines Server Wide - 0 Disables Limit")]
            public int MaximumActiveMines = 500;

            [JsonProperty("Maximum Active Mines Per Player - 0 Disables Limit")]
            public int MaximumMinesPerPlayer = 50;

            [JsonProperty("Maximum Mine Quantity Injected Into One Crate")]
            public int MaximumLootQuantityPerCrate = 10;

            [JsonProperty("Minimum Allowed Mine Scan Interval Seconds")]
            public float MinimumScanIntervalSeconds = 0.2f;

            [JsonProperty("Maximum Allowed Mine Trigger Radius Metres")]
            public float MaximumTriggerRadius = 8f;

            [JsonProperty("Maximum Allowed Character Damage Radius Metres")]
            public float MaximumCharacterDamageRadius = 10f;
        }

        private class PerformanceSettings
        {
            [JsonProperty("Central Mine Scheduler Tick Seconds")]
            public float SchedulerTickSeconds = 0.1f;

            [JsonProperty("Maximum Mine Scans Per Scheduler Tick")]
            public int MaximumMineScansPerTick = 64;

            [JsonProperty("Trigger Collider Buffer Size")]
            public int TriggerColliderBufferSize = 128;

            [JsonProperty("Character Damage Collider Buffer Size")]
            public int CharacterColliderBufferSize = 128;
        }

        private class ImageLibraryIconSettings
        {
            [JsonProperty("Enabled - Optional ImageLibrary Integration")]
            public bool Enabled = true;

            [JsonProperty("Scope Note")]
            public string ScopeNote = "ImageLibrary icons are used only in Vehicle Mines custom CUI. They cannot replace icons inside Rust's native inventory grid; native inventory artwork still requires a valid Workshop skin ID.";

            [JsonProperty("Anti-Vehicle Mine Custom Icon URL - Blank Uses Available Workshop Skin Image")]
            public string AntiVehicleIconUrl = string.Empty;

            [JsonProperty("Sea Mine Custom Icon URL - Blank Uses Available Workshop Skin Image")]
            public string SeaMineIconUrl = string.Empty;

            [JsonProperty("Depth Charge Custom Icon URL - Blank Uses Available Workshop Skin Image")]
            public string DepthChargeIconUrl = string.Empty;

            [JsonProperty("Show Depth Charge Icon In Deployment UI")]
            public bool ShowDepthChargeIconInDeploymentUi = true;
        }

        private class PersistenceSettings
        {
            [JsonProperty("Use Mine Location Database In oxide/data Instead Of Full Server Entity Scan")]
            public bool UseMineLocationDatabase = true;

            [JsonProperty("Performance Warning")]
            public string PerformanceWarning = "Setting the database option to false forces a full BaseNetworkable.serverEntities scan on every plugin/server load. This is supported but is NOT PERFORMANT on large servers.";

            [JsonProperty("Run One-Time Legacy Mine Scan When Database Has Never Been Built")]
            public bool RunOneTimeLegacyMigration = true;

            [JsonProperty("Remove All Placed Mines On Plugin Load, Reload, And Unload")]
            public bool RemoveAllMinesOnLoadReloadAndUnload = true;

            [JsonProperty("Automatic Mine Decay Seconds - 0 Disables")]
            public float AutomaticDecaySeconds = VehicleMines.DefaultAutomaticMineDecaySeconds;

            [JsonProperty("Mine Decay Check Interval Seconds")]
            public float DecayCheckIntervalSeconds = 30f;

            [JsonProperty("Mine Database Save Interval Seconds")]
            public float DatabaseSaveIntervalSeconds = 60f;

            [JsonProperty("Check Mine Database After Every Plant, Detonation, Decay, Salvage, Or Destruction")]
            public bool CheckAfterMineStateChange = true;

            [JsonProperty("Mine Database Integrity Check Interval Seconds - 0 Disables")]
            public float DatabaseIntegrityCheckIntervalSeconds = 60f;
        }

        private class MineDatabase
        {
            [JsonProperty("Schema Version")]
            public int SchemaVersion = 2;

            [JsonProperty("Initial Legacy Scan Completed")]
            public bool InitialLegacyScanCompleted;

            [JsonProperty("Remove-All Startup Migration Completed")]
            public bool RemoveAllStartupMigrationCompleted;

            [JsonProperty("Last Updated UTC")]
            public string LastUpdatedUtc = string.Empty;

            [JsonProperty("Players")]
            public Dictionary<ulong, PlayerMineRecords> Players = new Dictionary<ulong, PlayerMineRecords>();
        }

        private class PlayerMineRecords
        {
            [JsonProperty("Mines")]
            public Dictionary<ulong, MineRecord> Mines = new Dictionary<ulong, MineRecord>();
        }

        private class MineRecord
        {
            [JsonProperty("Entity Network ID")]
            public ulong NetworkId;

            [JsonProperty("Mine Type")]
            public string MineType = string.Empty;

            [JsonProperty("Prefab")]
            public string Prefab = string.Empty;

            [JsonProperty("Position")]
            public StoredPosition Position = new StoredPosition();

            [JsonProperty("Placed At UTC Unix Seconds")]
            public long PlacedAtUtc;

            [JsonProperty("Expires At UTC Unix Seconds - 0 Means No Decay")]
            public long ExpiresAtUtc;

            [JsonProperty("Seconds Until Death Or Decay")]
            public long SecondsUntilDecay;
        }

        private class StoredPosition
        {
            [JsonProperty("X")]
            public float X;

            [JsonProperty("Y")]
            public float Y;

            [JsonProperty("Z")]
            public float Z;

            public StoredPosition()
            {
            }

            public StoredPosition(Vector3 position)
            {
                X = position.x;
                Y = position.y;
                Z = position.z;
            }
        }

        #region Configuration

        private class PluginConfig
        {
            [JsonProperty("Items")]
            public ItemSettings Items = new ItemSettings();

            [JsonProperty("Placement")]
            public PlacementSettings Placement = new PlacementSettings();

            [JsonProperty("Administration And Safety Limits")]
            public SafetySettings Safety = new SafetySettings();

            [JsonProperty("Performance")]
            public PerformanceSettings Performance = new PerformanceSettings();

            [JsonProperty("Mine Database And Automatic Decay")]
            public PersistenceSettings Persistence = new PersistenceSettings();

            [JsonProperty("ImageLibrary Custom UI Icons")]
            public ImageLibraryIconSettings ImageLibraryIcons = new ImageLibraryIconSettings();

            [JsonProperty("PvE And NPC Vehicle Compatibility")]
            public CompatibilitySettings Compatibility = new CompatibilitySettings();

            [JsonProperty("Anti-Vehicle Mine")]
            public MineSettings AntiVehicle = MineSettings.CreateAntiVehicle();

            [JsonProperty("Sea Mine")]
            public MineSettings Sea = MineSettings.CreateSea();

            [JsonProperty("Depth Charge")]
            public MineSettings DepthCharge = MineSettings.CreateDepthCharge();

            [JsonProperty("Salvage")]
            public SalvageSettings Salvage = new SalvageSettings();

            [JsonProperty("Loot Settings")]
            public LootSettings Loot = new LootSettings();
        }

        private class ItemSettings
        {
            [JsonProperty("Anti-Vehicle Mine Base Item Shortname")]
            public string AntiVehicleBaseShortname = "trap.landmine";

            [JsonProperty("Anti-Vehicle Mine Display Name")]
            public string AntiVehicleDisplayName = "Anti-Vehicle Mine";

            [JsonProperty("Anti-Vehicle Mine Skin ID")]
            public ulong AntiVehicleSkinId = 9100000000001UL;

            [JsonProperty("Anti-Vehicle Craft Skin Inventory ID - 0 Disabled")]
            public int AntiVehicleCraftSkinInventoryId = 0;

            [JsonProperty("Sea Mine Base Item Shortname")]
            public string SeaBaseShortname = "trap.landmine";

            [JsonProperty("Sea Mine Display Name")]
            public string SeaDisplayName = "Sea Mine";

            [JsonProperty("Sea Mine Skin ID")]
            public ulong SeaSkinId = 9100000000002UL;

            [JsonProperty("Sea Mine Craft Skin Inventory ID - 0 Disabled")]
            public int SeaCraftSkinInventoryId = 0;

            [JsonProperty("Depth Charge Base Item Shortname")]
            public string DepthChargeBaseShortname = "trap.landmine";

            [JsonProperty("Depth Charge Display Name")]
            public string DepthChargeDisplayName = "Depth Charge";

            [JsonProperty("Depth Charge Skin ID")]
            public ulong DepthChargeSkinId = 9100000000003UL;

            [JsonProperty("Depth Charge Craft Skin Inventory ID - 0 Disabled")]
            public int DepthChargeCraftSkinInventoryId = 0;

            [JsonProperty("Note About Icons")]
            public string IconNote = "Workshop skin IDs control native inventory artwork. Optional ImageLibrary URLs are used only in Vehicle Mines custom CUI.";
        }

        private class PlacementSettings
        {
            [JsonProperty("Use Primary Fire To Place Custom Mines")]
            public bool UsePrimaryFire = true;

            [JsonProperty("Placement Distance")]
            public float Distance = 6f;

            [JsonProperty("Placement Cooldown Seconds")]
            public float CooldownSeconds = 0.35f;

            [JsonProperty("Anti-Vehicle Mine Surface Offset")]
            public float AntiVehicleSurfaceOffset = 0.025f;

            [JsonProperty("Sea Mine Waterline Offset")]
            public float SeaWaterlineOffset = -0.2f;

            [JsonProperty("Sea Mine Barrel Pitch Degrees")]
            public float SeaBarrelPitchDegrees = 90f;

            [JsonProperty("Sea Mine Roll Degrees", NullValueHandling = NullValueHandling.Ignore)]
            public float? LegacySeaBarrelRollDegrees;

            [JsonProperty("Sea Mine Minimum Water Depth")]
            public float SeaMinimumWaterDepth = 0.5f;

            [JsonProperty("Use Fixed Ocean Sea Level For Sea Mines And Depth Charges")]
            public bool UseFixedOceanSeaLevel = true;

            [JsonProperty("Fixed Ocean Sea Level World Y")]
            public float FixedOceanSeaLevelY = 0f;

            [JsonProperty("Water Placement Note")]
            public string WaterPlacementNote = "Ocean, lake, river, and supported custom water volumes are allowed when the measured water column meets the configured minimum depth.";

            [JsonProperty("Depth Charge Minimum Water Column Metres")]
            public float DepthChargeMinimumWaterDepth = 10f;

            [JsonProperty("Depth Charge Default Deployment Depth Metres")]
            public float DepthChargeDefaultDepth = 10f;

            [JsonProperty("Depth Charge Minimum Selectable Depth Metres")]
            public float DepthChargeMinimumSelectableDepth = 5f;

            [JsonProperty("Depth Charge Seabed Clearance Metres")]
            public float DepthChargeSeabedClearance = 0.25f;

            [JsonProperty("Depth Charge Placement Confirmation Timeout Seconds")]
            public float DepthChargePlacementTimeoutSeconds = 20f;

            [JsonProperty("Depth Charge Maximum Player Movement Before Confirmation Metres")]
            public float DepthChargeMaximumPlayerMovement = 3f;

            [JsonProperty("Depth Charge Maximum Seabed Raycast Distance Metres")]
            public float DepthChargeMaximumRaycastDepth = 500f;

            [JsonProperty("Dry Surface Water Tolerance")]
            public float DrySurfaceWaterTolerance = 0.05f;
        }

        private class CompatibilitySettings
        {
            [JsonProperty("Override TruePVE Vehicle Damage Blocking")]
            public bool OverrideTruePVEVehicleDamage = true;

            [JsonProperty("NPC Vehicle Override - Allow Triggering And Forced Damage")]
            public bool NpcVehicleOverride = true;

            [JsonProperty("NPC Vehicle Match Tokens", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> NpcVehicleTokens = new List<string>
            {
                "bradley",
                "scientistboat",
                "scientist_boat",
                "npcboat",
                "npc_boat",
                "patrolboat",
                "patrol_boat",
                "ptboat",
                "pt_boat"
            };
        }

        private class MineSettings
        {
            [JsonProperty("Enabled")]
            public bool Enabled = true;

            [JsonProperty("Deployed Prefab")]
            public string DeployedPrefab;

            [JsonProperty("Arm Delay Seconds")]
            public float ArmDelaySeconds;

            [JsonProperty("Scan Interval Seconds")]
            public float ScanIntervalSeconds;

            [JsonProperty("Trigger Radius")]
            public float TriggerRadius;

            [JsonProperty("Direct Vehicle Damage")]
            public float DirectVehicleDamage;

            [JsonProperty("Bradley Direct Damage")]
            public float BradleyDamage;

            [JsonProperty("Use Native Explosive Damage On Triggered Vehicle")]
            public bool UseNativeExplosiveDamage = false;

            [JsonProperty("Native Explosive Profile - C4 or Satchel")]
            public string NativeExplosiveProfile = "C4";

            [JsonProperty("Native Explosions Applied To Triggered Vehicle")]
            public int NativeExplosions = 0;

            [JsonProperty("Use Server-Owned Damage To Bypass PvE Vehicle Protection")]
            public bool UseServerOwnedVehicleDamage = true;

            [JsonProperty("Force Health Reduction If Damage Hooks Block Vehicle Damage")]
            public bool ForceHealthReductionIfBlocked = true;

            [JsonProperty("Vehicle Damage Verification Delay Seconds")]
            public float DamageVerificationDelaySeconds = 0.1f;

            [JsonProperty("Log Vehicle Damage Diagnostics")]
            public bool LogVehicleDamageDiagnostics = false;

            [JsonProperty("Use Native C4 Damage On Triggered Vehicle", NullValueHandling = NullValueHandling.Ignore)]
            public bool? LegacyUseNativeC4Damage;

            [JsonProperty("Native C4 Explosions Applied To Triggered Vehicle", NullValueHandling = NullValueHandling.Ignore)]
            public int? LegacyNativeC4Explosions;

            [JsonProperty("Occupant And Nearby Character Damage")]
            public float CharacterDamage;

            [JsonProperty("Character Damage Radius")]
            public float CharacterDamageRadius;

            [JsonProperty("Explosion Effect Prefab")]
            public string ExplosionEffect;

            [JsonProperty("Sympathetic Detonation From Explosive Damage")]
            public bool SympatheticDetonation = true;

            [JsonProperty("Destroy From Ordinary Damage Without Salvage")]
            public bool DestroyFromOrdinaryDamage = true;

            [JsonProperty("Vehicle Match Tokens", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> VehicleTokens = new List<string>();

            public static MineSettings CreateAntiVehicle()
            {
                return new MineSettings
                {
                    Enabled = true,
                    DeployedPrefab = AntiVehiclePrefab,
                    ArmDelaySeconds = 3f,
                    ScanIntervalSeconds = 0.2f,
                    TriggerRadius = 2.25f,
                    DirectVehicleDamage = 750f,
                    BradleyDamage = 300f,
                    UseNativeExplosiveDamage = false,
                    NativeExplosiveProfile = "C4",
                    NativeExplosions = 0,
                    UseServerOwnedVehicleDamage = true,
                    ForceHealthReductionIfBlocked = true,
                    DamageVerificationDelaySeconds = 0.1f,
                    LogVehicleDamageDiagnostics = false,
                    CharacterDamage = 40f,
                    CharacterDamageRadius = 4f,
                    ExplosionEffect = "assets/prefabs/tools/c4/effects/c4_explosion.prefab",
                    SympatheticDetonation = true,
                    DestroyFromOrdinaryDamage = true,
                    VehicleTokens = new List<string>
                    {
                        "bradleyapc",
                        "modularcar",
                        "snowmobile",
                        "motorbike",
                        "motorcycle",
                        "pedalbike",
                        "bicycle",
                        "workcart",
                        "locomotive",
                        "traincar"
                    }
                };
            }

            public static MineSettings CreateSea()
            {
                return new MineSettings
                {
                    Enabled = true,
                    DeployedPrefab = "assets/bundled/prefabs/radtown/oil_barrel.prefab",
                    ArmDelaySeconds = 3f,
                    ScanIntervalSeconds = 0.2f,
                    TriggerRadius = 3f,
                    DirectVehicleDamage = 900f,
                    BradleyDamage = 0f,
                    UseNativeExplosiveDamage = false,
                    NativeExplosiveProfile = "Satchel",
                    NativeExplosions = 0,
                    UseServerOwnedVehicleDamage = true,
                    ForceHealthReductionIfBlocked = true,
                    DamageVerificationDelaySeconds = 0.1f,
                    LogVehicleDamageDiagnostics = false,
                    CharacterDamage = 45f,
                    CharacterDamageRadius = 5f,
                    ExplosionEffect = "assets/prefabs/tools/c4/effects/c4_explosion.prefab",
                    SympatheticDetonation = true,
                    DestroyFromOrdinaryDamage = true,
                    VehicleTokens = new List<string>
                    {
                        "rowboat",
                        "rhib",
                        "ptboat",
                        "pt_boat",
                        "patrolboat",
                        "patrol_boat",
                        "tugboat",
                        "submarine",
                        "submarineduo",
                        "submarinesolo",
                        "kayak"
                    }
                };
            }

            public static MineSettings CreateDepthCharge()
            {
                return new MineSettings
                {
                    Enabled = true,
                    DeployedPrefab = "assets/bundled/prefabs/radtown/oil_barrel.prefab",
                    ArmDelaySeconds = 3f,
                    ScanIntervalSeconds = 0.2f,
                    TriggerRadius = 4f,
                    DirectVehicleDamage = 1200f,
                    BradleyDamage = 0f,
                    UseNativeExplosiveDamage = false,
                    NativeExplosiveProfile = "C4",
                    NativeExplosions = 0,
                    UseServerOwnedVehicleDamage = true,
                    ForceHealthReductionIfBlocked = true,
                    DamageVerificationDelaySeconds = 0.1f,
                    LogVehicleDamageDiagnostics = false,
                    CharacterDamage = 55f,
                    CharacterDamageRadius = 5f,
                    ExplosionEffect = "assets/prefabs/tools/c4/effects/c4_explosion.prefab",
                    SympatheticDetonation = true,
                    DestroyFromOrdinaryDamage = true,
                    VehicleTokens = new List<string>
                    {
                        "submarine",
                        "submarineduo",
                        "submarinesolo"
                    }
                };
            }
        }

        private class SalvageSettings
        {
            [JsonProperty("Required Tool Shortname")]
            public string RequiredToolShortname = "hammer.salvaged";

            [JsonProperty("Required Hammer Hits")]
            public int RequiredHits = 1;

            [JsonProperty("Drop Resources Into World")]
            public bool DropIntoWorld = true;

            [JsonProperty("Anti-Vehicle Mine Drops", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<ResourceDrop> AntiVehicleDrops = new List<ResourceDrop>
            {
                new ResourceDrop("explosives", 2),
                new ResourceDrop("lowgradefuel", 20),
                new ResourceDrop("metal.refined", 30)
            };

            [JsonProperty("Sea Mine Drops", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<ResourceDrop> SeaDrops = new List<ResourceDrop>
            {
                new ResourceDrop("explosives", 4),
                new ResourceDrop("lowgradefuel", 50),
                new ResourceDrop("metal.fragments", 200),
                new ResourceDrop("metal.refined", 20)
            };

            [JsonProperty("Depth Charge Drops", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<ResourceDrop> DepthChargeDrops = new List<ResourceDrop>
            {
                new ResourceDrop("explosives", 5),
                new ResourceDrop("lowgradefuel", 60),
                new ResourceDrop("metal.fragments", 250),
                new ResourceDrop("metal.refined", 25)
            };
        }

        private class ResourceDrop
        {
            [JsonProperty("Item Shortname")]
            public string Shortname;

            [JsonProperty("Amount")]
            public int Amount;

            public ResourceDrop()
            {
            }

            public ResourceDrop(string shortname, int amount)
            {
                Shortname = shortname;
                Amount = amount;
            }
        }

        private class LootSettings
        {
            [JsonProperty("Enabled")]
            public bool Enabled = true;

            [JsonProperty("Prevent Duplicate Injection")]
            public bool PreventDuplicateInjection = true;

            [JsonProperty("Drop Overflow Beside Crate")]
            public bool DropOverflowBesideCrate = true;

            [JsonProperty("Loot Mod Compatibility")]
            public LootModCompatibilitySettings ModCompatibility = new LootModCompatibilitySettings();

            [JsonProperty("Crates")]
            public Dictionary<string, CrateSettings> Crates = CreateDefaults();

            private static Dictionary<string, CrateSettings> CreateDefaults()
            {
                return new Dictionary<string, CrateSettings>
                {
                    ["Locked Crate"] = new CrateSettings
                    {
                        Enabled = true,
                        PrefabMatchTokens = new List<string> { "codelockedhackablecrate", "hackablelockedcrate" },
                        SpawnChancePercent = 15f,
                        MinimumAmount = 1,
                        MaximumAmount = 3,
                        RecommendedMaximum = 3,
                        QuantityOverride = 0,
                        AntiVehicleMineWeight = 70f,
                        SeaMineWeight = 30f,
                        DepthChargeWeight = 15f
                    },
                    ["Elite Crate"] = new CrateSettings
                    {
                        Enabled = true,
                        PrefabMatchTokens = new List<string> { "crate_elite" },
                        SpawnChancePercent = 10f,
                        MinimumAmount = 1,
                        MaximumAmount = 5,
                        RecommendedMaximum = 5,
                        QuantityOverride = 0,
                        AntiVehicleMineWeight = 70f,
                        SeaMineWeight = 30f,
                        DepthChargeWeight = 15f
                    },
                    ["Green Military Crate"] = new CrateSettings
                    {
                        Enabled = true,
                        PrefabMatchTokens = new List<string> { "crate_normal_2" },
                        SpawnChancePercent = 5f,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                        RecommendedMaximum = 1,
                        QuantityOverride = 0,
                        AntiVehicleMineWeight = 80f,
                        SeaMineWeight = 20f,
                        DepthChargeWeight = 15f
                    },
                    ["Tool Crate"] = new CrateSettings
                    {
                        Enabled = true,
                        PrefabMatchTokens = new List<string> { "crate_tools" },
                        SpawnChancePercent = 3f,
                        MinimumAmount = 1,
                        MaximumAmount = 1,
                        RecommendedMaximum = 1,
                        QuantityOverride = 0,
                        AntiVehicleMineWeight = 85f,
                        SeaMineWeight = 15f,
                        DepthChargeWeight = 15f
                    }
                };
            }
        }

        private class LootModCompatibilitySettings
        {
            [JsonProperty("Enabled")]
            public bool Enabled = true;

            [JsonProperty("Enable MagicLoot Compatibility")]
            public bool MagicLoot = true;

            [JsonProperty("Enable BetterLoot Compatibility")]
            public bool BetterLoot = true;

            [JsonProperty("Enable AlphaLoot Compatibility")]
            public bool AlphaLoot = true;

            [JsonProperty("Post-Population Injection Delay Seconds")]
            public float InjectionDelaySeconds = 0.1f;

            [JsonProperty("Inventory Ready Retry Count")]
            public int InventoryReadyRetryCount = 8;

            [JsonProperty("Inventory Ready Retry Interval Seconds")]
            public float InventoryReadyRetryIntervalSeconds = 0.25f;

            [JsonProperty("Reinject Existing Containers When A Supported Loot Mod Loads")]
            public bool ReinjectOnLootModLoad = true;

            [JsonProperty("MagicLoot Load Rescan Delay Seconds")]
            public float MagicLootRescanDelaySeconds = 2f;

            [JsonProperty("BetterLoot Load Rescan Delay Seconds")]
            public float BetterLootRescanDelaySeconds = 2f;

            [JsonProperty("AlphaLoot Load Rescan Delay Seconds")]
            public float AlphaLootRescanDelaySeconds = 22f;

            [JsonProperty("Startup Rescan For Direct-Population Loot Mods")]
            public bool StartupRescan = true;

            [JsonProperty("Startup Rescan Delay Seconds")]
            public float StartupRescanDelaySeconds = 3f;

            [JsonProperty("Expand Full Loot Containers For Mine Stacks")]
            public bool ExpandContainerCapacity = true;

            [JsonProperty("Maximum Container Capacity")]
            public int MaximumContainerCapacity = 36;

            [JsonProperty("Log Compatibility Activity")]
            public bool LogActivity = false;
        }

        private class CrateSettings
        {
            [JsonProperty("Enabled")]
            public bool Enabled = true;

            [JsonProperty("Prefab Name Match Tokens", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> PrefabMatchTokens = new List<string>();

            [JsonProperty("Spawn Chance Percent")]
            public float SpawnChancePercent;

            [JsonProperty("Minimum Combined Mine Quantity")]
            public int MinimumAmount = 1;

            [JsonProperty("Maximum Combined Mine Quantity")]
            public int MaximumAmount = 1;

            [JsonProperty("Recommended Maximum")]
            public int RecommendedMaximum = 1;

            [JsonProperty("Quantity Override - 0 Uses Min Max")]
            public int QuantityOverride;

            [JsonProperty("Anti-Vehicle Mine Weight")]
            public float AntiVehicleMineWeight = 50f;

            [JsonProperty("Sea Mine Weight")]
            public float SeaMineWeight = 50f;

            [JsonProperty("Depth Charge Weight")]
            public float DepthChargeWeight = 15f;
        }

        protected override void LoadDefaultConfig()
        {
            _config = new PluginConfig();
            ValidateConfig();
            SaveConfig();
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();

            try
            {
                _config = Config.ReadObject<PluginConfig>();
                if (_config == null)
                {
                    throw new JsonException("Configuration deserialized as null.");
                }
            }
            catch (Exception ex)
            {
                PrintError($"Configuration could not be read: {ex.Message}. A fresh configuration will be created.");
                _config = new PluginConfig();
            }

            ValidateConfig();
            SaveConfig();
        }

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

        private void ValidateConfig()
        {
            if (_config.Items == null) _config.Items = new ItemSettings();
            MigrateLegacyBaseItemShortnames();
            if (_config.Placement == null) _config.Placement = new PlacementSettings();
            if (_config.Safety == null) _config.Safety = new SafetySettings();
            if (_config.Performance == null) _config.Performance = new PerformanceSettings();
            if (_config.Persistence == null) _config.Persistence = new PersistenceSettings();
            if (_config.ImageLibraryIcons == null) _config.ImageLibraryIcons = new ImageLibraryIconSettings();
            if (_config.Compatibility == null) _config.Compatibility = new CompatibilitySettings();
            if (_config.Compatibility.NpcVehicleTokens == null) _config.Compatibility.NpcVehicleTokens = new CompatibilitySettings().NpcVehicleTokens;
            if (_config.AntiVehicle == null) _config.AntiVehicle = MineSettings.CreateAntiVehicle();
            if (_config.Sea == null) _config.Sea = MineSettings.CreateSea();
            if (_config.DepthCharge == null) _config.DepthCharge = MineSettings.CreateDepthCharge();
            if (_config.Salvage == null) _config.Salvage = new SalvageSettings();
            if (_config.Loot == null) _config.Loot = new LootSettings();
            if (_config.Loot.ModCompatibility == null) _config.Loot.ModCompatibility = new LootModCompatibilitySettings();
            if (_config.Loot.Crates == null) _config.Loot.Crates = new LootSettings().Crates;

            LootModCompatibilitySettings lootCompatibility = _config.Loot.ModCompatibility;
            lootCompatibility.InjectionDelaySeconds = Mathf.Clamp(lootCompatibility.InjectionDelaySeconds, 0f, 5f);
            lootCompatibility.InventoryReadyRetryCount = Mathf.Clamp(lootCompatibility.InventoryReadyRetryCount, 0, 20);
            lootCompatibility.InventoryReadyRetryIntervalSeconds = Mathf.Clamp(lootCompatibility.InventoryReadyRetryIntervalSeconds, 0.05f, 5f);
            lootCompatibility.MagicLootRescanDelaySeconds = Mathf.Clamp(lootCompatibility.MagicLootRescanDelaySeconds, 0f, 30f);
            lootCompatibility.BetterLootRescanDelaySeconds = Mathf.Clamp(lootCompatibility.BetterLootRescanDelaySeconds, 0f, 30f);
            lootCompatibility.AlphaLootRescanDelaySeconds = Mathf.Clamp(lootCompatibility.AlphaLootRescanDelaySeconds, 0f, 60f);
            lootCompatibility.StartupRescanDelaySeconds = Mathf.Clamp(lootCompatibility.StartupRescanDelaySeconds, 0f, 30f);
            lootCompatibility.MaximumContainerCapacity = Mathf.Clamp(lootCompatibility.MaximumContainerCapacity, 1, 36);

            _config.Safety.MaximumGiveAmount = Mathf.Clamp(_config.Safety.MaximumGiveAmount, 1, 1000);
            _config.Safety.GiveCommandCooldownSeconds = Mathf.Clamp(_config.Safety.GiveCommandCooldownSeconds, 0.1f, 60f);
            _config.Safety.MaximumActiveMines = Mathf.Clamp(_config.Safety.MaximumActiveMines, 0, 10000);
            _config.Safety.MaximumMinesPerPlayer = Mathf.Clamp(_config.Safety.MaximumMinesPerPlayer, 0, 1000);
            _config.Safety.MaximumLootQuantityPerCrate = Mathf.Clamp(_config.Safety.MaximumLootQuantityPerCrate, 1, 100);
            _config.Safety.MinimumScanIntervalSeconds = Mathf.Clamp(_config.Safety.MinimumScanIntervalSeconds, 0.1f, 5f);
            _config.Safety.MaximumTriggerRadius = Mathf.Clamp(_config.Safety.MaximumTriggerRadius, 1f, 20f);
            _config.Safety.MaximumCharacterDamageRadius = Mathf.Clamp(_config.Safety.MaximumCharacterDamageRadius, 1f, 20f);

            _config.Performance.SchedulerTickSeconds = Mathf.Clamp(_config.Performance.SchedulerTickSeconds, 0.05f, 1f);
            _config.Performance.MaximumMineScansPerTick = Mathf.Clamp(_config.Performance.MaximumMineScansPerTick, 1, 1000);
            _config.Performance.TriggerColliderBufferSize = Mathf.Clamp(_config.Performance.TriggerColliderBufferSize, 16, 1024);
            _config.Performance.CharacterColliderBufferSize = Mathf.Clamp(_config.Performance.CharacterColliderBufferSize, 16, 1024);

            if (_config.Persistence.PerformanceWarning == null)
            {
                _config.Persistence.PerformanceWarning = new PersistenceSettings().PerformanceWarning;
            }
            if (float.IsNaN(_config.Persistence.AutomaticDecaySeconds) || float.IsInfinity(_config.Persistence.AutomaticDecaySeconds))
            {
                _config.Persistence.AutomaticDecaySeconds = DefaultAutomaticMineDecaySeconds;
            }
            _config.Persistence.AutomaticDecaySeconds = Mathf.Clamp(_config.Persistence.AutomaticDecaySeconds, 0f, 31536000f);
            _config.Persistence.DecayCheckIntervalSeconds = Mathf.Clamp(_config.Persistence.DecayCheckIntervalSeconds, 5f, 600f);
            _config.Persistence.DatabaseSaveIntervalSeconds = Mathf.Clamp(_config.Persistence.DatabaseSaveIntervalSeconds, 5f, 600f);
            _config.Persistence.DatabaseIntegrityCheckIntervalSeconds = Mathf.Clamp(_config.Persistence.DatabaseIntegrityCheckIntervalSeconds, 0f, 3600f);
            if (_config.Persistence.DatabaseIntegrityCheckIntervalSeconds > 0f && _config.Persistence.DatabaseIntegrityCheckIntervalSeconds < 10f)
            {
                _config.Persistence.DatabaseIntegrityCheckIntervalSeconds = 10f;
            }

            if (!_config.Persistence.UseMineLocationDatabase)
            {
                PrintWarning("Mine location database is disabled. Vehicle Mines will scan BaseNetworkable.serverEntities on every load; this mode is supported but NOT PERFORMANT on large servers.");
            }

            if (_config.Persistence.RemoveAllMinesOnLoadReloadAndUnload)
            {
                PrintWarning("Remove-all lifecycle mode is enabled. Deployed Vehicle Mines and their database records are purged on plugin load, reload, and unload instead of being restored.");
            }

            _config.ImageLibraryIcons.ScopeNote = string.IsNullOrWhiteSpace(_config.ImageLibraryIcons.ScopeNote)
                ? new ImageLibraryIconSettings().ScopeNote
                : _config.ImageLibraryIcons.ScopeNote.Trim();
            _config.ImageLibraryIcons.AntiVehicleIconUrl = (_config.ImageLibraryIcons.AntiVehicleIconUrl ?? string.Empty).Trim();
            _config.ImageLibraryIcons.SeaMineIconUrl = (_config.ImageLibraryIcons.SeaMineIconUrl ?? string.Empty).Trim();
            _config.ImageLibraryIcons.DepthChargeIconUrl = (_config.ImageLibraryIcons.DepthChargeIconUrl ?? string.Empty).Trim();

            if (_config.Items.AntiVehicleSkinId == 0UL || _config.Items.SeaSkinId == 0UL || _config.Items.DepthChargeSkinId == 0UL)
            {
                PrintWarning("All custom mine items should use non-zero, unique skin IDs. Placeholder IDs are acceptable for identity, but published Workshop skin IDs are required for custom inventory artwork.");
            }

            if (_config.Items.AntiVehicleSkinId == 0UL)
            {
                _config.Items.AntiVehicleSkinId = 9100000000001UL;
            }

            HashSet<ulong> configuredMineSkins = new HashSet<ulong> { _config.Items.AntiVehicleSkinId };
            if (_config.Items.SeaSkinId == 0UL || !configuredMineSkins.Add(_config.Items.SeaSkinId))
            {
                PrintError("The Sea Mine skin ID was zero or duplicated another mine type. Resetting it to a unique placeholder.");
                _config.Items.SeaSkinId = 9100000000002UL;
                while (!configuredMineSkins.Add(_config.Items.SeaSkinId))
                {
                    _config.Items.SeaSkinId++;
                }
            }

            if (_config.Items.DepthChargeSkinId == 0UL || !configuredMineSkins.Add(_config.Items.DepthChargeSkinId))
            {
                PrintError("The Depth Charge skin ID was zero or duplicated another mine type. Resetting it to a unique placeholder.");
                _config.Items.DepthChargeSkinId = 9100000000003UL;
                while (!configuredMineSkins.Add(_config.Items.DepthChargeSkinId))
                {
                    _config.Items.DepthChargeSkinId++;
                }
            }

            _config.Placement.Distance = Mathf.Clamp(_config.Placement.Distance, 1f, 20f);
            _config.Placement.CooldownSeconds = Mathf.Max(0.1f, _config.Placement.CooldownSeconds);
            if (_config.Placement.LegacySeaBarrelRollDegrees.HasValue)
            {
                _config.Placement.SeaBarrelPitchDegrees = _config.Placement.LegacySeaBarrelRollDegrees.Value;
                _config.Placement.LegacySeaBarrelRollDegrees = null;
            }
            _config.Placement.SeaBarrelPitchDegrees = Mathf.Repeat(_config.Placement.SeaBarrelPitchDegrees + 180f, 360f) - 180f;
            _config.Placement.SeaMinimumWaterDepth = Mathf.Max(0.05f, _config.Placement.SeaMinimumWaterDepth);
            if (float.IsNaN(_config.Placement.FixedOceanSeaLevelY) || float.IsInfinity(_config.Placement.FixedOceanSeaLevelY))
            {
                _config.Placement.FixedOceanSeaLevelY = 0f;
            }
            _config.Placement.FixedOceanSeaLevelY = Mathf.Clamp(_config.Placement.FixedOceanSeaLevelY, -1000f, 1000f);
            _config.Placement.DepthChargeMinimumWaterDepth = Mathf.Max(10f, _config.Placement.DepthChargeMinimumWaterDepth);
            _config.Placement.DepthChargeMinimumSelectableDepth = Mathf.Clamp(_config.Placement.DepthChargeMinimumSelectableDepth, 5f, _config.Placement.DepthChargeMinimumWaterDepth);
            _config.Placement.DepthChargeDefaultDepth = Mathf.Max(_config.Placement.DepthChargeMinimumSelectableDepth, _config.Placement.DepthChargeDefaultDepth);
            _config.Placement.DepthChargeSeabedClearance = Mathf.Clamp(_config.Placement.DepthChargeSeabedClearance, 0f, 2f);
            _config.Placement.DepthChargePlacementTimeoutSeconds = Mathf.Clamp(_config.Placement.DepthChargePlacementTimeoutSeconds, 5f, 120f);
            _config.Placement.DepthChargeMaximumPlayerMovement = Mathf.Clamp(_config.Placement.DepthChargeMaximumPlayerMovement, 0.5f, 20f);
            _config.Placement.DepthChargeMaximumRaycastDepth = Mathf.Clamp(_config.Placement.DepthChargeMaximumRaycastDepth, 25f, 2000f);
            _config.Placement.DrySurfaceWaterTolerance = Mathf.Clamp(_config.Placement.DrySurfaceWaterTolerance, 0f, 0.25f);
            _config.Salvage.RequiredHits = Mathf.Max(1, _config.Salvage.RequiredHits);

            DeduplicateStrings(_config.Compatibility.NpcVehicleTokens);
            DeduplicateStrings(_config.AntiVehicle.VehicleTokens);
            DeduplicateStrings(_config.Sea.VehicleTokens);
            DeduplicateStrings(_config.DepthCharge.VehicleTokens);
            DeduplicateDrops(_config.Salvage.AntiVehicleDrops);
            DeduplicateDrops(_config.Salvage.SeaDrops);
            DeduplicateDrops(_config.Salvage.DepthChargeDrops);

            ValidateMineSettings(_config.AntiVehicle, "Anti-Vehicle Mine");
            ValidateMineSettings(_config.Sea, "Sea Mine");
            ValidateMineSettings(_config.DepthCharge, "Depth Charge");
            ValidateDrops(_config.Salvage.AntiVehicleDrops, "Anti-Vehicle Mine");
            ValidateDrops(_config.Salvage.SeaDrops, "Sea Mine");
            ValidateDrops(_config.Salvage.DepthChargeDrops, "Depth Charge");

            // Iterate over a snapshot of the keys. Assigning a value through the dictionary
            // indexer changes the dictionary vers, even when replacing an existing key,
            // which invalidates a live Dictionary enum.
            foreach (string crateKey in _config.Loot.Crates.Keys.ToList())
            {
                CrateSettings crate;
                if (!_config.Loot.Crates.TryGetValue(crateKey, out crate) || crate == null)
                {
                    crate = new CrateSettings();
                    _config.Loot.Crates[crateKey] = crate;
                }

                if (crate.PrefabMatchTokens == null) crate.PrefabMatchTokens = new List<string>();
                crate.SpawnChancePercent = Mathf.Clamp(crate.SpawnChancePercent, 0f, 100f);
                crate.MinimumAmount = Mathf.Clamp(crate.MinimumAmount, 1, _config.Safety.MaximumLootQuantityPerCrate);
                crate.MaximumAmount = Mathf.Clamp(crate.MaximumAmount, crate.MinimumAmount, _config.Safety.MaximumLootQuantityPerCrate);
    
// ... [Code preview truncated due to length. Full source in attached file] ...

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