Decompiled source of Bitencola Museum Tracker v1.0.0

BitencolaMuseumTracker.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using BitencolaMuseumTracker.Core;
using GameNetcodeStuff;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("BitencolaMuseumTracker")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+5de957a81301ed6629a30a8cbd6a586f989b1a1e")]
[assembly: AssemblyProduct("BitencolaMuseumTracker")]
[assembly: AssemblyTitle("BitencolaMuseumTracker")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace BitencolaMuseumTracker
{
	[BepInPlugin("Bitencola.MuseumTracker", "Bitencola Museum Tracker", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "Bitencola.MuseumTracker";

		public const string PluginName = "Bitencola Museum Tracker";

		public const string PluginVersion = "1.0.0";

		public static Plugin Instance { get; private set; }

		public static ManualLogSource Log { get; private set; }

		public static PluginConfig Cfg { get; private set; }

		private void Awake()
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Invalid comparison between Unknown and I4
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			Cfg = new PluginConfig(((BaseUnityPlugin)this).Config, (int)Application.systemLanguage == 28);
			Log.LogInfo((object)$"Idioma dos textos do mod: {Cfg.Lang} (opção {Cfg.LanguageOption.Value}, Windows em {Application.systemLanguage})");
			PatchEachClass(new Harmony("Bitencola.MuseumTracker"));
			Log.LogInfo((object)"Bitencola Museum Tracker 1.0.0 carregado");
		}

		private static void PatchEachClass(Harmony harmony)
		{
			Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(typeof(Plugin).Assembly);
			foreach (Type type in typesFromAssembly)
			{
				try
				{
					harmony.CreateClassProcessor(type).Patch();
				}
				catch (Exception arg)
				{
					Log.LogError((object)$"Falha ao aplicar os patches de {type.Name}: {arg}");
				}
			}
		}
	}
	public class PluginConfig
	{
		public ConfigEntry<LanguageSetting> LanguageOption { get; private set; }

		public Language Lang { get; private set; }

		public ConfigEntry<bool> CountVariants { get; private set; }

		public ConfigEntry<bool> IncludeModdedItems { get; private set; }

		public ConfigEntry<string> ExcludedItems { get; private set; }

		public ConfigEntry<bool> ShowBoard { get; private set; }

		public ConfigEntry<BoardMode> BoardModeOption { get; private set; }

		public ConfigEntry<Key> ToggleKey { get; private set; }

		public ConfigEntry<Key> PageKey { get; private set; }

		public ConfigEntry<int> BoardScalePercent { get; private set; }

		public ConfigEntry<bool> HeldItemHint { get; private set; }

		public ConfigEntry<bool> CompletionBanner { get; private set; }

		public ConfigEntry<bool> WriteObsFiles { get; private set; }

		public PluginConfig(ConfigFile config, bool systemIsPortuguese)
		{
			bool saveOnConfigSet = config.SaveOnConfigSet;
			config.SaveOnConfigSet = false;
			try
			{
				BindAll(config, systemIsPortuguese);
				SaveSafely(config);
			}
			finally
			{
				config.SaveOnConfigSet = saveOnConfigSet;
			}
		}

		private void BindAll(ConfigFile config, bool systemIsPortuguese)
		{
			LanguageOption = Add(config, ConfigKeys.Language, LanguageSetting.Auto, "Language of the mod's texts in the game and of these descriptions. Auto follows the computer's language: Portuguese for Portuguese, English for everything else. Takes effect the next time the game starts. / Idioma dos textos do mod no jogo e destas descrições. Auto segue o idioma do computador. Vale ao abrir o jogo de novo.");
			Lang = LanguageChoice.Resolve(LanguageOption.Value, systemIsPortuguese);
			CountVariants = Add(config, ConfigKeys.CountVariants, defaultValue: true, T("Cada variação de aparência (modelo ou textura) de uma sucata conta como uma entrada do museu, como manda a regra do Museum%. Desligada, cada sucata conta uma vez só.", "Each look variant (model or texture) of a scrap item counts as its own museum entry, as the Museum% rule says. Off, each scrap item counts once."));
			IncludeModdedItems = Add(config, ConfigKeys.IncludeModdedItems, defaultValue: false, T("Inclui no quadro a sucata criada por outros mods. Algumas podem ser impossíveis de achar; use a lista de exclusão.", "Includes scrap added by other mods on the board. Some may be impossible to find; use the exclusion list."));
			ExcludedItems = Add(config, ConfigKeys.ExcludedItems, "", T("Nomes de itens que não contam, separados por vírgula e comparados sem diferenciar maiúsculas de minúsculas. Exemplo: Item um, Item dois.", "Names of items that do not count, separated by commas and compared ignoring case. Example: Item one, Item two."));
			ShowBoard = Add(config, ConfigKeys.ShowBoard, defaultValue: true, T("Mostra o quadro ao entrar na partida. Você sempre pode mostrar ou esconder com a tecla do quadro.", "Shows the board when you enter the game. You can always show or hide it with the toggle key."));
			BoardModeOption = Add(config, ConfigKeys.BoardModeKey, BoardMode.Missing, T("O que o quadro lista: Missing mostra só o que falta no museu, All mostra tudo.", "What the board lists: Missing shows only what is still missing from the museum, All shows everything."));
			ToggleKey = Add<Key>(config, ConfigKeys.ToggleKey, (Key)27, T("Tecla que mostra e esconde o quadro. None desliga a tecla.", "Key that shows and hides the board. None turns the key off."));
			PageKey = Add<Key>(config, ConfigKeys.PageKey, (Key)28, T("Tecla que passa a página do quadro. None desliga a tecla.", "Key that turns the board page. None turns the key off."));
			BoardScalePercent = Add(config, ConfigKeys.BoardScale, 100, T("Tamanho do quadro, em porcentagem (50 a 200).", "Size of the board, in percent (50 to 200)."), (AcceptableValueBase?)(object)new AcceptableValueRange<int>(50, 200));
			HeldItemHint = Add(config, ConfigKeys.HeldItemHint, defaultValue: true, T("Avisa quando a sucata na sua mão é nova para o museu.", "Tells you when the scrap in your hand is new to the museum."));
			CompletionBanner = Add(config, ConfigKeys.CompletionBanner, defaultValue: true, T("Mostra \"museu completo\" no meio da tela quando todas as aparências estão na nave.", "Shows \"museum complete\" in the middle of the screen when every look is in the ship."));
			WriteObsFiles = Add(config, ConfigKeys.WriteObsFiles, defaultValue: true, T("Grava arquivos de texto com o progresso do museu, para o OBS mostrar na transmissão.", "Writes text files with the museum progress, for OBS to show on stream."));
		}

		private string T(string portuguese, string english)
		{
			if (Lang != Language.English)
			{
				return portuguese;
			}
			return english;
		}

		private static ConfigEntry<TValue> Add<TValue>(ConfigFile config, ConfigKey key, TValue defaultValue, string description, AcceptableValueBase? acceptable = null)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Expected O, but got Unknown
			//IL_0025: Expected O, but got Unknown
			return config.Bind<TValue>(new ConfigDefinition(key.Section, key.Key), defaultValue, new ConfigDescription(description, acceptable, Array.Empty<object>()));
		}

		private static void SaveSafely(ConfigFile config)
		{
			try
			{
				config.Save();
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao gravar o arquivo de config (as opções valem nesta partida): {arg}");
			}
		}
	}
}
namespace BitencolaMuseumTracker.Features
{
	internal static class BoardKeys
	{
		private static bool _failureLogged;

