Decompiled source of VikingCodex v1.0.8

plugins/VikingCodex/VikingCodex.dll

Decompiled 18 seconds 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.Versioning;
using System.Security.Cryptography;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.UI;
using VikingCodex.Codex;
using VikingCodex.Codex.UI;
using VikingCodex.Compatibility;
using VikingCodex.Configuration;
using VikingCodex.Core;
using VikingCodex.Sharing;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("VikingCodex")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.8.0")]
[assembly: AssemblyInformationalVersion("1.0.8")]
[assembly: AssemblyProduct("VikingCodex")]
[assembly: AssemblyTitle("VikingCodex")]
[assembly: AssemblyMetadata("AI_Assisted_Creation", "Code and documentation generated with assistance from OpenAI Codex.")]
[assembly: AssemblyMetadata("AI_Model_Vendor", "OpenAI")]
[assembly: AssemblyMetadata("AI_Agent", "OpenAI Codex")]
[assembly: AssemblyVersion("1.0.8.0")]
[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 VikingCodex
{
	[BepInPlugin("com.vikingcodex", "Viking Codex", "1.0.8")]
	[BepInDependency("com.jotunn.jotunn", "2.30.2")]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "com.vikingcodex";

		public const string Version = "1.0.8";

		internal static Plugin Instance;

		internal static ManualLogSource Log;

		internal ModSettings Settings;

		internal readonly CodexRegistry Registry = new CodexRegistry();

		internal readonly DiscoveryTracker Tracker = new DiscoveryTracker();

		internal DiscoverySync Sync;

		private CodexPanel panel;

		private MainMenu mainMenu;

		private Harmony harmony;

		private ObjectDB database;

		private ZNetScene scene;

		private float retryAt;

		private bool registryModded;

		private static readonly Dictionary<string, float> warningTimes = new Dictionary<string, float>(StringComparer.Ordinal);

		private void Awake()
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			warningTimes.Clear();
			Settings = new ModSettings(((BaseUnityPlugin)this).Config);
			Sync = new DiscoverySync(this);
			harmony = new Harmony("com.vikingcodex");
			harmony.PatchAll(typeof(Plugin).Assembly);
			CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new DiagnosticsCommand());
			if (!GUIManager.IsHeadless())
			{
				panel = new CodexPanel(this);
				mainMenu = new MainMenu();
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("Viking Codex {0}, protocol {1}. Player runtime acceptance remains pending.", "1.0.8", 2));
		}

		internal void ReloadRegistry()
		{
			if (!Object.op_Implicit((Object)(object)ObjectDB.instance) || !Object.op_Implicit((Object)(object)ZNetScene.instance))
			{
				throw new InvalidOperationException("A loaded world is required");
			}
			Registry.Build(Settings.Modded.Value, Path.Combine(Paths.ConfigPath, "VikingCodex", "metadata"));
			database = ObjectDB.instance;
			scene = ZNetScene.instance;
			registryModded = Settings.Modded.Value;
			Sync.RegistryChanged();
			Tracker.Changed();
		}

		private void Update()
		{
			if (Object.op_Implicit((Object)(object)ObjectDB.instance) && Object.op_Implicit((Object)(object)ZNetScene.instance) && ((Object)(object)database != (Object)(object)ObjectDB.instance || (Object)(object)scene != (Object)(object)ZNetScene.instance || registryModded != Settings.Modded.Value) && Time.unscaledTime >= retryAt)
			{
				retryAt = Time.unscaledTime + 5f;
				try
				{
					ReloadRegistry();
				}
				catch (Exception e)
				{
					Warn("Registry rebuild failed; retaining previous snapshot", e);
				}
			}
			try
			{
				Sync.Tick();
			}
			catch (Exception e2)
			{
				Warn("Discovery synchronization", e2);
			}
			try
			{
				mainMenu?.Tick();
			}
			catch (Exception e3)
			{
				mainMenu?.Dispose();
				Warn("Codex main menu", e3);
			}
			try
			{
				panel?.Tick();
			}
			catch (Exception e4)
			{
				panel?.RecoverFromError();
				Warn("Codex interface", e4);
			}
		}

		private void OnDestroy()
		{
			try
			{
				mainMenu?.Dispose();
				panel?.Dispose();
			}
			finally
			{
				Harmony obj = harmony;
				if (obj != null)
				{
					obj.UnpatchSelf();
				}
				Instance = null;
			}
		}

		internal static void Warn(string operation, Exception e)
		{
			float unscaledTime = Time.unscaledTime;
			if (!warningTimes.TryGetValue(operation, out var value) || !(unscaledTime - value < 5f))
			{
				if (warningTimes.Count > 256)
				{
					warningTimes.Clear();
				}
				warningTimes[operation] = unscaledTime;
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogWarning((object)(operation + ": " + e.GetType().Name + ": " + e.Message));
				}
			}
		}
	}
}
namespace VikingCodex.Sharing
{
	internal sealed class DiscoverySync
	{
		private readonly Plugin plugin;

		private readonly CustomRPC rpc;

		private WorldKnowledgeStore store;

		private DiscoveryAuthority authority;

		private DiscoveryClient client;

		private ZNet session;

		private Player character;

		private long characterId;

		private long sessionWorld;

		private int hostCursor;

		private bool applying;

		private bool failed;

		private bool hostStarted;

		private readonly HashSet<string> encountered = new HashSet<string>(StringComparer.Ordinal);

		private int sentPackets;

		private int receivedPackets;

		private int rejectedPackets;

		private int appliedItems;

		private float nextPoll;

		private float nextPeerPrune;

		private float nextApplyAttempt;

		internal string Diagnostics => $"Sent {sentPackets}; received {receivedPackets}; malformed {rejectedPackets}; applied {appliedItems}; encountered {encountered.Count}; incoming {client?.HasHistory}; pending {client?.PendingCount}";

		internal string Status { get; private set; } = "Waiting for character";

		private bool Enabled
		{
			get
			{
				if (plugin.Settings.Sharing.Value && plugin.Settings.Materials.Value && store != null)
				{
					return !failed;
				}
				return false;
			}
		}

		internal DiscoverySync(Plugin owner)
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Expected O, but got Unknown
			//IL_0050: Expected O, but got Unknown
			plugin = owner;
			rpc = NetworkManager.Instance.AddRPC("Discovery.v2", new CoroutineHandler(ServerReceive), new CoroutineHandler(ClientReceive));
		}

		internal void Tick()
		{
			ZNet instance = ZNet.instance;
			Player localPlayer = Player.m_localPlayer;
			long num = (Object.op_Implicit((Object)(object)localPlayer) ? localPlayer.GetPlayerID() : 0);
			long num2 = ((Object.op_Implicit((Object)(object)instance) && instance.IsServer()) ? instance.GetWorldUID() : 0);
			if ((Object)(object)session != (Object)(object)instance || sessionWorld != num2)
			{
				session = instance;
				sessionWorld = num2;
				store = null;
				authority = null;
				failed = false;
				ResetCharacter(localPlayer, num);
			}
			else if ((Object)(object)character != (Object)(object)localPlayer || characterId != num)
			{
				ResetCharacter(localPlayer, num);
			}
			if (!Object.op_Implicit((Object)(object)instance) || !plugin.Registry.Ready || (instance.IsServer() && (instance.GetWorld() == null || instance.GetWorld().m_menu)))
			{
				Status = "Waiting for world";
				return;
			}
			if (instance.IsServer() && store == null && !failed && num2 != 0L)
			{
				try
				{
					store = new WorldKnowledgeStore(Path.Combine(Paths.ConfigPath, "VikingCodex", "worlds"), num2);
					authority = new DiscoveryAuthority(store, (string value) => plugin.Registry.Materials.ContainsKey(value));
					if (store.Recovered)
					{
						Plugin.Log.LogWarning((object)"Shared history recovered from backup. Review the latest discoveries.");
					}
				}
				catch (Exception e)
				{
					Fail("History unavailable", e);
				}
			}
			if (instance.IsServer() && Time.unscaledTime >= nextPeerPrune)
			{
				authority?.RemoveDisconnected(from p in instance.GetPeers()
					where p.IsReady()
					select p.m_uid);
				nextPeerPrune = Time.unscaledTime + 2f;
			}
			if (instance.IsServer())
			{
				Status = (failed ? "Sharing unavailable: history error" : (Enabled ? $"Sharing enabled; history {store.Count}" : "Sharing disabled"));
			}
			if (!Object.op_Implicit((Object)(object)localPlayer) || num == 0L)
			{
				return;
			}
			if (instance.IsServer())
			{
				HostTick();
			}
			else
			{
				if (client == null)
				{
					return;
				}
				if (!plugin.Settings.Sharing.Value || !plugin.Settings.Materials.Value)
				{
					client.Pause();
				}
				if (Time.unscaledTime >= nextApplyAttempt && client.HasHistory)
				{
					try
					{
						if (client.Drain(Apply, 4) == 0 && client.HasHistory)
						{
							nextApplyAttempt = Time.unscaledTime + 2f;
						}
					}
					catch (Exception e2)
					{
						nextApplyAttempt = Time.unscaledTime + 2f;
						Plugin.Warn("Native discovery application will retry", e2);
					}
				}
				Status = ((!plugin.Settings.Sharing.Value) ? "Sharing disabled" : ((client.World == 0L) ? "Connecting discovery history" : (client.Enabled ? $"Shared history cursor {client.Cursor}; pending {client.PendingCount}" : "Server sharing disabled")));
				if (!(Time.unscaledTime < nextPoll) && instance.GetServerPeer() != null && instance.GetServerPeer().IsReady())
				{
					nextPoll = Time.unscaledTime + 2f;
					long uid = instance.GetServerPeer().m_uid;
					if (!client.HasHistory)
					{
						Send(uid, client.Hello());
					}
					if (client.Enabled && plugin.Settings.Sharing.Value && plugin.Settings.Materials.Value && client.PendingCount > 0)
					{
						Send(uid, client.Discover());
					}
				}
			}
		}

		private void ResetCharacter(Player player, long id)
		{
			character = player;
			characterId = id;
			client = ((id == 0L) ? null : new DiscoveryClient(id));
			encountered.Clear();
			hostStarted = false;
			hostCursor = 0;
			nextPoll = 0f;
			nextApplyAttempt = 0f;
			plugin.Tracker.Reset(player);
		}

		internal void RegistryChanged()
		{
			client?.RebuildHistory();
			hostStarted = false;
			nextPoll = 0f;
		}

		private void HostTick()
		{
			Status = (failed ? "Sharing unavailable: history error" : (Enabled ? $"Sharing enabled; history {store.Count}" : "Sharing disabled"));
			if (!Enabled)
			{
				return;
			}
			if (!hostStarted)
			{
				hostCursor = ((!plugin.Settings.CatchUp.Value) ? store.Count : 0);
				hostStarted = true;
			}
			if (!(Time.unscaledTime >= nextApplyAttempt))
			{
				return;
			}
			try
			{
				foreach (string item in store.Since(hostCursor).Take(4))
				{
					if (!Apply(item))
					{
						nextApplyAttempt = Time.unscaledTime + 2f;
						break;
					}
					hostCursor++;
				}
			}
			catch (Exception e)
			{
				nextApplyAttempt = Time.unscaledTime + 2f;
				Plugin.Warn("Host catch-up will retry", e);
			}
		}

		internal void Observe(ItemData item)
		{
			if (applying || !Object.op_Implicit((Object)(object)session) || !Object.op_Implicit((Object)(object)character) || !plugin.Registry.Ready || !plugin.Settings.Materials.Value || !plugin.Settings.Sharing.Value || item?.m_shared == null)
			{
				return;
			}
			string preferredPrefab = (Object.op_Implicit((Object)(object)item.m_dropPrefab) ? Utils.GetPrefabName(item.m_dropPrefab) : null);
			string text = plugin.Registry.MaterialIdentity.Resolve(item.m_shared.m_name, preferredPrefab);
			if (text == null || encountered.Contains(text))
			{
				return;
			}
			if (session.IsServer())
			{
				if (Enabled)
				{
					CommitHost(new string[1] { text });
					if (!failed)
					{
						encountered.Add(text);
					}
				}
			}
			else if (client != null && client.Queue(text))
			{
				encountered.Add(text);
			}
		}

