Decompiled source of WorldStageDirector v0.5.3

WorldStageDirector.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using JG224.ModCore.API;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using WorldStageDirector.Bosses;
using WorldStageDirector.Commands;
using WorldStageDirector.Config;
using WorldStageDirector.Core;
using WorldStageDirector.Networking;
using WorldStageDirector.Progression;
using WorldStageDirector.Spawning;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("jg224")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Server-side progression control. Protects players from content beyond their progress and provides expanded boss-stone progression management.")]
[assembly: AssemblyFileVersion("0.5.3.0")]
[assembly: AssemblyInformationalVersion("0.5.3+021621d422ae9d27eccba527722890746c0a2a66")]
[assembly: AssemblyProduct("WorldStageDirector")]
[assembly: AssemblyTitle("WorldStageDirector")]
[assembly: AssemblyVersion("0.5.3.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 WorldStageDirector
{
	[BepInPlugin("jg224.worldstagedirector", "WorldStageDirector", "0.5.3")]
	[BepInDependency("com.jg224.modcore", "0.5.0")]
	[BepInDependency("com.jotunn.jotunn", "2.30.0")]
	[BepInIncompatibility("ZenDragon.ZenBossStone")]
	[BepInIncompatibility("jg224.progressguard")]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "jg224.worldstagedirector";

		internal const string LegacyPluginGuid = "jg224.progressguard";

		public const string PluginName = "WorldStageDirector";

		public const string PluginVersion = "0.5.3";

		public const string ModCoreGuid = "com.jg224.modcore";

		public const string JotunnGuid = "com.jotunn.jotunn";

		public static readonly ModuleId ModuleId = new ModuleId("worldstagedirector");

		private Harmony _harmony;

		private readonly List<IDisposable> _registrations = new List<IDisposable>();

		private bool _shutDown;

		internal static ManualLogSource Log { get; private set; }

		internal static ICoreServices Core { get; private set; }

		internal static bool IsActive { get; private set; }

		private void Awake()
		{
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Expected O, but got Unknown
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_018f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0199: Expected O, but got Unknown
			//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_021c: Unknown result type (might be due to invalid IL or missing references)
			IsActive = false;
			_shutDown = false;
			Log = ((BaseUnityPlugin)this).Logger;
			try
			{
				if (!ModCoreApi.IsAvailable)
				{
					throw new InvalidOperationException("ModCore did not initialize before WorldStageDirector.");
				}
				Core = ModCoreApi.Services;
				SemanticVersion val = default(SemanticVersion);
				if (!SemanticVersion.TryParse("0.5.3", ref val))
				{
					throw new InvalidOperationException("WorldStageDirector has invalid release version metadata.");
				}
				_registrations.Add(Core.Modules.Register(new ModuleDescriptor(ModuleId, "jg224.worldstagedirector", "WorldStageDirector", val, 9, (ModuleSide)3, (ModuleRequirement)4, 0uL, 1, 1)));
				_registrations.Add(Core.Namespaces.Register(ModuleId, (NamespaceKind)1, "jg224.worldstagedirector.", 1, Array.Empty<string>()));
				_registrations.Add(Core.Namespaces.Register(ModuleId, (NamespaceKind)6, "jg224.worldstagedirector.", 1, Array.Empty<string>()));
				_registrations.Add(RoutedRpcIngress.Register(ModuleId, new string[10] { "jg224.worldstagedirector.SyncState", "jg224.worldstagedirector.ImportLegacyProgression", "jg224.worldstagedirector.ImportLegacyProgressionAck", "jg224.worldstagedirector.BossDeath", "jg224.worldstagedirector.BossDamage", "jg224.worldstagedirector.BossPlayerNotice", "jg224.worldstagedirector.BossSacrificeRequest", "jg224.worldstagedirector.BossSacrificeResult", "jg224.worldstagedirector.BossSacrificeEvent", "jg224.worldstagedirector.BossOfferingReconcile" }));
				_registrations.Add(Core.Namespaces.Register(ModuleId, (NamespaceKind)4, "WorldStageDirector", 1, Array.Empty<string>()));
				LegacyIdentityMigration.TryMigrateConfig(((BaseUnityPlugin)this).Config);
				ModConfig.Bind(((BaseUnityPlugin)this).Config);
				_harmony = new Harmony("jg224.worldstagedirector");
				_harmony.PatchAll(typeof(Plugin).Assembly);
				AdminCommands.Register();
				IsActive = true;
				Core.Modules.SetState(ModuleId, (ModuleRuntimeState)2, "Required server-authoritative progression protocol ready.");
				Log.LogInfo((object)"WorldStageDirector v0.5.3 loaded. Required on server and every client. Boss deaths now advance the world only after an eligible trophy sacrifice.");
			}
			catch (Exception ex)
			{
				Log.LogError((object)("WorldStageDirector failed to initialize safely: " + ex));
				if (Core != null)
				{
					Core.Modules.SetState(ModuleId, (ModuleRuntimeState)5, ex.Message);
				}
				Shutdown();
				throw;
			}
		}

		private void OnDestroy()
		{
			Shutdown();
		}

		private void Update()
		{
			if (IsActive)
			{
				LocalOfferingRecovery.Tick();
			}
		}

		private void Shutdown()
		{
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			if (_shutDown)
			{
				return;
			}
			_shutDown = true;
			IsActive = false;
			try
			{
				ProgressionRegistry.Save();
			}
			catch (Exception ex)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogError((object)("Shutdown save failed; retained durable progression: " + ex.Message));
				}
			}
			ProgressionSync.Shutdown();
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			_harmony = null;
			for (int num = _registrations.Count - 1; num >= 0; num--)
			{
				try
				{
					_registrations[num].Dispose();
				}
				catch (Exception ex2)
				{
					ManualLogSource log2 = Log;
					if (log2 != null)
					{
						log2.LogWarning((object)("Registration cleanup failed: " + ex2.Message));
					}
				}
			}
			_registrations.Clear();
			if (Core != null)
			{
				Core.Metrics.RemoveOwner(ModuleId);
			}
			Core = null;
		}

		internal static void Debug(string message)
		{
			ConfigEntry<bool> verboseLogging = ModConfig.VerboseLogging;
			if (verboseLogging != null && verboseLogging.Value)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogInfo((object)("[debug] " + message));
				}
			}
		}
	}
}
namespace WorldStageDirector.Spawning
{
	[HarmonyPatch(typeof(MonsterAI), "Start")]
	internal static class ExistingCreatureSafetyPatch
	{
		[HarmonyPostfix]
		private static void Postfix(MonsterAI __instance)
		{
			TryRemove(__instance);
		}

		internal static void SweepLoaded()
		{
			BaseAI[] array = BaseAI.GetAllInstances().ToArray();
			foreach (BaseAI obj in array)
			{
				MonsterAI val = (MonsterAI)(object)((obj is MonsterAI) ? obj : null);
				if (val != null)
				{
					TryRemove(val);
				}
			}
		}

		private static void TryRemove(MonsterAI monster)
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				ZNetView val = ((monster != null) ? ((Component)monster).GetComponent<ZNetView>() : null);
				if (!((Object)(object)val == (Object)null) && val.IsValid() && val.IsOwner() && RuntimeSpawnValidator.ShouldRemoveExisting(monster))
				{
					Plugin.Log.LogWarning((object)("Removing out-of-progression existing creature " + $"'{((Object)((Component)monster).gameObject).name}' at {((Component)monster).transform.position}."));
					val.Destroy();
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Existing-creature safety check failed: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(RandEventSystem), "SetRandomEvent", new Type[]
	{
		typeof(RandomEvent),
		typeof(Vector3)
	})]
	internal static class RaidProgressionSafetyPatch
	{
		[HarmonyPrefix]
		private static void Prefix(ref RandomEvent ev, Vector3 pos)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			if (ev != null && !ShouldAllow(ev, pos, out var _, out var _))
			{
				RaidRuleRegistry.TryGetRequiredTier(ev.m_name, ev.m_requiredGlobalKeys, out var tier);
				ProgressionTier onlineSafetyTier = ProgressionSync.GetOnlineSafetyTier();
				Plugin.Log.LogWarning((object)("Blocked random raid '" + ev.m_name + "': least-progressed online tier " + $"{onlineSafetyTier}, required {tier}."));
				ev = null;
			}
		}

