Decompiled source of TheFloods v0.10.2

TheFloods.dll

Decompiled 3 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("0.0.0.0")]
namespace TheFloods;

[BepInPlugin("marc.thefloods", "The Broken Cycle", "0.10.2")]
public sealed class TheFloodsPlugin : BaseUnityPlugin
{
	public const string PluginGuid = "marc.thefloods";

	public const string PluginName = "The Broken Cycle";

	public const string PluginVersion = "0.10.2";

	internal static TheFloodsPlugin Instance;

	private FloodConfig _config;

	private FloodDirector _director;

	private FloodVisuals _visuals;

	private FloodWaterAdapter _waterAdapter;

	private FloodEnvironmentAdapter _environmentAdapter;

	private bool _rpcRegistered;

	private bool _loggedReady;

	internal void ReapplyWaterAfterNativeUpdate(WaterVolume volume)
	{
		if (_waterAdapter != null)
		{
			_waterAdapter.ReapplyAfterNativeUpdate(volume);
		}
	}

	private void LateUpdate()
	{
		try
		{
			if (_waterAdapter != null)
			{
				_waterAdapter.ReapplyForFrame();
				_waterAdapter.SyncPhysicsIfNeeded();
			}
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("The Floods late water sync warning: " + ex.Message));
		}
	}

	private void OnEnable()
	{
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0016: Expected O, but got Unknown
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Expected O, but got Unknown
		Camera.onPreCull = (CameraCallback)Delegate.Combine((Delegate?)(object)Camera.onPreCull, (Delegate?)new CameraCallback(OnCameraPreCull));
	}

	private void OnDisable()
	{
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0016: Expected O, but got Unknown
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Expected O, but got Unknown
		Camera.onPreCull = (CameraCallback)Delegate.Remove((Delegate?)(object)Camera.onPreCull, (Delegate?)new CameraCallback(OnCameraPreCull));
	}

	private void OnCameraPreCull(Camera camera)
	{
		try
		{
			if (_waterAdapter != null)
			{
				_waterAdapter.ReapplyVisualsForRender();
			}
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("The Floods camera water sync warning: " + ex.Message));
		}
	}

	private void Awake()
	{
		//IL_0084: Unknown result type (might be due to invalid IL or missing references)
		Instance = this;
		_config = new FloodConfig(((BaseUnityPlugin)this).Config);
		_waterAdapter = new FloodWaterAdapter(((BaseUnityPlugin)this).Logger, _config);
		_environmentAdapter = new FloodEnvironmentAdapter(((BaseUnityPlugin)this).Logger, _config);
		_visuals = new FloodVisuals(_config);
		_director = new FloodDirector(((BaseUnityPlugin)this).Logger, _config, _waterAdapter, _environmentAdapter, _visuals);
		new Harmony("marc.thefloods").PatchAll(typeof(TheFloodsPlugin).Assembly);
		((BaseUnityPlugin)this).Logger.LogInfo((object)"The Broken Cycle 0.10.2: loaded. Waiting for world network.");
	}

	private void Update()
	{
		try
		{
			if (!_rpcRegistered && ZRoutedRpc.instance != null)
			{
				ZRoutedRpc.instance.Register<string>("TheFloods_State_01", (Action<long, string>)ReceiveStateRpc);
				ZRoutedRpc.instance.Register<string>("TheFloods_Message_01", (Action<long, string>)ReceiveMessageRpc);
				ZRoutedRpc.instance.Register<string>("TheFloods_Strike_01", (Action<long, string>)ReceiveStrikeRpc);
				_rpcRegistered = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)"The Floods: routed RPC handlers registered.");
			}
			_director.Tick();
			if (!_loggedReady && _director.IsWorldReady)
			{
				_loggedReady = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)"The Floods: world controller ready. Type 'floods help' in F5 for testing controls.");
			}
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)("The Floods Update error: " + ex));
		}
	}

	private void OnGUI()
	{
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0012: Invalid comparison between Unknown and I4
		try
		{
			if (Event.current != null && (int)Event.current.type == 7)
			{
				_visuals.Draw(_director.CurrentState, _director.CurrentStormStrength, _director.CurrentWildfireIntensity, _director.CurrentStormApproach, _config);
			}
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("The Floods sky overlay error: " + ex.Message));
		}
	}

	private void OnDestroy()
	{
		try
		{
			_waterAdapter.Dispose();
			if (_director != null)
			{
				_director.Dispose();
			}
			_environmentAdapter.ReleaseForcedEnvironment();
			_visuals.Dispose();
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("The Floods cleanup warning: " + ex.Message));
		}
		finally
		{
			Instance = null;
		}
	}

	private static void ReceiveStateRpc(long sender, string payload)
	{
		if (!((Object)(object)Instance == (Object)null))
		{
			Instance._director.ReceiveState(payload);
		}
	}

	private static void ReceiveMessageRpc(long sender, string message)
	{
		if ((Object)(object)MessageHud.instance != (Object)null && !string.IsNullOrWhiteSpace(message))
		{
			MessageHud.instance.ShowMessage((MessageType)2, message, 0, (Sprite)null, false);
		}
	}

	private static void ReceiveStrikeRpc(long sender, string payload)
	{
		if (!((Object)(object)Instance == (Object)null))
		{
			Instance._director.ReceiveStrike(payload);
		}
	}

	internal bool TryHandleConsoleCommand(Terminal terminal)
	{
		string text = TerminalReflection.ReadInput(terminal);
		if (string.IsNullOrWhiteSpace(text))
		{
			return false;
		}
		string[] array = text.Trim().Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
		bool flag = array.Length != 0 && array[0].Equals("floods", StringComparison.OrdinalIgnoreCase);
		bool flag2 = array.Length != 0 && array[0].Equals("strongwinds", StringComparison.OrdinalIgnoreCase);
		if (array.Length == 0 || (!flag && !flag2))
		{
			return false;
		}
		TerminalReflection.ClearInput(terminal);
		if (!_director.IsAuthoritative)
		{
			TerminalReflection.Write(terminal, "The Floods: commands must be run by the world host/server.");
			return true;
		}
		string message = (flag2 ? _director.HandleStrongWindsCommand(array.Skip(1).ToArray()) : _director.HandleCommand(array.Skip(1).ToArray()));
		TerminalReflection.Write(terminal, message);
		return true;
	}
}
internal sealed class FloodEventProfile
{
	internal readonly FloodEventType Type;

	internal readonly string DisplayName;

	internal readonly ConfigEntry<bool> Enabled;

	internal readonly ConfigEntry<float> HeightMeters;

	internal readonly ConfigEntry<float> MinimumHeightMeters;

	internal readonly ConfigEntry<float> MaximumHeightMeters;

	internal readonly ConfigEntry<float> OmenDays;

	internal readonly ConfigEntry<float> RisingDays;

	internal readonly ConfigEntry<float> PeakDays;

	internal readonly ConfigEntry<float> RecedingDays;

	internal readonly ConfigEntry<int> CooldownDays;

	internal readonly ConfigEntry<float> DailyChance;

	internal FloodEventProfile(ConfigFile config, FloodEventType type, string section, string displayName, float defaultHeight, float defaultMinimumHeight, float defaultMaximumHeight, float defaultOmenDays, float defaultRisingDays, float defaultPeakDays, float defaultRecedingDays, int defaultCooldownDays, float defaultDailyChance)
	{
		Type = type;
		DisplayName = displayName;
		Enabled = config.Bind<bool>(section, "Enabled", true, "Allow this event to occur naturally.");
		HeightMeters = config.Bind<float>(section, "HeightMeters", defaultHeight, "Legacy nominal height retained for older configs. Named events use MinimumHeightMeters and MaximumHeightMeters.");
		MinimumHeightMeters = config.Bind<float>(section, "MinimumHeightMeters", defaultMinimumHeight, "Lowest possible sea-level surge when this event begins. The exact height is rolled once and saved with the event.");
		MaximumHeightMeters = config.Bind<float>(section, "MaximumHeightMeters", defaultMaximumHeight, "Highest possible sea-level surge when this event begins. The exact height is rolled once and saved with the event.");
		OmenDays = config.Bind<float>(section, "OmenDays", defaultOmenDays, "How long the distant warning phase lasts in Valheim days.");
		RisingDays = config.Bind<float>(section, "RisingDays", defaultRisingDays, "How long the water takes to reach its height in Valheim days.");
		PeakDays = config.Bind<float>(section, "PeakDays", defaultPeakDays, "How long the event remains at full strength in Valheim days.");
		RecedingDays = config.Bind<float>(section, "RecedingDays", defaultRecedingDays, "How long the water takes to return in Valheim days.");
		CooldownDays = config.Bind<int>(section, "CooldownDays", defaultCooldownDays, "Minimum completed in-game days before this event can occur naturally again.");
		DailyChance = config.Bind<float>(section, "DailyChance", defaultDailyChance, "Natural chance for this event on each eligible in-game day. 0.01 = 1%.");
	}

	internal float GetMinimumHeight()
	{
		return Mathf.Min(MinimumHeightMeters.Value, MaximumHeightMeters.Value);
	}

	internal float GetMaximumHeight()
	{
		return Mathf.Max(MinimumHeightMeters.Value, MaximumHeightMeters.Value);
	}
}
internal sealed class FloodConfig
{
	internal readonly ConfigEntry<bool> Enabled;

	internal readonly ConfigEntry<int> MinimumWorldDay;

	internal readonly ConfigEntry<float> GameDaySeconds;

	internal readonly ConfigEntry<float> OmenDays;

	internal readonly ConfigEntry<float> RisingDays;

	internal readonly ConfigEntry<float> PeakDays;

	internal readonly ConfigEntry<float> RecedingDays;

	internal readonly ConfigEntry<float> MaximumSurgeMeters;

	internal readonly ConfigEntry<float> DebugMaximumSurgeMeters;

	internal readonly FloodEventProfile StormTide;

	internal readonly FloodEventProfile FlashSurge;

	internal readonly FloodEventProfile GreatFlood;

	internal readonly FloodEventProfile Drought;

	internal readonly FloodEventProfile Wildfire;

	internal readonly ConfigEntry<float> FirstOrdinaryEventMinimumHours;

	internal readonly ConfigEntry<float> FirstOrdinaryEventMaximumHours;

	internal readonly ConfigEntry<float> OrdinaryRecoveryMinimumHours;

	internal readonly ConfigEntry<float> OrdinaryRecoveryMaximumHours;

	internal readonly ConfigEntry<float> FlashSurgeRecoveryMinimumHours;

	internal readonly ConfigEntry<float> FlashSurgeRecoveryMaximumHours;

	internal readonly ConfigEntry<float> GreatFloodRecoveryMinimumHours;

	internal readonly ConfigEntry<float> GreatFloodRecoveryMaximumHours;

	internal readonly ConfigEntry<float> StormTideWeight;

	internal readonly ConfigEntry<float> DroughtWeight;

	internal readonly ConfigEntry<float> FlashSurgeWeight;

	internal readonly ConfigEntry<float> GreatFloodFirstEligibleWorldHours;

	internal readonly ConfigEntry<float> GreatFloodFirstTargetMinimumWorldHours;

	internal readonly ConfigEntry<float> GreatFloodFirstTargetMaximumWorldHours;

	internal readonly ConfigEntry<float> GreatFloodFirstForceByWorldHours;

	internal readonly ConfigEntry<float> GreatFloodRepeatMinimumHours;

	internal readonly ConfigEntry<float> GreatFloodRepeatMaximumHours;

	internal readonly ConfigEntry<float> GreatFloodRepeatForceByHours;

	internal readonly ConfigEntry<float> SevereDroughtChance;

	internal readonly ConfigEntry<float> SevereDroughtMinimumMeters;

	internal readonly ConfigEntry<float> SevereDroughtMaximumMeters;

	internal readonly ConfigEntry<bool> EnableFlashSurgeDrawdown;

	internal readonly ConfigEntry<float> FlashSurgeDrawdownMinimumMeters;

	internal readonly ConfigEntry<float> FlashSurgeDrawdownMaximumMeters;

	internal readonly ConfigEntry<bool> EnableLiveWaterLevel;

	internal readonly ConfigEntry<bool> EnableWaterSurfaceQueryPatch;

	internal readonly ConfigEntry<bool> EnableOceanRendererLift;

	internal readonly ConfigEntry<float> OceanRendererMinimumSpan;

	internal readonly ConfigEntry<float> WaterSurfaceScanSeconds;

	internal readonly ConfigEntry<bool> EnableNativeThunderstorm;

	internal readonly ConfigEntry<string> NativeStormEnvironment;

	internal readonly ConfigEntry<float> NativeStormStartStrength;

	internal readonly ConfigEntry<float> SkyDarkening;

	internal readonly ConfigEntry<bool> EnableBlackHorizonStormBank;

	internal readonly ConfigEntry<float> HorizonStormBankOpacity;

	internal readonly ConfigEntry<float> HorizonStormBankHeight;

	internal readonly ConfigEntry<bool> EnableDistantLightningFlashes;

	internal readonly ConfigEntry<float> WildfireSkyTintStrength;

	internal readonly ConfigEntry<bool> EnableApproachingStormFront;

	internal readonly ConfigEntry<float> StormFrontDarkness;

	internal readonly ConfigEntry<float> StormFrontHorizonHeight;

	internal readonly ConfigEntry<bool> EnableCustomStormAudio;

	internal readonly ConfigEntry<float> MasterStormAudioVolume;

	internal readonly ConfigEntry<float> IndoorStormAudioVolume;

	internal readonly ConfigEntry<float> OutdoorWindAudioVolume;