		private void CommitHost(string[] ids)
		{
			if (!Enabled)
			{
				return;
			}
			try
			{
				store.CommitBatch(ids);
			}
			catch (Exception e)
			{
				Fail("Discovery commit failed", e);
			}
		}

		private void Fail(string operation, Exception e)
		{
			failed = true;
			Status = "Sharing unavailable: history error";
			Plugin.Warn(operation + "; sharing disabled", e);
		}

		private void Send(long target, Message message)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(Protocol.Encode(message));
			rpc.SendPackage(target, val);
			sentPackets++;
		}

		private static Message Read(ZPackage package)
		{
			int num = package.Size() - package.GetPos();
			if (num < 4 || num > 16388)
			{
				throw new InvalidDataException("Oversized or truncated transport frame");
			}
			int num2 = package.ReadInt();
			Protocol.ValidateTransportFrame(num, num2);
			return Protocol.Decode(package.ReadByteArray(num2));
		}

		private IEnumerator ServerReceive(long sender, ZPackage package)
		{
			try
			{
				if (!Object.op_Implicit((Object)(object)session) || !session.IsServer() || authority == null || failed)
				{
					yield break;
				}
				ZNetPeer peer = session.GetPeer(sender);
				if (peer == null || !peer.IsReady() || peer.m_server)
				{
					yield break;
				}
				Message request = Read(package);
				receivedPackets++;
				ServerResult serverResult;
				try
				{
					serverResult = authority.Handle(sender, connectionReady: true, request, Time.unscaledTimeAsDouble, Enabled, plugin.Settings.CatchUp.Value);
				}
				catch (Exception e)
				{
					Fail("Shared history transaction failed", e);
					yield break;
				}
				if (serverResult.Committed.Length != 0)
				{
					Plugin.Log.LogInfo((object)$"Shared history committed {serverResult.Committed.Length} discoveries; total {store.Count}.");
				}
				foreach (Message reply in serverResult.Replies)
				{
					Send(sender, reply);
				}
			}
			catch (Exception e2)
			{
				rejectedPackets++;
				Plugin.Warn("Rejected discovery packet", e2);
			}
		}

		private IEnumerator ClientReceive(long sender, ZPackage package)
		{
			try
			{
				ZNet instance = ZNet.instance;
				if (Object.op_Implicit((Object)(object)instance) && !instance.IsServer() && client != null && instance.GetServerPeer() != null && sender == instance.GetServerPeer().m_uid)
				{
					Message message = Read(package);
					receivedPackets++;
					if (client.Receive(message, delegate(string id)
					{
						Plugin.Log.LogWarning((object)("Server does not support discovery " + id));
					}) && message.Kind == MessageKind.History && message.Ids.Length != 0)
					{
						nextPoll = Time.unscaledTime + 0.25f;
					}
				}
			}
			catch (Exception e)
			{
				rejectedPackets++;
				Plugin.Warn("Rejected history packet", e);
			}
			yield break;
		}

		private bool Apply(string id)
		{
			Player localPlayer = Player.m_localPlayer;
			if (!Object.op_Implicit((Object)(object)localPlayer))
			{
				return false;
			}
			if (!plugin.Registry.Materials.TryGetValue(id, out var value) || !Object.op_Implicit((Object)(object)value.Item))
			{
				return true;
			}
			if (localPlayer.IsMaterialKnown(value.Item.m_itemData.m_shared.m_name))
			{
				return true;
			}
			applying = true;
			try
			{
				localPlayer.AddKnownItem(value.Item.m_itemData);
				appliedItems++;
				plugin.Tracker.Changed();
				return localPlayer.IsMaterialKnown(value.Item.m_itemData.m_shared.m_name);
			}
			finally
			{
				applying = false;
			}
		}
	}
}
namespace VikingCodex.Core
{
	[Serializable]
	public sealed class CatalogDocument
	{
		public int schema;

		public CatalogMetadata[] entries = Array.Empty<CatalogMetadata>();
	}
	[Serializable]
	public sealed class CatalogMetadata
	{
		public string id = "";

		public string biome = "Unclassified";

		public string category = "";

		public string wiki = "";

		public string displayName = "";

		public string description = "";

		public string hint = "";

		public string iconPrefab = "";

		public string discoveryBehavior = "";

		public string sharing = "";

		public string source = "";

		public string visibility = "";

		public int order = 100000;

		public bool shareMaterial;
	}
	public static class CatalogValidation
	{
		public static Dictionary<string, CatalogMetadata> Validate(CatalogDocument document)
		{
			if (document == null || document.schema != 1 || document.entries == null || document.entries.Length > 20000)
			{
				throw new InvalidDataException("Unsupported metadata schema or size");
			}
			Dictionary<string, CatalogMetadata> dictionary = new Dictionary<string, CatalogMetadata>(StringComparer.Ordinal);
			CatalogMetadata[] entries = document.entries;
			foreach (CatalogMetadata catalogMetadata in entries)
			{
				if (catalogMetadata == null)
				{
					throw new InvalidDataException("Null metadata entry");
				}
				catalogMetadata.category = catalogMetadata.category ?? "";
				catalogMetadata.wiki = catalogMetadata.wiki ?? "";
				catalogMetadata.displayName = catalogMetadata.displayName ?? "";
				catalogMetadata.description = catalogMetadata.description ?? "";
				catalogMetadata.hint = catalogMetadata.hint ?? "";
				catalogMetadata.iconPrefab = catalogMetadata.iconPrefab ?? "";
				catalogMetadata.discoveryBehavior = catalogMetadata.discoveryBehavior ?? "";
				catalogMetadata.sharing = catalogMetadata.sharing ?? "";
				catalogMetadata.source = catalogMetadata.source ?? "";
				catalogMetadata.visibility = catalogMetadata.visibility ?? "";
				if (catalogMetadata == null || !ContentId(catalogMetadata.id) || !Text(catalogMetadata.biome, 80) || string.IsNullOrWhiteSpace(catalogMetadata.biome) || !Text(catalogMetadata.category, 80) || catalogMetadata.order < 0 || !Text(catalogMetadata.wiki, 150) || !Text(catalogMetadata.displayName, 160) || !Text(catalogMetadata.description, 4000, multiline: true) || !Text(catalogMetadata.source, 200))
				{
					throw new InvalidDataException("Invalid metadata entry");
				}
				if (catalogMetadata.iconPrefab != "" && !ContentId(catalogMetadata.iconPrefab))
				{
					throw new InvalidDataException("Invalid icon source");
				}
				if (!Text(catalogMetadata.hint, 180))
				{
					throw new InvalidDataException("Invalid discovery hint");
				}
				if (catalogMetadata.wiki.IndexOfAny(new char[4] { ':', '\\', '?', '#' }) >= 0)
				{
					throw new InvalidDataException("Wiki reference must be a page slug");
				}
				if (catalogMetadata.visibility != "" && catalogMetadata.visibility != "exclude")
				{
					throw new InvalidDataException("Unsupported visibility policy");
				}
				if (catalogMetadata.sharing != "" && catalogMetadata.sharing != "allow" && catalogMetadata.sharing != "deny")
				{
					throw new InvalidDataException("Unknown sharing policy");
				}
				if (catalogMetadata.discoveryBehavior != "" && catalogMetadata.discoveryBehavior != "native-material" && catalogMetadata.discoveryBehavior != "native-recipe" && catalogMetadata.discoveryBehavior != "encounter")
				{
					throw new InvalidDataException("Unsupported discovery behavior");
				}
				if (dictionary.ContainsKey(catalogMetadata.id))
				{
					throw new InvalidDataException("Duplicate metadata identifier: " + catalogMetadata.id);
				}
				dictionary.Add(catalogMetadata.id, catalogMetadata);
			}
			return dictionary;
		}

		public static bool ContentId(string id)
		{
			if (string.IsNullOrWhiteSpace(id) || id.Length > 180 || id != id.Trim())
			{
				return false;
			}
			foreach (char c in id)
			{
				if (char.IsControl(c) || c == '/' || c == '\\' || c == '|')
				{
					return false;
				}
			}
			return true;
		}

		private static bool Text(string value, int maximum, bool multiline = false)
		{
			if (value == null || value.Length > maximum)
			{
				return false;
			}
			foreach (char c in value)
			{
				if (char.IsControl(c) && (!multiline || (c != '\n' && c != '\r' && c != '\t')))
				{
					return false;
				}
			}
			return true;
		}

		public static bool Shareable(bool knownVanilla, bool nativeMaterial, CatalogMetadata metadata)
		{
			if (!nativeMaterial || metadata?.sharing == "deny")
			{
				return false;
			}
			if (metadata != null && metadata.discoveryBehavior != "" && metadata.discoveryBehavior != "native-material")
			{
				return false;
			}
			if (!knownVanilla && !(metadata?.sharing == "allow"))
			{
				return metadata?.shareMaterial ?? false;
			}
			return true;
		}
	}
	public enum DiscoveryFilter
	{
		All,
		Discovered,
		Undiscovered
	}
	public sealed class EntryView
	{
		public string Id = "";

		public string Name = "";

		public string Hint = "";

		public string Biome = "Unclassified";

		public string Category = "Miscellaneous";

		public int Order = 100000;

		public bool Discovered;

		public bool RecipeKnown;

		public bool Craftable;

		public string VisibleName(bool hide)
		{
			if (!hide || Discovered)
			{
				return Name;
			}
			return "???";
		}

		public string RowLabel(bool hide, string kind, bool isNew)
		{
			if (!hide || Discovered)
			{
				return Name + "  ·  " + kind + (isNew ? "  NEW" : "");
			}
			return "Undiscovered · " + kind + "\n" + ((Hint.Length > 86) ? (Hint.Substring(0, 83) + "…") : Hint);
		}
	}
	public static class CodexQuery
	{
		public static IEnumerable<EntryView> Filter(IEnumerable<EntryView> entries, string biome, string category, DiscoveryFilter state, string search, bool hide, bool alphabetical)
		{
			IEnumerable<EntryView> source = from e in entries
				where (biome == "All" || e.Biome == biome) && (category == "All" || e.Category == category)
				where state == DiscoveryFilter.All || e.Discovered == (state == DiscoveryFilter.Discovered)
				where string.IsNullOrWhiteSpace(search) || ((hide && !e.Discovered) ? e.Hint : e.Name).IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0
				select e;
			if (!alphabetical)
			{
				return source.OrderBy((EntryView e) => BiomeOrder(e.Biome)).ThenBy<EntryView, string>((EntryView e) => e.Biome, StringComparer.Ordinal).ThenBy((EntryView e) => e.Order)
					.ThenBy<EntryView, string>((EntryView e) => e.VisibleName(hide), StringComparer.OrdinalIgnoreCase)
					.ThenBy<EntryView, string>((EntryView e) => e.Id, StringComparer.Ordinal);
			}
			return source.OrderBy<EntryView, string>((EntryView e) => e.VisibleName(hide), StringComparer.OrdinalIgnoreCase).ThenBy<EntryView, string>((EntryView e) => e.Id, StringComparer.Ordinal);
		}

		public static int BiomeOrder(string biome)
		{
			int num = Array.IndexOf(new string[12]
			{
				"Meadows", "Black Forest", "Swamp", "Mountains", "Plains", "Mistlands", "Ashlands", "Deep North", "Ocean", "Cross-biome",
				"Special", "Unclassified"
			}, biome);
			if (num >= 0)
			{
				return num;
			}
			return 12;
		}
	}
	public sealed class ContentIdentity
	{
		private readonly Dictionary<string, string> names;

		private readonly Dictionary<string, string?> unique;

		public ContentIdentity(IEnumerable<KeyValuePair<string, string>> idsAndKnowledgeKeys)
		{
			names = idsAndKnowledgeKeys.Where<KeyValuePair<string, string>>((KeyValuePair<string, string> p) => Protocol.ValidId(p.Key) && !string.IsNullOrEmpty(p.Value)).ToDictionary<KeyValuePair<string, string>, string, string>((KeyValuePair<string, string> p) => p.Key, (KeyValuePair<string, string> p) => p.Value, StringComparer.Ordinal);
			unique = names.GroupBy<KeyValuePair<string, string>, string>((KeyValuePair<string, string> p) => p.Value, StringComparer.Ordinal).ToDictionary<IGrouping<string, KeyValuePair<string, string>>, string, string>((IGrouping<string, KeyValuePair<string, string>> g) => g.Key, (IGrouping<string, KeyValuePair<string, string>> g) => (g.Count() != 1) ? null : g.First().Key, StringComparer.Ordinal);
		}

