Decompiled source of ValheimCompletionist v0.1.0

ValheimCompletionist/plugins/ValheimCompletionist/ValheimCompletionist.dll

Decompiled 9 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.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);
					}
				}
			}
		}
	}
}