	internal readonly ConfigEntry<float> AudioFadeDuration;

	internal readonly ConfigEntry<bool> DebugAudioLogging;

	internal readonly ConfigEntry<bool> EnableLightningStrikes;

	internal readonly ConfigEntry<bool> EnableAmbientBolts;

	internal readonly ConfigEntry<float> PlayerStrikeMinStrength;

	internal readonly ConfigEntry<float> StrikeGapMinSeconds;

	internal readonly ConfigEntry<float> StrikeGapMaxSeconds;

	internal readonly ConfigEntry<float> DirectHitChance;

	internal readonly ConfigEntry<float> StrikeGraceSeconds;

	internal readonly ConfigEntry<float> MinLightningDamage;

	internal readonly ConfigEntry<float> MaxLightningDamage;

	internal readonly ConfigEntry<float> WetDamageMultiplier;

	internal readonly ConfigEntry<float> LightningPushForce;

	internal readonly ConfigEntry<bool> RequireExposed;

	internal readonly ConfigEntry<bool> NonLethalLightning;

	internal readonly ConfigEntry<float> BoltLightIntensity;

	internal readonly ConfigEntry<float> BoltLightRange;

	internal readonly ConfigEntry<float> BoltDurationSeconds;

	internal readonly ConfigEntry<float> AmbientBoltsPerMinuteAtPeak;

	internal readonly ConfigEntry<bool> EnableThunderAudio;

	internal readonly ConfigEntry<float> ThunderVolume;

	internal readonly ConfigEntry<float> SpeedOfSoundMetersPerSecond;

	internal readonly ConfigEntry<string> RainEnvironmentNames;

	internal readonly ConfigEntry<float> DrynessRisePerHour;

	internal readonly ConfigEntry<float> DrynessRiseDroughtMultiplier;

	internal readonly ConfigEntry<float> DrynessRainResetSeconds;

	internal readonly ConfigEntry<float> RainWetStormStrength;

	internal readonly ConfigEntry<float> WildfireWeight;

	internal readonly ConfigEntry<float> SpontaneousDrynessThreshold;

	internal readonly ConfigEntry<float> SpontaneousPerWindowChance;

	internal readonly ConfigEntry<float> SpontaneousWindowMinimumHours;

	internal readonly ConfigEntry<float> SpontaneousWindowMaximumHours;

	internal readonly ConfigEntry<float> LightningIgnitionChance;

	internal readonly ConfigEntry<float> IgnitionMinRadius;

	internal readonly ConfigEntry<float> IgnitionMaxRadius;

	internal readonly ConfigEntry<int> MaxFireNodes;

	internal readonly ConfigEntry<float> SpreadIntervalSeconds;

	internal readonly ConfigEntry<float> SpreadChance;

	internal readonly ConfigEntry<float> SpreadStepMeters;

	internal readonly ConfigEntry<float> NodeLifeSeconds;

	internal readonly ConfigEntry<float> NodeRadius;

	internal readonly ConfigEntry<float> PlayerFireDamagePerTick;

	internal readonly ConfigEntry<float> FireDamageIntervalSeconds;

	internal readonly ConfigEntry<bool> DamageStructures;

	internal readonly ConfigEntry<float> DownwindBias;

	internal readonly ConfigEntry<string> WildfireForcedEnvironment;

	internal readonly ConfigEntry<bool> EmberBedEnabled;

	internal readonly ConfigEntry<float> EmberBedRadiusMultiplier;

	internal readonly ConfigEntry<float> EmberBedOpacity;

	internal readonly ConfigEntry<float> FlameStretchLength;

	internal readonly ConfigEntry<float> MaxDetailedFireDistance;

	internal readonly ConfigEntry<float> MeadowsFlammability;

	internal readonly ConfigEntry<float> BlackForestFlammability;

	internal readonly ConfigEntry<float> PlainsFlammability;

	internal readonly ConfigEntry<float> SwampFlammability;

	internal readonly ConfigEntry<float> MountainFlammability;

	internal readonly ConfigEntry<float> MistlandsFlammability;

	internal readonly ConfigEntry<float> AshlandsFlammability;

	internal readonly ConfigEntry<float> DeepNorthFlammability;

	internal readonly ConfigEntry<float> OtherBiomeFlammability;

	internal readonly ConfigEntry<bool> EnableStrongWinds;

	internal readonly ConfigEntry<float> StrongWindsEventChance;

	internal readonly ConfigEntry<float> StrongWindsCooldownHours;

	internal readonly ConfigEntry<float> StrongWindsDurationMinutes;

	internal readonly ConfigEntry<int> StrongWindsMinimumWorldDay;

	internal readonly ConfigEntry<float> StrongWindsWindStrengthMultiplier;

	internal readonly ConfigEntry<bool> EnableTreefall;

	internal readonly ConfigEntry<float> TreefallChance;

	internal readonly ConfigEntry<float> TreefallDistanceFromPlayer;

	internal readonly ConfigEntry<int> MaxTreefallsPerPlayer;

	internal readonly ConfigEntry<int> MaxTreefallsGlobally;

	internal readonly ConfigEntry<float> TreefallCooldownPerZoneSeconds;

	internal readonly ConfigEntry<float> ProtectedBaseRadius;

	internal readonly ConfigEntry<float> FallingTreeDamageToPlayers;

	internal readonly ConfigEntry<float> FallingTreeDamageToCreatures;

	internal readonly ConfigEntry<bool> StrongWindStructureDamage;

	internal readonly ConfigEntry<float> WindDebrisAmount;

	internal readonly ConfigEntry<bool> StrongWindsDebugMode;

	internal readonly ConfigEntry<bool> StrongWindsDebugIgnoreBaseProtection;

	internal readonly ConfigEntry<bool> DebugLogging;

	internal readonly ConfigEntry<bool> VerboseWaterTileBindingLogs;

	internal readonly ConfigEntry<bool> StartTestEventOnWorldLoad;

	internal readonly ConfigEntry<string> TestStartPhase;