		internal static void EnforceActiveEvent()
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			ZNet instance = ZNet.instance;
			RandEventSystem instance2 = RandEventSystem.instance;
			if (!((Object)(object)instance == (Object)null) && instance.IsServer() && !((Object)(object)instance2 == (Object)null))
			{
				RandomEvent currentRandomEvent = instance2.GetCurrentRandomEvent();
				if (currentRandomEvent != null && !ShouldAllow(currentRandomEvent, currentRandomEvent.m_pos, out var requiredTier, out var onlineTier))
				{
					Plugin.Log.LogWarning((object)("Stopped random raid '" + currentRandomEvent.m_name + "' after online progression changed: " + $"least-progressed online tier {onlineTier}, required {requiredTier}."));
					instance2.ResetRandomEvent();
				}
			}
		}

		private static bool ShouldAllow(RandomEvent ev, Vector3 eventPosition, out ProgressionTier requiredTier, out ProgressionTier onlineTier)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			requiredTier = ProgressionTier.None;
			onlineTier = ProgressionTier.None;
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || !instance.IsServer() || ev == null || !ev.m_random)
			{
				return true;
			}
			GateSettings gateSettings = ModConfig.Snapshot();
			if (!gateSettings.Enabled || !gateSettings.LimitRaidsToOnlineProgression)
			{
				return true;
			}
			if (IsAshlandsTormentSpawnerRaid(ev, eventPosition))
			{
				return true;
			}
			if (!RaidRuleRegistry.TryGetRequiredTier(ev.m_name, ev.m_requiredGlobalKeys, out requiredTier))
			{
				Plugin.Debug("Random raid '" + ev.m_name + "' has no managed boss-tier rule; vanilla eligibility remains authoritative.");
				return true;
			}
			onlineTier = ProgressionSync.GetOnlineSafetyTier();
			return onlineTier >= requiredTier;
		}

		private static bool IsAshlandsTormentSpawnerRaid(RandomEvent ev, Vector3 eventPosition)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			if (RuntimeSpawnValidator.TryGetBiome(eventPosition, out var biome))
			{
				return RaidRuleRegistry.IsProgressionExempt(ev.m_name, biome);
			}
			return false;
		}
	}
	internal static class RuntimeSpawnValidator
	{
		private const string SkaldHallEnemyTagKey = "arenaguard.enemy_tag";

		private const string SkaldHallArenaIdKey = "arenaguard.arena_id";

		private const string SkaldHallSessionIdKey = "arenaguard.session_id";

		private const string SkaldHallEnemyIdKey = "arenaguard.enemy_id";

		internal static bool ShouldAllow(SpawnData spawn, Vector3 spawnPoint, bool eventSpawner)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			if (!HasAuthoritativeState())
			{
				return true;
			}
			if (spawn == null || (Object)(object)spawn.m_prefab == (Object)null)
			{
				return true;
			}
			if (!SpawnRuleRegistry.TryGet(spawn.m_requiredGlobalKey, ((Object)spawn.m_prefab).name, out var rule))
			{
				return true;
			}
			GateSettings effectiveSettings = GetEffectiveSettings();
			if (!effectiveSettings.Enabled || eventSpawner || !SpawnRuleRegistry.IsGateEnabled(rule, effectiveSettings))
			{
				return true;
			}
			NativeBiome biome;
			bool flag = TryGetBiome(spawnPoint, out biome);
			bool inNativeBiome = flag && rule.NativeBiome != NativeBiome.Unknown && (rule.NativeBiome & biome) != 0;
			List<ProgressionTier> relevantTiers = GetRelevantTiers(spawnPoint, effectiveSettings);
			SpawnDecision spawnDecision = SpawnDecisionEngine.Evaluate(rule, effectiveSettings, eventSpawner: false, inNativeBiome, relevantTiers);
			if (spawnDecision.Block)
			{
				if (effectiveSettings.LogBlockedSpawns)
				{
					Plugin.Log.LogInfo((object)("Blocked ambient " + ((Object)spawn.m_prefab).name + " at " + (flag ? biome.ToString() : "unresolved biome") + ": " + $"effective safety tier {spawnDecision.EffectiveTier}, required {rule.RequiredTier}."));
				}
				return false;
			}
			if (spawnDecision.Reason == SpawnDecisionReason.AllowedProgression && effectiveSettings.LogAllowedProgressionSpawns)
			{
				Plugin.Log.LogInfo((object)($"Allowed ambient {((Object)spawn.m_prefab).name} at {biome}: " + $"effective safety tier {spawnDecision.EffectiveTier}, required {rule.RequiredTier}."));
			}
			return true;
		}

		internal static bool ShouldRemoveExisting(MonsterAI monster)
		{
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			if (!HasAuthoritativeState())
			{
				return false;
			}
			if ((Object)(object)monster == (Object)null || monster.IsEventCreature())
			{
				return false;
			}
			if (IsSkaldHallArenaEnemy(monster))
			{
				return false;
			}
			Character component = ((Component)monster).GetComponent<Character>();
			if ((Object)(object)component == (Object)null || component.IsBoss() || component.IsTamed())
			{
				return false;
			}
			if (!SpawnRuleRegistry.TryGetCreatureSafetyRule(((Object)((Component)monster).gameObject).name, out var rule))
			{
				return false;
			}
			GateSettings effectiveSettings = GetEffectiveSettings();
			if (!effectiveSettings.Enabled || !SpawnRuleRegistry.IsGateEnabled(rule, effectiveSettings))
			{
				return false;
			}
			NativeBiome biome;
			bool inNativeBiome = TryGetBiome(((Component)monster).transform.position, out biome) && (rule.NativeBiome & biome) != 0;
			List<ProgressionTier> relevantTiers = GetRelevantTiers(((Component)monster).transform.position, effectiveSettings);
			return SpawnDecisionEngine.Evaluate(rule, effectiveSettings, eventSpawner: false, inNativeBiome, relevantTiers).Block;
		}

		private static bool IsSkaldHallArenaEnemy(MonsterAI monster)
		{
			ZNetView val = (((Object)(object)monster == (Object)null) ? null : ((Component)monster).GetComponent<ZNetView>());
			if ((Object)(object)val == (Object)null || !val.IsValid())
			{
				return false;
			}
			ZDO zDO = val.GetZDO();
			if (zDO != null)
			{
				return ArenaEnemyExemptionPolicy.IsSkaldHallArenaEnemy(zDO.GetInt("arenaguard.enemy_tag", 0), zDO.GetString("arenaguard.arena_id", string.Empty), zDO.GetString("arenaguard.session_id", string.Empty), zDO.GetString("arenaguard.enemy_id", string.Empty));
			}
			return false;
		}

		internal static bool HasAuthoritativeState()
		{
			ZNet instance = ZNet.instance;
			bool num = (Object)(object)instance != (Object)null;
			bool isServer = num && instance.IsServer();
			return AuthoritativeStatePolicy.CanEnforce(num, isServer, ClientProgressionState.HasServerSnapshot);
		}

		private static GateSettings GetEffectiveSettings()
		{
			ZNet instance = ZNet.instance;
			if (!((Object)(object)instance != (Object)null) || !instance.IsServer())
			{
				return ClientProgressionState.GetSettings();
			}
			return ModConfig.Snapshot();
		}

		private static List<ProgressionTier> GetRelevantTiers(Vector3 spawnPoint, GateSettings settings)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			if (settings.ProtectLeastProgressedOnline)
			{
				ZNet instance = ZNet.instance;
				ProgressionTier item = (((Object)(object)instance != (Object)null && instance.IsServer()) ? ProgressionSync.GetOnlineSafetyTier() : ClientProgressionState.OnlineSafetyTier);
				return new List<ProgressionTier> { item };
			}
			return GetNearbyTiers(spawnPoint, settings.NearbyPlayerRadius);
		}

		private static List<ProgressionTier> GetNearbyTiers(Vector3 spawnPoint, float radius)
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			List<ProgressionTier> list = new List<ProgressionTier>();
			float num = radius * radius;
			ZNet instance = ZNet.instance;
			bool flag = (Object)(object)instance != (Object)null && instance.IsServer();
			foreach (Player allPlayer in Player.GetAllPlayers())
			{
				if (!((Object)(object)allPlayer == (Object)null))
				{
					Vector3 val = ((Component)allPlayer).transform.position - spawnPoint;
					if (!(val.x * val.x + val.z * val.z > num))
					{
						long playerID = allPlayer.GetPlayerID();
						ProgressionTier item = (flag ? ProgressionRegistry.GetTier(playerID) : ClientProgressionState.GetTier(playerID));
						list.Add(item);
					}
				}
			}
			return list;
		}

		internal static bool TryGetBiome(Vector3 spawnPoint, out NativeBiome biome)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: 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_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			biome = NativeBiome.Unknown;
			ZoneSystem instance = ZoneSystem.instance;
			if ((Object)(object)instance != (Object)null)
			{
				try
				{
					Vector3 val = spawnPoint;
					Vector3 val2 = default(Vector3);
					Biome biome2 = default(Biome);
					BiomeArea val3 = default(BiomeArea);
					Heightmap val4 = default(Heightmap);
					instance.GetGroundData(ref val, ref val2, ref biome2, ref val3, ref val4);
					biome = ConvertBiome(biome2);
					if (biome != NativeBiome.Unknown)
					{
						return true;
					}
				}
				catch (Exception ex)
				{
					Plugin.Debug("Loaded-zone biome resolution failed: " + ex.Message);
				}
			}
			WorldGenerator instance2 = WorldGenerator.instance;
			if (instance2 == null)
			{
				return false;
			}
			try
			{
				biome = ConvertBiome(instance2.GetBiome(spawnPoint));
				return biome != NativeBiome.Unknown;
			}
			catch (Exception ex2)
			{
				Plugin.Debug("World biome resolution failed: " + ex2.Message);
				return false;
			}
		}

		private static NativeBiome ConvertBiome(Biome biome)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			NativeBiome nativeBiome = NativeBiome.Unknown;
			if ((biome & 1) != 0)
			{
				nativeBiome |= NativeBiome.Meadows;
			}
			if ((biome & 8) != 0)
			{
				nativeBiome |= NativeBiome.BlackForest;
			}
			if ((biome & 2) != 0)
			{
				nativeBiome |= NativeBiome.Swamp;
			}
			if ((biome & 4) != 0)
			{
				nativeBiome |= NativeBiome.Mountain;
			}
			if ((biome & 0x10) != 0)
			{
				nativeBiome |= NativeBiome.Plains;
			}
			if ((biome & 0x200) != 0)
			{
				nativeBiome |= NativeBiome.Mistlands;
			}
			if ((biome & 0x20) != 0)
			{
				nativeBiome |= NativeBiome.Ashlands;
			}
			return nativeBiome;
		}
	}
	[HarmonyPatch(typeof(SpawnSystem), "Spawn", new Type[]
	{
		typeof(SpawnData),
		typeof(Vector3),
		typeof(bool)
	})]
	internal static class AmbientSpawnPatch
	{
		[HarmonyPrefix]
		private static bool Prefix(SpawnData critter, Vector3 spawnPoint, bool eventSpawner)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			if (!TormentSpawnerSafety.ShouldAllowSpawn(critter, spawnPoint))
			{
				return false;
			}
			if (eventSpawner)
			{
				return true;
			}
			return RuntimeSpawnValidator.ShouldAllow(critter, spawnPoint, eventSpawner: false);
		}
	}
	[HarmonyPatch(typeof(SpawnSystem), "Awake")]
	internal static class SpawnRuleDiscoveryPatch
	{
		private static readonly HashSet<string> Seen = new HashSet<string>(StringComparer.Ordinal);

		[HarmonyPostfix]
		private static void Postfix(SpawnSystem __instance)
		{
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Expected I4, but got Unknown
			if (__instance?.m_spawnLists == null)
			{
				return;
			}
			foreach (SpawnSystemList spawnList in __instance.m_spawnLists)
			{
				if (spawnList?.m_spawners == null)
				{
					continue;
				}
				foreach (SpawnData spawner in spawnList.m_spawners)
				{
					if ((Object)(object)spawner?.m_prefab == (Object)null || string.IsNullOrEmpty(spawner.m_requiredGlobalKey))
					{
						continue;
					}
					string text = spawner.m_requiredGlobalKey + "|" + ((Object)spawner.m_prefab).name + "|" + (int)spawner.m_biome;
					if (Seen.Add(text))
					{
						if (SpawnRuleRegistry.TryGet(spawner.m_requiredGlobalKey, ((Object)spawner.m_prefab).name, out var rule))
						{
							Plugin.Debug($"Discovered managed spawn rule {text} => {rule.RequiredTier}, native {rule.NativeBiome}.");
						}
						else if (spawner.m_requiredGlobalKey.StartsWith("defeated_", StringComparison.OrdinalIgnoreCase))
						{
							Plugin.Debug("Discovered unmanaged defeated-key spawn rule " + text + "; it remains fail-open.");
						}
					}
				}
			}
		}
	}
	internal static class TormentSpawnerSafety
	{
		private const string EventPrefab = "spawnercharredstoneevent";

		internal static bool ShouldAllowSpawn(SpawnData spawn, Vector3 spawnPoint)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)spawn?.m_prefab == (Object)null || !IsEventTormentSpawner(((Object)spawn.m_prefab).name))
			{
				return true;
			}
			if (!TryGetBlockReason(spawnPoint, out var reason))
			{
				return true;
			}
			if (GetEffectiveSettings().LogBlockedSpawns)
			{
				Plugin.Log.LogInfo((object)$"Blocked event Monument of Torment at {spawnPoint}: {reason}.");
			}
			return false;
		}

		internal static bool IsEventTormentSpawner(string prefabName)
		{
			return string.Equals(SpawnRuleRegistry.NormalizePrefab(prefabName), "spawnercharredstoneevent", StringComparison.Ordinal);
		}

		internal static bool TryGetBlockReason(Vector3 position, out string reason)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			reason = string.Empty;
			if (!RuntimeSpawnValidator.HasAuthoritativeState())
			{
				return false;
			}
			GateSettings effectiveSettings = GetEffectiveSettings();
			if (!effectiveSettings.Enabled)
			{
				return false;
			}
			if (effectiveSettings.BlockTormentSpawnersOutsideAshlands)
			{
				NativeBiome biome;
				bool flag = RuntimeSpawnValidator.TryGetBiome(position, out biome);
				if (!flag || (biome & NativeBiome.Ashlands) == 0)
				{
					reason = (flag ? $"biome is {biome}, not Ashlands" : "biome could not be proven to be Ashlands");
					return true;
				}
			}
			return false;
		}

		private static GateSettings GetEffectiveSettings()
		{
			ZNet instance = ZNet.instance;
			if (!((Object)(object)instance != (Object)null) || !instance.IsServer())
			{
				return ClientProgressionState.GetSettings();
			}
			return ModConfig.Snapshot();
		}
	}
	[HarmonyPatch(typeof(SpawnArea), "Awake")]
	internal static class ExistingTormentSpawnerSafetyPatch
	{
		[HarmonyPostfix]
		private static void Postfix(SpawnArea __instance)
		{
			TryRemove(__instance);
		}

		internal static void SweepLoaded()
		{
			SpawnArea[] array = Resources.FindObjectsOfTypeAll<SpawnArea>();
			for (int i = 0; i < array.Length; i++)
			{
				TryRemove(array[i]);
			}
		}

		private static void TryRemove(SpawnArea spawnArea)
		{
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if ((Object)(object)spawnArea == (Object)null)
				{
					return;
				}
				ZNetView val = ((Component)spawnArea).GetComponent<ZNetView>() ?? ((Component)spawnArea).GetComponentInParent<ZNetView>();
				if (!((Object)(object)val == (Object)null) && val.IsValid() && val.IsOwner())
				{
					string text = ResolvePrefabName(val, ((Object)((Component)spawnArea).gameObject).name);
					if (TormentSpawnerSafety.IsEventTormentSpawner(text) && TormentSpawnerSafety.TryGetBlockReason(((Component)spawnArea).transform.position, out var reason))
					{
						Plugin.Log.LogWarning((object)("Removing event Monument of Torment '" + text + "' at " + $"{((Component)spawnArea).transform.position}: {reason}."));
						val.Destroy();
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Existing Monument of Torment safety check failed: {arg}");
			}
		}

		private static string ResolvePrefabName(ZNetView networkView, string fallback)
		{
			ZDO zDO = networkView.GetZDO();
			object obj;
			if (zDO != null)
			{
				ZNetScene instance = ZNetScene.instance;
				obj = ((instance != null) ? instance.GetPrefab(zDO.GetPrefab()) : null);
			}
			else
			{
				obj = null;
			}
			return ((obj != null) ? ((Object)obj).name : null) ?? ((Object)((Component)networkView).gameObject).name ?? fallback;
		}
	}
}
namespace WorldStageDirector.Progression
{
	public sealed class PlayerProgressRecord
	{
		public long PlayerId { get; }

		public string PlayerName { get; }

		public ProgressionTier Tier { get; }

		public BossCreditFlags KillReceipts { get; }

		public BossCreditFlags BossCredits { get; }

		public PlayerProgressRecord(long playerId, string playerName, ProgressionTier tier)
			: this(playerId, playerName, tier, BossCreditFlags.None, BossDefinitionRegistry.AllThrough(tier))
		{
		}

		public PlayerProgressRecord(long playerId, string playerName, ProgressionTier tier, BossCreditFlags killReceipts, BossCreditFlags bossCredits)
		{
			PlayerId = playerId;
			PlayerName = playerName ?? string.Empty;
			Tier = tier;
			KillReceipts = killReceipts;
			BossCredits = bossCredits;
		}
	}
	internal static class ProgressionPersistence
	{
		[DataContract]
		private sealed class RegistryFile
		{
			[DataMember(Name = "schemaVersion", Order = 1)]
			public int SchemaVersion;

