Decompiled source of ProgressGuard v0.1.0

ProgressGuard.dll

Decompiled 10 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using ProgressGuard.Commands;
using ProgressGuard.Config;
using ProgressGuard.Core;
using ProgressGuard.Integration;
using ProgressGuard.Networking;
using ProgressGuard.Progression;
using ProgressGuard.Spawning;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("jg224")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Per-character progression gates for Valheim ambient post-boss roamers.")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0+5d1bc7e28a96d49e3ea7a369558302aecdad7a8c")]
[assembly: AssemblyProduct("ProgressGuard")]
[assembly: AssemblyTitle("ProgressGuard")]
[assembly: AssemblyVersion("0.1.0.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 ProgressGuard
{
	[BepInPlugin("jg224.progressguard", "ProgressGuard", "0.1.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "jg224.progressguard";

		public const string PluginName = "ProgressGuard";

		public const string PluginVersion = "0.1.0";

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

		public const string ZenBossStoneGuid = "ZenDragon.ZenBossStone";

		private Harmony _harmony;

		internal static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			ModConfig.Bind(((BaseUnityPlugin)this).Config);
			ZenBossStoneIntegration.Initialize();
			_harmony = new Harmony("jg224.progressguard");
			_harmony.PatchAll(typeof(Plugin).Assembly);
			AdminCommands.Register();
			Log.LogInfo((object)("ProgressGuard v0.1.0 loaded. Required on server and every client. ZenBossStone " + ZenBossStoneIntegration.DetectedVersion + " detected."));
		}

		private void OnDestroy()
		{
			ProgressionRegistry.Save();
			ProgressionSync.Shutdown();
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		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 ProgressGuard.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}");
			}
		}
	}
	internal static class RuntimeSpawnValidator
	{
		internal static bool ShouldAllow(SpawnData spawn, Vector3 spawnPoint, bool eventSpawner)
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			if (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_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)monster == (Object)null || monster.IsEventCreature())
			{
				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 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_0015: 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_0028: 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)
			{
				return false;
			}
			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);
				return biome != NativeBiome.Unknown;
			}
			catch (Exception ex)
			{
				Plugin.Debug("Biome resolution failed: " + ex.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),
		typeof(int),
		typeof(float)
	})]
	internal static class AmbientSpawnPatch
	{
		[HarmonyPrefix]
		private static bool Prefix(SpawnData critter, Vector3 spawnPoint, bool eventSpawner)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			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.");
						}
					}
				}
			}
		}
	}
}
namespace ProgressGuard.Progression
{
	public sealed class PlayerProgressRecord
	{
		public long PlayerId { get; }

		public string PlayerName { get; }

		public ProgressionTier Tier { get; }

