Decompiled source of SkaldSaga v0.1.1

BepInEx/plugins/SkaldSaga/SkaldSaga.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETFramework,Version=v4.8.1", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("NomadicWar")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyDescription("Skald — A Valheim event broadcaster. Boss kills, deaths, biome discoveries, joins and departures announced in real-time. Install on server and clients.")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0")]
[assembly: AssemblyProduct("Skald")]
[assembly: AssemblyTitle("Skald")]
[assembly: AssemblyVersion("0.1.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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 SkaldSaga
{
	[BepInPlugin("com.nomadicwar.skaldsaga", "SkaldSaga", "0.1.1")]
	public class Plugin : BaseUnityPlugin
	{
		private struct BossKillRecord
		{
			public string prefab;

			public Vector3 position;

			public float time;
		}

		public const string PluginGUID = "com.nomadicwar.skaldsaga";

		public const string PluginName = "SkaldSaga";

		public const string PluginVersion = "0.1.1";

		internal static ManualLogSource Log;

		private readonly Harmony _harmony = new Harmony("com.nomadicwar.skaldsaga");

		public static ConfigEntry<bool> EnablePlayerDeath;

		public static ConfigEntry<bool> EnableBossKill;

		public static ConfigEntry<bool> EnablePlayerJoin;

		public static ConfigEntry<bool> EnablePlayerLeave;

		public static ConfigEntry<bool> EnableBiomeDiscovery;

		public static ConfigEntry<bool> EnableKillMilestone;

		public static ConfigEntry<bool> EnableDeathMilestone;

		public static ConfigEntry<bool> EnableGearTier;

		public static ConfigEntry<bool> EnableTitleEarned;

		public static ConfigEntry<bool> ShowDayNumber;

		public static ConfigEntry<bool> ShowOnlineCount;

		public static ConfigEntry<string> MessagePrefix;

		public static ConfigEntry<bool> EnableChronicleLog;

		public static ConfigEntry<bool> LogServerStartStop;

		public static ConfigEntry<bool> ShowInChat;

		public static ConfigEntry<bool> ShowOnHud;

		public static ConfigEntry<KeyCode> PanelToggleKey;

		public static ConfigEntry<KeyCode> TitleMenuKey;

		public static ConfigEntry<KeyCode> StatsMenuKey;

		public static ConfigEntry<float> HudScale;

		public static ConfigEntry<float> BossCreditRadius;

		private static ConfigFile _configFile = null;

		private static string _configFilePath = null;

		private static DateTime _lastConfigWrite = DateTime.MinValue;

		private const float CONFIG_CHECK_INTERVAL = 5f;

		private static float _configCheckTimer = 0f;

		public static readonly Queue<string> PendingMessages = new Queue<string>();

		private static readonly List<BossKillRecord> _recentBossKills = new List<BossKillRecord>();

		private const float BOSS_KILL_TTL = 10f;

		private const float BOSS_KILL_POS_TOL = 20f;

		private static readonly HashSet<string> _processedConfirmations = new HashSet<string>();

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			EnablePlayerDeath = ((BaseUnityPlugin)this).Config.Bind<bool>("Events", "EnablePlayerDeath", true, "Broadcast when a player dies");
			EnableBossKill = ((BaseUnityPlugin)this).Config.Bind<bool>("Events", "EnableBossKill", true, "Broadcast when a boss is killed");
			EnablePlayerJoin = ((BaseUnityPlugin)this).Config.Bind<bool>("Events", "EnablePlayerJoin", true, "Broadcast when a player joins");
			EnablePlayerLeave = ((BaseUnityPlugin)this).Config.Bind<bool>("Events", "EnablePlayerLeave", true, "Broadcast when a player leaves");
			EnableBiomeDiscovery = ((BaseUnityPlugin)this).Config.Bind<bool>("Events", "EnableBiomeDiscovery", true, "Broadcast when a player discovers a new biome");
			EnableKillMilestone = ((BaseUnityPlugin)this).Config.Bind<bool>("Events", "EnableKillMilestone", true, "Broadcast enemy kill milestones");
			EnableDeathMilestone = ((BaseUnityPlugin)this).Config.Bind<bool>("Events", "EnableDeathMilestone", true, "Broadcast death milestones");
			EnableGearTier = ((BaseUnityPlugin)this).Config.Bind<bool>("Events", "EnableGearTier", true, "Broadcast new gear tier");
			EnableTitleEarned = ((BaseUnityPlugin)this).Config.Bind<bool>("Events", "EnableTitleEarned", true, "Broadcast when a title is earned");
			ShowDayNumber = ((BaseUnityPlugin)this).Config.Bind<bool>("Format", "ShowDayNumber", true, "Append day number to messages");
			ShowOnlineCount = ((BaseUnityPlugin)this).Config.Bind<bool>("Format", "ShowOnlineCount", true, "Append online count to messages");
			MessagePrefix = ((BaseUnityPlugin)this).Config.Bind<string>("Format", "MessagePrefix", "⚔ ", "Prefix for all Skald messages");
			EnableChronicleLog = ((BaseUnityPlugin)this).Config.Bind<bool>("Log", "EnableChronicleLog", true, "Write SkaldSaga_Chronicle.log");
			LogServerStartStop = ((BaseUnityPlugin)this).Config.Bind<bool>("Log", "LogServerStartStop", true, "Log server start/stop");
			ShowInChat = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowInChat", false, "Show messages in chat");
			ShowOnHud = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowOnHud", true, "Show messages on HUD");
			PanelToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Display", "PanelToggleKey", (KeyCode)92, "Toggle event panel");
			TitleMenuKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Display", "TitleMenuKey", (KeyCode)93, "Toggle title menu");
			StatsMenuKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Display", "StatsMenuKey", (KeyCode)91, "Toggle stats panel");
			HudScale = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "HudScale", 1f, "HUD scale (0.5-2.0)");
			BossCreditRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Bosses", "BossCreditRadius", 100f, "Radius in meters in which players receive boss kill credit.");
			Log.LogInfo((object)"SkaldSaga 0.1.1 awakens.");
			try
			{
				_harmony.PatchAll();
				Log.LogInfo((object)"[SkaldSaga] All patches applied.");
			}
			catch (Exception ex)
			{
				Log.LogError((object)("[SkaldSaga] PatchAll failed: " + ex.Message));
			}
			InitConfigReload(((BaseUnityPlugin)this).Config);
		}

		private void OnDestroy()
		{
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer() && LogServerStartStop.Value)
			{
				Chronicle.Write("SERVER", "shutdown", "Server shutting down.", null);
			}
			Chronicle.Close();
			_harmony.UnpatchSelf();
		}

		public static bool IsServer()
		{
			return (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer();
		}

		public static void InitConfigReload(ConfigFile cfg)
		{
			_configFile = cfg;
			_configFilePath = cfg.ConfigFilePath;
			if (File.Exists(_configFilePath))
			{
				_lastConfigWrite = File.GetLastWriteTimeUtc(_configFilePath);
			}
		}

		public static void TickConfigReload(float dt)
		{
			if (_configFile == null || _configFilePath == null)
			{
				return;
			}
			_configCheckTimer += dt;
			if (_configCheckTimer < 5f)
			{
				return;
			}
			_configCheckTimer = 0f;
			try
			{
				if (!File.Exists(_configFilePath))
				{
					return;
				}
				DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(_configFilePath);
				if (!(lastWriteTimeUtc <= _lastConfigWrite))
				{
					_lastConfigWrite = lastWriteTimeUtc;
					_configFile.Reload();
					Log.LogInfo((object)"[SkaldSaga] Config reloaded from disk.");
					if ((Object)(object)MessageHud.instance != (Object)null)
					{
						MessageHud.instance.ShowMessage((MessageType)1, "SkaldSaga config reloaded.", 0, (Sprite)null, false);
					}
				}
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("[SkaldSaga] Config reload failed: " + ex.Message));
			}
		}

		public static void Broadcast(string message, string eventType = "event", string player = null, string detail = null)
		{
			try
			{
				string text = MessagePrefix.Value ?? "⚔ ";
				int num = (((Object)(object)EnvMan.instance != (Object)null) ? EnvMan.instance.GetDay() : 0);
				int num2 = (((Object)(object)ZNet.instance != (Object)null) ? (ZNet.instance.GetConnectedPeers().Count + 1) : 0);
				string text2 = ((ShowDayNumber.Value && num > 0) ? $" [Day {num}]" : "");
				string text3 = ((ShowOnlineCount.Value && num2 > 0) ? $" ({num2} online)" : "");
				string text4 = text + message + text2 + text3;
				if ((Object)(object)ZNet.instance != (Object)null)
				{
					foreach (ZNetPeer peer in ZNet.instance.GetPeers())
					{
						try
						{
							ZRoutedRpc.instance.InvokeRoutedRPC(peer.m_uid, "SkaldBroadcast", new object[1] { text4 });
						}
						catch
						{
						}
					}
				}
				Log.LogInfo((object)("[SkaldSaga] " + text4));
				Chronicle.Write(player ?? "world", eventType, message, new Dictionary<string, string>
				{
					{
						"day",
						num.ToString()
					},
					{
						"online",
						num2.ToString()
					},
					{
						"detail",
						detail ?? ""
					}
				});
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("[SkaldSaga] Broadcast failed: " + ex.Message));
			}
		}

		public static void OnSkaldEvent(long senderID, string eventType, string playerName, string message, string detail)
		{
			try
			{
				if (1 == 0)
				{
				}
				bool flag = eventType switch
				{
					"player_death" => EnablePlayerDeath.Value, 
					"boss_kill" => EnableBossKill.Value, 
					"biome_discovery" => EnableBiomeDiscovery.Value, 
					"kill_milestone" => EnableKillMilestone.Value, 
					"death_milestone" => EnableDeathMilestone.Value, 
					"gear_tier" => EnableGearTier.Value, 
					"title_earned" => EnableTitleEarned.Value, 
					_ => true, 
				};
				if (1 == 0)
				{
				}
				if (flag)
				{
					Broadcast(message, eventType, playerName, detail);
				}
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("[SkaldSaga] OnSkaldEvent error: " + ex.Message));
			}
		}

		public static void SendEvent(string eventType, string playerName, string message, string detail = "")
		{
			try
			{
				if (ZRoutedRpc.instance != null)
				{
					ZNet instance = ZNet.instance;
					ZNetPeer val = ((instance != null) ? instance.GetServerPeer() : null);
					if (val != null)
					{
						ZRoutedRpc.instance.InvokeRoutedRPC(val.m_uid, "SkaldEvent", new object[4] { eventType, playerName, message, detail });
						Log.LogInfo((object)("[SkaldSaga] Sent " + eventType + ": " + message));
					}
				}
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("[SkaldSaga] SendEvent failed: " + ex.Message));
			}
		}

		public static void OnSkaldBroadcast(long senderID, string message)
		{
			try
			{
				Log.LogInfo((object)("[SkaldSaga] Broadcast received: " + message));
				PendingMessages.Enqueue(message);
				SkaldPanel.AddEvent(message);
				FlushMessages();
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("[SkaldSaga] OnSkaldBroadcast failed: " + ex.Message));
			}
		}

		public static void FlushMessages()
		{
			bool flag = (Object)(object)Chat.instance != (Object)null;
			bool flag2 = (Object)(object)MessageHud.instance != (Object)null;
			if (!flag && !flag2)
			{
				return;
			}
			while (PendingMessages.Count > 0)
			{
				string text = PendingMessages.Dequeue();
				if (flag && ShowInChat.Value)
				{
					((Terminal)Chat.instance).AddString("SkaldSaga", text, (Type)2, false);
				}
				if (flag2 && ShowOnHud.Value)
				{
					MessageHud.instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false);
				}
			}
		}

		public static void OnSkaldBossDetected(long senderID, string prefab, Vector3 position, string bossDisplayName)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_01de: Unknown result type (might be due to invalid IL or missing references)
			//IL_0214: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: 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_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!IsServer())
				{
					return;
				}
				float now = Time.realtimeSinceStartup;
				_recentBossKills.RemoveAll((BossKillRecord r) => now - r.time > 10f);
				int num = Mathf.RoundToInt(position.x / 10f);
				int num2 = Mathf.RoundToInt(position.y / 10f);
				int num3 = Mathf.RoundToInt(position.z / 10f);
				foreach (BossKillRecord recentBossKill in _recentBossKills)
				{
					int num4 = Mathf.RoundToInt(recentBossKill.position.x / 10f);
					int num5 = Mathf.RoundToInt(recentBossKill.position.y / 10f);
					int num6 = Mathf.RoundToInt(recentBossKill.position.z / 10f);
					if (recentBossKill.prefab == prefab && num4 == num && num5 == num2 && num6 == num3)
					{
						Log.LogInfo((object)("[SkaldSaga] BossDetected duplicate suppressed: " + prefab));
						return;
					}
				}
				_recentBossKills.Add(new BossKillRecord
				{
					prefab = prefab,
					position = position,
					time = now
				});
				Log.LogInfo((object)("[SkaldSaga] BossDetected confirmed: " + prefab + " (" + bossDisplayName + ") — broadcasting"));
				foreach (ZNetPeer peer in ZNet.instance.GetPeers())
				{
					try
					{
						ZRoutedRpc.instance.InvokeRoutedRPC(peer.m_uid, "SkaldBossConfirmed", new object[3] { prefab, position, bossDisplayName });
					}
					catch
					{
					}
				}
				OnSkaldBossConfirmed(0L, prefab, position, bossDisplayName);
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("[SkaldSaga] OnSkaldBossDetected error: " + ex.Message));
			}
		}

		public static void OnSkaldBossConfirmed(long senderID, string prefab, Vector3 position, string bossDisplayName)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null || !SkaldData.IsLoaded)
				{
					return;
				}
				string text = $"{prefab}:{Mathf.RoundToInt(position.x / 10f)}:{Mathf.RoundToInt(position.y / 10f)}:{Mathf.RoundToInt(position.z / 10f)}";
				if (_processedConfirmations.Contains(text))
				{
					Log.LogInfo((object)("[SkaldSaga] BossConfirmed already processed: " + text));
					return;
				}
				_processedConfirmations.Add(text);
				string text2 = (string.IsNullOrEmpty(bossDisplayName) ? TitleSystem.GetBossDisplayName(prefab) : bossDisplayName);
				float num = Mathf.Clamp(BossCreditRadius.Value, 5f, 500f);
				foreach (Player allPlayer in Player.GetAllPlayers())
				{
					if ((Object)(object)allPlayer == (Object)null || Vector3.Distance(((Component)allPlayer).transform.position, position) > num)
					{
						continue;
					}
					string playerName = allPlayer.GetPlayerName();
					TitleSystem.OnBossKill(playerName, prefab);
					Log.LogInfo((object)("[SkaldSaga] BossConfirmed credit: " + playerName + " for " + prefab + " (" + text2 + ")"));
					if ((Object)(object)allPlayer == (Object)(object)localPlayer)
					{
						string displayName = TitleSystem.GetDisplayName(playerName);
						SendEvent("boss_kill", playerName, displayName + " helped defeat " + text2 + "!", prefab);
						if ((Object)(object)MessageHud.instance != (Object)null)
						{
							MessageHud.instance.ShowMessage((MessageType)1, text2 + " defeated! Title credit awarded.", 0, (Sprite)null, false);
						}
					}
				}
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("[SkaldSaga] OnSkaldBossConfirmed error: " + ex.Message));
			}
		}

		public static string GetDeathCause(Player player)
		{
			//IL_004c: 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_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_015f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: 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_019b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d0: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				object? obj = AccessTools.Field(typeof(Character), "m_lastHit")?.GetValue(player);
				HitData val = (HitData)((obj is HitData) ? obj : null);
				if (val == null)
				{
					return ((Character)player).IsSwimming() ? "drowning" : "a fall";
				}
				if (val.m_attacker != ZDOID.None)
				{
					ZDOMan instance = ZDOMan.instance;
					ZDO val2 = ((instance != null) ? instance.GetZDO(val.m_attacker) : null);
					if (val2 != null)
					{
						ZNetScene instance2 = ZNetScene.instance;
						GameObject val3 = ((instance2 != null) ? instance2.GetPrefab(val2.GetPrefab()) : null);
						if ((Object)(object)val3 != (Object)null)
						{
							Character component = val3.GetComponent<Character>();
							if ((Object)(object)component != (Object)null && !string.IsNullOrEmpty(component.m_name))
							{
								string text = Localization.instance.Localize(component.m_name);
								return component.IsBoss() ? text : ("a " + text);
							}
							return ((Object)val3).name.Replace("(Clone)", "").Trim();
						}
					}
				}
				DamageTypes damage = val.m_damage;
				if (damage.m_fire > 0f)
				{
					return "burning";
				}
				if (damage.m_frost > 0f)
				{
					return "freezing";
				}
				if (damage.m_poison > 0f)
				{
					return "poison";
				}
				if (damage.m_spirit > 0f)
				{
					return "spirit damage";
				}
				if (damage.m_blunt > 0f || damage.m_slash > 0f || damage.m_pierce > 0f)
				{
					return ((Character)player).IsSwimming() ? "drowning" : "combat";
				}
				return ((Character)player).IsSwimming() ? "drowning" : "a fall";
			}
			catch
			{
				return "unknown causes";
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "Awake")]
	public static class Patch_ZNetAwake
	{
		private static void Postfix()
		{
			try
			{
				if (ZNet.instance.IsServer())
				{
					ZRoutedRpc instance = ZRoutedRpc.instance;
					if (instance != null)
					{
						instance.Register<string, string, string, string>("SkaldEvent", (Method<string, string, string, string>)Plugin.OnSkaldEvent);
					}
					ZRoutedRpc instance2 = ZRoutedRpc.instance;
					if (instance2 != null)
					{
						instance2.Register<string, Vector3, string>("SkaldBossDetected", (Action<long, string, Vector3, string>)Plugin.OnSkaldBossDetected);
					}
					Chronicle.Init();
					if (Plugin.LogServerStartStop.Value)
					{
						Chronicle.Write("SERVER", "startup", "Skald is listening.", null);
					}
					Plugin.Log.LogInfo((object)"[SkaldSaga] Server RPC registered.");
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[SkaldSaga] ZNet.Awake error: " + ex.Message));
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
	public static class Patch_PlayerJoin
	{
		private static void Postfix(ZRpc rpc)
		{
			try
			{
				if (Plugin.IsServer() && Plugin.EnablePlayerJoin.Value)
				{
					ZNet instance = ZNet.instance;
					string text = ((instance == null) ? null : instance.GetPeers()?.Find((ZNetPeer p) => p.m_rpc == rpc))?.m_playerName;
					if (string.IsNullOrEmpty(text))
					{
						text = "A Viking";
					}
					Plugin.Broadcast(text + " has entered the world.", "player_join", text);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[SkaldSaga] PlayerJoin error: " + ex.Message));
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "RPC_Disconnect")]
	public static class Patch_PlayerLeave
	{
		[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
		public static class TrackJoin
		{
			private static void Postfix(ZRpc rpc)
			{
				if (!Plugin.IsServer())
				{
					return;
				}
				ZNet instance = ZNet.instance;
				ZNetPeer val = ((instance == null) ? null : instance.GetPeers()?.Find((ZNetPeer p) => p.m_rpc == rpc));
				if (val != null)
				{
					JoinTimes[val.m_uid] = DateTime.UtcNow;
					if (!string.IsNullOrEmpty(val.m_playerName))
					{
						JoinNames[val.m_uid] = val.m_playerName;
					}
				}
			}
		}

		private static readonly Dictionary<long, DateTime> JoinTimes = new Dictionary<long, DateTime>();

		private static readonly Dictionary<long, string> JoinNames = new Dictionary<long, string>();

		internal static void FireLeave(ZRpc rpc)
		{
			try
			{
				if (Plugin.IsServer() && Plugin.EnablePlayerLeave.Value)
				{
					ZNet instance = ZNet.instance;
					ZNetPeer val = ((instance == null) ? null : instance.GetPeers()?.Find((ZNetPeer p) => p.m_rpc == rpc));
					long num = val?.m_uid ?? 0;
					string value = val?.m_playerName;
					if (string.IsNullOrEmpty(value) && num != 0)
					{
						JoinNames.TryGetValue(num, out value);
					}
					if (string.IsNullOrEmpty(value))
					{
						value = "A Viking";
					}
					string text = "";
					if (num != 0L && JoinTimes.TryGetValue(num, out var value2))
					{
						TimeSpan timeSpan = DateTime.UtcNow - value2;
						text = ((timeSpan.TotalHours >= 1.0) ? $" (played {(int)timeSpan.TotalHours}h {timeSpan.Minutes}m)" : $" (played {timeSpan.Minutes}m)");
						JoinTimes.Remove(num);
						JoinNames.Remove(num);
					}
					Plugin.Broadcast(value + " has left the world" + text + ".", "player_leave", value, text.Trim());
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[SkaldSaga] PlayerLeave error: " + ex.Message));
			}
		}

		private static void Prefix(ZRpc rpc)
		{
			FireLeave(rpc);
		}
	}
	[HarmonyPatch(typeof(ZNet), "SendDisconnect", new Type[] { typeof(ZNetPeer) })]
	public static class Patch_PlayerLeave_Send
	{
		private static void Prefix(ZNetPeer peer)
		{
			try
			{
				if (Plugin.IsServer() && peer?.m_rpc != null)
				{
					Patch_PlayerLeave.FireLeave(peer.m_rpc);
				}
			}
			catch
			{
			}
		}
	}
	[HarmonyPatch(typeof(Game), "Start")]
	public static class Patch_GameStart
	{
		private static void Postfix()
		{
			try
			{
				ZRoutedRpc instance = ZRoutedRpc.instance;
				if (instance != null)
				{
					instance.Register<string>("SkaldBroadcast", (Action<long, string>)Plugin.OnSkaldBroadcast);
				}
				ZRoutedRpc instance2 = ZRoutedRpc.instance;
				if (instance2 != null)
				{
					instance2.Register<string, Vector3, string>("SkaldBossConfirmed", (Action<long, string, Vector3, string>)Plugin.OnSkaldBossConfirmed);
				}
				Plugin.Log.LogInfo((object)"[SkaldSaga] Client RPC registered.");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[SkaldSaga] Game.Start error: " + ex.Message));
			}
		}
	}
	[HarmonyPatch(typeof(Chat), "Awake")]
	public static class Patch_ChatAwake
	{
		private static void Postfix()
		{
			Plugin.FlushMessages();
		}
	}
	[HarmonyPatch(typeof(MessageHud), "Awake")]
	public static class Patch_MessageHudAwake
	{
		private static void Postfix()
		{
			Plugin.FlushMessages();
		}
	}
	[HarmonyPatch(typeof(Player), "OnSpawned")]
	public static class Patch_DataLoad
	{
		private static void Postfix(Player __instance)
		{
			if (((Character)__instance).IsOwner())
			{
				SkaldData.Load(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(Player), "OnDestroy")]
	public static class Patch_DataSave
	{
		private static void Prefix(Player __instance)
		{
			if (((Character)__instance).IsOwner())
			{
				SkaldData.Save();
			}
		}
	}
	[HarmonyPatch(typeof(Player), "Update")]
	public static class Patch_DataTick
	{
		private static void Postfix(Player __instance)
		{
			if (((Character)__instance).IsOwner())
			{
				SkaldData.Tick(Time.deltaTime);
				Plugin.TickConfigReload(Time.deltaTime);
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "SendDisconnect", new Type[] { typeof(ZNetPeer) })]
	public static class Patch_SaveOnLogout
	{
		private static void Prefix()
		{
			SkaldData.Save();
		}
	}
	[HarmonyPatch(typeof(Player), "CreateTombStone")]
	public static class Patch_PlayerDeath
	{
		private static void Postfix(Player __instance)
		{
			try
			{
				if (((Character)__instance).IsOwner())
				{
					string playerName = __instance.GetPlayerName();
					string deathCause = Plugin.GetDeathCause(__instance);
					string activeTitle = TitleSystem.GetActiveTitle();
					string text = (string.IsNullOrEmpty(activeTitle) ? playerName : (playerName + " the " + activeTitle));
					string message = ((deathCause == "a fall") ? (text + " fell to their death.") : (text + " has fallen to " + deathCause + "."));
					MilestoneTracker.OnDeath(playerName);
					Plugin.SendEvent("player_death", playerName, message, deathCause);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[SkaldSaga] PlayerDeath error: " + ex.Message));
			}
		}
	}
	[HarmonyPatch(typeof(Character), "OnDeath")]
	public static class Patch_CreatureKill
	{
		private static readonly HashSet<int> _processedBosses = new HashSet<int>();

		private static void Prefix(Character __instance)
		{
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: 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_016e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (__instance is Player)
				{
					return;
				}
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null || !SkaldData.IsLoaded)
				{
					return;
				}
				string text = ((Object)((Component)__instance).gameObject).name.Replace("(Clone)", "").Trim();
				string playerName = localPlayer.GetPlayerName();
				if (!TitleSystem.BossTitles.ContainsKey(text))
				{
					MilestoneTracker.OnKill(playerName);
					TitleSystem.OnCreatureKill(playerName, text);
					return;
				}
				int instanceID = ((Object)__instance).GetInstanceID();
				if (_processedBosses.Contains(instanceID))
				{
					return;
				}
				_processedBosses.Add(instanceID);
				Vector3 position = ((Component)__instance).transform.position;
				string text2 = Localization.instance.Localize(__instance.m_name);
				if (string.IsNullOrEmpty(text2))
				{
					text2 = TitleSystem.GetBossDisplayName(text);
				}
				Plugin.Log.LogInfo((object)$"[SkaldSaga] BossDetected: {text} ({text2}) at {position}");
				ZNet instance = ZNet.instance;
				ZNetPeer val = ((instance != null) ? instance.GetServerPeer() : null);
				if (val != null)
				{
					ZRoutedRpc instance2 = ZRoutedRpc.instance;
					if (instance2 != null)
					{
						instance2.InvokeRoutedRPC(val.m_uid, "SkaldBossDetected", new object[3] { text, position, text2 });
					}
				}
				else
				{
					Plugin.OnSkaldBossDetected(0L, text, position, text2);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[SkaldSaga] CreatureKill error: " + ex.Message));
			}
		}
	}
	[HarmonyPatch(typeof(MessageHud), "ShowBiomeFoundMsg")]
	public static class Patch_BiomeDiscovery
	{
		private static string _lastBiome = "";

		private static float _lastBiomeTime = -999f;

		private static void Prefix(string text, bool playStinger)
		{
			try
			{
				Player localPlayer = Player.m_localPlayer;
				if (!((Object)(object)localPlayer == (Object)null))
				{
					string playerName = localPlayer.GetPlayerName();
					string text2 = "the " + Localization.instance.Localize(text);
					float realtimeSinceStartup = Time.realtimeSinceStartup;
					if (!(text2 == _lastBiome) || !(realtimeSinceStartup - _lastBiomeTime < 30f))
					{
						_lastBiome = text2;
						_lastBiomeTime = realtimeSinceStartup;
						string displayName = TitleSystem.GetDisplayName(playerName);
						Plugin.SendEvent("biome_discovery", playerName, displayName + " has discovered " + text2 + " for the first time.", text2);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[SkaldSaga] BiomeDiscovery error: " + ex.Message));
			}
		}
	}
	[HarmonyPatch(typeof(Humanoid), "EquipItem")]
	public static class Patch_GearTier
	{
		private static readonly Dictionary<string, (int tier, string label)> TierMap = new Dictionary<string, (int, string)>
		{
			{
				"ArmorLeatherChest",
				(1, "Leather")
			},
			{
				"ArmorLeatherLegs",
				(1, "Leather")
			},
			{
				"HelmetLeather",
				(1, "Leather")
			},
			{
				"AxeFlint",
				(1, "Flint")
			},
			{
				"KnifeFlint",
				(1, "Flint")
			},
			{
				"SpearFlint",
				(1, "Flint")
			},
			{
				"HelmetBronze",
				(2, "Bronze")
			},
			{
				"ArmorBronzeChest",
				(2, "Bronze")
			},
			{
				"ArmorBronzeLegs",
				(2, "Bronze")
			},
			{
				"AtgeirBronze",
				(2, "Bronze")
			},
			{
				"AxeBronze",
				(2, "Bronze")
			},
			{
				"SwordBronze",
				(2, "Bronze")
			},
			{
				"SpearBronze",
				(2, "Bronze")
			},
			{
				"MaceBronze",
				(2, "Bronze")
			},
			{
				"ShieldBronze",
				(2, "Bronze")
			},
			{
				"ShieldBronzeBuckler",
				(2, "Bronze")
			},
			{
				"HelmetIron",
				(3, "Iron")
			},
			{
				"ArmorIronChest",
				(3, "Iron")
			},
			{
				"ArmorIronLegs",
				(3, "Iron")
			},
			{
				"SwordIron",
				(3, "Iron")
			},
			{
				"AtgeirIron",
				(3, "Iron")
			},
			{
				"AxeIron",
				(3, "Iron")
			},
			{
				"MaceIron",
				(3, "Iron")
			},
			{
				"SledgeIron",
				(3, "Iron")
			},
			{
				"ShieldIron",
				(3, "Iron")
			},
			{
				"ShieldIronSquare",
				(3, "Iron")
			},
			{
				"ShieldIronTower",
				(3, "Iron")
			},
			{
				"SpearElderbark",
				(3, "Iron")
			},
			{
				"SwordSilver",
				(4, "Silver")
			},
			{
				"Frostner",
				(4, "Silver")
			},
			{
				"KnifeSilver",
				(4, "Silver")
			},
			{
				"ShieldSilver",
				(4, "Silver")
			},
			{
				"ArmorWolfChest",
				(4, "Wolf")
			},
			{
				"ArmorWolfLegs",
				(4, "Wolf")
			},
			{
				"HelmetDrake",
				(4, "Wolf")
			},
			{
				"CapeWolf",
				(4, "Wolf")
			},
			{
				"ArmorWolf",
				(4, "Wolf")
			},
			{
				"SwordBlackmetal",
				(5, "Blackmetal")
			},
			{
				"AtgeirBlackmetal",
				(5, "Blackmetal")
			},
			{
				"AxeBlackmetal",
				(5, "Blackmetal")
			},
			{
				"KnifeBlackmetal",
				(5, "Blackmetal")
			},
			{
				"ShieldBlackmetal",
				(5, "Blackmetal")
			},
			{
				"ShieldBlackmetalTower",
				(5, "Blackmetal")
			},
			{
				"SwordBlackMetal",
				(5, "Blackmetal")
			},
			{
				"AtgeirBlackMetal",
				(5, "Blackmetal")
			},
			{
				"AxeBlackMetal",
				(5, "Blackmetal")
			},
			{
				"KnifeBlackMetal",
				(5, "Blackmetal")
			},
			{
				"ShieldBlackMetal",
				(5, "Blackmetal")
			},
			{
				"ShieldBlackMetalTower",
				(5, "Blackmetal")
			},
			{
				"ArmorPaddedCuirass",
				(5, "Padded")
			},
			{
				"ArmorPaddedGreaves",
				(5, "Padded")
			},
			{
				"HelmetPadded",
				(5, "Padded")
			},
			{
				"ArmorCarapaceChest",
				(6, "Carapace")
			},
			{
				"ArmorCarapaceLegs",
				(6, "Carapace")
			},
			{
				"HelmetCarapace",
				(6, "Carapace")
			},
			{
				"SwordCarapace",
				(6, "Carapace")
			},
			{
				"ShieldCarapace",
				(6, "Carapace")
			},
			{
				"SwordFlametal",
				(7, "Flametal")
			},
			{
				"AtgeirFlametal",
				(7, "Flametal")
			},
			{
				"AxeFlametal",
				(7, "Flametal")
			},
			{
				"ArmorFlametalChest",
				(7, "Flametal")
			},
			{
				"ArmorFlametalLegs",
				(7, "Flametal")
			},
			{
				"HelmetFlametal",
				(7, "Flametal")
			}
		};

		private static void Postfix(Humanoid __instance, ItemData item, bool __result)
		{
			try
			{
				if (!__result)
				{
					return;
				}
				Player val = (Player)(object)((__instance is Player) ? __instance : null);
				if (val == null || !((Character)val).IsOwner() || item?.m_shared == null || !SkaldData.IsLoaded)
				{
					return;
				}
				GameObject dropPrefab = item.m_dropPrefab;
				string text = ((dropPrefab != null) ? ((Object)dropPrefab).name : null) ?? item.m_shared.m_name?.Replace("$item_", "").Replace("$", "");
				if (!string.IsNullOrEmpty(text) && TierMap.TryGetValue(text, out (int, string) value))
				{
					int @int = SkaldData.GetInt("gear_tier");
					if (value.Item1 > @int)
					{
						SkaldData.SetInt("gear_tier", value.Item1);
						string playerName = val.GetPlayerName();
						string displayName = TitleSystem.GetDisplayName(playerName);
						Plugin.SendEvent("gear_tier", playerName, displayName + " has equipped " + value.Item2 + " gear for the first time!", value.Item2);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[SkaldSaga] GearTier error: " + ex.Message));
			}
		}
	}
	public static class SkaldData
	{
		private static Dictionary<string, string> _data = new Dictionary<string, string>();

		private static bool _dirty = false;

		private static string _charID = null;

		private static float _saveTimer = 0f;

		private const float SAVE_INTERVAL = 60f;

		private static string StableDir => Path.Combine(Paths.ConfigPath, "SkaldSaga");

		public static bool IsLoaded => _charID != null;

		private static string GetPath(string charID)
		{
			return Path.Combine(StableDir, "skaldsaga_" + charID + ".json");
		}

		private static void TryMigrate(string charID)
		{
			string path = GetPath(charID);
			if (File.Exists(path))
			{
				return;
			}
			string[] array = new string[2]
			{
				Path.Combine(Paths.PluginPath, "SkaldSaga"),
				Path.Combine(Paths.PluginPath, "Skald")
			};
			string[] array2 = new string[2] { "skaldsaga_", "skald_" };
			string[] array3 = array;
			foreach (string path2 in array3)
			{
				string[] array4 = array2;
				foreach (string text in array4)
				{
					string text2 = Path.Combine(path2, text + charID + ".json");
					if (File.Exists(text2))
					{
						try
						{
							File.Copy(text2, path, overwrite: false);
							Plugin.Log.LogInfo((object)("[SkaldSaga] Migrated save: " + text2 + " -> " + path));
							return;
						}
						catch (Exception ex)
						{
							Plugin.Log.LogWarning((object)("[SkaldSaga] Migration failed: " + ex.Message));
							return;
						}
					}
				}
			}
		}

		public static void Load(Player player)
		{
			try
			{
				string text = player.GetPlayerID().ToString();
				if (_charID == text && IsLoaded)
				{
					return;
				}
				_charID = text;
				Directory.CreateDirectory(StableDir);
				TryMigrate(text);
				_data = new Dictionary<string, string>();
				string path = GetPath(text);
				if (File.Exists(path))
				{
					string text2 = File.ReadAllText(path).Trim().TrimStart(new char[1] { '{' })
						.TrimEnd(new char[1] { '}' });
					string[] array = text2.Split(new char[1] { ',' });
					foreach (string text3 in array)
					{
						string[] array2 = text3.Trim().Split(new char[1] { ':' }, 2);
						if (array2.Length == 2)
						{
							_data[array2[0].Trim().Trim(new char[1] { '"' })] = array2[1].Trim().Trim(new char[1] { '"' });
						}
					}
				}
				_dirty = false;
				Plugin.Log.LogInfo((object)$"[SkaldSaga] Data loaded for {player.GetPlayerName()}: {_data.Count} entries from {path}");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[SkaldSaga] Load failed: " + ex.Message));
			}
		}

		public static void Save()
		{
			if (!_dirty || _charID == null)
			{
				return;
			}
			try
			{
				StringBuilder stringBuilder = new StringBuilder("{");
				bool flag = true;
				foreach (KeyValuePair<string, string> datum in _data)
				{
					if (!flag)
					{
						stringBuilder.Append(",");
					}
					stringBuilder.Append("\"" + datum.Key + "\":\"" + datum.Value + "\"");
					flag = false;
				}
				stringBuilder.Append("}");
				File.WriteAllText(GetPath(_charID), stringBuilder.ToString());
				_dirty = false;
				Plugin.Log.LogInfo((object)"[SkaldSaga] Data saved.");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[SkaldSaga] Save failed: " + ex.Message));
			}
		}

		public static void Tick(float dt)
		{
			if (_dirty)
			{
				_saveTimer += dt;
				if (_saveTimer >= 60f)
				{
					_saveTimer = 0f;
					Save();
				}
			}
		}

		public static int GetInt(string key)
		{
			if (_data.TryGetValue(key, out var value) && int.TryParse(value, out var result))
			{
				return result;
			}
			return 0;
		}

		public static void SetInt(string key, int val)
		{
			_data[key] = val.ToString();
			_dirty = true;
		}

		public static string GetString(string key)
		{
			_data.TryGetValue(key, out var value);
			return value ?? "";
		}

		public static void SetString(string key, string val)
		{
			_data[key] = val ?? "";
			_dirty = true;
		}

		public static HashSet<string> GetSet(string key)
		{
			if (_data.TryGetValue(key, out var value) && !string.IsNullOrEmpty(value))
			{
				return new HashSet<string>(value.Split(new char[1] { ',' }));
			}
			return new HashSet<string>();
		}

		public static void SetSet(string key, HashSet<string> set)
		{
			_data[key] = string.Join(",", set);
			_dirty = true;
		}
	}
	public static class MilestoneTracker
	{
		private static readonly int[] KillMilestones = new int[7] { 1, 10, 100, 500, 1000, 5000, 10000 };

		private static readonly int[] DeathMilestones = new int[8] { 1, 5, 10, 25, 50, 100, 500, 1000 };

		public static void OnKill(string playerName)
		{
			if (!Plugin.EnableKillMilestone.Value || !SkaldData.IsLoaded)
			{
				return;
			}
			int num = SkaldData.GetInt("kills") + 1;
			SkaldData.SetInt("kills", num);
			string displayName = TitleSystem.GetDisplayName(playerName);
			int[] killMilestones = KillMilestones;
			for (int i = 0; i < killMilestones.Length; i++)
			{
				int num2 = killMilestones[i];
				if (num == num2)
				{
					Plugin.SendEvent("kill_milestone", playerName, $"{displayName} has slain {num2} enemies!", num2.ToString());
					break;
				}
			}
		}

		public static void OnDeath(string playerName)
		{
			if (!Plugin.EnableDeathMilestone.Value || !SkaldData.IsLoaded)
			{
				return;
			}
			int num = SkaldData.GetInt("deaths") + 1;
			SkaldData.SetInt("deaths", num);
			string displayName = TitleSystem.GetDisplayName(playerName);
			int[] deathMilestones = DeathMilestones;
			for (int i = 0; i < deathMilestones.Length; i++)
			{
				int num2 = deathMilestones[i];
				if (num == num2)
				{
					string message = ((num2 == 1) ? (displayName + " has died for the first time. Welcome to Valheim.") : $"{displayName} has died {num2} times. The saga continues.");
					Plugin.SendEvent("death_milestone", playerName, message, num2.ToString());
					break;
				}
			}
		}
	}
	public static class TitleSystem
	{
		public static readonly Dictionary<string, (int kills, string title)[]> CreatureTitles = new Dictionary<string, (int, string)[]>
		{
			{
				"Boar",
				new(int, string)[3]
				{
					(100, "Boar Hunter"),
					(500, "Boar Slayer"),
					(1000, "Swine Reaper")
				}
			},
			{
				"Deer",
				new(int, string)[3]
				{
					(100, "Deer Stalker"),
					(500, "The Huntsman"),
					(1000, "Ghost of the Meadows")
				}
			},
			{
				"Neck",
				new(int, string)[3]
				{
					(100, "Neck Breaker"),
					(500, "Swamp Wader"),
					(1000, "Neck's Nightmare")
				}
			},
			{
				"Greyling",
				new(int, string)[3]
				{
					(100, "Greyling Crusher"),
					(500, "Ash Maker"),
					(1000, "Ember of Yggdrasil")
				}
			},
			{
				"Greydwarf",
				new(int, string)[3]
				{
					(100, "Greydwarf Hunter"),
					(500, "Greydwarf Bane"),
					(1000, "Terror of the Forest")
				}
			},
			{
				"Troll",
				new(int, string)[3]
				{
					(100, "Troll Fighter"),
					(500, "Troll Breaker"),
					(1000, "Giant Slayer")
				}
			},
			{
				"Draugr",
				new(int, string)[3]
				{
					(100, "Draugr Slayer"),
					(500, "Death Walker"),
					(1000, "Bane of the Fallen")
				}
			},
			{
				"Skeleton",
				new(int, string)[3]
				{
					(100, "Bone Breaker"),
					(500, "The Undying"),
					(1000, "Lord of Ash")
				}
			},
			{
				"Blob",
				new(int, string)[3]
				{
					(100, "Blob Stomper"),
					(500, "Ooze Wader"),
					(1000, "Plague Walker")
				}
			},
			{
				"Wolf",
				new(int, string)[3]
				{
					(100, "Wolf Hunter"),
					(500, "Pack Breaker"),
					(1000, "Alpha of Alphas")
				}
			},
			{
				"Drake",
				new(int, string)[3]
				{
					(100, "Drake Hunter"),
					(500, "Dragonslayer"),
					(1000, "Wyrm's Bane")
				}
			},
			{
				"StoneGolem",
				new(int, string)[3]
				{
					(100, "Stone Breaker"),
					(500, "Mountain Crusher"),
					(1000, "Wrath of the Peak")
				}
			},
			{
				"GoblinBrute",
				new(int, string)[3]
				{
					(100, "Fuling Fighter"),
					(500, "Plains Reaper"),
					(1000, "Goblin King's Dread")
				}
			},
			{
				"Goblin",
				new(int, string)[3]
				{
					(100, "Fuling Fighter"),
					(500, "Plains Reaper"),
					(1000, "Goblin King's Dread")
				}
			},
			{
				"Deathsquito",
				new(int, string)[3]
				{
					(100, "Bug Swatter"),
					(500, "Needle Dancer"),
					(1000, "Lord of Needles")
				}
			},
			{
				"Lox",
				new(int, string)[3]
				{
					(100, "Lox Tamer"),
					(500, "Lox Breaker"),
					(1000, "Plains Titan")
				}
			},
			{
				"Leech",
				new(int, string)[3]
				{
					(100, "Leech Stomper"),
					(500, "Bloodless Wader"),
					(1000, "The Undrained")
				}
			},
			{
				"Wraith",
				new(int, string)[3]
				{
					(100, "Wraith Breaker"),
					(500, "Ghost Render"),
					(1000, "Banisher of Souls")
				}
			},
			{
				"Abomination",
				new(int, string)[3]
				{
					(100, "Root Render"),
					(500, "Swamp Titan"),
					(1000, "The Unmade")
				}
			},
			{
				"Fenring",
				new(int, string)[3]
				{
					(100, "Fenring Slayer"),
					(500, "Moon Breaker"),
					(1000, "The Unwolfed")
				}
			},
			{
				"Bat",
				new(int, string)[3]
				{
					(100, "Bat Swatter"),
					(500, "Cave Clearer"),
					(1000, "Darkness Render")
				}
			},
			{
				"Cultist",
				new(int, string)[3]
				{
					(100, "Cultist Slayer"),
					(500, "Heretic Breaker"),
					(1000, "The Unconverted")
				}
			},
			{
				"Serpent",
				new(int, string)[3]
				{
					(100, "Serpent Hunter"),
					(500, "Sea Slayer"),
					(1000, "Leviathan's Bane")
				}
			},
			{
				"Seeker",
				new(int, string)[3]
				{
					(100, "Seeker Slayer"),
					(500, "Mistwalker"),
					(1000, "Bug Crusher")
				}
			},
			{
				"SeekerBrute",
				new(int, string)[3]
				{
					(100, "Brute Breaker"),
					(500, "Carapace Crusher"),
					(1000, "Titan of the Mist")
				}
			},
			{
				"SeekerSoldier",
				new(int, string)[3]
				{
					(100, "Soldier Slayer"),
					(500, "Legion Breaker"),
					(1000, "Mist Commander")
				}
			},
			{
				"Tick",
				new(int, string)[3]
				{
					(100, "Tick Flicker"),
					(500, "Bloodless"),
					(1000, "The Untapped")
				}
			},
			{
				"Gjall",
				new(int, string)[3]
				{
					(100, "Gjall Hunter"),
					(500, "Sky Render"),
					(1000, "The Unscreamed")
				}
			},
			{
				"Dvergr",
				new(int, string)[3]
				{
					(100, "Dvergr Duelist"),
					(500, "Rune Breaker"),
					(1000, "Slayer of Ancients")
				}
			},
			{
				"Charred",
				new(int, string)[3]
				{
					(100, "Ash Fighter"),
					(500, "Ember Breaker"),
					(1000, "Lord of Cinders")
				}
			},
			{
				"Morgen",
				new(int, string)[3]
				{
					(100, "Morgen Slayer"),
					(500, "Ashen Reaper"),
					(1000, "Wrath of the Ashlands")
				}
			},
			{
				"Volture",
				new(int, string)[3]
				{
					(100, "Volture Hunter"),
					(500, "Sky Scourge"),
					(1000, "Wings of Ash")
				}
			},
			{
				"FallenValkyrie",
				new(int, string)[3]
				{
					(100, "Valkyrie Breaker"),
					(500, "Heaven's Bane"),
					(1000, "The Unchosen")
				}
			},
			{
				"Ulv",
				new(int, string)[3]
				{
					(100, "Ulv Hunter"),
					(500, "Pack Render"),
					(1000, "Alpha of the North")
				}
			},
			{
				"Hare",
				new(int, string)[3]
				{
					(100, "Hare Hunter"),
					(500, "Swift Slayer"),
					(1000, "The Unbounding")
				}
			}
		};

		private static readonly Dictionary<string, string> CreatureKeyMap = new Dictionary<string, string>
		{
			{ "Boar", "Boar" },
			{ "Boar_piggy", "Boar" },
			{ "Deer", "Deer" },
			{ "Neck", "Neck" },
			{ "Greyling", "Greyling" },
			{ "Greydwarf", "Greydwarf" },
			{ "Greydwarf_elite", "Greydwarf" },
			{ "Greydwarf_shaman", "Greydwarf" },
			{ "Troll", "Troll" },
			{ "Draugr", "Draugr" },
			{ "Draugr_Elite", "Draugr" },
			{ "Skeleton", "Skeleton" },
			{ "Skeleton_NoArcher", "Skeleton" },
			{ "Blob", "Blob" },
			{ "BlobElite", "Blob" },
			{ "Wolf", "Wolf" },
			{ "Wolf_cub", "Wolf" },
			{ "Drake", "Drake" },
			{ "Hatchling", "Drake" },
			{ "DrakeHatchling", "Drake" },
			{ "StoneGolem", "StoneGolem" },
			{ "Goblin", "Goblin" },
			{ "GoblinArcher", "Goblin" },
			{ "GoblinShaman", "Goblin" },
			{ "GoblinBrute", "GoblinBrute" },
			{ "Deathsquito", "Deathsquito" },
			{ "Lox", "Lox" },
			{ "Leech", "Leech" },
			{ "Wraith", "Wraith" },
			{ "Abomination", "Abomination" },
			{ "Fenring", "Fenring" },
			{ "Fenring_CultistStar", "Fenring" },
			{ "Bat", "Bat" },
			{ "Cultist", "Cultist" },
			{ "CultistStar", "Cultist" },
			{ "Serpent", "Serpent" },
			{ "Seeker", "Seeker" },
			{ "SeekerStar", "Seeker" },
			{ "SeekerBrute", "SeekerBrute" },
			{ "SeekerSoldier", "SeekerSoldier" },
			{ "Tick", "Tick" },
			{ "Gjall", "Gjall" },
			{ "Dvergr", "Dvergr" },
			{ "DvergrMage", "Dvergr" },
			{ "DvergrRogue", "Dvergr" },
			{ "Charred", "Charred" },
			{ "CharredMage", "Charred" },
			{ "CharredArcher", "Charred" },
			{ "CharredTwitcher", "Charred" },
			{ "Morgen", "Morgen" },
			{ "MorgenStar", "Morgen" },
			{ "Volture", "Volture" },
			{ "FallenValkyrie", "FallenValkyrie" },
			{ "Ulv", "Ulv" },
			{ "Hare", "Hare" }
		};

		public static readonly Dictionary<string, string> BossTitles = new Dictionary<string, string>
		{
			{ "Eikthyr", "Stag Breaker" },
			{ "gd_king", "Voice of the Ancient" },
			{ "Bonemass", "Bane of the Swamp" },
			{ "Dragon", "Veil of the Frost" },
			{ "GoblinKing", "Ender of Giants" },
			{ "SeekerQueen", "Hive Ender" },
			{ "Fader", "The Ashen" }
		};

		private static readonly int[] BossKillMilestones = new int[3] { 3, 10, 25 };

		private static readonly string[] BossKillTitles = new string[3] { "Boss Hunter", "World Breaker", "God Ender" };

		public static readonly Dictionary<string, string> BossDisplayNames = new Dictionary<string, string>
		{
			{ "Eikthyr", "Eikthyr" },
			{ "gd_king", "The Elder" },
			{ "Bonemass", "Bonemass" },
			{ "Dragon", "Moder" },
			{ "GoblinKing", "Yagluth" },
			{ "SeekerQueen", "The Queen" },
			{ "Fader", "Fader" }
		};

		public static string GetActiveTitle()
		{
			return SkaldData.GetString("title");
		}

		public static void SetActiveTitle(string t)
		{
			SkaldData.SetString("title", t);
		}

		public static List<string> GetEarnedTitles()
		{
			return new List<string>(SkaldData.GetSet("earned"));
		}

		public static string GetDisplayName(string playerName)
		{
			string activeTitle = GetActiveTitle();
			return string.IsNullOrEmpty(activeTitle) ? playerName : (playerName + " the " + activeTitle);
		}

		private static void EarnTitle(string playerName, string title)
		{
			if (string.IsNullOrEmpty(title) || !SkaldData.IsLoaded)
			{
				return;
			}
			HashSet<string> set = SkaldData.GetSet("earned");
			if (!set.Contains(title))
			{
				set.Add(title);
				SkaldData.SetSet("earned", set);
				if (string.IsNullOrEmpty(GetActiveTitle()))
				{
					SetActiveTitle(title);
				}
				string displayName = GetDisplayName(playerName);
				Plugin.SendEvent("title_earned", playerName, displayName + " has earned the title: " + title + "!", title);
			}
		}

		public static void OnCreatureKill(string playerName, string prefab)
		{
			if (!SkaldData.IsLoaded)
			{
				return;
			}
			string value = null;
			if (!CreatureKeyMap.TryGetValue(prefab, out value))
			{
				foreach (KeyValuePair<string, string> item in CreatureKeyMap)
				{
					if (prefab.StartsWith(item.Key, StringComparison.OrdinalIgnoreCase))
					{
						value = item.Value;
						Plugin.Log.LogInfo((object)("[Skald AutoMap] " + prefab + " -> " + value + " (startsWith)"));
						break;
					}
				}
				if (value == null)
				{
					foreach (KeyValuePair<string, string> item2 in CreatureKeyMap)
					{
						if (prefab.IndexOf(item2.Key, StringComparison.OrdinalIgnoreCase) >= 0)
						{
							value = item2.Value;
							Plugin.Log.LogInfo((object)("[Skald AutoMap] " + prefab + " -> " + value + " (substring)"));
							break;
						}
					}
				}
				if (value == null)
				{
					value = prefab;
					Plugin.Log.LogInfo((object)("[Skald AutoMap] " + prefab + " -> NEW CATEGORY (" + value + ")"));
				}
			}
			string key = "c_" + value;
			int num = SkaldData.GetInt(key) + 1;
			SkaldData.SetInt(key, num);
			if (!CreatureTitles.TryGetValue(value, out (int, string)[] value2))
			{
				return;
			}
			(int, string)[] array = value2;
			for (int i = 0; i < array.Length; i++)
			{
				var (num2, title) = array[i];
				if (num == num2)
				{
					EarnTitle(playerName, title);
					break;
				}
			}
		}

		public static string GetBossDisplayName(string prefab)
		{
			string value;
			return BossDisplayNames.TryGetValue(prefab, out value) ? value : prefab;
		}

		public static void OnBossKill(string playerName, string prefab)
		{
			if (!SkaldData.IsLoaded)
			{
				return;
			}
			if (BossTitles.TryGetValue(prefab, out var value))
			{
				EarnTitle(playerName, value);
			}
			HashSet<string> set = SkaldData.GetSet("earned");
			bool flag = true;
			foreach (string value2 in BossTitles.Values)
			{
				if (!set.Contains(value2))
				{
					flag = false;
					break;
				}
			}
			if (flag)
			{
				EarnTitle(playerName, "Slayer of Gods");
			}
			int num = SkaldData.GetInt("boss_kills") + 1;
			SkaldData.SetInt("boss_kills", num);
			for (int i = 0; i < BossKillMilestones.Length; i++)
			{
				if (num == BossKillMilestones[i])
				{
					EarnTitle(playerName, BossKillTitles[i]);
				}
			}
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "IsVisible")]
	public static class Patch_CursorUnlock
	{
		private static void Postfix(ref bool __result)
		{
			if ((Object)(object)Player.m_localPlayer != (Object)null && (TitleMenu.IsVisible || StatsPanel.IsVisible))
			{
				__result = true;
			}
		}
	}
	[HarmonyPatch(typeof(Hud), "Awake")]
	public static class Patch_HudAwake
	{
		private static void Postfix(Hud __instance)
		{
			try
			{
				SkaldPanel.Build(((Component)__instance).transform);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[SkaldSaga] SkaldPanel.Build failed: " + ex.Message));
			}
			try
			{
				TitleMenu.Build(((Component)__instance).transform);
				Plugin.Log.LogInfo((object)"[SkaldSaga] TitleMenu built.");
			}
			catch (Exception ex2)
			{
				Plugin.Log.LogWarning((object)("[SkaldSaga] TitleMenu.Build failed: " + ex2.Message));
			}
			try
			{
				StatsPanel.Build(((Component)__instance).transform);
				Plugin.Log.LogInfo((object)"[SkaldSaga] StatsPanel built.");
			}
			catch (Exception ex3)
			{
				Plugin.Log.LogWarning((object)("[SkaldSaga] StatsPanel.Build failed: " + ex3.Message));
			}
		}
	}
	[HarmonyPatch(typeof(Hud), "Update")]
	public static class Patch_HudUpdate
	{
		private static void Postfix()
		{
			//IL_000d: 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_0052: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				SkaldPanel.ApplyScale();
				if (Input.GetKeyDown(Plugin.PanelToggleKey.Value))
				{
					SkaldPanel.Toggle();
				}
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer != (Object)null)
				{
					if (Input.GetKeyDown(Plugin.TitleMenuKey.Value))
					{
						TitleMenu.Toggle(localPlayer);
					}
					if (Input.GetKeyDown(Plugin.StatsMenuKey.Value))
					{
						StatsPanel.Toggle(localPlayer);
					}
				}
				if (Input.GetKeyDown((KeyCode)27))
				{
					if (TitleMenu.IsVisible)
					{
						TitleMenu.Hide();
					}
					if (StatsPanel.IsVisible)
					{
						StatsPanel.Hide();
					}
				}
			}
			catch
			{
			}
		}
	}
	public static class SkaldPanel
	{
		private static GameObject _root;

		private static GameObject _panel;

		private static Text[] _lines;

		private static bool _visible = false;

		private const int MaxLines = 5;

		private static readonly Queue<string> _events = new Queue<string>();

		public static void AddEvent(string msg)
		{
			_events.Enqueue(msg);
			while (_events.Count > 5)
			{
				_events.Dequeue();
			}
			Refresh();
		}

		public static void Toggle()
		{
			_visible = !_visible;
			if ((Object)(object)_panel != (Object)null)
			{
				_panel.SetActive(_visible);
			}
		}

		public static void Build(Transform hudRoot)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: 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_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: 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_00b7: Expected O, but got Unknown
			//IL_00de: 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_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: 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_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Expected O, but got Unknown
			//IL_018f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01de: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_023b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0285: Unknown result type (might be due to invalid IL or missing references)
			//IL_028b: Expected O, but got Unknown
			//IL_02aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02df: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_root != (Object)null))
			{
				_root = new GameObject("SkaldPanel");
				_root.transform.SetParent(hudRoot, false);
				RectTransform val = _root.AddComponent<RectTransform>();
				Vector2 val2 = default(Vector2);
				((Vector2)(ref val2))..ctor(1f, 1f);
				val.anchorMax = val2;
				val.anchorMin = val2;
				val.pivot = new Vector2(1f, 1f);
				val.anchoredPosition = new Vector2(-210f, -180f);
				val.sizeDelta = new Vector2(340f, 200f);
				_panel = new GameObject("BG");
				_panel.transform.SetParent(_root.transform, false);
				RectTransform val3 = _panel.AddComponent<RectTransform>();
				val3.anchorMin = Vector2.zero;
				val3.anchorMax = Vector2.one;
				val2 = (val3.offsetMin = (val3.offsetMax = Vector2.zero));
				((Graphic)_panel.AddComponent<Image>()).color = new Color(0f, 0f, 0f, 0.55f);
				_lines = (Text[])(object)new Text[5];
				for (int i = 0; i < 5; i++)
				{
					GameObject val5 = new GameObject($"L{i}");
					val5.transform.SetParent(_panel.transform, false);
					RectTransform val6 = val5.AddComponent<RectTransform>();
					val6.anchorMin = new Vector2(0f, 1f);
					val6.anchorMax = new Vector2(1f, 1f);
					val6.pivot = new Vector2(0f, 1f);
					val6.anchoredPosition = new Vector2(6f, -6f - (float)i * 36f);
					val6.sizeDelta = new Vector2(-12f, 34f);
					Text val7 = val5.AddComponent<Text>();
					val7.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
					val7.fontSize = 13;
					((Graphic)val7).color = new Color(0.9f, 0.85f, 0.7f, 1f);
					val7.alignment = (TextAnchor)0;
					val7.horizontalOverflow = (HorizontalWrapMode)0;
					val7.verticalOverflow = (VerticalWrapMode)1;
					_lines[i] = val7;
				}
				GameObject val8 = new GameObject("DH");
				val8.transform.SetParent(_root.transform, false);
				RectTransform val9 = val8.AddComponent<RectTransform>();
				val9.anchorMin = Vector2.zero;
				val9.anchorMax = Vector2.one;
				val2 = (val9.offsetMin = (val9.offsetMax = Vector2.zero));
				((Graphic)val8.AddComponent<Image>()).color = Color.clear;
				val8.AddComponent<SkaldDragHandler>();
				_panel.SetActive(_visible);
				ApplyScale();
				Plugin.Log.LogInfo((object)"[SkaldSaga] Panel built.");
			}
		}

		public static void ApplyScale()
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_root == (Object)null))
			{
				float num = Mathf.Clamp(Plugin.HudScale.Value, 0.5f, 2f);
				_root.transform.localScale = new Vector3(num, num, 1f);
			}
		}

		private static void Refresh()
		{
			if (_lines == null)
			{
				return;
			}
			string[] array = _events.ToArray();
			for (int i = 0; i < 5; i++)
			{
				if (!((Object)(object)_lines[i] == (Object)null))
				{
					int num = array.Length - 1 - i;
					_lines[i].text = ((num >= 0) ? array[num] : "");
				}
			}
		}
	}
	public class SkaldDragHandler : MonoBehaviour, IDragHandler, IEventSystemHandler, IBeginDragHandler
	{
		private RectTransform _rt;

		private Vector2 _offset;

		private void Awake()
		{
			_rt = ((Component)((Component)this).transform.parent).GetComponent<RectTransform>();
		}

		public void OnBeginDrag(PointerEventData e)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			RectTransformUtility.ScreenPointToLocalPointInRectangle(_rt, e.position, e.pressEventCamera, ref _offset);
		}

		public void OnDrag(PointerEventData e)
		{
			//IL_0024: 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_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)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			Vector2 val = default(Vector2);
			if (!((Object)(object)_rt == (Object)null) && RectTransformUtility.ScreenPointToLocalPointInRectangle((RectTransform)/*isinst with value type is only supported in some contexts*/, e.position, e.pressEventCamera, ref val))
			{
				((Transform)_rt).localPosition = Vector2.op_Implicit(val - _offset);
			}
		}
	}
	public static class StatsPanel
	{
		private static GameObject _root;

		private static bool _visible;

		public static bool IsVisible => _visible;

		public static void Toggle(Player p)
		{
			if (!((Object)(object)_root == (Object)null))
			{
				_visible = !_visible;
				if (_visible)
				{
					Refresh(p);
				}
				_root.SetActive(_visible);
			}
		}

		public static void Hide()
		{
			_visible = false;
			if ((Object)(object)_root != (Object)null)
			{
				_root.SetActive(false);
			}
		}

		public static void Build(Transform hudRoot)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: 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_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: 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_00b3: Expected O, but got Unknown
			//IL_00d2: 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_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: 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_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: 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_0131: Expected O, but got Unknown
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_015f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0176: Unknown result type (might be due to invalid IL or missing references)
			//IL_018d: 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)
			//IL_01ef: Expected O, but got Unknown
			//IL_021c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0233: Unknown result type (might be due to invalid IL or missing references)
			//IL_024a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0261: Unknown result type (might be due to invalid IL or missing references)
			//IL_0287: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_root != (Object)null))
			{
				_root = new GameObject("SkaldStats");
				_root.transform.SetParent(hudRoot, false);
				RectTransform val = _root.AddComponent<RectTransform>();
				Vector2 val2 = default(Vector2);
				((Vector2)(ref val2))..ctor(0.5f, 0.5f);
				val.anchorMax = val2;
				val.anchorMin = val2;
				val.pivot = new Vector2(0.5f, 0.5f);
				val.anchoredPosition = new Vector2(220f, 0f);
				val.sizeDelta = new Vector2(340f, 480f);
				GameObject val3 = new GameObject("BG");
				val3.transform.SetParent(_root.transform, false);
				RectTransform val4 = val3.AddComponent<RectTransform>();
				val4.anchorMin = Vector2.zero;
				val4.anchorMax = Vector2.one;
				val2 = (val4.offsetMin = (val4.offsetMax = Vector2.zero));
				((Graphic)val3.AddComponent<Image>()).color = new Color(0.05f, 0.05f, 0.05f, 0.92f);
				GameObject val6 = new GameObject("Content");
				val6.transform.SetParent(_root.transform, false);
				RectTransform val7 = val6.AddComponent<RectTransform>();
				val7.anchorMin = Vector2.zero;
				val7.anchorMax = Vector2.one;
				val7.offsetMin = new Vector2(10f, 10f);
				val7.offsetMax = new Vector2(-10f, -10f);
				_root.AddComponent<SkaldRef>().ContentRoot = val6.transform;
				Canvas val8 = _root.AddComponent<Canvas>();
				val8.renderMode = (RenderMode)0;
				val8.overrideSorting = true;
				val8.sortingOrder = 200;
				_root.AddComponent<GraphicRaycaster>();
				GameObject val9 = new GameObject("DH");
				val9.transform.SetParent(_root.transform, false);
				RectTransform val10 = val9.AddComponent<RectTransform>();
				val10.anchorMin = new Vector2(0f, 1f);
				val10.anchorMax = new Vector2(1f, 1f);
				val10.pivot = new Vector2(0.5f, 1f);
				val10.sizeDelta = new Vector2(0f, 40f);
				((Graphic)val9.AddComponent<Image>()).color = new Color(0f, 0f, 0f, 0.01f);
				val9.AddComponent<SkaldDragHandler>();
				_root.SetActive(false);
			}
		}

		public static void Refresh(Player player)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			//IL_0279: Unknown result type (might be due to invalid IL or missing references)
			//IL_0280: Expected O, but got Unknown
			//IL_02a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0316: Unknown result type (might be due to invalid IL or missing references)
			//IL_03fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c6: Unknown result type (might be due to invalid IL or missing references)
			GameObject root = _root;
			SkaldRef skaldRef = ((root != null) ? root.GetComponent<SkaldRef>() : null);
			if ((Object)(object)skaldRef?.ContentRoot == (Object)null)
			{
				return;
			}
			foreach (Transform item in skaldRef.ContentRoot)
			{
				Transform val = item;
				Object.Destroy((Object)(object)((Component)val).gameObject);
			}
			List<string> list = new List<string>
			{
				"--- Skald Stats ---",
				"",
				string.Format("Total Kills: {0}", SkaldData.GetInt("kills")),
				string.Format("Deaths:      {0}", SkaldData.GetInt("deaths")),
				"",
				"--- Creature Kills ---"
			};
			List<(string, int)> list2 = new List<(string, int)>();
			foreach (string key in TitleSystem.CreatureTitles.Keys)
			{
				int @int = SkaldData.GetInt("c_" + key);
				if (@int > 0)
				{
					list2.Add((key, @int));
				}
			}
			list2.Sort(((string name, int count) a, (string name, int count) b) => b.count.CompareTo(a.count));
			foreach (var (arg, num) in list2)
			{
				list.Add($"  {arg}: {num}");
			}
			list.Add("");
			list.Add("--- Titles ---");
			string activeTitle = TitleSystem.GetActiveTitle();
			foreach (string earnedTitle in TitleSystem.GetEarnedTitles())
			{
				list.Add((earnedTitle == activeTitle) ? ("  * " + earnedTitle) : ("  " + earnedTitle));
			}
			float num2 = -8f;
			foreach (string item2 in list)
			{
				GameObject val2 = new GameObject("L");
				val2.transform.SetParent(skaldRef.ContentRoot, false);
				RectTransform val3 = val2.AddComponent<RectTransform>();
				val3.anchorMin = new Vector2(0f, 1f);
				val3.anchorMax = new Vector2(1f, 1f);
				val3.pivot = new Vector2(0.5f, 1f);
				val3.anchoredPosition = new Vector2(0f, num2);
				bool flag = string.IsNullOrEmpty(item2);
				val3.sizeDelta = new Vector2(0f, flag ? 8f : 22f);
				num2 -= (flag ? 8f : 24f);
				if (!flag)
				{
					Text val4 = val2.AddComponent<Text>();
					val4.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
					val4.fontSize = (item2.StartsWith("---") ? 14 : 13);
					val4.fontStyle = (FontStyle)(item2.StartsWith("---") ? 1 : 0);
					((Graphic)val4).color = (item2.StartsWith("  *") ? new Color(0.95f, 0.85f, 0.4f, 1f) : (item2.StartsWith("---") ? new Color(0.9f, 0.8f, 0.4f, 1f) : new Color(0.85f, 0.85f, 0.85f, 1f)));
					val4.alignment = (TextAnchor)3;
					val4.text = item2;
				}
			}
		}
	}
	public static class TitleMenu
	{
		private static GameObject _root;

		private static bool _visible = false;

		private static List<GameObject> _buttons = new List<GameObject>();

		public static bool IsVisible => _visible;

		public static void Toggle(Player p)
		{
			if (!((Object)(object)_root == (Object)null))
			{
				_visible = !_visible;
				if (_visible)
				{
					Refresh(p);
				}
				_root.SetActive(_visible);
			}
		}

		public static void Hide()
		{
			_visible = false;
			if ((Object)(object)_root != (Object)null)
			{
				_root.SetActive(false);
			}
		}

		public static void Build(Transform hudRoot)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: 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_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Expected O, but got Unknown
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: 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_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Expected O, but got Unknown
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_015d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0216: Unknown result type (might be due to invalid IL or missing references)
			//IL_021d: Expected O, but got Unknown
			//IL_0240: Unknown result type (might be due to invalid IL or missing references)
			//IL_024d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0264: Unknown result type (might be due to invalid IL or missing references)
			//IL_027b: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02de: Expected O, but got Unknown
			//IL_030b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0322: Unknown result type (might be due to invalid IL or missing references)
			//IL_0339: Unknown result type (might be due to invalid IL or missing references)
			//IL_0350: Unknown result type (might be due to invalid IL or missing references)
			//IL_0376: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_root != (Object)null))
			{
				_root = new GameObject("SkaldTitleMenu");
				_root.transform.SetParent(hudRoot, false);
				RectTransform val = _root.AddComponent<RectTransform>();
				Vector2 val2 = default(Vector2);
				((Vector2)(ref val2))..ctor(0.5f, 0.5f);
				val.anchorMax = val2;
				val.anchorMin = val2;
				val.pivot = new Vector2(0.5f, 0.5f);
				val.sizeDelta = new Vector2(380f, 480f);
				GameObject val3 = new GameObject("BG");
				val3.transform.SetParent(_root.transform, false);
				RectTransform val4 = val3.AddComponent<RectTransform>();
				val4.anchorMin = Vector2.zero;
				val4.anchorMax = Vector2.one;
				val2 = (val4.offsetMin = (val4.offsetMax = Vector2.zero));
				((Graphic)val3.AddComponent<Image>()).color = new Color(0.05f, 0.05f, 0.05f, 0.92f);
				GameObject val6 = new GameObject("Header");
				val6.transform.SetParent(_root.transform, false);
				RectTransform val7 = val6.AddComponent<RectTransform>();
				val7.anchorMin = new Vector2(0f, 1f);
				val7.anchorMax = new Vector2(1f, 1f);
				val7.pivot = new Vector2(0.5f, 1f);
				val7.anchoredPosition = new Vector2(0f, -10f);
				val7.sizeDelta = new Vector2(0f, 40f);
				Text val8 = val6.AddComponent<Text>();
				val8.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
				val8.fontSize = 18;
				val8.fontStyle = (FontStyle)1;
				((Graphic)val8).color = new Color(0.9f, 0.8f, 0.4f, 1f);
				val8.alignment = (TextAnchor)4;
				val8.text = "— Choose Your Title —";
				GameObject val9 = new GameObject("Content");
				val9.transform.SetParent(_root.transform, false);
				RectTransform val10 = val9.AddComponent<RectTransform>();
				val10.anchorMin = Vector2.zero;
				val10.anchorMax = Vector2.one;
				val10.offsetMin = new Vector2(10f, 10f);
				val10.offsetMax = new Vector2(-10f, -60f);
				_root.AddComponent<SkaldRef>().ContentRoot = val9.transform;
				Canvas val11 = _root.AddComponent<Canvas>();
				val11.renderMode = (RenderMode)0;
				val11.overrideSorting = true;
				val11.sortingOrder = 200;
				_root.AddComponent<GraphicRaycaster>();
				GameObject val12 = new GameObject("DH");
				val12.transform.SetParent(_root.transform, false);
				RectTransform val13 = val12.AddComponent<RectTransform>();
				val13.anchorMin = new Vector2(0f, 1f);
				val13.anchorMax = new Vector2(1f, 1f);
				val13.pivot = new Vector2(0.5f, 1f);
				val13.sizeDelta = new Vector2(0f, 40f);
				((Graphic)val12.AddComponent<Image>()).color = new Color(0f, 0f, 0f, 0.01f);
				val12.AddComponent<SkaldDragHandler>();
				_root.SetActive(false);
			}
		}

		public static void Refresh(Player player)
		{
			GameObject root = _root;
			SkaldRef skaldRef = ((root != null) ? root.GetComponent<SkaldRef>() : null);
			if ((Object)(object)skaldRef?.ContentRoot == (Object)null)
			{
				return;
			}
			foreach (GameObject button in _buttons)
			{
				if ((Object)(object)button != (Object)null)
				{
					Object.Destroy((Object)(object)button);
				}
			}
			_buttons.Clear();
			List<string> earnedTitles = TitleSystem.GetEarnedTitles();
			string activeTitle = TitleSystem.GetActiveTitle();
			List<string> list = ((earnedTitles.Count == 0) ? new List<string> { "(No titles earned yet)" } : new List<string> { "(No Title)" });
			if (earnedTitles.Count > 0)
			{
				list.AddRange(earnedTitles);
			}
			float yPos = -8f;
			foreach (string item in list)
			{
				bool isActive = (item == "(No Title)" && activeTitle == "") || item == activeTitle;
				bool flag = item != "(No titles earned yet)";
				AddButton(skaldRef.ContentRoot, item, isActive, flag ? player : null, ref yPos);
			}
			Canvas.ForceUpdateCanvases();
		}

		private static void AddButton(Transform content, string title, bool isActive, Player player, ref float yPos)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Expected O, but got Unknown
			//IL_0032: 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_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: 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_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Expected O, but got Unknown
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: 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_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0216: Unknown result type (might be due to invalid IL or missing references)
			//IL_0220: Expected O, but got Unknown
			GameObject val = new GameObject("B_" + title);
			val.transform.SetParent(content, false);
			RectTransform val2 = val.AddComponent<RectTransform>();
			val2.anchorMin = new Vector2(0f, 1f);
			val2.anchorMax = new Vector2(1f, 1f);
			val2.pivot = new Vector2(0.5f, 1f);
			val2.anchoredPosition = new Vector2(0f, yPos);
			val2.sizeDelta = new Vector2(0f, 40f);
			yPos -= 46f;
			((Graphic)val.AddComponent<Image>()).color = (isActive ? new Color(0.3f, 0.25f, 0.05f, 0.9f) : new Color(0.15f, 0.15f, 0.15f, 0.8f));
			GameObject val3 = new GameObject("L");
			val3.transform.SetParent(val.transform, false);
			RectTransform val4 = val3.AddComponent<RectTransform>();
			val4.anchorMin = Vector2.zero;
			val4.anchorMax = Vector2.one;
			val4.offsetMin = new Vector2(12f, 0f);
			val4.offsetMax = Vector2.zero;
			Text val5 = val3.AddComponent<Text>();
			val5.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
			val5.fontSize = 15;
			((Graphic)val5).color = (isActive ? new Color(0.95f, 0.85f, 0.4f, 1f) : new Color(0.85f, 0.85f, 0.85f, 1f));
			val5.alignment = (TextAnchor)3;
			val5.text = (isActive ? ("* " + title) : title);
			if ((Object)(object)player != (Object)null)
			{
				Button val6 = val.AddComponent<Button>();
				string ct = ((title == "(No Title)") ? "" : title);
				Player cp = player;
				((UnityEvent)val6.onClick).AddListener((UnityAction)delegate
				{
					TitleSystem.SetActiveTitle(ct);
					Refresh(cp);
				});
			}
			_buttons.Add(val);
		}
	}
	public static class Chronicle
	{
		private static StreamWriter _writer;

		private static readonly object _lock = new object();

		private static string _logPath;

		public static void Init()
		{
			try
			{
				string text = Path.Combine(Paths.PluginPath, "SkaldSaga");
				Directory.CreateDirectory(text);
				string text2 = DateTime.UtcNow.ToString("yyyy-MM-dd");
				_logPath = Path.Combine(text, "SkaldSaga_Chronicle_" + text2 + ".log");
				_writer = new StreamWriter(_logPath, append: true)
				{
					AutoFlush = true
				};
				if (new FileInfo(_logPath).Length == 0)
				{
					_writer.WriteLine("TIMESTAMP_UTC\t\t\tDAY\tONLINE\tEVENT\t\t\tPLAYER\t\t\tMESSAGE\t\t\t\t\tDETAIL");
				}
				Plugin.Log.LogInfo((object)("[SkaldSaga] Chronicle: " + _logPath));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[SkaldSaga] Chronicle init failed: " + ex.Message));
			}
		}

		public static void Write(string player, string eventType, string message, Dictionary<string, string> data)
		{
			if (Plugin.EnableChronicleLog != null && !Plugin.EnableChronicleLog.Value)
			{
				return;
			}
			lock (_lock)
			{
				try
				{
					string value = DateTime.UtcNow.ToString("yyyy-MM-dd");
					if (_logPath != null && !_logPath.Contains(value))
					{
						_writer?.Close();
						Init();
					}
					string text = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss");
					string text2 = ((data != null && data.ContainsKey("day")) ? data["day"] : "-");
					string text3 = ((data != null && data.ContainsKey("online")) ? data["online"] : "-");
					string text4 = ((data != null && data.ContainsKey("detail")) ? data["detail"] : "");
					_writer?.WriteLine($"{text}\t{text2}\t{text3}\t{eventType,-16}\t{player,-20}\t{message,-40}\t{text4}");
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("[SkaldSaga] Chronicle write failed: " + ex.Message));
				}
			}
		}

		public static void Close()
		{
			lock (_lock)
			{
				try
				{
					_writer?.Close();
				}
				catch
				{
				}
			}
		}
	}
	public class SkaldRef : MonoBehaviour
	{
		public Transform ContentRoot;
	}
}