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 DeathMessage v0.1.3
plugins/DeathMessage.dll
Decompiled a week agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; 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 System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: AssemblyCompany("DeathMessage")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.3.0")] [assembly: AssemblyInformationalVersion("0.1.3+cc728459a9d16ca3852c09b1c71e196400fd302e")] [assembly: AssemblyProduct("DeathMessage")] [assembly: AssemblyTitle("DeathMessage")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.3.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace DeathMessage { [BepInPlugin("liekos47.deathmessage", "DeathMessage", "0.1.3")] public class DeathMessagePlugin : BaseUnityPlugin { public const string Guid = "liekos47.deathmessage"; public const string Name = "DeathMessage"; public const string Version = "0.1.3"; internal static ManualLogSource Log; internal static ConfigEntry<bool> LocalLog; internal static ConfigEntry<bool> SendToServer; internal static ConfigEntry<bool> ShoutDeath; internal static ConfigEntry<string> ShoutFormat; internal static ConfigEntry<float> DeathViewSeconds; internal static ConfigEntry<float> RespawnLoadSeconds; internal static ConfigEntry<bool> Announce; internal static ConfigEntry<string> AnnounceFormat; internal static ConfigEntry<MessageType> AnnounceWhere; internal static ConfigEntry<bool> Discord; internal static ConfigEntry<string> DiscordMessage; internal static ConfigEntry<string> DiscordFirstDeathMessage; internal static ConfigEntry<bool> GraveTeleport; internal static ConfigEntry<string> GraveTeleportItem; internal static ConfigEntry<int> GraveTeleportCost; internal static ConfigEntry<bool> DebugLog; internal static readonly bool Headless = (int)SystemInfo.graphicsDeviceType == 4; private Harmony harmony; private ZRoutedRpc registeredFor; private DateTime configStamp; private void Awake() { //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; LocalLog = ((BaseUnityPlugin)this).Config.Bind<bool>("Client", "LocalLog", true, "Show what killed you in the middle of the screen and in the chat box, and keep a history in BepInEx/config/" + Path.GetFileName(DeathTracker.FilePath) + " (/deaths in chat lists it)."); SendToServer = ((BaseUnityPlugin)this).Config.Bind<bool>("Client", "SendToServer", true, "Report the cause of your deaths to the server, so it can announce it to the other players and put it in the Discord death message."); ShoutDeath = ((BaseUnityPlugin)this).Config.Bind<bool>("Client", "ShoutDeath", true, "When you die, shout the cause, so every player reads it in their chat - also those without this mod. It appears as a shout from you."); ShoutFormat = ((BaseUnityPlugin)this).Config.Bind<string>("Client", "ShoutFormat", "{cause} at {pos}", "What is shouted; the game puts your name in front. {cause} = e.g. \"was killed by Troll\", {pos} = coordinates (x, height, z), {biome} = biome name."); Announce = ((BaseUnityPlugin)this).Config.Bind<bool>("Server", "Announce", true, "When a player's game reports a death, show the other players a message on screen. It reaches every player, with this mod or without."); AnnounceFormat = ((BaseUnityPlugin)this).Config.Bind<string>("Server", "AnnounceFormat", "{player} {cause} at {pos}.", "The message. {player} = name, {cause} = e.g. \"was killed by Troll\", {pos} = coordinates (x, height, z), {biome} = biome name."); AnnounceWhere = ((BaseUnityPlugin)this).Config.Bind<MessageType>("Server", "AnnounceWhere", (MessageType)1, "TopLeft = the message feed, which queues and is kept in the Compendium's log. Center = across the middle of the screen for a few seconds."); DeathViewSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Client", "DeathViewSeconds", 10f, "Seconds you look at where you fell before the respawn starts. The game's own value is 10; 1 is the least."); RespawnLoadSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Client", "RespawnLoadSeconds", 8f, "The least number of seconds the game then waits at your spawn point before putting you there. The game's own value is 8; 3 is the least. It still waits longer when the area is not ready. Set too low on a slow connection, the game can arrive before your base has, find no bed and reset your spawn point."); Discord = ((BaseUnityPlugin)this).Config.Bind<bool>("Server", "Discord", true, "When DiscordConnector is installed, put the cause into the death message it posts, and keep the shouted copy of it out of Discord. Whether a death is posted at all stays DiscordConnector's own setting."); DiscordMessage = ((BaseUnityPlugin)this).Config.Bind<string>("Server", "DiscordMessage", "%PLAYER_NAME% {cause}. Coords: {pos}", "Replaces DiscordConnector's death message when the cause is known. {cause}, {biome} and {pos} (where they died) are filled in here. DiscordConnector's own position is left off these messages: it is taken after the respawn, so it is the bed, not the grave; %PLAYER_NAME% and DiscordConnector's other variables by DiscordConnector."); DiscordFirstDeathMessage = ((BaseUnityPlugin)this).Config.Bind<string>("Server", "DiscordFirstDeathMessage", "%PLAYER_NAME% {cause}. That was their first death. Coords: {pos}", "The same, for the message DiscordConnector posts on a player's first death."); GraveTeleport = ((BaseUnityPlugin)this).Config.Bind<bool>("Server", "GraveTeleport", false, "Let players with the mod pay to be taken back to where they last died, once per death: /grave in chat. The usual portal rule about ore and the like applies."); GraveTeleportItem = ((BaseUnityPlugin)this).Config.Bind<string>("Server", "GraveTeleportItem", "Coins", "The item it costs, by prefab name. It is taken from what the player carries, and a player who has just died carries nothing: they have to fetch it first."); GraveTeleportCost = ((BaseUnityPlugin)this).Config.Bind<int>("Server", "GraveTeleportCost", 100, "How many of that item one trip costs. 0 = free."); DebugLog = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DebugLog", false, "Log every report and what is done with it."); harmony = new Harmony("liekos47.deathmessage"); if (!Headless) { DeathTracker.Load(); Type[] patches = DeathTracker.Patches; foreach (Type type in patches) { harmony.CreateClassProcessor(type).Patch(); } harmony.CreateClassProcessor(typeof(Respawn)).Patch(); DeathTracker.AddCommand(); Grave.AddCommand(); } DiscordBridge.Hook(harmony); Log.LogInfo((object)("DeathMessage 0.1.3 loaded (" + (Headless ? "server" : "client") + ")")); configStamp = ConfigStamp(); ((MonoBehaviour)this).InvokeRepeating("ReloadConfig", 30f, 30f); } private void Update() { try { RegisterRpc(); if (!Headless) { DeathTracker.Update(); } } catch (Exception arg) { Log.LogError((object)$"tick failed: {arg}"); } } private void RegisterRpc() { if (ZRoutedRpc.instance != null && ZRoutedRpc.instance != registeredFor) { string[] array = new string[3] { "DeathMessage_Report", "DeathMessage_Hello", "DeathMessage_Settings" }; foreach (string text in array) { ZRoutedRpc.instance.m_functions.Remove(StringExtensionMethods.GetStableHashCode(text)); } ZRoutedRpc.instance.Register<ZPackage>("DeathMessage_Report", (Action<long, ZPackage>)Relay.OnReport); ZRoutedRpc.instance.Register("DeathMessage_Hello", (Action<long>)Relay.OnHello); ZRoutedRpc.instance.Register<ZPackage>("DeathMessage_Settings", (Action<long, ZPackage>)Relay.OnSettings); registeredFor = ZRoutedRpc.instance; Relay.NewSession(); } } private DateTime ConfigStamp() { return File.GetLastWriteTimeUtc(((BaseUnityPlugin)this).Config.ConfigFilePath); } private void ReloadConfig() { try { if (!(ConfigStamp() == configStamp)) { ((BaseUnityPlugin)this).Config.Reload(); configStamp = ConfigStamp(); Log.LogInfo((object)"config file changed, settings re-read"); } } catch (Exception ex) { Log.LogWarning((object)("config reload failed: " + ex.Message)); } } private void OnDestroy() { ((MonoBehaviour)this).CancelInvoke(); Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } } internal static void Debug(string text) { if (DebugLog.Value) { Log.LogInfo((object)text); } } } internal static class DeathTracker { private class Hit { public float At; public float HealthAfter; public float MaxHealth; public string From; public string Kind; public string Element; public string Effect; public string Damage; } private class Death { public DateTime When; public string World; public string Biome; public string Cause; public Vector3 Pos; public List<string> Hits = new List<string>(); } private struct Wording { public string You; public string They; public Wording(string you, string they) { You = you; They = they; } public Wording Plus(string note) { return new Wording(You + note, They + note); } } [HarmonyPatch(typeof(Character), "ApplyDamage")] private static class Hit_Patch { private static void Prefix(Character __instance, HitData hit) { Record(__instance, hit); } } [HarmonyPatch(typeof(Character), "RPC_Damage")] private static class Source_Patch { private static void Prefix(Character __instance, HitData hit) { NoteSource(__instance, hit); } } [HarmonyPatch(typeof(SE_Stats), "UpdateStatusEffect")] private static class EffectStats_Patch { private static void Prefix(StatusEffect __instance) { EnterEffect(__instance); } private static void Finalizer() { LeaveEffect(); } } [HarmonyPatch(typeof(SE_Burning), "UpdateStatusEffect")] private static class EffectBurning_Patch { private static void Prefix(StatusEffect __instance) { EnterEffect(__instance); } private static void Finalizer() { LeaveEffect(); } } [HarmonyPatch(typeof(SE_Poison), "UpdateStatusEffect")] private static class EffectPoison_Patch { private static void Prefix(StatusEffect __instance) { EnterEffect(__instance); } private static void Finalizer() { LeaveEffect(); } } [HarmonyPatch(typeof(SE_Smoke), "UpdateStatusEffect")] private static class EffectSmoke_Patch { private static void Prefix(StatusEffect __instance) { EnterEffect(__instance); } private static void Finalizer() { LeaveEffect(); } } [HarmonyPatch(typeof(Player), "OnDeath")] private static class Death_Patch { private static void Prefix(Player __instance) { try { OnDeath(__instance); } catch (Exception arg) { DeathMessagePlugin.Log.LogError((object)$"death handling failed: {arg}"); } } } [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static Func<KeyValuePair<string, float>, float> <>9__20_0; public static Func<Death, string> <>9__30_0; public static ConsoleEvent <>9__34_0; internal float <Record>b__20_0(KeyValuePair<string, float> e) { return e.Value; } internal string <Save>b__30_0(Death d) { return string.Join("\t", d.When.ToString("o", CultureInfo.InvariantCulture), T(d.World), T(d.Biome), S(d.Pos.x), S(d.Pos.y), S(d.Pos.z), T(d.Cause), string.Join(" || ", d.Hits.Select(T))); } internal void <AddCommand>b__34_0(ConsoleEventArgs args) { if (Deaths.Count == 0) { args.Context.AddString("No deaths recorded."); return; } bool flag = args.Length > 1 && args[1].ToLower() == "hits"; foreach (Death item in Deaths.Take(10)) { args.Context.AddString($"{item.When:yyyy-MM-dd HH:mm} {item.World}, {item.Biome} ({item.Pos.x:0}, {item.Pos.y:0}, {item.Pos.z:0}) {item.Cause}"); if (!flag) { continue; } foreach (string hit in item.Hits) { args.Context.AddString(" " + hit); } } if (Deaths.Count > 10) { args.Context.AddString($"{Deaths.Count - 10} older ones are in {FilePath}"); } } } private const int KeepHits = 8; private const int KeepDeaths = 30; private const int ShownDeaths = 10; internal static readonly Type[] Patches = new Type[7] { typeof(Hit_Patch), typeof(Source_Patch), typeof(EffectStats_Patch), typeof(EffectBurning_Patch), typeof(EffectPoison_Patch), typeof(EffectSmoke_Patch), typeof(Death_Patch) }; private static readonly List<Hit> Recent = new List<Hit>(); private static readonly List<Death> Deaths = new List<Death>(); private static readonly Dictionary<string, KeyValuePair<string, float>> ElementFrom = new Dictionary<string, KeyValuePair<string, float>>(); private static StatusEffect effect; private static int effectDepth; private const string HitSeparator = " || "; internal static string FilePath => Path.Combine(Paths.ConfigPath, "liekos47.deathmessage.deaths.txt"); private static bool On { get { if (!DeathMessagePlugin.LocalLog.Value && !DeathMessagePlugin.SendToServer.Value) { return DeathMessagePlugin.ShoutDeath.Value; } return true; } } private static void EnterEffect(StatusEffect se) { effectDepth++; effect = se; } private static void LeaveEffect() { if (--effectDepth <= 0) { effectDepth = 0; effect = null; } } private static string EffectName(StatusEffect se) { if ((Object)(object)se == (Object)null) { return null; } string text = (string.IsNullOrEmpty(se.m_name) ? "" : Localization.instance.Localize(se.m_name)); if (!string.IsNullOrEmpty(text) && !text.StartsWith("$") && !text.StartsWith("[")) { return text; } return ((Object)se).name; } private static void NoteSource(Character target, HitData hit) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0054: 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) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) string from; if (On && hit != null && !((Object)(object)target == (Object)null) && !((Object)(object)target != (Object)(object)Player.m_localPlayer)) { Character attacker = hit.GetAttacker(); if (!((Object)(object)attacker == (Object)null) && !((Object)(object)attacker == (Object)(object)target)) { DamageTypes damage = hit.m_damage; from = Name(attacker); Note("poison", damage.m_poison); Note("fire", damage.m_fire); Note("spirit", damage.m_spirit); Note("frost", damage.m_frost); Note("lightning", damage.m_lightning); } } void Note(string element, float amount) { if (amount > 0f) { ElementFrom[element] = new KeyValuePair<string, float>(from, Time.time); } } } private static void Record(Character target, HitData hit) { //IL_0032: 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_0068: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) if (!On || hit == null || (Object)(object)target == (Object)null || (Object)(object)target != (Object)(object)Player.m_localPlayer) { return; } float totalDamage = hit.GetTotalDamage(); if (totalDamage <= 0f) { return; } DamageTypes damage = hit.m_damage; Character attacker = hit.GetAttacker(); string text = (((Object)(object)attacker != (Object)null && (Object)(object)attacker != (Object)(object)target) ? Name(attacker) : null); KeyValuePair<string, float>[] array = new KeyValuePair<string, float>[5] { new KeyValuePair<string, float>("fire", damage.m_fire), new KeyValuePair<string, float>("frost", damage.m_frost), new KeyValuePair<string, float>("lightning", damage.m_lightning), new KeyValuePair<string, float>("poison", damage.m_poison), new KeyValuePair<string, float>("spirit", damage.m_spirit) }; float num = damage.m_damage + damage.m_blunt + damage.m_slash + damage.m_pierce + damage.m_chop + damage.m_pickaxe; KeyValuePair<string, float> keyValuePair = array.OrderByDescending((KeyValuePair<string, float> e) => e.Value).First(); if (text != null) { KeyValuePair<string, float>[] array2 = array; for (int num2 = 0; num2 < array2.Length; num2++) { KeyValuePair<string, float> keyValuePair2 = array2[num2]; if (keyValuePair2.Value > 0f) { ElementFrom[keyValuePair2.Key] = new KeyValuePair<string, float>(text, Time.time); } } } Recent.Add(new Hit { At = Time.time, From = text, Kind = ((object)Unsafe.As<HitType, HitType>(ref hit.m_hitType)/*cast due to .constrained prefix*/).ToString(), Element = ((num <= 0f && keyValuePair.Value > 0f) ? keyValuePair.Key : null), Effect = EffectName(effect), Damage = Breakdown(damage), HealthAfter = Mathf.Max(0f, target.GetHealth() - totalDamage), MaxHealth = target.GetMaxHealth() }); if (Recent.Count > 8) { Recent.RemoveAt(0); } } private static string Name(Character c) { string text = Localization.instance.Localize(c.GetHoverName()); if (c.IsPlayer()) { return "player " + text; } if (c.GetLevel() <= 1) { return text; } return $"{text} ★{c.GetLevel() - 1}"; } private static string Breakdown(DamageTypes d) { //IL_0011: 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_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_006b: 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) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: 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_00c5: Unknown result type (might be due to invalid IL or missing references) List<string> parts = new List<string>(); Add("damage", d.m_damage); Add("blunt", d.m_blunt); Add("slash", d.m_slash); Add("pierce", d.m_pierce); Add("chop", d.m_chop); Add("pickaxe", d.m_pickaxe); Add("fire", d.m_fire); Add("frost", d.m_frost); Add("lightning", d.m_lightning); Add("poison", d.m_poison); Add("spirit", d.m_spirit); if (parts.Count <= 0) { return "no damage"; } return string.Join(", ", parts); void Add(string n, float v) { if (v >= 0.05f) { parts.Add($"{v:0.#} {n}"); } } } private static Wording Cause(Hit h) { string note = ""; if (h.From == null && h.Element != null && ElementFrom.TryGetValue(h.Element, out var value) && Time.time - value.Value < 90f) { note = " (from " + value.Key + ")"; } switch (h.Kind) { case "Fall": return new Wording("Fell to your death", "fell to their death"); case "Drowning": return new Wording("Drowned", "drowned"); case "CinderFire": case "Burning": return new Wording("Burned to death", "burned to death").Plus(note); case "Freezing": return new Wording("Froze to death", "froze to death"); case "Poisoned": return new Wording("Died from poison", "died from poison").Plus(note); case "Smoke": return new Wording("Suffocated in smoke", "suffocated in smoke"); case "Tree": return new Wording("Crushed by a falling tree", "was crushed by a falling tree"); case "Cart": return new Wording("Run over by a cart", "was run over by a cart"); case "Boat": return new Wording("Hit by a ship", "was hit by a ship"); case "Stalagtite": return new Wording("Hit by a falling stalactite", "was hit by a falling stalactite"); case "Structural": return new Wording("Crushed by a collapsing structure", "was crushed by a collapsing structure"); case "Impact": return new Wording("Killed by an impact", "was killed by an impact"); case "Turret": return new Wording("Shot by a ballista", "was shot by a ballista"); case "Catapult": return new Wording("Hit by a catapult", "was hit by a catapult"); case "EdgeOfWorld": return new Wording("Fell off the edge of the world", "fell off the edge of the world"); case "AshlandsOcean": return new Wording("Boiled in the Ashlands sea", "boiled in the Ashlands sea"); case "AshlandsLava": return new Wording("Burned in lava", "burned in lava"); case "Incinerator": return new Wording("Struck by the obliterator", "was struck by the obliterator"); case "DrawBridge": return new Wording("Crushed by a drawbridge", "was crushed by a drawbridge"); case "Water": return new Wording("Killed by water", "was killed by water"); case "Self": return new Wording("Killed by your own attack", "was killed by their own attack"); default: { if (h.From != null) { string text = ((h.Element != null) ? (h.From + " (" + h.Element + ")") : h.From); return new Wording("Killed by " + text, "was killed by " + text); } Wording result = new Wording("Died from the \"" + h.Effect + "\" effect", "died from the \"" + h.Effect + "\" effect"); if (h.Effect != null && h.Element == null) { return result; } switch (h.Element) { case "fire": return new Wording("Burned to death", "burned to death").Plus(note); case "poison": return new Wording("Died from poison", "died from poison").Plus(note); case "frost": return new Wording("Froze to death", "froze to death").Plus(note); case "lightning": return new Wording("Killed by lightning", "was killed by lightning").Plus(note); case "spirit": return new Wording("Killed by spirit damage", "was killed by spirit damage").Plus(note); default: if (h.Effect == null) { return new Wording("Killed by damage with no known source", "died of unknown causes"); } return result; } } } } private static void OnDeath(Player p) { //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: 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_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_029a: Unknown result type (might be due to invalid IL or missing references) if (!On || (Object)(object)p == (Object)null || (Object)(object)p != (Object)(object)Player.m_localPlayer || !((Character)p).m_nview.IsOwner()) { return; } float time = Time.time; Hit hit = ((Recent.Count > 0) ? Recent[Recent.Count - 1] : null); bool flag = hit != null && time - hit.At < 3f; Wording wording = (flag ? Cause(hit) : new Wording("Died with no damage seen just before (a command, or a hit this game did not process)", "died of unknown causes")); string biome = ((object)p.GetCurrentBiome()/*cast due to .constrained prefix*/).ToString(); if (DeathMessagePlugin.SendToServer.Value) { Relay.Report(wording.They, biome, ((Component)p).transform.position); } if (DeathMessagePlugin.ShoutDeath.Value) { Relay.Shout(wording.They, biome, ((Component)p).transform.position); } Death death = new Death { When = DateTime.Now, World = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetWorldName() : ""), Biome = biome, Pos = ((Component)p).transform.position, Cause = (flag ? (wording.You + " — last hit: " + hit.Damage) : wording.You) }; foreach (Hit item in Recent) { if (time - item.At < 30f) { death.Hits.Add($"{time - item.At:0.0} s before: {item.From ?? KindWords(item)}, {item.Damage} → {item.HealthAfter:0}/{item.MaxHealth:0} HP"); } } Recent.Clear(); ElementFrom.Clear(); if (DeathMessagePlugin.LocalLog.Value) { Deaths.Insert(0, death); if (Deaths.Count > 30) { Deaths.RemoveRange(30, Deaths.Count - 30); } Save(); DeathMessagePlugin.Log.LogInfo((object)($"{death.Cause} in {death.World} ({death.Biome}) at {death.Pos}" + ((death.Hits.Count > 0) ? ("\n " + string.Join("\n ", death.Hits)) : ""))); if (!DeathMessagePlugin.ShoutDeath.Value) { Relay.ChatLine(death.Cause); } if (!CentreShownByAnotherMod()) { ((Character)p).Message((MessageType)2, "<color=#ff6060>" + death.Cause + "</color>", 0, (Sprite)null, false); } } } private static bool CentreShownByAnotherMod() { if (Chainloader.PluginInfos.ContainsKey("local.valheimgodmode")) { return Shows("local.valheimgodmode"); } return Shows("liekos47.valheimvanillaplus"); static bool Shows(string guid) { if (Chainloader.PluginInfos.TryGetValue(guid, out var value) && (Object)(object)value.Instance != (Object)null) { ConfigEntry<bool> val = default(ConfigEntry<bool>); if (value.Instance.Config.TryGetEntry<bool>("DeathLog", "Enabled", ref val)) { return val.Value; } return true; } return false; } } private static string KindWords(Hit h) { if (h.Effect != null) { return "\"" + h.Effect + "\" effect"; } if (h.Element != null) { return h.Element; } switch (h.Kind) { case "Fall": return "fall"; case "Drowning": return "drowning"; case "Burning": case "CinderFire": return "fire"; case "AshlandsLava": return "lava"; case "AshlandsOcean": return "boiling sea"; case "Tree": return "falling tree"; case "Freezing": return "freezing"; case "Poisoned": return "poison"; case "Smoke": return "smoke"; default: return "unknown source"; } } internal static void Update() { Relay.Hello(); } internal static void Load() { //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) Deaths.Clear(); if (!File.Exists(FilePath)) { return; } string[] array = File.ReadAllLines(FilePath); for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Split(new char[1] { '\t' }); if (array2.Length < 7) { continue; } try { Death death = new Death { When = DateTime.Parse(array2[0], CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind), World = array2[1], Biome = array2[2], Pos = new Vector3(P(array2[3]), P(array2[4]), P(array2[5])), Cause = array2[6] }; if (array2.Length > 7 && array2[7].Length > 0) { death.Hits.AddRange(array2[7].Split(new string[1] { " || " }, StringSplitOptions.RemoveEmptyEntries)); } Deaths.Add(death); } catch (FormatException) { } } } private static void Save() { try { File.WriteAllLines(FilePath, Deaths.Select((Death d) => string.Join("\t", d.When.ToString("o", CultureInfo.InvariantCulture), T(d.World), T(d.Biome), S(d.Pos.x), S(d.Pos.y), S(d.Pos.z), T(d.Cause), string.Join(" || ", d.Hits.Select(T))))); } catch (IOException ex) { DeathMessagePlugin.Log.LogWarning((object)("could not write " + FilePath + ": " + ex.Message)); } } private static float P(string s) { return float.Parse(s, CultureInfo.InvariantCulture); } private static string S(float v) { return v.ToString("0.##", CultureInfo.InvariantCulture); } private static string T(string s) { return (s ?? "").Replace('\t', ' ').Replace('\n', ' '); } internal static void AddCommand() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_001e: 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_0029: Expected O, but got Unknown object obj = <>c.<>9__34_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { if (Deaths.Count == 0) { args.Context.AddString("No deaths recorded."); } else { bool flag = args.Length > 1 && args[1].ToLower() == "hits"; foreach (Death item in Deaths.Take(10)) { args.Context.AddString($"{item.When:yyyy-MM-dd HH:mm} {item.World}, {item.Biome} ({item.Pos.x:0}, {item.Pos.y:0}, {item.Pos.z:0}) {item.Cause}"); if (flag) { foreach (string hit in item.Hits) { args.Context.AddString(" " + hit); } } } if (Deaths.Count > 10) { args.Context.AddString($"{Deaths.Count - 10} older ones are in {FilePath}"); } } }; <>c.<>9__34_0 = val; obj = (object)val; } new ConsoleCommand("deaths", "[hits] - your recent deaths and what caused them", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } } internal static class DiscordBridge { private class Reported { public string Cause; public string Biome; public string Pos; public float At; public bool Swapped; public bool Posted; } private const float KeepSeconds = 300f; private const float ShoutSeconds = 15f; private static readonly Dictionary<long, Reported> reported = new Dictionary<long, Reported>(); private static MethodInfo death; private static bool posting; internal static void Hook(Harmony harmony) { //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Expected O, but got Unknown //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Expected O, but got Unknown //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Expected O, but got Unknown try { Type type = AccessTools.TypeByName("DiscordConnector.Handlers"); if (type == null) { return; } List<MethodInfo> declaredMethods = AccessTools.GetDeclaredMethods(type); MethodInfo methodInfo = declaredMethods.FirstOrDefault(delegate(MethodInfo m) { ParameterInfo[] parameters = m.GetParameters(); return m.Name == "FinalizeFormattingAndSend" && parameters.Length == 5 && parameters[0].ParameterType == typeof(ZNetPeer) && parameters[2].ParameterType == typeof(string) && parameters[2].Name == "preFormattedMessage" && parameters[3].ParameterType == typeof(bool) && parameters[4].ParameterType.IsEnum; }); if (methodInfo == null) { DeathMessagePlugin.Log.LogWarning((object)"DiscordConnector is installed but its message hook was not found (different version?). Deaths go to Discord without the cause."); return; } harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(DiscordBridge), "SendPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); death = declaredMethods.FirstOrDefault((MethodInfo m) => m.Name == "Death" && m.IsStatic && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == typeof(ZNetPeer)); if (death != null) { harmony.Patch((MethodBase)death, new HarmonyMethod(typeof(DiscordBridge), "DeathPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { DeathMessagePlugin.Log.LogWarning((object)"DiscordConnector's death handler was not found: the cause is added, but the message only goes out after the respawn."); } MethodInfo methodInfo2 = declaredMethods.FirstOrDefault(delegate(MethodInfo m) { ParameterInfo[] parameters = m.GetParameters(); return m.Name == "Shout" && parameters.Length == 3 && parameters[0].ParameterType == typeof(ZNetPeer) && parameters[2].ParameterType == typeof(string); }); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(DiscordBridge), "ShoutPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { DeathMessagePlugin.Log.LogWarning((object)"DiscordConnector's shout hook was not found: a shouted cause of death will also be posted as a shout."); } DeathMessagePlugin.Log.LogInfo((object)"DiscordConnector found: death messages will carry the cause"); } catch (Exception ex) { DeathMessagePlugin.Log.LogWarning((object)("could not hook DiscordConnector: " + ex.GetType().Name + ": " + ex.Message)); } } internal static void Report(ZNetPeer peer, string cause, string biome, string pos) { Reported reported = new Reported { Cause = cause, Biome = biome, Pos = pos, At = Time.unscaledTime }; DiscordBridge.reported[peer.m_uid] = reported; if (death == null) { return; } try { posting = true; death.Invoke(null, new object[1] { peer }); reported.Posted = true; } catch (Exception ex) { Exception ex2 = ex.InnerException ?? ex; DeathMessagePlugin.Log.LogWarning((object)("could not post the death through DiscordConnector, it will post it itself after the respawn: " + ex2.GetType().Name + ": " + ex2.Message)); } finally { posting = false; } } internal static void Clear() { reported.Clear(); } private static bool Fresh(long uid, float seconds, out Reported r) { if (reported.TryGetValue(uid, out r)) { return Time.unscaledTime - r.At <= seconds; } return false; } private static bool DeathPrefix(ZNetPeer __0) { try { if (posting || __0 == null || !Fresh(__0.m_uid, 300f, out var r) || !r.Posted) { return true; } r.Posted = false; DeathMessagePlugin.Debug("discord: late death notice for " + __0.m_playerName + " skipped, already posted"); return false; } catch (Exception ex) { DeathMessagePlugin.Log.LogWarning((object)("death check failed: " + ex.GetType().Name + ": " + ex.Message)); return true; } } private static void SendPrefix(ZNetPeer __0, ref string __2, ref bool __3, object __4) { try { string text = __4?.ToString() ?? ""; if (__0 != null && (!(text != "PlayerDeath") || !(text != "PlayerFirstDeath")) && DeathMessagePlugin.Discord.Value && Fresh(__0.m_uid, 300f, out var r) && !r.Swapped) { string text2 = ((text == "PlayerFirstDeath") ? DeathMessagePlugin.DiscordFirstDeathMessage.Value : DeathMessagePlugin.DiscordMessage.Value); if (!string.IsNullOrWhiteSpace(text2)) { r.Swapped = true; __2 = Relay.Fill(text2, r.Cause, r.Biome, r.Pos); __3 = false; DeathMessagePlugin.Debug("discord: " + __0.m_playerName + ": " + __2); } } } catch (Exception ex) { DeathMessagePlugin.Log.LogWarning((object)("discord message left unchanged: " + ex.GetType().Name + ": " + ex.Message)); } } private static bool ShoutPrefix(ZNetPeer __0, string __2) { try { if (__0 == null || __2 == null || !DeathMessagePlugin.Discord.Value) { return true; } if (!Fresh(__0.m_uid, 15f, out var r)) { return true; } if (__2.IndexOf(r.Cause, StringComparison.OrdinalIgnoreCase) < 0) { return true; } DeathMessagePlugin.Debug("discord: shout by " + __0.m_playerName + " held back, it is their cause of death"); return false; } catch (Exception ex) { DeathMessagePlugin.Log.LogWarning((object)("shout check failed: " + ex.GetType().Name + ": " + ex.Message)); return true; } } } internal static class Grave { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__16_0; internal void <AddCommand>b__16_0(ConsoleEventArgs args) { string text = Go(); if (text != null) { args.Context.AddString(text); } } } private const string Command = "grave"; private static bool serverEnabled; private static string serverItem = ""; private static int serverCost; private static Vector3? used; private static bool Hosting { get { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } } private static bool Enabled { get { if (!Hosting) { return serverEnabled; } return DeathMessagePlugin.GraveTeleport.Value; } } private static string ItemName { get { if (!Hosting) { return serverItem; } return DeathMessagePlugin.GraveTeleportItem.Value; } } private static int Cost => Mathf.Max(0, Hosting ? DeathMessagePlugin.GraveTeleportCost.Value : serverCost); internal static void NewSession() { serverEnabled = false; used = null; } internal static ZPackage Settings() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: 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_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(DeathMessagePlugin.GraveTeleport.Value); val.Write(DeathMessagePlugin.GraveTeleportItem.Value); val.Write(DeathMessagePlugin.GraveTeleportCost.Value); return val; } internal static void OnSettings(ZPackage pkg) { serverEnabled = pkg.ReadBool(); serverItem = pkg.ReadString(); serverCost = pkg.ReadInt(); DeathMessagePlugin.Debug("grave teleport: " + (serverEnabled ? $"on, {serverCost} {serverItem}" : "off")); } internal static void AddCommand() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_001e: 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_0029: Expected O, but got Unknown object obj = <>c.<>9__16_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { string text = Go(); if (text != null) { args.Context.AddString(text); } }; <>c.<>9__16_0 = val; obj = (object)val; } new ConsoleCommand("grave", "- pay to be taken back to where you last died, when the server allows it", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } private static string Go() { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)Game.instance == (Object)null || (Object)(object)ZNet.instance == (Object)null) { return "Not in a world."; } if (!Enabled) { return "Going back to your grave is not enabled on this server."; } if (((Character)localPlayer).IsDead() || ((Character)localPlayer).IsTeleporting() || ((Character)localPlayer).InCutscene()) { return "Not right now."; } PlayerProfile playerProfile = Game.instance.GetPlayerProfile(); if (!playerProfile.HaveDeathPoint()) { return "You have not died in this world."; } Vector3 deathPoint = playerProfile.GetDeathPoint(); if (used.HasValue && Vector3.Distance(used.Value, deathPoint) < 1f) { return "You have already gone back to this grave."; } if (!((Humanoid)localPlayer).IsTeleportable(ZoneSystem.instance.GetGlobalKey((GlobalKeys)35))) { return "You are carrying something that cannot be teleported."; } int cost = Cost; string text = null; string arg = ItemName; if (cost > 0) { GameObject val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(ItemName) : null); ItemDrop val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<ItemDrop>() : null); if ((Object)(object)val2 == (Object)null) { return "The server asks for an item this game does not know (\"" + ItemName + "\")."; } text = val2.m_itemData.m_shared.m_name; arg = Localization.instance.Localize(text); int num = ((Humanoid)localPlayer).GetInventory().CountItems(text, -1, false); if (num < cost) { return $"Going back to your grave costs {cost} {arg}. You are carrying {num}."; } } if (!((Character)localPlayer).TeleportTo(deathPoint + Vector3.up * 0.5f, ((Component)localPlayer).transform.rotation, true)) { return "Not right now - try again in a moment."; } if (cost > 0) { ((Humanoid)localPlayer).GetInventory().RemoveItem(text, cost, -1, false); } used = deathPoint; DeathMessagePlugin.Log.LogInfo((object)($"went back to the grave at {deathPoint}" + ((cost > 0) ? $" for {cost} {arg}" : ""))); ((Character)localPlayer).Message((MessageType)1, (cost > 0) ? $"Paid {cost} {arg} to return to your grave" : "Returning to your grave", 0, (Sprite)null, false); return null; } } internal static class Relay { internal const string ReportRpc = "DeathMessage_Report"; internal const string HelloRpc = "DeathMessage_Hello"; internal const string SettingsRpc = "DeathMessage_Settings"; private const int Protocol = 2; private const float MaxPosDrift = 300f; private const int MaxCause = 160; private const float MinSecondsBetweenReports = 3f; private const float HelloEverySeconds = 120f; private static readonly Dictionary<long, float> lastReport = new Dictionary<long, float>(); private static ZRoutedRpc greeted; private static float greetedAt; private static bool IsServer { get { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } } internal static void NewSession() { lastReport.Clear(); DiscordBridge.Clear(); Grave.NewSession(); } internal static void Shout(string cause, string biome, Vector3 at) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Chat.instance == (Object)null)) { string text = Fill(DeathMessagePlugin.ShoutFormat.Value, cause, biome, $"({at.x:0}, {at.y:0}, {at.z:0})").Trim(); if (text.Length > 0) { Chat.instance.SendText((Type)2, text); } } } internal static void Report(string cause, string biome, Vector3 at) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Expected O, but got Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) if (ZRoutedRpc.instance != null) { Hello(); ZPackage val = new ZPackage(); val.Write(2); val.Write(cause); val.Write(biome); val.Write(at); ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "DeathMessage_Report", new object[1] { val }); } } internal static void Hello() { if (ZRoutedRpc.instance != null && !IsServer && !((Object)(object)Player.m_localPlayer == (Object)null) && (ZRoutedRpc.instance != greeted || !(Time.unscaledTime - greetedAt < 120f))) { greeted = ZRoutedRpc.instance; greetedAt = Time.unscaledTime; ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "DeathMessage_Hello", Array.Empty<object>()); } } internal static void OnSettings(long sender, ZPackage pkg) { if (DeathMessagePlugin.Headless || ZRoutedRpc.instance == null || sender != ZRoutedRpc.instance.GetServerPeerID()) { return; } try { Grave.OnSettings(pkg); } catch (Exception ex) { DeathMessagePlugin.Log.LogWarning((object)("bad settings from the server: " + ex.GetType().Name + ": " + ex.Message)); } } internal static void ChatLine(string text) { if (!((Object)(object)Chat.instance == (Object)null)) { ((Terminal)Chat.instance).AddString("<color=#ff8080>" + text + "</color>"); Chat.instance.m_hideTimer = 0f; } } internal static void OnHello(long sender) { if (IsServer) { ZRoutedRpc.instance.InvokeRoutedRPC(sender, "DeathMessage_Settings", new object[1] { Grave.Settings() }); } } internal static void OnReport(long sender, ZPackage pkg) { //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) if (!IsServer) { return; } try { float unscaledTime = Time.unscaledTime; if (lastReport.TryGetValue(sender, out var value) && unscaledTime - value < 3f) { return; } lastReport[sender] = unscaledTime; int num = pkg.ReadInt(); if (num < 1 || num > 2) { return; } string text = Clean(pkg.ReadString(), 160); string text2 = new string(Clean(pkg.ReadString(), 24).Where(char.IsLetter).ToArray()); if (text.Length == 0) { return; } ZNetPeer peer = ZNet.instance.GetPeer(sender); string text3 = ((peer != null) ? peer.m_playerName : ((sender == ZRoutedRpc.instance.m_id && (Object)(object)Game.instance != (Object)null) ? Game.instance.GetPlayerProfile().GetName() : null)); if (string.IsNullOrEmpty(text3)) { return; } Vector3 val = peer?.m_refPos ?? (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Component)Player.m_localPlayer).transform.position : Vector3.zero); Vector3 val2 = val; if (num >= 2) { Vector3 val3 = pkg.ReadVector3(); if (Vector3.Distance(val3, val) <= 300f) { val2 = val3; } } string text4 = $"({val2.x:0}, {val2.y:0}, {val2.z:0})"; DeathMessagePlugin.Log.LogInfo((object)(text3 + " " + text + " (" + text2 + ") at " + text4)); if (peer != null && DeathMessagePlugin.Discord.Value) { DiscordBridge.Report(peer, text, text2, text4); } if (DeathMessagePlugin.Announce.Value) { Announce(sender, Fill(DeathMessagePlugin.AnnounceFormat.Value, text, text2, text4).Replace("{player}", text3)); } } catch (Exception ex) { DeathMessagePlugin.Log.LogWarning((object)$"bad death report from {sender}: {ex.GetType().Name}: {ex.Message}"); } } private static void Announce(long except, string text) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected I4, but got Unknown //IL_00a2: Unknown result type (might be due to invalid IL or missing references) int num = (int)DeathMessagePlugin.AnnounceWhere.Value; foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer.IsReady() && peer.m_uid != except) { ZRoutedRpc.instance.InvokeRoutedRPC(peer.m_uid, "ShowMessage", new object[2] { num, text }); } } if (!DeathMessagePlugin.Headless && except != ZRoutedRpc.instance.m_id && (Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage(DeathMessagePlugin.AnnounceWhere.Value, text, 0, (Sprite)null, false, true); } DeathMessagePlugin.Debug("announced: " + text); } internal static string Fill(string format, string cause, string biome, string pos) { return format.Replace("{cause}", cause).Replace("{biome}", Spaced(biome)).Replace("{pos}", pos); } private static string Spaced(string biome) { StringBuilder stringBuilder = new StringBuilder(); foreach (char c in biome) { if (char.IsUpper(c) && stringBuilder.Length > 0) { stringBuilder.Append(' '); } stringBuilder.Append(c); } return stringBuilder.ToString(); } private static string Clean(string text, int max) { if (string.IsNullOrEmpty(text)) { return ""; } StringBuilder stringBuilder = new StringBuilder(Math.Min(text.Length, max)); foreach (char c in text) { if (stringBuilder.Length >= max) { break; } if (!char.IsControl(c) && c != '<' && c != '>' && c != '@' && c != '%' && c != '{' && c != '}' && c != '`') { stringBuilder.Append(c); } } return stringBuilder.ToString().Trim(); } } [HarmonyPatch(typeof(Game), "RequestRespawn")] internal static class Respawn { internal const float VanillaDeathView = 10f; internal const float VanillaLoad = 8f; private const float MinLoad = 3f; private static bool saved; private static float gameLoad; private static float gameFade; private static void Prefix(Game __instance, ref float delay, bool afterDeath) { if (!saved) { saved = true; gameLoad = __instance.m_respawnLoadDuration; gameFade = __instance.m_fadeTimeDeath; } float num = Mathf.Clamp(DeathMessagePlugin.DeathViewSeconds.Value, 1f, 10f); float num2 = Mathf.Clamp(DeathMessagePlugin.RespawnLoadSeconds.Value, 3f, 8f); if (!afterDeath || (num >= 10f && num2 >= 8f)) { __instance.m_respawnLoadDuration = gameLoad; __instance.m_fadeTimeDeath = gameFade; } else { delay = Mathf.Min(delay, num); __instance.m_respawnLoadDuration = Mathf.Min(gameLoad, num2); __instance.m_fadeTimeDeath = Mathf.Min(gameFade, Mathf.Max(0.5f, delay - 0.5f)); } } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }