Decompiled source of MimesisPlayerEnhancement v26.6.3

MimesisPlayerEnhancement.dll

Decompiled 11 hours ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Bifrost.ConstEnum;
using Bifrost.Cooked;
using DunGen;
using FishNet.Object.Synchronizing;
using HarmonyLib;
using MelonLoader;
using MelonLoader.Logging;
using MelonLoader.Pastel;
using MelonLoader.Preferences;
using MelonLoader.Utils;
using Microsoft.CodeAnalysis;
using MimesisPlayerEnhancement;
using MimesisPlayerEnhancement.Features.DungeonRandomizer;
using MimesisPlayerEnhancement.Features.DungeonTime;
using MimesisPlayerEnhancement.Features.JoinAnytime;
using MimesisPlayerEnhancement.Features.LootMultiplicator;
using MimesisPlayerEnhancement.Features.MoneyMultiplier;
using MimesisPlayerEnhancement.Features.MorePlayers;
using MimesisPlayerEnhancement.Features.MoreVoices;
using MimesisPlayerEnhancement.Features.Persistence;
using MimesisPlayerEnhancement.Features.PlayerAnnouncements;
using MimesisPlayerEnhancement.Features.SpawnScaling;
using MimesisPlayerEnhancement.Features.SpectatorTransition;
using MimesisPlayerEnhancement.Features.Statistics;
using MimesisPlayerEnhancement.Features.Statistics.Models;
using MimesisPlayerEnhancement.Features.WebDashboard;
using MimesisPlayerEnhancement.Features.WebDashboard.Models;
using MimesisPlayerEnhancement.Util;
using Mimic.Actors;
using Mimic.Voice;
using Mimic.Voice.SpeechSystem;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using ReluProtocol;
using ReluProtocol.C2S;
using ReluProtocol.Enum;
using ReluReplay.Data;
using ReluReplay.Serializer;
using Steamworks;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Mod), "MimesisPlayerEnhancement", "26.6.3", "kalle", null)]
[assembly: MelonGame("ReLUGames", "MIMESIS")]
[assembly: HarmonyDontPatchAll]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace MimesisPlayerEnhancement
{
	public static class ModConfig
	{
		private const string MainCategoryId = "MimesisPlayerEnhancement";

		private static MelonPreferences_Category _mainCategory = null;

		private static MelonPreferences_Category _morePlayersCategory = null;

		private static MelonPreferences_Category _moreVoicesCategory = null;

		private static MelonPreferences_Category _persistenceCategory = null;

		private static MelonPreferences_Category _statisticsCategory = null;

		private static MelonPreferences_Category _playerAnnouncementsCategory = null;

		private static MelonPreferences_Category _joinAnytimeCategory = null;

		private static MelonPreferences_Category _spawnScalingCategory = null;

		private static MelonPreferences_Category _lootMultiplicatorCategory = null;

		private static MelonPreferences_Category _moneyMultiplierCategory = null;

		private static MelonPreferences_Category _dungeonTimeCategory = null;

		private static MelonPreferences_Category _spectatorTransitionCategory = null;

		private static MelonPreferences_Category _dungeonRandomizerCategory = null;

		private static MelonPreferences_Category _webDashboardCategory = null;

		private static readonly List<MelonPreferences_Entry<float>> FloatEntries = new List<MelonPreferences_Entry<float>>();

		public static int Version => ModConfigRegistry.Version;

		public static bool IsInitialized { get; private set; }

		public static string FilePath { get; private set; } = "";

		public static MelonPreferences_Category MainCategory { get; private set; } = null;

		public static MelonPreferences_Entry<bool> EnableMorePlayers { get; private set; } = null;

		public static MelonPreferences_Entry<int> MaxPlayers { get; private set; } = null;

		public static MelonPreferences_Entry<bool> EnableMoreVoices { get; private set; } = null;

		public static MelonPreferences_Entry<int> MaxIndoorVoiceEvents { get; private set; } = null;

		public static MelonPreferences_Entry<int> MaxDeathMatchVoiceEvents { get; private set; } = null;

		public static MelonPreferences_Entry<int> MaxOutdoorVoiceEvents { get; private set; } = null;

		public static MelonPreferences_Entry<bool> EnablePersistence { get; private set; } = null;

		public static MelonPreferences_Entry<bool> EnableStatistics { get; private set; } = null;

		public static MelonPreferences_Entry<int> SessionReconnectGraceMinutes { get; private set; } = null;

		public static MelonPreferences_Entry<bool> ShowStatisticsToasts { get; private set; } = null;

		public static MelonPreferences_Entry<bool> ShowPlayerAnnouncements { get; private set; } = null;

		public static MelonPreferences_Entry<float> ModToastDurationSeconds { get; private set; } = null;

		public static MelonPreferences_Entry<bool> EnableJoinAnytime { get; private set; } = null;

		public static MelonPreferences_Entry<bool> EnableSpawnScaling { get; private set; } = null;

		public static MelonPreferences_Entry<bool> AutoScaleMimicSpawnsByPlayerCount { get; private set; } = null;

		public static MelonPreferences_Entry<float> MimicSpawnMultiplier { get; private set; } = null;

		public static MelonPreferences_Entry<bool> AutoScaleBossSpawnsByPlayerCount { get; private set; } = null;

		public static MelonPreferences_Entry<float> BossSpawnMultiplier { get; private set; } = null;

		public static MelonPreferences_Entry<bool> AutoScaleJakoSpawnsByPlayerCount { get; private set; } = null;

		public static MelonPreferences_Entry<float> JakoSpawnMultiplier { get; private set; } = null;

		public static MelonPreferences_Entry<bool> AutoScaleSpecialSpawnsByPlayerCount { get; private set; } = null;

		public static MelonPreferences_Entry<float> SpecialSpawnMultiplier { get; private set; } = null;

		public static MelonPreferences_Entry<bool> AutoScaleTrapSpawnsByPlayerCount { get; private set; } = null;

		public static MelonPreferences_Entry<float> TrapSpawnMultiplier { get; private set; } = null;

		public static MelonPreferences_Entry<float> FixedSpawnRespawnDelayMinSeconds { get; private set; } = null;

		public static MelonPreferences_Entry<float> FixedSpawnRespawnDelayMaxSeconds { get; private set; } = null;

		public static MelonPreferences_Entry<float> FixedSpawnRespawnMinPlayerDistanceMeters { get; private set; } = null;

		public static MelonPreferences_Entry<bool> AutoScaleOtherSpawnsByPlayerCount { get; private set; } = null;

		public static MelonPreferences_Entry<float> OtherSpawnMultiplier { get; private set; } = null;

		public static MelonPreferences_Entry<bool> EnableLootMultiplicator { get; private set; } = null;

		public static MelonPreferences_Entry<bool> AutoScaleMapConsumableLootByPlayerCount { get; private set; } = null;

		public static MelonPreferences_Entry<float> MapConsumableLootMultiplier { get; private set; } = null;

		public static MelonPreferences_Entry<bool> AutoScaleMapEquipmentLootByPlayerCount { get; private set; } = null;

		public static MelonPreferences_Entry<float> MapEquipmentLootMultiplier { get; private set; } = null;

		public static MelonPreferences_Entry<bool> AutoScaleMapMiscellanyLootByPlayerCount { get; private set; } = null;

		public static MelonPreferences_Entry<float> MapMiscellanyLootMultiplier { get; private set; } = null;

		public static MelonPreferences_Entry<bool> AutoScaleDropConsumableLootByPlayerCount { get; private set; } = null;

		public static MelonPreferences_Entry<float> DropConsumableLootMultiplier { get; private set; } = null;

		public static MelonPreferences_Entry<bool> AutoScaleDropEquipmentLootByPlayerCount { get; private set; } = null;

		public static MelonPreferences_Entry<float> DropEquipmentLootMultiplier { get; private set; } = null;

		public static MelonPreferences_Entry<bool> AutoScaleDropMiscellanyLootByPlayerCount { get; private set; } = null;

		public static MelonPreferences_Entry<float> DropMiscellanyLootMultiplier { get; private set; } = null;

		public static MelonPreferences_Entry<bool> AutoScaleTriggerConsumableLootByPlayerCount { get; private set; } = null;

		public static MelonPreferences_Entry<float> TriggerConsumableLootMultiplier { get; private set; } = null;

		public static MelonPreferences_Entry<bool> AutoScaleTriggerEquipmentLootByPlayerCount { get; private set; } = null;

		public static MelonPreferences_Entry<float> TriggerEquipmentLootMultiplier { get; private set; } = null;

		public static MelonPreferences_Entry<bool> AutoScaleTriggerMiscellanyLootByPlayerCount { get; private set; } = null;

		public static MelonPreferences_Entry<float> TriggerMiscellanyLootMultiplier { get; private set; } = null;

		public static MelonPreferences_Entry<bool> EnableMoneyMultiplier { get; private set; } = null;

		public static MelonPreferences_Entry<bool> AutoScaleStartupMoneyByPlayerCount { get; private set; } = null;

		public static MelonPreferences_Entry<float> StartupMoneyMultiplier { get; private set; } = null;

		public static MelonPreferences_Entry<bool> AutoScaleRoundGoalMoneyByPlayerCount { get; private set; } = null;

		public static MelonPreferences_Entry<float> RoundGoalMoneyMultiplier { get; private set; } = null;

		public static MelonPreferences_Entry<bool> AutoScaleScrapSellValueByPlayerCount { get; private set; } = null;

		public static MelonPreferences_Entry<float> ScrapSellValueMultiplier { get; private set; } = null;

		public static MelonPreferences_Entry<bool> AutoScaleShopBuyPriceByPlayerCount { get; private set; } = null;

		public static MelonPreferences_Entry<float> ShopBuyPriceMultiplier { get; private set; } = null;

		public static MelonPreferences_Entry<bool> AutoScaleShopItemsByPlayerCount { get; private set; } = null;

		public static MelonPreferences_Entry<float> ShopItemsMultiplier { get; private set; } = null;

		public static MelonPreferences_Entry<int> ShopDiscountMinPercent { get; private set; } = null;

		public static MelonPreferences_Entry<int> ShopDiscountMaxPercent { get; private set; } = null;

		public static MelonPreferences_Entry<int> ShopDiscountChancePercent { get; private set; } = null;

		public static MelonPreferences_Entry<bool> AutoScaleReinforcePriceByPlayerCount { get; private set; } = null;

		public static MelonPreferences_Entry<float> ReinforcePriceMultiplier { get; private set; } = null;

		public static MelonPreferences_Entry<bool> EnableDungeonTime { get; private set; } = null;

		public static MelonPreferences_Entry<int> DungeonTimeBaselinePlayerCount { get; private set; } = null;

		public static MelonPreferences_Entry<float> ExtraShiftSecondsPerPlayerAboveBaseline { get; private set; } = null;

		public static MelonPreferences_Entry<bool> EnableSpectatorTransition { get; private set; } = null;

		public static MelonPreferences_Entry<float> DyingWaitTimeMultiplier { get; private set; } = null;

		public static MelonPreferences_Entry<float> DeadCameraDurationMultiplier { get; private set; } = null;

		public static MelonPreferences_Entry<bool> EnableDungeonRandomizer { get; private set; } = null;

		public static MelonPreferences_Entry<bool> RandomizeDungeonPick { get; private set; } = null;

		public static MelonPreferences_Entry<string> DungeonPickPoolMode { get; private set; } = null;

		public static MelonPreferences_Entry<string> DungeonAllowlist { get; private set; } = null;

		public static MelonPreferences_Entry<string> DungeonBlocklist { get; private set; } = null;

		public static MelonPreferences_Entry<bool> IgnoreDungeonExcludeList { get; private set; } = null;

		public static MelonPreferences_Entry<bool> RandomizeLayoutFlow { get; private set; } = null;

		public static MelonPreferences_Entry<bool> RandomizeMapVariant { get; private set; } = null;

		public static MelonPreferences_Entry<bool> RandomizeDungeonSeed { get; private set; } = null;

		public static MelonPreferences_Entry<bool> EnableWebDashboard { get; private set; } = null;

		public static MelonPreferences_Entry<string> WebDashboardListenAddress { get; private set; } = null;

		public static MelonPreferences_Entry<int> WebDashboardListenPort { get; private set; } = null;

		public static MelonPreferences_Entry<bool> EnableDebugLogging { get; private set; } = null;

		public static event Action? Changed;

		public static void Initialize(Instance logger)
		{
			FilePath = Path.Combine(MelonEnvironment.UserDataDirectory, "MimesisPlayerEnhancement.cfg");
			_mainCategory = CreateCategory("MimesisPlayerEnhancement", "Mimesis Player Enhancement");
			MainCategory = _mainCategory;
			_morePlayersCategory = CreateCategory("MimesisPlayerEnhancement_MorePlayers", "More Players");
			_moreVoicesCategory = CreateCategory("MimesisPlayerEnhancement_MoreVoices", "More Voices");
			_persistenceCategory = CreateCategory("MimesisPlayerEnhancement_Persistence", "Persistence");
			_statisticsCategory = CreateCategory("MimesisPlayerEnhancement_Statistics", "Statistics");
			_playerAnnouncementsCategory = CreateCategory("MimesisPlayerEnhancement_PlayerAnnouncements", "Player Announcements");
			_joinAnytimeCategory = CreateCategory("MimesisPlayerEnhancement_JoinAnytime", "Join Anytime");
			_spawnScalingCategory = CreateCategory("MimesisPlayerEnhancement_SpawnScaling", "Spawn Scaling");
			_lootMultiplicatorCategory = CreateCategory("MimesisPlayerEnhancement_LootMultiplicator", "Loot Multiplicator");
			_moneyMultiplierCategory = CreateCategory("MimesisPlayerEnhancement_MoneyMultiplier", "Money Multiplier");
			_dungeonTimeCategory = CreateCategory("MimesisPlayerEnhancement_DungeonTime", "Dungeon Time");
			_spectatorTransitionCategory = CreateCategory("MimesisPlayerEnhancement_SpectatorTransition", "Spectator Transition");
			_dungeonRandomizerCategory = CreateCategory("MimesisPlayerEnhancement_DungeonRandomizer", "Dungeon Randomizer");
			_webDashboardCategory = CreateCategory("MimesisPlayerEnhancement_WebDashboard", "Web Dashboard");
			ModToastDurationSeconds = _mainCategory.CreateEntry<float>("ModToastDurationSeconds", 5f, "Mod Toast Duration (seconds)", "How long [PlayerEnhancements] toasts stay visible before fading. Vanilla join/leave toasts are unchanged (~2 seconds). Each player controls this locally.", false, false, (ValueValidator)null, (string)null);
			EnableDebugLogging = _mainCategory.CreateEntry<bool>("EnableDebugLogging", false, "Enable Debug Logging", "Emit verbose diagnostic lines to the MelonLoader console.", false, false, (ValueValidator)null, (string)null);
			EnableMorePlayers = _morePlayersCategory.CreateEntry<bool>("EnableMorePlayers", true, "Enable More Players", "Raise the multiplayer player cap above 4.", false, false, (ValueValidator)null, (string)null);
			MaxPlayers = _morePlayersCategory.CreateEntry<int>("MaxPlayers", 32, "Max Players", "Maximum players in a session including the host (1 = solo, 2 = host + 1 client, etc.).", false, false, (ValueValidator)null, (string)null);
			EnableMoreVoices = _moreVoicesCategory.CreateEntry<bool>("EnableMoreVoices", true, "Enable More Voices", "Raise per-player voice recording limits.", false, false, (ValueValidator)null, (string)null);
			MaxIndoorVoiceEvents = _moreVoicesCategory.CreateEntry<int>("MaxIndoorVoiceEvents", 3000, "Max Indoor Voice Events", "Maximum stored voice events per player in indoor dungeon runs (default game limit is much lower).", false, false, (ValueValidator)null, (string)null);
			MaxDeathMatchVoiceEvents = _moreVoicesCategory.CreateEntry<int>("MaxDeathMatchVoiceEvents", 3000, "Max Deathmatch Voice Events", "Maximum stored voice events per player in deathmatch (default game limit is much lower).", false, false, (ValueValidator)null, (string)null);
			MaxOutdoorVoiceEvents = _moreVoicesCategory.CreateEntry<int>("MaxOutdoorVoiceEvents", 3000, "Max Outdoor Voice Events", "Maximum stored voice events per player outdoors (default game limit is much lower).", false, false, (ValueValidator)null, (string)null);
			EnablePersistence = _persistenceCategory.CreateEntry<bool>("EnablePersistence", true, "Enable Voice Persistence", "Save and restore mimic voice recordings across save/load.", false, false, (ValueValidator)null, (string)null);
			EnableStatistics = _statisticsCategory.CreateEntry<bool>("EnableStatistics", true, "Enable Player Statistics", "Track per-session and global player statistics per save slot.", false, false, (ValueValidator)null, (string)null);
			SessionReconnectGraceMinutes = _statisticsCategory.CreateEntry<int>("SessionReconnectGraceMinutes", 5, "Session Reconnect Grace (minutes)", "Reuse the previous session when a player reconnects within this many minutes.", false, false, (ValueValidator)null, (string)null);
			ShowStatisticsToasts = _statisticsCategory.CreateEntry<bool>("ShowStatisticsToasts", true, "Show Statistics Toasts", "Show mod stats toasts in plain English (session intro for you, global stats on join/leave). Does not replace the game's own connect messages.", false, false, (ValueValidator)null, (string)null);
			ShowPlayerAnnouncements = _playerAnnouncementsCategory.CreateEntry<bool>("ShowPlayerAnnouncements", true, "Show Player Announcements", "Show in-game toasts for dungeon run settings, boss spawns, and your per-map stats when you die. Does not replace the game's own messages.", false, false, (ValueValidator)null, (string)null);
			EnableJoinAnytime = _joinAnytimeCategory.CreateEntry<bool>("EnableJoinAnytime", true, "Enable Join Anytime", "Allow players to join a session after it has already started.", false, false, (ValueValidator)null, (string)null);
			EnableSpawnScaling = _spawnScalingCategory.CreateEntry<bool>("EnableSpawnScaling", true, "Enable Spawn Scaling", "Scale dungeon monster spawn budgets by type. Host only.", false, false, (ValueValidator)null, (string)null);
			AutoScaleMimicSpawnsByPlayerCount = _spawnScalingCategory.CreateEntry<bool>("AutoScaleMimicSpawnsByPlayerCount", true, "Auto Scale Mimic Spawns By Player Count", "When enabled, multiply mimic spawn budgets by player count / 4 for sessions with more than 4 players (stacks with MimicSpawnMultiplier).", false, false, (ValueValidator)null, (string)null);
			MimicSpawnMultiplier = _spawnScalingCategory.CreateEntry<float>("MimicSpawnMultiplier", 1f, "Mimic Spawn Multiplier", "Mimic spawn budget multiplier (1 = vanilla, 2 = double).", false, false, (ValueValidator)null, (string)null);
			AutoScaleBossSpawnsByPlayerCount = _spawnScalingCategory.CreateEntry<bool>("AutoScaleBossSpawnsByPlayerCount", true, "Auto Scale Boss Spawns By Player Count", "When enabled, multiply boss spawn budgets by player count / 4 for sessions with more than 4 players (stacks with BossSpawnMultiplier).", false, false, (ValueValidator)null, (string)null);
			BossSpawnMultiplier = _spawnScalingCategory.CreateEntry<float>("BossSpawnMultiplier", 1f, "Boss Spawn Multiplier", "Boss spawn budget multiplier (1 = vanilla, 2 = double).", false, false, (ValueValidator)null, (string)null);
			AutoScaleJakoSpawnsByPlayerCount = _spawnScalingCategory.CreateEntry<bool>("AutoScaleJakoSpawnsByPlayerCount", true, "Auto Scale Jako Spawns By Player Count", "When enabled, multiply jako spawn budgets by player count / 4 for sessions with more than 4 players (stacks with JakoSpawnMultiplier).", false, false, (ValueValidator)null, (string)null);
			JakoSpawnMultiplier = _spawnScalingCategory.CreateEntry<float>("JakoSpawnMultiplier", 1f, "Jako Spawn Multiplier", "Jako (normal monster) spawn budget multiplier (1 = vanilla, 2 = double).", false, false, (ValueValidator)null, (string)null);
			AutoScaleSpecialSpawnsByPlayerCount = _spawnScalingCategory.CreateEntry<bool>("AutoScaleSpecialSpawnsByPlayerCount", true, "Auto Scale Special Spawns By Player Count", "When enabled, multiply special spawn budgets by player count / 4 for sessions with more than 4 players (stacks with SpecialSpawnMultiplier).", false, false, (ValueValidator)null, (string)null);
			SpecialSpawnMultiplier = _spawnScalingCategory.CreateEntry<float>("SpecialSpawnMultiplier", 1f, "Special Spawn Multiplier", "Special monster spawn budget multiplier (1 = vanilla, 2 = double).", false, false, (ValueValidator)null, (string)null);
			AutoScaleTrapSpawnsByPlayerCount = _spawnScalingCategory.CreateEntry<bool>("AutoScaleTrapSpawnsByPlayerCount", true, "Auto Scale Trap Spawns By Player Count", "When enabled, multiply trap spawn counts by player count / 4 for sessions with more than 4 players (stacks with TrapSpawnMultiplier).", false, false, (ValueValidator)null, (string)null);
			TrapSpawnMultiplier = _spawnScalingCategory.CreateEntry<float>("TrapSpawnMultiplier", 1f, "Trap Spawn Multiplier", "Trap spawn multiplier (1 = vanilla, 2 = double). Map-placed traps use unused markers first, then respawn at the same marker when gone.", false, false, (ValueValidator)null, (string)null);
			FixedSpawnRespawnDelayMinSeconds = _spawnScalingCategory.CreateEntry<float>("FixedSpawnRespawnDelayMinSeconds", 5f, "Fixed Spawn Respawn Delay Min Seconds", "Minimum random delay before a map-placed monster or trap respawns at the same marker when no unused markers remain.", false, false, (ValueValidator)null, (string)null);
			FixedSpawnRespawnDelayMaxSeconds = _spawnScalingCategory.CreateEntry<float>("FixedSpawnRespawnDelayMaxSeconds", 30f, "Fixed Spawn Respawn Delay Max Seconds", "Maximum random delay before a map-placed monster or trap respawns at the same marker when no unused markers remain.", false, false, (ValueValidator)null, (string)null);
			FixedSpawnRespawnMinPlayerDistanceMeters = _spawnScalingCategory.CreateEntry<float>("FixedSpawnRespawnMinPlayerDistanceMeters", 10f, "Fixed Spawn Respawn Min Player Distance Meters", "After the respawn delay, wait until no players are within this distance (meters) before spawning at the marker. Set to 0 to respawn immediately.", false, false, (ValueValidator)null, (string)null);
			AutoScaleOtherSpawnsByPlayerCount = _spawnScalingCategory.CreateEntry<bool>("AutoScaleOtherSpawnsByPlayerCount", true, "Auto Scale Other Spawns By Player Count", "When enabled, multiply other spawn counts by player count / 4 for sessions with more than 4 players (stacks with OtherSpawnMultiplier).", false, false, (ValueValidator)null, (string)null);
			OtherSpawnMultiplier = _spawnScalingCategory.CreateEntry<float>("OtherSpawnMultiplier", 1f, "Other Spawn Multiplier", "Spawn multiplier for entities that are not mimics, bosses, jakos, specials, or traps.", false, false, (ValueValidator)null, (string)null);
			EnableLootMultiplicator = _lootMultiplicatorCategory.CreateEntry<bool>("EnableLootMultiplicator", true, "Enable Loot Multiplicator", "Scale how much loot appears in a run. Host only. See each Map/Drop/Trigger entry below for what it affects.", false, false, (ValueValidator)null, (string)null);
			AutoScaleMapConsumableLootByPlayerCount = _lootMultiplicatorCategory.CreateEntry<bool>("AutoScaleMapConsumableLootByPlayerCount", true, "Auto Scale Map Consumable Loot By Player Count", "Map loot = items placed on the dungeon map (spawn markers, shelves, floors). Consumables = ammo, healing, and other used-up items. When enabled, multiply by player count / 4 above 4 players (stacks with MapConsumableLootMultiplier).", false, false, (ValueValidator)null, (string)null);
			MapConsumableLootMultiplier = _lootMultiplicatorCategory.CreateEntry<float>("MapConsumableLootMultiplier", 1f, "Map Consumable Loot Multiplier", "Multiplier for consumables on map spawn points: stack size and respawn count at room load. 1 = vanilla, 2 = double. Fixed map loot (specific item at a marker) may also use unused loot markers and respawn at the same spot. Random loot pools only get stack/respawn scaling.", false, false, (ValueValidator)null, (string)null);
			AutoScaleMapEquipmentLootByPlayerCount = _lootMultiplicatorCategory.CreateEntry<bool>("AutoScaleMapEquipmentLootByPlayerCount", true, "Auto Scale Map Equipment Loot By Player Count", "Map loot = items placed on the dungeon map. Equipment = tools, weapons, and gear you equip. When enabled, multiply by player count / 4 above 4 players (stacks with MapEquipmentLootMultiplier).", false, false, (ValueValidator)null, (string)null);
			MapEquipmentLootMultiplier = _lootMultiplicatorCategory.CreateEntry<float>("MapEquipmentLootMultiplier", 1f, "Map Equipment Loot Multiplier", "Multiplier for equipment on map spawn points: stack size and respawn count at room load. 1 = vanilla, 2 = double. Fixed map loot (specific item at a marker) may also use unused loot markers and respawn at the same spot. Random loot pools only get stack/respawn scaling.", false, false, (ValueValidator)null, (string)null);
			AutoScaleMapMiscellanyLootByPlayerCount = _lootMultiplicatorCategory.CreateEntry<bool>("AutoScaleMapMiscellanyLootByPlayerCount", true, "Auto Scale Map Miscellany Loot By Player Count", "Map loot = items placed on the dungeon map. Miscellany = other pickup items (keys, misc objects). When enabled, multiply by player count / 4 above 4 players (stacks with MapMiscellanyLootMultiplier).", false, false, (ValueValidator)null, (string)null);
			MapMiscellanyLootMultiplier = _lootMultiplicatorCategory.CreateEntry<float>("MapMiscellanyLootMultiplier", 1f, "Map Miscellany Loot Multiplier", "Multiplier for miscellany on map spawn points: stack size and respawn count at room load. 1 = vanilla, 2 = double. Fixed map loot (specific item at a marker) may also use unused loot markers and respawn at the same spot. Random loot pools only get stack/respawn scaling. Random pools use the dominant item type in the pool to pick a multiplier.", false, false, (ValueValidator)null, (string)null);
			AutoScaleDropConsumableLootByPlayerCount = _lootMultiplicatorCategory.CreateEntry<bool>("AutoScaleDropConsumableLootByPlayerCount", true, "Auto Scale Drop Consumable Loot By Player Count", "Drop loot = items from enemy death tables when killed. Consumables = ammo, healing, and other used-up items. When enabled, multiply by player count / 4 above 4 players (stacks with DropConsumableLootMultiplier).", false, false, (ValueValidator)null, (string)null);
			DropConsumableLootMultiplier = _lootMultiplicatorCategory.CreateEntry<float>("DropConsumableLootMultiplier", 1f, "Drop Consumable Loot Multiplier", "Multiplier for consumables in enemy death drops: duplicates extra item IDs in the drop list and scales consumable stack count on spawn. 1 = vanilla, 2 = double.", false, false, (ValueValidator)null, (string)null);
			AutoScaleDropEquipmentLootByPlayerCount = _lootMultiplicatorCategory.CreateEntry<bool>("AutoScaleDropEquipmentLootByPlayerCount", true, "Auto Scale Drop Equipment Loot By Player Count", "Drop loot = items from enemy death tables when killed. Equipment = tools, weapons, and gear you equip. When enabled, multiply by player count / 4 above 4 players (stacks with DropEquipmentLootMultiplier).", false, false, (ValueValidator)null, (string)null);
			DropEquipmentLootMultiplier = _lootMultiplicatorCategory.CreateEntry<float>("DropEquipmentLootMultiplier", 1f, "Drop Equipment Loot Multiplier", "Multiplier for equipment in enemy death drops: duplicates extra item IDs in the drop list. Stack scaling on spawn is best-effort for non-consumables. 1 = vanilla, 2 = double.", false, false, (ValueValidator)null, (string)null);
			AutoScaleDropMiscellanyLootByPlayerCount = _lootMultiplicatorCategory.CreateEntry<bool>("AutoScaleDropMiscellanyLootByPlayerCount", true, "Auto Scale Drop Miscellany Loot By Player Count", "Drop loot = items from enemy death tables when killed. Miscellany = other pickup items. When enabled, multiply by player count / 4 above 4 players (stacks with DropMiscellanyLootMultiplier).", false, false, (ValueValidator)null, (string)null);
			DropMiscellanyLootMultiplier = _lootMultiplicatorCategory.CreateEntry<float>("DropMiscellanyLootMultiplier", 1f, "Drop Miscellany Loot Multiplier", "Multiplier for miscellany in enemy death drops: duplicates extra item IDs in the drop list. Stack scaling on spawn is best-effort for non-consumables. 1 = vanilla, 2 = double.", false, false, (ValueValidator)null, (string)null);
			AutoScaleTriggerConsumableLootByPlayerCount = _lootMultiplicatorCategory.CreateEntry<bool>("AutoScaleTriggerConsumableLootByPlayerCount", true, "Auto Scale Trigger Consumable Loot By Player Count", "Trigger loot = items spawned by map events/trigger volumes (EventAction spawns only). Consumables = ammo, healing, and other used-up items. When enabled, multiply by player count / 4 above 4 players (stacks with TriggerConsumableLootMultiplier).", false, false, (ValueValidator)null, (string)null);
			TriggerConsumableLootMultiplier = _lootMultiplicatorCategory.CreateEntry<float>("TriggerConsumableLootMultiplier", 1f, "Trigger Consumable Loot Multiplier", "Multiplier for consumables from map events/triggers: scales consumable stack count when the item spawns. 1 = vanilla, 2 = double.", false, false, (ValueValidator)null, (string)null);
			AutoScaleTriggerEquipmentLootByPlayerCount = _lootMultiplicatorCategory.CreateEntry<bool>("AutoScaleTriggerEquipmentLootByPlayerCount", true, "Auto Scale Trigger Equipment Loot By Player Count", "Trigger loot = items spawned by map events/trigger volumes (EventAction spawns only). Equipment = tools, weapons, and gear you equip. When enabled, multiply by player count / 4 above 4 players (stacks with TriggerEquipmentLootMultiplier).", false, false, (ValueValidator)null, (string)null);
			TriggerEquipmentLootMultiplier = _lootMultiplicatorCategory.CreateEntry<float>("TriggerEquipmentLootMultiplier", 1f, "Trigger Equipment Loot Multiplier", "Multiplier for equipment from map events/triggers: stack scaling on spawn is best-effort for non-consumables. 1 = vanilla, 2 = double.", false, false, (ValueValidator)null, (string)null);
			AutoScaleTriggerMiscellanyLootByPlayerCount = _lootMultiplicatorCategory.CreateEntry<bool>("AutoScaleTriggerMiscellanyLootByPlayerCount", true, "Auto Scale Trigger Miscellany Loot By Player Count", "Trigger loot = items spawned by map events/trigger volumes (EventAction spawns only). Miscellany = other pickup items. When enabled, multiply by player count / 4 above 4 players (stacks with TriggerMiscellanyLootMultiplier).", false, false, (ValueValidator)null, (string)null);
			TriggerMiscellanyLootMultiplier = _lootMultiplicatorCategory.CreateEntry<float>("TriggerMiscellanyLootMultiplier", 1f, "Trigger Miscellany Loot Multiplier", "Multiplier for miscellany from map events/triggers: stack scaling on spawn is best-effort for non-consumables. 1 = vanilla, 2 = double.", false, false, (ValueValidator)null, (string)null);
			EnableMoneyMultiplier = _moneyMultiplierCategory.CreateEntry<bool>("EnableMoneyMultiplier", true, "Enable Money Multiplier", "Scale startup money, round goal quota, scrap/sell values, shop buy prices, shop item count, and reinforce costs. Host only.", false, false, (ValueValidator)null, (string)null);
			AutoScaleStartupMoneyByPlayerCount = _moneyMultiplierCategory.CreateEntry<bool>("AutoScaleStartupMoneyByPlayerCount", true, "Auto Scale Startup Money By Player Count", "When enabled, multiply startup money by player count / 4 for sessions with more than 4 players (stacks with StartupMoneyMultiplier).", false, false, (ValueValidator)null, (string)null);
			StartupMoneyMultiplier = _moneyMultiplierCategory.CreateEntry<float>("StartupMoneyMultiplier", 1f, "Startup Money Multiplier", "Starting maintenance-room currency on a new game or session reset (1 = vanilla, 2 = double).", false, false, (ValueValidator)null, (string)null);
			AutoScaleRoundGoalMoneyByPlayerCount = _moneyMultiplierCategory.CreateEntry<bool>("AutoScaleRoundGoalMoneyByPlayerCount", true, "Auto Scale Round Goal Money By Player Count", "When enabled, multiply the stage target currency (quota) by player count / 4 for sessions with more than 4 players (stacks with RoundGoalMoneyMultiplier).", false, false, (ValueValidator)null, (string)null);
			RoundGoalMoneyMultiplier = _moneyMultiplierCategory.CreateEntry<float>("RoundGoalMoneyMultiplier", 1f, "Round Goal Money Multiplier", "Target currency required to finish a stage (1 = vanilla, 2 = double).", false, false, (ValueValidator)null, (string)null);
			AutoScaleScrapSellValueByPlayerCount = _moneyMultiplierCategory.CreateEntry<bool>("AutoScaleScrapSellValueByPlayerCount", true, "Auto Scale Scrap Sell Value By Player Count", "When enabled, multiply item scrap/sell values by player count / 4 for sessions with more than 4 players (stacks with ScrapSellValueMultiplier).", false, false, (ValueValidator)null, (string)null);
			ScrapSellValueMultiplier = _moneyMultiplierCategory.CreateEntry<float>("ScrapSellValueMultiplier", 1f, "Scrap Sell Value Multiplier", "Currency earned when scrapping items and item value counted toward the tram quota (1 = vanilla, 2 = double).", false, false, (ValueValidator)null, (string)null);
			AutoScaleShopBuyPriceByPlayerCount = _moneyMultiplierCategory.CreateEntry<bool>("AutoScaleShopBuyPriceByPlayerCount", true, "Auto Scale Shop Buy Price By Player Count", "When enabled, multiply maintenance shop buy prices by player count / 4 for sessions with more than 4 players (stacks with ShopBuyPriceMultiplier).", false, false, (ValueValidator)null, (string)null);
			ShopBuyPriceMultiplier = _moneyMultiplierCategory.CreateEntry<float>("ShopBuyPriceMultiplier", 1f, "Shop Buy Price Multiplier", "Maintenance shop and vending-machine kiosk purchase cost multiplier (1 = vanilla, 2 = double). Applied when shop items are initialized each maintenance round.", false, false, (ValueValidator)null, (string)null);
			AutoScaleShopItemsByPlayerCount = _moneyMultiplierCategory.CreateEntry<bool>("AutoScaleShopItemsByPlayerCount", true, "Auto Scale Shop Items By Player Count", "When enabled, multiply maintenance shop item count by player count / 4 for sessions with more than 4 players (stacks with ShopItemsMultiplier).", false, false, (ValueValidator)null, (string)null);
			ShopItemsMultiplier = _moneyMultiplierCategory.CreateEntry<float>("ShopItemsMultiplier", 1f, "Shop Items Multiplier", "Number of unique items offered in the maintenance shop (1 = vanilla, 2 = double). Extra items are rolled from vending-machine shop groups on the map.", false, false, (ValueValidator)null, (string)null);
			ShopDiscountMinPercent = _moneyMultiplierCategory.CreateEntry<int>("ShopDiscountMinPercent", 0, "Shop Discount Min Percent", "Minimum shop discount percentage when a discount is rolled (0-100). Only used when ShopDiscountChancePercent is above 0.", false, false, (ValueValidator)null, (string)null);
			ShopDiscountMaxPercent = _moneyMultiplierCategory.CreateEntry<int>("ShopDiscountMaxPercent", 100, "Shop Discount Max Percent", "Maximum shop discount percentage when a discount is rolled (0-100). Must be >= ShopDiscountMinPercent.", false, false, (ValueValidator)null, (string)null);
			ShopDiscountChancePercent = _moneyMultiplierCategory.CreateEntry<int>("ShopDiscountChancePercent", 0, "Shop Discount Chance Percent", "Chance per shop item to receive a discount between min and max percent (0 = vanilla shop discounts, 100 = every item discounted).", false, false, (ValueValidator)null, (string)null);
			AutoScaleReinforcePriceByPlayerCount = _moneyMultiplierCategory.CreateEntry<bool>("AutoScaleReinforcePriceByPlayerCount", true, "Auto Scale Reinforce Price By Player Count", "When enabled, multiply item reinforcement costs by player count / 4 for sessions with more than 4 players (stacks with ReinforcePriceMultiplier).", false, false, (ValueValidator)null, (string)null);
			ReinforcePriceMultiplier = _moneyMultiplierCategory.CreateEntry<float>("ReinforcePriceMultiplier", 1f, "Reinforce Price Multiplier", "Maintenance item reinforcement cost multiplier (1 = vanilla, 2 = double).", false, false, (ValueValidator)null, (string)null);
			EnableDungeonTime = _dungeonTimeCategory.CreateEntry<bool>("EnableDungeonTime", true, "Enable Dungeon Time", "Extend dungeon shift length on the host when player count exceeds the baseline.", false, false, (ValueValidator)null, (string)null);
			DungeonTimeBaselinePlayerCount = _dungeonTimeCategory.CreateEntry<int>("DungeonTimeBaselinePlayerCount", 4, "Dungeon Time Baseline Player Count", "No extra shift time at or below this player count (vanilla is 4). Minimum is 1.", false, false, (ValueValidator)null, (string)null);
			ExtraShiftSecondsPerPlayerAboveBaseline = _dungeonTimeCategory.CreateEntry<float>("ExtraShiftSecondsPerPlayerAboveBaseline", 10f, "Extra Shift Seconds Per Player Above Baseline", "Real seconds added to the shift deadline for each player above the baseline. Minimum is 0.", false, false, (ValueValidator)null, (string)null);
			EnableSpectatorTransition = _spectatorTransitionCategory.CreateEntry<bool>("EnableSpectatorTransition", true, "Enable Spectator Transition", "Shorten downed time and dead-camera duration before entering spectator mode.", false, false, (ValueValidator)null, (string)null);
			DyingWaitTimeMultiplier = _spectatorTransitionCategory.CreateEntry<float>("DyingWaitTimeMultiplier", 1f, "Dying Wait Time Multiplier", "Scales server down/dying time before spectator (1 = vanilla, 0 = instant). Also shortens the teammate revive window. Host only.", false, false, (ValueValidator)null, (string)null);
			DeadCameraDurationMultiplier = _spectatorTransitionCategory.CreateEntry<float>("DeadCameraDurationMultiplier", 1f, "Dead Camera Duration Multiplier", "Scales local dead-camera transition time before spectator (1 = vanilla, 0 = instant). Applies on each machine with the mod loaded.", false, false, (ValueValidator)null, (string)null);
			EnableDungeonRandomizer = _dungeonRandomizerCategory.CreateEntry<bool>("EnableDungeonRandomizer", false, "Enable Dungeon Randomizer", "Randomize dungeon selection on the host: tram dungeon pick, layout flow, map variant, and procedural seed. Host only.", false, false, (ValueValidator)null, (string)null);
			RandomizeDungeonPick = _dungeonRandomizerCategory.CreateEntry<bool>("RandomizeDungeonPick", true, "Randomize Dungeon Pick", "Override which dungeon master ID is rolled on the tram.", false, false, (ValueValidator)null, (string)null);
			DungeonPickPoolMode = _dungeonRandomizerCategory.CreateEntry<string>("DungeonPickPoolMode", "WidenVanilla", "Dungeon Pick Pool Mode", "WidenVanilla = keep cycle weights but allow repeats sooner; AllActiveUniform = pick uniformly from all active dungeons (ignores cycle table).", false, false, (ValueValidator)null, (string)null);
			DungeonAllowlist = _dungeonRandomizerCategory.CreateEntry<string>("DungeonAllowlist", "", "Dungeon Allowlist", "Comma-separated dungeon master IDs. When non-empty, only these IDs are eligible.", false, false, (ValueValidator)null, (string)null);
			DungeonBlocklist = _dungeonRandomizerCategory.CreateEntry<string>("DungeonBlocklist", "", "Dungeon Blocklist", "Comma-separated dungeon master IDs to exclude from the pool.", false, false, (ValueValidator)null, (string)null);
			IgnoreDungeonExcludeList = _dungeonRandomizerCategory.CreateEntry<bool>("IgnoreDungeonExcludeList", true, "Ignore Dungeon Exclude List", "When using WidenVanilla, do not exclude recently played dungeons from the tram roll.", false, false, (ValueValidator)null, (string)null);
			RandomizeLayoutFlow = _dungeonRandomizerCategory.CreateEntry<bool>("RandomizeLayoutFlow", true, "Randomize Layout Flow", "Pick DunGen layout flows uniformly from each dungeon's candidates instead of using weighted vanilla rolls.", false, false, (ValueValidator)null, (string)null);
			RandomizeMapVariant = _dungeonRandomizerCategory.CreateEntry<bool>("RandomizeMapVariant", true, "Randomize Map Variant", "Pick map variants uniformly from each dungeon's MapIDs instead of vanilla selection.", false, false, (ValueValidator)null, (string)null);
			RandomizeDungeonSeed = _dungeonRandomizerCategory.CreateEntry<bool>("RandomizeDungeonSeed", true, "Randomize Dungeon Seed", "Replace the procedural dungeon seed with a new random value when a dungeon is chosen.", false, false, (ValueValidator)null, (string)null);
			EnableWebDashboard = _webDashboardCategory.CreateEntry<bool>("EnableWebDashboard", false, "Enable Web Dashboard", "Serve a local web UI for connected players and host moderation. Default bind is loopback only.", false, false, (ValueValidator)null, (string)null);
			WebDashboardListenAddress = _webDashboardCategory.CreateEntry<string>("WebDashboardListenAddress", "127.0.0.1", "Listen Address", "HTTP bind address. Use 127.0.0.1 for local-only access.", false, false, (ValueValidator)null, (string)null);
			WebDashboardListenPort = _webDashboardCategory.CreateEntry<int>("WebDashboardListenPort", 8001, "Listen Port", "TCP port for the local web dashboard.", false, false, (ValueValidator)null, (string)null);
			if (MaxPlayers.Value < 1)
			{
				logger.Warning("MaxPlayers must be at least 1; resetting to 1.");
				MaxPlayers.Value = 1;
			}
			SanitizeShopDiscountPercents(logger);
			OnDungeonPickPoolModeChanged(logger, DungeonPickPoolMode.Value);
			((MelonEventBase<LemonAction<int, int>>)(object)MaxPlayers.OnEntryValueChanged).Subscribe((LemonAction<int, int>)delegate(int _, int value)
			{
				if (value < 1)
				{
					logger.Warning("MaxPlayers must be at least 1; resetting to 1.");
					MaxPlayers.Value = 1;
				}
				else
				{
					NotifyChanged();
				}
			}, 0, false);
			((MelonEventBase<LemonAction<int, int>>)(object)MaxIndoorVoiceEvents.OnEntryValueChanged).Subscribe((LemonAction<int, int>)delegate(int _, int value)
			{
				if (value < 1)
				{
					logger.Warning("MaxIndoorVoiceEvents must be at least 1; resetting to 1.");
					MaxIndoorVoiceEvents.Value = 1;
				}
				else
				{
					NotifyChanged();
				}
			}, 0, false);
			((MelonEventBase<LemonAction<int, int>>)(object)MaxDeathMatchVoiceEvents.OnEntryValueChanged).Subscribe((LemonAction<int, int>)delegate(int _, int value)
			{
				if (value < 1)
				{
					logger.Warning("MaxDeathMatchVoiceEvents must be at least 1; resetting to 1.");
					MaxDeathMatchVoiceEvents.Value = 1;
				}
				else
				{
					NotifyChanged();
				}
			}, 0, false);
			((MelonEventBase<LemonAction<int, int>>)(object)MaxOutdoorVoiceEvents.OnEntryValueChanged).Subscribe((LemonAction<int, int>)delegate(int _, int value)
			{
				if (value < 1)
				{
					logger.Warning("MaxOutdoorVoiceEvents must be at least 1; resetting to 1.");
					MaxOutdoorVoiceEvents.Value = 1;
				}
				else
				{
					NotifyChanged();
				}
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)EnableMorePlayers.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)EnableMoreVoices.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)EnablePersistence.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<int, int>>)(object)SessionReconnectGraceMinutes.OnEntryValueChanged).Subscribe((LemonAction<int, int>)delegate(int _, int value)
			{
				if (value < 1)
				{
					logger.Warning("SessionReconnectGraceMinutes must be at least 1; resetting to 1.");
					SessionReconnectGraceMinutes.Value = 1;
				}
				else
				{
					NotifyChanged();
				}
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)EnableStatistics.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)ShowStatisticsToasts.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)ShowPlayerAnnouncements.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)ModToastDurationSeconds.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				if (value < 1f)
				{
					logger.Warning("ModToastDurationSeconds must be at least 1; resetting to 1.");
					ModToastDurationSeconds.Value = 1f;
				}
				else
				{
					NotifyChanged();
				}
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)EnableJoinAnytime.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)EnableSpawnScaling.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)AutoScaleMimicSpawnsByPlayerCount.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)AutoScaleBossSpawnsByPlayerCount.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)AutoScaleJakoSpawnsByPlayerCount.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)AutoScaleSpecialSpawnsByPlayerCount.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)AutoScaleTrapSpawnsByPlayerCount.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)FixedSpawnRespawnDelayMinSeconds.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnFixedSpawnRespawnDelayChanged(logger, value, FixedSpawnRespawnDelayMinSeconds);
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)FixedSpawnRespawnDelayMaxSeconds.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnFixedSpawnRespawnDelayChanged(logger, value, FixedSpawnRespawnDelayMaxSeconds);
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)FixedSpawnRespawnMinPlayerDistanceMeters.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnFixedSpawnRespawnMinPlayerDistanceChanged(logger, value);
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)AutoScaleOtherSpawnsByPlayerCount.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)EnableLootMultiplicator.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)AutoScaleMapConsumableLootByPlayerCount.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)AutoScaleMapEquipmentLootByPlayerCount.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)AutoScaleMapMiscellanyLootByPlayerCount.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)AutoScaleDropConsumableLootByPlayerCount.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)AutoScaleDropEquipmentLootByPlayerCount.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)AutoScaleDropMiscellanyLootByPlayerCount.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)AutoScaleTriggerConsumableLootByPlayerCount.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)AutoScaleTriggerEquipmentLootByPlayerCount.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)AutoScaleTriggerMiscellanyLootByPlayerCount.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)MimicSpawnMultiplier.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnSpawnMultiplierChanged(logger, value, MimicSpawnMultiplier);
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)BossSpawnMultiplier.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnSpawnMultiplierChanged(logger, value, BossSpawnMultiplier);
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)JakoSpawnMultiplier.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnSpawnMultiplierChanged(logger, value, JakoSpawnMultiplier);
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)SpecialSpawnMultiplier.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnSpawnMultiplierChanged(logger, value, SpecialSpawnMultiplier);
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)TrapSpawnMultiplier.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnSpawnMultiplierChanged(logger, value, TrapSpawnMultiplier);
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)OtherSpawnMultiplier.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnSpawnMultiplierChanged(logger, value, OtherSpawnMultiplier);
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)MapConsumableLootMultiplier.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnSpawnMultiplierChanged(logger, value, MapConsumableLootMultiplier);
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)MapEquipmentLootMultiplier.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnSpawnMultiplierChanged(logger, value, MapEquipmentLootMultiplier);
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)MapMiscellanyLootMultiplier.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnSpawnMultiplierChanged(logger, value, MapMiscellanyLootMultiplier);
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)DropConsumableLootMultiplier.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnSpawnMultiplierChanged(logger, value, DropConsumableLootMultiplier);
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)DropEquipmentLootMultiplier.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnSpawnMultiplierChanged(logger, value, DropEquipmentLootMultiplier);
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)DropMiscellanyLootMultiplier.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnSpawnMultiplierChanged(logger, value, DropMiscellanyLootMultiplier);
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)TriggerConsumableLootMultiplier.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnSpawnMultiplierChanged(logger, value, TriggerConsumableLootMultiplier);
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)TriggerEquipmentLootMultiplier.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnSpawnMultiplierChanged(logger, value, TriggerEquipmentLootMultiplier);
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)TriggerMiscellanyLootMultiplier.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnSpawnMultiplierChanged(logger, value, TriggerMiscellanyLootMultiplier);
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)EnableMoneyMultiplier.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)AutoScaleStartupMoneyByPlayerCount.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)AutoScaleRoundGoalMoneyByPlayerCount.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)AutoScaleScrapSellValueByPlayerCount.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)AutoScaleShopBuyPriceByPlayerCount.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)AutoScaleShopItemsByPlayerCount.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)AutoScaleReinforcePriceByPlayerCount.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)StartupMoneyMultiplier.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnSpawnMultiplierChanged(logger, value, StartupMoneyMultiplier);
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)RoundGoalMoneyMultiplier.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnSpawnMultiplierChanged(logger, value, RoundGoalMoneyMultiplier);
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)ScrapSellValueMultiplier.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnSpawnMultiplierChanged(logger, value, ScrapSellValueMultiplier);
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)ShopBuyPriceMultiplier.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnSpawnMultiplierChanged(logger, value, ShopBuyPriceMultiplier);
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)ShopItemsMultiplier.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnSpawnMultiplierChanged(logger, value, ShopItemsMultiplier);
			}, 0, false);
			((MelonEventBase<LemonAction<int, int>>)(object)ShopDiscountMinPercent.OnEntryValueChanged).Subscribe((LemonAction<int, int>)delegate(int _, int value)
			{
				OnShopDiscountPercentChanged(logger, value, ShopDiscountMinPercent);
			}, 0, false);
			((MelonEventBase<LemonAction<int, int>>)(object)ShopDiscountMaxPercent.OnEntryValueChanged).Subscribe((LemonAction<int, int>)delegate(int _, int value)
			{
				OnShopDiscountPercentChanged(logger, value, ShopDiscountMaxPercent);
			}, 0, false);
			((MelonEventBase<LemonAction<int, int>>)(object)ShopDiscountChancePercent.OnEntryValueChanged).Subscribe((LemonAction<int, int>)delegate(int _, int value)
			{
				OnShopDiscountPercentChanged(logger, value, ShopDiscountChancePercent);
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)ReinforcePriceMultiplier.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnSpawnMultiplierChanged(logger, value, ReinforcePriceMultiplier);
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)EnableDungeonTime.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<int, int>>)(object)DungeonTimeBaselinePlayerCount.OnEntryValueChanged).Subscribe((LemonAction<int, int>)delegate(int _, int value)
			{
				if (value < 1)
				{
					logger.Warning("DungeonTimeBaselinePlayerCount must be at least 1; resetting to 1.");
					DungeonTimeBaselinePlayerCount.Value = 1;
				}
				else
				{
					NotifyChanged();
				}
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)ExtraShiftSecondsPerPlayerAboveBaseline.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnExtraShiftSecondsPerPlayerChanged(logger, value);
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)EnableSpectatorTransition.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)DyingWaitTimeMultiplier.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnSpawnMultiplierChanged(logger, value, DyingWaitTimeMultiplier);
			}, 0, false);
			((MelonEventBase<LemonAction<float, float>>)(object)DeadCameraDurationMultiplier.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value)
			{
				OnSpawnMultiplierChanged(logger, value, DeadCameraDurationMultiplier);
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)EnableDungeonRandomizer.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)RandomizeDungeonPick.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<string, string>>)(object)DungeonPickPoolMode.OnEntryValueChanged).Subscribe((LemonAction<string, string>)delegate(string _, string value)
			{
				OnDungeonPickPoolModeChanged(logger, value);
			}, 0, false);
			((MelonEventBase<LemonAction<string, string>>)(object)DungeonAllowlist.OnEntryValueChanged).Subscribe((LemonAction<string, string>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<string, string>>)(object)DungeonBlocklist.OnEntryValueChanged).Subscribe((LemonAction<string, string>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)IgnoreDungeonExcludeList.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)RandomizeLayoutFlow.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)RandomizeMapVariant.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)RandomizeDungeonSeed.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)EnableWebDashboard.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<string, string>>)(object)WebDashboardListenAddress.OnEntryValueChanged).Subscribe((LemonAction<string, string>)delegate
			{
				NotifyChanged();
			}, 0, false);
			((MelonEventBase<LemonAction<int, int>>)(object)WebDashboardListenPort.OnEntryValueChanged).Subscribe((LemonAction<int, int>)delegate(int _, int value)
			{
				if ((value < 1 || value > 65535) ? true : false)
				{
					logger.Warning("WebDashboardListenPort must be between 1 and 65535; resetting to 8001.");
					WebDashboardListenPort.Value = 8001;
				}
				else
				{
					NotifyChanged();
				}
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)EnableDebugLogging.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				NotifyChanged();
			}, 0, false);
			RegisterFloatEntries();
			ModConfigFloatHelper.SanitizeAll(FloatEntries);
			NormalizeSavedFloats();
			ModConfigRegistry.Rebuild();
			IsInitialized = true;
		}

		public static void SaveToFile()
		{
			ModConfigRegistry.SaveToFile();
		}

		public static bool TrySetEntryValue(string sectionId, string key, string value, out string? error)
		{
			return ModConfigRegistry.TrySetEntryValue(sectionId, key, value, out error);
		}

		internal static void NotifyFileReloaded()
		{
			NotifyChanged();
		}

		public static void NormalizeSavedFloats()
		{
			ModConfigFloatHelper.NormalizeSavedFloats(FilePath, FloatEntries);
		}

		internal static void SanitizeFloatEntries()
		{
			ModConfigFloatHelper.SanitizeAll(FloatEntries);
		}

		private static void RegisterFloatEntries()
		{
			FloatEntries.AddRange(new <>z__ReadOnlyArray<MelonPreferences_Entry<float>>(new MelonPreferences_Entry<float>[27]
			{
				MimicSpawnMultiplier, BossSpawnMultiplier, JakoSpawnMultiplier, SpecialSpawnMultiplier, TrapSpawnMultiplier, FixedSpawnRespawnDelayMinSeconds, FixedSpawnRespawnDelayMaxSeconds, FixedSpawnRespawnMinPlayerDistanceMeters, OtherSpawnMultiplier, MapConsumableLootMultiplier,
				MapEquipmentLootMultiplier, MapMiscellanyLootMultiplier, DropConsumableLootMultiplier, DropEquipmentLootMultiplier, DropMiscellanyLootMultiplier, TriggerConsumableLootMultiplier, TriggerEquipmentLootMultiplier, TriggerMiscellanyLootMultiplier, StartupMoneyMultiplier, RoundGoalMoneyMultiplier,
				ScrapSellValueMultiplier, ShopBuyPriceMultiplier, ShopItemsMultiplier, ReinforcePriceMultiplier, ExtraShiftSecondsPerPlayerAboveBaseline, DyingWaitTimeMultiplier, DeadCameraDurationMultiplier
			}));
		}

		private static void OnExtraShiftSecondsPerPlayerChanged(Instance logger, float value)
		{
			if (value < 0f)
			{
				logger.Warning("ExtraShiftSecondsPerPlayerAboveBaseline must be >= 0; resetting to 0.");
				ExtraShiftSecondsPerPlayerAboveBaseline.Value = 0f;
			}
			else
			{
				ModConfigFloatHelper.SanitizeEntry(ExtraShiftSecondsPerPlayerAboveBaseline);
				NotifyChanged();
			}
		}

		private static void OnFixedSpawnRespawnDelayChanged(Instance logger, float value, MelonPreferences_Entry<float> entry)
		{
			if (value < 0f)
			{
				logger.Warning(((MelonPreferences_Entry)entry).Identifier + " must be >= 0; resetting to 0.");
				entry.Value = 0f;
				return;
			}
			float value2 = FixedSpawnRespawnDelayMinSeconds.Value;
			if (FixedSpawnRespawnDelayMaxSeconds.Value < value2)
			{
				logger.Warning("FixedSpawnRespawnDelayMaxSeconds must be >= FixedSpawnRespawnDelayMinSeconds; syncing max to min.");
				FixedSpawnRespawnDelayMaxSeconds.Value = value2;
			}
			ModConfigFloatHelper.SanitizeEntry(entry);
			NotifyChanged();
		}

		private static void OnFixedSpawnRespawnMinPlayerDistanceChanged(Instance logger, float value)
		{
			if (value < 0f)
			{
				logger.Warning("FixedSpawnRespawnMinPlayerDistanceMeters must be >= 0; resetting to 0.");
				FixedSpawnRespawnMinPlayerDistanceMeters.Value = 0f;
			}
			else
			{
				ModConfigFloatHelper.SanitizeEntry(FixedSpawnRespawnMinPlayerDistanceMeters);
				NotifyChanged();
			}
		}

		private static void OnSpawnMultiplierChanged(Instance logger, float value, MelonPreferences_Entry<float> entry)
		{
			if (value < 0f)
			{
				logger.Warning(((MelonPreferences_Entry)entry).Identifier + " must be >= 0; resetting to 0.");
				entry.Value = 0f;
			}
			else
			{
				ModConfigFloatHelper.SanitizeEntry(entry);
				NotifyChanged();
			}
		}

		private static void SanitizeShopDiscountPercents(Instance logger)
		{
			OnShopDiscountPercentChanged(logger, ShopDiscountMinPercent.Value, ShopDiscountMinPercent);
			OnShopDiscountPercentChanged(logger, ShopDiscountMaxPercent.Value, ShopDiscountMaxPercent);
			OnShopDiscountPercentChanged(logger, ShopDiscountChancePercent.Value, ShopDiscountChancePercent);
		}

		private static void OnShopDiscountPercentChanged(Instance logger, int value, MelonPreferences_Entry<int> entry)
		{
			if (value < 0)
			{
				logger.Warning(((MelonPreferences_Entry)entry).Identifier + " must be >= 0; resetting to 0.");
				entry.Value = 0;
				return;
			}
			if (value > 100)
			{
				logger.Warning(((MelonPreferences_Entry)entry).Identifier + " must be <= 100; resetting to 100.");
				entry.Value = 100;
				return;
			}
			if (ShopDiscountMaxPercent.Value < ShopDiscountMinPercent.Value)
			{
				logger.Warning("ShopDiscountMaxPercent must be >= ShopDiscountMinPercent; syncing max to min.");
				ShopDiscountMaxPercent.Value = ShopDiscountMinPercent.Value;
			}
			NotifyChanged();
		}

		private static void OnDungeonPickPoolModeChanged(Instance logger, string value)
		{
			if (!string.Equals(value, "WidenVanilla", StringComparison.OrdinalIgnoreCase) && !string.Equals(value, "AllActiveUniform", StringComparison.OrdinalIgnoreCase))
			{
				logger.Warning("DungeonPickPoolMode must be WidenVanilla or AllActiveUniform; resetting to WidenVanilla.");
				DungeonPickPoolMode.Value = "WidenVanilla";
			}
			else
			{
				NotifyChanged();
			}
		}

		private static MelonPreferences_Category CreateCategory(string id, string displayName)
		{
			MelonPreferences_Category obj = MelonPreferences.CreateCategory(id, displayName);
			obj.SetFilePath(FilePath);
			return obj;
		}

		private static void NotifyChanged()
		{
			ModConfigRegistry.NotifyRuntimeChange();
			ModConfig.Changed?.Invoke();
		}
	}
	internal static class ModConfigFloatHelper
	{
		internal const int DecimalPlaces = 2;

		private static bool _normalizingFile;

		internal static float Round(float value)
		{
			return (float)Math.Round(value, 2, MidpointRounding.AwayFromZero);
		}

		internal static string Format(float value)
		{
			return Round(value).ToString("0.0#", CultureInfo.InvariantCulture);
		}

		internal static void SanitizeEntry(MelonPreferences_Entry<float> entry)
		{
			float num = Round(entry.Value);
			if (!entry.Value.Equals(num))
			{
				entry.Value = num;
			}
		}

		internal static void SanitizeAll(IReadOnlyList<MelonPreferences_Entry<float>> entries)
		{
			for (int i = 0; i < entries.Count; i++)
			{
				SanitizeEntry(entries[i]);
			}
		}

		internal static void NormalizeSavedFloats(string filePath, IReadOnlyList<MelonPreferences_Entry<float>> entries)
		{
			if (_normalizingFile || !File.Exists(filePath))
			{
				return;
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			for (int i = 0; i < entries.Count; i++)
			{
				hashSet.Add(((MelonPreferences_Entry)entries[i]).Identifier);
			}
			string[] array = File.ReadAllLines(filePath);
			bool flag = false;
			for (int j = 0; j < array.Length; j++)
			{
				if (TryNormalizeLine(array[j], hashSet, out string normalized) && !(normalized == array[j]))
				{
					array[j] = normalized;
					flag = true;
				}
			}
			if (!flag)
			{
				return;
			}
			_normalizingFile = true;
			try
			{
				File.WriteAllLines(filePath, array);
			}
			finally
			{
				_normalizingFile = false;
			}
		}

		private static bool TryNormalizeLine(string line, HashSet<string> floatKeys, out string normalized)
		{
			normalized = line;
			int num = line.IndexOf('=');
			if (num < 0)
			{
				return false;
			}
			string text = line.Substring(0, num).Trim();
			if (!floatKeys.Contains(text))
			{
				return false;
			}
			string text2 = line;
			int num2 = num + 1;
			string text3 = text2.Substring(num2, text2.Length - num2);
			string text4 = text3;
			string text5 = "";
			int num3 = text3.IndexOf('#');
			if (num3 >= 0)
			{
				text4 = text3.Substring(0, num3);
				text2 = text3;
				num2 = num3;
				text5 = text2.Substring(num2, text2.Length - num2);
			}
			text4 = text4.Trim();
			if (!float.TryParse(text4, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				return false;
			}
			string text6 = Format(result);
			if (text4 == text6)
			{
				return false;
			}
			normalized = text + " = " + text6 + text5;
			return true;
		}
	}
	internal static class ModConfigRegistry
	{
		private static readonly Dictionary<string, Dictionary<string, MelonPreferences_Entry>> EntriesBySection = new Dictionary<string, Dictionary<string, MelonPreferences_Entry>>(StringComparer.OrdinalIgnoreCase);

		internal static int Version { get; private set; }

		internal static void Rebuild()
		{
			EntriesBySection.Clear();
			PropertyInfo[] properties = typeof(ModConfig).GetProperties(BindingFlags.Static | BindingFlags.Public);
			foreach (PropertyInfo propertyInfo in properties)
			{
				Type propertyType = propertyInfo.PropertyType;
				if (propertyType.IsGenericType && !(propertyType.GetGenericTypeDefinition() != typeof(MelonPreferences_Entry<>)))
				{
					object? value = propertyInfo.GetValue(null);
					MelonPreferences_Entry val = (MelonPreferences_Entry)((value is MelonPreferences_Entry) ? value : null);
					if (val != null)
					{
						Register(val);
					}
				}
			}
		}

		internal static bool TryGetEntry(string sectionId, string key, out MelonPreferences_Entry? entry)
		{
			entry = null;
			if (!string.IsNullOrWhiteSpace(sectionId) && !string.IsNullOrWhiteSpace(key) && EntriesBySection.TryGetValue(sectionId, out Dictionary<string, MelonPreferences_Entry> value))
			{
				return value.TryGetValue(key, out entry);
			}
			return false;
		}

		internal static bool TrySetEntryValue(string sectionId, string key, string rawValue, out string? error)
		{
			error = null;
			if (!ModConfig.IsInitialized)
			{
				error = "Configuration is not initialized.";
				return false;
			}
			if (!TryGetEntry(sectionId, key, out MelonPreferences_Entry entry) || entry == null)
			{
				error = "Unknown setting.";
				return false;
			}
			if (!TryParseValue(entry.GetReflectedType() ?? throw new InvalidOperationException("Setting " + sectionId + "/" + key + " has no value type."), rawValue, out object value, out error))
			{
				return false;
			}
			try
			{
				if (!TrySetBoxedValue(entry, value))
				{
					error = "Failed to apply setting value.";
					return false;
				}
			}
			catch (Exception ex)
			{
				error = ex.Message;
				return false;
			}
			return true;
		}

		internal static void SaveToFile()
		{
			if (ModConfig.IsInitialized)
			{
				ModConfig.MainCategory.SaveToFile(false);
			}
		}

		internal static void NotifyRuntimeChange()
		{
			Version++;
		}

		private static bool TrySetBoxedValue(MelonPreferences_Entry entry, object? parsed)
		{
			PropertyInfo property = ((object)entry).GetType().GetProperty("Value");
			if (property?.GetSetMethod() != null)
			{
				property.SetValue(entry, parsed);
				return true;
			}
			entry.BoxedValue = parsed;
			return true;
		}

		private static void Register(MelonPreferences_Entry entry)
		{
			string identifier = entry.Category.Identifier;
			if (!EntriesBySection.TryGetValue(identifier, out Dictionary<string, MelonPreferences_Entry> value))
			{
				value = new Dictionary<string, MelonPreferences_Entry>(StringComparer.OrdinalIgnoreCase);
				EntriesBySection[identifier] = value;
			}
			value[entry.Identifier] = entry;
		}

		private static bool TryParseValue(Type type, string rawValue, out object? value, out string? error)
		{
			value = null;
			error = null;
			rawValue = rawValue?.Trim() ?? "";
			if (type == typeof(bool))
			{
				if (bool.TryParse(rawValue, out var result))
				{
					value = result;
					return true;
				}
				if (string.Equals(rawValue, "1", StringComparison.Ordinal) || string.Equals(rawValue, "on", StringComparison.OrdinalIgnoreCase) || string.Equals(rawValue, "yes", StringComparison.OrdinalIgnoreCase))
				{
					value = true;
					return true;
				}
				if (string.Equals(rawValue, "0", StringComparison.Ordinal) || string.Equals(rawValue, "off", StringComparison.OrdinalIgnoreCase) || string.Equals(rawValue, "no", StringComparison.OrdinalIgnoreCase))
				{
					value = false;
					return true;
				}
				error = "Invalid boolean value.";
				return false;
			}
			if (type == typeof(int))
			{
				if (int.TryParse(rawValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2))
				{
					value = result2;
					return true;
				}
				error = "Invalid integer value.";
				return false;
			}
			if (type == typeof(float))
			{
				if (float.TryParse(rawValue, NumberStyles.Float, CultureInfo.InvariantCulture, out var result3))
				{
					value = result3;
					return true;
				}
				error = "Invalid number value.";
				return false;
			}
			if (type == typeof(string))
			{
				value = rawValue;
				return true;
			}
			error = "Unsupported setting type: " + type.Name + ".";
			return false;
		}
	}
	public static class ModLog
	{
		internal static readonly ColorARGB SuccessGreen = ColorARGB.Green;

		internal static readonly ColorARGB FailureRed = ColorARGB.Red;

		internal static readonly ColorARGB Neutral = MelonLogger.DefaultTextColor;

		private static readonly MethodInfo? PassLogMsgMethod = typeof(MelonLogger).GetMethod("PassLogMsg", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[5]
		{
			typeof(ColorARGB),
			typeof(string),
			typeof(ColorARGB),
			typeof(string),
			typeof(string)
		}, null);

		private static bool UseLegacyConsoleColors => MelonUtils.IsUnderWineOrSteamProton();

		internal static string FeatureSection(string feature, string title)
		{
			return feature + "][" + title;
		}

		internal static void PassLogSegmented(string section, string stripped, params (ColorARGB? color, string text)[] segments)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: 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)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			if (PassLogMsgMethod == null)
			{
				MelonLogger.Msg(stripped);
				return;
			}
			bool useLegacyConsoleColors = UseLegacyConsoleColors;
			string text = BuildMessageBody(segments, useLegacyConsoleColors);
			ColorARGB val = (useLegacyConsoleColors ? PickMessageColor(segments) : Neutral);
			PassLogMsgMethod.Invoke(null, new object[5] { val, text, Neutral, section, stripped });
		}

		private static string BuildMessageBody((ColorARGB? color, string text)[] segments, bool plain)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < segments.Length; i++)
			{
				var (val, text) = segments[i];
				stringBuilder.Append((plain || !val.HasValue) ? text : ConsoleExtensions.Pastel(text, val.Value));
			}
			return stringBuilder.ToString();
		}

		private static ColorARGB PickMessageColor((ColorARGB? color, string text)[] segments)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			if (segments.Any(delegate((ColorARGB? color, string text) s)
			{
				//IL_0015: 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_001d: Unknown result type (might be due to invalid IL or missing references)
				if (s.color.HasValue)
				{
					ColorARGB value = s.color.Value;
					return ((ColorARGB)(ref value)).Equals(FailureRed);
				}
				return false;
			}))
			{
				return FailureRed;
			}
			if (segments.Any(delegate((ColorARGB? color, string text) s)
			{
				//IL_0015: 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_001d: Unknown result type (might be due to invalid IL or missing references)
				if (s.color.HasValue)
				{
					ColorARGB value = s.color.Value;
					return ((ColorARGB)(ref value)).Equals(SuccessGreen);
				}
				return false;
			}))
			{
				return SuccessGreen;
			}
			ColorARGB[] array = (from s in segments
				where s.color.HasValue
				select s.color.Value).Distinct().Take(2).ToArray();
			if (array.Length != 1)
			{
				return Neutral;
			}
			return array[0];
		}

		public static void Info(string feature, string message)
		{
			MelonLogger.Msg("[" + feature + "] " + message);
		}

		public static void Warn(string feature, string message)
		{
			MelonLogger.Warning("[" + feature + "] " + message);
		}

		public static void Error(string feature, string message)
		{
			MelonLogger.Error("[" + feature + "] " + message);
		}

		public static void Debug(string feature, string message)
		{
			if (ModConfig.EnableDebugLogging.Value)
			{
				MelonLogger.Msg("[" + feature + ":debug] " + message);
			}
		}
	}
	public sealed class PlayerConnectionInfo
	{
		public long PlayerUid;

		public string DisplayName = "";

		public string ConnectionRole = "";

		public ulong SteamId;

		public string ConnectionAddress = "";

		public int VoiceEventCount;
	}
	public static class VoiceEventStats
	{
		private const BindingFlags InstanceMemberFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		public static int GetEventCount(SpeechEventArchive? archive)
		{
			if ((Object)(object)archive == (Object)null)
			{
				return 0;
			}
			try
			{
				return archive.events?.Count ?? 0;
			}
			catch
			{
				return 0;
			}
		}

		public static string ResolveDisplayName(SpeechEventArchive? archive, long playerUid, bool isLocal)
		{
			if ((Object)(object)archive != (Object)null)
			{
				try
				{
					FishNetDissonancePlayer player = archive.Player;
					ProtoActor val = ((player != null) ? player.ProtoActorCache : null);
					if ((Object)(object)val != (Object)null && !string.IsNullOrWhiteSpace(val.nickName))
					{
						return val.nickName;
					}
				}
				catch
				{
				}
			}
			if (playerUid != 0L)
			{
				string text = ResolveNickNameFromActorMap(playerUid);
				if (!string.IsNullOrWhiteSpace(text))
				{
					return text;
				}
			}
			if (isLocal)
			{
				string hostNickName = GetHostNickName();
				if (!string.IsNullOrWhiteSpace(hostNickName))
				{
					return hostNickName;
				}
			}
			return "(pending)";
		}

		public static string GetVoiceId(SpeechEventArchive? archive)
		{
			if ((Object)(object)archive == (Object)null)
			{
				return "?";
			}
			try
			{
				string playerId = archive.PlayerId;
				return string.IsNullOrEmpty(playerId) ? "(pending)" : playerId;
			}
			catch
			{
				return "(unavailable)";
			}
		}

		public static string DescribePlayer(SpeechEventArchive? archive)
		{
			if ((Object)(object)archive == (Object)null)
			{
				return "archive=null";
			}
			if (!TryGetConnectionInfo(archive, out PlayerConnectionInfo info))
			{
				return "archive=unavailable";
			}
			string text = ((info.PlayerUid == 0L) ? "(pending)" : info.PlayerUid.ToString());
			string text2 = ((info.SteamId == 0L) ? "(pending)" : info.SteamId.ToString());
			return $"uid={text} name={info.DisplayName} role={info.ConnectionRole} steamId={text2} ip={info.ConnectionAddress} voiceEvents={info.VoiceEventCount}";
		}

		public static bool TryGetConnectionInfo(SpeechEventArchive? archive, out PlayerConnectionInfo info)
		{
			return TryGetConnectionInfo(archive, null, 0uL, out info);
		}

		public static bool TryGetConnectionInfo(SpeechEventArchive? archive, SessionContext? knownContext, ulong steamIdHint, out PlayerConnectionInfo info)
		{
			info = new PlayerConnectionInfo();
			if ((Object)(object)archive == (Object)null)
			{
				return false;
			}
			long num = 0L;
			bool isLocal = false;
			try
			{
				num = archive.PlayerUID;
				isLocal = archive.IsLocal;
			}
			catch
			{
			}
			SessionContext val = knownContext;
			ulong num2 = steamIdHint;
			if (val != null)
			{
				try
				{
					if (num2 == 0L)
					{
						num2 = val.SteamID;
					}
					if (num == 0L)
					{
						num = val.GetPlayerUID();
					}
				}
				catch
				{
				}
			}
			if (num2 == 0L)
			{
				num2 = ResolveSteamId(num, isLocal, val);
			}
			if (val == null)
			{
				val = FindSessionContext(num, num2);
			}
			if (val != null && num2 == 0L)
			{
				num2 = ResolveSteamId(num, isLocal, val);
			}
			return TryBuildConnectionInfo(archive, num, isLocal, num2, val, out info);
		}

		public static bool TryGetConnectionInfo(SessionContext? session, long playerUid, ulong steamId, bool isLocal, out PlayerConnectionInfo info)
		{
			return TryBuildConnectionInfo(null, playerUid, isLocal, steamId, session, out info);
		}

		private static bool TryBuildConnectionInfo(SpeechEventArchive? archive, long playerUid, bool isLocal, ulong steamIdValue, SessionContext? session, out PlayerConnectionInfo info)
		{
			info = new PlayerConnectionInfo
			{
				PlayerUid = playerUid,
				DisplayName = ResolveDisplayName(archive, playerUid, isLocal),
				ConnectionRole = (isLocal ? "host" : "client"),
				SteamId = steamIdValue,
				ConnectionAddress = ResolveConnectionAddress(isLocal, session),
				VoiceEventCount = GetEventCount(archive)
			};
			return true;
		}

		public static string DescribePlayerVerbose(SpeechEventArchive? archive)
		{
			string text = DescribePlayer(archive);
			if ((Object)(object)archive == (Object)null)
			{
				return text;
			}
			long playerUid = 0L;
			try
			{
				playerUid = archive.PlayerUID;
			}
			catch
			{
			}
			SessionContext val = FindSessionContext(playerUid, 0uL);
			string text2 = "(unknown)";
			try
			{
				if (val != null)
				{
					text2 = val.ServerID.ToString();
				}
			}
			catch
			{
			}
			return text + " voiceId=" + GetVoiceId(archive) + " serverId=" + text2;
		}

		private static ulong ResolveSteamId(long playerUid, bool isLocal, SessionContext? session)
		{
			if (session != null)
			{
				try
				{
					ulong steamID = session.SteamID;
					if (steamID != 0L)
					{
						return steamID;
					}
				}
				catch
				{
				}
			}
			if (playerUid != 0L)
			{
				try
				{
					object hubMember = GetHubMember("pdata");
					if ((hubMember?.GetType().GetField("actorUIDToSteamID", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))?.GetValue(hubMember) is Dictionary<long, ulong> dictionary && dictionary.TryGetValue(playerUid, out var value))
					{
						return value;
					}
				}
				catch
				{
				}
			}
			if (!isLocal)
			{
				return 0uL;
			}
			return GetLocalSteamId();
		}

		private static ulong GetLocalSteamId()
		{
			try
			{
				PlatformMgr instance = MonoSingleton<PlatformMgr>.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					return 0uL;
				}
				string text = typeof(PlatformMgr).GetField("_uniqueUserPath", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(instance) as string;
				if (!string.IsNullOrEmpty(text) && ulong.TryParse(text, out var result))
				{
					return result;
				}
			}
			catch
			{
			}
			return 0uL;
		}

		private static string ResolveConnectionAddress(bool isLocal, SessionContext? session)
		{
			if (isLocal)
			{
				return "local";
			}
			if (session == null)
			{
				return "(unavailable)";
			}
			try
			{
				ISession session2 = session.Session;
				IPEndPoint iPEndPoint = ((session2 != null) ? session2.GetRemoteEndPoint() : null);
				if (iPEndPoint != null)
				{
					string text = iPEndPoint.Address.ToString();
					return (iPEndPoint.Port > 0) ? $"{text}:{iPEndPoint.Port}" : text;
				}
			}
			catch
			{
			}
			try
			{
				if (session.IsSDRLink)
				{
					return "steam-sdr";
				}
			}
			catch
			{
			}
			return "(unavailable)";
		}

		private static SessionContext? FindSessionContext(long playerUid, ulong steamId)
		{
			SessionManager sessionManager = GetSessionManager();
			if (sessionManager == null)
			{
				return null;
			}
			try
			{
				object? obj = typeof(SessionManager).GetField("_hostSessionContext", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(sessionManager);
				SessionContext val = (SessionContext)((obj is SessionContext) ? obj : null);
				if (val != null && MatchesSessionContext(val, playerUid, steamId))
				{
					return val;
				}
				if (typeof(SessionManager).GetField("m_Contexts", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(sessionManager) is Dictionary<long, SessionContext> dictionary)
				{
					foreach (SessionContext value in dictionary.Values)
					{
						if (MatchesSessionContext(value, playerUid, steamId))
						{
							return value;
						}
					}
				}
			}
			catch
			{
			}
			return null;
		}

		private static bool MatchesSessionContext(SessionContext context, long playerUid, ulong steamId)
		{
			if (context == null)
			{
				return false;
			}
			try
			{
				if (playerUid != 0L && context.GetPlayerUID() == playerUid)
				{
					return true;
				}
				if (steamId != 0L && context.SteamID == steamId)
				{
					return true;
				}
			}
			catch
			{
			}
			return false;
		}

		private static SessionManager? GetSessionManager()
		{
			try
			{
				object hubMember = GetHubMember("vworld");
				if (hubMember == null)
				{
					return null;
				}
				object? obj = hubMember.GetType().GetField("_sessionManager", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(hubMember);
				return (SessionManager?)((obj is SessionManager) ? obj : null);
			}
			catch
			{
				return null;
			}
		}

		private static string? ResolveNickNameFromActorMap(long playerUid)
		{
			try
			{
				object gameMain = GetGameMain();
				if (gameMain == null)
				{
					return null;
				}
				if (!(gameMain.GetType().GetMethod("GetProtoActorMap", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.Invoke(gameMain, null) is Dictionary<int, ProtoActor> dictionary))
				{
					return null;
				}
				foreach (ProtoActor value in dictionary.Values)
				{
					if (!((Object)(object)value == (Object)null) && value.UID == playerUid)
					{
						return string.IsNullOrWhiteSpace(value.nickName) ? null : value.nickName;
					}
				}
			}
			catch
			{
			}
			return null;
		}

		private static string? GetHostNickName()
		{
			try
			{
				object gameMain = GetGameMain();
				if (gameMain != null && gameMain.GetType().GetMethod("GetHostActorNickName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.Invoke(gameMain, null) is string text && !string.IsNullOrWhiteSpace(text))
				{
					return text;
				}
				object hubMember = GetHubMember("pdata");
				if (hubMember == null)
				{
					return null;
				}
				if (hubMember.GetType().GetField("MyNickName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(hubMember) is string text2 && !string.IsNullOrWhiteSpace(text2))
				{
					return text2;
				}
			}
			catch
			{
			}
			return null;
		}

		private static object? GetGameMain()
		{
			object hubMember = GetHubMember("pdata");
			return hubMember?.GetType().GetField("main", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(hubMember);
		}

		private static object? GetHubMember(string name)
		{
			if ((Object)(object)Hub.s == (Object)null)
			{
				return null;
			}
			Type typeFromHandle = typeof(Hub);
			FieldInfo field = typeFromHandle.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (field != null)
			{
				return field.GetValue(Hub.s);
			}
			PropertyInfo property = typeFromHandle.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (!(property != null) || !property.CanRead)
			{
				return null;
			}
			return property.GetValue(Hub.s);
		}
	}
	public sealed class Mod : MelonMod
	{
		private Harmony? _harmony;

		private bool _statisticsWasEnabled;

		private float _nextFixedRespawnProcessTime;

		public override void OnInitializeMelon()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			ModConfig.Initialize(((MelonBase)this).LoggerInstance);
			ModConfig.Changed += SyncFromConfig;
			_harmony = new Harmony("com.mimesis.playerenhancement");
			foreach (IFeatureModule item in FeatureModules.All)
			{
				item.ApplyPatches(_harmony);
			}
			_statisticsWasEnabled = ModConfig.EnableStatistics.Value;
			SyncFromConfig();
			LogStartupSummary();
		}

		public override void OnPreferencesSaved(string filepath)
		{
			if (IsOurConfigFile(filepath))
			{
				ModConfig.NormalizeSavedFloats();
			}
		}

		public override void OnPreferencesLoaded(string filepath)
		{
			if (IsOurConfigFile(filepath))
			{
				ModConfig.SanitizeFloatEntries();
				ModConfig.NormalizeSavedFloats();
				ModConfig.NotifyFileReloaded();
			}
		}

		public override void OnUpdate()
		{
			foreach (IFeatureModule item in FeatureModules.All)
			{
				if (!(item is FeatureModule { ThrottledUpdate: not false }))
				{
					item.OnUpdate();
				}
			}
			if ((!ModConfig.EnableSpawnScaling.Value && !ModConfig.EnableLootMultiplicator.Value) || !(Time.time >= _nextFixedRespawnProcessTime))
			{
				return;
			}
			_nextFixedRespawnProcessTime = Time.time + 1f;
			foreach (IFeatureModule item2 in FeatureModules.All)
			{
				if (item2 is FeatureModule { ThrottledUpdate: not false })
				{
					item2.OnUpdate();
				}
			}
		}

		public override void OnDeinitializeMelon()
		{
			StatisticsWriteQueue.FlushAllSync();
			WebDashboardServer.StopOnDeinit();
			HostStatusCache.Invalidate();
			ModConfig.Changed -= SyncFromConfig;
			if (_harmony != null)
			{
				_harmony.UnpatchSelf();
				ModLog.Debug("Startup", "Harmony patches removed.");
			}
		}

		private static bool IsOurConfigFile(string filepath)
		{
			return string.Equals(filepath, ModConfig.FilePath, StringComparison.OrdinalIgnoreCase);
		}

		private void SyncFromConfig()
		{
			if (!ModConfig.IsInitialized)
			{
				return;
			}
			foreach (IFeatureModule item in FeatureModules.All)
			{
				item.SyncFromConfig();
			}
			int num = (ModConfig.EnableMorePlayers.Value ? ModConfig.MaxPlayers.Value : 4);
			if (_statisticsWasEnabled && !ModConfig.EnableStatistics.Value)
			{
				StatisticsTracker.ClearRuntimeState();
			}
			_statisticsWasEnabled = ModConfig.EnableStatistics.Value;
			ModLog.Debug("Config", $"Synced — MorePlayers={ModConfig.EnableMorePlayers.Value} (session cap {num}), " + $"MoreVoices={ModConfig.EnableMoreVoices.Value}" + (ModConfig.EnableMoreVoices.Value ? $" (indoor {ModConfig.MaxIndoorVoiceEvents.Value}, deathmatch {ModConfig.MaxDeathMatchVoiceEvents.Value}, outdoor {ModConfig.MaxOutdoorVoiceEvents.Value})" : "") + $", Persistence={ModConfig.EnablePersistence.Value}, " + $"Statistics={ModConfig.EnableStatistics.Value}, " + $"JoinAnytime={ModConfig.EnableJoinAnytime.Value}, " + $"SpawnScaling={ModConfig.EnableSpawnScaling.Value}, " + $"LootMultiplicator={ModConfig.EnableLootMultiplicator.Value}, " + $"MoneyMultiplier={ModConfig.EnableMoneyMultiplier.Value}, " + $"DungeonTime={ModConfig.EnableDungeonTime.Value}, " + $"SpectatorTransition={ModConfig.EnableSpectatorTransition.Value}, " + $"DungeonRandomizer={ModConfig.EnableDungeonRandomizer.Value}, " + $"WebDashboard={ModConfig.EnableWebDashboard.Value}" + (ModConfig.EnableWebDashboard.Value ? $" ({ModConfig.WebDashboardListenAddress.Value}:{ModConfig.WebDashboardListenPort.Value})" : "") + $", DebugLogging={ModConfig.EnableDebugLogging.Value}");
		}

		private void LogStartupSummary()
		{
			ModLog.Info("Startup", "v26.6.3 loaded — " + $"MorePlayers={ModConfig.EnableMorePlayers.Value}" + (ModConfig.EnableMorePlayers.Value ? $" (session cap {ModConfig.MaxPlayers.Value})" : "") + $", MoreVoices={ModConfig.EnableMoreVoices.Value}" + (ModConfig.EnableMoreVoices.Value ? $" (indoor {ModConfig.MaxIndoorVoiceEvents.Value}, deathmatch {ModConfig.MaxDeathMatchVoiceEvents.Value}, outdoor {ModConfig.MaxOutdoorVoiceEvents.Value})" : "") + $", Persistence={ModConfig.EnablePersistence.Value}" + $", Statistics={ModConfig.EnableStatistics.Value}, " + $"JoinAnytime={ModConfig.EnableJoinAnytime.Value}, " + $"SpawnScaling={ModConfig.EnableSpawnScaling.Value}, " + $"LootMultiplicator={ModConfig.EnableLootMultiplicator.Value}, " + $"MoneyMultiplier={ModConfig.EnableMoneyMultiplier.Value}, " + $"DungeonTime={ModConfig.EnableDungeonTime.Value}, " + $"SpectatorTransition={ModConfig.EnableSpectatorTransition.Value}, " + $"DungeonRandomizer={ModConfig.EnableDungeonRandomizer.Value}, " + $"WebDashboard={ModConfig.EnableWebDashboard.Value}" + (ModConfig.EnableWebDashboard.Value ? $" ({ModConfig.WebDashboardListenAddress.Value}:{ModConfig.WebDashboardListenPort.Value})" : "") + $", DebugLogging={ModConfig.EnableDebugLogging.Value}");
		}
	}
	public static class VersionInfo
	{
		public const string ModuleVersion = "26.6.3";
	}
}
namespace MimesisPlayerEnhancement.Util
{
	internal interface IFeatureModule
	{
		string Name { get; }

		void ApplyPatches(Harmony harmony);

		void SyncFromConfig()
		{
		}

		void OnUpdate()
		{
		}
	}
	internal sealed class FeatureModule : IFeatureModule
	{
		private readonly Action<Harmony> _applyPatches;

		private readonly Action? _syncFromConfig;

		private readonly Action? _onUpdate;

		public string Name { get; }

		internal bool ThrottledUpdate { get; }

		public FeatureModule(string name, Action<Harmony> applyPatches, Action? syncFromConfig = null, Action? onUpdate = null, bool throttledUpdate = false)
		{
			Name = name;
			_applyPatches = applyPatches;
			_syncFromConfig = syncFromConfig;
			_onUpdate = onUpdate;
			if (throttledUpdate)
			{
				ThrottledUpdate = true;
			}
		}

		public void ApplyPatches(Harmony harmony)
		{
			_applyPatches(harmony);
		}

		public void SyncFromConfig()
		{
			_syncFromConfig?.Invoke();
		}

		public void OnUpdate()
		{
			_onUpdate?.Invoke();
		}
	}
	internal static class FeatureModules
	{
		internal static IReadOnlyList<IFeatureModule> All { get; } = new <>z__ReadOnlyArray<IFeatureModule>(new IFeatureModule[13]
		{
			new FeatureModule("MoreVoices", MoreVoicesPatches.Apply, MoreVoicesPatches.RefreshFromConfig),
			new FeatureModule("Persistence", PersistencePatches.Apply, null, delegate
			{
				if (ModConfig.EnablePersistence.Value)
				{
					SpeechEventPoolManager.ProcessDeferredUpdates();
				}
			}),
			new FeatureModule("Statistics", StatisticsPatches.Apply, null, delegate
			{
				if (ModConfig.EnableStatistics.Value)
				{
					StatisticsTracker.OnUpdate();
				}
			}),
			new FeatureModule("PlayerAnnouncements", PlayerAnnouncementPatches.Apply),
			new FeatureModule("MorePlayers", MorePlayersPatches.Apply, MorePlayersPatches.RefreshFromConfig),
			new FeatureModule("JoinAnytime", JoinAnytimePatches.Apply),
			new FeatureModule("SpawnScaling", SpawnScalingPatches.Apply, null, delegate
			{
				if (ModConfig.EnableSpawnScaling.Value)
				{
					FixedSpawnCoordinator.ProcessPendingRespawns();
				}
			}, throttledUpdate: true),
			new FeatureModule("LootMultiplicator", LootMultiplicatorPatches.Apply, null, delegate
			{
				if (ModConfig.EnableLootMultiplicator.Value)
				{
					FixedLootSpawnCoordinator.ProcessPendingRespawns();
				}
			}, throttledUpdate: true),
			new FeatureModule("MoneyMultiplier", MoneyMultiplierPatches.Apply),
			new FeatureModule("DungeonTime", DungeonTimePatches.Apply),
			new FeatureModule("SpectatorTransition", SpectatorTransitionPatches.Apply),
			new FeatureModule("DungeonRandomizer", DungeonRandomizerPatches.Apply),
			new FeatureModule("WebDashboard", WebDashboardServer.Apply, WebDashboardServer.SyncFromConfig, WebDashboardServer.OnUpdate)
		});
	}
	internal static class FixedRespawnTiming
	{
		internal const float RetryIntervalSeconds = 1f;
	}
	internal static class GameNetworkApi
	{
		private const BindingFlags StaticFlags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		public static Assembly? GetGameAssembly()
		{
			try
			{
				Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => a.GetName().Name == "Assembly-CSharp");
				if (assembly != null)
				{
					return assembly;
				}
				return typeof(IVroom).Assembly;
			}
			catch
			{
				return null;
			}
		}

		public static Type? GetIVroomType()
		{
			return GetGameAssembly()?.GetType("IVroom");
		}

		public static Type? GetGameSessionInfoType()
		{
			return GetGameAssembly()?.GetType("GameSessionInfo");
		}

		public static object? GetServerSocket()
		{
			object vWorld = GetVWorld();
			object obj;
			if (vWorld != null)
			{
				obj = ReflectionHelper.GetFieldValue(vWorld, "_sdrServer");
				if (obj == null)
				{
					return ReflectionHelper.GetFieldValue(vWorld, "_rudpServer");
				}
			}
			else
			{
				obj = null;
			}
			return obj;
		}

		public static void SetMaximumClients(object serverSocket, int value)
		{
			ReflectionHelper.SetFieldValue(serverSocket, "_maximumClients", value);
		}

		public static int GetRoomPlayerCount(object? room)
		{
			if (room == null)
			{
				return 0;
			}
			return (ReflectionHelper.GetFieldValue(room, "_vPlayerDict") as IDictionary)?.Count ?? 0;
		}

		private static object? GetVWorld()
		{
			object hub = GetHub();
			if (hub != null)
			{
				return ReflectionHelper.GetFieldValue(hub, "<VWorld>k__BackingField");
			}
			return null;
		}

		public static object? GetHub()
		{
			return (GetGameAssembly()?.GetType("Hub"))?.GetProperty("s", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null);
		}

		public static object? GetVRoomManager()
		{
			Type type = GetGameAssembly()?.GetType("Hub");
			object hub = GetHub();
			if (!(type == null) && hub != null)
			{
				return type.GetProperty("VRoomManager", BindingFlags.Instance | BindingFlags.Public)?.GetValue(hub);
			}
			return null;
		}
	}
	internal static class ReflectionHelper
	{
		private const BindingFlags DefaultFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		public static object? GetFieldValue(object target, string fieldName)
		{
			return target?.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(target);
		}

		public static T GetFieldValue<T>(object target, string fieldName)
		{
			object fieldValue = GetFieldValue(target, fieldName);
			if (fieldValue != null)
			{
				return (T)fieldValue;
			}
			return default(T);
		}

		public static void SetFieldValue(object target, string fieldName, object value)
		{
			target?.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.SetValue(target, value);
		}
	}
	public static class HarmonyPatchHelper
	{
		public struct PatchApplyResult
		{
			public int Applied;

			public int Failed;
		}

		public static IEnumerable<Type> GetNestedPatchTypes(Type containerType)
		{
			return from t in containerType.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic)
				where t.GetCustomAttributes(typeof(HarmonyPatch), inherit: false).Length != 0
				select t;
		}

		public static IEnumerable<Type> GetNamespacePatchTypes(Type anchorType, string suffix = ".Patches")
		{
			return from t in anchorType.Assembly.GetTypes()
				where t.Namespace == anchorType.Namespace + suffix && t.GetCustomAttributes(typeof(HarmonyPatch), inherit: false).Length != 0
				select t;
		}

		public static PatchApplyResult ApplyPatchTypes(Harmony harmony, string feature, IEnumerable<Type> patchTypes)
		{
			int num = 0;
			int num2 = 0;
			foreach (Type patchType in patchTypes)
			{
				try
				{
					List<MethodInfo> list = harmony.CreateClassProcessor(patchType).Patch();
					if (list != null && list.Count > 0)
					{
						num += list.Count;
						continue;
					}
					num2++;
					if (patchType.GetMethod("TargetMethod", BindingFlags.Static | BindingFlags.Public | Bindin