Decompiled source of Gjallarhorn v0.1.0

plugins/Gjallarhorn.dll

Decompiled 3 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
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.Configuration;
using Gjallarhorn.Chronicle;
using Gjallarhorn.Config;
using Gjallarhorn.Logic;
using Gjallarhorn.Model;
using Gjallarhorn.Net;
using Gjallarhorn.Server;
using HarmonyLib;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using Splatform;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: IgnoresAccessChecksTo("assembly_guiutils")]
[assembly: IgnoresAccessChecksTo("assembly_utils")]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: IgnoresAccessChecksTo("Splatform")]
[assembly: AssemblyCompany("RAGEmedia")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Server-side Viking death announcements and boss-defeat chronicles. Vanilla clients supported; installing it client-side upgrades the feed to exact killer and cause of death.")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0")]
[assembly: AssemblyProduct("Gjallarhorn")]
[assembly: AssemblyTitle("Gjallarhorn")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class IsReadOnlyAttribute : Attribute
	{
	}
	[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 Gjallarhorn
{
	[BepInPlugin("com.ragemedia.gjallarhorn", "Gjallarhorn", "0.1.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public class GjallarhornPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.ragemedia.gjallarhorn";

		public const string PluginName = "Gjallarhorn";

		public const string PluginVersion = "0.1.0";

		internal static GjallarhornPlugin Instance;

		private readonly Harmony _harmony = new Harmony("com.ragemedia.gjallarhorn");

		private ZNet _knownNet;

		private void Awake()
		{
			Instance = this;
			GjallarhornConfig.Bind(((BaseUnityPlugin)this).Config);
			GjallarhornRpc.Register();
			_harmony.PatchAll(Assembly.GetExecutingAssembly());
			VerifyPatches();
		}

		private void OnDestroy()
		{
			((MonoBehaviour)this).CancelInvoke("PollDeaths");
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void Update()
		{
			ZNet instance = ZNet.instance;
			if (instance == _knownNet)
			{
				return;
			}
			_knownNet = instance;
			((MonoBehaviour)this).CancelInvoke("PollDeaths");
			if (!((Object)(object)instance == (Object)null))
			{
				if (!instance.IsServer())
				{
					LogInfo("Running as a client. Deaths here are reported to the server with exact detail; announcements come back from the server.");
					return;
				}
				float num = Math.Max(0.25f, GjallarhornConfig.PollInterval.Value);
				((MonoBehaviour)this).InvokeRepeating("PollDeaths", 2f, num);
				LogInfo($"Running as the server. Watching for deaths every {num:0.00}s.");
			}
		}

		private void PollDeaths()
		{
			try
			{
				DeathWatcher.Poll();
			}
			catch (Exception arg)
			{
				LogError($"Death poll failed: {arg}");
			}
		}

		private void VerifyPatches()
		{
			List<MethodBase> list = _harmony.GetPatchedMethods().ToList();
			LogInfo($"Harmony bound {list.Count} target(s):");
			foreach (MethodBase item in list)
			{
				LogInfo("  bound  " + item.DeclaringType?.FullName + "." + item.Name);
			}
			int num = 0;
			foreach (Type item2 in from t in Assembly.GetExecutingAssembly().GetTypes()
				where t.GetCustomAttributes(typeof(HarmonyPatch), inherit: true).Any()
				select t)
			{
				List<HarmonyMethod> list2 = (from HarmonyPatch a in item2.GetCustomAttributes(typeof(HarmonyPatch), inherit: true)
					select ((HarmonyAttribute)a).info).ToList();
				HarmonyMethod val;
				try
				{
					val = HarmonyMethod.Merge(list2);
				}
				catch (Exception ex)
				{
					LogError("  UNRESOLVED  " + item2.Name + ": could not merge patch attributes: " + ex.Message);
					num++;
					continue;
				}
				if (!(val?.declaringType == null) && !string.IsNullOrEmpty(val.methodName) && AccessTools.Method(val.declaringType, val.methodName, val.argumentTypes, (Type[])null) == null)
				{
					num++;
					LogError("  UNRESOLVED  " + item2.Name + " -> " + val.declaringType.Name + "." + val.methodName + " does not exist. This patch will never run. The game version likely changed; re-check the method against the decompiled source.");
				}
			}
			if (num > 0)
			{
				LogError(string.Format("{0}: {1} patch target(s) failed to resolve. ", "Gjallarhorn", num) + "This mod is loaded but not fully functional.");
			}
			else
			{
				LogInfo("Gjallarhorn v0.1.0 ready.");
			}
		}

		internal static void LogInfo(string msg)
		{
			((BaseUnityPlugin)Instance).Logger.LogInfo((object)("[Gjallarhorn] " + msg));
		}

		internal static void LogWarning(string msg)
		{
			((BaseUnityPlugin)Instance).Logger.LogWarning((object)("[Gjallarhorn] " + msg));
		}

		internal static void LogError(string msg)
		{
			((BaseUnityPlugin)Instance).Logger.LogError((object)("[Gjallarhorn] " + msg));
		}

		internal static void LogVerbose(string msg)
		{
			if (GjallarhornConfig.VerboseLogging != null && GjallarhornConfig.VerboseLogging.Value)
			{
				((BaseUnityPlugin)Instance).Logger.LogInfo((object)("[Gjallarhorn] " + msg));
			}
		}
	}
}
namespace Gjallarhorn.Server
{
	public static class AnnouncementService
	{
		private sealed class Pending
		{
			public DeathReport Report;

			public DateTime GuessDueAt;
		}

		private static readonly object Gate = new object();

		private static readonly Dictionary<ZDOID, DateTime> Announced = new Dictionary<ZDOID, DateTime>();

		private static readonly Dictionary<ZDOID, Pending> Waiting = new Dictionary<ZDOID, Pending>();

		private static readonly Dictionary<string, DateTime> RecentBosses = new Dictionary<string, DateTime>(StringComparer.OrdinalIgnoreCase);

		private static readonly TimeSpan AnnouncedRetention = TimeSpan.FromMinutes(10.0);

		private static readonly TimeSpan BossDedupeWindow = TimeSpan.FromSeconds(15.0);

		public static void SubmitExact(DeathReport report)
		{
			//IL_0026: 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_0065: Unknown result type (might be due to invalid IL or missing references)
			if (report == null || !GjallarhornConfig.AnnounceDeaths.Value)
			{
				return;
			}
			lock (Gate)
			{
				if (Announced.ContainsKey(report.VictimZdoId))
				{
					GjallarhornPlugin.LogVerbose("Exact report for '" + report.VictimName + "' arrived after the guess went out. Raise ExactReportGrace if this keeps happening.");
					return;
				}
				Waiting.Remove(report.VictimZdoId);
				Announced[report.VictimZdoId] = DateTime.UtcNow;
			}
			Dispatch(report);
		}

		public static void SubmitObserved(DeathReport report)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			if (report == null || !GjallarhornConfig.AnnounceDeaths.Value)
			{
				return;
			}
			lock (Gate)
			{
				if (Announced.ContainsKey(report.VictimZdoId) || Waiting.ContainsKey(report.VictimZdoId))
				{
					return;
				}
				Waiting[report.VictimZdoId] = new Pending
				{
					Report = report,
					GuessDueAt = DateTime.UtcNow.AddSeconds(Math.Max(0f, GjallarhornConfig.ExactReportGrace.Value))
				};
			}
			GjallarhornPlugin.LogVerbose("Observed death of '" + report.VictimName + "'; waiting " + $"{GjallarhornConfig.ExactReportGrace.Value:0.0}s for exact details.");
		}

		public static void Tick()
		{
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			List<DeathReport> list = null;
			DateTime utcNow = DateTime.UtcNow;
			lock (Gate)
			{
				if (Waiting.Count > 0)
				{
					List<ZDOID> list2 = null;
					foreach (KeyValuePair<ZDOID, Pending> item in Waiting)
					{
						if (!(item.Value.GuessDueAt > utcNow))
						{
							(list2 ?? (list2 = new List<ZDOID>())).Add(item.Key);
							(list ?? (list = new List<DeathReport>())).Add(item.Value.Report);
						}
					}
					if (list2 != null)
					{
						foreach (ZDOID item2 in list2)
						{
							Waiting.Remove(item2);
							Announced[item2] = utcNow;
						}
					}
				}
				Prune(utcNow);
			}
			if (list == null)
			{
				return;
			}
			foreach (DeathReport item3 in list)
			{
				KillerGuesser.Fill(item3);
				Dispatch(item3);
			}
		}

		public static void BossDefeated(string bossName, string killerName, Vector3? position)
		{
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			if (!GjallarhornConfig.AnnounceBossDefeats.Value || string.IsNullOrWhiteSpace(bossName))
			{
				return;
			}
			lock (Gate)
			{
				DateTime utcNow = DateTime.UtcNow;
				if (RecentBosses.TryGetValue(bossName, out var value) && utcNow - value < BossDedupeWindow)
				{
					GjallarhornPlugin.LogVerbose("Boss '" + bossName + "' already announced; skipping.");
					return;
				}
				RecentBosses[bossName] = utcNow;
			}
			string biome = (position.HasValue ? ResolveBiome(position.Value) : string.Empty);
			string text = MessageFormatter.BossDefeat(bossName, killerName, biome);
			if (GjallarhornConfig.LogToConsole.Value)
			{
				GjallarhornPlugin.LogInfo("[boss] " + text);
			}
			Broadcast.Hud(text, GjallarhornConfig.BossUsesCenterBanner.Value);
			if (GjallarhornConfig.UseChatLine.Value)
			{
				Broadcast.Chat(text, (Vector3)(((??)position) ?? Vector3.zero));
			}
		}

		private static void Dispatch(DeathReport report)
		{
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			string text = MessageFormatter.Death(report);
			if (GjallarhornConfig.LogToConsole.Value)
			{
				GjallarhornPlugin.LogInfo("[" + ((report.Source == ReportSource.Exact) ? "exact" : "guess") + "] " + text + " " + $"(cause={report.Cause}, pos={report.Position})");
			}
			ChronicleRegistry.Record(report, text);
			if (GjallarhornConfig.UseTopLeftFeed.Value)
			{
				Broadcast.Hud(text, center: false);
			}
			if (GjallarhornConfig.UseChatLine.Value)
			{
				Broadcast.Chat(text, report.Position);
			}
		}

		public static string ResolveBiome(Vector3 position)
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: 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)
			try
			{
				if (WorldGenerator.instance != null)
				{
					return ((object)WorldGenerator.instance.GetBiome(position.x, position.z, 0.02f, false)/*cast due to .constrained prefix*/).ToString();
				}
			}
			catch (Exception ex)
			{
				GjallarhornPlugin.LogVerbose($"Biome lookup failed at {position}: {ex.Message}");
			}
			return string.Empty;
		}

		private static void Prune(DateTime now)
		{
			//IL_004e: 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_0084: 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)
			if (Announced.Count > 0)
			{
				List<ZDOID> list = null;
				foreach (KeyValuePair<ZDOID, DateTime> item in Announced)
				{
					if (now - item.Value > AnnouncedRetention)
					{
						(list ?? (list = new List<ZDOID>())).Add(item.Key);
					}
				}
				if (list != null)
				{
					foreach (ZDOID item2 in list)
					{
						Announced.Remove(item2);
					}
				}
			}
			if (RecentBosses.Count <= 0)
			{
				return;
			}
			List<string> list2 = null;
			foreach (KeyValuePair<string, DateTime> recentBoss in RecentBosses)
			{
				if (now - recentBoss.Value > BossDedupeWindow)
				{
					(list2 ?? (list2 = new List<string>())).Add(recentBoss.Key);
				}
			}
			if (list2 == null)
			{
				return;
			}
			foreach (string item3 in list2)
			{
				RecentBosses.Remove(item3);
			}
		}
	}
	public static class DeathWatcher
	{
		private static readonly Dictionary<ZDOID, bool> LastSeenDead = new Dictionary<ZDOID, bool>();

		private static readonly List<ZDOID> Seen = new List<ZDOID>();

		private static readonly List<ZDOID> Gone = new List<ZDOID>();

		private static readonly List<ZNetPeer> EmptyPeers = new List<ZNetPeer>();

		public static void Poll()
		{
			//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_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || ZDOMan.instance == null)
			{
				return;
			}
			Seen.Clear();
			foreach (ZNetPeer item in ZNet.instance.GetPeers() ?? EmptyPeers)
			{
				if (item == null || !item.IsReady() || ((ZDOID)(ref item.m_characterID)).IsNone())
				{
					continue;
				}
				ZDO zDO = ZDOMan.instance.GetZDO(item.m_characterID);
				if (zDO == null)
				{
					continue;
				}
				ZDOID characterID = item.m_characterID;
				Seen.Add(characterID);
				bool flag = zDO.GetBool(ZDOVars.s_dead, false);
				if (!LastSeenDead.TryGetValue(characterID, out var value))
				{
					LastSeenDead[characterID] = flag;
					continue;
				}
				LastSeenDead[characterID] = flag;
				if (!value && flag)
				{
					string text = zDO.GetString(ZDOVars.s_playerName, string.Empty);
					if (string.IsNullOrWhiteSpace(text))
					{
						text = item.m_playerName;
					}
					Vector3 position = zDO.GetPosition();
					AnnouncementService.SubmitObserved(new DeathReport
					{
						VictimZdoId = characterID,
						VictimName = text,
						Position = position,
						Biome = AnnouncementService.ResolveBiome(position),
						Source = ReportSource.Guessed
					});
				}
			}
			ForgetDeparted();
			AnnouncementService.Tick();
		}

		private static void ForgetDeparted()
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			if (LastSeenDead.Count == 0)
			{
				return;
			}
			Gone.Clear();
			foreach (ZDOID key in LastSeenDead.Keys)
			{
				if (!Seen.Contains(key))
				{
					Gone.Add(key);
				}
			}
			foreach (ZDOID item in Gone)
			{
				LastSeenDead.Remove(item);
			}
		}
	}
	public static class KillerGuesser
	{
		private readonly struct Candidate
		{
			public readonly string Name;

			public readonly int Level;

			public readonly float Distance;

			public readonly bool IsBoss;

			public Candidate(string name, int level, float distance, bool isBoss)
			{
				Name = name;
				Level = level;
				Distance = distance;
				IsBoss = isBoss;
			}
		}

		public static void Fill(DeathReport report)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			List<Candidate> list = Scan(report.Position, GjallarhornConfig.KillerSearchRadius.Value);
			if (list.Count == 0)
			{
				report.Cause = DeathCause.Generic;
				report.KillerName = string.Empty;
				report.KillerLevel = 0;
				return;
			}
			float value = GjallarhornConfig.SwarmRadius.Value;
			int num = 0;
			foreach (Candidate item in list)
			{
				if (item.Distance <= value)
				{
					num++;
				}
			}
			if (num >= GjallarhornConfig.SwarmThreshold.Value)
			{
				report.Cause = DeathCause.Overwhelmed;
				report.KillerName = "a horde";
				report.KillerLevel = 0;
				return;
			}
			Candidate candidate = list[0];
			foreach (Candidate item2 in list)
			{
				if (item2.Distance < candidate.Distance)
				{
					candidate = item2;
				}
			}
			report.Cause = ((!candidate.IsBoss) ? DeathCause.Monster : DeathCause.Boss);
			report.KillerName = candidate.Name;
			report.KillerLevel = candidate.Level;
		}

		private static List<Candidate> Scan(Vector3 position, float radius)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: 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_008f: Unknown result type (might be due to invalid IL or missing references)
			List<Candidate> list = new List<Candidate>();
			if (ZDOMan.instance == null || (Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ZoneSystem.instance == (Object)null)
			{
				return list;
			}
			try
			{
				Vector2s zone = ZoneSystem.GetZone(position);
				List<ZDO> list2 = new List<ZDO>();
				ZDOMan.instance.FindSectorObjects(zone, new SimulationDistance(1, 0, false), list2, (List<ZDO>)null);
				GjallarhornPlugin.LogVerbose($"Killer scan at {position}: {list2.Count} ZDO(s) in the surrounding zones.");
				float num2 = default(float);
				foreach (ZDO item in list2)
				{
					if (item == null)
					{
						continue;
					}
					float num = Vector3.Distance(item.GetPosition(), position);
					if (num > radius)
					{
						continue;
					}
					GameObject prefab = ZNetScene.instance.GetPrefab(item.GetPrefab());
					if (!((Object)(object)prefab == (Object)null))
					{
						Character component = prefab.GetComponent<Character>();
						if (!((Object)(object)component == (Object)null) && !component.IsPlayer() && !((Object)(object)prefab.GetComponent<BaseAI>() == (Object)null) && !item.GetBool(ZDOVars.s_tamed, false) && (!item.GetFloat(ZDOVars.s_health, ref num2) || !(num2 <= 0f)))
						{
							int num3 = item.GetInt(ZDOVars.s_level, 1);
							string text = NameTable.Creature(string.IsNullOrWhiteSpace(((Object)prefab).name) ? component.m_name : ((Object)prefab).name);
							GjallarhornPlugin.LogVerbose($"  candidate '{text}' level={num3} dist={num:0.0} boss={component.IsBoss()}");
							list.Add(new Candidate(text, num3, num, component.IsBoss()));
						}
					}
				}
			}
			catch (Exception arg)
			{
				GjallarhornPlugin.LogError($"Killer scan failed: {arg}");
			}
			return list;
		}
	}
}
namespace Gjallarhorn.Patches
{
	[HarmonyPatch(typeof(Character), "OnDeath")]
	public static class BossDeathPatch
	{
		private static void Prefix(Character __instance)
		{
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: 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)
			try
			{
				if (!((Object)(object)__instance == (Object)null) && __instance.IsBoss() && GjallarhornConfig.AnnounceBossDefeats.Value && !((Object)(object)__instance.m_nview == (Object)null) && __instance.m_nview.IsValid() && __instance.m_nview.IsOwner())
				{
					string bossName = NameTable.Creature(string.IsNullOrWhiteSpace(__instance.m_name) ? ((Object)((Component)__instance).gameObject).name : __instance.m_name);
					string killerName = string.Empty;
					HitData lastHit = __instance.m_lastHit;
					Character obj = ((lastHit != null) ? lastHit.GetAttacker() : null);
					Player val = (Player)(object)((obj is Player) ? obj : null);
					if (val != null)
					{
						killerName = val.GetPlayerName();
					}
					else if ((Object)(object)Player.m_localPlayer != (Object)null)
					{
						killerName = Player.m_localPlayer.GetPlayerName();
					}
					Vector3 position = ((Component)__instance).transform.position;
					if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
					{
						AnnouncementService.BossDefeated(bossName, killerName, position);
					}
					else
					{
						GjallarhornRpc.SendBoss(bossName, killerName, position);
					}
				}
			}
			catch (Exception arg)
			{
				GjallarhornPlugin.LogError($"Failed to report a boss defeat: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(ZoneSystem), "RPC_SetGlobalKey")]
	public static class GlobalKeyPatch
	{
		private static void Prefix(ZoneSystem __instance, string name)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (GjallarhornConfig.AnnounceBossDefeats.Value && NameTable.IsBossDefeatKey(name) && !__instance.m_globalKeys.Contains(name))
				{
					AnnouncementService.BossDefeated(NameTable.BossFromDefeatKey(name), string.Empty, Vector3.zero);
				}
			}
			catch (Exception arg)
			{
				GjallarhornPlugin.LogError($"Failed to handle global key '{name}': {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(Player), "OnDeath")]
	public static class PlayerDeathPatch
	{
		private static void Prefix(Player __instance)
		{
			try
			{
				if ((Object)(object)__instance == (Object)null || (Object)(object)((Character)__instance).m_nview == (Object)null || !((Character)__instance).m_nview.IsValid() || !((Character)__instance).m_nview.IsOwner())
				{
					return;
				}
				DeathReport deathReport = Build(__instance);
				if (deathReport != null)
				{
					if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
					{
						AnnouncementService.SubmitExact(deathReport);
					}
					else
					{
						GjallarhornRpc.SendDeath(deathReport);
					}
				}
			}
			catch (Exception arg)
			{
				GjallarhornPlugin.LogError($"Failed to report own death: {arg}");
			}
		}

		private static DeathReport Build(Player player)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: 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_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			Vector3 position = ((Component)player).transform.position;
			DeathReport deathReport = new DeathReport
			{
				VictimZdoId = ((Character)player).GetZDOID(),
				VictimName = player.GetPlayerName(),
				Position = position,
				Biome = AnnouncementService.ResolveBiome(position),
				Source = ReportSource.Exact
			};
			HitData lastHit = ((Character)player).m_lastHit;
			if (lastHit == null)
			{
				deathReport.Cause = DeathCause.Generic;
				return deathReport;
			}
			Character attacker = lastHit.GetAttacker();
			if ((Object)(object)attacker != (Object)null)
			{
				deathReport.KillerLevel = attacker.GetLevel();
				Player val = (Player)(object)((attacker is Player) ? attacker : null);
				deathReport.KillerName = ((val != null) ? val.GetPlayerName() : NameTable.Creature(((Object)((Component)attacker).gameObject).name));
			}
			deathReport.Cause = Classify(lastHit, attacker);
			return deathReport;
		}

		private static DeathCause Classify(HitData hit, Character attacker)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Expected I4, but got Unknown
			HitType hitType = hit.m_hitType;
			switch (hitType - 1)
			{
			case 1:
				return DeathCause.Pvp;
			case 0:
				if (!((Object)(object)attacker != (Object)null) || !attacker.IsBoss())
				{
					return DeathCause.Monster;
				}
				return DeathCause.Boss;
			case 2:
				return DeathCause.Fall;
			case 3:
			case 7:
			case 20:
				return DeathCause.Drowning;
			case 4:
			case 19:
			case 21:
			case 22:
				return DeathCause.Fire;
			case 5:
				return DeathCause.Freezing;
			case 6:
				return DeathCause.Poison;
			case 12:
				return DeathCause.Tree;
			case 13:
				return DeathCause.Self;
			case 8:
			case 9:
			case 10:
			case 11:
			case 14:
			case 15:
			case 16:
			case 17:
			case 18:
			case 23:
				return DeathCause.Environment;
			default:
				return DeathCause.Generic;
			}
		}
	}
}
namespace Gjallarhorn.Net
{
	public static class Broadcast
	{
		private const string ShowMessageRpc = "ShowMessage";

		private const string ChatMessageRpc = "ChatMessage";

		private static bool _warnedNoChatIdentity;

		public static void Hud(string text, bool center)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Expected I4, but got Unknown
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			if (ZRoutedRpc.instance == null || string.IsNullOrWhiteSpace(text))
			{
				return;
			}
			MessageType val = (MessageType)((!center) ? 1 : 2);
			try
			{
				ZRoutedRpc.instance.InvokeRoutedRPC(0L, "ShowMessage", new object[2]
				{
					(int)val,
					text
				});
				GjallarhornPlugin.LogVerbose($"HUD broadcast ({val}): {text}");
			}
			catch (Exception ex)
			{
				GjallarhornPlugin.LogWarning("HUD broadcast failed: " + ex.Message);
			}
		}

		public static void Chat(string text, Vector3 position)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: 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_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			if (ZRoutedRpc.instance == null || string.IsNullOrWhiteSpace(text))
			{
				return;
			}
			if (!TryGetChatIdentity(out var userId))
			{
				if (!_warnedNoChatIdentity)
				{
					_warnedNoChatIdentity = true;
					GjallarhornPlugin.LogWarning("No connected player with a valid platform ID, so chat lines cannot be sent -- clients would reject them on the permission check. Falling back to the top-left feed only. This resolves once someone is connected.");
				}
				return;
			}
			UserInfo val = new UserInfo
			{
				Name = GjallarhornConfig.ChatSenderName.Value,
				UserId = userId
			};
			try
			{
				ZRoutedRpc.instance.InvokeRoutedRPC(0L, "ChatMessage", new object[4] { position, 1, val, text });
				GjallarhornPlugin.LogVerbose("Chat broadcast: " + text);
			}
			catch (Exception ex)
			{
				GjallarhornPlugin.LogWarning("Chat broadcast failed: " + ex.Message);
			}
		}

		private static bool TryGetChatIdentity(out PlatformUserID userId)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: 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_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			userId = PlatformUserID.None;
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				return false;
			}
			foreach (PlayerInfo player in ZNet.instance.GetPlayerList())
			{
				PlatformUserID id = player.m_userInfo.m_id;
				if (((PlatformUserID)(ref id)).IsValid)
				{
					userId = player.m_userInfo.m_id;
					return true;
				}
			}
			return false;
		}
	}
	public static class GjallarhornRpc
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static CoroutineHandler <0>__OnDeathReceived;

			public static CoroutineHandler <1>__NoOp;

			public static CoroutineHandler <2>__OnBossReceived;
		}

		private const string DeathRpcName = "GjallarhornDeath";

		private const string BossRpcName = "GjallarhornBoss";

		private static CustomRPC _deathRpc;

		private static CustomRPC _bossRpc;

		public static void Register()
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Expected O, but got Unknown
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Expected O, but got Unknown
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Expected O, but got Unknown
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Expected O, but got Unknown
			NetworkManager instance = NetworkManager.Instance;
			object obj = <>O.<0>__OnDeathReceived;
			if (obj == null)
			{
				CoroutineHandler val = OnDeathReceived;
				<>O.<0>__OnDeathReceived = val;
				obj = (object)val;
			}
			object obj2 = <>O.<1>__NoOp;
			if (obj2 == null)
			{
				CoroutineHandler val2 = NoOp;
				<>O.<1>__NoOp = val2;
				obj2 = (object)val2;
			}
			_deathRpc = instance.AddRPC("GjallarhornDeath", (CoroutineHandler)obj, (CoroutineHandler)obj2);
			NetworkManager instance2 = NetworkManager.Instance;
			object obj3 = <>O.<2>__OnBossReceived;
			if (obj3 == null)
			{
				CoroutineHandler val3 = OnBossReceived;
				<>O.<2>__OnBossReceived = val3;
				obj3 = (object)val3;
			}
			object obj4 = <>O.<1>__NoOp;
			if (obj4 == null)
			{
				CoroutineHandler val4 = NoOp;
				<>O.<1>__NoOp = val4;
				obj4 = (object)val4;
			}
			_bossRpc = instance2.AddRPC("GjallarhornBoss", (CoroutineHandler)obj3, (CoroutineHandler)obj4);
			GjallarhornPlugin.LogInfo("Registered RPCs 'GjallarhornDeath' and 'GjallarhornBoss'.");
		}

		public static void SendDeath(DeathReport report)
		{
			if (_deathRpc != null && ZRoutedRpc.instance != null)
			{
				_deathRpc.SendPackage(ZRoutedRpc.instance.GetServerPeerID(), report.Write());
				GjallarhornPlugin.LogVerbose("Sent exact death report for '" + report.VictimName + "'.");
			}
		}

		public static void SendBoss(string bossName, string killerName, Vector3 position)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			if (_bossRpc != null && ZRoutedRpc.instance != null)
			{
				BossReport bossReport = new BossReport
				{
					BossName = bossName,
					KillerName = killerName,
					Position = position
				};
				_bossRpc.SendPackage(ZRoutedRpc.instance.GetServerPeerID(), bossReport.Write());
				GjallarhornPlugin.LogVerbose("Sent boss defeat report for '" + bossName + "'.");
			}
		}

		private static IEnumerator OnDeathReceived(long sender, ZPackage package)
		{
			if (package != null && package.Size() > 1)
			{
				DeathReport deathReport = DeathReport.Read(package);
				if (deathReport == null)
				{
					GjallarhornPlugin.LogWarning($"Discarded an unreadable death report from peer {sender}. That client " + "is most likely running a different version of Gjallarhorn.");
				}
				else
				{
					AnnouncementService.SubmitExact(deathReport);
				}
			}
			yield break;
		}

		private static IEnumerator OnBossReceived(long sender, ZPackage package)
		{
			if (package != null && package.Size() > 1)
			{
				BossReport bossReport = BossReport.Read(package);
				if (bossReport == null)
				{
					GjallarhornPlugin.LogWarning($"Discarded an unreadable boss report from peer {sender}.");
				}
				else
				{
					AnnouncementService.BossDefeated(bossReport.BossName, bossReport.KillerName, bossReport.Position);
				}
			}
			yield break;
		}

		private static IEnumerator NoOp(long sender, ZPackage package)
		{
			yield break;
		}
	}
}
namespace Gjallarhorn.Model
{
	public class BossReport
	{
		public const int WireVersion = 1;