		public static bool WasPressed(Key key)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: 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_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected I4, but got Unknown
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if ((int)key == 0)
				{
					return false;
				}
				Keyboard current = Keyboard.current;
				if (current == null)
				{
					return false;
				}
				int num = key - 1;
				if (num < 0 || num >= current.allKeys.Count)
				{
					return false;
				}
				KeyControl val = current[key];
				return val != null && ((ButtonControl)val).wasPressedThisFrame;
			}
			catch (Exception arg)
			{
				if (!_failureLogged)
				{
					_failureLogged = true;
					Plugin.Log.LogError((object)$"Falha ao ler a tecla {key}: {arg}");
				}
				return false;
			}
		}
	}
	internal static class CatalogSource
	{
		public static MuseumCatalog Build(StartOfRound round, PluginConfig cfg, out Dictionary<Item, int> keys)
		{
			keys = new Dictionary<Item, int>();
			List<ItemInfo> list = new List<ItemInfo>();
			Dictionary<int, ItemInfo> dictionary = new Dictionary<int, ItemInfo>();
			List<string> list2 = new List<string>();
			List<Item> list3 = (((Object)(object)round.allItemsList != (Object)null) ? round.allItemsList.itemsList : null);
			int num = list3?.Count ?? 0;
			for (int i = 0; i < num; i++)
			{
				Item val = list3[i];
				if (!((Object)(object)val == (Object)null))
				{
					if (keys.ContainsKey(val))
					{
						list2.Add(NameOf(val));
						continue;
					}
					keys.Add(val, i);
					ItemInfo itemInfo = Describe(val, i);
					list.Add(itemInfo);
					dictionary[i] = itemInfo;
				}
			}
			CatalogRules rules = new CatalogRules(cfg.CountVariants.Value, cfg.IncludeModdedItems.Value, cfg.ExcludedItems.Value);
			MuseumCatalog museumCatalog = MuseumCatalog.Build(list, rules);
			LogSummary(museumCatalog, rules, dictionary, num, list2);
			return museumCatalog;
		}

		public static string NameOf(Item item)
		{
			string text = item.itemName;
			if (string.IsNullOrWhiteSpace(text))
			{
				text = ((Object)item).name;
			}
			return (text ?? "").Trim();
		}

		private static ItemInfo Describe(Item item, int key)
		{
			string text = NameOf(item);
			GameObject spawnPrefab = item.spawnPrefab;
			bool flag = (Object)(object)spawnPrefab != (Object)null;
			bool isPlayerBody = text == "Body" || (flag && (Object)(object)spawnPrefab.GetComponent<RagdollGrabbableObject>() != (Object)null);
			int meshVariantCount = ((item.meshVariants != null) ? item.meshVariants.Length : 0);
			int materialVariantCount = ((item.materialVariants != null) ? item.materialVariants.Length : 0);
			return new ItemInfo(key, text, item.isScrap, flag, isPlayerBody, !VanillaItems.IsVanilla(item), meshVariantCount, materialVariantCount);
		}

		private static void LogSummary(MuseumCatalog catalog, CatalogRules rules, Dictionary<int, ItemInfo> byKey, int listCount, List<string> repeated)
		{
			Plugin.Log.LogInfo((object)($"Catálogo do museu: {catalog.Total} entradas de {catalog.Items.Count} itens " + string.Format("({0} itens na lista do jogo; variações contam: {1}; ", listCount, rules.CountVariants ? "sim" : "não") + "itens de mods: " + (rules.IncludeModded ? "incluídos" : "fora") + "; " + $"nomes excluídos na config: {rules.ExcludedNames.Count})"));
			foreach (MuseumItem item in catalog.Items)
			{
				if (byKey.TryGetValue(item.Key, out ItemInfo value))
				{
					int meshVariantCount = value.MeshVariantCount;
					int materialVariantCount = value.MaterialVariantCount;
					if (meshVariantCount > 0 || materialVariantCount > 0)
					{
						Plugin.Log.LogInfo((object)("Variações de " + value.Name + ": " + VariantText(meshVariantCount, materialVariantCount)));
					}
				}
			}
			if (repeated.Count > 0)
			{
				Plugin.Log.LogWarning((object)("Itens repetidos na lista do jogo (contados uma vez só): " + string.Join(", ", repeated)));
			}
		}

		private static string VariantText(int mesh, int material)
		{
			string text = mesh + ((mesh == 1) ? " modelo" : " modelos");
			string text2 = material + ((material == 1) ? " textura" : " texturas");
			if (mesh > 0 && material > 0)
			{
				return text + " e " + text2;
			}
			if (mesh <= 0)
			{
				return text2;
			}
			return text;
		}
	}
	[HarmonyPatch]
	internal static class ItemRegistry
	{
		private static readonly HashSet<GrabbableObject> Objects = new HashSet<GrabbableObject>();

		private static readonly Predicate<GrabbableObject> IsDestroyed = (GrabbableObject obj) => (Object)(object)obj == (Object)null;

		private static StartOfRound? _round;

		public static int Count => Objects.Count;

		[HarmonyPatch(typeof(GrabbableObject), "Start")]
		[HarmonyPostfix]
		private static void AfterStart(GrabbableObject __instance)
		{
			try
			{
				Add(__instance);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao registrar um item: {arg}");
			}
		}

		public static void StartMatch(StartOfRound round)
		{
			UseRound(round);
			Sweep();
		}

		public static void Sweep()
		{
			GrabbableObject[] array = Object.FindObjectsOfType<GrabbableObject>();
			for (int i = 0; i < array.Length; i++)
			{
				Add(array[i]);
			}
		}

		public static void Clear()
		{
			Objects.Clear();
		}

		public static void CopyLiveTo(List<GrabbableObject> buffer)
		{
			Objects.RemoveWhere(IsDestroyed);
			buffer.Clear();
			buffer.AddRange(Objects);
		}

		private static void Add(GrabbableObject obj)
		{
			if (!((Object)(object)obj == (Object)null))
			{
				UseRound(StartOfRound.Instance);
				Objects.Add(obj);
			}
		}

		private static void UseRound(StartOfRound? round)
		{
			if (round != _round)
			{
				Clear();
				_round = round;
			}
		}
	}
	internal static class LookReader
	{
		private static readonly HashSet<string> WarnedItems = new HashSet<string>(StringComparer.Ordinal);

		public static void Read(GrabbableObject obj, out int meshIndex, out int materialIndex)
		{
			meshIndex = -1;
			materialIndex = -1;
			Item itemProperties = obj.itemProperties;
			if (!((Object)(object)itemProperties == (Object)null))
			{
				Mesh[] meshVariants = itemProperties.meshVariants;
				bool flag = meshVariants != null && meshVariants.Length != 0;
				string meshName = null;
				if (flag)
				{
					MeshFilter component = ((Component)obj).GetComponent<MeshFilter>();
					Mesh val = (((Object)(object)component != (Object)null) ? component.sharedMesh : null);
					meshName = (((Object)(object)val != (Object)null) ? ((Object)val).name : null);
					Object[] variants = (Object[])(object)meshVariants;
					meshIndex = IndexOf(variants, (Object?)(object)val);
				}
				Material[] materialVariants = itemProperties.materialVariants;
				bool flag2 = materialVariants != null && materialVariants.Length != 0;
				string materialName = null;
				if (flag2)
				{
					MeshRenderer component2 = ((Component)obj).GetComponent<MeshRenderer>();
					Material val2 = (((Object)(object)component2 != (Object)null) ? ((Renderer)component2).sharedMaterial : null);
					materialName = (((Object)(object)val2 != (Object)null) ? ((Object)val2).name : null);
					Object[] variants = (Object[])(object)materialVariants;
					materialIndex = IndexOf(variants, (Object?)(object)val2);
				}
				if ((flag && meshIndex < 0) || (flag2 && materialIndex < 0))
				{
					WarnOnce(itemProperties, flag, meshName, flag2, materialName);
				}
			}
		}

		private static int IndexOf(Object?[] variants, Object? current)
		{
			if (current == (Object)null)
			{
				return -1;
			}
			for (int i = 0; i < variants.Length; i++)
			{
				if (variants[i] == current)
				{
					return i;
				}
			}
			string[] array = new string[variants.Length];
			for (int j = 0; j < variants.Length; j++)
			{
				Object val = variants[j];
				array[j] = ((val != (Object)null) ? val.name : null);
			}
			return VariantMatcher.IndexByName(array, current.name);
		}

		private static void WarnOnce(Item item, bool hasMeshes, string? meshName, bool hasMaterials, string? materialName)
		{
			string text = CatalogSource.NameOf(item);
			if (WarnedItems.Add(text))
			{
				List<string> list = new List<string>();
				if (hasMeshes)
				{
					string[] obj = new string[5]
					{
						"modelo \"",
						meshName ?? "nenhum",
						"\" (variações: ",
						null,
						null
					};
					Object[] meshVariants = (Object[])(object)item.meshVariants;
					obj[3] = NamesOf(meshVariants);
					obj[4] = ")";
					list.Add(string.Concat(obj));
				}
				if (hasMaterials)
				{
					string[] obj2 = new string[5]
					{
						"textura \"",
						materialName ?? "nenhuma",
						"\" (variações: ",
						null,
						null
					};
					Object[] meshVariants = (Object[])(object)item.materialVariants;
					obj2[3] = NamesOf(meshVariants);
					obj2[4] = ")";
					list.Add(string.Concat(obj2));
				}
				Plugin.Log.LogWarning((object)("Aparência de \"" + text + "\" fora da lista de variações: " + string.Join("; ", list) + ". Esse objeto não conta para nenhuma entrada do museu."));
			}
		}

		private static string NamesOf(Object?[] variants)
		{
			List<string> list = new List<string>();
			foreach (Object val in variants)
			{
				list.Add((val != (Object)null) ? val.name : "nulo");
			}
			return string.Join(", ", list);
		}
	}
	[HarmonyPatch]
	internal static class MuseumTracker
	{
		[HarmonyPatch]
		private static class SetItemInElevatorPatch
		{
			[HarmonyPatch(typeof(PlayerControllerB), "SetItemInElevator", new Type[]
			{
				typeof(bool),
				typeof(bool),
				typeof(GrabbableObject)
			})]
			[HarmonyPostfix]
			private static void AfterSetItemInElevator()
			{
				try
				{
					MarkDirty();
				}
				catch (Exception arg)
				{
					Plugin.Log.LogError((object)$"Falha ao olhar um item entrando ou saindo da nave: {arg}");
				}
			}
		}

		[HarmonyPatch]
		private static class DiscardItemPatch
		{
			[HarmonyPatch(typeof(GrabbableObject), "DiscardItem")]
			[HarmonyPostfix]
			private static void AfterDiscardItem()
			{
				try
				{
					MarkDirty();
				}
				catch (Exception arg)
				{
					Plugin.Log.LogError((object)$"Falha ao olhar um item largado: {arg}");
				}
			}
		}

		[HarmonyPatch]
		private static class EnemyDropPatch
		{
			[HarmonyPatch(typeof(EnemyAI), "SetItemInElevatorNonPlayer", new Type[]
			{
				typeof(bool),
				typeof(bool),
				typeof(GrabbableObject)
			})]
			[HarmonyPostfix]
			private static void AfterSetItemInElevatorNonPlayer()
			{
				try
				{
					MarkDirty();
				}
				catch (Exception arg)
				{
					Plugin.Log.LogError((object)$"Falha ao olhar um item largado por um monstro: {arg}");
				}
			}
		}

		private const float RefreshSeconds = 0.25f;

		private const int MaxErrorsPerMatch = 20;

		private static readonly CompletionTracker Completion = new CompletionTracker();

		private static readonly List<GrabbableObject> Live = new List<GrabbableObject>();

		private static readonly List<ShipObject> Snapshot = new List<ShipObject>();

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

		private static StartOfRound? _round;

		private static Dictionary<Item, int> _keys = new Dictionary<Item, int>();

		private static int _itemCount = -1;

		private static bool _dirty;

		private static float _lastRefresh;

		private static bool _obsWritePending;

		public static MuseumCatalog? Catalog { get; private set; }

		public static MuseumState? State { get; private set; }

		public static string? HeldLabel { get; private set; }

		public static int Version { get; private set; }

		public static event Action? Completed;

		public static void MarkDirty()
		{
			_dirty = true;
		}

		[HarmonyPatch(typeof(HUDManager), "Update")]
		[HarmonyPostfix]
		private static void AfterHudUpdate()
		{
			try
			{
				Tick();
			}
			catch (Exception e)
			{
				ReportOnce("Falha ao atualizar o museu", e);
			}
		}

		private static void Tick()
		{
			StartOfRound instance = StartOfRound.Instance;
			if (!((Object)(object)instance == (Object)null))
			{
				if (instance != _round)
				{
					StartMatch(instance);
				}
				int num = (((Object)(object)instance.allItemsList != (Object)null) ? instance.allItemsList.itemsList : null)?.Count ?? 0;
				if (num != _itemCount)
				{
					RebuildCatalog(instance, num);
				}
				float unscaledTime = Time.unscaledTime;
				if (_dirty || unscaledTime - _lastRefresh >= 0.25f)
				{
					Refresh(instance, unscaledTime);
				}
			}
		}

		private static void StartMatch(StartOfRound round)
		{
			_round = round;
			LoggedErrors.Clear();
			ObsWriter.ResetErrors();
			Completion.Reset();
			Catalog = null;
			State = null;
			HeldLabel = null;
			_keys = new Dictionary<Item, int>();
			_itemCount = -1;
			_dirty = true;
			_obsWritePending = false;
			Version++;
			ItemRegistry.StartMatch(round);
			Plugin.Log.LogInfo((object)$"Partida nova: museu recomeçado, {ItemRegistry.Count} itens na cena");
		}

		private static void RebuildCatalog(StartOfRound round, int count)
		{
			_itemCount = count;
			Catalog = CatalogSource.Build(round, Plugin.Cfg, out Dictionary<Item, int> keys);
			_keys = keys;
			State = null;
			HeldLabel = null;
			_dirty = true;
			Version++;
		}

		private static void Refresh(StartOfRound round, float now)
		{
			_dirty = false;
			_lastRefresh = now;
			MuseumCatalog catalog = Catalog;
			if (catalog == null)
			{
				return;
			}
			ItemRegistry.CopyLiveTo(Live);
			Snapshot.Clear();
			foreach (GrabbableObject item in Live)
			{
				Item itemProperties = item.itemProperties;
				if (!((Object)(object)itemProperties == (Object)null) && _keys.TryGetValue(itemProperties, out var value))
				{
					int meshIndex = -1;
					int materialIndex = -1;
					if (catalog.Contains(value))
					{
						LookReader.Read(item, out meshIndex, out materialIndex);
					}
					Snapshot.Add(new ShipObject(value, meshIndex, materialIndex, ((Component)item).gameObject.activeInHierarchy, item.isInShipRoom, item.isHeld || (Object)(object)item.playerHeldBy != (Object)null, item.isHeldByEnemy));
				}
			}
			Live.Clear();
			MuseumState museumState = MuseumState.Compute(catalog, Snapshot);
			string text = HeldLabelFor(catalog, museumState);
			bool flag = !museumState.SameAs(State);
			bool flag2 = !string.Equals(text, HeldLabel, StringComparison.Ordinal);
			if (flag || flag2)
			{
				if (flag)
				{
					State = museumState;
				}
				HeldLabel = text;
				Version++;
			}
			if ((flag || _obsWritePending) && Plugin.Cfg.WriteObsFiles.Value)
			{
				_obsWritePending = !ObsWriter.Write(catalog, museumState, Plugin.Cfg.Lang);
			}
			if (flag && Completion.Observe(museumState.CollectedCount, museumState.Total))
			{
				AnnounceComplete(round, museumState);
			}
		}

		private static string? HeldLabelFor(MuseumCatalog catalog, MuseumState state)
		{
			if (!Plugin.Cfg.HeldItemHint.Value)
			{
				return null;
			}
			GameNetworkManager instance = GameNetworkManager.Instance;
			PlayerControllerB val = (((Object)(object)instance != (Object)null) ? instance.localPlayerController : null);
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			GrabbableObject currentlyHeldObjectServer = val.currentlyHeldObjectServer;
			if ((Object)(object)currentlyHeldObjectServer == (Object)null)
			{
				return null;
			}
			Item itemProperties = currentlyHeldObjectServer.itemProperties;
			if ((Object)(object)itemProperties == (Object)null || !itemProperties.isScrap || !_keys.TryGetValue(itemProperties, out var value) || !catalog.Contains(value))
			{
				return null;
			}
			LookReader.Read(currentlyHeldObjectServer, out var meshIndex, out var materialIndex);
			return HeldHint.LabelFor(catalog, state, value, meshIndex, materialIndex);
		}

		private static void AnnounceComplete(StartOfRound round, MuseumState state)
		{
			int num = ((round.gameStats != null) ? (round.gameStats.daysSpent + 1) : 0);
			Plugin.Log.LogInfo((object)$"Museu completo: {state.CollectedCount}/{state.Total}, dia {num}, {DateTime.Now:HH:mm:ss}");
			try
			{
				MuseumTracker.Completed?.Invoke();
			}
			catch (Exception e)
			{
				ReportOnce("Falha ao avisar que o museu ficou completo", e);
			}
		}

		private static void ReportOnce(string what, Exception e)
		{
			string text = what + ": " + e;
			if (LoggedErrors.Count < 20 && LoggedErrors.Add(text))
			{
				Plugin.Log.LogError((object)text);
			}
		}
	}
	internal static class ObsWriter
	{
		private static bool _errorLogged;

		public static string Folder => Path.Combine(Paths.BepInExRootPath, "MuseumTracker");

		public static bool Write(MuseumCatalog c, MuseumState s, Language lang)
		{
			string text = "";
			try
			{
				text = Folder;
				ObsFileSet obsFileSet = ObsFiles.Build(c, s, lang);
				return WriteOne(text, "museum-count.txt", obsFileSet.Count) & WriteOne(text, "museum-missing.txt", obsFileSet.Missing) & WriteOne(text, "museum-all.txt", obsFileSet.All);
			}
			catch (Exception e)
			{
				ReportOnce(text, e);
				return false;
			}
		}

		public static void ResetErrors()
		{
			_errorLogged = false;
		}

		private static bool WriteOne(string folder, string fileName, string contents)
		{
			try
			{
				AtomicFile.WriteAllText(Path.Combine(folder, fileName), contents);
				return true;
			}
			catch (Exception e)
			{
				ReportOnce(Path.Combine(folder, fileName), e);
				return false;
			}
		}

		private static void ReportOnce(string path, Exception e)
		{
			if (!_errorLogged)
			{
				_errorLogged = true;
				Plugin.Log.LogError((object)$"Falha ao gravar os arquivos para o OBS ({path}); o quadro continua funcionando e a gravação é tentada de novo: {e}");
			}
		}
	}
	[HarmonyPatch]
	internal static class VanillaItems
	{
		private static HashSet<string>? _names;

		private static bool _captureTried;

		private static bool _warned;

		[HarmonyPatch(typeof(StartOfRound), "Awake")]
		[HarmonyPrefix]
		[HarmonyPriority(800)]
		private static void BeforeRoundAwake(StartOfRound __instance)
		{
			try
			{
				if (!_captureTried)
				{
					_captureTried = true;
					Capture(__instance);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao guardar a lista de itens do jogo: {arg}");
			}
		}

		private static void Capture(StartOfRound round)
		{
			List<Item> list = (((Object)(object)round.allItemsList != (Object)null) ? round.allItemsList.itemsList : null);
			if (list == null)
			{
				Plugin.Log.LogWarning((object)"A lista de itens do jogo não estava disponível no começo; todos os itens vão contar como do jogo");
				return;
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (Item item in list)
			{
				if ((Object)(object)item != (Object)null)
				{
					hashSet.Add(((Object)item).name);
				}
			}
			_names = hashSet;
			Plugin.Log.LogInfo((object)$"Itens do jogo guardados: {hashSet.Count} (o que outros mods acrescentarem depois conta como item de mod)");
		}

		public static bool IsVanilla(Item item)
		{
			HashSet<string> names = _names;
			if (names == null || names.Count == 0)
			{
				if (!_warned)
				{
					_warned = true;
					Plugin.Log.LogWarning((object)"Não deu para saber quais itens são do jogo; todos os itens vão contar como do jogo");
				}
				return true;
			}
			return names.Contains(((Object)item).name);
		}
	}
}
namespace BitencolaMuseumTracker.Features.Hud
{
	[HarmonyPatch]
	internal static class BannerOverlay
	{
		private const string BannerName = "BitencolaMuseumBanner";

		private const float ShowSeconds = 8f;

		private const int MaxErrorsPerMatch = 20;

		private const float OffsetFraction = 0.2f;

		private const float WidthFraction = 0.4f;

		private const float HeightFraction = 0.2f;

		private const float FontFraction = 0.07f;

		private const float MinFontFraction = 0.03f;

		private static readonly Color BannerColor = new Color(1f, 0.82f, 0.29f, 1f);

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

		private static bool _subscribed;

		private static StartOfRound? _seenRound;

		private static TextMeshProUGUI? _banner;

		private static StartOfRound? _bannerRound;

		private static string? _message;

		private static float _hideAt;

		private static bool _laidOut;

		private static Vector2 _layoutSize;

		[HarmonyPatch(typeof(HUDManager), "Update")]
		[HarmonyPostfix]
		private static void AfterHudUpdate(HUDManager __instance)
		{
			try
			{
				if (!_subscribed)
				{
					_subscribed = true;
					MuseumTracker.Completed += OnCompleted;
				}
				Tick(__instance);
			}
			catch (Exception e)
			{
				ReportOnce("Falha ao mostrar o aviso de museu completo", e);
			}
		}

		private static void OnCompleted()
		{
			try
			{
				if (Plugin.Cfg.CompletionBanner.Value)
				{
					MuseumState state = MuseumTracker.State;
					if (state != null)
					{
						_bannerRound = StartOfRound.Instance;
						_message = Texts.BannerTitle(Plugin.Cfg.Lang) + "\n" + state.CollectedCount + "/" + state.Total;
						_hideAt = Time.unscaledTime + 8f;
					}
				}
			}
			catch (Exception e)
			{
				ReportOnce("Falha ao preparar o aviso de museu completo", e);
			}
		}

		private static void Tick(HUDManager hud)
		{
			StartOfRound instance = StartOfRound.Instance;
			if (instance != _seenRound)
			{
				_seenRound = instance;
				LoggedErrors.Clear();
			}
			if (_message != null && ((Object)(object)instance == (Object)null || instance != _bannerRound || Time.unscaledTime >= _hideAt))
			{
				_message = null;
			}
			if (_message == null || !Plugin.Cfg.CompletionBanner.Value)
			{
				Hide();
				return;
			}
			TextMeshProUGUI val = (((Object)(object)_banner != (Object)null) ? _banner : CreateBanner(hud));
			if (!((Object)(object)val == (Object)null))
			{
				Layout(val);
				if (!string.Equals(((TMP_Text)val).text, _message, StringComparison.Ordinal))
				{
					((TMP_Text)val).text = _message;
				}
				if (!((Component)val).gameObject.activeSelf)
				{
					PlaceAboveBoard(val);
					((Component)val).gameObject.SetActive(true);
				}
			}
		}

		private static void Hide()
		{
			if ((Object)(object)_banner != (Object)null && ((Component)_banner).gameObject.activeSelf)
			{
				((Component)_banner).gameObject.SetActive(false);
			}
		}

		private static TextMeshProUGUI? CreateBanner(HUDManager hud)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			TextMeshProUGUI val = BoardOverlay.FindOrCreateText(hud, "BitencolaMuseumBanner");
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			((TMP_Text)val).alignment = (TextAlignmentOptions)514;
			((Graphic)val).color = BannerColor;
			_banner = val;
			_laidOut = false;
			PlaceAboveBoard(val);
			return val;
		}

		private static void PlaceAboveBoard(TextMeshProUGUI banner)
		{
			TextMeshProUGUI board = BoardOverlay.Board;
			if ((Object)(object)board == (Object)null)
			{
				return;
			}
			Transform transform = ((TMP_Text)banner).transform;
			Transform transform2 = ((TMP_Text)board).transform;
			if (!((Object)(object)transform.parent != (Object)(object)transform2.parent))
			{
				int siblingIndex = transform2.GetSiblingIndex();
				int siblingIndex2 = transform.GetSiblingIndex();
				if (siblingIndex2 != siblingIndex + 1)
				{
					transform.SetSiblingIndex((siblingIndex2 < siblingIndex) ? siblingIndex : (siblingIndex + 1));
				}
			}
		}

		private static void Layout(TextMeshProUGUI banner)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: 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_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: 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_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			Transform parent = ((Transform)((TMP_Text)banner).rectTransform).parent;
			RectTransform val = (RectTransform)(object)((parent is RectTransform) ? parent : null);
			if (!((Object)(object)val == (Object)null))
			{
				Rect rect = val.rect;
				Vector2 size = ((Rect)(ref rect)).size;
				if (!_laidOut || !(size == _layoutSize))
				{
					_laidOut = true;
					_layoutSize = size;
					RectTransform rectTransform = ((TMP_Text)banner).rectTransform;
					Vector2 val2 = default(Vector2);
					((Vector2)(ref val2))..ctor(0.5f, 0.5f);
					rectTransform.anchorMin = val2;
					rectTransform.anchorMax = val2;
					rectTransform.pivot = val2;
					rectTransform.anchoredPosition = new Vector2(0f, size.y * 0.2f);
					((Transform)rectTransform).localScale = Vector3.one;
					rectTransform.sizeDelta = new Vector2(Mathf.Max(size.x * 0.4f, 1f), Mathf.Max(size.y * 0.2f, 1f));
					((TMP_Text)banner).fontSizeMin = size.y * 0.03f;
					((TMP_Text)banner).fontSizeMax = size.y * 0.07f;
					((TMP_Text)banner).fontSize = ((TMP_Text)banner).fontSizeMax;
				}
			}
		}

		private static void ReportOnce(string what, Exception e)
		{
			string text = what + ": " + e;
			if (LoggedErrors.Count < 20 && LoggedErrors.Add(text))
			{
				Plugin.Log.LogError((object)text);
			}
		}
	}
	[HarmonyPatch]
	internal static class BoardOverlay
	{
		private const string BoardName = "BitencolaMuseumBoard";

		private const int MaxErrorsPerMatch = 20;

		private const float MarginFraction = 0.0125f;

		private const float FontFraction = 0.022f;

		private const float MinFontFraction = 0.012f;

		private const float BoxHeightFraction = 0.62f;

		private const string HeaderColor = "#FFD24A";

		private const string HintColor = "#5CFF5C";

		private static readonly StringBuilder Builder = new StringBuilder();

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

		private static readonly Dictionary<string, (Material Source, Material Copy)> OwnMaterials = new Dictionary<string, (Material, Material)>(StringComparer.Ordinal);

		private static StartOfRound? _round;

		private static TextMeshProUGUI? _board;

		private static bool _visible;

		private static int _page;

		private static bool _built;

		private static int _builtVersion;

		private static int _builtPage;

		private static BoardMode _builtMode;

		private static bool _laidOut;

		private static Vector2 _layoutSize;

		private static float _layoutScale;

		internal static TextMeshProUGUI? Board => _board;

		[HarmonyPatch(typeof(HUDManager), "Update")]
		[HarmonyPostfix]
		private static void AfterHudUpdate(HUDManager __instance)
		{
			try
			{
				Tick(__instance);
			}
			catch (Exception e)
			{
				ReportOnce("Falha ao mostrar o quadro do museu", e);
			}
		}

		private static void Tick(HUDManager hud)
		{
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				Hide();
				return;
			}
			if (instance != _round)
			{
				StartMatch(instance);
			}
			GameNetworkManager instance2 = GameNetworkManager.Instance;
			PlayerControllerB val = (((Object)(object)instance2 != (Object)null) ? instance2.localPlayerController : null);
			if ((Object)(object)val == (Object)null)
			{
				Hide();
				return;
			}
			bool menuOpen = val.inSpecialMenu || ((Object)(object)val.quickMenuManager != (Object)null && val.quickMenuManager.isMenuOpen);
			if (BoardRules.KeysEnabled(val.isTypingChat, val.inTerminalMenu, menuOpen))
			{
				ReadKeys();
			}
			MuseumCatalog catalog = MuseumTracker.Catalog;
			MuseumState state = MuseumTracker.State;
			if (catalog == null || state == null || !BoardRules.ShouldShowBoard(_visible, val.inTerminalMenu, menuOpen))
			{
				Hide();
				return;
			}
			TextMeshProUGUI val2 = (((Object)(object)_board != (Object)null) ? _board : CreateBoard(hud));
			if (!((Object)(object)val2 == (Object)null))
			{
				Layout(val2);
				BoardMode value = Plugin.Cfg.BoardModeOption.Value;
				if (!_built || _builtVersion != MuseumTracker.Version || _builtPage != _page || _builtMode != value)
				{
					Rebuild(val2, catalog, state, value);
				}
				if (!((Component)val2).gameObject.activeSelf)
				{
					((Component)val2).gameObject.SetActive(true);
				}
			}
		}

		private static void StartMatch(StartOfRound round)
		{
			_round = round;
			LoggedErrors.Clear();
			_visible = Plugin.Cfg.ShowBoard.Value;
			_page = 0;
			_built = false;
		}

		private static void ReadKeys()
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			PluginConfig cfg = Plugin.Cfg;
			if (BoardKeys.WasPressed(cfg.ToggleKey.Value))
			{
				_visible = !_visible;
				_built = false;
			}
			if (BoardKeys.WasPressed(cfg.PageKey.Value))
			{
				_page++;
			}
		}

		private static void Hide()
		{
			if ((Object)(object)_board != (Object)null && ((Component)_board).gameObject.activeSelf)
			{
				((Component)_board).gameObject.SetActive(false);
			}
		}

		private static TextMeshProUGUI? CreateBoard(HUDManager hud)
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			TextMeshProUGUI val = FindOrCreateText(hud, "BitencolaMuseumBoard");
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			((TMP_Text)val).alignment = (TextAlignmentOptions)513;
			_board = val;
			_built = false;
			_laidOut = false;
			Transform parent = ((Transform)((TMP_Text)val).rectTransform).parent;
			RectTransform val2 = (RectTransform)(object)((parent is RectTransform) ? parent : null);
			if ((Object)(object)val2 != (Object)null)
			{
				Rect rect = val2.rect;
				Vector2 size = ((Rect)(ref rect)).size;
				Plugin.Log.LogInfo((object)($"Quadro do museu no HUD: dentro de {((Object)val2).name} ({size.x:0}x{size.y:0}), " + "fonte " + (((Object)(object)((TMP_Text)val).font != (Object)null) ? ((Object)((TMP_Text)val).font).name : "nenhuma")));
			}
			return val;
		}

		internal static TextMeshProUGUI? FindOrCreateText(HUDManager hud, string name)
		{
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			GameObject hUDContainer = hud.HUDContainer;
			TextMeshProUGUI weightCounter = hud.weightCounter;
			if ((Object)(object)hUDContainer == (Object)null || (Object)(object)weightCounter == (Object)null || (Object)(object)((TMP_Text)weightCounter).font == (Object)null)
			{
				return null;
			}
			Transform parent = hUDContainer.transform.parent;
			RectTransform val = (RectTransform)(object)((parent is RectTransform) ? parent : null);
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			Transform val2 = ((Transform)val).Find(name);
			if ((Object)(object)val2 != (Object)null)
			{
				TextMeshProUGUI component = ((Component)val2).GetComponent<TextMeshProUGUI>();
				if ((Object)(object)component != (Object)null)
				{
					UseOwnMaterial(component, weightCounter, name);
					return component;
				}
			}
			GameObject val3 = new GameObject(name, new Type[1] { typeof(RectTransform) })
			{
				layer = hUDContainer.layer
			};
			RectTransform val4 = (RectTransform)val3.transform;
			((Transform)val4).SetParent((Transform)(object)val, false);
			((Transform)val4).SetSiblingIndex(hUDContainer.transform.GetSiblingIndex() + 1);
			TextMeshProUGUI obj = val3.AddComponent<TextMeshProUGUI>();
			((TMP_Text)obj).font = ((TMP_Text)weightCounter).font;
			UseOwnMaterial(obj, weightCounter, name);
			((TMP_Text)obj).richText = true;
			((TMP_Text)obj).enableWordWrapping = false;
			((TMP_Text)obj).overflowMode = (TextOverflowModes)0;
			((Graphic)obj).raycastTarget = false;
			((TMP_Text)obj).enableAutoSizing = true;
			return obj;
		}

		private static void UseOwnMaterial(TextMeshProUGUI text, TextMeshProUGUI template, string name)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Expected O, but got Unknown
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			int iD_FaceColor = ShaderUtilities.ID_FaceColor;
			Material fontSharedMaterial = ((TMP_Text)template).fontSharedMaterial;
			Color val = (((Object)(object)fontSharedMaterial != (Object)null && fontSharedMaterial.HasProperty(iD_FaceColor)) ? fontSharedMaterial.GetColor(iD_FaceColor) : Color.white);
			Color color = ((Graphic)template).color * val;
			color.a = 1f;
			((Graphic)text).color = color;
			if ((Object)(object)fontSharedMaterial == (Object)null)
			{
				return;
			}
			if (!OwnMaterials.TryGetValue(name, out (Material, Material) value) || (Object)(object)value.Item2 == (Object)null || (Object)(object)value.Item1 != (Object)(object)fontSharedMaterial)
			{
				if ((Object)(object)value.Item2 != (Object)null)
				{
					Object.Destroy((Object)(object)value.Item2);
				}
				Material val2 = new Material(fontSharedMaterial)
				{
					name = ((Object)fontSharedMaterial).name + " (" + name + ")",
					hideFlags = (HideFlags)32
				};
				if (val2.HasProperty(iD_FaceColor))
				{
					val2.SetColor(iD_FaceColor, Color.white);
				}
				value = (fontSharedMaterial, val2);
				OwnMaterials[name] = value;
			}
			((TMP_Text)text).fontSharedMaterial = value.Item2;
		}

		private static void Layout(TextMeshProUGUI board)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_0164: 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_0056: Unknown result type (might be due to invalid IL or missing references)
			Transform parent = ((Transform)((TMP_Text)board).rectTransform).parent;
			RectTransform val = (RectTransform)(object)((parent is RectTransform) ? parent : null);
			if (!((Object)(object)val == (Object)null))
			{
				Rect rect = val.rect;
				Vector2 size = ((Rect)(ref rect)).size;
				float num = (float)Mathf.Clamp(Plugin.Cfg.BoardScalePercent.Value, 50, 200) / 100f;
				if (!_laidOut || !(size == _layoutSize) || !Mathf.Approximately(num, _layoutScale))
				{
					_laidOut = true;
					_layoutSize = size;
					_layoutScale = num;
					float num2 = size.x * 0.0125f;
					RectTransform rectTransform = ((TMP_Text)board).rectTransform;
					Vector2 val2 = default(Vector2);
					((Vector2)(ref val2))..ctor(0f, 0.5f);
					rectTransform.anchorMin = val2;
					rectTransform.anchorMax = val2;
					rectTransform.pivot = val2;
					rectTransform.anchoredPosition = new Vector2(num2, 0f);
					((Transform)rectTransform).localScale = new Vector3(num, num, 1f);
					float num3 = Mathf.Min(size.x / 3f - num2, (size.x / 2f - 2f * num2) / num);
					float num4 = Mathf.Min(size.y * 0.62f, (size.y - 2f * num2) / num);
					rectTransform.sizeDelta = new Vector2(Mathf.Max(num3, 1f), Mathf.Max(num4, 1f));
					((TMP_Text)board).fontSizeMin = size.y * 0.012f;
					((TMP_Text)board).fontSizeMax = size.y * 0.022f;
					((TMP_Text)board).fontSize = ((TMP_Text)board).fontSizeMax;
				}
			}
		}

		private static void Rebuild(TextMeshProUGUI board, MuseumCatalog catalog, MuseumState state, BoardMode mode)
		{
			_built = true;
			_builtVersion = MuseumTracker.Version;
			_builtPage = _page;
			_builtMode = mode;
			BoardPage boardPage = BoardText.BuildPage(catalog, state, mode, Plugin.Cfg.Lang, _page, MuseumTracker.HeldLabel);
			_page = boardPage.PageIndex;
			_builtPage = _page;
			Builder.Clear();
			Builder.Append("<color=").Append("#FFD24A").Append('>')
				.Append(boardPage.Header)
				.Append("</color>");
			if (boardPage.Hint != null)
			{
				Builder.Append('\n').Append("<color=").Append("#5CFF5C")
					.Append('>')
					.Append(Plain(boardPage.Hint))
					.Append("</color>");
			}
			foreach (string line in boardPage.Lines)
			{
				Builder.Append('\n').Append(Plain(line));
			}
			if (boardPage.Footer != null)
			{
				Builder.Append('\n').Append(boardPage.Footer);
			}
			((TMP_Text)board).text = Builder.ToString();
		}

		private static string Plain(string text)
		{
			if (text.IndexOf('<') >= 0)
			{
				return text.Replace("<", "<noparse><</noparse>");
			}
			return text;
		}

		private static void ReportOnce(string what, Exception e)
		{
			string text = what + ": " + e;
			if (LoggedErrors.Count < 20 && LoggedErrors.Add(text))
			{
				Plugin.Log.LogError((object)text);
			}
		}
	}
}
namespace BitencolaMuseumTracker.Core
{
	public static class AtomicFile
	{
		public static void WriteAllText(string path, string contents)
		{
			string directoryName = Path.GetDirectoryName(path);
			if (!string.IsNullOrEmpty(directoryName))
			{
				Directory.CreateDirectory(directoryName);
			}
			string text = path + ".tmp";
			File.WriteAllText(text, contents, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			if (File.Exists(path))
			{
				File.Replace(text, path, null);
			}
			else
			{
				File.Move(text, path);
			}
		}
	}
	public enum BoardMode
	{
		Missing,
		All
	}
	public sealed class BoardPage
	{
		public string Header { get; }