			[DataMember(Name = "worldUid", Order = 2)]
			public long WorldUid;

			[DataMember(Name = "players", Order = 3)]
			public List<PlayerRecord> Players = new List<PlayerRecord>();

			[DataMember(Name = "offerings", Order = 4)]
			public List<OfferingRecord> Offerings = new List<OfferingRecord>();
		}

		[DataContract]
		private sealed class PlayerRecord
		{
			[DataMember(Name = "playerId", Order = 1)]
			public long PlayerId;

			[DataMember(Name = "name", Order = 2)]
			public string PlayerName = string.Empty;

			[DataMember(Name = "tier", Order = 3)]
			public int Tier;

			[DataMember(Name = "killReceipts", Order = 4)]
			public int KillReceipts;

			[DataMember(Name = "bossCredits", Order = 5)]
			public int BossCredits;
		}

		private const int SchemaVersion = 3;

		internal static List<OfferingRecord> LoadedOfferings = new List<OfferingRecord>();

		internal static bool RecoveryRequired { get; private set; }

		internal static List<PlayerProgressRecord> Load(long worldUid)
		{
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			RecoveryRequired = false;
			LoadedOfferings = new List<OfferingRecord>();
			string text = GetPath(worldUid);
			string legacyPath = GetLegacyPath(worldUid);
			if (IdentityPaths.ShouldMigrate(File.Exists(text), File.Exists(legacyPath)))
			{
				try
				{
					File.Copy(legacyPath, text, overwrite: false);
					Plugin.Log.LogInfo((object)("Copied legacy progression registry from '" + legacyPath + "' to '" + text + "'."));
				}
				catch (Exception ex)
				{
					text = legacyPath;
					Plugin.Log.LogWarning((object)("Could not copy legacy progression registry to its new filename. Loading the preserved legacy file instead. " + ex.Message));
				}
			}
			if (!File.Exists(text))
			{
				return new List<PlayerProgressRecord>();
			}
			try
			{
				using FileStream fileStream = File.OpenRead(text);
				if (fileStream.Length > 33554432)
				{
					throw new SerializationException("Registry exceeds 32 MiB.");
				}
				RegistryFile registryFile = (RegistryFile)((XmlObjectSerializer)new DataContractJsonSerializer(typeof(RegistryFile))).ReadObject((Stream)fileStream);
				if (registryFile == null || registryFile.SchemaVersion < 1 || registryFile.SchemaVersion > 3 || registryFile.WorldUid != worldUid)
				{
					throw new SerializationException("Registry schema or world UID does not match.");
				}
				if (registryFile.Offerings != null)
				{
					HashSet<string> hashSet = new HashSet<string>();
					if (registryFile.Offerings.Count > 100000)
					{
						throw new SerializationException("Offering ledger exceeds its safe limit.");
					}
					foreach (OfferingRecord offering in registryFile.Offerings)
					{
						if (offering == null || !offering.IsValid() || !hashSet.Add(offering.Id))
						{
							throw new SerializationException("Invalid or duplicate offering record.");
						}
					}
					LoadedOfferings = registryFile.Offerings;
				}
				List<PlayerProgressRecord> list = new List<PlayerProgressRecord>();
				if (registryFile.Players == null)
				{
					return list;
				}
				if (registryFile.Players.Count > 10000)
				{
					throw new SerializationException("Registry exceeds 10000 players.");
				}
				HashSet<long> hashSet2 = new HashSet<long>();
				foreach (PlayerRecord player in registryFile.Players)
				{
					if (player == null || !PlayerIdentityRules.IsValid(player.PlayerId) || !hashSet2.Add(player.PlayerId) || player.Tier < 0 || player.Tier > 7 || (player.PlayerName?.Length ?? 0) > 256)
					{
						throw new SerializationException("Invalid or duplicate player record.");
					}
					ProgressionTier tier = (ProgressionTier)player.Tier;
					if (!BossProgressionMigration.TryNormalize(Math.Min(2, registryFile.SchemaVersion), tier, player.KillReceipts, player.BossCredits, out var killReceipts, out var bossCredits))
					{
						throw new SerializationException("Invalid boss flags.");
					}
					list.Add(new PlayerProgressRecord(player.PlayerId, player.PlayerName, tier, killReceipts, bossCredits));
				}
				return list;
			}
			catch (Exception arg)
			{
				RecoveryRequired = true;
				LoadedOfferings.Clear();
				string text2 = text + ".corrupt-" + DateTime.UtcNow.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture);
				try
				{
					File.Copy(text, text2, overwrite: false);
				}
				catch
				{
				}
				Plugin.Log.LogError((object)$"Could not load progression registry '{text}'. Progression writes are blocked; restore a validated world backup and run wsd reload. Preserved copy: '{text2}'. {arg}");
				return new List<PlayerProgressRecord>();
			}
		}

		internal static void Save(long worldUid, IReadOnlyCollection<PlayerProgressRecord> records, IReadOnlyCollection<OfferingRecord> offerings)
		{
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			if (worldUid == 0L || RecoveryRequired)
			{
				throw new IOException("Progression storage is unavailable; recover the world registry before changing progression.");
			}
			string path = GetPath(worldUid);
			string text = path + ".tmp";
			string destinationBackupFileName = path + ".bak";
			try
			{
				Directory.CreateDirectory(Path.GetDirectoryName(path));
				RegistryFile registryFile = new RegistryFile
				{
					SchemaVersion = 3,
					WorldUid = worldUid,
					Offerings = new List<OfferingRecord>(offerings)
				};
				foreach (PlayerProgressRecord record in records)
				{
					registryFile.Players.Add(new PlayerRecord
					{
						PlayerId = record.PlayerId,
						PlayerName = record.PlayerName,
						Tier = (int)record.Tier,
						KillReceipts = (int)record.KillReceipts,
						BossCredits = (int)record.BossCredits
					});
				}
				using (FileStream fileStream = File.Create(text))
				{
					((XmlObjectSerializer)new DataContractJsonSerializer(typeof(RegistryFile))).WriteObject((Stream)fileStream, (object)registryFile);
					fileStream.Flush(flushToDisk: true);
				}
				if (File.Exists(path))
				{
					File.Replace(text, path, destinationBackupFileName);
				}
				else
				{
					File.Move(text, path);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)$"Could not save progression registry '{path}': {ex}");
				try
				{
					if (File.Exists(text))
					{
						File.Delete(text);
					}
				}
				catch
				{
				}
				throw new IOException("Progression was not committed. Existing registry and backup were preserved.", ex);
			}
		}

		internal static string GetPath(long worldUid)
		{
			return Path.Combine(Paths.ConfigPath, IdentityPaths.RegistryFileName("jg224.worldstagedirector", worldUid));
		}

		internal static string GetLegacyPath(long worldUid)
		{
			return Path.Combine(Paths.ConfigPath, IdentityPaths.RegistryFileName("jg224.progressguard", worldUid));
		}
	}
	internal static class ProgressionRegistry
	{
		private static readonly object Sync = new object();

		private static readonly Dictionary<long, PlayerProgressRecord> Records = new Dictionary<long, PlayerProgressRecord>();

		private static readonly Dictionary<string, OfferingRecord> Offerings = new Dictionary<string, OfferingRecord>();

		private static List<PlayerProgressRecord> _committedRecords = new List<PlayerProgressRecord>();

		private static List<OfferingRecord> _committedOfferings = new List<OfferingRecord>();

		private static long _worldUid;

		internal static long WorldUid
		{
			get
			{
				lock (Sync)
				{
					return _worldUid;
				}
			}
		}

		internal static int Count
		{
			get
			{
				lock (Sync)
				{
					return Records.Count;
				}
			}
		}

		internal static void LoadForWorld(long worldUid)
		{
			if (worldUid == 0L)
			{
				throw new ArgumentOutOfRangeException("worldUid", "The authoritative world is not ready.");
			}
			lock (Sync)
			{
				_worldUid = worldUid;
				Records.Clear();
				foreach (PlayerProgressRecord item in ProgressionPersistence.Load(worldUid))
				{
					if (PlayerIdentityRules.IsValid(item.PlayerId))
					{
						Records[item.PlayerId] = item;
					}
				}
				Offerings.Clear();
				foreach (OfferingRecord loadedOffering in ProgressionPersistence.LoadedOfferings)
				{
					Offerings.Add(loadedOffering.Id, loadedOffering);
				}
				_committedRecords = Records.Values.ToList();
				_committedOfferings = Offerings.Values.ToList();
			}
			Plugin.Log.LogInfo((object)$"Progression registry loaded for world {worldUid}: {Count} player(s).");
		}

		internal static bool EnsureLoadedForWorld(long worldUid)
		{
			if (worldUid == 0L)
			{
				return false;
			}
			if (WorldUid != worldUid)
			{
				LoadForWorld(worldUid);
			}
			return !ProgressionPersistence.RecoveryRequired;
		}

		internal static bool Promote(long playerId, string playerName, ProgressionTier tier)
		{
			return ImportLegacy(playerId, playerName, BossDefinitionRegistry.AllThrough(tier));
		}

		internal static bool ImportLegacy(long playerId, string playerName, BossCreditFlags importedCredits)
		{
			if (!PlayerIdentityRules.IsValid(playerId) || importedCredits == BossCreditFlags.None || !BossDefinitionRegistry.IsValid(importedCredits))
			{
				return false;
			}
			ProgressionTier progressionTier = BossDefinitionRegistry.HighestTier(importedCredits);
			lock (Sync)
			{
				if (Records.TryGetValue(playerId, out var value))
				{
					ProgressionTier progressionTier2 = ((value.Tier > progressionTier) ? value.Tier : progressionTier);
					string text = (string.IsNullOrWhiteSpace(playerName) ? value.PlayerName : playerName);
					BossCreditFlags bossCreditFlags = value.BossCredits | importedCredits;
					BossCreditFlags bossCreditFlags2 = BossProgressionRules.OpenReceipts(value.KillReceipts, bossCreditFlags);
					if (progressionTier2 == value.Tier && bossCreditFlags2 == value.KillReceipts && bossCreditFlags == value.BossCredits && string.Equals(text, value.PlayerName, StringComparison.Ordinal))
					{
						return false;
					}
					Records[playerId] = new PlayerProgressRecord(playerId, text, progressionTier2, bossCreditFlags2, bossCreditFlags);
					return true;
				}
				Records[playerId] = new PlayerProgressRecord(playerId, playerName, progressionTier, BossCreditFlags.None, importedCredits);
				return true;
			}
		}

		internal static bool RecordKill(long playerId, string playerName, ProgressionTier tier)
		{
			if (!PlayerIdentityRules.IsValid(playerId) || !BossDefinitionRegistry.TryFromTier(tier, out var _))
			{
				return false;
			}
			lock (Sync)
			{
				BossCreditFlags bossCreditFlags = BossDefinitionRegistry.FlagFor(tier);
				if (Records.TryGetValue(playerId, out var value))
				{
					string text = (string.IsNullOrWhiteSpace(playerName) ? value.PlayerName : playerName);
					if (BossDefinitionRegistry.Contains(value.BossCredits, tier))
					{
						return false;
					}
					if ((value.KillReceipts & bossCreditFlags) == bossCreditFlags && string.Equals(text, value.PlayerName, StringComparison.Ordinal))
					{
						return false;
					}
					Records[playerId] = new PlayerProgressRecord(playerId, text, value.Tier, value.KillReceipts | bossCreditFlags, value.BossCredits);
					return true;
				}
				Records[playerId] = new PlayerProgressRecord(playerId, playerName, ProgressionTier.None, bossCreditFlags, BossCreditFlags.None);
				return true;
			}
		}

		internal static bool GrantBossCredit(long playerId, string playerName, ProgressionTier tier)
		{
			if (!PlayerIdentityRules.IsValid(playerId) || !BossDefinitionRegistry.TryFromTier(tier, out var _))
			{
				return false;
			}
			lock (Sync)
			{
				BossCreditFlags bossCreditFlags = BossDefinitionRegistry.FlagFor(tier);
				if (!Records.TryGetValue(playerId, out var value) || (value.KillReceipts & bossCreditFlags) != bossCreditFlags)
				{
					return false;
				}
				string text = (string.IsNullOrWhiteSpace(playerName) ? value.PlayerName : playerName);
				ProgressionTier progressionTier = ((value.Tier > tier) ? value.Tier : tier);
				if ((value.BossCredits & bossCreditFlags) == bossCreditFlags && progressionTier == value.Tier && string.Equals(text, value.PlayerName, StringComparison.Ordinal))
				{
					return false;
				}
				BossCreditFlags bossCredits = value.BossCredits | bossCreditFlags;
				Records[playerId] = new PlayerProgressRecord(playerId, text, progressionTier, BossProgressionRules.OpenReceipts(value.KillReceipts, bossCredits), bossCredits);
				return true;
			}
		}

		internal static bool HasKillReceipt(long playerId, ProgressionTier tier)
		{
			lock (Sync)
			{
				PlayerProgressRecord value;
				return PlayerIdentityRules.IsValid(playerId) && Records.TryGetValue(playerId, out value) && BossProgressionRules.HasOpenReceipt(value.KillReceipts, value.BossCredits, tier);
			}
		}

		internal static bool HasBossCredit(long playerId, ProgressionTier tier)
		{
			lock (Sync)
			{
				PlayerProgressRecord value;
				return PlayerIdentityRules.IsValid(playerId) && Records.TryGetValue(playerId, out value) && BossDefinitionRegistry.Contains(value.BossCredits, tier);
			}
		}

		internal static PlayerProgressRecord Get(long playerId)
		{
			lock (Sync)
			{
				PlayerProgressRecord value;
				return (PlayerIdentityRules.IsValid(playerId) && Records.TryGetValue(playerId, out value)) ? value : null;
			}
		}

		internal static bool Set(long playerId, string playerName, ProgressionTier tier)
		{
			if (!PlayerIdentityRules.IsValid(playerId) || tier < ProgressionTier.None || tier > ProgressionTier.Fader)
			{
				return false;
			}
			lock (Sync)
			{
				Records[playerId] = new PlayerProgressRecord(playerId, playerName, tier);
			}
			return true;
		}

		internal static bool Reset(long playerId)
		{
			lock (Sync)
			{
				return Records.Remove(playerId);
			}
		}

		internal static ProgressionTier GetTier(long playerId)
		{
			lock (Sync)
			{
				PlayerProgressRecord value;
				return (PlayerIdentityRules.IsValid(playerId) && Records.TryGetValue(playerId, out value)) ? value.Tier : ProgressionTier.None;
			}
		}

		internal static List<PlayerProgressRecord> Snapshot()
		{
			lock (Sync)
			{
				return Records.Values.OrderBy<PlayerProgressRecord, string>((PlayerProgressRecord r) => r.PlayerName, StringComparer.OrdinalIgnoreCase).ThenBy((PlayerProgressRecord r) => r.PlayerId).ToList();
			}
		}

		internal static PlayerProgressRecord Find(string playerIdOrName)
		{
			if (string.IsNullOrWhiteSpace(playerIdOrName))
			{
				return null;
			}
			lock (Sync)
			{
				if (long.TryParse(playerIdOrName, out var result) && Records.TryGetValue(result, out var value))
				{
					return value;
				}
				return Records.Values.FirstOrDefault((PlayerProgressRecord record) => string.Equals(record.PlayerName, playerIdOrName, StringComparison.OrdinalIgnoreCase));
			}
		}

		internal static OfferingRecord FindOffering(string id)
		{
			lock (Sync)
			{
				OfferingRecord value;
				return (id != null && Offerings.TryGetValue(id, out value)) ? value : null;
			}
		}

		internal static void RecordOffering(OfferingRecord record)
		{
			lock (Sync)
			{
				if (record == null || !record.IsValid() || Offerings.ContainsKey(record.Id) || Offerings.Count >= 100000)
				{
					throw new InvalidOperationException("Invalid, duplicate or full offering ledger.");
				}
				Offerings.Add(record.Id, record);
			}
		}

		internal static List<OfferingRecord> OfferingHistory(long playerId)
		{
			lock (Sync)
			{
				return (from r in Offerings.Values
					where r.PlayerId == playerId
					orderby r.UtcTicks descending
					select r).ToList();
			}
		}

		internal static List<ProgressionTier> CommittedOfferingTiers()
		{
			lock (Sync)
			{
				return (from r in Offerings.Values
					where r.Accepted && !r.Recovery
					select r.Tier).Distinct().ToList();
			}
		}

		internal static void Save()
		{
			lock (Sync)
			{
				if (_worldUid == 0L)
				{
					return;
				}
				try
				{
					ProgressionPersistence.Save(_worldUid, Records.Values.ToList(), Offerings.Values.ToList());
					_committedRecords = Records.Values.ToList();
					_committedOfferings = Offerings.Values.ToList();
				}
				catch
				{
					RollbackPending();
					throw;
				}
			}
		}

		internal static void RollbackPending()
		{
			lock (Sync)
			{
				Records.Clear();
				foreach (PlayerProgressRecord committedRecord in _committedRecords)
				{
					Records.Add(committedRecord.PlayerId, committedRecord);
				}
				Offerings.Clear();
				foreach (OfferingRecord committedOffering in _committedOfferings)
				{
					Offerings.Add(committedOffering.Id, committedOffering);
				}
			}
		}

		internal static void Clear()
		{
			lock (Sync)
			{
				Records.Clear();
				Offerings.Clear();
				_committedRecords.Clear();
				_committedOfferings.Clear();
				_worldUid = 0L;
			}
		}
	}
}
namespace WorldStageDirector.Networking
{
	internal static class ClientProgressionState
	{
		private static readonly object Sync = new object();