		public PlayerProgressRecord(long playerId, string playerName, ProgressionTier tier)
		{
			PlayerId = playerId;
			PlayerName = playerName ?? string.Empty;
			Tier = tier;
		}
	}
	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>();
		}

		[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;
		}

		private const int SchemaVersion = 1;

		internal static List<PlayerProgressRecord> Load(long worldUid)
		{
			string path = GetPath(worldUid);
			if (!File.Exists(path))
			{
				return new List<PlayerProgressRecord>();
			}
			try
			{
				using FileStream stream = File.OpenRead(path);
				RegistryFile registryFile = (RegistryFile)new DataContractJsonSerializer(typeof(RegistryFile)).ReadObject(stream);
				if (registryFile == null || registryFile.SchemaVersion != 1 || registryFile.WorldUid != worldUid)
				{
					throw new SerializationException("Registry schema or world UID does not match.");
				}
				List<PlayerProgressRecord> list = new List<PlayerProgressRecord>();
				if (registryFile.Players == null)
				{
					return list;
				}
				foreach (PlayerRecord player in registryFile.Players)
				{
					if (player.PlayerId > 0 && player.Tier >= 0 && player.Tier <= 7)
					{
						list.Add(new PlayerProgressRecord(player.PlayerId, player.PlayerName, (ProgressionTier)player.Tier));
					}
				}
				return list;
			}
			catch (Exception arg)
			{
				string text = path + ".corrupt-" + DateTime.UtcNow.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture);
				try
				{
					File.Copy(path, text, overwrite: false);
				}
				catch
				{
				}
				Plugin.Log.LogError((object)$"Could not load progression registry '{path}'. Starting empty; preserved copy: '{text}'. {arg}");
				return new List<PlayerProgressRecord>();
			}
		}

		internal static void Save(long worldUid, IReadOnlyCollection<PlayerProgressRecord> records)
		{
			if (worldUid == 0L)
			{
				return;
			}
			string path = GetPath(worldUid);
			string text = path + ".tmp";
			string destinationBackupFileName = path + ".bak";
			try
			{
				Directory.CreateDirectory(Path.GetDirectoryName(path));
				RegistryFile registryFile = new RegistryFile
				{
					SchemaVersion = 1,
					WorldUid = worldUid
				};
				foreach (PlayerProgressRecord record in records)
				{
					registryFile.Players.Add(new PlayerRecord
					{
						PlayerId = record.PlayerId,
						PlayerName = record.PlayerName,
						Tier = (int)record.Tier
					});
				}
				using (FileStream fileStream = File.Create(text))
				{
					new DataContractJsonSerializer(typeof(RegistryFile)).WriteObject(fileStream, registryFile);
					fileStream.Flush(flushToDisk: true);
				}
				if (File.Exists(path))
				{
					try
					{
						File.Replace(text, path, destinationBackupFileName);
						return;
					}
					catch
					{
						File.Copy(text, path, overwrite: true);
						File.Delete(text);
						return;
					}
				}
				File.Move(text, path);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Could not save progression registry '{path}': {arg}");
				try
				{
					if (File.Exists(text))
					{
						File.Delete(text);
					}
				}
				catch
				{
				}
			}
		}

		internal static string GetPath(long worldUid)
		{
			return Path.Combine(Paths.ConfigPath, "jg224.progressguard.world-" + worldUid.ToString(CultureInfo.InvariantCulture) + ".json");
		}
	}
	internal static class ProgressionRegistry
	{
		private static readonly object Sync = new object();

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

		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)
		{
			lock (Sync)
			{
				_worldUid = worldUid;
				Records.Clear();
				foreach (PlayerProgressRecord item in ProgressionPersistence.Load(worldUid))
				{
					if (item.PlayerId > 0)
					{
						Records[item.PlayerId] = item;
					}
				}
			}
			Plugin.Log.LogInfo((object)$"Progression registry loaded for world {worldUid}: {Count} player(s).");
		}

		internal static bool Promote(long playerId, string playerName, ProgressionTier tier)
		{
			if (playerId <= 0 || tier <= ProgressionTier.None || tier > ProgressionTier.Fader)
			{
				return false;
			}
			lock (Sync)
			{
				if (Records.TryGetValue(playerId, out var value))
				{
					ProgressionTier progressionTier = ((value.Tier > tier) ? value.Tier : tier);
					string text = (string.IsNullOrWhiteSpace(playerName) ? value.PlayerName : playerName);
					if (progressionTier == value.Tier && string.Equals(text, value.PlayerName, StringComparison.Ordinal))
					{
						return false;
					}
					Records[playerId] = new PlayerProgressRecord(playerId, text, progressionTier);
					return progressionTier != value.Tier;
				}
				Records[playerId] = new PlayerProgressRecord(playerId, playerName, tier);
				return true;
			}
		}

		internal static bool Set(long playerId, string playerName, ProgressionTier tier)
		{
			if (playerId <= 0 || 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 (playerId > 0 && 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 void Save()
		{
			long worldUid;
			List<PlayerProgressRecord> records;
			lock (Sync)
			{
				worldUid = _worldUid;
				if (worldUid == 0L)
				{
					return;
				}
				records = Records.Values.ToList();
			}
			ProgressionPersistence.Save(worldUid, records);
		}

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

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

		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)
		{
			lock (Sync)
			{
				WorldUid = worldUid;
				_settings = settings?.Clone() ?? new GateSettings();
				OnlineSafetyTier = onlineSafetyTier;
				_tiers = tiers ?? new Dictionary<long, ProgressionTier>();
				HasServerSnapshot = true;
			}
			ExistingCreatureSafetyPatch.SweepLoaded();
		}

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

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

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

		internal static void Register(ZRoutedRpc rpc)
		{
			rpc.Register<int>("jg224.progressguard.ImportLegacyProgression", (Action<long, int>)OnImportRequest);
		}

		internal static void Submit(Player player)
		{
			if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			ProgressionTier progressionTier = LegacyProgressionDetector.DetectHighest(player.GetUniqueKeys());
			if (progressionTier == ProgressionTier.None)
			{
				return;
			}
			ZNet instance = ZNet.instance;
			ZRoutedRpc instance2 = ZRoutedRpc.instance;
			if (!((Object)(object)instance == (Object)null) && instance2 != null)
			{
				if (instance.IsServer())
				{
					Apply(player, progressionTier);
				}
				else
				{
					((MonoBehaviour)player).StartCoroutine(SubmitAfterReplication(player, progressionTier));
				}
			}
		}

		private static IEnumerator SubmitAfterReplication(Player player, ProgressionTier tier)
		{
			yield return (object)new WaitForSeconds(2f);
			if (!((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer))
			{
				ZNet instance = ZNet.instance;
				ZRoutedRpc instance2 = ZRoutedRpc.instance;
				if (!((Object)(object)instance == (Object)null) && instance2 != null && !instance.IsServer())
				{
					instance2.InvokeRoutedRPC("jg224.progressguard.ImportLegacyProgression", new object[1] { (int)tier });
					Plugin.Debug($"Submitted legacy ZenBossStone tier {tier} for local character {player.GetPlayerID()}.");
				}
			}
		}

		private static void OnImportRequest(long sender, int rawTier)
		{
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || !instance.IsServer())
			{
				return;
			}
			if (rawTier <= 0 || rawTier > 7)
			{
				Plugin.Log.LogWarning((object)$"Rejected invalid legacy progression tier {rawTier} from peer {sender}.");
				return;
			}
			try
			{
				ZNetPeer peer = instance.GetPeer(sender);
				object obj;
				if (peer != null)
				{
					ZNetScene instance2 = ZNetScene.instance;
					obj = ((instance2 != null) ? instance2.FindInstance(peer.m_characterID) : null);
				}
				else
				{
					obj = null;
				}
				Player val = ((obj != null) ? ((GameObject)obj).GetComponent<Player>() : null);
				if (peer == null || (Object)(object)val == (Object)null)
				{
					Plugin.Log.LogWarning((object)$"Rejected legacy progression import from unresolved peer {sender}.");
				}
				else
				{
					Apply(val, (ProgressionTier)rawTier);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Legacy progression import failed for peer {sender}: {arg}");
			}
		}

		private static void Apply(Player player, ProgressionTier tier)
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || !instance.IsServer())
			{
				return;
			}
			long playerID = player.GetPlayerID();
			if (playerID <= 0)
			{
				Plugin.Log.LogWarning((object)$"Rejected legacy progression import for invalid PlayerID {playerID}.");
				return;
			}
			if (ProgressionRegistry.WorldUid == 0L)
			{
				ProgressionRegistry.LoadForWorld(instance.GetWorldUID());
			}
			bool num = ProgressionRegistry.Promote(playerID, player.GetPlayerName(), tier);
			if (num)
			{
				ProgressionRegistry.Save();
			}
			ProgressionSync.BroadcastState();
			if (num)
			{
				Plugin.Log.LogInfo((object)("Imported legacy ZenBossStone progression for " + $"'{player.GetPlayerName()}' ({playerID}): {tier}."));
			}
		}
	}
	[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();
			ProgressionSync.Register(ZRoutedRpc.instance);
		}
	}
	[HarmonyPatch(typeof(Game), "Awake")]
	internal static class GameAwakePatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			ZNet instance = ZNet.instance;
			if (!((Object)(object)instance == (Object)null) && instance.IsServer())
			{
				ProgressionRegistry.LoadForWorld(instance.GetWorldUID());
				ProgressionSync.BroadcastState();
			}
		}
	}
	[HarmonyPatch(typeof(Game), "OnDestroy")]
	internal static class GameDestroyPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance != (Object)null && instance.IsServer())
			{
				ProgressionRegistry.Save();
			}
			ProgressionRegistry.Clear();
			ClientProgressionState.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 ProgressionSync
	{
		private const int ProtocolVersion = 2;

		private const int MaximumRecords = 10000;

		private const string SyncRpcName = "jg224.progressguard.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.progressguard.SyncState", (Action<long, ZPackage>)OnSyncState);
				LegacyProgressionImport.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)
		{
			BroadcastState();
		}

		internal static void BroadcastState()
		{
			ZNet instance = ZNet.instance;
			ZRoutedRpc instance2 = ZRoutedRpc.instance;
			if ((Object)(object)instance == (Object)null || instance2 == null || !instance.IsServer())
			{
				return;
			}
			foreach (ZNetPeer connectedPeer in instance.GetConnectedPeers())
			{
				if (connectedPeer != null)
				{
					SendState(connectedPeer.m_uid);
				}
			}
			ExistingCreatureSafetyPatch.SweepLoaded();
		}

		internal static void SendState(long peerUid)
		{
			ZNet instance = ZNet.instance;
			ZRoutedRpc instance2 = ZRoutedRpc.instance;
			if ((Object)(object)instance == (Object)null || instance2 == null || !instance.IsServer())
			{
				return;
			}
			try
			{
				ZPackage val = BuildPackage();
				instance2.InvokeRoutedRPC(peerUid, "jg224.progressguard.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();
		}

		internal static ProgressionTier GetOnlineSafetyTier()
		{
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			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;
					ZNetScene instance2 = ZNetScene.instance;
					GameObject obj = ((instance2 != null) ? instance2.FindInstance(connectedPeer.m_characterID) : null);
					Player val = ((obj != null) ? obj.GetComponent<Player>() : null);
					ProgressionTier progressionTier2 = ((!((Object)(object)val == (Object)null)) ? ProgressionRegistry.GetTier(val.GetPlayerID()) : 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(2);
			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);
			}
			return val;
		}

		private static void OnSyncState(long sender, ZPackage package)
		{
			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 != 2)
				{
					throw new InvalidOperationException($"Unsupported ProgressGuard protocol {num}; expected {2}.");
				}
				long num2 = package.ReadLong();
				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);
				for (int i = 0; i < num4; i++)
				{
					long num5 = package.ReadLong();
					int num6 = package.ReadInt();
					if (num5 > 0 && num6 >= 0 && num6 <= 7)
					{
						dictionary[num5] = (ProgressionTier)num6;
					}
				}
				ProgressionTier progressionTier = (ProgressionTier)num3;
				ClientProgressionState.Replace(num2, settings, progressionTier, dictionary);
				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.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(),
				VerboseLogging = package.ReadBool(),
				LogBlockedSpawns = package.ReadBool(),
				LogAllowedProgressionSpawns = package.ReadBool()
			};
		}
	}
}
namespace ProgressGuard.Integration
{
	internal static class ZenBossStoneIntegration
	{
		private static FieldInfo _sacrificeRangeField;