		public string? Hint { get; }

		public IReadOnlyList<string> Lines { get; }

		public string? Footer { get; }

		public int PageIndex { get; }

		public int PageCount { get; }

		public BoardPage(string header, string? hint, IReadOnlyList<string> lines, string? footer, int pageIndex, int pageCount)
		{
			Header = header;
			Hint = hint;
			Lines = lines;
			Footer = footer;
			PageIndex = pageIndex;
			PageCount = pageCount;
		}
	}
	public static class BoardRules
	{
		public static bool ShouldShowBoard(bool visible, bool inTerminal, bool menuOpen)
		{
			if (visible && !inTerminal)
			{
				return !menuOpen;
			}
			return false;
		}

		public static bool KeysEnabled(bool typingChat, bool inTerminal, bool menuOpen)
		{
			if (!typingChat && !inTerminal)
			{
				return !menuOpen;
			}
			return false;
		}
	}
	public static class BoardText
	{
		public const int PageSize = 15;

		public static IReadOnlyList<string> MissingLines(MuseumCatalog c, MuseumState s)
		{
			List<string> list = new List<string>();
			foreach (MuseumItem item in c.Items)
			{
				int count = item.EntryIndexes.Count;
				int num = s.CollectedIn(item);
				if (count == 1)
				{
					if (num == 0)
					{
						list.Add(item.Name);
					}
				}
				else if (num < count)
				{
					list.Add(item.Name + " " + num + "/" + count);
				}
			}
			return list;
		}