		private static Dictionary<long, ProgressionTier> _tiers = new Dictionary<long, ProgressionTier>();

		private static Dictionary<long, BossCreditFlags> _killReceipts = new Dictionary<long, BossCreditFlags>();

		private static Dictionary<long, BossCreditFlags> _bossCredits = new Dictionary<long, BossCreditFlags>();

		private static GateSettings _settings = new GateSettings();

		internal static bool HasServerSnapshot { get; private set; }

		internal static long WorldUid { get; private set; }

		internal static ProgressionTier OnlineSafetyTier { get; private set; } = ProgressionTier.None;

		internal static void Replace(long worldUid, GateSettings settings, ProgressionTier onlineSafetyTier, Dictionary<long, ProgressionTier> tiers, Dictionary<long, BossCreditFlags> killReceipts, Dictionary<long, BossCreditFlags> bossCredits)
		{
			lock (Sync)
			{
				WorldUid = worldUid;
				_settings = settings?.Clone() ?? new GateSettings();
				OnlineSafetyTier = onlineSafetyTier;
				_tiers = tiers ?? new Dictionary<long, ProgressionTier>();
				_killReceipts = killReceipts ?? new Dictionary<long, BossCreditFlags>();
				_bossCredits = bossCredits ?? new Dictionary<long, BossCreditFlags>();
				HasServerSnapshot = true;
			}
			ExistingCreatureSafetyPatch.SweepLoaded();
			ExistingTormentSpawnerSafetyPatch.SweepLoaded();
			TrophyRules.ApplyLoadedPrefabs();
			BossStonePresentation.RefreshLoadedStones();
		}

		internal static GateSettings GetSettings()
		{
			lock (Sync)
			{
				return _settings.Clone();
			}
		}

		internal static ProgressionTier GetTier(long playerId)
		{
			lock (Sync)
			{
				ProgressionTier value;
				return (PlayerIdentityRules.IsValid(playerId) && _tiers.TryGetValue(playerId, out value)) ? value : ProgressionTier.None;
			}
		}

		internal static BossCreditFlags GetKillReceipts(long playerId)
		{
			lock (Sync)
			{
				BossCreditFlags value;
				return (PlayerIdentityRules.IsValid(playerId) && _killReceipts.TryGetValue(playerId, out value)) ? value : BossCreditFlags.None;
			}
		}

		internal static BossCreditFlags GetBossCredits(long playerId)
		{
			lock (Sync)
			{
				BossCreditFlags value;
				return (PlayerIdentityRules.IsValid(playerId) && _bossCredits.TryGetValue(playerId, out value)) ? value : BossCreditFlags.None;
			}
		}

