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 ValheimCompletionist v0.1.0
ValheimCompletionist/plugins/ValheimCompletionist/ValheimCompletionist.dll
Decompiled 9 hours 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.InteropServices; using System.Runtime.Versioning; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Jotunn; using Jotunn.Entities; using Jotunn.Managers; using UnityEngine; using ValheimCompletionist.Checklist; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("ValheimCompletionist")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("ValheimCompletionist")] [assembly: AssemblyCopyright("Copyright © 2021")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("e3243d22-4307-4008-ba36-9f326008cde5")] [assembly: AssemblyFileVersion("0.1.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.0.0")] namespace ValheimCompletionist { [BepInPlugin("com.ryansberc21.ValheimCompletionist", "ValheimCompletionist", "0.1.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] internal class ValheimCompletionist : BaseUnityPlugin { private Harmony harmony; public const string PluginGUID = "com.ryansberc21.ValheimCompletionist"; public const string PluginName = "ValheimCompletionist"; public const string PluginVersion = "0.1.0"; public static CustomLocalization Localization = LocalizationManager.Instance.GetLocalization(); private ConfigEntry<bool> enableDebugExport; private float itemScanTimer; private float bossScanTimer; private const float ItemScanInterval = 5f; private const float BossScanInterval = 5f; private string loadedCharacterName; private void Awake() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown Logger.LogInfo((object)"ValheimCompletionist by realberch loading..."); BindConfig(); CompletionDatabase.Load(); harmony = new Harmony("com.ryansberc21.ValheimCompletionist"); harmony.PatchAll(); Logger.LogInfo((object)"Harmony patches applied."); if (!GUIManager.IsHeadless()) { ((Component)this).gameObject.AddComponent<CompletionMenu>(); Logger.LogInfo((object)"Completion menu registered."); } Logger.LogInfo((object)"ValheimCompletionist loaded."); } private void OnDestroy() { Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } CompletionProgress.Unload(); loadedCharacterName = null; GUIManager.BlockInput(false); } private void Update() { HandleCharacterProgressState(); HandleDebugInput(); if (CompletionProgress.IsLoadedForCharacter) { HandlePeriodicItemScan(); HandlePeriodicBossScan(); } } private void BindConfig() { enableDebugExport = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "Enable ObjectDB Export", false, "If true, pressing F10 exports ObjectDB item data to BepInEx/config/ValheimCompletionist/objectdb_items.csv."); } private void HandleDebugInput() { if (enableDebugExport.Value && Input.GetKeyDown((KeyCode)291)) { ExportObjectDBItemsToCsv(); } } private void HandlePeriodicItemScan() { if (CompletionProgress.IsLoadedForCharacter) { itemScanTimer += Time.deltaTime; if (!(itemScanTimer < 5f)) { itemScanTimer = 0f; ItemCompletionTracker.ScanPlayerInventory(); } } } private void HandlePeriodicBossScan() { if (CompletionProgress.IsLoadedForCharacter) { bossScanTimer += Time.deltaTime; if (!(bossScanTimer < 5f)) { bossScanTimer = 0f; ScanBossGlobalKeys(); } } } private void ScanBossGlobalKeys() { if (!CompletionProgress.IsLoadedForCharacter || (Object)(object)ZoneSystem.instance == (Object)null) { return; } foreach (ChecklistEntry item in CompletionDatabase.GetByCategory(ChecklistCategory.Bosses)) { if (!string.IsNullOrWhiteSpace(item.GlobalKey) && ZoneSystem.instance.GetGlobalKey(item.GlobalKey)) { CompletionProgress.MarkCompleted(item.Id); } } } private void HandleCharacterProgressLoading() { if (!((Object)(object)Player.m_localPlayer == (Object)null)) { string playerName = Player.m_localPlayer.GetPlayerName(); if (!string.IsNullOrWhiteSpace(playerName) && !(loadedCharacterName == playerName)) { loadedCharacterName = playerName; CompletionProgress.LoadForCharacter(playerName); Logger.LogInfo((object)("ValheimCompletionist using progress for character: " + playerName)); } } } private void HandleCharacterProgressState() { if ((Object)(object)Player.m_localPlayer == (Object)null) { if (!string.IsNullOrWhiteSpace(loadedCharacterName)) { Logger.LogInfo((object)("Unloading completion progress for character: " + loadedCharacterName)); CompletionProgress.Unload(); loadedCharacterName = null; } return; } string playerName = Player.m_localPlayer.GetPlayerName(); if (!string.IsNullOrWhiteSpace(playerName) && (!(loadedCharacterName == playerName) || !CompletionProgress.IsLoadedForCharacter)) { if (!string.IsNullOrWhiteSpace(loadedCharacterName)) { Logger.LogInfo((object)("Switching completion progress from '" + loadedCharacterName + "' to '" + playerName + "'.")); CompletionProgress.Unload(); } loadedCharacterName = playerName; CompletionProgress.LoadForCharacter(playerName); Logger.LogInfo((object)("Loaded completion progress for character: " + playerName)); } } private void ExportObjectDBItemsToCsv() { //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Invalid comparison between Unknown and I4 if ((Object)(object)ObjectDB.instance == (Object)null) { Logger.LogWarning((object)"ObjectDB.instance is null. Enter a world first."); return; } string text = Path.Combine(Paths.ConfigPath, "ValheimCompletionist"); Directory.CreateDirectory(text); string text2 = Path.Combine(text, "objectdb_items.csv"); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("PrefabName,NameToken,ItemType,MaxStack,Weight"); foreach (GameObject item in ObjectDB.instance.m_items) { if ((Object)(object)item == (Object)null) { continue; } ItemDrop component = item.GetComponent<ItemDrop>(); if (!((Object)(object)component == (Object)null)) { SharedData shared = component.m_itemData.m_shared; if (shared.m_name.StartsWith("$item_") || (int)shared.m_itemType == 21) { stringBuilder.AppendLine(EscapeCsv(((Object)item).name) + "," + EscapeCsv(shared.m_name) + "," + EscapeCsv(((object)(ItemType)(ref shared.m_itemType)).ToString()) + "," + $"{shared.m_maxStackSize}," + $"{shared.m_weight}"); } } } File.WriteAllText(text2, stringBuilder.ToString()); Logger.LogInfo((object)("Exported ObjectDB items to: " + text2)); } private string EscapeCsv(string value) { if (value == null) { return ""; } if (value.Contains(",") || value.Contains("\"") || value.Contains("\n")) { return "\"" + value.Replace("\"", "\"\"") + "\""; } return value; } } } namespace ValheimCompletionist.Checklist { public enum Biome { Meadows, BlackForest, Swamp, Mountain, Plains, Mistlands, Ashlands, Ocean, DeepNorth, Global } public enum ChecklistCategory { Bosses, Enemies, Materials, Items, Weapons, Armor, Tools, Food, Trophies, Locations, Dungeons, Builds, Crafting, Fishing, Misc } public class ChecklistEntry { public string Id { get; } public string DisplayName { get; } public Biome Biome { get; } public ChecklistCategory Category { get; } public CompletionType CompletionType { get; } public string PrefabName { get; } public string GlobalKey { get; } public ChecklistEntry(string id, string displayName, Biome biome, ChecklistCategory category, CompletionType completionType, string prefabName = null, string globalKey = null) { Id = id; DisplayName = displayName; Biome = biome; Category = category; CompletionType = completionType; PrefabName = prefabName; GlobalKey = globalKey; } } public static class ChecklistCsvLoader { public static List<ChecklistEntry> LoadFromCsv(string fileName) { List<ChecklistEntry> list = new List<ChecklistEntry>(); string text = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "data", fileName); Logger.LogInfo((object)("Trying to load checklist CSV: " + text)); if (!File.Exists(text)) { Logger.LogWarning((object)("Checklist CSV not found: " + text)); return list; } string[] array = File.ReadAllLines(text); Logger.LogInfo((object)$"{fileName} line count: {array.Length}"); if (array.Length <= 1) { Logger.LogWarning((object)(fileName + " has no data rows.")); return list; } Logger.LogInfo((object)(fileName + " header: " + array[0])); for (int i = 1; i < array.Length; i++) { string text2 = array[i]; if (string.IsNullOrWhiteSpace(text2)) { continue; } string[] array2 = text2.Split(new char[1] { ',' }); if (array2.Length < 5) { Logger.LogWarning((object)$"Skipping row {i + 1} in {fileName}: expected at least 5 columns, found {array2.Length}. Row: {text2}"); continue; } string text3 = array2[0].Trim(); string text4 = array2[1].Trim(); string text5 = array2[2].Trim(); string text6 = array2[3].Trim(); string text7 = array2[4].Trim(); string prefabName = ((array2.Length > 5) ? array2[5].Trim() : null); string globalKey = ((array2.Length > 6) ? array2[6].Trim() : null); if (string.IsNullOrWhiteSpace(text3)) { Logger.LogWarning((object)$"Skipping row {i + 1} in {fileName}: missing id."); continue; } if (string.IsNullOrWhiteSpace(text4)) { Logger.LogWarning((object)$"Skipping row {i + 1} in {fileName}: missing displayName."); continue; } if (!Enum.TryParse<Biome>(text5, ignoreCase: true, out var result)) { Logger.LogWarning((object)$"Skipping row {i + 1} in {fileName}: invalid Biome '{text5}'."); continue; } if (!Enum.TryParse<ChecklistCategory>(text6, ignoreCase: true, out var result2)) { Logger.LogWarning((object)$"Skipping row {i + 1} in {fileName}: invalid Category '{text6}'."); continue; } if (!Enum.TryParse<CompletionType>(text7, ignoreCase: true, out var result3)) { Logger.LogWarning((object)$"Skipping row {i + 1} in {fileName}: invalid CompletionType '{text7}'."); continue; } ChecklistEntry item = new ChecklistEntry(text3, text4, result, result2, result3, prefabName, globalKey); list.Add(item); } Logger.LogInfo((object)$"Loaded {list.Count} entries from {fileName}."); return list; } } public static class CompletionDatabase { public static List<ChecklistEntry> ItemEntries { get; private set; } = new List<ChecklistEntry>(); public static List<ChecklistEntry> EnemyEntries { get; private set; } = new List<ChecklistEntry>(); public static List<ChecklistEntry> BossEntries { get; private set; } = new List<ChecklistEntry>(); public static List<ChecklistEntry> Entries { get; private set; } = new List<ChecklistEntry>(); public static void Load() { ItemEntries = ChecklistCsvLoader.LoadFromCsv("items.csv"); EnemyEntries = ChecklistCsvLoader.LoadFromCsv("enemies.csv"); BossEntries = ChecklistCsvLoader.LoadFromCsv("bosses.csv"); Entries = new List<ChecklistEntry>(); Entries.AddRange(ItemEntries); Entries.AddRange(EnemyEntries); Entries.AddRange(BossEntries); if (Entries.Count == 0) { Logger.LogWarning((object)"No checklist entries loaded from CSV files. Please ensure that the \nItems.csv and Enemies.csv files are in the BepInEx\nconfig folder."); } Logger.LogInfo((object)$"CompletionDatabase loaded {Entries.Count} total entries."); Logger.LogInfo((object)$"Loaded {ItemEntries.Count} item entries."); Logger.LogInfo((object)$"Loaded {EnemyEntries.Count} enemy entries."); Logger.LogInfo((object)$"Loaded {BossEntries.Count} boss entries."); } public static IEnumerable<ChecklistEntry> GetAll() { return Entries; } public static IEnumerable<ChecklistEntry> GetItems() { return ItemEntries; } public static IEnumerable<ChecklistEntry> GetEnemies() { return EnemyEntries; } public static IEnumerable<ChecklistEntry> GetByBiome(Biome biome) { return Entries.Where((ChecklistEntry entry) => entry.Biome == biome); } public static IEnumerable<ChecklistEntry> GetByCategory(ChecklistCategory category) { return Entries.Where((ChecklistEntry entry) => entry.Category == category); } public static IEnumerable<ChecklistEntry> GetByBiomeAndCategory(Biome biome, ChecklistCategory category) { return Entries.Where((ChecklistEntry entry) => entry.Biome == biome && entry.Category == category); } public static ChecklistEntry GetById(string id) { if (string.IsNullOrWhiteSpace(id)) { return null; } return Entries.FirstOrDefault((ChecklistEntry entry) => entry.Id == id); } public static ChecklistEntry GetByPrefabName(string prefabName) { if (string.IsNullOrWhiteSpace(prefabName)) { return null; } return Entries.FirstOrDefault((ChecklistEntry entry) => entry.PrefabName == prefabName); } public static ChecklistEntry GetByPrefabName(string prefabName, CompletionType completionType) { if (string.IsNullOrWhiteSpace(prefabName)) { return null; } return Entries.FirstOrDefault((ChecklistEntry entry) => entry.PrefabName == prefabName && entry.CompletionType == completionType); } public static ChecklistEntry GetByGlobalKey(string globalKey) { if (string.IsNullOrWhiteSpace(globalKey)) { return null; } return Entries.FirstOrDefault((ChecklistEntry entry) => entry.GlobalKey == globalKey); } public static bool ContainsId(string id) { return GetById(id) != null; } public static int CountByBiome(Biome biome) { return Entries.Count((ChecklistEntry entry) => entry.Biome == biome); } public static int CountByCategory(ChecklistCategory category) { return Entries.Count((ChecklistEntry entry) => entry.Category == category); } public static int CountItems() { return ItemEntries.Count; } public static int CountEnemies() { return EnemyEntries.Count; } } public class CompletionMenu : MonoBehaviour { private bool isOpen; private Rect windowRect = new Rect(120f, 80f, 900f, 700f); private Vector2 scrollPosition = Vector2.zero; private readonly Dictionary<Biome, bool> biomeFoldouts = new Dictionary<Biome, bool>(); private readonly Dictionary<string, bool> sectionFoldouts = new Dictionary<string, bool>(); private GUIStyle titleStyle; private GUIStyle headerStyle; private GUIStyle normalStyle; private GUIStyle completedStyle; private GUIStyle incompleteStyle; private GUIStyle foldoutButtonStyle; private GUIStyle entryBoxStyle; private GUIStyle completedBiomeStyle; private GUIStyle goldBiomeStyle; private const int WindowId = 832710; private void Update() { if (Input.GetKeyDown((KeyCode)289)) { ToggleMenu(); } if (isOpen && Input.GetKeyDown((KeyCode)27)) { CloseMenu(); } } private void OnDestroy() { GUIManager.BlockInput(false); } private void OnGUI() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown //IL_002c: 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) if (isOpen) { InitializeStyles(); windowRect = GUI.Window(832710, windowRect, new WindowFunction(DrawWindow), "Valheim Completionist"); } } private void ToggleMenu() { isOpen = !isOpen; GUIManager.BlockInput(isOpen); } private void CloseMenu() { isOpen = false; GUIManager.BlockInput(false); } private void DrawWindow(int windowId) { //IL_001c: 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_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginVertical(Array.Empty<GUILayoutOption>()); DrawHeader(); GUILayout.Space(8f); scrollPosition = GUILayout.BeginScrollView(scrollPosition, false, true, Array.Empty<GUILayoutOption>()); DrawBiomeSections(); GUILayout.EndScrollView(); GUILayout.Space(8f); DrawFooter(); GUILayout.EndVertical(); GUI.DragWindow(new Rect(0f, 0f, 10000f, 26f)); } private void DrawHeader() { int count = CompletionDatabase.Entries.Count; int num = CompletionProgress.CountCompleted(CompletionDatabase.Entries); GUILayout.Label("Valheim Completionist Mod - by realberch", titleStyle, Array.Empty<GUILayoutOption>()); GUILayout.Label("Version 0.1.0", normalStyle, Array.Empty<GUILayoutOption>()); GUILayout.Label($"Total Completion: {num}/{count} ({GetPercent(num, count):0.0}%)", headerStyle, Array.Empty<GUILayoutOption>()); GUILayout.Label("Checklist for Bosses, Enemies, and all Items.", normalStyle, Array.Empty<GUILayoutOption>()); } private void DrawFooter() { GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); if (GUILayout.Button("Refresh", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(32f) })) { RepaintMenu(); } if (GUILayout.Button("Close", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(32f) })) { CloseMenu(); } GUILayout.EndHorizontal(); } private void RepaintMenu() { } private void DrawBiomeSections() { List<Biome> biomeDisplayOrder = GetBiomeDisplayOrder(); bool allBiomesComplete = AreAllBiomesComplete(); foreach (Biome biome in biomeDisplayOrder) { List<ChecklistEntry> list = (from entry in CompletionDatabase.Entries where entry.Biome == biome orderby entry.DisplayName select entry).ToList(); if (list.Count != 0) { DrawBiomeSection(biome, list, allBiomesComplete); } } } private void DrawBiomeSection(Biome biome, List<ChecklistEntry> biomeEntries, bool allBiomesComplete) { int count = biomeEntries.Count; int num = CompletionProgress.CountCompleted(biomeEntries); bool flag = count > 0 && num == count; GUIStyle val = foldoutButtonStyle; if (flag && allBiomesComplete) { val = goldBiomeStyle; } else if (flag) { val = completedBiomeStyle; } if (!biomeFoldouts.ContainsKey(biome)) { biomeFoldouts[biome] = false; } GUILayout.BeginVertical(entryBoxStyle, Array.Empty<GUILayoutOption>()); biomeFoldouts[biome] = GUILayout.Toggle(biomeFoldouts[biome], $"{GetFoldoutSymbol(biomeFoldouts[biome])} {biome} — {num}/{count} ({GetPercent(num, count):0.0}%)", val, Array.Empty<GUILayoutOption>()); if (biomeFoldouts[biome]) { GUILayout.Space(4f); DrawItemsSection(biome, biomeEntries); DrawEnemiesSection(biome, biomeEntries); DrawBossSection(biome, biomeEntries); } GUILayout.EndVertical(); } private List<Biome> GetBiomeDisplayOrder() { return Enum.GetValues(typeof(Biome)).Cast<Biome>().ToList(); } private void DrawItemsSection(Biome biome, List<ChecklistEntry> biomeEntries) { List<ChecklistEntry> itemEntries = (from entry in biomeEntries.Where(IsItemEntry) orderby entry.Category.ToString(), entry.DisplayName select entry).ToList(); if (itemEntries.Count == 0) { return; } string key = $"{biome}_items"; DrawFoldoutSection(key, "Items", itemEntries, 16f, delegate { foreach (IGrouping<ChecklistCategory, ChecklistEntry> item in (from entry in itemEntries group entry by entry.Category into @group orderby @group.Key.ToString() select @group).ToList()) { DrawItemCategorySection(biome, item.Key, item.OrderBy((ChecklistEntry entry) => entry.DisplayName).ToList()); } }); } private void DrawItemCategorySection(Biome biome, ChecklistCategory category, List<ChecklistEntry> entries) { string key = $"{biome}_items_{category}"; DrawFoldoutSection(key, category.ToString(), entries, 34f, delegate { foreach (ChecklistEntry entry in entries) { DrawEntryRow(entry, 54f); } }); } private void DrawEnemiesSection(Biome biome, List<ChecklistEntry> biomeEntries) { List<ChecklistEntry> enemyEntries = (from entry in biomeEntries.Where(IsEnemyEntry) orderby entry.DisplayName select entry).ToList(); if (enemyEntries.Count == 0) { return; } string key = $"{biome}_enemies"; DrawFoldoutSection(key, "Enemies", enemyEntries, 16f, delegate { foreach (ChecklistEntry item in enemyEntries) { DrawEntryRow(item, 36f); } }); } private void DrawBossSection(Biome biome, List<ChecklistEntry> biomeEntries) { List<ChecklistEntry> bossEntries = (from entry in biomeEntries.Where(IsBossEntry) orderby entry.DisplayName select entry).ToList(); if (bossEntries.Count == 0) { return; } string key = $"{biome}_bosses"; DrawFoldoutSection(key, "Boss", bossEntries, 16f, delegate { foreach (ChecklistEntry item in bossEntries) { DrawEntryRow(item, 36f); } }); } private void DrawFoldoutSection(string key, string title, List<ChecklistEntry> entries, float indent, Action drawContents) { if (!sectionFoldouts.ContainsKey(key)) { sectionFoldouts[key] = false; } int count = entries.Count; int num = CompletionProgress.CountCompleted(entries); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Space(indent); sectionFoldouts[key] = GUILayout.Toggle(sectionFoldouts[key], $"{GetFoldoutSymbol(sectionFoldouts[key])} {title} — {num}/{count} ({GetPercent(num, count):0.0}%)", foldoutButtonStyle, Array.Empty<GUILayoutOption>()); GUILayout.EndHorizontal(); if (sectionFoldouts[key]) { drawContents(); } } private void DrawEntryRow(ChecklistEntry entry, float indent) { bool flag = CompletionProgress.IsCompleted(entry.Id); string obj = (flag ? "[X]" : "[ ]"); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Space(indent); GUILayout.Label(obj, flag ? completedStyle : incompleteStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(36f) }); GUILayout.Label(entry.DisplayName, flag ? completedStyle : normalStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(260f) }); GUILayout.Label(entry.Category.ToString(), normalStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(130f) }); GUILayout.Label(entry.CompletionType.ToString(), normalStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(150f) }); if (!string.IsNullOrWhiteSpace(entry.PrefabName)) { GUILayout.Label("Prefab: " + entry.PrefabName, normalStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(220f) }); } GUILayout.EndHorizontal(); } private bool AreAllBiomesComplete() { foreach (Biome biome in GetBiomeDisplayOrder()) { List<ChecklistEntry> list = CompletionDatabase.Entries.Where((ChecklistEntry entry) => entry.Biome == biome).ToList(); if (list.Count != 0 && CompletionProgress.CountCompleted(list) < list.Count) { return false; } } return true; } private bool IsItemEntry(ChecklistEntry entry) { if (entry == null) { return false; } return entry.CompletionType == CompletionType.ItemCollected; } private bool IsEnemyEntry(ChecklistEntry entry) { if (entry == null) { return false; } return entry.CompletionType == CompletionType.EnemyKilled; } private bool IsBossEntry(ChecklistEntry entry) { if (entry == null) { return false; } return entry.CompletionType == CompletionType.BossDefeated; } private string GetFoldoutSymbol(bool expanded) { if (!expanded) { return "[+]"; } return "[-]"; } private float GetPercent(int completed, int total) { if (total <= 0) { return 0f; } return (float)completed / (float)total * 100f; } private void InitializeStyles() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: 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_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Expected O, but got Unknown //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Expected O, but got Unknown //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Expected O, but got Unknown //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Expected O, but got Unknown //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Expected O, but got Unknown //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_01f2: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Expected O, but got Unknown //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Expected O, but got Unknown //IL_0225: Expected O, but got Unknown if (titleStyle == null) { GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 24, fontStyle = (FontStyle)1 }; val.normal.textColor = Color.white; titleStyle = val; GUIStyle val2 = new GUIStyle(GUI.skin.label) { fontSize = 16, fontStyle = (FontStyle)1 }; val2.normal.textColor = Color.white; headerStyle = val2; GUIStyle val3 = new GUIStyle(GUI.skin.label) { fontSize = 14 }; val3.normal.textColor = Color.white; normalStyle = val3; GUIStyle val4 = new GUIStyle(GUI.skin.label) { fontSize = 14 }; val4.normal.textColor = Color.green; completedStyle = val4; GUIStyle val5 = new GUIStyle(GUI.skin.label) { fontSize = 14 }; val5.normal.textColor = Color.gray; incompleteStyle = val5; foldoutButtonStyle = new GUIStyle(GUI.skin.button) { alignment = (TextAnchor)3, fontSize = 15, fontStyle = (FontStyle)1 }; GUIStyle val6 = new GUIStyle(foldoutButtonStyle); val6.normal.textColor = Color.green; val6.hover.textColor = Color.green; val6.active.textColor = Color.green; val6.focused.textColor = Color.green; completedBiomeStyle = val6; GUIStyle val7 = new GUIStyle(foldoutButtonStyle); val7.normal.textColor = new Color(1f, 0.75f, 0.15f); val7.hover.textColor = new Color(1f, 0.75f, 0.15f); val7.active.textColor = new Color(1f, 0.75f, 0.15f); val7.focused.textColor = new Color(1f, 0.75f, 0.15f); goldBiomeStyle = val7; entryBoxStyle = new GUIStyle(GUI.skin.box) { padding = new RectOffset(8, 8, 8, 8) }; } } } public static class CompletionProgress { private static readonly HashSet<string> CompletedEntryIds = new HashSet<string>(); private static string currentCharacterName = null; private static bool isLoadedForCharacter = false; private static string ProgressFolderPath { get { string text = Path.Combine(Paths.ConfigPath, "ValheimCompletionist", "progress"); if (!Directory.Exists(text)) { Directory.CreateDirectory(text); } return text; } } private static string ProgressFilePath { get { string safeFileName = GetSafeFileName(currentCharacterName); return Path.Combine(ProgressFolderPath, safeFileName + ".txt"); } } public static string CurrentCharacterName => currentCharacterName; public static bool IsLoadedForCharacter => isLoadedForCharacter; public static void LoadForCharacter(string characterName) { if (string.IsNullOrWhiteSpace(characterName)) { Logger.LogWarning((object)"Cannot load completion progress: character name is blank."); return; } characterName = characterName.Trim(); if (isLoadedForCharacter && currentCharacterName == characterName) { return; } CompletedEntryIds.Clear(); currentCharacterName = characterName; isLoadedForCharacter = true; if (!File.Exists(ProgressFilePath)) { Logger.LogInfo((object)("No progress file found for character '" + currentCharacterName + "'. Starting fresh.")); Save(); return; } string[] array = File.ReadAllLines(ProgressFilePath); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (!string.IsNullOrWhiteSpace(text)) { CompletedEntryIds.Add(text); } } Logger.LogInfo((object)$"Loaded {CompletedEntryIds.Count} completed checklist entries for character '{currentCharacterName}'."); } public static void Unload() { Save(); CompletedEntryIds.Clear(); currentCharacterName = null; isLoadedForCharacter = false; } public static void Save() { if (isLoadedForCharacter && !string.IsNullOrWhiteSpace(currentCharacterName)) { File.WriteAllLines(ProgressFilePath, CompletedEntryIds.OrderBy((string id) => id)); } } public static bool IsCompleted(string id) { if (string.IsNullOrWhiteSpace(id)) { return false; } return CompletedEntryIds.Contains(id); } public static void MarkCompleted(string id) { if (!string.IsNullOrWhiteSpace(id)) { if (!isLoadedForCharacter) { Logger.LogWarning((object)("Cannot mark checklist entry complete before character progress is loaded: " + id)); } else if (CompletedEntryIds.Add(id)) { Logger.LogInfo((object)("Checklist entry completed for '" + currentCharacterName + "': " + id)); Save(); } } } public static int CountCompleted(IEnumerable<ChecklistEntry> entries) { if (entries == null) { return 0; } int num = 0; foreach (ChecklistEntry entry in entries) { if (entry != null && IsCompleted(entry.Id)) { num++; } } return num; } public static int CountCompletedIds() { return CompletedEntryIds.Count; } private static string GetSafeFileName(string value) { if (string.IsNullOrWhiteSpace(value)) { return "UnknownCharacter"; } string text = value.Trim(); char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char oldChar in invalidFileNameChars) { text = text.Replace(oldChar, '_'); } return text; } } public enum CompletionType { Manual, BossDefeated, EnemyKilled, ItemCollected, ItemInCompletionStorage, Crafted, Built, LocationDiscovered, FoodEaten, FishCaught } public static class EnemyCompletionTracker { private static readonly Dictionary<int, float> RecentlyHitByLocalPlayer = new Dictionary<int, float>(); private const float PlayerKillCreditWindowSeconds = 20f; public static void RegisterPlayerHit(Character character) { if (!((Object)(object)character == (Object)null)) { RecentlyHitByLocalPlayer[((Object)character).GetInstanceID()] = Time.time; } } public static void TryMarkEnemyKilled(Character character) { if ((Object)(object)character == (Object)null) { return; } int instanceID = ((Object)character).GetInstanceID(); if (!RecentlyHitByLocalPlayer.TryGetValue(instanceID, out var value)) { return; } RecentlyHitByLocalPlayer.Remove(instanceID); if (Time.time - value > 20f) { return; } string prefabName = GetPrefabName(character); if (!string.IsNullOrWhiteSpace(prefabName)) { ChecklistEntry byPrefabName = CompletionDatabase.GetByPrefabName(prefabName, CompletionType.EnemyKilled); if (byPrefabName == null) { Logger.LogInfo((object)("Killed enemy prefab not in checklist: " + prefabName)); return; } CompletionProgress.MarkCompleted(byPrefabName.Id); Logger.LogInfo((object)("Enemy checklist completed: " + byPrefabName.DisplayName + " (" + prefabName + ")")); } } private static string GetPrefabName(Character character) { ZNetView component = ((Component)character).GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null) { string prefabName = component.GetPrefabName(); if (!string.IsNullOrWhiteSpace(prefabName)) { return prefabName; } } string text = ((Object)((Component)character).gameObject).name; if (text.EndsWith("(Clone)")) { text = text.Replace("(Clone)", "").Trim(); } return text; } } [HarmonyPatch(typeof(Character), "Damage")] public static class CharacterDamagePatch { private static void Prefix(Character __instance, HitData hit) { if (!((Object)(object)__instance == (Object)null) && hit != null && !((Object)(object)Player.m_localPlayer == (Object)null) && (Object)(object)hit.GetAttacker() == (Object)(object)Player.m_localPlayer) { EnemyCompletionTracker.RegisterPlayerHit(__instance); } } } [HarmonyPatch(typeof(Character), "OnDeath")] public static class CharacterOnDeathPatch { private static void Postfix(Character __instance) { EnemyCompletionTracker.TryMarkEnemyKilled(__instance); } } public static class ItemCompletionTracker { public static void ScanPlayerInventory() { if ((Object)(object)Player.m_localPlayer == (Object)null) { return; } Inventory inventory = ((Humanoid)Player.m_localPlayer).GetInventory(); if (inventory == null) { return; } foreach (ItemData allItem in inventory.GetAllItems()) { if (allItem != null && !((Object)(object)allItem.m_dropPrefab == (Object)null)) { ChecklistEntry byPrefabName = CompletionDatabase.GetByPrefabName(Utils.GetPrefabName(allItem.m_dropPrefab), CompletionType.ItemCollected); if (byPrefabName != null) { CompletionProgress.MarkCompleted(byPrefabName.Id); } } } } } }