		public string BossName = string.Empty;

		public string KillerName = string.Empty;

		public Vector3 Position = Vector3.zero;

		public ZPackage Write()
		{
			//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_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(1);
			val.Write(BossName ?? string.Empty);
			val.Write(KillerName ?? string.Empty);
			val.Write(Position);
			return val;
		}

		public static BossReport Read(ZPackage pkg)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (pkg.ReadInt() != 1)
				{
					return null;
				}
				return new BossReport
				{
					BossName = pkg.ReadString(),
					KillerName = pkg.ReadString(),
					Position = pkg.ReadVector3()
				};
			}
			catch
			{
				return null;
			}
		}
	}
	public enum DeathCause
	{
		Generic,
		Monster,
		Boss,
		Pvp,
		Overwhelmed,
		Fall,
		Drowning,
		Fire,
		Freezing,
		Poison,
		Tree,
		Environment,
		Self
	}
	public enum ReportSource
	{
		Guessed,
		Exact
	}
	public class DeathReport
	{
		public const int WireVersion = 1;

		public ZDOID VictimZdoId = ZDOID.None;

		public string VictimName = string.Empty;

		public string KillerName = string.Empty;

		public int KillerLevel;

		public DeathCause Cause;

		public Vector3 Position = Vector3.zero;

		public string Biome = string.Empty;

		public ReportSource Source;

		public ZPackage Write()
		{
			//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_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: 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_007c: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(1);
			val.Write(VictimZdoId);
			val.Write(VictimName ?? string.Empty);
			val.Write(KillerName ?? string.Empty);
			val.Write(KillerLevel);
			val.Write((int)Cause);
			val.Write(Position);
			val.Write(Biome ?? string.Empty);
			return val;
		}

		public static DeathReport Read(ZPackage pkg)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (pkg.ReadInt() != 1)
				{
					return null;
				}
				return new DeathReport
				{
					VictimZdoId = pkg.ReadZDOID(),
					VictimName = pkg.ReadString(),
					KillerName = pkg.ReadString(),
					KillerLevel = pkg.ReadInt(),
					Cause = (DeathCause)pkg.ReadInt(),
					Position = pkg.ReadVector3(),
					Biome = pkg.ReadString(),
					Source = ReportSource.Exact
				};
			}
			catch
			{
				return null;
			}
		}
	}
}
namespace Gjallarhorn.Logic
{
	public static class MessageFormatter
	{
		private static readonly Random Rng = new Random();