		internal static bool HasLocalKillReceipt(ProgressionTier tier)
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null)
			{
				return BossDefinitionRegistry.Contains(GetKillReceipts(localPlayer.GetPlayerID()), tier);
			}
			return false;
		}

		internal static bool HasLocalBossCredit(ProgressionTier tier)
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null)
			{
				return BossDefinitionRegistry.Contains(GetBossCredits(localPlayer.GetPlayerID()), tier);
			}
			return false;
		}

		internal static void Reset()
		{
			lock (Sync)
			{
				WorldUid = 0L;
				OnlineSafetyTier = ProgressionTier.None;
				_settings = new GateSettings();
				_tiers = new Dictionary<long, ProgressionTier>();
				_killReceipts = new Dictionary<long, BossCreditFlags>();
				_bossCredits = new Dictionary<long, BossCreditFlags>();
				HasServerSnapshot = false;
			}
			BossStonePresentation.Clear();
		}
	}
	internal static class LegacyProgressionImport
	{
		internal const string ImportRpcName = "jg224.worldstagedirector.ImportLegacyProgression";

		internal const string ImportAckRpcName = "jg224.worldstagedirector.ImportLegacyProgressionAck";

		private const int ImportAttempts = 10;

		private const float InitialReplicationDelaySeconds = 2f;

		private const float RetryDelaySeconds = 2f;

		private static bool _importAcknowledged;

		internal static void Register(ZRoutedRpc rpc)
		{
			rpc.Register<ZPackage>("jg224.worldstagedirector.ImportLegacyProgression", (Action<long, ZPackage>)OnImportRequest);
			rpc.Register<ZPackage>("jg224.worldstagedirector.ImportLegacyProgressionAck", (Action<long, ZPackage>)OnImportAcknowledged);
		}

		internal static void Submit(Player player)
		{
			if (!Plugin.IsActive || (Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			BossCreditFlags bossCreditFlags = LegacyProgressionDetector.DetectFlags(player.GetUniqueKeys());
			if (bossCreditFlags == BossCreditFlags.None)
			{
				return;
			}
			_importAcknowledged = false;
			ZNet instance = ZNet.instance;
			ZRoutedRpc instance2 = ZRoutedRpc.instance;
			if (!((Object)(object)instance == (Object)null) && instance2 != null)
			{
				if (instance.IsServer())
				{
					Apply(player, bossCreditFlags);
				}
				else
				{
					((MonoBehaviour)player).StartCoroutine(SubmitAfterReplication(player, bossCreditFlags));
				}
			}
		}

		private static IEnumerator SubmitAfterReplication(Player player, BossCreditFlags credits)
		{
			yield return (object)new WaitForSeconds(2f);
			for (int attempt = 1; attempt <= 10; attempt++)
			{
				if (!Plugin.IsActive)
				{
					break;
				}
				if (_importAcknowledged)
				{
					break;
				}
				if ((Object)(object)player == (Object)null)
				{
					break;
				}
				if ((Object)(object)player != (Object)(object)Player.m_localPlayer)
				{
					break;
				}
				ZNet instance = ZNet.instance;
				ZRoutedRpc instance2 = ZRoutedRpc.instance;
				if ((Object)(object)instance == (Object)null || instance2 == null || instance.IsServer())
				{
					break;
				}
				ZPackage val = new ZPackage();
				val.Write((int)credits);
				instance2.InvokeRoutedRPC("jg224.worldstagedirector.ImportLegacyProgression", new object[1] { val });
				Plugin.Debug($"Submitted exact legacy boss-stone credits {credits} for local character " + $"{player.GetPlayerID()} (migration attempt {attempt}/{10}).");
				if (attempt < 10)
				{
					yield return (object)new WaitForSeconds(2f);
				}
			}
		}

		private static void OnImportRequest(long sender, ZPackage package)
		{
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Expected O, but got Unknown
			if (!Plugin.IsActive)
			{
				return;
			}
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || !instance.IsServer())
			{
				return;
			}
			BossCreditFlags bossCreditFlags = (BossCreditFlags)package.ReadInt();
			if (bossCreditFlags == BossCreditFlags.None || !BossDefinitionRegistry.IsValid(bossCreditFlags))
			{
				Plugin.Log.LogWarning((object)$"Rejected invalid legacy boss credits {(int)bossCreditFlags} from peer {sender}.");
				return;
			}
			try
			{
				if (!PeerCharacterIdentity.TryResolve(instance.GetPeer(sender), out var playerId, out var playerName))
				{
					Plugin.Log.LogWarning((object)$"Rejected legacy progression import from unresolved peer {sender}.");
					return;
				}
				Apply(playerId, playerName, bossCreditFlags);
				ZPackage val = new ZPackage();
				val.Write((int)bossCreditFlags);
				ZRoutedRpc instance2 = ZRoutedRpc.instance;
				if (instance2 != null)
				{
					instance2.InvokeRoutedRPC(sender, "jg224.worldstagedirector.ImportLegacyProgressionAck", new object[1] { val });
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Legacy progression import failed for peer {sender}: {arg}");
			}
		}

		private static void OnImportAcknowledged(long sender, ZPackage package)
		{
			if (!Plugin.IsActive)
			{
				return;
			}
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || instance.IsServer())
			{
				return;
			}
			ZNetPeer serverPeer = instance.GetServerPeer();
			if (serverPeer != null && serverPeer.m_uid == sender)
			{
				BossCreditFlags bossCreditFlags = (BossCreditFlags)package.ReadInt();
				if (bossCreditFlags != BossCreditFlags.None && BossDefinitionRegistry.IsValid(bossCreditFlags))
				{
					_importAcknowledged = true;
					Plugin.Debug($"Server acknowledged preserved legacy boss credits: {bossCreditFlags}.");
				}
			}
		}

		private static void Apply(Player player, BossCreditFlags credits)
		{
			if (!((Object)(object)player == (Object)null))
			{
				Apply(player.GetPlayerID(), player.GetPlayerName(), credits);
			}
		}

		private static void Apply(long playerId, string playerName, BossCreditFlags credits)
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || !instance.IsServer())
			{
				return;
			}
			if (!PlayerIdentityRules.IsValid(playerId))
			{
				Plugin.Log.LogWarning((object)$"Rejected legacy progression import for invalid PlayerID {playerId}.");
				return;
			}
			if (ProgressionRegistry.WorldUid == 0L)
			{
				ProgressionRegistry.LoadForWorld(instance.GetWorldUID());
			}
			bool num = ProgressionRegistry.ImportLegacy(playerId, playerName, credits);
			if (num)
			{
				ProgressionRegistry.Save();
			}
			ProgressionSync.BroadcastState();
			if (num)
			{
				Plugin.Log.LogInfo((object)("Imported legacy boss-stone progression for " + $"'{playerName}' ({playerId}): {credits} " + $"(highest {BossDefinitionRegistry.HighestTier(credits)})."));
			}
		}
	}
	[HarmonyPatch(typeof(Player), "OnSpawned", new Type[] { typeof(bool) })]
	internal static class PlayerSpawnedLegacyImportPatch
	{
		[HarmonyPostfix]
		private static void Postfix(Player __instance)
		{
			LegacyProgressionImport.Submit(__instance);
		}
	}
	[HarmonyPatch(typeof(ZNet), "Awake")]
	internal static class ZNetAwakePatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			ClientProgressionState.Reset();
			ServerBossFightLedger.Clear();
			PlayerGuidance.Reset();
			ProgressionSync.Register(ZRoutedRpc.instance);
		}
	}
	[HarmonyPatch(typeof(ZNet), "Start")]
	internal static class ZNetStartPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			ZNet instance = ZNet.instance;
			if (!((Object)(object)instance == (Object)null) && instance.IsServer() && ProgressionRegistry.EnsureLoadedForWorld(instance.GetWorldUID()))
			{
				TrophyRules.ApplyLoadedPrefabs();
				ProgressionSync.BroadcastState();
			}
		}
	}
	[HarmonyPatch(typeof(Game), "OnDestroy")]
	internal static class GameDestroyPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			ZNet instance = ZNet.instance;
			try
			{
				if ((Object)(object)instance != (Object)null && instance.IsServer())
				{
					ProgressionRegistry.Save();
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("World shutdown retained the previous durable registry: " + ex.Message));
			}
			ProgressionRegistry.Clear();
			ClientProgressionState.Reset();
			ServerBossFightLedger.Clear();
			BossStonePresentation.Clear();
			PlayerGuidance.Reset();
		}
	}
	[HarmonyPatch(typeof(ZNet), "RPC_CharacterID", new Type[]
	{
		typeof(ZRpc),
		typeof(ZDOID)
	})]
	internal static class CharacterIdReceivedPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
			{
				ProgressionSync.BroadcastState();
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "Disconnect", new Type[] { typeof(ZNetPeer) })]
	internal static class PeerDisconnectedPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
			{
				ProgressionSync.BroadcastState();
			}
		}
	}
	internal static class PeerCharacterIdentity
	{
		internal static bool TryResolve(ZNetPeer peer, out long playerId, out string playerName)
		{
			Vector3 position;
			return TryResolve(peer, out playerId, out playerName, out position);
		}

		internal static bool TryResolve(ZNetPeer peer, out long playerId, out string playerName, out Vector3 position)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			playerId = 0L;
			playerName = peer?.m_playerName ?? string.Empty;
			position = Vector3.zero;
			if (peer == null || peer.m_characterID == ZDOID.None || ZDOMan.instance == null)
			{
				return false;
			}
			ZDO zDO = ZDOMan.instance.GetZDO(peer.m_characterID);
			if (zDO == null || zDO.GetOwner() != peer.m_uid)
			{
				return false;
			}
			ZNetScene instance = ZNetScene.instance;
			GameObject val = ((instance != null) ? instance.GetPrefab(zDO.GetPrefab()) : null);
			if ((Object)(object)val == (Object)null || (Object)(object)val.GetComponent<Player>() == (Object)null)
			{
				return false;
			}
			playerId = zDO.GetLong(ZDOVars.s_playerID, 0L);
			string text = zDO.GetString(ZDOVars.s_playerName, string.Empty);
			if (!string.IsNullOrWhiteSpace(text))
			{
				playerName = text;
			}
			position = zDO.GetPosition();
			return PlayerIdentityRules.IsValid(playerId);
		}
	}
	internal static class ProgressionSync
	{
		internal const int ProtocolVersion = 9;

		private const int MaximumRecords = 10000;

		internal const string SyncRpcName = "jg224.worldstagedirector.SyncState";

		private static ZRoutedRpc _registeredInstance;

		private static Action<long> _newPeerHandler;

		internal static void Register(ZRoutedRpc rpc)
		{
			if (rpc == null)
			{
				return;
			}
			if (_registeredInstance != rpc)
			{
				if (_registeredInstance != null && _newPeerHandler != null)
				{
					ZRoutedRpc registeredInstance = _registeredInstance;
					registeredInstance.m_onNewPeer = (Action<long>)Delegate.Remove(registeredInstance.m_onNewPeer, _newPeerHandler);
				}
				_newPeerHandler = null;
				rpc.Register<ZPackage>("jg224.worldstagedirector.SyncState", (Action<long, ZPackage>)OnSyncState);
				LegacyProgressionImport.Register(rpc);
				BossProgressionRpc.Register(rpc);
				_registeredInstance = rpc;
				Plugin.Debug("Registered progression synchronization and legacy-import routed RPCs.");
			}
			if (_newPeerHandler != null)
			{
				rpc.m_onNewPeer = (Action<long>)Delegate.Remove(rpc.m_onNewPeer, _newPeerHandler);
				_newPeerHandler = null;
			}
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance != (Object)null && instance.IsServer())
			{
				_newPeerHandler = OnNewPeer;
				rpc.m_onNewPeer = (Action<long>)Delegate.Combine(rpc.m_onNewPeer, _newPeerHandler);
			}
		}

		private static void OnNewPeer(long peerUid)
		{
			if (Plugin.IsActive)
			{
				BroadcastState();
			}
		}

		internal static void BroadcastState()
		{
			if (!Plugin.IsActive)
			{
				return;
			}
			ZNet instance = ZNet.instance;
			ZRoutedRpc instance2 = ZRoutedRpc.instance;
			if ((Object)(object)instance == (Object)null || instance2 == null || !instance.IsServer() || !AuthoritativeStatePolicy.IsValidWorldSnapshot(ProgressionRegistry.WorldUid) || ProgressionPersistence.RecoveryRequired)
			{
				return;
			}
			RaidProgressionSafetyPatch.EnforceActiveEvent();
			foreach (ZNetPeer connectedPeer in instance.GetConnectedPeers())
			{
				if (connectedPeer != null)
				{
					SendState(connectedPeer.m_uid);
				}
			}
			ExistingCreatureSafetyPatch.SweepLoaded();
			ExistingTormentSpawnerSafetyPatch.SweepLoaded();
		}

		internal static void SendState(long peerUid)
		{
			if (!Plugin.IsActive)
			{
				return;
			}
			ZNet instance = ZNet.instance;
			ZRoutedRpc instance2 = ZRoutedRpc.instance;
			if ((Object)(object)instance == (Object)null || instance2 == null || !instance.IsServer() || !AuthoritativeStatePolicy.IsValidWorldSnapshot(ProgressionRegistry.WorldUid) || ProgressionPersistence.RecoveryRequired)
			{
				return;
			}
			try
			{
				ZPackage val = BuildPackage();
				instance2.InvokeRoutedRPC(peerUid, "jg224.worldstagedirector.SyncState", new object[1] { val });
				Plugin.Debug($"Sent progression snapshot to peer {peerUid} ({ProgressionRegistry.Count} records).");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)$"Could not send progression snapshot to peer {peerUid}: {ex.Message}");
			}
		}

		internal static void Shutdown()
		{
			if (_registeredInstance != null && _newPeerHandler != null)
			{
				ZRoutedRpc registeredInstance = _registeredInstance;
				registeredInstance.m_onNewPeer = (Action<long>)Delegate.Remove(registeredInstance.m_onNewPeer, _newPeerHandler);
			}
			_registeredInstance = null;
			_newPeerHandler = null;
			ClientProgressionState.Reset();
			BossProgressionRpc.Shutdown();
		}

		internal static ProgressionTier GetOnlineSafetyTier()
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || !instance.IsServer())
			{
				return ClientProgressionState.OnlineSafetyTier;
			}
			bool flag = false;
			ProgressionTier progressionTier = ProgressionTier.Fader;
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null)
			{
				flag = true;
				progressionTier = ProgressionRegistry.GetTier(localPlayer.GetPlayerID());
			}
			foreach (ZNetPeer connectedPeer in instance.GetConnectedPeers())
			{
				if (connectedPeer != null)
				{
					flag = true;
					long playerId;
					string playerName;
					ProgressionTier progressionTier2 = (PeerCharacterIdentity.TryResolve(connectedPeer, out playerId, out playerName) ? ProgressionRegistry.GetTier(playerId) : ProgressionTier.None);
					if (progressionTier2 < progressionTier)
					{
						progressionTier = progressionTier2;
					}
				}
			}
			if (!flag)
			{
				return ProgressionTier.None;
			}
			return progressionTier;
		}

		private static ZPackage BuildPackage()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Expected O, but got Unknown
			GateSettings settings = ModConfig.Snapshot();
			List<PlayerProgressRecord> list = ProgressionRegistry.Snapshot();
			ZPackage val = new ZPackage();
			val.Write(9);
			val.Write(ProgressionRegistry.WorldUid);
			WriteSettings(val, settings);
			val.Write((int)GetOnlineSafetyTier());
			val.Write(list.Count);
			foreach (PlayerProgressRecord item in list)
			{
				val.Write(item.PlayerId);
				val.Write((int)item.Tier);
				val.Write((int)item.KillReceipts);
				val.Write((int)item.BossCredits);
			}
			return val;
		}

		private static void OnSyncState(long sender, ZPackage package)
		{
			if (!Plugin.IsActive)
			{
				return;
			}
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || instance.IsServer())
			{
				return;
			}
			ZNetPeer serverPeer = instance.GetServerPeer();
			if (serverPeer == null || sender != serverPeer.m_uid)
			{
				Plugin.Log.LogWarning((object)$"Ignored progression snapshot from non-server peer {sender}.");
				return;
			}
			try
			{
				int num = package.ReadInt();
				if (num != 9)
				{
					throw new InvalidOperationException($"Unsupported WorldStageDirector protocol {num}; expected {9}.");
				}
				long num2 = package.ReadLong();
				if (!AuthoritativeStatePolicy.IsValidWorldSnapshot(num2))
				{
					Plugin.Log.LogWarning((object)"Ignored progression snapshot before the server's world registry was ready.");
					return;
				}
				GateSettings settings = ReadSettings(package);
				int num3 = package.ReadInt();
				if (num3 < 0 || num3 > 7)
				{
					throw new InvalidOperationException($"Invalid online safety tier {num3}.");
				}
				int num4 = package.ReadInt();
				if (num4 < 0 || num4 > 10000)
				{
					throw new InvalidOperationException($"Invalid progression record count {num4}.");
				}
				Dictionary<long, ProgressionTier> dictionary = new Dictionary<long, ProgressionTier>(num4);
				Dictionary<long, BossCreditFlags> dictionary2 = new Dictionary<long, BossCreditFlags>(num4);
				Dictionary<long, BossCreditFlags> dictionary3 = new Dictionary<long, BossCreditFlags>(num4);
				for (int i = 0; i < num4; i++)
				{
					long num5 = package.ReadLong();
					int num6 = package.ReadInt();
					BossCreditFlags bossCreditFlags = (BossCreditFlags)package.ReadInt();
					BossCreditFlags bossCreditFlags2 = (BossCreditFlags)package.ReadInt();
					if (PlayerIdentityRules.IsValid(num5) && num6 >= 0 && num6 <= 7 && BossDefinitionRegistry.IsValid(bossCreditFlags) && BossDefinitionRegistry.IsValid(bossCreditFlags2))
					{
						dictionary[num5] = (ProgressionTier)num6;
						dictionary2[num5] = bossCreditFlags;
						dictionary3[num5] = bossCreditFlags2;
					}
				}
				ProgressionTier progressionTier = (ProgressionTier)num3;
				ClientProgressionState.Replace(num2, settings, progressionTier, dictionary, dictionary2, dictionary3);
				Plugin.Log.LogInfo((object)($"Received authoritative progression snapshot for world {num2}: " + $"{dictionary.Count} player(s), online safety tier {progressionTier}."));
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Rejected invalid progression snapshot: {arg}");
				ClientProgressionState.Reset();
			}
		}

		private static void WriteSettings(ZPackage package, GateSettings settings)
		{
			package.Write(settings.Enabled);
			package.Write(settings.NearbyPlayerRadius);
			package.Write(settings.ProtectLeastProgressedOnline);
			package.Write(settings.UseLowestNearbyProgression);
			package.Write(settings.GateYagluthRoamers);
			package.Write(settings.GateQueenRoamers);
			package.Write(settings.GateFaderRoamers);
			package.Write(settings.GateEarlyGameRoamers);
			package.Write(settings.BlockTormentSpawnersOutsideAshlands);
			package.Write(settings.LimitRaidsToOnlineProgression);
			package.Write(settings.BossProgressionEnabled);
			package.Write(settings.AutoEnablePlayerEvents);
			package.Write(settings.BossKillWitnessRadius);
			package.Write(settings.SacrificeTrophyRange);
			package.Write(settings.BossLootOnSacrifice);
			package.Write((int)settings.CreditedPlayerDeathDrops);
			package.Write(settings.SacrificeLootDelaySeconds);
			package.Write(settings.BossTrophyWeight);
			package.Write(settings.BossTrophyMaxStackSize);
			package.Write(settings.BossTrophyAutoPickup);
			package.Write((int)settings.BossTrophyPortalRestriction);
			package.Write(settings.VerboseLogging);
			package.Write(settings.LogBlockedSpawns);
			package.Write(settings.LogAllowedProgressionSpawns);
		}

		private static GateSettings ReadSettings(ZPackage package)
		{
			return new GateSettings
			{
				Enabled = package.ReadBool(),
				NearbyPlayerRadius = package.ReadSingle(),
				ProtectLeastProgressedOnline = package.ReadBool(),
				UseLowestNearbyProgression = package.ReadBool(),
				GateYagluthRoamers = package.ReadBool(),
				GateQueenRoamers = package.ReadBool(),
				GateFaderRoamers = package.ReadBool(),
				GateEarlyGameRoamers = package.ReadBool(),
				BlockTormentSpawnersOutsideAshlands = package.ReadBool(),
				LimitRaidsToOnlineProgression = package.ReadBool(),
				BossProgressionEnabled = package.ReadBool(),
				AutoEnablePlayerEvents = package.ReadBool(),
				BossKillWitnessRadius = package.ReadSingle(),
				SacrificeTrophyRange = package.ReadSingle(),
				BossLootOnSacrifice = package.ReadBool(),
				CreditedPlayerDeathDrops = (CreditedPlayerDropMode)package.ReadInt(),
				SacrificeLootDelaySeconds = package.ReadSingle(),
				BossTrophyWeight = package.ReadSingle(),
				BossTrophyMaxStackSize = package.ReadInt(),
				BossTrophyAutoPickup = package.ReadBool(),
				BossTrophyPortalRestriction = (TrophyPortalRestriction)package.ReadInt(),
				VerboseLogging = package.ReadBool(),
				LogBlockedSpawns = package.ReadBool(),
				LogAllowedProgressionSpawns = package.ReadBool()
			};
		}
	}
}
namespace WorldStageDirector.Core
{
	public static class ArenaEnemyExemptionPolicy
	{
		public static bool IsSkaldHallArenaEnemy(int enemyTag, string arenaId, string sessionId, string enemyId)
		{
			if (enemyTag == 1 && IsGuid(arenaId) && IsGuid(sessionId))
			{
				return IsGuid(enemyId);
			}
			return false;
		}

