Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of TheFloods v0.10.2
TheFloods.dll
Decompiled 3 weeks ago
The result has been truncated due to the large size, download it to view full contents!
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