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 Hearthbound v1.0.0
plugins/Hearthbound/Hearthbound.dll
Decompiled a week agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: AssemblyCompany("Hearthbound")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("(c) 2026 Cody Keats")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+a39e0e375d6e32cb9d7f852ad69de887d3146b83")] [assembly: AssemblyProduct("Hearthbound")] [assembly: AssemblyTitle("Hearthbound")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] namespace Hearthbound { [BepInPlugin("com.cocopuff2u.hearthbound", "Hearthbound", "1.0.0")] public class Plugin : BaseUnityPlugin { internal enum Place { Center, TopLeft } public const string GUID = "com.cocopuff2u.hearthbound"; public const string NAME = "Hearthbound"; public const string VERSION = "1.0.0"; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<Place> MessagePlace; internal static ConfigEntry<int> CountdownSeconds; internal static ConfigEntry<string> AcceptCommand; internal static ConfigEntry<string> DeclineCommand; internal static ConfigEntry<string> DailyResetTime; internal static ConfigEntry<bool> AcceptByCrouch; internal static ConfigEntry<bool> RequireCrouch; internal static ConfigEntry<bool> AskFirst; internal static ConfigEntry<bool> ShowHint; internal static ConfigEntry<bool> AnnounceToAll; internal static ConfigEntry<bool> AllowInDungeons; internal static ConfigEntry<bool> AdminsNoLimits; internal static ConfigEntry<bool> SelfRecall; internal static ConfigEntry<int> MaxMinutesSinceDeath; internal static ConfigEntry<int> CooldownMinutes; internal static ConfigEntry<int> MaxPerDay; internal static ConfigEntry<int> AcceptSeconds; internal static ConfigEntry<int> RescuerCooldownMinutes; internal static ConfigEntry<int> RescuerMaxPerDay; internal static ConfigEntry<float> MinDistance; internal static ConfigEntry<float> BossRadius; internal static ConfigEntry<string> BossLocations; internal static ConfigEntry<string> RecallCommand; internal static ConfigEntry<string> MsgBrought; internal static ConfigEntry<string> MsgDone; internal static ConfigEntry<string> MsgAnnounce; internal static ConfigEntry<string> MsgHint; internal static ConfigEntry<string> MsgAsk; internal static ConfigEntry<string> MsgAskSent; internal static ConfigEntry<string> MsgDeclined; internal static ConfigEntry<string> MsgCooldown; internal static ConfigEntry<string> MsgNoneLeft; internal static ConfigEntry<string> MsgOffline; internal static ConfigEntry<string> MsgNotBack; internal static ConfigEntry<string> MsgTooLate; internal static ConfigEntry<string> MsgAlreadyThere; internal static ConfigEntry<string> MsgDungeon; internal static ConfigEntry<string> MsgBoss; internal static ConfigEntry<string> MsgRescuerCooldown; internal static ConfigEntry<string> MsgRescuerNoneLeft; internal static ConfigEntry<string> MsgRecalled; internal static ConfigEntry<string> MsgNoTombstone; internal static ConfigEntry<string> MsgCountdown; internal static ConfigEntry<bool> LogEachRez; internal static string UsesFile; internal static ManualLogSource Logs; private FileSystemWatcher watcher; private volatile bool changed; private float reloadAt; private void Awake() { //IL_063e: Unknown result type (might be due to invalid IL or missing references) Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Let players bring fallen friends back to their tombstones"); RequireCrouch = ((BaseUnityPlugin)this).Config.Bind<bool>("Rez", "RequireCrouch", false, "The rescuer has to crouch while opening the tombstone (E). Off: just opening a friend's tombstone is enough"); CooldownMinutes = ((BaseUnityPlugin)this).Config.Bind<int>("Rez", "CooldownMinutes", 10, "Minutes before the same player can be brought back again (0 = no cooldown)"); MaxPerDay = ((BaseUnityPlugin)this).Config.Bind<int>("Rez", "MaxPerDay", 3, "Times one player can be brought back in 24 hours (0 = no limit). Kept across restarts"); MaxMinutesSinceDeath = ((BaseUnityPlugin)this).Config.Bind<int>("Rez", "MaxMinutesSinceDeath", 30, "Only for tombstones at most this many minutes old (0 = any age)"); MinDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Rez", "MinDistance", 30f, "Nothing happens if the fallen player is already this close to their tombstone (meters)"); AskFirst = ((BaseUnityPlugin)this).Config.Bind<bool>("Rez", "AskFirst", true, "The fallen player is asked first and accepts by crouching within AcceptSeconds; doing nothing says no. Off: they're brought right away"); AcceptSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("Rez", "AcceptSeconds", 20, "How long the fallen player has to accept, when AskFirst is on"); AcceptByCrouch = ((BaseUnityPlugin)this).Config.Bind<bool>("Rez", "AcceptByCrouch", true, "The fallen player can accept by crouching"); AcceptCommand = ((BaseUnityPlugin)this).Config.Bind<string>("Rez", "AcceptCommand", "!yes", "Or by typing this in chat (empty = off)"); DeclineCommand = ((BaseUnityPlugin)this).Config.Bind<string>("Rez", "DeclineCommand", "!no", "Typing this in chat says no right away (empty = off; doing nothing also says no)"); CountdownSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("Rez", "CountdownSeconds", 3, "Seconds of \"taking you there in 3...\" before the teleport, so nobody is pulled away mid-step (0 = right away)"); DailyResetTime = ((BaseUnityPlugin)this).Config.Bind<string>("Rez", "DailyResetTime", "", "When the daily limits start over, as a time of day on the server's clock (e.g. \"00:00\" or \"06:00\"). Empty: each use counts for 24 hours from when it happened"); ShowHint = ((BaseUnityPlugin)this).Config.Bind<bool>("Rez", "ShowHint", true, "With RequireCrouch on: opening a friend's tombstone without crouching shows how to bring them back"); AnnounceToAll = ((BaseUnityPlugin)this).Config.Bind<bool>("Rez", "AnnounceToAll", false, "Tell everyone on the server when someone is brought back"); RescuerCooldownMinutes = ((BaseUnityPlugin)this).Config.Bind<int>("Rescuers", "RescuerCooldownMinutes", 0, "Minutes before the same player can bring someone back again (0 = no cooldown)"); RescuerMaxPerDay = ((BaseUnityPlugin)this).Config.Bind<int>("Rescuers", "RescuerMaxPerDay", 6, "Times one player can bring others back in 24 hours (0 = no limit). Kept across restarts"); AdminsNoLimits = ((BaseUnityPlugin)this).Config.Bind<bool>("Rescuers", "AdminsNoLimits", true, "Cooldowns and daily limits don't apply when the rescuer or the fallen player is a server admin (adminlist.txt)"); AllowInDungeons = ((BaseUnityPlugin)this).Config.Bind<bool>("Places", "AllowInDungeons", true, "Allow bringing players to a tombstone inside a dungeon, crypt, cave or other interior"); BossRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Places", "BossRadius", 0f, "No bringing players to a tombstone this close to a boss (meters). 0 = off, rescues allowed anywhere; 80 keeps them out of every boss arena"); BossLocations = ((BaseUnityPlugin)this).Config.Bind<string>("Places", "BossLocations", "Eikthyrnir, GDKing, Bonemass, Dragonqueen, GoblinKing, Mistlands_DvergrBossEntrance1, FaderLocation, DN_Bossroom", "Which bosses BossRadius keeps rescues away from, by the game's name for each boss's place, comma separated. Remove one to allow rescues near that boss. Eikthyrnir = Eikthyr, GDKing = The Elder, Bonemass = Bonemass, Dragonqueen = Moder, GoblinKing = Yagluth, Mistlands_DvergrBossEntrance1 = The Seeker Queen, FaderLocation = Fader, DN_Bossroom = the Deep North boss"); SelfRecall = ((BaseUnityPlugin)this).Config.Bind<bool>("Recall", "SelfRecall", false, "Players can call themselves back to their newest tombstone by typing RecallCommand in chat. Counts toward the same cooldown and daily limit as being brought back, and the same places and age rules apply"); RecallCommand = ((BaseUnityPlugin)this).Config.Bind<string>("Recall", "RecallCommand", "!recall", "What to type in chat (not starting with /, which the game keeps to itself)"); MessagePlace = ((BaseUnityPlugin)this).Config.Bind<Place>("Messages", "MessagePlace", Place.Center, "Where players see these: Center (big, middle of the screen) or TopLeft (small, under the minimap side, out of the way of open chests)"); MsgBrought = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "Brought", "{rescuer} brought you back to your tombstone", "To the fallen player. Placeholders: {rescuer} {player} {minutes} {seconds} {left} {yes} {no}"); MsgDone = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "Done", "{player} is on the way. {left}", "To the rescuer. Placeholders: {rescuer} {player} {minutes} {seconds} {left} {yes} {no}"); MsgAnnounce = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "Announce", "{rescuer} brought {player} back to their tombstone", "To everyone, with AnnounceToAll. Placeholders: {rescuer} {player} {minutes} {seconds} {left} {yes} {no}"); MsgHint = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "Hint", "Crouch and open this tombstone to bring {player} here", "To someone opening a friend's tombstone. Placeholders: {rescuer} {player} {minutes} {seconds} {left} {yes} {no}"); MsgAsk = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "Ask", "{rescuer} can bring you to your tombstone. Crouch or type {yes} to accept", "To the fallen player, with AskFirst. Placeholders: {rescuer} {player} {minutes} {seconds} {left} {yes} {no}"); MsgAskSent = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "AskSent", "Asked {player}...", "To the rescuer, with AskFirst. Placeholders: {rescuer} {player} {minutes} {seconds} {left} {yes} {no}"); MsgDeclined = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "Declined", "{player} didn't accept", "To the rescuer when the time runs out. Placeholders: {rescuer} {player} {minutes} {seconds} {left} {yes} {no}"); MsgCooldown = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "Cooldown", "{player} can be brought back again in {minutes} min", "Placeholders: {rescuer} {player} {minutes} {seconds} {left} {yes} {no}"); MsgNoneLeft = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "NoneLeft", "{player} has been brought back too many times today", "Placeholders: {rescuer} {player} {minutes} {seconds} {left} {yes} {no}"); MsgOffline = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "Offline", "{player} isn't online", "Placeholders: {rescuer} {player} {minutes} {seconds} {left} {yes} {no}"); MsgNotBack = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "NotBack", "{player} hasn't respawned yet", "Placeholders: {rescuer} {player} {minutes} {seconds} {left} {yes} {no}"); MsgTooLate = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "TooLate", "This tombstone is too old to bring {player} back", "Placeholders: {rescuer} {player} {minutes} {seconds} {left} {yes} {no}"); MsgAlreadyThere = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "AlreadyThere", "{player} is already close by", "Placeholders: {rescuer} {player} {minutes} {seconds} {left} {yes} {no}"); MsgDungeon = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "Dungeon", "{player} can't be brought into a dungeon", "Placeholders: {rescuer} {player} {minutes} {seconds} {left} {yes} {no}"); MsgBoss = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "Boss", "{player} can't be brought this close to a boss", "Placeholders: {rescuer} {player} {minutes} {seconds} {left} {yes} {no}"); MsgRescuerCooldown = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "RescuerCooldown", "You can bring someone back again in {minutes} min", "Placeholders: {rescuer} {player} {minutes} {seconds} {left} {yes} {no}"); MsgRescuerNoneLeft = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "RescuerNoneLeft", "You've brought back as many players as you can today", "Placeholders: {rescuer} {player} {minutes} {seconds} {left} {yes} {no}"); MsgRecalled = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "Recalled", "Back to your tombstone. {left}", "With SelfRecall. Placeholders: {rescuer} {player} {minutes} {seconds} {left} {yes} {no}"); MsgNoTombstone = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "NoTombstone", "You don't have a tombstone", "With SelfRecall. Placeholders: {rescuer} {player} {minutes} {seconds} {left} {yes} {no}"); MsgCountdown = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "Countdown", "Taking you to your tombstone in {seconds}...", "To the fallen player, each second of CountdownSeconds. Placeholders: {rescuer} {player} {minutes} {seconds} {left} {yes} {no}"); LogEachRez = ((BaseUnityPlugin)this).Config.Bind<bool>("Log", "LogEachRez", true, "A log line each time someone is brought back, or turned away"); Logs = ((BaseUnityPlugin)this).Logger; UsesFile = Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)this).Config.ConfigFilePath), "hearthbound.uses"); new Harmony("com.cocopuff2u.hearthbound").PatchAll(Assembly.GetExecutingAssembly()); WatchConfig(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Hearthbound 1.0.0 loaded"); } private void Update() { ReloadIfChanged(); Rez.Update(); } private void WatchConfig() { try { watcher = new FileSystemWatcher(Path.GetDirectoryName(((BaseUnityPlugin)this).Config.ConfigFilePath), Path.GetFileName(((BaseUnityPlugin)this).Config.ConfigFilePath)); watcher.Changed += delegate { changed = true; }; watcher.Created += delegate { changed = true; }; watcher.Renamed += delegate { changed = true; }; watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite; watcher.EnableRaisingEvents = true; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Settings won't reload by themselves: " + ex.Message)); } } private void ReloadIfChanged() { if (changed) { changed = false; reloadAt = Time.time + 1f; } if (reloadAt == 0f || Time.time < reloadAt) { return; } reloadAt = 0f; try { bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet; ((BaseUnityPlugin)this).Config.SaveOnConfigSet = false; try { ((BaseUnityPlugin)this).Config.Reload(); } finally { ((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet; } ((BaseUnityPlugin)this).Logger.LogInfo((object)"Settings reloaded"); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Settings not reloaded: " + ex.Message)); } } } internal static class Rez { private sealed class Ask { public long Rescuer; public long Target; public long PlayerId; public ZDOID Tomb; public string Name; public float Until; public bool WasUp; } private sealed class Sent { public long Target; public long PlayerId; public long RescuerId; public ZDOID Tomb; public string Name; public DateTime Use; public float CheckAt; } private sealed class Countdown { public long Target; public long PlayerId; public long RescuerId; public ZDOID Tomb; public string Name; public string Rescuer; public DateTime Use; public float At; public int Shown = -1; } private sealed class Recall { public long Peer; public int Index; public readonly List<ZDO> Found = new List<ZDO>(); } [HarmonyPatch(typeof(ZDO), "Deserialize")] private static class Arrived { private static void Prefix(ZDO __instance, out bool __state) { __state = tombPrefab != 0 && __instance.GetPrefab() == tombPrefab && __instance.GetInt(ZDOVars.s_inUse, 0) == 1; } private static void Postfix(ZDO __instance, bool __state) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (tombPrefab != 0 && !__state && __instance.GetPrefab() == tombPrefab && __instance.GetInt(ZDOVars.s_inUse, 0) == 1) { opened.Add(__instance.m_uid); } } } [HarmonyPatch(typeof(RoutedRPCData), "Deserialize")] private static class Message { private static void Postfix(RoutedRPCData __instance) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown //IL_004c: Unknown result type (might be due to invalid IL or missing references) int methodHash = __instance.m_methodHash; if ((methodHash != SayHash && methodHash != ShoutHash) || (!Plugin.SelfRecall.Value && asks.Count == 0)) { return; } try { ZPackage val = new ZPackage(__instance.m_parameters.GetArray()); if (methodHash == ShoutHash) { val.ReadVector3(); } val.ReadInt(); val.ReadString(); val.ReadString(); said.Add((__instance.m_senderPeerID, val.ReadString())); } catch (Exception) { } } } internal static float ArriveSeconds = 30f; internal static Func<ZNetPeer, bool> IsAdmin = (ZNetPeer p) => p.m_socket != null && ZNet.instance.ListContainsId(ZNet.instance.m_adminList, p.m_socket.GetHostName()); private const float DUNGEON_HEIGHT = 3000f; private static int tombPrefab; private static string tombName; private static float nextFind; private static readonly int SayHash = StringExtensionMethods.GetStableHashCode("Say"); private static readonly int ShoutHash = StringExtensionMethods.GetStableHashCode("ChatMessage"); private static readonly List<ZDOID> opened = new List<ZDOID>(); private static readonly List<Ask> asks = new List<Ask>(); private static readonly List<Sent> sent = new List<Sent>(); private static readonly List<Countdown> countdowns = new List<Countdown>(); private static readonly List<(long Peer, string Text)> said = new List<(long, string)>(); private static readonly List<Recall> recalls = new List<Recall>(); private static readonly Dictionary<long, float> lastCommand = new Dictionary<long, float>(); private static readonly HashSet<(ZDOID, long)> hinted = new HashSet<(ZDOID, long)>(); private static Dictionary<long, List<DateTime>> uses; private static Dictionary<long, List<DateTime>> rescues; private static List<Vector3> bosses; private static string bossesFrom; private static bool Active { get { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer() && ZDOMan.instance != null) { return (Object)(object)ZNetScene.instance != (Object)null; } return false; } } internal static Vector3? FirstBoss { get { //IL_0030: Unknown result type (might be due to invalid IL or missing references) if (bosses == null) { FindBosses(); } if (bosses == null || bosses.Count <= 0) { return null; } return bosses[0]; } } internal static int BossCount { get { if (bosses == null) { FindBosses(); } return bosses?.Count ?? 0; } } internal static bool Recalling => recalls.Count > 0; internal static int TombPrefab => tombPrefab; internal static bool Busy { get { if (asks.Count <= 0 && sent.Count <= 0) { return countdowns.Count > 0; } return true; } } public static void Update() { //IL_003c: Unknown result type (might be due to invalid IL or missing references) if (!Active) { return; } if (tombPrefab == 0 && Time.time >= nextFind) { FindTombstone(); } if (opened.Count > 0) { foreach (ZDOID item in opened) { Opened(item); } opened.Clear(); } if (said.Count > 0) { foreach (var item2 in said) { Said(item2.Peer, item2.Text); } said.Clear(); } if (asks.Count > 0) { CheckAsks(); } if (recalls.Count > 0) { FindForRecall(); } if (countdowns.Count > 0) { CheckCountdowns(); } if (sent.Count > 0) { CheckArrivals(); } } private static void FindTombstone() { nextFind = Time.time + 1f; GameObject prefab = ZNetScene.instance.GetPrefab("Player"); GameObject val = ((prefab == null) ? null : prefab.GetComponent<Player>()?.m_tombstone); if (!((Object)(object)val == (Object)null)) { tombName = ((Object)val).name; tombPrefab = StringExtensionMethods.GetStableHashCode(((Object)val).name); } } internal static void Opened(ZDOID id) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) ZDO zDO = ZDOMan.instance.GetZDO(id); if (zDO == null || !Plugin.Enabled.Value) { return; } ZNetPeer peer = ZNet.instance.GetPeer(zDO.GetOwner()); long num = zDO.GetLong(ZDOVars.s_owner, 0L); if (peer == null || num == 0L || PlayerId(peer) == num) { return; } string text = zDO.GetString(ZDOVars.s_ownerName, ""); if (hinted.Count > 1000) { hinted.Clear(); } ZNetPeer target; string text2 = Check(zDO, num, text, peer, out target); if (Plugin.RequireCrouch.Value && !Crouched(peer)) { if (text2 == null && Plugin.ShowHint.Value && hinted.Add((id, peer.m_uid))) { Tell(peer.m_uid, Say(Plugin.MsgHint, peer.m_playerName, text, 0, 0L)); } } else if (text2 != null) { if (Plugin.RequireCrouch.Value || hinted.Add((id, peer.m_uid))) { Tell(peer.m_uid, text2); } Note(peer.m_playerName + " tried to bring " + text + " back: " + text2); } else if (!asks.Any((Ask a) => a.Target == target.m_uid)) { if (Plugin.AskFirst.Value) { asks.Add(new Ask { Rescuer = peer.m_uid, Target = target.m_uid, PlayerId = num, Tomb = id, Name = text, Until = Time.time + (float)Mathf.Max(1, Plugin.AcceptSeconds.Value), WasUp = !Crouched(target) }); Tell(target.m_uid, Say(Plugin.MsgAsk, peer.m_playerName, text, 0, 0L)); Tell(peer.m_uid, Say(Plugin.MsgAskSent, peer.m_playerName, text, 0, 0L)); } else { Bring(peer, target, zDO, num, text); } } } internal static string Check(ZDO tomb, long playerId, string name, ZNetPeer rescuer, out ZNetPeer target) { //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) target = ((IEnumerable<ZNetPeer>)ZNet.instance.GetPeers()).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer p) => PlayerId(p) == playerId)); string text = ((rescuer != null) ? rescuer.m_playerName : name); if (target == null) { return Say(Plugin.MsgOffline, text, name, 0, 0L); } int value = Plugin.MaxMinutesSinceDeath.Value; long num = tomb.GetLong(ZDOVars.s_timeOfDeath, 0L); if (value > 0 && num != 0L && ZNet.instance.GetTime().Ticks - num > TimeSpan.FromMinutes(value).Ticks) { return Say(Plugin.MsgTooLate, text, name, 0, 0L); } ZDO zDO = ZDOMan.instance.GetZDO(target.m_characterID); if (zDO == null) { return Say(Plugin.MsgNotBack, text, name, 0, 0L); } Vector3 position = tomb.GetPosition(); if (Vector3.Distance(zDO.GetPosition(), position) < Plugin.MinDistance.Value) { return Say(Plugin.MsgAlreadyThere, text, name, 0, 0L); } if (!Plugin.AllowInDungeons.Value && position.y > 3000f) { return Say(Plugin.MsgDungeon, text, name, 0, 0L); } if (NearBoss(position)) { return Say(Plugin.MsgBoss, text, name, 0, 0L); } if (Plugin.AdminsNoLimits.Value && (IsAdmin(target) || (rescuer != null && IsAdmin(rescuer)))) { return null; } string text2 = Limits(Uses(playerId), Plugin.CooldownMinutes.Value, Plugin.MaxPerDay.Value, text, name, Plugin.MsgCooldown, Plugin.MsgNoneLeft); if (text2 != null || rescuer == null) { return text2; } return Limits(Rescues(PlayerId(rescuer)), Plugin.RescuerCooldownMinutes.Value, Plugin.RescuerMaxPerDay.Value, text, name, Plugin.MsgRescuerCooldown, Plugin.MsgRescuerNoneLeft); } private static string Limits(List<DateTime> times, int cooldown, int max, string by, string name, ConfigEntry<string> cooling, ConfigEntry<string> noneLeft) { if (cooldown > 0 && times.Count > 0) { TimeSpan timeSpan = times.Max() + TimeSpan.FromMinutes(cooldown) - DateTime.UtcNow; if (timeSpan > TimeSpan.Zero) { return Say(cooling, by, name, (int)Math.Ceiling(timeSpan.TotalMinutes), 0L); } } if (max > 0 && CountToday(times) >= max) { return Say(noneLeft, by, name, 0, 0L); } return null; } private static bool NearBoss(Vector3 at) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) float value = Plugin.BossRadius.Value; if (value <= 0f) { return false; } if (bosses == null || bossesFrom != Plugin.BossLocations.Value) { FindBosses(); } float num = value * value; foreach (Vector3 boss in bosses) { float num2 = boss.x - at.x; float num3 = boss.z - at.z; if (num2 * num2 + num3 * num3 < num) { return true; } } return false; } private static void FindBosses() { //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) bossesFrom = Plugin.BossLocations.Value; HashSet<string> hashSet = new HashSet<string>(from s in bossesFrom.Split(new char[1] { ',' }) select s.Trim() into s where s.Length > 0 select s, StringComparer.OrdinalIgnoreCase); bosses = new List<Vector3>(); if ((Object)(object)ZoneSystem.instance == (Object)null) { bosses = null; return; } foreach (LocationInstance value in ZoneSystem.instance.m_locationInstances.Values) { if (value.m_location != null && hashSet.Contains(value.m_location.m_prefabName)) { bosses.Add(value.m_position); } } } private static void Bring(ZNetPeer rescuer, ZNetPeer target, ZDO tomb, long playerId, string name) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) DateTime utcNow = DateTime.UtcNow; Uses(playerId).Add(utcNow); long num = ((rescuer != null) ? PlayerId(rescuer) : 0); if (num != 0L) { Rescues(num).Add(utcNow); } SaveUses(); if (rescuer != null) { Tell(rescuer.m_uid, Say(Plugin.MsgDone, rescuer.m_playerName, name, 0, playerId)); } Countdown countdown = new Countdown { Target = target.m_uid, PlayerId = playerId, RescuerId = num, Tomb = tomb.m_uid, Name = name, Rescuer = rescuer?.m_playerName, Use = utcNow, At = Time.time + (float)Mathf.Max(0, Plugin.CountdownSeconds.Value) }; if (Plugin.CountdownSeconds.Value <= 0) { Teleport(countdown, target, tomb); } else { countdowns.Add(countdown); } } private static void CheckCountdowns() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) for (int num = countdowns.Count - 1; num >= 0; num--) { Countdown countdown = countdowns[num]; ZNetPeer peer = ZNet.instance.GetPeer(countdown.Target); ZDO zDO = ZDOMan.instance.GetZDO(countdown.Tomb); if (peer == null || zDO == null) { countdowns.RemoveAt(num); Refund(countdown.PlayerId, countdown.RescuerId, countdown.Use); Note(countdown.Name + " wasn't brought back (left the game, or the tombstone is gone); that one doesn't count"); } else { float num2 = countdown.At - Time.time; if (num2 <= 0f) { countdowns.RemoveAt(num); Teleport(countdown, peer, zDO); } else { int num3 = Mathf.CeilToInt(num2); if (num3 != countdown.Shown) { countdown.Shown = num3; Tell(peer.m_uid, Say(Plugin.MsgCountdown, countdown.Rescuer ?? countdown.Name, countdown.Name, 0, 0L, num3)); } } } } } private static void Teleport(Countdown c, ZNetPeer target, ZDO tomb) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) Vector3 val = tomb.GetPosition() + Vector3.up * 0.5f; ZRoutedRpc.instance.InvokeRoutedRPC(target.m_uid, "RPC_TeleportPlayer", new object[3] { val, Quaternion.identity, true }); if (c.Rescuer != null) { Tell(target.m_uid, Say(Plugin.MsgBrought, c.Rescuer, c.Name, 0, 0L)); } else { Tell(target.m_uid, Say(Plugin.MsgRecalled, c.Name, c.Name, 0, c.PlayerId)); } if (Plugin.AnnounceToAll.Value && c.Rescuer != null) { string text = Say(Plugin.MsgAnnounce, c.Rescuer, c.Name, 0, 0L); foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer.m_uid != target.m_uid) { Tell(peer.m_uid, text); } } } sent.Add(new Sent { Target = target.m_uid, PlayerId = c.PlayerId, RescuerId = c.RescuerId, Tomb = c.Tomb, Name = c.Name, Use = c.Use, CheckAt = Time.time + ArriveSeconds }); Note((c.Rescuer != null) ? (c.Rescuer + " brought " + c.Name + " back to their tombstone at " + Where(val)) : (c.Name + " called themselves back to their tombstone at " + Where(val))); } private static void Refund(long playerId, long rescuerId, DateTime use) { bool flag = Uses(playerId).Remove(use); if (rescuerId != 0L) { flag |= Rescues(rescuerId).Remove(use); } if (flag) { SaveUses(); } } private static void CheckAsks() { //IL_0048: Unknown result type (might be due to invalid IL or missing references) for (int num = asks.Count - 1; num >= 0; num--) { Ask ask = asks[num]; if (ZNet.instance.GetPeer(ask.Target) == null || ZNet.instance.GetPeer(ask.Rescuer) == null || ZDOMan.instance.GetZDO(ask.Tomb) == null) { asks.RemoveAt(num); continue; } if (Plugin.AcceptByCrouch.Value) { if (!Crouched(ZNet.instance.GetPeer(ask.Target))) { ask.WasUp = true; } else if (ask.WasUp) { Answer(ask, yes: true); continue; } } if (Time.time >= ask.Until) { asks.RemoveAt(num); Tell(ask.Rescuer, Say(Plugin.MsgDeclined, NameOf(ask.Rescuer), ask.Name, 0, 0L)); } } } private static void Answer(Ask a, bool yes) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) asks.Remove(a); ZNetPeer peer = ZNet.instance.GetPeer(a.Target); ZNetPeer peer2 = ZNet.instance.GetPeer(a.Rescuer); ZDO zDO = ZDOMan.instance.GetZDO(a.Tomb); if (peer == null || peer2 == null || zDO == null) { return; } if (!yes) { Tell(peer2.m_uid, Say(Plugin.MsgDeclined, peer2.m_playerName, a.Name, 0, 0L)); return; } ZNetPeer target; string text = Check(zDO, a.PlayerId, a.Name, peer2, out target); if (text != null) { Tell(peer2.m_uid, text); Tell(peer.m_uid, text); } else { Bring(peer2, peer, zDO, a.PlayerId, a.Name); } } private static string NameOf(long peer) { return ZNet.instance.GetPeer(peer)?.m_playerName ?? ""; } private static bool Is(string text, ConfigEntry<string> command) { string text2 = command.Value.Trim(); if (text2.Length > 0) { return string.Equals(text, text2, StringComparison.OrdinalIgnoreCase); } return false; } private static void Said(long peerId, string text) { if (!Plugin.Enabled.Value || text == null) { return; } text = text.Trim(); Ask ask = asks.FirstOrDefault((Ask a) => a.Target == peerId); if (ask != null) { if (Is(text, Plugin.AcceptCommand)) { Answer(ask, yes: true); return; } if (Is(text, Plugin.DeclineCommand)) { Answer(ask, yes: false); return; } } if (Plugin.SelfRecall.Value && Is(text, Plugin.RecallCommand) && ZNet.instance.GetPeer(peerId) != null && (!lastCommand.TryGetValue(peerId, out var value) || !(Time.time - value < 3f))) { lastCommand[peerId] = Time.time; if (!recalls.Any((Recall r) => r.Peer == peerId)) { recalls.Add(new Recall { Peer = peerId }); } } } private static void FindForRecall() { Recall recall = recalls[0]; if (tombName == null || !ZDOMan.instance.GetAllZDOsWithPrefabIterative(tombName, recall.Found, ref recall.Index)) { return; } recalls.RemoveAt(0); ZNetPeer peer = ZNet.instance.GetPeer(recall.Peer); if (peer == null) { return; } long playerId = PlayerId(peer); ZDO val = (from z in recall.Found where z.GetLong(ZDOVars.s_owner, 0L) == playerId orderby z.GetLong(ZDOVars.s_timeOfDeath, 0L) descending select z).FirstOrDefault(); if (val == null) { Tell(peer.m_uid, Say(Plugin.MsgNoTombstone, peer.m_playerName, peer.m_playerName, 0, 0L)); return; } string text = val.GetString(ZDOVars.s_ownerName, ""); ZNetPeer target; string text2 = Check(val, playerId, text, null, out target); if (text2 != null) { Tell(peer.m_uid, text2); Note(text + " tried to call themselves back: " + text2); } else { Bring(null, target, val, playerId, text); } } private static void CheckArrivals() { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_007c: 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) for (int num = Rez.sent.Count - 1; num >= 0; num--) { Sent sent = Rez.sent[num]; if (!(Time.time < sent.CheckAt)) { Rez.sent.RemoveAt(num); ZNetPeer peer = ZNet.instance.GetPeer(sent.Target); ZDO zDO = ZDOMan.instance.GetZDO(sent.Tomb); ZDO val = ((peer != null) ? ZDOMan.instance.GetZDO(peer.m_characterID) : null); if (val != null && zDO != null && !(Vector3.Distance(val.GetPosition(), zDO.GetPosition()) <= Mathf.Max(Plugin.MinDistance.Value, 10f))) { Refund(sent.PlayerId, sent.RescuerId, sent.Use); Note(sent.Name + " didn't arrive at their tombstone; that one doesn't count"); } } } } internal static long PlayerId(ZNetPeer p) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) if (p.m_playerID != 0L) { return p.m_playerID; } ZDO zDO = ZDOMan.instance.GetZDO(p.m_characterID); if (zDO == null) { return 0L; } return zDO.GetLong(ZDOVars.s_playerID, 0L); } internal static bool Crouched(ZNetPeer p) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) ZDO zDO = ZDOMan.instance.GetZDO(p.m_characterID); if (zDO != null) { return zDO.GetInt(438569 + Player.s_crouching, 0) == 1; } return false; } private static void Tell(long peer, string text) { if (!string.IsNullOrEmpty(text)) { ZRoutedRpc.instance.InvokeRoutedRPC(peer, "ShowMessage", new object[2] { (Plugin.MessagePlace.Value == Plugin.Place.TopLeft) ? 1 : 2, text }); } } internal static string Say(ConfigEntry<string> msg, string rescuer, string player, int minutes = 0, long playerId = 0L, int seconds = 0) { string newValue = ""; int value = Plugin.MaxPerDay.Value; if (value > 0 && playerId != 0L) { newValue = $"{Math.Max(0, value - CountToday(Uses(playerId)))} more today"; } return msg.Value.Replace("{rescuer}", rescuer).Replace("{player}", player).Replace("{minutes}", minutes.ToString()) .Replace("{seconds}", seconds.ToString()) .Replace("{left}", newValue) .Replace("{yes}", Plugin.AcceptCommand.Value.Trim()) .Replace("{no}", Plugin.DeclineCommand.Value.Trim()) .Trim(); } internal static int CountToday(List<DateTime> times) { DateTime dateTime = DayStart(); int num = 0; foreach (DateTime time in times) { if (time >= dateTime) { num++; } } return num; } internal static DateTime DayStart() { string text = Plugin.DailyResetTime.Value.Trim(); if (text.Length == 0 || !TimeSpan.TryParse(text, out var result) || result < TimeSpan.Zero || result >= TimeSpan.FromDays(1.0)) { return DateTime.UtcNow.AddHours(-24.0); } DateTime now = DateTime.Now; DateTime dateTime = now.Date + result; if (dateTime > now) { dateTime = dateTime.AddDays(-1.0); } return dateTime.ToUniversalTime(); } internal static void ForgetUses() { uses = null; rescues = null; } internal static List<DateTime> Uses(long playerId) { return Recent(ref uses, playerId); } internal static List<DateTime> Rescues(long playerId) { return Recent(ref rescues, playerId); } private static List<DateTime> Recent(ref Dictionary<long, List<DateTime>> map, long playerId) { if (map == null) { LoadUses(); } if (!map.TryGetValue(playerId, out var value)) { value = (map[playerId] = new List<DateTime>()); } DateTime dayAgo = DateTime.UtcNow.AddHours(-24.0); value.RemoveAll((DateTime t) => t < dayAgo); return value; } private static void LoadUses() { uses = new Dictionary<long, List<DateTime>>(); rescues = new Dictionary<long, List<DateTime>>(); try { if (!File.Exists(Plugin.UsesFile)) { return; } string[] array = File.ReadAllLines(Plugin.UsesFile); for (int i = 0; i < array.Length; i++) { List<string> list = array[i].Split(new char[1] { ' ' }).ToList(); Dictionary<long, List<DateTime>> dictionary = uses; if (list.Count > 0 && list[0] == "r") { dictionary = rescues; list.RemoveAt(0); } if (list.Count < 2 || !long.TryParse(list[0], out var result)) { continue; } List<DateTime> list2 = new List<DateTime>(); for (int j = 1; j < list.Count; j++) { if (long.TryParse(list[j], out var result2)) { list2.Add(new DateTime(result2, DateTimeKind.Utc)); } } dictionary[result] = list2; } } catch (Exception ex) { Plugin.Logs.LogWarning((object)("Couldn't read " + Plugin.UsesFile + ": " + ex.Message)); } } private static void SaveUses() { try { DateTime dayAgo = DateTime.UtcNow.AddHours(-24.0); File.WriteAllLines(Plugin.UsesFile, Lines(uses, "").Concat(Lines(rescues, "r "))); IEnumerable<string> Lines(Dictionary<long, List<DateTime>> map, string prefix) { return from kv in map select (Key: kv.Key, kv.Value.Where((DateTime t) => t >= dayAgo).ToList()) into x where x.Item2.Count > 0 select prefix + x.Key + " " + string.Join(" ", x.Item2.Select((DateTime t) => t.Ticks)); } } catch (Exception ex) { Plugin.Logs.LogWarning((object)("Couldn't save " + Plugin.UsesFile + ": " + ex.Message)); } } private static void Note(string s) { if (Plugin.LogEachRez.Value) { Plugin.Logs.LogInfo((object)s); } } private static string Where(Vector3 p) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) return $"({p.x:0}, {p.z:0})"; } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { internal IgnoresAccessChecksToAttribute(string assemblyName) { } } }