		private static readonly char[] Separator = new char[1] { ';' };

		public static string Death(DeathReport report)
		{
			string text = Pick(PoolFor(report.Cause));
			string text2 = (string.IsNullOrWhiteSpace(report.KillerName) ? "an unseen hand" : report.KillerName);
			if (GjallarhornConfig.IncludeStarRank.Value && report.KillerLevel > 1)
			{
				text2 = NameTable.WithStars(text2, report.KillerLevel);
			}
			string newValue = (GjallarhornConfig.IncludeBiome.Value ? NameTable.Biome(report.Biome) : "the wilds");
			return text.Replace("{victim}", Blank(report.VictimName, "A Viking")).Replace("{killer}", text2).Replace("{biome}", newValue);
		}

		public static string BossDefeat(string bossName, string killerName, string biome)
		{
			return Pick(GjallarhornConfig.BossDefeatMessages.Value).Replace("{boss}", Blank(bossName, "a great beast")).Replace("{killer}", Blank(killerName, "the Vikings")).Replace("{biome}", GjallarhornConfig.IncludeBiome.Value ? NameTable.Biome(biome) : "the wilds");
		}

		private static string PoolFor(DeathCause cause)
		{
			return cause switch
			{
				DeathCause.Monster => GjallarhornConfig.MonsterMessages.Value, 
				DeathCause.Boss => GjallarhornConfig.BossKilledPlayerMessages.Value, 
				DeathCause.Pvp => GjallarhornConfig.PvpMessages.Value, 
				DeathCause.Overwhelmed => GjallarhornConfig.OverwhelmedMessages.Value, 
				DeathCause.Fall => GjallarhornConfig.FallMessages.Value, 
				DeathCause.Drowning => GjallarhornConfig.DrowningMessages.Value, 
				DeathCause.Fire => GjallarhornConfig.FireMessages.Value, 
				DeathCause.Freezing => GjallarhornConfig.FreezingMessages.Value, 
				DeathCause.Poison => GjallarhornConfig.PoisonMessages.Value, 
				DeathCause.Tree => GjallarhornConfig.TreeMessages.Value, 
				DeathCause.Environment => GjallarhornConfig.EnvironmentMessages.Value, 
				DeathCause.Self => GjallarhornConfig.SelfMessages.Value, 
				_ => GjallarhornConfig.GenericMessages.Value, 
			};
		}

