Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of SunkenSpoils v0.3.0
plugins/SunkenSpoils.dll
Decompiled 20 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("SunkenSpoils")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("SunkenSpoils")] [assembly: AssemblyTitle("SunkenSpoils")] [assembly: AssemblyVersion("1.0.0.0")] namespace SunkenSpoils; [BepInPlugin("com.angell.sunkenspoils", "Sunken Spoils", "0.3.0")] public class SunkenSpoilsPlugin : BaseUnityPlugin { internal enum TreasureTier { Tier1Crate = 1, Tier2Barrel, Tier3Chest } internal enum OceanDisturbanceStage { Calm, Restless, Dangerous, Severe, Catastrophic } private sealed class ServerPlayerOceanState { public long PeerID; public ZDOID CharacterID; public float Disturbance; public OceanDisturbanceStage Stage; public float LastReportTime; } private sealed class LocalOwnedSerpent { public ZDOID ID; public GameObject GameObject; } internal class TreasureRollState { public bool IsTreasure; public Biome Biome; public float Chance; public float Roll; public TreasureTier Tier; public ZDOID FishID; public OceanDisturbanceStage RewardStage; } internal class LootEntry { public string PrefabName; public int MinAmount; public int MaxAmount; public int Weight; public TreasureTier MinimumTier; public string RequiredGlobalKey; public bool IsOre; public LootEntry(string prefabName, int minAmount, int maxAmount, int weight, TreasureTier minimumTier = TreasureTier.Tier1Crate, string requiredGlobalKey = null, bool isOre = false) { PrefabName = prefabName; MinAmount = minAmount; MaxAmount = maxAmount; Weight = weight; MinimumTier = minimumTier; RequiredGlobalKey = requiredGlobalKey; IsOre = isOre; } } internal class LootAward { public string PrefabName; public int Amount; } internal class RendererState { public Renderer Renderer; public bool WasEnabled; } internal class ActiveTreasureProxy { public int BobberID; public FishingFloat Bobber; public Fish Fish; public ZDOID FishID; public TreasureRollState Treasure; public GameObject Visual; public readonly List<RendererState> HiddenRenderers = new List<RendererState>(); public bool Completed; public float OriginalStaminaUse; public float OriginalEscapeStaminaUse; public float AppliedFightMultiplier; public bool FightStrengthApplied; } internal struct TreasureRollKey : IEquatable<TreasureRollKey> { public int BobberInstanceID; public ZDOID FishID; public TreasureRollKey(int bobberInstanceID, ZDOID fishID) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) BobberInstanceID = bobberInstanceID; FishID = fishID; } public bool Equals(TreasureRollKey other) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) return BobberInstanceID == other.BobberInstanceID && ((ZDOID)(ref FishID)).Equals(other.FishID); } public override bool Equals(object obj) { return obj is TreasureRollKey other && Equals(other); } public override int GetHashCode() { return (BobberInstanceID * 397) ^ ((object)Unsafe.As<ZDOID, ZDOID>(ref FishID)/*cast due to .constrained prefix*/).GetHashCode(); } } internal static SunkenSpoilsPlugin Instance; internal static ManualLogSource Log; private Harmony _harmony; private const string RpcStateReport = "SunkenSpoils_StateReport_v1"; private const string RpcSharedStorm = "SunkenSpoils_SharedStorm_v1"; private const string RpcSerpentKillReport = "SunkenSpoils_SerpentKillReport_v1"; private const string RpcSerpentKillAward = "SunkenSpoils_SerpentKillAward_v1"; private const float StateHeartbeatSeconds = 5f; private const float ServerStateTimeoutSeconds = 20f; private const int CurrentConfigRevision = 300; internal static ConfigEntry<bool> EnableTreasureFishing; internal static ConfigEntry<float> TreasureChance; internal static ConfigEntry<int> CrateWeight; internal static ConfigEntry<int> BarrelWeight; internal static ConfigEntry<int> ChestWeight; internal static ConfigEntry<float> OceanTreasureMultiplier; internal static ConfigEntry<bool> EnableProgressionGating; internal static ConfigEntry<bool> EnableOreLoot; internal static ConfigEntry<float> OreLootWeightMultiplier; internal static ConfigEntry<int> TreasureVisualLifetime; internal static ConfigEntry<float> TreasureFightStrength; internal static ConfigEntry<float> Tier1FightMultiplier; internal static ConfigEntry<float> Tier2FightMultiplier; internal static ConfigEntry<float> Tier3FightMultiplier; internal static ConfigEntry<int> Tier1LootRolls; internal static ConfigEntry<int> Tier2LootRolls; internal static ConfigEntry<int> Tier3LootRolls; internal static ConfigEntry<int> Tier1CoinMin; internal static ConfigEntry<int> Tier1CoinMax; internal static ConfigEntry<int> Tier2CoinMin; internal static ConfigEntry<int> Tier2CoinMax; internal static ConfigEntry<int> Tier3CoinMin; internal static ConfigEntry<int> Tier3CoinMax; internal static ConfigEntry<bool> EnableOceanDisturbance; internal static ConfigEntry<bool> LogOceanDisturbance; internal static ConfigEntry<bool> EnableOceanWarnings; internal static ConfigEntry<float> OceanDisturbanceMaximum; internal static ConfigEntry<float> DisturbancePerBite; internal static ConfigEntry<float> DisturbancePerTreasureHook; internal static ConfigEntry<float> DisturbancePerTreasureRecovery; internal static ConfigEntry<float> DisturbancePerSerpentKill; internal static ConfigEntry<float> DisturbanceDecayDelay; internal static ConfigEntry<float> DisturbanceDecayPerSecond; internal static ConfigEntry<float> RestlessThreshold; internal static ConfigEntry<float> DangerousThreshold; internal static ConfigEntry<float> SevereThreshold; internal static ConfigEntry<float> CatastrophicThreshold; internal static ConfigEntry<string> RestlessWarningText; internal static ConfigEntry<string> DangerousWarningText; internal static ConfigEntry<string> SevereWarningText; internal static ConfigEntry<string> CatastrophicWarningText; internal static ConfigEntry<bool> EnableOceanMonsterAttraction; internal static ConfigEntry<bool> EnableGlobalSerpentSafetyCap; internal static ConfigEntry<float> DangerousSerpentChance; internal static ConfigEntry<float> SevereSerpentChance; internal static ConfigEntry<float> CatastrophicSerpentChance; internal static ConfigEntry<int> DangerousSerpentCap; internal static ConfigEntry<int> SevereSerpentCap; internal static ConfigEntry<int> CatastrophicSerpentCap; internal static ConfigEntry<int> AbsoluteNearbySerpentCap; internal static ConfigEntry<float> SerpentAttractionCooldown; internal static ConfigEntry<float> SerpentSpawnDistanceMinimum; internal static ConfigEntry<float> SerpentSpawnDistanceMaximum; internal static ConfigEntry<float> SerpentNearbyCheckRadius; internal static ConfigEntry<int> GlobalMaximumNearbySerpents; internal static ConfigEntry<bool> EnableDisturbanceWeather; internal static ConfigEntry<bool> WeatherOnlyWhileInOcean; internal static ConfigEntry<string> SevereWeatherEnvironment; internal static ConfigEntry<string> CatastrophicWeatherEnvironment; internal static ConfigEntry<bool> EnableDisturbanceRewardScaling; internal static ConfigEntry<int> DangerousCrateWeight; internal static ConfigEntry<int> DangerousBarrelWeight; internal static ConfigEntry<int> DangerousChestWeight; internal static ConfigEntry<int> SevereCrateWeight; internal static ConfigEntry<int> SevereBarrelWeight; internal static ConfigEntry<int> SevereChestWeight; internal static ConfigEntry<int> CatastrophicCrateWeight; internal static ConfigEntry<int> CatastrophicBarrelWeight; internal static ConfigEntry<int> CatastrophicChestWeight; private static ConfigEntry<int> ConfigRevision; private static float _localOceanDisturbance; private static float _lastLocalOceanActivityTime; private static float _nextLocalDisturbanceDecayTick; private static int _lastLocalDisturbanceLogBucket = -1; private static OceanDisturbanceStage _localDisturbanceStage = OceanDisturbanceStage.Calm; private static OceanDisturbanceStage _sharedStormStage = OceanDisturbanceStage.Calm; private static float _lastSerpentAttemptTime = -99999f; private static float _nextStateHeartbeatTime; private static string _localCharacterKey; private static ZRoutedRpc _registeredRpcInstance; private static MethodInfo _setCatchFishMethod; private static MethodInfo _getServerPeerIdMethod; private static bool _warnedNibbleArgs; private static bool _warnedProgressionUnavailable; private static bool _warnedSetCatchMethodMissing; private static bool _warnedRpcRegistration; private static readonly Dictionary<long, ServerPlayerOceanState> ServerPlayerStates = new Dictionary<long, ServerPlayerOceanState>(); private static readonly HashSet<ZDOID> ServerCreditedSerpentKills = new HashSet<ZDOID>(); private static readonly HashSet<ZDOID> LocallyReportedSerpentKills = new HashSet<ZDOID>(); private static readonly List<LocalOwnedSerpent> LocalOwnedSerpents = new List<LocalOwnedSerpent>(); private static readonly Dictionary<TreasureRollKey, TreasureRollState> TreasureRolls = new Dictionary<TreasureRollKey, TreasureRollState>(); private static readonly Dictionary<int, ActiveTreasureProxy> ActiveProxyByBobber = new Dictionary<int, ActiveTreasureProxy>(); private static readonly Dictionary<ZDOID, ActiveTreasureProxy> ActiveProxyByFish = new Dictionary<ZDOID, ActiveTreasureProxy>(); private static readonly Dictionary<string, List<LootEntry>> LootTables = new Dictionary<string, List<LootEntry>>(); private void Awake() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; CreateConfig(); MigrateConfigIfNeeded(); BuildLootTables(); ResetLocalOceanState(); ResetNetworkRuntimeState(); _harmony = new Harmony("com.angell.sunkenspoils"); _harmony.PatchAll(); Log.LogWarning((object)"=================================================="); Log.LogWarning((object)"SUNKEN SPOILS 0.3.0 MULTIPLAYER OCEAN BUILD LOADED"); Log.LogWarning((object)"=================================================="); Log.LogInfo((object)$"Treasure fishing: {EnableTreasureFishing.Value} | Chance: {TreasureChance.Value:F1}%"); Log.LogInfo((object)$"Personal disturbance | Max {OceanDisturbanceMaximum.Value:F0} | Restless {GetRestlessThreshold():F0} | Dangerous {GetDangerousThreshold():F0} | Severe {GetSevereThreshold():F0} | Catastrophic {GetCatastrophicThreshold():F0}"); Log.LogInfo((object)$"Personal gains | Bite +{DisturbancePerBite.Value:F0} | Hook +{DisturbancePerTreasureHook.Value:F0} | Recovery +{DisturbancePerTreasureRecovery.Value:F0} | Serpent Kill +{DisturbancePerSerpentKill.Value:F0}"); Log.LogInfo((object)$"Personal serpents | D {DangerousSerpentChance.Value:F0}%/{DangerousSerpentCap.Value} | S {SevereSerpentChance.Value:F0}%/{SevereSerpentCap.Value} | C {CatastrophicSerpentChance.Value:F0}%/{CatastrophicSerpentCap.Value} | Absolute {AbsoluteNearbySerpentCap.Value}"); Log.LogInfo((object)$"Serpent spawn distance: {SerpentSpawnDistanceMinimum.Value:F0}-{SerpentSpawnDistanceMaximum.Value:F0}m"); Log.LogInfo((object)("Shared weather | Severe " + SevereWeatherEnvironment.Value + " | Catastrophic " + CatastrophicWeatherEnvironment.Value)); Log.LogInfo((object)"Multiplayer model: personal risk/reward + per-player serpent caps + shared server-coordinated storm."); } private void Update() { UpdateLocalCharacterTracking(); UpdateLocalOceanDisturbance(); UpdateStateHeartbeat(); UpdateServerOceanCoordinator(); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } foreach (ActiveTreasureProxy item in new List<ActiveTreasureProxy>(ActiveProxyByBobber.Values)) { CancelProxy(item, restoreFish: true, "Plugin unloaded"); } TreasureRolls.Clear(); ActiveProxyByBobber.Clear(); ActiveProxyByFish.Clear(); ResetLocalOceanState(); ResetNetworkRuntimeState(); Instance = null; } private ConfigEntry<bool> B(string s, string k, bool d, string desc) { return ((BaseUnityPlugin)this).Config.Bind<bool>(s, k, d, desc); } private ConfigEntry<string> S(string s, string k, string d, string desc) { return ((BaseUnityPlugin)this).Config.Bind<string>(s, k, d, desc); } private ConfigEntry<int> I(string s, string k, int d, int min, int max, string desc) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown return ((BaseUnityPlugin)this).Config.Bind<int>(s, k, d, new ConfigDescription(desc, (AcceptableValueBase)(object)new AcceptableValueRange<int>(min, max), Array.Empty<object>())); } private ConfigEntry<float> F(string s, string k, float d, float min, float max, string desc) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown return ((BaseUnityPlugin)this).Config.Bind<float>(s, k, d, new ConfigDescription(desc, (AcceptableValueBase)(object)new AcceptableValueRange<float>(min, max), Array.Empty<object>())); } private void CreateConfig() { EnableTreasureFishing = B("General", "Enable Treasure Fishing", d: true, "Enable Sunken Spoils treasure fishing."); TreasureChance = F("General", "Treasure Catch Chance", 7f, 0f, 50f, "Percent chance that a valid bite becomes treasure."); CrateWeight = I("Treasure Containers", "Tier 1 - Driftwood Crate Weight", 70, 0, 1000, "Base Tier 1 weight."); BarrelWeight = I("Treasure Containers", "Tier 2 - Waterlogged Barrel Weight", 25, 0, 1000, "Base Tier 2 weight."); ChestWeight = I("Treasure Containers", "Tier 3 - Sunken Chest Weight", 5, 0, 1000, "Base Tier 3 weight."); TreasureFightStrength = F("Difficulty", "Treasure Fight Strength", 1f, 0.25f, 5f, "Global treasure fight multiplier."); Tier1FightMultiplier = F("Difficulty", "Tier 1 Fight Multiplier", 1f, 0.25f, 5f, "Tier 1 fight multiplier."); Tier2FightMultiplier = F("Difficulty", "Tier 2 Fight Multiplier", 1.25f, 0.25f, 5f, "Tier 2 fight multiplier."); Tier3FightMultiplier = F("Difficulty", "Tier 3 Fight Multiplier", 1.6f, 0.25f, 5f, "Tier 3 fight multiplier."); TreasureVisualLifetime = I("Treasure Visuals", "Treasure Visual Lifetime", 120, 30, 600, "Maximum active treasure lifetime."); OceanTreasureMultiplier = F("Ocean", "Ocean Treasure Multiplier", 1.5f, 0f, 5f, "Treasure chance multiplier in actual Ocean."); EnableProgressionGating = B("Progression", "Enable Progression Gating", d: true, "Gate advanced ore behind boss progression."); EnableOreLoot = B("Loot - Ore", "Enable Ore Loot", d: true, "Allow ore and scrap in treasure."); OreLootWeightMultiplier = F("Loot - Ore", "Ore Loot Weight Multiplier", 1f, 0f, 5f, "Ore relative weight multiplier."); Tier1LootRolls = I("Loot", "Tier 1 Loot Rolls", 2, 1, 10, "Tier 1 bonus loot rolls."); Tier2LootRolls = I("Loot", "Tier 2 Loot Rolls", 3, 1, 10, "Tier 2 bonus loot rolls."); Tier3LootRolls = I("Loot", "Tier 3 Loot Rolls", 4, 1, 10, "Tier 3 bonus loot rolls."); Tier1CoinMin = I("Loot - Coins", "Tier 1 Minimum Coins", 10, 0, 10000, "Tier 1 min coins."); Tier1CoinMax = I("Loot - Coins", "Tier 1 Maximum Coins", 25, 0, 10000, "Tier 1 max coins."); Tier2CoinMin = I("Loot - Coins", "Tier 2 Minimum Coins", 25, 0, 10000, "Tier 2 min coins."); Tier2CoinMax = I("Loot - Coins", "Tier 2 Maximum Coins", 60, 0, 10000, "Tier 2 max coins."); Tier3CoinMin = I("Loot - Coins", "Tier 3 Minimum Coins", 60, 0, 10000, "Tier 3 min coins."); Tier3CoinMax = I("Loot - Coins", "Tier 3 Maximum Coins", 120, 0, 10000, "Tier 3 max coins."); EnableOceanDisturbance = B("Ocean Disturbance", "Enable Ocean Disturbance", d: true, "Enable per-player Ocean Disturbance."); OceanDisturbanceMaximum = F("Ocean Disturbance", "Maximum Disturbance", 400f, 10f, 1000f, "Maximum personal disturbance."); DisturbancePerBite = F("Ocean Disturbance", "Disturbance Per Valid Bite", 6f, 0f, 100f, "Personal disturbance per valid Ocean bite."); DisturbancePerTreasureHook = F("Ocean Disturbance", "Disturbance Per Treasure Hook", 24f, 0f, 100f, "Personal disturbance per treasure hook."); DisturbancePerTreasureRecovery = F("Ocean Disturbance", "Disturbance Per Treasure Recovery", 40f, 0f, 100f, "Personal disturbance per treasure recovery."); DisturbancePerSerpentKill = F("Ocean Disturbance", "Disturbance Per Serpent Kill", 40f, 0f, 100f, "Personal disturbance for the player credited with killing a Sea Serpent."); DisturbanceDecayDelay = F("Ocean Disturbance", "Disturbance Decay Delay", 120f, 0f, 600f, "Personal decay delay."); DisturbanceDecayPerSecond = F("Ocean Disturbance", "Disturbance Decay Per Second", 2f, 0f, 100f, "Personal disturbance decay per second."); LogOceanDisturbance = B("Ocean Disturbance", "Log Disturbance Changes", d: true, "Log personal disturbance changes."); EnableOceanWarnings = B("Ocean Disturbance - Warnings", "Enable Ocean Warnings", d: true, "Show personal stage warnings."); RestlessThreshold = F("Ocean Disturbance - Warnings", "Restless Threshold", 80f, 0f, 1000f, "Restless threshold."); DangerousThreshold = F("Ocean Disturbance - Warnings", "Dangerous Threshold", 140f, 0f, 1000f, "Dangerous threshold."); SevereThreshold = F("Ocean Disturbance - Warnings", "Severe Threshold", 220f, 0f, 1000f, "Severe threshold."); CatastrophicThreshold = F("Ocean Disturbance - Warnings", "Catastrophic Threshold", 300f, 0f, 1000f, "Catastrophic threshold."); RestlessWarningText = S("Ocean Disturbance - Warnings", "Restless Warning Text", "What's that in the water?", "Restless warning."); DangerousWarningText = S("Ocean Disturbance - Warnings", "Dangerous Warning Text", "The sea is beginning to stir...", "Dangerous warning."); SevereWarningText = S("Ocean Disturbance - Warnings", "Severe Warning Text", "The sea awakens...", "Severe warning."); CatastrophicWarningText = S("Ocean Disturbance - Warnings", "Catastrophic Warning Text", "THE SEA AIMS TO CLAIM YOU!", "Catastrophic warning."); EnableOceanMonsterAttraction = B("Ocean Monster Attraction", "Enable Ocean Monster Attraction", d: true, "Allow personal serpent attraction."); DangerousSerpentChance = F("Ocean Monster Attraction", "Dangerous Serpent Attraction Chance", 15f, 0f, 100f, "Dangerous attraction chance."); SevereSerpentChance = F("Ocean Monster Attraction", "Severe Serpent Attraction Chance", 25f, 0f, 100f, "Severe attraction chance."); CatastrophicSerpentChance = F("Ocean Monster Attraction", "Catastrophic Serpent Attraction Chance", 40f, 0f, 100f, "Catastrophic attraction chance."); DangerousSerpentCap = I("Ocean Monster Attraction", "Dangerous Personal Serpent Cap", 1, 0, 20, "Personal cap in Dangerous."); SevereSerpentCap = I("Ocean Monster Attraction", "Severe Personal Serpent Cap", 2, 0, 20, "Personal cap in Severe."); CatastrophicSerpentCap = I("Ocean Monster Attraction", "Catastrophic Personal Serpent Cap", 3, 0, 20, "Personal cap in Catastrophic."); AbsoluteNearbySerpentCap = I("Ocean Monster Attraction", "Absolute Personal Serpent Cap", 3, 0, 20, "Hard personal cap."); SerpentAttractionCooldown = F("Ocean Monster Attraction", "Serpent Attraction Cooldown", 60f, 0f, 600f, "Personal attraction cooldown."); SerpentSpawnDistanceMinimum = F("Ocean Monster Attraction", "Serpent Spawn Distance Minimum", 35f, 15f, 200f, "Minimum attracted Serpent spawn distance."); SerpentSpawnDistanceMaximum = F("Ocean Monster Attraction", "Serpent Spawn Distance Maximum", 60f, 20f, 250f, "Maximum attracted Serpent spawn distance."); SerpentNearbyCheckRadius = F("Ocean Monster Attraction", "Nearby Serpent Check Radius", 150f, 25f, 500f, "Radius for personal cap checks."); EnableGlobalSerpentSafetyCap = B("Ocean Monster Attraction", "Enable Global Serpent Safety Cap", d: true, "Optional emergency nearby safety cap across all Serpents."); GlobalMaximumNearbySerpents = I("Ocean Monster Attraction", "Global Maximum Nearby Serpents", 12, 1, 50, "Emergency nearby safety cap."); EnableDisturbanceWeather = B("Ocean Disturbance - Shared Weather", "Enable Shared Disturbance Weather", d: true, "Share Severe/Catastrophic storm state through the server."); WeatherOnlyWhileInOcean = B("Ocean Disturbance - Shared Weather", "Weather Only While In Ocean", d: true, "Apply shared storm only while the local player is in Ocean."); SevereWeatherEnvironment = S("Ocean Disturbance - Shared Weather", "Severe Weather Environment", "Rain", "Environment during shared Severe."); CatastrophicWeatherEnvironment = S("Ocean Disturbance - Shared Weather", "Catastrophic Weather Environment", "ThunderStorm", "Environment during shared Catastrophic."); EnableDisturbanceRewardScaling = B("Treasure Reward Escalation", "Enable Disturbance Reward Scaling", d: true, "Improve only this player's Ocean treasure odds at higher personal stages."); DangerousCrateWeight = I("Treasure Reward Escalation - Dangerous", "Driftwood Crate Weight", 60, 0, 1000, "Dangerous Tier 1 weight."); DangerousBarrelWeight = I("Treasure Reward Escalation - Dangerous", "Waterlogged Barrel Weight", 30, 0, 1000, "Dangerous Tier 2 weight."); DangerousChestWeight = I("Treasure Reward Escalation - Dangerous", "Sunken Chest Weight", 10, 0, 1000, "Dangerous Tier 3 weight."); SevereCrateWeight = I("Treasure Reward Escalation - Severe", "Driftwood Crate Weight", 45, 0, 1000, "Severe Tier 1 weight."); SevereBarrelWeight = I("Treasure Reward Escalation - Severe", "Waterlogged Barrel Weight", 35, 0, 1000, "Severe Tier 2 weight."); SevereChestWeight = I("Treasure Reward Escalation - Severe", "Sunken Chest Weight", 20, 0, 1000, "Severe Tier 3 weight."); CatastrophicCrateWeight = I("Treasure Reward Escalation - Catastrophic", "Driftwood Crate Weight", 30, 0, 1000, "Catastrophic Tier 1 weight."); CatastrophicBarrelWeight = I("Treasure Reward Escalation - Catastrophic", "Waterlogged Barrel Weight", 40, 0, 1000, "Catastrophic Tier 2 weight."); CatastrophicChestWeight = I("Treasure Reward Escalation - Catastrophic", "Sunken Chest Weight", 30, 0, 1000, "Catastrophic Tier 3 weight."); ConfigRevision = ((BaseUnityPlugin)this).Config.Bind<int>("Internal", "Config Revision", 0, "Sunken Spoils internal config revision."); } private void MigrateConfigIfNeeded() { if (ConfigRevision.Value < 300) { if (Mathf.Approximately(OceanDisturbanceMaximum.Value, 200f)) { OceanDisturbanceMaximum.Value = 400f; Log.LogWarning((object)"CONFIG MIGRATION | Maximum Disturbance 200 -> 400."); } if (Mathf.Approximately(SerpentSpawnDistanceMinimum.Value, 50f) && Mathf.Approximately(SerpentSpawnDistanceMaximum.Value, 80f)) { SerpentSpawnDistanceMinimum.Value = 35f; SerpentSpawnDistanceMaximum.Value = 60f; Log.LogWarning((object)"CONFIG MIGRATION | Serpent spawn distance 50-80m -> 35-60m."); } ConfigRevision.Value = 300; ((BaseUnityPlugin)this).Config.Save(); } } private static void UpdateLocalCharacterTracking() { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Player.m_localPlayer == (Object)null) { if (_localCharacterKey != null) { _localCharacterKey = null; ResetLocalOceanState(); LocalOwnedSerpents.Clear(); LocallyReportedSerpentKills.Clear(); } return; } string text = ((object)((Character)Player.m_localPlayer).GetZDOID()/*cast due to .constrained prefix*/).ToString(); if (!(_localCharacterKey == text)) { _localCharacterKey = text; ResetLocalOceanState(); LocalOwnedSerpents.Clear(); LocallyReportedSerpentKills.Clear(); _nextStateHeartbeatTime = 0f; Log.LogInfo((object)("LOCAL OCEAN STATE | Character session initialized: " + text)); } } private static void ResetLocalOceanState() { _localOceanDisturbance = 0f; _lastLocalOceanActivityTime = 0f; _nextLocalDisturbanceDecayTick = 0f; _lastLocalDisturbanceLogBucket = -1; _localDisturbanceStage = OceanDisturbanceStage.Calm; _lastSerpentAttemptTime = -99999f; _nextStateHeartbeatTime = 0f; } private static bool IsActualOcean(Biome biome) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 return (int)biome == 256; } private static float GetOceanDisturbanceMaximum() { return Mathf.Max(1f, OceanDisturbanceMaximum.Value); } private static float GetRestlessThreshold() { return Mathf.Clamp(RestlessThreshold.Value, 0f, GetOceanDisturbanceMaximum()); } private static float GetDangerousThreshold() { return Mathf.Clamp(Mathf.Max(GetRestlessThreshold(), DangerousThreshold.Value), 0f, GetOceanDisturbanceMaximum()); } private static float GetSevereThreshold() { return Mathf.Clamp(Mathf.Max(GetDangerousThreshold(), SevereThreshold.Value), 0f, GetOceanDisturbanceMaximum()); } private static float GetCatastrophicThreshold() { return Mathf.Clamp(Mathf.Max(GetSevereThreshold(), CatastrophicThreshold.Value), 0f, GetOceanDisturbanceMaximum()); } private static OceanDisturbanceStage GetDisturbanceStage(float disturbance) { if (disturbance >= GetCatastrophicThreshold()) { return OceanDisturbanceStage.Catastrophic; } if (disturbance >= GetSevereThreshold()) { return OceanDisturbanceStage.Severe; } if (disturbance >= GetDangerousThreshold()) { return OceanDisturbanceStage.Dangerous; } if (disturbance >= GetRestlessThreshold()) { return OceanDisturbanceStage.Restless; } return OceanDisturbanceStage.Calm; } private static string GetStageWarningText(OceanDisturbanceStage stage) { return stage switch { OceanDisturbanceStage.Restless => RestlessWarningText.Value, OceanDisturbanceStage.Dangerous => DangerousWarningText.Value, OceanDisturbanceStage.Severe => SevereWarningText.Value, OceanDisturbanceStage.Catastrophic => CatastrophicWarningText.Value, _ => "", }; } private static void ShowCenterMessage(string text) { if (!string.IsNullOrEmpty(text) && !((Object)(object)MessageHud.instance == (Object)null)) { MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false, true); } } private static void UpdateLocalDisturbanceStage(float oldValue, float newValue) { OceanDisturbanceStage disturbanceStage = GetDisturbanceStage(oldValue); OceanDisturbanceStage oceanDisturbanceStage = (_localDisturbanceStage = GetDisturbanceStage(newValue)); if (disturbanceStage == oceanDisturbanceStage) { return; } Log.LogWarning((object)$"PERSONAL OCEAN STAGE | {oceanDisturbanceStage.ToString().ToUpper()} | {newValue:F1}/{GetOceanDisturbanceMaximum():F1}"); SendLocalStateReport(); if (EnableOceanWarnings.Value && oceanDisturbanceStage > disturbanceStage) { string stageWarningText = GetStageWarningText(oceanDisturbanceStage); if (!string.IsNullOrEmpty(stageWarningText)) { ShowCenterMessage(stageWarningText); } } } private static void AddPersonalOceanDisturbance(float amount, string reason, bool requireActualOcean, Biome biome) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) if (EnableOceanDisturbance.Value && !((Object)(object)Player.m_localPlayer == (Object)null) && !(amount <= 0f) && (!requireActualOcean || IsActualOcean(biome))) { float oceanDisturbanceMaximum = GetOceanDisturbanceMaximum(); float localOceanDisturbance = _localOceanDisturbance; _localOceanDisturbance = Mathf.Clamp(_localOceanDisturbance + amount, 0f, oceanDisturbanceMaximum); _lastLocalOceanActivityTime = Time.time; _nextLocalDisturbanceDecayTick = Time.time + Mathf.Max(0f, DisturbanceDecayDelay.Value); _lastLocalDisturbanceLogBucket = Mathf.FloorToInt(_localOceanDisturbance / 5f); UpdateLocalDisturbanceStage(localOceanDisturbance, _localOceanDisturbance); float num = _localOceanDisturbance - localOceanDisturbance; if (LogOceanDisturbance.Value && num > 0.001f) { Log.LogWarning((object)$"PERSONAL OCEAN DISTURBANCE | +{num:F1} {reason} | Total: {_localOceanDisturbance:F1} / {oceanDisturbanceMaximum:F1}"); } SendLocalStateReport(); } } private static void AddPersonalOceanDisturbance(Biome biome, float amount, string reason) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) AddPersonalOceanDisturbance(amount, reason, requireActualOcean: true, biome); } private static void AddPersonalSerpentKillDisturbance() { float num = Mathf.Clamp(DisturbancePerSerpentKill.Value, 0f, 100f); if (!(num <= 0f)) { AddPersonalOceanDisturbance(num, "Serpent Slain", requireActualOcean: false, (Biome)0); } } private static bool HasActiveOceanTreasureProxy() { //IL_0036: Unknown result type (might be due to invalid IL or missing references) foreach (ActiveTreasureProxy value in ActiveProxyByBobber.Values) { if (value != null && !value.Completed && value.Treasure != null && IsActualOcean(value.Treasure.Biome)) { return true; } } return false; } private static void UpdateLocalOceanDisturbance() { if (!EnableOceanDisturbance.Value || (Object)(object)Player.m_localPlayer == (Object)null) { return; } if (_localOceanDisturbance <= 0f) { _localOceanDisturbance = 0f; return; } if (HasActiveOceanTreasureProxy()) { _lastLocalOceanActivityTime = Time.time; _nextLocalDisturbanceDecayTick = Time.time + Mathf.Max(0f, DisturbanceDecayDelay.Value); return; } float num = Mathf.Max(0f, DisturbanceDecayDelay.Value); if (Time.time < _lastLocalOceanActivityTime + num || Time.time < _nextLocalDisturbanceDecayTick) { return; } _nextLocalDisturbanceDecayTick = Time.time + 1f; float num2 = Mathf.Max(0f, DisturbanceDecayPerSecond.Value); if (num2 <= 0f) { return; } float localOceanDisturbance = _localOceanDisturbance; _localOceanDisturbance = Mathf.Max(0f, _localOceanDisturbance - num2); UpdateLocalDisturbanceStage(localOceanDisturbance, _localOceanDisturbance); if (LogOceanDisturbance.Value) { int num3 = Mathf.FloorToInt(_localOceanDisturbance / 5f); if (num3 != _lastLocalDisturbanceLogBucket || _localOceanDisturbance <= 0f) { _lastLocalDisturbanceLogBucket = num3; Log.LogInfo((object)$"PERSONAL OCEAN DECAY | {localOceanDisturbance:F1} -> {_localOceanDisturbance:F1} | Stage: {GetDisturbanceStage(_localOceanDisturbance)}"); } } } private static void ResetNetworkRuntimeState() { _registeredRpcInstance = null; ServerPlayerStates.Clear(); ServerCreditedSerpentKills.Clear(); LocallyReportedSerpentKills.Clear(); LocalOwnedSerpents.Clear(); _sharedStormStage = OceanDisturbanceStage.Calm; } internal static void RegisterNetworkRpcs() { if (ZRoutedRpc.instance == null || _registeredRpcInstance == ZRoutedRpc.instance) { return; } try { ZRoutedRpc.instance.Register<ZPackage>("SunkenSpoils_StateReport_v1", (Action<long, ZPackage>)RPC_ReceiveStateReport); ZRoutedRpc.instance.Register<ZPackage>("SunkenSpoils_SharedStorm_v1", (Action<long, ZPackage>)RPC_ReceiveSharedStorm); ZRoutedRpc.instance.Register<ZPackage>("SunkenSpoils_SerpentKillReport_v1", (Action<long, ZPackage>)RPC_ReceiveSerpentKillReport); ZRoutedRpc.instance.Register<ZPackage>("SunkenSpoils_SerpentKillAward_v1", (Action<long, ZPackage>)RPC_ReceiveSerpentKillAward); _registeredRpcInstance = ZRoutedRpc.instance; _nextStateHeartbeatTime = 0f; Log.LogWarning((object)"MULTIPLAYER RPC | Sunken Spoils network handlers registered."); } catch (Exception arg) { if (!_warnedRpcRegistration) { _warnedRpcRegistration = true; Log.LogError((object)$"MULTIPLAYER RPC REGISTRATION FAILED | {arg}"); } } } private static bool NetworkReady() { return (Object)(object)ZNet.instance != (Object)null && ZRoutedRpc.instance != null && _registeredRpcInstance == ZRoutedRpc.instance; } private static bool IsServer() { return (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer(); } private static long GetServerPeerID() { if (ZRoutedRpc.instance == null) { return 0L; } try { if (_getServerPeerIdMethod == null) { _getServerPeerIdMethod = AccessTools.Method(typeof(ZRoutedRpc), "GetServerPeerID", (Type[])null, (Type[])null); } if (_getServerPeerIdMethod == null) { Log.LogWarning((object)"MULTIPLAYER RPC | GetServerPeerID is unavailable in this Valheim build."); return 0L; } return (_getServerPeerIdMethod.Invoke(ZRoutedRpc.instance, null) is long num) ? num : 0; } catch (Exception ex) { Log.LogWarning((object)("MULTIPLAYER RPC | Could not resolve server peer ID: " + ex.Message)); return 0L; } } private static long GetLocalPeerID() { return ((Object)(object)ZNet.instance == (Object)null) ? 0 : ZNet.GetUID(); } private static OceanDisturbanceStage ClampStage(int value) { value = Mathf.Clamp(value, 0, 4); return (OceanDisturbanceStage)value; } private static void UpdateStateHeartbeat() { if (!((Object)(object)Player.m_localPlayer == (Object)null) && NetworkReady() && !(Time.time < _nextStateHeartbeatTime)) { _nextStateHeartbeatTime = Time.time + 5f; SendLocalStateReport(); } } private static void SendLocalStateReport() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown //IL_0076: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Player.m_localPlayer == (Object)null || !NetworkReady()) { return; } ZDOID zDOID = ((Character)Player.m_localPlayer).GetZDOID(); OceanDisturbanceStage disturbanceStage = GetDisturbanceStage(_localOceanDisturbance); if (IsServer()) { ServerReceiveState(GetLocalPeerID(), zDOID, _localOceanDisturbance, disturbanceStage); return; } long serverPeerID = GetServerPeerID(); if (serverPeerID == 0) { return; } try { ZPackage val = new ZPackage(); val.Write(zDOID); val.Write(_localOceanDisturbance); val.Write((int)disturbanceStage); ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerID, "SunkenSpoils_StateReport_v1", new object[1] { val }); } catch (Exception ex) { Log.LogWarning((object)("STATE REPORT FAILED | " + ex.Message)); } } private static void RPC_ReceiveStateReport(long sender, ZPackage pkg) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) if (!IsServer() || pkg == null) { return; } try { ZDOID characterID = pkg.ReadZDOID(); float disturbance = pkg.ReadSingle(); OceanDisturbanceStage stage = ClampStage(pkg.ReadInt()); ServerReceiveState(sender, characterID, disturbance, stage); SendSharedStormStageToPeer(sender); } catch (Exception ex) { Log.LogWarning((object)("SERVER STATE REPORT FAILED | " + ex.Message)); } } private static void ServerReceiveState(long peerID, ZDOID characterID, float disturbance, OceanDisturbanceStage stage) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) if (IsServer()) { if (!ServerPlayerStates.TryGetValue(peerID, out var value)) { value = new ServerPlayerOceanState { PeerID = peerID }; ServerPlayerStates[peerID] = value; } value.CharacterID = characterID; value.Disturbance = Mathf.Max(0f, disturbance); value.Stage = stage; value.LastReportTime = Time.time; RecomputeSharedStormStage(forceBroadcast: false); } } private static void UpdateServerOceanCoordinator() { if (!IsServer() || !NetworkReady()) { return; } List<long> list = null; foreach (KeyValuePair<long, ServerPlayerOceanState> serverPlayerState in ServerPlayerStates) { if (Time.time - serverPlayerState.Value.LastReportTime > 20f) { if (list == null) { list = new List<long>(); } list.Add(serverPlayerState.Key); } } if (list == null) { return; } foreach (long item in list) { ServerPlayerStates.Remove(item); } RecomputeSharedStormStage(forceBroadcast: false); } private static void RecomputeSharedStormStage(bool forceBroadcast) { if (!IsServer()) { return; } OceanDisturbanceStage oceanDisturbanceStage = OceanDisturbanceStage.Calm; foreach (ServerPlayerOceanState value in ServerPlayerStates.Values) { if (value != null && value.Stage > oceanDisturbanceStage) { oceanDisturbanceStage = value.Stage; } } if (oceanDisturbanceStage < OceanDisturbanceStage.Severe) { oceanDisturbanceStage = OceanDisturbanceStage.Calm; } if (forceBroadcast || oceanDisturbanceStage != _sharedStormStage) { OceanDisturbanceStage sharedStormStage = _sharedStormStage; _sharedStormStage = oceanDisturbanceStage; Log.LogWarning((object)$"SHARED OCEAN WEATHER STAGE | {sharedStormStage} -> {_sharedStormStage} | Tracked Players: {ServerPlayerStates.Count}"); BroadcastSharedStormStage(); } } private static void BroadcastSharedStormStage() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown if (!IsServer() || ZRoutedRpc.instance == null) { return; } try { ZPackage val = new ZPackage(); val.Write((int)_sharedStormStage); ZRoutedRpc.instance.InvokeRoutedRPC(0L, "SunkenSpoils_SharedStorm_v1", new object[1] { val }); } catch (Exception ex) { Log.LogWarning((object)("SHARED STORM BROADCAST FAILED | " + ex.Message)); } } private static void SendSharedStormStageToPeer(long peerID) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown if (!IsServer() || ZRoutedRpc.instance == null || peerID == 0) { return; } try { ZPackage val = new ZPackage(); val.Write((int)_sharedStormStage); ZRoutedRpc.instance.InvokeRoutedRPC(peerID, "SunkenSpoils_SharedStorm_v1", new object[1] { val }); } catch (Exception ex) { Log.LogWarning((object)("SHARED STORM PEER SYNC FAILED | " + ex.Message)); } } private static void RPC_ReceiveSharedStorm(long sender, ZPackage pkg) { if (pkg == null || (!IsServer() && sender != GetServerPeerID())) { return; } try { OceanDisturbanceStage oceanDisturbanceStage = ClampStage(pkg.ReadInt()); if (oceanDisturbanceStage < OceanDisturbanceStage.Severe) { oceanDisturbanceStage = OceanDisturbanceStage.Calm; } if (oceanDisturbanceStage != _sharedStormStage) { OceanDisturbanceStage sharedStormStage = _sharedStormStage; _sharedStormStage = oceanDisturbanceStage; Log.LogWarning((object)$"SHARED OCEAN WEATHER RECEIVED | {sharedStormStage} -> {_sharedStormStage}"); } } catch (Exception ex) { Log.LogWarning((object)("SHARED STORM RECEIVE FAILED | " + ex.Message)); } } internal static void ApplySharedEnvironmentOverride(ref string currentOverride) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) if (!EnableDisturbanceWeather.Value || (Object)(object)Player.m_localPlayer == (Object)null || !string.IsNullOrEmpty(currentOverride)) { return; } if (WeatherOnlyWhileInOcean.Value) { Biome biome = Heightmap.FindBiome(((Component)Player.m_localPlayer).transform.position); if (!IsActualOcean(biome)) { return; } } string text = null; if (_sharedStormStage >= OceanDisturbanceStage.Catastrophic) { text = CatastrophicWeatherEnvironment.Value; } else if (_sharedStormStage >= OceanDisturbanceStage.Severe) { text = SevereWeatherEnvironment.Value; } if (!string.IsNullOrWhiteSpace(text)) { currentOverride = text.Trim(); } } private static int GetStageSerpentCap(OceanDisturbanceStage stage) { int num = 0; switch (stage) { case OceanDisturbanceStage.Dangerous: num = DangerousSerpentCap.Value; break; case OceanDisturbanceStage.Severe: num = SevereSerpentCap.Value; break; case OceanDisturbanceStage.Catastrophic: num = CatastrophicSerpentCap.Value; break; } return Mathf.Min(Mathf.Max(0, num), Mathf.Max(0, AbsoluteNearbySerpentCap.Value)); } private static float GetStageSerpentChance(OceanDisturbanceStage stage) { return stage switch { OceanDisturbanceStage.Dangerous => Mathf.Clamp(DangerousSerpentChance.Value, 0f, 100f), OceanDisturbanceStage.Severe => Mathf.Clamp(SevereSerpentChance.Value, 0f, 100f), OceanDisturbanceStage.Catastrophic => Mathf.Clamp(CatastrophicSerpentChance.Value, 0f, 100f), _ => 0f, }; } private static bool IsSerpent(Character character) { if ((Object)(object)character == (Object)null) { return false; } string name = ((Object)((Component)character).gameObject).name; return !string.IsNullOrEmpty(name) && name.StartsWith("Serpent", StringComparison.OrdinalIgnoreCase); } private static int CountAllNearbySerpents(Vector3 position) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) int num = 0; float num2 = Mathf.Max(1f, SerpentNearbyCheckRadius.Value); Character[] array = Object.FindObjectsByType<Character>((FindObjectsSortMode)0); Character[] array2 = array; foreach (Character val in array2) { if (IsSerpent(val) && Vector3.Distance(position, ((Component)val).transform.position) <= num2) { num++; } } return num; } private static int CountPersonalNearbyAttributedSerpents(Vector3 playerPosition) { //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) int num = 0; float num2 = Mathf.Max(1f, SerpentNearbyCheckRadius.Value); for (int num3 = LocalOwnedSerpents.Count - 1; num3 >= 0; num3--) { LocalOwnedSerpent localOwnedSerpent = LocalOwnedSerpents[num3]; if (localOwnedSerpent == null || (Object)(object)localOwnedSerpent.GameObject == (Object)null) { LocalOwnedSerpents.RemoveAt(num3); } else { Character component = localOwnedSerpent.GameObject.GetComponent<Character>(); if ((Object)(object)component == (Object)null || component.IsDead()) { LocalOwnedSerpents.RemoveAt(num3); } else if (Vector3.Distance(playerPosition, ((Component)component).transform.position) <= num2) { num++; } } } return num; } private static bool TryFindSerpentSpawnPosition(Vector3 playerPosition, out Vector3 spawnPosition) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Invalid comparison between Unknown and I4 //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) spawnPosition = Vector3.zero; if ((Object)(object)ZoneSystem.instance == (Object)null) { return false; } float num = Mathf.Max(15f, SerpentSpawnDistanceMinimum.Value); float num2 = Mathf.Max(num, SerpentSpawnDistanceMaximum.Value); for (int i = 0; i < 20; i++) { float num3 = Random.Range(0f, MathF.PI * 2f); float num4 = Random.Range(num, num2); Vector3 val = playerPosition + new Vector3(Mathf.Cos(num3) * num4, 0f, Mathf.Sin(num3) * num4); if ((int)Heightmap.FindBiome(val) == 256) { val.y = ZoneSystem.instance.m_waterLevel - 1f; spawnPosition = val; return true; } } return false; } private static bool SpawnPersonalAttractedSerpent() { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)ZNetScene.instance == (Object)null) { return false; } GameObject prefab = ZNetScene.instance.GetPrefab("Serpent"); if ((Object)(object)prefab == (Object)null) { Log.LogError((object)"SERPENT SPAWN FAILED | Prefab 'Serpent' not found."); return false; } Vector3 position = ((Component)Player.m_localPlayer).transform.position; if (!TryFindSerpentSpawnPosition(position, out var spawnPosition)) { Log.LogWarning((object)"SERPENT SPAWN FAILED | No valid nearby Ocean position."); return false; } Vector3 val = position - spawnPosition; val.y = 0f; Quaternion val2 = ((((Vector3)(ref val)).sqrMagnitude > 0.01f) ? Quaternion.LookRotation(((Vector3)(ref val)).normalized) : Quaternion.identity); try { GameObject val3 = Object.Instantiate<GameObject>(prefab, spawnPosition, val2); if ((Object)(object)val3 == (Object)null) { return false; } Character component = val3.GetComponent<Character>(); ZDOID iD = (ZDOID)(((Object)(object)component != (Object)null) ? component.GetZDOID() : default(ZDOID)); LocalOwnedSerpents.Add(new LocalOwnedSerpent { ID = iD, GameObject = val3 }); Log.LogWarning((object)("PERSONAL SEA MONSTER ATTRACTED | Serpent spawned " + $"{Vector3.Distance(position, spawnPosition):F1}m away | " + $"Tracked personal serpents: {LocalOwnedSerpents.Count}")); ShowCenterMessage("Something answers from the deep..."); return true; } catch (Exception arg) { Log.LogError((object)$"SERPENT SPAWN FAILED | {arg}"); return false; } } private static void TryPersonalSerpentAttractionRoll(Biome biome) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) if (!EnableOceanMonsterAttraction.Value || !EnableOceanDisturbance.Value || !IsActualOcean(biome) || (Object)(object)Player.m_localPlayer == (Object)null) { return; } OceanDisturbanceStage disturbanceStage = GetDisturbanceStage(_localOceanDisturbance); int stageSerpentCap = GetStageSerpentCap(disturbanceStage); if (stageSerpentCap <= 0) { return; } float stageSerpentChance = GetStageSerpentChance(disturbanceStage); if (stageSerpentChance <= 0f) { return; } Vector3 position = ((Component)Player.m_localPlayer).transform.position; int num = CountPersonalNearbyAttributedSerpents(position); if (num >= stageSerpentCap) { Log.LogInfo((object)$"PERSONAL SERPENT BLOCKED | Stage {disturbanceStage} | Personal Nearby {num}/{stageSerpentCap}"); return; } if (EnableGlobalSerpentSafetyCap.Value) { int num2 = CountAllNearbySerpents(position); int num3 = Mathf.Max(1, GlobalMaximumNearbySerpents.Value); if (num2 >= num3) { Log.LogWarning((object)$"GLOBAL SERPENT SAFETY CAP | Nearby {num2}/{num3} | Personal spawn suppressed."); return; } } float num4 = Mathf.Max(0f, SerpentAttractionCooldown.Value); float num5 = Time.time - _lastSerpentAttemptTime; if (num5 < num4) { Log.LogInfo((object)$"PERSONAL SERPENT COOLDOWN | {num4 - num5:F1}s remaining."); return; } _lastSerpentAttemptTime = Time.time; float num6 = Random.Range(0f, 100f); bool flag = num6 < stageSerpentChance; Log.LogWarning((object)($"PERSONAL SERPENT ATTRACTION ROLL | Stage: {disturbanceStage} | " + $"Disturbance: {_localOceanDisturbance:F1}/{GetOceanDisturbanceMaximum():F1} | " + $"Personal Nearby: {num}/{stageSerpentCap} | Chance: {stageSerpentChance:F1}% | " + string.Format("Roll: {0:F1} | Result: {1}", num6, flag ? "SUCCESS" : "No Spawn"))); if (flag) { SpawnPersonalAttractedSerpent(); } } internal static void ObserveSerpentDamageResult(Character serpent, HitData hit) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)serpent == (Object)null || hit == null || !IsSerpent(serpent) || !serpent.IsDead() || !NetworkReady()) { return; } Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); if (!((Object)(object)val == (Object)null)) { ZDOID zDOID = serpent.GetZDOID(); if (!LocallyReportedSerpentKills.Contains(zDOID)) { LocallyReportedSerpentKills.Add(zDOID); ReportSerpentKillToServer(zDOID, ((Character)val).GetZDOID()); } } } private static void ReportSerpentKillToServer(ZDOID serpentID, ZDOID killerCharacterID) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) if (!NetworkReady()) { return; } if (IsServer()) { ServerProcessSerpentKillReport(GetLocalPeerID(), serpentID, killerCharacterID); return; } long serverPeerID = GetServerPeerID(); if (serverPeerID == 0) { return; } try { ZPackage val = new ZPackage(); val.Write(serpentID); val.Write(killerCharacterID); ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerID, "SunkenSpoils_SerpentKillReport_v1", new object[1] { val }); } catch (Exception ex) { Log.LogWarning((object)("SERPENT KILL REPORT FAILED | " + ex.Message)); } } private static void RPC_ReceiveSerpentKillReport(long sender, ZPackage pkg) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) if (!IsServer() || pkg == null) { return; } try { ServerProcessSerpentKillReport(sender, pkg.ReadZDOID(), pkg.ReadZDOID()); } catch (Exception ex) { Log.LogWarning((object)("SERVER SERPENT KILL REPORT FAILED | " + ex.Message)); } } private static void ServerProcessSerpentKillReport(long reportingPeer, ZDOID serpentID, ZDOID killerCharacterID) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) if (!IsServer() || ServerCreditedSerpentKills.Contains(serpentID)) { return; } long num = 0L; foreach (ServerPlayerOceanState value2 in ServerPlayerStates.Values) { if (value2 != null && ((ZDOID)(ref value2.CharacterID)).Equals(killerCharacterID)) { num = value2.PeerID; break; } } if (num == 0L && ServerPlayerStates.TryGetValue(reportingPeer, out var value) && ((ZDOID)(ref value.CharacterID)).Equals(killerCharacterID)) { num = reportingPeer; } if (num == 0) { Log.LogWarning((object)$"SERPENT KILL CREDIT FAILED | Could not map killer character {killerCharacterID}."); return; } ServerCreditedSerpentKills.Add(serpentID); Log.LogWarning((object)$"SERPENT KILL CREDIT | Serpent {serpentID} | Killer {killerCharacterID} | Peer {num}"); SendSerpentKillAward(num); } private static void SendSerpentKillAward(long killerPeer) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown if (!IsServer() || !NetworkReady() || killerPeer == 0) { return; } if ((Object)(object)Player.m_localPlayer != (Object)null && killerPeer == GetLocalPeerID()) { ApplySerpentKillAward(); return; } try { ZPackage val = new ZPackage(); val.Write(1); ZRoutedRpc.instance.InvokeRoutedRPC(killerPeer, "SunkenSpoils_SerpentKillAward_v1", new object[1] { val }); } catch (Exception ex) { Log.LogWarning((object)("SERPENT KILL AWARD SEND FAILED | " + ex.Message)); } } private static void RPC_ReceiveSerpentKillAward(long sender, ZPackage pkg) { if (pkg == null || (Object)(object)Player.m_localPlayer == (Object)null || (!IsServer() && sender != GetServerPeerID())) { return; } try { if (pkg.ReadInt() == 1) { ApplySerpentKillAward(); } } catch (Exception ex) { Log.LogWarning((object)("SERPENT KILL AWARD RECEIVE FAILED | " + ex.Message)); } } private static void ApplySerpentKillAward() { Log.LogWarning((object)$"SERPENT SLAIN | Personal disturbance reward: +{DisturbancePerSerpentKill.Value:F1}"); AddPersonalSerpentKillDisturbance(); } internal static string GetTreasureName(TreasureTier tier) { return tier switch { TreasureTier.Tier1Crate => "Driftwood Crate", TreasureTier.Tier2Barrel => "Waterlogged Barrel", TreasureTier.Tier3Chest => "Sunken Chest", _ => "Unknown Treasure", }; } internal static string GetTreasurePrefabName(TreasureTier tier) { return tier switch { TreasureTier.Tier1Crate => "dvergrprops_crate_ashlands", TreasureTier.Tier2Barrel => "piece_chest_barrel", TreasureTier.Tier3Chest => "piece_chest", _ => null, }; } private static int GetLootRollCount(TreasureTier tier) { return tier switch { TreasureTier.Tier1Crate => Tier1LootRolls.Value, TreasureTier.Tier2Barrel => Tier2LootRolls.Value, TreasureTier.Tier3Chest => Tier3LootRolls.Value, _ => 1, }; } private static void GetCoinRange(TreasureTier tier, out int min, out int max) { min = 0; max = 0; switch (tier) { case TreasureTier.Tier1Crate: min = Tier1CoinMin.Value; max = Tier1CoinMax.Value; break; case TreasureTier.Tier2Barrel: min = Tier2CoinMin.Value; max = Tier2CoinMax.Value; break; case TreasureTier.Tier3Chest: min = Tier3CoinMin.Value; max = Tier3CoinMax.Value; break; } if (max < min) { int num = min; min = max; max = num; } } private static void GetTreasureTierWeights(Biome biome, OceanDisturbanceStage stage, out int crate, out int barrel, out int chest) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) crate = Mathf.Max(0, CrateWeight.Value); barrel = Mathf.Max(0, BarrelWeight.Value); chest = Mathf.Max(0, ChestWeight.Value); if (EnableDisturbanceRewardScaling.Value && IsActualOcean(biome)) { switch (stage) { case OceanDisturbanceStage.Dangerous: crate = DangerousCrateWeight.Value; barrel = DangerousBarrelWeight.Value; chest = DangerousChestWeight.Value; break; case OceanDisturbanceStage.Severe: crate = SevereCrateWeight.Value; barrel = SevereBarrelWeight.Value; chest = SevereChestWeight.Value; break; case OceanDisturbanceStage.Catastrophic: crate = CatastrophicCrateWeight.Value; barrel = CatastrophicBarrelWeight.Value; chest = CatastrophicChestWeight.Value; break; } crate = Mathf.Max(0, crate); barrel = Mathf.Max(0, barrel); chest = Mathf.Max(0, chest); } } internal static TreasureTier RollTreasureTier(Biome biome, OceanDisturbanceStage stage) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) GetTreasureTierWeights(biome, stage, out var crate, out var barrel, out var chest); int num = crate + barrel + chest; if (num <= 0) { return TreasureTier.Tier1Crate; } int num2 = Random.Range(0, num); if (num2 < crate) { return TreasureTier.Tier1Crate; } num2 -= crate; if (num2 < barrel) { return TreasureTier.Tier2Barrel; } return TreasureTier.Tier3Chest; } private static float GetTierFightMultiplier(TreasureTier tier) { return tier switch { TreasureTier.Tier1Crate => Tier1FightMultiplier.Value, TreasureTier.Tier2Barrel => Tier2FightMultiplier.Value, TreasureTier.Tier3Chest => Tier3FightMultiplier.Value, _ => 1f, }; } private static float GetFinalFightMultiplier(TreasureTier tier) { return Mathf.Max(0.01f, TreasureFightStrength.Value) * Mathf.Max(0.01f, GetTierFightMultiplier(tier)); } private static void ApplyTreasureFightStrength(ActiveTreasureProxy proxy) { if (proxy != null && !((Object)(object)proxy.Fish == (Object)null) && !proxy.FightStrengthApplied) { proxy.OriginalStaminaUse = proxy.Fish.m_staminaUse; proxy.OriginalEscapeStaminaUse = proxy.Fish.m_escapeStaminaUse; proxy.AppliedFightMultiplier = GetFinalFightMultiplier(proxy.Treasure.Tier); proxy.Fish.m_staminaUse = proxy.OriginalStaminaUse * proxy.AppliedFightMultiplier; proxy.Fish.m_escapeStaminaUse = proxy.OriginalEscapeStaminaUse * proxy.AppliedFightMultiplier; proxy.FightStrengthApplied = true; Log.LogWarning((object)("TREASURE FIGHT STRENGTH APPLIED | " + GetTreasureName(proxy.Treasure.Tier) + " | " + $"{proxy.AppliedFightMultiplier:F2}x | Stamina {proxy.OriginalStaminaUse:F2}->{proxy.Fish.m_staminaUse:F2} | " + $"Escape {proxy.OriginalEscapeStaminaUse:F2}->{proxy.Fish.m_escapeStaminaUse:F2}")); } } private static void RestoreTreasureFightStrength(ActiveTreasureProxy proxy) { if (proxy != null && !((Object)(object)proxy.Fish == (Object)null) && proxy.FightStrengthApplied) { proxy.Fish.m_staminaUse = proxy.OriginalStaminaUse; proxy.Fish.m_escapeStaminaUse = proxy.OriginalEscapeStaminaUse; proxy.FightStrengthApplied = false; Log.LogInfo((object)"Treasure proxy fish stamina values restored."); } } private static bool HasRequiredProgression(string globalKey) { if (string.IsNullOrEmpty(globalKey) || !EnableProgressionGating.Value) { return true; } if ((Object)(object)ZoneSystem.instance == (Object)null) { if (!_warnedProgressionUnavailable) { _warnedProgressionUnavailable = true; Log.LogWarning((object)"PROGRESSION CHECK FAILED | ZoneSystem unavailable. Gated loot remains locked."); } return false; } try { return ZoneSystem.instance.GetGlobalKey(globalKey); } catch (Exception ex) { Log.LogWarning((object)("PROGRESSION CHECK FAILED | " + globalKey + " | " + ex.Message)); return false; } } private static bool IsLootAvailable(LootEntry entry, TreasureTier tier) { if (entry == null || tier < entry.MinimumTier) { return false; } if (entry.IsOre && !EnableOreLoot.Value) { return false; } return HasRequiredProgression(entry.RequiredGlobalKey); } private static int GetEffectiveLootWeight(LootEntry entry, TreasureTier tier) { if (!IsLootAvailable(entry, tier)) { return 0; } float num = entry.Weight; if (entry.IsOre) { num *= Mathf.Max(0f, OreLootWeightMultiplier.Value); } return Mathf.Max(0, Mathf.RoundToInt(num)); } private static LootEntry E(string prefab, int min, int max, int weight, TreasureTier minimumTier = TreasureTier.Tier1Crate, string progressionKey = null, bool ore = false) { return new LootEntry(prefab, min, max, weight, minimumTier, progressionKey, ore); } private static void SetLootTable(string biome, params LootEntry[] entries) { LootTables[biome] = new List<LootEntry>(entries); } private static void BuildLootTables() { LootTables.Clear(); SetLootTable("Meadows", E("Honey", 1, 4, 25), E("Raspberry", 2, 6, 25), E("LeatherScraps", 1, 4, 20), E("Flint", 2, 6, 20), E("MeadHealthMinor", 1, 2, 10), E("MeadStaminaMinor", 1, 2, 10), E("Amber", 1, 3, 8), E("Ruby", 1, 1, 2, TreasureTier.Tier3Chest)); SetLootTable("BlackForest", E("Blueberries", 2, 6, 25), E("Amber", 1, 4, 25), E("AmberPearl", 1, 2, 15), E("BronzeNails", 4, 12, 15), E("MeadHealthMinor", 1, 2, 10), E("MeadStaminaMinor", 1, 2, 10), E("Ruby", 1, 2, 4, TreasureTier.Tier2Barrel), E("CopperOre", 1, 3, 8, TreasureTier.Tier1Crate, "defeated_eikthyr", ore: true), E("TinOre", 1, 2, 6, TreasureTier.Tier1Crate, "defeated_eikthyr", ore: true)); SetLootTable("Swamp", E("Bloodbag", 1, 4, 25), E("Entrails", 1, 4, 20), E("Ooze", 1, 3, 20), E("Amber", 1, 3, 10), E("Chain", 1, 2, 8, TreasureTier.Tier2Barrel), E("MeadPoisonResist", 1, 2, 12, TreasureTier.Tier2Barrel), E("Ruby", 1, 2, 4, TreasureTier.Tier3Chest), E("IronScrap", 1, 3, 8, TreasureTier.Tier2Barrel, "defeated_gdking", ore: true)); SetLootTable("Mountain", E("Obsidian", 2, 6, 25), E("FreezeGland", 1, 3, 20), E("WolfPelt", 1, 3, 20), E("MeadFrostResist", 1, 2, 15), E("Amber", 1, 3, 10), E("Ruby", 1, 2, 8, TreasureTier.Tier2Barrel), E("SilverNecklace", 1, 1, 2, TreasureTier.Tier3Chest), E("SilverOre", 1, 2, 6, TreasureTier.Tier2Barrel, "defeated_bonemass", ore: true)); SetLootTable("Plains", E("Cloudberry", 2, 6, 25), E("Needle", 1, 4, 20), E("Barley", 2, 5, 18), E("MeadHealthMedium", 1, 2, 12, TreasureTier.Tier2Barrel), E("MeadStaminaMedium", 1, 2, 12, TreasureTier.Tier2Barrel), E("Ruby", 1, 2, 8), E("SilverNecklace", 1, 1, 2, TreasureTier.Tier3Chest), E("BlackMetalScrap", 1, 3, 6, TreasureTier.Tier2Barrel, "defeated_dragon", ore: true)); SetLootTable("Mistlands", E("MushroomMagecap", 1, 4, 20), E("MushroomJotunPuffs", 1, 4, 20), E("RoyalJelly", 1, 3, 20), E("Carapace", 1, 3, 12, TreasureTier.Tier2Barrel), E("Sap", 1, 3, 12, TreasureTier.Tier2Barrel), E("MeadEitrMinor", 1, 2, 10, TreasureTier.Tier2Barrel), E("Ruby", 1, 2, 6), E("CopperScrap", 1, 3, 5, TreasureTier.Tier2Barrel, "defeated_goblinking", ore: true), E("IronScrap", 1, 2, 4, TreasureTier.Tier2Barrel, "defeated_goblinking", ore: true)); SetLootTable("AshLands", E("CharredBone", 1, 4, 24), E("Grausten", 2, 6, 20), E("AskHide", 1, 3, 15), E("Ruby", 1, 2, 8), E("FlametalOreNew", 1, 2, 4, TreasureTier.Tier3Chest, "defeated_queen", ore: true)); SetLootTable("Ocean", E("FishingBaitOcean", 2, 6, 25), E("Chitin", 1, 3, 22), E("Amber", 1, 4, 18), E("AmberPearl", 1, 3, 12), E("MeadHealthMinor", 1, 2, 10), E("MeadStaminaMinor", 1, 2, 10), E("Ruby", 1, 2, 8, TreasureTier.Tier2Barrel), E("SerpentMeat", 1, 2, 6, TreasureTier.Tier2Barrel), E("SerpentScale", 1, 1, 2, TreasureTier.Tier3Chest), E("SilverNecklace", 1, 1, 1, TreasureTier.Tier3Chest), E("IronScrap", 1, 3, 5, TreasureTier.Tier2Barrel, "defeated_gdking", ore: true)); } private unsafe static List<LootEntry> GetBiomeLootPool(Biome biome) { if (LootTables.TryGetValue(((object)(*(Biome*)(&biome))/*cast due to .constrained prefix*/).ToString(), out var value)) { return value; } return LootTables["Ocean"]; } internal static Character GetFishingFloatOwner(FishingFloat bobber) { if ((Object)(object)bobber == (Object)null) { return null; } try { return Traverse.Create((object)bobber).Method("GetOwner", Array.Empty<object>()).GetValue<Character>(); } catch (Exception ex) { Log.LogError((object)("Could not determine FishingFloat owner: " + ex.Message)); return null; } } private static bool IsLocalOwnedBobber(FishingFloat bobber) { return (Object)(object)bobber != (Object)null && (Object)(object)Player.m_localPlayer != (Object)null && (Object)(object)GetFishingFloatOwner(bobber) == (Object)(object)Player.m_localPlayer; } internal static bool TryGetFishID(Fish fish, out ZDOID fishID) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) fishID = default(ZDOID); if ((Object)(object)fish == (Object)null) { return false; } try { ZNetView component = ((Component)fish).GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null) { return false; } ZDO zDO = component.GetZDO(); if (zDO == null) { return false; } fishID = zDO.m_uid; return true; } catch (Exception ex) { Log.LogWarning((object)("Could not retrieve fish ZDOID: " + ex.Message)); return false; } } private static TreasureRollKey CreateRollKey(FishingFloat bobber, ZDOID fishID) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) return new TreasureRollKey(((Object)bobber).GetInstanceID(), fishID); } private static bool TryGetTreasureRoll(FishingFloat bobber, ZDOID fishID, out TreasureRollState state) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) state = null; return (Object)(object)bobber != (Object)null && TreasureRolls.TryGetValue(CreateRollKey(bobber, fishID), out state); } private static void StoreTreasureRoll(FishingFloat bobber, ZDOID fishID, TreasureRollState state) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)bobber == (Object)null) && state != null) { TreasureRollKey key = CreateRollKey(bobber, fishID); TreasureRolls[key] = state; if (TreasureRolls.Count > 1000) { TreasureRolls.Clear(); TreasureRolls[key] = state; } } } private static void CleanupRollHistoryForBobber(int bobberID) { List<TreasureRollKey> list = new List<TreasureRollKey>(); foreach (TreasureRollKey key in TreasureRolls.Keys) { if (key.BobberInstanceID == bobberID) { list.Add(key); } } foreach (TreasureRollKey item in list) { TreasureRolls.Remove(item); } } private static bool HasActiveProxy(FishingFloat bobber) { return (Object)(object)bobber != (Object)null && ActiveProxyByBobber.ContainsKey(((Object)bobber).GetInstanceID()); } internal static bool TryGetProxyForFish(Fish fish, out ActiveTreasureProxy proxy) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) proxy = null; ZDOID fishID; return TryGetFishID(fish, out fishID) && ActiveProxyByFish.TryGetValue(fishID, out proxy); } private static Vector3 GetTreasureProxyPosition(Fish fish) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) return ((Object)(object)fish == (Object)null) ? Vector3.zero : (((Component)fish).transform.position + Vector3.up * 0.15f); } private static GameObject CreateTreasureVisual(Fish fish, TreasureRollState treasure) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)fish == (Object)null || treasure == null || (Object)(object)ZNetScene.instance == (Object)null) { return null; } string treasurePrefabName = GetTreasurePrefabName(treasure.Tier); GameObject prefab = ZNetScene.instance.GetPrefab(treasurePrefabName); if ((Object)(object)prefab == (Object)null) { Log.LogError((object)("TREASURE VISUAL FAILED | Prefab not found: " + treasurePrefabName)); return null; } try { GameObject val = Object.Instantiate<GameObject>(prefab, GetTreasureProxyPosition(fish), Quaternion.Euler(0f, Random.Range(0f, 360f), 0f)); ((Object)val).name = "SunkenSpoils_" + treasurePrefabName + "_ProxyVisual"; PrepareTreasureVisual(val); Log.LogWarning((object)("TREASURE VISUAL SPAWNED | " + GetTreasureName(treasure.Tier) + " | " + treasurePrefabName)); return val; } catch (Exception arg) { Log.LogError((object)$"TREASURE VISUAL FAILED | {arg}"); return null; } } private static void PrepareTreasureVisual(GameObject visual) { //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)visual == (Object)null)) { Container[] componentsInChildren = visual.GetComponentsInChildren<Container>(true); foreach (Container val in componentsInChildren) { ((Behaviour)val).enabled = false; } Piece[] componentsInChildren2 = visual.GetComponentsInChildren<Piece>(true); foreach (Piece val2 in componentsInChildren2) { ((Behaviour)val2).enabled = false; } WearNTear[] componentsInChildren3 = visual.GetComponentsInChildren<WearNTear>(true); foreach (WearNTear val3 in componentsInChildren3) { ((Behaviour)val3).enabled = false; } Collider[] componentsInChildren4 = visual.GetComponentsInChildren<Collider>(true); foreach (Collider val4 in componentsInChildren4) { val4.enabled = false; } Rigidbody[] componentsInChildren5 = visual.GetComponentsInChildren<Rigidbody>(true); foreach (Rigidbody val5 in componentsInChildren5) { val5.useGravity = false; val5.isKinematic = true; val5.linearVelocity = Vector3.zero; val5.angularVelocity = Vector3.zero; } } } private static void SafeDestroyVisual(GameObject visual) { if ((Object)(object)visual == (Object)null) { return; } try { ZNetView component = visual.GetComponent<ZNetView>(); if ((Object)(object)ZNetScene.instance != (Object)null && (Object)(object)component != (Object)null) { ZNetScene.instance.Destroy(visual); } else { Object.Destroy((Object)(object)visual); } } catch { if ((Object)(object)visual != (Object)null) { Object.Destroy((Object)(object)visual); } } } private static void HideFishRenderers(ActiveTreasureProxy proxy) { if (proxy == null || (Object)(object)proxy.Fish == (Object)null) { return; } Renderer[] componentsInChildren = ((Component)proxy.Fish).GetComponentsInChildren<Renderer>(true); foreach (Renderer val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { proxy.HiddenRenderers.Add(new RendererState { Renderer = val, WasEnabled = val.enabled }); val.enabled = false; } } Log.LogInfo((object)$"Hidden {proxy.HiddenRenderers.Count} fish renderer(s)."); } private static void RestoreFishRenderers(ActiveTreasureProxy proxy) { if (proxy == null) { return; } foreach (RendererState hiddenRenderer in proxy.HiddenRenderers) { if (hiddenRenderer != null && (Object)(object)hiddenRenderer.Renderer != (Object)null) { hiddenRenderer.Renderer.enabled = hiddenRenderer.WasEnabled; } } proxy.HiddenRenderers.Clear(); } private static bool ActivateTreasureProxy(FishingFloat bobber, Fish fish, TreasureRollState treasure) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)bobber == (Object)null || (Object)(object)fish == (Object)null || treasure == null) { return false; } int instanceID = ((Object)bobber).GetInstanceID(); if (ActiveProxyByBobber.ContainsKey(instanceID)) { return false; } GameObject val = CreateTreasureVisual(fish, treasure); if ((Object)(object)val == (Object)null) { return false; } ActiveTreasureProxy activeTreasureProxy = new ActiveTreasureProxy { BobberID = instanceID, Bobber = bobber, Fish = fish, FishID = treasure.FishID, Treasure = treasure, Visual = val }; HideFishRenderers(activeTreasureProxy); ApplyTreasureFightStrength(activeTreasureProxy); ActiveProxyByBobber[instanceID] = activeTreasureProxy; ActiveProxyByFish[activeTreasureProxy.FishID] = activeTreasureProxy; AddPersonalOceanDisturbance(treasure.Biome, DisturbancePerTreasureHook.Value, "Treasure Hooked"); Log.LogWarning((object)($"TREASURE PROXY ACTIVATED | Tier {(int)treasure.Tier} | {GetTreasureName(treasure.Tier)} | " + $"Personal Reward Stage {treasure.RewardStage} | Fight {activeTreasureProxy.AppliedFightMultiplier:F2}x")); SunkenSpoilsPlugin instance = Instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(FollowTreasureProxy(activeTreasureProxy)); } return true; } private static IEnumerator FollowTreasureProxy(ActiveTreasureProxy proxy) { float startedAt = Time.time; while (proxy != null && !proxy.Completed && Time.time - startedAt < (float)TreasureVisualLifetime.Value && !((Object)(object)proxy.Fish == (Object)null) && !((Object)(object)proxy.Bobber == (Object)null) && !((Object)(object)proxy.Bobber.GetCatch() != (Object)(object)proxy.Fish)) { if ((Object)(object)proxy.Visual != (Object)null) { proxy.Visual.transform.position = GetTreasureProxyPosition(proxy.Fish); } yield return (object)new WaitForFixedUpdate(); } if (!(proxy?.Completed ?? true)) { CancelProxy(proxy, restoreFish: true, "Treasure escaped, line cancelled, or proxy timed out."); } } private static void RemoveProxyState(ActiveTreasureProxy proxy) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (proxy != null) { ActiveProxyByBobber.Remove(proxy.BobberID); ActiveProxyByFish.Remove(proxy.FishID); CleanupRollHistoryForBobber(proxy.BobberID); } } internal static void CompleteProxy(ActiveTreasureProxy proxy) { if (proxy != null && !proxy.Completed) { RestoreTreasureFightStrength(proxy); proxy.Completed = true; if ((Object)(object)proxy.Visual != (Object)null) { SafeDestroyVisual(proxy.Visual); proxy.Visual = null; } RemoveProxyState(proxy); } } internal static void CancelProxy(ActiveTreasureProxy proxy, bool restoreFish, string reason) { if (proxy != null && !proxy.Completed) { RestoreTreasureFightStrength(proxy); proxy.Completed = true; if (restoreFish) { RestoreFishRenderers(proxy); } if ((Object)(object)proxy.Visual != (Object)null) { SafeDestroyVisual(proxy.Visual); proxy.Visual = null; } RemoveProxyState(proxy); Log.LogInfo((object)("TREASURE PROXY CANCELLED | " + reason)); } } private static void RollTreasureForNibble(FishingFloat bobber, ZDOID fishID) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Unknown result type (might be due to invalid IL or missing references) if (!TryGetTreasureRoll(bobber, fishID, out var _)) { Biome val = Heightmap.FindBiome(((Component)bobber).transform.position); AddPersonalOceanDisturbance(val, DisturbancePerBite.Value, "Valid Bite"); TryPersonalSerpentAttractionRoll(val); OceanDisturbanceStage oceanDisturbanceStage = (IsActualOcean(val) ? GetDisturbanceStage(_localOceanDisturbance) : OceanDisturbanceStage.Calm); float num = TreasureChance.Value; if (IsActualOcean(val)) { num *= OceanTreasureMultiplier.Value; } num = Mathf.Clamp(num, 0f, 100f); float num2 = Random.Range(0f, 100f); bool flag = num2 < num; TreasureRollState treasureRollState = new TreasureRollState { FishID = fishID, IsTreasure = flag, Biome = val, Chance = num, Roll = num2, RewardStage = oceanDisturbanceStage }; if (flag) { treasureRollState.Tier = RollTreasureTier(val, oceanDisturbanceStage); GetTreasureTierWeights(val, oceanDisturbanceStage, out var crate, out var barrel, out var chest); Log.LogInfo((object)($"TREASURE BITE ROLLED | Biome: {val} | Chance: {num:F1}% | Roll: {num2:F1} | " + $"Personal Reward Stage: {oceanDisturbanceStage} | Weights: {crate}/{barrel}/{chest} | " + $"Tier: {(int)treasureRollState.Tier} | {GetTreasureName(treasureRollState.Tier)}")); } else { Log.LogInfo((object)($"NORMAL FISH ROLLED | Biome: {val} | Chance: {num:F1}% | Roll: {num2:F1} | " + $"Personal Stage: {oceanDisturbanceStage}")); } StoreTreasureRoll(bobber, fishID, treasureRollState); } } internal static List<LootAward> GenerateTreasureLoot(TreasureRollState treasure) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) List<LootAward> list = new List<LootAward>(); if (treasure == null) { return list; } GetCoinRange(treasure.Tier, out var min, out var max); int num = Random.Range(min, max + 1); if (num > 0) { AddOrCombineAward(list, "Coins", num); } List<LootEntry> biomeLootPool = GetBiomeLootPool(treasure.Biome); int lootRollCount = GetLootRollCount(treasure.Tier); for (int i = 0; i < lootRollCount; i++) { LootEntry lootEntry = RollLootEntry(biomeLootPool, treasure.Tier); if (lootEntry != null) { int num2 = Mathf.Max(1, lootEntry.MinAmount); int num3 = Mathf.Max(num2, lootEntry.MaxAmount); AddOrCombineAward(list, lootEntry.PrefabName, Random.Range(num2, num3 + 1)); } } return list; } private static LootEntry RollLootEntry(List<LootEntry> pool, TreasureTier tier) { if (pool == null || pool.Count == 0) { return null; } int num = 0; foreach (LootEntry item in pool) { num += GetEffectiveLootWeight(item, tier); } if (num <= 0) { return null; } int num2 = Random.Range(0, num); foreach (LootEntry item2 in pool) { int effectiveLootWeight = GetEffectiveLootWeight(item2, tier); if (effectiveLootWeight > 0) { if (num2 < effectiveLootWeight) { return item2; } num2 -= effectiveLootWeight; } } return null; } private static void AddOrCombineAward(List<LootAward> awards, string prefabName, int amount) { if (awards == null || string.IsNullOrEmpty(prefabName) || amount <= 0) { return; } foreach (LootAward award in awards) { if (award.PrefabName == prefabName) { award.Amount += amount; return; } } awards.Add(new LootAward { PrefabName = prefabName, Amount = amount }); } internal static string GiveTreasureLoot(Player player, List<LootAward> awards) { //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || awards == null) { return ""; } Inventory inventory = ((Humanoid)player).GetInventory(); StringBuilder stringBuilder = new StringBuilder(); foreach (LootAward award in awards) { if (award == null || award.Amount <= 0) { continue; } GameObject val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(award.PrefabName) : null); if ((Object)(object)val == (Object)null) { Log.LogWarning((object)("LOOT SKIPPED | Item prefab not found: " + award.PrefabName)); continue; } ItemDrop component = val.GetComponent<ItemDrop>(); if ((Object)(object)component == (Object)null) { Log.LogWarning((object)("LOOT SKIPPED | " + award.PrefabName + " has no ItemDrop.")); continue; } int num = award.Amount; int num2 = 0; int num3 = 0; int num4 = Mathf.Max(1, component.m_itemData.m_shared.m_maxStackSize); while (num > 0) { int num5 = Mathf.Min(num, num4); ItemData val2 = component.m_itemData.Clone(); val2.m_dropPrefab = val; val2.m_stack = num5; if (inventory.AddItem(val2)) { num2 += num5; } else { Vector3 val3 = ((Component)player).transform.position + ((Component)player).transform.forward * 0.8f + Vector3.up * 0.5f; ItemDrop.DropItem(val2, num5, val3, Quaternion.identity); num3 += num5; } num -= num5; } if (stringBuilder.Length > 0) { stringBuilder.Append(", "); } stringBuilder.Append(award.PrefabName); stringBuilder.Append(" x"); stringBuilder.Append(award.Amount); if (num3 > 0) { stringBuilder.Append(" (overflow dropped)"); } Log.LogInfo((object)($"TREASURE LOOT | {award.PrefabName} x{award.Amount} | " + $"Inventory: {num2} | Dropped: {num3}")); } return stringBuilder.ToString(); } private static string RecoverTreasure(ActiveTreasureProxy proxy, Player player) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) if (proxy == null || proxy.Completed) { return ""; } string treasureName = GetTreasureName(proxy.Treasure.Tier); List<LootAward> awards = GenerateTreasureLoot(proxy.Treasure); string text = GiveTreasureLoot(player, awards); AddPersonalOceanDisturbance(proxy.Treasure.Biome, DisturbancePerTreasureRecovery.Value, "Treasure Recovered"); Log.LogWarning((object)($"TREASURE PAYOUT COMPLETE | {treasureName} | Biome: {proxy.Treasure.Biome} | " + $"Personal Reward Stage: {proxy.Treasure.RewardStage} | " + $"Current Personal Stage: {GetDisturbanceStage(_localOceanDisturbance)} | " + "Loot: " + text)); CompleteProxy(proxy); return string.IsNullOrEmpty(text) ? ("Recovered " + treasureName + "!") : ("Recovered " + treasureName + "! " + text); } internal static string GetTreasureHoverText(Fish fish, string original) { if (!TryGetProxyForFish(fish, out var proxy) || proxy == null || proxy.Completed) { return original; } return GetTreasureName(proxy.Treasure.Tier) + "\n[<color=yellow><b>E</b></color>] Haul In"; } internal static string GetTreasureHoverName(Fish fish, string original) { if (!TryGetProxyForFish(fish, out var proxy) || proxy == null || proxy.Completed) { return original; } return GetTreasureName(proxy.Treasure.Tier); } private static void ClearBobberCatchExact(ActiveTreasureProxy proxy, Fish fish) { if (proxy == null || (Object)(object)proxy.Bobber == (Object)null || (Object)(object)proxy.Bobber.GetCatch() != (Object)(object)fish) { return; } try { if (_setCatchFishMethod == null) { _setCatchFishMethod = AccessTools.Method(typeof(FishingFloat), "SetCatch", new Type[1] { typeof(Fish) }, (Type[])null); } if (_setCatchFishMethod == null) { if (!_warnedSetCatchMethodMissing) { _warnedSetCatchMethodMissing = true; Log.LogWarning((object)"HAUL IN CLEANUP | FishingFloat.SetCatch(Fish) not found."); } } else { _setCatchFishMethod.Invoke(proxy.Bobber, new object[1]); } } catch (Exception ex) { Log.LogWarning((object)("HAUL IN CLEANUP | Could not clear bobber catch: " + ex.Message)); } } internal static bool HandleFishInteract(Fish fish, Humanoid character, bool alt, bool repeat, ref bool result) { if (!TryGetProxyForFish(fish, out var proxy) || proxy == null || proxy.Completed) { return true; } if (repeat) { result = false; return false; } Player val = (Player)(object)((character is Player) ? character : null); if ((Object)(object)val == (Object)null) { val = Player.m_localPlayer; } if ((Object)(object)val == (Object)null) { result = false; return false; } Log.LogWarning((object)("TREASURE HAUL IN | " + GetTreasureName(proxy.Treasure.Tier))); string text = FishingFloat.Catch(fish, (Character)(object)val); ClearBobberCatchExact(proxy, fish); try { fish.OnHooked((FishingFloat)null); } catch (Exception ex) { Log.LogWarning((object)("HAUL IN CLEANUP | Could not unhook proxy fish: " + ex.Message)); } try { if ((Object)(object)fish != (Object)null) { if ((Object)(object)ZNetScene.instance != (Object)null) { ZNetScene.instance.Destroy(((Component)fish).gameObject); } else { Object.Destroy((Object)(object)((Component)fish).gameObject); } } } catch (Exception ex2) { Log.LogWarning((object)("HAUL IN CLEANUP | Could not destroy proxy fish: " + ex2.Message)); } if (!string.IsNullOrEmpty(text)) { ShowCenterMessage(text); } result = true; return false; } internal static void HandleNibble(FishingFloat bobber, object[] args) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) if (!EnableTreasureFishing.Value || !IsLocalOwnedBobber(bobber) || HasActiveProxy(bobber)) { return; } ZDOID fishID = default(ZDOID); bool flag = false; bool flag2 = false; bool flag3 = false; if (args != null) { foreach (object obj in args) { if (!flag2 && obj is ZDOID) { fishID = (ZDOID)obj; flag2 = true; } else if (!flag3 && obj is bool) { flag = (bool)obj; flag3 = true; } } } if (!flag2 || !flag3) { if (!_warnedNibbleArgs) { _warnedNibbleArgs = true; Log.LogWarning((object)"RPC_Nibble fish ID / bait flag not found."); } } else if (flag) { RollTreasureForNibble(bobber, fishID); } } internal static void HandleSetCatch(FishingFloat bobber, Fish fish) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)fish == (Object)null) && EnableTreasureFishing.Value && IsLocalOwnedBobber(bobber) && !HasActiveProxy(bobber) && TryGetFishID(fish, out var fishID) && TryGetTreasureRoll(bobber, fishID, out var state) && state != null && state.IsTreasure) { bool flag = ActivateTreasureProxy(bobber, fish, state); Log.LogWarning((object)$"TREASURE PROXY RESULT | Success: {flag}"); } } internal static bool HandleCatch(Fish fish, Character character, ref string result) { if ((Object)(object)fish == (Object)null) { return true; } if (!TryGetProxyForFish(fish, out var proxy)) { return true; } Player val = (Player)(object)((character is Player) ? character : null); if ((Object)(object)val == (Object)null) { val = Player.m_localPlayer; } result = RecoverTreasure(proxy, val); return false; } internal static void HandleGameStarted() { RegisterNetworkRpcs(); if (IsServer()) { RecomputeSharedStormStage(forceBroadcast: true); } } } [HarmonyPatch(typeof(Game), "Start")] internal static class SunkenSpoilsGameStartPatch { private static void Postfix() { SunkenSpoilsPlugin.HandleGameStarted(); } } [HarmonyPatch(typeof(FishingFloat), "RPC_Nibble")] internal static class FishingFloatNibblePatch { private static void Prefix(FishingFloat __instance, object[] __args) { SunkenSpoilsPlugin.HandleNibble(__instance, __args); } } [HarmonyPatch(typeof(FishingFloat), "SetCatch", new Type[] { typeof(Fish) })] internal static class FishingFloatSetCatchPatch { private static void Postfix(FishingFloat __instance, Fish __0) { SunkenSpoilsPlugin.HandleSetCatch(__instance, __0); } } [HarmonyPatch(typeof(FishingFloat), "Catch", new Type[] { typeof(Fish), typeof(Character) })] internal static class FishingFloatCatchPatch { private static bool Prefix(Fish __0, Character __1, ref string __result) { return SunkenSpoilsPlugin.HandleCatch(__0, __1, ref __result); } } [HarmonyPatch(typeof(Fish), "GetHoverText")] internal static class FishHoverTextPatch { private static void Postfix(Fish __instance, ref string __result) { __result = SunkenSpoilsPlugin.GetTreasureHoverText(__instance, __result); } } [HarmonyPatch(typeof(Fish), "GetHoverName")] internal static class FishHoverNamePatch { private static void Postfix(Fish __instance, ref string __result) { __result = SunkenSpoilsPlugin.GetTreasureHoverName(__instance, __result); } } [HarmonyPatch(typeof(Fish), "Interact")] internal static class FishInteractPatch { private static bool Prefix(Fish __instance, Humanoid __0, bool __1, bool __2, ref bool __result) { return SunkenSpoilsPlugin.HandleFishInteract(__instance, __0, __1, __2, ref __result); } } [HarmonyPatch(typeof(EnvMan), "GetEnvironmentOverride")] internal static class EnvManEnvironmentOverridePatch { private static void Postfix(ref string __result) { SunkenSpoilsPlugin.ApplySharedEnvironmentOverride(ref __result); } } [HarmonyPatch(typeof(Character), "RPC_Damage", new Type[] { typeof(long), typeof(HitData) })] internal static class CharacterRpcDamageSunkenSpoilsPatch { private static void Postfix(Character __instance, HitData __1) { SunkenSpoilsPlugin.ObserveSerpentDamageResult(__instance, __1); } }