Decompiled source of Chronicles of Northarun v0.1.1

Chronicles.dll

Decompiled an hour ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn.Configs;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.Networking;
using UnityEngine.Rendering;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace Chronicles;

public enum Cat
{
	All,
	Battles,
	Fallen,
	Explorers,
	Gods,
	Guilds,
	Villages,
	Skald
}
public class Entry
{
	public long Id;

	public long Ticks;

	public int Day;

	public string Kind = "";

	public string[] A = new string[0];

	public Cat Category
	{
		get
		{
			if (Kind == "api")
			{
				foreach (Cat value in Enum.GetValues(typeof(Cat)))
				{
					if (value != Cat.All && value.ToString().ToLowerInvariant() == Arg(2))
					{
						return value;
					}
				}
			}
			return CategoryOf(Kind);
		}
	}

	public string Arg(int i)
	{
		if (i < 0 || i >= A.Length)
		{
			return "";
		}
		return A[i] ?? "";
	}

	public static Cat CategoryOf(string kind)
	{
		switch (kind)
		{
		case "boss":
		case "raid":
			return Cat.Battles;
		case "death":
			return Cat.Fallen;
		case "biome":
		case "newcomer":
			return Cat.Explorers;
		case "god_chosen":
		case "god_legend":
		case "blot_end":
		case "blot_failed":
			return Cat.Gods;
		case "guild_found":
		case "guild_level":
		case "guild_ach":
		case "guild_banner":
		case "guild_disband":
			return Cat.Guilds;
		case "vv_saga":
		case "vv_tales":
		case "vv_tier":
			return Cat.Villages;
		default:
			return Cat.Skald;
		}
	}

	public void Write(ZPackage p)
	{
		p.Write(Id);
		p.Write(Ticks);
		p.Write(Day);
		p.Write(Kind ?? "");
		p.Write(A.Length);
		string[] a = A;
		foreach (string text in a)
		{
			p.Write(text ?? "");
		}
	}

	public static Entry Read(ZPackage p)
	{
		Entry entry = new Entry();
		entry.Id = p.ReadLong();
		entry.Ticks = p.ReadLong();
		entry.Day = p.ReadInt();
		entry.Kind = p.ReadString();
		int num = Math.Min(p.ReadInt(), 32);
		entry.A = new string[num];
		for (int i = 0; i < num; i++)
		{
			entry.A[i] = p.ReadString();
		}
		return entry;
	}
}
public class Hero
{
	public long Pid;

	public string Name = "";

	public int Bosses;

	public int Deaths;

	public int Firsts;

	public long Joined;

	public long LastDeathVerse;

	public void Write(ZPackage p)
	{
		p.Write(Pid);
		p.Write(Name ?? "");
		p.Write(Bosses);
		p.Write(Deaths);
		p.Write(Firsts);
		p.Write(Joined);
		p.Write(LastDeathVerse);
	}

	public static Hero Read(ZPackage p)
	{
		Hero hero = new Hero();
		hero.Pid = p.ReadLong();
		hero.Name = p.ReadString();
		hero.Bosses = p.ReadInt();
		hero.Deaths = p.ReadInt();
		hero.Firsts = p.ReadInt();
		hero.Joined = p.ReadLong();
		hero.LastDeathVerse = p.ReadLong();
		return hero;
	}
}
public class SagaStats
{
	public int Day;

	public int Verses;

	public int Heroes;

	public int BossKills;

	public long Since;

	public readonly List<KeyValuePair<string, int>> TopBosses = new List<KeyValuePair<string, int>>();

	public readonly List<KeyValuePair<string, int>> TopDeaths = new List<KeyValuePair<string, int>>();

	public readonly List<KeyValuePair<string, int>> TopFirsts = new List<KeyValuePair<string, int>>();

	public readonly List<KeyValuePair<string, int>> Bosses = new List<KeyValuePair<string, int>>();

	public void Write(ZPackage p)
	{
		p.Write(Day);
		p.Write(Verses);
		p.Write(Heroes);
		p.Write(BossKills);
		p.Write(Since);
		List<KeyValuePair<string, int>>[] array = new List<KeyValuePair<string, int>>[4] { TopBosses, TopDeaths, TopFirsts, Bosses };
		foreach (List<KeyValuePair<string, int>> list in array)
		{
			p.Write(list.Count);
			foreach (KeyValuePair<string, int> item in list)
			{
				p.Write(item.Key ?? "");
				p.Write(item.Value);
			}
		}
	}

	public static SagaStats Read(ZPackage p)
	{
		SagaStats sagaStats = new SagaStats();
		sagaStats.Day = p.ReadInt();
		sagaStats.Verses = p.ReadInt();
		sagaStats.Heroes = p.ReadInt();
		sagaStats.BossKills = p.ReadInt();
		sagaStats.Since = p.ReadLong();
		List<KeyValuePair<string, int>>[] array = new List<KeyValuePair<string, int>>[4] { sagaStats.TopBosses, sagaStats.TopDeaths, sagaStats.TopFirsts, sagaStats.Bosses };
		foreach (List<KeyValuePair<string, int>> list in array)
		{
			int num = Math.Min(p.ReadInt(), 64);
			for (int j = 0; j < num; j++)
			{
				list.Add(new KeyValuePair<string, int>(p.ReadString(), p.ReadInt()));
			}
		}
		return sagaStats;
	}
}
internal static class Hooks
{
	private static readonly FieldInfo LastHit = AccessTools.Field(typeof(Character), "m_lastHit");

	private static readonly FieldInfo NView = AccessTools.Field(typeof(Character), "m_nview");

	private static readonly FieldInfo KnownBiome = AccessTools.Field(typeof(Player), "m_knownBiome");

	[HarmonyPatch(typeof(Player), "OnSpawned")]
	[HarmonyPostfix]
	private static void Spawned(Player __instance, bool spawnValkyrie)
	{
		if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer)
		{
			return;
		}
		try
		{
			Rpc.ToServer("CH_Hello", spawnValkyrie);
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Saga hello: " + ex.Message));
		}
	}

	[HarmonyPatch(typeof(Character), "OnDeath")]
	[HarmonyPrefix]
	private static void CharacterDeath(Character __instance)
	{
		//IL_0062: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: Expected O, but got Unknown
		//IL_0095: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			if (!((Object)(object)__instance == (Object)null) && __instance.m_boss && !__instance.IsPlayer() && !__instance.IsTamed())
			{
				ZNetView val = (ZNetView)((NView != null) ? /*isinst with value type is only supported in some contexts*/: null);
				if (!((Object)(object)val == (Object)null) && val.IsValid() && val.IsOwner())
				{
					ZPackage val2 = new ZPackage();
					val2.Write(Utils.GetPrefabName(((Component)__instance).gameObject));
					val2.Write(__instance.m_name ?? "");
					val2.Write(((Component)__instance).transform.position);
					val2.Write(__instance.GetLevel());
					Rpc.ToServer("CH_Boss", val2);
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Saga boss death: " + ex.Message));
		}
	}

	[HarmonyPatch(typeof(Player), "OnDeath")]
	[HarmonyPrefix]
	private static void PlayerDeath(Player __instance)
	{
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c7: Expected O, but got Unknown
		//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f5: Expected I4, but got Unknown
		if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer)
		{
			return;
		}
		try
		{
			HitData val = (HitData)((LastHit != null) ? /*isinst with value type is only supported in some contexts*/: null);
			int num = ((val != null) ? ((int)val.m_hitType) : 0);
			string text = "";
			string text2 = "";
			string text3 = "";
			Character val2 = ((val != null) ? val.GetAttacker() : null);
			if ((Object)(object)val2 != (Object)null && (Object)(object)val2 != (Object)(object)__instance)
			{
				Player val3 = (Player)(object)((val2 is Player) ? val2 : null);
				if ((Object)(object)val3 != (Object)null)
				{
					text3 = val3.GetPlayerName();
				}
				else
				{
					text = Utils.GetPrefabName(((Component)val2).gameObject);
					text2 = val2.m_name ?? "";
				}
			}
			else if ((Object)(object)val2 == (Object)(object)__instance && num < 3)
			{
				num = 14;
			}
			ZPackage val4 = new ZPackage();
			val4.Write(num);
			val4.Write(text);
			val4.Write(text2);
			val4.Write(text3);
			val4.Write((int)__instance.GetCurrentBiome());
			Rpc.ToServer("CH_Death", val4);
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Saga death: " + ex.Message));
		}
	}

	[HarmonyPrefix]
	[HarmonyPatch(typeof(Player), "AddKnownBiome")]
	private static void BiomeBefore(Player __instance, BiomeSector biome, out bool __state)
	{
		__state = false;
		try
		{
			if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && biome != null && !(KnownBiome == null))
			{
				HashSet<string> hashSet = KnownBiome.GetValue(__instance) as HashSet<string>;
				__state = hashSet != null && !hashSet.Contains(biome.GetName(false));
			}
		}
		catch
		{
		}
	}

	[HarmonyPostfix]
	[HarmonyPatch(typeof(Player), "AddKnownBiome")]
	private static void BiomeAfter(BiomeSector biome, bool __state)
	{
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//IL_000e: Expected I4, but got Unknown
		if (!__state || biome == null)
		{
			return;
		}
		try
		{
			int num = (int)biome.Biome;
			if (num != 0)
			{
				Rpc.ToServer("CH_Biome", num);
			}
		}
		catch
		{
		}
	}

	[HarmonyPostfix]
	[HarmonyPatch(typeof(RandEventSystem), "SetRandomEvent")]
	private static void RaidStarted(RandomEvent ev, Vector3 pos)
	{
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		if (ev == null || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
		{
			return;
		}
		try
		{
			SagaServer.OnRaid(ev, pos);
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Saga raid: " + ex.Message));
		}
	}
}
internal static class Integrations
{
	public static bool Gods;

	public static bool Guilds;

	public static bool Villages;

	private static FieldInfo _gName;

	private static FieldInfo _gTag;

	private static bool Server
	{
		get
		{
			if ((Object)(object)ZNet.instance != (Object)null)
			{
				return ZNet.instance.IsServer();
			}
			return false;
		}
	}

	public static void Apply(Harmony h)
	{
		Gods = Patch(h, "Gods.GodsServer", "AnnounceAll", "GodsAnnounce", postfix: false);
		Villages = Patch(h, "VikingsVillage.VillageServer", "AnnounceAll", "VillageAnnounce", postfix: false);
		Guilds = Patch(h, "Guilds.Guild", "AddLog", "GuildLog", postfix: true);
		if (Guilds)
		{
			Patch(h, "Guilds.GuildServer", "DisbandGuild", "GuildDisband", postfix: false);
		}
		Plugin.Log.LogInfo((object)("Saga sources: Gods " + (Gods ? "yes" : "no") + ", Guilds " + (Guilds ? "yes" : "no") + ", Vikings Village " + (Villages ? "yes" : "no") + "."));
	}

