using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.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 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(); 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("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(); 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 LoadFromCsv(string fileName) { List list = new List(); 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(text5, ignoreCase: true, out var result)) { Logger.LogWarning((object)$"Skipping row {i + 1} in {fileName}: invalid Biome '{text5}'."); continue; } if (!Enum.TryParse(text6, ignoreCase: true, out var result2)) { Logger.LogWarning((object)$"Skipping row {i + 1} in {fileName}: invalid Category '{text6}'."); continue; } if (!Enum.TryParse(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 ItemEntries { get; private set; } = new List(); public static List EnemyEntries { get; private set; } = new List(); public static List BossEntries { get; private set; } = new List(); public static List Entries { get; private set; } = new List(); public static void Load() { ItemEntries = ChecklistCsvLoader.LoadFromCsv("items.csv"); EnemyEntries = ChecklistCsvLoader.LoadFromCsv("enemies.csv"); BossEntries = ChecklistCsvLoader.LoadFromCsv("bosses.csv"); Entries = new List(); 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 GetAll() { return Entries; } public static IEnumerable GetItems() { return ItemEntries; } public static IEnumerable GetEnemies() { return EnemyEntries; } public static IEnumerable GetByBiome(Biome biome) { return Entries.Where((ChecklistEntry entry) => entry.Biome == biome); } public static IEnumerable GetByCategory(ChecklistCategory category) { return Entries.Where((ChecklistEntry entry) => entry.Category == category); } public static IEnumerable 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 biomeFoldouts = new Dictionary(); private readonly Dictionary sectionFoldouts = new Dictionary(); 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()); DrawHeader(); GUILayout.Space(8f); scrollPosition = GUILayout.BeginScrollView(scrollPosition, false, true, Array.Empty()); 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()); GUILayout.Label("Version 0.1.0", normalStyle, Array.Empty()); GUILayout.Label($"Total Completion: {num}/{count} ({GetPercent(num, count):0.0}%)", headerStyle, Array.Empty()); GUILayout.Label("Checklist for Bosses, Enemies, and all Items.", normalStyle, Array.Empty()); } private void DrawFooter() { GUILayout.BeginHorizontal(Array.Empty()); 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 biomeDisplayOrder = GetBiomeDisplayOrder(); bool allBiomesComplete = AreAllBiomesComplete(); foreach (Biome biome in biomeDisplayOrder) { List 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 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()); biomeFoldouts[biome] = GUILayout.Toggle(biomeFoldouts[biome], $"{GetFoldoutSymbol(biomeFoldouts[biome])} {biome} — {num}/{count} ({GetPercent(num, count):0.0}%)", val, Array.Empty()); if (biomeFoldouts[biome]) { GUILayout.Space(4f); DrawItemsSection(biome, biomeEntries); DrawEnemiesSection(biome, biomeEntries); DrawBossSection(biome, biomeEntries); } GUILayout.EndVertical(); } private List GetBiomeDisplayOrder() { return Enum.GetValues(typeof(Biome)).Cast().ToList(); } private void DrawItemsSection(Biome biome, List biomeEntries) { List 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 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 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 biomeEntries) { List 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 biomeEntries) { List 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 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()); GUILayout.Space(indent); sectionFoldouts[key] = GUILayout.Toggle(sectionFoldouts[key], $"{GetFoldoutSymbol(sectionFoldouts[key])} {title} — {num}/{count} ({GetPercent(num, count):0.0}%)", foldoutButtonStyle, Array.Empty()); 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()); 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 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 CompletedEntryIds = new HashSet(); 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 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 RecentlyHitByLocalPlayer = new Dictionary(); 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(); 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); } } } } } }