	internal FloodConfig(ConfigFile config)
	{
		Enabled = config.Bind<bool>("General", "Enabled", true, "Master switch for The Floods.");
		MinimumWorldDay = config.Bind<int>("Event Rules", "MinimumWorldDay", 6, "No natural water event can occur before this Valheim day. V0.8 defaults to day 6 so an ordinary event can arrive during an active early world without appearing on day one.");
		GameDaySeconds = config.Bind<float>("Timeline", "GameDaySecondsFallback", 1800f, "Fallback full Valheim day length used only if the game day API is unavailable.");
		OmenDays = config.Bind<float>("Timeline", "OmenDays", 0.5f, "Custom/debug event omen duration in Valheim days.");
		RisingDays = config.Bind<float>("Timeline", "RisingDays", 2f, "Custom/debug event rise duration in Valheim days.");
		PeakDays = config.Bind<float>("Timeline", "PeakDays", 2f, "Custom/debug event peak duration in Valheim days.");
		RecedingDays = config.Bind<float>("Timeline", "RecedingDays", 2f, "Custom/debug event receding duration in Valheim days.");
		MaximumSurgeMeters = config.Bind<float>("Flood Height", "MaximumSurgeMeters", 3.5f, "Height used by legacy floods start and debug test events.");
		DebugMaximumSurgeMeters = config.Bind<float>("Flood Height", "DebugMaximumSurgeMeters", 20f, "Safety cap for floods set <metres>.");
		StormTide = new FloodEventProfile(config, FloodEventType.StormTide, "Storm Tide", "Storm Tide", 2.5f, 1.5f, 5f, 0.066667f, 0.166667f, 0.2f, 0.2f, 7, 0.075f);
		FlashSurge = new FloodEventProfile(config, FloodEventType.FlashSurge, "Flash Surge", "Flash Surge", 10f, 6f, 12f, 0.035f, 0.066667f, 0.1f, 0.233333f, 20, 0.028f);
		GreatFlood = new FloodEventProfile(config, FloodEventType.GreatFlood, "The Great Flood", "The Great Flood", 12f, 8f, 16f, 0.233333f, 0.6f, 0.4f, 0.6f, 55, 0.0075f);
		Drought = new FloodEventProfile(config, FloodEventType.Drought, "Drought", "Drought", 4f, 3f, 5f, 0.1f, 0.3f, 0.4f, 0.3f, 10, 0.12f);
		Wildfire = new FloodEventProfile(config, FloodEventType.Wildfire, "Wildfire", "Wildfire", 0f, 0f, 0f, 0.08f, 0.18f, 0.28f, 0.22f, 12, 0.02f);
		FirstOrdinaryEventMinimumHours = config.Bind<float>("Water Cycle Scheduler", "FirstOrdinaryEventMinimumHours", 0.75f, "After MinimumWorldDay, the first ordinary event begins after a hidden random delay in this range. Hours are active server/world hours.");
		FirstOrdinaryEventMaximumHours = config.Bind<float>("Water Cycle Scheduler", "FirstOrdinaryEventMaximumHours", 2f, "Largest hidden delay after MinimumWorldDay before the first ordinary event can begin.");
		OrdinaryRecoveryMinimumHours = config.Bind<float>("Water Cycle Scheduler", "OrdinaryRecoveryMinimumHours", 1.5f, "Random quiet-world minimum after a Storm Tide or Drought ends before another ordinary event can begin.");
		OrdinaryRecoveryMaximumHours = config.Bind<float>("Water Cycle Scheduler", "OrdinaryRecoveryMaximumHours", 3.5f, "Random quiet-world maximum after a Storm Tide or Drought ends before another ordinary event can begin.");
		FlashSurgeRecoveryMinimumHours = config.Bind<float>("Water Cycle Scheduler", "FlashSurgeRecoveryMinimumHours", 2.5f, "Random quiet-world minimum after a Flash Surge ends before another ordinary event can begin.");
		FlashSurgeRecoveryMaximumHours = config.Bind<float>("Water Cycle Scheduler", "FlashSurgeRecoveryMaximumHours", 5f, "Random quiet-world maximum after a Flash Surge ends before another ordinary event can begin.");
		GreatFloodRecoveryMinimumHours = config.Bind<float>("Water Cycle Scheduler", "GreatFloodRecoveryMinimumHours", 6f, "Random quiet-world minimum after the Great Flood ends before any natural water event can begin.");
		GreatFloodRecoveryMaximumHours = config.Bind<float>("Water Cycle Scheduler", "GreatFloodRecoveryMaximumHours", 10f, "Random quiet-world maximum after the Great Flood ends before any natural water event can begin.");
		StormTideWeight = config.Bind<float>("Water Cycle Scheduler", "StormTideWeight", 45f, "Relative chance that the next ordinary water event is a Storm Tide.");
		DroughtWeight = config.Bind<float>("Water Cycle Scheduler", "DroughtWeight", 40f, "Relative chance that the next ordinary water event is a Drought.");
		FlashSurgeWeight = config.Bind<float>("Water Cycle Scheduler", "FlashSurgeWeight", 15f, "Relative chance that the next ordinary water event is a Flash Surge.");
		GreatFloodFirstEligibleWorldHours = config.Bind<float>("Water Cycle Scheduler", "GreatFloodFirstEligibleWorldHours", 8f, "The Great Flood cannot naturally occur before this much total active world age.");
		GreatFloodFirstTargetMinimumWorldHours = config.Bind<float>("Water Cycle Scheduler", "GreatFloodFirstTargetMinimumWorldHours", 15f, "Earliest target age for a world's first Great Flood. The exact target is hidden and randomized.");
		GreatFloodFirstTargetMaximumWorldHours = config.Bind<float>("Water Cycle Scheduler", "GreatFloodFirstTargetMaximumWorldHours", 25f, "Latest target age for a world's first Great Flood. The exact target is hidden and randomized.");
		GreatFloodFirstForceByWorldHours = config.Bind<float>("Water Cycle Scheduler", "GreatFloodFirstForceByWorldHours", 32f, "Safety limit: if a mature world still has not seen its first Great Flood, the scheduler waits for the next safe quiet window and begins one by this world age.");
		GreatFloodRepeatMinimumHours = config.Bind<float>("Water Cycle Scheduler", "GreatFloodRepeatMinimumHours", 35f, "Minimum active-world hours before another Great Flood can be scheduled after one completes.");
		GreatFloodRepeatMaximumHours = config.Bind<float>("Water Cycle Scheduler", "GreatFloodRepeatMaximumHours", 60f, "Maximum randomized active-world hours before another Great Flood is scheduled after one completes.");
		GreatFloodRepeatForceByHours = config.Bind<float>("Water Cycle Scheduler", "GreatFloodRepeatForceByHours", 72f, "Safety limit after a Great Flood: the next one will be allowed at the first safe quiet window by this many active-world hours later.");
		SevereDroughtChance = config.Bind<float>("Drought", "SevereDroughtChance", 0.2f, "Chance that a naturally scheduled Drought becomes a Severe Drought instead of the normal 3–5m retreat. 0.20 means 20%.");
		SevereDroughtMinimumMeters = config.Bind<float>("Drought", "SevereDroughtMinimumMeters", 5f, "Smallest sea retreat for a Severe Drought.");
		SevereDroughtMaximumMeters = config.Bind<float>("Drought", "SevereDroughtMaximumMeters", 7.5f, "Largest sea retreat for a Severe Drought.");
		RemoveLegacyDailyRollSettings(config);
		EnableFlashSurgeDrawdown = config.Bind<bool>("Flash Surge", "EnableDrawdown", true, "Before a Flash Surge, the sea can briefly pull back as a warning.");
		FlashSurgeDrawdownMinimumMeters = config.Bind<float>("Flash Surge", "DrawdownMinimumMeters", 1f, "Smallest pre-surge sea retreat in metres. Used only when EnableDrawdown is true.");
		FlashSurgeDrawdownMaximumMeters = config.Bind<float>("Flash Surge", "DrawdownMaximumMeters", 3f, "Largest pre-surge sea retreat in metres. Used only when EnableDrawdown is true.");
		EnableLiveWaterLevel = config.Bind<bool>("Flood Height", "EnableLiveWaterLevel", true, "Master switch for the live flood-water adapter. Use a copied test world first.");
		EnableWaterSurfaceQueryPatch = config.Bind<bool>("Flood Height", "EnableWaterSurfaceQueryPatch", true, "Uses Valheim WaterVolume terrain-water offsets for live flood physics without editing terrain.");
		EnableOceanRendererLift = config.Bind<bool>("Flood Height", "EnableOceanRendererLift", true, "Raises terrain-water mesh children so the shoreline visibly floods. Does not move terrain.");
		OceanRendererMinimumSpan = config.Bind<float>("Flood Height", "OceanRendererMinimumSpan", 256f, "Legacy compatibility setting. Ocean tiles are identified through their linked Heightmap.");
		WaterSurfaceScanSeconds = config.Bind<float>("Flood Height", "WaterSurfaceScanSeconds", 3f, "How often the mod scans for newly loaded terrain water tiles.");
		EnableNativeThunderstorm = config.Bind<bool>("Storm Visuals", "EnableNativeThunderstorm", true, "Use Valheim's native thunderstorm during active flood phases.");
		NativeStormEnvironment = config.Bind<string>("Storm Visuals", "NativeStormEnvironment", "ThunderStorm", "Internal Valheim environment name to force during active flood phases.");
		NativeStormStartStrength = config.Bind<float>("Storm Visuals", "NativeStormStartStrength", 0.45f, "Storm strength at which native thunder, rain and lightning begin. The omen remains mostly distant and black before this.");
		SkyDarkening = config.Bind<float>("Storm Visuals", "SkyDarkening", 0.72f, "Strength of the storm-darkening overlay. 0 disables it.");
		EnableBlackHorizonStormBank = config.Bind<bool>("Storm Visuals", "EnableBlackHorizonStormBank", false, "Legacy screen-space storm-bank option. Disabled by default because it could form a hard black horizontal band on some displays.");
		HorizonStormBankOpacity = config.Bind<float>("Storm Visuals", "HorizonStormBankOpacity", 0.88f, "Opacity of the distant black storm bank.");
		HorizonStormBankHeight = config.Bind<float>("Storm Visuals", "HorizonStormBankHeight", 0.64f, "Vertical screen fraction occupied by the distant storm bank.");
		EnableDistantLightningFlashes = config.Bind<bool>("Storm Visuals", "EnableDistantLightningFlashes", true, "Adds subtle distant lightning flashes on top of Valheim's native thunderstorm.");
		WildfireSkyTintStrength = config.Bind<float>("Storm Visuals", "WildfireSkyTintStrength", 0.52f, "Strength of the warm smoke tint during a wildfire event.");
		EnableApproachingStormFront = config.Bind<bool>("Storm Visuals", "EnableApproachingStormFront", false, "Legacy screen-space storm-front option. Disabled by default because it could look like a black overlay band instead of a distant sky.");
		StormFrontDarkness = config.Bind<float>("Storm Visuals", "StormFrontDarkness", 0.95f, "Opacity of the approaching black storm front at full strength.");
		StormFrontHorizonHeight = config.Bind<float>("Storm Visuals", "StormFrontHorizonHeight", 0.72f, "Screen height used by the distant storm-wall cloud band.");
		EnableCustomStormAudio = config.Bind<bool>("Storm Audio", "Enable Custom Storm Audio", true, "Loads the three supplied MP3 storm loops from TheBrokenCycle/Audio.");
		MasterStormAudioVolume = config.Bind<float>("Storm Audio", "Master Storm Audio Volume", 0.85f, "Master volume multiplier for all custom storm audio.");
		IndoorStormAudioVolume = config.Bind<float>("Storm Audio", "Indoor Storm Audio Volume", 0.72f, "Volume multiplier for stormoutside.mp3 when the player is inside a sealed shelter.");
		OutdoorWindAudioVolume = config.Bind<float>("Storm Audio", "Outdoor Wind Audio Volume", 0.8f, "Volume multiplier for loudwind.mp3 and windthroughtrees.mp3 outdoors.");
		AudioFadeDuration = config.Bind<float>("Storm Audio", "Audio Fade Duration", 4.5f, "Seconds used for custom storm audio fades and crossfades.");
		DebugAudioLogging = config.Bind<bool>("Storm Audio", "Debug Audio Logging", false, "Writes custom storm audio load/playback diagnostics when enabled.");
		EnableLightningStrikes = config.Bind<bool>("Lightning", "EnableLightningStrikes", true, "Allows storms to create real ground lightning strikes. Direct strikes can damage and kill exposed players.");
		EnableAmbientBolts = config.Bind<bool>("Lightning", "EnableAmbientBolts", true, "Shows non-damaging procedural bolts around players during active storm phases.");
		PlayerStrikeMinStrength = config.Bind<float>("Lightning", "PlayerStrikeMinStrength", 0.55f, "Minimum storm strength before dangerous player-targeting lightning is allowed.");
		StrikeGapMinSeconds = config.Bind<float>("Lightning", "StrikeGapMinSeconds", 25f, "Shortest real-time gap between dangerous lightning checks at peak storm strength.");
		StrikeGapMaxSeconds = config.Bind<float>("Lightning", "StrikeGapMaxSeconds", 90f, "Longest real-time gap between dangerous lightning checks at the edge of a storm.");
		DirectHitChance = config.Bind<float>("Lightning", "DirectHitChance", 0.12f, "Chance that a dangerous strike targets an exposed player instead of a nearby miss.");
		StrikeGraceSeconds = config.Bind<float>("Lightning", "StrikeGraceSeconds", 8f, "Minimum real-time grace period between damaging strikes on the same player.");
		MinLightningDamage = config.Bind<float>("Lightning", "MinLightningDamage", 40f, "Lightning damage near the dangerous-storm threshold.");
		MaxLightningDamage = config.Bind<float>("Lightning", "MaxLightningDamage", 140f, "Lightning damage at peak storm strength. This can kill low-health players.");
		WetDamageMultiplier = config.Bind<float>("Lightning", "WetDamageMultiplier", 1.5f, "Damage multiplier for wet players struck by lightning.");
		LightningPushForce = config.Bind<float>("Lightning", "LightningPushForce", 8f, "Knockback force applied by a direct lightning strike.");
		RequireExposed = config.Bind<bool>("Lightning", "RequireExposed", true, "Sheltered players are immune to direct lightning strikes.");
		NonLethalLightning = config.Bind<bool>("Lightning", "NonLethalLightning", false, "If true, direct lightning leaves the player at 1 HP instead of killing them.");
		BoltLightIntensity = config.Bind<float>("Lightning", "BoltLightIntensity", 6f, "World light intensity for procedural lightning bolts. V0.9.1 keeps this modest and uses the screen flash for most brightness.");
		BoltLightRange = config.Bind<float>("Lightning", "BoltLightRange", 55f, "World light range for procedural lightning bolts. Kept short to avoid bloom streaking.");
		BoltDurationSeconds = config.Bind<float>("Lightning", "BoltDurationSeconds", 0.48f, "How long a procedural bolt remains visible. The renderer enforces a visible hold so forks can be read by the eye.");
		AmbientBoltsPerMinuteAtPeak = config.Bind<float>("Lightning", "AmbientBoltsPerMinuteAtPeak", 12f, "Approximate number of non-damaging ambient bolts per minute at peak storm strength.");
		EnableThunderAudio = config.Bind<bool>("Lightning", "EnableThunderAudio", true, "Play distance-delayed thunder cracks after each lightning strike.");
		ThunderVolume = config.Bind<float>("Lightning", "ThunderVolume", 0.8f, "Volume multiplier for procedural thunder.");
		SpeedOfSoundMetersPerSecond = config.Bind<float>("Lightning", "SpeedOfSoundMetersPerSecond", 340f, "Used to compute the delay between flash and thunder.");
		RainEnvironmentNames = config.Bind<string>("Lightning", "RainEnvironmentNames", "Rain,LightRain,ThunderStorm,SwampRain,Mistlands_rain,Mistlands_thunder,Ashrain,Ashlands_ashrain", "Comma-separated environment names that count as wet rain for lightning ignition and wildfire suppression.");
		DrynessRisePerHour = config.Bind<float>("Dryness", "DrynessRisePerHour", 0.055f, "How much the saved dryness index rises per active world hour without rain.");
		DrynessRiseDroughtMultiplier = config.Bind<float>("Dryness", "DrynessRiseDroughtMultiplier", 3f, "Multiplier for dryness gain during drought phases.");
		DrynessRainResetSeconds = config.Bind<float>("Dryness", "DrynessRainResetSeconds", 900f, "Real active seconds of rain/storm needed to pull dryness from 1 toward 0.");
		RainWetStormStrength = config.Bind<float>("Dryness", "RainWetStormStrength", 0.62f, "Storm strength at which rain environments make the ground wet enough to block ignition.");
		WildfireWeight = config.Bind<float>("Wildfire", "WildfireWeight", 1f, "Reserved tuning weight for wildfire scheduling. Wildfires use their own dryness-driven schedule instead of the water-event pool.");
		SpontaneousDrynessThreshold = config.Bind<float>("Wildfire", "SpontaneousDrynessThreshold", 0.7f, "Dryness index required before spontaneous wildfire ignition can be scheduled.");
		SpontaneousPerWindowChance = config.Bind<float>("Wildfire", "SpontaneousPerWindowChance", 0.05f, "Chance per wildfire schedule window to ignite when dryness and biome fuel allow it.");
		SpontaneousWindowMinimumHours = config.Bind<float>("Wildfire", "SpontaneousWindowMinimumHours", 1f, "Shortest active-world delay between spontaneous wildfire ignition checks.");
		SpontaneousWindowMaximumHours = config.Bind<float>("Wildfire", "SpontaneousWindowMaximumHours", 3f, "Longest active-world delay between spontaneous wildfire ignition checks.");
		LightningIgnitionChance = config.Bind<float>("Wildfire", "LightningIgnitionChance", 0.25f, "Base chance that a dry lightning strike ignites a wildfire, before dryness and biome flammability are applied.");
		IgnitionMinRadius = config.Bind<float>("Wildfire", "IgnitionMinRadius", 40f, "Closest distance from a player that natural wildfire ignition may choose.");
		IgnitionMaxRadius = config.Bind<float>("Wildfire", "IgnitionMaxRadius", 120f, "Farthest distance from a player that natural wildfire ignition may choose.");
		MaxFireNodes = config.Bind<int>("Wildfire", "MaxFireNodes", 96, "Hard cap on simultaneous local fire nodes for performance. The wildfire starts as a connected moving fire-front, then grows within this budget.");
		SpreadIntervalSeconds = config.Bind<float>("Wildfire", "SpreadIntervalSeconds", 2.4f, "How often each fire node can attempt to spread. Lower values make the moving front build without waiting for isolated spots.");
		SpreadChance = config.Bind<float>("Wildfire", "SpreadChance", 0.75f, "Chance that a fire node spreads on each interval during active fire phases.");
		SpreadStepMeters = config.Bind<float>("Wildfire", "SpreadStepMeters", 3.6f, "Average distance between parent and child fire nodes in the connected wildfire front.");
		NodeLifeSeconds = config.Bind<float>("Wildfire", "NodeLifeSeconds", 85f, "How long a fire node burns without rain, floodwater, or burnout suppression.");
		NodeRadius = config.Bind<float>("Wildfire", "NodeRadius", 3.6f, "Base radius around each node that shows fire and can damage the player. The visual front joins neighbouring nodes into a continuous fire line.");
		PlayerFireDamagePerTick = config.Bind<float>("Wildfire", "PlayerFireDamagePerTick", 8f, "Fire damage applied per tick when an unwet player stands in a fire node.");
		FireDamageIntervalSeconds = config.Bind<float>("Wildfire", "FireDamageIntervalSeconds", 1.5f, "Real seconds between player fire-damage ticks.");
		DamageStructures = config.Bind<bool>("Wildfire", "DamageStructures", false, "Reserved safety valve for future structure/tree fire damage. The v0.9.0 implementation leaves structures untouched.");
		DownwindBias = config.Bind<float>("Wildfire", "DownwindBias", 0.72f, "How strongly fire spread prefers the current wind direction.");
		WildfireForcedEnvironment = config.Bind<string>("Wildfire", "WildfireForcedEnvironment", "", "Optional Valheim environment to force during wildfire. Empty leaves natural weather alone.");
		EmberBedEnabled = config.Bind<bool>("Wildfire Visuals", "EmberBedEnabled", true, "Adds a connected alpha-blended ember bed under wildfire nodes.");
		EmberBedRadiusMultiplier = config.Bind<float>("Wildfire Visuals", "EmberBedRadiusMultiplier", 1.9f, "Ember-bed radius as a multiple of SpreadStepMeters.");
		EmberBedOpacity = config.Bind<float>("Wildfire Visuals", "EmberBedOpacity", 0.55f, "Base alpha for the wildfire ground ember glow.");
		FlameStretchLength = config.Bind<float>("Wildfire Visuals", "FlameStretchLength", 3f, "Vertical stretch length for flame particles.");
		MaxDetailedFireDistance = config.Bind<float>("Wildfire Visuals", "MaxDetailedFireDistance", 60f, "Distance in metres for full flame and heat-haze particle density before LOD reduces emissions.");
		MeadowsFlammability = config.Bind<float>("Wildfire Biomes", "MeadowsFlammability", 1f, "Ignition fuel multiplier for Meadows.");
		BlackForestFlammability = config.Bind<float>("Wildfire Biomes", "BlackForestFlammability", 0.9f, "Ignition fuel multiplier for Black Forest.");
		PlainsFlammability = config.Bind<float>("Wildfire Biomes", "PlainsFlammability", 1f, "Ignition fuel multiplier for Plains.");
		SwampFlammability = config.Bind<float>("Wildfire Biomes", "SwampFlammability", 0.15f, "Ignition fuel multiplier for Swamp.");
		MountainFlammability = config.Bind<float>("Wildfire Biomes", "MountainFlammability", 0.1f, "Ignition fuel multiplier for Mountain.");
		MistlandsFlammability = config.Bind<float>("Wildfire Biomes", "MistlandsFlammability", 0.5f, "Ignition fuel multiplier for Mistlands.");
		AshlandsFlammability = config.Bind<float>("Wildfire Biomes", "AshlandsFlammability", 0f, "Ignition fuel multiplier for Ashlands.");
		DeepNorthFlammability = config.Bind<float>("Wildfire Biomes", "DeepNorthFlammability", 0f, "Ignition fuel multiplier for Deep North.");
		OtherBiomeFlammability = config.Bind<float>("Wildfire Biomes", "OtherBiomeFlammability", 0.3f, "Ignition fuel multiplier for unknown or modded biomes.");
		EnableStrongWinds = config.Bind<bool>("Strong Winds", "Enable Strong Winds", true, "Allows the standalone Strong Winds disaster and Broken Cycle wind escalation.");
		StrongWindsEventChance = config.Bind<float>("Strong Winds", "Event chance", 0.08f, "Chance that a strong-winds schedule window starts the event.");
		StrongWindsCooldownHours = config.Bind<float>("Strong Winds", "Cooldown", 5f, "Minimum active world hours before another natural Strong Winds check.");
		StrongWindsDurationMinutes = config.Bind<float>("Strong Winds", "Duration", 18f, "Approximate real-time duration of a full Strong Winds event.");
		StrongWindsMinimumWorldDay = config.Bind<int>("Strong Winds", "Minimum world progression requirement", 6, "Earliest world day for natural Strong Winds.");
		StrongWindsWindStrengthMultiplier = config.Bind<float>("Strong Winds", "Wind strength multiplier", 1f, "Overall multiplier for strong-wind force and debris.");
		EnableTreefall = config.Bind<bool>("Strong Winds", "Enable treefall", true, "Allows host-controlled native tree damage during severe winds.");
		TreefallChance = config.Bind<float>("Strong Winds", "Treefall chance", 0.35f, "Chance for each eligible severe gust window to topple one nearby valid tree.");
		TreefallDistanceFromPlayer = config.Bind<float>("Strong Winds", "Treefall distance from player", 55f, "Maximum tree candidate scan distance around active players.");
		MaxTreefallsPerPlayer = config.Bind<int>("Strong Winds", "Maximum treefalls per player", 3, "Maximum host-triggered treefalls credited near each player per event.");
		MaxTreefallsGlobally = config.Bind<int>("Strong Winds", "Maximum treefalls globally", 12, "Maximum host-triggered treefalls per Strong Winds or Broken Cycle event.");
		TreefallCooldownPerZoneSeconds = config.Bind<float>("Strong Winds", "Treefall cooldown per zone", 120f, "Cooldown before another tree can be toppled in the same loaded zone.");
		ProtectedBaseRadius = config.Bind<float>("Strong Winds", "Protected base radius", 32f, "Radius around beds, portals, workbenches, wards, and player pieces protected from treefall targeting.");
		FallingTreeDamageToPlayers = config.Bind<float>("Strong Winds", "Falling-tree damage to players", 0.55f, "Reserved multiplier for vanilla fallen-tree player danger. Lower values keep storms survivable.");
		FallingTreeDamageToCreatures = config.Bind<float>("Strong Winds", "Falling-tree damage to creatures", 0.75f, "Reserved multiplier for vanilla fallen-tree creature danger.");
		StrongWindStructureDamage = config.Bind<bool>("Strong Winds", "Structure damage setting", false, "If false, treefall targeting avoids protected player structures where detectable.");
		WindDebrisAmount = config.Bind<float>("Strong Winds", "Debris amount", 1f, "Client-side windblown debris particle density.");
		StrongWindsDebugMode = config.Bind<bool>("Strong Winds", "Debug Mode", false, "Writes Strong Winds diagnostics to the log.");
		StrongWindsDebugIgnoreBaseProtection = config.Bind<bool>("Strong Winds", "Debug ignore base protection", false, "Allows strongwinds testtree to ignore base protection for controlled testing.");
		DebugLogging = config.Bind<bool>("Debug", "DebugLogging", false, "Writes concise event/state diagnostics to BepInEx LogOutput.log.");
		VerboseWaterTileBindingLogs = config.Bind<bool>("Debug", "VerboseWaterTileBindingLogs", false, "Logs every individual terrain-water tile. Leave false for normal play.");
		StartTestEventOnWorldLoad = config.Bind<bool>("Debug", "StartTestEventOnWorldLoad", false, "Starts a custom test flood whenever the host loads a world. Turn off after testing.");
		TestStartPhase = config.Bind<string>("Debug", "TestStartPhase", "Omen", "Custom test phase: Omen, Rising, Peak, or Receding.");
	}

