Decompiled source of Unshamed v1.0.5

Unshamed.dll

Decompiled a month ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using Unshamed.Guide;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Unshamed")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Azumatt")]
[assembly: AssemblyProduct("Unshamed")]
[assembly: AssemblyCopyright("Copyright ©  2022")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("E0E2F92E-557C-4A05-9D89-AA92A0BD75C4")]
[assembly: AssemblyFileVersion("1.0.5")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.5.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 Unshamed
{
	internal static class Audit
	{
		private const int Raw = 0;

		private const int Any = 1;

		private static readonly int FirstProgressKey = 42;

		private static readonly HashSet<string> LooksLikeCheating = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "nobuildcost", "nocraftcost", "allrecipesunlocked", "allpiecesunlocked", "noworkbench", "dungeonbuild", "teleportall", "passivemobs" };

		internal static List<string> Run(PlayerProfile profile)
		{
			List<string> list = new List<string>();
			PlayerStats val = profile.m_playerStats[0];
			list.Add("--- eligibility ---");
			list.Add("Can earn achievements: " + Eligibility.CanEarn() + " (vanilla would say " + Eligibility.VanillaCanEarn() + ")");
			list.Add("   " + Eligibility.Describe());
			string bypassWarning = Eligibility.BypassWarning;
			if (bypassWarning != null)
			{
				list.Add(bypassWarning);
			}
			list.Add("Cheats stat: " + (val.m_stats.TryGetValue((PlayerStatType)4, out var value) ? value : 0f));
			List<string> list2 = CheatFlags.CheatCommandsUsed(profile).ToList();
			list.Add("Cheat commands in history: " + ((list2.Count == 0) ? "none" : string.Join(", ", list2)));
			list.Add("Commands in history overall: " + val.m_knownCommands.Count + " (vanilla records every command you run, not just cheats)");
			Buckets(profile, list);
			Keys(val, list);
			return list;
		}

		private static void Buckets(PlayerProfile profile, List<string> report)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Invalid comparison between Unknown and I4
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Expected I4, but got Unknown
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			report.Add("");
			report.Add("--- stat buckets ---");
			foreach (DifficultyRequirement item in Enum.GetValues(typeof(DifficultyRequirement)).Cast<DifficultyRequirement>())
			{
				if ((int)item != 10)
				{
					PlayerStats val = profile.m_playerStats[item];
					int num = val.m_enemyStats.Sum((Dictionary<string, float> d) => d.Count) + val.m_itemPickupStats.Count + val.m_itemCraftStats.Count + val.m_pickableStats.Count + val.m_foodEatenStats.Count + val.m_piecesPlacedStats.Count;
					report.Add($"{(int)item,2} {item,-12} {val.m_stats.Count((KeyValuePair<PlayerStatType, float> s) => s.Value > 0f),4} counters, {num,5} named entries");
				}
			}
			StatSync.Result result = StatSync.Apply(profile, preview: true);
			report.Add(result.Any ? ("RawStats is ahead of Any by " + result.ToString() + ". 'unshamed retro' copies that forward.") : "Any is level with RawStats, nothing to copy forward.");
		}

		private static void Keys(PlayerStats raw, List<string> report)
		{
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Invalid comparison between Unknown and I4
			List<string> list = new List<string>();
			List<string> list2 = new List<string>();
			List<string> list3 = new List<string>();
			List<string> list4 = new List<string>();
			List<string> list5 = new List<string>();
			foreach (KeyValuePair<string, float> knownWorldKey in raw.m_knownWorldKeys)
			{
				Split(knownWorldKey.Key, out string key, out string value);
				string item = $"{knownWorldKey.Key} ({TimeSpan.FromSeconds(knownWorldKey.Value):c})";
				GlobalKeys result;
				if (key.Length > 0 && key.All(char.IsDigit))
				{
					list4.Add(item);
				}
				else if (!Enum.TryParse<GlobalKeys>(key, ignoreCase: true, out result) || !Enum.IsDefined(typeof(GlobalKeys), result))
				{
					(key.Contains("7656119") ? list5 : list3).Add(item);
				}
				else if (!(value == "default") && (int)result < FirstProgressKey)
				{
					(LooksLikeCheating.Contains(key) ? list2 : list).Add(item);
				}
			}
			report.Add("");
			report.Add("--- world keys (" + raw.m_knownWorldKeys.Count + " entries, and vanilla never clears this) ---");
			Section(report, "Most likely to be read as cheating", list2);
			Section(report, "Other non-default modifiers", list);
			Section(report, "No vanilla GlobalKeys member matches, so a mod put them there", list3);
			Section(report, "Entries carrying somebody's SteamID", list5);
			Section(report, "Bare numbers, which vanilla wrote itself", list4);
			report.Add("");
			report.Add("These are your character's keys. What actually marks a world cheated in 1.0 is the world's own m_startingGlobalKeys, checked by ServerOptionsGUI.WorldContainsCheatedModifiers: anything you cannot set through the World Modifiers menu counts.");
		}

		private static void Section(List<string> report, string title, List<string> lines)
		{
			report.Add(title + ": " + ((lines.Count == 0) ? "none" : lines.Count.ToString()));
			foreach (string item in lines.OrderBy((string l) => l))
			{
				report.Add("   " + item);
			}
		}

		internal static void Split(string entry, out string key, out string? value)
		{
			int num = entry.IndexOf(' ');
			if (num < 0)
			{
				key = entry;
				value = null;
			}
			else
			{
				key = entry.Substring(0, num);
				value = entry.Substring(num + 1);
			}
		}
	}
	[HarmonyPatch(typeof(Achievements), "CanGetAchievements")]
	internal static class Achievements_CanGetAchievements_Patch
	{
		private static void Postfix(ref bool __result)
		{
			if (!__result && UnshamedPlugin.On(UnshamedPlugin.IgnoreCheatFlags))
			{
				__result = true;
			}
		}
	}
	internal static class Eligibility
	{
		internal static bool VanillaBypass => PlayerProfile.s_bypassCheatChecks;

		internal static string? BypassWarning
		{
			get
			{
				if (!VanillaBypass)
				{
					return null;
				}
				return "<color=orange>Vanilla's cheat bypass is ON for this character. Anything spawned while it's on is NOT flagged as cheated. Unshamed doesn't need it, 'yesiuseddevcommandsbutiwantmyachievementsanyway 0' turns it off.</color>";
			}
		}

		internal static bool CanEarn()
		{
			return Achievements.CanGetAchievements(false);
		}

		internal static bool VanillaCanEarn()
		{
			if (Achievements.IsCheatedAtAll())
			{
				return VanillaBypass;
			}
			return true;
		}

		private static bool WorldCheated()
		{
			try
			{
				return Achievements.IsWorldCheated();
			}
			catch (Exception)
			{
				return false;
			}
		}

		internal static string Describe()
		{
			if (!Object.op_Implicit((Object)(object)Game.instance))
			{
				return "no game instance yet";
			}
			bool usedCheats = Game.instance.GetPlayerProfile().m_usedCheats;
			bool flag = Object.op_Implicit((Object)(object)Player.m_localPlayer) && ((Humanoid)Player.m_localPlayer).GetInventory().AnyCheatedItem();
			return $"m_usedCheats {usedCheats}, cheated world {WorldCheated()}, cheated items {flag}, Game.isModded {Game.isModded}, vanilla bypass {VanillaBypass}" + (UnshamedPlugin.On(UnshamedPlugin.IgnoreCheatFlags) ? " (recorded, ignored, never cleared)" : "");
		}
	}
	internal static class StatSync
	{
		internal readonly struct Result
		{
			internal readonly int Stats;

			internal readonly int Entries;

			internal bool Any => Stats + Entries > 0;

			internal Result(int stats, int entries)
			{
				Stats = stats;
				Entries = entries;
			}

			public override string ToString()
			{
				return $"{Stats} counters and {Entries} named entries";
			}
		}

		internal sealed class Wanted
		{
			internal readonly HashSet<PlayerStatType> Stats = new HashSet<PlayerStatType>();

			internal readonly HashSet<string> Names = new HashSet<string>(StringComparer.Ordinal);

			internal Wanted(IEnumerable<Achievement> achievements)
			{
				//IL_0044: Unknown result type (might be due to invalid IL or missing references)
				//IL_0049: Unknown result type (might be due to invalid IL or missing references)
				//IL_0050: Unknown result type (might be due to invalid IL or missing references)
				//IL_0051: Unknown result type (might be due to invalid IL or missing references)
				//IL_0086: Unknown result type (might be due to invalid IL or missing references)
				//IL_008b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0093: Unknown result type (might be due to invalid IL or missing references)
				foreach (Achievement achievement in achievements)
				{
					foreach (PlayerStatRequirement item in achievement.m_statTrigger)
					{
						Stats.Add(item.m_stat);
					}
					foreach (EnemyStatRequirement enemyStatsTrigger in achievement.m_enemyStatsTriggers)
					{
						Names.Add(enemyStatsTrigger.m_stat);
					}
					Add(achievement.m_itemCraftTriggers);
					Add(achievement.m_itemPickupTriggers);
					Add(achievement.m_pickableTriggers);
					Add(achievement.m_foodEatenTriggers);
					Add(achievement.m_piecePlacedTriggers);
					Add(achievement.m_knownCommandsTriggers);
					Add(achievement.m_knownWorldTriggers);
					Add(achievement.m_knownWorldKeysTriggers);
					foreach (Achievement otherAchievementTrigger in achievement.m_otherAchievementTriggers)
					{
						if ((Object)(object)otherAchievementTrigger != (Object)null)
						{
							Merge(otherAchievementTrigger);
						}
					}
				}
			}

			private void Merge(Achievement a)
			{
				//IL_0010: Unknown result type (might be due to invalid IL or missing references)
				//IL_0015: Unknown result type (might be due to invalid IL or missing references)
				//IL_001c: Unknown result type (might be due to invalid IL or missing references)
				//IL_001d: 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_0056: Unknown result type (might be due to invalid IL or missing references)
				//IL_005d: Unknown result type (might be due to invalid IL or missing references)
				foreach (PlayerStatRequirement item in a.m_statTrigger)
				{
					Stats.Add(item.m_stat);
				}
				foreach (EnemyStatRequirement enemyStatsTrigger in a.m_enemyStatsTriggers)
				{
					Names.Add(enemyStatsTrigger.m_stat);
				}
				Add(a.m_itemCraftTriggers);
				Add(a.m_itemPickupTriggers);
				Add(a.m_pickableTriggers);
				Add(a.m_foodEatenTriggers);
				Add(a.m_piecePlacedTriggers);
				Add(a.m_knownCommandsTriggers);
				Add(a.m_knownWorldTriggers);
				Add(a.m_knownWorldKeysTriggers);
			}

			private void Add(List<DictStatRequirement> triggers)
			{
				//IL_000b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0010: Unknown result type (might be due to invalid IL or missing references)
				//IL_0017: Unknown result type (might be due to invalid IL or missing references)
				foreach (DictStatRequirement trigger in triggers)
				{
					Names.Add(trigger.m_stat);
				}
			}
		}

		private sealed class Snapshot
		{
			private readonly Dictionary<PlayerStatType, float> _stats;

			private readonly Dictionary<string, float>[] _enemy;

			private readonly Dictionary<string, float>[] _named;

			internal Snapshot(PlayerStats from)
			{
				_stats = new Dictionary<PlayerStatType, float>(from.m_stats);
				_enemy = from.m_enemyStats.Select((Dictionary<string, float> d) => new Dictionary<string, float>(d)).ToArray();
				_named = (from d in Named(@from)
					select new Dictionary<string, float>(d)).ToArray();
			}

			internal void RestoreTo(PlayerStats to)
			{
				Replace(to.m_stats, _stats);
				for (int i = 0; i < _enemy.Length; i++)
				{
					Replace(to.m_enemyStats[i], _enemy[i]);
				}
				Dictionary<string, float>[] array = Named(to);
				for (int j = 0; j < array.Length; j++)
				{
					Replace(array[j], _named[j]);
				}
			}

			private static Dictionary<string, float>[] Named(PlayerStats s)
			{
				return new Dictionary<string, float>[8] { s.m_itemPickupStats, s.m_itemCraftStats, s.m_pickableStats, s.m_foodEatenStats, s.m_piecesPlacedStats, s.m_knownWorlds, s.m_knownWorldKeys, s.m_knownCommands };
			}

			private static void Replace<TKey>(Dictionary<TKey, float> live, Dictionary<TKey, float> saved)
			{
				live.Clear();
				foreach (KeyValuePair<TKey, float> item in saved)
				{
					live[item.Key] = item.Value;
				}
			}
		}

		private const int Raw = 0;

		private const int Any = 1;

		internal static Result Apply(PlayerProfile profile, bool preview, Wanted? only = null)
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			int num = 0;
			int num2 = 0;
			PlayerStats val = profile.m_playerStats[0];
			PlayerStats val2 = profile.m_playerStats[1];
			foreach (KeyValuePair<PlayerStatType, float> stat in val.m_stats)
			{
				if ((only == null || only.Stats.Contains(stat.Key)) && (!val2.m_stats.TryGetValue(stat.Key, out var value) || !(value >= stat.Value)))
				{
					if (!preview)
					{
						val2.m_stats[stat.Key] = stat.Value;
					}
					num++;
				}
			}
			for (int i = 0; i < val.m_enemyStats.Length; i++)
			{
				num2 += Merge(val.m_enemyStats[i], val2.m_enemyStats[i], preview, only);
			}
			num2 += Merge(val.m_itemPickupStats, val2.m_itemPickupStats, preview, only);
			num2 += Merge(val.m_itemCraftStats, val2.m_itemCraftStats, preview, only);
			num2 += Merge(val.m_pickableStats, val2.m_pickableStats, preview, only);
			num2 += Merge(val.m_foodEatenStats, val2.m_foodEatenStats, preview, only);
			num2 += Merge(val.m_piecesPlacedStats, val2.m_piecesPlacedStats, preview, only);
			num2 += Merge(val.m_knownWorlds, val2.m_knownWorlds, preview, only);
			num2 += Merge(val.m_knownWorldKeys, val2.m_knownWorldKeys, preview, only);
			num2 += Merge(val.m_knownCommands, val2.m_knownCommands, preview, only);
			return new Result(num, num2);
		}

		private static int Merge(Dictionary<string, float> from, Dictionary<string, float> to, bool preview, Wanted? only)
		{
			int num = 0;
			foreach (KeyValuePair<string, float> item in from)
			{
				if ((only == null || only.Names.Contains(item.Key)) && (!to.TryGetValue(item.Key, out var value) || !(value >= item.Value)))
				{
					if (!preview)
					{
						to[item.Key] = item.Value;
					}
					num++;
				}
			}
			return num;
		}

		internal static List<Achievement> Preview(PlayerProfile profile)
		{
			List<Achievement> list = new List<Achievement>();
			PlayerStats val = profile.m_playerStats[1];
			Snapshot snapshot = new Snapshot(val);
			try
			{
				Achievements.MarkAchievementDifficultyDirty();
				Apply(profile, preview: false);
				foreach (Achievement item in All())
				{
					if (!item.m_unlocked && item.CheckUnlocked())
					{
						item.m_unlocked = false;
						list.Add(item);
					}
				}
			}
			finally
			{
				snapshot.RestoreTo(val);
				foreach (Achievement item2 in list)
				{
					item2.m_unlocked = false;
				}
				Achievements.MarkAchievementDifficultyDirty();
			}
			return list;
		}

		internal static List<Achievement> Sweep()
		{
			List<Achievement> list = new List<Achievement>();
			if (!Object.op_Implicit((Object)(object)Achievements.m_instance))
			{
				return list;
			}
			Achievements.MarkAchievementDifficultyDirty();
			foreach (Achievement item in All())
			{
				if (!item.m_unlocked && item.CheckUnlocked())
				{
					Achievements.AchievementEvent(item, false, UnshamedPlugin.On(UnshamedPlugin.ShowPopups));
					list.Add(item);
				}
			}
			return list;
		}

		internal static IEnumerable<Achievement> All()
		{
			if (!Object.op_Implicit((Object)(object)Achievements.m_instance))
			{
				yield break;
			}
			foreach (AchievementList achievementList in Achievements.m_instance.m_achievementLists)
			{
				if ((Object)(object)achievementList == (Object)null)
				{
					continue;
				}
				foreach (Achievement achievement in achievementList.m_achievements)
				{
					if ((Object)(object)achievement != (Object)null)
					{
						yield return achievement;
					}
				}
			}
		}

		internal static string Name(Achievement a)
		{
			string text = Localization.instance.Localize(a.m_name);
			if (!string.IsNullOrEmpty(text))
			{
				return text;
			}
			return a.m_id;
		}

		internal static Achievement? Find(string id)
		{
			return All().FirstOrDefault((Func<Achievement, bool>)((Achievement a) => string.Equals(a.m_id, id, StringComparison.OrdinalIgnoreCase)));
		}
	}
	internal static class AutoRetro
	{
		private static bool _done;

		private static bool _announced;

		private static float _next;

		internal static void Tick()
		{
			Announce();
			if (_done || !UnshamedPlugin.On(UnshamedPlugin.Retroactive) || Time.realtimeSinceStartup < _next)
			{
				return;
			}
			_next = Time.realtimeSinceStartup + 3f;
			if (!Object.op_Implicit((Object)(object)Game.instance) || !Object.op_Implicit((Object)(object)Player.m_localPlayer) || !Object.op_Implicit((Object)(object)Achievements.m_instance))
			{
				return;
			}
			PlayerProfile playerProfile = Game.instance.GetPlayerProfile();
			if (playerProfile == null)
			{
				return;
			}
			if (!Eligibility.CanEarn())
			{
				_done = true;
				UnshamedPlugin.UnshamedLogger.LogInfo((object)("Retroactive skipped, this character still counts as cheated: " + Eligibility.Describe()));
				return;
			}
			StatSync.Result result = StatSync.Apply(playerProfile, preview: true);
			_done = true;
			if (!result.Any)
			{
				return;
			}
			StatSync.Apply(playerProfile, preview: false);
			List<Achievement> list = StatSync.Sweep();
			playerProfile.Save();
			UnshamedPlugin.UnshamedLogger.LogInfo((object)$"Retroactive copied {result}, vanilla unlocked {list.Count}");
			foreach (Achievement item in list)
			{
				UnshamedPlugin.UnshamedLogger.LogInfo((object)("  " + item.m_id + " - " + StatSync.Name(item)));
			}
		}

		private static void Announce()
		{
			if (_announced || !Object.op_Implicit((Object)(object)Game.instance) || !Object.op_Implicit((Object)(object)Player.m_localPlayer) || !Object.op_Implicit((Object)(object)Achievements.m_instance))
			{
				return;
			}
			_announced = true;
			PlayerProfile playerProfile = Game.instance.GetPlayerProfile();
			int num = StatSync.All().Count((Achievement a) => a.m_unlocked);
			int num2 = StatSync.All().Count();
			UnshamedPlugin.UnshamedLogger.LogInfo((object)$"Eligible for achievements: {Eligibility.CanEarn()} ({Eligibility.Describe()})");
			if (Eligibility.VanillaBypass)
			{
				UnshamedPlugin.UnshamedLogger.LogWarning((object)"Vanilla's cheat bypass is on for this character. Items spawned now won't be flagged.");
			}
			UnshamedPlugin.UnshamedLogger.LogInfo((object)$"Achievements {num}/{num2} unlocked.");
			if (!Eligibility.CanEarn())
			{
				return;
			}
			List<Achievement> list = StatSync.Preview(playerProfile);
			if (list.Count == 0)
			{
				UnshamedPlugin.UnshamedLogger.LogInfo((object)"Nothing waiting, this character has already been credited with what it earned.");
				return;
			}
			UnshamedPlugin.UnshamedLogger.LogInfo((object)$"{list.Count} achievement(s) you already earned are waiting. Run 'unshamed retro' to see them, or 'unshamed retro apply' to take them.");
			foreach (Achievement item in list)
			{
				UnshamedPlugin.UnshamedLogger.LogInfo((object)("  " + item.m_id + " - " + StatSync.Name(item)));
			}
		}
	}
	internal static class CheatFlags
	{
		internal static IEnumerable<string> CheatCommandsUsed(PlayerProfile profile)
		{
			ConsoleCommand value;
			return profile.m_playerStats[0].m_knownCommands.Keys.Where((string k) => Terminal.commands.TryGetValue(k.ToLower(), out value) && value.IsCheat);
		}
	}
	[HarmonyPatch(typeof(Terminal), "InitTerminal")]
	internal static class Commands
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static ConsoleEvent <0>__Run;

			public static Func<PlayerProfile, List<string>> <1>__Run;
		}

		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static ConsoleOptionsFetcher <>9__1_0;

			public static Func<Achievement, bool> <>9__8_0;

			public static Func<Achievement, bool> <>9__9_0;

			public static Func<string, bool> <>9__12_0;

			internal List<string> <Postfix>b__1_0()
			{
				return new List<string> { "status", "audit", "list", "retro", "guide", "recon" };
			}

			internal bool <Status>b__8_0(Achievement a)
			{
				return a.m_unlocked;
			}

			internal bool <List>b__9_0(Achievement a)
			{
				return a.m_unlocked;
			}

			internal bool <Pick>b__12_0(string t)
			{
				return t.Length > 0;
			}
		}

		private static bool _registered;

		private static PlayerProfile? Profile
		{
			get
			{
				if (!Object.op_Implicit((Object)(object)Game.instance))
				{
					return null;
				}
				return Game.instance.GetPlayerProfile();
			}
		}

		private static void Postfix()
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected O, but got Unknown
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Expected O, but got Unknown
			if (_registered)
			{
				return;
			}
			_registered = true;
			object obj = <>O.<0>__Run;
			if (obj == null)
			{
				ConsoleEvent val = Run;
				<>O.<0>__Run = val;
				obj = (object)val;
			}
			object obj2 = <>c.<>9__1_0;
			if (obj2 == null)
			{
				ConsoleOptionsFetcher val2 = () => new List<string> { "status", "audit", "list", "retro", "guide", "recon" };
				<>c.<>9__1_0 = val2;
				obj2 = (object)val2;
			}
			new ConsoleCommand("unshamed", "status | audit | list | retro [apply [n|id ...]] | guide [id] | recon", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)obj2, false, false, false);
		}

		private static void Out(ConsoleEventArgs args, string line)
		{
			args.Context.AddString(line);
			UnshamedPlugin.UnshamedLogger.LogInfo((object)Strip(line));
		}

		private static string Strip(string line)
		{
			return Regex.Replace(line, "<[^>]+>", "");
		}

		private static void Run(ConsoleEventArgs args)
		{
			string text = ((args.Length > 1) ? args[1].ToLower() : "status");
			switch (text)
			{
			case "status":
				Status(args);
				break;
			case "audit":
				Report(args, Audit.Run);
				break;
			case "list":
				List(args);
				break;
			case "retro":
				Retroactive(args);
				break;
			case "guide":
				GuideCommand(args);
				break;
			case "recon":
				Out(args, "Wrote " + Recon.Run());
				break;
			default:
				Out(args, "Don't know '" + text + "'. Try status, audit, list, retro, guide or recon.");
				break;
			}
		}

		private static void Report(ConsoleEventArgs args, Func<PlayerProfile, List<string>> build)
		{
			PlayerProfile profile = Profile;
			if (profile == null)
			{
				Out(args, "No profile loaded yet.");
				return;
			}
			foreach (string item in build(profile))
			{
				Out(args, item);
			}
		}

		private static void Status(ConsoleEventArgs args)
		{
			PlayerProfile profile = Profile;
			if (profile == null)
			{
				Out(args, "No profile loaded yet.");
				return;
			}
			Out(args, "Eligible for achievements: " + Eligibility.CanEarn());
			Out(args, "   " + Eligibility.Describe());
			string bypassWarning = Eligibility.BypassWarning;
			if (bypassWarning != null)
			{
				Out(args, bypassWarning);
			}
			List<Achievement> list = StatSync.All().ToList();
			Out(args, (list.Count == 0) ? "Achievements: none loaded yet, get into a world first." : ("Achievements: " + list.Count((Achievement a) => a.m_unlocked) + " / " + list.Count + " unlocked"));
			StatSync.Result result = StatSync.Apply(profile, preview: true);
			Out(args, result.Any ? ("Retroactive: " + result.ToString() + " waiting, run 'unshamed retro'") : "Retroactive: nothing to copy forward");
		}

		private static void List(ConsoleEventArgs args)
		{
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			List<Achievement> list = StatSync.All().ToList();
			if (list.Count == 0)
			{
				Out(args, "No achievements loaded. Get into a world first.");
				return;
			}
			string filter = ((args.Length > 2) ? args[2].ToLower() : "");
			foreach (Achievement item in list.Where((Achievement a) => filter.Length == 0 || a.m_id.ToLower().Contains(filter)))
			{
				Out(args, string.Format("{0} {1} ({2}) - {3}", item.m_unlocked ? "<color=green>[x]</color>" : "[ ]", item.m_id, item.m_difficultyRequirement, StatSync.Name(item)));
			}
			Out(args, list.Count((Achievement a) => a.m_unlocked) + " / " + list.Count + " unlocked");
		}

		private static void GuideCommand(ConsoleEventArgs args)
		{
			if (Profile == null || !Object.op_Implicit((Object)(object)Achievements.m_instance))
			{
				Out(args, "Achievements aren't loaded yet. Get into a world first.");
				return;
			}
			if (args.Length < 3)
			{
				Out(args, "Wrote " + GuideDump.Write());
				return;
			}
			Achievement val = StatSync.Find(args[2]);
			if ((Object)(object)val == (Object)null)
			{
				Out(args, "Don't know '" + args[2] + "'. 'unshamed list' has the ids.");
				return;
			}
			Out(args, "<color=yellow>" + StatSync.Name(val) + "</color> (" + val.m_id + ")");
			string[] array = AchievementsPanel.Compose(val, rich: false).Split(new char[1] { '\n' });
			foreach (string text in array)
			{
				Out(args, text.TrimEnd(new char[1] { '\r' }));
			}
			foreach (Requirement item in Requirements.Sorted(val))
			{
				Out(args, "  " + (item.Met ? "<color=green>[x]</color>" : "[ ]") + " " + item.Label + " (" + item.Progress + ")");
			}
		}

		private static void Retroactive(ConsoleEventArgs args)
		{
			PlayerProfile profile = Profile;
			if (profile == null)
			{
				Out(args, "No profile loaded yet.");
				return;
			}
			if (!Object.op_Implicit((Object)(object)Achievements.m_instance))
			{
				Out(args, "Achievements aren't loaded yet. Get into a world first.");
				return;
			}
			if (!Eligibility.CanEarn())
			{
				Out(args, "<color=orange>This character still counts as cheated, so vanilla would refuse the unlocks.</color>");
				Out(args, "   " + Eligibility.Describe());
				return;
			}
			List<Achievement> list = StatSync.Preview(profile);
			if (list.Count == 0)
			{
				StatSync.Result result = StatSync.Apply(profile, preview: true);
				Out(args, result.Any ? ("Nothing new would unlock, though there is still " + result.ToString() + " to copy forward.") : "Nothing to copy forward and nothing new to unlock.");
				return;
			}
			if (args.Length <= 2 || !(args[2].ToLower() == "apply"))
			{
				Out(args, list.Count + " achievement(s) your character already earned:");
				for (int i = 0; i < list.Count; i++)
				{
					Out(args, $"  <color=yellow>{i + 1,2}</color> {list[i].m_id,-28} {StatSync.Name(list[i])}");
				}
				Out(args, "'unshamed retro apply' takes all of them.");
				Out(args, "'unshamed retro apply 1 3 4' or 'unshamed retro apply <id>' takes only those.");
				return;
			}
			List<string> unknown;
			List<Achievement> list2 = Pick(args, list, out unknown);
			foreach (string item in unknown)
			{
				Out(args, "<color=orange>Don't know '" + item + "', skipping it.</color>");
			}
			if (list2.Count == 0)
			{
				Out(args, "Nothing selected, nothing done.");
				return;
			}
			StatSync.Wanted only = new StatSync.Wanted(list2);
			StatSync.Result result2 = StatSync.Apply(profile, preview: false, only);
			List<Achievement> list3 = StatSync.Sweep();
			profile.Save();
			Out(args, "Copied " + result2.ToString() + ".");
			Out(args, "Vanilla unlocked " + list3.Count + ":");
			foreach (Achievement item2 in list3)
			{
				Out(args, "   " + item2.m_id + " - " + StatSync.Name(item2));
			}
			int num = list.Count - list3.Count;
			if (num > 0)
			{
				Out(args, num + " left on the table, run 'unshamed retro' again to see them.");
			}
		}

		private static List<Achievement> Pick(ConsoleEventArgs args, List<Achievement> would, out List<string> unknown)
		{
			unknown = new List<string>();
			List<string> list = (from t in args.Args.Skip(3)
				where t.Length > 0
				select t).ToList();
			if (list.Count == 0)
			{
				return would;
			}
			List<Achievement> list2 = new List<Achievement>();
			foreach (string token in list)
			{
				if (int.TryParse(token, out var result))
				{
					if (result >= 1 && result <= would.Count)
					{
						list2.Add(would[result - 1]);
					}
					else
					{
						unknown.Add(token);
					}
					continue;
				}
				Achievement val = ((IEnumerable<Achievement>)would).FirstOrDefault((Func<Achievement, bool>)((Achievement a) => string.Equals(a.m_id, token, StringComparison.OrdinalIgnoreCase)));
				if ((Object)(object)val != (Object)null)
				{
					list2.Add(val);
				}
				else
				{
					unknown.Add(token);
				}
			}
			return list2.Distinct().ToList();
		}
	}
	internal static class Messaging
	{
		[HarmonyPatch(typeof(Localization), "SetupLanguage")]
		private static class SetupLanguage_Patch
		{
			private static void Postfix()
			{
				Vanilla.Clear();
				Refresh();
			}
		}

		private static readonly Dictionary<string, string> Rewrites = new Dictionary<string, string>
		{
			["achievements_confirm_cheat"] = "That command is a cheat, please enter 'confirmcheats' in the console to use cheats. <color=orange>Unshamed keeps your achievements working. Vanilla will still mark this character and this world as cheated.</color>",
			["achievements_permanently_cheated_character"] = "Vanilla has this character marked as cheated. Unshamed ignores that, so achievements still count.",
			["achievements_permanently_cheated_world"] = "Vanilla has this world marked as cheated. Unshamed ignores that, so achievements still count.",
			["achievements_temporarily_cheated"] = "You are carrying a cheated item. Unshamed ignores that, so achievements still count.",
			["achievements_picked_up_cheated_item"] = "Picked up a cheated item. Achievements still count.",
			["achievements_dropped_cheated_item"] = "No more cheated items in your inventory."
		};

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

		internal static void Refresh()
		{
			Localization instance = Localization.m_instance;
			if (instance == null)
			{
				return;
			}
			bool flag = UnshamedPlugin.On(UnshamedPlugin.IgnoreCheatFlags);
			foreach (KeyValuePair<string, string> rewrite in Rewrites)
			{
				if (instance.m_translations.TryGetValue(rewrite.Key, out var value))
				{
					if (!Vanilla.ContainsKey(rewrite.Key))
					{
						Vanilla[rewrite.Key] = value;
					}
					instance.m_translations[rewrite.Key] = (flag ? rewrite.Value : Vanilla[rewrite.Key]);
				}
			}
			instance.m_cache.EvictAll();
		}
	}
	[HarmonyPatch(typeof(Game), "Awake")]
	internal static class Game_Awake_Patch
	{
		private static void Prefix()
		{
			Game.isModded = true;
		}
	}
	[BepInPlugin("Azumatt.Unshamed", "Unshamed", "1.0.5")]
	public class UnshamedPlugin : BaseUnityPlugin
	{
		public enum Toggle
		{
			On = 1,
			Off = 0
		}

		private class ConfigurationManagerAttributes
		{
			[UsedImplicitly]
			public int? Order;

			[UsedImplicitly]
			public bool? Browsable;

			[UsedImplicitly]
			public string? Category;

			[UsedImplicitly]
			public Action<ConfigEntryBase>? CustomDrawer;
		}

		internal const string ModName = "Unshamed";

		internal const string ModVersion = "1.0.5";

		internal const string Author = "Azumatt";

		private const string ModGUID = "Azumatt.Unshamed";

		private static string ConfigFileName = "Azumatt.Unshamed.cfg";

		private static string ConfigFileFullPath;

		private readonly Harmony _harmony = new Harmony("Azumatt.Unshamed");

		public static readonly ManualLogSource UnshamedLogger;

		internal static UnshamedPlugin Instance;

		internal static ConfigEntry<Toggle> Enabled;

		internal static ConfigEntry<Toggle> IgnoreCheatFlags;

		internal static ConfigEntry<Toggle> ShowPopups;

		internal static ConfigEntry<string> ForceTrue;

		internal static ConfigEntry<string> ForceFalse;

		internal static ConfigEntry<Toggle> Retroactive;

		internal static ConfigEntry<Toggle> RevealRequirements;

		internal static ConfigEntry<Toggle> GuidePanel;

		public void Awake()
		{
			Instance = this;
			bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet;
			((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
			Enabled = config("1 - General", "Enabled", Toggle.On, "Master switch. Off means the mod touches nothing.");
			IgnoreCheatFlags = config("1 - General", "Ignore Cheat Flags", Toggle.On, "The fix. Achievements.CanGetAchievements is forced true, so none of the four cheat checks can stop you earning: Game.isModded, m_usedCheats, cheated items, cheated worlds. Nothing gets cleared. Vanilla still records all four, spawned items still carry m_cheated onto the server, and the warnings that used to say achievements were disabled get reworded instead of hidden. Turn this off and you are back to vanilla, disqualified for having BepInEx.");
			ShowPopups = config("1 - General", "Show Popups", Toggle.Off, "Show the vanilla unlock popup for each achievement the retroactive pass grants. Off because a big backlog means a lot of popups at once.");
			ForceTrue = config("2 - Overrides", "Force True Methods", "", "Comma separated Type::Method whose bool return gets forced true. A compatibility hatch for when a game patch moves something and the mod stops working.");
			ForceFalse = config("2 - Overrides", "Force False Methods", "", "Same thing the other way, bool return forced false.");
			Retroactive = config("3 - Retroactive", "Enable Retroactive", Toggle.Off, "Automatically grant EVERY achievement your character already earned, once, next time you load into a world. Off by default, and you do not need it: 'unshamed retro' in the console previews the list and lets you pick individual ones whether this is on or off. Turn this on only if you want the lot without looking.");
			RevealRequirements = config("4 - Guide", "Reveal Requirements", Toggle.Off, "The achievements panel stops hiding things. Secret achievements show, every achievement opens its details, rows you haven't finished show their real name and numbers instead of ??? / ???, and unfinished ones sort to the top. Spoilers, obviously.");
			GuidePanel = config("4 - Guide", "Guide Panel", Toggle.Off, "Show a panel next to the achievement details explaining in plain English what the achievement actually checks, the difficulty it needs and how far along you are.");
			Enabled.SettingChanged += delegate
			{
				Messaging.Refresh();
			};
			IgnoreCheatFlags.SettingChanged += delegate
			{
				Messaging.Refresh();
			};
			Assembly executingAssembly = Assembly.GetExecutingAssembly();
			_harmony.PatchAll(executingAssembly);
			ReturnOverrides.Apply(_harmony, ForceTrue.Value, ForceFalse.Value);
			Messaging.Refresh();
			SetupWatcher();
			if (saveOnConfigSet)
			{
				((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet;
				((BaseUnityPlugin)this).Config.Save();
			}
			UnshamedLogger.LogInfo((object)"Unshamed 1.0.5 loaded. Achievements ignore the cheat flags, nothing clears them.");
		}

		private void Update()
		{
			AutoRetro.Tick();
		}

		private void OnDestroy()
		{
			((BaseUnityPlugin)this).Config.Save();
		}

		private void SetupWatcher()
		{
			FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(Paths.ConfigPath, ConfigFileName);
			fileSystemWatcher.Changed += ReadConfigValues;
			fileSystemWatcher.Created += ReadConfigValues;
			fileSystemWatcher.Renamed += ReadConfigValues;
			fileSystemWatcher.IncludeSubdirectories = true;
			fileSystemWatcher.SynchronizingObject = ThreadingHelper.SynchronizingObject;
			fileSystemWatcher.EnableRaisingEvents = true;
		}

		private void ReadConfigValues(object sender, FileSystemEventArgs e)
		{
			if (!File.Exists(ConfigFileFullPath))
			{
				return;
			}
			try
			{
				UnshamedLogger.LogDebug((object)"ReadConfigValues called");
				((BaseUnityPlugin)this).Config.Reload();
			}
			catch
			{
				UnshamedLogger.LogError((object)("There was an issue loading your " + ConfigFileName));
				UnshamedLogger.LogError((object)"Please check your config entries for spelling and format!");
			}
		}

		internal static bool On(ConfigEntry<Toggle> entry)
		{
			if (Enabled.Value == Toggle.On)
			{
				return entry.Value == Toggle.On;
			}
			return false;
		}

		private ConfigEntry<T> config<T>(string group, string name, T value, ConfigDescription description)
		{
			return ((BaseUnityPlugin)this).Config.Bind<T>(group, name, value, description);
		}

		private ConfigEntry<T> config<T>(string group, string name, T value, string description)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			return config(group, name, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>()));
		}

		static UnshamedPlugin()
		{
			string configPath = Paths.ConfigPath;
			char directorySeparatorChar = Path.DirectorySeparatorChar;
			ConfigFileFullPath = configPath + directorySeparatorChar + ConfigFileName;
			UnshamedLogger = Logger.CreateLogSource("Unshamed");
			Instance = null;
			Enabled = null;
			IgnoreCheatFlags = null;
			ShowPopups = null;
			ForceTrue = null;
			ForceFalse = null;
			Retroactive = null;
			RevealRequirements = null;
			GuidePanel = null;
		}
	}
	public static class KeyboardExtensions
	{
		public static bool IsKeyDown(this KeyboardShortcut shortcut)
		{
			//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)
			if ((int)((KeyboardShortcut)(ref shortcut)).MainKey != 0 && Input.GetKeyDown(((KeyboardShortcut)(ref shortcut)).MainKey))
			{
				return ((KeyboardShortcut)(ref shortcut)).Modifiers.All((Func<KeyCode, bool>)Input.GetKey);
			}
			return false;
		}

		public static bool IsKeyHeld(this KeyboardShortcut shortcut)
		{
			//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)
			if ((int)((KeyboardShortcut)(ref shortcut)).MainKey != 0 && Input.GetKey(((KeyboardShortcut)(ref shortcut)).MainKey))
			{
				return ((KeyboardShortcut)(ref shortcut)).Modifiers.All((Func<KeyCode, bool>)Input.GetKey);
			}
			return false;
		}
	}
	internal static class Recon
	{
		private static readonly string[] Interesting = new string[5] { "achiev", "trophy", "unlock", "cheat", "milestone" };

		private static readonly string[] GameAssemblies = new string[6] { "assembly_valheim", "assembly_utils", "assembly_guiutils", "Splatform", "Splatform.Steam", "SoftReferenceableAssets" };

		internal static string Run()
		{
			StringBuilder stringBuilder = new StringBuilder();
			Versions(stringBuilder);
			Flags(stringBuilder);
			Symbols(stringBuilder);
			Enums(stringBuilder);
			CheatCommands(stringBuilder);
			Achievements(stringBuilder);
			string text = Path.Combine(Paths.ConfigPath, "Unshamed_recon.txt");
			File.WriteAllText(text, stringBuilder.ToString());
			return text;
		}

		private static void Versions(StringBuilder sb)
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: 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_00b5: Unknown result type (might be due to invalid IL or missing references)
			sb.AppendLine("=== versions ===");
			sb.AppendLine("game        " + Version.GetVersionString(false));
			sb.AppendLine("mod         1.0.5");
			sb.AppendLine("network     " + 40u);
			sb.AppendLine("player      " + ((object)(Player)46/*cast due to .constrained prefix*/).ToString());
			sb.AppendLine("world       " + ((object)(World)41/*cast due to .constrained prefix*/).ToString());
			sb.AppendLine("item data   " + ((object)(Item)109/*cast due to .constrained prefix*/).ToString());
			sb.AppendLine("player data " + ((object)(PlayerData)33/*cast due to .constrained prefix*/).ToString());
			sb.AppendLine("isModded    " + Game.isModded);
			sb.AppendLine();
		}

		private static void Flags(StringBuilder sb)
		{
			sb.AppendLine("=== bool fields worth a look ===");
			Type[] array = new Type[4]
			{
				typeof(PlayerProfile),
				typeof(World),
				typeof(ItemData),
				typeof(ConsoleCommand)
			};
			foreach (Type type in array)
			{
				foreach (FieldInfo item in from f in type.GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
					where f.FieldType == typeof(bool) && Matches(f.Name)
					select f)
				{
					sb.AppendLine(type.Name + "." + item.Name);
				}
			}
			sb.AppendLine();
		}

		private static void Symbols(StringBuilder sb)
		{
			sb.AppendLine("=== types and members matching " + string.Join("/", Interesting) + " ===");
			foreach (Assembly item in from a in AppDomain.CurrentDomain.GetAssemblies()
				where GameAssemblies.Contains<string>(a.GetName().Name)
				select a)
			{
				foreach (Type item2 in Types(item))
				{
					bool flag = Matches(item2.Name);
					if (flag)
					{
						sb.AppendLine("type   " + item2.FullName);
					}
					foreach (MemberInfo item3 in from m in item2.GetMembers(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
						where Matches(m.Name)
						select m)
					{
						if (!flag || item3.MemberType != MemberTypes.Method || !item3.Name.StartsWith("get_"))
						{
							sb.AppendLine("member " + item2.FullName + "." + Signature(item3));
						}
					}
				}
			}
			sb.AppendLine();
		}

		private static string Signature(MemberInfo member)
		{
			if (!(member is MethodInfo methodInfo))
			{
				return member.Name;
			}
			return methodInfo.Name + "(" + string.Join(", ", from p in methodInfo.GetParameters()
				select p.ParameterType.Name) + ") -> " + methodInfo.ReturnType.Name;
		}

		private static void Enums(StringBuilder sb)
		{
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Expected I4, but got Unknown
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_014a: Expected I4, but got Unknown
			//IL_014a: Unknown result type (might be due to invalid IL or missing references)
			PlayerProfile obj = (Object.op_Implicit((Object)(object)Game.instance) ? Game.instance.GetPlayerProfile() : null);
			Dictionary<PlayerStatType, float> dictionary = ((obj != null) ? obj.m_playerStats[0].m_stats : null);
			sb.AppendLine("=== PlayerStatType (" + Enum.GetValues(typeof(PlayerStatType)).Length + " members) ===");
			foreach (PlayerStatType item in Enum.GetValues(typeof(PlayerStatType)).Cast<PlayerStatType>())
			{
				float value;
				float num = ((dictionary != null && dictionary.TryGetValue(item, out value)) ? value : 0f);
				sb.AppendLine($"{(int)item,4}  {item,-28} {num}");
			}
			sb.AppendLine();
			sb.AppendLine("=== GlobalKeys ===");
			string text = default(string);
			foreach (GlobalKeys item2 in Enum.GetValues(typeof(GlobalKeys)).Cast<GlobalKeys>())
			{
				string arg = ((Object.op_Implicit((Object)(object)ZoneSystem.instance) && ZoneSystem.instance.GetGlobalKey(item2, ref text)) ? (" = " + text) : "");
				sb.AppendLine($"{(int)item2,4}  {item2}{arg}");
			}
			sb.AppendLine();
		}

		private static void CheatCommands(StringBuilder sb)
		{
			sb.AppendLine("=== commands that set the flag ===");
			foreach (KeyValuePair<string, ConsoleCommand> item in from c in Terminal.commands
				where c.Value.IsCheat
				orderby c.Key
				select c)
			{
				sb.AppendLine(item.Key);
			}
			sb.AppendLine();
		}

		private static void Achievements(StringBuilder sb)
		{
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			sb.AppendLine("=== achievements ===");
			List<Achievement> list = StatSync.All().ToList();
			if (list.Count == 0)
			{
				sb.AppendLine("none loaded, get into a world first");
				return;
			}
			sb.AppendLine(list.Count + " defined, " + list.Count((Achievement a) => a.m_unlocked) + " unlocked");
			foreach (Achievement item in list.OrderBy((Achievement a) => a.m_id))
			{
				sb.AppendLine(string.Format("{0} {1,-32} {2,-10} {3,-28} {4}", item.m_unlocked ? "[x]" : "[ ]", item.m_id, item.m_difficultyRequirement, item.m_name, StatSync.Name(item)));
			}
		}

		private static IEnumerable<Type> Types(Assembly assembly)
		{
			try
			{
				return assembly.GetTypes();
			}
			catch (ReflectionTypeLoadException ex)
			{
				return ex.Types.Where((Type t) => t != null);
			}
		}

		private static bool Matches(string name)
		{
			return Interesting.Any((string i) => name.IndexOf(i, StringComparison.OrdinalIgnoreCase) >= 0);
		}
	}
	internal static class ReturnOverrides
	{
		private static readonly HarmonyMethod TruePostfix = new HarmonyMethod(AccessTools.Method(typeof(ReturnOverrides), "ReturnTrue", (Type[])null, (Type[])null));

		private static readonly HarmonyMethod FalsePostfix = new HarmonyMethod(AccessTools.Method(typeof(ReturnOverrides), "ReturnFalse", (Type[])null, (Type[])null));

		internal static void Apply(Harmony harmony, string forceTrue, string forceFalse)
		{
			foreach (MethodInfo item in Resolve(forceTrue))
			{
				Patch(harmony, item, TruePostfix, "true");
			}
			foreach (MethodInfo item2 in Resolve(forceFalse))
			{
				Patch(harmony, item2, FalsePostfix, "false");
			}
		}

		private static void Patch(Harmony harmony, MethodInfo method, HarmonyMethod postfix, string value)
		{
			try
			{
				harmony.Patch((MethodBase)method, (HarmonyMethod)null, postfix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				UnshamedPlugin.UnshamedLogger.LogInfo((object)("Forcing " + method.DeclaringType?.Name + "." + method.Name + " to return " + value));
			}
			catch (Exception ex)
			{
				UnshamedPlugin.UnshamedLogger.LogError((object)("Couldn't patch " + method.DeclaringType?.Name + "." + method.Name + ": " + ex.Message));
			}
		}

		private static IEnumerable<MethodInfo> Resolve(string entries)
		{
			if (string.IsNullOrWhiteSpace(entries))
			{
				yield break;
			}
			foreach (string item in from e in entries.Split(new char[1] { ',' })
				select e.Trim() into e
				where e.Length > 0
				select e)
			{
				string[] array = item.Split(new string[1] { "::" }, StringSplitOptions.RemoveEmptyEntries);
				if (array.Length != 2)
				{
					UnshamedPlugin.UnshamedLogger.LogError((object)("'" + item + "' isn't a Type::Method entry, skipping it"));
					continue;
				}
				Type type = AccessTools.TypeByName(array[0]);
				if (type == null)
				{
					UnshamedPlugin.UnshamedLogger.LogError((object)("No type named '" + array[0] + "', skipping '" + item + "'"));
					continue;
				}
				MethodInfo[] array2 = Candidates(type, array[1]).ToArray();
				if (array2.Length == 0)
				{
					UnshamedPlugin.UnshamedLogger.LogError((object)(type.Name + " has no bool-returning '" + array[1] + "', skipping it"));
					continue;
				}
				MethodInfo[] array3 = array2;
				for (int num = 0; num < array3.Length; num++)
				{
					yield return array3[num];
				}
			}
		}

		private static IEnumerable<MethodInfo> Candidates(Type type, string name)
		{
			foreach (MethodInfo item in from m in type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
				where m.Name == name && m.ReturnType == typeof(bool) && !m.IsAbstract
				select m)
			{
				yield return item;
			}
			PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (property?.PropertyType == typeof(bool) && property.GetMethod != null)
			{
				yield return property.GetMethod;
			}
		}

		private static void ReturnTrue(ref bool __result)
		{
			__result = true;
		}

		private static void ReturnFalse(ref bool __result)
		{
			__result = false;
		}
	}
}
namespace Unshamed.Guide
{
	internal static class AchievementsPanel
	{
		[HarmonyPatch(typeof(InventoryGui), "Awake")]
		private static class InventoryGui_Awake_Patch
		{
			private static void Postfix(InventoryGui __instance)
			{
				if (!Object.op_Implicit((Object)(object)__instance.m_achievementsPanel))
				{
					return;
				}
				try
				{
					Build(__instance.m_achievementsPanel);
				}
				catch (Exception ex)
				{
					UnshamedPlugin.UnshamedLogger.LogError((object)("Couldn't build the achievement guide panel, the vanilla hierarchy probably moved: " + ex));
					_guide = null;
				}
			}
		}

		[HarmonyPatch(typeof(AchievementsGui), "CreateStatRow")]
		private static class AchievementsGui_CreateStatRow_Patch
		{
			private static bool Prefix(AchievementsGui __instance, string statKey, float currentAmount, float totalAmount)
			{
				if (!Reveal)
				{
					return true;
				}
				Row(__instance, statKey, currentAmount, totalAmount);
				return false;
			}
		}

		[HarmonyPatch(typeof(AchievementsGui), "OnOpenAchievementDetails")]
		private static class AchievementsGui_OnOpenAchievementDetails_Patch
		{
			private static void Prefix(AchievementsGui __instance, Achievement achievement, bool clickable, out bool __state)
			{
				__state = false;
				_opening = clickable && (Object)(object)achievement != (Object)null && !__instance.m_achievementDetails.activeSelf;
				if (_opening)
				{
					_unmet = 0;
					if (Reveal)
					{
						__state = achievement.m_isSecret;
						achievement.m_isSecret = false;
					}
				}
			}

			private static void Postfix(AchievementsGui __instance, Achievement achievement)
			{
				if (!_opening)
				{
					return;
				}
				if (Reveal)
				{
					foreach (Achievement item in achievement.m_otherAchievementTriggers.Where((Achievement o) => (Object)(object)o != (Object)null))
					{
						Row(__instance, item.m_name, item.m_unlocked ? 1 : 0, 1f);
					}
				}
				Show(achievement);
			}

			private static void Finalizer(Achievement achievement, bool __state)
			{
				if (__state)
				{
					achievement.m_isSecret = true;
				}
			}
		}

		[HarmonyPatch(typeof(InventoryGui), "UpdateAchievementsList")]
		private static class InventoryGui_UpdateAchievementsList_Patch
		{
			private static void Prefix(out List<(Achievement a, bool secret, bool clickable)>? __state)
			{
				__state = null;
				if (!Reveal)
				{
					return;
				}
				__state = (from a in StatSync.All()
					select (a: a, m_isSecret: a.m_isSecret, m_clickable: a.m_clickable)).ToList();
				foreach (var item2 in __state)
				{
					Achievement item = item2.a;
					item.m_isSecret = false;
					item.m_clickable = true;
				}
			}

			private static void Postfix(InventoryGui __instance)
			{
				if (!Reveal || !Object.op_Implicit((Object)(object)__instance.m_achievementsPanel))
				{
					return;
				}
				Dictionary<string, Achievement> dictionary = new Dictionary<string, Achievement>();
				foreach (Achievement item in StatSync.All())
				{
					dictionary[Localization.instance.Localize(item.m_name)] = item;
				}
				foreach (GameObject achievements in __instance.m_achievementsPanel.m_achievementsList)
				{
					Transform obj = achievements.transform.Find("name");
					TMP_Text val = ((obj != null) ? ((Component)obj).GetComponent<TMP_Text>() : null);
					Transform obj2 = achievements.transform.Find("description");
					TMP_Text val2 = ((obj2 != null) ? ((Component)obj2).GetComponent<TMP_Text>() : null);
					if (!((Object)(object)val == (Object)null) && !((Object)(object)val2 == (Object)null) && dictionary.TryGetValue(val.text, out var value) && !value.m_unlocked)
					{
						var (num, num2) = Requirements.Count(value);
						if (num2 > 0)
						{
							val2.text += $"\n<color=#C8B48C>{num} / {num2}</color>";
						}
					}
				}
			}

			private static void Finalizer(List<(Achievement a, bool secret, bool clickable)>? __state)
			{
				if (__state == null)
				{
					return;
				}
				foreach (var item4 in __state)
				{
					Achievement item = item4.a;
					bool item2 = item4.secret;
					bool item3 = item4.clickable;
					item.m_isSecret = item2;
					item.m_clickable = item3;
				}
			}
		}

		private const float Width = 520f;

		private const float Gap = 12f;

		private static readonly Color Done = new Color(0.45f, 0.85f, 0.4f, 1f);

		private static readonly Color Pending = new Color(0.85f, 0.8f, 0.72f, 1f);

		private static RectTransform? _details;

		private static Vector2 _detailsHome;

		private static RectTransform? _cover;

		private static Vector2 _coverMin;

		private static GameObject? _guide;

		private static TMP_Text? _title;

		private static TMP_Text? _body;

		private static ScrollRect? _scroll;

		private static int _unmet;

		private static bool _opening;

		internal static bool Reveal => UnshamedPlugin.On(UnshamedPlugin.RevealRequirements);

		private static bool ShowGuide => UnshamedPlugin.On(UnshamedPlugin.GuidePanel);

		private static void Build(AchievementsGui gui)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Expected O, but got Unknown
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: 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_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Unknown result type (might be due to invalid IL or missing references)
			//IL_0168: Expected O, but got Unknown
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_017f: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			//IL_019a: Unknown result type (might be due to invalid IL or missing references)
			//IL_019f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_022d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0234: Expected O, but got Unknown
			//IL_02e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_030f: Unknown result type (might be due to invalid IL or missing references)
			//IL_031b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0327: Unknown result type (might be due to invalid IL or missing references)
			//IL_0346: Unknown result type (might be due to invalid IL or missing references)
			//IL_0350: Expected O, but got Unknown
			//IL_03ee: Unknown result type (might be due to invalid IL or missing references)
			_details = (RectTransform)gui.m_achievementDetails.transform;
			_detailsHome = _details.anchoredPosition;
			Transform obj = ((Transform)_details).Find("coverInteraction");
			_cover = (RectTransform?)(object)((obj is RectTransform) ? obj : null);
			if (Object.op_Implicit((Object)(object)_cover))
			{
				_coverMin = _cover.offsetMin;
			}
			GameObject val = Object.Instantiate<GameObject>(((Component)_details).gameObject, (Transform)(object)_details);
			((Object)val).name = "UnshamedGuide";
			Transform val2 = val.transform.Find("coverInteraction");
			if (Object.op_Implicit((Object)(object)val2))
			{
				Object.DestroyImmediate((Object)(object)((Component)val2).gameObject);
			}
			RectTransform val3 = (RectTransform)val.transform;
			Vector2 val4 = default(Vector2);
			((Vector2)(ref val4))..ctor(0.5f, 0.5f);
			val3.pivot = val4;
			Vector2 anchorMin = (val3.anchorMax = val4);
			val3.anchorMin = anchorMin;
			((Transform)val3).localScale = Vector3.one;
			val3.sizeDelta = new Vector2(520f, _details.sizeDelta.y);
			val3.anchoredPosition = new Vector2((0f - (_details.sizeDelta.x + 520f)) / 2f - 12f, 0f);
			Transform val6 = val.transform.Find("Closebutton");
			if (Object.op_Implicit((Object)(object)val6))
			{
				Object.DestroyImmediate((Object)(object)((Component)val6).gameObject);
			}
			RectTransform val7 = (RectTransform)val.transform.Find("DetailsContainer");
			val7.sizeDelta = new Vector2(480f, val7.sizeDelta.y + 40f);
			val7.anchoredPosition += new Vector2(0f, -20f);
			RectTransform val8 = (RectTransform)((Transform)val7).Find("DetailsListScroll");
			val8.anchoredPosition = new Vector2(val7.sizeDelta.x / 2f + 6f, 0f);
			val8.sizeDelta = new Vector2(val8.sizeDelta.x, val7.sizeDelta.y);
			Transform obj2 = ((Transform)val7).Find("DetailsList");
			_scroll = ((Component)obj2).GetComponent<ScrollRect>();
			Transform val9 = obj2.Find("UnlockConditionElement");
			RectTransform val10 = (RectTransform)obj2.Find("ListRoot");
			_body = ((Component)val9.Find("name")).GetComponent<TMP_Text>();
			_body.transform.SetParent((Transform)(object)val10, false);
			Object.DestroyImmediate((Object)(object)((Component)val9).gameObject);
			foreach (Transform item in (from Transform t in (IEnumerable)val10
				where (Object)(object)t != (Object)(object)_body.transform
				select t).ToList())
			{
				Object.DestroyImmediate((Object)(object)((Component)item).gameObject);
			}
			Object.DestroyImmediate((Object)(object)((Component)val10).GetComponent<GridLayoutGroup>());
			val10.anchorMin = new Vector2(0f, 1f);
			val10.anchorMax = new Vector2(1f, 1f);
			val10.pivot = new Vector2(0.5f, 1f);
			val10.anchoredPosition = Vector2.zero;
			val10.sizeDelta = Vector2.zero;
			VerticalLayoutGroup obj3 = ((Component)val10).gameObject.AddComponent<VerticalLayoutGroup>();
			((LayoutGroup)obj3).padding = new RectOffset(12, 18, 10, 10);
			((HorizontalOrVerticalLayoutGroup)obj3).childControlWidth = true;
			((HorizontalOrVerticalLayoutGroup)obj3).childControlHeight = true;
			((HorizontalOrVerticalLayoutGroup)obj3).childForceExpandWidth = true;
			((HorizontalOrVerticalLayoutGroup)obj3).childForceExpandHeight = false;
			ContentSizeFitter obj4 = ((Component)val10).GetComponent<ContentSizeFitter>() ?? ((Component)val10).gameObject.AddComponent<ContentSizeFitter>();
			obj4.verticalFit = (FitMode)2;
			obj4.horizontalFit = (FitMode)0;
			((Component)_body).gameObject.SetActive(true);
			_body.enableAutoSizing = false;
			_body.fontSize = 17f;
			_body.textWrappingMode = (TextWrappingModes)1;
			_body.overflowMode = (TextOverflowModes)0;
			_body.alignment = (TextAlignmentOptions)257;
			_body.richText = true;
			((Graphic)_body).color = Pending;
			_title = ((Component)val.transform.Find("topic")).GetComponent<TMP_Text>();
			_guide = val;
			val.SetActive(false);
		}

		private static void Show(Achievement a)
		{
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Expected O, but got Unknown
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)_details) || !Object.op_Implicit((Object)(object)_guide) || (Object)(object)_title == (Object)null || (Object)(object)_body == (Object)null)
			{
				return;
			}
			if (!ShowGuide)
			{
				_guide.SetActive(false);
				_details.anchoredPosition = _detailsHome;
				if (Object.op_Implicit((Object)(object)_cover))
				{
					_cover.offsetMin = _coverMin;
				}
				return;
			}
			_details.anchoredPosition = _detailsHome + new Vector2(266f, 0f);
			if (Object.op_Implicit((Object)(object)_cover))
			{
				_cover.offsetMin = _coverMin - new Vector2(532f, 0f);
			}
			_guide.SetActive(true);
			_title.text = Localization.instance.Localize(a.m_name);
			_body.text = Compose(a);
			LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform)_body.transform.parent);
			if (Object.op_Implicit((Object)(object)_scroll))
			{
				_scroll.verticalNormalizedPosition = 1f;
			}
		}

		internal static string Compose(Achievement a, bool rich = true)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine(Hi(Localization.instance.Localize(a.m_description), "#FFA13C"));
			stringBuilder.AppendLine();
			stringBuilder.AppendLine(Blurbs.For(a));
			stringBuilder.AppendLine();
			stringBuilder.AppendLine(Bold("Difficulty: ") + Requirements.DifficultyText(a) + " (this world: " + Requirements.CurrentDifficulty() + ")");
			if (a.m_isSecret)
			{
				stringBuilder.AppendLine(Bold("Secret: ") + "vanilla hides this one until you unlock it.");
			}
			var (num, num2) = Requirements.Count(a);
			if (a.m_unlocked)
			{
				stringBuilder.AppendLine(Hi("Unlocked.", "#74D966"));
			}
			else if (a.m_lenientBuildAchievement)
			{
				var (value, value2) = Requirements.Lenient(a);
				stringBuilder.AppendLine(Bold("Progress: ") + "lenient score " + Requirements.Number(value) + ", needs more than " + Requirements.Number(value2) + ". Each row counts up to 115% of its target.");
			}
			else if (num2 > 0)
			{
				stringBuilder.AppendLine(Bold("Progress: ") + $"{num} of {num2} met" + ((num2 > 1) ? ", missing ones sort to the top of the list." : "."));
			}
			if (!Achievements.CanGetAchievements(false))
			{
				stringBuilder.AppendLine().AppendLine(Hi("This character can't earn achievements right now, so none of this will move. 'unshamed status' in the console says why.", "#FFA13C"));
			}
			string text = Extras.For(a, rich);
			if (text.Length > 0)
			{
				stringBuilder.AppendLine().AppendLine(text);
			}
			return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
			string Bold(string text2)
			{
				if (!rich)
				{
					return text2;
				}
				return "<b>" + text2 + "</b>";
			}
			string Hi(string text2, string color)
			{
				if (!rich)
				{
					return text2;
				}
				return "<color=" + color + ">" + text2 + "</color>";
			}
		}

		private static void Row(AchievementsGui gui, string key, float have, float need)
		{
			//IL_003c: 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_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			AchievementDetailUnlockCondition val = Object.Instantiate<AchievementDetailUnlockCondition>(gui.m_achievementDetailsElementPrefab, (Transform)(object)gui.m_achievementDetailsListRoot);
			((Component)val).gameObject.SetActive(true);
			bool flag = have >= need;
			TextMeshProUGUI statName = val.StatName;
			Color color = (((Graphic)val.Progress).color = (flag ? Done : Pending));
			((Graphic)statName).color = color;
			((TMP_Text)val.StatName).text = Requirements.Label(key);
			((TMP_Text)val.Progress).text = Requirements.Number(have) + " / " + Requirements.Number(need);
			if (!flag)
			{
				((Component)val).transform.SetSiblingIndex(_unmet++);
			}
		}
	}
	internal static class Blurbs
	{
		internal const string KillCredit = "Kill credit goes to every player who damaged it at any point and is still online when it dies, the last hit doesn't matter. Damage from your tames doesn't put you on the list.";

		private static readonly Dictionary<string, string> Text = new Dictionary<string, string>
		{
			["FirstDeath"] = "Die once. Any cause at all.",
			["Arrived"] = "Spawn into a world once. \"The tenth world\" is Valheim itself in the lore, it is not asking you to visit ten worlds.",
			["BigFish"] = "Catch a quality 4 fish on a fishing rod. That's the [4] shown after its name when you hover it. A fish grabbed by hand out of the water doesn't count, the stat only moves when a catch comes in on the rod.",
			["GrindFish"] = "Catch one of each of the 12 fish on a rod: Perch, Pike, Tuna, Tetra, Trollfish, Giant Herring, Grouper, Coral Cod, Anglerfish, Northern Salmon, Magmafish and Pufferfish. Hand-grabbed fish don't count.",
			["Boss1Eikthyr"] = "Kill Eikthyr. Kill credit goes to every player who damaged it at any point and is still online when it dies, the last hit doesn't matter. Damage from your tames doesn't put you on the list.",
			["Boss2Elder"] = "Kill The Elder. Kill credit goes to every player who damaged it at any point and is still online when it dies, the last hit doesn't matter. Damage from your tames doesn't put you on the list.",
			["Boss3Bonemass"] = "Kill Bonemass. Kill credit goes to every player who damaged it at any point and is still online when it dies, the last hit doesn't matter. Damage from your tames doesn't put you on the list.",
			["Boss4Moder"] = "Kill Moder. Kill credit goes to every player who damaged it at any point and is still online when it dies, the last hit doesn't matter. Damage from your tames doesn't put you on the list.",
			["Boss5Yagluth"] = "Kill Yagluth. Kill credit goes to every player who damaged it at any point and is still online when it dies, the last hit doesn't matter. Damage from your tames doesn't put you on the list.",
			["Boss6Queen"] = "Kill The Queen. Kill credit goes to every player who damaged it at any point and is still online when it dies, the last hit doesn't matter. Damage from your tames doesn't put you on the list.",
			["Boss7Fader"] = "Kill Fader. Kill credit goes to every player who damaged it at any point and is still online when it dies, the last hit doesn't matter. Damage from your tames doesn't put you on the list.",
			["Boss8FrozenKing"] = "Kill Kall Fimbulbringer, the Deep North boss. It's his final phase that registers as the kill. Kill credit goes to every player who damaged it at any point and is still online when it dies, the last hit doesn't matter. Damage from your tames doesn't put you on the list.",
			["SoloBoss"] = "Kill any boss where you were the only player who damaged it. Friends can stand and watch, they just can't land a single hit, and one who hit it and then logged off still counts against you.",
			["MultiplayerBoss"] = "Kill any boss that at least two different players damaged. Everyone who hit it gets the achievement.",
			["AllBosses"] = "Kill all eight bosses on any difficulty: Eikthyr, The Elder, Bonemass, Moder, Yagluth, The Queen, Fader and Kall Fimbulbringer. Kill credit goes to every player who damaged it at any point and is still online when it dies, the last hit doesn't matter. Damage from your tames doesn't put you on the list.",
			["AllBossesNormal"] = "Kill all eight bosses with the world's Combat modifier on Normal, Hard or Very hard at the moment of each kill. Kills on Easy, Very easy or Casual don't count, and neither do kills from before you changed the setting. Kill credit goes to every player who damaged it at any point and is still online when it dies, the last hit doesn't matter. Damage from your tames doesn't put you on the list.",
			["AllBossesHard"] = "Kill all eight bosses with the world's Combat modifier on Hard or Very hard at the moment of each kill. Kill credit goes to every player who damaged it at any point and is still online when it dies, the last hit doesn't matter. Damage from your tames doesn't put you on the list.",
			["AllMiniBosses"] = "Kill Hildir's three quest bosses and Lord Reto: Brenna (Black Forest), Geirrhafa (Mountains), both Plains brothers (the list has one entry for Zil & Thungr and a separate one for Zil), and Lord Reto in the Ashlands. Kill credit goes to every player who damaged it at any point and is still online when it dies, the last hit doesn't matter. Damage from your tames doesn't put you on the list.",
			["BuildDyrnwyn"] = "Craft Dyrnwyn from its recipe. It has to actually come out of crafting.",
			["KillAllCreatures"] = "Kill one of every creature on the list. The list is built when you load in, from every creature prefab flagged for this achievement, so a modded creature cloned from a vanilla one can end up on it too. The details list on the right is your exact list, with the ones you still need at the top. Kill credit goes to every player who damaged it at any point and is still online when it dies, the last hit doesn't matter. Damage from your tames doesn't put you on the list.",
			["KillAllCreaturesHard"] = "Same list as The Hunter, but each kill has to happen with the world's Combat modifier on Hard or Very hard. Kill credit goes to every player who damaged it at any point and is still online when it dies, the last hit doesn't matter. Damage from your tames doesn't put you on the list.",
			["FindAllTrophies"] = "Pick up one of every trophy. The list is every trophy item loaded (mod trophies included) minus a few Iron Gate excluded. Trophies you already carry get counted the next time your inventory changes, so move them around if old ones aren't showing.",
			["AllFoodEaten"] = "Eat one of every item that fills a food slot. The list is built from every loaded item with a food value, so modded food makes it longer. Meads and potions aren't food and aren't on it.",
			["AllFoodCooked"] = "Make one of every dish. That's everything with a food value that comes out of a crafting recipe (the cauldron, mostly) plus everything with a food value a cooking station turns out: the grill, the iron cooking station, the oven. Pulling cooked meat off the grill counts.",
			["AllItemCraft"] = "Craft one of every recipe that needs a crafting station, minus the handful Iron Gate excluded. Taking finished food off a cooking station counts as crafting it. Modded recipes with a station are on the list too, which can make this a lot longer in a modded game.",
			["AllWeaponCraft"] = "Craft one of every weapon that has a recipe, minus Iron Gate's exclusions.",
			["AllBuildPieces"] = "Place one of every build piece on every tool that can deconstruct pieces (the hammer, plus any modded tool that allows removal). The repair \"piece\" isn't on it. The details list shows exactly which pieces are left.",
			["GrindBuild"] = "Place 500 pieces with a tool that trains Crafting (the hammer). The catch: every piece you remove puts you one placement in debt, up to 20, and those placements pay the debt off without counting. Tear down and rebuild and you're treading water. The debt isn't saved, relogging clears it.",
			["BuildHigh"] = "Place a piece at least 49 m above whatever terrain is straight below it. It also has to sit on your own foundation: a line straight down from it has to pass through at least 5 pieces, and more than 45% of those have to be ones you placed. No terrain within 200 m below means no reading.",
			["BuildHighWorld"] = "Place a piece outdoors at world height 432 or more (sea level is 30). Height alone isn't enough, it has to sit on your own foundation: a line straight down from it (up to 200 m) has to pass through at least 5 build pieces, and more than 45% of those have to be ones you placed. Terrain and rock don't count toward that, so a single piece dropped on a tall peak gives no reading. Stack a tower of 5+ of your own pieces and place the last one on top, above 432. Build up from a mountain peak.",
			["BuildHouse"] = "Have your own placed pieces loaded around you with these build menu tags: Floor 6, Wall 8, Roof 4, Furniture 1, Lighting 1, Storage 1. It's lenient: each tag counts up to 115% of its target and the capped total has to beat 21, so being over in one tag can cover being a little short in another. It isn't checked on every placement: the first check is your 11th piece after loading in, then every 21st piece after that, so keep placing.",
			["BuildVillage"] = "Have your own placed pieces loaded around you with these build menu tags: Building 10, Floor 40, Wall 40, Roof 20, Architecture 10, Furniture 10, Lighting 5, Decor 5. Lenient like The Homesteader: each tag counts up to 115% of its target and the capped total has to beat 140. Checked on the same schedule too, every 21st piece you place.",
			["ChildOfOdin"] = "Unlock The Pathfinder, The Toughest Viking, The Master Hunter and The Survivor. It checks whenever another achievement unlocks and when you load in.",
			["KingsQueens"] = "Unlock The Viking, The Explorer, The Carpenter, The Hunter, The Master Chef and The Artisan. It checks whenever another achievement unlocks and when you load in.",
			["DeathByAllTypes"] = "Die once to each of 16 causes. Only the hit that actually kills you counts: poisoned by a blob and finished off by a draugr is Enemy, not Poison.\n- Enemy: a creature's attack. Other players don't count.\n- Falling: fall damage.\n- Drowning: running out of stamina while swimming.\n- Burning: the burning damage over time, not the fire hit itself.\n- Freezing: the Freezing status, cold with no frost protection.\n- Poisoning: poison damage over time.\n- Smoke: breathing smoke indoors.\n- Edge of the world: going over the edge, past about 10,420 m out and dropping below the sea.\n- Tree: a falling tree or its log landing on you.\n- Ballista: shot by a ballista.\n- Stalactite: a falling icicle in the frost caves.\n- Catapult: a catapult shot.\n- Ashlands ocean: the boiling Ashlands water.\n- Obliterator: standing in the Obliterator.\n- Drawbridge: a drawbridge coming down on you.\n- Ashlands lava: lava.\nNot on the list: other players, carts, boats, structures, cinder fire, and killing yourself.",
			["DeathByTree"] = "Get killed by a falling tree or its log. Chop, don't run.",
			["DeathByObliterator"] = "Die inside the Obliterator.",
			["SailEdge"] = "Die by going over the edge of the world. Sailing out there isn't enough, it's counted from the death: past about 10,420 m from the centre, dropping below the sea.",
			["DeathByTreeAll"] = "Get killed by each of 10 kinds of tree. The log that kills you remembers which tree it came from, and its half logs do too.\n- Fir and Pine: Black Forest and Meadows.\n- Beech: Meadows.\n- Birch and Oak: Meadows and Plains, needs a bronze axe.\n- Swamp tree: Swamp, needs a bronze axe.\n- Snow fir and Snow pine: Mountains.\n- Yggdrasil shoot: Mistlands, needs a black metal axe.\n- Ashlands tree: Ashlands.\nHeads up: in the game data, some meadows and heath treasure chests, Deep North loot, glow worms, frost cores and mountain cave crystals carry a stale stat id that lands on Death by Birch. If Birch is already green and a birch never flattened you, that's why.",
			["ExploreNSEW"] = "Go more than 10,350 m from the centre of the world to the north, south, east and west. It only ticks while you're moving (more than a metre every 2.5 seconds) and never while debug flying. The world ends at 10,500 m, so it's a thin strip and mostly ocean.",
			["ExploreNSEWNoMap"] = "Same as The Explorer, but the world has to have the No Map modifier on while you're out there.",
			["GrindComfort"] = "Reach comfort level 20. You start at 1, get +1 for being sheltered, then each comfort piece within 10 m adds its value (only the best one from each group). The record only updates when your base value changes, so if you're sat at 20 and nothing happens, walk out of the base and back in.",
			["GrindCook"] = "Craft 100 consumables from a crafting menu, the cauldron mostly. It counts every item made, meads and potions included. Bait doesn't count, and the grill and oven don't count here at all.",
			["GrindDays"] = "Survive 30 in-game days in a row. A day counts when the morning \"Day N\" message shows while you're in the world, and dying resets the streak to 0. Your best streak is what's kept.",
			["GrindEat"] = "Eat 500 food items. Repeats count.",
			["GrindForager"] = "Pick 250 berries and 250 mushrooms. Berries are raspberry, blueberry, cloudberry and lingonberry bushes plus vineberry vines. Mushrooms are the red, yellow and blue ones, Jotun puffs, magecaps and smoke puffs. Replanted and wild both count.",
			["GrindHarvest"] = "Harvest 500 grown crops: barley, carrots, flax, kale, oats, onions, poteitr and turnips. Seed carrots, seed onions and seed turnips don't count, their stat id in the game data is stale.",
			["GrindHoney"] = "Take 500 honey out of beehives. Emptying a full hive counts everything in it.",
			["GrindLeviathan"] = "Be within 10 m of a Leviathan (measured flat, height ignored) when it dives. Every pickaxe hit on its back has a 25% chance to make it shake, and it goes under 5 seconds later. It doesn't have to be your hit, anyone close enough when it dives gets it, so don't row off after it shakes.",
			["GrindSail"] = "Travel 131,946 m while aboard a ship, anywhere on deck.",
			["GrindSailHelm"] = "Travel 65,973 m while steering a ship.",
			["GrindTreasure"] = "Open 10 buried treasure chests, the ones you dig up. Dungeon and location chests don't count. Each chest counts once per character name.",
			["GrindTrees"] = "Fell 500 trees yourself. Your hit has to be the one that brings it down. Logs and stumps don't count."
		};

		internal static string For(Achievement a)
		{
			if (!Text.TryGetValue(a.m_id, out string value))
			{
				return "No write-up for this one, it isn't a vanilla achievement I know about. The list on the right is what it actually checks.";
			}
			return value;
		}
	}
	internal static class Comfort
	{
		private const float Radius = 10f;

		internal static string Describe(bool rich)
		{
			//IL_0171: Unknown result type (might be due to invalid IL or missing references)
			//IL_0257: Unknown result type (might be due to invalid IL or missing references)
			//IL_025c: Unknown result type (might be due to invalid IL or missing references)
			List<Piece> list = Pieces();
			if (list.Count == 0)
			{
				return "";
			}
			List<IGrouping<ComfortGroup, Piece>> list2 = (from p in list
				where (int)p.m_comfortGroup > 0
				group p by p.m_comfortGroup into g
				orderby g.Key
				select g).ToList();
			List<Piece> list3 = Distinct(list.Where((Piece p) => (int)p.m_comfortGroup == 0));
			int num = 2 + list2.Sum((IGrouping<ComfortGroup, Piece> g) => g.Max((Piece p) => p.m_comfort)) + list3.Sum((Piece p) => p.m_comfort);
			StringBuilder stringBuilder = new StringBuilder();
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null)
			{
				stringBuilder.AppendLine(Fmt.Heading("Right now", rich));
				if (!localPlayer.InShelter())
				{
					stringBuilder.AppendLine($"Comfort {localPlayer.GetComfortLevel()}. You're not sheltered, so nothing nearby counts.");
				}
				else
				{
					List<Piece> list4 = Counting(((Component)localPlayer).transform.position);
					stringBuilder.AppendLine($"Comfort {localPlayer.GetComfortLevel()}, counting these within {10f:0} m:");
					stringBuilder.AppendLine((list4.Count == 0) ? Fmt.Dim("  nothing", rich) : Fmt.Columns(list4.Select((Piece p) => Entry(p, p.GetComfort())), rich));
				}
				stringBuilder.AppendLine(Fmt.Gap(rich));
			}
			stringBuilder.AppendLine(Fmt.Heading("Comfort pieces", rich));
			stringBuilder.AppendLine($"Most you can stack with what's loaded: {num}. You start at 1, sheltered adds 1, and only the best piece from each group counts.");
			foreach (IGrouping<ComfortGroup, Piece> item in list2)
			{
				stringBuilder.AppendLine(Fmt.Gap(rich));
				stringBuilder.AppendLine(Fmt.Sub(((object)item.Key/*cast due to .constrained prefix*/).ToString(), "best counts", rich));
				stringBuilder.AppendLine(Fmt.Columns(from p in Distinct(item)
					select Entry(p, p.m_comfort), rich));
			}
			if (list3.Count > 0)
			{
				stringBuilder.AppendLine(Fmt.Gap(rich));
				stringBuilder.AppendLine(Fmt.Sub("No group", "each different one counts", rich));
				stringBuilder.AppendLine(Fmt.Columns(list3.Select((Piece p) => Entry(p, p.m_comfort)), rich));
			}
			return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
			string Entry(Piece p, int comfort)
			{
				return Name(p) + " " + Fmt.Dim("+" + comfort, rich);
			}
			static string Name(Piece p)
			{
				return Fmt.Loc(p.m_name, ((Object)p).name);
			}
		}

		private static List<Piece> Pieces()
		{
			if (!Object.op_Implicit((Object)(object)ObjectDB.instance))
			{
				return new List<Piece>();
			}
			return (from p in ObjectDB.instance.GetAllBuildPieces(true)
				where Object.op_Implicit((Object)(object)p) && p.m_comfort > 0
				select p).ToList();
		}

		private static List<Piece> Distinct(IEnumerable<Piece> pieces)
		{
			return (from p in pieces
				group p by p.m_name into g
				select g.OrderByDescending((Piece p) => p.m_comfort).First() into p
				orderby p.m_comfort descending, p.m_name
				select p).ToList();
		}

		private static List<Piece> Counting(Vector3 position)
		{
			//IL_0006: 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)
			//IL_0060: 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)
			List<Piece> list = new List<Piece>();
			Piece.GetAllComfortPiecesInRadius(position, 10f, list);
			list.Sort((Comparison<Piece>)SE_Rested.PieceComfortSort);
			List<Piece> list2 = new List<Piece>();
			for (int i = 0; i < list.Count; i++)
			{
				if (i > 0)
				{
					Piece val = list[i - 1];
					if (((int)list[i].m_comfortGroup != 0 && list[i].m_comfortGroup == val.m_comfortGroup) || list[i].m_name == val.m_name)
					{
						continue;
					}
				}
				if (list[i].GetComfort() > 0)
				{
					list2.Add(list[i]);
				}
			}
			return list2;
		}
	}
	internal static class Extras
	{
		private const float ExploreLine = 10350f;

		internal static string For(Achievement a, bool rich)
		{
			try
			{
				string result;
				switch (a.m_id)
				{
				case "GrindComfort":
					result = Comfort.Describe(rich);
					break;
				case "BuildVillage":
				case "BuildHouse":
					result = Tags(a, rich);
					break;
				case "GrindForager":
					result = Pickables(rich, (PlayerStatType)124, (PlayerStatType)125);
					break;
				case "GrindHarvest":
					result = Pickables(rich, (PlayerStatType)123);
					break;
				case "DeathByTreeAll":
					result = Trees(rich);
					break;
				case "GrindCook":
					result = Cookables(rich);
					break;
				case "ExploreNSEW":
					result = Explore(rich, noMap: false);
					break;
				case "ExploreNSEWNoMap":
					result = Explore(rich, noMap: true);
					break;
				case "GrindDays":
					result = Days(rich);
					break;
				case "GrindBuild":
					result = Debt(rich);
					break;
				default:
					result = "";
					break;
				}
				return result;
			}
			catch (Exception ex)
			{
				UnshamedPlugin.UnshamedLogger.LogWarning((object)("Guide extras for " + a.m_id + " fell over: " + ex.Message));
				return "";
			}
		}

		private static IEnumerable<T> Prefabs<T>() where T : Component
		{
			if (!Object.op_Implicit((Object)(object)ZNetScene.instance))
			{
				yield break;
			}
			foreach (GameObject prefab in ZNetScene.instance.m_prefabs)
			{
				if (Object.op_Implicit((Object)(object)prefab))
				{
					T component = prefab.GetComponent<T>();
					if (component != null)
					{
						yield return component;
					}
				}
			}
		}

		private static string Pair(string label, string value, bool rich)
		{
			if (!rich)
			{
				return "  " + label + ": " + value;
			}
			return label + "<pos=48%>" + value;
		}

		private static string Met(string text, bool met, bool rich)
		{
			if (!rich)
			{
				return text;
			}
			return "<color=" + (met ? "#74D966" : "#A39884") + ">" + text + "</color>";
		}

		private static string Tags(Achievement a, bool rich)
		{
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Expected I4, but got Unknown
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0280: Unknown result type (might be due to invalid IL or missing references)
			//IL_0285: Unknown result type (might be due to invalid IL or missing references)
			List<Piece> source = (Object.op_Implicit((Object)(object)ObjectDB.instance) ? (from p in ObjectDB.instance.GetAllBuildPieces(true)
				where Object.op_Implicit((Object)(object)p)
				select p).ToList() : new List<Piece>());
			UsageTagFlags[] array = (UsageTagFlags[])Enum.GetValues(typeof(UsageTagFlags));
			List<(string, UsageTagFlags, int, float)> list = new List<(string, UsageTagFlags, int, float)>();
			foreach (PlayerStatRequirement item6 in a.m_statTrigger)
			{
				int num = item6.m_stat - 171;
				if (num >= 0 && num < array.Length)
				{
					list.Add((Fmt.Loc("$tag_" + ((object)Unsafe.As<UsageTagFlags, UsageTagFlags>(ref array[num])/*cast due to .constrained prefix*/).ToString().ToLowerInvariant(), ((object)Unsafe.As<UsageTagFlags, UsageTagFlags>(ref array[num])/*cast due to .constrained prefix*/).ToString()), array[num], num, item6.m_amountAboveEquals));
				}
			}
			StringBuilder stringBuilder = new StringBuilder();
			if (Piece.m_tagStats != null)
			{
				float num2 = 0f;
				float num3 = 0f;
				stringBuilder.AppendLine(Fmt.Heading("Right now", rich));
				stringBuilder.AppendLine(Fmt.Dim("Your pieces loaded around you, as of the last one you placed.", rich));
				foreach (var item7 in list)
				{
					string item = item7.Item1;
					int item2 = item7.Item3;
					float item3 = item7.Item4;
					int num4 = ((item2 < Piece.m_tagStats.Length) ? Piece.m_tagStats[item2] : 0);
					num3 += item3;
					num2 += Mathf.Min((float)num4, item3 * 1.15f);
					stringBuilder.AppendLine(Pair(item, Met($"{num4} / {Requirements.Number(item3)}", (float)num4 >= item3, rich), rich));
				}
				stringBuilder.AppendLine(Pair("Lenient score", Met(Requirements.Number(num2) + ", needs over " + Requirements.Number(num3), num2 > num3, rich), rich));
				stringBuilder.AppendLine(Fmt.Gap(rich));
			}
			stringBuilder.AppendLine(Fmt.Heading("Pieces with each tag", rich));
			stringBuilder.AppendLine("One piece can carry several tags and counts toward all of them.");
			foreach (var item8 in list)
			{
				string item4 = item8.Item1;
				UsageTagFlags flag = item8.Item2;
				float item5 = item8.Item4;
				List<string> list2 = (from n in (from p in source
						where (p.m_usage & flag) > 0
						select Fmt.Loc(p.m_name, ((Object)p).name)).Distinct()
					orderby n
					select n).ToList();
				stringBuilder.AppendLine(Fmt.Gap(rich));
				stringBuilder.AppendLine(Fmt.Sub(item4, $"{Requirements.Number(item5)} needed, {list2.Count} pieces", rich));
				stringBuilder.AppendLine((list2.Count == 0) ? Fmt.Dim("  none loaded", rich) : Fmt.Columns(list2, rich));
			}
			return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
		}

		private static string Pickables(bool rich, params PlayerStatType[] stats)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine(Fmt.Heading("What counts", rich));
			for (int i = 0; i < stats.Length; i++)
			{
				PlayerStatType stat = stats[i];
				List<string> list = (from n in (from p in Prefabs<Pickable>()
						where p.m_harvestStat == stat && Object.op_Implicit((Object)(object)p.m_itemPrefab)
						select Fmt.Loc(p.GetHoverName(), ((Object)p).name)).Distinct()
					orderby n
					select n).ToList();
				stringBuilder.AppendLine(Fmt.Gap(rich));
				stringBuilder.AppendLine(Fmt.Sub(Requirements.Label(((object)Unsafe.As<PlayerStatType, PlayerStatType>(ref stat)/*cast due to .constrained prefix*/).ToString()), $"{list.Count} kinds", rich));
				stringBuilder.AppendLine((list.Count == 0) ? Fmt.Dim("  nothing loaded counts", rich) : Fmt.Columns(list, rich));
			}
			return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
		}

		private static string Trees(bool rich)
		{
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			List<ItemDrop> axes = (Object.op_Implicit((Object)(object)ObjectDB.instance) ? (from i in ObjectDB.instance.m_items
				select (!Object.op_Implicit((Object)(object)i)) ? null : i.GetComponent<ItemDrop>() into i
				where Object.op_Implicit((Object)(object)i) && (int)i.m_itemData.m_shared.m_skillType == 7
				select i).Cast<ItemDrop>().ToList() : new List<ItemDrop>());
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine(Fmt.Heading("Axe needed to fell each kind", rich));
			foreach (IGrouping<PlayerStatType, TreeBase> item in from t in Prefabs<TreeBase>()
				group t by t.m_treeStatType into g
				orderby g.Key
				select g)
			{
				string text = Requirements.Label(((object)item.Key/*cast due to .constrained prefix*/).ToString()).Replace("Death by ", "");
				string text2 = Axe(item.Min((TreeBase t) => t.m_minToolTier));
				stringBuilder.AppendLine(rich ? Pair(text, text2, rich: true) : ("  " + text + ": " + text2 + " (" + string.Join(", ", item.Select((TreeBase t) => ((Object)t).name).Distinct()) + ")"));
			}
			return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
			string Axe(int tier)
			{
				ItemDrop val = (from x in axes
					where x.m_itemData.m_shared.m_toolTier >= tier
					orderby x.m_itemData.m_shared.m_toolTier
					select x).FirstOrDefault();
				if (tier > 0)
				{
					if (!Object.op_Implicit((Object)(object)val))
					{
						return "tool tier " + tier;
					}
					return Fmt.Loc(val.m_itemData.m_shared.m_name, ((Object)val).name) + " or better";
				}
				return "any axe";
			}
		}

		private static string Cookables(bool rich)
		{
			if (!Object.op_Implicit((Object)(object)ObjectDB.instance))
			{
				return "";
			}
			IEnumerable<Recipe> source = ObjectDB.instance.m_recipes.Where((Recipe r) => Object.op_Implicit((Object)(object)r) && Object.op_Implicit((Object)(object)r.m_item) && r.m_enabled && (int)r.m_item.m_itemData.m_shared.m_itemType == 2 && !r.m_item.m_itemData.m_shared.m_name.ToLower().Contains("bait"));
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine(Fmt.Heading("Recipes that count", rich));
			foreach (IGrouping<string, Recipe> item in from r in source
				group r by (!Object.op_Implicit((Object)(object)r.m_craftingStation)) ? "No station" : Fmt.Loc(r.m_craftingStation.m_name, ((Object)r.m_craftingStation).name) into g
				orderby g.Key
				select g)
			{
				List<string> list = (from n in item.Select((Recipe r) => Fmt.Loc(r.m_item.m_itemData.m_shared.m_name, ((Object)r.m_item).name)).Distinct()
					orderby n
					select n).ToList();
				stringBuilder.AppendLine(Fmt.Gap(rich));
				stringBuilder.AppendLine(Fmt.Sub(item.Key, $"{list.Count} recipes", rich));
				stringBuilder.AppendLine(Fmt.Columns(list, rich));
			}
			return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
		}

		private static string Explore(bool rich, bool noMap)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return "";
			}
			Vector3 position = ((Component)localPlayer).transform.position;
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine(Fmt.Heading("Right now", rich));
			stringBuilder.AppendLine(Dir("North", position.z));
			stringBuilder.AppendLine(Dir("South", 0f - position.z));
			stringBuilder.AppendLine(Dir("East", 0f - position.x));
			stringBuilder.AppendLine(Dir("West", position.x));
			if (noMap)
			{
				stringBuilder.AppendLine(Pair("No Map on this world", Met(Achievements.IsCleanNoMap() ? "yes, it counts here" : "no, nothing counts here", Achievements.IsCleanNoMap(), rich), rich));
			}
			return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
			string Dir(string name, float along)
			{
				return Pair(name, (along > 10350f) ? Met("past the line", met: true, rich) : Met(Requirements.Number(10350f - along) + " m to go", met: false, rich), rich);
			}
		}

		private static string Days(bool rich)
		{
			PlayerProfile val = (Object.op_Implicit((Object)(object)Game.instance) ? Game.instance.GetPlayerProfile() : null);
			if (val == null)
			{
				return "";
			}
			return Fmt.Heading("Right now", rich) + "\n" + Pair("Current streak", Requirements.Number(val.GetStat((PlayerStatType)128)) + " days", rich) + "\n" + Pair("Best streak", Requirements.Number(val.GetStat((PlayerStatType)129)) + " days", rich);
		}

		private static string Debt(bool rich)
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return "";
			}
			return Fmt.Heading("Right now", rich) + "\n" + Pair("Removal debt", Met((localPlayer.m_buildRemoveDebt > 0) ? $"next {localPlayer.m_buildRemoveDebt} placement(s) won't count" : "none, every placement counts", localPlayer.m_buildRemoveDebt == 0, rich), rich);
		}
	}
	internal static class Fmt
	{
		internal const string Accent = "#FFA13C";

		internal const string Muted = "#A39884";

		private const string ListColor = "#D9CFBF";

		private const int ColumnChars = 24;

		internal static string Heading(string text, bool rich)
		{
			if (!rich)
			{
				return "### " + text;
			}
			return "<size=112%><color=#FFA13C><b>" + text + "</b></color></size>";
		}

		internal static string Sub(string title, string note, bool rich)
		{
			if (!rich)
			{
				return title + " (" + note + ")";
			}
			return "<b>" + title + "</b>  <color=#A39884>" + note + "</color>";
		}

		internal static string Label(string label, string text, bool rich)
		{
			return (rich ? ("<b>" + label + "</b> ") : (label + " ")) + text;
		}

		internal static string Dim(string text, bool rich)
		{
			if (!rich)
			{
				return text;
			}
			return "<color=#A39884>" + text + "</color>";
		}

		internal static string Gap(bool rich)
		{
			if (!rich)
			{
				return "";
			}
			return "<size=45%>\n</size>";
		}

		internal static string Columns(IEnumerable<string> items, bool rich)
		{
			List<string> list = items.ToList();
			if (!rich)
			{
				return "  " + string.Join(", ", list.Select(Strip));
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("<size=92%><color=#D9CFBF>");
			string text = null;
			foreach (string item in list)
			{
				if (Strip(item).Length > 24)
				{
					if (text != null)
					{
						stringBuilder.Append(text).Append('\n');
					}
					text = null;
					stringBuilder.Append(item).Append('\n');
				}
				else if (text == null)
				{
					text = item;
				}
				else
				{
					stringBuilder.Append(text).Append("<pos=50%>").Append(item)
						.Append('\n');
					text = null;
				}
			}
			if (text != null)
			{
				stringBuilder.Append(text).Append('\n');
			}
			stringBuilder.Length = ((stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] == '\n') ? (stringBuilder.Length - 1) : stringBuilder.Length);
			return stringBuilder.Append("</color></size>").ToString();
		}

		internal static string Strip(string text)
		{
			return Regex.Replace(text, "<[^>]+>", "");
		}

		internal static string Loc(string token, string fallback)
		{
			string text = Localization.instance.Localize(token);
			if (text.Length == 0 || (text.StartsWith("[") && text.EndsWith("]")))
			{
				return Regex.Replace(fallback, "(?<=[a-z])(?=[A-Z0-9])|_", " ").Trim();
			}
			return Strip(text).Trim();
		}