Decompiled source of SunkenSpoils v0.3.0

plugins/SunkenSpoils.dll

Decompiled 20 hours ago
using 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);
	}
}