		public string? Resolve(string knowledgeKey, string? preferredPrefab)
		{
			if (string.IsNullOrEmpty(knowledgeKey))
			{
				return null;
			}
			if (preferredPrefab != null && names.TryGetValue(preferredPrefab, out string value) && value == knowledgeKey)
			{
				return preferredPrefab;
			}
			if (!unique.TryGetValue(knowledgeKey, out string value2))
			{
				return null;
			}
			return value2;
		}
	}
	public sealed class ServerResult
	{
		public readonly List<Message> Replies = new List<Message>();

		public string[] Committed = Array.Empty<string>();
	}
	public sealed class DiscoveryAuthority
	{
		private sealed class Peer
		{
			internal readonly RateLimit Limit = new RateLimit();

			internal long Character;

			internal int Floor;

			internal bool Handshake;
		}

		private readonly WorldKnowledgeStore store;

		private readonly Func<string, bool> eligible;

		private readonly Dictionary<long, Peer> peers = new Dictionary<long, Peer>();

		public int PeerCount => peers.Count;

		public DiscoveryAuthority(WorldKnowledgeStore store, Func<string, bool> eligible)
		{
			this.store = store;
			this.eligible = eligible;
		}

		public void RemoveDisconnected(IEnumerable<long> connected)
		{
			HashSet<long> active = new HashSet<long>(connected);
			long[] array = peers.Keys.Where((long id) => !active.Contains(id)).ToArray();
			foreach (long key in array)
			{
				peers.Remove(key);
			}
		}

		public ServerResult Handle(long sender, bool connectionReady, Message request, double now, bool enabled, bool catchUp)
		{
			ServerResult serverResult = new ServerResult();
			if (!connectionReady || sender == 0L || request.Character == 0L || request.Ids == null || request.Ids.Length > 64)
			{
				return serverResult;
			}
			if (!peers.TryGetValue(sender, out Peer value))
			{
				value = (peers[sender] = new Peer());
			}
			int cost = ((request.Kind != MessageKind.Discover) ? 1 : Math.Max(1, (request.Ids.Length + 7) / 8));
			if (!value.Limit.Accept(now, cost))
			{
				return serverResult;
			}
			if (request.Kind == MessageKind.Hello)
			{
				if (request.Ids.Length != 0 || request.Cursor < 0)
				{
					return serverResult;
				}
				if (!value.Handshake || value.Character != request.Character)
				{
					value.Character = request.Character;
					value.Floor = ((!catchUp) ? store.Count : 0);
					value.Handshake = true;
				}
				int val = ((request.World == store.World && request.Epoch == store.Epoch && request.Cursor <= store.Count) ? request.Cursor : 0);
				val = Math.Max(val, value.Floor);
				serverResult.Replies.Add(Envelope(MessageKind.History, request.Character, enabled, enabled ? store.Since(val) : Array.Empty<string>(), val));
				return serverResult;
			}
			if (request.Kind != MessageKind.Discover || !value.Handshake || value.Character != request.Character || request.World != store.World || request.Epoch != store.Epoch || request.Ids.Length == 0)
			{
				return serverResult;
			}
			if (!enabled)
			{
				serverResult.Replies.Add(Envelope(MessageKind.History, request.Character, enabled: false, Array.Empty<string>(), 0));
				return serverResult;
			}
			string[] array = request.Ids.Where((string id) => Protocol.ValidId(id) && eligible(id)).Distinct<string>(StringComparer.Ordinal).ToArray();
			string[] array2 = request.Ids.Except<string>(array, StringComparer.Ordinal).ToArray();
			serverResult.Committed = store.CommitBatch(array);
			if (array.Length != 0)
			{
				serverResult.Replies.Add(Envelope(MessageKind.Acknowledge, request.Character, enabled: true, array, 0));
			}
			if (array2.Length != 0)
			{
				serverResult.Replies.Add(Envelope(MessageKind.Rejected, request.Character, enabled: true, array2, 0));
			}
			return serverResult;
		}

		private Message Envelope(MessageKind kind, long character, bool enabled, string[] ids, int cursor)
		{
			return new Message
			{
				Kind = kind,
				Character = character,
				World = store.World,
				Epoch = store.Epoch,
				Enabled = enabled,
				Ids = ids,
				Cursor = cursor
			};
		}
	}
	public sealed class DiscoveryClient
	{
		private Message? pendingHistory;

		private int pendingOffset;

		private readonly LinkedList<string> pending = new LinkedList<string>();

		private readonly Dictionary<string, LinkedListNode<string>> queued = new Dictionary<string, LinkedListNode<string>>(StringComparer.Ordinal);

		public long Character { get; }

		public long World { get; private set; }

		public string Epoch { get; private set; } = "";

		public int Cursor { get; private set; }

		public bool Enabled { get; private set; }

		public bool HasHistory => pendingHistory != null;

		public int PendingCount => queued.Count;

		public DiscoveryClient(long character)
		{
			if (character == 0L)
			{
				throw new ArgumentException("Character ID must be initialized");
			}
			Character = character;
		}

		public void Pause()
		{
			Enabled = false;
			pendingHistory = null;
			pendingOffset = 0;
		}

		public void RebuildHistory()
		{
			World = 0L;
			Epoch = "";
			Cursor = 0;
			Pause();
		}

		public Message Hello()
		{
			return Envelope(MessageKind.Hello, Array.Empty<string>());
		}

		public Message Discover()
		{
			return Envelope(MessageKind.Discover, pending.Take(64).ToArray());
		}

		private Message Envelope(MessageKind kind, string[] ids)
		{
			return new Message
			{
				Kind = kind,
				Character = Character,
				World = World,
				Epoch = Epoch,
				Cursor = Cursor,
				Ids = ids
			};
		}

		public bool Queue(string id)
		{
			if (!Protocol.ValidId(id) || queued.Count >= 100000 || queued.ContainsKey(id))
			{
				return false;
			}
			queued.Add(id, pending.AddLast(id));
			return true;
		}

		private void Remove(string id)
		{
			if (queued.TryGetValue(id, out LinkedListNode<string> value))
			{
				pending.Remove(value);
				queued.Remove(id);
			}
		}

		public bool Receive(Message message, Action<string> rejected)
		{
			if (message.Character != Character || message.World == 0L || !Guid.TryParseExact(message.Epoch, "N", out var _) || message.Cursor < 0 || message.Ids == null || message.Ids.Length > 64)
			{
				return false;
			}
			if (message.Kind != MessageKind.History && message.Kind != MessageKind.Acknowledge && message.Kind != MessageKind.Rejected)
			{
				return false;
			}
			if (World != 0L && (message.World != World || message.Epoch != Epoch))
			{
				return false;
			}
			if (World == 0L)
			{
				if (message.Kind != MessageKind.History)
				{
					return false;
				}
				World = message.World;
				Epoch = message.Epoch;
				Cursor = message.Cursor;
			}
			if (message.Kind == MessageKind.Acknowledge || message.Kind == MessageKind.Rejected)
			{
				string[] ids = message.Ids;
				foreach (string text in ids)
				{
					Remove(text);
					if (message.Kind == MessageKind.Rejected)
					{
						rejected(text);
					}
				}
				return true;
			}
			Enabled = message.Enabled;
			if (!Enabled)
			{
				pendingHistory = null;
				pendingOffset = 0;
				return true;
			}
			if (message.Cursor != Cursor || HasHistory)
			{
				return false;
			}
			if (message.Ids.Length != 0)
			{
				pendingHistory = message;
				pendingOffset = 0;
			}
			return true;
		}

		public int Drain(Func<string, bool> apply, int maximum)
		{
			if (maximum < 1)
			{
				throw new ArgumentOutOfRangeException("maximum");
			}
			int num = 0;
			while (pendingHistory != null && num < maximum)
			{
				string text = pendingHistory.Ids[pendingOffset];
				if (!apply(text))
				{
					break;
				}
				Remove(text);
				Cursor++;
				pendingOffset++;
				num++;
				if (pendingOffset == pendingHistory.Ids.Length)
				{
					pendingHistory = null;
					pendingOffset = 0;
				}
			}
			return num;
		}
	}
	public static class ProgressionHints
	{
		public static bool IsBaseGame(string id, string prefab, ISet<string> vanilla)
		{
			if (!vanilla.Contains(id))
			{
				return vanilla.Contains(prefab);
			}
			return true;
		}

		public static string Resolve(string authored, string biome, string category, string form)
		{
			if (!string.IsNullOrWhiteSpace(authored))
			{
				return authored;
			}
			object obj;
			switch (biome)
			{
			default:
				obj = "the " + biome;
				break;
			case "Unclassified":
			case "Cross-biome":
			case "Special":
				obj = "the world";
				break;
			}
			string text = (string)obj;
			if (!string.IsNullOrWhiteSpace(form))
			{
				return form;
			}
			return category switch
			{
				"Bosses" => "Follow the summoning clues in " + text + ".", 
				"Creatures" => "Encounter a living inhabitant of " + text + ".", 
				"Raw materials" => "Gather a natural resource from " + text + ".", 
				"Processed materials" => "Process resources from " + text + ".", 
				"Food" => "Prepare a meal with resources from " + text + ".", 
				"Consumables" => "Prepare a useful consumable for your travels.", 
				"Crafting stations" => "Learn a workshop that opens more crafting options.", 
				"Building pieces" => "Learn a construction using this biome's resources.", 
				"Armor" => "Learn protective clothing for this stage of your journey.", 
				"Weapons" => "Learn a weapon suited to this stage of your journey.", 
				"Tools" => "Learn a tool that helps you gather or build.", 
				"Equipment" => "Learn equipment that helps you survive and explore.", 
				_ => "Collect a new object while exploring " + text + ".", 
			};
		}

		public static string Checklist(IEnumerable<EntryView> entries, string biome)
		{
			EntryView[] array = entries.Where((EntryView e) => biome == "All" || e.Biome == biome).ToArray();
			int num = array.Length;
			int num2 = array.Count((EntryView e) => e.Discovered);
			string text = ((num == 0) ? "—" : (((int)((long)num2 * 100L / num)).ToString(CultureInfo.InvariantCulture) + "%"));
			string text2 = ((biome == "All") ? "Base-game checklist" : (biome + " checklist"));
			return text2 + ": " + num2 + "/" + num + " (" + text + ") · " + (num - num2) + " left";
		}
	}
	public enum MessageKind : byte
	{
		Hello = 1,
		Discover,
		History,
		Acknowledge,
		Rejected
	}
	public sealed class Message
	{
		public MessageKind Kind;

		public long World;

		public long Character;

		public string Epoch = "";

		public int Cursor;

		public bool Enabled;

		public string[] Ids = Array.Empty<string>();
	}
	public static class Protocol
	{
		public const int Version = 2;

		public const int MaxBytes = 16384;

		public const int MaxBatch = 64;

		public const int MaxIdLength = 128;

		public static bool ValidId(string id)
		{
			if (string.IsNullOrEmpty(id) || id.Length > 128)
			{
				return false;
			}
			foreach (char c in id)
			{
				if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z') && (c < '0' || c > '9') && c != '_' && c != '-' && c != '.')
				{
					return false;
				}
			}
			return true;
		}

		public static void ValidateTransportFrame(int availableBytes, int declaredBytes)
		{
			if (availableBytes < 4 || availableBytes > 16388 || declaredBytes < 0 || declaredBytes > 16384 || declaredBytes != availableBytes - 4)
			{
				throw new InvalidDataException("Invalid transport frame length");
			}
		}