		private static bool IsGuid(string value)
		{
			Guid result;
			if (!string.IsNullOrWhiteSpace(value))
			{
				return Guid.TryParse(value, out result);
			}
			return false;
		}
	}
	internal static class AuthoritativeStatePolicy
	{
		internal static bool IsValidWorldSnapshot(long worldUid)
		{
			return worldUid != 0;
		}

		internal static bool CanEnforce(bool networkAvailable, bool isServer, bool hasServerSnapshot)
		{
			if (networkAvailable)
			{
				return isServer || hasServerSnapshot;
			}
			return false;
		}
	}
	[Flags]
	public enum BossCreditFlags
	{
		None = 0,
		Eikthyr = 1,
		Elder = 2,
		Bonemass = 4,
		Moder = 8,
		Yagluth = 0x10,
		Queen = 0x20,
		Fader = 0x40,
		All = 0x7F
	}
	public sealed class BossDefinition
	{
		public ProgressionTier Tier { get; }

		public string DefeatKey { get; }

		public string GuardianPowerName { get; }

		public string TrophyPrefabName { get; }

		public string BossPrefabName { get; }

		public BossDefinition(ProgressionTier tier, string defeatKey, string guardianPowerName, string trophyPrefabName, string bossPrefabName)
		{
			Tier = tier;
			DefeatKey = defeatKey;
			GuardianPowerName = guardianPowerName;
			TrophyPrefabName = trophyPrefabName;
			BossPrefabName = bossPrefabName;
		}
	}
	public static class BossDefinitionRegistry
	{
		private static readonly BossDefinition[] Definitions;

		private static readonly Dictionary<string, BossDefinition> ByDefeatKey;

		private static readonly Dictionary<string, BossDefinition> ByGuardianPower;

		private static readonly Dictionary<string, BossDefinition> ByTrophy;

		private static readonly Dictionary<string, BossDefinition> ByBossPrefab;

		public static IReadOnlyList<BossDefinition> All => Definitions;

		static BossDefinitionRegistry()
		{
			Definitions = new BossDefinition[7]
			{
				new BossDefinition(ProgressionTier.Eikthyr, "defeated_eikthyr", "GP_Eikthyr", "TrophyEikthyr", "Eikthyr"),
				new BossDefinition(ProgressionTier.Elder, "defeated_gdking", "GP_TheElder", "TrophyTheElder", "gd_king"),
				new BossDefinition(ProgressionTier.Bonemass, "defeated_bonemass", "GP_Bonemass", "TrophyBonemass", "Bonemass"),
				new BossDefinition(ProgressionTier.Moder, "defeated_dragon", "GP_Moder", "TrophyDragonQueen", "Dragon"),
				new BossDefinition(ProgressionTier.Yagluth, "defeated_goblinking", "GP_Yagluth", "TrophyGoblinKing", "GoblinKing"),
				new BossDefinition(ProgressionTier.Queen, "defeated_queen", "GP_Queen", "TrophySeekerQueen", "SeekerQueen"),
				new BossDefinition(ProgressionTier.Fader, "defeated_fader", "GP_Ashlands", "TrophyFader", "Fader")
			};
			ByDefeatKey = Build((BossDefinition definition) => definition.DefeatKey);
			ByGuardianPower = Build((BossDefinition definition) => definition.GuardianPowerName);
			ByTrophy = Build((BossDefinition definition) => definition.TrophyPrefabName);
			ByBossPrefab = Build((BossDefinition definition) => definition.BossPrefabName);
			ByGuardianPower["GP_Fader"] = Definitions[6];
		}

		public static bool TryFromDefeatKey(string defeatKey, out BossDefinition definition)
		{
			return TryGet(ByDefeatKey, defeatKey, out definition);
		}

		public static bool TryFromGuardianPower(string guardianPowerName, out BossDefinition definition)
		{
			return TryGet(ByGuardianPower, guardianPowerName, out definition);
		}

		public static bool TryFromTrophy(string trophyPrefabName, out BossDefinition definition)
		{
			return TryGet(ByTrophy, trophyPrefabName, out definition);
		}

		public static bool TryFromBossPrefab(string bossPrefabName, out BossDefinition definition)
		{
			return TryGet(ByBossPrefab, bossPrefabName, out definition);
		}

		public static bool TryFromTier(ProgressionTier tier, out BossDefinition definition)
		{
			if (tier > ProgressionTier.None && tier <= ProgressionTier.Fader)
			{
				definition = Definitions[(int)(tier - 1)];
				return true;
			}
			definition = null;
			return false;
		}

		public static BossCreditFlags FlagFor(ProgressionTier tier)
		{
			if (tier <= ProgressionTier.None || tier > ProgressionTier.Fader)
			{
				return BossCreditFlags.None;
			}
			return (BossCreditFlags)(1 << (int)(tier - 1));
		}

		public static BossCreditFlags AllThrough(ProgressionTier tier)
		{
			if (tier <= ProgressionTier.None)
			{
				return BossCreditFlags.None;
			}
			if (tier >= ProgressionTier.Fader)
			{
				return BossCreditFlags.All;
			}
			return (BossCreditFlags)((1 << (int)tier) - 1);
		}

		public static bool Contains(BossCreditFlags flags, ProgressionTier tier)
		{
			BossCreditFlags bossCreditFlags = FlagFor(tier);
			if (bossCreditFlags != BossCreditFlags.None)
			{
				return (flags & bossCreditFlags) == bossCreditFlags;
			}
			return false;
		}

		public static bool IsValid(BossCreditFlags flags)
		{
			return (flags & ~BossCreditFlags.All) == 0;
		}

		public static ProgressionTier HighestTier(BossCreditFlags flags)
		{
			for (int num = 7; num > 0; num--)
			{
				ProgressionTier progressionTier = (ProgressionTier)num;
				if (Contains(flags, progressionTier))
				{
					return progressionTier;
				}
			}
			return ProgressionTier.None;
		}

		private static Dictionary<string, BossDefinition> Build(Func<BossDefinition, string> keySelector)
		{
			Dictionary<string, BossDefinition> dictionary = new Dictionary<string, BossDefinition>(StringComparer.OrdinalIgnoreCase);
			BossDefinition[] definitions = Definitions;
			foreach (BossDefinition bossDefinition in definitions)
			{
				dictionary[keySelector(bossDefinition)] = bossDefinition;
			}
			return dictionary;
		}

		private static bool TryGet(Dictionary<string, BossDefinition> map, string key, out BossDefinition definition)
		{
			if (!string.IsNullOrWhiteSpace(key) && map.TryGetValue(key.Trim(), out definition))
			{
				return true;
			}
			definition = null;
			return false;
		}
	}
	public sealed class BossParticipationLedger<TBossId>
	{
		private sealed class Fight
		{
			internal readonly HashSet<long> Participants = new HashSet<long>();

			internal bool Settling;

			internal bool Completed;
		}

		private readonly Dictionary<TBossId, Fight> _fights = new Dictionary<TBossId, Fight>();

		private readonly int _maximumBosses;

		private readonly int _maximumParticipants;

		public bool AtCapacity => _fights.Count >= _maximumBosses;

		public BossParticipationLedger(int maximumBosses = 1024, int maximumParticipants = 100)
		{
			if (maximumBosses <= 0)
			{
				throw new ArgumentOutOfRangeException("maximumBosses");
			}
			if (maximumParticipants <= 0)
			{
				throw new ArgumentOutOfRangeException("maximumParticipants");
			}
			_maximumBosses = maximumBosses;
			_maximumParticipants = maximumParticipants;
		}

		public bool RecordDamage(TBossId bossId, long playerId)
		{
			if (!PlayerIdentityRules.IsValid(playerId) || !TryGetOrCreate(bossId, out var fight) || fight.Settling || fight.Completed)
			{
				return false;
			}
			if (fight.Participants.Contains(playerId))
			{
				return true;
			}
			if (fight.Participants.Count < _maximumParticipants)
			{
				return fight.Participants.Add(playerId);
			}
			return false;
		}

		public bool HasDamaged(TBossId bossId, long playerId)
		{
			if (IsValidBoss(bossId) && PlayerIdentityRules.IsValid(playerId) && _fights.TryGetValue(bossId, out var value))
			{
				return value.Participants.Contains(playerId);
			}
			return false;
		}

		public bool TrySettle(TBossId bossId)
		{
			if (!TryGetOrCreate(bossId, out var fight) || fight.Settling || fight.Completed)
			{
				return false;
			}
			fight.Settling = true;
			return true;
		}

		public void Retry(TBossId bossId)
		{
			if (IsValidBoss(bossId) && _fights.TryGetValue(bossId, out var value) && !value.Completed)
			{
				value.Settling = false;
			}
		}

		public void Complete(TBossId bossId)
		{
			if (IsValidBoss(bossId) && _fights.TryGetValue(bossId, out var value) && value.Settling)
			{
				value.Completed = true;
				value.Participants.Clear();
			}
		}

		public void RemoveMissing(Func<TBossId, bool> bossExists)
		{
			if (bossExists == null)
			{
				throw new ArgumentNullException("bossExists");
			}
			List<TBossId> list = new List<TBossId>();
			foreach (KeyValuePair<TBossId, Fight> fight in _fights)
			{
				if ((!fight.Value.Settling || fight.Value.Completed) && !bossExists(fight.Key))
				{
					list.Add(fight.Key);
				}
			}
			foreach (TBossId item in list)
			{
				_fights.Remove(item);
			}
		}

		public void Clear()
		{
			_fights.Clear();
		}

		private bool TryGetOrCreate(TBossId bossId, out Fight fight)
		{
			fight = null;
			if (!IsValidBoss(bossId))
			{
				return false;
			}
			if (_fights.TryGetValue(bossId, out fight))
			{
				return true;
			}
			if (AtCapacity)
			{
				return false;
			}
			_fights.Add(bossId, fight = new Fight());
			return true;
		}