	private static bool Patch(Harmony h, string type, string method, string patch, bool postfix)
	{
		//IL_0083: Unknown result type (might be due to invalid IL or missing references)
		//IL_0089: Expected O, but got Unknown
		try
		{
			Type type2 = AccessTools.TypeByName(type);
			if (type2 == null)
			{
				return false;
			}
			MethodInfo methodInfo = AccessTools.Method(type2, method, (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				Plugin.Log.LogWarning((object)("Saga: " + type + "." + method + " not found — that mod's events are not written."));
				return false;
			}
			HarmonyMethod val = new HarmonyMethod(typeof(Integrations).GetMethod(patch, BindingFlags.Static | BindingFlags.NonPublic));
			if (postfix)
			{
				h.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			else
			{
				h.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			return true;
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Saga: could not hook " + type + "." + method + ": " + ex.Message));
			return false;
		}
	}

	private static string S(object[] args, int i)
	{
		if (args == null || i >= args.Length || args[i] == null)
		{
			return "";
		}
		return Convert.ToString(args[i], CultureInfo.InvariantCulture);
	}

	private static string After(string s, string prefix)
	{
		if (!s.StartsWith(prefix))
		{
			return s;
		}
		return s.Substring(prefix.Length);
	}

	private static void GodsAnnounce(string key, object[] args)
	{
		if (!Server || !Plugin.RecordGods.Value)
		{
			return;
		}
		try
		{
			switch (key)
			{
			case "announce_chosen":
				SagaServer.Record("god_chosen", S(args, 0), After(S(args, 1), "@godg_"));
				break;
			case "announce_legend":
				SagaServer.Record("god_legend", S(args, 0), After(S(args, 1), "@godg_"));
				break;
			case "blot_end":
				SagaServer.Record("blot_end", After(S(args, 0), "@godg_"), S(args, 1));
				break;
			case "blot_failed":
				SagaServer.Record("blot_failed", After(S(args, 0), "@godg_"), S(args, 1), S(args, 2));
				break;
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Saga (Gods " + key + "): " + ex.Message));
		}
	}

	private static void VillageAnnounce(string key, object[] args)
	{
		if (!Server || !Plugin.RecordVillages.Value)
		{
			return;
		}
		try
		{
			switch (key)
			{
			case "announce_saga":
				SagaServer.Record("vv_saga", S(args, 0), "~vvq:" + After(S(args, 1), "#q:"));
				break;
			case "announce_all_tales":
				SagaServer.Record("vv_tales", S(args, 0));
				break;
			case "announce_tier":
				SagaServer.Record("vv_tier", S(args, 0), "~vv:" + After(S(args, 1), "@"));
				break;
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Saga (Vikings Village " + key + "): " + ex.Message));
		}
	}

	private static void GuildInfo(object g, out string tag, out string name)
	{
		tag = (name = "");
		if (g != null)
		{
			if (_gName == null)
			{
				_gName = AccessTools.Field(g.GetType(), "Name");
			}
			if (_gTag == null)
			{
				_gTag = AccessTools.Field(g.GetType(), "Tag");
			}
			if (_gName != null)
			{
				name = (_gName.GetValue(g) as string) ?? "";
			}
			if (_gTag != null)
			{
				tag = (_gTag.GetValue(g) as string) ?? "";
			}
		}
	}

	private static void GuildLog(object __instance, object[] __args)
	{
		if (!Server || !Plugin.RecordGuilds.Value || __args == null || __args.Length < 6)
		{
			return;
		}
		try
		{
			int num = Convert.ToInt32(__args[0]);
			GuildInfo(__instance, out var tag, out var name);
			string text = S(__args, 1);
			string text2 = S(__args, 5);
			long num2 = Convert.ToInt64(__args[4]);
			switch (num)
			{
			case 0:
				SagaServer.Record("guild_found", text, tag, (name.Length > 0) ? name : text2);
				break;
			case 18:
				SagaServer.Record("guild_level", tag, name, num2.ToString());
				break;
			case 20:
				if (num2 >= 3)
				{
					SagaServer.Record("guild_ach", tag, name, "~guilds:ach_" + text2);
				}
				break;
			case 22:
				if (SagaServer.BannerAllowed(tag))
				{
					SagaServer.Record("guild_banner", text, tag, name);
				}
				break;
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Saga (Guilds log): " + ex.Message));
		}
	}

	private static void GuildDisband(object[] __args)
	{
		if (!Server || !Plugin.RecordGuilds.Value || __args == null || __args.Length < 1)
		{
			return;
		}
		try
		{
			GuildInfo(__args[0], out var tag, out var name);
			SagaServer.Record("guild_disband", tag, name);
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Saga (Guilds disband): " + ex.Message));
		}
	}
}
internal static class Refs
{
	private static MethodInfo _guildsT;

	private static MethodInfo _vvT;

	private static MethodInfo _godsT;

	private static MethodInfo _vvQuest;

	private static MethodInfo _vvTitle;

	private static FieldInfo _vvCurrent;

	private static bool _looked;

	public static string Text(string arg)
	{
		if (string.IsNullOrEmpty(arg))
		{
			return "";
		}
		try
		{
			if (arg[0] == '$')
			{
				return (Localization.instance != null) ? Localization.instance.Localize(arg) : arg.Substring(1);
			}
			if (arg[0] != '~')
			{
				return arg;
			}
			Look();
			int num = arg.IndexOf(':');
			if (num < 0)
			{
				return arg;
			}
			string text = arg.Substring(1, num - 1);
			string text2 = arg.Substring(num + 1);
			return text switch
			{
				"guilds" => Call(_guildsT, text2), 
				"vv" => Call(_vvT, text2), 
				"gods" => Call(_godsT, text2), 
				"vvq" => Quest(text2), 
				_ => text2, 
			};
		}
		catch
		{
			return arg;
		}
	}

	private static void Look()
	{
		if (!_looked)
		{
			_looked = true;
			_guildsT = LocT("Guilds.Loc");
			_vvT = LocT("VikingsVillage.Loc");
			_godsT = LocT("Gods.Loc");
			Type type = AccessTools.TypeByName("VikingsVillage.Content");
			if (type != null)
			{
				_vvCurrent = AccessTools.Field(type, "Current");
				_vvQuest = AccessTools.Method(type, "Quest", new Type[1] { typeof(string) }, (Type[])null);
			}
			Type type2 = AccessTools.TypeByName("VikingsVillage.QuestText");
			if (type2 != null)
			{
				_vvTitle = AccessTools.Method(type2, "Title", (Type[])null, (Type[])null);
			}
		}
	}

	private static MethodInfo LocT(string type)
	{
		Type type2 = AccessTools.TypeByName(type);
		if (!(type2 != null))
		{
			return null;
		}
		return AccessTools.Method(type2, "T", new Type[1] { typeof(string) }, (Type[])null);
	}

	private static string Call(MethodInfo m, string key)
	{
		if (m == null)
		{
			return Pretty(key);
		}
		string text = m.Invoke(null, new object[1] { key }) as string;
		if (!string.IsNullOrEmpty(text) && !(text == key))
		{
			return text;
		}
		return Pretty(key);
	}

	private static string Quest(string id)
	{
		if (_vvCurrent == null || _vvQuest == null)
		{
			return Pretty(id);
		}
		object value = _vvCurrent.GetValue(null);
		object obj = ((value != null) ? _vvQuest.Invoke(value, new object[1] { id }) : null);
		if (obj == null)
		{
			return Pretty(id);
		}
		string text = null;
		try
		{
			if (_vvTitle != null)
			{
				text = _vvTitle.Invoke(null, new object[1] { obj }) as string;
			}
		}
		catch
		{
		}
		if (string.IsNullOrEmpty(text))
		{
			FieldInfo fieldInfo = AccessTools.Field(obj.GetType(), "Title");
			text = ((fieldInfo != null) ? (fieldInfo.GetValue(obj) as string) : null);
		}
		if (!string.IsNullOrEmpty(text))
		{
			return text;
		}
		return Pretty(id);
	}

	private static string Pretty(string key)
	{
		int num = key.IndexOf('_');
		string text = ((num >= 0 && num < 6) ? key.Substring(num + 1) : key).Replace('_', ' ');
		if (text.Length <= 0)
		{
			return text;
		}
		return char.ToUpperInvariant(text[0]) + text.Substring(1);
	}
}
public static class ChronicleApi
{
	public static bool IsReady()
	{
		return SagaServer.Ready;
	}

	public static long Write(string category, string textEn, string textPl)
	{
		if (string.IsNullOrEmpty(textEn))
		{
			return 0L;
		}
		return SagaServer.Record("api", textEn, string.IsNullOrEmpty(textPl) ? textEn : textPl, (category ?? "skald").ToLowerInvariant())?.Id ?? 0;
	}
}
internal static class Loc
{
	private static readonly Dictionary<string, string> En = new Dictionary<string, string>
	{
		{ "title", "Chronicles of Northarun" },
		{ "subtitle", "Day {0} · {1} verses · the saga began {2}" },
		{ "subtitle_empty", "Day {0} · the saga has not begun yet" },
		{ "cat_0", "All" },
		{ "cat_1", "Battles" },
		{ "cat_2", "Fallen" },
		{ "cat_3", "Explorers" },
		{ "cat_4", "Gods" },
		{ "cat_5", "Guilds" },
		{ "cat_6", "Villages" },
		{ "cat_7", "Skald" },
		{ "day", "Day {0}" },
		{ "older", "Older verses…" },
		{ "loading", "The skald is reading the stone…" },
		{ "empty", "Nothing has been carved here yet." },
		{ "heroes_title", "Heroes of the saga" },
		{ "h_bosses", "Boss slayers" },
		{ "h_deaths", "Regulars of Valhalla" },
		{ "h_firsts", "Pathfinders" },
		{ "h_bosslist", "Bosses felled" },
		{ "h_stats", "{0} heroes · {1} boss kills" },
		{ "h_none", "nobody yet" },
		{ "hint", "The server carves here every deed of every Viking of Northarun. Admins: saga add <text> · saga del <id> · saga last" },
		{ "hover_name", "Runestone of the Chronicles" },
		{ "hover_read", "Read the saga" },
		{ "hover_latest", "Latest verse:" },
		{ "piece_desc", "A tall runestone where the skalds carve the saga of Northarun: bosses slain, lands discovered, deaths absurd and glorious. Interact to read." },
		{ "msg_new", "Saga: {0}" },
		{ "and", "and" },
		{ "god_odin", "Odin" },
		{ "god_thor", "Thor" },
		{ "god_freya", "Freyja" },
		{ "god_njord", "Njord" },
		{ "god_loki", "Loki" },
		{ "godg_odin", "Odin" },
		{ "godg_thor", "Thor" },
		{ "godg_freya", "Freyja" },
		{ "godg_njord", "Njord" },
		{ "godg_loki", "Loki" },
		{ "bin_0", "somewhere in the wilds" },
		{ "bin_1", "in the Meadows" },
		{ "bin_8", "in the Black Forest" },
		{ "bin_2", "in the Swamp" },
		{ "bin_4", "in the Mountains" },
		{ "bin_16", "on the Plains" },
		{ "bin_256", "on the open sea" },
		{ "bin_512", "in the Mistlands" },
		{ "bin_32", "in the Ashlands" },
		{ "bin_64", "in the Deep North" },
		{ "bnom_1", "the Meadows" },
		{ "bnom_8", "the Black Forest" },
		{ "bnom_2", "the Swamp" },
		{ "bnom_4", "the Mountains" },
		{ "bnom_16", "the Plains" },
		{ "bnom_256", "the Ocean" },
		{ "bnom_512", "the Mistlands" },
		{ "bnom_32", "the Ashlands" },
		{ "bnom_64", "the Deep North" },
		{ "v_boss_first", "For the first time since the world was young, {0} has fallen. The deed belongs to {1} — let the skalds never forget it." },
		{ "v_boss_0", "{0} fell once more before {1}. The saga now counts {2} such falls." },
		{ "v_boss_1", "{1} challenged {0}, and the beast did not rise again. It was its {3} death on Northarun." },
		{ "v_boss_2", "Steel sang and {0} was silenced. The glory goes to {1}." },
		{ "v_boss_solo_0", "With no shield-brothers and no fear, {1} faced {0} — and walked away. It was the {3} fall of the beast." },
		{ "v_boss_solo_1", "{1} needed no war band: {0} fell to a single Viking." },
		{ "v_d_pvp_0", "Viking blood spilled by a Viking hand: {3} slew {0} {1}. Such things are not forgotten." },
		{ "v_d_pvp_1", "{3} and {0} settled their quarrel {1}. Only {3} walked away." },
		{ "v_d_weak_0", "{0} met their end {1} at the hands of a mighty {2}. The skalds will tell it with a straight face, if they can." },
		{ "v_d_weak_1", "Let it be written that {0} was slain {1} by a fearsome {2}. Valhalla laughed for an hour." },
		{ "v_d_env3_0", "The ground {1} rose to meet {0} far faster than expected. Odin's ravens noted the height." },
		{ "v_d_env3_1", "{0} trusted the cliffs {1} more than the cliffs deserved. The fall was long; the landing, final." },
		{ "v_d_env4_0", "{0} tried to drink the sea {1} and lost. Rán's nets are fuller tonight." },
		{ "v_d_env4_1", "The waves {1} closed over {0}. Rán keeps what she takes." },
		{ "v_d_env5_0", "{0} came too close to the fire {1} and became part of it." },
		{ "v_d_env6_0", "The cold {1} crept into the bones of {0} and stayed there." },
		{ "v_d_env7_0", "Poison did quietly {1} what no blade could: {0} is no more." },
		{ "v_d_env8_0", "The waters {1} proved deadlier than any foe for {0}." },
		{ "v_d_env9_0", "{0} lit a fire without a smoke hole. The smoke did the rest." },
		{ "v_d_env10_0", "{0} sailed to the very edge of the world and learned that it is real." },
		{ "v_d_env11_0", "Something heavy fell on {0} {1}. The saga does not say what, only that it was heavy." },
		{ "v_d_env12_0", "{0} was run down {1} by their own cart. The cart shows no remorse." },
		{ "v_d_env13_0", "The forest takes its revenge: a tree felled with great skill by {0} landed exactly on {0}." },
		{ "v_d_env13_1", "{0} shouted \"Timber!\" a heartbeat too late {1}." },
		{ "v_d_env14_0", "{0} faced the most dangerous foe of all — their own hand — and lost." },
		{ "v_d_env15_0", "The roof that was meant to shelter {0} came down upon them {1}." },
		{ "v_d_env16_0", "{0} was cut down {1} by a ballista. Somebody built it well." },
		{ "v_d_env17_0", "The ship turned on its own crew {1}: {0} did not survive the voyage." },
		{ "v_d_env18_0", "A stalactite chose {0} {1}. Caves remember who disturbs them." },
		{ "v_d_env19_0", "A catapult stone found {0} {1}." },
		{ "v_d_env20_0", "Fire rained from the sky {1} and found {0}." },
		{ "v_d_env21_0", "{0} went for a swim in the boiling sea of the Ashlands. Once." },
		{ "v_d_env22_0", "{0} stepped into the lava {1}. The lava did not step back." },
		{ "v_d_env23_0", "{0} was fed to the obliterator. Nothing was left to bury." },
		{ "v_d_env24_0", "A drawbridge closed on {0} {1}. Mind the gap." },
		{ "v_d_foe_0", "{0} fell {1}, slain by {2}." },
		{ "v_d_foe_1", "{2} ended the journey of {0} {1}." },
		{ "v_d_other_0", "{0} died {1} in a way the skalds still cannot explain." },
		{ "v_d_milestone", "That was their {0} death; the Valkyries greet them by name now." },
		{ "v_biome_land_0", "{0} was the first of all Vikings to set foot {1}. Where one goes, others will follow." },
		{ "v_biome_land_1", "No Viking of Northarun had seen {2} before {0}. Now the way is known." },
		{ "v_biome_ocean_0", "{0} was the first to sail out onto the open sea. Beyond the horizon, Njord waits." },
		{ "v_newcomer_0", "A Valkyrie descends over Northarun and leaves {0} on its shores — the {1} soul to walk these lands." },
		{ "v_newcomer_1", "The Valkyries brought a new warrior: {0}. The saga of Northarun has its {1} hero." },
		{ "v_raid_0", "Trouble came for {0} {1}: <i>{2}</i>" },
		{ "v_raid_nobody_0", "Trouble stirred {1}: <i>{2}</i>" },
		{ "v_god_chosen_0", "{0} has joined the Chosen of {2}. The god's gaze rests upon them." },
		{ "v_god_legend_0", "{0} has become a Legend of {2}. Few mortals ever stand so close to the gods." },
		{ "v_blot_end_0", "The Blót of {1} is over: {2} Vikings brought their gifts, and the god was pleased." },
		{ "v_blot_failed_0", "The Blót of {1} ended with gifts worth {2} of the {3} the god demanded. {0} turned away, disappointed." },
		{ "v_guild_found_0", "{0} founded the guild <b>[{1}] {2}</b>. A new banner rises over Northarun." },
		{ "v_guild_level_0", "The guild <b>[{0}] {1}</b> grows in strength and reaches level {2}." },
		{ "v_guild_ach_0", "The guild <b>[{0}] {1}</b> earned golden glory: {2}." },
		{ "v_guild_banner_0", "{0} planted the banner of <b>[{1}] {2}</b> and claimed the land around it." },
		{ "v_guild_disband_0", "The guild <b>[{0}] {1}</b> is no more. Its banner has been lowered." },
		{ "v_vv_saga_0", "{0} closed a chapter of the village saga: <i>{1}</i>." },
		{ "v_vv_tales_0", "{0} has heard every tale the village skalds know. Few remember so much." },
		{ "v_vv_tier_0", "The villagers now call {0} by a new name: <b>{1}</b>." },
		{ "v_skald", "The skald {1} carves: <i>{0}</i>" },
		{ "v_skald_anon", "The skald carves: <i>{0}</i>" }
	};

	private static readonly Dictionary<string, string> Pl = new Dictionary<string, string>
	{
		{ "title", "Kroniki Northarun" },
		{ "subtitle", "Dzień {0} · wersów: {1} · saga trwa od {2}" },
		{ "subtitle_empty", "Dzień {0} · saga jeszcze się nie zaczęła" },
		{ "cat_0", "Wszystko" },
		{ "cat_1", "Bitwy" },
		{ "cat_2", "Polegli" },
		{ "cat_3", "Odkrywcy" },
		{ "cat_4", "Bogowie" },
		{ "cat_5", "Gildie" },
		{ "cat_6", "Wioski" },
		{ "cat_7", "Skald" },
		{ "day", "Dzień {0}" },
		{ "older", "Starsze wersy…" },
		{ "loading", "Skald odczytuje kamień…" },
		{ "empty", "Nic tu jeszcze nie wyryto." },
		{ "heroes_title", "Bohaterowie sagi" },
		{ "h_bosses", "Pogromcy bossów" },
		{ "h_deaths", "Stali bywalcy Valhalli" },
		{ "h_firsts", "Przecierający szlaki" },
		{ "h_bosslist", "Pokonani bossowie" },
		{ "h_stats", "Bohaterów: {0} · zabitych bossów: {1}" },
		{ "h_none", "jeszcze nikt" },
		{ "hint", "Serwer ryje tu każdy czyn każdego Wikinga Northarun. Admini: saga add <tekst> · saga del <id> · saga last" },
		{ "hover_name", "Kamień Kronik" },
		{ "hover_read", "Czytaj sagę" },
		{ "hover_latest", "Najnowszy wers:" },
		{ "piece_desc", "Wysoki kamień runiczny, na którym skaldowie ryją sagę Northarun: pokonanych bossów, odkryte krainy, śmierci absurdalne i chwalebne. Użyj, by czytać." },
		{ "msg_new", "Saga: {0}" },
		{ "and", "i" },
		{ "god_odin", "Odyn" },
		{ "god_thor", "Thor" },
		{ "god_freya", "Freja" },
		{ "god_njord", "Njord" },
		{ "god_loki", "Loki" },
		{ "godg_odin", "Odyna" },
		{ "godg_thor", "Thora" },
		{ "godg_freya", "Frei" },
		{ "godg_njord", "Njorda" },
		{ "godg_loki", "Lokiego" },
		{ "bin_0", "gdzieś w dziczy" },
		{ "bin_1", "na Łąkach" },
		{ "bin_8", "w Czarnym Lesie" },
		{ "bin_2", "na Bagnach" },
		{ "bin_4", "w Górach" },
		{ "bin_16", "na Równinach" },
		{ "bin_256", "na otwartym morzu" },
		{ "bin_512", "w Mglistych Ziemiach" },
		{ "bin_32", "w Popielnych Ziemiach" },
		{ "bin_64", "na Dalekiej Północy" },
		{ "bnom_1", "Łąki" },
		{ "bnom_8", "Czarny Las" },
		{ "bnom_2", "Bagna" },
		{ "bnom_4", "Góry" },
		{ "bnom_16", "Równiny" },
		{ "bnom_256", "Ocean" },
		{ "bnom_512", "Mgliste Ziemie" },
		{ "bnom_32", "Popielne Ziemie" },
		{ "bnom_64", "Daleka Północ" },
		{ "v_boss_first", "Po raz pierwszy, odkąd świat jest młody, pada {0}. Ten czyn należy do: {1}. Niech skaldowie nigdy o nim nie zapomną." },
		{ "v_boss_0", "{0} znów pada. Zwycięzcy: {1}. Saga liczy już {2} takich upadków." },
		{ "v_boss_1", "Wyzwanie zostaje rzucone, a {0} nie powstaje więcej. Chwała dla: {1}. To już {3} śmierć bestii na Northarun." },
		{ "v_boss_2", "Stal śpiewa i {0} milknie. Chwała dla: {1}." },
		{ "v_boss_solo_0", "Bez tarczowych braci i bez lęku: {1} mierzy się z bestią — {0} — i odchodzi zwycięsko. To {3} upadek bestii." },
		{ "v_boss_solo_1", "{1} nie potrzebuje drużyny: {0} pada z ręki jednego Wikinga." },
		{ "v_d_pvp_0", "Krew Wikinga przelana ręką Wikinga: {3} pokonuje {0} {1}. Takich rzeczy się nie zapomina." },
		{ "v_d_pvp_1", "{3} i {0} rozstrzygają spór {1}. Odchodzi tylko {3}." },
		{ "v_d_weak_0", "{0} ginie {1} z ręki groźnego przeciwnika: {2}. Skaldowie spróbują opowiedzieć to z kamienną twarzą." },
		{ "v_d_weak_1", "Niech zostanie zapisane, że {1} straszliwy przeciwnik — {2} — pokonuje wędrowca imieniem {0}. Valhalla śmieje się do dziś." },
		{ "v_d_env3_0", "{0} spada z wysoka {1} — ziemia okazuje się twardsza niż odwaga. Kruki Odyna zanotowały wysokość." },
		{ "v_d_env3_1", "Klify {1} są mniej godne zaufania, niż sądzi {0}. Upadek jest długi, lądowanie — ostateczne." },
		{ "v_d_env4_0", "{0} próbuje wypić morze {1} i przegrywa. Sieci Rán są dziś pełniejsze." },
		{ "v_d_env4_1", "Fale {1} zamykają się nad głową: {0}. Rán zatrzymuje to, co weźmie." },
		{ "v_d_env5_0", "{0} podchodzi zbyt blisko ognia {1} i staje się jego częścią." },
		{ "v_d_env6_0", "Mróz {1} wkrada się w kości i już z nich nie wychodzi. Zamarza: {0}." },
		{ "v_d_env7_0", "Trucizna robi po cichu {1} to, czego nie zdołało żadne ostrze. Odchodzi {0}." },
		{ "v_d_env8_0", "Wody {1} okazują się groźniejsze niż każdy wróg. Ofiara: {0}." },
		{ "v_d_env9_0", "{0} rozpala ogień bez otworu na dym. Dym robi resztę." },
		{ "v_d_env10_0", "{0} żegluje na sam skraj świata i przekonuje się, że skraj istnieje naprawdę." },
		{ "v_d_env11_0", "Coś ciężkiego spada {1} prosto na: {0}. Saga nie mówi co — tylko że było ciężkie." },
		{ "v_d_env12_0", "Własny wóz przejeżdża {0} {1}. Wóz nie okazuje skruchy." },
		{ "v_d_env13_0", "Las bierze odwet: drzewo ścięte z wielką wprawą przez {0} spada dokładnie na {0}." },
		{ "v_d_env13_1", "{0} krzyczy „Uwaga, drzewo!” o jedno uderzenie serca za późno {1}." },
		{ "v_d_env14_0", "{0} staje do walki z najgroźniejszym wrogiem — własną ręką — i przegrywa." },
		{ "v_d_env15_0", "Dach, który miał chronić, wali się {1}. Pod gruzami: {0}." },
		{ "v_d_env16_0", "Balista ścina {0} {1}. Ktoś ją dobrze zbudował." },
		{ "v_d_env17_0", "Statek zwraca się przeciw własnej załodze {1}. Rejsu nie przeżywa: {0}." },
		{ "v_d_env18_0", "Stalaktyt wybiera {1} swój cel: {0}. Jaskinie pamiętają, kto je niepokoi." },
		{ "v_d_env19_0", "Kamień z katapulty znajduje {1} swój cel: {0}." },
		{ "v_d_env20_0", "Ogień spada z nieba {1} i znajduje: {0}." },
		{ "v_d_env21_0", "{0} kąpie się we wrzącym morzu Popielnych Ziem. Raz." },
		{ "v_d_env22_0", "{0} wchodzi w lawę {1}. Lawa się nie cofa." },
		{ "v_d_env23_0", "{0} trafia do niszczarki. Nie zostaje nic do pochowania." },
		{ "v_d_env24_0", "Zwodzony most zamyka się {1} na: {0}." },
		{ "v_d_foe_0", "{0} ginie {1}. Zabójca: {2}." },
		{ "v_d_foe_1", "Wędrówka dobiega końca {1} — {2} pokonuje: {0}." },
		{ "v_d_other_0", "{0} ginie {1} w okolicznościach, których skaldowie wciąż nie potrafią wyjaśnić." },
		{ "v_d_milestone", "To już {0} śmierć — Walkirie witają po imieniu." },
		{ "v_biome_land_0", "Pierwsze stopy, które stanęły {1}, należą do: {0}. Gdzie jeden ruszy, inni pójdą śladem." },
		{ "v_biome_land_1", "{2}: żaden Wiking Northarun nie oglądał wcześniej tej krainy. Drogę wskazuje {0}." },
		{ "v_biome_ocean_0", "Pierwsza łódź na otwartym morzu — za sterem {0}. Za horyzontem czeka Njord." },
		{ "v_newcomer_0", "Walkiria zstępuje nad Northarun i zostawia na brzegu nowego wędrowca: {0} — {1} dusza w tych krainach." },
		{ "v_newcomer_1", "Walkirie przynoszą nowego wojownika: {0}. Saga Northarun ma już {1} bohatera." },
		{ "v_raid_0", "Kłopoty {1}! Przychodzą po: {0}. <i>{2}</i>" },
		{ "v_raid_nobody_0", "Kłopoty {1}! <i>{2}</i>" },
		{ "v_god_chosen_0", "{0} wstępuje w krąg Wybranych {2}. Wzrok boga spoczywa na tym Wikingu." },
		{ "v_god_legend_0", "{0} staje się Legendą {2}. Niewielu śmiertelników staje tak blisko bogów." },
		{ "v_blot_end_0", "Blót ku czci {1} dobiega końca: dary składa {2} Wikingów, a bóg jest zadowolony." },
		{ "v_blot_failed_0", "Blót ku czci {1} kończy się darami wartymi {2} z {3}, których żądał bóg. {0} z rozczarowaniem odwraca wzrok." },
		{ "v_guild_found_0", "{0} zakłada gildię <b>[{1}] {2}</b>. Nad Northarun wznosi się nowy sztandar." },
		{ "v_guild_level_0", "Gildia <b>[{0}] {1}</b> rośnie w siłę i osiąga poziom {2}." },
		{ "v_guild_ach_0", "Gildia <b>[{0}] {1}</b> zdobywa złotą chwałę: {2}." },
		{ "v_guild_banner_0", "{0} wbija sztandar <b>[{1}] {2}</b> i obejmuje we władanie okoliczne ziemie." },
		{ "v_guild_disband_0", "Gildia <b>[{0}] {1}</b> przestaje istnieć. Jej sztandar zostaje opuszczony." },
		{ "v_vv_saga_0", "{0} zamyka rozdział sagi wioski: <i>{1}</i>." },
		{ "v_vv_tales_0", "{0} zna już każdą opowieść skaldów z wiosek. Niewielu pamięta tak wiele." },
		{ "v_vv_tier_0", "Mieszkańcy wiosek nazywają teraz {0} nowym mianem: <b>{1}</b>." },
		{ "v_skald", "Skald {1} ryje w kamieniu: <i>{0}</i>" },
		{ "v_skald_anon", "Skald ryje w kamieniu: <i>{0}</i>" }
	};

	public static bool IsPolish
	{
		get
		{
			if (Plugin.Language == null)
			{
				return false;
			}
			if (Plugin.Language.Value == SagaLanguage.Polish)
			{
				return true;
			}
			if (Plugin.Language.Value == SagaLanguage.English)
			{
				return false;
			}
			try
			{
				return Localization.instance != null && Localization.instance.GetSelectedLanguage() == "Polish";
			}
			catch
			{
				return false;
			}
		}
	}

	public static bool Has(bool pl, string key)
	{
		if (!(pl ? Pl : En).ContainsKey(key))
		{
			return En.ContainsKey(key);
		}
		return true;
	}

	public static string Get(bool pl, string key)
	{
		if ((pl ? Pl : En).TryGetValue(key, out var value))
		{
			return value;
		}
		if (En.TryGetValue(key, out value))
		{
			return value;
		}
		return key;
	}

	public static string Get(bool pl, string key, params object[] args)
	{
		string text = Get(pl, key);
		if (args == null || args.Length == 0)
		{
			return text;
		}
		try
		{
			return string.Format(text, args);
		}
		catch
		{
			return text;
		}
	}

	public static string T(string key)
	{
		return Get(IsPolish, key);
	}

	public static string T(string key, params object[] args)
	{
		return Get(IsPolish, key, args);
	}
}
internal static class Rpc
{
	public const string Hello = "CH_Hello";

	public const string Boss = "CH_Boss";

	public const string Death = "CH_Death";

	public const string Biome = "CH_Biome";

	public const string Request = "CH_Request";

	public const string Admin = "CH_Admin";

	public const string Page = "CH_Page";

	public const string New = "CH_New";

	public const string Msg = "CH_Msg";

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

	public static void Register()
	{
		ZRoutedRpc instance = ZRoutedRpc.instance;
		if (instance != null)
		{
			instance.Register<bool>("CH_Hello", (Action<long, bool>)SagaServer.RPC_Hello);
			instance.Register<ZPackage>("CH_Boss", (Action<long, ZPackage>)SagaServer.RPC_Boss);
			instance.Register<ZPackage>("CH_Death", (Action<long, ZPackage>)SagaServer.RPC_Death);
			instance.Register<int>("CH_Biome", (Action<long, int>)SagaServer.RPC_Biome);
			instance.Register<ZPackage>("CH_Request", (Action<long, ZPackage>)SagaServer.RPC_Request);
			instance.Register<string>("CH_Admin", (Action<long, string>)SagaServer.RPC_Admin);
			instance.Register<ZPackage>("CH_Page", (Action<long, ZPackage>)SagaClient.RPC_Page);
			instance.Register<ZPackage>("CH_New", (Action<long, ZPackage>)SagaClient.RPC_New);
			instance.Register<string>("CH_Msg", (Action<long, string>)SagaClient.RPC_Msg);
		}
	}

	public static long ServerId()
	{
		if ((Object)(object)ZNet.instance == (Object)null)
		{
			return 0L;
		}
		if (ZNet.instance.IsServer())
		{
			return ZDOMan.GetSessionID();
		}
		return ZNet.instance.GetServerPeer()?.m_uid ?? 0;
	}

	public static void ToServer(string method, params object[] args)
	{
		long num = ServerId();
		if (ZRoutedRpc.instance != null && num != 0)
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(num, method, args);
		}
	}

	public static bool Resolve(long sender, out long pid, out string name)
	{
		//IL_005b: Unknown result type (might be due to invalid IL or missing references)
		pid = 0L;
		name = "";
		if (sender == ZDOMan.GetSessionID())
		{
			if ((Object)(object)Player.m_localPlayer == (Object)null)
			{
				return false;
			}
			pid = Player.m_localPlayer.GetPlayerID();
			name = Player.m_localPlayer.GetPlayerName();
			return pid != 0;
		}
		ZNetPeer peer = ZNet.instance.GetPeer(sender);
		if (peer == null)
		{
			return false;
		}
		ZDO zDO = ZDOMan.instance.GetZDO(peer.m_characterID);
		if (zDO != null)
		{
			pid = zDO.GetLong(ZDOVars.s_playerID, 0L);
		}
		if (pid == 0)
		{
			pid = peer.m_playerID;
		}
		name = peer.m_playerName ?? "";
		return pid != 0;
	}

	public static bool IsAdminSender(long sender)
	{
		try
		{
			if (sender == ZDOMan.GetSessionID())
			{
				return ZNet.instance.LocalPlayerIsAdminOrHost();
			}
			ZNetPeer peer = ZNet.instance.GetPeer(sender);
			if (peer == null || peer.m_socket == null)
			{
				return false;
			}
			string text = peer.m_socket.GetHostName() ?? "";
			if (ZNet.instance.IsAdmin(text))
			{
				return true;
			}
			int num = text.IndexOf('_');
			string text2 = ((num >= 0) ? text.Substring(num + 1) : text);
			return text2.Length > 0 && (ZNet.instance.IsAdmin(text2) || ZNet.instance.IsAdmin("Steam_" + text2));
		}
		catch
		{
			return false;
		}
	}

	public static List<OnlinePlayer> Online()
	{
		//IL_006a: Unknown result type (might be due to invalid IL or missing references)
		//IL_006f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b5: 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_00ef: Unknown result type (might be due to invalid IL or missing references)
		//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
		List<OnlinePlayer> list = new List<OnlinePlayer>();
		if ((Object)(object)ZNet.instance == (Object)null)
		{
			return list;
		}
		if ((Object)(object)Player.m_localPlayer != (Object)null && !ZNet.instance.IsDedicated())
		{
			OnlinePlayer onlinePlayer = new OnlinePlayer();
			onlinePlayer.Pid = Player.m_localPlayer.GetPlayerID();
			onlinePlayer.Uid = ZDOMan.GetSessionID();
			onlinePlayer.Name = Player.m_localPlayer.GetPlayerName();
			onlinePlayer.Pos = ((Component)Player.m_localPlayer).transform.position;
			list.Add(onlinePlayer);
		}
		foreach (ZNetPeer peer in ZNet.instance.GetPeers())
		{
			if (!((ZDOID)(ref peer.m_characterID)).IsNone())
			{
				OnlinePlayer onlinePlayer2 = new OnlinePlayer();
				ZDO zDO = ZDOMan.instance.GetZDO(peer.m_characterID);
				if (zDO != null)
				{
					onlinePlayer2.Pid = zDO.GetLong(ZDOVars.s_playerID, 0L);
					onlinePlayer2.Pos = zDO.GetPosition();
				}
				else
				{
					onlinePlayer2.Pos = peer.m_refPos;
				}
				if (onlinePlayer2.Pid == 0)
				{
					onlinePlayer2.Pid = peer.m_playerID;
				}
				if (onlinePlayer2.Pid != 0)
				{
					onlinePlayer2.Uid = peer.m_uid;
					onlinePlayer2.Name = peer.m_playerName ?? "";
					list.Add(onlinePlayer2);
				}
			}
		}
		return list;
	}
}
internal class OnlinePlayer
{
	public long Pid;

	public long Uid;

	public string Name = "";

	public Vector3 Pos;
}
public enum SagaLanguage
{
	Auto,
	English,
	Polish
}
public enum DeathMode
{
	Notable,
	All,
	Off
}
[BepInDependency(/*Could not decode attribute arguments.*/)]
[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInPlugin("adrian.valheim.chronicles", "Chronicles of Northarun", "0.1.1")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class Plugin : BaseUnityPlugin
{
	public const string PluginGUID = "adrian.valheim.chronicles";

	public const string PluginName = "Chronicles of Northarun";

	public const string PluginVersion = "0.1.1";

	public const string DiscordUrl = "https://discord.gg/6x9j6N58m";

	public static Plugin Instance;

	public static ManualLogSource Log;

	public static ConfigEntry<SagaLanguage> Language;

	public static ConfigEntry<bool> ShowNewEntries;

	public static ConfigEntry<bool> RunestoneLight;

	public static ConfigEntry<int> MaxEntries;

	public static ConfigEntry<bool> RecordBosses;

	public static ConfigEntry<float> BossPartyRange;

	public static ConfigEntry<DeathMode> Deaths;

	public static ConfigEntry<float> DeathCooldownMinutes;

	public static ConfigEntry<int> DeathVersesPerHour;

	public static ConfigEntry<float> DeathChance;

	public static ConfigEntry<string> DeathMilestones;

	public static ConfigEntry<string> WeakCreatures;

	public static ConfigEntry<bool> RecordBiomes;

	public static ConfigEntry<bool> RecordNewcomers;

	public static ConfigEntry<bool> RecordRaids;

	public static ConfigEntry<bool> RecordGods;

	public static ConfigEntry<bool> RecordGuilds;

	public static ConfigEntry<bool> RecordVillages;

	public static ConfigEntry<string> DiscordWebhook;

	public static ConfigEntry<SagaLanguage> DiscordLanguage;

	public static ConfigEntry<string> DiscordCategories;

	public static ConfigEntry<string> Recipe;

	public static ConfigEntry<string> Station;

	public static ConfigEntry<string> HammerCategory;

	private Harmony _harmony;

	private float _serverTick;

	private void Awake()
	{
		//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ce: Expected O, but got Unknown
		//IL_0122: Unknown result type (might be due to invalid IL or missing references)
		//IL_012c: Expected O, but got Unknown
		//IL_0180: Unknown result type (might be due to invalid IL or missing references)
		//IL_018a: Expected O, but got Unknown
		//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_01bd: Expected O, but got Unknown
		//IL_01f1: Unknown result type (might be due to invalid IL or missing references)
		//IL_01fb: Expected O, but got Unknown
		//IL_03e2: Unknown result type (might be due to invalid IL or missing references)
		//IL_03ec: Expected O, but got Unknown
		Instance = this;
		Log = ((BaseUnityPlugin)this).Logger;
		Language = ((BaseUnityPlugin)this).Config.Bind<SagaLanguage>("1 - Client", "Language", SagaLanguage.Auto, "Language of the saga window and messages: Auto (follows the game language), English or Polish.");
		Language.SettingChanged += delegate
		{
			SagaUI.Rebuild();
		};
		ShowNewEntries = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - Client", "ShowNewEntries", true, "Show new verses of the saga (bosses, deaths, discoveries, raids, the skald) in the top-left corner and in chat. Gods, Guilds and Vikings Village announce their own events already, so those are not repeated.");
		RunestoneLight = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - Client", "RunestoneLight", true, "The runes of the Chronicles runestone cast a warm light. Turn off on weak computers.");
		MaxEntries = ((BaseUnityPlugin)this).Config.Bind<int>("2 - Saga", "MaxEntries", 5000, new ConfigDescription("Most verses kept in the saga; the oldest are forgotten first.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(100, 100000), new object[0]));
		RecordBosses = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Saga", "RecordBosses", true, "Write every boss kill into the saga (who, how many, the first kill on the server).");
		BossPartyRange = ((BaseUnityPlugin)this).Config.Bind<float>("2 - Saga", "BossPartyRange", 80f, new ConfigDescription("Players within this many metres of a dying boss share the glory.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 300f), new object[0]));
		Deaths = ((BaseUnityPlugin)this).Config.Bind<DeathMode>("2 - Saga", "Deaths", DeathMode.Notable, "Deaths in the saga: Notable = only absurd ones (falls, trees, drowning, own cart, weak creatures, another Viking, milestones), All = every death, Off = none.");
		DeathCooldownMinutes = ((BaseUnityPlugin)this).Config.Bind<float>("2 - Saga", "DeathPlayerCooldownMinutes", 90f, new ConfigDescription("Real minutes between two death verses of the same player (milestones are always written).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1440f), new object[0]));
		DeathVersesPerHour = ((BaseUnityPlugin)this).Config.Bind<int>("2 - Saga", "DeathVersesPerHour", 3, new ConfigDescription("Most death verses for the whole server in one real hour (milestones not counted). 0 = no limit.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), new object[0]));
		DeathChance = ((BaseUnityPlugin)this).Config.Bind<float>("2 - Saga", "DeathChance", 0.2f, new ConfigDescription("Chance that a common death (fall, drowning, fire, frost, poison, lava…) gets a verse. Rare ones (a tree, own cart, the edge of the world, a Neck, another Viking…) are always written if the limits allow.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), new object[0]));
		DeathMilestones = ((BaseUnityPlugin)this).Config.Bind<string>("2 - Saga", "DeathMilestones", "10,25,50,100,200,500,1000", "Numbers of deaths of one player that are always worth a verse (comma separated).");
		WeakCreatures = ((BaseUnityPlugin)this).Config.Bind<string>("2 - Saga", "WeakCreatures", "Neck,Greyling,Boar,Boar_piggy,Deer,Hare,Hen,Chicken,Leech,Wolf_cub,Lox_Calf", "Creature prefabs whose victory over a Viking is embarrassing enough for the saga (comma separated).");
		RecordBiomes = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Saga", "RecordBiomes", true, "Write the first Viking of the server to set foot in each biome.");
		RecordNewcomers = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Saga", "RecordNewcomers", true, "Write the first arrival of every new character on the server.");
		RecordRaids = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Saga", "RecordRaids", true, "Write raids and other world events (the ground shakes, the forest moves...) with the Viking they came for.");
		RecordGods = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Saga", "RecordGods", true, "Favor of the Gods: Chosen and Legends of the gods, the end of every Blót.");
		RecordGuilds = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Saga", "RecordGuilds", true, "Guilds: founding, levels, gold achievements, banners, disbanding.");
		RecordVillages = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Saga", "RecordVillages", true, "Vikings Village: chapters of the village saga, all tales heard, the Kin and Hero reputation.");
		DiscordWebhook = ((BaseUnityPlugin)this).Config.Bind<string>("3 - Discord", "Webhook", "", "Optional Discord webhook URL (server only) — every new verse of the chosen categories is posted to the channel.");
		DiscordLanguage = ((BaseUnityPlugin)this).Config.Bind<SagaLanguage>("3 - Discord", "Language", SagaLanguage.English, "Language of the verses posted to Discord (Auto = English).");
		DiscordCategories = ((BaseUnityPlugin)this).Config.Bind<string>("3 - Discord", "Categories", "battles,fallen,explorers,gods,guilds,villages,skald", "Categories posted to Discord (comma separated): battles, fallen, explorers, gods, guilds, villages, skald.");
		Recipe = ((BaseUnityPlugin)this).Config.Bind<string>("4 - Runestone", "Recipe", "Stone:40,Coal:10", "Build cost of the Chronicles runestone (Prefab:amount, comma separated). Restart needed.");
		Station = ((BaseUnityPlugin)this).Config.Bind<string>("4 - Runestone", "CraftingStation", "piece_workbench", "Crafting station that must be nearby to build the runestone (empty = none). Restart needed.");
		HammerCategory = ((BaseUnityPlugin)this).Config.Bind<string>("4 - Runestone", "HammerCategory", "Misc", "Hammer tab of the runestone (Misc, Crafting, Building, Furniture or a new tab name). Restart needed.");
		_harmony = new Harmony("adrian.valheim.chronicles");
		_harmony.PatchAll(typeof(Patches));
		_harmony.PatchAll(typeof(Hooks));
		Integrations.Apply(_harmony);
		Runestone.Register();
		CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new SagaAdminCommand());
		Log.LogInfo((object)"Chronicles of Northarun 0.1.1 loaded.");
	}

	private void Update()
	{
		if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
		{
			_serverTick += Time.deltaTime;
			if (_serverTick >= 1f)
			{
				_serverTick = 0f;
				SagaServer.Tick();
			}
		}
		if (GUIManager.IsHeadless())
		{
			return;
		}
		if ((Object)(object)Player.m_localPlayer == (Object)null)
		{
			if (SagaUI.IsOpen)
			{
				SagaUI.Close();
			}
		}
		else if (SagaUI.IsOpen)
		{
			bool keyDown = ZInput.GetKeyDown((KeyCode)27, false);
			bool flag = ZInput.GetButtonDown("Use") && Time.frameCount > SagaUI.OpenedFrame + 1;
			if (keyDown || flag || ((Character)Player.m_localPlayer).IsDead() || SagaUI.TooFar())
			{
				SagaUI.Close();
			}
		}
	}

	private void OnDestroy()
	{
		if (_harmony != null)
		{
			_harmony.UnpatchSelf();
		}
	}
}
internal static class Patches
{
	[HarmonyPatch(typeof(Game), "Start")]
	[HarmonyPostfix]
	private static void GameStart()
	{
		Rpc.Register();
		if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
		{
			SagaServer.EnsureLoaded();
		}
	}

	[HarmonyPostfix]
	[HarmonyPatch(typeof(ZNet), "OnDestroy")]
	private static void ZNetDestroy()
	{
		SagaServer.Unload();
		SagaClient.Reset();
		if (SagaUI.IsOpen)
		{
			SagaUI.Close();
		}
	}

	[HarmonyPatch(typeof(Menu), "Update")]
	[HarmonyPrefix]
	private static bool MenuUpdate()
	{
		if (!SagaUI.IsOpen)
		{
			return SagaUI.ClosedFrame != Time.frameCount;
		}
		return false;
	}
}
internal class SagaAdminCommand : ConsoleCommand
{
	public override string Name => "saga";

	public override string Help => "Chronicles of Northarun (server admins): add <text> (a verse from the skald) | del <id> | last [n] (recent verses with their ids)";

	public override void Run(string[] args)
	{
		if (ZRoutedRpc.instance == null || Rpc.ServerId() == 0)
		{
			Console.instance.Print("Not connected to a world.");
			return;
		}
		ZRoutedRpc.instance.InvokeRoutedRPC(Rpc.ServerId(), "CH_Admin", new object[1] { string.Join(" ", args) });
	}
}
internal static class Runestone
{
	public const string PrefabName = "Chronicles_Runestone";

	public static readonly Color Glow = new Color(1f, 0.62f, 0.26f);

	private static readonly string[] BaseCandidates = new string[3] { "stone_wall_2x1", "stone_wall_1x1", "stone_wall_4x2" };

	public static void Register()
	{
		PrefabManager.OnVanillaPrefabsAvailable += Create;
	}

	private static void Create()
	{
		//IL_005d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: Expected O, but got Unknown
		//IL_0150: Unknown result type (might be due to invalid IL or missing references)
		//IL_0157: Expected O, but got Unknown
		PrefabManager.OnVanillaPrefabsAvailable -= Create;
		string text = null;
		string[] baseCandidates = BaseCandidates;
		foreach (string text2 in baseCandidates)
		{
			if ((Object)(object)PrefabManager.Instance.GetPrefab(text2) != (Object)null)
			{
				text = text2;
				break;
			}
		}
		if (text == null)
		{
			Plugin.Log.LogError((object)"No vanilla stone piece found to build the runestone from — the runestone is not available.");
			return;
		}
		try
		{
			PieceConfig val = new PieceConfig();
			val.Name = Loc.T("hover_name");
			val.Description = Loc.T("piece_desc");
			val.PieceTable = "Hammer";
			val.Category = (string.IsNullOrEmpty(Plugin.HammerCategory.Value) ? "Misc" : Plugin.HammerCategory.Value.Trim());
			if (!string.IsNullOrEmpty(Plugin.Station.Value))
			{
				val.CraftingStation = Plugin.Station.Value.Trim();
			}
			string[] array = Plugin.Recipe.Value.Split(',');
			foreach (string text3 in array)
			{
				string[] array2 = text3.Split(':');
				if (array2.Length == 2 && int.TryParse(array2[1].Trim(), out var result) && result > 0)
				{
					val.AddRequirement(array2[0].Trim(), result, true);
				}
			}
			CustomPiece val2 = new CustomPiece("Chronicles_Runestone", text, val);
			GameObject piecePrefab = val2.PiecePrefab;
			if ((Object)(object)piecePrefab == (Object)null)
			{
				Plugin.Log.LogError((object)("Could not clone " + text + " for the runestone."));
				return;
			}
			if (!RuneModel.Apply(piecePrefab))
			{
				Plugin.Log.LogError((object)"Runestone model could not be loaded — runestone skipped.");
				return;
			}
			piecePrefab.AddComponent<RunestoneSaga>();
			PieceManager.Instance.AddPiece(val2);
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("Runestone registration failed: " + ex));
		}
	}
}
public class RunestoneSaga : MonoBehaviour, Hoverable, Interactable
{
	private ZNetView _nv;

	private Light[] _lights = (Light[])(object)new Light[0];

	private float[] _base = new float[0];

	private void Awake()
	{
		_nv = ((Component)this).GetComponent<ZNetView>();
	}

	private void Start()
	{
		//IL_0052: 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_005d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: Expected O, but got Unknown
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0090: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)_nv == (Object)null || !_nv.IsValid() || GUIManager.IsHeadless() || !Plugin.RunestoneLight.Value || RuneModel.Lights.Count == 0)
		{
			return;
		}
		List<Light> list = new List<Light>();
		foreach (Vector3 light in RuneModel.Lights)
		{
			GameObject val = new GameObject("ChroniclesLight");
			val.transform.SetParent(((Component)this).transform, false);
			val.transform.localPosition = light;
			Light val2 = val.AddComponent<Light>();
			val2.type = (LightType)2;
			val2.color = Runestone.Glow;
			val2.intensity = 0.9f;
			val2.range = 4.5f;
			val2.shadows = (LightShadows)0;
			list.Add(val2);
		}
		_lights = list.ToArray();
		_base = new float[_lights.Length];
		for (int i = 0; i < _lights.Length; i++)
		{
			_base[i] = _lights[i].intensity;
		}
	}

	private void Update()
	{
		if (_lights.Length == 0)
		{
			return;
		}
		float time = Time.time;
		for (int i = 0; i < _lights.Length; i++)
		{
			if ((Object)(object)_lights[i] != (Object)null)
			{
				_lights[i].intensity = _base[i] * (0.85f + 0.2f * Mathf.PerlinNoise(time * 1.3f + (float)i * 5f, 0.4f));
			}
		}
	}

	public float GetHoverOffset()
	{
		return 0f;
	}

	public string GetHoverName()
	{
		return Loc.T("hover_name");
	}

	public string GetHoverText()
	{
		string text = "<color=#FFB75E>" + Loc.T("hover_name") + "</color>";
		text = text + "\n[<color=yellow><b>$KEY_Use</b></color>] " + Loc.T("hover_read");
		Entry latest = SagaClient.Latest;
		if (latest != null)
		{
			string text2 = SagaServer.Strip(SagaText.Verse(latest, Loc.IsPolish));
			if (text2.Length > 140)
			{
				text2 = text2.Substring(0, 137) + "…";
			}
			string text3 = text;
			text = text3 + "\n<color=#A89F8A>" + Loc.T("hover_latest") + "</color>\n<size=14><color=#D8CCB0>" + text2 + "</color></size>";
		}
		return Localization.instance.Localize(text);
	}

	public bool Interact(Humanoid user, bool hold, bool alt)
	{
		if (hold || (Object)(object)user != (Object)(object)Player.m_localPlayer)
		{
			return false;
		}
		SagaUI.Open(this);
		return true;
	}

	public bool UseItem(Humanoid user, ItemData item)
	{
		return false;
	}
}
internal static class RuneModel
{
	private const float Emission = 1.5f;

	public static Mesh Mesh;

	public static Material[] Materials;

	public static readonly List<Vector3> BoxCenters = new List<Vector3>();

	public static readonly List<Vector3> BoxSizes = new List<Vector3>();

	public static readonly List<Vector3> Lights = new List<Vector3>();

	public static Sprite Icon;

	private static Texture2D _white;

	internal static byte[] Resource(string name)
	{
		Assembly assembly = typeof(RuneModel).Assembly;
		using Stream stream = assembly.GetManifestResourceStream("Chronicles." + name);
		if (stream == null)
		{
			return null;
		}
		byte[] array = new byte[stream.Length];
		for (int i = 0; i < array.Length; i += stream.Read(array, i, array.Length - i))
		{
		}
		return array;
	}

	public static Texture2D LoadTexture(string name, bool mips, bool linear)
	{
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: Expected O, but got Unknown
		byte[] array = Resource(name);
		if (array == null)
		{
			return null;
		}
		Texture2D val = new Texture2D(2, 2, (TextureFormat)4, mips, linear);
		ImageConversion.LoadImage(val, array, false);
		((Object)val).name = "Chronicles_" + name;
		return val;
	}

	public static Sprite LoadSprite(string name)
	{
		//IL_0034: 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)
		Texture2D val = LoadTexture(name, mips: false, linear: false);
		if ((Object)(object)val == (Object)null)
		{
			return null;
		}
		((Texture)val).wrapMode = (TextureWrapMode)1;
		Sprite val2 = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f);
		((Object)val2).name = "Chronicles_" + name;
		return val2;
	}

	private static bool Load(Material template)
	{
		//IL_0078: Unknown result type (might be due to invalid IL or missing references)
		//IL_007d: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
		//IL_016b: Unknown result type (might be due to invalid IL or missing references)
		//IL_018c: Unknown result type (might be due to invalid IL or missing references)
		//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e3: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ea: Expected O, but got Unknown
		//IL_024a: Unknown result type (might be due to invalid IL or missing references)
		//IL_024f: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)Mesh != (Object)null)
		{
			return true;
		}
		byte[] array = Resource("runestone.mesh");
		if (array == null || (Object)(object)template == (Object)null)
		{
			return false;
		}
		using (BinaryReader binaryReader = new BinaryReader(new MemoryStream(array)))
		{
			binaryReader.ReadInt32();
			int num = binaryReader.ReadInt32();
			Vector3[] array2 = (Vector3[])(object)new Vector3[num];
			Vector3[] array3 = (Vector3[])(object)new Vector3[num];
			Vector2[] array4 = (Vector2[])(object)new Vector2[num];
			for (int i = 0; i < num; i++)
			{
				ref Vector3 reference = ref array2[i];
				reference = new Vector3(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle());
			}
			for (int j = 0; j < num; j++)
			{
				ref Vector3 reference2 = ref array3[j];
				reference2 = new Vector3(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle());
			}
			for (int k = 0; k < num; k++)
			{
				ref Vector2 reference3 = ref array4[k];
				reference3 = new Vector2(binaryReader.ReadSingle(), binaryReader.ReadSingle());
			}
			int num2 = binaryReader.ReadInt32();
			int[][] array5 = new int[num2][];
			for (int l = 0; l < num2; l++)
			{
				array5[l] = new int[binaryReader.ReadInt32()];
				for (int m = 0; m < array5[l].Length; m++)
				{
					array5[l][m] = binaryReader.ReadInt32();
				}
			}
			int num3 = binaryReader.ReadInt32();
			for (int n = 0; n < num3; n++)
			{
				BoxCenters.Add(new Vector3(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle()));
				BoxSizes.Add(new Vector3(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle()));
			}
			int num4 = binaryReader.ReadInt32();
			for (int num5 = 0; num5 < num4; num5++)
			{
				binaryReader.ReadInt32();
				Lights.Add(new Vector3(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle()));
			}
			Mesh val = new Mesh();
			((Object)val).name = "ChroniclesRunestone";
			if (num > 65000)
			{
				val.indexFormat = (IndexFormat)1;
			}
			val.vertices = array2;
			val.normals = array3;
			val.uv = array4;
			Color32[] array6 = (Color32[])(object)new Color32[num];
			for (int num6 = 0; num6 < num; num6++)
			{
				ref Color32 reference4 = ref array6[num6];
				reference4 = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue);
			}
			val.colors32 = array6;
			val.subMeshCount = num2;
			for (int num7 = 0; num7 < num2; num7++)
			{
				val.SetTriangles(array5[num7], num7);
			}
			val.RecalculateTangents();
			val.RecalculateBounds();
			Mesh = val;
		}
		Texture2D val2 = LoadTexture("stone.png", mips: true, linear: false);
		if ((Object)(object)val2 != (Object)null)
		{
			((Texture)val2).wrapMode = (TextureWrapMode)0;
			((Texture)val2).anisoLevel = 4;
			val2.Compress(true);
		}
		Materials = (Material[])(object)new Material[4]
		{
			MakeMaterial(template, val2, "ChroniclesRunestone_Stone", glows: false, null, Normal("stone_n.png", repeat: true)),
			MakeMaterial(template, Panel("panel_front.png"), "ChroniclesRunestone_Front", glows: true, Mask("glow_front.png"), Normal("panel_front_n.png", repeat: false)),
			MakeMaterial(template, null, "ChroniclesRunestone_Glow", glows: true, null, null),
			MakeMaterial(template, Panel("panel_back.png"), "ChroniclesRunestone_Back", glows: true, Mask("glow_back.png"), Normal("panel_back_n.png", repeat: false))
		};
		if (Materials.Length > Mesh.subMeshCount)
		{
			Array.Resize(ref Materials, Mesh.subMeshCount);
		}
		Icon = LoadSprite("icon_runestone.png");
		Plugin.Log.LogInfo((object)("Runestone: " + Mesh.vertexCount + " vertices, " + BoxCenters.Count + " colliders, shader " + ((Object)template.shader).name));
		return true;
	}

	private static Texture2D Panel(string name)
	{
		Texture2D val = LoadTexture(name, mips: true, linear: false);
		if ((Object)(object)val == (Object)null)
		{
			return null;
		}
		((Texture)val).wrapMode = (TextureWrapMode)1;
		((Texture)val).anisoLevel = 4;
		val.Compress(true);
		return val;
	}

	private static Texture2D Mask(string name)
	{
		Texture2D val = LoadTexture(name, mips: true, linear: true);
		if ((Object)(object)val != (Object)null)
		{
			((Texture)val).wrapMode = (TextureWrapMode)1;
		}
		return val;
	}

	private static Texture2D Normal(string name, bool repeat)
	{
		Texture2D val = LoadTexture(name, mips: true, linear: true);
		if ((Object)(object)val == (Object)null)
		{
			return null;
		}
		((Texture)val).wrapMode = (TextureWrapMode)(!repeat);
		((Texture)val).anisoLevel = 4;
		val.Apply(true, true);
		return val;
	}

	private static Texture2D White()
	{
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Expected O, but got Unknown
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)_white != (Object)null)
		{
			return _white;
		}
		_white = new Texture2D(4, 4, (TextureFormat)4, false);
		Color32[] array = (Color32[])(object)new Color32[16];
		for (int i = 0; i < 16; i++)
		{
			ref Color32 reference = ref array[i];
			reference = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue);
		}
		_white.SetPixels32(array);
		_white.Apply();
		((Object)_white).name = "ChroniclesWhite";
		return _white;
	}

	private static Material MakeMaterial(Material template, Texture2D tex, string name, bool glows, Texture2D emissionMap, Texture2D bump)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0007: Expected O, but got Unknown
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Invalid comparison between Unknown and I4
		//IL_0097: 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_0042: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e9: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
		Material val = new Material(template);
		((Object)val).name = name;
		Shader shader = val.shader;
		if ((Object)(object)shader != (Object)null)
		{
			for (int i = 0; i < shader.GetPropertyCount(); i++)
			{
				if ((int)shader.GetPropertyType(i) == 4)
				{
					string propertyName = shader.GetPropertyName(i);
					val.SetTextureScale(propertyName, Vector2.one);
					val.SetTextureOffset(propertyName, Vector2.zero);
				}
			}
		}
		if ((Object)(object)tex == (Object)null)
		{
			tex = White();
		}
		val.mainTexture = (Texture)(object)tex;
		if (val.HasProperty("_MainTex"))
		{
			val.SetTexture("_MainTex", (Texture)(object)tex);
		}
		if (val.HasProperty("_Color"))
		{
			val.color = Color.white;
		}
		string[] array = new string[5] { "_MetallicGlossMap", "_MetallicTex", "_OcclusionMap", "_DetailAlbedoMap", "_DetailNormalMap" };
		foreach (string text in array)
		{
			if (val.HasProperty(text))
			{
				val.SetTexture(text, (Texture)null);
			}
		}
		if ((Object)(object)bump != (Object)null && val.HasProperty("_BumpMap"))
		{
			val.SetTexture("_BumpMap", (Texture)(object)bump);
			if (val.HasProperty("_BumpScale"))
			{
				val.SetFloat("_BumpScale", 1f);
			}
			val.EnableKeyword("_NORMALMAP");
		}
		else if (val.HasProperty("_BumpMap"))
		{
			val.SetTexture("_BumpMap", (Texture)null);
		}
		if (val.HasProperty("_TriplanarMap"))
		{
			val.SetFloat("_TriplanarMap", 0f);
		}
		if (val.HasProperty("_Cutoff"))
		{
			val.SetFloat("_Cutoff", 0f);
		}
		if (glows && val.HasProperty("_EmissionColor"))
		{
			if (val.HasProperty("_EmissionMap"))
			{
				val.SetTexture("_EmissionMap", (Texture)(object)(((Object)(object)emissionMap != (Object)null) ? emissionMap : White()));
			}
			val.SetColor("_EmissionColor", Runestone.Glow * 1.5f);
			val.EnableKeyword("_EMISSION");
			val.globalIlluminationFlags = (MaterialGlobalIlluminationFlags)1;
		}
		return val;
	}

	public static bool Apply(GameObject prefab)
	{
		//IL_0113: Unknown result type (might be due to invalid IL or missing references)
		//IL_011a: Expected O, but got Unknown
		//IL_017a: 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_01fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_0203: Expected O, but got Unknown
		//IL_0231: 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)
		Material template = PickTemplate(prefab);
		int layer = prefab.layer;
		Collider[] componentsInChildren = prefab.GetComponentsInChildren<Collider>(true);
		int num = 0;
		if (num < componentsInChildren.Length)
		{
			Collider val = componentsInChildren[num];
			layer = ((Component)val).gameObject.layer;
		}
		if (!Load(template))
		{
			return false;
		}
		LODGroup[] componentsInChildren2 = prefab.GetComponentsInChildren<LODGroup>(true);
		foreach (LODGroup val2 in componentsInChildren2)
		{
			Object.DestroyImmediate((Object)(object)val2);
		}
		Renderer[] componentsInChildren3 = prefab.GetComponentsInChildren<Renderer>(true);
		foreach (Renderer val3 in componentsInChildren3)
		{
			Object.DestroyImmediate((Object)(object)val3);
		}
		MeshFilter[] componentsInChildren4 = prefab.GetComponentsInChildren<MeshFilter>(true);
		foreach (MeshFilter val4 in componentsInChildren4)
		{
			Object.DestroyImmediate((Object)(object)val4);
		}
		Collider[] componentsInChildren5 = prefab.GetComponentsInChildren<Collider>(true);
		foreach (Collider val5 in componentsInChildren5)
		{
			Object.DestroyImmediate((Object)(object)val5);
		}
		GameObject val6 = new GameObject("ChroniclesRunestoneModel", new Type[2]
		{
			typeof(MeshFilter),
			typeof(MeshRenderer)
		});
		val6.layer = layer;
		val6.transform.SetParent(prefab.transform, false);
		val6.GetComponent<MeshFilter>().sharedMesh = Mesh;
		MeshRenderer component = val6.GetComponent<MeshRenderer>();
		((Renderer)component).sharedMaterials = Materials;
		((Renderer)component).shadowCastingMode = (ShadowCastingMode)1;
		for (int m = 0; m < BoxCenters.Count; m++)
		{
			BoxCollider val7 = val6.AddComponent<BoxCollider>();
			val7.center = BoxCenters[m];
			val7.size = BoxSizes[m];
		}
		Transform[] componentsInChildren6 = prefab.GetComponentsInChildren<Transform>(true);
		foreach (Transform val8 in componentsInChildren6)
		{
			if ((Object)(object)val8 != (Object)(object)prefab.transform && ((Component)val8).CompareTag("snappoint"))
			{
				Object.DestroyImmediate((Object)(object)((Component)val8).gameObject);
			}
		}
		GameObject val9 = new GameObject("_snappoint");
		val9.tag = "snappoint";
		val9.layer = layer;
		val9.transform.SetParent(prefab.transform, false);
		val9.transform.localPosition = Vector3.zero;
		val9.SetActive(false);
		Piece component2 = prefab.GetComponent<Piece>();
		if ((Object)(object)component2 != (Object)null)
		{
			component2.m_notOnFloor = false;
			component2.m_inCeilingOnly = false;
			component2.m_groundPiece = false;
			component2.m_groundOnly = false;
			component2.m_cultivatedGroundOnly = false;
			component2.m_waterPiece = false;
			component2.m_noInWater = true;
			component2.m_notOnWood = false;
			component2.m_notOnTiltingSurface = false;
			component2.m_clipGround = true;
			component2.m_clipEverything = false;
			component2.m_noClipping = false;
			component2.m_onlyInTeleportArea = false;
			component2.m_allowedInDungeons = false;
			component2.m_comfort = 0;
			if ((Object)(object)Icon != (Object)null)
			{
				component2.m_icon = Icon;
			}
		}
		WearNTear component3 = prefab.GetComponent<WearNTear>();
		if ((Object)(object)component3 != (Object)null)
		{
			component3.m_health = 2500f;
			component3.m_noRoofWear = true;
			component3.m_noSupportWear = true;
			component3.m_supports = true;
			component3.m_materialType = (MaterialType)1;
			component3.m_burnable = false;
		}
		return true;
	}

	private static Material PickTemplate(GameObject prefab)
	{
		Material result = null;
		int num = -1;
		Renderer[] componentsInChildren = prefab.GetComponentsInChildren<Renderer>(true);
		foreach (Renderer val in componentsInChildren)
		{
			if (!(val is MeshRenderer))
			{
				continue;
			}
			Material[] sharedMaterials = val.sharedMaterials;
			foreach (Material val2 in sharedMaterials)
			{
				if ((Object)(object)val2 == (Object)null || (Object)(object)val2.shader == (Object)null)
				{
					continue;
				}
				string text = ((Object)val2.shader).name.ToLowerInvariant();
				string text2 = ((Object)val2).name.ToLowerInvariant();
				if (!text.Contains("snow") && !text2.Contains("snow"))
				{
					int num2 = 1;
					if (text.Contains("piece"))
					{
						num2 += 4;
					}
					if (text2.Contains("stone"))
					{
						num2 += 2;
					}
					if (val2.HasProperty("_MainTex") && (Object)(object)val2.GetTexture("_MainTex") != (Object)null)
					{
						num2 += 2;
					}
					if (num2 > num)
					{
						num = num2;
						result = val2;
					}
				}
			}
		}
		return result;
	}
}
internal static class SagaClient
{
	public static readonly List<Entry> Loaded = new List<Entry>();

	public static int LoadedCat = -1;

	public static bool HasMore;

	public static bool Waiting;

	public static SagaStats Stats;

	public static Entry Latest;

	public static void Reset()
	{
		Loaded.Clear();
		LoadedCat = -1;
		HasMore = false;
		Waiting = false;
		Stats = null;
		Latest = null;
	}

	public static void Request(int cat, bool older)
	{
		//IL_0056: Unknown result type (might be due to invalid IL or missing references)
		//IL_005c: Expected O, but got Unknown
		long num = 0L;
		if (older && cat == LoadedCat && Loaded.Count > 0)
		{
			num = Loaded[Loaded.Count - 1].Id;
		}
		if (!older)
		{
			Loaded.Clear();
			LoadedCat = cat;
			HasMore = false;
		}
		Waiting = true;
		ZPackage val = new ZPackage();
		val.Write(cat);
		val.Write(num);
		val.Write(40);
		Rpc.ToServer("CH_Request", val);
	}

	public static void RPC_Page(long sender, ZPackage pkg)
	{
		try
		{
			int num = pkg.ReadInt();
			long num2 = pkg.ReadLong();
			int num3 = pkg.ReadInt();
			List<Entry> list = new List<Entry>();
			for (int i = 0; i < num3; i++)
			{
				list.Add(Entry.Read(pkg));
			}
			bool hasMore = pkg.ReadBool();
			if (pkg.ReadBool())
			{
				Stats = SagaStats.Read(pkg);
			}
			if (num != LoadedCat)
			{
				return;
			}
			if (num2 == 0)
			{
				Loaded.Clear();
			}
			foreach (Entry e in list)
			{
				if (!Loaded.Exists((Entry x) => x.Id == e.Id))
				{
					Loaded.Add(e);
				}
			}
			HasMore = hasMore;
			Waiting = false;
			if (num2 == 0 && list.Count > 0 && (Latest == null || list[0].Id > Latest.Id) && num == 0)
			{
				Latest = list[0];
			}
			SagaUI.OnData();
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Bad saga page: " + ex.Message));
		}
	}

	public static void RPC_New(long sender, ZPackage pkg)
	{
		try
		{
			bool flag = pkg.ReadBool();
			Entry e = Entry.Read(pkg);
			if (Latest == null || e.Id >= Latest.Id)
			{
				Latest = e;
			}
			if (!flag)
			{
				return;
			}
			if (LoadedCat >= 0 && (LoadedCat == 0 || LoadedCat == (int)e.Category) && !Loaded.Exists((Entry x) => x.Id == e.Id))
			{
				Loaded.Insert(0, e);
				if (Stats != null)
				{
					Stats.Verses++;
				}
				SagaUI.OnData();
			}
			if (!Plugin.ShowNewEntries.Value || !Announced(e) || GUIManager.IsHeadless())
			{
				return;
			}
			string text = Loc.T("msg_new", SagaText.Verse(e, Loc.IsPolish));
			if ((Object)(object)MessageHud.instance != (Object)null)
			{
				MessageHud.instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false, true);
			}
			try
			{
				if ((Object)(object)Chat.instance != (Object)null)
				{
					((Terminal)Chat.instance).AddString("<color=#E8C77A>" + text + "</color>");
				}
			}
			catch
			{
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Bad saga verse: " + ex.Message));
		}
	}

	private static bool Announced(Entry e)
	{
		switch (e.Category)
		{
		case Cat.Battles:
		case Cat.Fallen:
		case Cat.Explorers:
		case Cat.Skald:
			return true;
		default:
			return false;
		}
	}

	public static void RPC_Msg(long sender, string text)
	{
		string[] array = (text ?? "").Split('\n');
		foreach (string text2 in array)
		{
			if ((Object)(object)Console.instance != (Object)null)
			{
				Console.instance.Print(text2);
			}
			Plugin.Log.LogInfo((object)text2);
		}
	}
}
internal static class SagaServer
{
	private const int SaveVersion = 1;

	private static bool _loaded;

	private static bool _dirty;

	private static string _path;

	private static long _nextId = 1L;

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

	private static readonly Dictionary<long, Hero> Heroes = new Dictionary<long, Hero>();

	private static readonly HashSet<int> Biomes = new HashSet<int>();

	private static readonly Dictionary<string, int> BossKills = new Dictionary<string, int>();

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

	private static readonly Dictionary<string, float> RecentBoss = new Dictionary<string, float>();

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

	private static string _lastRaid = "";

	private static float _lastRaidAt = -1000f;

	private static int _seconds;

	private static readonly HashSet<int> EnvDeaths = new HashSet<int>
	{
		3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
		13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
		23, 24
	};

	private static readonly Random Rnd = new Random();

	private static readonly List<long> RecentDeathVerses = new List<long>();

	private static readonly HashSet<int> RareEnv = new HashSet<int>
	{
		9, 10, 12, 13, 14, 15, 16, 17, 18, 19,
		23, 24
	};

	private static readonly HashSet<int> KnownBiomes = new HashSet<int> { 1, 2, 4, 8, 16, 32, 64, 256, 512 };

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

	private static bool _posting;

	private static readonly Regex Tags = new Regex("<[^>]+>");

	public static bool Ready => _loaded;

	private static bool IsServer
	{
		get
		{
			if ((Object)(object)ZNet.instance != (Object)null)
			{
				return ZNet.instance.IsServer();
			}
			return false;
		}
	}

	public static void EnsureLoaded()
	{
		//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fb: Expected O, but got Unknown
		if (_loaded || !IsServer)
		{
			return;
		}
		_loaded = true;
		Entries.Clear();
		Heroes.Clear();
		Biomes.Clear();
		BossKills.Clear();
		BossNames.Clear();
		_nextId = 1L;
		string text = ZNet.instance.GetWorldName();
		if (string.IsNullOrEmpty(text))
		{
			text = "world";
		}
		char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
		foreach (char oldChar in invalidFileNameChars)
		{
			text = text.Replace(oldChar, '_');
		}
		string text2 = Path.Combine(Paths.ConfigPath, "Chronicles");
		Directory.CreateDirectory(text2);
		_path = Path.Combine(text2, text + ".dat");
		if (!File.Exists(_path))
		{
			Plugin.Log.LogInfo((object)("A new saga begins for world " + text));
			SeedFromWorld();
			_dirty = true;
			return;
		}
		try
		{
			ZPackage val = new ZPackage(File.ReadAllBytes(_path));
			int num = val.ReadInt();
			_nextId = val.ReadLong();
			int num2 = val.ReadInt();
			for (int j = 0; j < num2; j++)
			{
				Entries.Add(Entry.Read(val));
			}
			num2 = val.ReadInt();
			for (int k = 0; k < num2; k++)
			{
				Hero hero = Hero.Read(val);
				Heroes[hero.Pid] = hero;
			}
			num2 = val.ReadInt();
			for (int l = 0; l < num2; l++)
			{
				Biomes.Add(val.ReadInt());
			}
			num2 = val.ReadInt();
			for (int m = 0; m < num2; m++)
			{
				string key = val.ReadString();
				BossNames[key] = val.ReadString();
				BossKills[key] = val.ReadInt();
			}
			foreach (Entry entry in Entries)
			{
				if (entry.Id >= _nextId)
				{
					_nextId = entry.Id + 1;
				}
			}
			Plugin.Log.LogInfo((object)("Saga loaded: " + Entries.Count + " verses, " + Heroes.Count + " heroes (save v" + num + ")."));
		}
		catch (Exception ex)
		{
			string text3 = _path + ".broken-" + DateTime.Now.ToString("yyyyMMdd-HHmmss");
			Plugin.Log.LogError((object)("Could not load " + _path + " — copy saved as " + text3 + ". " + ex));
			try
			{
				File.Copy(_path, text3, overwrite: true);
			}
			catch
			{
			}
		}
	}

	private static void SeedFromWorld()
	{
		Biomes.Add(1);
		string[][] array = new string[7][]
		{
			new string[4] { "defeated_eikthyr", "Eikthyr", "$enemy_eikthyr", "8" },
			new string[4] { "defeated_gdking", "gd_king", "$enemy_gdking", "2,256" },
			new string[4] { "defeated_bonemass", "Bonemass", "$enemy_bonemass", "4" },
			new string[4] { "defeated_dragon", "Dragon", "$enemy_dragon", "16" },
			new string[4] { "defeated_goblinking", "GoblinKing", "$enemy_goblinking", "512" },
			new string[4] { "defeated_queen", "SeekerQueen", "$enemy_seekerqueen", "32" },
			new string[4] { "defeated_fader", "Fader", "$enemy_fader", "64" }
		};
		int num = -1;
		for (int i = 0; i < array.Length; i++)
		{
			if (GlobalKey(array[i][0]))
			{
				num = i;
				BossKills[array[i][1]] = 1;
				BossNames[array[i][1]] = array[i][2];
			}
		}
		for (int j = 0; j <= num; j++)
		{
			string[] array2 = array[j][3].Split(',');
			foreach (string s in array2)
			{
				Biomes.Add(int.Parse(s));
			}
		}
		if (num >= 0)
		{
			Plugin.Log.LogInfo((object)("Saga: the world already knows " + (num + 1) + " bosses — their biomes and first kills are not announced again."));
		}
	}

	private static bool GlobalKey(string key)
	{
		try
		{
			return (Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey(key);
		}
		catch
		{
			return false;
		}
	}

	private static void Save()
	{
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Expected O, but got Unknown
		if (!_loaded || string.IsNullOrEmpty(_path))
		{
			return;
		}
		_dirty = false;
		try
		{
			ZPackage val = new ZPackage();
			val.Write(1);
			val.Write(_nextId);
			val.Write(Entries.Count);
			foreach (Entry entry in Entries)
			{
				entry.Write(val);
			}
			val.Write(Heroes.Count);
			foreach (Hero value2 in Heroes.Values)
			{
				value2.Write(val);
			}
			val.Write(Biomes.Count);
			foreach (int biome in Biomes)
			{
				val.Write(biome);
			}
			val.Write(BossKills.Count);
			foreach (KeyValuePair<string, int> bossKill in BossKills)
			{
				BossNames.TryGetValue(bossKill.Key, out var value);
				val.Write(bossKill.Key);
				val.Write(value ?? bossKill.Key);
				val.Write(bossKill.Value);
			}
			string text = _path + ".tmp";
			File.WriteAllBytes(text, val.GetArray());
			if (File.Exists(_path))
			{
				File.Copy(_path, _path + ".bak", overwrite: true);
			}
			File.Copy(text, _path, overwrite: true);
			File.Delete(text);
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("Saga save error: " + ex));
		}
	}

	public static void Unload()
	{
		if (_loaded && _dirty)
		{
			Save();
		}
		_loaded = false;
		Entries.Clear();
		Heroes.Clear();
		Biomes.Clear();
		BossKills.Clear();
		BossNames.Clear();
		RecentBoss.Clear();
		RecentBanner.Clear();
		_discord.Clear();
		_posting = false;
	}

	public static void Tick()
	{
		if (!_loaded)
		{
			EnsureLoaded();
		}
		if (_loaded)
		{
			_seconds++;
			if (_dirty && _seconds % 30 == 0)
			{
				Save();
			}
		}
	}

	public static Entry Record(string kind, params string[] args)
	{
		//IL_009f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a5: Expected O, but got Unknown
		if (!IsServer)
		{
			return null;
		}
		EnsureLoaded();
		Entry entry = new Entry();
		entry.Id = _nextId++;
		entry.Ticks = DateTime.UtcNow.Ticks;
		entry.Day = WorldDay();
		entry.Kind = kind;
		entry.A = args ?? new string[0];
		Entries.Add(entry);
		int value = Plugin.MaxEntries.Value;
		if (Entries.Count > value)
		{
			Entries.RemoveRange(0, Entries.Count - value);
		}
		_dirty = true;
		try
		{
			ZPackage val = new ZPackage();
			val.Write(true);
			entry.Write(val);
			ZRoutedRpc.instance.InvokeRoutedRPC(0L, "CH_New", new object[1] { val });
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Saga broadcast: " + ex.Message));
		}
		PostDiscord(entry);
		return entry;
	}

	private static int WorldDay()
	{
		try
		{
			return ((Object)(object)EnvMan.instance != (Object)null) ? EnvMan.instance.GetDay() : 0;
		}
		catch
		{
			return 0;
		}
	}

	private static Hero HeroOf(long pid, string name)
	{
		if (!Heroes.TryGetValue(pid, out var value))
		{
			value = new Hero();
			value.Pid = pid;
			value.Joined = DateTime.UtcNow.Ticks;
			Heroes[pid] = value;
			_dirty = true;
		}
		if (!string.IsNullOrEmpty(name) && value.Name != name)
		{
			value.Name = name;
			_dirty = true;
		}
		return value;
	}

	public static void RPC_Hello(long sender, bool valkyrie)
	{
		//IL_0081: Unknown result type (might be due to invalid IL or missing references)
		//IL_0087: Expected O, but got Unknown
		if (!IsServer)
		{
			return;
		}
		EnsureLoaded();
		if (Rpc.Resolve(sender, out var pid, out var name))
		{
			bool flag = !Heroes.ContainsKey(pid);
			HeroOf(pid, name);
			if (flag && valkyrie && Plugin.RecordNewcomers.Value)
			{
				Record("newcomer", name, Heroes.Count.ToString());
			}
			if (Entries.Count > 0)
			{
				ZPackage val = new ZPackage();
				val.Write(false);
				Entries[Entries.Count - 1].Write(val);
				ZRoutedRpc.instance.InvokeRoutedRPC(sender, "CH_New", new object[1] { val });
			}
		}
	}

	public static void RPC_Boss(long sender, ZPackage pkg)
	{
		//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_0110: 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)
		if (!IsServer || !Plugin.RecordBosses.Value)
		{
			return;
		}
		EnsureLoaded();
		if (!Rpc.Resolve(sender, out var pid, out var name))
		{
			return;
		}
		string text;
		string text2;
		Vector3 val;
		int num;
		try
		{
			text = pkg.ReadString();
			text2 = pkg.ReadString();
			val = pkg.ReadVector3();
			num = pkg.ReadInt();
		}
		catch
		{
			return;
		}
		GameObject val2 = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(text) : null);
		Character val3 = (((Object)(object)val2 != (Object)null) ? val2.GetComponent<Character>() : null);
		if ((Object)(object)val3 == (Object)null || !val3.m_boss)
		{
			return;
		}
		if (string.IsNullOrEmpty(text2) || text2.Length > 80)
		{
			text2 = val3.m_name;
		}
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		if (RecentBoss.TryGetValue(text, out var value) && realtimeSinceStartup - value < 30f)
		{
			return;
		}
		RecentBoss[text] = realtimeSinceStartup;
		List<string> list = new List<string>();
		float value2 = Plugin.BossPartyRange.Value;
		foreach (OnlinePlayer item in Rpc.Online())
		{
			if (item.Pid == pid || !(Vector3.Distance(item.Pos, val) > value2))
			{
				list.Add(item.Name);
				HeroOf(item.Pid, item.Name).Bosses++;
			}
		}
		if (list.Count == 0)
		{
			list.Add(name);
		}
		BossKills.TryGetValue(text, out var value3);
		value3++;
		BossKills[text] = value3;
		BossNames[text] = text2;
		Record("boss", text2, text, string.Join("\n", list.ToArray()), value3.ToString(), (value3 == 1) ? "1" : "0", num.ToString());
	}

	public static void RPC_Death(long sender, ZPackage pkg)
	{
		if (!IsServer)
		{
			return;
		}
		EnsureLoaded();
		if (!Rpc.Resolve(sender, out var pid, out var name))
		{
			return;
		}
		int num;
		string text;
		string text2;
		string text3;
		int num2;
		try
		{
			num = pkg.ReadInt();
			text = pkg.ReadString();
			text2 = pkg.ReadString();
			text3 = pkg.ReadString();
			num2 = pkg.ReadInt();
		}
		catch
		{
			return;
		}
		Hero hero = HeroOf(pid, name);
		hero.Deaths++;
		_dirty = true;
		if (Plugin.Deaths.Value == DeathMode.Off)
		{
			return;
		}
		if (text3.Length > 0)
		{
			bool flag = false;
			foreach (OnlinePlayer item in Rpc.Online())
			{
				if (item.Name == text3 && item.Pid != pid)
				{
					flag = true;
					break;
				}
			}
			if (!flag)
			{
				text3 = "";
			}
		}
		string text4 = ((text3.Length > 0) ? "pvp" : (EnvDeaths.Contains(num) ? ("env" + num) : ((text.Length <= 0 || !IsWeak(text)) ? "foe" : "weak")));
		bool flag2 = IsMilestone(hero.Deaths);
		bool flag3 = text4 != "foe" || flag2;
		if (Plugin.Deaths.Value == DeathMode.Notable && !flag3)
		{
			return;
		}
		long now = DateTime.UtcNow.Ticks;
		if (!flag2)
		{
			if (now - hero.LastDeathVerse < (long)(Plugin.DeathCooldownMinutes.Value * 600000000f))
			{
				return;
			}
			RecentDeathVerses.RemoveAll((long t) => now - t >= 36000000000L);
			if ((Plugin.DeathVersesPerHour.Value > 0 && RecentDeathVerses.Count >= Plugin.DeathVersesPerHour.Value) || (!RareDeath(text4) && Rnd.NextDouble() >= (double)Plugin.DeathChance.Value))
			{
				return;
			}
			RecentDeathVerses.Add(now);
		}
		hero.LastDeathVerse = now;
		if (text2.Length > 80)
		{
			text2 = "";
		}
		Record("death", name, text4, text2, text3, num2.ToString(), hero.Deaths.ToString(), flag2 ? "1" : "0");
	}

	private static bool RareDeath(string kind)
	{
		if (kind == "pvp" || kind == "weak")
		{
			return true;
		}
		if (kind.StartsWith("env") && int.TryParse(kind.Substring(3), out var result))
		{
			return RareEnv.Contains(result);
		}
		return false;
	}

	private static bool IsWeak(string prefab)
	{
		string[] array = (Plugin.WeakCreatures.Value ?? "").Split(',');
		foreach (string text in array)
		{
			if (string.Equals(text.Trim(), prefab, StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
		}
		return false;
	}

	private static bool IsMilestone(int deaths)
	{
		string[] array = (Plugin.DeathMilestones.Value ?? "").Split(',');
		foreach (string text in array)
		{
			if (int.TryParse(text.Trim(), out var result) && result == deaths)
			{
				return true;
			}
		}
		return false;
	}

	public static void RPC_Biome(long sender, int biome)
	{
		if (IsServer && Plugin.RecordBiomes.Value && KnownBiomes.Contains(biome))
		{
			EnsureLoaded();
			if (Rpc.Resolve(sender, out var pid, out var name) && Biomes.Add(biome))
			{
				HeroOf(pid, name).Firsts++;
				Record("biome", name, biome.ToString());
			}
		}
	}

	public static void OnRaid(RandomEvent ev, Vector3 pos)
	{
		//IL_0091: Unknown result type (might be due to invalid IL or missing references)
		//IL_0096: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00dd: Expected I4, but got Unknown
		if (!IsServer || ev == null || !Plugin.RecordRaids.Value)
		{
			return;
		}
		EnsureLoaded();
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		if (realtimeSinceStartup < 60f || (ev.m_name == _lastRaid && realtimeSinceStartup - _lastRaidAt < 120f))
		{
			return;
		}
		_lastRaid = ev.m_name;
		_lastRaidAt = realtimeSinceStartup;
		string text = "";
		float num = Mathf.Max(60f, ev.m_eventRange + 30f);
		foreach (OnlinePlayer item in Rpc.Online())
		{
			float num2 = Vector3.Distance(item.Pos, pos);
			if (num2 < num)
			{
				num = num2;
				text = item.Name;
			}
		}
		int num3 = 0;
		try
		{
			if (WorldGenerator.instance != null)
			{
				num3 = (int)WorldGenerator.instance.GetBiome(pos);
			}
		}
		catch
		{
		}
		Record("raid", text, ev.m_name ?? "", ev.m_startMessage ?? "", num3.ToString());
	}

	public static bool BannerAllowed(string tag)
	{
		long ticks = DateTime.UtcNow.Ticks;
		if (RecentBanner.TryGetValue(tag ?? "", out var value) && ticks - value < 864000000000L)
		{
			return false;
		}
		RecentBanner[tag ?? ""] = ticks;
		return true;
	}

	public static void RPC_Request(long sender, ZPackage req)
	{
		//IL_0096: Unknown result type (might be due to invalid IL or missing references)
		//IL_009d: Expected O, but got Unknown
		if (!IsServer)
		{
			return;
		}
		EnsureLoaded();
		int num;
		long num2;
		int num3;
		try
		{
			num = req.ReadInt();
			num2 = req.ReadLong();
			num3 = Mathf.Clamp(req.ReadInt(), 1, 80);
		}
		catch
		{
			return;
		}
		List<Entry> list = new List<Entry>();
		bool flag = false;
		for (int num4 = Entries.Count - 1; num4 >= 0; num4--)
		{
			Entry entry = Entries[num4];
			if ((num2 <= 0 || entry.Id < num2) && (num == 0 || entry.Category == (Cat)num))
			{
				if (list.Count >= num3)
				{
					flag = true;
					break;
				}
				list.Add(entry);
			}
		}
		ZPackage val = new ZPackage();
		val.Write(num);
		val.Write(num2);
		val.Write(list.Count);
		foreach (Entry item in list)
		{
			item.Write(val);
		}
		val.Write(flag);
		val.Write(num2 == 0);
		if (num2 == 0)
		{
			Stats().Write(val);
		}
		ZRoutedRpc.instance.InvokeRoutedRPC(sender, "CH_Page", new object[1] { val });
	}

	private static SagaStats Stats()
	{
		SagaStats sagaStats = new SagaStats();
		sagaStats.Day = WorldDay();
		sagaStats.Verses = Entries.Count;
		sagaStats.Heroes = Heroes.Count;
		sagaStats.Since = ((Entries.Count > 0) ? Entries[0].Ticks : 0);
		foreach (int value2 in BossKills.Values)
		{
			sagaStats.BossKills += value2;
		}
		List<Hero> hs = new List<Hero>(Heroes.Values);
		Top(hs, sagaStats.TopBosses, (Hero h) => h.Bosses);
		Top(hs, sagaStats.TopDeaths, (Hero h) => h.Deaths);
		Top(hs, sagaStats.TopFirsts, (Hero h) => h.Firsts);
		List<KeyValuePair<string, int>> list = new List<KeyValuePair<string, int>>();
		foreach (KeyValuePair<string, int> bossKill in BossKills)
		{
			BossNames.TryGetValue(bossKill.Key, out var value);
			list.Add(new KeyValuePair<string, int>(value ?? bossKill.Key, bossKill.Value));
		}
		list.Sort((KeyValuePair<string, int> a, KeyValuePair<string, int> b) => b.Value.CompareTo(a.Value));
		for (int num = 0; num < list.Count && num < 16; num++)
		{
			sagaStats.Bosses.Add(list[num]);
		}
		return sagaStats;
	}

	private static void Top(List<Hero> hs, List<KeyValuePair<string, int>> into, Func<Hero, int> value)
	{
		hs.Sort((Hero a, Hero b) => value(b).CompareTo(value(a)));
		foreach (Hero h in hs)
		{
			if (into.Count < 5 && value(h) > 0)
			{
				into.Add(new KeyValuePair<string, int>(h.Name, value(h)));
				continue;
			}
			break;
		}
	}

	public static void RPC_Admin(long sender, string cmd)
	{
		if (!IsServer)
		{
			return;
		}
		EnsureLoaded();
		Rpc.Resolve(sender, out var _, out var name);
		if (!Rpc.IsAdminSender(sender))
		{
			Reply(sender, "Only server admins can use this command.");
			return;
		}
		cmd = (cmd ?? "").Trim();
		string text = cmd;
		string text2 = "";
		int num = cmd.IndexOf(' ');
		if (num > 0)
		{
			text = cmd.Substring(0, num);
			text2 = cmd.Substring(num + 1).Trim();
		}
		switch (text.ToLowerInvariant())
		{
		case "add":
		{
			if (text2.Length == 0)
			{
				Reply(sender, "Usage: saga add <text>");
				break;
			}
			if (text2.Length > 600)
			{
				text2 = text2.Substring(0, 600);
			}
			Entry entry2 = Record("skald", text2, name);
			Plugin.Log.LogInfo((object)("Saga: " + name + " added verse #" + entry2.Id));
			Reply(sender, "Verse #" + entry2.Id + " carved into the saga.");
			break;
		}
		case "del":
		case "delete":
		case "remove":
		{
			if (!long.TryParse(text2.TrimStart('#'), out var id))
			{
				Reply(sender, "Usage: saga del <id>");
				break;
			}
			int num2 = Entries.RemoveAll((Entry x) => x.Id == id);
			if (num2 > 0)
			{
				_dirty = true;
				Plugin.Log.LogInfo((object)("Saga: " + name + " removed verse #" + id));
			}
			Reply(sender, (num2 > 0) ? ("Verse #" + id + " erased from the saga.") : ("No verse #" + id + "."));
			break;
		}
		case "last":
		case "list":
		{
			if (!int.TryParse(text2, out var result))
			{
				result = 10;
			}
			result = Mathf.Clamp(result, 1, 40);
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = Math.Max(0, Entries.Count - result); i < Entries.Count; i++)
			{
				Entry entry = Entries[i];
				stringBuilder.Append('#').Append(entry.Id).Append(" (")
					.Append(entry.Kind)
					.Append(") ")
					.Append(Strip(SagaText.Verse(entry, pl: false)))
					.Append('\n');
			}
			Reply(sender, (stringBuilder.Length > 0) ? stringBuilder.ToString().TrimEnd('\n') : "The saga is still empty.");
			break;
		}
		default:
			Reply(sender, "saga add <text> | saga del <id> | saga last [n]");
			break;
		}
	}

	private static void Reply(long target, string text)
	{
		if (target != 0 && ZRoutedRpc.instance != null)
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(target, "CH_Msg", new object[1] { text });
		}
	}

	private static void PostDiscord(Entry e)
	{
		string text = (Plugin.DiscordWebhook.Value ?? "").Trim();
		if (!text.StartsWith("https://") || (Object)(object)Plugin.Instance == (Object)null)
		{
			return;
		}
		string text2 = e.Category.ToString().ToLowerInvariant();
		bool flag = false;
		string[] array = (Plugin.DiscordCategories.Value ?? "").Split(',');
		foreach (string text3 in array)
		{
			if (text3.Trim().ToLowerInvariant() == text2)
			{
				flag = true;
				break;
			}
		}
		if (flag)
		{
			bool pl = Plugin.DiscordLanguage.Value == SagaLanguage.Polish;
			string item;
			try
			{
				item = "ᚱ **" + Loc.Get(pl, "day", e.Day) + "** · " + Markdown(SagaText.Verse(e, pl));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Saga verse for Discord: " + ex.Message));
				return;
			}
			if (_discord.Count > 50)
			{
				_discord.Dequeue();
			}
			_discord.Enqueue(item);
			if (!_posting)
			{
				((MonoBehaviour)Plugin.Instance).StartCoroutine(PostLoop(text));
			}
		}
	}

	private static IEnumerator PostLoop(string url)
	{
		_posting = true;
		while (_discord.Count > 0)
		{
			string text = _discord.Dequeue();
			string json = "{\"content\":\"" + text.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n") + "\"}";
			UnityWebRequest req = new UnityWebRequest(url, "POST");
			try
			{
				req.uploadHandler = (UploadHandler)new UploadHandlerRaw(Encoding.UTF8.GetBytes(json));
				req.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
				req.SetRequestHeader("Content-Type", "application/json");
				req.timeout = 15;
				yield return req.SendWebRequest();
				if ((int)req.result != 1)
				{
					Plugin.Log.LogWarning((object)("Discord webhook failed: " + req.error));
				}
			}
			finally
			{
				((IDisposable)req)?.Dispose();
			}
			yield return (object)new WaitForSecondsRealtime(1.5f);
		}
		_posting = false;
	}

	public static string Strip(string s)
	{
		return Tags.Replace(s ?? "", "");
	}

	private static string Markdown(string s)
	{
		s = (s ?? "").Replace("<b>", "**").Replace("</b>", "**").Replace("<i>", "*")
			.Replace("</i>", "*");
		return Strip(s);
	}
}
internal static class SagaText
{
	public static readonly string[] CatColor = new string[8] { "#E8D9B0", "#E86A4A", "#A9A2C8", "#7FC7E8", "#F2C14E", "#8FD16A", "#D9A066", "#C8B88A" };

	public static string Verse(Entry e, bool pl)
	{
		Func<string, object[], string> func = (string key, object[] a) => Variant(pl, key, e.Id, a);
		switch (e.Kind)
		{
		case "boss":
		{
			string text2 = Who(Refs.Text(e.Arg(0)));
			string[] array = e.Arg(2).Split(new char[1] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
			int num = Int(e.Arg(3));
			object[] array2 = new object[4]
			{
				text2,
				Names(array, pl),
				num,
				Ord(num, pl)
			};
			if (e.Arg(4) == "1")
			{
				return Loc.Get(pl, "v_boss_first", array2);
			}
			return func((array.Length == 1) ? "v_boss_solo" : "v_boss", array2);
		}
		case "death":
		{
			string text3 = e.Arg(1);
			string text4 = Refs.Text(e.Arg(2));
			object[] arg2 = new object[4]
			{
				Who(e.Arg(0)),
				BiomeIn(e.Arg(4), pl),
				text4,
				Who(e.Arg(3))
			};
			string text5 = "v_d_" + text3;
			if (!Loc.Has(pl, text5 + "_0") || (text3 == "foe" && text4.Length == 0))
			{
				text5 = "v_d_other";
			}
			string text6 = func(text5, arg2);
			if (e.Arg(6) == "1")
			{
				text6 = text6 + " " + Loc.Get(pl, "v_d_milestone", Ord(Int(e.Arg(5)), pl));
			}
			return text6;
		}
		case "biome":
		{
			string text = e.Arg(1);
			object[] arg = new object[3]
			{
				Who(e.Arg(0)),
				BiomeIn(text, pl),
				Loc.Get(pl, "bnom_" + text)
			};
			return func((text == "256") ? "v_biome_ocean" : "v_biome_land", arg);
		}
		case "newcomer":
			return func("v_newcomer", new object[2]
			{
				Who(e.Arg(0)),
				Ord(Int(e.Arg(1)), pl)
			});
		case "raid":
		{
			string text7 = Refs.Text(e.Arg(2));
			if (text7.Length == 0 || text7[0] == '[')
			{
				text7 = e.Arg(1);
			}
			object[] arg3 = new object[3]
			{
				Who(e.Arg(0)),
				BiomeIn(e.Arg(3), pl),
				text7
			};
			return func((e.Arg(0).Length > 0) ? "v_raid" : "v_raid_nobody", arg3);
		}
		case "god_chosen":
		case "god_legend":
			return func("v_" + e.Kind, new object[3]
			{
				Who(e.Arg(0)),
				God(e.Arg(1), pl, genitive: false),
				God(e.Arg(1), pl, genitive: true)
			});
		case "blot_end":
			return func("v_blot_end", new object[3]
			{
				God(e.Arg(0), pl, genitive: false),
				God(e.Arg(0), pl, genitive: true),
				e.Arg(1)
			});
		case "blot_failed":
			return func("v_blot_failed", new object[4]
			{
				God(e.Arg(0), pl, genitive: false),
				God(e.Arg(0), pl, genitive: true),
				e.Arg(1),
				e.Arg(2)
			});
		case "guild_found":
		case "guild_banner":
			return func("v_" + e.Kind, new object[3]
			{
				Who(e.Arg(0)),
				e.Arg(1),
				e.Arg(2)
			});
		case "guild_level":
		case "guild_disband":
			return func("v_" + e.Kind, new object[3]
			{
				e.Arg(0),
				e.Arg(1),
				e.Arg(2)
			});
		case "guild_ach":
			return func("v_guild_ach", new object