		public static IReadOnlyList<string> AllLines(MuseumCatalog c, MuseumState s)
		{
			List<string> list = new List<string>();
			for (int i = 0; i < c.Entries.Count; i++)
			{
				list.Add((s.IsCollected(i) ? "[x] " : "[ ] ") + c.Entries[i].Label);
			}
			return list;
		}

		public static BoardPage BuildPage(MuseumCatalog c, MuseumState s, BoardMode mode, Language lang, int pageIndex, string? heldLabel)
		{
			IReadOnlyList<string> readOnlyList;
			if (mode == BoardMode.All)
			{
				readOnlyList = AllLines(c, s);
			}
			else
			{
				IReadOnlyList<string> readOnlyList2 = MissingLines(c, s);
				IReadOnlyList<string> readOnlyList3;
				if (readOnlyList2.Count != 0)
				{
					readOnlyList3 = readOnlyList2;
				}
				else
				{
					IReadOnlyList<string> readOnlyList4 = new string[1] { Texts.NothingMissing(lang) };
					readOnlyList3 = readOnlyList4;
				}
				readOnlyList = readOnlyList3;
			}
			int num = Math.Max(1, (readOnlyList.Count + 15 - 1) / 15);
			int num2 = (pageIndex % num + num) % num;
			int num3 = num2 * 15;
			int num4 = Math.Max(0, Math.Min(15, readOnlyList.Count - num3));
			List<string> list = new List<string>(num4);
			for (int i = 0; i < num4; i++)
			{
				list.Add(readOnlyList[num3 + i]);
			}
			return new BoardPage(Texts.Header(lang, s.CollectedCount, s.Total), (heldLabel == null) ? null : Texts.HeldHint(lang, heldLabel), list, (num > 1) ? Texts.Page(lang, num2 + 1, num) : null, num2, num);
		}
	}
	public sealed class CatalogRules
	{
		private readonly HashSet<string> _excluded;

