Decompiled source of SealedTombstone v1.0.0

IronLabs.SealedTombstone.dll

Decompiled 3 hours ago
using 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 IronLabs.SharedLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("IronLabs.SealedTombstone")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("IronLabs.SealedTombstone")]
[assembly: AssemblyCopyright("Copyright ©  2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("37181DCE-EEA5-4CC8-8DF3-32B9AB883DE8")]
[assembly: AssemblyFileVersion("1.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.0.0.2687")]
namespace IronLabs.SealedTombstone
{
	internal sealed class PendingRequest
	{
		internal long RequesterPlayerId { get; set; }

		internal string RequesterName { get; set; }

		internal ZDOID TombstoneId { get; set; }

		internal DateTime CreatedAt { get; set; }
	}
	internal static class RecentAttackers
	{
		private static readonly TimeSpan Window = TimeSpan.FromMinutes(2.0);

		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);
			if (!((Object)(object)victim != (Object)(object)Player.m_localPlayer) && !((Object)(object)val == (Object)null))
			{
				long playerID = val.GetPlayerID();
				if (playerID != 0L && playerID != victim.GetPlayerID())
				{
					LastHits[playerID] = DateTime.UtcNow;
				}
			}
		}

		internal static void SnapshotForDeath(Player player)
		{
			if (!((Object)(object)player != (Object)(object)Player.m_localPlayer))
			{
				DateTime threshold = DateTime.UtcNow - Window;
				_deathSnapshot = (from entry in LastHits
					where entry.Value >= threshold
					select entry.Key).ToArray();
				LastHits.Clear();
				SealedTombstonePlugin.Log.LogDebug($"Captured {_deathSnapshot.Length} recent PvP attackers.");
			}
		}

		internal static string ConsumeSnapshot()
		{
			string result = string.Join(",", _deathSnapshot.Select((long id) => id.ToString(CultureInfo.InvariantCulture)));
			_deathSnapshot = new long[0];
			return result;
		}

		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 void Reset()
		{
			LastHits.Clear();
			_deathSnapshot = new long[0];
		}
	}
	internal static class TombstoneAccess
	{
		private const string LockDayKey = "IronLabs.SealedTombstone.LockDay";

		private const string BlockedPlayersKey = "IronLabs.SealedTombstone.BlockedPlayers";

		private const string RequestRpc = "IronLabs_SealedTombstone_Request";

		private const string DecisionRpc = "IronLabs_SealedTombstone_Decision";

		private const int UnlockAfterDays = 10;

		private const int RequestExpirySeconds = 30;

		private const int RequestCooldownSeconds = 120;

		private static PendingRequest _pendingRequest;

		private static DateTime _lastRequestAt = DateTime.MinValue;

		private static ZRoutedRpc _registeredRpc;

		internal static void RegisterRpcs()
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance != null && instance != _registeredRpc)
			{
				instance.Register<long, string, ZDOID, long>("IronLabs_SealedTombstone_Request", (Method<long, string, ZDOID, long>)ReceiveRequest);
				instance.Register<long, ZDOID, bool, string>("IronLabs_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;
			RecentAttackers.Reset();
		}

		internal static void RecordLockDay(TombStone tombstone, long ownerId)
		{
			//IL_005b: 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("IronLabs.SealedTombstone.LockDay", CurrentDay());
				val.Set("IronLabs.SealedTombstone.BlockedPlayers", RecentAttackers.ConsumeSnapshot());
				SealedTombstonePlugin.Log.LogDebug($"Locked tombstone {val.m_uid} for player {ownerId}.");
			}
		}

		internal static bool AllowInteraction(TombStone tombstone, Humanoid character)
		{
			//IL_00b1: 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 num = val2.GetLong(ZDOVars.s_owner, 0L);
			if (num == 0L || num == val.GetPlayerID() || IsExpired(val2))
			{
				return true;
			}
			if (IsBlocked(val2, val.GetPlayerID()))
			{
				((Character)val).Message((MessageType)2, "You cannot request access to this tombstone.", 0, (Sprite)null);
				SealedTombstonePlugin.Log.LogDebug($"Blocked tombstone request from recent attacker {val.GetPlayerID()}.");
				return false;
			}
			RequestAccess(val, num, val2.m_uid);
			return false;
		}

		internal static void Tick()
		{
			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_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			if ((DateTime.UtcNow - _lastRequestAt).TotalSeconds < 120.0)
			{
				((Character)requester).Message((MessageType)2, "Please wait before sending another request.", 0, (Sprite)null);
				return;
			}
			if (ZRoutedRpc.instance == null)
			{
				((Character)requester).Message((MessageType)2, "The tombstone owner is offline.", 0, (Sprite)null);
				return;
			}
			_lastRequestAt = DateTime.UtcNow;
			((Character)requester).Message((MessageType)2, "Access request sent to the tombstone owner.", 0, (Sprite)null);
			ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "IronLabs_SealedTombstone_Request", new object[4]
			{
				requester.GetPlayerID(),
				requester.GetPlayerName(),
				tombstoneId,
				ownerId
			});
			SealedTombstonePlugin.Log.LogInfo($"Player {requester.GetPlayerID()} requested access to {tombstoneId}.");
		}

		private static void ReceiveRequest(long sender, long requesterId, string requesterName, ZDOID tombstoneId, long ownerId)
		{
			//IL_0013: 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)
			Player localPlayer = Player.m_localPlayer;
			ZDOMan instance = ZDOMan.instance;
			ZDO tombstone = ((instance != null) ? instance.GetZDO(tombstoneId) : null);
			if (!((Object)(object)localPlayer == (Object)null) && localPlayer.GetPlayerID() == ownerId && _pendingRequest == null && !IsBlocked(tombstone, requesterId))
			{
				_pendingRequest = new PendingRequest
				{
					RequesterPlayerId = requesterId,
					RequesterName = SafeName(requesterName),
					TombstoneId = tombstoneId,
					CreatedAt = DateTime.UtcNow
				};
				ShowDecisionPopup(_pendingRequest);
			}
		}

		private static void ShowDecisionPopup(PendingRequest request)
		{
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Expected O, but got Unknown
			//IL_0079: Expected O, but got Unknown
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: 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;
			}
			string text = "Allow " + request.RequesterName + " to loot this tombstone?";
			UnifiedPopup.Push((PopupBase)new YesNoPopup("Tombstone access request", text, (PopupButtonCallback)delegate
			{
				Decide(request, accepted: true);
			}, (PopupButtonCallback)delegate
			{
				Decide(request, accepted: 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);
			}
		}

		private static void SendDecision(PendingRequest request, bool accepted)
		{
			//IL_0048: 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)
			{
				ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "IronLabs_SealedTombstone_Decision", new object[4]
				{
					request.RequesterPlayerId,
					request.TombstoneId,
					accepted,
					localPlayer.GetPlayerName()
				});
				SealedTombstonePlugin.Log.LogInfo($"Tombstone request from {request.RequesterPlayerId} accepted={accepted}.");
			}
		}

		private static void ReceiveDecision(long sender, long requesterId, ZDOID tombstoneId, bool accepted, string ownerName)
		{
			//IL_002c: 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)
			{
				if (accepted)
				{
					Unlock(tombstoneId);
				}
				string text = (accepted ? (SafeName(ownerName) + " granted access to the tombstone.") : (SafeName(ownerName) + " denied or did not answer the request."));
				((Character)localPlayer).Message((MessageType)2, text, 0, (Sprite)null);
			}
		}

		private static void Unlock(ZDOID tombstoneId)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: 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)
		{
			return tombstone != null && RecentAttackers.Contains(tombstone.GetString("IronLabs.SealedTombstone.BlockedPlayers", ""), playerId);
		}

		private static bool IsExpired(ZDO tombstone)
		{
			long num = tombstone.GetLong("IronLabs.SealedTombstone.LockDay", -1L);
			long num2 = CurrentDay();
			return num >= 0 && num2 >= 0 && num2 - num >= 10;
		}

		private static long CurrentDay()
		{
			return ((Object)(object)EnvMan.instance != (Object)null) ? EnvMan.instance.GetDay() : (-1);
		}

		private static bool HasExpired(PendingRequest request)
		{
			return (DateTime.UtcNow - request.CreatedAt).TotalSeconds > 30.0;
		}

		private static string SafeName(string name)
		{
			if (string.IsNullOrWhiteSpace(name))
			{
				return "A player";
			}
			string text = name.Replace("<", string.Empty).Replace(">", string.Empty);
			return (text.Length <= 64) ? text : text.Substring(0, 64);
		}

		private static void ClosePopup()
		{
			if (UnifiedPopup.IsAvailable() && UnifiedPopup.IsVisible())
			{
				UnifiedPopup.Pop();
			}
		}
	}
	[BepInPlugin("IronLabs.SealedTombstone", "IronLabs.SealedTombstone", "1.0.0")]
	public sealed class SealedTombstonePlugin : IronLabsPlugin
	{
		private const string PluginGuid = "IronLabs.SealedTombstone";

		private const string PluginName = "IronLabs.SealedTombstone";

		private const string PluginVersion = "1.0.0";

		internal static ModLog Log { get; private set; }

		private void Awake()
		{
			Log = InitializePlugin("IronLabs.SealedTombstone");
			Log.LogInfo("IronLabs.SealedTombstone 1.0.0 is loaded.");
		}

		private void Update()
		{
			TombstoneAccess.Tick();
		}

		private void OnDestroy()
		{
			TombstoneAccess.ResetSession();
			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 IronLabs.SharedLib
{
	public abstract class IronLabsPlugin : BaseUnityPlugin
	{
		private Harmony _harmony;

		private bool _patchesApplied;

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

		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 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_001a: 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}");
		}
	}
}