Decompiled source of StructureProtection v1.0.6

Landoria.StructureProtection.dll

Decompiled a month ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using Landoria.SharedLib;
using Splatform;
using TMPro;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Landoria.StructureProtection")]
[assembly: AssemblyDescription("Prevents creatures from deliberately targeting structures whose creator is offline and blocks player damage to structures inside active wards.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Landoria")]
[assembly: AssemblyProduct("Landoria.StructureProtection")]
[assembly: AssemblyCopyright("Copyright © 2026 End3rbyte")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("C6FAAFDF-2D25-4CD3-BD71-59BAA98F1D20")]
[assembly: AssemblyFileVersion("1.0.6")]
[assembly: AssemblyInformationalVersion("1.0.6")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = "")]
[assembly: AssemblyVersion("1.0.6.13986")]
namespace Landoria.StructureProtection
{
	internal static class CreatorActivityPolicy
	{
		internal static HashSet<long> GetActiveCreators(IEnumerable<long> onlinePlayers)
		{
			return new HashSet<long>(onlinePlayers);
		}

		internal static bool IsCreatorActive(long creator, ISet<long> onlinePlayers)
		{
			return onlinePlayers.Contains(creator);
		}
	}
	internal static class CreatureProtection
	{
		[HarmonyPatch(typeof(StaticTarget), "IsPriorityTarget")]
		private static class PriorityTargetPatch
		{
			private static void Postfix(StaticTarget __instance, ref bool __result)
			{
				__result = CreatureProtectionPolicy.CanTarget(StructureProtectionPlugin.Settings.CreatureTargetingEnabled, __result, GetActivity(__instance));
			}
		}

		[HarmonyPatch(typeof(StaticTarget), "IsRandomTarget")]
		private static class RandomTargetPatch
		{
			private static void Postfix(StaticTarget __instance, ref bool __result)
			{
				__result = CreatureProtectionPolicy.CanTarget(StructureProtectionPlugin.Settings.CreatureTargetingEnabled, __result, GetActivity(__instance));
			}
		}

		[HarmonyPatch(typeof(BaseAI), "CanSeeTarget", new Type[] { typeof(StaticTarget) })]
		private static class VisibilityPatch
		{
			private static void Postfix(StaticTarget target, ref bool __result)
			{
				__result = CreatureProtectionPolicy.CanTarget(StructureProtectionPlugin.Settings.CreatureTargetingEnabled, __result, GetActivity(target));
			}
		}

		private static float GetActivity(StaticTarget target)
		{
			return PieceActivity.GetMultiplier((target != null) ? ((Component)target).GetComponentInParent<Piece>() : null);
		}
	}
	internal static class CreatureProtectionPolicy
	{
		internal static bool CanTarget(bool enabled, bool vanillaCanTarget, float activityMultiplier)
		{
			if (vanillaCanTarget)
			{
				if (enabled)
				{
					return activityMultiplier > 0f;
				}
				return true;
			}
			return false;
		}
	}
	[BepInPlugin("Landoria.StructureProtection", "Landoria.StructureProtection", "1.0.6")]
	public sealed class StructureProtectionPlugin : LandoriaPlugin
	{
		private const string PluginGuid = "Landoria.StructureProtection";

		private const string PluginName = "Landoria.StructureProtection";

		private const string PluginVersion = "1.0.6";

		internal static ModLog Log { get; private set; }

		internal static StructureProtectionSettings Settings { get; private set; }

		private void Awake()
		{
			Log = InitializePlugin("Landoria.StructureProtection");
			Settings = new StructureProtectionSettings();
			Settings.InitializeServer(Log);
			Log.LogInfo("Landoria.StructureProtection 1.0.6 is loaded.");
		}

		private void Update()
		{
			Settings.InitializeServer(Log);
			CharacterActivityRegistry.Update();
			StructureProtectionSession.Update();
			WardQuota.Update();
			WardInactivityJob.Update();
		}

		private void OnDestroy()
		{
			CharacterActivityRegistry.Reset();
			StructureProtectionSession.Reset();
			WardQuota.Reset();
			WardInactivityJob.Reset();
			Log?.LogInfo("Landoria.StructureProtection 1.0.6 is unloaded.");
			ShutdownPlugin();
			Settings = null;
			Log = null;
		}
	}
	internal static class StructureProtectionArgumentPolicy
	{
		internal static bool Resolve(string[] arguments, string name, bool defaultValue)
		{
			string text = ReadValue(arguments, name);
			if (text == null)
			{
				return defaultValue;
			}
			if (bool.TryParse(text, out var result))
			{
				return result;
			}
			throw new InvalidOperationException("Command-line switch " + name + " requires true or false.");
		}

		internal static int ResolveMinimum(string[] arguments, string name, int defaultValue, int minimum)
		{
			string text = ReadValue(arguments, name);
			if (text == null)
			{
				return defaultValue;
			}
			if (int.TryParse(text, out var result) && result >= minimum)
			{
				return result;
			}
			throw new InvalidOperationException($"Command-line switch {name} requires an integer of at least {minimum}.");
		}

		private static string ReadValue(string[] arguments, string name)
		{
			string text = null;
			for (int i = 0; i < arguments.Length; i++)
			{
				if (string.Equals(arguments[i], name, StringComparison.OrdinalIgnoreCase))
				{
					if (text != null || i + 1 >= arguments.Length)
					{
						throw new InvalidOperationException("Command-line switch " + name + " is missing or duplicated.");
					}
					text = arguments[++i];
				}
			}
			return text;
		}
	}
	internal sealed class StructureProtectionServerConfiguration
	{
		internal bool CreatureTargetingEnabled { get; private set; }

		internal bool WardPlayerDamageEnabled { get; private set; }

		internal int MaximumWardsPerCharacter { get; private set; }

		internal int WardInactivityDays { get; private set; }

		internal int WardInactivityCheckHours { get; private set; }

		internal static StructureProtectionServerConfiguration FromArguments(string[] arguments)
		{
			return new StructureProtectionServerConfiguration
			{
				CreatureTargetingEnabled = StructureProtectionArgumentPolicy.Resolve(arguments, "--structure-protection-offline-creature-targeting", defaultValue: true),
				WardPlayerDamageEnabled = StructureProtectionArgumentPolicy.Resolve(arguments, "--structure-protection-ward-player-damage", defaultValue: true),
				MaximumWardsPerCharacter = StructureProtectionArgumentPolicy.ResolveMinimum(arguments, "--structure-protection-max-wards-per-character", 5, -1),
				WardInactivityDays = StructureProtectionArgumentPolicy.ResolveMinimum(arguments, "--structure-protection-ward-inactivity-days", 30, -1),
				WardInactivityCheckHours = StructureProtectionArgumentPolicy.ResolveMinimum(arguments, "--structure-protection-ward-inactivity-check-hours", 6, 1)
			};
		}
	}
	internal static class StructureProtectionSession
	{
		[HarmonyPatch(typeof(ZNet), "Save")]
		private static class WorldSaveActivityPatch
		{
			private static void Prefix(ZNet __instance)
			{
				if (!__instance.IsServer())
				{
					return;
				}
				DateTime utcNow = DateTime.UtcNow;
				foreach (KeyValuePair<long, long> peerPlayer in PeerPlayers)
				{
					RecordActivity(__instance.GetPeer(peerPlayer.Key), peerPlayer.Value, utcNow);
				}
			}
		}