	internal FloodEventProfile GetProfile(FloodEventType type)
	{
		return type switch
		{
			FloodEventType.StormTide => StormTide, 
			FloodEventType.FlashSurge => FlashSurge, 
			FloodEventType.GreatFlood => GreatFlood, 
			FloodEventType.Drought => Drought, 
			FloodEventType.Wildfire => Wildfire, 
			_ => null, 
		};
	}

	internal float GetFlashSurgeDrawdownMinimum()
	{
		return Mathf.Min(FlashSurgeDrawdownMinimumMeters.Value, FlashSurgeDrawdownMaximumMeters.Value);
	}

	internal float GetFlashSurgeDrawdownMaximum()
	{
		return Mathf.Max(FlashSurgeDrawdownMinimumMeters.Value, FlashSurgeDrawdownMaximumMeters.Value);
	}

	private static void RemoveLegacyDailyRollSettings(ConfigFile config)
	{
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_003e: Expected O, but got Unknown
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		//IL_0052: Expected O, but got Unknown
		string[] array = new string[4] { "Storm Tide", "Flash Surge", "The Great Flood", "Drought" };
		for (int i = 0; i < array.Length; i++)
		{
			config.Remove(new ConfigDefinition(array[i], "CooldownDays"));
			config.Remove(new ConfigDefinition(array[i], "DailyChance"));
		}
	}
}
internal enum FloodEventType
{
	None,
	Custom,
	StormTide,
	FlashSurge,
	GreatFlood,
	Drought,
	Wildfire,
	StrongWinds
}
internal enum FloodPhase
{
	Dormant,
	Omen,
	Rising,
	Peak,
	Receding
}
[Serializable]
internal sealed class FloodState
{
	internal int FormatVersion = 6;

	internal FloodPhase Phase;

	internal FloodEventType EventType;

	internal double PhaseStartedWorldSeconds;

	internal double PhaseEndsWorldSeconds;

	internal float PeakMeters = 3.5f;

	internal float DrawdownMeters;

	internal long EventNumber;

	internal long EventSeed;

	internal long LastCompletedDay = -99999L;

	internal long LastRollDay = -99999L;

	internal long LastStormTideDay = -99999L;

	internal long LastFlashSurgeDay = -99999L;

	internal long LastGreatFloodDay = -99999L;

	internal long LastDroughtDay = -99999L;

	internal bool ManualEvent;

	internal bool IsSevereDrought;

	internal bool SchedulerInitialized;

	internal double NextOrdinaryEventWorldSeconds;

	internal double NextGreatFloodWorldSeconds;

	internal double GreatFloodForceWorldSeconds;

	internal double GlobalRecoveryEndsWorldSeconds;

	internal long SchedulerCycle;

	internal float DrynessIndex;

	internal float WildfireOriginX;

	internal float WildfireOriginY;

	internal float WildfireOriginZ;

	internal double NextStrikeWorldSeconds;

	internal double LastDrynessUpdateWorldSeconds;

	internal double NextWildfireWorldSeconds;

	internal double WildfireForceWorldSeconds;

	internal double NextStrongWindsWorldSeconds;

	internal double StrongWindsRecoveryEndsWorldSeconds;

	internal long LastStrongWindsDay = -99999L;

	internal string Serialize()
	{
		string[] array = new string[34];
		array[0] = FormatVersion.ToString(CultureInfo.InvariantCulture);
		int phase = (int)Phase;
		array[1] = phase.ToString(CultureInfo.InvariantCulture);
		array[2] = PhaseStartedWorldSeconds.ToString("R", CultureInfo.InvariantCulture);
		array[3] = PhaseEndsWorldSeconds.ToString("R", CultureInfo.InvariantCulture);
		array[4] = PeakMeters.ToString("R", CultureInfo.InvariantCulture);
		array[5] = DrawdownMeters.ToString("R", CultureInfo.InvariantCulture);
		array[6] = EventNumber.ToString(CultureInfo.InvariantCulture);
		array[7] = EventSeed.ToString(CultureInfo.InvariantCulture);
		array[8] = LastCompletedDay.ToString(CultureInfo.InvariantCulture);
		array[9] = LastRollDay.ToString(CultureInfo.InvariantCulture);
		array[10] = (ManualEvent ? "1" : "0");
		phase = (int)EventType;
		array[11] = phase.ToString(CultureInfo.InvariantCulture);
		array[12] = LastStormTideDay.ToString(CultureInfo.InvariantCulture);
		array[13] = LastFlashSurgeDay.ToString(CultureInfo.InvariantCulture);
		array[14] = LastGreatFloodDay.ToString(CultureInfo.InvariantCulture);
		array[15] = LastDroughtDay.ToString(CultureInfo.InvariantCulture);
		array[16] = (IsSevereDrought ? "1" : "0");
		array[17] = (SchedulerInitialized ? "1" : "0");
		array[18] = NextOrdinaryEventWorldSeconds.ToString("R", CultureInfo.InvariantCulture);
		array[19] = NextGreatFloodWorldSeconds.ToString("R", CultureInfo.InvariantCulture);
		array[20] = GreatFloodForceWorldSeconds.ToString("R", CultureInfo.InvariantCulture);
		array[21] = GlobalRecoveryEndsWorldSeconds.ToString("R", CultureInfo.InvariantCulture);
		array[22] = SchedulerCycle.ToString(CultureInfo.InvariantCulture);
		array[23] = DrynessIndex.ToString("R", CultureInfo.InvariantCulture);
		array[24] = WildfireOriginX.ToString("R", CultureInfo.InvariantCulture);
		array[25] = WildfireOriginY.ToString("R", CultureInfo.InvariantCulture);
		array[26] = WildfireOriginZ.ToString("R", CultureInfo.InvariantCulture);
		array[27] = NextStrikeWorldSeconds.ToString("R", CultureInfo.InvariantCulture);
		array[28] = LastDrynessUpdateWorldSeconds.ToString("R", CultureInfo.InvariantCulture);
		array[29] = NextWildfireWorldSeconds.ToString("R", CultureInfo.InvariantCulture);
		array[30] = WildfireForceWorldSeconds.ToString("R", CultureInfo.InvariantCulture);
		array[31] = NextStrongWindsWorldSeconds.ToString("R", CultureInfo.InvariantCulture);
		array[32] = StrongWindsRecoveryEndsWorldSeconds.ToString("R", CultureInfo.InvariantCulture);
		array[33] = LastStrongWindsDay.ToString(CultureInfo.InvariantCulture);
		return string.Join("|", array);
	}