		private static string Pick(string pool)
		{
			if (string.IsNullOrWhiteSpace(pool))
			{
				return "{victim} has died in {biome}";
			}
			string[] array = pool.Split(Separator, StringSplitOptions.RemoveEmptyEntries);
			if (array.Length == 0)
			{
				return "{victim} has died in {biome}";
			}
			lock (Rng)
			{
				return array[Rng.Next(array.Length)].Trim();
			}
		}

		private static string Blank(string value, string fallback)
		{
			if (!string.IsNullOrWhiteSpace(value))
			{
				return value.Trim();
			}
			return fallback;
		}
	}
	public static class NameTable
	{
		private static readonly Dictionary<string, string> Creatures = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
		{
			{ "Greydwarf_Elite", "Greydwarf Brute" },
			{ "Greydwarf_Shaman", "Greydwarf Shaman" },
			{ "Skeleton_NoArcher", "Skeleton" },
			{ "gd_king", "The Elder" },
			{ "Blob", "Blob" },
			{ "BlobElite", "Oozer" },
			{ "Draugr_Elite", "Draugr Elite" },
			{ "Draugr_Ranged", "Draugr Archer" },
			{ "Leech", "Leech" },
			{ "Surtling", "Surtling" },
			{ "Wraith", "Wraith" },
			{ "StoneGolem", "Stone Golem" },
			{ "Hatchling", "Drake" },
			{ "Ulv", "Ulv" },
			{ "Fenring_Cultist", "Cultist" },
			{ "Goblin", "Fuling" },
			{ "GoblinArcher", "Fuling Archer" },
			{ "GoblinBrute", "Fuling Berserker" },
			{ "GoblinShaman", "Fuling Shaman" },
			{ "GoblinKing", "Yagluth" },
			{ "Deathsquito", "Deathsquito" },
			{ "BlobTar", "Growth" },
			{ "Lox", "Lox" },
			{ "Seeker", "Seeker" },
			{ "SeekerBrute", "Seeker Soldier" },
			{ "SeekerBrood", "Seeker Brood" },
			{ "SeekerQueen", "The Queen" },
			{ "Dverger", "Dvergr" },
			{ "DvergerMage", "Dvergr Mage" },
			{ "DvergerMageFire", "Dvergr Rogue" },
			{ "DvergerMageIce", "Dvergr Mage" },
			{ "DvergerMageSupport", "Dvergr Mage" },
			{ "Gjall", "Gjall" },
			{ "Tick", "Tick" },
			{ "Charred_Melee", "Charred Warrior" },
			{ "Charred_Archer", "Charred Marksman" },
			{ "Charred_Mage", "Charred Twitcher" },
			{ "Charred_Twitcher", "Charred Twitcher" },
			{ "FallenValkyrie", "Fallen Valkyrie" },
			{ "Morgen", "Morgen" },
			{ "Volture", "Volture" },
			{ "Asksvin", "Asksvin" },
			{ "BonemawSerpent", "Bonemaw" },
			{ "Fader", "Fader" },
			{ "Eikthyr", "Eikthyr" },
			{ "Bonemass", "Bonemass" },
			{ "Dragon", "Moder" },
			{ "Serpent", "Sea Serpent" }
		};