		internal static MethodInfo SacrificeHandler { get; private set; }

		internal static string DetectedVersion { get; private set; } = "unknown";

		internal static void Initialize()
		{
			if (Chainloader.PluginInfos.TryGetValue("ZenDragon.ZenBossStone", out var value))
			{
				DetectedVersion = value.Metadata.Version?.ToString() ?? "unknown";
			}
			Type type = AccessTools.TypeByName("ZenBossStone.State");
			SacrificeHandler = ((type == null) ? null : AccessTools.Method(type, "RPC_BossStoneSacrifice", new Type[2]
			{
				typeof(long),
				typeof(ZDOID)
			}, (Type[])null));
			Type type2 = AccessTools.TypeByName("ZenBossStone.Configs");
			_sacrificeRangeField = ((type2 == null) ? null : AccessTools.Field(type2, "SacrificeTrophyRange"));
			if (SacrificeHandler == null)
			{
				Plugin.Log.LogError((object)"ZenBossStone RPC_BossStoneSacrifice(long, ZDOID) was not found. Progress observation is disabled.");
			}
			if (_sacrificeRangeField == null)
			{
				Plugin.Log.LogWarning((object)"ZenBossStone SacrificeTrophyRange was not found. Falling back to 25 meters.");
			}
		}

		internal static int GetSacrificeRange()
		{
			try
			{
				if (_sacrificeRangeField?.GetValue(null) is ConfigEntry<int> val)
				{
					return Math.Max(1, val.Value);
				}
			}
			catch (Exception ex)
			{
				Plugin.Debug("Could not read ZenBossStone sacrifice range: " + ex.Message);
			}
			return 25;
		}