		[HarmonyPatch(typeof(Player), "OnSpawned")]
		private static class PlayerSpawnPatch
		{
			private static void Postfix(Player __instance)
			{
				if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer)
				{
					identityServer = 0L;
					Update();
				}
			}
		}

		private const string IdentityRpc = "Landoria_StructureProtection_Identity";

		private const string SnapshotRpc = "Landoria_StructureProtection_Snapshot";

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

		private static readonly HashSet<long> OnlinePlayers = new HashSet<long>();

		private static ZRoutedRpc registeredRpc;

		private static long identityServer;

		internal static void Update()
		{
			RegisterRpcs();
			SendLocalIdentity();
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer() && RemoveDisconnectedPeers())
			{
				BroadcastSnapshot();
			}
		}

		internal static void Reset()
		{
			registeredRpc = null;
			identityServer = 0L;
			ClearState();
		}

		private static void ClearState()
		{
			PeerPlayers.Clear();
			OnlinePlayers.Clear();
			WardQuota.ResetClientState();
			StructureProtectionPlugin.Settings?.ResetClientState();
		}

		internal static HashSet<long> GetOnlinePlayers()
		{
			return new HashSet<long>(OnlinePlayers);
		}

		private static void RegisterRpcs()
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance != null && instance != registeredRpc)
			{
				instance.Register<long>("Landoria_StructureProtection_Identity", (Action<long, long>)ReceiveIdentity);
				instance.Register<ZPackage>("Landoria_StructureProtection_Snapshot", (Action<long, ZPackage>)ReceiveSnapshot);
				ClearState();
				registeredRpc = instance;
				identityServer = 0L;
			}
		}

		private static void SendLocalIdentity()
		{
			ZNet instance = ZNet.instance;
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)(object)instance == (Object)null) && !((Object)(object)localPlayer == (Object)null) && !instance.IsServer() && registeredRpc != null)
			{
				ZNetPeer serverPeer = instance.GetServerPeer();
				if (serverPeer != null && serverPeer.m_uid != identityServer)
				{
					identityServer = serverPeer.m_uid;
					registeredRpc.InvokeRoutedRPC(serverPeer.m_uid, "Landoria_StructureProtection_Identity", new object[1] { localPlayer.GetPlayerID() });
				}
			}
		}

		private static void ReceiveIdentity(long sender, long playerId)
		{
			if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && ZNet.instance.GetPeer(sender) != null && playerId != 0L)
			{
				PeerPlayers[sender] = playerId;
				OnlinePlayers.Add(playerId);
				BroadcastSnapshot();
				WardQuota.RegisterIdentity(sender, playerId);
				RecordActivity(ZNet.instance.GetPeer(sender), playerId, DateTime.UtcNow);
			}
		}

		private static void RecordActivity(ZNetPeer peer, long playerId, DateTime seenOnlineUtc)
		{
			if (peer != null && peer.IsReady())
			{
				ISocket socket = peer.m_socket;
				string text = ((socket != null) ? socket.GetHostName() : null);
				if (!string.IsNullOrWhiteSpace(text))
				{
					CharacterActivityRegistry.Record(text, playerId, peer.m_playerName ?? string.Empty, seenOnlineUtc);
				}
			}
		}

		private static bool RemoveDisconnectedPeers()
		{
			bool result = false;
			foreach (long item in new List<long>(PeerPlayers.Keys))
			{
				ZNetPeer peer = ZNet.instance.GetPeer(item);
				if (peer == null || !peer.IsReady())
				{
					OnlinePlayers.Remove(PeerPlayers[item]);
					WardQuota.RemoveIdentity(PeerPlayers[item]);
					PeerPlayers.Remove(item);
					result = true;
				}
			}
			return result;
		}

		private static void BroadcastSnapshot()
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Expected O, but got Unknown
			if (registeredRpc != null)
			{
				ZPackage val = new ZPackage();
				WriteMappings(val);
				registeredRpc.InvokeRoutedRPC(0L, "Landoria_StructureProtection_Snapshot", new object[1] { val });
			}
		}

		private static void WriteMappings(ZPackage package)
		{
			StructureProtectionPlugin.Settings.WriteClientState(package);
			package.Write(PeerPlayers.Count);
			foreach (KeyValuePair<long, long> peerPlayer in PeerPlayers)
			{
				package.Write(peerPlayer.Value);
			}
		}

		private static void ReceiveSnapshot(long sender, ZPackage package)
		{
			if (IsTrustedServerCore(sender))
			{
				StructureProtectionPlugin.Settings.ReadClientState(package);
				OnlinePlayers.Clear();
				int num = package.ReadInt();
				for (int i = 0; i < num; i++)
				{
					long item = package.ReadLong();
					OnlinePlayers.Add(item);
				}
			}
		}

		private static bool IsTrustedServerCore(long sender)
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || instance.IsServer())
			{
				if ((Object)(object)instance != (Object)null)
				{
					return instance.IsServer();
				}
				return false;
			}
			ZNetPeer serverPeer = instance.GetServerPeer();
			if (serverPeer == null)
			{
				return false;
			}
			return serverPeer.m_uid == sender;
		}

		internal static bool IsTrustedServer(long sender)
		{
			return IsTrustedServerCore(sender);
		}

		internal static bool TryGetPeer(long playerId, out long peerId)
		{
			foreach (KeyValuePair<long, long> peerPlayer in PeerPlayers)
			{
				if (peerPlayer.Value == playerId)
				{
					peerId = peerPlayer.Key;
					return true;
				}
			}
			peerId = 0L;
			return false;
		}
	}
	internal sealed class StructureProtectionSettings
	{
		private bool serverInitialized;

		internal bool CreatureTargetingEnabled { get; private set; }

		internal bool WardPlayerDamageEnabled { get; private set; }

		internal int MaximumWardsPerCharacter { get; private set; } = -1;

		internal int WardInactivityDays { get; private set; } = -1;

		internal int WardInactivityCheckHours { get; private set; } = 6;

		internal void InitializeServer(ModLog logger)
		{
			if (!serverInitialized && ServerRole.IsDedicatedServer)
			{
				StructureProtectionServerConfiguration structureProtectionServerConfiguration = StructureProtectionServerConfiguration.FromArguments(Environment.GetCommandLineArgs());
				CreatureTargetingEnabled = structureProtectionServerConfiguration.CreatureTargetingEnabled;
				WardPlayerDamageEnabled = structureProtectionServerConfiguration.WardPlayerDamageEnabled;
				MaximumWardsPerCharacter = structureProtectionServerConfiguration.MaximumWardsPerCharacter;
				WardInactivityDays = structureProtectionServerConfiguration.WardInactivityDays;
				WardInactivityCheckHours = structureProtectionServerConfiguration.WardInactivityCheckHours;
				serverInitialized = true;
				logger.LogInfo("Effective structure protection settings: " + $"creatureTargeting={CreatureTargetingEnabled}, " + $"wardPlayerDamage={WardPlayerDamageEnabled}, " + $"maximumWardsPerCharacter={MaximumWardsPerCharacter}, " + $"wardInactivityDays={WardInactivityDays}, " + $"wardInactivityCheckHours={WardInactivityCheckHours}.");
			}
		}

		internal void WriteClientState(ZPackage package)
		{
			package.Write(CreatureTargetingEnabled);
			package.Write(WardPlayerDamageEnabled);
			package.Write(MaximumWardsPerCharacter);
		}

		internal void ReadClientState(ZPackage package)
		{
			CreatureTargetingEnabled = package.ReadBool();
			WardPlayerDamageEnabled = package.ReadBool();
			MaximumWardsPerCharacter = package.ReadInt();
		}

		internal void ResetClientState()
		{
			if (!serverInitialized)
			{
				CreatureTargetingEnabled = false;
				WardPlayerDamageEnabled = false;
				MaximumWardsPerCharacter = -1;
			}
		}
	}
	internal static class PieceActivity
	{
		internal static float GetMultiplier(Piece piece)
		{
			if ((Object)(object)piece == (Object)null || !piece.IsPlacedByPlayer())
			{
				return 1f;
			}
			long creator = piece.GetCreator();
			if (creator == 0L)
			{
				return 1f;
			}
			if (!CreatorActivityPolicy.IsCreatorActive(creator, StructureProtectionSession.GetOnlinePlayers()))
			{
				return 0f;
			}
			return 1f;
		}
	}
	internal static class WardProtection
	{
		[HarmonyPatch(typeof(PrivateArea), "Awake")]
		private static class WardAwakePatch
		{
			private static void Postfix(PrivateArea __instance)
			{
				Wards.Add(__instance);
				WardQuota.Observe(__instance);
			}
		}

		[HarmonyPatch(typeof(PrivateArea), "OnDestroy")]
		private static class WardDestroyPatch
		{
			private static void Prefix(PrivateArea __instance)
			{
				Wards.Remove(__instance);
			}
		}

		[HarmonyPatch(typeof(WearNTear), "RPC_Damage")]
		private static class PlayerDamagePatch
		{
			private static bool Prefix(WearNTear __instance, HitData hit)
			{
				//IL_0022: Unknown result type (might be due to invalid IL or missing references)
				//IL_0028: Invalid comparison between Unknown and I4
				//IL_0030: Unknown result type (might be due to invalid IL or missing references)
				Character obj = ((hit != null) ? hit.GetAttacker() : null);
				Player val = (Player)(object)((obj is Player) ? obj : null);
				if (StructureProtectionPlugin.Settings.WardPlayerDamageEnabled && hit != null && (int)hit.m_hitType == 2)
				{
					return !ShouldBlockPlayerDamage(((Component)__instance).transform.position, (val != null) ? val.GetPlayerID() : 0);
				}
				return true;
			}
		}

		private static readonly HashSet<PrivateArea> Wards = new HashSet<PrivateArea>();

		private static bool ShouldBlockPlayerDamage(Vector3 position, long attacker)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			foreach (PrivateArea ward in Wards)
			{
				if (TryGetWardState(ward, position, out var creator, out var permitted) && !WardProtectionPolicy.IsAuthorized(creator, permitted, attacker))
				{
					return true;
				}
			}
			return false;
		}

		private static bool TryGetWardState(PrivateArea ward, Vector3 position, out long creator, out List<long> permitted)
		{
			//IL_003d: 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)
			creator = 0L;
			permitted = null;
			ZNetView obj = (((Object)(object)ward != (Object)null) ? ((Component)ward).GetComponent<ZNetView>() : null);
			ZDO val = ((obj != null) ? obj.GetZDO() : null);
			if (val == null || !val.GetBool(ZDOVars.s_enabled, false) || Utils.DistanceXZ(((Component)ward).transform.position, position) >= ward.m_radius)
			{
				return false;
			}
			Piece component = ((Component)ward).GetComponent<Piece>();
			creator = (((Object)(object)component != (Object)null) ? component.GetCreator() : 0);
			permitted = ReadPermittedPlayers(val);
			return creator != 0;
		}

		private static List<long> ReadPermittedPlayers(ZDO zdo)
		{
			List<long> list = new List<long>();
			int num = zdo.GetInt(ZDOVars.s_permitted, 0);
			for (int i = 0; i < num; i++)
			{
				long num2 = zdo.GetLong("pu_id" + i, 0L);
				if (num2 != 0L)
				{
					list.Add(num2);
				}
			}
			return list;
		}
	}
	internal static class WardProtectionPolicy
	{
		internal static bool IsAuthorized(long creator, IEnumerable<long> permittedPlayers, long player)
		{
			if (player == 0L)
			{
				return false;
			}
			if (player == creator)
			{
				return true;
			}
			foreach (long permittedPlayer in permittedPlayers)
			{
				if (player == permittedPlayer)
				{
					return true;
				}
			}
			return false;
		}
	}
	internal static class WardQuota
	{
		[HarmonyPatch(typeof(Player), "TryPlacePiece")]
		private static class PlacementPatch
		{
			private static bool Prefix(Player __instance, Piece piece, ref bool __result)
			{
				if (CanPlaceLocalWard(__instance, piece))
				{
					return true;
				}
				__result = false;
				return false;
			}
		}

		[HarmonyPatch(typeof(PrivateArea), "Setup")]
		private static class WardSetupPatch
		{
			private static void Postfix(PrivateArea __instance)
			{
				TagLocalWard(__instance);
			}
		}

		[HarmonyPatch(typeof(WearNTear), "Destroy")]
		private static class WardDestroyPatch
		{
			private static void Prefix(WearNTear __instance)
			{
				RemoveLocalWard(__instance);
			}
		}

		internal const string OwnerKey = "Landoria.StructureProtection.WardOwner";

		private const string QuotaRpc = "Landoria_StructureProtection_WardQuota";

		private const string WardPrefab = "guard_stone";

		private static readonly Dictionary<string, int> Counts = new Dictionary<string, int>();

		private static readonly Dictionary<ZDOID, string> Tracked = new Dictionary<ZDOID, string>();

		private static readonly Dictionary<ZDOID, long> LegacyCreators = new Dictionary<ZDOID, long>();

		private static readonly Dictionary<long, string> Identities = new Dictionary<long, string>();

		private static readonly HashSet<PrivateArea> Pending = new HashSet<PrivateArea>();

		private static readonly HashSet<ZDOID> AllWards = new HashSet<ZDOID>();

		private static readonly List<ZDO> ScannedWards = new List<ZDO>();

		private static ZDOMan world;

		private static ZRoutedRpc registeredRpc;

		private static float nextReconciliation;

		private static int scanIndex;

		private static bool rebuilding;

		private static string localOwner;

		private static int localCount;

		private static bool localReady;

		internal static void Update()
		{
			RegisterRpc();
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || ZDOMan.instance == null)
			{
				return;
			}
			if (world != ZDOMan.instance)
			{
				BeginRebuild(ZDOMan.instance);
			}
			if (rebuilding)
			{
				ContinueRebuild();
				return;
			}
			ProcessPending();
			if (Time.unscaledTime >= nextReconciliation)
			{
				ReconcileDestroyed();
				nextReconciliation = Time.unscaledTime + 5f;
			}
		}

		internal static void Reset()
		{
			Counts.Clear();
			Tracked.Clear();
			LegacyCreators.Clear();
			Identities.Clear();
			Pending.Clear();
			AllWards.Clear();
			ScannedWards.Clear();
			world = null;
			registeredRpc = null;
			scanIndex = 0;
			rebuilding = false;
			localOwner = null;
			localCount = 0;
			localReady = false;
		}

		internal static void RegisterIdentity(long peerId, long characterId)
		{
			RegisterRpc();
			ZNet instance = ZNet.instance;
			ZNetPeer val = ((instance != null) ? instance.GetPeer(peerId) : null);
			if (val?.m_socket == null || string.IsNullOrWhiteSpace(val.m_playerName))
			{
				StructureProtectionPlugin.Log.LogWarning($"Could not register ward quota identity for peer {peerId}.");
				return;
			}
			string text = MakeOwner(val.m_socket.GetHostName(), val.m_playerName);
			Identities[characterId] = text;
			StructureProtectionPlugin.Log.LogInfo($"Registered ward quota identity {text} for character {characterId}.");
			if (!rebuilding && world != null)
			{
				AttributeLegacyWards(characterId, text);
				SendQuota(peerId, text);
			}
		}

		internal static void RemoveIdentity(long characterId)
		{
			Identities.Remove(characterId);
		}

		internal static void ResetClientState()
		{
			localOwner = null;
			localCount = 0;
			localReady = false;
		}

		internal static bool TryGetWardSnapshot(out List<ZDO> wards)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: 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)
			wards = new List<ZDO>();
			if (world == null || rebuilding)
			{
				return false;
			}
			foreach (ZDOID allWard in AllWards)
			{
				ZDO zDO = world.GetZDO(allWard);
				if (zDO != null)
				{
					wards.Add(zDO);
				}
			}
			return true;
		}

		internal static void Observe(PrivateArea ward)
		{
			if ((Object)(object)ward != (Object)null && (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
			{
				Pending.Add(ward);
			}
		}

		internal static void TagLocalWard(PrivateArea ward)
		{
			object obj;
			if (ward == null)
			{
				obj = null;
			}
			else
			{
				ZNetView component = ((Component)ward).GetComponent<ZNetView>();
				obj = ((component != null) ? component.GetZDO() : null);
			}
			ZDO val = (ZDO)obj;
			if (val != null && !string.IsNullOrWhiteSpace(localOwner))
			{
				val.Set("Landoria.StructureProtection.WardOwner", localOwner);
				localCount++;
				StructureProtectionPlugin.Log.LogInfo($"Tagged newly placed ward for {localOwner}; local count is {localCount}.");
			}
		}

		internal static bool CanPlaceLocalWard(Player player, Piece piece)
		{
			int maximumWardsPerCharacter = StructureProtectionPlugin.Settings.MaximumWardsPerCharacter;
			if ((Object)(object)player != (Object)(object)Player.m_localPlayer || (Object)(object)((piece != null) ? ((Component)piece).GetComponent<PrivateArea>() : null) == (Object)null)
			{
				return true;
			}
			if (!localReady)
			{
				((Character)player).Message((MessageType)2, "Ward limits are still loading.", 0, (Sprite)null, false);
				return false;
			}
			if (maximumWardsPerCharacter < 0 || localCount < maximumWardsPerCharacter)
			{
				return true;
			}
			((Character)player).Message((MessageType)2, $"Ward limit reached ({localCount}/{maximumWardsPerCharacter}).", 0, (Sprite)null, false);
			StructureProtectionPlugin.Log.LogInfo($"Blocked ward placement for {localOwner}: {localCount}/{maximumWardsPerCharacter} wards.");
			return false;
		}

		private static void RegisterRpc()
		{
			if (ZRoutedRpc.instance != null && registeredRpc != ZRoutedRpc.instance)
			{
				registeredRpc = ZRoutedRpc.instance;
				registeredRpc.Register<ZPackage>("Landoria_StructureProtection_WardQuota", (Action<long, ZPackage>)ReceiveQuota);
			}
		}

		private static void BeginRebuild(ZDOMan manager)
		{
			Counts.Clear();
			Tracked.Clear();
			LegacyCreators.Clear();
			AllWards.Clear();
			ScannedWards.Clear();
			world = manager;
			scanIndex = 0;
			rebuilding = true;
			StructureProtectionPlugin.Log.LogInfo("Started iterative ward quota reconstruction.");
		}

		private static void ContinueRebuild()
		{
			if (!world.GetAllZDOsWithPrefabIterative("guard_stone", ScannedWards, ref scanIndex))
			{
				return;
			}
			int num = ReadScannedWards();
			rebuilding = false;
			StructureProtectionPlugin.Log.LogInfo($"Rebuilt ward quotas from the world: {Tracked.Count} tracked wards, " + $"{Counts.Count} characters, {num} legacy wards awaiting attribution.");
			KeyValuePair<long, string>[] array = Identities.ToArray();
			for (int i = 0; i < array.Length; i++)
			{
				KeyValuePair<long, string> keyValuePair = array[i];
				AttributeLegacyWards(keyValuePair.Key, keyValuePair.Value);
			}
			foreach (string item in Identities.Values.Distinct())
			{
				SendQuotaForOwner(item);
			}
		}

		private static int ReadScannedWards()
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: 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)
			int num = 0;
			foreach (ZDO scannedWard in ScannedWards)
			{
				if (scannedWard != null)
				{
					AllWards.Add(scannedWard.m_uid);
					string text = scannedWard.GetString("Landoria.StructureProtection.WardOwner", "");
					if (string.IsNullOrWhiteSpace(text))
					{
						LegacyCreators[scannedWard.m_uid] = scannedWard.GetLong(ZDOVars.s_creator, 0L);
						num++;
					}
					else
					{
						Track(scannedWard.m_uid, text);
					}
				}
			}
			ScannedWards.Clear();
			return num;
		}

		private static void ProcessPending()
		{
			PrivateArea[] array = Pending.ToArray();
			for (int i = 0; i < array.Length; i++)
			{
				ProcessPendingWard(array[i]);
			}
		}

		private static void ProcessPendingWard(PrivateArea ward)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			object obj;
			if (ward == null)
			{
				obj = null;
			}
			else
			{
				ZNetView component = ((Component)ward).GetComponent<ZNetView>();
				obj = ((component != null) ? component.GetZDO() : null);
			}
			ZDO val = (ZDO)obj;
			if (val == null || Tracked.ContainsKey(val.m_uid))
			{
				Pending.Remove(ward);
				return;
			}
			string text = ResolveOwner(ward, val);
			if (text != null)
			{
				int maximumWardsPerCharacter = StructureProtectionPlugin.Settings.MaximumWardsPerCharacter;
				if (!string.IsNullOrWhiteSpace(val.GetString("Landoria.StructureProtection.WardOwner", "")) && maximumWardsPerCharacter >= 0 && GetCount(text) >= maximumWardsPerCharacter)
				{
					RejectExcessWard(ward, val, text, maximumWardsPerCharacter);
					Pending.Remove(ward);
				}
				else
				{
					RegisterWard(val, text);
					Pending.Remove(ward);
				}
			}
		}

		private static void RegisterWard(ZDO zdo, string owner)
		{
			//IL_0006: 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_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			AllWards.Add(zdo.m_uid);
			zdo.Set("Landoria.StructureProtection.WardOwner", owner);
			LegacyCreators.Remove(zdo.m_uid);
			Track(zdo.m_uid, owner);
			SendQuotaForOwner(owner);
			StructureProtectionPlugin.Log.LogInfo($"Registered ward {zdo.m_uid} for {owner}; count is {Counts[owner]}.");
		}

		private static void AttributeLegacyWards(long characterId, string owner)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			if (world == null)
			{
				return;
			}
			int num = 0;
			KeyValuePair<ZDOID, long>[] array = LegacyCreators.ToArray();
			for (int i = 0; i < array.Length; i++)
			{
				KeyValuePair<ZDOID, long> keyValuePair = array[i];
				if (keyValuePair.Value == characterId)
				{
					ZDO zDO = world.GetZDO(keyValuePair.Key);
					LegacyCreators.Remove(keyValuePair.Key);
					if (zDO != null)
					{
						zDO.Set("Landoria.StructureProtection.WardOwner", owner);
						Track(keyValuePair.Key, owner);
						num++;
					}
				}
			}
			if (num > 0)
			{
				StructureProtectionPlugin.Log.LogInfo($"Attributed {num} legacy wards to {owner}; count is {GetCount(owner)}.");
			}
		}

		private static void RejectExcessWard(PrivateArea ward, ZDO zdo, string owner, int maximum)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			StructureProtectionPlugin.Log.LogWarning($"Rejected excess ward {zdo.m_uid} for {owner}: " + $"{GetCount(owner)}/{maximum} wards already exist.");
			SendQuotaForOwner(owner);
			((Component)ward).GetComponent<ZNetView>().ClaimOwnership();
			ZNetScene.instance.Destroy(((Component)ward).gameObject);
		}

		private static string ResolveOwner(PrivateArea ward, ZDO zdo)
		{
			string value = zdo.GetString("Landoria.StructureProtection.WardOwner", "");
			if (!string.IsNullOrWhiteSpace(value))
			{
				return value;
			}
			Piece component = ((Component)ward).GetComponent<Piece>();
			long key = ((component != null) ? component.GetCreator() : 0);
			if (!Identities.TryGetValue(key, out value))
			{
				return null;
			}
			return value;
		}

		private static void ReconcileDestroyed()
		{
			//IL_0021: 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_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			KeyValuePair<ZDOID, string>[] array = Tracked.ToArray();
			for (int i = 0; i < array.Length; i++)
			{
				KeyValuePair<ZDOID, string> keyValuePair = array[i];
				if (world.GetZDO(keyValuePair.Key) == null)
				{
					Tracked.Remove(keyValuePair.Key);
					AllWards.Remove(keyValuePair.Key);
					Counts[keyValuePair.Value]--;
					if (Counts[keyValuePair.Value] == 0)
					{
						Counts.Remove(keyValuePair.Value);
					}
					SendQuotaForOwner(keyValuePair.Value);
					StructureProtectionPlugin.Log.LogInfo($"Removed destroyed ward {keyValuePair.Key} from {keyValuePair.Value}; " + $"count is {GetCount(keyValuePair.Value)}.");
				}
			}
		}

		private static void Track(ZDOID id, string owner)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			Tracked[id] = owner;
			Counts[owner] = GetCount(owner) + 1;
		}

		private static int GetCount(string owner)
		{
			if (!Counts.TryGetValue(owner, out var value))
			{
				return 0;
			}
			return value;
		}

		private static void SendQuotaForOwner(string owner)
		{
			foreach (KeyValuePair<long, string> identity in Identities)
			{
				if (identity.Value == owner)
				{
					StructureProtectionSession.TryGetPeer(identity.Key, out var peerId);
					SendQuota(peerId, owner);
				}
			}
		}

		private static void SendQuota(long peerId, string owner)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Expected O, but got Unknown
			if (registeredRpc != null && peerId != 0L)
			{
				ZPackage val = new ZPackage();
				val.Write(owner);
				val.Write(GetCount(owner));
				registeredRpc.InvokeRoutedRPC(peerId, "Landoria_StructureProtection_WardQuota", new object[1] { val });
			}
		}

		private static void ReceiveQuota(long sender, ZPackage package)
		{
			if (StructureProtectionSession.IsTrustedServer(sender))
			{
				localOwner = package.ReadString();
				localCount = package.ReadInt();
				localReady = true;
				StructureProtectionPlugin.Log.LogInfo($"Received ward quota for {localOwner}: {localCount}/" + $"{StructureProtectionPlugin.Settings.MaximumWardsPerCharacter}.");
			}
		}

		private static string MakeOwner(string platformId, string playerName)
		{
			return SafeSegment(platformId) + "_" + SafeSegment(playerName);
		}

		private static string SafeSegment(string value)
		{
			return new string(value.Select((char character) => (!char.IsControl(character) && !Enumerable.Contains("<>:\"/\\|?*", character)) ? character : '_').ToArray());
		}

		private static void RemoveLocalWard(WearNTear wear)
		{
			object obj;
			if (wear == null)
			{
				obj = null;
			}
			else
			{
				PrivateArea component = ((Component)wear).GetComponent<PrivateArea>();
				if (component == null)
				{
					obj = null;
				}
				else
				{
					ZNetView component2 = ((Component)component).GetComponent<ZNetView>();
					obj = ((component2 != null) ? component2.GetZDO() : null);
				}
			}
			ZDO val = (ZDO)obj;
			if (val != null && !string.IsNullOrWhiteSpace(localOwner) && !(val.GetString("Landoria.StructureProtection.WardOwner", "") != localOwner))
			{
				localCount = Math.Max(0, localCount - 1);
				StructureProtectionPlugin.Log.LogInfo($"Removed local ward for {localOwner}; local count is {localCount}.");
			}
		}
	}
	internal static class WardInactivityJob
	{
		private enum JobState
		{
			Idle,
			WaitingForDependencies,
			Running
		}

		private sealed class JobMetrics
		{
			internal int Examined;

			internal int Active;

			internal int AlreadyInactive;

			internal int Disabled;

			internal int KeptActive;

			internal int MissingActivity;

			internal int InvalidCreator;

			internal int Expired;

			internal int CreatorOnline;
		}

		private const int BatchSize = 50;

		private static readonly List<ZDO> Wards = new List<ZDO>();

		private static JobState state;

		private static HashSet<long> onlineCreators;

		private static DateTime cutoffUtc;

		private static DateTime startedUtc;

		private static float nextRun;

		private static float nextDependencyLog;

		private static int index;

		private static JobMetrics metrics;

		internal static void Update()
		{
			ZNet instance = ZNet.instance;
			if (instance != null && instance.IsServer() && StructureProtectionPlugin.Settings.WardInactivityDays >= 0)
			{
				if (state == JobState.Running)
				{
					ProcessBatchSafely();
				}
				else if (!(Time.unscaledTime < nextRun))
				{
					TryStart();
				}
			}
		}

		internal static void Reset()
		{
			Wards.Clear();
			onlineCreators = null;
			state = JobState.Idle;
			nextRun = 0f;
			nextDependencyLog = 0f;
			index = 0;
			metrics = null;
		}

		private static void TryStart()
		{
			List<ZDO> wards;
			if (state == JobState.Running)
			{
				StructureProtectionPlugin.Log.LogWarning("Skipped a concurrent ward inactivity job launch.");
			}
			else if (!CharacterActivityRegistry.IsReady || !WardQuota.TryGetWardSnapshot(out wards))
			{
				WaitForDependencies();
			}
			else
			{
				Begin(wards);
			}
		}

		private static void WaitForDependencies()
		{
			state = JobState.WaitingForDependencies;
			nextRun = Time.unscaledTime + 10f;
			if (!(Time.unscaledTime < nextDependencyLog))
			{
				nextDependencyLog = Time.unscaledTime + 60f;
				StructureProtectionPlugin.Log.LogInfo("Deferred the ward inactivity job until activity and ward data are ready.");
			}
		}

		private static void Begin(List<ZDO> wards)
		{
			Wards.Clear();
			Wards.AddRange(wards);
			onlineCreators = StructureProtectionSession.GetOnlinePlayers();
			startedUtc = DateTime.UtcNow;
			cutoffUtc = startedUtc.AddDays(-StructureProtectionPlugin.Settings.WardInactivityDays);
			index = 0;
			metrics = new JobMetrics();
			state = JobState.Running;
			StructureProtectionPlugin.Log.LogInfo($"Started ward inactivity job for {Wards.Count} wards; " + $"cutoff={cutoffUtc:O}.");
		}

		private static void ProcessBatchSafely()
		{
			try
			{
				if (!CharacterActivityRegistry.IsReady)
				{
					Finish();
					WaitForDependencies();
					return;
				}
				int num = Math.Min(index + 50, Wards.Count);
				while (index < num)
				{
					ProcessWard(Wards[index++]);
				}
				if (index >= Wards.Count)
				{
					Complete();
				}
			}
			catch (Exception arg)
			{
				StructureProtectionPlugin.Log.LogError($"Ward inactivity job failed: {arg}");
				Finish();
			}
		}

		private static void ProcessWard(ZDO ward)
		{
			metrics.Examined++;
			if (ward == null || !ward.GetBool(ZDOVars.s_enabled, false))
			{
				metrics.AlreadyInactive++;
				return;
			}
			metrics.Active++;
			long num = ward.GetLong(ZDOVars.s_creator, 0L);
			if (num == 0L)
			{
				metrics.InvalidCreator++;
				Disable(ward, num, "creator ID is missing");
			}
			else if (onlineCreators.Contains(num))
			{
				metrics.CreatorOnline++;
			}
			else
			{
				EvaluateLastConnection(ward, num);
			}
		}

		private static void EvaluateLastConnection(ZDO ward, long creator)
		{
			if (!CharacterActivityRegistry.TryGetCharacterLastSeenOnlineUtc(creator, out var lastSeenOnlineUtc))
			{
				metrics.MissingActivity++;
				Disable(ward, creator, "last seen online is missing");
			}
			else if (lastSeenOnlineUtc <= cutoffUtc)
			{
				metrics.Expired++;
				Disable(ward, creator, $"last seen online at {lastSeenOnlineUtc:O}");
			}
			else
			{
				metrics.KeptActive++;
			}
		}

		private static void Disable(ZDO ward, long creator, string reason)
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			ward.SetOwner(ZDOMan.GetSessionID());
			ward.Set(ZDOVars.s_enabled, false);
			metrics.Disabled++;
			string text = ward.GetString("Landoria.StructureProtection.WardOwner", "");
			StructureProtectionPlugin.Log.LogInfo($"Disabled inactive ward {ward.m_uid}; creator={creator}, " + "owner=" + text + ", reason=" + reason + ".");
		}

		private static void Complete()
		{
			double totalMilliseconds = (DateTime.UtcNow - startedUtc).TotalMilliseconds;
			StructureProtectionPlugin.Log.LogInfo($"Completed ward inactivity job in {totalMilliseconds:F0} ms: " + $"examined={metrics.Examined}, active={metrics.Active}, " + $"alreadyInactive={metrics.AlreadyInactive}, disabled={metrics.Disabled}, " + $"keptActive={metrics.KeptActive}, missingActivity={metrics.MissingActivity}, " + $"invalidCreator={metrics.InvalidCreator}, expired={metrics.Expired}, " + $"creatorOnline={metrics.CreatorOnline}.");
			Finish();
		}

		private static void Finish()
		{
			Wards.Clear();
			onlineCreators = null;
			metrics = null;
			state = JobState.Idle;
			nextRun = Time.unscaledTime + (float)StructureProtectionPlugin.Settings.WardInactivityCheckHours * 3600f;
		}
	}
	internal static class CharacterActivityRegistry
	{
		private sealed class ActivityObservation
		{
			internal string PlatformId { get; }

			internal long CharacterId { get; }

			internal string PlayerName { get; }

			internal DateTime SeenOnlineUtc { get; }

			internal string CharacterKey { get; }

			internal ActivityObservation(string platformId, long characterId, string playerName, DateTime seenOnlineUtc)
			{
				PlatformId = platformId;
				CharacterId = characterId;
				PlayerName = playerName;
				SeenOnlineUtc = seenOnlineUtc;
				CharacterKey = SafeSegment(platformId) + "_" + SafeSegment(playerName);
			}
		}

		private const string RecordTypeKey = "Landoria.StructureProtection.Activity.Type";

		private const string PlatformIdKey = "Landoria.StructureProtection.Activity.PlatformId";

		private const string PlayerNameKey = "Landoria.StructureProtection.Activity.PlayerName";

		private const string CharacterKeyKey = "Landoria.StructureProtection.Activity.CharacterKey";

		private const string CharacterIdKey = "Landoria.StructureProtection.Activity.CharacterId";

		private const string FirstConnectedKey = "Landoria.StructureProtection.Activity.FirstConnectedUtc";

		private const string LastSeenOnlineKey = "Landoria.StructureProtection.Activity.LastSeenOnlineUtc";

		private const int PlatformRecord = 1;

		private const int CharacterRecord = 2;

		private static readonly Vector3 StoragePosition = new Vector3(100000f, -10000f, 100000f);

		private static readonly Dictionary<string, ZDO> Platforms = new Dictionary<string, ZDO>(StringComparer.Ordinal);

		private static readonly Dictionary<long, ZDO> Characters = new Dictionary<long, ZDO>();

		private static readonly List<ZDO> Scanned = new List<ZDO>();

		private static readonly List<ActivityObservation> Pending = new List<ActivityObservation>();

		private static ZDOMan world;

		private static int scanIndex;

		private static bool scanning;

		internal static bool IsReady
		{
			get
			{
				if (world != null)
				{
					return !scanning;
				}
				return false;
			}
		}

		internal static void Update()
		{
			ZNet instance = ZNet.instance;
			if (instance != null && instance.IsServer() && ZDOMan.instance != null)
			{
				if (world != ZDOMan.instance)
				{
					BeginScan(ZDOMan.instance);
				}
				if (scanning)
				{
					ContinueScan();
				}
			}
		}

		internal static void Reset()
		{
			Platforms.Clear();
			Characters.Clear();
			Scanned.Clear();
			Pending.Clear();
			world = null;
			scanIndex = 0;
			scanning = false;
		}

		internal static void Record(string platformId, long characterId, string playerName, DateTime seenOnlineUtc)
		{
			ActivityObservation activityObservation = new ActivityObservation(platformId, characterId, playerName, seenOnlineUtc);
			if (world == null || scanning)
			{
				Pending.Add(activityObservation);
			}
			else
			{
				Record(activityObservation);
			}
		}

		internal static bool TryGetPlatformLastSeenOnlineUtc(string platformPlayerId, out DateTime lastSeenOnlineUtc)
		{
			lastSeenOnlineUtc = default(DateTime);
			if (platformPlayerId != null && Platforms.TryGetValue(platformPlayerId, out var value))
			{
				return TryReadLastSeenOnlineUtc(value, out lastSeenOnlineUtc);
			}
			return false;
		}

		internal static bool TryGetCharacterLastSeenOnlineUtc(long characterId, out DateTime lastSeenOnlineUtc)
		{
			lastSeenOnlineUtc = default(DateTime);
			if (Characters.TryGetValue(characterId, out var value))
			{
				return TryReadLastSeenOnlineUtc(value, out lastSeenOnlineUtc);
			}
			return false;
		}

		private static void BeginScan(ZDOMan manager)
		{
			Platforms.Clear();
			Characters.Clear();
			Scanned.Clear();
			world = manager;
			scanIndex = 0;
			scanning = true;
			StructureProtectionPlugin.Log.LogInfo("Started character activity reconstruction.");
		}

		private static void ContinueScan()
		{
			if (!world.GetAllZDOsWithPrefabIterative("Landoria_CharacterActivity", Scanned, ref scanIndex))
			{
				return;
			}
			foreach (ZDO item in Scanned)
			{
				ReadRecord(item);
			}
			scanning = false;
			StructureProtectionPlugin.Log.LogInfo($"Rebuilt character activity: {Platforms.Count} platform accounts, " + $"{Characters.Count} characters.");
			FlushPending();
		}

		private static void ReadRecord(ZDO zdo)
		{
			switch (zdo.GetInt("Landoria.StructureProtection.Activity.Type", 0))
			{
			case 1:
				AddPlatform(zdo);
				break;
			case 2:
				AddCharacter(zdo);
				break;
			}
		}

		private static void AddPlatform(ZDO zdo)
		{
			string text = zdo.GetString("Landoria.StructureProtection.Activity.PlatformId", "");
			if (!string.IsNullOrWhiteSpace(text))
			{
				Platforms[text] = zdo;
			}
		}

		private static void AddCharacter(ZDO zdo)
		{
			long num = zdo.GetLong("Landoria.StructureProtection.Activity.CharacterId", 0L);
			if (num != 0L)
			{
				Characters[num] = zdo;
			}
		}

		private static void FlushPending()
		{
			foreach (ActivityObservation item in Pending)
			{
				Record(item);
			}
			Pending.Clear();
		}

		private static void Record(ActivityObservation observation)
		{
			long ticks = observation.SeenOnlineUtc.ToUniversalTime().Ticks;
			GetOrCreatePlatform(observation.PlatformId, ticks).Set("Landoria.StructureProtection.Activity.LastSeenOnlineUtc", ticks);
			ZDO orCreateCharacter = GetOrCreateCharacter(observation, ticks);
			orCreateCharacter.Set("Landoria.StructureProtection.Activity.PlatformId", observation.PlatformId);
			orCreateCharacter.Set("Landoria.StructureProtection.Activity.PlayerName", observation.PlayerName);
			orCreateCharacter.Set("Landoria.StructureProtection.Activity.CharacterKey", observation.CharacterKey);
			orCreateCharacter.Set("Landoria.StructureProtection.Activity.LastSeenOnlineUtc", ticks);
			StructureProtectionPlugin.Log.LogInfo("Recorded last seen online for " + observation.CharacterKey + " at " + $"{observation.SeenOnlineUtc:O}.");
		}

		private static ZDO GetOrCreatePlatform(string platformId, long ticks)
		{
			if (Platforms.TryGetValue(platformId, out var value))
			{
				return value;
			}
			value = CreateRecord(1, ticks);
			value.Set("Landoria.StructureProtection.Activity.PlatformId", platformId);
			Platforms[platformId] = value;
			return value;
		}

		private static ZDO GetOrCreateCharacter(ActivityObservation observation, long ticks)
		{
			if (Characters.TryGetValue(observation.CharacterId, out var value))
			{
				return value;
			}
			value = CreateRecord(2, ticks);
			value.Set("Landoria.StructureProtection.Activity.CharacterId", observation.CharacterId);
			Characters[observation.CharacterId] = value;
			return value;
		}

		private static ZDO CreateRecord(int type, long ticks)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			int stableHashCode = StringExtensionMethods.GetStableHashCode("Landoria_CharacterActivity");
			ZDO obj = world.CreateNewZDO(StoragePosition, stableHashCode);
			obj.Persistent = true;
			obj.SetPrefab(stableHashCode);
			obj.Set("Landoria.StructureProtection.Activity.Type", type);
			obj.Set("Landoria.StructureProtection.Activity.FirstConnectedUtc", ticks);
			return obj;
		}

		private static bool TryReadUtc(ZDO zdo, string key, out DateTime value)
		{
			long num = zdo.GetLong(key, 0L);
			if (num > 0)
			{
				DateTime maxValue = DateTime.MaxValue;
				if (num <= maxValue.Ticks)
				{
					value = new DateTime(num, DateTimeKind.Utc);
					return true;
				}
			}
			value = default(DateTime);
			return false;
		}

		private static bool TryReadLastSeenOnlineUtc(ZDO zdo, out DateTime value)
		{
			return TryReadUtc(zdo, "Landoria.StructureProtection.Activity.LastSeenOnlineUtc", out value);
		}

		private static string SafeSegment(string value)
		{
			return new string(value.Select((char character) => (!char.IsControl(character) && !Enumerable.Contains("<>:\"/\\|?*", character)) ? character : '_').ToArray());
		}
	}
	internal static class CharacterActivityPrefab
	{
		[HarmonyPatch(typeof(ZNetScene), "Awake")]
		private static class ZNetSceneAwakePatch
		{
			private static void Prefix(ZNetScene __instance)
			{
				//IL_0032: Unknown result type (might be due to invalid IL or missing references)
				//IL_0038: Expected O, but got Unknown
				//IL_0054: Unknown result type (might be due to invalid IL or missing references)
				if (!__instance.m_prefabs.Any((GameObject prefab) => (Object)(object)prefab != (Object)null && ((Object)prefab).name == "Landoria_CharacterActivity"))
				{
					GameObject val = new GameObject("Landoria_CharacterActivity");
					val.SetActive(false);
					ZNetView obj = val.AddComponent<ZNetView>();
					obj.m_persistent = true;
					obj.m_distant = false;
					obj.m_type = (ObjectType)0;
					__instance.m_prefabs.Add(val);
					StructureProtectionPlugin.Log.LogInfo("Registered the Landoria_CharacterActivity world-record prefab.");
				}
			}
		}

		internal const string Name = "Landoria_CharacterActivity";
	}
}
namespace Landoria.SharedLib
{
	public abstract class LandoriaPlugin : BaseUnityPlugin
	{
		private Harmony _harmony;

