Decompiled source of Odins Saga v0.1.2

plugins/OdinsSaga.dll

Decompiled 16 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("OdinsSaga")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.2.0")]
[assembly: AssemblyInformationalVersion("0.1.2+b491b4185a5d52ac2e4408ac75db5306ab474b7e")]
[assembly: AssemblyProduct("OdinsSaga")]
[assembly: AssemblyTitle("OdinsSaga")]
[assembly: AssemblyVersion("0.1.2.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace OdinsSaga
{
	internal class DeedBook
	{
		internal readonly Dictionary<string, Dictionary<string, double>> Counters = new Dictionary<string, Dictionary<string, double>>();

		internal readonly Dictionary<string, string> Notes = new Dictionary<string, string>();

		internal bool IsEmpty => Counters.Count == 0;

		internal double Get(string cat, string name = "")
		{
			if (!Counters.TryGetValue(cat, out var value) || !value.TryGetValue(name, out var value2))
			{
				return 0.0;
			}
			return value2;
		}

		internal bool Has(string cat, string name = "")
		{
			if (Counters.TryGetValue(cat, out var value))
			{
				return value.ContainsKey(name);
			}
			return false;
		}

		internal void Add(string cat, string name, double amount)
		{
			Dictionary<string, double> dictionary = Names(cat);
			dictionary.TryGetValue(name, out var value);
			dictionary[name] = value + amount;
		}

		internal void Set(string cat, string name, double value)
		{
			Names(cat)[name] = value;
		}

		internal List<KeyValuePair<string, double>> Ranked(string cat)
		{
			Dictionary<string, double> value;
			List<KeyValuePair<string, double>> obj = (Counters.TryGetValue(cat, out value) ? new List<KeyValuePair<string, double>>(value) : new List<KeyValuePair<string, double>>());
			obj.Sort((KeyValuePair<string, double> a, KeyValuePair<string, double> b) => b.Value.CompareTo(a.Value));
			return obj;
		}

		internal double Total(string cat)
		{
			double num = 0.0;
			if (Counters.TryGetValue(cat, out var value))
			{
				foreach (double value2 in value.Values)
				{
					num += value2;
				}
			}
			return num;
		}

		internal void Clear()
		{
			Counters.Clear();
			Notes.Clear();
		}

		private Dictionary<string, double> Names(string cat)
		{
			if (!Counters.TryGetValue(cat, out var value))
			{
				value = (Counters[cat] = new Dictionary<string, double>());
			}
			return value;
		}
	}
	internal class SagaLine
	{
		internal string Fact;

		internal string Remark;
	}
	internal class SagaRecord
	{
		internal string Title;

		internal int Day;

		internal string Reason;

		internal readonly List<SagaLine> Lines = new List<SagaLine>();

		internal string Closing;
	}
	internal static class Chronicle
	{
		private const string DataKey = "OdinsSaga";

		internal static readonly DeedBook Today = new DeedBook();

		internal static readonly DeedBook Life = new DeedBook();

		internal static readonly List<SagaRecord> History = new List<SagaRecord>();

		private static long _boundId;

		internal static bool Ready
		{
			get
			{
				if (_boundId != 0L && (Object)(object)Player.m_localPlayer != (Object)null)
				{
					return Player.m_localPlayer.GetPlayerID() == _boundId;
				}
				return false;
			}
		}

		internal static void Bind(Player player)
		{
			if ((Object)(object)player == (Object)null)
			{
				return;
			}
			long playerID = player.GetPlayerID();
			if (playerID == 0L || playerID == _boundId)
			{
				return;
			}
			_boundId = playerID;
			Today.Clear();
			Life.Clear();
			History.Clear();
			if (!player.m_customData.TryGetValue("OdinsSaga", out var value))
			{
				return;
			}
			try
			{
				Read(value);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Couldn't read this character's saga, starting a fresh one: " + ex.Message));
			}
		}

		internal static void Add(string cat, string name, double amount = 1.0)
		{
			if (Ready && !(amount <= 0.0))
			{
				Today.Add(cat, name ?? "", amount);
				Life.Add(cat, name ?? "", amount);
			}
		}

		internal static void Lowest(string cat, double value, string cause)
		{
			if (!Ready)
			{
				return;
			}
			DeedBook[] array = new DeedBook[2] { Today, Life };
			foreach (DeedBook deedBook in array)
			{
				if (!deedBook.Has(cat) || value < deedBook.Get(cat))
				{
					deedBook.Set(cat, "", value);
					deedBook.Notes[cat] = cause ?? "";
				}
			}
		}

		internal static SagaRecord CloseDay(string reason)
		{
			if (!Ready)
			{
				return null;
			}
			SagaRecord sagaRecord = (Today.IsEmpty ? null : SagaWriter.Write(Today, Life, reason));
			if (sagaRecord != null)
			{
				History.Insert(0, sagaRecord);
				Trim();
				Life.Add("sagas", "", 1.0);
			}
			Today.Clear();
			return sagaRecord;
		}

		internal static void Trim()
		{
			int value = Plugin.HistorySize.Value;
			if (History.Count > value)
			{
				History.RemoveRange(value, History.Count - value);
			}
		}

		internal static void SaveTo(Player player)
		{
			if (!((Object)(object)player == (Object)null) && _boundId != 0L && player.GetPlayerID() == _boundId)
			{
				player.m_customData["OdinsSaga"] = Write();
			}
		}

		private static string Write()
		{
			StringBuilder stringBuilder = new StringBuilder("v1\n");
			WriteBook(stringBuilder, "T", Today);
			WriteBook(stringBuilder, "L", Life);
			Trim();
			foreach (SagaRecord item in History)
			{
				stringBuilder.Append("H|").Append(item.Day).Append('|')
					.Append(Esc(item.Title))
					.Append('|')
					.Append(Esc(item.Reason))
					.Append('|')
					.Append(Esc(item.Closing))
					.Append('\n');
				foreach (SagaLine line in item.Lines)
				{
					stringBuilder.Append("HL|").Append(Esc(line.Fact)).Append('|')
						.Append(Esc(line.Remark))
						.Append('\n');
				}
			}
			return stringBuilder.ToString();
		}

		private static void WriteBook(StringBuilder sb, string tag, DeedBook book)
		{
			foreach (KeyValuePair<string, Dictionary<string, double>> counter in book.Counters)
			{
				foreach (KeyValuePair<string, double> item in counter.Value)
				{
					sb.Append(tag).Append('|').Append(Esc(counter.Key))
						.Append('|')
						.Append(Esc(item.Key))
						.Append('|')
						.Append(item.Value.ToString("R", CultureInfo.InvariantCulture))
						.Append('\n');
				}
			}
			foreach (KeyValuePair<string, string> note in book.Notes)
			{
				sb.Append(tag).Append("N|").Append(Esc(note.Key))
					.Append('|')
					.Append(Esc(note.Value))
					.Append('\n');
			}
		}

		private static void Read(string data)
		{
			SagaRecord sagaRecord = null;
			string[] array = data.Split(new char[1] { '\n' });
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Split(new char[1] { '|' });
				switch (array2[0])
				{
				case "T":
					if (array2.Length == 4)
					{
						ReadValue(Today, array2);
					}
					break;
				case "L":
					if (array2.Length == 4)
					{
						ReadValue(Life, array2);
					}
					break;
				case "TN":
					if (array2.Length == 3)
					{
						Today.Notes[Un(array2[1])] = Un(array2[2]);
					}
					break;
				case "LN":
					if (array2.Length == 3)
					{
						Life.Notes[Un(array2[1])] = Un(array2[2]);
					}
					break;
				case "H":
					if (array2.Length == 5)
					{
						sagaRecord = new SagaRecord
						{
							Title = Un(array2[2]),
							Reason = Un(array2[3]),
							Closing = Un(array2[4])
						};
						int.TryParse(array2[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out sagaRecord.Day);
						History.Add(sagaRecord);
					}
					break;
				case "HL":
					if (array2.Length == 3)
					{
						sagaRecord?.Lines.Add(new SagaLine
						{
							Fact = Un(array2[1]),
							Remark = Un(array2[2])
						});
					}
					break;
				}
			}
		}

		private static void ReadValue(DeedBook book, string[] f)
		{
			if (double.TryParse(f[3], NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				book.Set(Un(f[1]), Un(f[2]), result);
			}
		}

		private static string Esc(string s)
		{
			return Uri.EscapeDataString(s ?? "");
		}

		private static string Un(string s)
		{
			return Uri.UnescapeDataString(s);
		}
	}
	internal static class OdinText
	{
		internal static readonly Dictionary<string, string[]> Titles = new Dictionary<string, string[]>
		{
			["kill"] = new string[8] { "The Saga of Too Many {names}", "The Saga of {name}-Bane", "The Saga of the {name} Problem", "The Saga of the {name} Grudge", "The Saga of the Short-Lived {names}", "The Saga of the Unlucky {names}", "The Saga of {name}-Smiter", "The Saga of Fewer {names}" },
			["boss"] = new string[5] { "The Saga of {name}'s Last Day", "The Saga of the Fallen {name}", "The Saga of the Big One", "The Saga of the Trophy on the Wall", "The Saga of {name}, Briefly" },
			["firstkill"] = new string[3] { "The Saga of the First {name}", "The Saga of New Acquaintances", "The Saga of the Unfamiliar Foe" },
			["milestone"] = new string[3] { "The Saga of the Round Number", "The Saga of the Long Tally", "The Saga of the Notched Axe" },
			["death"] = new string[6] { "The Saga of the Short Respawn", "The Saga of Getting Back Up", "The Saga of the Corpse Run", "The Saga of the Familiar Tombstone", "The Saga of Valhalla's Waiting Room", "The Saga of Almost, Again" },
			["deathweak"] = new string[3] { "The Saga of the Embarrassing Ending", "The Saga We Will Not Speak Of", "The Saga of the {name} Incident" },
			["~fall"] = new string[3] { "The Saga of the Sudden Ground", "The Saga of the Short Way Down", "The Saga of Gravity's Win" },
			["~drowning"] = new string[3] { "The Saga of the Wet Ending", "The Saga of the Unswum Sea", "The Saga of Too Much Water" },
			["~tree"] = new string[2] { "The Saga of the Tree That Fought Back", "The Saga of Timber!" },
			["close"] = new string[4] { "The Saga of the Narrow Escape", "The Saga of the Last Sliver", "The Saga of Not Today", "The Saga of the Thin Red Bar" },
			["lowkill"] = new string[3] { "The Saga of the Last Stand", "The Saga of the Desperate Blow", "The Saga of One Hit Left" },
			["walk"] = new string[4] { "The Saga of the Muddy Boots", "The Saga of the Long Road", "The Saga of the Sore Feet", "The Saga of the Wandering Viking" },
			["sail"] = new string[4] { "The Saga of the Salt Beard", "The Saga of the Open Sea", "The Saga of the Creaking Hull", "The Saga of Fair Winds, Mostly" },
			["swim"] = new string[3] { "The Saga of the Soggy Beard", "The Saga of the Forgotten Boat", "The Saga of the Wet Viking" },
			["ride"] = new string[2] { "The Saga of the Patient Mount", "The Saga of the Borrowed Legs" },
			["trees"] = new string[4] { "The Saga of the Thinning Forest", "The Saga of the Busy Axe", "The Saga of the Angry Greydwarves", "The Saga of Splinters" },
			["rocks"] = new string[3] { "The Saga of the Heavy Pick", "The Saga of the Broken Hill", "The Saga of Gravel" },
			["haul"] = new string[3] { "The Saga of the Heavy Pockets", "The Saga of the Overloaded Back", "The Saga of the Groaning Cart" },
			["harvest"] = new string[2] { "The Saga of the Full Basket", "The Saga of the Patient Farmer" },
			["fish"] = new string[2] { "The Saga of the One That Didn't Get Away", "The Saga of the Quiet Line" },
			["build"] = new string[4] { "The Saga of the Rising Hall", "The Saga of the Hammer and the Plan", "The Saga of One More Wall", "The Saga of the Leaning Roof" },
			["unbuild"] = new string[2] { "The Saga of Second Thoughts", "The Saga of the Demolition" },
			["craft"] = new string[3] { "The Saga of the Busy Bench", "The Saga of the Glowing Forge", "The Saga of Sawdust and Sparks" },
			["food"] = new string[3] { "The Saga of the Full Belly", "The Saga of the Second Helping", "The Saga of the Long Table" },
			["skill"] = new string[3] { "The Saga of the Sharpening", "The Saga of Practice", "The Saga of Getting Better at Things" },
			["biome"] = new string[3] { "The Saga of the Unmapped Lands", "The Saga of the Far Horizon", "The Saga of New Ground" },
			["oddity"] = new string[3] { "The Saga of Small Things", "The Saga of an Odd Sort of Day", "The Saga Nobody Asked For" },
			["quiet"] = new string[3] { "The Saga of Not Much, Really", "The Saga of the Quiet Day", "The Very Short Saga" }
		};

		internal static readonly string[] KillOneFact = new string[3] { "Slew {a}.", "Put down {a}.", "Ended {a}." };

		internal static readonly string[] KillManyFact = new string[3] { "Slew {n} {names}.", "Felled {n} {names}.", "Sent {n} {names} on their way." };

		internal static readonly string[] KillOne = new string[6] { "It had it coming.", "Odin saw. Odin nodded.", "One less in the world. The world is fine with it.", "Clean work.", "The ravens noted it. Muninn wants it on a tapestry.", "It will not be missed. Much." };

		internal static readonly string[] KillSome = new string[7] { "Their mothers have been notified.", "Word gets around.", "A respectable pile.", "Their kind is drafting a strongly worded letter.", "Huginn lost count. Muninn didn't.", "The skalds are warming up their voices.", "A good day for you. Less so for them." };

		internal static readonly string[] KillMany = new string[7] { "At this point it's a hobby.", "The survivors are holding a meeting about you.", "Odin is starting to feel sorry for them. Starting.", "Their population graph has a dent in it.", "This is no longer a fight. It's pest control.", "Some of them had families. Had.", "Valhalla's back door is jammed with them." };

		internal static readonly string[] KillKinds = new string[4] { "A varied diet of violence.", "Something for everyone.", "Odin admires the range.", "You do not discriminate. Admirable, in a way." };

		internal static readonly string[] KillKindsFact = new string[1] { "Slew {n} creatures of {kinds} kinds." };

		internal static readonly Dictionary<string, string[]> CreatureRemarks = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
		{
			["$enemy_greydwarf"] = new string[2] { "There are always more. There are always, always more.", "The forest keeps sending them. You keep sending them back." },
			["$enemy_neck"] = new string[2] { "All neck, no chance.", "The Necks have stopped coming to the lake." },
			["$enemy_troll"] = new string[2] { "The skalds will need a bigger song.", "Odin had a file on that Troll. Closed now." },
			["$enemy_deer"] = new string[2] { "Dinner, several times over.", "The deer have started grazing in pairs, for safety." },
			["$enemy_boar"] = new string[2] { "Bacon, with fewer opinions now.", "The smell of cooking pork follows you around." },
			["$enemy_greyling"] = new string[2] { "It was rude. Now it's quiet.", "Not a threat. Not anymore." },
			["$enemy_draugr"] = new string[2] { "They were dead already. Now they're more dead.", "Bonemass is short a few employees." },
			["$enemy_leech"] = new string[2] { "They wanted a drink. They got a lesson.", "The swamp is slightly less itchy." },
			["$enemy_wolf"] = new string[2] { "Not your good boys.", "The howling has gone a bit quieter." },
			["$enemy_drake"] = new string[2] { "Fewer things spitting ice at you. Good.", "The mountain is a little less shouty." },
			["$enemy_goblin"] = new string[2] { "Small, loud, gone.", "The Fuling villages are posting your likeness on trees." },
			["$enemy_deathsquito"] = new string[2] { "Odin regrets that one. You fixed it.", "Every one swatted is a gift to the Plains." },
			["$enemy_seeker"] = new string[2] { "Too many legs. Now none.", "The Mistlands buzz a little less." },
			["$enemy_serpent"] = new string[2] { "The sea is a little safer and a lot more confused.", "Fish for a week." },
			["$enemy_blob"] = new string[2] { "Hope you didn't lick it.", "Squelched." },
			["$enemy_skeleton"] = new string[2] { "Rattled. Permanently.", "Bones go back in the ground. That's where they live." },
			["$enemy_ghost"] = new string[2] { "Boo yourself.", "It was already a ghost. Odin isn't sure what it is now." },
			["$enemy_surtling"] = new string[2] { "Free cores. Mind the beard.", "Put out, in a manner of speaking." },
			["$enemy_abomination"] = new string[2] { "That was not a tree.", "The swamp has one fewer surprise." },
			["$enemy_lox"] = new string[2] { "Gentle giant, briefly not.", "That is a lot of meat to carry home." },
			["$enemy_tick"] = new string[2] { "Small, thirsty, finished.", "You will be checking your armour for a week." }
		};

		internal static readonly int[] KillMilestones = new int[10] { 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000 };

		internal static readonly Dictionary<int, string[]> KillMilestoneLines = new Dictionary<int, string[]>
		{
			[10] = new string[2] { "Your 10th {name}. Word is getting around.", "{name} number 10. That's a habit forming." },
			[25] = new string[2] { "Your 25th {name}. You could start a collection. You have.", "25 {names} so far. The tally stick needs a second side." },
			[50] = new string[2] { "Your 50th {name}. Fifty. The skalds had to buy more parchment.", "50 {names}. Their elders now use you to scare the young." },
			[100] = new string[2] { "Your 100th {name}. Their kind now tells stories about you.", "100 {names}. Odin had a horn of mead in your honour." },
			[250] = new string[2] { "Your 250th {name}. The {names} have a word for you. It's not polite.", "250 {names}. A quarter of a thousand. Odin did the maths twice." },
			[500] = new string[2] { "Your 500th {name}. Half a thousand. The ravens needed a nap.", "500 {names}. There may be a {name} shortage." },
			[1000] = new string[2] { "Your 1000th {name}. This is no longer a hunt. It's a career.", "1000 {names}. Odin has commissioned a runestone. It's a big one." },
			[2500] = new string[2] { "Your 2500th {name}. Odin is genuinely concerned now.", "2500 {names}. Is everything all right at home?" },
			[5000] = new string[2] { "Your 5000th {name}. The {names} have filed for extinction.", "5000 {names}. The tally stick is now a tally tree." },
			[10000] = new string[2] { "Your 10,000th {name}. Even Odin doesn't know what to say. He's saying it anyway.", "10,000 {names}. Ragnarök is jealous." }
		};

		internal static readonly string[] FirstKill = new string[4] { "Met your first {name}. It went better for you.", "Your first {name}. Many more to come, Odin suspects.", "First {name} down. Now you know what it drops.", "A first {name}. Odin opened a new page for it." };

		internal static readonly string[] BossFact = new string[3] { "Felled {name}.", "Brought down {name}.", "Defeated {name}." };

		internal static readonly string[] BossRemark = new string[5] { "The skalds are already arguing about the details.", "Hang the trophy somewhere everyone can see it. Odin will.", "Another of the Forsaken returns to the dark. Odin approves.", "That one will be sung about. Loudly. Badly. For years.", "The ravens flew straight home to tell Odin. He knew. He wanted to hear it anyway." };

		internal static readonly string[] BossAgain = new string[2] { "Again. Some people collect stamps.", "Again? It's starting to feel personal." };

		internal static readonly string[] DeathOnceFact = new string[1] { "Died once, to {cause}." };

		internal static readonly string[] DeathManyFact = new string[2] { "Died {n} times, mostly to {cause}.", "Died {n} times. {Cause} did most of it." };

		internal static readonly string[] DeathOnce = new string[5] { "Valhalla had the bench warm. Not yet.", "Happens to the best of us. Happened to you.", "Odin has seen worse. Not often.", "Your tombstone is getting a lot of visitors.", "The Valkyries said you were 'adorable'. Not a compliment." };

		internal static readonly string[] DeathMany = new string[5] { "Valhalla is holding your seat, but it's getting awkward.", "The Valkyries have stopped coming. They just wave now.", "Hel asked if you're doing this on purpose.", "Your tombstones are forming a village.", "Odin has started a separate scroll for this." };

		internal static readonly string[] DeathWeak = new string[5] { "{A}. A <i>{name}</i>. Odin has questions.", "Of all things. {A}.", "Odin is not angry. Odin is disappointed.", "The skalds have agreed to leave this one out. For a price.", "The {name} is telling everyone. Everyone." };

		internal static readonly Dictionary<string, string[]> DeathByCause = new Dictionary<string, string[]>
		{
			["~fall"] = new string[3] { "Discovered that the ground is, in fact, hard.", "The fall was fine. It was the stopping.", "Vikings can't fly. Now you know." },
			["~drowning"] = new string[3] { "Vikings sink. Write that down.", "Forgot the boat. The sea didn't.", "Ran out of breath, and then of everything else." },
			["~burning"] = new string[2] { "Well done. Not the good kind.", "Fire: warm, bright, and as it turns out, fatal." },
			["~freezing"] = new string[2] { "Should have packed the cape.", "Frozen solid. A very short ice sculpture." },
			["~poisoned"] = new string[2] { "That was not a healthy colour.", "Poison. Odin suggests the mead next time." },
			["~tree"] = new string[2] { "Felled by a tree you were felling. The forest calls it even.", "Timber! Too late." },
			["~self"] = new string[2] { "Killed by your own hand. Impressive, in a way.", "Odin is still working out how you did that." },
			["~structural"] = new string[2] { "Your own building did this. Check the supports.", "Death by architecture." },
			["~smoke"] = new string[2] { "A chimney would have helped.", "Smoked like a herring." },
			["~cart"] = new string[2] { "Killed by a cart. Odin has no further comment.", "Run down by your own luggage." },
			["~edgeofworld"] = new string[1] { "Found the edge of the world. It found you back." },
			["~ashlandslava"] = new string[2] { "That was lava. The orange stuff. Yes, that.", "Briefly very warm." },
			["~ashlandsocean"] = new string[1] { "The sea there boils. You found out." }
		};

		internal static readonly string[] NoDeaths = new string[4] { "Hel is disappointed. Odin is not.", "Not a scratch that counted.", "The Valkyries had the day off.", "Your tombstone count holds steady." };

		internal static readonly string[] NoDeathsFact = new string[2] { "Didn't die once.", "Died zero times." };

		internal static readonly int[] DeathMilestones = new int[6] { 10, 25, 50, 100, 250, 500 };

		internal static readonly string[] DeathMilestone = new string[3] { "That makes {n} deaths for life. Valhalla is naming a bench after you.", "{n} deaths, all told. Death is starting to recognise your face.", "Death number {n}. Hel sends a card." };

		internal static readonly string[] CloseFact = new string[2] { "Survived {cause} with {hp} HP left.", "Walked away from {cause} on {hp} HP." };

		internal static readonly string[] Close = new string[5] { "Barely counts as left.", "Odin held his breath. Odin doesn't need to breathe.", "The Valkyries were already circling.", "A sliver of red and pure stubbornness.", "Valhalla had your name on a cup. It's back in the cupboard." };

		internal static readonly string[] LowKillFact = new string[2] { "Slew {a} at {hp} HP.", "Finished {a} with {hp} HP to your name." };

		internal static readonly string[] LowKill = new string[4] { "The skalds will exaggerate this, as is tradition.", "That's the kind of thing that gets a song.", "Odin stood up for that one.", "Both of you were nearly dead. Only one of you is." };

		internal static readonly string[] WalkFact = new string[2] { "Walked {km}.", "Covered {km} on foot." };

		internal static readonly string[] Walk = new string[5] { "Your boots would like a word.", "The paths know your footsteps now.", "Somewhere, a cobbler smiles.", "Most of it uphill. Both ways.", "Odin suggests a cart. Or a portal. Or a nap." };

		internal static readonly string[] SailFact = new string[2] { "Sailed {km}.", "Put {km} of sea behind you." };

		internal static readonly string[] Sail = new string[5] { "The sea let you go. This time.", "Salt in the beard, wind in the sail.", "Njord sends his regards.", "The serpents watched. The serpents always watch.", "Not one seasick moment. Odin is lying." };

		internal static readonly string[] SwimFact = new string[2] { "Swam {km}.", "Paddled {km}." };

		internal static readonly string[] Swim = new string[3] { "Boats exist, you know.", "Fish think you're very strange.", "Wet. Very wet. Commendably wet." };

		internal static readonly string[] RideFact = new string[1] { "Rode {km}." };

		internal static readonly string[] Ride = new string[2] { "Your mount did most of the work.", "Saddle sore, but proud." };

		internal static readonly int[] WalkMilestonesKm = new int[6] { 10, 50, 100, 250, 500, 1000 };

		internal static readonly string[] WalkMilestone = new string[2] { "That's {n} km walked for life. Odin's knees hurt just thinking about it.", "{n} km on foot, all told. The world is getting smaller." };

		internal static readonly string[] TreesFact = new string[2] { "Felled {n} trees.", "Brought down {n} trees." };

		internal static readonly string[] Trees = new string[6] { "The forest has filed a complaint.", "The Greydwarves have noticed. They are not pleased.", "Timber! (x{n})", "Somewhere, an Elder grinds his roots.", "Odin smells the sawdust from here.", "Fewer trees. More house." };

		internal static readonly int[] TreeMilestones = new int[6] { 100, 500, 1000, 2500, 5000, 10000 };

		internal static readonly string[] TreeMilestone = new string[2] { "That's {n} trees for life. The forest has hired a lawyer.", "{n} trees, all told. There is a small desert where a wood used to be." };

		internal static readonly string[] RocksFact = new string[2] { "Broke {n} rocks and veins.", "Smashed {n} rocks." };

		internal static readonly string[] Rocks = new string[3] { "The hills are a little lumpier.", "Your pickaxe is tired. So is Odin.", "Every swing a small earthquake." };

		internal static readonly string[] HaulFact = new string[2] { "Hauled {n} {name}.", "Carried home {n} {name}." };

		internal static readonly string[] HaulOre = new string[4] { "The smelter is hungry, and so is your back.", "Heavy. Shiny. Not teleportable. Classic.", "Ore, the reason portals have rules.", "Your back has filed a complaint too." };

		internal static readonly string[] Haul = new string[3] { "Your pockets are full and your back is unhappy.", "Odin admires a full inventory.", "Every chest has a story. This one is heavy." };

		internal static readonly string[] HarvestFact = new string[2] { "Harvested {n} crops and berries.", "Gathered {n} things from the earth." };

		internal static readonly string[] Harvest = new string[3] { "Farmer of the year.", "The earth gives, you take. That's the deal.", "Odin likes a well-kept field." };

		internal static readonly string[] FishFact = new string[2] { "Caught {n} fish.", "Landed {n} fish." };

		internal static readonly string[] Fish = new string[3] { "The fish caught nothing. Fair's fair.", "Patience is a weapon too.", "Smells like victory. And fish." };

		internal static readonly string[] BuildFact = new string[2] { "Built {n} pieces, {top} of them {name}.", "Raised {n} pieces. Favourite: {name}." };

		internal static readonly string[] Build = new string[5] { "Odin respects a wall.", "It will stand. Probably. Check the supports.", "A hall worthy of the name. Getting there.", "Huginn perched on it. It held.", "The troll that knocks this down will need a run-up." };

		internal static readonly string[] UnbuildFact = new string[2] { "Tore down {n} pieces.", "Demolished {n} pieces of your own work." };

		internal static readonly string[] Unbuild = new string[3] { "Odin calls that 'iterating'.", "Measure twice, hammer once. Or many times.", "The building was fine. You wanted it to be better." };

		internal static readonly int[] BuildMilestones = new int[5] { 100, 500, 1000, 5000, 10000 };

		internal static readonly string[] BuildMilestone = new string[2] { "That's {n} pieces built for life. A town, more or less.", "{n} pieces, all told. Odin is drafting a planning permit." };

		internal static readonly string[] CraftFact = new string[2] { "Crafted {n} things, including {a}.", "Made {n} things at the bench and fire." };

		internal static readonly string[] Craft = new string[3] { "The bench is warm and the forge is warmer.", "Fine work. Fine-ish.", "Sawdust everywhere. Odin approves." };

		internal static readonly string[] BurntFact = new string[1] { "Burnt {n} things on the grill." };

		internal static readonly string[] Burnt = new string[2] { "Charcoal is technically food. It is not.", "The smoke alarm hasn't been invented. You'd have set it off." };

		internal static readonly string[] FoodFact = new string[2] { "Ate {n} meals. Favourite: {name}.", "Put away {n} meals, mostly {name}." };

		internal static readonly string[] Food = new string[4] { "A Viking marches on the belly.", "Odin would have had seconds too.", "The kitchen is a battlefield of its own.", "Belly full, heart fuller." };

		internal static readonly string[] SkillFact = new string[2] { "Gained {n} skill levels, most in {name}.", "Got better at things {n} times. Mostly {name}." };

		internal static readonly string[] SkillOneFact = new string[2] { "Got better at {name}.", "Grew one level in {name}." };

		internal static readonly string[] Skill = new string[4] { "Practice, it seems, works.", "The hands remember.", "Odin noticed. Odin always notices.", "A little sharper every day." };

		internal static readonly string[] SkillReached = new string[2] { "Reached {name} {n}. Well practised.", "{name} is now {n}. The ravens nod." };

		internal static readonly string[] SkillMax = new string[1] { "{name} 100. There is nothing left to teach you. Odin is checking." };

		internal static readonly string[] BiomeFact = new string[2] { "Set foot in {list} for the first time.", "Found {list}." };

		internal static readonly string[] Biome = new string[4] { "It did not roll out a welcome mat.", "New land, new ways to die. Odin is excited.", "The map gets bigger. So do the problems.", "Muninn drew it on the map. Badly." };

		internal static readonly string[] PortalFact = new string[1] { "Stepped through {n} portals." };

		internal static readonly string[] Portal = new string[3] { "Walking is for people without portals.", "Your atoms are slightly confused.", "Somewhere, a pile of ore is jealous." };

		internal static readonly string[] TreasureFact = new string[1] { "Dug up or found {n} treasures." };

		internal static readonly string[] Treasure = new string[2] { "Finders keepers. Odin wrote that rule.", "Someone hid that. Not well enough." };

		internal static readonly string[] JumpsFact = new string[1] { "Jumped {n} times." };

		internal static readonly string[] Jumps = new string[3] { "The ground was fine, you know.", "Very bouncy. Odin is mildly concerned.", "Frogs are taking notes." };

		internal static readonly string[] DoorsFact = new string[1] { "Opened {n} doors and closed {closed}." };

		internal static readonly string[] Doors = new string[2] { "Were you born in a barn? Don't answer that.", "The heat is escaping. Odin feels it from Asgard." };

		internal static readonly string[] PetsFact = new string[1] { "Petted your animals {n} times." };

		internal static readonly string[] Pets = new string[3] { "Good. They deserved it.", "The best use of hands yet.", "Odin pets Huginn sometimes. Don't tell Muninn." };

		internal static readonly string[] TamedFact = new string[1] { "Tamed {n} creatures." };

		internal static readonly string[] Tamed = new string[2] { "Odin calls that diplomacy.", "A friend for life, or until dinner." };

		internal static readonly string[] HoneyFact = new string[1] { "Robbed the bees {n} times." };

		internal static readonly string[] Honey = new string[2] { "They'll remember.", "Sweet crime." };

		internal static readonly string[] ArrowsFact = new string[1] { "Loosed {n} arrows." };

		internal static readonly string[] Arrows = new string[2] { "Some of them hit things.", "The fletcher thanks you for your business." };

		internal static readonly string[] GravesFact = new string[1] { "Collected your own belongings from {n} graves." };

		internal static readonly string[] Graves = new string[2] { "Tidy.", "Nothing left behind. Except dignity." };

		internal static readonly string[] LeviathanFact = new string[1] { "Felt a leviathan sink beneath you." };

		internal static readonly string[] Leviathan = new string[1] { "Should have mined faster." };

		internal static readonly string[] Quiet = new string[4] { "Nothing worth a verse today. The skalds are on a break.", "A quiet day. Odin approves of naps.", "The ravens came back with nothing to report. They were glad of the rest.", "Some days are for living, not for sagas." };

		internal static readonly string[] ClosingSleep = new string[5] { "Sleep well. The ravens keep watch.", "So it is written. Rest now.", "Huginn saw it. Muninn will remember it.", "Another day in the saga. Tomorrow, another.", "Odin closes the book. Gently. For once." };

		internal static readonly string[] ClosingLogout = new string[4] { "Go, then. The saga keeps.", "Odin will hold your place.", "The ravens will tell no one. They will tell everyone.", "Come back soon. The mead won't drink itself. It will try." };

		internal static readonly string[] DeedsRemark = new string[4] { "Odin keeps a scroll on you. It's getting long.", "Every deed counted. Some of them twice, to be safe.", "The ravens have been busy. So have you.", "Read it and feel proud. Or not. Odin won't judge. Much." };
	}
	[BepInPlugin("hevyh2o.valheim.odinssaga", "Odin's Saga", "0.1.2")]
	public class Plugin : BaseUnityPlugin
	{
		internal enum ShareChannel
		{
			Shout,
			Say
		}

		public const string Guid = "hevyh2o.valheim.odinssaga";

		public const string Name = "Odin's Saga";

		public const string Version = "0.1.2";

		internal static ManualLogSource Log;

		internal static ConfigEntry<KeyboardShortcut> DeedsKey;

		internal static ConfigEntry<KeyboardShortcut> PastSagaKey;

		internal static ConfigEntry<float> ShowSeconds;

		internal static ConfigEntry<float> FadeInSeconds;

		internal static ConfigEntry<float> FadeOutSeconds;

		internal static ConfigEntry<float> PositionX;

		internal static ConfigEntry<float> PositionY;

		internal static ConfigEntry<bool> ShowOnSleep;

		internal static ConfigEntry<bool> ShowOnLogout;

		internal static ConfigEntry<float> LogoutSeconds;

		internal static ConfigEntry<int> SagaLines;

		internal static ConfigEntry<bool> ShowFlavor;

		internal static ConfigEntry<int> HistorySize;

		internal static ConfigEntry<ShareChannel> ShareTo;

		internal static ConfigEntry<bool> TrackKills;

		internal static ConfigEntry<bool> TrackDeaths;

		internal static ConfigEntry<bool> TrackCloseCalls;

		internal static ConfigEntry<bool> TrackTravel;

		internal static ConfigEntry<bool> TrackGathering;

		internal static ConfigEntry<bool> TrackBuilding;

		internal static ConfigEntry<bool> TrackFood;

		internal static ConfigEntry<bool> TrackSkills;

		internal static ConfigEntry<bool> TrackExploration;

		internal static ConfigEntry<bool> TrackOddities;

		private Harmony _harmony;

		private SagaPanel _panel;

		private void Awake()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Expected O, but got Unknown
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Expected O, but got Unknown
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Expected O, but got Unknown
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0159: Expected O, but got Unknown
			//IL_018c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0196: Expected O, but got Unknown
			//IL_0209: Unknown result type (might be due to invalid IL or missing references)
			//IL_0213: Expected O, but got Unknown
			//IL_023b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0245: Expected O, but got Unknown
			//IL_028e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0298: Expected O, but got Unknown
			//IL_0403: Unknown result type (might be due to invalid IL or missing references)
			//IL_040d: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			DeedsKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Display", "DeedsKey", new KeyboardShortcut((KeyCode)106, Array.Empty<KeyCode>()), "Key that opens your lifetime Deeds page. Press again to close it early, or while it's fading to bring it back.");
			PastSagaKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Display", "PastSagaKey", new KeyboardShortcut((KeyCode)106, (KeyCode[])(object)new KeyCode[1] { (KeyCode)304 }), "Key that reads back your past sagas. The first press shows the latest; each press after that goes one further back.");
			ShowSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "ShowSeconds", 20f, new ConfigDescription("How long a saga or the Deeds page stays fully visible before it starts to fade.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(3f, 120f), Array.Empty<object>()));
			FadeInSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "FadeInSeconds", 0.3f, new ConfigDescription("How long the page takes to fade in. Also used when closing it with the key. 0 = instant.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>()));
			FadeOutSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "FadeOutSeconds", 5f, new ConfigDescription("How long the page takes to fade away once ShowSeconds is up. 0 = vanish at once.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>()));
			PositionX = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "PositionX", 0.5f, new ConfigDescription("Horizontal position of the page, 0 = left edge, 1 = right edge.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			PositionY = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "PositionY", 0.6f, new ConfigDescription("Vertical position of the page, 0 = bottom, 1 = top.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			ShowOnSleep = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowOnSleep", true, "Odin reads you the day's saga when you wake up in a bed.");
			ShowOnLogout = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowOnLogout", true, "Odin reads you the saga when you log out or quit from the menu, holding the door for LogoutSeconds. Any key leaves at once.");
			LogoutSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("General", "LogoutSeconds", 12f, new ConfigDescription("How long Odin holds the door on logout before letting you go.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 60f), Array.Empty<object>()));
			SagaLines = ((BaseUnityPlugin)this).Config.Bind<int>("General", "SagaLines", 5, new ConfigDescription("How many deeds make it into a saga. Odin picks the most remarkable.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>()));
			ShowFlavor = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowFlavor", true, "Odin's remark after each deed. Off = just the facts.");
			HistorySize = ((BaseUnityPlugin)this).Config.Bind<int>("General", "HistorySize", 20, new ConfigDescription("How many past sagas Odin keeps for PastSagaKey and /saga.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>()));
			ShareTo = ((BaseUnityPlugin)this).Config.Bind<ShareChannel>("General", "ShareTo", ShareChannel.Shout, "Where /saga and /deeds send their lines: Shout (everyone in the world) or Say (players near you).");
			TrackKills = ((BaseUnityPlugin)this).Config.Bind<bool>("Categories", "Kills", true, "Creatures slain, bosses felled and milestones.");
			TrackDeaths = ((BaseUnityPlugin)this).Config.Bind<bool>("Categories", "Deaths", true, "Your deaths and what caused them.");
			TrackCloseCalls = ((BaseUnityPlugin)this).Config.Bind<bool>("Categories", "CloseCalls", true, "The lowest health you survived, and kills made while nearly dead.");
			TrackTravel = ((BaseUnityPlugin)this).Config.Bind<bool>("Categories", "Travel", true, "Distance walked, sailed, swum and ridden.");
			TrackGathering = ((BaseUnityPlugin)this).Config.Bind<bool>("Categories", "Gathering", true, "Trees felled, rocks broken, things hauled, crops harvested, fish caught.");
			TrackBuilding = ((BaseUnityPlugin)this).Config.Bind<bool>("Categories", "Building", true, "Pieces built and torn down, items crafted.");
			TrackFood = ((BaseUnityPlugin)this).Config.Bind<bool>("Categories", "Food", true, "Meals eaten and favourite food.");
			TrackSkills = ((BaseUnityPlugin)this).Config.Bind<bool>("Categories", "Skills", true, "Skill levels gained.");
			TrackExploration = ((BaseUnityPlugin)this).Config.Bind<bool>("Categories", "Exploration", true, "Biomes discovered and portals used.");
			TrackOddities = ((BaseUnityPlugin)this).Config.Bind<bool>("Categories", "Oddities", true, "Jumps, doors, petting, taming, bee robbery and other things Odin noticed.");
			_harmony = new Harmony("hevyh2o.valheim.odinssaga");
			_harmony.PatchAll(typeof(Plugin).Assembly);
			_panel = ((Component)this).gameObject.AddComponent<SagaPanel>();
			((Component)this).gameObject.AddComponent<Recorder>();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Odin's Saga 0.1.2 loaded. Huginn and Muninn are taking notes.");
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			if ((Object)(object)_panel != (Object)null)
			{
				Object.Destroy((Object)(object)_panel);
			}
		}
	}
	internal class Recorder : MonoBehaviour
	{
		private const float SampleInterval = 0.5f;

		private const float TeleportDistance = 60f;

		private const float WakeDelay = 3f;

		internal static Recorder Instance;

		private Vector3 _lastPos;

		private bool _hasLastPos;

		private float _nextSample;

		private float _tellAt = -1f;

		private bool _holding;

		private bool _leaving;

		private static readonly KeyCode[] SkipKeys;

		internal static bool Holding
		{
			get
			{
				if ((Object)(object)Instance != (Object)null)
				{
					return Instance._holding;
				}
				return false;
			}
		}

		internal static bool Leaving
		{
			get
			{
				if ((Object)(object)Instance != (Object)null)
				{
					return Instance._leaving;
				}
				return false;
			}
		}

		private void Awake()
		{
			Instance = this;
		}

		private void Update()
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				_hasLastPos = false;
				return;
			}
			Chronicle.Bind(localPlayer);
			if (Time.unscaledTime >= _nextSample)
			{
				_nextSample = Time.unscaledTime + 0.5f;
				SampleTravel(localPlayer);
			}
			if (_tellAt >= 0f && Time.unscaledTime >= _tellAt)
			{
				_tellAt = -1f;
				SagaRecord sagaRecord = Chronicle.CloseDay("sleep");
				if (sagaRecord != null && Plugin.ShowOnSleep.Value)
				{
					SagaPanel.Instance?.ShowSaga(sagaRecord, farewell: false);
				}
			}
		}

		private void SampleTravel(Player player)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			Vector3 position = ((Component)player).transform.position;
			if (!_hasLastPos || ((Character)player).IsDead() || ((Character)player).IsTeleporting() || ((Character)player).IsDebugFlying() || player.IsSleeping())
			{
				_lastPos = position;
				_hasLastPos = true;
				return;
			}
			float num = Vector3.Distance(position, _lastPos);
			_lastPos = position;
			if (!(num < 0.05f) && !(num > 60f))
			{
				if ((Object)(object)Ship.GetLocalShip() != (Object)null)
				{
					Chronicle.Add("sail", "", num);
				}
				else if (((Character)player).IsRiding())
				{
					Chronicle.Add("ride", "", num);
				}
				else if (((Character)player).IsSwimming() && !((Character)player).IsOnGround())
				{
					Chronicle.Add("swim", "", num);
				}
				else if (((Character)player).IsOnGround())
				{
					Chronicle.Add("walk", "", num);
				}
			}
		}

		internal void OnWoke()
		{
			_tellAt = Time.unscaledTime + 3f;
		}

		internal bool Farewell(Action leave)
		{
			if (_leaving)
			{
				return true;
			}
			if (_holding)
			{
				return false;
			}
			if (!Plugin.ShowOnLogout.Value || !Chronicle.Ready)
			{
				return true;
			}
			SagaRecord sagaRecord = Chronicle.CloseDay("logout");
			if (sagaRecord == null || (Object)(object)SagaPanel.Instance == (Object)null || !SagaPanel.Instance.ShowSaga(sagaRecord, farewell: true))
			{
				return true;
			}
			Menu instance = Menu.instance;
			if ((Object)(object)instance != (Object)null)
			{
				((Component)instance.m_logoutDialog).gameObject.SetActive(false);
				((Component)instance.m_quitDialog).gameObject.SetActive(false);
				((Component)instance.m_menuDialog).gameObject.SetActive(false);
			}
			((MonoBehaviour)this).StartCoroutine(HoldTheDoor(leave));
			return false;
		}

		private IEnumerator HoldTheDoor(Action leave)
		{
			_holding = true;
			float until = Time.unscaledTime + Plugin.LogoutSeconds.Value;
			yield return null;
			while (Time.unscaledTime < until && !AnyKeyDown())
			{
				yield return null;
			}
			SagaPanel.Instance?.HideNow();
			_holding = false;
			_leaving = true;
			try
			{
				leave();
			}
			finally
			{
				_leaving = false;
			}
		}

		private static bool AnyKeyDown()
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			if (ZInput.GetMouseButtonDown(0) || ZInput.GetMouseButtonDown(1) || ZInput.GetButtonDown("JoyButtonA") || ZInput.GetButtonDown("JoyButtonB"))
			{
				return true;
			}
			KeyCode[] skipKeys = SkipKeys;
			for (int i = 0; i < skipKeys.Length; i++)
			{
				if (ZInput.GetKeyDown(skipKeys[i], false))
				{
					return true;
				}
			}
			KeyboardShortcut value = Plugin.DeedsKey.Value;
			if ((int)((KeyboardShortcut)(ref value)).MainKey != 0)
			{
				value = Plugin.DeedsKey.Value;
				return ZInput.GetKeyDown(((KeyboardShortcut)(ref value)).MainKey, false);
			}
			return false;
		}

		internal static bool IsLocal(Character c)
		{
			if ((Object)(object)c != (Object)null)
			{
				return (Object)(object)c == (Object)(object)Player.m_localPlayer;
			}
			return false;
		}

		internal static string CauseOf(HitData hit)
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Invalid comparison between Unknown and I4
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Invalid comparison between Unknown and I4
			if (hit == null)
			{
				return "~undefined";
			}
			Character attacker = hit.GetAttacker();
			Player val = (Player)(object)((attacker is Player) ? attacker : null);
			if (val != null)
			{
				if (!((Object)(object)val == (Object)(object)Player.m_localPlayer))
				{
					return "@" + val.GetPlayerName();
				}
				return "~self";
			}
			if ((Object)(object)attacker != (Object)null && (int)hit.m_hitType == 1)
			{
				return attacker.m_name;
			}
			if ((int)hit.m_hitType == 1)
			{
				return "~enemyhit";
			}
			return "~" + ((object)Unsafe.As<HitType, HitType>(ref hit.m_hitType)/*cast due to .constrained prefix*/).ToString().ToLowerInvariant();
		}

		static Recorder()
		{
			KeyCode[] array = new KeyCode[5];
			RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/);
			SkipKeys = (KeyCode[])(object)array;
		}
	}
	[HarmonyPatch(typeof(PlayerProfile), "IncrementStat")]
	internal static class StatPatch
	{
		private static readonly Dictionary<PlayerStatType, string> Map = new Dictionary<PlayerStatType, string>
		{
			[(PlayerStatType)28] = "trees",
			[(PlayerStatType)36] = "logs",
			[(PlayerStatType)38] = "rocks",
			[(PlayerStatType)160] = "fish",
			[(PlayerStatType)48] = "tamed",
			[(PlayerStatType)191] = "pets",
			[(PlayerStatType)123] = "harvest",
			[(PlayerStatType)124] = "harvest",
			[(PlayerStatType)125] = "harvest",
			[(PlayerStatType)126] = "harvest",
			[(PlayerStatType)3] = "jumps",
			[(PlayerStatType)74] = "doors",
			[(PlayerStatType)75] = "doorsclosed",
			[(PlayerStatType)15] = "portals",
			[(PlayerStatType)51] = "arrows",
			[(PlayerStatType)52] = "graves",
			[(PlayerStatType)76] = "honey",
			[(PlayerStatType)170] = "unbuild",
			[(PlayerStatType)133] = "treasure",
			[(PlayerStatType)134] = "treasure",
			[(PlayerStatType)135] = "treasure",
			[(PlayerStatType)136] = "leviathan",
			[(PlayerStatType)137] = "leviathan",
			[(PlayerStatType)112] = "burnt",
			[(PlayerStatType)46] = "ravens"
		};

		private static void Postfix(PlayerProfile __instance, PlayerStatType stat, float amount)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)Game.instance == (Object)null) && __instance == Game.instance.GetPlayerProfile() && Map.TryGetValue(stat, out var value))
			{
				Chronicle.Add(value, "", amount);
			}
		}
	}
	[HarmonyPatch(typeof(PlayerProfile), "IncrementStatItemPickup")]
	internal static class PickupPatch
	{
		private static void Postfix(PlayerProfile __instance, string name, float amount)
		{
			if ((Object)(object)Game.instance != (Object)null && __instance == Game.instance.GetPlayerProfile())
			{
				Chronicle.Add("haul", name, amount);
			}
		}
	}
	[HarmonyPatch(typeof(PlayerProfile), "IncrementStatItemCraft")]
	internal static class CraftPatch
	{
		private static void Postfix(PlayerProfile __instance, string name, float amount)
		{
			if ((Object)(object)Game.instance != (Object)null && __instance == Game.instance.GetPlayerProfile())
			{
				Chronicle.Add("craft", name, amount);
			}
		}
	}
	[HarmonyPatch(typeof(PlayerProfile), "IncrementStatFoodEaten")]
	internal static class FoodPatch
	{
		private static void Postfix(PlayerProfile __instance, string name, float amount)
		{
			if ((Object)(object)Game.instance != (Object)null && __instance == Game.instance.GetPlayerProfile())
			{
				Chronicle.Add("food", name, amount);
			}
		}
	}
	[HarmonyPatch(typeof(PlayerProfile), "IncrementStatBuildPiecePlaced")]
	internal static class BuildPatch
	{
		private static void Postfix(PlayerProfile __instance, string name, float amount)
		{
			if ((Object)(object)Game.instance != (Object)null && __instance == Game.instance.GetPlayerProfile())
			{
				Chronicle.Add("build", name, amount);
			}
		}
	}
	[HarmonyPatch(typeof(Game), "RPC_RegisterKill")]
	internal static class KillPatch
	{
		private static void Postfix(string enemyName, int bossNumber)
		{
			if (!string.IsNullOrEmpty(enemyName))
			{
				Chronicle.Add((bossNumber > 0) ? "boss" : "kill", enemyName);
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer != (Object)null && !((Character)localPlayer).IsDead() && ((Character)localPlayer).GetHealth() > 0f && ((Character)localPlayer).GetHealth() <= ((Character)localPlayer).GetMaxHealth() * 0.25f)
				{
					Chronicle.Lowest("lowkill", Mathf.Ceil(((Character)localPlayer).GetHealth()), enemyName);
				}
			}
		}
	}
	[HarmonyPatch(typeof(Player), "OnDeath")]
	internal static class DeathPatch
	{
		private static void Prefix(Player __instance, HitData ___m_lastHit)
		{
			if (Recorder.IsLocal((Character)(object)__instance))
			{
				string name = Recorder.CauseOf(___m_lastHit);
				Chronicle.Add("death", name);
				Character val = ((___m_lastHit != null) ? ___m_lastHit.GetAttacker() : null);
				if ((Object)(object)val != (Object)null && !val.IsPlayer() && !val.IsBoss() && val.GetMaxHealth() <= 60f)
				{
					Chronicle.Add("deathweak", name);
				}
			}
		}
	}
	[HarmonyPatch(typeof(Character), "ApplyDamage")]
	internal static class DamagePatch
	{
		private static void Postfix(Character __instance, HitData hit)
		{
			if (Recorder.IsLocal(__instance))
			{
				float health = __instance.GetHealth();
				if (health > 0f && health < __instance.GetMaxHealth())
				{
					Chronicle.Lowest("close", Mathf.Ceil(health), Recorder.CauseOf(hit));
				}
			}
		}
	}
	[HarmonyPatch(typeof(Player), "OnSkillLevelup")]
	internal static class SkillPatch
	{
		private unsafe static void Postfix(Player __instance, SkillType skill, float level)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			if ((int)skill != 0 && Recorder.IsLocal((Character)(object)__instance))
			{
				string name = "$skill_" + ((object)(*(SkillType*)(&skill))/*cast due to .constrained prefix*/).ToString().ToLowerInvariant();
				Chronicle.Add("skill", name);
				if (Chronicle.Ready && (double)level > Chronicle.Today.Get("skillat", name))
				{
					Chronicle.Today.Set("skillat", name, Mathf.Floor(level));
				}
			}
		}
	}
	[HarmonyPatch(typeof(Player), "AddKnownBiome")]
	internal static class BiomePatch
	{
		private static void Prefix(Player __instance, BiomeSector biome, HashSet<string> ___m_knownBiome)
		{
			if (biome != null && Recorder.IsLocal((Character)(object)__instance))
			{
				string name = biome.GetName(false);
				if (!string.IsNullOrEmpty(name) && !___m_knownBiome.Contains(name))
				{
					Chronicle.Add("biome", name);
				}
			}
		}
	}
	[HarmonyPatch(typeof(Player), "SetSleeping")]
	internal static class SleepPatch
	{
		private static void Prefix(Player __instance, out bool __state)
		{
			__state = __instance.IsSleeping();
		}

		private static void Postfix(Player __instance, bool sleep, bool __state)
		{
			if (__state && !sleep && Recorder.IsLocal((Character)(object)__instance))
			{
				Recorder.Instance?.OnWoke();
			}
		}
	}
	[HarmonyPatch(typeof(Player), "Save")]
	internal static class SavePatch
	{
		private static void Prefix(Player __instance)
		{
			Chronicle.SaveTo(__instance);
		}
	}
	[HarmonyPatch(typeof(Menu), "OnLogoutYes")]
	internal static class LogoutPatch
	{
		private static bool Prefix(Menu __instance)
		{
			if (!((Object)(object)Recorder.Instance == (Object)null))
			{
				return Recorder.Instance.Farewell((Action)__instance.OnLogoutYes);
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(Menu), "OnQuitYes")]
	internal static class QuitPatch
	{
		private static bool Prefix(Menu __instance)
		{
			if (!((Object)(object)Recorder.Instance == (Object)null))
			{
				return Recorder.Instance.Farewell((Action)__instance.OnQuitYes);
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(Menu), "Update")]
	internal static class MenuUpdatePatch
	{
		private static bool Prefix()
		{
			return !Recorder.Holding;
		}
	}
	[HarmonyPatch(typeof(Terminal), "InitTerminal")]
	internal static class SagaCommands
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static ConsoleEvent <>9__2_0;

			public static ConsoleEvent <>9__2_1;

			public static ConsoleEvent <>9__2_2;

			internal void <Postfix>b__2_0(ConsoleEventArgs args)
			{
				int result = 1;
				if (args.Length > 1 && (!int.TryParse(args[1], out result) || result < 1))
				{
					Terminal context = args.Context;
					if (context != null)
					{
						context.AddString("Usage: saga [n], where 1 is your latest saga. Type sagas to see the list.");
					}
				}
				else if (Chronicle.History.Count == 0)
				{
					Terminal context2 = args.Context;
					if (context2 != null)
					{
						context2.AddString("Odin hasn't told any sagas yet. Sleep in a bed first.");
					}
				}
				else if (result > Chronicle.History.Count)
				{
					Terminal context3 = args.Context;
					if (context3 != null)
					{
						context3.AddString($"Odin only remembers {Chronicle.History.Count} sagas.");
					}
				}
				else
				{
					Share(SagaWriter.ChatLines(Chronicle.History[result - 1]), args.Context);
				}
			}

			internal void <Postfix>b__2_1(ConsoleEventArgs args)
			{
				if (Chronicle.History.Count == 0)
				{
					Terminal context = args.Context;
					if (context != null)
					{
						context.AddString("Odin hasn't told any sagas yet. Sleep in a bed first.");
					}
					return;
				}
				for (int i = 0; i < Chronicle.History.Count; i++)
				{
					SagaRecord sagaRecord = Chronicle.History[i];
					Terminal context2 = args.Context;
					if (context2 != null)
					{
						context2.AddString((sagaRecord.Day > 0) ? $"{i + 1}. {sagaRecord.Title} (day {sagaRecord.Day})" : $"{i + 1}. {sagaRecord.Title}");
					}
				}
				Terminal context3 = args.Context;
				if (context3 != null)
				{
					context3.AddString("Type /saga <number> to share one.");
				}
			}

			internal void <Postfix>b__2_2(ConsoleEventArgs args)
			{
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null || !Chronicle.Ready)
				{
					Terminal context = args.Context;
					if (context != null)
					{
						context.AddString("Odin can only read your deeds while you're in the world.");
					}
				}
				else
				{
					Share(SagaWriter.DeedsChatLines(Chronicle.Life, localPlayer.GetPlayerName()), args.Context);
				}
			}
		}

		private const float LineGap = 0.8f;

		private static bool _sharing;

		private static void Postfix()
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			//IL_006c: 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_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Expected O, but got Unknown
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Expected O, but got Unknown
			object obj = <>c.<>9__2_0;
			if (obj == null)
			{
				ConsoleEvent val = delegate(ConsoleEventArgs args)
				{
					int result = 1;
					if (args.Length > 1 && (!int.TryParse(args[1], out result) || result < 1))
					{
						Terminal context = args.Context;
						if (context != null)
						{
							context.AddString("Usage: saga [n], where 1 is your latest saga. Type sagas to see the list.");
						}
					}
					else if (Chronicle.History.Count == 0)
					{
						Terminal context2 = args.Context;
						if (context2 != null)
						{
							context2.AddString("Odin hasn't told any sagas yet. Sleep in a bed first.");
						}
					}
					else if (result > Chronicle.History.Count)
					{
						Terminal context3 = args.Context;
						if (context3 != null)
						{
							context3.AddString($"Odin only remembers {Chronicle.History.Count} sagas.");
						}
					}
					else
					{
						Share(SagaWriter.ChatLines(Chronicle.History[result - 1]), args.Context);
					}
				};
				<>c.<>9__2_0 = val;
				obj = (object)val;
			}
			new ConsoleCommand("saga", "[n] Odin's Saga: read your latest saga (or the nth latest) aloud in chat.", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj2 = <>c.<>9__2_1;
			if (obj2 == null)
			{
				ConsoleEvent val2 = delegate(ConsoleEventArgs args)
				{
					if (Chronicle.History.Count == 0)
					{
						Terminal context = args.Context;
						if (context != null)
						{
							context.AddString("Odin hasn't told any sagas yet. Sleep in a bed first.");
						}
					}
					else
					{
						for (int i = 0; i < Chronicle.History.Count; i++)
						{
							SagaRecord sagaRecord = Chronicle.History[i];
							Terminal context2 = args.Context;
							if (context2 != null)
							{
								context2.AddString((sagaRecord.Day > 0) ? $"{i + 1}. {sagaRecord.Title} (day {sagaRecord.Day})" : $"{i + 1}. {sagaRecord.Title}");
							}
						}
						Terminal context3 = args.Context;
						if (context3 != null)
						{
							context3.AddString("Type /saga <number> to share one.");
						}
					}
				};
				<>c.<>9__2_1 = val2;
				obj2 = (object)val2;
			}
			new ConsoleCommand("sagas", "Odin's Saga: list your past sagas (only you see this).", (ConsoleEvent)obj2, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj3 = <>c.<>9__2_2;
			if (obj3 == null)
			{
				ConsoleEvent val3 = delegate(ConsoleEventArgs args)
				{
					Player localPlayer = Player.m_localPlayer;
					if ((Object)(object)localPlayer == (Object)null || !Chronicle.Ready)
					{
						Terminal context = args.Context;
						if (context != null)
						{
							context.AddString("Odin can only read your deeds while you're in the world.");
						}
					}
					else
					{
						Share(SagaWriter.DeedsChatLines(Chronicle.Life, localPlayer.GetPlayerName()), args.Context);
					}
				};
				<>c.<>9__2_2 = val3;
				obj3 = (object)val3;
			}
			new ConsoleCommand("deeds", "Odin's Saga: brag about your lifetime deeds in chat.", (ConsoleEvent)obj3, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		}

		private static void Share(List<string> lines, Terminal context)
		{
			if ((Object)(object)Chat.instance == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)Recorder.Instance == (Object)null)
			{
				if (context != null)
				{
					context.AddString("Odin can only share sagas while you're in the world.");
				}
			}
			else if (_sharing)
			{
				if (context != null)
				{
					context.AddString("Odin is still reading the last one. Patience.");
				}
			}
			else
			{
				((MonoBehaviour)Recorder.Instance).StartCoroutine(SendLines(lines));
			}
		}

		private static IEnumerator SendLines(List<string> lines)
		{
			_sharing = true;
			Type type = (Type)((Plugin.ShareTo.Value == Plugin.ShareChannel.Say) ? 1 : 2);
			try
			{
				foreach (string line in lines)
				{
					if (!((Object)(object)Chat.instance == (Object)null) && !((Object)(object)Player.m_localPlayer == (Object)null))
					{
						Chat.instance.SendText(type, line);
						yield return (object)new WaitForSecondsRealtime(0.8f);
						continue;
					}
					yield break;
				}
			}
			finally
			{
				_sharing = false;
			}
		}
	}
	internal class SagaPanel : MonoBehaviour
	{
		private enum Mode
		{
			Saga,
			Past,
			Deeds
		}

		private const float MinWidth = 480f;

		internal static SagaPanel Instance;

		private Canvas _canvas;

		private GameObject _tooltip;

		private RectTransform _panel;

		private TMP_Text _topic;

		private TMP_Text _text;

		private CanvasGroup _group;

		private readonly Vector3[] _corners = (Vector3[])(object)new Vector3[4];

		private Mode _mode;

		private SagaRecord _saga;

		private bool _farewell;

		private int _pastIndex;

		private float _shownAt;

		private float _fadeOutStart = -1f;

		private float _fadeOutLength;

		private static float Now => Time.unscaledTime;

		private void Awake()
		{
			Instance = this;
		}

		private void LateUpdate()
		{
			bool num = CanShow();
			if (num && !_farewell)
			{
				HandleKeys();
			}
			if (!num || !IsOpen())
			{
				Hide();
			}
			else if (!((Object)(object)_tooltip == (Object)null) || CreateTooltip())
			{
				if (!_tooltip.activeSelf)
				{
					_tooltip.SetActive(true);
				}
				_group.alpha = Alpha();
				Place();
			}
		}

		private void OnDestroy()
		{
			if ((Object)(object)_canvas != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)_canvas).gameObject);
			}
		}

		internal bool ShowSaga(SagaRecord saga, bool farewell)
		{
			if (saga == null || ((Object)(object)_tooltip == (Object)null && !CreateTooltip()))
			{
				return false;
			}
			_mode = Mode.Saga;
			_saga = saga;
			_farewell = farewell;
			Fill();
			Open(farewell ? (Plugin.LogoutSeconds.Value + 1f) : Plugin.ShowSeconds.Value);
			return true;
		}

		internal void HideNow()
		{
			_farewell = false;
			_fadeOutStart = -1f;
			_fadeOutLength = 0f;
			Hide();
		}

		private void HandleKeys()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			if (Pressed(Plugin.PastSagaKey.Value, exact: false))
			{
				if (Chronicle.History.Count == 0)
				{
					Player localPlayer = Player.m_localPlayer;
					if (localPlayer != null)
					{
						((Character)localPlayer).Message((MessageType)1, "Odin hasn't told any sagas yet. Sleep in a bed first.", 0, (Sprite)null, false);
					}
					return;
				}
				_pastIndex = ((_mode == Mode.Past && IsOpen() && !IsFading()) ? ((_pastIndex + 1) % Chronicle.History.Count) : 0);
				_mode = Mode.Past;
				_saga = Chronicle.History[_pastIndex];
				if ((Object)(object)_tooltip != (Object)null || CreateTooltip())
				{
					Fill();
					Open(Plugin.ShowSeconds.Value);
				}
			}
			else if (Pressed(Plugin.DeedsKey.Value, exact: true))
			{
				if (IsOpen() && !IsFading())
				{
					Close();
				}
				else if (IsOpen())
				{
					Open(Plugin.ShowSeconds.Value);
				}
				else if ((Object)(object)_tooltip != (Object)null || CreateTooltip())
				{
					_mode = Mode.Deeds;
					Fill();
					Open(Plugin.ShowSeconds.Value);
				}
			}
		}

		private void Fill()
		{
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			switch (_mode)
			{
			case Mode.Deeds:
			{
				string text = (((Object)(object)Player.m_localPlayer != (Object)null) ? Player.m_localPlayer.GetPlayerName() : "you");
				_topic.text = "The Deeds of " + text;
				_text.text = SagaWriter.DeedsBody(Chronicle.Life);
				break;
			}
			case Mode.Past:
				_topic.text = _saga.Title;
				_text.text = string.Format("<color={0}>Saga {1} of {2}. ", "#B0B0B0", _pastIndex + 1, Chronicle.History.Count) + $"{SagaWriter.KeyName(Plugin.PastSagaKey.Value)} for older; /saga {_pastIndex + 1} shares it.</color>\n" + SagaWriter.Body(_saga, farewell: false);
				break;
			default:
				_topic.text = _saga.Title;
				_text.text = SagaWriter.Body(_saga, _farewell);
				break;
			}
			LayoutRebuilder.ForceRebuildLayoutImmediate(_panel);
		}

		private bool IsOpen()
		{
			return Now < _fadeOutStart + _fadeOutLength;
		}

		private bool IsFading()
		{
			return Now >= _fadeOutStart;
		}

		private void Open(float seconds)
		{
			float num = (IsOpen() ? Alpha() : 0f);
			_shownAt = Now - num * Plugin.FadeInSeconds.Value;
			_fadeOutStart = Now + seconds;
			_fadeOutLength = Plugin.FadeOutSeconds.Value;
		}

		private void Close()
		{
			_fadeOutStart = Now;
			_fadeOutLength = Plugin.FadeInSeconds.Value;
		}

		private float Alpha()
		{
			float value = Plugin.FadeInSeconds.Value;
			float num = ((value > 0f) ? Mathf.Clamp01((Now - _shownAt) / value) : 1f);
			float num2 = ((_fadeOutLength > 0f) ? Mathf.Clamp01((Now - _fadeOutStart) / _fadeOutLength) : (IsFading() ? 1f : 0f));
			return num * (1f - num2);
		}

		private bool CanShow()
		{
			if ((Object)(object)InventoryGui.instance == (Object)null)
			{
				return false;
			}
			if (_farewell)
			{
				return Recorder.Holding;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).IsDead() || (Object)(object)Hud.instance == (Object)null || Hud.IsUserHidden() || !Hud.instance.IsVisible())
			{
				return false;
			}
			if (!InventoryGui.IsVisible() && !Menu.IsVisible() && !Minimap.IsOpen() && !TextInput.IsVisible() && !Console.IsVisible() && (!((Object)(object)Chat.instance != (Object)null) || !Chat.instance.HasFocus()))
			{
				return !localPlayer.IsSleeping();
			}
			return false;
		}

		private static bool Pressed(KeyboardShortcut key, bool exact)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			if ((int)((KeyboardShortcut)(ref key)).MainKey == 0 || !ZInput.GetKeyDown(((KeyboardShortcut)(ref key)).MainKey, false))
			{
				return false;
			}
			foreach (KeyCode modifier in ((KeyboardShortcut)(ref key)).Modifiers)
			{
				if (!ZInput.GetKey(modifier, false))
				{
					return false;
				}
			}
			if (exact)
			{
				KeyCode[] array = new KeyCode[6];
				RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/);
				KeyCode[] array2 = (KeyCode[])(object)array;
				foreach (KeyCode val in array2)
				{
					if (ZInput.GetKey(val, false) && Array.IndexOf(((KeyboardShortcut)(ref key)).Modifiers.ToArray(), val) < 0)
					{
						return false;
					}
				}
			}
			return true;
		}

		private void Place()
		{
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: 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_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: 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_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			Canvas componentInParent = ((Component)InventoryGui.instance).GetComponentInParent<Canvas>();
			Canvas val = ((componentInParent != null) ? componentInParent.rootCanvas : null);
			if ((Object)(object)val != (Object)null)
			{
				_canvas.scaleFactor = val.scaleFactor;
			}
			Vector2 val2 = default(Vector2);
			((Vector2)(ref val2))..ctor(Plugin.PositionX.Value * (float)Screen.width, Plugin.PositionY.Value * (float)Screen.height);
			_tooltip.transform.position = Vector2.op_Implicit(val2);
			_panel.GetWorldCorners(_corners);
			Vector2 val3 = Vector2.op_Implicit((_corners[0] + _corners[2]) / 2f);
			Transform transform = _tooltip.transform;
			transform.position += Vector2.op_Implicit(val2 - val3);
			Utils.ClampUIToScreen(_panel);
		}

		private bool CreateTooltip()
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Expected O, but got Unknown
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = FindTooltipPrefab();
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			if ((Object)(object)_canvas == (Object)null)
			{
				GameObject val2 = new GameObject("OdinsSagaCanvas");
				Object.DontDestroyOnLoad((Object)(object)val2);
				_canvas = val2.AddComponent<Canvas>();
				_canvas.renderMode = (RenderMode)0;
				_canvas.sortingOrder = 30000;
			}
			_tooltip = Object.Instantiate<GameObject>(val, ((Component)_canvas).transform);
			((Object)_tooltip).name = "OdinsSaga";
			ref RectTransform panel = ref _panel;
			Transform child = _tooltip.transform.GetChild(0);
			panel = (RectTransform)(object)((child is RectTransform) ? child : null);
			Transform obj = Utils.FindChild(_tooltip.transform, "Topic", (IterativeSearchType)0);
			_topic = ((obj != null) ? ((Component)obj).GetComponent<TMP_Text>() : null);
			Transform obj2 = Utils.FindChild(_tooltip.transform, "Text", (IterativeSearchType)0);
			_text = ((obj2 != null) ? ((Component)obj2).GetComponent<TMP_Text>() : null);
			if ((Object)(object)_panel == (Object)null || (Object)(object)_topic == (Object)null || (Object)(object)_text == (Object)null)
			{
				Object.Destroy((Object)(object)_tooltip);
				_tooltip = null;
				return false;
			}
			ContentSizeFitter component = ((Component)_panel).GetComponent<ContentSizeFitter>();
			if ((Object)(object)component == (Object)null || (int)component.horizontalFit == 0)
			{
				Rect rect = _panel.rect;
				if (((Rect)(ref rect)).width < 480f)
				{
					_panel.SetSizeWithCurrentAnchors((Axis)0, 480f);
				}
			}
			Graphic[] componentsInChildren = _tooltip.GetComponentsInChildren<Graphic>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				componentsInChildren[i].raycastTarget = false;
			}
			_group = _tooltip.GetComponent<CanvasGroup>();
			if ((Object)(object)_group == (Object)null)
			{
				_group = _tooltip.AddComponent<CanvasGroup>();
			}
			_group.interactable = false;
			_group.blocksRaycasts = false;
			_group.alpha = 0f;
			_tooltip.SetActive(false);
			return true;
		}

		private static GameObject FindTooltipPrefab()
		{
			InventoryGui instance = InventoryGui.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return null;
			}
			UITooltip val = (((Object)(object)instance.m_playerGrid != (Object)null && (Object)(object)instance.m_playerGrid.m_elementPrefab != (Object)null) ? instance.m_playerGrid.m_elementPrefab.GetComponentInChildren<UITooltip>(true) : null);
			if ((Object)(object)val != (Object)null && (Object)(object)val.m_tooltipPrefab != (Object)null)
			{
				return val.m_tooltipPrefab;
			}
			UITooltip[] componentsInChildren = ((Component)instance).GetComponentsInChildren<UITooltip>(true);
			foreach (UITooltip val2 in componentsInChildren)
			{
				if ((Object)(object)val2.m_tooltipPrefab != (Object)null)
				{
					return val2.m_tooltipPrefab;
				}
			}
			return null;
		}

		private void Hide()
		{
			if ((Object)(object)_tooltip != (Object)null && _tooltip.activeSelf)
			{
				_tooltip.SetActive(false);
			}
		}
	}
	internal static class SagaWriter
	{
		private class Candidate
		{
			internal double Score;

			internal int Order;

			internal string Theme;

			internal string ThemeName;

			internal SagaLine Line;
		}

		private const string Value = "orange";

		internal const string FlavorColor = "#B9B3A8";

		internal const string NoteColor = "#B0B0B0";

		private static readonly Random Rng = new Random();

		private static readonly HashSet<string> SamePlural = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
		{
			"deer", "lox", "draugr", "dvergr", "fish", "sheep", "bjørn", "unbjørn", "fenring", "gjall",
			"ulv", "volture"
		};

		internal static SagaRecord Write(DeedBook today, DeedBook life, string reason)
		{
			List<Candidate> list = new List<Candidate>();
			if (Plugin.TrackKills.Value)
			{
				Kills(today, life, list);
			}
			if (Plugin.TrackDeaths.Value)
			{
				Deaths(today, life, list);
			}
			if (Plugin.TrackCloseCalls.Value)
			{
				CloseCalls(today, list);
			}
			if (Plugin.TrackTravel.Value)
			{
				Travel(today, life, list);
			}
			if (Plugin.TrackGathering.Value)
			{
				Gathering(today, life, list);
			}
			if (Plugin.TrackBuilding.Value)
			{
				Building(today, life, list);
			}
			if (Plugin.TrackFood.Value)
			{
				Food(today, list);
			}
			if (Plugin.TrackSkills.Value)
			{
				Skills(today, list);
			}
			if (Plugin.TrackExploration.Value)
			{
				Exploration(today, list);
			}
			if (Plugin.TrackOddities.Value)
			{
				Oddities(today, list);
			}
			foreach (Candidate item in list)
			{
				item.Score += Rng.NextDouble() * 8.0;
			}
			list.Sort((Candidate a, Candidate b) => b.Score.CompareTo(a.Score));
			SagaRecord sagaRecord = new SagaRecord
			{
				Day = (((Object)(object)EnvMan.instance != (Object)null) ? EnvMan.instance.GetDay() : 0),
				Reason = reason,
				Closing = Pick((reason == "logout") ? OdinText.ClosingLogout : OdinText.ClosingSleep)
			};
			if (list.Count == 0)
			{
				sagaRecord.Title = Fill(Pick(OdinText.Titles["quiet"]), null);
				sagaRecord.Lines.Add(new SagaLine
				{
					Fact = Pick(OdinText.Quiet),
					Remark = ""
				});
				return sagaRecord;
			}
			Candidate candidate = list[0];
			string[] value;
			string[] pool = (OdinText.Titles.TryGetValue(candidate.Theme, out value) ? value : OdinText.Titles["oddity"]);
			sagaRecord.Title = Fill(Pick(pool), new Dictionary<string, string>
			{
				["name"] = candidate.ThemeName ?? "",
				["names"] = Plural(candidate.ThemeName ?? "")
			});
			List<Candidate> range = list.GetRange(0, Math.Min(Plugin.SagaLines.Value, list.Count));
			range.Sort((Candidate a, Candidate b) => (a.Order == b.Order) ? b.Score.CompareTo(a.Score) : a.Order.CompareTo(b.Order));
			foreach (Candidate item2 in range)
			{
				sagaRecord.Lines.Add(item2.Line);
			}
			return sagaRecord;
		}

		private static void Kills(DeedBook today, DeedBook life, List<Candidate> c)
		{
			foreach (KeyValuePair<string, double> item in today.Ranked("boss"))
			{
				string text = L(item.Key);
				bool flag = life.Get("boss", item.Key) > item.Value;
				Add(c, 100.0, 0, "boss", text, Pick(OdinText.BossFact), flag ? OdinText.BossAgain : OdinText.BossRemark, Vars(0.0, text));
			}
			List<KeyValuePair<string, double>> list = today.Ranked("kill");
			if (list.Count == 0)
			{
				return;
			}
			int num = 0;
			foreach (KeyValuePair<string, double> item2 in list)
			{
				string text2 = L(item2.Key);
				double num2 = life.Get("kill", item2.Key);
				double num3 = num2 - item2.Value;
				int num4 = Crossed(OdinText.KillMilestones, num3, num2);
				if (num4 > 0)
				{
					Add(c, 55.0 + Math.Log10(num4) * 8.0, 2, "milestone", text2, Pick(OdinText.KillMilestoneLines[num4]), null, Vars(0.0, text2));
				}
				else if (num3 <= 0.0 && num++ < 2)
				{
					Add(c, 38.0, 2, "firstkill", text2, Pick(OdinText.FirstKill), null, Vars(0.0, text2));
				}
			}
			KeyValuePair<string, double> keyValuePair = list[0];
			string text3 = L(keyValuePair.Key);
			int num5 = (int)keyValuePair.Value;
			string[] remarks = ((num5 == 1) ? OdinText.KillOne : ((num5 < 15) ? OdinText.KillSome : OdinText.KillMany));
			if (OdinText.CreatureRemarks.TryGetValue(keyValuePair.Key, out var value) && Rng.Next(2) == 0)
			{
				remarks = value;
			}
			Add(c, Math.Min(10.0 + (double)num5 * 2.5, 55.0), 1, "kill", text3, Pick((num5 == 1) ? OdinText.KillOneFact : OdinText.KillManyFact), remarks, Vars(num5, text3));
			double num6 = today.Total("kill");
			if (list.Count >= 4 && num6 >= 10.0)
			{
				Add(c, 20 + list.Count * 2, 1, "kill", text3, Pick(OdinText.KillKindsFact), OdinText.KillKinds, Vars(num6, null, 0.0, -1.0, new Dictionary<string, string> { ["kinds"] = list.Count.ToString() }));
			}
		}

		private static void Deaths(DeedBook today, DeedBook life, List<Candidate> c)
		{
			List<KeyValuePair<string, double>> list = today.Ranked("death");
			double num = today.Total("death");
			if (num <= 0.0)
			{
				if (today.Total("kill") + today.Get("trees") / 4.0 + today.Get("walk") / 1000.0 >= 15.0 && Plugin.TrackKills.Value)
				{
					Add(c, 14.0, 4, "oddity", null, Pick(OdinText.NoDeathsFact), OdinText.NoDeaths, Vars());
				}
				return;
			}
			string key = list[0].Key;
			string text = Cause(key);
			Dictionary<string, string> dictionary = Vars(num, CauseName(key));
			dictionary["cause"] = text;
			dictionary["Cause"] = Cap(text);
			dictionary["a"] = text;
			dictionary["A"] = Cap(text);
			bool flag = today.Get("deathweak", key) > 0.0;
			string theme = (flag ? "deathweak" : (OdinText.Titles.ContainsKey(key) ? key : "death"));
			string[] value;
			string[] remarks = (flag ? OdinText.DeathWeak : ((OdinText.DeathByCause.TryGetValue(key, out value) && num == 1.0) ? value : ((num == 1.0) ? OdinText.DeathOnce : OdinText.DeathMany)));
			Add(c, (double)(flag ? 70 : 45) + num * 5.0, 4, theme, CauseName(key), Pick((num == 1.0) ? OdinText.DeathOnceFact : OdinText.DeathManyFact), remarks, dictionary);
			double num2 = life.Total("death");
			int num3 = Crossed(OdinText.DeathMilestones, num2 - num, num2);
			if (num3 > 0)
			{
				Add(c, 40.0, 4, "death", null, Pick(OdinText.DeathMilestone), null, Vars(num3));
			}
		}

		private static void CloseCalls(DeedBook today, List<Candidate> c)
		{
			if (today.Has("lowkill"))
			{
				double num = today.Get("lowkill");
				string value;
				string text = L(today.Notes.TryGetValue("lowkill", out value) ? value : "");
				Add(c, (num <= 10.0) ? 68 : 46, 3, "lowkill", text, Pick(OdinText.LowKillFact), OdinText.LowKill, Vars(0.0, text, num));
			}
			if (today.Has("close") && today.Get("close") <= 25.0)
			{
				double num2 = today.Get("close");
				string value3;
				string value2 = Cause(today.Notes.TryGetValue("close", out value3) ? value3 : "");
				Dictionary<string, string> dictionary = Vars(0.0, null, num2);
				dictionary["cause"] = value2;
				Add(c, (num2 <= 5.0) ? 60 : ((num2 <= 12.0) ? 45 : 30), 3, "close", null, Pick(OdinText.CloseFact), OdinText.Close, dictionary);
			}
		}

		private static void Travel(DeedBook today, DeedBook life, List<Candidate> c)
		{
			double num = today.Get("walk");
			double num2 = today.Get("sail");
			double num3 = today.Get("swim");
			double num4 = today.Get("ride");
			if (num >= 1000.0)
			{
				Add(c, Math.Min(10.0 + num / 250.0, 40.0), 5, "walk", null, Pick(OdinText.WalkFact), OdinText.Walk, Vars(0.0, null, 0.0, num));
			}
			if (num2 >= 500.0)
			{
				Add(c, Math.Min(15.0 + num2 / 200.0, 45.0), 5, "sail", null, Pick(OdinText.SailFact), OdinText.Sail, Vars(0.0, null, 0.0, num2));
			}
			if (num3 >= 100.0)
			{
				Add(c, Math.Min(15.0 + num3 / 40.0, 40.0), 5, "swim", null, Pick(OdinText.SwimFact), OdinText.Swim, Vars(0.0, null, 0.0, num3));
			}
			if (num4 >= 300.0)
			{
				Add(c, Math.Min(18.0 + num4 / 300.0, 35.0), 5, "ride", null, Pick(OdinText.RideFact), OdinText.Ride, Vars(0.0, null, 0.0, num4));
			}
			double num5 = life.Get("walk") / 1000.0;
			int num6 = Crossed(OdinText.WalkMilestonesKm, num5 - num / 1000.0, num5);
			if (num6 > 0)
			{
				Add(c, 45.0, 5, "walk", null, Pick(OdinText.WalkMilestone), null, Vars(num6));
			}
		}

		private static void Gathering(DeedBook today, DeedBook life, List<Candidate> c)
		{
			double num = today.Get("trees");
			if (num >= 5.0)
			{
				Add(c, Math.Min(10.0 + num, 45.0), 6, "trees", null, Pick(OdinText.TreesFact), OdinText.Trees, Vars(num));
			}
			int num2 = Crossed(OdinText.TreeMilestones, life.Get("trees") - num, life.Get("trees"));
			if (num2 > 0)
			{
				Add(c, 50.0, 6, "trees", null, Pick(OdinText.TreeMilestone), null, Vars(num2));
			}
			double num3 = today.Get("rocks");
			if (num3 >= 5.0)
			{
				Add(c, Math.Min(10.0 + num3 / 2.0, 35.0), 6, "rocks", null, Pick(OdinText.RocksFact), OdinText.Rocks, Vars(num3));
			}
			List<KeyValuePair<string, double>> list = today.Ranked("haul");
			if (list.Count > 0 && list[0].Value >= 20.0)
			{
				bool flag = IsOre(list[0].Key);
				string text = L(list[0].Key);
				Add(c, Math.Min((double)(flag ? 25 : 12) + list[0].Value / 8.0, 42.0), 6, "haul", text, Pick(OdinText.HaulFact), flag ? OdinText.HaulOre : OdinText.Haul, Vars(list[0].Value, text));
			}
			double num4 = today.Get("harvest");
			if (num4 >= 10.0)
			{
				Add(c, Math.Min(12.0 + num4 / 5.0, 30.0), 6, "harvest", null, Pick(OdinText.HarvestFact), OdinText.Harvest, Vars(num4));
			}
			double num5 = today.Get("fish");
			if (num5 >= 1.0)
			{
				Add(c, Math.Min(25.0 + num5 * 3.0, 45.0), 6, "fish", null, Pick(OdinText.FishFact), OdinText.Fish, Vars(num5));
			}
		}

		private static void Building(DeedBook today, DeedBook life, List<Candidate> c)
		{
			List<KeyValuePair<string, double>> list = today.Ranked("build");
			double num = today.Total("build");
			if (num >= 10.0)
			{
				string text = L(list[0].Key);
				Dictionary<string, string> dictionary = Vars(num, text);
				dictionary["top"] = Num(list[0].Value);
				Add(c, Math.Min(10.0 + num / 5.0, 40.0), 7, "build", text, Pick(OdinText.BuildFact), OdinText.Build, dictionary);
			}
			int num2 = Crossed(OdinText.BuildMilestones, life.Total("build") - num, life.Total("build"));
			if (num2 > 0)
			{
				Add(c, 45.0, 7, "build", null, Pick(OdinText.BuildMilestone), null, Vars(num2));
			}
			double num3 = today.Get("unbuild");
			if (num3 >= 10.0)
			{
				Add(c, (num3 > num / 2.0) ? 30 : 18, 7, "unbuild", null, Pick(OdinText.UnbuildFact), OdinText.Unbuild, Vars(num3));
			}
			List<KeyValuePair<string, double>> list2 = today.Ranked("craft");
			double num4 = today.Total("craft");
			if (num4 >= 3.0)
			{
				Add(c, Math.Min(10.0 + num4, 30.0), 7, "craft", L(list2[0].Key), Pick(OdinText.CraftFact), OdinText.Craft, Vars(num4, L(list2[0].Key)));
			}
			double num5 = today.Get("burnt");
			if (num5 >= 2.0)
			{
				Add(c, 22.0 + num5 * 3.0, 7, "food", null, Pick(OdinText.BurntFact), OdinText.Burnt, Vars(num5));
			}
		}

		private static void Food(DeedBook today, List<Candidate> c)
		{
			List<KeyValuePair<string, double>> list = today.Ranked("food");
			double num = today.Total("food");
			if (num >= 3.0)
			{
				Add(c, Math.Min(8.0 + num * 1.5, 25.0), 8, "food", L(list[0].Key), Pick(OdinText.FoodFact), OdinText.Food, Vars(num, L(list[0].Key)));
			}
		}

		private static void Skills(DeedBook today, List<Candidate> c)
		{
			List<KeyValuePair<string, double>> list = today.Ranked("skill");
			double num = today.Total("skill");
			if (num <= 0.0)
			{
				return;
			}
			string text = SkillName(list[0].Key);
			Add(c, Math.Min(10.0 + num * 4.0, 40.0), 9, "skill", text, Pick((num == 1.0) ? OdinText.SkillOneFact : OdinText.SkillFact), OdinText.Skill, Vars(num, text));
			foreach (KeyValuePair<string, double> item in today.Ranked("skillat"))
			{
				int num2 = (int)item.Value;
				if (num2 >= 100)
				{
					Add(c, 80.0, 9, "skill", SkillName(item.Key), Pick(OdinText.SkillMax), null, Vars(num2, SkillName(item.Key)));
				}
				else if (num2 >= 10 && num2 % 10 == 0)
				{
					Add(c, 22.0 + (double)num2 / 4.0, 9, "skill", SkillName(item.Key), Pick(OdinText.SkillReached), null, Vars(num2, SkillName(item.Key)));
				}
			}
		}

		private static void Exploration(DeedBook today, List<Candidate> c)
		{
			List<KeyValuePair<string, double>> list = today.Ranked("biome");
			if (list.Count > 0)
			{
				List<string> list2 = new List<string>();
				foreach (KeyValuePair<string, double> item in list)
				{
					list2.Add(L(item.Key));
				}
				Dictionary<string, string> dictionary = Vars(0.0, list2[0]);
				dictionary["list"] = JoinAnd(list2);
				Add(c, 48 + list.Count * 4, 10, "biome", list2[0], Pick(OdinText.BiomeFact), OdinText.Biome, dictionary);
			}
			double num = today.Get("portals");
			if (num >= 3.0)
			{
				Add(c, Math.Min(12.0 + num, 28.0), 10, "oddity", null, Pick(OdinText.PortalFact), OdinText.Portal, Vars(num));
			}
			double num2 = today.Get("treasure");
			if (num2 >= 1.0)
			{
				Add(c, 25.0 + num2 * 3.0, 10, "oddity", null, Pick(OdinText.TreasureFact), OdinText.Treasure, Vars(num2));
			}
		}

		private static void Oddities(DeedBook today, List<Candidate> c)
		{
			double num = today.Get("jumps");
			if (num >= 150.0)
			{
				Add(c, Math.Min(12.0 + num / 60.0, 32.0), 11, "oddity", null, Pick(OdinText.JumpsFact), OdinText.Jumps, Vars(num));
			}
			double num2 = today.Get("doors");
			double num3 = today.Get("doorsclosed");
			if (num2 >= 15.0 && num2 - num3 >= 8.0)
			{
				Dictionary<string, string> dictionary = Vars(num2);
				dictionary["closed"] = Num(num3);
				Add(c, 30.0, 11, "oddity", null, Pick(OdinText.DoorsFact), OdinText.Doors, dictionary);
			}
			double num4 = today.Get("pets");
			if (num4 >= 5.0)
			{
				Add(c, Math.Min(18.0 + num4, 34.0), 11, "oddity", null, Pick(OdinText.PetsFact), OdinText.Pets, Vars(num4));
			}
			double num5 = today.Get("tamed");
			if (num5 >= 1.0)
			{
				Add(c, 35.0, 11, "oddity", null, Pick(OdinText.TamedFact), OdinText.Tamed, Vars(num5));
			}
			double num6 = today.Get("honey");
			if (num6 >= 1.0)
			{
				Add(c, 18.0, 11, "oddity", null, Pick(OdinText.HoneyFact), OdinText.Honey, Vars(num6));
			}
			double num7 = today.Get("arrows");
			if (num7 >= 60.0)
			{
				Add(c, 15.0, 11, "oddity", null, Pick(OdinText.ArrowsFact), OdinText.Arrows, Vars(num7));
			}
			double num8 = today.Get("graves");
			if (num8 >= 1.0)
			{
				Add(c, 20.0, 11, "death", null, Pick(OdinText.GravesFact), OdinText.Graves, Vars(num8));
			}
			if (today.Get("leviathan") >= 1.0)
			{
				Add(c, 42.0, 11, "oddity", null, Pick(OdinText.LeviathanFact), OdinText.Leviathan, Vars());
			}
		}

		private static void Add(List<Candidate> c, double score, int order, string theme, string themeName, string fact, string[] remarks, Dictionary<string, string> v)
		{
			c.Add(new Candidate
			{
				Score = score,
				Order = order,
				Theme = theme,
				ThemeName = themeName,
				Line = new SagaLine
				{
					Fact = Fill(fact, v),
					Remark = ((remarks != null) ? Fill(Pick(remarks), v) : "")
				}
			});
		}

		private static int Crossed(int[] marks, double before, double after)
		{
			int result = 0;
			foreach (int num in marks)
			{
				if (before < (double)num && after >= (double)num)
				{
					result = num;
				}
			}
			return result;
		}

		internal static string Body(SagaRecord s, bool farewell)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("<color=").Append("#B0B0B0").Append('>')
				.Append((s.Day > 0) ? $"Day {s.Day}, as told by Odin" : "As told by Odin")
				.Append("</color>\n\n");
			foreach (SagaLine line in s.Lines)
			{
				stringBuilder.Append("• ").Append(line.Fact);
				if (Plugin.ShowFlavor.Value && !string.IsNullOrEmpty(line.Remark))
				{
					stringBuilder.Append(" <i><color=").Append("#B9B3A8").Append('>')
						.Append(line.Remark)
						.Append("</color></i>");
				}
				stringBuilder.Append('\n');
			}
			if (Plugin.ShowFlavor.Value && !string.IsNullOrEmpty(s.Closing))
			{
				stringBuilder.Append("\n<i><color=").Append("#B9B3A8").Append('>')
					.Append(s.Closing)
					.Append("</color></i>");
			}
			if (farewell)
			{
				stringBuilder.Append("\n<color=").Append("#B0B0B0").Append(">Press any key to leave now.</color>");
			}
			return stringBuilder.ToString();
		}

		internal static string DeedsBody(DeedBook life)
		{
			//IL_060b: Unknown result type (might be due to invalid IL or missing references)
			StringBuilder stringBuilder = new StringBuilder();
			if (Plugin.ShowFlavor.Value)
			{
				stringBuilder.Append("<i><color=").Append("#B9B3A8").Append('>')
					.Append(Pick(OdinText.DeedsRemark))
					.Append("</color></i>\n\n");
			}
			int length = stringBuilder.Length;
			Row(stringBuilder, "Sagas told", life.Get("sagas"));
			if (Plugin.TrackKills.Value)
			{
				double num = life.Total("kill");
				if (num > 0.0)
				{
					Row(stringBuilder, "Slain", V(num) + " of " + V(life.Ranked("kill").Count) + " kinds");
				}
				string text = TopList(life.Ranked("kill"), 4);
				if (text != null)
				{
					stringBuilder.Append("  ").Append(text).Append('\n');
				}
				List<KeyValuePair<string, double>> list = life.Ranked("boss");
				if (list.Count > 0)
				{
					Row(stringBuilder, "Bosses felled", TopList(list, 8, raw: true));
				}
			}
			if (Plugin.TrackDeaths.Value && life.Total("death") > 0.0)
			{
				Row(stringBuilder, "Deaths", V(life.Total("death")) + ", most to " + Cause(life.Ranked("death")[0].Key));
			}
			if (Plugin.TrackCloseCalls.Value && life.Has("close"))
			{
				Row(stringBuilder, "Closest call", V(life.Get("close")) + " HP, against " + Cause(life.Notes.TryGetValue("close", out var value) ? value : ""));
			}
			if (Plugin.TrackTravel.Value)
			{
				List<string> list2 = new List<string>();
				if (life.Get("walk") >= 10.0)
				{
					list2.Add("walked " + V(Km(life.Get("walk"))));
				}
				if (life.Get("sail") >= 10.0)
				{
					list2.Add("sailed " + V(Km(life.Get("sail"))));
				}
				if (life.Get("swim") >= 10.0)
				{
					list2.Add("swum " + V(Km(life.Get("swim"))));
				}
				if (life.Get("ride") >= 10.0)
				{
					list2.Add("ridden " + V(Km(life.Get("ride"))));
				}
				if (list2.Count > 0)
				{
					Row(stringBuilder, "Travelled", string.Join(", ", list2.ToArray()));
				}
			}
			if (Plugin.TrackGathering.Value)
			{
				Row(stringBuilder, "Trees felled", life.Get("trees"));
				Row(stringBuilder, "Rocks broken", life.Get("rocks"));
				string text2 = TopList(life.Ranked("haul"), 3);
				if (text2 != null)
				{
					Row(stringBuilder, "Hauled most", text2);
				}
				Row(stringBuilder, "Harvested", life.Get("harvest"));
				Row(stringBuilder, "Fish caught", life.Get("fish"));
			}
			if (Plugin.TrackBuilding.Value)
			{
				Row(stringBuilder, "Pieces built", life.Total("build"));
				Row(stringBuilder, "Torn down", life.Get("unbuild"));
				Row(stringBuilder, "Items crafted", life.Total("craft"));
			}
			if (Plugin.TrackFood.Value && life.Total("food") > 0.0)
			{
				Row(stringBuilder, "Meals eaten", V(life.Total("food")) + ", favourite " + L(life.Ranked("food")[0].Key));
			}
			if (Plugin.TrackSkills.Value)
			{
				Row(stringBuilder, "Skill levels gained", life.Total("skill"));
			}
			if (Plugin.TrackExploration.Value)
			{
				List<KeyValuePair<string, double>> list3 = life.Ranked("biome");
				if (list3.Count > 0)
				{
					List<string> list4 = new List<string>();
					foreach (KeyValuePair<string, double> item in list3)
					{
						list4.Add(L(item.Key));
					}
					Row(stringBuilder, "Lands discovered", string.Join(", ", list4.ToArray()));
				}
				Row(stringBuilder, "Portals used", life.Get("portals"));
			}
			if (Plugin.TrackOddities.Value)
			{
				Row(stringBuilder, "Creatures tamed", life.Get("tamed"));
				Row(stringBuilder, "Pets given", life.Get("pets"));
				Row(stringBuilder, "Jumps", life.Get("jumps"));
			}
			if (stringBuilder.Length == length)
			{
				stringBuilder.Append("<color=").Append("#B0B0B0").Append(">No deeds yet. Go and do something worth a verse.</color>\n");
			}
			stringBuilder.Append("\n<color=").Append("#B0B0B0").Append('>')
				.Append(KeyName(Plugin.PastSagaKey.Value))
				.Append(": past sagas. Type /saga in chat to share one.</color>");
			return stringBuilder.ToString();
		}

		private static void Row(StringBuilder sb, string label, double n)
		{
			if (n > 0.0)
			{
				Row(sb, label, V(n));
			}
		}

		private static void Row(StringBuilder sb, string label, string value)
		{
			sb.Append(label).Append(": ").Append(value)
				.Append('\n');
		}

		private static string TopList(List<KeyValuePair<string, double>> ranked, int count, bool raw = false)
		{
			if (ranked.Count == 0)
			{
				return null;
			}
			List<string> list = new List<string>();
			for (int i = 0; i < ranked.Count && i < count; i++)
			{
				list.Add((raw && ranked[i].Value == 1.0) ? L(ranked[i].Key) : (L(ranked[i].Key) + " ×" + V(ranked[i].Value)));
			}
			return string.Join(", ", list.ToArray());
		}

		internal static List<string> ChatLines(SagaRecord s)
		{
			List<string> list = new List<string> { (s.Day > 0) ? $"~ {s.Title}, day {s.Day} ~" : ("~ " + s.Title + " ~") };
			foreach (SagaLine line in s.Lines)
			{
				list.Add(Plain((Plugin.ShowFlavor.Value && !string.IsNullOrEmpty(line.Remark)) ? (line.Fact + " " + line.Remark) : line.Fact));
			}
			return list;
		}

		internal static List<string> DeedsChatLines(DeedBook life, string who)
		{
			List<string> list = new List<string> { "~ The Deeds of " + who + " ~" };
			List<KeyValuePair<string, double>> list2 = life.Ranked("kill");
			if (list2.Count > 0)
			{
				list.Add("Slain: " + Num(life.Total("kill")) + ", most of them " + Plural(L(list2[0].Key)) + " (" + Num(list2[0].Value) + ").");
			}
			List<KeyValuePair<string, double>> list3 = life.Ranked("boss");
			if (list3.Count > 0)
			{
				list.Add("Bosses felled: " + Plain(TopList(list3, 8, raw: true)) + ".");
			}
			if (life.Total("death") > 0.0)
			{
				list.Add("Died " + Num(life.Total("death")) + " times, most to " + Plain(Cause(life.Ranked("death")[0].Key)) + ".");
			}
			if (life.Has("close"))
			{
				list.Add("Closest call: " + Num(life.Get("close")) + " HP.");
			}
			if (life.Get("walk") >= 1000.0)
			{
				list.Add("Walked " + Km(life.Get("walk")) + ", sailed " + Km(life.Get("sail")) + ".");
			}
			if (life.Get("trees") > 0.0 || life.Total("build") > 0.0)
			{
				list.Add("Felled " + Num(life.Get("trees")) + " trees and built " + Num(life.Total("build")) + " pieces.");
			}
			return list;
		}

		internal static string Plain(string s)
		{
			return Regex.Replace(s ?? "", "<[^>]*>", "");
		}

		private static Dictionary<string, string> Vars(double n = 0.0, string name = null, double hp = 0.0, double km = -1.0, Dictionary<string, string> extra = null)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string> { ["n"] = V(n) };
			if (name != null)
			{
				dictionary["name"] = name;
				dictionary["names"] = Plural(name);
				dictionary["a"] = Article(name) + " " + name;
				dictionary["A"] = Cap(dictionary["a"]);
			}
			if (hp > 0.0)
			{
				dictionary["hp"] = V(hp);
			}
			if (km >= 0.0)
			{
				dictionary["km"] = V(Km(km));
			}
			if (extra != null)
			{
				foreach (KeyValuePair<string, string> item in extra)
				{
					dictionary[item.Key] = V(item.Value);
				}
			}
			return dictionary;
		}

		private static string V(double n)
		{
			return V(Num(n));
		}

		private static string V(string s)
		{
			return "<color=orange>" + s + "</color>";
		}

		private static string Num(double n)
		{
			return ((long)Math.Round(n)).ToString("N0", CultureInfo.InvariantCulture);
		}

		private static string Km(double metres)
		{
			if (!(metres >= 10000.0))
			{
				if (!(metres >= 1000.0))
				{
					return $"{Math.Round(metres):0} m";
				}
				return (metres / 1000.0).ToString("0.0", CultureInfo.InvariantCulture) + " km";
			}
			return $"{metres / 1000.0:0} km";
		}

		internal static string Fill(string template, Dictionary<string, string> v)
		{
			if (v == null)
			{
				return template;
			}
			StringBuilder stringBuilder = new StringBuilder(template);
			foreach (KeyValuePair<string, string> item in v)
			{
				stringBuilder.Replace("{" + item.Key + "}", item.Value);
			}
			return stringBuilder.ToString();
		}

		internal static string Pick(string[] pool)
		{
			return pool[Rng.Next(pool.Length)];
		}

		internal static string L(string token)
		{
			if (string.IsNullOrEmpty(token))
			{
				return "something";
			}
			string text = ((Localization.instance != null) ? Localization.instance.Localize(token) : token);
			if (text.StartsWith("[") && text.EndsWith("]"))
			{
				text = text.Substring(1, text.Length - 2);
			}
			text = text.TrimStart(new char[1] { '$' });
			int num = text.IndexOf('_');
			if (num >= 0 && text.IndexOf(' ') < 0)
			{
				text = text.Substring(num + 1).Replace('_', ' ');
			}
			if (text.Length <= 0)
			{
				return text;
			}
			return char.ToUpperInvariant(text[0]) + text.Substring(1);
		}

		private static string SkillName(string token)
		{
			string text = L(token);
			if (!long.TryParse(text, out var _))
			{
				return text;
			}
			return "a secret skill";
		}

		internal static string Cause(string key)
		{
			if (string.IsNullOrEmpty(key))
			{
				return "something";
			}
			if (key.StartsWith("@"))
			{
				return key.Substring(1);
			}
			if (!key.StartsWith("~"))
			{
				string text = L(key);
				return Article(text) + " " + text;
			}
			return key switch
			{
				"~fall" => "the ground", 
				"~drowning" => "the sea", 
				"~burning" => "fire", 
				"~freezing" => "the cold", 
				"~poisoned" => "poison",