		internal static bool TryResolveSacrifice(ZDOID itemStandId, out BossStone stone, out ProgressionTier tier, out string guardianPowerName)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			stone = null;
			tier = ProgressionTier.None;
			guardianPowerName = string.Empty;
			try
			{
				ZNetScene instance = ZNetScene.instance;
				if ((Object)(object)instance == (Object)null)
				{
					return false;
				}
				GameObject val = instance.FindInstance(itemStandId);
				if ((Object)(object)val == (Object)null)
				{
					return false;
				}
				stone = val.GetComponentInParent<BossStone>();
				if ((Object)(object)stone == (Object)null || (Object)(object)stone.m_itemStand == (Object)null || (Object)(object)stone.m_itemStand.m_guardianPower == (Object)null)
				{
					return false;
				}
				guardianPowerName = ((Object)stone.m_itemStand.m_guardianPower).name ?? string.Empty;
				return BossTierMapping.TryMapGuardianPower(guardianPowerName, out tier);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not resolve ZenBossStone sacrifice target: " + ex.Message));
				return false;
			}
		}
	}
	[HarmonyPatch]
	internal static class ZenBossStoneSacrificePatch
	{
		private static bool Prepare()
		{
			return ZenBossStoneIntegration.SacrificeHandler != null;
		}

		private static MethodBase TargetMethod()
		{
			return ZenBossStoneIntegration.SacrificeHandler;
		}

		[HarmonyPostfix]
		private static void Postfix(long sender, ZDOID itemStandID)
		{
			//IL_0019: 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_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || !instance.IsServer())
			{
				return;
			}
			try
			{
				if (!ZenBossStoneIntegration.TryResolveSacrifice(itemStandID, out var stone, out var tier, out var guardianPowerName))
				{
					Plugin.Log.LogWarning((object)$"ZenBossStone sacrifice could not be mapped for ZDO {itemStandID}.");
					return;
				}
				if (ProgressionRegistry.WorldUid == 0L)
				{
					ProgressionRegistry.LoadForWorld(instance.GetWorldUID());
				}
				int sacrificeRange = ZenBossStoneIntegration.GetSacrificeRange();
				float num = sacrificeRange * sacrificeRange;
				Vector3 position = ((Component)stone).transform.position;
				if (instance.IsDedicated() && !IsValidRemoteSacrifice(instance, sender, position, num))
				{
					return;
				}
				int num2 = 0;
				int num3 = 0;
				foreach (Player allPlayer in Player.GetAllPlayers())
				{
					if ((Object)(object)allPlayer == (Object)null)
					{
						continue;
					}
					Vector3 val = ((Component)allPlayer).transform.position - position;
					if (val.x * val.x + val.z * val.z > num)
					{
						continue;
					}
					long playerID = allPlayer.GetPlayerID();
					if (playerID <= 0)
					{
						Plugin.Log.LogWarning((object)$"Skipped in-range player '{allPlayer.GetPlayerName()}' with invalid PlayerID {playerID}.");
						continue;
					}
					num2++;
					if (ProgressionRegistry.Promote(playerID, allPlayer.GetPlayerName(), tier))
					{
						num3++;
					}
				}
				if (num3 > 0)
				{
					ProgressionRegistry.Save();
					ProgressionSync.BroadcastState();
				}
				Plugin.Log.LogInfo((object)($"ZenBossStone sacrifice '{guardianPowerName}' => {tier}; " + $"{num2} player(s) in {sacrificeRange}m range, {num3} progression record(s) advanced."));
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Progression observation failed after ZenBossStone sacrifice: {arg}");
			}
		}

		private static bool IsValidRemoteSacrifice(ZNet znet, long sender, Vector3 stonePosition, float rangeSquared)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			ZNetPeer peer = znet.GetPeer(sender);
			if (peer == null)
			{
				Plugin.Log.LogWarning((object)$"Ignored ZenBossStone sacrifice from unknown routed peer {sender}.");
				return false;
			}
			ZNetScene instance = ZNetScene.instance;
			GameObject obj = ((instance != null) ? instance.FindInstance(peer.m_characterID) : null);
			Player val = ((obj != null) ? obj.GetComponent<Player>() : null);
			if ((Object)(object)val == (Object)null)
			{
				Plugin.Log.LogWarning((object)$"Ignored ZenBossStone sacrifice because peer {sender}'s player could not be resolved.");
				return false;
			}
			Vector3 val2 = ((Component)val).transform.position - stonePosition;
			if (val2.x * val2.x + val2.z * val2.z > rangeSquared)
			{
				Plugin.Log.LogWarning((object)("Ignored out-of-range ZenBossStone sacrifice from " + $"'{val.GetPlayerName()}' (peer {sender})."));
				return false;
			}
			return true;
		}
	}
}
namespace ProgressGuard.Core
{
	public static class BossTierMapping
	{
		public static bool TryMapGuardianPower(string guardianPowerName, out ProgressionTier tier)
		{
			string text = Normalize(guardianPowerName);
			if (text.Contains("eikthyr"))
			{
				return Found(ProgressionTier.Eikthyr, out tier);
			}
			if (text.Contains("theelder") || text == "elder" || text.EndsWith("elder", StringComparison.Ordinal))
			{
				return Found(ProgressionTier.Elder, out tier);
			}
			if (text.Contains("bonemass"))
			{
				return Found(ProgressionTier.Bonemass, out tier);
			}
			if (text.Contains("moder"))
			{
				return Found(ProgressionTier.Moder, out tier);
			}
			if (text.Contains("yagluth") || text.Contains("goblinking"))
			{
				return Found(ProgressionTier.Yagluth, out tier);
			}
			if (text.Contains("queen"))
			{
				return Found(ProgressionTier.Queen, out tier);
			}
			if (text.Contains("fader"))
			{
				return Found(ProgressionTier.Fader, out tier);
			}
			tier = ProgressionTier.None;
			return false;
		}

