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

FREE DeepSeaPlus : 1.5.4 1.5.4

Нет прав для скачивания
C#:
#nullable disable
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using HarmonyLib;
using ProtoBuf;
using Priority = HarmonyLib.Priority;
using UnityEngine;
using Random = UnityEngine.Random;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Rust;
using Facepunch;
using Network;
using Prefabs.Misc;
using static RpcTarget;
namespace Oxide.Plugins;
[Info("DeepSeaPlus", "crash", "1.5.4")]
[Description("Enhances the Deep Sea experience with notifications, admin tools, custom loot, events, portal features, and more")]
public class DeepSeaPlus : RustPlugin {
#region Constants & Fields
    private static DeepSeaPlus _instance;
    private PluginConfig _config;
    private StoredData _data;
    private bool _dataChanged;
    private DeepSeaManager _manager;
    private bool _deepSeaOpen;
    private bool _manualForceClosed;
    private bool _manualForceOpened;
    private bool _globalPatchesApplied;
    private bool _originalDeepSeaFogOfWar;
    private bool _conVarsModified;
    private Dictionary<string, object> _originalConVars;
    private bool _trackingPollActive;
    private readonly HashSet<Coroutine> _activeCoroutines = new();
    private Dictionary<int, string> _formattedIntervals;
    private HashSet<string> _allowedVehiclePrefabsSet;
    private readonly HashSet<ulong> _playersInDeepSea = new();
    private readonly Dictionary<ulong, PlayerSession> _activeSessions = new();
    private readonly Dictionary<ulong, float> _portalCooldowns = new();
    private readonly Dictionary<ulong, float> _recentFeeCharges = new();
    private readonly Dictionary<ulong, List<SnapshotItem>> _inventorySnapshots = new();
    private readonly HashSet<ulong> _deathsInDeepSea = new();
    private readonly List<BaseEntity> _spawnedEventEntities = new();
    private readonly HashSet<int> _announcedMilestones = new();
    private readonly Dictionary<ulong, int> _foundationCounts = new();
    private readonly HashSet<ulong> _enhancedContainers = new();
    private readonly HashSet<uint> _foundationPrefabIds = new();
    private readonly HashSet<uint> _nonFoundationPrefabIds = new();
    private readonly Dictionary<ulong, float> _portalInfoShownAt = new();
    private readonly Dictionary<ulong, bool> _buildBypassCache = new();
    private readonly Dictionary<ulong, bool> _vipCache = new();
    private readonly Dictionary<SpawnGroup, int> _originalMaxPopulations = new();
    private Timer _debugPathTimer;
    private Timer _populationMultiplierTimer;
    private readonly HashSet<BasePlayer> _debugPathViewers = new();
    private readonly Dictionary<ulong, List<Vector3>> _mappingWaypoints = new();
    private Dictionary<string, ItemDefinition> _cachedItemDefs;
    private static readonly Bounds DeepSeaBounds = new(new Vector3(-5900f, 0f, 0f), new Vector3(4000f, 4000f, 4000f));
    private static readonly float[] ShoreSearchOffsets = {
        30f, 50f, 80f, 120f, 200f
    }
    ;
    private const string PermAdmin = "deepseaplus.admin";
    private const string PermTeleport = "deepseaplus.tp";
    private const string PermVIP = "deepseaplus.vip";
    private const string PermNotify = "deepseaplus.notify";
    private const string PermStats = "deepseaplus.stats";
    private const string PermBuildBypass = "deepseaplus.build.bypass";
    private const string SmallStashPrefab = "assets/prefabs/deployable/small stash/small_stash_deployed.prefab";
    private const string BradleyPrefab = "assets/prefabs/npc/m2bradley/bradleyapc.prefab";
    private const string SupplyDropPrefab = "assets/prefabs/misc/supply drop/supply_drop.prefab";
    private static readonly Dictionary<DeepSeaIsland.IslandType, Vector3[]> IslandPatrolRoutes = new() {
        [DeepSeaIsland.IslandType.Horseshoe]
        = new[] {
            new Vector3(90.7f, 0f, -2.4f), new Vector3(65.7f, 0f, -9.2f), new Vector3(45.0f, 0f, -7.0f), new Vector3(8.5f, 0f, -28.3f), new Vector3(-28.9f, 0f, -36.0f), new Vector3(-66.5f, 0f, -25.6f), new Vector3(-58.0f, 0f, -3.9f), new Vector3(-61.9f, 0f, 15.9f), new Vector3(-54.1f, 0f, 37.2f), new Vector3(-33.7f, 0f, 55.1f), new Vector3(-46.3f, 0f, 87.2f), new Vector3(-29.5f, 0f, 113.4f), new Vector3(-3.8f, 0f, 133.3f), new Vector3(22.1f, 0f, 140.7f), new Vector3(28.2f, 0f, 128.9f), new Vector3(44.6f, 0f, 113.7f), new Vector3(14.3f, 0f, 77.0f), new Vector3(13.6f, 0f, 42.0f), new Vector3(36.0f, 0f, 18.7f), new Vector3(61.8f, 0f, 11.3f), new Vector3(96.7f, 0f, 28.9f), new Vector3(103.4f, 0f, 17.9f)
        }
        , [DeepSeaIsland.IslandType.Blob] = new[] {
            new Vector3(40.5f, 0f, -32.4f), new Vector3(-7.1f, 0f, -11.6f), new Vector3(-50.1f, 0f, 36.6f), new Vector3(-85.3f, 0f, 43.1f), new Vector3(-88.5f, 0f, -11.2f), new Vector3(-53.4f, 0f, -82.1f), new Vector3(-34.7f, 0f, -103.7f), new Vector3(-22.1f, 0f, -106.5f), new Vector3(-10.3f, 0f, -93.8f), new Vector3(-1.1f, 0f, -82.6f), new Vector3(14.4f, 0f, -95.9f), new Vector3(38.0f, 0f, -135.9f), new Vector3(60.3f, 0f, -149.9f), new Vector3(94.8f, 0f, -128.8f), new Vector3(121.9f, 0f, -124.4f), new Vector3(130.0f, 0f, -108.4f), new Vector3(132.7f, 0f, -83.9f), new Vector3(120.3f, 0f, -58.8f), new Vector3(85.4f, 0f, -34.0f)
        }
        , [DeepSeaIsland.IslandType.Round] = new[] {
            new Vector3(-9.1f, 0f, -18.3f), new Vector3(-42.7f, 0f, 0.3f), new Vector3(-68.4f, 0f, 41.1f), new Vector3(-69.1f, 0f, 68.1f), new Vector3(-51.8f, 0f, 80.9f), new Vector3(-14.6f, 0f, 79.4f), new Vector3(8.6f, 0f, 69.9f), new Vector3(37.8f, 0f, 37.8f), new Vector3(44.1f, 0f, 9.1f), new Vector3(27.8f, 0f, -16.7f)
        }
        , [DeepSeaIsland.IslandType.Line] = new[] {
            new Vector3(8.1f, 0f, 8.8f), new Vector3(-22.1f, 0f, 27.6f), new Vector3(-56.7f, 0f, 34.9f), new Vector3(-65.5f, 0f, 46.3f), new Vector3(-88.8f, 0f, 41.0f), new Vector3(-98.0f, 0f, 56.5f), new Vector3(-78.2f, 0f, 80.6f), new Vector3(-62.9f, 0f, 80.5f), new Vector3(-37.6f, 0f, 56.2f), new Vector3(-14.0f, 0f, 44.7f), new Vector3(12.3f, 0f, 34.5f), new Vector3(57.6f, 0f, 10.9f), new Vector3(67.6f, 0f, -24.5f), new Vector3(49.5f, 0f, -41.9f), new Vector3(33.3f, 0f, -12.0f)
        }
    }
    ;
    private static readonly Translate.Phrase CooldownPhrase = new("deepsea.plus.cooldown", "Portal cooldown is active. Please wait.");
    private static readonly Translate.Phrase InsufficientFundsPhrase = new("deepsea.plus.nofunds", "Insufficient funds to enter the Deep Sea.");
    private static readonly Dictionary<string, string> DiscordHeaders = new() {
        ["Content-Type"]
        = "application/json"
    }
    ;
    private const int NcpInfo = 0;
    private const int NcpWarning = 1;
    private const int NcpError = 2;
    private const int NcpSuccess = 3;
    [PluginReference]
    private readonly Plugin Economics, ServerRewards, NCP;
    private readonly HashSet<string> _conditionalHooks = new() {
        nameof(CanBuild)
    }
    ;
    private static readonly string[] _deepSeaRuntimeHooks = {
        nameof(OnEntityDeath), nameof(OnEntityKill), nameof(OnLootEntity), nameof(OnLootEntityEnd), nameof(OnEntityBuilt), nameof(OnPlayerDeath), nameof(OnPlayerRespawned)
    }
    ;
    private readonly HashSet<string> _subscribedRuntimeHooks = new();
#endregion
#region Configuration
    private enum ScheduleMode {
        Vanilla, AlwaysOpen, Disabled
    }
    private enum DeepSeaEnabledOverride {
        DontTouch, ForceEnabled, ForceDisabled
    }
    private sealed class PluginConfig {
        [JsonProperty("General Controls")]
        public GeneralControlSettings GeneralControls {
            get;
            set;
        }
        = new();
        [JsonProperty("Schedule")]
        public ScheduleSettings Schedule {
            get;
            set;
        }
        = new();
        [JsonProperty("Notifications")]
        public NotificationSettings Notifications {
            get;
            set;
        }
        = new();
        [JsonProperty("Loot Overrides")]
        public LootOverrideSettings LootOverrides {
            get;
            set;
        }
        = new();
        [JsonProperty("Portal Settings")]
        public PortalSettings PortalSettings {
            get;
            set;
        }
        = new();
        [JsonProperty("Deep Sea Overrides")]
        public DeepSeaOverrideSettings DeepSeaOverrides {
            get;
            set;
        }
        = new();
        [JsonProperty("Respawn System")]
        public RespawnSystemSettings RespawnSystem {
            get;
            set;
        }
        = new();
        [JsonProperty("Building Controls")]
        public BuildingControlSettings BuildingControls {
            get;
            set;
        }
        = new();
        [JsonProperty("Events")]
        public EventSettings Events {
            get;
            set;
        }
        = new();
        [JsonProperty("Death Rules")]
        public DeathRuleSettings DeathRules {
            get;
            set;
        }
        = new();
        [JsonProperty("Discord Webhook")]
        public DiscordSettings Discord {
            get;
            set;
        }
        = new();
        [JsonProperty("Config Version (DO NOT MODIFY)")]
        public VersionNumber Version {
            get;
            set;
        }
    }
    private sealed class GeneralControlSettings {
        [JsonProperty("Control schedule (enables schedule modes)")]
        public bool ControlSchedule {
            get;
            set;
        }
        [JsonProperty("Control content (enables spawn count overrides)")]
        public bool ControlContent {
            get;
            set;
        }
        [JsonProperty("Control travel rules (enables granular portal access)")]
        public bool ControlTravelRules {
            get;
            set;
        }
        [JsonProperty("Control building (enables building controls)")]
        public bool ControlBuilding {
            get;
            set;
        }
        [JsonProperty("Control loot (enables loot overrides)")]
        public bool ControlLoot {
            get;
            set;
        }
        = true;
        [JsonProperty("Control events (enables deep sea events)")]
        public bool ControlEvents {
            get;
            set;
        }
        = true;
        [JsonProperty("Control notifications (enables announcements)")]
        public bool ControlNotifications {
            get;
            set;
        }
        = true;
        [JsonProperty("Force apply on reload (close and reopen Deep Sea)")]
        public bool ForceApplyOnReload {
            get;
            set;
        }
        [JsonProperty("Deduplicate spawn groups on startup (removes duplicate loot/ores after server restart)")]
        public bool DeduplicateOnStartup {
            get;
            set;
        }
        = true;
        [JsonProperty("Enforce interval seconds (0 = once on startup, >0 = repeating)")]
        public float EnforceIntervalSeconds {
            get;
            set;
        }
    }
    private sealed class ScheduleSettings {
        [JsonProperty("Mode (Vanilla, AlwaysOpen, Disabled)")]
        [JsonConverter(typeof(StringEnumConverter))]
        public ScheduleMode Mode {
            get;
            set;
        }
        = ScheduleMode.Vanilla;
        [JsonProperty("Open time seconds (Vanilla mode, overrides wipeDuration)")]
        public int OpenTimeSeconds {
            get;
            set;
        }
        = 10800;
        [JsonProperty("Cooldown seconds (Vanilla mode, overrides wipeCooldownMin/Max)")]
        public int CooldownSeconds {
            get;
            set;
        }
        = 5400;
        [JsonProperty("Final phase seconds (Vanilla mode, overrides wipeEndPhaseDuration)")]
        public int FinalPhaseSeconds {
            get;
            set;
        }
        = 1800;
        [JsonProperty("Radiation warning phase seconds (Vanilla mode)")]
        public int RadiationWarningPhaseSeconds {
            get;
            set;
        }
        = 300;
        [JsonProperty("AlwaysOpen - Keep time left around (seconds)")]
        public float AlwaysOpenKeepTimeLeftAround {
            get;
            set;
        }
        = 9000f;
        [JsonProperty("AlwaysOpen - Refill when below (seconds)")]
        public float AlwaysOpenRefillWhenBelow {
            get;
            set;
        }
        = 3600f;
        [JsonProperty("Disabled - Push next opening to (seconds)")]
        public float DisabledPushNextOpeningTo {
            get;
            set;
        }
        = 999999f;
        [JsonProperty("Deep Sea enabled override (DontTouch, ForceEnabled, ForceDisabled)")]
        [JsonConverter(typeof(StringEnumConverter))]
        public DeepSeaEnabledOverride EnabledOverride {
            get;
            set;
        }
        = DeepSeaEnabledOverride.DontTouch;
    }
    private sealed class NotificationSettings {
        [JsonProperty("Announce when Deep Sea opens")]
        public bool AnnounceOnOpen {
            get;
            set;
        }
        = true;
        [JsonProperty("Announce when Deep Sea closes")]
        public bool AnnounceOnClose {
            get;
            set;
        }
        = true;
        [JsonProperty("Countdown intervals (seconds remaining)", ObjectCreationHandling = ObjectCreationHandling.Replace)]
        public List<int> CountdownIntervals {
            get;
            set;
        }
        = new() {
            1800, 900, 300, 60
        }
        ;
        [JsonProperty("Use toast messages")]
        public bool UseToastMessages {
            get;
            set;
        }
        = true;
        [JsonProperty("Use chat messages")]
        public bool UseChatMessages {
            get;
            set;
        }
        = true;
        [JsonProperty("Countdown check interval (seconds)")]
        public float CountdownCheckInterval {
            get;
            set;
        }
        = 10f;
        [JsonProperty("Use NCP notifications (requires NCP plugin)")]
        public bool UseNCPNotifications {
            get;
            set;
        }
    }
    private sealed class LootOverrideSettings {
        [JsonProperty("Enable loot overrides")]
        public bool Enabled {
            get;
            set;
        }
        = true;
        [JsonProperty("Stack size multiplier")]
        public float StackMultiplier {
            get;
            set;
        }
        = 1.5f;
        [JsonProperty("Bonus items", ObjectCreationHandling = ObjectCreationHandling.Replace)]
        public List<BonusItemEntry> BonusItems {
            get;
            set;
        }
        = new() {
            new BonusItemEntry {
                Shortname = "rifle.ak", Amount = 1, Chance = 0.1f
            }
            , new BonusItemEntry {
                Shortname = "ammo.rifle", Amount = 128, Chance = 0.5f
            }
        }
        ;
        [JsonProperty("Override hackable crate loot on ghost ships")]
        public bool OverrideHackableCrates {
            get;
            set;
        }
        [JsonProperty("Override hackable crate hack time in Deep Sea (seconds, 0 to use vanilla)")]
        public float HackableCrateHackTimeOverride {
            get;
            set;
        }
        = 450f;
        [JsonProperty("Hackable crate bonus items", ObjectCreationHandling = ObjectCreationHandling.Replace)]
        public List<BonusItemEntry> HackableCrateBonusItems {
            get;
            set;
        }
        = new();
        [JsonProperty("Dynamic Loot Scaling")]
        public DynamicLootScalingSettings DynamicScaling {
            get;
            set;
        }
        = new();
    }
    private sealed class DynamicLootScalingSettings {
        [JsonProperty("Enable dynamic scaling")]
        public bool Enabled {
            get;
            set;
        }
        [JsonProperty("Base player threshold")]
        public int BasePlayerThreshold {
            get;
            set;
        }
        = 3;
        [JsonProperty("Max bonus multiplier")]
        public float MaxBonusMultiplier {
            get;
            set;
        }
        = 2f;
        [JsonProperty("Bonus per extra player")]
        public float BonusPerExtraPlayer {
            get;
            set;
        }
        = 0.1f;
    }
    private sealed class BonusItemEntry {
        [JsonProperty("Item shortname")]
        public string Shortname {
            get;
            set;
        }
        = "";
        [JsonProperty("Amount")]
        public int Amount {
            get;
            set;
        }
        = 1;
        [JsonProperty("Chance (0.0 - 1.0)")]
        public float Chance {
            get;
            set;
        }
        = 1f;
        [JsonProperty("Skin ID")]
        public ulong SkinId {
            get;
            set;
        }
    }
    private sealed class EntitySpawnOverride {
        [JsonProperty("Count")]
        public int Count {
            get;
            set;
        }
        [JsonProperty("Radius")]
        public float Radius {
            get;
            set;
        }
        [JsonProperty("Edge margin")]
        public float EdgeMargin {
            get;
            set;
        }
        [JsonProperty("Minimum distance")]
        public float MinDistance {
            get;
            set;
        }
    }
    private sealed class PortalSettings {
        [JsonProperty("Enable portal features")]
        public bool EnablePortalFeatures {
            get;
            set;
        }
        = true;
        [JsonProperty("Cooldown after leaving Deep Sea (seconds)")]
        public float CooldownSeconds {
            get;
            set;
        }
        = 300f;
        [JsonProperty("Entry fee amount (0 to disable)")]
        public double EntryFee {
            get;
            set;
        }
        = 500.0;
        [JsonProperty("Entry fee plugin (Economics or ServerRewards)")]
        public string EntryFeePlugin {
            get;
            set;
        }
        = "Economics";
        [JsonProperty("VIP bypasses cooldown")]
        public bool VIPBypassCooldown {
            get;
            set;
        }
        = true;
        [JsonProperty("VIP bypasses food toll")]
        public bool VIPBypassFoodToll {
            get;
            set;
        }
        = true;
        [JsonProperty("Allow any vehicle (bypass whitelist)")]
        public bool AllowAnyVehicle {
            get;
            set;
        }
        [JsonProperty("Allowed vehicle prefabs (overrides vanilla whitelist)", ObjectCreationHandling = ObjectCreationHandling.Replace)]
        public List<string> AllowedVehiclePrefabs {
            get;
            set;
        }
        = new();
        [JsonProperty("Require boat type only")]
        public bool RequireBoatTypeOnly {
            get;
            set;
        }
        = true;
        [JsonProperty("Allow players without vehicle (on foot)")]
        public bool AllowPlayersWithoutVehicle {
            get;
            set;
        }
        [JsonProperty("Allow noclip/admin players to bypass checks")]
        public bool AllowNoclipPlayers {
            get;
            set;
        }
        = true;
        [JsonProperty("Allow NPC passengers")]
        public bool AllowNPCPassengers {
            get;
            set;
        }
        [JsonProperty("Allow NPC player teleport")]
        public bool AllowNPCPlayerTeleport {
            get;
            set;
        }
        [JsonProperty("Allow entry during radiation warning phase")]
        public bool AllowEntryDuringRadiationWarningPhase {
            get;
            set;
        }
        [JsonProperty("Ignore extra vehicle onboard check")]
        public bool IgnoreExtraVehicleOnboardCheck {
            get;
            set;
        }
        [JsonProperty("Log entry/exit decisions")]
        public bool LogEntryExit {
            get;
            set;
        }
        [JsonProperty("Show info panel on portal approach")]
        public bool PortalApproachInfoEnabled {
            get;
            set;
        }
        = true;
        [JsonProperty("Portal approach trigger radius (portals are ~300 wide)")]
        public float PortalApproachRadius {
            get;
            set;
        }
        = 150f;
        [JsonProperty("Portal info cooldown per player (seconds)")]
        public float PortalInfoCooldownSeconds {
            get;
            set;
        }
        = 30f;
        [JsonProperty("Override portal height (0 = vanilla, recommended 6000 for tall portals)")]
        public float PortalHeightOverride {
            get;
            set;
        }
    }
    private sealed class DeepSeaOverrideSettings {
        [JsonProperty("Enable ConVar overrides")]
        public bool Enabled {
            get;
            set;
        }
        [JsonProperty("Wipe duration (seconds)")]
        public int WipeDuration {
            get;
            set;
        }
        = 10800;
        [JsonProperty("Wipe cooldown min (seconds)")]
        public int WipeCooldownMin {
            get;
            set;
        }
        = 5400;
        [JsonProperty("Wipe cooldown max (seconds)")]
        public int WipeCooldownMax {
            get;
            set;
        }
        = 10800;
        [JsonProperty("Radiation phase duration (seconds)")]
        public int WipeRadiationPhaseDuration {
            get;
            set;
        }
        = 300;
        [JsonProperty("End phase duration (seconds)")]
        public int WipeEndPhaseDuration {
            get;
            set;
        }
        = 1800;
        [JsonProperty("Island Spawns")]
        public EntitySpawnOverride Islands {
            get;
            set;
        }
        = new() {
            Count = 6, Radius = 300f, EdgeMargin = 750f, MinDistance = 600f
        }
        ;
        [JsonProperty("Ghost Ship Spawns")]
        public EntitySpawnOverride GhostShips {
            get;
            set;
        }
        = new() {
            Count = 4, Radius = 80f, EdgeMargin = 450f, MinDistance = 400f
        }
        ;
        [JsonProperty("Floating City Spawns")]
        public EntitySpawnOverride FloatingCities {
            get;
            set;
        }
        = new() {
            Count = 1, Radius = 500f, EdgeMargin = 1500f, MinDistance = 1500f
        }
        ;
        [JsonProperty("RHIB Spawns")]
        public EntitySpawnOverride RHIBs {
            get;
            set;
        }
        = new() {
            Count = 4, Radius = 10f, EdgeMargin = 1150f, MinDistance = 300f
        }
        ;
        [JsonProperty("Hackable crate count")]
        public int HackableCrateCount {
            get;
            set;
        }
        = 1;
        [JsonProperty("Entities spawn interval (seconds between island/ghostship/city spawns - raise to reduce opening lag)")]
        public float EntitiesSpawnInterval {
            get;
            set;
        }
        = 5f;
        [JsonProperty("Spawn groups interval (seconds between loot/npc fills - raise to reduce opening lag)")]
        public float SpawnGroupsSpawnInterval {
            get;
            set;
        }
        = 0.15f;
        [JsonProperty("NavMesh spawn interval (seconds between dwelling navmesh generations - raise to reduce opening lag)")]
        public float NavMeshSpawnInterval {
            get;
            set;
        }
        = 5f;
        [JsonProperty("Loot scale (global multiplier applied to initial spawn groups on islands - 1.0 = vanilla, also scales per-tick spawn rate. IGNORED when PopulationMultipliers is enabled)")]
        public float LootScale {
            get;
            set;
        }
        = 1f;
        [JsonProperty("Loot respawn scale (global multiplier applied to respawn spawn groups on islands - 1.0 = vanilla, also scales per-tick spawn rate. IGNORED when PopulationMultipliers is enabled)")]
        public float LootRespawnScale {
            get;
            set;
        }
        = 1f;
        [JsonProperty("Force entrance portal direction (0=Map-based, 1=N, 2=E, 3=S, 4=W)")]
        public int ForceEntrancePortalDirection {
            get;
            set;
        }
        [JsonProperty("Island portal terrain distance")]
        public float IslandPortalTerrainDistance {
            get;
            set;
        }
        = 750f;
        [JsonProperty("Mainland billboard scale (0 to hide)")]
        public float MainIslandBillboardScale {
            get;
            set;
        }
        = 1f;
        [JsonProperty("Islands billboard scale (0 to hide)")]
        public float IslandsBillboardScale {
            get;
            set;
        }
        = 1f;
        [JsonProperty("Remove fog of war")]
        public bool RemoveFogOfWar {
            get;
            set;
        }
    }
    private sealed class EventSettings {
        [JsonProperty("Treasure Hunt")]
        public TreasureHuntSettings TreasureHunt {
            get;
            set;
        }
        = new();
        [JsonProperty("Boss Spawn")]
        public BossSpawnSettings BossSpawn {
            get;
            set;
        }
        = new();
        [JsonProperty("Supply Drops")]
        public SupplyDropSettings SupplyDrops {
            get;
            set;
        }
        = new();
        [JsonProperty("Announce ghost ship crate hacking")]
        public bool AnnounceGhostShipHack {
            get;
            set;
        }
        = true;
    }
    private sealed class TreasureHuntSettings {
        [JsonProperty("Enable treasure hunt")]
        public bool Enabled {
            get;
            set;
        }
        = true;
        [JsonProperty("Number of stashes to spawn")]
        public int Count {
            get;
            set;
        }
        = 3;
        [JsonProperty("Treasure items", ObjectCreationHandling = ObjectCreationHandling.Replace)]
        public List<BonusItemEntry> TreasureItems {
            get;
            set;
        }
        = new() {
            new BonusItemEntry {
                Shortname = "supply.signal", Amount = 2, Chance = 1f
            }
            , new BonusItemEntry {
                Shortname = "targeting.computer", Amount = 1, Chance = 0.5f
            }
            , new BonusItemEntry {
                Shortname = "techparts", Amount = 3, Chance = 1f
            }
        }
        ;
        [JsonProperty("Show treasure markers on map")]
        public bool ShowMapMarkers {
            get;
            set;
        }
        = true;
        [JsonProperty("Map marker radius")]
        public float MapMarkerRadius {
            get;
            set;
        }
        = 0.08f;
    }
    private sealed class BossSpawnSettings {
        [JsonProperty("Enable boss spawn")]
        public bool Enabled {
            get;
            set;
        }
        [JsonProperty("Minutes before wipe to spawn boss")]
        public int MinutesBeforeWipe {
            get;
            set;
        }
        = 30;
        [JsonProperty("Boss health multiplier")]
        public float HealthMultiplier {
            get;
            set;
        }
        = 2f;
        [JsonProperty("Patrol radius around island")]
        public float PatrolRadius {
            get;
            set;
        }
        = 75f;
        [JsonProperty("Show boss marker on map")]
        public bool ShowMapMarker {
            get;
            set;
        }
        = true;
        [JsonProperty("Boss map marker radius")]
        public float MapMarkerRadius {
            get;
            set;
        }
        = 0.15f;
    }
    private sealed class SupplyDropSettings {
        [JsonProperty("Enable supply drops")]
        public bool Enabled {
            get;
            set;
        }
        = true;
        [JsonProperty("Interval between drops (minutes)")]
        public int IntervalMinutes {
            get;
            set;
        }
        = 45;
        [JsonProperty("Show supply drop markers on map")]
        public bool ShowMapMarkers {
            get;
            set;
        }
        = true;
    }
    private sealed class DeathRuleSettings {
        [JsonProperty("Enable death tracking")]
        public bool EnableDeathTracking {
            get;
            set;
        }
        = true;
        [JsonProperty("Show death summary message")]
        public bool ShowDeathMessage {
            get;
            set;
        }
        = true;
        [JsonProperty("Snapshot inventory on entry")]
        public bool SnapshotInventoryOnEntry {
            get;
            set;
        }
        [JsonProperty("Restore inventory on respawn after Deep Sea death")]
        public bool RestoreInventoryOnRespawn {
            get;
            set;
        }
    }
    private sealed class RespawnSystemSettings {
        [JsonProperty("Enable respawn system")]
        public bool Enabled {
            get;
            set;
        }
        [JsonProperty("Check interval (minutes)")]
        public float CheckIntervalMinutes {
            get;
            set;
        }
        = 15f;
        [JsonProperty("Minimum distance from players to respawn")]
        public float MinDistanceFromPlayers {
            get;
            set;
        }
        = 50f;
        [JsonProperty("Respawn hackable crates via ghost ships")]
        public bool RespawnHackableCrates {
            get;
            set;
        }
        = true;
        [JsonProperty("Maximum hackable crates active at once")]
        public int HackableCratesCount {
            get;
            set;
        }
        = 1;
        [JsonProperty("Refill island SpawnGroups (loot crates, ores, NPCs) when below max population")]
        public bool TriggerIslandSpawnGroups {
            get;
            set;
        }
        = true;
        [JsonProperty("Despawn island crates after a player takes any items and closes the loot UI (frees the SpawnPoint so a new crate can refill it next cycle)")]
        public bool ForceKillLootedCrates {
            get;
            set;
        }
        = true;
        [JsonProperty("When despawning a partially-looted crate, drop its remaining items on the ground as a sack instead of deleting them with the crate")]
        public bool DropRemainingItemsOnDespawn {
            get;
            set;
        }
        = true;
        [JsonProperty("Respawn RHIBs when all are destroyed")]
        public bool RespawnRHIBs {
            get;
            set;
        }
        = true;
        [JsonProperty("RHIB groups to respawn (each group = 3 boats)")]
        public int RHIBGroupCount {
            get;
            set;
        }
        = 4;
        [JsonProperty("Population Multipliers (1.0 = vanilla)")]
        public PopulationMultiplierSettings PopulationMultipliers {
            get;
            set;
        }
        = new();
    }
    private sealed class PopulationMultiplierSettings {
        [JsonProperty("Enable population multipliers")]
        public bool Enabled {
            get;
            set;
        }
        [JsonProperty("Loot crate multiplier")]
        public float CrateMultiplier {
            get;
            set;
        }
        = 1.0f;
        [JsonProperty("Ore (sulfur/metal/stone) multiplier")]
        public float OreMultiplier {
            get;
            set;
        }
        = 1.0f;
        [JsonProperty("NPC (scientist) multiplier")]
        public float NpcMultiplier {
            get;
            set;
        }
        = 1.0f;
        [JsonProperty("Other resource (collectables, barrels) multiplier")]
        public float OtherMultiplier {
            get;
            set;
        }
        = 1.0f;
    }
    private sealed class BuildingControlSettings {
        [JsonProperty("Allow building in Deep Sea")]
        public bool AllowBuildingInDeepSea {
            get;
            set;
        }
        [JsonProperty("Limit foundations per player")]
        public bool LimitFoundations {
            get;
            set;
        }
        [JsonProperty("Max foundations per player")]
        public int MaxFoundationsPerPlayer {
            get;
            set;
        }
        = 10;
        [JsonProperty("Show remaining foundations on place")]
        public bool ShowRemainingOnPlace {
            get;
            set;
        }
        = true;
        [JsonProperty("Show message when denied")]
        public bool ShowMessageWhenDenied {
            get;
            set;
        }
        = true;
    }
    private sealed class DiscordSettings {
        [JsonProperty("Enable Discord webhook")]
        public bool Enabled {
            get;
            set;
        }
        [JsonProperty("Webhook URL")]
        public string WebhookUrl {
            get;
            set;
        }
        = "";
        [JsonProperty("Announce Deep Sea open")]
        public bool AnnounceOpen {
            get;
            set;
        }
        = true;
        [JsonProperty("Announce Deep Sea close")]
        public bool AnnounceClose {
            get;
            set;
        }
        = true;
        [JsonProperty("Announce notable player achievements")]
        public bool AnnounceAchievements {
            get;
            set;
        }
        = true;
        [JsonProperty("Embed color (decimal)")]
        public int EmbedColor {
            get;
            set;
        }
        = 3447003;
    }
    protected override void LoadDefaultConfig() {
        _config = new PluginConfig {
            Version = Version
        }
        ;
        SaveConfig();
    }
    protected override void LoadConfig() {
        base.LoadConfig();
        try {
            _config = Config.ReadObject<PluginConfig>();
            if (_config == null) {
                LoadDefaultConfig();
                return;
            }
            if (_config.Version < Version) {
                MigrateConfig();
                _config.Version = Version;
            }
        }
        catch {
            PrintError("Configuration file is corrupt. Creating new config.");
            LoadDefaultConfig();
        }
        if (IsConfigOutdated(_config)) PrintWarning("Configuration appears to be outdated (missing or renamed keys detected); rewriting with defaults merged in.");
        ValidateConfig();
        SaveConfig();
    }
    protected override void SaveConfig() => Config.WriteObject(_config, true);
    private bool IsConfigOutdated(PluginConfig cfg) {
        try {
            var expected = JToken.FromObject(cfg);
            var actual = Config.ReadObject<JObject>();
            if (actual == null) return false;
            return !JsonKeysMatch(expected, actual);
        }
        catch {
            return false;
        }
    }
    private static bool JsonKeysMatch(JToken expected, JToken actual) {
        while (true) {
            if (expected.Type != actual.Type) return false;
            switch (expected) {
                case JObject eo when actual is JObject ao : {
                    if (eo.Count != ao.Count) return false;
                    foreach (var prop in eo.Properties()) {
                        if (!ao.TryGetValue(prop.Name, out var actualValue)) return false;
                        if (!JsonKeysMatch(prop.Value, actualValue)) return false;
                    }
                    return true;
                }
                case JArray ea when actual is JArray aa : {
                    if (ea.Count == 0 || aa.Count == 0) return true;
                    expected = ea[0];
                    actual = aa[0];
                    continue;
                }
                default : return true;
            }
        }
    }
    private void ValidateConfig() {
        var n = _config.Notifications;
        n.CountdownCheckInterval = Mathf.Max(n.CountdownCheckInterval, 1f);
        var l = _config.LootOverrides;
        l.StackMultiplier = Mathf.Max(l.StackMultiplier, 1f);
        l.HackableCrateHackTimeOverride = Mathf.Max(l.HackableCrateHackTimeOverride, 0f);
        var ds = l.DynamicScaling;
        if (ds != null) {
            ds.BasePlayerThreshold = Mathf.Max(ds.BasePlayerThreshold, 1);
            ds.MaxBonusMultiplier = Mathf.Max(ds.MaxBonusMultiplier, 1f);
            ds.BonusPerExtraPlayer = Mathf.Max(ds.BonusPerExtraPlayer, 0f);
        }
        var p = _config.PortalSettings;
        p.CooldownSeconds = Mathf.Max(p.CooldownSeconds, 0f);
        p.EntryFee = Math.Max(p.EntryFee, 0);
        p.PortalApproachRadius = Mathf.Max(p.PortalApproachRadius, 10f);
        p.PortalInfoCooldownSeconds = Mathf.Max(p.PortalInfoCooldownSeconds, 0f);
        var b = _config.BuildingControls;
        b.MaxFoundationsPerPlayer = Mathf.Max(b.MaxFoundationsPerPlayer, 1);
        var boss = _config.Events.BossSpawn;
        boss.HealthMultiplier = Mathf.Max(boss.HealthMultiplier, 1f);
        boss.PatrolRadius = Mathf.Max(boss.PatrolRadius, 10f);
        boss.MinutesBeforeWipe = Mathf.Max(boss.MinutesBeforeWipe, 1);
        var sd = _config.Events.SupplyDrops;
        sd.IntervalMinutes = Mathf.Max(sd.IntervalMinutes, 1);
        var th = _config.Events.TreasureHunt;
        th.Count = Mathf.Max(th.Count, 1);
        if (p.EntryFee > 0 && string.IsNullOrEmpty(p.EntryFeePlugin)) PrintWarning("Entry fee is set but EntryFeePlugin is empty. Fee checks will fail.");
        if (!string.IsNullOrEmpty(p.EntryFeePlugin) && !string.Equals(p.EntryFeePlugin, "Economics", StringComparison.OrdinalIgnoreCase) && !string.Equals(p.EntryFeePlugin, "ServerRewards", StringComparison.OrdinalIgnoreCase)) PrintWarning($"Unknown EntryFeePlugin '{p.EntryFeePlugin}'. Must be 'Economics' or 'ServerRewards'.");
    }
    private void MigrateConfig() {
        _config.GeneralControls ??= new GeneralControlSettings();
        _config.Schedule ??= new ScheduleSettings();
        _config.Notifications ??= new NotificationSettings();
        _config.LootOverrides ??= new LootOverrideSettings();
        _config.PortalSettings ??= new PortalSettings();
        _config.DeepSeaOverrides ??= new DeepSeaOverrideSettings();
        _config.RespawnSystem ??= new RespawnSystemSettings();
        _config.BuildingControls ??= new BuildingControlSettings();
        _config.Events ??= new EventSettings();
        _config.DeathRules ??= new DeathRuleSettings();
        _config.Discord ??= new DiscordSettings();
        var overrides = _config.DeepSeaOverrides;
        overrides.Islands ??= new EntitySpawnOverride {
            Count = 6, Radius = 300f, EdgeMargin = 750f, MinDistance = 600f
        }
        ;
        overrides.GhostShips ??= new EntitySpawnOverride {
            Count = 4, Radius = 80f, EdgeMargin = 450f, MinDistance = 400f
        }
        ;
        overrides.FloatingCities ??= new EntitySpawnOverride {
            Count = 1, Radius = 500f, EdgeMargin = 1500f, MinDistance = 1500f
        }
        ;
        overrides.RHIBs ??= new EntitySpawnOverride {
            Count = 4, Radius = 10f, EdgeMargin = 1150f, MinDistance = 300f
        }
        ;
        _config.LootOverrides.DynamicScaling ??= new DynamicLootScalingSettings();
        try {
            var rawConfig = Config.ReadObject<Dictionary<string, object>>();
            if (rawConfig != null && rawConfig.TryGetValue("Deep Sea Overrides", out var overridesObj)) {
                if (overridesObj is JObject overridesJObj) {
                    if (overridesJObj["Wipe cooldown (seconds)"] != null && overridesJObj["Wipe cooldown min (seconds)"] == null) {
                        var oldCooldown = overridesJObj["Wipe cooldown (seconds)"]?.Value<int>() ?? 5400;
                        _config.DeepSeaOverrides.WipeCooldownMin = oldCooldown;
                        _config.DeepSeaOverrides.WipeCooldownMax = oldCooldown;
                        Puts($"Migrated WipeCooldown ({oldCooldown}s) to WipeCooldownMin/WipeCooldownMax.");
                    }
                }
            }
        }
        catch {
            // Ignored
        }
        Puts("Configuration migrated to latest version.");
    }
#endregion
#region Data Classes
    [ProtoContract]
    private sealed class StoredData {
        [ProtoMember(1)]
        public readonly Dictionary<ulong, PlayerLifetimeStats> PlayerStats = new();
        [ProtoMember(2)]
        public int TotalCyclesCompleted;
        [ProtoMember(3)]
        public Dictionary<ulong, int> FoundationCounts = new();
        [ProtoMember(4)]
        public bool FoundationCountsValid;
        [ProtoMember(5)]
        public bool ManualForceOpened;
    }
    [ProtoContract]
    private sealed class PlayerLifetimeStats {
        [ProtoMember(1)]
        public string DisplayName = "";
        [ProtoMember(2)]
        public int TotalVisits;
        [ProtoMember(3)]
        public float TotalTimeSpent;
        [ProtoMember(4)]
        public int TotalNpcKills;
        [ProtoMember(5)]
        public int TotalContainersLooted;
        [ProtoMember(6)]
        public int TotalDeaths;
    }
    private sealed class PlayerSession {
        public ulong UserId;
        public string DisplayName;
        public float EntryTime;
        public int NpcKills;
        public int ContainersLooted;
        public int Deaths;
        public bool HasDiedThisCycle;
        public BasePlayer Player;
    }
    private struct SnapshotItem {
        public string Shortname;
        public int Amount;
        public float Condition;
        public float MaxCondition;
        public ulong SkinId;
        public int Position;
        public string Container;
        public int BlueprintTarget;
        public List<SnapshotItem> Contents;
    }
#endregion
#region Initialization & Cleanup
    private void Init() {
        _instance = this;
        permission.RegisterPermission(PermAdmin, this);
        permission.RegisterPermission(PermTeleport, this);
        permission.RegisterPermission(PermVIP, this);
        permission.RegisterPermission(PermNotify, this);
        permission.RegisterPermission(PermStats, this);
        permission.RegisterPermission(PermBuildBypass, this);
        foreach (var hook in _conditionalHooks) Unsubscribe(hook);
        foreach (var hook in _deepSeaRuntimeHooks) Unsubscribe(hook);
        LoadData();
        PatchNestedClasses();
    }
    private void SubscribeRuntimeHook(string hook) {
        if (_subscribedRuntimeHooks.Add(hook)) Subscribe(hook);
    }
    private void SubscribeDeepSeaRuntimeHooks() {
        SubscribeRuntimeHook(nameof(OnEntityDeath));
        SubscribeRuntimeHook(nameof(OnLootEntity));
        if (_config.RespawnSystem.Enabled && _config.RespawnSystem.ForceKillLootedCrates) SubscribeRuntimeHook(nameof(OnLootEntityEnd));
        if (_config.GeneralControls.ControlBuilding && _config.BuildingControls.LimitFoundations) SubscribeRuntimeHook(nameof(OnEntityKill));
        if (_config.GeneralControls.ControlBuilding && _config.BuildingControls.LimitFoundations) SubscribeRuntimeHook(nameof(OnEntityBuilt));
        if (!_config.DeathRules.EnableDeathTracking) return;
        SubscribeRuntimeHook(nameof(OnPlayerDeath));
        if (_config.DeathRules.RestoreInventoryOnRespawn) SubscribeRuntimeHook(nameof(OnPlayerRespawned));
    }
    private void UnsubscribeDeepSeaRuntimeHooks() {
        foreach (var hook in _subscribedRuntimeHooks) Unsubscribe(hook);
        _subscribedRuntimeHooks.Clear();
    }
    private void OnServerInitialized() {
        _manager = FindDeepSeaManager();
        _manualForceOpened = _data.ManualForceOpened && _manager is not null && !_manager.IsDestroyed && _manager.IsOpen();
        if (_data.ManualForceOpened != _manualForceOpened) {
            _data.ManualForceOpened = _manualForceOpened;
            _dataChanged = true;
        }
        CacheFormattedIntervals();
        CacheAllowedVehiclePrefabs();
        CacheItemDefinitions();
        if (_config.PortalSettings.PortalHeightOverride > 0f) ApplyPortalHeightToExistingPortals();
        if (_manager is null) PrintWarning("DeepSeaManager not found on this server. Hooks will activate if it spawns later.");
        else if (_manager.IsOpen()) {
            _deepSeaOpen = true;
            ApplyGlobalPatches();
            SubscribeConditionalHooks();
            StartCountdownTimer();
            AttachTrackingZone();
            AttachPortalApproachZones();
            if (_config.GeneralControls.ControlBuilding && _config.BuildingControls.LimitFoundations) RestoreOrScanFoundationCounts();
            if (_config.GeneralControls.ControlEvents) ScheduleEvents();
            if (_config.RespawnSystem.Enabled) StartRespawnTimer();
            if (_config.RespawnSystem.PopulationMultipliers.Enabled) {
                _populationMultiplierTimer?.Destroy();
                _populationMultiplierTimer = timer.Once(2f, ApplyPopulationMultipliers);
            }
            if (_config.GeneralControls.DeduplicateOnStartup) StartTrackedCoroutine(DeduplicateDeepSeaEntitiesCoroutine());
        }
        if (_config.DeepSeaOverrides.Enabled || _config.GeneralControls.ControlSchedule || _config.GeneralControls.ControlBuilding || _config.GeneralControls.ControlTravelRules) SaveOriginalConVars();
        if (_config.DeepSeaOverrides.Enabled) ApplyConVarOverrides();
        if (_config.GeneralControls.ControlSchedule) {
            ApplyScheduleMode();
            if (_config.Schedule.EnabledOverride != DeepSeaEnabledOverride.DontTouch) {
                ConVar.DeepSea.enabled = _config.Schedule.EnabledOverride == DeepSeaEnabledOverride.ForceEnabled;
                BroadcastReplicatedVars();
            }
        }
        var scheduleNeedsEnforcement = _config.GeneralControls.ControlSchedule && _config.Schedule.Mode != ScheduleMode.Vanilla;
        if (_config.GeneralControls.EnforceIntervalSeconds > 0f) {
            StartEnforceTimer();
        }
        else if (scheduleNeedsEnforcement) {
            Puts($"Schedule mode {_config.Schedule.Mode} requires repeating enforcement - starting enforce timer at {DefaultEnforceIntervalSeconds}s (set 'Enforce interval seconds' to customize).");
            StartEnforceTimer();
        }
        if (_config.GeneralControls.ForceApplyOnReload && _manager is not null) {
            if (!_manager.IsBusy()) {
                Puts("Force apply on reload enabled - closing and reopening Deep Sea.");
                _manager.CloseDeepSea();
                timer.Once(15f, () => {
                    if (_manager is null || _manager.IsDestroyed || _manager.IsBusy()) 
// ... [Code preview truncated due to length. Full source in attached file] ...

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