		public static byte[] Encode(Message m)
		{
			if (m == null || m.Ids == null || m.Ids.Length > 64 || m.Cursor < 0 || !Enum.IsDefined(typeof(MessageKind), m.Kind))
			{
				throw new InvalidDataException("Batch too large");
			}
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true);
			binaryWriter.Write(2);
			binaryWriter.Write((byte)m.Kind);
			binaryWriter.Write(m.World);
			binaryWriter.Write(m.Character);
			WriteString(binaryWriter, m.Epoch, 64);
			binaryWriter.Write(m.Cursor);
			binaryWriter.Write(m.Enabled);
			binaryWriter.Write(m.Ids.Length);
			string[] ids = m.Ids;
			foreach (string text in ids)
			{
				if (!ValidId(text))
				{
					throw new InvalidDataException("Invalid identifier");
				}
				WriteString(binaryWriter, text, 128);
			}
			binaryWriter.Flush();
			if (memoryStream.Length > 16384)
			{
				throw new InvalidDataException("Message too large");
			}
			return memoryStream.ToArray();
		}

		public static Message Decode(byte[] bytes)
		{
			if (bytes == null || bytes.Length > 16384)
			{
				throw new InvalidDataException("Message too large");
			}
			using MemoryStream memoryStream = new MemoryStream(bytes, writable: false);
			using BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.UTF8, leaveOpen: true);
			if (binaryReader.ReadInt32() != 2)
			{
				throw new InvalidDataException("Unsupported protocol");
			}
			Message message = new Message
			{
				Kind = (MessageKind)binaryReader.ReadByte(),
				World = binaryReader.ReadInt64(),
				Character = binaryReader.ReadInt64(),
				Epoch = ReadString(binaryReader, 64),
				Cursor = binaryReader.ReadInt32(),
				Enabled = binaryReader.ReadBoolean()
			};
			if (!Enum.IsDefined(typeof(MessageKind), message.Kind) || message.Cursor < 0)
			{
				throw new InvalidDataException("Invalid envelope");
			}
			int num = binaryReader.ReadInt32();
			if (num < 0 || num > 64)
			{
				throw new InvalidDataException("Invalid count");
			}
			message.Ids = new string[num];
			for (int i = 0; i < num; i++)
			{
				message.Ids[i] = ReadString(binaryReader, 128);
				if (!ValidId(message.Ids[i]))
				{
					throw new InvalidDataException("Invalid identifier");
				}
			}
			if (memoryStream.Position != memoryStream.Length)
			{
				throw new InvalidDataException("Trailing data");
			}
			return message;
		}

		internal static void WriteString(BinaryWriter w, string value, int limit)
		{
			byte[] bytes = Encoding.UTF8.GetBytes(value ?? "");
			if (bytes.Length > limit)
			{
				throw new InvalidDataException("String too long");
			}
			w.Write((ushort)bytes.Length);
			w.Write(bytes);
		}

		internal static string ReadString(BinaryReader r, int limit)
		{
			int num = r.ReadUInt16();
			if (num > limit)
			{
				throw new InvalidDataException("String too long");
			}
			byte[] array = r.ReadBytes(num);
			if (array.Length != num)
			{
				throw new EndOfStreamException();
			}
			return new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetString(array);
		}
	}
	public sealed class RateLimit
	{
		private double tokens = 24.0;

		private double last;

		public bool Accept(double now, int cost = 1)
		{
			if (double.IsNaN(now) || double.IsInfinity(now))
			{
				return false;
			}
			tokens = Math.Min(24.0, tokens + Math.Max(0.0, now - last) * 4.0);
			last = Math.Max(last, now);
			if (cost < 1 || cost > 24 || tokens < (double)cost)
			{
				return false;
			}
			tokens -= cost;
			return true;
		}
	}
	public sealed class UnsupportedSchemaException : Exception
	{
		public UnsupportedSchemaException()
			: base("History schema is newer than this plugin; refusing downgrade")
		{
		}
	}
	public sealed class WorldKnowledgeStore
	{
		public const int Schema = 1;

		public const int MaxEntries = 100000;

		private readonly string path;

		private readonly List<string> entries = new List<string>();

		private readonly HashSet<string> membership = new HashSet<string>(StringComparer.Ordinal);

		public long World { get; }

		public string Epoch { get; private set; } = Guid.NewGuid().ToString("N");

		public int Count => entries.Count;

		public IReadOnlyList<string> Entries => entries;

		public bool Recovered { get; private set; }

		public WorldKnowledgeStore(string directory, long world)
		{
			World = world;
			Directory.CreateDirectory(directory);
			path = Path.Combine(directory, world.ToString(CultureInfo.InvariantCulture) + ".vck");
			if (!File.Exists(path))
			{
				if (File.Exists(path + ".bak"))
				{
					Load(path + ".bak");
					Recovered = true;
				}
				return;
			}
			try
			{
				Load(path);
			}
			catch (Exception ex) when (ex is IOException || ex is InvalidDataException || ex is ArgumentException)
			{
				if (!File.Exists(path + ".bak"))
				{
					throw new InvalidDataException("History damaged; sharing disabled until owner recovery", ex);
				}
				Load(path + ".bak");
				Recovered = true;
			}
		}

		public bool Contains(string id)
		{
			return membership.Contains(id);
		}

		public string[] Since(int cursor)
		{
			return entries.Skip(Math.Max(0, Math.Min(cursor, Count))).Take(64).ToArray();
		}

		public bool Commit(string id)
		{
			return CommitBatch(new string[1] { id }).Length != 0;
		}

		public string[] CommitBatch(IEnumerable<string> identifiers)
		{
			string[] array = identifiers.ToArray();
			if (array.Length > 64 || array.Any((string id) => !Protocol.ValidId(id)))
			{
				throw new InvalidDataException("Invalid discovery batch");
			}
			string[] array2 = (from id in array.Distinct<string>(StringComparer.Ordinal)
				where !membership.Contains(id)
				select id).ToArray();
			if (array2.Length == 0)
			{
				return Array.Empty<string>();
			}
			if (Count + array2.Length > 100000)
			{
				throw new InvalidDataException("History capacity exceeded");
			}
			entries.AddRange(array2);
			membership.UnionWith(array2);
			try
			{
				Save();
				return array2;
			}
			catch
			{
				entries.RemoveRange(entries.Count - array2.Length, array2.Length);
				membership.ExceptWith(array2);
				throw;
			}
		}

		private void Load(string source)
		{
			FileInfo fileInfo = new FileInfo(source);
			if (fileInfo.Length < 32 || fileInfo.Length > 16000000)
			{
				throw new InvalidDataException("Invalid history size");
			}
			byte[] array = File.ReadAllBytes(source);
			using (SHA256 sHA = SHA256.Create())
			{
				if (!sHA.ComputeHash(array, 32, array.Length - 32).SequenceEqual(array.Take(32)))
				{
					throw new InvalidDataException("History checksum mismatch");
				}
			}
			List<string> list = new List<string>();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			using (MemoryStream memoryStream = new MemoryStream(array, 32, array.Length - 32, writable: false))
			{
				using BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.UTF8);
				if (binaryReader.ReadInt32() != 1447250737)
				{
					throw new InvalidDataException("Invalid history header");
				}
				if (binaryReader.ReadInt32() != 1)
				{
					throw new UnsupportedSchemaException();
				}
				if (binaryReader.ReadInt64() != World)
				{
					throw new InvalidDataException("Wrong world");
				}
				string text = Protocol.ReadString(binaryReader, 64);
				if (!Guid.TryParseExact(text, "N", out var _))
				{
					throw new InvalidDataException("Invalid epoch");
				}
				int num = binaryReader.ReadInt32();
				if (num < 0 || num > 100000)
				{
					throw new InvalidDataException("Invalid history count");
				}
				for (int i = 0; i < num; i++)
				{
					string text2 = Protocol.ReadString(binaryReader, 128);
					if (!Protocol.ValidId(text2) || !hashSet.Add(text2))
					{
						throw new InvalidDataException("Invalid history entry");
					}
					list.Add(text2);
				}
				if (memoryStream.Position != memoryStream.Length)
				{
					throw new InvalidDataException("Trailing history data");
				}
				Epoch = text;
			}
			entries.Clear();
			entries.AddRange(list);
			membership.Clear();
			membership.UnionWith(hashSet);
		}

		private void Save()
		{
			byte[] array;
			using (MemoryStream memoryStream = new MemoryStream())
			{
				using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true);
				binaryWriter.Write(1447250737);
				binaryWriter.Write(1);
				binaryWriter.Write(World);
				Protocol.WriteString(binaryWriter, Epoch, 64);
				binaryWriter.Write(Count);
				foreach (string entry in entries)
				{
					Protocol.WriteString(binaryWriter, entry, 128);
				}
				binaryWriter.Flush();
				array = memoryStream.ToArray();
			}
			string sourceFileName = path + ".tmp";
			using (FileStream fileStream = new FileStream(sourceFileName, FileMode.Create, FileAccess.Write, FileShare.None))
			{
				using SHA256 sHA = SHA256.Create();
				byte[] array2 = sHA.ComputeHash(array);
				fileStream.Write(array2, 0, array2.Length);
				fileStream.Write(array, 0, array.Length);
				fileStream.Flush(flushToDisk: true);
			}
			if (File.Exists(path))
			{
				if (Recovered)
				{
					File.Copy(path, path + ".corrupt-" + DateTime.UtcNow.ToString("yyyyMMddHHmmssfff"), overwrite: false);
					File.Delete(path);
					File.Move(sourceFileName, path);
					Recovered = false;
				}
				else
				{
					File.Replace(sourceFileName, path, path + ".bak");
				}
			}
			else
			{
				File.Move(sourceFileName, path);
			}
		}
	}
}
namespace VikingCodex.Configuration
{
	internal sealed class ModSettings
	{
		internal readonly ConfigEntry<bool> Codex;

		internal readonly ConfigEntry<bool> Sharing;

		internal readonly ConfigEntry<bool> CatchUp;

		internal readonly ConfigEntry<bool> Import;

		internal readonly ConfigEntry<bool> Materials;

		internal readonly ConfigEntry<bool> HideNames;

		internal readonly ConfigEntry<bool> Placeholders;

		internal readonly ConfigEntry<bool> Modded;

		internal readonly ConfigEntry<bool> Wiki;

		internal readonly ConfigEntry<bool> Debug;

		internal readonly ConfigEntry<bool> InventoryButton;

		internal readonly ConfigEntry<Vector2> InventoryButtonOffset;

		internal readonly ConfigEntry<KeyboardShortcut> Shortcut;

		internal ModSettings(ConfigFile c)
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0196: Unknown result type (might be due to invalid IL or missing references)
			InventoryButton = c.Bind<bool>("Client", "ShowInventoryButton", true, "Show the Codex button when inventory is visible. Disable if another UI mod conflicts.");
			InventoryButtonOffset = c.Bind<Vector2>("Client", "InventoryButtonOffset", new Vector2(-100f, 35f), "Button offset from the bottom-right corner; adjust for UI mods.");
			Codex = c.Bind<bool>("Client", "EnableCodex", true, "Enable the Codex interface independently of sharing.");
			Sharing = Server("EnableSharedDiscovery", value: true, "Share new eligible discoveries with this server. Requires compatible clients.");
			CatchUp = Server("OfflineCatchUp", value: true, "Send previously recorded history to reconnecting characters.");
			Import = Server("ImportExistingDiscoveries", value: false, "Deprecated: ignored. Incoming character knowledge is never imported; only session item encounters contribute.");
			Materials = Server("ShareEligibleMaterials", value: true, "Share Material-type items registered in the supported registry.");
			Modded = Server("EnableModdedContentDetection", value: false, "Legacy setting retained for config compatibility. The Codex and material registry now include base-game identifiers only, regardless of this value.");
			HideNames = c.Bind<bool>("Client", "HideUndiscoveredNames", true, "Keep unknown identities and recipe details hidden; show discovery clues and icon silhouettes.");
			Placeholders = c.Bind<bool>("Client", "ShowUnknownPlaceholders", true, "Show unknown slots in the Codex.");
			Wiki = c.Bind<bool>("Client", "EnableWikiLinks", true, "Allow an intentional click to open known entries on the Wiki.");
			Debug = c.Bind<bool>("Diagnostics", "EnableDebugLogging", false, "Enable additional diagnostic logs.");
			Shortcut = c.Bind<KeyboardShortcut>("Client", "CodexShortcut", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), "Open Codex. Set MainKey to None to disable.");
			ConfigEntry<bool> Server(string key, bool value, string text)
			{
				//IL_0017: 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_0024: Expected O, but got Unknown
				//IL_0024: Unknown result type (might be due to invalid IL or missing references)
				//IL_002e: Expected O, but got Unknown
				return c.Bind<bool>("Server", key, value, new ConfigDescription(text, (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
				{
					IsAdminOnly = true
				} }));
			}
		}
	}
}
namespace VikingCodex.Compatibility
{
	internal sealed class DiagnosticsCommand : ConsoleCommand
	{
		public override string Name => "vikingcodex";