		private static readonly Dictionary<string, string> BossKeys = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
		{
			{ "defeated_eikthyr", "Eikthyr" },
			{ "defeated_gdking", "The Elder" },
			{ "defeated_bonemass", "Bonemass" },
			{ "defeated_dragon", "Moder" },
			{ "defeated_goblinking", "Yagluth" },
			{ "defeated_queen", "The Queen" },
			{ "defeated_fader", "Fader" }
		};

		private static readonly Dictionary<string, string> Biomes = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
		{
			{ "None", "the wilds" },
			{ "Meadows", "the Meadows" },
			{ "BlackForest", "the Black Forest" },
			{ "Swamp", "the Swamp" },
			{ "Mountain", "the Mountains" },
			{ "Plains", "the Plains" },
			{ "Ocean", "the Ocean" },
			{ "Mistlands", "the Mistlands" },
			{ "AshLands", "the Ashlands" },
			{ "DeepNorth", "the Deep North" }
		};

		public static bool IsBossDefeatKey(string globalKey)
		{
			if (!string.IsNullOrEmpty(globalKey))
			{
				return globalKey.StartsWith("defeated_", StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}

		public static string BossFromDefeatKey(string globalKey)
		{
			if (string.IsNullOrEmpty(globalKey))
			{
				return "a great beast";
			}
			if (BossKeys.TryGetValue(globalKey.Trim(), out var value))
			{
				return value;
			}
			string text = globalKey.Trim();
			int num = text.IndexOf('_');
			if (num >= 0 && num + 1 < text.Length)
			{
				text = text.Substring(num + 1);
			}
			return Prettify(text);
		}

		public static string Creature(string raw)
		{
			if (string.IsNullOrWhiteSpace(raw))
			{
				return "something unseen";
			}
			string text = raw.Trim();
			if (text.EndsWith("(Clone)", StringComparison.Ordinal))
			{
				text = text.Substring(0, text.Length - "(Clone)".Length).Trim();
			}
			if (text.StartsWith("$", StringComparison.Ordinal))
			{
				text = text.Substring(1);
				if (text.StartsWith("enemy_", StringComparison.OrdinalIgnoreCase))
				{
					text = text.Substring("enemy_".Length);
				}
			}
			if (!Creatures.TryGetValue(text, out var value))
			{
				return Prettify(text);
			}
			return value;
		}

		public static string WithStars(string creature, int level)
		{
			if (level < 2)
			{
				return creature;
			}
			return $"{level - 1}-star {creature}";
		}

		public static string Biome(string raw)
		{
			if (string.IsNullOrWhiteSpace(raw))
			{
				return "the wilds";
			}
			if (!Biomes.TryGetValue(raw.Trim(), out var value))
			{
				return "the wilds";
			}
			return value;
		}

		private static string Prettify(string name)
		{
			name = name.Replace('_', ' ').Trim();
			if (name.Length == 0)
			{
				return "something unseen";
			}
			StringBuilder stringBuilder = new StringBuilder(name.Length + 8);
			for (int i = 0; i < name.Length; i++)
			{
				char c = name[i];
				if (i > 0 && char.IsUpper(c) && !char.IsUpper(name[i - 1]) && name[i - 1] != ' ')
				{
					stringBuilder.Append(' ');
				}
				stringBuilder.Append(c);
			}
			string text = stringBuilder.ToString();
			return char.ToUpperInvariant(text[0]) + text.Substring(1);
		}
	}
}
namespace Gjallarhorn.Config
{
	public static class GjallarhornConfig
	{
		public static ConfigEntry<bool> AnnounceDeaths;