		public bool CountVariants { get; }

		public bool IncludeModded { get; }

		public IReadOnlyCollection<string> ExcludedNames => _excluded;

		public CatalogRules(bool countVariants, bool includeModded, string excludedItems)
		{
			CountVariants = countVariants;
			IncludeModded = includeModded;
			_excluded = new HashSet<string>(ParseNames(excludedItems), StringComparer.OrdinalIgnoreCase);
		}

		public bool IsExcluded(string name)
		{
			return _excluded.Contains(name);
		}

		public static IReadOnlyCollection<string> ParseNames(string? csv)
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			if (string.IsNullOrEmpty(csv))
			{
				return hashSet;
			}
			string[] array = csv.Split(',');
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length > 0)
				{
					hashSet.Add(text);
				}
			}
			return hashSet;
		}
	}
	public sealed class CompletionTracker
	{
		private bool _seen;

		private bool _wasComplete;

		public void Reset()
		{
			_seen = false;
			_wasComplete = false;
		}

		public bool Observe(int collected, int total)
		{
			bool flag = total > 0 && collected >= total;
			bool result = _seen && !_wasComplete && flag;
			_seen = true;
			_wasComplete = flag;
			return result;
		}
	}
	public sealed class ConfigKey : IEquatable<ConfigKey>
	{
		public string Section { get; }

		public string Key { get; }

		public ConfigKey(string section, string key)
		{
			Section = section;
			Key = key;
		}

		public bool Equals(ConfigKey? other)
		{
			if (other != null && string.Equals(Section, other.Section, StringComparison.Ordinal))
			{
				return string.Equals(Key, other.Key, StringComparison.Ordinal);
			}
			return false;
		}

		public override bool Equals(object? obj)
		{
			return Equals(obj as ConfigKey);
		}

		public override int GetHashCode()
		{
			return StringComparer.Ordinal.GetHashCode(Section) * 31 + StringComparer.Ordinal.GetHashCode(Key);
		}

		public override string ToString()
		{
			return "[" + Section + "] " + Key;
		}
	}
	public static class ConfigKeys
	{
		public const string General = "0. General";

		public const string Museum = "1. Museum";

		public const string Board = "2. Board";

		public const string Files = "3. Files";

		public static readonly ConfigKey Language = new ConfigKey("0. General", "Language");

		public static readonly ConfigKey CountVariants = new ConfigKey("1. Museum", "Count variants");

		public static readonly ConfigKey IncludeModdedItems = new ConfigKey("1. Museum", "Include modded items");

		public static readonly ConfigKey ExcludedItems = new ConfigKey("1. Museum", "Excluded items");

		public static readonly ConfigKey ShowBoard = new ConfigKey("2. Board", "Show board");

		public static readonly ConfigKey BoardModeKey = new ConfigKey("2. Board", "Board mode");

		public static readonly ConfigKey ToggleKey = new ConfigKey("2. Board", "Toggle key");

		public static readonly ConfigKey PageKey = new ConfigKey("2. Board", "Page key");

		public static readonly ConfigKey BoardScale = new ConfigKey("2. Board", "Board scale (%)");

		public static readonly ConfigKey HeldItemHint = new ConfigKey("2. Board", "Held item hint");

		public static readonly ConfigKey CompletionBanner = new ConfigKey("2. Board", "Completion banner");

		public static readonly ConfigKey WriteObsFiles = new ConfigKey("3. Files", "Write OBS files");

		public static IReadOnlyList<string> Sections { get; } = new string[4] { "0. General", "1. Museum", "2. Board", "3. Files" };

		public static IReadOnlyList<ConfigKey> All { get; } = new ConfigKey[12]
		{
			Language, CountVariants, IncludeModdedItems, ExcludedItems, ShowBoard, BoardModeKey, ToggleKey, PageKey, BoardScale, HeldItemHint,
			CompletionBanner, WriteObsFiles
		};
	}
	public static class HeldHint
	{
		public static string? LabelFor(MuseumCatalog catalog, MuseumState state, int itemKey, int meshIndex, int materialIndex)
		{
			int num = catalog.FindEntry(itemKey, meshIndex, materialIndex);
			if (num < 0 || state.IsCollected(num))
			{
				return null;
			}
			return catalog.Entries[num].Label;
		}
	}
	public sealed class ItemInfo
	{
		public int Key { get; }

		public string Name { get; }

		public bool IsScrap { get; }

		public bool HasPrefab { get; }

		public bool IsPlayerBody { get; }

		public bool IsModded { get; }

		public int MeshVariantCount { get; }

		public int MaterialVariantCount { get; }

		public ItemInfo(int key, string name, bool isScrap, bool hasPrefab, bool isPlayerBody, bool isModded, int meshVariantCount, int materialVariantCount)
		{
			Key = key;
			Name = name;
			IsScrap = isScrap;
			HasPrefab = hasPrefab;
			IsPlayerBody = isPlayerBody;
			IsModded = isModded;
			MeshVariantCount = meshVariantCount;
			MaterialVariantCount = materialVariantCount;
		}
	}
	public enum Language
	{
		Portuguese,
		English
	}
	public enum LanguageSetting
	{
		Auto,
		English,
		Portugues
	}
	public static class LanguageChoice
	{
		public static Language Resolve(LanguageSetting setting, bool systemIsPortuguese)
		{
			switch (setting)
			{
			case LanguageSetting.English:
				return Language.English;
			case LanguageSetting.Portugues:
				return Language.Portuguese;
			default:
				if (!systemIsPortuguese)
				{
					return Language.English;
				}
				return Language.Portuguese;
			}
		}
	}
	public sealed class MuseumCatalog
	{
		private readonly List<MuseumEntry> _entries;

		private readonly List<MuseumItem> _items;

		private readonly Dictionary<int, MuseumItem> _byKey;

		private readonly bool _countVariants;

		public IReadOnlyList<MuseumEntry> Entries => _entries;

		public IReadOnlyList<MuseumItem> Items => _items;

		public int Total => _entries.Count;

		private MuseumCatalog(List<MuseumEntry> entries, List<MuseumItem> items, bool countVariants)
		{
			_entries = entries;
			_items = items;
			_countVariants = countVariants;
			_byKey = items.ToDictionary((MuseumItem i) => i.Key);
		}

		public static MuseumCatalog Build(IEnumerable<ItemInfo> items, CatalogRules rules)
		{
			List<ItemInfo> list = items.Where((ItemInfo i) => i.IsScrap && i.HasPrefab && !i.IsPlayerBody && !rules.IsExcluded(i.Name) && (rules.IncludeModded || !i.IsModded)).OrderBy<ItemInfo, string>((ItemInfo i) => i.Name, StringComparer.OrdinalIgnoreCase).ThenBy((ItemInfo i) => i.Key)
				.ToList();
			List<MuseumEntry> list2 = new List<MuseumEntry>();
			List<MuseumItem> list3 = new List<MuseumItem>();
			foreach (ItemInfo item in list)
			{
				List<int> list4 = new List<int>();
				int meshVariantCount = item.MeshVariantCount;
				int materialVariantCount = item.MaterialVariantCount;
				if (!rules.CountVariants || (meshVariantCount <= 0 && materialVariantCount <= 0))
				{
					list4.Add(list2.Count);
					list2.Add(new MuseumEntry(item.Key, item.Name, -1, -1, item.Name));
				}
				else
				{
					for (int num = 0; num < Math.Max(1, meshVariantCount); num++)
					{
						for (int num2 = 0; num2 < Math.Max(1, materialVariantCount); num2++)
						{
							string label = ((meshVariantCount > 0 && materialVariantCount > 0) ? (item.Name + " " + (num + 1) + "-" + (num2 + 1)) : (item.Name + " " + (((meshVariantCount > 0) ? num : num2) + 1)));
							list4.Add(list2.Count);
							list2.Add(new MuseumEntry(item.Key, item.Name, (meshVariantCount > 0) ? num : (-1), (materialVariantCount > 0) ? num2 : (-1), label));
						}
					}
				}
				list3.Add(new MuseumItem(item.Key, item.Name, list4));
			}
			return new MuseumCatalog(list2, list3, rules.CountVariants);
		}

		public bool Contains(int itemKey)
		{
			return _byKey.ContainsKey(itemKey);
		}

		public int FindEntry(int itemKey, int meshIndex, int materialIndex)
		{
			if (!_byKey.TryGetValue(itemKey, out MuseumItem value))
			{
				return -1;
			}
			MuseumEntry museumEntry = _entries[value.EntryIndexes[0]];
			if (!_countVariants || (museumEntry.MeshIndex < 0 && museumEntry.MaterialIndex < 0))
			{
				return value.EntryIndexes[0];
			}
			foreach (int entryIndex in value.EntryIndexes)
			{
				MuseumEntry museumEntry2 = _entries[entryIndex];
				if (museumEntry2.MeshIndex == meshIndex && museumEntry2.MaterialIndex == materialIndex)
				{
					return entryIndex;
				}
			}
			return -1;
		}
	}
	public sealed class MuseumEntry
	{
		public int ItemKey { get; }

		public string ItemName { get; }

		public int MeshIndex { get; }

		public int MaterialIndex { get; }

		public string Label { get; }

		public MuseumEntry(int itemKey, string itemName, int meshIndex, int materialIndex, string label)
		{
			ItemKey = itemKey;
			ItemName = itemName;
			MeshIndex = meshIndex;
			MaterialIndex = materialIndex;
			Label = label;
		}
	}
	public sealed class MuseumItem
	{
		public int Key { get; }

		public string Name { get; }

		public IReadOnlyList<int> EntryIndexes { get; }

		public MuseumItem(int key, string name, IReadOnlyList<int> entryIndexes)
		{
			Key = key;
			Name = name;
			EntryIndexes = entryIndexes;
		}
	}
	public static class MuseumRules
	{
		public static bool CountsInMuseum(in ShipObject o)
		{
			if (o.Active && o.InShipRoom && !o.HeldByPlayer)
			{
				return !o.HeldByEnemy;
			}
			return false;
		}
	}
	public sealed class MuseumState
	{
		private readonly bool[] _collected;

		public int Total => _collected.Length;

		public int CollectedCount { get; }

		public bool IsComplete
		{
			get
			{
				if (Total > 0)
				{
					return CollectedCount == Total;
				}
				return false;
			}
		}

		private MuseumState(bool[] collected, int count)
		{
			_collected = collected;
			CollectedCount = count;
		}

		public static MuseumState Compute(MuseumCatalog catalog, IEnumerable<ShipObject> objects)
		{
			bool[] array = new bool[catalog.Total];
			int num = 0;
			foreach (ShipObject @object in objects)
			{
				ShipObject o = @object;
				if (MuseumRules.CountsInMuseum(in o))
				{
					int num2 = catalog.FindEntry(o.ItemKey, o.MeshIndex, o.MaterialIndex);
					if (num2 >= 0 && num2 < array.Length && !array[num2])
					{
						array[num2] = true;
						num++;
					}
				}
			}
			return new MuseumState(array, num);
		}

		public bool IsCollected(int entryIndex)
		{
			if (entryIndex >= 0 && entryIndex < _collected.Length)
			{
				return _collected[entryIndex];
			}
			return false;
		}

		public int CollectedIn(MuseumItem item)
		{
			int num = 0;
			foreach (int entryIndex in item.EntryIndexes)
			{
				if (IsCollected(entryIndex))
				{
					num++;
				}
			}
			return num;
		}

		public bool SameAs(MuseumState? other)
		{
			if (other == null || other.Total != Total)
			{
				return false;
			}
			for (int i = 0; i < _collected.Length; i++)
			{
				if (_collected[i] != other._collected[i])
				{
					return false;
				}
			}
			return true;
		}
	}
	public sealed class ObsFileSet
	{
		public string Count { get; }

		public string Missing { get; }

		public string All { get; }

		public ObsFileSet(string count, string missing, string all)
		{
			Count = count;
			Missing = missing;
			All = all;
		}
	}
	public static class ObsFiles
	{
		public const string CountFile = "museum-count.txt";

		public const string MissingFile = "museum-missing.txt";

		public const string AllFile = "museum-all.txt";

		public static ObsFileSet Build(MuseumCatalog c, MuseumState s, Language lang)
		{
			IReadOnlyList<string> readOnlyList = BoardText.MissingLines(c, s);
			return new ObsFileSet(s.CollectedCount + "/" + s.Total, (readOnlyList.Count == 0) ? Texts.NothingMissing(lang) : string.Join("\n", readOnlyList), string.Join("\n", BoardText.AllLines(c, s)));
		}
	}
	public readonly struct ShipObject
	{
		public int ItemKey { get; }

		public int MeshIndex { get; }

		public int MaterialIndex { get; }

		public bool Active { get; }

		public bool InShipRoom { get; }

		public bool HeldByPlayer { get; }

		public bool HeldByEnemy { get; }

		public ShipObject(int itemKey, int meshIndex, int materialIndex, bool active, bool inShipRoom, bool heldByPlayer, bool heldByEnemy)
		{
			ItemKey = itemKey;
			MeshIndex = meshIndex;
			MaterialIndex = materialIndex;
			Active = active;
			InShipRoom = inShipRoom;
			HeldByPlayer = heldByPlayer;
			HeldByEnemy = heldByEnemy;
		}
	}
	public static class Texts
	{
		public static string Header(Language lang, int collected, int total)
		{
			return ((lang == Language.Portuguese) ? "Museu " : "Museum ") + collected + "/" + total;
		}

		public static string Page(Language lang, int pageNumber, int pageCount)
		{
			return ((lang == Language.Portuguese) ? "Página " : "Page ") + pageNumber + "/" + pageCount;
		}

		public static string HeldHint(Language lang, string label)
		{
			if (lang != Language.Portuguese)
			{
				return "In your hands: " + label + " (new!)";
			}
			return "Na sua mão: " + label + " (nova!)";
		}

		public static string NothingMissing(Language lang)
		{
			if (lang != Language.Portuguese)
			{
				return "Nothing missing!";
			}
			return "Não falta nada!";
		}

		public static string BannerTitle(Language lang)
		{
			if (lang != Language.Portuguese)
			{
				return "MUSEUM COMPLETE";
			}
			return "MUSEU COMPLETO";
		}
	}
	public static class VariantMatcher
	{
		public static int IndexByName(IReadOnlyList<string?> variantNames, string? currentName)
		{
			if (currentName == null)
			{
				return -1;
			}
			string b = CleanName(currentName);
			for (int i = 0; i < variantNames.Count; i++)
			{
				string text = variantNames[i];
				if (text != null && string.Equals(CleanName(text), b, StringComparison.Ordinal))
				{
					return i;
				}
			}
			return -1;
		}

		public static string CleanName(string name)
		{
			string text = name;
			while (true)
			{
				if (text.EndsWith(" (Instance)", StringComparison.Ordinal))
				{
					text = text.Substring(0, text.Length - 11);
					continue;
				}
				if (!text.EndsWith(" Instance", StringComparison.Ordinal))
				{
					break;
				}
				text = text.Substring(0, text.Length - 9);
			}
			return text;
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}