		public override string Help => "status | reload | export confirm (export contains catalog spoilers)";

		public override void Run(string[] args)
		{
			Execute(args, null);
		}

		public override void Run(string[] args, Terminal terminal)
		{
			Execute(args, terminal);
		}

		private static void Execute(string[] args, Terminal terminal)
		{
			Plugin instance = Plugin.Instance;
			if (!Object.op_Implicit((Object)(object)instance))
			{
				Print("Viking Codex is not initialized.");
				return;
			}
			try
			{
				switch ((args.Length == 0) ? "status" : args[0].ToLowerInvariant())
				{
				case "status":
					Print(string.Format("Viking Codex {0}; protocol {1}; {2}", "1.0.8", 2, instance.Sync.Status));
					Print(instance.Sync.Diagnostics);
					Print($"Registry: {instance.Registry.Entries.Count} entries, {instance.Registry.Materials.Count} eligible materials, {instance.Registry.UnclassifiedCount} unclassified; last build {instance.Registry.BuildMilliseconds:F1} ms.");
					break;
				case "reload":
					instance.ReloadRegistry();
					Print("Local catalog metadata reloaded. Existing character knowledge was not imported or revoked.");
					break;
				case "export":
				{
					if (args.Length != 2 || args[1] != "confirm")
					{
						Print("Export includes undiscovered identities. Use: vikingcodex export confirm");
						break;
					}
					if (!instance.Registry.Ready)
					{
						Print("Enter a world before exporting its loaded registry.");
						break;
					}
					string text = Path.Combine(Paths.ConfigPath, "VikingCodex", "diagnostics");
					Directory.CreateDirectory(text);
					string text2 = Path.Combine(text, "catalog-" + DateTime.UtcNow.ToString("yyyyMMdd-HHmmssfff") + ".tsv");
					using (StreamWriter streamWriter = new StreamWriter(text2, append: false, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)))
					{
						streamWriter.WriteLine("id\tlocalized_name\tbiome\tcategory\torder\tshareable\thas_native_icon");
						foreach (CodexEntry item in instance.Registry.Entries.Values.OrderBy<CodexEntry, string>((CodexEntry e) => e.Id, StringComparer.Ordinal))
						{
							streamWriter.WriteLine(string.Join("\t", Cell(item.Id), Cell(Localization.instance.Localize(item.Name)), Cell(item.Biome), Cell(item.Category), item.Order.ToString(CultureInfo.InvariantCulture), item.Shareable.ToString(), ((Object)(object)item.Icon != (Object)null).ToString()));
						}
					}
					Print("Full loaded-content catalog exported to " + text2);
					break;
				}
				default:
					Print("Usage: vikingcodex status | reload | export confirm");
					break;
				}
			}
			catch (Exception ex)
			{
				Print("Viking Codex diagnostic failed: " + ex.Message);
			}
			static string Cell(string text3)
			{
				return (text3 ?? "").Replace("\t", " ").Replace("\r", " ").Replace("\n", " ");
			}
			void Print(string value)
			{
				if ((Object)(object)terminal != (Object)null)
				{
					terminal.AddString(value);
				}
				Plugin.Log.LogInfo((object)value);
			}
		}
	}
}
namespace VikingCodex.Codex
{
	internal sealed class CodexEntry
	{
		internal string Id;

		internal string Prefab;

		internal string Name;

		internal string Description;

		internal string Hint;

		internal string Biome;

		internal string Category;

		internal string Wiki;

		internal string DiscoveryBehavior;

		internal int Order;

		internal bool Shareable;

		internal ItemDrop Item;

		internal Recipe Recipe;

		internal readonly List<Recipe> Recipes = new List<Recipe>();

		internal Piece Piece;

		internal Character Creature;

		internal Sprite Icon;
	}
	internal sealed class CodexRegistry
	{
		private Dictionary<string, List<string>> related = new Dictionary<string, List<string>>(StringComparer.Ordinal);

		internal Dictionary<string, CodexEntry> Entries { get; private set; } = new Dictionary<string, CodexEntry>(StringComparer.Ordinal);

		internal Dictionary<string, CodexEntry> Materials { get; private set; } = new Dictionary<string, CodexEntry>(StringComparer.Ordinal);

		internal ContentIdentity MaterialIdentity { get; private set; } = new ContentIdentity(Enumerable.Empty<KeyValuePair<string, string>>());

		internal bool Ready { get; private set; }

		internal int Revision { get; private set; }

		internal double BuildMilliseconds { get; private set; }

		internal int UnclassifiedCount { get; private set; }

		private static CatalogDocument Read(string json)
		{
			return JsonConvert.DeserializeObject<CatalogDocument>(json);
		}

		private static CatalogDocument Embedded(string name)
		{
			using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("VikingCodex." + name);
			if (stream == null)
			{
				throw new InvalidDataException("Missing embedded catalog " + name);
			}
			using StreamReader streamReader = new StreamReader(stream);
			CatalogDocument catalogDocument = Read(streamReader.ReadToEnd());
			if (catalogDocument?.entries == null || catalogDocument.entries.Length == 0)
			{
				throw new InvalidDataException("Embedded catalog is empty: " + name);
			}
			return catalogDocument;
		}