		private static bool Found(ProgressionTier value, out ProgressionTier tier)
		{
			tier = value;
			return true;
		}

		private static string Normalize(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));
				}
			}
			return stringBuilder.ToString();
		}
	}
	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 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,
				VerboseLogging = VerboseLogging,
				LogBlockedSpawns = LogBlockedSpawns,
				LogAllowedProgressionSpawns = LogAllowedProgressionSpawns
			};
		}
	}
	public static class LegacyProgressionDetector
	{
		private static readonly KeyValuePair<string, ProgressionTier>[] GuardianPowerKeys = new KeyValuePair<string, ProgressionTier>[7]
		{
			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)
		};

		public static ProgressionTier DetectHighest(IEnumerable<string> uniqueKeys)
		{
			if (uniqueKeys == null)
			{
				return ProgressionTier.None;
			}
			HashSet<string> hashSet = new HashSet<string>(uniqueKeys, StringComparer.OrdinalIgnoreCase);
			ProgressionTier progressionTier = ProgressionTier.None;
			KeyValuePair<string, ProgressionTier>[] guardianPowerKeys = GuardianPowerKeys;
			for (int i = 0; i < guardianPowerKeys.Length; i++)
			{
				KeyValuePair<string, ProgressionTier> keyValuePair = guardianPowerKeys[i];
				if (keyValuePair.Value > progressionTier && hashSet.Contains(keyValuePair.Key))
				{
					progressionTier = keyValuePair.Value;
				}
			}
			return progressionTier;
		}
	}
	public enum ProgressionTier
	{
		None,
		Eikthyr,
		Elder,
		Bonemass,
		Moder,
		Yagluth,
		Queen,
		Fader
	}
	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, biome);
			rules.Add(BuildKey(globalKey, prefab), value);
		}

		private static string BuildKey(string globalKey, string prefab)
		{
			return (globalKey ?? string.Empty).Trim().ToLowerInvariant() + "|" + NormalizePrefab(prefab);
		}
	}
}
namespace ProgressGuard.Config
{
	internal static class ModConfig
	{
		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<float> NearbyPlayerRadius;

