Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Bitencola Museum Tracker v1.0.0
BitencolaMuseumTracker.dll
Decompiled a day agousing 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) { } } }