		internal void Build(bool includeModded, string directory)
		{
			//IL_03ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f4: Invalid comparison between Unknown and I4
			//IL_0404: Unknown result type (might be due to invalid IL or missing references)
			//IL_040a: Invalid comparison between Unknown and I4
			//IL_041a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0421: Invalid comparison between Unknown and I4
			//IL_0431: Unknown result type (might be due to invalid IL or missing references)
			//IL_0438: Invalid comparison between Unknown and I4
			//IL_0448: Unknown result type (might be due to invalid IL or missing references)
			//IL_044e: Invalid comparison between Unknown and I4
			//IL_045e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0465: Invalid comparison between Unknown and I4
			//IL_0469: Unknown result type (might be due to invalid IL or missing references)
			//IL_0470: Invalid comparison between Unknown and I4
			Stopwatch stopwatch = Stopwatch.StartNew();
			Dictionary<string, CatalogMetadata> vanilla = CatalogValidation.Validate(Embedded("vanilla-content.json"));
			Dictionary<string, CatalogMetadata> metadata = new Dictionary<string, CatalogMetadata>(vanilla, StringComparer.Ordinal);
			foreach (KeyValuePair<string, CatalogMetadata> item in CatalogValidation.Validate(Embedded("progression.json")))
			{
				metadata[item.Key] = item.Value;
			}
			if (Directory.Exists(directory))
			{
				foreach (string item2 in Directory.GetFiles(directory, "*.json").OrderBy<string, string>((string p) => p, StringComparer.Ordinal))
				{
					try
					{
						if (new FileInfo(item2).Length > 4000000)
						{
							throw new InvalidDataException("Metadata exceeds 4MB");
						}
						foreach (KeyValuePair<string, CatalogMetadata> item3 in CatalogValidation.Validate(Read(File.ReadAllText(item2))))
						{
							metadata[item3.Key] = item3.Value;
						}
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("Metadata ignored: " + Path.GetFileName(item2) + ": " + ex.Message));
					}
				}
			}
			Dictionary<string, CodexEntry> entries = new Dictionary<string, CodexEntry>(StringComparer.Ordinal);
			Dictionary<string, CodexEntry> materials = new Dictionary<string, CodexEntry>(StringComparer.Ordinal);
			HashSet<string> vanillaIds = new HashSet<string>(vanilla.Keys, StringComparer.Ordinal);
			GameObject[] array = ObjectDB.instance.m_items.Where((GameObject p) => Object.op_Implicit((Object)(object)p)).ToArray();
			foreach (GameObject prefab in array)
			{
				Isolate(((Object)prefab).name, delegate
				{
					ItemDrop component = prefab.GetComponent<ItemDrop>();
					if (Object.op_Implicit((Object)(object)component) && component.m_itemData?.m_shared != null && Include(((Object)prefab).name, ((Object)prefab).name))
					{
						CodexEntry codexEntry = Make(((Object)prefab).name, ((Object)prefab).name, component.m_itemData.m_shared.m_name, component.m_itemData.m_shared.m_description, Classify(component));
						codexEntry.Item = component;
						codexEntry.Icon = Icon(component);
						codexEntry.Shareable = !string.IsNullOrWhiteSpace(component.m_itemData.m_shared.m_name) && Protocol.ValidId(codexEntry.Prefab) && CatalogValidation.Shareable(vanilla.ContainsKey(((Object)prefab).name), (Object)(object)codexEntry.Icon != (Object)null, Metadata(codexEntry.Id, codexEntry.Prefab));
						if (Metadata(codexEntry.Id, codexEntry.Prefab)?.visibility != "exclude")
						{
							entries[codexEntry.Id] = codexEntry;
						}
						if (codexEntry.Shareable)
						{
							materials[codexEntry.Prefab] = codexEntry;
						}
					}
				});
			}
			Recipe[] array2 = ObjectDB.instance.m_recipes.Where((Recipe r) => Object.op_Implicit((Object)(object)r) && Object.op_Implicit((Object)(object)r.m_item) && r.m_enabled).ToArray();
			foreach (Recipe recipe in array2)
			{
				Isolate(((Object)recipe).name, delegate
				{
					string name2 = ((Object)((Component)recipe.m_item).gameObject).name;
					if (entries.TryGetValue(name2, out var value2))
					{
						string text = "recipe." + ((Object)recipe).name;
						if (!(Metadata(text, name2)?.visibility == "exclude"))
						{
							if (entries.ContainsKey(text))
							{
								Plugin.Log.LogWarning((object)("Duplicate recipe identifier skipped: " + text));
							}
							else
							{
								CodexEntry codexEntry = Make(text, name2, value2.Name, value2.Description, "Crafting recipes");
								codexEntry.Category = "Crafting recipes";
								codexEntry.Recipe = recipe;
								codexEntry.Recipes.Add(recipe);
								codexEntry.Item = recipe.m_item;
								codexEntry.Icon = value2.Icon;
								entries.Add(text, codexEntry);
								value2.Recipes.Add(recipe);
								if (!Object.op_Implicit((Object)(object)value2.Recipe))
								{
									value2.Recipe = recipe;
								}
							}
						}
					}
				});
			}
			array = (from p in ZNetScene.instance.m_prefabs.Concat(ZNetScene.instance.m_nonNetViewPrefabs)
				where Object.op_Implicit((Object)(object)p)
				select p).Distinct().ToArray();
			foreach (GameObject prefab2 in array)
			{
				Isolate(((Object)prefab2).name, delegate
				{
					Character component = prefab2.GetComponent<Character>();
					if (Object.op_Implicit((Object)(object)component) && !(component is Player) && Include("creature." + ((Object)prefab2).name, ((Object)prefab2).name) && Metadata("creature." + ((Object)prefab2).name, ((Object)prefab2).name)?.visibility != "exclude")
					{
						CodexEntry codexEntry = Make("creature." + ((Object)prefab2).name, ((Object)prefab2).name, component.m_name, "", component.m_boss ? "Bosses" : "Creatures");
						codexEntry.Creature = component;
						entries[codexEntry.Id] = codexEntry;
					}
					Piece component2 = prefab2.GetComponent<Piece>();
					if (Object.op_Implicit((Object)(object)component2) && component2.m_enabled && component2.m_resources != null && component2.m_resources.Length != 0 && Include("piece." + ((Object)prefab2).name, ((Object)prefab2).name) && Metadata("piece." + ((Object)prefab2).name, ((Object)prefab2).name)?.visibility != "exclude")
					{
						CodexEntry codexEntry2 = Make("piece." + ((Object)prefab2).name, ((Object)prefab2).name, component2.m_name, component2.m_description, Object.op_Implicit((Object)(object)prefab2.GetComponent<CraftingStation>()) ? "Crafting stations" : "Building pieces");
						codexEntry2.Piece = component2;
						codexEntry2.Icon = component2.m_icon;
						entries[codexEntry2.Id] = codexEntry2;
					}
				});
			}
			foreach (CodexEntry value4 in entries.Values)
			{
				CatalogMetadata catalogMetadata = Metadata(value4.Id, value4.Prefab);
				string form = "";
				if (Object.op_Implicit((Object)(object)value4.Item))
				{
					SharedData shared = value4.Item.m_itemData.m_shared;
					if (value4.Prefab.StartsWith("Trophy", StringComparison.Ordinal))
					{
						form = "A keepsake earned by defeating a creature.";
					}
					else if ((int)shared.m_itemType == 6)
					{
						form = "Learn protection worn on your head.";
					}
					else if ((int)shared.m_itemType == 7)
					{
						form = "Learn protection worn on your torso.";
					}
					else if ((int)shared.m_itemType == 11)
					{
						form = "Learn protection worn on your legs.";
					}
					else if ((int)shared.m_itemType == 17)
					{
						form = "Learn a cloak for your shoulders.";
					}
					else if ((int)shared.m_itemType == 5)
					{
						form = "Learn a shield to block incoming attacks.";
					}
					else if ((int)shared.m_itemType == 9 || (int)shared.m_itemType == 23)
					{
						form = "Learn ammunition for a ranged weapon.";
					}
					else if (value4.Category == "Weapons" || value4.Category == "Tools")
					{
						switch (((object)Unsafe.As<SkillType, SkillType>(ref shared.m_skillType)/*cast due to .constrained prefix*/).ToString())
						{
						case "Axes":
							form = "Learn a chopping blade for this stage of your journey.";
							break;
						case "Pickaxes":
							form = "Learn a stronger tool for breaking mineral deposits.";
							break;
						case "Swords":
							form = "Learn a long blade for close combat.";
							break;
						case "Knives":
							form = "Learn a short blade for quick attacks.";
							break;
						case "Spears":
							form = "Learn a pointed weapon for thrusting and throwing.";
							break;
						case "Polearms":
							form = "Learn a long weapon for keeping enemies at a distance.";
							break;
						case "Clubs":
							form = "Learn a weapon that crushes rather than cuts.";
							break;
						case "Bows":
							form = "Learn a hunting weapon that sends arrows from afar.";
							break;
						case "Crossbows":
							form = "Learn a heavy ranged weapon that fires bolts.";
							break;
						case "ElementalMagic":
							form = "Learn a weapon that channels elemental power.";
							break;
						case "BloodMagic":
							form = "Learn a weapon that channels life into magic.";
							break;
						}
					}
				}
				value4.Hint = ProgressionHints.Resolve(value4.Hint, value4.Biome, value4.Category, form);
				if (!string.IsNullOrEmpty(catalogMetadata?.iconPrefab))
				{
					GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(catalogMetadata.iconPrefab);
					ItemDrop val = (Object.op_Implicit((Object)(object)itemPrefab) ? itemPrefab.GetComponent<ItemDrop>() : null);
					if (Object.op_Implicit((Object)(object)val))
					{
						value4.Icon = Icon(val);
					}
				}
			}
			Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>(StringComparer.Ordinal);
			foreach (CodexEntry item4 in entries.Values.Where((CodexEntry e) => e.Id.StartsWith("recipe.", StringComparison.Ordinal) || Object.op_Implicit((Object)(object)e.Piece)))
			{
				foreach (Requirement item5 in ((Object.op_Implicit((Object)(object)item4.Recipe) ? item4.Recipe.m_resources : item4.Piece.m_resources) ?? Array.Empty<Requirement>()).Where((Requirement r) => Object.op_Implicit((Object)(object)r?.m_resItem)))
				{
					string name = ((Object)((Component)item5.m_resItem).gameObject).name;
					if (!dictionary.TryGetValue(name, out var value))
					{
						value = (dictionary[name] = new List<string>());
					}
					if (!value.Contains(item4.Id))
					{
						value.Add(item4.Id);
					}
				}
			}
			ContentIdentity materialIdentity = new ContentIdentity(materials.Select((KeyValuePair<string, CodexEntry> p) => new KeyValuePair<string, string>(p.Key, p.Value.Item.m_itemData.m_shared.m_name)));
			Entries = entries;
			Materials = materials;
			MaterialIdentity = materialIdentity;
			related = dictionary;
			UnclassifiedCount = entries.Values.Count((CodexEntry e) => e.Biome == "Unclassified");
			Ready = true;
			Revision++;
			stopwatch.Stop();
			BuildMilliseconds = stopwatch.Elapsed.TotalMilliseconds;
			Plugin.Log.LogInfo((object)$"Registry: {entries.Count} entries, {materials.Count} materials, {UnclassifiedCount} unclassified, {BuildMilliseconds:F1} ms on this machine.");
			static Sprite Icon(ItemDrop item)
			{
				try
				{
					return item.m_itemData.GetIcon();
				}
				catch (Exception)
				{
					return null;
				}
			}
			bool Include(string id, string prefab3)
			{
				return ProgressionHints.IsBaseGame(id, prefab3, vanillaIds);
			}
			static void Isolate(string id, Action action)
			{
				try
				{
					action();
				}
				catch (Exception ex2)
				{
					Plugin.Log.LogWarning((object)("Content skipped: " + id + ": " + ex2.Message));
				}
			}
			CodexEntry Make(string id, string text, string text2, string description, string category)
			{
				CatalogMetadata catalogMetadata2 = Metadata(id, text);
				return new CodexEntry
				{
					Id = id,
					Prefab = text,
					Name = ((!string.IsNullOrEmpty(catalogMetadata2?.displayName)) ? catalogMetadata2.displayName : (string.IsNullOrEmpty(text2) ? text : text2)),
					Description = (string.IsNullOrEmpty(catalogMetadata2?.description) ? (description ?? "") : catalogMetadata2.description),
					DiscoveryBehavior = ((!string.IsNullOrEmpty(catalogMetadata2?.discoveryBehavior)) ? catalogMetadata2.discoveryBehavior : (id.StartsWith("creature.", StringComparison.Ordinal) ? "encounter" : ((id.StartsWith("piece.", StringComparison.Ordinal) || id.StartsWith("recipe.", StringComparison.Ordinal)) ? "native-recipe" : "native-material"))),
					Hint = (catalogMetadata2?.hint ?? ""),
					Biome = (catalogMetadata2?.biome ?? "Unclassified"),
					Category = (string.IsNullOrEmpty(catalogMetadata2?.category) ? category : catalogMetadata2.category),
					Order = (catalogMetadata2?.order ?? 100000),
					Wiki = (catalogMetadata2?.wiki ?? "")
				};
			}
			CatalogMetadata Metadata(string id, string key)
			{
				if (metadata.TryGetValue(id, out var value2))
				{
					return value2;
				}
				metadata.TryGetValue(key, out var value3);
				return value3;
			}
		}

		internal IEnumerable<CodexEntry> RelatedTo(CodexEntry entry)
		{
			if (!related.TryGetValue(entry.Prefab, out var value))
			{
				return Enumerable.Empty<CodexEntry>();
			}
			return from id in value.Where(Entries.ContainsKey)
				select Entries[id];
		}

		private static string Classify(ItemDrop item)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: 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_007b: Expected I4, but got Unknown
			SharedData shared = item.m_itemData.m_shared;
			ItemType itemType = shared.m_itemType;
			switch (itemType - 1)
			{
			case 0:
				return "Raw materials";
			case 1:
				if (!(shared.m_food > 0f) && !(shared.m_foodStamina > 0f) && !(shared.m_foodEitr > 0f))
				{
					return "Consumables";
				}
				return "Food";
			case 18:
				return "Tools";
			case 5:
			case 6:
			case 10:
			case 11:
			case 16:
				return "Armor";
			case 2:
			case 3:
			case 8:
			case 13:
			case 19:
			case 21:
			case 22:
				return "Weapons";
			case 4:
			case 17:
			case 23:
				return "Equipment";
			default:
				return "Miscellaneous items";
			}
		}
	}
	internal sealed class DiscoveryTracker
	{
		private const string ObservationKey = "com.vikingcodex.observations.v1";

		private Player character;

		private readonly HashSet<string> observed = new HashSet<string>(StringComparer.Ordinal);

		private const string ViewedKey = "com.vikingcodex.viewed.v1";

		private readonly HashSet<string> viewed = new HashSet<string>(StringComparer.Ordinal);

		private readonly HashSet<string> baseline = new HashSet<string>(StringComparer.Ordinal);

		private bool baselineReady;

		internal int Revision { get; private set; }

		internal void Reset(Player player)
		{
			character = player;
			observed.Clear();
			viewed.Clear();
			baseline.Clear();
			baselineReady = false;
			if (Object.op_Implicit((Object)(object)player) && player.m_customData.TryGetValue("com.vikingcodex.viewed.v1", out var value) && value != null && value.Length <= 4000000)
			{
				string[] array = value.Split(new char[1] { '|' }, 20001);
				foreach (string text in array)
				{
					if (viewed.Count < 20000 && CatalogValidation.ContentId(text))
					{
						viewed.Add(text);
					}
				}
			}
			if (Object.op_Implicit((Object)(object)player) && player.m_customData.TryGetValue("com.vikingcodex.observations.v1", out var value2) && value2 != null && value2.Length <= 4000000)
			{
				string[] array = value2.Split(new char[1] { '|' }, 20001);
				foreach (string text2 in array)
				{
					if (observed.Count < 20000 && CatalogValidation.ContentId(text2))
					{
						observed.Add(text2);
					}
				}
			}
			Revision++;
		}

		internal void Changed()
		{
			Revision++;
		}

		internal void SeedHighlights(IEnumerable<CodexEntry> entries, Player player)
		{
			if (baselineReady || !Object.op_Implicit((Object)(object)player))
			{
				return;
			}
			foreach (CodexEntry entry in entries)
			{
				if (View(entry, player).Discovered)
				{
					baseline.Add(entry.Id);
				}
			}
			baselineReady = true;
		}

		internal bool IsNew(CodexEntry entry, Player player)
		{
			if (baselineReady && !baseline.Contains(entry.Id) && !viewed.Contains(entry.Id))
			{
				return View(entry, player).Discovered;
			}
			return false;
		}

		internal void MarkViewed(string id)
		{
			if (Object.op_Implicit((Object)(object)character) && viewed.Count < 20000 && viewed.Add(id))
			{
				character.m_customData["com.vikingcodex.viewed.v1"] = string.Join("|", viewed.OrderBy<string, string>((string x) => x, StringComparer.Ordinal));
			}
		}

		internal void Observe(string id)
		{
			if (Object.op_Implicit((Object)(object)character) && CatalogValidation.ContentId(id) && observed.Count < 20000 && observed.Add(id))
			{
				character.m_customData["com.vikingcodex.observations.v1"] = string.Join("|", observed.OrderBy<string, string>((string x) => x, StringComparer.Ordinal));
				Revision++;
			}
		}

		internal bool Known(CodexEntry e, Player p)
		{
			if (!Object.op_Implicit((Object)(object)p))
			{
				return false;
			}
			if (e.DiscoveryBehavior == "encounter")
			{
				return observed.Contains(e.Id);
			}
			if (Object.op_Implicit((Object)(object)e.Piece))
			{
				if (!observed.Contains(e.Id))
				{
					return p.IsRecipeKnown(e.Piece.m_name);
				}
				return true;
			}
			if (e.DiscoveryBehavior == "native-recipe")
			{
				if (Object.op_Implicit((Object)(object)e.Item))
				{
					return p.IsRecipeKnown(e.Item.m_itemData.m_shared.m_name);
				}
				return false;
			}
			if (Object.op_Implicit((Object)(object)e.Item))
			{
				return p.IsMaterialKnown(e.Item.m_itemData.m_shared.m_name);
			}
			return false;
		}

		internal EntryView View(CodexEntry e, Player p, bool evaluateCraftable = false)
		{
			bool flag = Known(e, p);
			Recipe[] array = e.Recipes.Where((Recipe r) => Object.op_Implicit((Object)(object)r) && Object.op_Implicit((Object)(object)r.m_item) && Object.op_Implicit((Object)(object)p) && p.IsRecipeKnown(r.m_item.m_itemData.m_shared.m_name)).ToArray();
			bool flag2 = array.Length != 0 || (Object.op_Implicit((Object)(object)p) && Object.op_Implicit((Object)(object)e.Piece) && p.IsRecipeKnown(e.Piece.m_name));
			return new EntryView
			{
				Id = e.Id,
				Name = Localization.instance.Localize(e.Name),
				Hint = e.Hint,
				Biome = e.Biome,
				Category = e.Category,
				Order = e.Order,
				Discovered = (flag || flag2),
				RecipeKnown = flag2,
				Craftable = (evaluateCraftable && flag2 && (array.Any((Recipe r) => r.m_enabled && !r.m_noCraftOnlyUpgrade && p.RequiredCraftingStation(r, 1, false) && p.HaveRequirements(r, false, 1, 1)) || (Object.op_Implicit((Object)(object)e.Piece) && p.HaveRequirements(e.Piece, (RequirementMode)0))))
			};
		}
	}
	[HarmonyPatch(typeof(Player), "AddKnownItem")]
	internal static class MaterialObservation
	{
		private static void Prefix(Player __instance, ItemData item, out bool __state)
		{
			__state = false;
			try
			{
				__state = (Object)(object)__instance == (Object)(object)Player.m_localPlayer && item?.m_shared != null;
			}
			catch (Exception e)
			{
				Plugin.Warn("Discovery observation", e);
			}
		}

		private static void Postfix(Player __instance, ItemData item, bool __state)
		{
			try
			{
				if (__state && Object.op_Implicit((Object)(object)Plugin.Instance) && __instance.IsMaterialKnown(item.m_shared.m_name))
				{
					Plugin.Instance.Tracker.Changed();
					Plugin.Instance.Sync.Observe(item);
				}
			}
			catch (Exception e)
			{
				Plugin.Warn("Discovery propagation", e);
			}
		}
	}
	[HarmonyPatch(typeof(Player), "UpdateHover")]
	internal static class EncounterObservation
	{
		private static void Postfix(Player __instance)
		{
			try
			{
				if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer || !Object.op_Implicit((Object)(object)Plugin.Instance) || !Plugin.Instance.Settings.Codex.Value)
				{
					return;
				}
				Character hoverCreature = __instance.GetHoverCreature();
				if (Object.op_Implicit((Object)(object)hoverCreature) && !(hoverCreature is Player))
				{
					Plugin.Instance.Tracker.Observe("creature." + Utils.GetPrefabName(((Component)hoverCreature).gameObject));
				}
				Piece hoveringPiece = __instance.GetHoveringPiece();
				if (Object.op_Implicit((Object)(object)hoveringPiece))
				{
					Plugin.Instance.Tracker.Observe("piece." + Utils.GetPrefabName(((Component)hoveringPiece).gameObject));
				}
				GameObject hoverObject = ((Humanoid)__instance).GetHoverObject();
				ItemDrop val = (Object.op_Implicit((Object)(object)hoverObject) ? hoverObject.GetComponentInParent<ItemDrop>() : null);
				if (Object.op_Implicit((Object)(object)val))
				{
					string prefabName = Utils.GetPrefabName(((Component)val).gameObject);
					if (Plugin.Instance.Registry.Entries.TryGetValue(prefabName, out var value) && value.DiscoveryBehavior == "encounter")
					{
						Plugin.Instance.Tracker.Observe(prefabName);
					}
				}
			}
			catch (Exception e)
			{
				Plugin.Warn("Encounter observation", e);
			}
		}
	}
	[HarmonyPatch(typeof(Player), "AddKnownStation")]
	internal static class StationObservation
	{
		private static void Postfix(Player __instance)
		{
			try
			{
				if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer && Object.op_Implicit((Object)(object)Plugin.Instance))
				{
					Plugin.Instance.Tracker.Changed();
				}
			}
			catch (Exception e)
			{
				Plugin.Warn("Station knowledge observation", e);
			}
		}
	}
}
namespace VikingCodex.Codex.UI
{
	internal sealed class CodexPanel : IDisposable
	{
		private readonly Plugin plugin;