		internal static ConfigEntry<bool> ProtectLeastProgressedOnline;

		internal static ConfigEntry<bool> UseLowestNearbyProgression;

		internal static ConfigEntry<bool> GateYagluthRoamers;

		internal static ConfigEntry<bool> GateQueenRoamers;

		internal static ConfigEntry<bool> GateFaderRoamers;

		internal static ConfigEntry<bool> GateEarlyGameRoamers;

		internal static ConfigEntry<bool> VerboseLogging;

		internal static ConfigEntry<bool> LogBlockedSpawns;

		internal static ConfigEntry<bool> LogAllowedProgressionSpawns;

		internal static void Bind(ConfigFile config)
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			Enabled = config.Bind<bool>("General", "Enabled", true, "Master switch. The server value is authoritative and is synchronized to clients.");
			NearbyPlayerRadius = config.Bind<float>("Progression", "NearbyPlayerRadius", 100f, new ConfigDescription("Players within this horizontal distance of an ambient spawn influence its progression gate.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 300f), Array.Empty<object>()));
			ProtectLeastProgressedOnline = config.Bind<bool>("Progression", "ProtectLeastProgressedOnline", true, "When true, the least-progressed currently connected character is the server-wide safety ceiling for boss-key ambient roamers.");
			UseLowestNearbyProgression = config.Bind<bool>("Progression", "UseLowestNearbyProgression", true, "Used only when ProtectLeastProgressedOnline is false. Chooses the least- or most-progressed nearby character.");
			GateYagluthRoamers = config.Bind<bool>("Spawn Gates", "GateYagluthRoamers", true, "Gate post-Yagluth Fulings outside the Plains.");
			GateQueenRoamers = config.Bind<bool>("Spawn Gates", "GateQueenRoamers", true, "Gate post-Queen Seekers, Seeker Broods, and Ticks outside the Mistlands.");
			GateFaderRoamers = config.Bind<bool>("Spawn Gates", "GateFaderRoamers", true, "Gate post-Fader Charred outside the Ashlands.");
			GateEarlyGameRoamers = config.Bind<bool>("Spawn Gates", "GateEarlyGameRoamers", true, "Gate the Eikthyr, Elder, Bonemass, and Moder boss-key ambient-roamer families.");
			VerboseLogging = config.Bind<bool>("Debug", "VerboseLogging", false, "Log protocol, discovery, and validation detail at Info level.");
			LogBlockedSpawns = config.Bind<bool>("Debug", "LogBlockedSpawns", true, "Log each managed ambient spawn blocked for progression.");
			LogAllowedProgressionSpawns = config.Bind<bool>("Debug", "LogAllowedProgressionSpawns", false, "Log managed ambient spawns allowed by nearby progression. This can be noisy.");
			Subscribe<bool>(Enabled);
			Subscribe<float>(NearbyPlayerRadius);
			Subscribe<bool>(ProtectLeastProgressedOnline);
			Subscribe<bool>(UseLowestNearbyProgression);
			Subscribe<bool>(GateYagluthRoamers);
			Subscribe<bool>(GateQueenRoamers);
			Subscribe<bool>(GateFaderRoamers);
			Subscribe<bool>(GateEarlyGameRoamers);
			Subscribe<bool>(VerboseLogging);
			Subscribe<bool>(LogBlockedSpawns);
			Subscribe<bool>(LogAllowedProgressionSpawns);
		}

		internal static GateSettings Snapshot()
		{
			return new GateSettings
			{
				Enabled = Enabled.Value,
				NearbyPlayerRadius = NearbyPlayerRadius.Value,
				ProtectLeastProgressedOnline = ProtectLeastProgressedOnline.Value,
				UseLowestNearbyProgression = UseLowestNearbyProgression.Value,
				GateYagluthRoamers = GateYagluthRoamers.Value,
				GateQueenRoamers = GateQueenRoamers.Value,
				GateFaderRoamers = GateFaderRoamers.Value,
				GateEarlyGameRoamers = GateEarlyGameRoamers.Value,
				VerboseLogging = VerboseLogging.Value,
				LogBlockedSpawns = LogBlockedSpawns.Value,
				LogAllowedProgressionSpawns = LogAllowedProgressionSpawns.Value
			};
		}

		private static void Subscribe<T>(ConfigEntry<T> entry)
		{
			entry.SettingChanged += OnSettingChanged;
		}

		private static void OnSettingChanged(object sender, EventArgs args)
		{
			ProgressionSync.BroadcastState();
		}
	}
}
namespace ProgressGuard.Commands
{
	internal static class AdminCommands
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static ConsoleEvent <0>__Run;