		private bool _patchesApplied;

		protected ModLog InitializePlugin(string pluginGuid)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Expected O, but got Unknown
			ModLog modLog = new ModLog(((BaseUnityPlugin)this).Logger);
			Version version = ((object)this).GetType().Assembly.GetName().Version;
			modLog.LogInfo($"AssemblyVersion: {version}.");
			EnsureSharedPatches(modLog);
			_harmony = new Harmony(pluginGuid);
			PatchOwnNamespace(modLog);
			return modLog;
		}

		protected ModLog InitializePlugin(string pluginGuid, Type[] patchTypes)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Expected O, but got Unknown
			ModLog modLog = new ModLog(((BaseUnityPlugin)this).Logger);
			Version version = ((object)this).GetType().Assembly.GetName().Version;
			modLog.LogInfo($"AssemblyVersion: {version}.");
			EnsureSharedPatches(modLog);
			_harmony = new Harmony(pluginGuid);
			foreach (Type type in patchTypes)
			{
				_harmony.CreateClassProcessor(type).Patch();
				modLog.LogInfo("Server patch: " + type.Name);
			}
			_patchesApplied = true;
			return modLog;
		}

		private static void EnsureSharedPatches(ModLog log)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			lock (AppDomain.CurrentDomain)
			{
				if (AppDomain.CurrentDomain.GetData("Landoria.SharedLib.ConnectionFailureMenuPatch.v1") == null)
				{
					new Harmony("Landoria.SharedLib").CreateClassProcessor(typeof(ConnectionFailureMenuPatch)).Patch();
					AppDomain.CurrentDomain.SetData("Landoria.SharedLib.ConnectionFailureMenuPatch.v1", true);
					log.LogDebug("Shared connection failure menu patch was applied.");
				}
			}
		}

		protected void PatchOwnNamespace(ModLog log)
		{
			if (_patchesApplied)
			{
				log.LogDebug("Harmony patches are already active; skipping registration.");
				return;
			}
			string text = ((object)this).GetType().Namespace;
			Type[] types = Assembly.GetExecutingAssembly().GetTypes();
			foreach (Type type in types)
			{
				if (type.Namespace == text)
				{
					_harmony.CreateClassProcessor(type).Patch();
				}
			}
			_patchesApplied = true;
			log.LogDebug("Harmony patches were applied for the plugin namespace.");
		}

		protected void ShutdownPlugin()
		{
			if (_patchesApplied)
			{
				Harmony harmony = _harmony;
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
				_patchesApplied = false;
			}
		}
	}
	public static class ConnectionFailureMessages
	{
		private const string StateKey = "Landoria.SharedLib.ConnectionFailureMessages.v1";

		private static readonly ManualLogSource Log = Logger.CreateLogSource("Landoria.ConnectionFailureMessages");

		public static void Push(string source, string message)
		{
			Push(source, message, (string)null);
		}

		public static void Push(string source, string userMessage, string systemMessage)
		{
			Push(source, userMessage, systemMessage, null);
		}

		public static void Push(string source, string userMessage, Exception exception)
		{
			Push(source, userMessage, exception?.Message, exception);
		}

		public static void Push(string source, string userMessage, string systemMessage, Exception exception)
		{
			string text = DisplayMessage(userMessage, systemMessage ?? exception?.Message);
			if (string.IsNullOrWhiteSpace(source) || text == null)
			{
				return;
			}
			bool flag = false;
			lock (AppDomain.CurrentDomain)
			{
				Stack<Tuple<string, string, string>> messages = GetMessages();
				Tuple<string, string, string> item = Tuple.Create(source, text, systemMessage);
				if (!messages.Contains(item))
				{
					messages.Push(item);
					flag = true;
				}
			}
			if (flag)
			{
				LogQueued(source, text, systemMessage, exception);
			}
		}

		public static void Clear(string source)
		{
			if (string.IsNullOrWhiteSpace(source))
			{
				return;
			}
			lock (AppDomain.CurrentDomain)
			{
				Stack<Tuple<string, string, string>> messages = GetMessages();
				Stack<Tuple<string, string, string>> stack = new Stack<Tuple<string, string, string>>();
				while (messages.Count > 0)
				{
					Tuple<string, string, string> tuple = messages.Pop();
					if (tuple.Item1 != source)
					{
						stack.Push(tuple);
					}
				}
				while (stack.Count > 0)
				{
					messages.Push(stack.Pop());
				}
			}
		}

		internal static bool TryPopAll(out string message)
		{
			lock (AppDomain.CurrentDomain)
			{
				Stack<Tuple<string, string, string>> messages = GetMessages();
				if (messages.Count > 0)
				{
					List<string> list = new List<string>();
					while (messages.Count > 0)
					{
						list.Add(messages.Pop().Item2);
					}
					message = string.Join("\n", list);
					return true;
				}
			}
			message = null;
			return false;
		}

		private static Stack<Tuple<string, string, string>> GetMessages()
		{
			if (AppDomain.CurrentDomain.GetData("Landoria.SharedLib.ConnectionFailureMessages.v1") is Stack<Tuple<string, string, string>> result)
			{
				return result;
			}
			Stack<Tuple<string, string, string>> stack = new Stack<Tuple<string, string, string>>();
			AppDomain.CurrentDomain.SetData("Landoria.SharedLib.ConnectionFailureMessages.v1", stack);
			return stack;
		}

		private static string DisplayMessage(string userMessage, string systemMessage)
		{
			if (!string.IsNullOrWhiteSpace(userMessage))
			{
				return userMessage;
			}
			if (!string.IsNullOrWhiteSpace(systemMessage))
			{
				return systemMessage;
			}
			return null;
		}

		private static void LogQueued(string source, string userMessage, string systemMessage, Exception exception)
		{
			string text = exception?.ToString() ?? Environment.StackTrace;
			if (string.IsNullOrWhiteSpace(systemMessage) || systemMessage == userMessage)
			{
				Log.LogWarning((object)("Queued connection failure message from " + source + ": " + userMessage + "\nDiagnostic stack:\n" + text));
			}
			else
			{
				Log.LogWarning((object)("Queued connection failure message from " + source + ": userMessage=" + userMessage + "; systemMessage=" + systemMessage + "\nDiagnostic stack:\n" + text));
			}
		}
	}
	[HarmonyPatch]
	internal static class ConnectionFailureMenuPatch
	{
		[HarmonyPostfix]
		[HarmonyPatch(typeof(FejdStartup), "ShowConnectError")]
		private static void ShowConnectError(TMP_Text ___m_connectionFailedError)
		{
			if (ConnectionFailureMessages.TryPopAll(out var message))
			{
				___m_connectionFailedError.text = message;
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(FejdStartup), "Start")]
		private static void Start(GameObject ___m_connectionFailedPanel, TMP_Text ___m_connectionFailedError)
		{
			ShowNext(___m_connectionFailedPanel, ___m_connectionFailedError);
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(FejdStartup), "OnConnectionFailedOk")]
		private static void OnConnectionFailedOk(GameObject ___m_connectionFailedPanel, TMP_Text ___m_connectionFailedError)
		{
			ShowNext(___m_connectionFailedPanel, ___m_connectionFailedError);
		}

		private static void ShowNext(GameObject panel, TMP_Text text)
		{
			if (ConnectionFailureMessages.TryPopAll(out var message))
			{
				text.text = message;
				panel.SetActive(true);
			}
		}
	}
	public sealed class ModLog
	{
		private readonly ManualLogSource _logger;

		public ModLog(ManualLogSource logger)
		{
			_logger = logger;
		}

		public void LogFatal(object message)
		{
			Write((LogLevel)1, message);
		}

		public void LogError(object message)
		{
			Write((LogLevel)2, message);
		}

		public void LogWarning(object message)
		{
			Write((LogLevel)4, message);
		}

		public void LogMessage(object message)
		{
			Write((LogLevel)8, message);
		}

		public void LogInfo(object message)
		{
			Write((LogLevel)16, message);
		}

		public void LogDebug(object message)
		{
			Write((LogLevel)32, message);
		}

		public void Log(LogLevel level, object message)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			Write(level, message);
		}

		private void Write(LogLevel level, object message)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			string arg = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
			_logger.Log(level, (object)$"[{arg}] {message}");
		}
	}
	public static class PlatformPlayerIdentity
	{
		public unsafe static string Resolve(PlayerInfo player)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			string text = ((object)(*(PlatformUserID*)(&player.m_userInfo.m_id))/*cast due to .constrained prefix*/).ToString();
			if (!string.IsNullOrWhiteSpace(text))
			{
				return text;
			}
			ZNetPeer obj = FindPeer(player);
			if (obj == null)
			{
				return null;
			}
			ISocket socket = obj.m_socket;
			if (socket == null)
			{
				return null;
			}
			return socket.GetHostName();
		}

		public static ZNetPeer FindPeer(PlayerInfo player)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ZNet.instance == (Object)null || ((ZDOID)(ref player.m_characterID)).IsNone())
			{
				return null;
			}
			return ((IEnumerable<ZNetPeer>)ZNet.instance.GetPeers()).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer peer) => peer != null && ((ZDOID)(ref peer.m_characterID)).Equals(player.m_characterID)));
		}
	}
	public static class ServerRole
	{
		public static bool IsDedicatedServer
		{
			get
			{
				if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
				{
					return ZNet.instance.IsDedicated();
				}
				return false;
			}
		}
	}
}