		private static bool IsValidBoss(TBossId bossId)
		{
			return !EqualityComparer<TBossId>.Default.Equals(bossId, default(TBossId));
		}
	}
	public static class BossDamageParticipation
	{
		public static bool AcceptedDamageOverTime(float mitigatedDamage, bool accepted)
		{
			if (accepted && !float.IsNaN(mitigatedDamage) && !float.IsInfinity(mitigatedDamage))
			{
				return mitigatedDamage > 0f;
			}
			return false;
		}

		public static bool PoisonDamageIncreased(float before, float after, float mitigatedDamage)
		{
			if (AcceptedDamageOverTime(mitigatedDamage, accepted: true) && !float.IsNaN(before) && !float.IsInfinity(before) && after == mitigatedDamage)
			{
				return after > before;
			}
			return false;
		}

		public static bool CausedHealthLoss(float before, float after)
		{
			if (!float.IsNaN(before) && !float.IsInfinity(before) && !float.IsNaN(after) && !float.IsInfinity(after) && before > 0f)
			{
				return after < before;
			}
			return false;
		}
	}
	public static class BossProgressionMigration
	{
		public static bool TryNormalize(int schemaVersion, ProgressionTier tier, int storedKillReceipts, int storedBossCredits, out BossCreditFlags killReceipts, out BossCreditFlags bossCredits)
		{
			killReceipts = BossCreditFlags.None;
			bossCredits = BossCreditFlags.None;
			if (tier < ProgressionTier.None || tier > ProgressionTier.Fader)
			{
				return false;
			}
			switch (schemaVersion)
			{
			case 1:
				bossCredits = BossDefinitionRegistry.AllThrough(tier);
				killReceipts = BossCreditFlags.None;
				return true;
			default:
				return false;
			case 2:
				bossCredits = (BossCreditFlags)storedBossCredits;
				if (!BossDefinitionRegistry.IsValid((BossCreditFlags)storedKillReceipts) || !BossDefinitionRegistry.IsValid(bossCredits))
				{
					return false;
				}
				killReceipts = BossProgressionRules.OpenReceipts((BossCreditFlags)storedKillReceipts, bossCredits);
				return true;
			}
		}
	}
	public static class BossProgressionRules
	{
		public static bool CanRecordKill(float horizontalDistance, float witnessRadius)
		{
			if (witnessRadius > 0f && horizontalDistance >= 0f)
			{
				return horizontalDistance <= witnessRadius;
			}
			return false;
		}

		public static bool CanRecordParticipatingKill(bool dealtDamage, float horizontalDistance, float witnessRadius)
		{
			if (dealtDamage)
			{
				return CanRecordKill(horizontalDistance, witnessRadius);
			}
			return false;
		}

		public static bool CanRequestSacrifice(BossCreditFlags killReceipts, ProgressionTier tier, bool trophyMatches, float horizontalDistance, float sacrificeRange)
		{
			if (trophyMatches && sacrificeRange > 0f && horizontalDistance >= 0f && horizontalDistance <= sacrificeRange)
			{
				return BossDefinitionRegistry.Contains(killReceipts, tier);
			}
			return false;
		}

		public static bool CanReceiveCredit(BossCreditFlags killReceipts, ProgressionTier tier, float horizontalDistance, float sacrificeRange)
		{
			if (sacrificeRange > 0f && horizontalDistance >= 0f && horizontalDistance <= sacrificeRange)
			{
				return BossDefinitionRegistry.Contains(killReceipts, tier);
			}
			return false;
		}

		public static bool ShouldPlaySacrificeEffects(bool effectRequested, bool sacrificeActive)
		{
			return effectRequested && sacrificeActive;
		}

		public static bool HasOpenReceipt(BossCreditFlags killReceipts, BossCreditFlags bossCredits, ProgressionTier tier)
		{
			if (BossDefinitionRegistry.Contains(killReceipts, tier))
			{
				return !BossDefinitionRegistry.Contains(bossCredits, tier);
			}
			return false;
		}

		public static BossCreditFlags OpenReceipts(BossCreditFlags killReceipts, BossCreditFlags bossCredits)
		{
			return killReceipts & ~bossCredits & BossCreditFlags.All;
		}

		public static bool CanReceivePersonalDeathLoot(BossCreditFlags killReceipts, BossCreditFlags bossCredits, ProgressionTier tier, bool bossLootOnSacrifice, CreditedPlayerDropMode creditedPlayerDropMode)
		{
			if (BossDefinitionRegistry.TryFromTier(tier, out var _) && creditedPlayerDropMode >= CreditedPlayerDropMode.Nothing && creditedPlayerDropMode <= CreditedPlayerDropMode.ReservedLoot)
			{
				if (!BossDefinitionRegistry.Contains(bossCredits, tier))
				{
					return !BossDefinitionRegistry.Contains(killReceipts, tier);
				}
				return creditedPlayerDropMode == CreditedPlayerDropMode.ReservedLoot;
			}
			return false;
		}

		public static int ScaleRewardAmount(int rolledAmount, int eligiblePlayers, bool onePerPlayer)
		{
			if (rolledAmount <= 0 || eligiblePlayers <= 0)
			{
				return 0;
			}
			if (onePerPlayer)
			{
				return eligiblePlayers;
			}
			return (int)Math.Min((long)rolledAmount * (long)eligiblePlayers, 10000L);
		}
	}
	public static class BossTierMapping
	{
		public static bool TryMapGuardianPower(string guardianPowerName, out ProgressionTier tier)
		{
			if (BossDefinitionRegistry.TryFromGuardianPower(guardianPowerName, out var definition))
			{
				tier = definition.Tier;
				return true;
			}
			tier = ProgressionTier.None;
			return false;
		}
	}
	public enum CreditedPlayerDropMode
	{
		Nothing,
		ReservedLoot
	}
	public sealed class GateSettings
	{
		public bool Enabled { get; set; } = true;

		public float NearbyPlayerRadius { get; set; } = 100f;

		public bool ProtectLeastProgressedOnline { get; set; } = true;

		public bool UseLowestNearbyProgression { get; set; } = true;

		public bool GateYagluthRoamers { get; set; } = true;

		public bool GateQueenRoamers { get; set; } = true;

		public bool GateFaderRoamers { get; set; } = true;

		public bool GateEarlyGameRoamers { get; set; } = true;

		public bool BlockTormentSpawnersOutsideAshlands { get; set; } = true;

		public bool LimitRaidsToOnlineProgression { get; set; } = true;

		public bool BossProgressionEnabled { get; set; } = true;

		public bool AutoEnablePlayerEvents { get; set; } = true;

		public float BossKillWitnessRadius { get; set; } = 100f;

		public float SacrificeTrophyRange { get; set; } = 25f;

		public bool BossLootOnSacrifice { get; set; }

		public CreditedPlayerDropMode CreditedPlayerDeathDrops { get; set; } = CreditedPlayerDropMode.ReservedLoot;

		public float SacrificeLootDelaySeconds { get; set; } = 12f;

		public float BossTrophyWeight { get; set; } = -1f;

		public int BossTrophyMaxStackSize { get; set; } = 1;

		public bool BossTrophyAutoPickup { get; set; }

		public TrophyPortalRestriction BossTrophyPortalRestriction { get; set; } = TrophyPortalRestriction.Vanilla;

		public bool VerboseLogging { get; set; }

		public bool LogBlockedSpawns { get; set; } = true;

		public bool LogAllowedProgressionSpawns { get; set; }

		public GateSettings Clone()
		{
			return new GateSettings
			{
				Enabled = Enabled,
				NearbyPlayerRadius = NearbyPlayerRadius,
				ProtectLeastProgressedOnline = ProtectLeastProgressedOnline,
				UseLowestNearbyProgression = UseLowestNearbyProgression,
				GateYagluthRoamers = GateYagluthRoamers,
				GateQueenRoamers = GateQueenRoamers,
				GateFaderRoamers = GateFaderRoamers,
				GateEarlyGameRoamers = GateEarlyGameRoamers,
				BlockTormentSpawnersOutsideAshlands = BlockTormentSpawnersOutsideAshlands,
				LimitRaidsToOnlineProgression = LimitRaidsToOnlineProgression,
				BossProgressionEnabled = BossProgressionEnabled,
				AutoEnablePlayerEvents = AutoEnablePlayerEvents,
				BossKillWitnessRadius = BossKillWitnessRadius,
				SacrificeTrophyRange = SacrificeTrophyRange,
				BossLootOnSacrifice = BossLootOnSacrifice,
				CreditedPlayerDeathDrops = CreditedPlayerDeathDrops,
				SacrificeLootDelaySeconds = SacrificeLootDelaySeconds,
				BossTrophyWeight = BossTrophyWeight,
				BossTrophyMaxStackSize = BossTrophyMaxStackSize,
				BossTrophyAutoPickup = BossTrophyAutoPickup,
				BossTrophyPortalRestriction = BossTrophyPortalRestriction,
				VerboseLogging = VerboseLogging,
				LogBlockedSpawns = LogBlockedSpawns,
				LogAllowedProgressionSpawns = LogAllowedProgressionSpawns
			};
		}
	}
	public static class IdentityPaths
	{
		public const string CurrentPluginGuid = "jg224.worldstagedirector";

		public const string LegacyPluginGuid = "jg224.progressguard";

		public static bool ShouldMigrate(bool currentExists, bool legacyExists)
		{
			return !currentExists && legacyExists;
		}

		public static string ConfigFileName(string pluginGuid)
		{
			return pluginGuid + ".cfg";
		}

		public static string RegistryFileName(string pluginGuid, long worldUid)
		{
			return pluginGuid + ".world-" + worldUid.ToString(CultureInfo.InvariantCulture) + ".json";
		}
	}
	public static class LegacyProgressionDetector
	{
		private static readonly KeyValuePair<string, ProgressionTier>[] GuardianPowerKeys = new KeyValuePair<string, ProgressionTier>[8]
		{
			new KeyValuePair<string, ProgressionTier>("GP_Eikthyr", ProgressionTier.Eikthyr),
			new KeyValuePair<string, ProgressionTier>("GP_TheElder", ProgressionTier.Elder),
			new KeyValuePair<string, ProgressionTier>("GP_Bonemass", ProgressionTier.Bonemass),
			new KeyValuePair<string, ProgressionTier>("GP_Moder", ProgressionTier.Moder),
			new KeyValuePair<string, ProgressionTier>("GP_Yagluth", ProgressionTier.Yagluth),
			new KeyValuePair<string, ProgressionTier>("GP_Queen", ProgressionTier.Queen),
			new KeyValuePair<string, ProgressionTier>("GP_Fader", ProgressionTier.Fader),
			new KeyValuePair<string, ProgressionTier>("GP_Ashlands", ProgressionTier.Fader)
		};

		public static ProgressionTier DetectHighest(IEnumerable<string> uniqueKeys)
		{
			return BossDefinitionRegistry.HighestTier(DetectFlags(uniqueKeys));
		}

		public static BossCreditFlags DetectFlags(IEnumerable<string> uniqueKeys)
		{
			if (uniqueKeys == null)
			{
				return BossCreditFlags.None;
			}
			HashSet<string> hashSet = new HashSet<string>(uniqueKeys, StringComparer.OrdinalIgnoreCase);
			BossCreditFlags bossCreditFlags = BossCreditFlags.None;
			KeyValuePair<string, ProgressionTier>[] guardianPowerKeys = GuardianPowerKeys;
			for (int i = 0; i < guardianPowerKeys.Length; i++)
			{
				KeyValuePair<string, ProgressionTier> keyValuePair = guardianPowerKeys[i];
				if (hashSet.Contains(keyValuePair.Key))
				{
					bossCreditFlags |= BossDefinitionRegistry.FlagFor(keyValuePair.Value);
				}
			}
			return bossCreditFlags;
		}
	}
	[DataContract]
	public sealed class OfferingRecord
	{
		[DataMember(Order = 1)]
		public string Id;

		[DataMember(Order = 2)]
		public long PlayerId;

		[DataMember(Order = 3)]
		public ProgressionTier Tier;

		[DataMember(Order = 4)]
		public bool Accepted;

		[DataMember(Order = 5)]
		public bool Recovery;

		[DataMember(Order = 6)]
		public long UtcTicks;

		[DataMember(Order = 7)]
		public string Reason = string.Empty;

		public bool IsValid()
		{
			if (Guid.TryParseExact(Id, "N", out var _) && PlayerIdentityRules.IsValid(PlayerId) && Tier >= ProgressionTier.Eikthyr && Tier <= ProgressionTier.Fader && UtcTicks > 0)
			{
				long utcTicks = UtcTicks;
				DateTime maxValue = DateTime.MaxValue;
				if (utcTicks <= maxValue.Ticks)
				{
					if (Reason != null)
					{
						if (Reason.Length <= 120)
						{
							return !Reason.Any(char.IsControl);
						}
						return false;
					}
					return true;
				}
			}
			return false;
		}

		public bool Matches(long playerId, ProgressionTier tier)
		{
			if (PlayerId == playerId && Tier == tier)
			{
				return !Recovery;
			}
			return false;
		}
	}
	public enum OfferingPhase
	{
		Prepared,
		Removed,
		Refunding,
		Accepted
	}
	[DataContract]
	public sealed class PendingOffering
	{
		[DataMember]
		public int Schema = 1;