	internal static bool TryDeserialize(string data, out FloodState state)
	{
		state = null;
		if (string.IsNullOrWhiteSpace(data))
		{
			return false;
		}
		string[] array = data.Split(new char[1] { '|' });
		if (array.Length != 10 && array.Length != 14 && array.Length != 16 && array.Length != 23 && array.Length != 31 && array.Length != 34)
		{
			return false;
		}
		try
		{
			state = new FloodState
			{
				FormatVersion = int.Parse(array[0], CultureInfo.InvariantCulture),
				Phase = (FloodPhase)int.Parse(array[1], CultureInfo.InvariantCulture),
				PhaseStartedWorldSeconds = double.Parse(array[2], CultureInfo.InvariantCulture),
				PhaseEndsWorldSeconds = double.Parse(array[3], CultureInfo.InvariantCulture),
				PeakMeters = float.Parse(array[4], CultureInfo.InvariantCulture)
			};
			if (array.Length == 23 || array.Length == 31 || array.Length == 34)
			{
				state.DrawdownMeters = float.Parse(array[5], CultureInfo.InvariantCulture);
				state.EventNumber = long.Parse(array[6], CultureInfo.InvariantCulture);
				state.EventSeed = long.Parse(array[7], CultureInfo.InvariantCulture);
				state.LastCompletedDay = long.Parse(array[8], CultureInfo.InvariantCulture);
				state.LastRollDay = long.Parse(array[9], CultureInfo.InvariantCulture);
				state.ManualEvent = array[10] == "1";
				state.EventType = (FloodEventType)int.Parse(array[11], CultureInfo.InvariantCulture);
				state.LastStormTideDay = long.Parse(array[12], CultureInfo.InvariantCulture);
				state.LastFlashSurgeDay = long.Parse(array[13], CultureInfo.InvariantCulture);
				state.LastGreatFloodDay = long.Parse(array[14], CultureInfo.InvariantCulture);
				state.LastDroughtDay = long.Parse(array[15], CultureInfo.InvariantCulture);
				state.IsSevereDrought = array[16] == "1";
				state.SchedulerInitialized = array[17] == "1";
				state.NextOrdinaryEventWorldSeconds = double.Parse(array[18], CultureInfo.InvariantCulture);
				state.NextGreatFloodWorldSeconds = double.Parse(array[19], CultureInfo.InvariantCulture);
				state.GreatFloodForceWorldSeconds = double.Parse(array[20], CultureInfo.InvariantCulture);
				state.GlobalRecoveryEndsWorldSeconds = double.Parse(array[21], CultureInfo.InvariantCulture);
				state.SchedulerCycle = long.Parse(array[22], CultureInfo.InvariantCulture);
				if (array.Length == 31 || array.Length == 34)
				{
					state.DrynessIndex = Mathf.Clamp01(float.Parse(array[23], CultureInfo.InvariantCulture));
					state.WildfireOriginX = float.Parse(array[24], CultureInfo.InvariantCulture);
					state.WildfireOriginY = float.Parse(array[25], CultureInfo.InvariantCulture);
					state.WildfireOriginZ = float.Parse(array[26], CultureInfo.InvariantCulture);
					state.NextStrikeWorldSeconds = double.Parse(array[27], CultureInfo.InvariantCulture);
					state.LastDrynessUpdateWorldSeconds = double.Parse(array[28], CultureInfo.InvariantCulture);
					state.NextWildfireWorldSeconds = double.Parse(array[29], CultureInfo.InvariantCulture);
					state.WildfireForceWorldSeconds = double.Parse(array[30], CultureInfo.InvariantCulture);
				}
				if (array.Length == 34)
				{
					state.NextStrongWindsWorldSeconds = double.Parse(array[31], CultureInfo.InvariantCulture);
					state.StrongWindsRecoveryEndsWorldSeconds = double.Parse(array[32], CultureInfo.InvariantCulture);
					state.LastStrongWindsDay = long.Parse(array[33], CultureInfo.InvariantCulture);
				}
				state.FormatVersion = 6;
			}
			else if (array.Length == 16)
			{
				state.DrawdownMeters = float.Parse(array[5], CultureInfo.InvariantCulture);
				state.EventNumber = long.Parse(array[6], CultureInfo.InvariantCulture);
				state.EventSeed = long.Parse(array[7], CultureInfo.InvariantCulture);
				state.LastCompletedDay = long.Parse(array[8], CultureInfo.InvariantCulture);
				state.LastRollDay = long.Parse(array[9], CultureInfo.InvariantCulture);
				state.ManualEvent = array[10] == "1";
				state.EventType = (FloodEventType)int.Parse(array[11], CultureInfo.InvariantCulture);
				state.LastStormTideDay = long.Parse(array[12], CultureInfo.InvariantCulture);
				state.LastFlashSurgeDay = long.Parse(array[13], CultureInfo.InvariantCulture);
				state.LastGreatFloodDay = long.Parse(array[14], CultureInfo.InvariantCulture);
				state.LastDroughtDay = long.Parse(array[15], CultureInfo.InvariantCulture);
				state.FormatVersion = 6;
			}
			else if (array.Length == 14)
			{
				state.EventNumber = long.Parse(array[5], CultureInfo.InvariantCulture);
				state.EventSeed = long.Parse(array[6], CultureInfo.InvariantCulture);
				state.LastCompletedDay = long.Parse(array[7], CultureInfo.InvariantCulture);
				state.LastRollDay = long.Parse(array[8], CultureInfo.InvariantCulture);
				state.ManualEvent = array[9] == "1";
				state.EventType = (FloodEventType)int.Parse(array[10], CultureInfo.InvariantCulture);
				state.LastStormTideDay = long.Parse(array[11], CultureInfo.InvariantCulture);
				state.LastFlashSurgeDay = long.Parse(array[12], CultureInfo.InvariantCulture);
				state.LastGreatFloodDay = long.Parse(array[13], CultureInfo.InvariantCulture);
				state.LastDroughtDay = state.LastCompletedDay;
				state.DrawdownMeters = 0f;
				state.FormatVersion = 6;
			}
			else
			{
				state.EventNumber = long.Parse(array[5], CultureInfo.InvariantCulture);
				state.EventSeed = long.Parse(array[6], CultureInfo.InvariantCulture);
				state.LastCompletedDay = long.Parse(array[7], CultureInfo.InvariantCulture);
				state.LastRollDay = long.Parse(array[8], CultureInfo.InvariantCulture);
				state.ManualEvent = array[9] == "1";
				state.EventType = ((state.Phase != FloodPhase.Dormant) ? FloodEventType.Custom : FloodEventType.None);
				state.LastStormTideDay = state.LastCompletedDay;
				state.LastFlashSurgeDay = state.LastCompletedDay;
				state.LastGreatFloodDay = state.LastCompletedDay;
				state.LastDroughtDay = state.LastCompletedDay;
				state.DrawdownMeters = 0f;
				state.FormatVersion = 6;
			}
			return true;
		}
		catch
		{
			state = null;
			return false;
		}
	}
}
internal sealed class LightningStrikePayload
{
	internal Vector3 Position;

	internal float Damage;

	internal bool IsDirectHit;

	internal long TargetPlayerId;

	internal int Ignite;

	internal long Seed;

	internal string Serialize()
	{
		return string.Join("|", Position.x.ToString("R", CultureInfo.InvariantCulture), Position.y.ToString("R", CultureInfo.InvariantCulture), Position.z.ToString("R", CultureInfo.InvariantCulture), Damage.ToString("R", CultureInfo.InvariantCulture), IsDirectHit ? "1" : "0", TargetPlayerId.ToString(CultureInfo.InvariantCulture), Ignite.ToString(CultureInfo.InvariantCulture), Seed.ToString(CultureInfo.InvariantCulture));
	}

	internal static bool TryDeserialize(string data, out LightningStrikePayload payload)
	{
		//IL_0056: Unknown result type (might be due to invalid IL or missing references)
		//IL_005b: Unknown result type (might be due to invalid IL or missing references)
		payload = null;
		if (string.IsNullOrWhiteSpace(data))
		{
			return false;
		}
		string[] array = data.Split(new char[1] { '|' });
		if (array.Length != 8)
		{
			return false;
		}
		try
		{
			payload = new LightningStrikePayload
			{
				Position = new Vector3(float.Parse(array[0], CultureInfo.InvariantCulture), float.Parse(array[1], CultureInfo.InvariantCulture), float.Parse(array[2], CultureInfo.InvariantCulture)),
				Damage = float.Parse(array[3], CultureInfo.InvariantCulture),
				IsDirectHit = (array[4] == "1"),
				TargetPlayerId = long.Parse(array[5], CultureInfo.InvariantCulture),
				Ignite = int.Parse(array[6], CultureInfo.InvariantCulture),
				Seed = long.Parse(array[7], CultureInfo.InvariantCulture)
			};
			return true;
		}
		catch
		{
			payload = null;
			return false;
		}
	}
}
internal sealed class FloodStateStore
{
	private readonly ManualLogSource _logger;

	private readonly string _folder;

	internal FloodStateStore(ManualLogSource logger)
	{
		_logger = logger;
		_folder = Path.Combine(Paths.ConfigPath, "marc.thefloods");
	}

	internal FloodState Load(string worldName)
	{
		try
		{
			string path = GetPath(worldName);
			if (!File.Exists(path))
			{
				return new FloodState();
			}
			if (FloodState.TryDeserialize(File.ReadAllText(path), out var state))
			{
				return state;
			}
			_logger.LogWarning((object)"The Floods: state file could not be read, beginning a fresh inactive state.");
		}
		catch (Exception ex)
		{
			_logger.LogError((object)("The Floods: could not load state: " + ex.Message));
		}
		return new FloodState();
	}

	internal void Save(string worldName, FloodState state)
	{
		try
		{
			Directory.CreateDirectory(_folder);
			string path = GetPath(worldName);
			string text = path + ".tmp";
			File.WriteAllText(text, state.Serialize());
			File.Copy(text, path, overwrite: true);
			File.Delete(text);
		}
		catch (Exception ex)
		{
			_logger.LogError((object)("The Floods: could not save state: " + ex.Message));
		}
	}

	private string GetPath(string worldName)
	{
		string text = (string.IsNullOrWhiteSpace(worldName) ? "UnknownWorld" : worldName);
		char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
		foreach (char oldChar in invalidFileNameChars)
		{
			text = text.Replace(oldChar, '_');
		}
		return Path.Combine(_folder, text + ".floods-state.txt");
	}
}
internal sealed class StormApproachVisual
{
	internal static readonly StormApproachVisual Inactive = new StormApproachVisual();

	internal bool Active;

	internal Vector3 FrontDirection;

	internal float FrontProgress;

	internal float OverheadStrength;

	internal float WallStrength;

	internal float WindStrength;

	internal float SurgeMeters;

	internal long Seed;
}
internal sealed class FloodDirector
{
	internal const string RpcState = "TheFloods_State_01";

	internal const string RpcMessage = "TheFloods_Message_01";

	internal const string RpcStrike = "TheFloods_Strike_01";

	private readonly ManualLogSource _logger;

	private readonly FloodConfig _config;

	private readonly FloodWaterAdapter _water;

	private readonly FloodEnvironmentAdapter _environment;

	private readonly FloodVisuals _visuals;

	private readonly FloodStateStore _store;

	private readonly FloodLightningSystem _lightning;

	private readonly WildfireSystem _wildfires;

	private readonly StrongWindsSystem _strongWinds;

	private readonly StormAudioManager _audio;

	private FloodState _state = new FloodState();

	private StormApproachVisual _stormApproach = StormApproachVisual.Inactive;

	private bool _loaded;

	private string _worldName;

	private float _lastSyncAt;

	private float _lastVerboseAt;

	private bool _autoTestStarted;

	private MethodInfo _findBiomeMethod;

	private MethodInfo _currentEnvironmentMethod;

	private FieldInfo _environmentNameField;

	private bool _environmentReflectionSearched;

	private MethodInfo _getSEManMethod;

	private MethodInfo _haveStatusEffectIntMethod;

	private MethodInfo _haveStatusEffectStringMethod;

	private MethodInfo _envManIsWetMethod;

	private bool _playerWetReflectionSearched;

	private bool _statusEffectReflectionSearched;

	private bool _envWetReflectionSearched;

	private static readonly int WetStatusHash = GetStableStatusHash("Wet");

