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 SealedTombstone v1.0.10
Landoria.SealedTombstone.dll
Decompiled 2 weeks agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; 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.SealedTombstone")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Landoria.SealedTombstone")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("37181DCE-EEA5-4CC8-8DF3-32B9AB883DE8")] [assembly: AssemblyFileVersion("1.0.10")] [assembly: AssemblyInformationalVersion("1.0.10")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = "")] [assembly: AssemblyVersion("1.0.10.13992")] namespace Landoria.SealedTombstone { internal sealed class PendingRequest { internal long RequesterPlayerId { get; set; } internal string RequesterName { get; set; } internal long OwnerPlayerId { get; set; } internal ZDOID TombstoneId { get; set; } internal DateTime CreatedAt { get; set; } } internal static class RecentAttackerPolicy { internal static readonly TimeSpan Window = TimeSpan.FromMinutes(2.0); internal static bool ShouldRecord(bool victimIsLocal, bool hasPlayerAttacker, long victimId, long attackerId) { if (victimIsLocal && hasPlayerAttacker && attackerId != 0L) { return attackerId != victimId; } return false; } internal static long[] Snapshot(IReadOnlyDictionary<long, DateTime> lastHits, DateTime deathAt) { DateTime threshold = deathAt - Window; return (from entry in lastHits where entry.Value >= threshold select entry.Key).ToArray(); } internal static string Serialize(IEnumerable<long> playerIds) { return string.Join(",", playerIds.Select((long id) => id.ToString(CultureInfo.InvariantCulture))); } internal static bool Contains(string serializedIds, long playerId) { if (string.IsNullOrEmpty(serializedIds)) { return false; } string expected = playerId.ToString(CultureInfo.InvariantCulture); return serializedIds.Split(new char[1] { ',' }).Any((string id) => id == expected); } } internal static class RecentAttackers { private static readonly Dictionary<long, DateTime> LastHits = new Dictionary<long, DateTime>(); private static long[] _deathSnapshot = new long[0]; internal static void Record(Player victim, HitData hit) { Character obj = ((hit != null) ? hit.GetAttacker() : null); Player val = (Player)(object)((obj is Player) ? obj : null); long num = ((val != null) ? val.GetPlayerID() : 0); if (RecentAttackerPolicy.ShouldRecord((Object)(object)victim == (Object)(object)Player.m_localPlayer, (Object)(object)val != (Object)null, victim.GetPlayerID(), num)) { LastHits[num] = DateTime.UtcNow; } } internal static void SnapshotForDeath(Player player) { if (!((Object)(object)player != (Object)(object)Player.m_localPlayer)) { _deathSnapshot = RecentAttackerPolicy.Snapshot(LastHits, DateTime.UtcNow); LastHits.Clear(); SealedTombstonePlugin.Log.LogDebug($"Captured {_deathSnapshot.Length} recent PvP attackers."); } } internal static string ConsumeSnapshot() { string result = RecentAttackerPolicy.Serialize(_deathSnapshot); _deathSnapshot = new long[0]; return result; } internal static bool Contains(string serializedIds, long playerId) { return RecentAttackerPolicy.Contains(serializedIds, playerId); } internal static void Reset() { LastHits.Clear(); _deathSnapshot = new long[0]; } } internal enum TombstoneInteraction { Allow, RequestAccess, Block } internal static class TombstoneAccessPolicy { internal const int UnlockAfterDays = 10; internal static readonly TimeSpan RequestExpiry = TimeSpan.FromSeconds(30.0); internal static readonly TimeSpan RequestCooldown = TimeSpan.FromMinutes(2.0); internal static TombstoneInteraction Evaluate(bool hasPlayerAndTombstone, long ownerId, long playerId, long lockDay, long currentDay, bool blocked) { if (!hasPlayerAndTombstone || ownerId == 0L || ownerId == playerId) { return TombstoneInteraction.Allow; } if (blocked) { return TombstoneInteraction.Block; } if (!IsExpired(lockDay, currentDay)) { return TombstoneInteraction.RequestAccess; } return TombstoneInteraction.Allow; } internal static bool IsExpired(long lockDay, long currentDay) { if (lockDay >= 0 && currentDay >= 0) { return currentDay - lockDay >= 10; } return false; } internal static bool HasRequestExpired(DateTime createdAt, DateTime now) { return now - createdAt > RequestExpiry; } internal static bool IsCooldownActive(DateTime lastRequestAt, DateTime now) { return now - lastRequestAt < RequestCooldown; } internal static string SafeName(string name) { if (string.IsNullOrWhiteSpace(name)) { return "A player"; } string text = name.Replace("<", string.Empty).Replace(">", string.Empty); if (text.Length > 64) { return text.Substring(0, 64); } return text; } } internal sealed class TombstoneAvailabilityResult { internal DateTime LastRequestAt; internal string Message; } internal static class TombstoneRequestPolicy { internal static TombstoneAvailabilityResult ApplyAvailability(bool ownerOnline, DateTime lastRequestAt, DateTime now) { return new TombstoneAvailabilityResult { LastRequestAt = (ownerOnline ? now : lastRequestAt), Message = (ownerOnline ? "Access request sent to the tombstone owner." : "The tombstone owner is offline.") }; } internal static string DecisionMessage(bool accepted, string ownerName) { if (!accepted) { return TombstoneAccessPolicy.SafeName(ownerName) + " denied or did not answer the request."; } return TombstoneAccessPolicy.SafeName(ownerName) + " granted access to the tombstone."; } } internal sealed class TombstoneRequestPresentation { internal string Title; internal string Message; } internal static class TombstonePresentationPolicy { internal static TombstoneRequestPresentation Build(string requesterName) { return new TombstoneRequestPresentation { Title = "Tombstone access request", Message = "Allow " + TombstoneAccessPolicy.SafeName(requesterName) + " to loot this tombstone?" }; } } internal static class TombstoneAccess { private const string LockDayKey = "Landoria.SealedTombstone.LockDay"; private const string BlockedPlayersKey = "Landoria.SealedTombstone.BlockedPlayers"; private const string AccessCheckRpc = "Landoria_SealedTombstone_AccessCheck"; private const string IdentityRpc = "Landoria_SealedTombstone_Identity"; private const string RequestRpc = "Landoria_SealedTombstone_Request"; private const string AvailabilityRpc = "Landoria_SealedTombstone_Availability"; private const string DecisionSubmitRpc = "Landoria_SealedTombstone_DecisionSubmit"; private const string DecisionRpc = "Landoria_SealedTombstone_Decision"; private static PendingRequest _pendingRequest; private static DateTime _lastRequestAt = DateTime.MinValue; private static ZRoutedRpc _registeredRpc; private static readonly Dictionary<long, long> PeerPlayers = new Dictionary<long, long>(); private static long _identityServer; internal static void RegisterRpcs() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && instance != _registeredRpc) { instance.Register<long, string, ZDOID, long>("Landoria_SealedTombstone_AccessCheck", (Method<long, string, ZDOID, long>)ReceiveAccessCheck); instance.Register<long>("Landoria_SealedTombstone_Identity", (Action<long, long>)ReceiveIdentity); instance.Register<long, string, ZDOID, long>("Landoria_SealedTombstone_Request", (Method<long, string, ZDOID, long>)ReceiveRequest); instance.Register<bool>("Landoria_SealedTombstone_Availability", (Action<long, bool>)ReceiveAvailability); instance.Register<long, ZDOID, bool, string, long>("Landoria_SealedTombstone_DecisionSubmit", (Method<long, ZDOID, bool, string, long>)ReceiveDecisionSubmission); instance.Register<long, ZDOID, bool, string>("Landoria_SealedTombstone_Decision", (Method<long, ZDOID, bool, string>)ReceiveDecision); _registeredRpc = instance; SealedTombstonePlugin.Log.LogDebug("Tombstone access RPCs registered for the current session."); } } internal static void ResetSession() { _pendingRequest = null; _lastRequestAt = DateTime.MinValue; _registeredRpc = null; PeerPlayers.Clear(); _identityServer = 0L; RecentAttackers.Reset(); } internal static void RecordLockDay(TombStone tombstone, long ownerId) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) ZNetView component = ((Component)tombstone).GetComponent<ZNetView>(); ZDO val = ((component != null) ? component.GetZDO() : null); if (ownerId != 0L && val != null && component.IsOwner()) { val.Set("Landoria.SealedTombstone.LockDay", CurrentDay()); val.Set("Landoria.SealedTombstone.BlockedPlayers", RecentAttackers.ConsumeSnapshot()); SealedTombstonePlugin.Log.LogDebug($"Locked tombstone {val.m_uid} for player {ownerId}."); } } internal static bool AllowInteraction(TombStone tombstone, Humanoid character) { //IL_009e: Unknown result type (might be due to invalid IL or missing references) Player val = (Player)(object)((character is Player) ? character : null); ZNetView component = ((Component)tombstone).GetComponent<ZNetView>(); ZDO val2 = ((component != null) ? component.GetZDO() : null); if ((Object)(object)val == (Object)null || val2 == null) { return true; } long playerID = val.GetPlayerID(); long ownerId = val2.GetLong(ZDOVars.s_owner, 0L); switch (TombstoneAccessPolicy.Evaluate(hasPlayerAndTombstone: true, ownerId, playerID, val2.GetLong("Landoria.SealedTombstone.LockDay", -1L), CurrentDay(), IsBlocked(val2, playerID))) { case TombstoneInteraction.Allow: return true; case TombstoneInteraction.Block: ((Character)val).Message((MessageType)2, "You cannot request access to this tombstone.", 0, (Sprite)null, false); SealedTombstonePlugin.Log.LogDebug($"Blocked tombstone request from recent attacker {val.GetPlayerID()}."); return false; default: RequestAccess(val, ownerId, val2.m_uid); return false; } } internal static void Tick() { SyncIdentity(); if (_pendingRequest != null && HasExpired(_pendingRequest)) { SealedTombstonePlugin.Log.LogDebug($"Request from {_pendingRequest.RequesterPlayerId} expired."); ClosePopup(); SendDecision(_pendingRequest, accepted: false); _pendingRequest = null; } } private static void RequestAccess(Player requester, long ownerId, ZDOID tombstoneId) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) if (TombstoneAccessPolicy.IsCooldownActive(_lastRequestAt, DateTime.UtcNow)) { ((Character)requester).Message((MessageType)2, "Please wait before sending another request.", 0, (Sprite)null, false); return; } ZRoutedRpc instance = ZRoutedRpc.instance; ZNet instance2 = ZNet.instance; if (instance == null || (Object)(object)instance2 == (Object)null) { ((Character)requester).Message((MessageType)2, "The tombstone owner is offline.", 0, (Sprite)null, false); return; } long playerID = requester.GetPlayerID(); if (instance2.IsServer()) { ProcessAccessCheck(0L, playerID, requester.GetPlayerName(), tombstoneId, ownerId); return; } ZNetPeer serverPeer = instance2.GetServerPeer(); if (serverPeer == null) { ((Character)requester).Message((MessageType)2, "The tombstone owner is offline.", 0, (Sprite)null, false); return; } instance.InvokeRoutedRPC(serverPeer.m_uid, "Landoria_SealedTombstone_AccessCheck", new object[4] { playerID, requester.GetPlayerName(), tombstoneId, ownerId }); } private static void ReceiveAccessCheck(long sender, long requesterId, string requesterName, ZDOID tombstoneId, long ownerId) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && ZNet.instance.GetPeer(sender) != null && requesterId != 0L) { PeerPlayers[sender] = requesterId; ProcessAccessCheck(sender, requesterId, requesterName, tombstoneId, ownerId); } } private static void ProcessAccessCheck(long requesterPeer, long requesterId, string requesterName, ZDOID tombstoneId, long ownerId) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) long num = FindOnlinePeer(ownerId); Player localPlayer = Player.m_localPlayer; bool flag = localPlayer != null && localPlayer.GetPlayerID() == ownerId; SendAvailability(requesterPeer, num != 0 || flag); if (flag) { ReceiveRequest(0L, requesterId, requesterName, tombstoneId, ownerId); } else if (num != 0L) { ZRoutedRpc.instance.InvokeRoutedRPC(num, "Landoria_SealedTombstone_Request", new object[4] { requesterId, requesterName, tombstoneId, ownerId }); } } private static long FindOnlinePeer(long playerId) { foreach (KeyValuePair<long, long> peerPlayer in PeerPlayers) { ZNetPeer peer = ZNet.instance.GetPeer(peerPlayer.Key); if (peerPlayer.Value == playerId && peer != null && peer.IsReady()) { return peerPlayer.Key; } } return 0L; } private static void SyncIdentity() { ZNet instance = ZNet.instance; Player localPlayer = Player.m_localPlayer; if ((Object)(object)instance == (Object)null || (Object)(object)localPlayer == (Object)null || instance.IsServer()) { return; } ZNetPeer serverPeer = instance.GetServerPeer(); if (serverPeer != null && serverPeer.m_uid != _identityServer) { _identityServer = serverPeer.m_uid; ZRoutedRpc instance2 = ZRoutedRpc.instance; if (instance2 != null) { instance2.InvokeRoutedRPC(serverPeer.m_uid, "Landoria_SealedTombstone_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; } } private static void SendAvailability(long requesterPeer, bool online) { if (requesterPeer == 0L) { ReceiveAvailability(0L, online); return; } ZRoutedRpc.instance.InvokeRoutedRPC(requesterPeer, "Landoria_SealedTombstone_Availability", new object[1] { online }); } private static void ReceiveAvailability(long sender, bool ownerOnline) { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && IsServerResponse(sender)) { TombstoneAvailabilityResult tombstoneAvailabilityResult = TombstoneRequestPolicy.ApplyAvailability(ownerOnline, _lastRequestAt, DateTime.UtcNow); _lastRequestAt = tombstoneAvailabilityResult.LastRequestAt; ((Character)localPlayer).Message((MessageType)2, tombstoneAvailabilityResult.Message, 0, (Sprite)null, false); if (ownerOnline) { SealedTombstonePlugin.Log.LogInfo($"Player {localPlayer.GetPlayerID()} requested tombstone access."); } } } private static bool IsServerResponse(long sender) { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null) { return false; } ZNetPeer serverPeer = instance.GetServerPeer(); return TombstoneDecisionPolicy.IsTrustedResponse(instance.IsServer(), sender, serverPeer != null, serverPeer?.m_uid ?? 0); } private static void ReceiveRequest(long sender, long requesterId, string requesterName, ZDOID tombstoneId, long ownerId) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; ZDOMan instance = ZDOMan.instance; ZDO tombstone = ((instance != null) ? instance.GetZDO(tombstoneId) : null); if (IsServerResponse(sender) && !((Object)(object)localPlayer == (Object)null) && localPlayer.GetPlayerID() == ownerId && _pendingRequest == null && !IsBlocked(tombstone, requesterId)) { _pendingRequest = new PendingRequest { RequesterPlayerId = requesterId, RequesterName = SafeName(requesterName), OwnerPlayerId = ownerId, TombstoneId = tombstoneId, CreatedAt = DateTime.UtcNow }; ShowDecisionPopup(_pendingRequest); } } private static void ShowDecisionPopup(PendingRequest request) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown //IL_006c: Expected O, but got Unknown //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown if (!UnifiedPopup.IsAvailable()) { SealedTombstonePlugin.Log.LogWarning("The unlock request was rejected because the VV popup is unavailable."); Decide(request, accepted: false); return; } TombstoneRequestPresentation tombstoneRequestPresentation = TombstonePresentationPolicy.Build(request.RequesterName); UnifiedPopup.Push((PopupBase)new YesNoPopup(tombstoneRequestPresentation.Title, tombstoneRequestPresentation.Message, (PopupButtonCallback)delegate { Decide(request, accepted: true); }, (PopupButtonCallback)delegate { Decide(request, accepted: false); }, false, false)); } private static void Decide(PendingRequest request, bool accepted) { if (request != _pendingRequest || HasExpired(request)) { ClosePopup(); _pendingRequest = null; return; } ClosePopup(); _pendingRequest = null; SendDecision(request, accepted); Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, accepted ? "Tombstone access granted." : "Tombstone access denied.", 0, (Sprite)null, false); } } private static void SendDecision(PendingRequest request, bool accepted) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || ZRoutedRpc.instance == null) { return; } ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null) { return; } if (instance.IsServer()) { ForwardDecision(0L, request.RequesterPlayerId, request.TombstoneId, accepted, localPlayer.GetPlayerName(), request.OwnerPlayerId); } else { ZNetPeer serverPeer = instance.GetServerPeer(); if (serverPeer != null) { ZRoutedRpc.instance.InvokeRoutedRPC(serverPeer.m_uid, "Landoria_SealedTombstone_DecisionSubmit", new object[5] { request.RequesterPlayerId, request.TombstoneId, accepted, localPlayer.GetPlayerName(), request.OwnerPlayerId }); } } SealedTombstonePlugin.Log.LogInfo($"Tombstone request from {request.RequesterPlayerId} accepted={accepted}."); } private static void ReceiveDecisionSubmission(long sender, long requesterId, ZDOID tombstoneId, bool accepted, string ownerName, long ownerId) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) ForwardDecision(sender, requesterId, tombstoneId, accepted, ownerName, ownerId); } private static void ForwardDecision(long sender, long requesterId, ZDOID tombstoneId, bool accepted, string ownerName, long ownerId) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) ZNet instance = ZNet.instance; ZDOMan instance2 = ZDOMan.instance; ZDO val = ((instance2 != null) ? instance2.GetZDO(tombstoneId) : null); long value; long mappedPlayerId = ((sender == 0L) ? ownerId : (PeerPlayers.TryGetValue(sender, out value) ? value : 0)); bool senderPeerExists = sender == 0L || ((instance != null) ? instance.GetPeer(sender) : null) != null; if (!TombstoneDecisionPolicy.CanForward(instance != null && instance.IsServer(), senderPeerExists, mappedPlayerId, ownerId, (val != null) ? val.GetLong(ZDOVars.s_owner, 0L) : 0)) { return; } long num = FindOnlinePeer(requesterId); if (num == 0L) { Player localPlayer = Player.m_localPlayer; if (localPlayer != null && localPlayer.GetPlayerID() == requesterId) { ReceiveDecision(0L, requesterId, tombstoneId, accepted, ownerName); return; } } if (num != 0L) { ZRoutedRpc.instance.InvokeRoutedRPC(num, "Landoria_SealedTombstone_Decision", new object[4] { requesterId, tombstoneId, accepted, ownerName }); } } private static void ReceiveDecision(long sender, long requesterId, ZDOID tombstoneId, bool accepted, string ownerName) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && localPlayer.GetPlayerID() == requesterId && IsServerResponse(sender)) { if (TombstoneDecisionPolicy.ShouldUnlock(accepted)) { Unlock(tombstoneId); } ((Character)localPlayer).Message((MessageType)2, TombstoneRequestPolicy.DecisionMessage(accepted, ownerName), 0, (Sprite)null, false); } } private static void Unlock(ZDOID tombstoneId) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) ZNetScene instance = ZNetScene.instance; object obj; if (instance == null) { obj = null; } else { GameObject obj2 = instance.FindInstance(tombstoneId); obj = ((obj2 != null) ? obj2.GetComponent<ZNetView>() : null); } ZNetView val = (ZNetView)obj; if ((Object)(object)val == (Object)null || !val.IsValid()) { SealedTombstonePlugin.Log.LogWarning($"Cannot unlock unavailable tombstone {tombstoneId}."); return; } val.ClaimOwnership(); val.GetZDO().Set(ZDOVars.s_owner, 0L); SealedTombstonePlugin.Log.LogInfo($"Tombstone {tombstoneId} was unlocked."); } private static bool IsBlocked(ZDO tombstone, long playerId) { if (tombstone != null) { return RecentAttackers.Contains(tombstone.GetString("Landoria.SealedTombstone.BlockedPlayers", ""), playerId); } return false; } private static long CurrentDay() { if (!((Object)(object)EnvMan.instance != (Object)null)) { return -1L; } return EnvMan.instance.GetDay(); } private static bool HasExpired(PendingRequest request) { return TombstoneAccessPolicy.HasRequestExpired(request.CreatedAt, DateTime.UtcNow); } private static string SafeName(string name) { return TombstoneAccessPolicy.SafeName(name); } private static void ClosePopup() { if (UnifiedPopup.IsAvailable() && UnifiedPopup.IsVisible()) { UnifiedPopup.Pop(); } } } internal static class TombstoneDecisionPolicy { internal static bool CanForward(bool isServer, bool senderPeerExists, long mappedPlayerId, long expectedOwnerId, long tombstoneOwnerId) { if (isServer && senderPeerExists && mappedPlayerId != 0L && mappedPlayerId == expectedOwnerId) { return tombstoneOwnerId == expectedOwnerId; } return false; } internal static bool IsTrustedResponse(bool isServerHost, long sender, bool hasServerPeer, long serverPeerId) { if (!isServerHost) { if (hasServerPeer) { return sender == serverPeerId; } return false; } return sender == 0; } internal static bool ShouldUnlock(bool accepted) { return accepted; } } [BepInPlugin("Landoria.SealedTombstone", "Landoria.SealedTombstone", "1.0.10")] public sealed class SealedTombstonePlugin : LandoriaPlugin { private const string PluginGuid = "Landoria.SealedTombstone"; private const string PluginName = "Landoria.SealedTombstone"; private const string PluginVersion = "1.0.10"; internal static ModLog Log { get; private set; } private void Awake() { Log = InitializePlugin("Landoria.SealedTombstone"); Log.LogInfo("Landoria.SealedTombstone 1.0.10 is loaded."); } private void Update() { TombstoneAccess.Tick(); } private void OnDestroy() { TombstoneAccess.ResetSession(); Log?.LogInfo("Landoria.SealedTombstone 1.0.10 is unloaded."); ShutdownPlugin(); Log = null; } } [HarmonyPatch(typeof(Game), "Start")] internal static class RpcRegistrationPatch { private static void Postfix() { TombstoneAccess.RegisterRpcs(); } } [HarmonyPatch(typeof(TombStone), "Setup")] internal static class TombstoneSetupPatch { private static void Postfix(TombStone __instance, long ownerUID) { TombstoneAccess.RecordLockDay(__instance, ownerUID); } } [HarmonyPatch(typeof(TombStone), "Interact")] internal static class TombstoneInteractPatch { private static bool Prefix(TombStone __instance, Humanoid character) { return TombstoneAccess.AllowInteraction(__instance, character); } } [HarmonyPatch(typeof(Player), "OnDamaged")] internal static class PlayerDamagedPatch { private static void Postfix(Player __instance, HitData hit) { RecentAttackers.Record(__instance, hit); } } [HarmonyPatch(typeof(Player), "OnDeath")] internal static class PlayerDeathPatch { private static void Prefix(Player __instance) { RecentAttackers.SnapshotForDeath(__instance); } } } 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; } } } }