Decompiled source of DeathMessage v0.1.3

plugins/DeathMessage.dll

Decompiled a week ago
using 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)
		{
		}
	}
}