	private readonly Dictionary<string, bool> _rainEnvironmentCache = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);

	internal FloodState CurrentState => _state;

	internal bool IsWorldReady => _loaded;

	internal bool IsAuthoritative
	{
		get
		{
			if ((Object)(object)ZNet.instance != (Object)null)
			{
				return ZNet.instance.IsServer();
			}
			return false;
		}
	}

	internal float CurrentStormStrength
	{
		get
		{
			if (!TryGetWorldSeconds(out var seconds))
			{
				return 0f;
			}
			return GetStormStrength(seconds);
		}
	}

	internal float CurrentWildfireIntensity
	{
		get
		{
			if (!TryGetWorldSeconds(out var seconds))
			{
				return 0f;
			}
			return GetWildfireIntensity(seconds);
		}
	}

	internal StormApproachVisual CurrentStormApproach => _stormApproach ?? StormApproachVisual.Inactive;

	internal FloodDirector(ManualLogSource logger, FloodConfig config, FloodWaterAdapter water, FloodEnvironmentAdapter environment, FloodVisuals visuals)
	{
		_logger = logger;
		_config = config;
		_water = water;
		_environment = environment;
		_visuals = visuals;
		_store = new FloodStateStore(logger);
		_lightning = new FloodLightningSystem(logger, config, visuals);
		_wildfires = new WildfireSystem(logger, config);
		_strongWinds = new StrongWindsSystem(logger, config);
		_audio = new StormAudioManager(logger, config);
	}

	internal void Tick()
	{
		if (!_config.Enabled.Value || (Object)(object)ZNet.instance == (Object)null)
		{
			_water.RestoreOriginalWaterLevel();
			_environment.ReleaseForcedEnvironment();
		}
		else
		{
			if (!TryGetWorldSeconds(out var seconds))
			{
				return;
			}
			EnsureLoaded(seconds);
			ApplyLocalEffects(seconds);
			if (IsAuthoritative)
			{
				RunAuthoritativeState(seconds);
				if (Time.unscaledTime - _lastSyncAt > 4f)
				{
					BroadcastState();
				}
				if (_config.DebugLogging.Value && Time.unscaledTime - _lastVerboseAt > 30f)
				{
					_lastVerboseAt = Time.unscaledTime;
					_logger.LogInfo((object)("The Floods: " + DescribeState(seconds)));
				}
			}
		}
	}

	internal void ReceiveState(string payload)
	{
		if (!FloodState.TryDeserialize(payload, out var state))
		{
			_logger.LogWarning((object)"The Floods: ignored malformed state sync.");
		}
		else if (!IsAuthoritative)
		{
			_state = state;
			_loaded = true;
		}
	}

	internal void ReceiveStrike(string payload)
	{
		if (!LightningStrikePayload.TryDeserialize(payload, out var payload2))
		{
			_logger.LogWarning((object)"The Floods: ignored malformed lightning strike sync.");
			return;
		}
		_lightning.RenderStrike(payload2);
		ApplyLocalLightningDamage(payload2);
	}

	internal void Dispose()
	{
		_lightning.Dispose();
		_wildfires.Dispose();
		_strongWinds.Dispose();
		_audio.Dispose();
	}

	internal string HandleCommand(string[] args)
	{
		//IL_02b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b9: Unknown result type (might be due to invalid IL or missing references)
		//IL_02bc: Unknown result type (might be due to invalid IL or missing references)
		//IL_02f3: Unknown result type (might be due to invalid IL or missing references)
		if (!TryGetWorldSeconds(out var seconds))
		{
			return "The Floods: world clock is not ready yet.";
		}
		EnsureLoaded(seconds);
		if (args == null || args.Length == 0 || args[0].Equals("help", StringComparison.OrdinalIgnoreCase))
		{
			return "The Floods: floods status | floods schedule | floods event stormtide|flashsurge|greatflood|drought|wildfire|strongwinds [omen|rise|spread|peak|recede|burnout] | floods strike | floods strikeme | floods dryness <0..1> | floods ignite [x z] | floods fireout | floods start omen|rise|peak|recede | floods set <metres> | floods fastforward | floods stop | floods probe | strongwinds start|stop|peak|status|testtree.";
		}
		switch (args[0].ToLowerInvariant())
		{
		case "status":
			return DescribeState(seconds);
		case "schedule":
			return DescribeSchedule(seconds);
		case "event":
		{
			if (args.Length < 2)
			{
				return "Usage: floods event stormtide|flashsurge|greatflood|drought|wildfire|strongwinds [omen|rise|spread|peak|recede|burnout]";
			}
			if (!TryParseEventType(args[1], out var type) || type == FloodEventType.Custom)
			{
				return "The Floods: choose stormtide, flashsurge, greatflood, drought, wildfire, or strongwinds.";
			}
			FloodPhase phase = ((args.Length <= 2) ? FloodPhase.Omen : ParsePhase(args[2]));
			if (type == FloodEventType.StrongWinds)
			{
				BeginPhase(FloodEventType.StrongWinds, phase, seconds, manual: true, 0f, rollProfileHeight: false);
				return "The Floods: Strong Winds started in " + phase.ToString() + ".";
			}
			FloodEventProfile profile = _config.GetProfile(type);
			if (profile == null)
			{
				return "The Floods: profile was not found.";
			}
			if (type == FloodEventType.Wildfire)
			{
				Vector3 val = FindIgnitionPointNearPlayer(seconds, allowFallback: true);
				BeginWildfireAt(val, phase, seconds, manual: true, "Smoke rises beyond the trees. Fire and flood are now both in the weather.");
				return "The Floods: Wildfire started in " + phase.ToString() + " at " + FormatVector(val) + ".";
			}
			float num = StartManualEvent(type, phase, seconds);
			string text = ((type == FloodEventType.Drought) ? ("Rolled drawdown: -" + Mathf.Abs(num).ToString("0.0", CultureInfo.InvariantCulture)) : ("Rolled height: +" + num.ToString("0.0", CultureInfo.InvariantCulture)));
			string text2 = ((type == FloodEventType.Drought) ? ("drawdown range " + profile.GetMinimumHeight().ToString("0.0", CultureInfo.InvariantCulture) + "–" + profile.GetMaximumHeight().ToString("0.0", CultureInfo.InvariantCulture) + "m") : ("range " + profile.GetMinimumHeight().ToString("0.0", CultureInfo.InvariantCulture) + "–" + profile.GetMaximumHeight().ToString("0.0", CultureInfo.InvariantCulture) + "m"));
			return "The Floods: " + profile.DisplayName + " started in " + phase.ToString() + ". " + text + " (" + text2 + ").";
		}
		case "start":
		{
			FloodPhase phase2 = ((args.Length <= 1) ? FloodPhase.Omen : ParsePhase(args[1]));
			StartManualEvent(FloodEventType.Custom, phase2, seconds, _config.MaximumSurgeMeters.Value);
			return "The Floods: custom event started in " + phase2.ToString() + ".";
		}
		case "set":
		{
			if (args.Length < 2)
			{
				return "Usage: floods set <metres>";
			}
			if (!float.TryParse(args[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result2))
			{
				return "The Floods: use a number such as 2.5";
			}
			float num2 = Mathf.Max(1f, _config.DebugMaximumSurgeMeters.Value);
			result2 = Mathf.Clamp(result2, 0f - num2, num2);
			StartManualEvent(FloodEventType.Custom, FloodPhase.Peak, seconds, result2);
			return "The Floods: forced custom water offset set to " + FormatSignedMeters(result2) + ".";
		}
		case "fastforward":
			if (_state.Phase == FloodPhase.Dormant)
			{
				return "The Floods: no active event to advance.";
			}
			AdvancePhase(seconds);
			return "The Floods: advanced to " + _state.Phase.ToString() + ".";
		case "stop":
			StopEvent(seconds, manualStop: true);
			return "The Floods: stopped and runtime water restored.";
		case "probe":
			LogWaterProbe();
			return "The Floods: water/environment probe written to BepInEx LogOutput.log.";
		case "strike":
			return TriggerManualStrike(seconds, direct: false);
		case "strikeme":
			return TriggerManualStrike(seconds, direct: true);
		case "dryness":
		{
			if (args.Length < 2)
			{
				return "Usage: floods dryness <0..1>";
			}
			if (!float.TryParse(args[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				return "The Floods: use a dryness value such as 0.75";
			}
			_state.DrynessIndex = Mathf.Clamp01(result);
			_state.LastDrynessUpdateWorldSeconds = seconds;
			SaveAndBroadcast();
			return "The Floods: dryness set to " + _state.DrynessIndex.ToString("0.00", CultureInfo.InvariantCulture) + ".";
		}
		case "ignite":
			return TriggerManualIgnition(args, seconds);
		case "fireout":
			ExtinguishWildfire(seconds, manual: true);
			return "The Floods: wildfire nodes extinguished.";
		default:
			return "The Floods: unknown command. Type floods help";
		}
	}

	internal string HandleStrongWindsCommand(string[] args)
	{
		//IL_0128: Unknown result type (might be due to invalid IL or missing references)
		//IL_012d: Unknown result type (might be due to invalid IL or missing references)
		if (!TryGetWorldSeconds(out var seconds))
		{
			return "Strong Winds: world clock is not ready yet.";
		}
		EnsureLoaded(seconds);
		if (args == null || args.Length == 0 || args[0].Equals("help", StringComparison.OrdinalIgnoreCase))
		{
			return "Strong Winds: strongwinds start | strongwinds stop | strongwinds peak | strongwinds status | strongwinds testtree";
		}
		switch (args[0].ToLowerInvariant())
		{
		case "start":
			BeginPhase(FloodEventType.StrongWinds, FloodPhase.Omen, seconds, manual: true, 0f, rollProfileHeight: false);
			return "Strong Winds: event started.";
		case "peak":
			BeginPhase(FloodEventType.StrongWinds, FloodPhase.Peak, seconds, manual: true, 0f, rollProfileHeight: false);
			return "Strong Winds: event forced to peak.";
		case "stop":
			if (_state.EventType == FloodEventType.StrongWinds)
			{
				StopEvent(seconds, manualStop: true);
				return "Strong Winds: event stopped.";
			}
			_strongWinds.ResetRuntime();
			return "Strong Winds: no active Strong Winds event; runtime wind state cleared.";
		case "status":
			return "Strong Winds: intensity=" + GetStrongWindIntensity(seconds, GetStormStrength(seconds)).ToString("0.00", CultureInfo.InvariantCulture) + " treefalls=" + _strongWinds.TreefallStatus;
		case "testtree":
			return _strongWinds.TryDebugTreefall(GetLoadedPlayers(), GetEffectiveWindDirection(GetStormFrontDirection()), _config.StrongWindsDebugIgnoreBaseProtection.Value);
		default:
			return "Strong Winds: unknown command. Type strongwinds help";
		}
	}

	private void EnsureLoaded(double now)
	{
		if (_loaded)
		{
			return;
		}
		_worldName = ZNet.instance.GetWorldName();
		if (!string.IsNullOrWhiteSpace(_worldName))
		{
			if (IsAuthoritative)
			{
				_state = _store.Load(_worldName);
				_logger.LogInfo((object)("The Floods: loaded state for world '" + _worldName + "'. " + DescribeState(now)));
			}
			_loaded = true;
		}
	}

	private void RunAuthoritativeState(double now)
	{
		if (!_autoTestStarted && _config.StartTestEventOnWorldLoad.Value)
		{
			_autoTestStarted = true;
			StartManualEvent(FloodEventType.Custom, ParsePhase(_config.TestStartPhase.Value), now, _config.MaximumSurgeMeters.Value);
			return;
		}
		UpdateDryness(now);
		if (_state.Phase != FloodPhase.Dormant && now >= _state.PhaseEndsWorldSeconds)
		{
			AdvancePhase(now);
		}
		RunLightningScheduler(now, GetStormStrength(now));
		if (_state.Phase == FloodPhase.Dormant)
		{
			TryRunScheduledNaturalEvent(now);
		}
	}

	private void TryRunScheduledNaturalEvent(double now)
	{
		EnsureWaterCycleSchedule(now);
		if (GetWorldDay(now) < _config.MinimumWorldDay.Value || now < _state.GlobalRecoveryEndsWorldSeconds)
		{
			return;
		}
		if (CanStartGreatFlood(now))
		{
			BeginPhase(FloodEventType.GreatFlood, FloodPhase.Omen, now, manual: false, 0f, rollProfileHeight: true);
		}
		else
		{
			if (TryRunStrongWindsSchedule(now))
			{
				return;
			}
			if (now < _state.NextOrdinaryEventWorldSeconds)
			{
				TryRunWildfireSchedule(now);
				return;
			}
			FloodEventType floodEventType = SelectOrdinaryEventType();
			if (floodEventType == FloodEventType.None)
			{
				_state.NextOrdinaryEventWorldSeconds = now + 1800.0;
				SaveAndBroadcast();
			}
			else
			{
				BeginPhase(floodEventType, FloodPhase.Omen, now, manual: false, 0f, rollProfileHeight: true);
			}
		}
	}

	private void EnsureWaterCycleSchedule(double now)
	{
		if (!_state.SchedulerInitialized)
		{
			long worldDay = GetWorldDay(now);
			double num = Math.Max(60.0, _config.GameDaySeconds.Value);
			double num2 = now + (double)Math.Max(0L, _config.MinimumWorldDay.Value - worldDay) * num;
			float hours = RollSchedulerHours(_config.FirstOrdinaryEventMinimumHours.Value, _config.FirstOrdinaryEventMaximumHours.Value, "FirstOrdinary");
			_state.GlobalRecoveryEndsWorldSeconds = num2;
			_state.NextOrdinaryEventWorldSeconds = num2 + HoursToSeconds(hours);
			ScheduleFirstGreatFlood(now);
			ScheduleNextWildfireCheck(now);
			ScheduleNextStrongWindsCheck(now);
			_state.SchedulerInitialized = true;
			SaveAndBroadcast();
			if (_config.DebugLogging.Value)
			{
				_logger.LogInfo((object)("The Floods V0.8 scheduler initialized. " + DescribeSchedule(now)));
			}
		}
	}

	private void ScheduleFirstGreatFlood(double now)
	{
		double worldAgeHours = GetWorldAgeHours(now);
		float num = RollSchedulerHours(_config.GreatFloodFirstTargetMinimumWorldHours.Value, _config.GreatFloodFirstTargetMaximumWorldHours.Value, "FirstGreatTarget");
		float num2 = Mathf.Max(0f, _config.GreatFloodFirstEligibleWorldHours.Value);
		float num3 = Mathf.Max(Mathf.Max(num2, num), _config.GreatFloodFirstForceByWorldHours.Value);
		double num4 = (double)num - worldAgeHours;
		if (num4 <= 0.0)
		{
			num4 = RollSchedulerHours(0.75f, 2.5f, "MatureWorldFirstGreat");
		}
		double num5 = (double)num3 - worldAgeHours;
		if (num5 <= 0.0)
		{
			num5 = 3.0;
		}
		_state.NextGreatFloodWorldSeconds = now + HoursToSeconds((float)Math.Max(num4, (double)num2 - worldAgeHours));
		_state.GreatFloodForceWorldSeconds = now + HoursToSeconds((float)Math.Max(num5, 1.0));
	}

	private void ScheduleNextGreatFlood(double now)
	{
		float num = RollSchedulerHours(_config.GreatFloodRepeatMinimumHours.Value, _config.GreatFloodRepeatMaximumHours.Value, "RepeatGreat");
		float hours = Mathf.Max(num, _config.GreatFloodRepeatForceByHours.Value);
		_state.NextGreatFloodWorldSeconds = now + HoursToSeconds(num);
		_state.GreatFloodForceWorldSeconds = now + HoursToSeconds(hours);
	}

	private bool CanStartGreatFlood(double now)
	{
		FloodEventProfile greatFlood = _config.GreatFlood;
		if (greatFlood == null || !greatFlood.Enabled.Value)
		{
			return false;
		}
		if (GetWorldAgeHours(now) < (double)Mathf.Max(0f, _config.GreatFloodFirstEligibleWorldHours.Value))
		{
			return false;
		}
		if (!(now >= _state.NextGreatFloodWorldSeconds))
		{
			return now >= _state.GreatFloodForceWorldSeconds;
		}
		return true;
	}

	private void ScheduleAfterNaturalEvent(FloodEventType completedType, double now)
	{
		float hours;
		switch (completedType)
		{
		case FloodEventType.FlashSurge:
			hours = RollSchedulerHours(_config.FlashSurgeRecoveryMinimumHours.Value, _config.FlashSurgeRecoveryMaximumHours.Value, "AfterFlash");
			break;
		case FloodEventType.GreatFlood:
			hours = RollSchedulerHours(_config.GreatFloodRecoveryMinimumHours.Value, _config.GreatFloodRecoveryMaximumHours.Value, "AfterGreat");
			ScheduleNextGreatFlood(now);
			break;
		default:
			hours = RollSchedulerHours(_config.OrdinaryRecoveryMinimumHours.Value, _config.OrdinaryRecoveryMaximumHours.Value, "AfterOrdinary");
			break;
		}
		_state.GlobalRecoveryEndsWorldSeconds = now + HoursToSeconds(hours);
		_state.NextOrdinaryEventWorldSeconds = _state.GlobalRecoveryEndsWorldSeconds;
		ScheduleNextWildfireCheck(now);
		if (completedType == FloodEventType.StrongWinds)
		{
			_state.StrongWindsRecoveryEndsWorldSeconds = now + HoursToSeconds(Mathf.Max(0.1f, _config.StrongWindsCooldownHours.Value));
		}
		ScheduleNextStrongWindsCheck(now);
	}

	private void ScheduleNextStrongWindsCheck(double now)
	{
		float num = Mathf.Max(0.1f, _config.StrongWindsCooldownHours.Value);
		float hours = RollSchedulerHours(num * 0.75f, num * 1.35f, "StrongWinds");
		_state.NextStrongWindsWorldSeconds = now + HoursToSeconds(hours);
	}

	private bool TryRunStrongWindsSchedule(double now)
	{
		if (!_config.EnableStrongWinds.Value || _state.EventType != FloodEventType.None || _state.Phase != FloodPhase.Dormant)
		{
			return false;
		}
		if (GetWorldDay(now) < _config.StrongWindsMinimumWorldDay.Value || now < _state.StrongWindsRecoveryEndsWorldSeconds)
		{
			return false;
		}
		if (_state.NextStrongWindsWorldSeconds <= 0.0)
		{
			ScheduleNextStrongWindsCheck(now);
			SaveAndBroadcast();
			return false;
		}
		if (now < _state.NextStrongWindsWorldSeconds)
		{
			return false;
		}
		_state.SchedulerCycle++;
		if (DeterministicRoll(_worldName + "|Floods|StrongWinds|" + _state.SchedulerCycle.ToString(CultureInfo.InvariantCulture)) <= Mathf.Clamp01(_config.StrongWindsEventChance.Value))
		{
			BeginPhase(FloodEventType.StrongWinds, FloodPhase.Omen, now, manual: false, 0f, rollProfileHeight: false);
			return true;
		}
		ScheduleNextStrongWindsCheck(now);
		SaveAndBroadcast();
		return false;
	}

	private void UpdateDryness(double now)
	{
		if (_state.LastDrynessUpdateWorldSeconds <= 0.0 || now < _state.LastDrynessUpdateWorldSeconds)
		{
			_state.LastDrynessUpdateWorldSeconds = now;
			return;
		}
		double num = Math.Min(30.0, Math.Max(0.0, now - _state.LastDrynessUpdateWorldSeconds));
		_state.LastDrynessUpdateWorldSeconds = now;
		if (!(num <= 0.001))
		{
			float drynessIndex = _state.DrynessIndex;
			float stormStrength = GetStormStrength(now);
			if (IsGroundWetForFire(now, stormStrength))
			{
				float num2 = Mathf.Max(30f, _config.DrynessRainResetSeconds.Value);
				_state.DrynessIndex = Mathf.Clamp01(_state.DrynessIndex - (float)(num / (double)num2));
			}
			else
			{
				float num3 = ((_state.EventType == FloodEventType.Drought && _state.Phase != FloodPhase.Dormant) ? Mathf.Max(1f, _config.DrynessRiseDroughtMultiplier.Value) : 1f);
				_state.DrynessIndex = Mathf.Clamp01(_state.DrynessIndex + (float)(num / 3600.0) * Mathf.Max(0f, _config.DrynessRisePerHour.Value) * num3);
			}
			if (Mathf.Abs(drynessIndex - _state.DrynessIndex) > 0.01f && Time.unscaledTime - _lastSyncAt > 8f)
			{
				SaveAndBroadcast();
			}
		}
	}

	private void ScheduleNextWildfireCheck(double now)
	{
		float num = RollSchedulerHours(_config.SpontaneousWindowMinimumHours.Value, _config.SpontaneousWindowMaximumHours.Value, "WildfireWindow");
		float num2 = Mathf.Max(num, Mathf.Max(_config.SpontaneousWindowMinimumHours.Value, _config.SpontaneousWindowMaximumHours.Value));
		_state.NextWildfireWorldSeconds = now + HoursToSeconds(num);
		_state.WildfireForceWorldSeconds = now + HoursToSeconds(Mathf.Max(num2 * 4f, num));
	}

	private void TryRunWildfireSchedule(double now)
	{
		//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b4: 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_017d: Unknown result type (might be due to invalid IL or missing references)
		if (_state.EventType != FloodEventType.None || _state.Phase != FloodPhase.Dormant)
		{
			return;
		}
		FloodEventProfile wildfire = _config.Wildfire;
		if (wildfire == null || !wildfire.Enabled.Value)
		{
			return;
		}
		if (_state.NextWildfireWorldSeconds <= 0.0)
		{
			ScheduleNextWildfireCheck(now);
			SaveAndBroadcast();
		}
		else
		{
			if ((_state.DrynessIndex < Mathf.Clamp01(_config.SpontaneousDrynessThreshold.Value) && now < _state.WildfireForceWorldSeconds) || (now < _state.NextWildfireWorldSeconds && now < _state.WildfireForceWorldSeconds))
			{
				return;
			}
			Vector3 val = FindIgnitionPointNearPlayer(now, allowFallback: false);
			if (val == Vector3.zero)
			{
				ScheduleNextWildfireCheck(now);
				SaveAndBroadcast();
				return;
			}
			float biomeFlammability = GetBiomeFlammability(val);
			float num = Mathf.Clamp01(_config.SpontaneousPerWindowChance.Value * Mathf.Clamp01(_state.DrynessIndex) * biomeFlammability);
			bool num2 = now >= _state.WildfireForceWorldSeconds && _state.DrynessIndex >= Mathf.Clamp01(_config.SpontaneousDrynessThreshold.Value);
			_state.SchedulerCycle++;
			float num3 = DeterministicRoll(_worldName + "|Floods|WildfireSpontaneous|" + _state.SchedulerCycle.ToString(CultureInfo.InvariantCulture));
			if (num2 || num3 < num)
			{
				BeginWildfireAt(val, FloodPhase.Omen, now, manual: false, "Smoke rises beyond the trees. Drought has made the land ready to burn.");
				return;
			}
			ScheduleNextWildfireCheck(now);
			SaveAndBroadcast();
		}
	}

	private void RunLightningScheduler(double now, float stormStrength)
	{
		//IL_012e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0121: Unknown result type (might be due to invalid IL or missing references)
		//IL_0133: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c2: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f2: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
		//IL_01de: Unknown result type (might be due to invalid IL or missing references)
		if (!_config.EnableLightningStrikes.Value || stormStrength < Mathf.Clamp01(_config.PlayerStrikeMinStrength.Value) || _state.Phase == FloodPhase.Omen || _state.Phase == FloodPhase.Dormant)
		{
			return;
		}
		if (_state.NextStrikeWorldSeconds <= 0.0 || _state.NextStrikeWorldSeconds < now - 300.0)
		{
			ScheduleNextStrike(now, stormStrength);
			SaveAndBroadcast();
		}
		else
		{
			if (now < _state.NextStrikeWorldSeconds)
			{
				return;
			}
			Player val = PickLightningTarget();
			if ((Object)(object)val == (Object)null)
			{
				ScheduleNextStrike(now + 10.0, stormStrength);
				SaveAndBroadcast();
				return;
			}
			_state.SchedulerCycle++;
			bool flag = DeterministicRoll(_worldName + "|Floods|LightningDirect|" + _state.SchedulerCycle.ToString(CultureInfo.InvariantCulture)) < Mathf.Clamp01(_config.DirectHitChance.Value) && IsPlayerExposed(val);
			Vector3 val2 = (flag ? ((Component)val).transform.position : PickNearMissPoint(val));
			float damage = (flag ? RollLightningDamage(val, stormStrength) : 0f);
			long targetPlayerId = (flag ? GetPlayerId(val) : 0);
			_state.SchedulerCycle++;
			long seed = StableHash(_worldName + "|Floods|Strike|" + _state.SchedulerCycle.ToString(CultureInfo.InvariantCulture) + "|" + now.ToString("R", CultureInfo.InvariantCulture));
			int num = (ShouldLightningIgnite(val2, stormStrength, seed) ? 1 : 0);
			if (num == 1 && IsAuthoritative)
			{
				BeginWildfireAt(val2, FloodPhase.Omen, now, manual: false, "Dry lightning splits the sky. Fire takes hold where the land stayed thirsty.");
			}
			LightningStrikePayload payload = new LightningStrikePayload
			{
				Position = val2,
				Damage = damage,
				IsDirectHit = flag,
				TargetPlayerId = targetPlayerId,
				Ignite = num,
				Seed = seed
			};
			BroadcastStrike(payload);
			ScheduleNextStrike(now, stormStrength);
			SaveAndBroadcast();
		}
	}

	private void ScheduleNextStrike(double now, float stormStrength)
	{
		float num = Mathf.Max(3f, Mathf.Min(_config.StrikeGapMinSeconds.Value, _config.StrikeGapMaxSeconds.Value));
		float num2 = Mathf.Max(num, Mathf.Max(_config.StrikeGapMinSeconds.Value, _config.StrikeGapMaxSeconds.Value));
		_state.SchedulerCycle++;
		float num3 = DeterministicRoll(_worldName + "|Floods|StrikeGap|" + _state.SchedulerCycle.ToString(CultureInfo.InvariantCulture));
		float num4 = Mathf.Lerp(num2, num, Mathf.Clamp01(stormStrength));
		float num5 = Mathf.Lerp(0.75f, 1.25f, num3);
		_state.NextStrikeWorldSeconds = now + Math.Max(2.0, num4 * num5);
	}

	private Player PickLightningTarget()
	{
		List<Player> loadedPlayers = GetLoadedPlayers();
		if (loadedPlayers.Count == 0)
		{
			return null;
		}
		List<Player> list = new List<Player>();
		for (int i = 0; i < loadedPlayers.Count; i++)
		{
			if ((Object)(object)loadedPlayers[i] != (Object)null && IsPlayerExposed(loadedPlayers[i]))
			{
				list.Add(loadedPlayers[i]);
			}
		}
		if (list.Count == 0)
		{
			return null;
		}
		_state.SchedulerCycle++;
		int num = Mathf.FloorToInt(DeterministicRoll(_worldName + "|Floods|StrikeTarget|" + _state.SchedulerCycle.ToString(CultureInfo.InvariantCulture)) * (float)list.Count);
		return list[Mathf.Clamp(num, 0, list.Count - 1)];
	}

	private Vector3 PickNearMissPoint(Player target)
	{
		//IL_0016: 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_0094: Unknown result type (might be due to invalid IL or missing references)
		//IL_0099: Unknown result type (might be due to invalid IL or missing references)
		//IL_009e: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a0: 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_00aa: Unknown result type (might be due to invalid IL or missing references)
		Vector3 val = (((Object)(object)target == (Object)null) ? Vector3.zero : ((Component)target).transform.position);
		float num = Mathf.Max(8f, _config.IgnitionMinRadius.Value * 0.25f);
		float num2 = Mathf.Max(num + 1f, Mathf.Min(35f, _config.IgnitionMaxRadius.Value * 0.45f));
		float num3 = Random.Range(0f, (float)Math.PI * 2f);
		float num4 = Random.Range(num, num2);
		Vector3 val2 = val + new Vector3(Mathf.Cos(num3) * num4, 0f, Mathf.Sin(num3) * num4);
		if (!TryFindGround(val2, out var grounded))
		{
			return val2;
		}
		return grounded;
	}

	private bool ShouldLightningIgnite(Vector3 point, float stormStrength, long seed)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		if (!IsIgnitablePoint(point, requireDryness: true, stormStrength))
		{
			return false;
		}
		float biomeFlammability = GetBiomeFlammability(point);
		float num = Mathf.Clamp01(_config.LightningIgnitionChance.Value * biomeFlammability * Mathf.Clamp01(_state.DrynessIndex));
		return DeterministicRoll(_worldName + "|Floods|LightningIgnition|" + seed.ToString(CultureInfo.InvariantCulture)) < num;
	}

	private void BroadcastStrike(LightningStrikePayload payload)
	{
		if (ZRoutedRpc.instance != null && payload != null)
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "TheFloods_Strike_01", new object[1] { payload.Serialize() });
		}
	}

	private void ApplyLocalLightningDamage(LightningStrikePayload strike)
	{
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_005f: 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_0069: 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_00c2: Expected O, but got Unknown
		//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
		//IL_011f: Unknown result type (might be due to invalid IL or missing references)
		if (strike == null || !strike.IsDirectHit || strike.Damage <= 0.001f || (Object)(object)Player.m_localPlayer == (Object)null)
		{
			return;
		}
		Player localPlayer = Player.m_localPlayer;
		long playerId = GetPlayerId(localPlayer);
		bool flag = strike.TargetPlayerId != 0L && playerId == strike.TargetPlayerId;
		if (!flag && strike.TargetPlayerId == 0L)
		{
			Vector3 val = ((Component)localPlayer).transform.position - strike.Position;
			flag = ((Vector3)(ref val)).sqrMagnitude <= 9f;
		}
		if (!flag)
		{
			return;
		}
		float num = strike.Damage;
		if (_config.NonLethalLightning.Value)
		{
			float playerHealth = GetPlayerHealth(localPlayer);
			if (playerHealth > 1f)
			{
				num = Mathf.Min(num, playerHealth - 1f);
			}
		}
		HitData val2 = new HitData();
		float playerHealth2 = GetPlayerHealth(localPlayer);
		val2.m_damage.m_lightning = Mathf.Max(0f, num);
		val2.m_point = strike.Position;
		val2.m_dir = Vector3.down;
		val2.m_pushForce = Mathf.Max(0f, _config.LightningPushForce.Value);
		val2.m_hitType = (HitType)0;
		((Character)localPlayer).Damage(val2);
		if ((Object)(object)MessageHud.instance != (Object)null)
		{
			MessageHud.instance.ShowMessage((MessageType)2, (num >= playerHealth2) ? "You are struck down by the storm." : "Lightning tears through you!", 0, (Sprite)null, false);
		}
	}

	private float RollLightningDamage(Player target, float stormStrength)
	{
		float num = Mathf.Lerp(Mathf.Max(0f, _config.MinLightningDamage.Value), Mathf.Max(_config.MinLightningDamage.Value, _config.MaxLightningDamage.Value), Mathf.Clamp01(stormStrength));
		if ((Object)(object)target != (Object)null && IsPlayerWet(target))
		{
			num *= Mathf.Max(0.1f, _config.WetDamageMultiplier.Value);
		}
		return num;
	}

	private string TriggerManualStrike(double now, bool direct)
	{
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: 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_0058: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer == (Object)null)
		{
			return "The Floods: no local player found for strike test.";
		}
		Vector3 val = (direct ? ((Component)localPlayer).transform.position : PickNearMissPoint(localPlayer));
		float stormStrength = Mathf.Max(GetStormStrength(now), 0.9f);
		float damage = (direct ? RollLightningDamage(localPlayer, stormStrength) : 0f);
		LightningStrikePayload payload = new LightningStrikePayload
		{
			Position = val,
			Damage = damage,
			IsDirectHit = direct,
			TargetPlayerId = (direct ? GetPlayerId(localPlayer) : 0),
			Ignite = 0,
			Seed = StableHash(_worldName + "|Floods|ManualStrike|" + now.ToString("R", CultureInfo.InvariantCulture))
		};
		BroadcastStrike(payload);
		_state.NextStrikeWorldSeconds = now + (double)Mathf.Max(2f, _config.StrikeGraceSeconds.Value);
		SaveAndBroadcast();
		if (!direct)
		{
			return "The Floods: near-miss lightning strike requested at " + FormatVector(val) + ".";
		}
		return "The Floods: direct lightning strike requested at your position for " + damage.ToString("0", CultureInfo.InvariantCulture) + " lightning damage.";
	}

	private string TriggerManualIgnition(string[] args, double now)
	{
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0080: Unknown result type (might be due to invalid IL or missing references)
		//IL_0087: 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_0052: Unknown result type (might be due to invalid IL or missing references)
		//IL_006a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0074: Unknown result type (might be due to invalid IL or missing references)
		//IL_0075: Unknown result type (might be due to invalid IL or missing references)
		Vector3 grounded;
		if (args.Length >= 3)
		{
			if (!float.TryParse(args[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || !float.TryParse(args[2], NumberStyles.Float, CultureInfo.InvariantCulture, out var result2))
			{
				return "Usage: floods ignite [x z]";
			}
			Vector3 val = default(Vector3);
			((Vector3)(ref val))..ctor(result, ((Object)(object)Player.m_localPlayer == (Object)null) ? 35f : ((Component)Player.m_localPlayer).transform.position.y, result2);
			if (!TryFindGround(val, out grounded))
			{
				grounded = val;
			}
		}
		else
		{
			grounded = FindIgnitionPointNearPlayer(now, allowFallback: true);
		}
		_wildfires.Ignite(grounded, now, StableHash(_worldName + "|Floods|ManualIgnite|" + now.ToString("R", CultureInfo.InvariantCulture)));
		return "The Floods: test fire node ignited at " + FormatVector(grounded) + ".";
	}

	private void BeginWildfireAt(Vector3 origin, FloodPhase phase, double now, bool manual, string reason)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0001: 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_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0061: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		if (origin == Vector3.zero)
		{
			origin = FindIgnitionPointNearPlayer(now, allowFallback: true);
		}
		_state.WildfireOriginX = origin.x;
		_state.WildfireOriginY = origin.y;
		_state.WildfireOriginZ = origin.z;
		BeginPhase(FloodEventType.Wildfire, phase, now, manual, 0f, rollProfileHeight: false);
		_wildfires.Ignite(origin, now, _state.EventSeed);
		if (!string.IsNullOrEmpty(reason))
		{
			BroadcastMessage(reason);
		}
	}

	private void ExtinguishWildfire(double now, bool manual)
	{
		_wildfires.ExtinguishAll();
		if (_state.EventType == FloodEventType.Wildfire)
		{
			StopEvent(now, manual);
		}
		else
		{
			SaveAndBroadcast();
		}
	}

	private Vector3 FindIgnitionPointNearPlayer(double now, bool allowFallback)
	{
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00de: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_0101: Unknown result type (might be due to invalid IL or missing references)
		//IL_0106: Unknown result type (might be due to invalid IL or missing references)
		//IL_0109: Unknown result type (might be due to invalid IL or missing references)
		//IL_0152: Unknown result type (might be due to invalid IL or missing references)
		//IL_015d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0163: Unknown result type (might be due to invalid IL or missing references)
		//IL_0168: Unknown result type (might be due to invalid IL or missing references)
		//IL_016d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0170: Unknown result type (might be due to invalid IL or missing references)
		//IL_013e: Unknown result type (might be due to invalid IL or missing references)
		//IL_017e: Unknown result type (might be due to invalid IL or missing references)
		//IL_017b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0129: Unknown result type (might be due to invalid IL or missing references)
		//IL_0118: Unknown result type (might be due to invalid IL or missing references)
		List<Player> loadedPlayers = GetLoadedPlayers();
		if (loadedPlayers.Count == 0 && (Object)(object)Player.m_localPlayer != (Object)null)
		{
			loadedPlayers.Add(Player.m_localPlayer);
		}
		if (loadedPlayers.Count == 0)
		{
			return Vector3.zero;
		}
		float num = Mathf.Max(5f, Mathf.Min(_config.IgnitionMinRadius.Value, _config.IgnitionMaxRadius.Value));
		float num2 = Mathf.Max(num + 1f, Mathf.Max(_config.IgnitionMinRadius.Value, _config.IgnitionMaxRadius.Value));
		for (int i = 0; i < 16; i++)
		{
			Player val = loadedPlayers[Random.Range(0, loadedPlayers.Count)];
			if (!((Object)(object)val == (Object)null))
			{
				float num3 = Random.Range(0f, (float)Math.PI * 2f);
				float num4 = Random.Range(num, num2);
				Vector3 candidate = ((Component)val).transform.position + new Vector3(Mathf.Cos(num3) * num4, 0f, Mathf.Sin(num3) * num4);
				if (TryFindGround(candidate, out var grounded) && (allowFallback || IsIgnitablePoint(grounded, requireDryness: true, GetStormStrength(now))))
				{
					return grounded;
				}
			}
		}
		if (!allowFallback)
		{
			return Vector3.zero;
		}
		Player val2 = loadedPlayers[0];
		Vector3 val3 = ((Component)val2).transform.position + ((Component)val2).transform.forward * num;
		if (!TryFindGround(val3, out var grounded2))
		{
			return val3;
		}
		return grounded2;
	}

	private bool TryFindGround(Vector3 candidate, out Vector3 grounded)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//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_000d: Unknown result type (might be due to invalid IL or missing references)
		//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)
		//IL_0024: 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_0041: Unknown result type (might be due to invalid IL or missing references)
		grounded = candidate;
		try
		{
			RaycastHit val = default(RaycastHit);
			if (Physics.Raycast(new Vector3(candidate.x, candidate.y + 120f, candidate.z), Vector3.down, ref val, 260f, -1, (QueryTriggerInteraction)1))
			{
				grounded = ((RaycastHit)(ref val)).point;
				return true;
			}
		}
		catch
		{
		}
		return false;
	}

	private bool IsIgnitableForSpread(Vector3 point)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		double seconds;
		return IsIgnitablePoint(point, requireDryness: true, GetStormStrength(TryGetWorldSeconds(out seconds) ? seconds : 0.0));
	}

	private bool IsIgnitablePoint(Vector3 point, bool requireDryness, float stormStrength)
	{
		//IL_004e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		if (requireDryness && _state.DrynessIndex < Mathf.Clamp01(_config.SpontaneousDrynessThreshold.Value))
		{
			return false;
		}
		if (IsGroundWetForFire(TryGetWorldSeconds(out var seconds) ? seconds : 0.0, stormStrength))
		{
			return false;
		}
		if (_water.IsPointUnderKnownWater(point))
		{
			return false;
		}
		return GetBiomeFlammability(point) > 0.01f;
	}

	private bool IsGroundWetForFire(double now, float stormStrength)
	{
		if (IsWorldWetFromEnvMan())
		{
			return true;
		}
		if ((_state.EventType == FloodEventType.StormTide || _state.EventType == FloodEventType.FlashSurge || _state.EventType == FloodEventType.GreatFlood) && _state.Phase != FloodPhase.Omen && stormStrength >= Mathf.Clamp01(_config.RainWetStormStrength.Value))
		{
			return true;
		}
		string currentEnvironmentName = GetCurrentEnvironmentName();
		if (IsRainEnvironmentName(currentEnvironmentName))
		{
			if (!(stormStrength <= 0.001f) && !(stormStrength >= Mathf.Clamp01(_config.RainWetStormStrength.Value)))
			{
				return _state.EventType == FloodEventType.Wildfire;
			}
			return true;
		}
		return false;
	}

	private float GetBiomeFlammability(Vector3 point)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		string text = (GetBiomeName(point) ?? string.Empty).Replace(" ", string.Empty).Replace("_", string.Empty).ToLowerInvariant();
		if (text.Contains("meadows"))
		{
			return Mathf.Max(0f, _config.MeadowsFlammability.Value);
		}
		if (text.Contains("blackforest"))
		{
			return Mathf.Max(0f, _config.BlackForestFlammability.Value);
		}
		if (text.Contains("plains"))
		{
			return Mathf.Max(0f, _config.PlainsFlammability.Value);
		}
		if (text.Contains("swamp"))
		{
			return Mathf.Max(0f, _config.SwampFlammability.Value);
		}
		if (text.Contains("mountain"))
		{
			return Mathf.Max(0f, _config.MountainFlammability.Value);
		}
		if (text.Contains("mistlands"))
		{
			return Mathf.Max(0f, _config.MistlandsFlammability.Value);
		}
		if (text.Contains("ashlands"))
		{
			return Mathf.Max(0f, _config.AshlandsFlammability.Value);
		}
		if (text.Contains("deepnorth"))
		{
			return Mathf.Max(0f, _config.DeepNorthFlammability.Value);
		}
		if (text.Contains("ocean"))
		{
			return 0f;
		}
		return Mathf.Max(0f, _config.OtherBiomeFlammability.Value);
	}

	private string GetBiomeName(Vector3 point)
	{
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			if (_findBiomeMethod == null)
			{
				_findBiomeMethod = AccessTools.Method(typeof(Heightmap), "FindBiome", new Type[1] { typeof(Vector3) }, (Type[])null);
			}
			if (_findBiomeMethod != null)
			{
				object obj = _findBiomeMethod.Invoke(null, new object[1] { point });
				return (obj == null) ? string.Empty : obj.ToString();
			}
		}
		catch
		{
		}
		return string.Empty;
	}

	private string GetCurrentEnvironmentName()
	{
		if ((Object)(object)EnvMan.instance == (Object)null)
		{
			return string.Empty;
		}
		try
		{
			if (!_environmentReflectionSearched)
			{
				_environmentReflectionSearched = true;
				_currentEnvironmentMethod = AccessTools.Method(typeof(EnvMan), "GetCurrentEnvironment", Type.EmptyTypes, (Type[])null);
			}
			object obj = ((_currentEnvironmentMethod == null) ? null : _currentEnvironmentMethod.Invoke(EnvMan.instance, null));
			if (obj == null)
			{
				return string.Empty;
			}
			if (_environmentNameField == null)
			{
				_environmentNameField = AccessTools.Field(obj.GetType(), "m_name");
			}
			return (((_environmentNameField == null) ? null : _environmentNameField.GetValue(obj)) as string) ?? string.Empty;
		}
		catch
		{
			return string.Empty;
		}
	}

	private bool IsRainEnvironmentName(string environmentName)
	{
		if (string.IsNullOrWhiteSpace(environmentName))
		{
			return false;
		}
		if (_rainEnvironmentCache.TryGetValue(environmentName, out var value))
		{
			return value;
		}
		string[] array = (_config.RainEnvironmentNames.Value ?? string.Empty).Split(new char[1] { ',' });
		bool flag = false;
		for (int i = 0; i < array.Length; i