Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of StructureProtection v1.0.6
Landoria.StructureProtection.dll
Decompiled a month agousing 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; } } } }