		[DataMember]
		public string Id;

		[DataMember]
		public long World;

		[DataMember]
		public long Player;

		[DataMember]
		public ProgressionTier Tier;

		[DataMember]
		public string Trophy;

		[DataMember]
		public OfferingPhase Phase;

		public static PendingOffering Read(string path, long world, long player)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			if (!File.Exists(path))
			{
				return null;
			}
			using FileStream fileStream = File.OpenRead(path);
			if (fileStream.Length > 4096)
			{
				throw new IOException("Offering journal exceeds 4 KiB.");
			}
			PendingOffering obj = (PendingOffering)((XmlObjectSerializer)new DataContractJsonSerializer(typeof(PendingOffering))).ReadObject((Stream)fileStream);
			obj.Validate(world, player);
			return obj;
		}

		public void Write(string path)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			Validate(World, Player);
			Directory.CreateDirectory(Path.GetDirectoryName(path));
			string text = path + ".tmp";
			using (FileStream fileStream = new FileStream(text, FileMode.Create, FileAccess.Write, FileShare.None))
			{
				((XmlObjectSerializer)new DataContractJsonSerializer(typeof(PendingOffering))).WriteObject((Stream)fileStream, (object)this);
				fileStream.Flush(flushToDisk: true);
			}
			if (File.Exists(path))
			{
				File.Replace(text, path, path + ".bak");
			}
			else
			{
				File.Move(text, path);
			}
		}

		public void Validate(long world, long player)
		{
			if (Schema != 1 || World == 0L || World != world || !PlayerIdentityRules.IsValid(Player) || Player != player || !Guid.TryParseExact(Id, "N", out var _) || Phase < OfferingPhase.Prepared || Phase > OfferingPhase.Accepted || !BossDefinitionRegistry.TryFromTrophy(Trophy, out var definition) || definition.Tier != Tier)
			{
				throw new IOException("Invalid or mismatched offering journal; preserve it for administrator review.");
			}
		}
	}
	public static class PersonalDropReservation
	{
		public static string EncodeOwner(long playerId)
		{
			if (!PlayerIdentityRules.IsValid(playerId))
			{
				return string.Empty;
			}
			return playerId.ToString(CultureInfo.InvariantCulture);
		}

		public static bool TryDecodeOwner(string value, out long playerId)
		{
			if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out playerId))
			{
				return PlayerIdentityRules.IsValid(playerId);
			}
			return false;
		}

		public static bool CanClaim(long reservedPlayerId, long claimingPlayerId)
		{
			if (PlayerIdentityRules.IsValid(reservedPlayerId))
			{
				return reservedPlayerId == claimingPlayerId;
			}
			return false;
		}
	}
	internal static class PlayerIdentityRules
	{
		internal static bool IsValid(long playerId)
		{
			return playerId != 0;
		}
	}
	public enum ProgressionTier
	{
		None,
		Eikthyr,
		Elder,
		Bonemass,
		Moder,
		Yagluth,
		Queen,
		Fader
	}
	public static class RaidRuleRegistry
	{
		private static readonly Dictionary<string, ProgressionTier> ExplicitTiers = new Dictionary<string, ProgressionTier>(StringComparer.OrdinalIgnoreCase)
		{
			["army_eikthyr"] = ProgressionTier.None,
			["hildirboss1"] = ProgressionTier.Eikthyr,
			["hildirboss2"] = ProgressionTier.Bonemass,
			["hildirboss3"] = ProgressionTier.Moder
		};

		public static bool TryGetRequiredTier(string eventName, IEnumerable<string> requiredGlobalKeys, out ProgressionTier tier)
		{
			bool flag = false;
			tier = ProgressionTier.None;
			if (requiredGlobalKeys != null)
			{
				foreach (string requiredGlobalKey in requiredGlobalKeys)
				{
					if (SpawnRuleRegistry.TryGetRequiredTier(requiredGlobalKey, out var tier2))
					{
						if (!flag || tier2 > tier)
						{
							tier = tier2;
						}
						flag = true;
					}
				}
			}
			if (flag)
			{
				return true;
			}
			return ExplicitTiers.TryGetValue((eventName ?? string.Empty).Trim(), out tier);
		}

		public static bool IsProgressionExempt(string eventName, NativeBiome eventBiome)
		{
			if (string.Equals((eventName ?? string.Empty).Trim(), "army_charredspawners", StringComparison.OrdinalIgnoreCase))
			{
				return (eventBiome & NativeBiome.Ashlands) != 0;
			}
			return false;
		}
	}
	public enum SpawnDecisionReason
	{
		AllowedDisabled,
		AllowedEvent,
		AllowedUnmanaged,
		AllowedGateDisabled,
		AllowedNativeBiome,
		AllowedNoNearbyPlayers,
		AllowedProgression,
		BlockedProgression
	}
	public sealed class SpawnDecision
	{
		public bool Block { get; }

		public SpawnDecisionReason Reason { get; }

		public ProgressionTier EffectiveTier { get; }

		public SpawnDecision(bool block, SpawnDecisionReason reason, ProgressionTier effectiveTier)
		{
			Block = block;
			Reason = reason;
			EffectiveTier = effectiveTier;
		}
	}
	public static class SpawnDecisionEngine
	{
		public static SpawnDecision Evaluate(SpawnRule rule, GateSettings settings, bool eventSpawner, bool inNativeBiome, IReadOnlyList<ProgressionTier> nearbyTiers)
		{
			if (settings == null || !settings.Enabled)
			{
				return Allow(SpawnDecisionReason.AllowedDisabled);
			}
			if (eventSpawner)
			{
				return Allow(SpawnDecisionReason.AllowedEvent);
			}
			if (rule == null)
			{
				return Allow(SpawnDecisionReason.AllowedUnmanaged);
			}
			if (!SpawnRuleRegistry.IsGateEnabled(rule, settings))
			{
				return Allow(SpawnDecisionReason.AllowedGateDisabled);
			}
			if (inNativeBiome)
			{
				return Allow(SpawnDecisionReason.AllowedNativeBiome);
			}
			if (nearbyTiers == null || nearbyTiers.Count == 0)
			{
				return Allow(SpawnDecisionReason.AllowedNoNearbyPlayers);
			}
			ProgressionTier progressionTier = nearbyTiers[0];
			for (int i = 1; i < nearbyTiers.Count; i++)
			{
				ProgressionTier progressionTier2 = nearbyTiers[i];
				if (settings.UseLowestNearbyProgression)
				{
					if (progressionTier2 < progressionTier)
					{
						progressionTier = progressionTier2;
					}
				}
				else if (progressionTier2 > progressionTier)
				{
					progressionTier = progressionTier2;
				}
			}
			if (progressionTier >= rule.RequiredTier)
			{
				return new SpawnDecision(block: false, SpawnDecisionReason.AllowedProgression, progressionTier);
			}
			return new SpawnDecision(block: true, SpawnDecisionReason.BlockedProgression, progressionTier);
		}

		private static SpawnDecision Allow(SpawnDecisionReason reason)
		{
			return new SpawnDecision(block: false, reason, ProgressionTier.None);
		}
	}
	[Flags]
	public enum NativeBiome
	{
		Unknown = 0,
		Meadows = 1,
		BlackForest = 2,
		Swamp = 4,
		Mountain = 8,
		Plains = 0x10,
		Mistlands = 0x20,
		Ashlands = 0x40
	}
	public sealed class SpawnRule
	{
		public string RequiredGlobalKey { get; }

		public string PrefabName { get; }

		public ProgressionTier RequiredTier { get; }

		public NativeBiome NativeBiome { get; }

		public SpawnRule(string requiredGlobalKey, string prefabName, ProgressionTier requiredTier, NativeBiome nativeBiome)
		{
			RequiredGlobalKey = requiredGlobalKey;
			PrefabName = prefabName;
			RequiredTier = requiredTier;
			NativeBiome = nativeBiome;
		}
	}
	public static class SpawnRuleRegistry
	{
		private static readonly Dictionary<string, SpawnRule> Rules = BuildRules();

		private static readonly Dictionary<string, SpawnRule> CreatureSafetyRules = BuildCreatureSafetyRules();

		public static bool TryGet(string requiredGlobalKey, string prefabName, out SpawnRule rule)
		{
			string key = BuildKey(requiredGlobalKey, prefabName);
			if (Rules.TryGetValue(key, out rule))
			{
				return true;
			}
			if (NormalizePrefab(prefabName) == "odin")
			{
				rule = null;
				return false;
			}
			if (TryGetRequiredTier(requiredGlobalKey, out var tier))
			{
				rule = new SpawnRule(requiredGlobalKey, prefabName, tier, NativeBiome.Unknown);
				return true;
			}
			rule = null;
			return false;
		}

		public static bool TryGetCreatureSafetyRule(string prefabName, out SpawnRule rule)
		{
			return CreatureSafetyRules.TryGetValue(NormalizePrefab(prefabName), out rule);
		}

		public static bool TryGetRequiredTier(string requiredGlobalKey, out ProgressionTier tier)
		{
			switch ((requiredGlobalKey ?? string.Empty).Trim().ToLowerInvariant())
			{
			case "defeated_eikthyr":
				tier = ProgressionTier.Eikthyr;
				return true;
			case "defeated_gdking":
				tier = ProgressionTier.Elder;
				return true;
			case "defeated_bonemass":
				tier = ProgressionTier.Bonemass;
				return true;
			case "defeated_dragon":
				tier = ProgressionTier.Moder;
				return true;
			case "defeated_goblinking":
				tier = ProgressionTier.Yagluth;
				return true;
			case "defeated_queen":
				tier = ProgressionTier.Queen;
				return true;
			case "defeated_fader":
				tier = ProgressionTier.Fader;
				return true;
			default:
				tier = ProgressionTier.None;
				return false;
			}
		}

		public static bool IsGateEnabled(SpawnRule rule, GateSettings settings)
		{
			if (rule == null || settings == null)
			{
				return false;
			}
			return rule.RequiredTier switch
			{
				ProgressionTier.Yagluth => settings.GateYagluthRoamers, 
				ProgressionTier.Queen => settings.GateQueenRoamers, 
				ProgressionTier.Fader => settings.GateFaderRoamers, 
				_ => settings.GateEarlyGameRoamers, 
			};
		}

		public static string NormalizePrefab(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return string.Empty;
			}
			StringBuilder stringBuilder = new StringBuilder(value.Length);
			foreach (char c in value)
			{
				if (char.IsLetterOrDigit(c))
				{
					stringBuilder.Append(char.ToLowerInvariant(c));
				}
			}
			if (stringBuilder.Length >= "clone".Length && stringBuilder.ToString().EndsWith("clone", StringComparison.Ordinal))
			{
				stringBuilder.Length -= "clone".Length;
			}
			return stringBuilder.ToString();
		}

		private static Dictionary<string, SpawnRule> BuildRules()
		{
			Dictionary<string, SpawnRule> dictionary = new Dictionary<string, SpawnRule>(StringComparer.Ordinal);
			Add(dictionary, "defeated_eikthyr", "Greydwarf", ProgressionTier.Eikthyr, NativeBiome.BlackForest);
			Add(dictionary, "defeated_gdking", "Draugr", ProgressionTier.Elder, NativeBiome.Swamp);
			Add(dictionary, "defeated_gdking", "Greydwarf_Elite", ProgressionTier.Elder, NativeBiome.BlackForest);
			Add(dictionary, "defeated_gdking", "Greydwarf_Shaman", ProgressionTier.Elder, NativeBiome.BlackForest);
			Add(dictionary, "defeated_bonemass", "Skeleton", ProgressionTier.Bonemass, NativeBiome.BlackForest | NativeBiome.Swamp);
			Add(dictionary, "defeated_goblinking", "Goblin", ProgressionTier.Yagluth, NativeBiome.Plains);
			Add(dictionary, "defeated_queen", "Seeker", ProgressionTier.Queen, NativeBiome.Mistlands);
			Add(dictionary, "defeated_queen", "SeekerBrood", ProgressionTier.Queen, NativeBiome.Mistlands);
			Add(dictionary, "defeated_queen", "Tick", ProgressionTier.Queen, NativeBiome.Mistlands);
			Add(dictionary, "defeated_fader", "Charred_Archer", ProgressionTier.Fader, NativeBiome.Ashlands);
			Add(dictionary, "defeated_fader", "Charred_Melee", ProgressionTier.Fader, NativeBiome.Ashlands);
			return dictionary;
		}

		private static Dictionary<string, SpawnRule> BuildCreatureSafetyRules()
		{
			Dictionary<string, SpawnRule> dictionary = new Dictionary<string, SpawnRule>(StringComparer.Ordinal);
			foreach (SpawnRule value in Rules.Values)
			{
				dictionary[NormalizePrefab(value.PrefabName)] = value;
			}
			return dictionary;
		}

		private static void Add(Dictionary<string, SpawnRule> rules, string globalKey, string prefab, ProgressionTier tier, NativeBiome biome)
		{
			SpawnRule value = new SpawnRule(globalKey, prefab, tier,