		private const int RowHeight = 64;

		private GameObject root;

		private GameObject menuButton;

		private InventoryGui inventory;

		private Text progress;

		private Text details;

		private Text title;

		private Image detailIcon;

		private Texture2D fallbackTexture;

		private Sprite fallbackIcon;

		private Button wiki;

		private Dropdown biome;

		private Dropdown category;

		private Dropdown state;

		private Dropdown sort;

		private InputField search;

		private ScrollRect scroll;

		private ScrollRect detailScrollRect;

		private RectTransform content;

		private readonly List<Button> rows = new List<Button>();

		private readonly List<Image> icons = new List<Image>();

		private List<EntryView> views = new List<EntryView>();

		private string selected;

		private int revision = -1;

		private int registryRevision = -1;

		private readonly int[] rowIndices = Enumerable.Repeat(-1, 14).ToArray();

		private string renderedDetail = "";

		private bool filtering;

		private bool opened;

		private float detailAt;

		private float nextRefresh;

		private bool lastHide = true;

		private bool lastPlaceholders = true;

		private readonly Vector2 topLeft = new Vector2(0f, 1f);

		private int First => Math.Max(0, Math.Min(Math.Max(0, views.Count - 1), (int)(content.anchoredPosition.y / 64f)));

		internal CodexPanel(Plugin p)
		{
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			plugin = p;
		}