			public static Func<PlayerProgressRecord, string> <1>__FormatRecord;
		}

		private static bool _registered;

		internal static void Register()
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Expected O, but got Unknown
			if (_registered)
			{
				return;
			}
			try
			{
				object obj = <>O.<0>__Run;
				if (obj == null)
				{
					ConsoleEvent val = Run;
					<>O.<0>__Run = val;
					obj = (object)val;
				}
				new ConsoleCommand("pg", "ProgressGuard admin: pg list | get <id|name> | set <id> <tier> [name] | reset <id> | reload", (ConsoleEvent)obj, false, false, true, false, false, (ConsoleOptionsFetcher)null, false, true, true);
				_registered = true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not register ProgressGuard admin command: " + ex.Message));
			}
		}

		private static void Run(ConsoleEventArgs args)
		{
			try
			{
				if (args.Length < 2)
				{
					Reply(args, "Usage: pg list | get <id|name> | set <id> <tier> [name] | reset <id> | reload");
					return;
				}
				string text = args[1].ToLowerInvariant();
				switch (text)
				{
				case "list":
					List(args);
					break;
				case "get":
					Get(args);
					break;
				case "set":
					Set(args);
					break;
				case "reset":
					Reset(args);
					break;
				case "reload":
					Reload(args);
					break;
				default:
					Reply(args, "Unknown subcommand '" + text + "'.");
					break;
				}
			}
			catch (Exception ex)
			{
				Reply(args, "ProgressGuard command failed: " + ex.Message);
			}
		}

		private static void List(ConsoleEventArgs args)
		{
			List<PlayerProgressRecord> list = ProgressionRegistry.Snapshot();
			Reply(args, (list.Count == 0) ? "ProgressGuard registry is empty." : string.Join("\n", list.Select(FormatRecord)));
		}

		private static void Get(ConsoleEventArgs args)
		{
			if (args.Length < 3)
			{
				Reply(args, "Usage: pg get <playerID|name>");
				return;
			}
			PlayerProgressRecord playerProgressRecord = ProgressionRegistry.Find(args[2]);
			Reply(args, (playerProgressRecord == null) ? ("No progression record for '" + args[2] + "'.") : FormatRecord(playerProgressRecord));
		}

		private static void Set(ConsoleEventArgs args)
		{
			if (args.Length < 4 || !long.TryParse(args[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || !TryParseTier(args[3], out var tier))
			{
				Reply(args, "Usage: pg set <playerID> <None|Eikthyr|Elder|Bonemass|Moder|Yagluth|Queen|Fader|0-7> [name]");
				return;
			}
			string text = ((args.Length >= 5) ? args[4] : (ProgressionRegistry.Find(args[2])?.PlayerName ?? string.Empty));
			if (!ProgressionRegistry.Set(result, text, tier))
			{
				Reply(args, "Invalid player ID or tier.");
				return;
			}
			Commit();
			Reply(args, $"Set {result} ({text}) to {tier}.");
		}

		private static void Reset(ConsoleEventArgs args)
		{
			if (args.Length < 3 || !long.TryParse(args[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
			{
				Reply(args, "Usage: pg reset <playerID>");
				return;
			}
			bool flag = ProgressionRegistry.Reset(result);
			Commit();
			Reply(args, flag ? $"Reset progression for {result}." : $"No record existed for {result}.");
		}

		private static void Reload(ConsoleEventArgs args)
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || !instance.IsServer())
			{
				Reply(args, "Registry reload is server-only.");
				return;
			}
			ProgressionRegistry.LoadForWorld(instance.GetWorldUID());
			ProgressionSync.BroadcastState();
			Reply(args, $"Reloaded {ProgressionRegistry.Count} progression record(s).");
		}

		private static void Commit()
		{
			ProgressionRegistry.Save();
			ProgressionSync.BroadcastState();
		}

		private static bool TryParseTier(string value, out ProgressionTier tier)
		{
			if (Enum.TryParse<ProgressionTier>(value, ignoreCase: true, out tier) && tier >= ProgressionTier.None && tier <= ProgressionTier.Fader)
			{
				return true;
			}
			if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && result >= 0 && result <= 7)
			{
				tier = (ProgressionTier)result;
				return true;
			}
			tier = ProgressionTier.None;
			return false;
		}

		private static string FormatRecord(PlayerProgressRecord record)
		{
			return $"{record.PlayerId} | {record.PlayerName} | {record.Tier} ({(int)record.Tier})";
		}

		private static void Reply(ConsoleEventArgs args, string message)
		{
			Terminal context = args.Context;
			if (context != null)
			{
				context.AddString(message);
			}
			Plugin.Log.LogInfo((object)("[admin] " + message));
		}
	}
}