		public static ConfigEntry<bool> AnnounceBossDefeats;

		public static ConfigEntry<bool> UseTopLeftFeed;

		public static ConfigEntry<bool> UseChatLine;

		public static ConfigEntry<bool> BossUsesCenterBanner;

		public static ConfigEntry<string> ChatSenderName;

		public static ConfigEntry<bool> IncludeBiome;

		public static ConfigEntry<bool> IncludeStarRank;

		public static ConfigEntry<bool> LogToConsole;

		public static ConfigEntry<float> PollInterval;

		public static ConfigEntry<float> ExactReportGrace;

		public static ConfigEntry<float> KillerSearchRadius;

		public static ConfigEntry<float> SwarmRadius;

		public static ConfigEntry<int> SwarmThreshold;

		public static ConfigEntry<string> MonsterMessages;

		public static ConfigEntry<string> BossKilledPlayerMessages;

		public static ConfigEntry<string> PvpMessages;

		public static ConfigEntry<string> OverwhelmedMessages;

		public static ConfigEntry<string> FallMessages;

		public static ConfigEntry<string> DrowningMessages;

		public static ConfigEntry<string> FireMessages;

		public static ConfigEntry<string> FreezingMessages;

		public static ConfigEntry<string> PoisonMessages;

		public static ConfigEntry<string> TreeMessages;