		internal void Tick()
		{
			//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Expected O, but got Unknown
			//IL_018d: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02db: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_030e: Unknown result type (might be due to invalid IL or missing references)
			if (opened && !Object.op_Implicit((Object)(object)root))
			{
				Close();
			}
			if (!Object.op_Implicit((Object)(object)Player.m_localPlayer) || !plugin.Settings.Codex.Value)
			{
				if (opened)
				{
					Close();
				}
				if (Object.op_Implicit((Object)(object)menuButton))
				{
					menuButton.SetActive(false);
				}
				return;
			}
			if (Object.op_Implicit((Object)(object)InventoryGui.instance) && (Object)(object)inventory != (Object)(object)InventoryGui.instance)
			{
				inventory = InventoryGui.instance;
				if (Object.op_Implicit((Object)(object)menuButton))
				{
					Object.Destroy((Object)(object)menuButton);
				}
				menuButton = GUIManager.Instance.CreateButton("Codex", ((Component)inventory).transform, new Vector2(1f, 0f), new Vector2(1f, 0f), plugin.Settings.InventoryButtonOffset.Value, 160f, 38f);
				((Object)menuButton).name = "VikingCodex.Open";
				((UnityEvent)menuButton.GetComponent<Button>().onClick).AddListener(new UnityAction(Toggle));
			}
			if (Object.op_Implicit((Object)(object)menuButton))
			{
				menuButton.SetActive(plugin.Settings.InventoryButton.Value && InventoryGui.IsVisible());
				menuButton.GetComponent<RectTransform>().anchoredPosition = plugin.Settings.InventoryButtonOffset.Value;
			}
			KeyboardShortcut value = plugin.Settings.Shortcut.Value;
			if (((KeyboardShortcut)(ref value)).IsDown() && (!Object.op_Implicit((Object)(object)Chat.instance) || !Chat.instance.HasFocus()))
			{
				Toggle();
			}
			if (plugin.Registry.Ready)
			{
				plugin.Tracker.SeedHighlights(plugin.Registry.Entries.Values, Player.m_localPlayer);
			}
			if (!Object.op_Implicit((Object)(object)root) || !root.activeSelf)
			{
				return;
			}
			if (Input.GetKeyDown((KeyCode)27))
			{
				Close();
				return;
			}
			if (!search.isFocused)
			{
				GameObject val = (Object.op_Implicit((Object)(object)EventSystem.current) ? EventSystem.current.currentSelectedGameObject : null);
				bool flag = Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)val.GetComponentInParent<Dropdown>());
				if (!flag && Input.GetKeyDown((KeyCode)274))
				{
					MoveSelection(1);
				}
				else if (!flag && Input.GetKeyDown((KeyCode)273))
				{
					MoveSelection(-1);
				}
			}
			Transform parent = root.transform.parent;
			RectTransform val2 = (RectTransform)(object)((parent is RectTransform) ? parent : null);
			if (Object.op_Implicit((Object)(object)val2))
			{
				Transform transform = root.transform;
				Vector3 one = Vector3.one;
				Rect rect = val2.rect;
				float num = ((Rect)(ref rect)).width / 1140f;
				rect = val2.rect;
				transform.localScale = one * Mathf.Min(1f, Mathf.Min(num, ((Rect)(ref rect)).height / 750f));
			}
			if (Time.unscaledTime >= nextRefresh && (registryRevision != plugin.Registry.Revision || revision != plugin.Tracker.Revision || lastHide != plugin.Settings.HideNames.Value || lastPlaceholders != plugin.Settings.Placeholders.Value))
			{
				nextRefresh = Time.unscaledTime + 0.15f;
				Refresh(resetScroll: false);
			}
			if (Time.unscaledTime >= detailAt)
			{
				detailAt = Time.unscaledTime + 1f;
				Detail();
			}
		}

		private void Toggle()
		{
			if (!plugin.Settings.Codex.Value || !Object.op_Implicit((Object)(object)Player.m_localPlayer) || !Object.op_Implicit((Object)(object)GUIManager.CustomGUIFront))
			{
				return;
			}
			if (!Object.op_Implicit((Object)(object)root))
			{
				try
				{
					rows.Clear();
					icons.Clear();
					Create();
				}
				catch (Exception e)
				{
					Close();
					if (Object.op_Implicit((Object)(object)root))
					{
						Object.Destroy((Object)(object)root);
					}
					root = null;
					Plugin.Warn("Codex interface creation failed", e);
					return;
				}
			}
			if (root.activeSelf)
			{
				Close();
				return;
			}
			root.SetActive(true);
			opened = true;
			GUIManager.BlockInput(true);
			Refresh();
			((Selectable)search).Select();
		}

		private void Close()
		{
			if (Object.op_Implicit((Object)(object)root))
			{
				root.SetActive(false);
			}
			if (opened)
			{
				GUIManager.BlockInput(false);
			}
			opened = false;
		}

		private Text Text(Transform parent, string value, float x, float y, float w, float h, int size = 18)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: 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)
			GameObject obj = GUIManager.Instance.CreateText(value, parent, topLeft, topLeft, new Vector2(x, y), GUIManager.Instance.AveriaSerif, size, new Color(0.94f, 0.88f, 0.73f), false, Color.black, w, h, false);
			obj.GetComponent<RectTransform>().pivot = topLeft;
			Text component = obj.GetComponent<Text>();
			component.alignment = (TextAnchor)0;
			component.supportRichText = false;
			return component;
		}

		private Button Button(Transform parent, string label, float x, float y, float w, float h, Action action)
		{
			//IL_0016: 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_0024: 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_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Expected O, but got Unknown
			GameObject obj = GUIManager.Instance.CreateButton(label, parent, topLeft, topLeft, new Vector2(x, y), w, h);
			obj.GetComponent<RectTransform>().pivot = topLeft;
			Button component = obj.GetComponent<Button>();
			((UnityEvent)component.onClick).AddListener((UnityAction)delegate
			{
				action();
			});
			return component;
		}

		private Dropdown Drop(Transform parent, float x, float y, float w, IEnumerable<string> options)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			GameObject obj = GUIManager.Instance.CreateDropDown(parent, topLeft, topLeft, new Vector2(x, y), 16, w, 36f);
			obj.GetComponent<RectTransform>().pivot = topLeft;
			Dropdown component = obj.GetComponent<Dropdown>();
			component.ClearOptions();
			component.AddOptions(options.ToList());
			((UnityEvent<int>)(object)component.onValueChanged).AddListener((UnityAction<int>)delegate
			{
				if (!filtering)
				{
					Refresh();
				}
			});
			return component;
		}

		private void Create()
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Expected O, but got Unknown
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_018c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f6: Expected O, but got Unknown
			//IL_0418: Unknown result type (might be due to invalid IL or missing references)
			//IL_041d: Unknown result type (might be due to invalid IL or missing references)
			//IL_041e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0425: Unknown result type (might be due to invalid IL or missing references)
			//IL_0427: Unknown result type (might be due to invalid IL or missing references)
			//IL_0428: Unknown result type (might be due to invalid IL or missing references)
			//IL_042f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0441: Unknown result type (might be due to invalid IL or missing references)
			//IL_0456: Unknown result type (might be due to invalid IL or missing references)
			//IL_047a: Unknown result type (might be due to invalid IL or missing references)
			//IL_04a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_04d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_04dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_04de: Unknown result type (might be due to invalid IL or missing references)
			//IL_04e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_04e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_04e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_04ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_0506: Unknown result type (might be due to invalid IL or missing references)
			//IL_05ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_05b1: Expected O, but got Unknown
			//IL_05d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_05dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_05dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_05e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_05e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_05e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_05ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_0601: Unknown result type (might be due to invalid IL or missing references)
			//IL_0617: Unknown result type (might be due to invalid IL or missing references)
			//IL_063b: Unknown result type (might be due to invalid IL or missing references)
			//IL_066a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0696: Unknown result type (might be due to invalid IL or missing references)
			//IL_06a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_06b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_06d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_06e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_06f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_07b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_07b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_07c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0817: Unknown result type (might be due to invalid IL or missing references)
			//IL_082b: Unknown result type (might be due to invalid IL or missing references)
			//IL_085a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0894: Unknown result type (might be due to invalid IL or missing references)
			//IL_089b: Unknown result type (might be due to invalid IL or missing references)
			//IL_08ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_08c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_08d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_093f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0979: Unknown result type (might be due to invalid IL or missing references)
			//IL_097e: Unknown result type (might be due to invalid IL or missing references)
			//IL_097f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0986: Unknown result type (might be due to invalid IL or missing references)
			//IL_0988: Unknown result type (might be due to invalid IL or missing references)
			//IL_0989: Unknown result type (might be due to invalid IL or missing references)
			//IL_0990: Unknown result type (might be due to invalid IL or missing references)
			//IL_09a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_09b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_09ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a06: 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_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a8d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a92: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a93: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a9a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a9c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a9d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0aa4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ab5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0afe: Unknown result type (might be due to invalid IL or missing references)
			fallbackTexture = new Texture2D(32, 32, (TextureFormat)4, false);
			Color[] array = (Color[])(object)new Color[1024];
			for (int i = 4; i < 28; i++)
			{
				for (int j = 7; j < 25; j++)
				{
					if (j < 21 || i < 24)
					{
						array[i * 32 + j] = Color.white;
					}
				}
			}
			fallbackTexture.SetPixels(array);
			fallbackTexture.Apply();
			fallbackIcon = Sprite.Create(fallbackTexture, new Rect(0f, 0f, 32f, 32f), new Vector2(0.5f, 0.5f));
			root = GUIManager.Instance.CreateWoodpanel(GUIManager.CustomGUIFront.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, 1120f, 720f, false);
			((Object)root).name = "VikingCodex.Panel";
			root.SetActive(false);
			title = Text(root.transform, "Viking Codex", 32f, -24f, 600f, 44f, 32);
			Button(root.transform, "Close", 980f, -24f, 104f, 38f, Close);
			search = GUIManager.Instance.CreateInputField(root.transform, topLeft, topLeft, new Vector2(32f, -84f), (ContentType)0, "Search names or discovery clues…", 18, 410f, 38f).GetComponent<InputField>();
			((Component)search).GetComponent<RectTransform>().pivot = topLeft;
			search.characterLimit = 100;
			((UnityEvent<string>)(object)search.onValueChanged).AddListener((UnityAction<string>)delegate
			{
				if (!filtering)
				{
					Refresh();
				}
			});
			Text(root.transform, "Biome", 460f, -62f, 190f, 22f, 14);
			Text(root.transform, "Category", 668f, -62f, 220f, 22f, 14);
			biome = Drop(root.transform, 460f, -84f, 190f, new string[1] { "All" });
			category = Drop(root.transform, 668f, -84f, 220f, new string[1] { "All" });
			Button(root.transform, "Clear filters", 908f, -84f, 176f, 38f, Clear);
			state = Drop(root.transform, 32f, -138f, 210f, new string[3] { "All", "Discovered", "Undiscovered" });
			sort = Drop(root.transform, 260f, -138f, 220f, new string[2] { "Progression order", "Alphabetical" });
			progress = Text(root.transform, "", 500f, -140f, 584f, 44f, 17);
			GameObject val = new GameObject("VikingCodex.List", new Type[4]
			{
				typeof(RectTransform),
				typeof(Image),
				typeof(Mask),
				typeof(ScrollRect)
			});
			val.transform.SetParent(root.transform, false);
			RectTransform component = val.GetComponent<RectTransform>();
			Vector2 val2 = (component.pivot = topLeft);
			Vector2 val4 = (component.anchorMin = (component.anchorMax = val2));
			component.anchoredPosition = new Vector2(32f, -194f);
			component.sizeDelta = new Vector2(490f, 480f);
			((Graphic)val.GetComponent<Image>()).color = new Color(0.08f, 0.065f, 0.045f, 0.8f);
			val.GetComponent<Mask>().showMaskGraphic = true;
			content = new GameObject("Content", new Type[1] { typeof(RectTransform) }).GetComponent<RectTransform>();
			((Transform)content).SetParent((Transform)(object)component, false);
			RectTransform obj = content;
			RectTransform obj2 = content;
			val2 = (content.pivot = topLeft);
			val4 = (obj.anchorMin = (obj2.anchorMax = val2));
			content.sizeDelta = new Vector2(486f, 480f);
			scroll = val.GetComponent<ScrollRect>();
			scroll.viewport = component;
			scroll.content = content;
			scroll.horizontal = false;
			scroll.vertical = true;
			scroll.movementType = (MovementType)2;
			scroll.scrollSensitivity = 132f;
			scroll.inertia = false;
			GameObject val10 = new GameObject("VikingCodex.ListScrollbar", new Type[3]
			{
				typeof(RectTransform),
				typeof(Image),
				typeof(Scrollbar)
			});
			val10.transform.SetParent(root.transform, false);
			RectTransform component2 = val10.GetComponent<RectTransform>();
			val2 = (component2.pivot = topLeft);
			val4 = (component2.anchorMin = (component2.anchorMax = val2));
			component2.anchoredPosition = new Vector2(528f, -194f);
			component2.sizeDelta = new Vector2(20f, 480f);
			((Graphic)val10.GetComponent<Image>()).color = new Color(0.12f, 0.09f, 0.05f, 0.9f);
			Image component3 = new GameObject("Handle", new Type[2]
			{
				typeof(RectTransform),
				typeof(Image)
			}).GetComponent<Image>();
			((Component)component3).transform.SetParent((Transform)(object)component2, false);
			((Graphic)component3).color = new Color(0.72f, 0.51f, 0.23f);
			((Graphic)component3).rectTransform.anchorMin = Vector2.zero;
			((Graphic)component3).rectTransform.anchorMax = Vector2.one;
			((Graphic)component3).rectTransform.pivot = new Vector2(0.5f, 0.5f);
			((Graphic)component3).rectTransform.sizeDelta = Vector2.zero;
			((Graphic)component3).rectTransform.anchoredPosition = Vector2.zero;
			Scrollbar component4 = val10.GetComponent<Scrollbar>();
			component4.direction = (Direction)2;
			component4.handleRect = ((Graphic)component3).rectTransform;
			((Selectable)component4).targetGraphic = (Graphic)(object)component3;
			scroll.verticalScrollbar = component4;
			scroll.verticalScrollbarVisibility = (ScrollbarVisibility)0;
			((UnityEvent<Vector2>)(object)scroll.onValueChanged).AddListener((UnityAction<Vector2>)delegate
			{
				RenderRows();
			});
			for (int num = 0; num < 14; num++)
			{
				int slot = num;
				Button val14 = Button((Transform)(object)content, "", 0f, -num * 64, 480f, 62f, delegate
				{
					SelectRow(slot);
				});
				Navigation navigation = ((Selectable)val14).navigation;
				((Navigation)(ref navigation)).mode = (Mode)0;
				((Selectable)val14).navigation = navigation;
				((Component)val14).GetComponentInChildren<Text>().supportRichText = false;
				((Component)val14).GetComponentInChildren<Text>().alignment = (TextAnchor)3;
				((Component)val14).GetComponentInChildren<Text>().fontSize = 16;
				((Component)val14).GetComponentInChildren<Text>().horizontalOverflow = (HorizontalWrapMode)0;
				RectTransform rectTransform = ((Graphic)((Component)val14).GetComponentInChildren<Text>()).rectTransform;
				rectTransform.offsetMin = new Vector2(46f, 0f);
				rectTransform.offsetMax = new Vector2(-12f, 0f);
				Image component5 = new GameObject("Icon", new Type[2]
				{
					typeof(RectTransform),
					typeof(Image)
				}).GetComponent<Image>();
				((Component)component5).transform.SetParent(((Component)val14).transform, false);
				RectTransform rectTransform2 = ((Graphic)component5).rectTransform;
				((Vector2)(ref val4))..ctor(0f, 0.5f);
				rectTransform2.anchorMax = val4;
				rectTransform2.anchorMin = val4;
				rectTransform2.pivot = new Vector2(0f, 0.5f);
				rectTransform2.anchoredPosition = new Vector2(8f, 0f);
				rectTransform2.sizeDelta = new Vector2(30f, 30f);
				component5.preserveAspect = true;
				((Graphic)component5).raycastTarget = false;
				rows.Add(val14);
				icons.Add(component5);
			}
			detailIcon = new GameObject("EntryIcon", new Type[2]
			{
				typeof(RectTransform),
				typeof(Image)
			}).GetComponent<Image>();
			((Component)detailIcon).transform.SetParent(root.transform, false);
			RectTransform rectTransform3 = ((Graphic)detailIcon).rectTransform;
			val2 = (rectTransform3.pivot = topLeft);
			val4 = (rectTransform3.anchorMin = (rectTransform3.anchorMax = val2));
			rectTransform3.anchoredPosition = new Vector2(580f, -198f);
			rectTransform3.sizeDelta = new Vector2(64f, 64f);
			detailIcon.preserveAspect = true;
			GameObject obj3 = GUIManager.Instance.CreateScrollView(root.transform, false, true, 0f, 10f, GUIManager.Instance.ValheimScrollbarHandleColorBlock, new Color(0.08f, 0.065f, 0.045f, 0.7f), 510f, 350f);
			ScrollRect val18 = (detailScrollRect = obj3.GetComponentInChildren<ScrollRect>());
			val18.scrollSensitivity = 132f;
			val18.inertia = false;
			val18.movementType = (MovementType)2;
			VerticalLayoutGroup component6 = ((Component)val18.content).GetComponent<VerticalLayoutGroup>();
			if (Object.op_Implicit((Object)(object)component6))
			{
				((Behaviour)component6).enabled = false;
			}
			ContentSizeFitter component7 = ((Component)val18.content).GetComponent<ContentSizeFitter>();
			if (Object.op_Implicit((Object)(object)component7))
			{
				((Behaviour)component7).enabled = false;
			}
			RectTransform component8 = obj3.GetComponent<RectTransform>();
			val2 = (component8.pivot = topLeft);
			val4 = (component8.anchorMin = (component8.anchorMax = val2));
			component8.anchoredPosition = new Vector2(574f, -280f);
			details = Text((Transform)(object)val18.content, "Select an entry.", 10f, -10f, 460f, 800f);
			val18.content.sizeDelta = new Vector2(480f, 800f);
			wiki = Button(root.transform, "Open Wiki", 574f, -642f, 180f, 38f, OpenWiki);
			Text(root.transform, "Base-game checklist. Unclassified entries are listed separately; biome coverage is still under review.", 32f, -686f, 1050f, 24f, 15);
		}

		private static string Option(Dropdown d)
		{
			if (d.options.Count != 0)
			{
				return d.options[d.value].text.Split(new string[1] { " | " }, StringSplitOptions.None)[0];
			}
			return "All";
		}

		private void UpdateOptions(Dropdown d, IEnumerable<string>