		public static ConfigEntry<string> EnvironmentMessages;

		public static ConfigEntry<string> SelfMessages;

		public static ConfigEntry<string> GenericMessages;

		public static ConfigEntry<string> BossDefeatMessages;

		public static ConfigEntry<bool> VerboseLogging;

		public static void Bind(ConfigFile config)
		{
			//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_000d: Expected O, but got Unknown
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Expected O, but got Unknown
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Expected O, but got Unknown
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Expected O, but got Unknown
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Expected O, but got Unknown
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Expected O, but got Unknown
			//IL_014a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Expected O, but got Unknown
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			//IL_017f: Expected O, but got Unknown
			//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01aa: Expected O, but got Unknown
			//IL_01dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e7: Expected O, but got Unknown
			//IL_021a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0224: Expected O, but got Unknown
			//IL_0257: Unknown result type (might be due to invalid IL or missing references)
			//IL_0261: Expected O, but got Unknown
			//IL_0294: Unknown result type (might be due to invalid IL or missing references)
			//IL_029e: Expected O, but got Unknown
			//IL_02c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d0: Expected O, but got Unknown
			ConfigurationManagerAttributes val = new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			};
			VerboseLogging = config.Bind<bool>("0 - Diagnostics", "VerboseLogging", false, "Log every poll tick, proximity scan candidate, and broadcast. Very noisy; for debugging only. Local preference, not synced.");
			AnnounceDeaths = config.Bind<bool>("1 - Channels", "AnnounceDeaths", true, new ConfigDescription("Announce player deaths server-wide.", (AcceptableValueBase)null, new object[1] { val }));
			AnnounceBossDefeats = config.Bind<bool>("1 - Channels", "AnnounceBossDefeats", true, new ConfigDescription("Announce when a boss is defeated.", (AcceptableValueBase)null, new object[1] { val }));
			UseTopLeftFeed = config.Bind<bool>("1 - Channels", "UseTopLeftFeed", true, new ConfigDescription("Show death announcements in the top-left message feed, where pickup and skill-up messages appear.", (AcceptableValueBase)null, new object[1] { val }));
			UseChatLine = config.Bind<bool>("1 - Channels", "UseChatLine", true, new ConfigDescription("Also post death announcements as a chat line, so they stay in the chat history. Players standing near the death, or with the map open, will also see it as floating text at the spot -- that is vanilla chat behaviour, not something this mod can suppress.", (AcceptableValueBase)null, new object[1] { val }));
			BossUsesCenterBanner = config.Bind<bool>("1 - Channels", "BossUsesCenterBanner", true, new ConfigDescription("Boss defeats get the large centre-screen banner instead of the top-left feed. Player deaths never use the centre banner.", (AcceptableValueBase)null, new object[1] { val }));
			ChatSenderName = config.Bind<string>("1 - Channels", "ChatSenderName", "Gjallarhorn", new ConfigDescription("Name shown as the speaker on chat lines.", (AcceptableValueBase)null, new object[1] { val }));
			IncludeBiome = config.Bind<bool>("2 - Content", "IncludeBiome", true, new ConfigDescription("Resolve the {biome} token. When false, {biome} renders as 'the wilds'.", (AcceptableValueBase)null, new object[1] { val }));
			IncludeStarRank = config.Bind<bool>("2 - Content", "IncludeStarRank", true, new ConfigDescription("Prefix starred creatures in {killer}, e.g. '2-star Greydwarf'.", (AcceptableValueBase)null, new object[1] { val }));
			LogToConsole = config.Bind<bool>("2 - Content", "LogToConsole", true, new ConfigDescription("Write every announcement to the server console log.", (AcceptableValueBase)null, new object[1] { val }));
			PollInterval = config.Bind<float>("3 - Detection", "PollInterval", 1f, new ConfigDescription("Seconds between server-side checks of each connected player's death flag. Only used for players who do not have Gjallarhorn installed.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 10f), new object[1] { val }));
			ExactReportGrace = config.Bind<float>("3 - Detection", "ExactReportGrace", 1.5f, new ConfigDescription("After the server notices a death, wait this long for the victim's client to send exact details before falling back to a guess. Players with Gjallarhorn installed get an accurate killer; raising this only delays the fallback.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), new object[1] { val }));
			KillerSearchRadius = config.Bind<float>("3 - Detection", "KillerSearchRadius", 20f, new ConfigDescription("Radius searched for a likely killer when guessing. Guess only.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 64f), new object[1] { val }));
			SwarmRadius = config.Bind<float>("3 - Detection", "SwarmRadius", 6f, new ConfigDescription("Radius counted for the 'overwhelmed by a horde' message. Guess only.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 32f), new object[1] { val }));
			SwarmThreshold = config.Bind<int>("3 - Detection", "SwarmThreshold", 4, new ConfigDescription("Hostiles within SwarmRadius needed before a death reads as a horde death.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(2, 20), new object[1] { val }));
			MonsterMessages = Pool(config, "MonsterMessages", "{victim} was slain by {killer} in {biome};{victim} was torn apart by {killer};{killer} claimed the soul of {victim} in {biome};{victim} fell before the wrath of {killer}", "Killed by a creature. Tokens: {victim} {killer} {biome}", val);
			BossKilledPlayerMessages = Pool(config, "BossKilledPlayerMessages", "{victim} was annihilated by {killer};The legendary {killer} crushed {victim} into dust;{victim} dared challenge {killer}, and was destroyed", "Killed by a boss. Tokens: {victim} {killer} {biome}", val);
			PvpMessages = Pool(config, "PvpMessages", "{victim} was cut down by {killer};{killer} sent {victim} to the halls of Valhalla;{victim} lost a duel with {killer} in {biome}", "Killed by another player. Tokens: {victim} {killer} {biome}", val);
			OverwhelmedMessages = Pool(config, "OverwhelmedMessages", "{victim} was overwhelmed by a horde in {biome};{victim} made a last stand against overwhelming odds;{victim} fell fighting, surrounded, in {biome}", "Killed while surrounded. Tokens: {victim} {biome}", val);
			FallMessages = Pool(config, "FallMessages", "{victim} discovered that Valheim has gravity;{victim} fell to their death in {biome};The ground rose up to meet {victim}", "Fall damage. Tokens: {victim} {biome}", val);
			DrowningMessages = Pool(config, "DrowningMessages", "{victim} was claimed by the sea;{victim} drowned in {biome};The waves closed over {victim}", "Drowning or lost at sea. Tokens: {victim} {biome}", val);
			FireMessages = Pool(config, "FireMessages", "{victim} burned to ash in {biome};{victim} learned that fire is hot;The flames took {victim}", "Burning, cinders, lava, incinerator. Tokens: {victim} {biome}", val);
			FreezingMessages = Pool(config, "FreezingMessages", "{victim} froze to death in {biome};The cold took {victim};{victim} should have packed warmer clothes", "Freezing. Tokens: {victim} {biome}", val);
			PoisonMessages = Pool(config, "PoisonMessages", "{victim} succumbed to poison in {biome};Venom finished what {killer} started;{victim} died gasping, poisoned, in {biome}", "Poison. Tokens: {victim} {killer} {biome}", val);
			TreeMessages = Pool(config, "TreeMessages", "{victim} was crushed by a falling tree;The forest took its revenge on {victim};{victim} lost an argument with a tree in {biome}", "Killed by a felled tree. Tokens: {victim} {biome}", val);
			EnvironmentMessages = Pool(config, "EnvironmentMessages", "{victim} died to their own surroundings in {biome};{victim} was undone by the terrain;{biome} itself finished {victim}", "Carts, boats, structures, turrets, stalagtites, smoke, edge of world. Tokens: {victim} {biome}", val);
			SelfMessages = Pool(config, "SelfMessages", "{victim} died by their own hand;{victim} made a poor decision in {biome}", "Self-inflicted. Tokens: {victim} {biome}", val);
			GenericMessages = Pool(config, "GenericMessages", "{victim} has departed for the halls of Valhalla;The Norns have cut the thread of {victim}'s life;{victim} died in {biome}", "Fallback when the cause is unknown. Tokens: {victim} {biome}", val);
			BossDefeatMessages = Pool(config, "BossDefeatMessages", "{boss} has been defeated!;{killer} has slain {boss}!;The mighty {boss} falls. Gjallarhorns will sing of this day.", "Boss defeated. {killer} renders as 'the Vikings' when the killer is unknown. Tokens: {boss} {killer} {biome}", val);
		}

		private static ConfigEntry<string> Pool(ConfigFile config, string key, string value, string help, ConfigurationManagerAttributes admin)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			return config.Bind<string>("4 - Messages", key, value, new ConfigDescription(help + " Separate alternatives with semicolons.", (AcceptableValueBase)null, new object[1] { admin }));
		}
	}
}
namespace Gjallarhorn.Chronicle
{
	public sealed class ChronicleEntry
	{
		public DateTime TimestampUtc;

		public string VictimName;

		public string KillerName;

		public DeathCause Cause;

		public string Biome;

		public Vector3 Position;

		public bool WasExact;

		public string Message;
	}
	public static class ChronicleRegistry
	{
		private const int MaxEntries = 200;

		private static readonly List<ChronicleEntry> Entries = new List<ChronicleEntry>();

		private static readonly object Gate = new object();

		public static void Record(DeathReport report, string message)
		{
			//IL_0042: 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)
			ChronicleEntry item = new ChronicleEntry
			{
				TimestampUtc = DateTime.UtcNow,
				VictimName = report.VictimName,
				KillerName = report.KillerName,
				Cause = report.Cause,
				Biome = report.Biome,
				Position = report.Position,
				WasExact = (report.Source == ReportSource.Exact),
				Message = message
			};
			lock (Gate)
			{
				Entries.Add(item);
				if (Entries.Count > 200)
				{
					Entries.RemoveRange(0, Entries.Count - 200);
				}
			}
		}

		public static List<ChronicleEntry> Recent(int count = 25)
		{
			lock (Gate)
			{
				int num = Math.Min(count, Entries.Count);
				List<ChronicleEntry> list = new List<ChronicleEntry>(num);
				for (int num2 = Entries.Count - 1; num2 >= Entries.Count - num; num2--)
				{
					list.Add(Entries[num2]);
				}
				return list;
			}
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}