Decompiled source of balrond Achievement Rewards v0.1.0

plugins/BalrondAchievementRewards.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using BalrondAchievementRewards.AchievementIntegration;
using BalrondAchievementRewards.Configuration;
using BalrondAchievementRewards.Core;
using BalrondAchievementRewards.Data;
using BalrondAchievementRewards.Networking;
using BalrondAchievementRewards.Patches;
using BalrondAchievementRewards.Runtime;
using BalrondAchievementRewards.UI;
using BalrondAchievementRewards.Utils;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using ServerSync;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("BalrondAchievementRewards")]
[assembly: AssemblyDescription("Cosmetic achievement reward titles for Valheim 1.0")]
[assembly: AssemblyCompany("Balrond")]
[assembly: AssemblyProduct("BalrondAchievementRewards")]
[assembly: ComVisible(false)]
[assembly: Guid("0b62d0e0-7d43-47cb-9acd-cac9d7212aae")]
[assembly: AssemblyFileVersion("0.3.3.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.3.3.0")]
[module: UnverifiableCode]
namespace BalrondAchievementRewards.Utils
{
	internal static class ModLog
	{
		private static ManualLogSource _log;

		private static readonly HashSet<string> OnceKeys = new HashSet<string>();

		internal static void Initialize(ManualLogSource log)
		{
			_log = log;
			OnceKeys.Clear();
		}

		internal static void Info(string message)
		{
			if (_log != null)
			{
				_log.LogInfo((object)message);
			}
		}

		internal static void Warning(string message)
		{
			if (_log != null)
			{
				_log.LogWarning((object)message);
			}
		}

		internal static void Error(string message)
		{
			if (_log != null)
			{
				_log.LogError((object)message);
			}
		}

		internal static void Debug(string message)
		{
			if (_log != null && BARConfig.VerboseLogging != null && BARConfig.VerboseLogging.Value)
			{
				_log.LogInfo((object)("[DEBUG] " + message));
			}
		}

		internal static void WarningOnce(string key, string message)
		{
			if (OnceKeys.Add("W:" + key))
			{
				Warning(message);
			}
		}

		internal static void ErrorOnce(string key, string message)
		{
			if (OnceKeys.Add("E:" + key))
			{
				Error(message);
			}
		}

		internal static void Reset()
		{
			OnceKeys.Clear();
			_log = null;
		}
	}
	internal static class UnsafeTextHash
	{
		internal unsafe static uint Compute(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return 2166136261u;
			}
			uint num = 2166136261u;
			fixed (char* ptr = value)
			{
				for (int i = 0; i < value.Length; i++)
				{
					char c = ptr[i];
					num ^= (byte)(c & 0xFF);
					num *= 16777619;
					num ^= (byte)(((int)c >> 8) & 0xFF);
					num *= 16777619;
				}
			}
			return num;
		}
	}
}
namespace BalrondAchievementRewards.UI
{
	internal static class TitleSelectionGui
	{
		private sealed class AchievementButtonHook
		{
			internal Button Button;

			internal UnityAction Listener;
		}

		private const string NoTitleButtonName = "BAR_NoTitleButton";

		private const string TitleClickOverlayName = "BAR_TitleClickOverlay";

		private static readonly List<AchievementButtonHook> AchievementButtonHooks = new List<AchievementButtonHook>();

		private static InventoryGui _inventoryOwner;

		private static Button _noTitleButton;

		private static TMP_Text _noTitleButtonText;

		private static bool _refreshRequested;

		private static bool _overlayReadyLogged;

		internal static void Tick()
		{
			if (_refreshRequested)
			{
				_refreshRequested = false;
				InventoryGui instance = InventoryGui.m_instance;
				if (IsAchievementsPanelVisible(instance))
				{
					EnsureNoTitleButton(instance);
					UpdateNoTitleButton();
					BindNativeAchievementButtons(instance);
				}
			}
		}

		internal static void Shutdown()
		{
			TitleConfirmationPopup.Shutdown();
			ClearNativeAchievementButtonHooks();
			DestroyNoTitleButton();
			_inventoryOwner = null;
			_refreshRequested = false;
			_overlayReadyLogged = false;
		}

		internal static void RequestRefresh()
		{
			_refreshRequested = true;
		}

		internal static void ShowToast(string message)
		{
			if (string.IsNullOrWhiteSpace(message) || (Object)(object)Player.m_localPlayer == (Object)null)
			{
				return;
			}
			try
			{
				((Character)Player.m_localPlayer).Message((MessageType)1, message.Trim(), 0, (Sprite)null, false);
			}
			catch (Exception ex)
			{
				ModLog.Debug("Could not show title notification through MessageHud: " + ex.Message);
			}
		}

		internal static void OnInventoryGuiAwake(InventoryGui gui)
		{
			if (!((Object)(object)gui == (Object)null))
			{
				if ((Object)(object)_inventoryOwner != (Object)null && (Object)(object)_inventoryOwner != (Object)(object)gui)
				{
					TitleConfirmationPopup.Shutdown();
					ClearNativeAchievementButtonHooks();
					DestroyNoTitleButton();
				}
				_inventoryOwner = gui;
			}
		}

		internal static void OnAchievementsOpened(InventoryGui gui)
		{
			if (!((Object)(object)gui == (Object)null))
			{
				_inventoryOwner = gui;
				AchievementWatcher.ScanImmediately("native Achievements menu opened");
				EnsureNoTitleButton(gui);
				UpdateNoTitleButton();
				BindNativeAchievementButtons(gui);
			}
		}

		internal static void OnAchievementsClosed(InventoryGui gui)
		{
			TitleConfirmationPopup.Hide();
			ClearNativeAchievementButtonHooks();
		}

		internal static void OnVanillaAchievementsListUpdated(InventoryGui gui)
		{
			if (!((Object)(object)gui == (Object)null) && IsAchievementsPanelVisible(gui))
			{
				EnsureNoTitleButton(gui);
				UpdateNoTitleButton();
				BindNativeAchievementButtons(gui);
			}
		}

		internal static void OnInventoryHidden()
		{
			TitleConfirmationPopup.Hide();
			ClearNativeAchievementButtonHooks();
		}

		internal static void OnLocalPlayerCleared()
		{
			TitleConfirmationPopup.Hide();
			ClearNativeAchievementButtonHooks();
			RequestRefresh();
		}

		internal static void OnNativeAchievementClicked(Achievement achievement)
		{
			if (!((Object)(object)achievement == (Object)null) && !((Object)(object)Player.m_localPlayer == (Object)null) && (BARConfig.Enabled == null || BARConfig.Enabled.Value))
			{
				AchievementWatcher.ScanImmediately("vanilla achievement button clicked: " + (achievement.m_id ?? "<no id>"));
				TitleDefinition titleDefinition = TitleRewardCatalog.FindForAchievement(achievement);
				if (titleDefinition == null)
				{
					ModLog.Debug("Clicked vanilla achievement has no synchronized title reward: " + (achievement.m_id ?? "<no id>"));
				}
				else if (!achievement.m_unlocked)
				{
					ModLog.Debug("Clicked achievement is not unlocked for title selection: " + (achievement.m_id ?? "<no id>"));
				}
				else if (string.Equals(TitleRuntime.ActiveTitleId, titleDefinition.TitleId, StringComparison.OrdinalIgnoreCase))
				{
					ShowToast("Title already active: " + titleDefinition.DisplayName);
				}
				else
				{
					TitleConfirmationPopup.Show(titleDefinition, achievement);
				}
			}
		}

		private static void BindNativeAchievementButtons(InventoryGui gui)
		{
			//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ae: Expected O, but got Unknown
			//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ba: Expected O, but got Unknown
			ClearNativeAchievementButtonHooks();
			if ((Object)(object)gui == (Object)null || (Object)(object)gui.m_achievementsPanel == (Object)null)
			{
				return;
			}
			AchievementsGui achievementsPanel = gui.m_achievementsPanel;
			if (achievementsPanel.m_achievementsList == null || achievementsPanel.m_achievementsList.Count == 0)
			{
				return;
			}
			List<Achievement> orderedVanillaAchievements = GetOrderedVanillaAchievements();
			if (orderedVanillaAchievements.Count == 0)
			{
				return;
			}
			if (achievementsPanel.m_achievementsList.Count != orderedVanillaAchievements.Count)
			{
				ModLog.WarningOnce("achievement_ui_row_count_mismatch", "Valheim generated " + achievementsPanel.m_achievementsList.Count + " achievement UI row(s), while the current achievement registry exposes " + orderedVanillaAchievements.Count + ". Title overlays will use visible row-name matching first and registry order only as a fallback.");
			}
			int num = 0;
			int num2 = 0;
			for (int i = 0; i < achievementsPanel.m_achievementsList.Count; i++)
			{
				GameObject val = achievementsPanel.m_achievementsList[i];
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				Achievement val2 = ResolveAchievementForRow(val, i, orderedVanillaAchievements);
				TitleDefinition titleDefinition = (((Object)(object)val2 != (Object)null) ? TitleRewardCatalog.FindForAchievement(val2) : null);
				if ((Object)(object)val2 == (Object)null || !val2.m_unlocked || titleDefinition == null)
				{
					SetExistingOverlayActive(val, active: false);
					continue;
				}
				bool created;
				Button val3 = EnsureUnlockedAchievementOverlay(val, out created);
				if (!((Object)(object)val3 == (Object)null))
				{
					if (created)
					{
						num2++;
					}
					Achievement captured = val2;
					UnityAction val4 = (UnityAction)delegate
					{
						OnNativeAchievementClicked(captured);
					};
					val3.onClick = new ButtonClickedEvent();
					((UnityEvent)val3.onClick).AddListener(val4);
					ButtonSfx component = val.GetComponent<ButtonSfx>();
					if ((Object)(object)component != (Object)null)
					{
						component.m_selectable = (Selectable)(object)val3;
					}
					UIGamePad component2 = val.GetComponent<UIGamePad>();
					if ((Object)(object)component2 != (Object)null)
					{
						component2.m_button = val3;
						component2.m_submit = (ISubmitHandler)(object)val3;
					}
					AchievementButtonHooks.Add(new AchievementButtonHook
					{
						Button = val3,
						Listener = val4
					});
					num++;
				}
			}
			if (num > 0 && !_overlayReadyLogged)
			{
				_overlayReadyLogged = true;
				ModLog.Info("Achievement title overlay input ready: " + num + " unlocked reward row(s) are clickable through BAR child overlays.");
			}
			ModLog.Debug("Achievement title overlay input prepared: " + num + " unlocked reward row(s) bound, " + num2 + " overlay child object(s) created.");
		}

		private static Button EnsureUnlockedAchievementOverlay(GameObject row, out bool created)
		{
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Expected O, but got Unknown
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: 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_0193: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
			created = false;
			if ((Object)(object)row == (Object)null)
			{
				return null;
			}
			Transform val = row.transform.Find("BAR_TitleClickOverlay");
			GameObject val2;
			if ((Object)(object)val == (Object)null)
			{
				val2 = new GameObject("BAR_TitleClickOverlay", new Type[4]
				{
					typeof(RectTransform),
					typeof(CanvasRenderer),
					typeof(Image),
					typeof(Button)
				});
				RectTransform component = val2.GetComponent<RectTransform>();
				((Transform)component).SetParent(row.transform, false);
				component.anchorMin = Vector2.zero;
				component.anchorMax = Vector2.one;
				component.pivot = new Vector2(0.5f, 0.5f);
				component.offsetMin = Vector2.zero;
				component.offsetMax = Vector2.zero;
				((Transform)component).localScale = Vector3.one;
				((Transform)component).SetAsLastSibling();
				created = true;
			}
			else
			{
				val2 = ((Component)val).gameObject;
				RectTransform val3 = (RectTransform)(object)((val is RectTransform) ? val : null);
				if ((Object)(object)val3 != (Object)null)
				{
					val3.anchorMin = Vector2.zero;
					val3.anchorMax = Vector2.one;
					val3.offsetMin = Vector2.zero;
					val3.offsetMax = Vector2.zero;
					((Transform)val3).SetAsLastSibling();
				}
			}
			val2.layer = row.layer;
			val2.SetActive(true);
			Image val4 = val2.GetComponent<Image>();
			if ((Object)(object)val4 == (Object)null)
			{
				val4 = val2.AddComponent<Image>();
			}
			val4.sprite = null;
			((Graphic)val4).color = Color.clear;
			((Graphic)val4).raycastTarget = true;
			((MaskableGraphic)val4).maskable = true;
			Button val5 = val2.GetComponent<Button>();
			if ((Object)(object)val5 == (Object)null)
			{
				val5 = val2.AddComponent<Button>();
			}
			((Selectable)val5).interactable = true;
			((Selectable)val5).transition = (Transition)0;
			((Selectable)val5).targetGraphic = (Graphic)(object)val4;
			Button obj = val5;
			Navigation navigation = default(Navigation);
			((Navigation)(ref navigation)).mode = (Mode)3;
			((Selectable)obj).navigation = navigation;
			return val5;
		}

		private static void SetExistingOverlayActive(GameObject row, bool active)
		{
			if (!((Object)(object)row == (Object)null))
			{
				Transform val = row.transform.Find("BAR_TitleClickOverlay");
				if ((Object)(object)val != (Object)null && ((Component)val).gameObject.activeSelf != active)
				{
					((Component)val).gameObject.SetActive(active);
				}
			}
		}

		private static List<Achievement> GetOrderedVanillaAchievements()
		{
			List<Achievement> list = new List<Achievement>();
			Achievements instance = Achievements.m_instance;
			if ((Object)(object)instance == (Object)null || instance.m_achievementLists == null)
			{
				return list;
			}
			for (int i = 0; i < instance.m_achievementLists.Count; i++)
			{
				AchievementList val = instance.m_achievementLists[i];
				if ((Object)(object)val == (Object)null || val.m_achievements == null)
				{
					continue;
				}
				for (int j = 0; j < val.m_achievements.Count; j++)
				{
					Achievement val2 = val.m_achievements[j];
					if ((Object)(object)val2 != (Object)null)
					{
						list.Add(val2);
					}
				}
			}
			return list;
		}

		private static Achievement ResolveAchievementForRow(GameObject row, int index, List<Achievement> orderedAchievements)
		{
			string rowAchievementName = GetRowAchievementName(row);
			if (!string.IsNullOrWhiteSpace(rowAchievementName) && !rowAchievementName.Equals("???", StringComparison.OrdinalIgnoreCase) && rowAchievementName.IndexOf("conceal", StringComparison.OrdinalIgnoreCase) < 0)
			{
				Achievement val = null;
				for (int i = 0; i < orderedAchievements.Count; i++)
				{
					Achievement val2 = orderedAchievements[i];
					if ((Object)(object)val2 == (Object)null)
					{
						continue;
					}
					string text = AchievementApi.Localize(val2.m_name);
					if (string.Equals((text ?? string.Empty).Trim(), rowAchievementName.Trim(), StringComparison.OrdinalIgnoreCase))
					{
						if ((Object)(object)val != (Object)null)
						{
							val = null;
							break;
						}
						val = val2;
					}
				}
				if ((Object)(object)val != (Object)null)
				{
					return val;
				}
			}
			if (index >= 0 && index < orderedAchievements.Count)
			{
				return orderedAchievements[index];
			}
			return null;
		}

		private static string GetRowAchievementName(GameObject row)
		{
			if ((Object)(object)row == (Object)null)
			{
				return string.Empty;
			}
			Transform val = row.transform.Find("name");
			if ((Object)(object)val == (Object)null)
			{
				val = row.transform.Find("Name");
			}
			TMP_Text val2 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent<TMP_Text>() : null);
			return ((Object)(object)val2 != (Object)null) ? (val2.text ?? string.Empty) : string.Empty;
		}

		private static void ClearNativeAchievementButtonHooks()
		{
			for (int num = AchievementButtonHooks.Count - 1; num >= 0; num--)
			{
				AchievementButtonHook achievementButtonHook = AchievementButtonHooks[num];
				if (achievementButtonHook != null)
				{
					try
					{
						if ((Object)(object)achievementButtonHook.Button != (Object)null && achievementButtonHook.Listener != null)
						{
							((UnityEvent)achievementButtonHook.Button.onClick).RemoveListener(achievementButtonHook.Listener);
						}
					}
					catch
					{
					}
				}
			}
			AchievementButtonHooks.Clear();
		}

		private static void EnsureNoTitleButton(InventoryGui gui)
		{
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Expected O, but got Unknown
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Expected O, but got Unknown
			if ((Object)(object)gui == (Object)null || (Object)(object)gui.m_achievementsPanel == (Object)null || ((Object)(object)_noTitleButton != (Object)null && (Object)(object)((Component)_noTitleButton).gameObject != (Object)null && (Object)(object)_inventoryOwner == (Object)(object)gui))
			{
				return;
			}
			DestroyNoTitleButton();
			_inventoryOwner = gui;
			Button val = FindNativeCloseButton(gui, gui.m_achievementsPanel);
			if ((Object)(object)val == (Object)null)
			{
				ModLog.WarningOnce("no_title_button_source", "Could not find Valheim's native Achievements Close button. Achievement title activation still works, but the No Title control could not be created.");
				return;
			}
			GameObject val2 = Object.Instantiate<GameObject>(((Component)val).gameObject, ((Component)val).transform.parent, false);
			if ((Object)(object)val2 == (Object)null)
			{
				return;
			}
			((Object)val2).name = "BAR_NoTitleButton";
			val2.SetActive(true);
			val2.transform.SetAsLastSibling();
			_noTitleButton = val2.GetComponent<Button>();
			if ((Object)(object)_noTitleButton == (Object)null)
			{
				_noTitleButton = val2.GetComponentInChildren<Button>(true);
			}
			if ((Object)(object)_noTitleButton == (Object)null)
			{
				Object.Destroy((Object)(object)val2);
				return;
			}
			_noTitleButton.onClick = new ButtonClickedEvent();
			((UnityEvent)_noTitleButton.onClick).AddListener(new UnityAction(OnNoTitleButtonClicked));
			UIInputHandler[] componentsInChildren = val2.GetComponentsInChildren<UIInputHandler>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				componentsInChildren[i].m_onLeftClick = null;
				componentsInChildren[i].m_onRightClick = null;
				componentsInChildren[i].m_onLeftDown = null;
				componentsInChildren[i].m_onRightDown = null;
			}
			_noTitleButtonText = FindPrimaryButtonText(val2);
			PositionNoTitleButton(val, _noTitleButton);
			UpdateNoTitleButton();
			ModLog.Debug("Created native-styled No Title button beside Valheim's Achievements Close button.");
		}

		private static Button FindNativeCloseButton(InventoryGui gui, AchievementsGui achievementsGui)
		{
			if ((Object)(object)gui == (Object)null || (Object)(object)((Component)gui).gameObject == (Object)null || (Object)(object)achievementsGui == (Object)null)
			{
				return null;
			}
			string b = "Close";
			try
			{
				if (Localization.instance != null)
				{
					b = Localization.instance.Localize("$menu_close");
				}
			}
			catch
			{
			}
			Button[] componentsInChildren = ((Component)gui).gameObject.GetComponentsInChildren<Button>(true);
			Button val = null;
			int num = int.MinValue;
			foreach (Button val2 in componentsInChildren)
			{
				if ((Object)(object)val2 == (Object)null || (Object)(object)((Component)val2).gameObject == (Object)null || ((Object)((Component)val2).gameObject).name == "BAR_NoTitleButton" || !IsUnderNamedAncestor(((Component)val2).transform, "Achievements", ((Component)gui).transform) || ((Object)(object)achievementsGui.m_achievementDetails != (Object)null && ((Component)val2).transform.IsChildOf(achievementsGui.m_achievementDetails.transform)))
				{
					continue;
				}
				int num2 = 0;
				string text = ((Object)((Component)val2).gameObject).name ?? string.Empty;
				if (text.Equals("Closebutton", StringComparison.OrdinalIgnoreCase))
				{
					num2 += 120;
				}
				else if (text.IndexOf("close", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					num2 += 60;
				}
				TMP_Text val3 = FindPrimaryButtonText(((Component)val2).gameObject);
				if ((Object)(object)val3 != (Object)null)
				{
					string a = (val3.text ?? string.Empty).Trim();
					if (string.Equals(a, b, StringComparison.OrdinalIgnoreCase) || string.Equals(a, "Close", StringComparison.OrdinalIgnoreCase))
					{
						num2 += 100;
					}
				}
				if (((Component)val2).gameObject.activeInHierarchy)
				{
					num2 += 30;
				}
				if (num2 > num)
				{
					val = val2;
					num = num2;
				}
			}
			return (num > 0) ? val : null;
		}

		private static bool IsUnderNamedAncestor(Transform transform, string ancestorName, Transform stopAt)
		{
			Transform val = transform;
			while ((Object)(object)val != (Object)null)
			{
				if (((Object)val).name.Equals(ancestorName, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
				if ((Object)(object)val == (Object)(object)stopAt)
				{
					break;
				}
				val = val.parent;
			}
			return false;
		}

		private static TMP_Text FindPrimaryButtonText(GameObject root)
		{
			if ((Object)(object)root == (Object)null)
			{
				return null;
			}
			TMP_Text[] componentsInChildren = root.GetComponentsInChildren<TMP_Text>(true);
			foreach (TMP_Text val in componentsInChildren)
			{
				if ((Object)(object)val != (Object)null && ((Component)val).gameObject.activeInHierarchy)
				{
					return val;
				}
			}
			return (componentsInChildren.Length != 0) ? componentsInChildren[0] : null;
		}

		private static void PositionNoTitleButton(Button source, Button clone)
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			RectTransform val = (RectTransform)(((Object)(object)source != (Object)null) ? /*isinst with value type is only supported in some contexts*/: null);
			RectTransform val2 = (RectTransform)(((Object)(object)clone != (Object)null) ? /*isinst with value type is only supported in some contexts*/: null);
			if (!((Object)(object)val == (Object)null) && !((Object)(object)val2 == (Object)null))
			{
				float[] obj = new float[3] { 180f, 0f, 0f };
				Rect rect = val.rect;
				obj[1] = Mathf.Abs(((Rect)(ref rect)).width);
				obj[2] = Mathf.Abs(val.sizeDelta.x);
				float num = Mathf.Max(obj);
				val2.anchorMin = val.anchorMin;
				val2.anchorMax = val.anchorMax;
				val2.pivot = val.pivot;
				val2.sizeDelta = val.sizeDelta;
				((Transform)val2).localScale = ((Transform)val).localScale;
				val2.anchoredPosition = val.anchoredPosition + new Vector2(0f - (num + 24f), 0f);
			}
		}

		private static void UpdateNoTitleButton()
		{
			if (!((Object)(object)_noTitleButton == (Object)null))
			{
				bool flag = string.IsNullOrEmpty(TitleRuntime.ActiveTitleId);
				((Selectable)_noTitleButton).interactable = !flag;
				if ((Object)(object)_noTitleButtonText != (Object)null)
				{
					_noTitleButtonText.richText = false;
					_noTitleButtonText.text = (flag ? "✓ No Title" : "No Title");
				}
			}
		}

		private static void OnNoTitleButtonClicked()
		{
			if (!string.IsNullOrEmpty(TitleRuntime.ActiveTitleId) && TitleRuntime.SelectTitle(string.Empty))
			{
				ShowToast("Title disabled.");
				UpdateNoTitleButton();
			}
		}

		private static void DestroyNoTitleButton()
		{
			if ((Object)(object)_noTitleButton != (Object)null && (Object)(object)((Component)_noTitleButton).gameObject != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)_noTitleButton).gameObject);
			}
			_noTitleButton = null;
			_noTitleButtonText = null;
		}

		private static bool IsAchievementsPanelVisible(InventoryGui gui)
		{
			return (Object)(object)gui != (Object)null && (Object)(object)gui.m_achievementsPanel != (Object)null && (Object)(object)((Component)gui.m_achievementsPanel).gameObject != (Object)null && ((Component)gui.m_achievementsPanel).gameObject.activeInHierarchy;
		}
	}
	internal static class TitleConfirmationPopup
	{
		private static YesNoPopup _popup;

		private static string _pendingTitleId = string.Empty;

		internal static bool IsVisible => IsOurPopupInStack();

		internal static void Show(TitleDefinition definition, Achievement achievement)
		{
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Expected O, but got Unknown
			//IL_00eb: Expected O, but got Unknown
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Expected O, but got Unknown
			if (definition != null && !string.IsNullOrEmpty(definition.TitleId))
			{
				if (!UnifiedPopup.IsAvailable())
				{
					TitleSelectionGui.ShowToast("Valheim confirmation popup is not available yet.");
					return;
				}
				Hide();
				_pendingTitleId = definition.TitleId;
				string playerName = (((Object)(object)Player.m_localPlayer != (Object)null) ? Player.m_localPlayer.GetPlayerName() : "Player");
				string text = TitleTextFormatter.FormatHoverName(playerName, definition);
				string text2 = (((Object)(object)achievement != (Object)null) ? AchievementApi.Localize(achievement.m_name) : TitleRewardCatalog.GetRequirementText(definition));
				string text3 = "Achievement: " + text2 + "\nTitle reward: " + definition.DisplayName + "\n\nUse this title?\n" + text;
				_popup = new YesNoPopup("Activate title?", text3, new PopupButtonCallback(OnYes), new PopupButtonCallback(OnNo), false, true);
				UnifiedPopup.Push((PopupBase)(object)_popup);
			}
		}

		internal static void Hide()
		{
			if (IsOurPopupTopmost())
			{
				UnifiedPopup.Pop();
			}
			_popup = null;
			_pendingTitleId = string.Empty;
		}

		internal static void Shutdown()
		{
			Hide();
		}

		private static void OnNo()
		{
			if (IsOurPopupTopmost())
			{
				UnifiedPopup.Pop();
			}
			_popup = null;
			_pendingTitleId = string.Empty;
		}

		private static void OnYes()
		{
			string pendingTitleId = _pendingTitleId;
			if (IsOurPopupTopmost())
			{
				UnifiedPopup.Pop();
			}
			_popup = null;
			_pendingTitleId = string.Empty;
			if (!string.IsNullOrEmpty(pendingTitleId) && TitleRewardCatalog.TryGet(pendingTitleId, out var definition) && definition != null && TitleRuntime.SelectTitle(pendingTitleId))
			{
				TitleSelectionGui.ShowToast("Title active: " + definition.DisplayName);
				TitleSelectionGui.RequestRefresh();
				LocalPlayerTitleHud.RequestRefresh();
			}
		}

		private static bool IsOurPopupInStack()
		{
			if (_popup == null || (Object)(object)UnifiedPopup.instance == (Object)null || UnifiedPopup.instance.popupStack == null)
			{
				return false;
			}
			foreach (PopupBase item in UnifiedPopup.instance.popupStack)
			{
				if ((object)item == _popup)
				{
					return true;
				}
			}
			return false;
		}

		private static bool IsOurPopupTopmost()
		{
			if (_popup == null || (Object)(object)UnifiedPopup.instance == (Object)null || UnifiedPopup.instance.popupStack == null || UnifiedPopup.instance.popupStack.Count == 0)
			{
				return false;
			}
			return (object)UnifiedPopup.instance.popupStack.Peek() == _popup;
		}
	}
	internal static class LocalPlayerTitleHud
	{
		private const string LocalHudName = "BAR_LocalPlayerTitleHud";

		private static EnemyHud _enemyHudOwner;

		private static Player _playerOwner;

		private static GameObject _hudObject;

		private static TMP_Text _nameText;

		private static string _lastText;

		private static bool _refreshRequested = true;

		internal static void RequestRefresh()
		{
			_refreshRequested = true;
		}

		internal static void Tick()
		{
			if (BARConfig.Enabled != null && !BARConfig.Enabled.Value)
			{
				SetVisible(visible: false);
				return;
			}
			if (BARConfig.ShowOwnNameplate == null || !BARConfig.ShowOwnNameplate.Value)
			{
				SetVisible(visible: false);
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			EnemyHud instance = EnemyHud.instance;
			if ((Object)(object)localPlayer == (Object)null || (Object)(object)instance == (Object)null || (Object)(object)instance.m_hudRoot == (Object)null || (Object)(object)instance.m_baseHudPlayer == (Object)null)
			{
				SetVisible(visible: false);
			}
			else if (((!((Object)(object)_hudObject == (Object)null) && !((Object)(object)_playerOwner != (Object)(object)localPlayer) && !((Object)(object)_enemyHudOwner != (Object)(object)instance)) || Create(localPlayer, instance)) && !((Object)(object)_nameText == (Object)null) && !((Object)(object)_hudObject == (Object)null))
			{
				if (_refreshRequested)
				{
					_refreshRequested = false;
					_lastText = null;
				}
				UpdateText(localPlayer);
				UpdatePosition(localPlayer);
			}
		}

		internal static void Shutdown()
		{
			if ((Object)(object)_hudObject != (Object)null)
			{
				Object.Destroy((Object)(object)_hudObject);
			}
			_enemyHudOwner = null;
			_playerOwner = null;
			_hudObject = null;
			_nameText = null;
			_lastText = null;
			_refreshRequested = true;
		}

		private static bool Create(Player player, EnemyHud enemyHud)
		{
			Shutdown();
			try
			{
				_enemyHudOwner = enemyHud;
				_playerOwner = player;
				_hudObject = Object.Instantiate<GameObject>(enemyHud.m_baseHudPlayer, enemyHud.m_hudRoot.transform, false);
				if ((Object)(object)_hudObject == (Object)null)
				{
					return false;
				}
				((Object)_hudObject).name = "BAR_LocalPlayerTitleHud";
				_hudObject.SetActive(true);
				DisableChild(_hudObject.transform, "Health");
				DisableChild(_hudObject.transform, "Stamina");
				DisableChild(_hudObject.transform, "level_2");
				DisableChild(_hudObject.transform, "level_3");
				DisableChild(_hudObject.transform, "Alerted");
				DisableChild(_hudObject.transform, "Aware");
				Transform val = _hudObject.transform.Find("Name");
				if ((Object)(object)val != (Object)null)
				{
					_nameText = (TMP_Text)(object)((Component)val).GetComponent<TextMeshProUGUI>();
				}
				if ((Object)(object)_nameText == (Object)null)
				{
					TMP_Text[] componentsInChildren = _hudObject.GetComponentsInChildren<TMP_Text>(true);
					for (int i = 0; i < componentsInChildren.Length; i++)
					{
						if ((Object)(object)componentsInChildren[i] != (Object)null && ((Object)componentsInChildren[i]).name.Equals("Name", StringComparison.OrdinalIgnoreCase))
						{
							_nameText = componentsInChildren[i];
							break;
						}
					}
				}
				if ((Object)(object)_nameText == (Object)null)
				{
					ModLog.WarningOnce("local_nameplate_name_missing", "Valheim's m_baseHudPlayer prefab did not expose its expected Name TMP element. Local self title HUD was disabled; remote title display is unaffected.");
					Shutdown();
					return false;
				}
				_nameText.richText = true;
				((Behaviour)_nameText).enabled = true;
				_refreshRequested = true;
				return true;
			}
			catch (Exception ex)
			{
				ModLog.ErrorOnce("local_nameplate_create", "Could not create local title nameplate from Valheim's native player HUD prefab: " + ex);
				Shutdown();
				return false;
			}
		}

		private static void UpdateText(Player player)
		{
			string text = player.GetPlayerName() ?? string.Empty;
			TitleDefinition activeDefinition = TitleRuntime.GetActiveDefinition();
			if (activeDefinition != null)
			{
				text = TitleTextFormatter.FormatHoverName(text, activeDefinition);
			}
			if (!string.Equals(_lastText, text, StringComparison.Ordinal))
			{
				_lastText = text;
				_nameText.text = text;
			}
		}

		private static void UpdatePosition(Player player)
		{
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: 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_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			Camera mainCamera = Utils.GetMainCamera();
			if ((Object)(object)mainCamera == (Object)null || (Object)(object)player == (Object)null || ((Character)player).IsDead())
			{
				SetVisible(visible: false);
				return;
			}
			if ((Object)(object)Hud.instance != (Object)null && !Hud.instance.IsVisible())
			{
				SetVisible(visible: false);
				return;
			}
			float num = ((BARConfig.OwnNameplateVerticalOffset != null) ? BARConfig.OwnNameplateVerticalOffset.Value : 0.3f);
			Vector3 val = ((Character)player).GetHeadPoint() + Vector3.up * num;
			Vector3 val2 = Utils.WorldToScreenPointScaled(mainCamera, val);
			if (!(val2.z > 0f) || !(val2.x >= 0f) || !(val2.x <= (float)Screen.width) || !(val2.y >= 0f) || !(val2.y <= (float)Screen.height))
			{
				SetVisible(visible: false);
				return;
			}
			_hudObject.transform.position = val2;
			SetVisible(visible: true);
		}

		private static void DisableChild(Transform root, string path)
		{
			if (!((Object)(object)root == (Object)null))
			{
				Transform val = root.Find(path);
				if ((Object)(object)val != (Object)null)
				{
					((Component)val).gameObject.SetActive(false);
				}
			}
		}

		private static void SetVisible(bool visible)
		{
			if ((Object)(object)_hudObject != (Object)null && _hudObject.activeSelf != visible)
			{
				_hudObject.SetActive(visible);
			}
		}
	}
}
namespace BalrondAchievementRewards.Runtime
{
	internal static class CharacterIdentity
	{
		internal static string GetStableId(Player player)
		{
			PlayerProfile currentProfile = GetCurrentProfile();
			if (currentProfile != null)
			{
				long playerID = currentProfile.GetPlayerID();
				if (playerID != 0)
				{
					return "player-id:" + playerID.ToString(CultureInfo.InvariantCulture);
				}
				string filename = currentProfile.GetFilename();
				if (!string.IsNullOrWhiteSpace(filename))
				{
					return "profile-file:" + filename.Trim();
				}
				if (!string.IsNullOrWhiteSpace(currentProfile.m_filename))
				{
					return "profile-file:" + currentProfile.m_filename.Trim();
				}
				string name = currentProfile.GetName();
				if (!string.IsNullOrWhiteSpace(name))
				{
					return "profile-name-fallback:" + name.Trim();
				}
			}
			if ((Object)(object)player != (Object)null)
			{
				string playerName = player.GetPlayerName();
				if (!string.IsNullOrWhiteSpace(playerName))
				{
					return "player-name-fallback:" + playerName.Trim();
				}
			}
			return null;
		}

		internal static PlayerProfile GetCurrentProfile()
		{
			if ((Object)(object)Game.instance == (Object)null)
			{
				return null;
			}
			try
			{
				return Game.instance.GetPlayerProfile();
			}
			catch
			{
				return null;
			}
		}
	}
	internal static class TitleRewardCatalog
	{
		private static readonly List<TitleDefinition> DefinitionsInternal = new List<TitleDefinition>();

		private static readonly Dictionary<string, TitleDefinition> ById = new Dictionary<string, TitleDefinition>(StringComparer.OrdinalIgnoreCase);

		private static uint _fingerprint;

		private static string _lastCoverageSignature;

		internal static IReadOnlyList<TitleDefinition> Definitions => DefinitionsInternal;

		internal static uint Fingerprint => _fingerprint;

		internal static void Initialize()
		{
			RebuildFromConfig();
		}

		internal static void Shutdown()
		{
			DefinitionsInternal.Clear();
			ById.Clear();
			_fingerprint = 0u;
			_lastCoverageSignature = null;
		}

		internal static void RebuildFromConfig()
		{
			string text = ((BARConfig.TitleRewards != null) ? BARConfig.TitleRewards.Value : "id:AllBosses|forsakensbane|Forsaken's Bane;id:AllBossesHard|doomwalker|Doomwalker;id:AllBossesNormal|realmwarden|Realmwarden;id:AllBuildPieces|masterbuilder|Master Builder;id:AllFoodCooked|masterchef|Master Chef;id:AllFoodEaten|ironstomach|Iron Stomach;id:AllItemCraft|masterartisan|Master Artisan;id:AllMiniBosses|legendhunter|Legend Hunter;id:AllWeaponCraft|arsenalkeeper|Arsenal Keeper;id:Arrived|newblood|New Blood;id:BigFish|masterangler|Master Angler;id:Boss1Eikthyr|stormtouched|Stormtouched;id:Boss2Elder|rootbreaker|Rootbreaker;id:Boss3Bonemass|lightbringer|Lightbringer;id:Boss4Moder|dragonsbane|Dragonsbane;id:Boss5Yagluth|kingsbane|Kingsbane;id:Boss6Queen|hivebreaker|Hivebreaker;id:Boss7Fader|flamebreaker|Flamebreaker;id:Boss8FrozenKing|chainbreaker|Chainbreaker;id:BuildDyrnwyn|flameforged|Flameforged;id:BuildHigh|skybuilder|Skybuilder;id:BuildHighWorld|cloudpiercer|Cloudpiercer;id:BuildHouse|homesteader|Homesteader;id:BuildVillage|hearthjarl|Hearth Jarl;id:ChildOfOdin|odinschosen|Odin's Chosen;id:DeathByAllTypes|deathscompanion|Death's Companion;id:DeathByObliterator|atomized|Atomized;id:DeathByTree|timbered|Timbered;id:DeathByTreeAll|treemagnet|Tree Magnet;id:ExploreNSEW|wayfinder|Wayfinder;id:ExploreNSEWNoMap|pathless|Pathless;id:FindAllTrophies|trophykeeper|Trophy Keeper;id:FirstDeath|returned|Returned;id:GrindBuild|architect|Architect;id:GrindComfort|hearthkeeper|Hearthkeeper;id:GrindCook|cookfireveteran|Cookfire Veteran;id:GrindDays|survivor|Survivor;id:GrindEat|bottomless|Bottomless;id:GrindFish|fishmaster|Fishmaster;id:GrindForager|wildharvester|Wild Harvester;id:GrindHarvest|harvestlord|Harvest Lord;id:GrindHoney|beekeeper|Beekeeper;id:GrindLeviathan|leviathansbane|Leviathan's Bane;id:GrindSail|searover|Sea Rover;id:GrindSailHelm|helmsman|Helmsman;id:GrindTreasure|treasurehunter|Treasure Hunter;id:GrindTrees|woodsman|Woodsman;id:KillAllCreatures|beastbane|Beastbane;id:KillAllCreaturesHard|apexhunter|Apex Hunter;id:KingsQueens|crownbreaker|Crownbreaker;id:MultiplayerBoss|oathbound|Oathbound;id:SailEdge|edgewalker|Edgewalker;id:SoloBoss|lonewolf|Lone Wolf");
			_fingerprint = UnsafeTextHash.Compute(text ?? string.Empty);
			DefinitionsInternal.Clear();
			ById.Clear();
			_lastCoverageSignature = null;
			if (string.IsNullOrWhiteSpace(text))
			{
				ModLog.Warning("Title reward catalog is empty.");
				return;
			}
			string[] array = text.Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				if (!TryParse(array[i], out var definition, out var error))
				{
					ModLog.Warning("Skipping title reward entry #" + (i + 1).ToString(CultureInfo.InvariantCulture) + ": " + error);
					continue;
				}
				if (ById.ContainsKey(definition.TitleId))
				{
					ModLog.Warning("Skipping duplicate TitleId '" + definition.TitleId + "'. Title IDs must be unique.");
					continue;
				}
				DefinitionsInternal.Add(definition);
				ById.Add(definition.TitleId, definition);
			}
			ResolveRequirements(AchievementWatcher.LastScan);
			ModLog.Info("Loaded " + DefinitionsInternal.Count.ToString(CultureInfo.InvariantCulture) + " vanilla-achievement title reward(s). Catalog fingerprint 0x" + _fingerprint.ToString("X8", CultureInfo.InvariantCulture) + ".");
		}

		internal static bool TryGet(string titleId, out TitleDefinition definition)
		{
			definition = null;
			if (string.IsNullOrWhiteSpace(titleId))
			{
				return false;
			}
			return ById.TryGetValue(titleId.Trim(), out definition);
		}

		internal static TitleDefinition FindForAchievement(Achievement achievement)
		{
			if ((Object)(object)achievement == (Object)null)
			{
				return null;
			}
			string text = ((achievement.m_id == null) ? string.Empty : achievement.m_id.Trim());
			string text2 = (string.IsNullOrEmpty(text) ? string.Empty : ("id:" + text));
			string text3 = ((achievement.m_name == null) ? string.Empty : achievement.m_name.Trim());
			TitleDefinition result = null;
			for (int i = 0; i < DefinitionsInternal.Count; i++)
			{
				TitleDefinition titleDefinition = DefinitionsInternal[i];
				if (!string.IsNullOrEmpty(text2) && string.Equals(titleDefinition.ResolvedAchievementKey, text2, StringComparison.Ordinal))
				{
					return titleDefinition;
				}
				if (titleDefinition.MatchMode == AchievementMatchMode.Id && !string.IsNullOrEmpty(text) && string.Equals(titleDefinition.AchievementMatcher, text, StringComparison.OrdinalIgnoreCase))
				{
					return titleDefinition;
				}
				if (titleDefinition.MatchMode == AchievementMatchMode.Name && !string.IsNullOrEmpty(text3) && string.Equals(titleDefinition.AchievementMatcher, text3, StringComparison.OrdinalIgnoreCase))
				{
					result = titleDefinition;
				}
			}
			return result;
		}

		internal static void ResolveRequirements(AchievementScanResult scan)
		{
			for (int i = 0; i < DefinitionsInternal.Count; i++)
			{
				TitleDefinition titleDefinition = DefinitionsInternal[i];
				titleDefinition.ResolvedAchievementKey = null;
				titleDefinition.ResolvedAchievementDisplayName = null;
				titleDefinition.AmbiguousRequirement = false;
				if (scan == null || scan.Achievements == null || scan.Achievements.Count == 0)
				{
					continue;
				}
				AchievementSnapshot achievementSnapshot = null;
				int num = 0;
				bool flag = false;
				for (int j = 0; j < scan.Achievements.Count; j++)
				{
					AchievementSnapshot achievementSnapshot2 = scan.Achievements[j];
					int matchScore = GetMatchScore(titleDefinition, achievementSnapshot2);
					if (matchScore > 0)
					{
						if (matchScore > num)
						{
							achievementSnapshot = achievementSnapshot2;
							num = matchScore;
							flag = false;
						}
						else if (matchScore == num && achievementSnapshot != null && !string.Equals(achievementSnapshot.StableKey, achievementSnapshot2.StableKey, StringComparison.Ordinal))
						{
							flag = true;
						}
					}
				}
				if (achievementSnapshot != null && !flag)
				{
					titleDefinition.ResolvedAchievementKey = achievementSnapshot.StableKey;
					titleDefinition.ResolvedAchievementDisplayName = achievementSnapshot.DisplayName;
					ModLog.Debug("Resolved title '" + titleDefinition.TitleId + "' -> vanilla achievement '" + achievementSnapshot.DisplayName + "' [" + achievementSnapshot.StableKey + "] score=" + num.ToString(CultureInfo.InvariantCulture));
				}
				else if (flag)
				{
					titleDefinition.AmbiguousRequirement = true;
					ModLog.WarningOnce("ambiguous_" + titleDefinition.TitleId, "Title '" + titleDefinition.TitleId + "' matcher '" + titleDefinition.AchievementMatcher + "' matches multiple vanilla achievements equally. Prefer an id: matcher.");
				}
				else
				{
					ModLog.WarningOnce("unresolved_" + titleDefinition.TitleId, "Title '" + titleDefinition.TitleId + "' could not resolve vanilla achievement matcher '" + titleDefinition.AchievementMatcher + "'.");
				}
			}
			ValidateCoverage(scan);
		}

		internal static bool IsUnlocked(TitleDefinition definition, AchievementScanResult scan)
		{
			if (definition == null || scan == null || scan.Achievements == null || string.IsNullOrEmpty(definition.ResolvedAchievementKey))
			{
				return false;
			}
			for (int i = 0; i < scan.Achievements.Count; i++)
			{
				AchievementSnapshot achievementSnapshot = scan.Achievements[i];
				if (string.Equals(achievementSnapshot.StableKey, definition.ResolvedAchievementKey, StringComparison.Ordinal))
				{
					return achievementSnapshot.Unlocked;
				}
			}
			return false;
		}

		internal static string GetRequirementText(TitleDefinition definition)
		{
			if (definition == null)
			{
				return string.Empty;
			}
			return (!string.IsNullOrWhiteSpace(definition.ResolvedAchievementDisplayName)) ? definition.ResolvedAchievementDisplayName : (definition.AchievementMatcher ?? string.Empty);
		}

		private static void ValidateCoverage(AchievementScanResult scan)
		{
			if (scan == null || scan.Achievements == null || scan.Achievements.Count == 0)
			{
				return;
			}
			int num = 0;
			List<string> list = new List<string>();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			for (int i = 0; i < scan.Achievements.Count; i++)
			{
				AchievementSnapshot achievementSnapshot = scan.Achievements[i];
				bool flag = false;
				for (int j = 0; j < DefinitionsInternal.Count; j++)
				{
					TitleDefinition titleDefinition = DefinitionsInternal[j];
					if (!string.IsNullOrEmpty(titleDefinition.ResolvedAchievementKey) && string.Equals(titleDefinition.ResolvedAchievementKey, achievementSnapshot.StableKey, StringComparison.Ordinal))
					{
						flag = true;
						hashSet.Add(titleDefinition.ResolvedAchievementKey);
						break;
					}
				}
				if (flag)
				{
					num++;
				}
				else
				{
					list.Add((!string.IsNullOrWhiteSpace(achievementSnapshot.InternalId)) ? achievementSnapshot.InternalId : achievementSnapshot.StableKey);
				}
			}
			string text = num.ToString(CultureInfo.InvariantCulture) + "/" + scan.Achievements.Count.ToString(CultureInfo.InvariantCulture) + ":" + string.Join(",", list.ToArray());
			if (!string.Equals(_lastCoverageSignature, text, StringComparison.Ordinal))
			{
				_lastCoverageSignature = text;
				ModLog.Info("Vanilla achievement title coverage: " + num.ToString(CultureInfo.InvariantCulture) + "/" + scan.Achievements.Count.ToString(CultureInfo.InvariantCulture) + ".");
				if (list.Count > 0)
				{
					ModLog.Warning("Vanilla achievements without a configured title: " + string.Join(", ", list.ToArray()));
				}
			}
		}

		private static bool TryParse(string rawEntry, out TitleDefinition definition, out string error)
		{
			definition = null;
			error = null;
			string[] array = rawEntry.Split(new char[1] { '|' }, StringSplitOptions.None);
			if (array.Length < 3 || array.Length > 5)
			{
				error = "expected 3-5 '|' separated fields, got " + array.Length.ToString(CultureInfo.InvariantCulture) + ".";
				return false;
			}
			string text = (array[0] ?? string.Empty).Trim();
			string text2 = (array[1] ?? string.Empty).Trim();
			string text3 = (array[2] ?? string.Empty).Trim();
			string text4 = ((array.Length >= 4) ? (array[3] ?? string.Empty).Trim() : "{player} the {title}");
			string colorHex = ((array.Length >= 5) ? (array[4] ?? string.Empty).Trim() : string.Empty);
			if (string.IsNullOrWhiteSpace(text))
			{
				error = "vanilla achievement matcher is empty.";
				return false;
			}
			if (!IsValidTitleId(text2))
			{
				error = "TitleId '" + text2 + "' is invalid. Use only letters, numbers, '.', '_' or '-'.";
				return false;
			}
			if (string.IsNullOrWhiteSpace(text3))
			{
				error = "display name is empty.";
				return false;
			}
			if (text3.IndexOf('|') >= 0 || text3.IndexOf(';') >= 0)
			{
				error = "display name contains a reserved delimiter ('|' or ';').";
				return false;
			}
			if (string.IsNullOrWhiteSpace(text4))
			{
				text4 = "{player} the {title}";
			}
			if (text4.IndexOf("{player}", StringComparison.OrdinalIgnoreCase) < 0 || text4.IndexOf("{title}", StringComparison.OrdinalIgnoreCase) < 0)
			{
				error = "NameFormat must contain both {player} and {title}.";
				return false;
			}
			int num = text.IndexOf(':');
			string text5 = ((num > 0) ? text.Substring(0, num).Trim() : "auto");
			string text6 = ((num > 0) ? text.Substring(num + 1).Trim() : text);
			AchievementMatchMode matchMode;
			if (text5.Equals("id", StringComparison.OrdinalIgnoreCase))
			{
				matchMode = AchievementMatchMode.Id;
			}
			else if (text5.Equals("name", StringComparison.OrdinalIgnoreCase))
			{
				matchMode = AchievementMatchMode.Name;
			}
			else if (text5.Equals("auto", StringComparison.OrdinalIgnoreCase))
			{
				matchMode = AchievementMatchMode.Auto;
			}
			else
			{
				if (num > 0)
				{
					error = "unsupported matcher prefix '" + text5 + "'. BalrondAchievementRewards 0.3.2 supports VANILLA achievements only: id:, name:, auto:.";
					return false;
				}
				matchMode = AchievementMatchMode.Auto;
				text6 = text;
			}
			if (string.IsNullOrWhiteSpace(text6))
			{
				error = "achievement matcher is empty after its prefix.";
				return false;
			}
			definition = new TitleDefinition
			{
				MatchMode = matchMode,
				AchievementMatcher = text6,
				TitleId = text2,
				DisplayName = text3,
				NameFormat = text4,
				ColorHex = colorHex
			};
			return true;
		}

		private static bool IsValidTitleId(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return false;
			}
			foreach (char c in value)
			{
				if (!char.IsLetterOrDigit(c) && c != '.' && c != '_' && c != '-')
				{
					return false;
				}
			}
			return true;
		}

		private static int GetMatchScore(TitleDefinition definition, AchievementSnapshot snapshot)
		{
			if (definition == null || snapshot == null)
			{
				return 0;
			}
			string matcher = definition.AchievementMatcher ?? string.Empty;
			if (definition.MatchMode == AchievementMatchMode.Id)
			{
				return ScoreId(matcher, snapshot);
			}
			if (definition.MatchMode == AchievementMatchMode.Name)
			{
				return ScoreName(matcher, snapshot);
			}
			int num = ScoreId(matcher, snapshot);
			if (num > 0)
			{
				return num;
			}
			return ScoreName(matcher, snapshot);
		}

		private static int ScoreId(string matcher, AchievementSnapshot snapshot)
		{
			if (EqualsLoose(matcher, snapshot.InternalId))
			{
				return 100;
			}
			if (EqualsLoose(matcher, snapshot.PlatformId))
			{
				return 98;
			}
			if (EqualsLoose(matcher, snapshot.StableKey))
			{
				return 96;
			}
			string text = Normalize(matcher);
			if (text.Length == 0)
			{
				return 0;
			}
			if (Normalize(snapshot.InternalId) == text)
			{
				return 92;
			}
			if (Normalize(snapshot.PlatformId) == text)
			{
				return 90;
			}
			if (Normalize(snapshot.StableKey) == text)
			{
				return 88;
			}
			return 0;
		}

		private static int ScoreName(string matcher, AchievementSnapshot snapshot)
		{
			if (EqualsLoose(matcher, snapshot.DisplayName))
			{
				return 86;
			}
			if (EqualsLoose(matcher, snapshot.NameTextId))
			{
				return 84;
			}
			string text = ReduceName(Normalize(matcher));
			if (text.Length == 0)
			{
				return 0;
			}
			if (ReduceName(Normalize(snapshot.DisplayName)) == text)
			{
				return 82;
			}
			if (ReduceName(Normalize(snapshot.NameTextId)) == text)
			{
				return 80;
			}
			return 0;
		}

		private static bool EqualsLoose(string a, string b)
		{
			return !string.IsNullOrWhiteSpace(a) && !string.IsNullOrWhiteSpace(b) && string.Equals(a.Trim(), b.Trim(), StringComparison.OrdinalIgnoreCase);
		}

		private static string Normalize(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return string.Empty;
			}
			StringBuilder stringBuilder = new StringBuilder(value.Length);
			foreach (char c in value)
			{
				if (char.IsLetterOrDigit(c))
				{
					stringBuilder.Append(char.ToLowerInvariant(c));
				}
			}
			return stringBuilder.ToString();
		}

		private static string ReduceName(string normalized)
		{
			if (string.IsNullOrEmpty(normalized))
			{
				return string.Empty;
			}
			string text = normalized;
			text = text.Replace("achievements", string.Empty).Replace("achievement", string.Empty).Replace("name", string.Empty)
				.Replace("title", string.Empty);
			if (text.StartsWith("the", StringComparison.Ordinal) && text.Length > 3)
			{
				text = text.Substring(3);
			}
			return text;
		}
	}
	internal static class TitleRuntime
	{
		private static readonly HashSet<string> UnlockedTitleIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static Player _localPlayer;

		private static string _characterIdentity;

		private static string _preferredTitleId = string.Empty;

		private static string _activeTitleId = string.Empty;

		private static float _nextPublishTime;

		private static bool _initialized;

		internal static string ActiveTitleId => _activeTitleId;

		internal static string PreferredTitleId => _preferredTitleId;

		internal static string CharacterIdentityKey => _characterIdentity;

		internal static void Initialize()
		{
			UnlockedTitleIds.Clear();
			_localPlayer = null;
			_characterIdentity = null;
			_preferredTitleId = string.Empty;
			_activeTitleId = string.Empty;
			_nextPublishTime = 0f;
			_initialized = true;
		}

		internal static void Shutdown()
		{
			if ((Object)(object)_localPlayer != (Object)null)
			{
				try
				{
					TitleNetwork.Publish(_localPlayer, string.Empty);
				}
				catch
				{
				}
			}
			UnlockedTitleIds.Clear();
			_localPlayer = null;
			_characterIdentity = null;
			_preferredTitleId = string.Empty;
			_activeTitleId = string.Empty;
			_nextPublishTime = 0f;
			_initialized = false;
		}

		internal static void Tick(float now)
		{
			if (!_initialized)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)(object)_localPlayer)
			{
				if ((Object)(object)localPlayer != (Object)null)
				{
					OnLocalPlayerSpawned(localPlayer);
				}
				else
				{
					ClearLocalPlayer();
				}
			}
			if ((Object)(object)_localPlayer != (Object)null && now >= _nextPublishTime)
			{
				_nextPublishTime = now + 5f;
				TitleNetwork.Publish(_localPlayer, _activeTitleId);
			}
		}

		internal static void OnLocalPlayerSpawned(Player player)
		{
			if (_initialized && !((Object)(object)player == (Object)null))
			{
				_localPlayer = player;
				_characterIdentity = CharacterIdentity.GetStableId(player);
				_preferredTitleId = TitleSelectionStore.GetPreferredTitle(_characterIdentity);
				_activeTitleId = string.Empty;
				_nextPublishTime = 0f;
				AchievementWatcher.MarkDirty("local player spawned");
				RefreshUnlocks(AchievementWatcher.LastScan, initial: true);
				EvaluateActiveTitle(immediatePublish: false);
				TitleSelectionGui.RequestRefresh();
				LocalPlayerTitleHud.RequestRefresh();
				ModLog.Debug("Local character identity resolved: " + (_characterIdentity ?? "<none>") + "; preferred title='" + _preferredTitleId + "'.");
			}
		}

		internal static void OnAchievementSnapshot(AchievementScanResult scan, IList<string> newlyUnlockedAchievementKeys, bool initial)
		{
			if (_initialized)
			{
				RefreshUnlocks(scan, initial);
			}
		}

		internal static void OnRewardConfigurationChanged()
		{
			RefreshUnlocks(AchievementWatcher.LastScan, initial: true);
		}

		internal static bool IsTitleUnlocked(string titleId)
		{
			return !string.IsNullOrWhiteSpace(titleId) && UnlockedTitleIds.Contains(titleId.Trim());
		}

		internal static bool SelectTitle(string titleId)
		{
			if ((Object)(object)_localPlayer == (Object)null || string.IsNullOrWhiteSpace(_characterIdentity))
			{
				return false;
			}
			string text = ((titleId == null) ? string.Empty : titleId.Trim());
			if (!string.IsNullOrEmpty(text) && (!TitleRewardCatalog.TryGet(text, out var _) || !IsTitleUnlocked(text)))
			{
				ModLog.Warning("Rejected title selection '" + text + "' because its vanilla achievement is not unlocked in the synchronized catalog.");
				return false;
			}
			_preferredTitleId = text;
			TitleSelectionStore.SetPreferredTitle(_characterIdentity, _preferredTitleId);
			EvaluateActiveTitle(immediatePublish: true);
			TitleSelectionGui.RequestRefresh();
			LocalPlayerTitleHud.RequestRefresh();
			return true;
		}

		internal static TitleDefinition GetActiveDefinition()
		{
			TitleDefinition definition;
			return TitleRewardCatalog.TryGet(_activeTitleId, out definition) ? definition : null;
		}

		internal static string GetStatusText()
		{
			if (BARConfig.Enabled != null && !BARConfig.Enabled.Value)
			{
				return "Achievement title rewards are disabled by the server.";
			}
			AchievementScanResult lastScan = AchievementWatcher.LastScan;
			if (lastScan == null || !lastScan.ManagerAvailable)
			{
				return "Vanilla achievements are still initializing.";
			}
			if (!lastScan.PlatformInitialized)
			{
				return "Platform achievements are still initializing.";
			}
			if (lastScan.CanGetAchievements.HasValue && !lastScan.CanGetAchievements.Value)
			{
				return "Valheim currently blocks achievement progress for this character/session.";
			}
			if (!string.IsNullOrEmpty(lastScan.Error) && lastScan.Achievements.Count == 0)
			{
				return lastScan.Error;
			}
			if (!string.IsNullOrEmpty(_preferredTitleId) && string.IsNullOrEmpty(_activeTitleId))
			{
				return "Preferred title is not currently available or its vanilla achievement is no longer unlocked.";
			}
			return "Unlocked titles: " + UnlockedTitleIds.Count.ToString(CultureInfo.InvariantCulture) + "/" + TitleRewardCatalog.Definitions.Count.ToString(CultureInfo.InvariantCulture);
		}

		private static void RefreshUnlocks(AchievementScanResult scan, bool initial)
		{
			if (!_initialized)
			{
				return;
			}
			HashSet<string> hashSet = new HashSet<string>(UnlockedTitleIds, StringComparer.OrdinalIgnoreCase);
			string activeTitleId = _activeTitleId;
			UnlockedTitleIds.Clear();
			if (BARConfig.Enabled != null && BARConfig.Enabled.Value)
			{
				IReadOnlyList<TitleDefinition> definitions = TitleRewardCatalog.Definitions;
				for (int i = 0; i < definitions.Count; i++)
				{
					TitleDefinition titleDefinition = definitions[i];
					if (TitleRewardCatalog.IsUnlocked(titleDefinition, scan))
					{
						UnlockedTitleIds.Add(titleDefinition.TitleId);
					}
				}
			}
			EvaluateActiveTitle(immediatePublish: false);
			if (!hashSet.SetEquals(UnlockedTitleIds) || !string.Equals(activeTitleId, _activeTitleId, StringComparison.OrdinalIgnoreCase))
			{
				TitleSelectionGui.RequestRefresh();
				LocalPlayerTitleHud.RequestRefresh();
			}
			if (!initial)
			{
				foreach (string unlockedTitleId in UnlockedTitleIds)
				{
					if (!hashSet.Contains(unlockedTitleId) && TitleRewardCatalog.TryGet(unlockedTitleId, out var definition))
					{
						TitleSelectionGui.ShowToast("Title unlocked: " + definition.DisplayName);
					}
				}
			}
			ModLog.Debug("Vanilla achievement reward snapshot applied. Unlocked titles=" + UnlockedTitleIds.Count.ToString(CultureInfo.InvariantCulture) + ", active='" + _activeTitleId + "'.");
		}

		private static void EvaluateActiveTitle(bool immediatePublish)
		{
			string text = string.Empty;
			if (BARConfig.Enabled != null && BARConfig.Enabled.Value && !string.IsNullOrWhiteSpace(_preferredTitleId) && IsTitleUnlocked(_preferredTitleId) && TitleRewardCatalog.TryGet(_preferredTitleId, out var definition))
			{
				text = definition.TitleId;
			}
			if (!string.Equals(_activeTitleId, text, StringComparison.OrdinalIgnoreCase))
			{
				_activeTitleId = text;
				immediatePublish = true;
				LocalPlayerTitleHud.RequestRefresh();
			}
			if (immediatePublish && (Object)(object)_localPlayer != (Object)null)
			{
				TitleNetwork.Publish(_localPlayer, _activeTitleId);
				_nextPublishTime = Time.unscaledTime + 5f;
			}
		}

		private static void ClearLocalPlayer()
		{
			_localPlayer = null;
			_characterIdentity = null;
			_preferredTitleId = string.Empty;
			_activeTitleId = string.Empty;
			_nextPublishTime = 0f;
			TitleSelectionGui.OnLocalPlayerCleared();
			LocalPlayerTitleHud.Shutdown();
		}
	}
	internal static class TitleSelectionStore
	{
		private const string SelectionMagic = "BAR_TITLE_SELECTIONS_V1";

		private static readonly Dictionary<string, string> SelectionByIdentityHash = new Dictionary<string, string>(StringComparer.Ordinal);

		private static string _selectionPath;

		private static bool _selectionDirty;

		private static bool _initialized;

		internal static void Initialize()
		{
			string text = Path.Combine(Paths.ConfigPath, "BalrondAchievementRewards");
			Directory.CreateDirectory(text);
			_selectionPath = Path.Combine(text, "character_titles.v1.tsv");
			SelectionByIdentityHash.Clear();
			_selectionDirty = false;
			LoadSelections();
			_initialized = true;
		}

		internal static string GetPreferredTitle(string stableIdentity)
		{
			if (!_initialized || string.IsNullOrWhiteSpace(stableIdentity))
			{
				return string.Empty;
			}
			string value;
			return SelectionByIdentityHash.TryGetValue(HashIdentity(stableIdentity), out value) ? value : string.Empty;
		}

		internal static void SetPreferredTitle(string stableIdentity, string titleId)
		{
			if (!_initialized || string.IsNullOrWhiteSpace(stableIdentity))
			{
				return;
			}
			string key = HashIdentity(stableIdentity);
			string text = ((titleId == null) ? string.Empty : titleId.Trim());
			if (!SelectionByIdentityHash.TryGetValue(key, out var value) || !string.Equals(value, text, StringComparison.OrdinalIgnoreCase))
			{
				if (string.IsNullOrEmpty(text))
				{
					SelectionByIdentityHash.Remove(key);
				}
				else
				{
					SelectionByIdentityHash[key] = text;
				}
				_selectionDirty = true;
				Flush();
			}
		}

		internal static void Flush()
		{
			if (!_initialized || !_selectionDirty || string.IsNullOrEmpty(_selectionPath))
			{
				return;
			}
			try
			{
				StringBuilder stringBuilder = new StringBuilder(2048);
				stringBuilder.AppendLine("BAR_TITLE_SELECTIONS_V1");
				foreach (KeyValuePair<string, string> item in SelectionByIdentityHash)
				{
					stringBuilder.Append(item.Key).Append('\t').Append(ToBase64(item.Value ?? string.Empty))
						.AppendLine();
				}
				AtomicWrite(_selectionPath, stringBuilder.ToString());
				_selectionDirty = false;
			}
			catch (Exception ex)
			{
				ModLog.Warning("Could not persist selected title state: " + ex.Message);
			}
		}

		private static void LoadSelections()
		{
			if (string.IsNullOrEmpty(_selectionPath) || !File.Exists(_selectionPath))
			{
				return;
			}
			try
			{
				string[] array = File.ReadAllLines(_selectionPath);
				if (array.Length == 0 || !string.Equals(array[0].Trim(), "BAR_TITLE_SELECTIONS_V1", StringComparison.Ordinal))
				{
					ModLog.Warning("Ignoring unknown title selection file format at " + _selectionPath);
					return;
				}
				for (int i = 1; i < array.Length; i++)
				{
					if (!TrySplitLine(array[i], out var key, out var payload))
					{
						continue;
					}
					try
					{
						string text = FromBase64(payload);
						if (!string.IsNullOrWhiteSpace(key) && !string.IsNullOrWhiteSpace(text))
						{
							SelectionByIdentityHash[key] = text.Trim();
						}
					}
					catch
					{
					}
				}
			}
			catch (Exception ex)
			{
				ModLog.Warning("Could not load title selection state: " + ex.Message);
			}
		}

		private static bool TrySplitLine(string line, out string key, out string payload)
		{
			key = null;
			payload = null;
			if (string.IsNullOrWhiteSpace(line))
			{
				return false;
			}
			int num = line.IndexOf('\t');
			if (num <= 0 || num >= line.Length - 1)
			{
				return false;
			}
			key = line.Substring(0, num).Trim();
			payload = line.Substring(num + 1).Trim();
			return key.Length > 0 && payload.Length > 0;
		}

		private static void AtomicWrite(string path, string content)
		{
			string text = path + ".tmp";
			File.WriteAllText(text, content, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			File.Copy(text, path, overwrite: true);
			File.Delete(text);
		}

		private static string ToBase64(string value)
		{
			return Convert.ToBase64String(Encoding.UTF8.GetBytes(value ?? string.Empty));
		}

		private static string FromBase64(string value)
		{
			return Encoding.UTF8.GetString(Convert.FromBase64String(value ?? string.Empty));
		}

		private static string HashIdentity(string stableIdentity)
		{
			byte[] bytes = Encoding.UTF8.GetBytes(stableIdentity ?? string.Empty);
			using SHA256 sHA = SHA256.Create();
			byte[] array = sHA.ComputeHash(bytes);
			StringBuilder stringBuilder = new StringBuilder(32);
			for (int i = 0; i < 16; i++)
			{
				stringBuilder.Append(array[i].ToString("x2"));
			}
			return stringBuilder.ToString();
		}
	}
	internal static class TitleTextFormatter
	{
		internal static string FormatHoverName(string playerName, TitleDefinition definition)
		{
			if (definition == null)
			{
				return playerName ?? string.Empty;
			}
			string text = playerName ?? string.Empty;
			string text2 = SanitizeLiteral(definition.DisplayName);
			string text3 = (string.IsNullOrWhiteSpace(definition.NameFormat) ? "{player} the {title}" : SanitizeFormat(definition.NameFormat));
			if (BARConfig.UseTitleColors != null && BARConfig.UseTitleColors.Value && definition.TryGetColor(out var _))
			{
				string text4 = NormalizeColorHex(definition.ColorHex);
				text2 = "<color=" + text4 + ">" + text2 + "</color>";
			}
			string text5 = text3.Replace("{player}", text).Replace("{title}", text2);
			if (text5.IndexOf("{player}", StringComparison.OrdinalIgnoreCase) >= 0 || text5.IndexOf("{title}", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return text + " " + text2;
			}
			return text5.Trim();
		}

		internal static string SanitizeLiteral(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return string.Empty;
			}
			return value.Replace('<', '‹').Replace('>', '›');
		}

		private static string SanitizeFormat(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return "{player} the {title}";
			}
			StringBuilder stringBuilder = new StringBuilder(value.Length);
			foreach (char c in value)
			{
				switch (c)
				{
				case '<':
					stringBuilder.Append('‹');
					break;
				case '>':
					stringBuilder.Append('›');
					break;
				default:
					stringBuilder.Append(c);
					break;
				}
			}
			return stringBuilder.ToString();
		}

		private static string NormalizeColorHex(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return "#FFFFFF";
			}
			string text = value.Trim();
			if (!text.StartsWith("#", StringComparison.Ordinal))
			{
				text = "#" + text;
			}
			return text;
		}
	}
}
namespace BalrondAchievementRewards.Patches
{
	internal static class PatchInstaller
	{
		internal static void Install(Harmony harmony)
		{
			if (harmony == null)
			{
				throw new ArgumentNullException("harmony");
			}
			harmony.PatchAll(typeof(PatchInstaller).Assembly);
			ModLog.Info("Installed direct publicized Valheim API patches for vanilla achievement rewards, child-overlay click-to-title selection on unlocked AchElements, and player title presentation.");
		}
	}
	[HarmonyPatch(typeof(Player), "OnSpawned", new Type[] { typeof(bool) })]
	internal static class PlayerOnSpawnedPatch
	{
		private static void Postfix(Player __instance)
		{
			if ((Object)(object)__instance != (Object)null && (Object)(object)__instance == (Object)(object)Player.m_localPlayer)
			{
				TitleRuntime.OnLocalPlayerSpawned(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(Player), "GetHoverName")]
	internal static class PlayerGetHoverNamePatch
	{
		[HarmonyPriority(0)]
		private static void Postfix(Player __instance, ref string __result)
		{
			if (!((Object)(object)__instance == (Object)null) && BARConfig.ShowTitlesAbovePlayers != null && BARConfig.ShowTitlesAbovePlayers.Value && (BARConfig.Enabled == null || BARConfig.Enabled.Value))
			{
				string text = TitleNetwork.ReadPublishedTitle(__instance);
				if (!string.IsNullOrEmpty(text) && TitleRewardCatalog.TryGet(text, out var definition))
				{
					__result = TitleTextFormatter.FormatHoverName(__result, definition);
				}
			}
		}
	}
	[HarmonyPatch(typeof(Achievements), "SetPlatformInitialized")]
	internal static class AchievementsSetPlatformInitializedPatch
	{
		private static void Postfix()
		{
			AchievementWatcher.MarkDirty("Achievements.SetPlatformInitialized");
		}
	}
	[HarmonyPatch(typeof(Achievements), "OnAchievementUnlocked", new Type[] { typeof(bool) })]
	internal static class AchievementsOnAchievementUnlockedPatch
	{
		private static void Postfix()
		{
			AchievementWatcher.MarkDirty("Achievements.OnAchievementUnlocked");
		}
	}
	[HarmonyPatch(typeof(Achievements), "ResetAchievement", new Type[] { typeof(string) })]
	internal static class AchievementsResetAchievementPatch
	{
		private static void Postfix()
		{
			AchievementWatcher.MarkDirty("Achievements.ResetAchievement");
		}
	}
	[HarmonyPatch(typeof(Achievements), "ResetAllAchievements")]
	internal static class AchievementsResetAllAchievementsPatch
	{
		private static void Postfix()
		{
			AchievementWatcher.MarkDirty("Achievements.ResetAllAchievements");
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "Awake")]
	internal static class InventoryGuiAwakeAchievementRewardsPatch
	{
		private static void Postfix(InventoryGui __instance)
		{
			TitleSelectionGui.OnInventoryGuiAwake(__instance);
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "OnOpenAchievements")]
	internal static class InventoryGuiOpenAchievementsRewardPatch
	{
		private static void Postfix(InventoryGui __instance)
		{
			TitleSelectionGui.OnAchievementsOpened(__instance);
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "OnCloseAchievements")]
	internal static class InventoryGuiCloseAchievementsRewardPatch
	{
		private static void Postfix(InventoryGui __instance)
		{
			TitleSelectionGui.OnAchievementsClosed(__instance);
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "UpdateAchievementsList")]
	internal static class InventoryGuiUpdateAchievementsListRewardPatch
	{
		private static void Postfix(InventoryGui __instance)
		{
			TitleSelectionGui.OnVanillaAchievementsListUpdated(__instance);
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "Hide")]
	internal static class InventoryGuiHideAchievementRewardsPatch
	{
		private static void Postfix()
		{
			TitleSelectionGui.OnInventoryHidden();
		}
	}
}
namespace BalrondAchievementRewards.Networking
{
	internal static class TitleNetwork
	{
		internal const string ZdoTitleKey = "balrond_achievement_rewards_title_v1";

		internal static void Publish(Player player, string titleId)
		{
			if ((Object)(object)player == (Object)null)
			{
				return;
			}
			try
			{
				ZNetView nview = ((Character)player).m_nview;
				if ((Object)(object)nview == (Object)null || !nview.IsValid() || !nview.IsOwner())
				{
					return;
				}
				ZDO zDO = nview.GetZDO();
				if (zDO != null)
				{
					string text = string.Empty;
					if (!string.IsNullOrWhiteSpace(titleId) && TitleRewardCatalog.TryGet(titleId.Trim(), out var definition))
					{
						text = definition.TitleId;
					}
					string a = zDO.GetString("balrond_achievement_rewards_title_v1", string.Empty);
					if (!string.Equals(a, text, StringComparison.Ordinal))
					{
						zDO.Set("balrond_achievement_rewards_title_v1", text);
					}
				}
			}
			catch (Exception ex)
			{
				ModLog.ErrorOnce("title_publish", "Could not publish active title to Player ZDO: " + ex.Message);
			}
		}

		internal static string ReadPublishedTitle(Player player)
		{
			if ((Object)(object)player == (Object)null)
			{
				return string.Empty;
			}
			try
			{
				ZNetView nview = ((Character)player).m_nview;
				if ((Object)(object)nview == (Object)null || !nview.IsValid())
				{
					return string.Empty;
				}
				ZDO zDO = nview.GetZDO();
				if (zDO == null)
				{
					return string.Empty;
				}
				string text = zDO.GetString("balrond_achievement_rewards_title_v1", string.Empty);
				if (string.IsNullOrWhiteSpace(text))
				{
					return string.Empty;
				}
				TitleDefinition definition;
				return TitleRewardCatalog.TryGet(text.Trim(), out definition) ? definition.TitleId : string.Empty;
			}
			catch
			{
				return string.Empty;
			}
		}
	}
}
namespace BalrondAchievementRewards.Data
{
	internal sealed class AchievementSnapshot
	{
		internal string StableKey;

		internal string InternalId;

		internal string PlatformId;

		internal string NameTextId;

		internal string DisplayName;

		internal string DescriptionTextId;

		internal string DisplayDescription;

		internal bool Unlocked;
	}
	internal sealed class AchievementScanResult
	{
		internal bool ManagerAvailable;

		internal bool PlatformInitialized;

		internal bool? CanGetAchievements;

		internal string Error;

		internal List<AchievementSnapshot> Achievements = new List<AchievementSnapshot>();
	}
	internal enum AchievementMatchMode
	{
		Auto,
		Id,
		Name
	}
	internal sealed class TitleDefinition
	{
		internal AchievementMatchMode MatchMode;

		internal string AchievementMatcher;

		internal string TitleId;

		internal string DisplayName;

		internal string NameFormat;

		internal string ColorHex;

		internal string ResolvedAchievementKey;

		internal string ResolvedAchievementDisplayName;

		internal bool AmbiguousRequirement;

		internal bool TryGetColor(out Color color)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			color = Color.white;
			if (string.IsNullOrWhiteSpace(ColorHex))
			{
				return false;
			}
			string text = ColorHex.Trim();
			if (!text.StartsWith("#", StringComparison.Ordinal))
			{
				text = "#" + text;
			}
			return ColorUtility.TryParseHtmlString(text, ref color);
		}
	}
}
namespace BalrondAchievementRewards.Core
{
	[BepInPlugin("balrond.astafaraios.BalrondAchievementRewards", "BalrondAchievementRewards", "0.1.0")]
	public sealed class Launch : BaseUnityPlugin
	{
		public const string PluginGUID = "balrond.astafaraios.BalrondAchievementRewards";

		public const string PluginName = "BalrondAchievementRewards";

		public const string PluginVersion = "0.1.0";

		private Harmony _harmony;

		private bool _shutdown;

		internal static Launch Instance { get; private set; }

		private void Awake()
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Expected O, but got Unknown
			Instance = this;
			ModLog.Initialize(((BaseUnityPlugin)this).Logger);
			try
			{
				ServerSyncBridge.Initialize("balrond.astafaraios.BalrondAchievementRewards", "BalrondAchievementRewards", "0.1.0");
				BARConfig.Initialize(this);
				TitleSelectionStore.Initialize();
				TitleRewardCatalog.Initialize();
				AchievementWatcher.Initialize();
				TitleRuntime.Initialize();
				_harmony = new Harmony("balrond.astafaraios.BalrondAchievementRewards");
				PatchInstaller.Install(_harmony);
				ModLog.Info("BalrondAchievementRewards 0.1.0 initialized.");
			}
			catch (Exception ex)
			{
				ModLog.Error("Initialization failed; BalrondAchievementRewards is disabling itself for this session: " + ex);
				SafeShutdown();
				((Behaviour)this).enabled = false;
			}
		}

		private void Update()
		{
			if (_shutdown)
			{
				return;
			}
			try
			{
				AchievementWatcher.Tick(Time.unscaledTime);
				TitleRuntime.Tick(Time.unscaledTime);
				TitleSelectionGui.Tick();
				LocalPlayerTitleHud.Tick();
			}
			catch (Exception ex)
			{
				ModLog.ErrorOnce("update_exception", "Runtime update failed: " + ex);
			}
		}

		private void OnDestroy()
		{
			SafeShutdown();
		}

		private void OnApplicationQuit()
		{
			try
			{
				TitleSelectionStore.Flush();
			}
			catch
			{
			}
		}

		private void SafeShutdown()
		{
			if (_shutdown)
			{
				return;
			}
			_shutdown = true;
			try
			{
				TitleSelectionGui.Shutdown();
			}
			catch
			{
			}
			try
			{
				LocalPlayerTitleHud.Shutdown();
			}
			catch
			{
			}
			try
			{
				TitleRuntime.Shutdown();
			}
			catch
			{
			}
			try
			{
				AchievementWatcher.Shutdown();
			}
			catch
			{
			}
			try
			{
				TitleRewardCatalog.Shutdown();
			}
			catch
			{
			}
			try
			{
				TitleSelectionStore.Flush();
			}
			catch
			{
			}
			try
			{
				if (_harmony != null)
				{
					_harmony.UnpatchSelf();
				}
			}
			catch
			{
			}
			try
			{
				ServerSyncBridge.Reset();
			}
			catch
			{
			}
			_harmony = null;
			Instance = null;
			ModLog.Reset();
		}
	}
}
namespace BalrondAchievementRewards.Configuration
{
	internal static class BARConfig
	{
		internal const string DefaultTitleRewards = "id:AllBosses|forsakensbane|Forsaken's Bane;id:AllBossesHard|doomwalker|Doomwalker;id:AllBossesNormal|realmwarden|Realmwarden;id:AllBuildPieces|masterbuilder|Master Builder;id:AllFoodCooked|masterchef|Master Chef;id:AllFoodEaten|ironstomach|Iron Stomach;id:AllItemCraft|masterartisan|Master Artisan;id:AllMiniBosses|legendhunter|Legend Hunter;id:AllWeaponCraft|arsenalkeeper|Arsenal Keeper;id:Arrived|newblood|New Blood;id:BigFish|masterangler|Master Angler;id:Boss1Eikthyr|stormtouched|Stormtouched;id:Boss2Elder|rootbreaker|Rootbreaker;id:Boss3Bonemass|lightbringer|Lightbringer;id:Boss4Moder|dragonsbane|Dragonsbane;id:Boss5Yagluth|kingsbane|Kingsbane;id:Boss6Queen|hivebreaker|Hivebreaker;id:Boss7Fader|flamebreaker|Flamebreaker;id:Boss8FrozenKing|chainbreaker|Chainbreaker;id:BuildDyrnwyn|flameforged|Flameforged;id:BuildHigh|skybuilder|Skybuilder;id:BuildHighWorld|cloudpiercer|Cloudpiercer;id:BuildHouse|homesteader|Homesteader;id:BuildVillage|hearthjarl|Hearth Jarl;id:ChildOfOdin|odinschosen|Odin's Chosen;id:DeathByAllTypes|deathscompanion|Death's Companion;id:DeathByObliterator|atomized|Atomized;id:DeathByTree|timbered|Timbered;id:DeathByTreeAll|treemagnet|Tree Magnet;id:ExploreNSEW|wayfinder|Wayfinder;id:ExploreNSEWNoMap|pathless|Pathless;id:FindAllTrophies|trophykeeper|Trophy Keeper;id:FirstDeath|returned|Returned;id:GrindBuild|architect|Architect;id:GrindComfort|hearthkeeper|Hearthkeeper;id:GrindCook|cookfireveteran|Cookfire Veteran;id:GrindDays|survivor|Survivor;id:GrindEat|bottomless|Bottomless;id:GrindFish|fishmaster|Fishmaster;id:GrindForager|wildharvester|Wild Harvester;id:GrindHarvest|harvestlord|Harvest Lord;id:GrindHoney|beekeeper|Beekeeper;id:GrindLeviathan|leviathansbane|Leviathan's Bane;id:GrindSail|searover|Sea Rover;id:GrindSailHelm|helmsman|Helmsman;id:GrindTreasure|treasurehunter|Treasure Hunter;id:GrindTrees|woodsman|Woodsman;id:KillAllCreatures|beastbane|Beastbane;id:KillAllCreaturesHard|apexhunter|Apex Hunter;id:KingsQueens|crownbreaker|Crownbreaker;id:MultiplayerBoss|oathbound|Oathbound;id:SailEdge|edgewalker|Edgewalker;id:SoloBoss|lonewolf|Lone Wolf";

		private const string Legacy010TitleRewards = "name:$ach_boss1eikthyr|stormtouched|Stormtouched|{player} the {title}|#8FD3FF;name:$ach_boss2elder|rootbreaker|Rootbreaker|{player} the {title}|#A7D46F;name:$ach_boss3bonemass|lightbringer|Lightbringer|{player} the {title}|#E7F5B6;name:$ach_boss4moder|dragonsbane|Dragonsbane|{player} the {title}|#C3E5FF;name:$ach_boss5yagluth|kingsbane|Kingsbane|{player} the {title}|#FFB072;name:$ach_boss6queen|hivebreaker|Hivebreaker|{player} the {title}|#D8B5FF;name:$ach_boss7fader|flamebreaker|Flamebreaker|{player} the {title}|#7FFFC7;name:$ach_boss8frozenking|chainbreaker|Chainbreaker|{player} the {title}|#C6EEFF;name:$ach_multiplayerboss|oathbound|Oathbound|{player} the {title}|#FFD27A;name:$ach_soloboss|lonewolf|Lone Wolf|{player} the {title}|#D0D0D0";

		internal static ConfigEntry<bool> LockConfiguration;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<string> TitleRewards;

		internal static ConfigEntry<bool> ShowTitlesAbovePlayers;

		internal static ConfigEntry<bool> ShowOwnNameplate;

		internal static ConfigEntry<float> OwnNameplateVerticalOffset;

		internal static ConfigEntry<bool> UseTitleColors;

		internal static ConfigEntry<bool> VerboseLogging;

		internal static ConfigEntry<bool> WriteAchievementDumpOnInitialize;

		internal static void Initialize(Launch plugin)
		{
			LockConfiguration = ((BaseUnityPlugin)plugin).Config.Bind<bool>("00 - Server", "Lock Configuration", true, "Lock server-synchronized BalrondAchievementRewards settings to the server configuration.");
			ServerSyncBridge.AddLockingConfigEntry(LockConfiguration);
			Enabled = ((BaseUnityPlugin)plugin).Config.Bind<bool>("01 - Rewards", "Enabled", true, "Enable achievement reward titles. Server-synchronized.");
			ServerSyncBridge.AddConfigEntry<bool>(Enabled);
			TitleRewards = ((BaseUnityPlugin)plugin).Config.Bind<string>("01 - Rewards", "Title Rewards", "id:AllBosses|forsakensbane|Forsaken's Bane;id:AllBossesHard|doomwalker|Doomwalker;id:AllBossesNormal|realmwarden|Realmwarden;id:AllBuildPieces|masterbuilder|Master Builder;id:AllFoodCooked|masterchef|Master Chef;id:AllFoodEaten|ironstomach|Iron Stomach;id:AllItemCraft|masterartisan|Master Artisan;id:AllMiniBosses|legendhunter|Legend Hunter;id:AllWeaponCraft|arsenalkeeper|Arsenal Keeper;id:Arrived|newblood|New Blood;id:BigFish|masterangler|Master Angler;id:Boss1Eikthyr|stormtouched|Stormtouched;id:Boss2Elder|rootbreaker|Rootbreaker;id:Boss3Bonemass|lightbringer|Lightbringer;id:Boss4Moder|dragonsbane|Dragonsbane;id:Boss5Yagluth|kingsbane|Kingsbane;id:Boss6Queen|hivebreaker|Hivebreaker;id:Boss7Fader|flamebreaker|Flamebreaker;id:Boss8FrozenKing|chainbreaker|Chainbreaker;id:BuildDyrnwyn|flameforged|Flameforged;id:BuildHigh|skybuilder|Skybuilder;id:BuildHighWorld|cloudpiercer|Cloudpiercer;id:BuildHouse|homesteader|Homesteader;id:BuildVillage|hearthjarl|Hearth Jarl;id:ChildOfOdin|odinschosen|Odin's Chosen;id:DeathByAllTypes|deathscompanion|Death's Companion;id:DeathByObliterator|atomized|Atomized;id:DeathByTree|timbered|Timbered;id:DeathByTreeAll|treemagnet|Tree Magnet;id:ExploreNSEW|wayfinder|Wayfinder;id:ExploreNSEWNoMap|pathless|Pathless;id:FindAllTrophies|trophykeeper|Trophy Keeper;id:FirstDeath|returned|Returned;id:GrindBuild|architect|Architect;id:GrindComfort|hearthkeeper|Hearthkeeper;id:GrindCook|cookfireveteran|Cookfire Veteran;id:GrindDays|survivor|Survivor;id:GrindEat|bottomless|Bottomless;id:GrindFish|fishmaster|Fishmaster;id:GrindForager|wildharvester|Wild Harvester;id:GrindHarvest|harvestlord|Harvest Lord;id:GrindHoney|beekeeper|Beekeeper;id:GrindLeviathan|leviathansbane|Leviathan's Bane;id:GrindSail|searover|Sea Rover;id:GrindSailHelm|helmsman|Helmsman;id:GrindTreasure|treasurehunter|Treasure Hunter;id:GrindTrees|woodsman|Woodsman;id:KillAllCreatures|beastbane|Beastbane;id:KillAllCreaturesHard|apexhunter|Apex Hunter;id:KingsQueens|crownbreaker|Crownbreaker;id:MultiplayerBoss|oathbound|Oathbound;id:SailEdge|edgewalker|Edgewalker;id:SoloBoss|lonewolf|Lone Wolf", "Server-synchronized one-to-one mapping from VANILLA Valheim achievements to cosmetic titles. Entries are separated by ';'. Fields: AchievementMatcher|TitleId|DisplayName|NameFormat|OptionalColor. Supported matcher prefixes: id:, name:, auto:. Tokens in NameFormat: {player}, {title}. This mod does not create custom achievements or custom progression conditions.");
			ServerSyncBridge.AddConfigEntry<string>(TitleRewards);
			MigrateLegacyDefaultCatalog();
			ShowTitlesAbovePlayers = ((BaseUnityPlugin)plugin).Config.Bind<bool>("10 - Client Appearance", "Show Titles Above Players", true, "Show active cosmetic titles in other players' native hover/name HUD. Client-local.");
			ShowOwnNameplate = ((BaseUnityPlugin)plugin).Config.Bind<bool>("10 - Client Appearance", "Show Own Nameplate", true, "Show your own character name and active title above your character using Valheim's native player HUD prefab. Client-local.");
			OwnNameplateVerticalOffset = ((BaseUnityPlugin)plugin).Config.Bind<float>("10 - Client Appearance", "Own Nameplate Vertical Offset", 0.3f, "Additional world-space height above the local player's head for the self nameplate. Valheim's native player HUD uses roughly 0.30. Client-local.");
			UseTitleColors = ((BaseUnityPlugin)plugin).Config.Bind<bool>("10 - Client Appearance", "Use Title Colors", true, "Render optional server-defined title colors using TextMeshPro rich text. Client-local.");
			VerboseLogging = ((BaseUnityPlugin)plugin).Config.Bind<bool>("90 - Diagnostics", "Verbose Logging", false, "Write detailed achievement matching and title state diagnostics. Client-local.");
			WriteAchievementDumpOnInitialize = ((BaseUnityPlugin)plugin).Config.Bind<bool>("90 - Diagnostics", "Write Achievement Dump On Initialize", false, "Write discovered vanilla achievement identifiers and state to BepInEx/config/BalrondAchievementRewards/achievement_dump.txt after the achievement system initializes. Client-local.");
			Enabled.SettingChanged += delegate
			{
				OnSynchronizedRewardConfigurationChanged();
			};
			TitleRewards.SettingChanged += delegate
			{
				OnSynchronizedRewardConfigurationChanged();
			};
			UseTitleColors.SettingChanged += delegate
			{
				TitleSelectionGui.RequestRefresh();
			};
			ShowOwnNameplate.SettingChanged += delegate
			{
				LocalPlayerTitleHud.RequestRefresh();
			};
			OwnNameplateVerticalOffset.SettingChanged += delegate
			{
				LocalPlayerTitleHud.RequestRefresh();
			};
		}

		private static void MigrateLegacyDefaultCatalog()
		{
			string text = TitleRewards.Value ?? string.Empty;
			if (string.Equals(text, "name:$ach_boss1eikthyr|stormtouched|Stormtouched|{player} the {title}|#8FD3FF;name:$ach_boss2elder|rootbreaker|Rootbreaker|{player} the {title}|#A7D46F;name:$ach_boss3bonemass|lightbringer|Lightbringer|{player} the {title}|#E7F5B6;name:$ach_boss4moder|dragonsbane|Dragonsbane|{player} the {title}|#C3E5FF;name:$ach_boss5yagluth|kingsbane|Kingsbane|{player} the {title}|#FFB072;name:$ach_boss6queen|hivebreaker|Hivebreaker|{player} the {title}|#D8B5FF;name:$ach_boss7fader|flamebreaker|Flamebreaker|{player} the {title}|#7FFFC7;name:$ach_boss8frozenking|chainbreaker|Chainbreaker|{player} the {title}|#C6EEFF;name:$ach_multiplayerboss|oathbound|Oathbound|{player} the {title}|#FFD27A;name:$ach_soloboss|lonewolf|Lone Wolf|{player} the {title}|#D0D0D0", StringComparison.Ordinal))
			{
				TitleRewards.Value = "id:AllBosses|forsakensbane|Forsaken's Bane;id:AllBossesHard|doomwalker|Doomwalker;id:AllBossesNormal|realmwarden|Realmwarden;id:AllBuildPieces|masterbuilder|Master Builder;id:AllFoodCooked|masterchef|Master Chef;id:AllFoodEaten|ironstomach|Iron Stomach;id:AllItemCraft|masterartisan|Master Artisan;id:AllMiniBosses|legendhunter|Legend Hunter;id:AllWeaponCraft|arsenalkeeper|Arsenal Keeper;id:Arrived|newblood|New Blood;id:BigFish|masterangler|Master Angler;id:Boss1Eikthyr|stormtouched|Stormtouched;id:Boss2Elder|rootbreaker|Rootbreaker;id:Boss3Bonemass|lightbringer|Lightbringer;id:Boss4Moder|dragonsbane|Dragonsbane;id:Boss5Yagluth|kingsbane|Kingsbane;id:Boss6Queen|hivebreaker|Hivebreaker;id:Boss7Fader|flamebreaker|Flamebreaker;id:Boss8FrozenKing|chainbreaker|Chainbreaker;id:BuildDyrnwyn|flameforged|Flameforged;id:BuildHigh|skybuilder|Skybuilder;id:BuildHighWorld|cloudpiercer|Cloudpiercer;id:BuildHouse|homesteader|Homesteader;id:BuildVillage|hearthjarl|Hearth Jarl;id:ChildOfOdin|odinschosen|Odin's Chosen;id:DeathByAllTypes|deathscompanion|Death's Companion;id:DeathByObliterator|atomized|Atomized;id:DeathByTree|timbered|Timbered;id:DeathByTreeAll|treemagnet|Tree Magnet;id:ExploreNSEW|wayfinder|Wayfinder;id:ExploreNSEWNoMap|pathless|Pathless;id:FindAllTrophies|trophykeeper|Trophy Keeper;id:FirstDeath|returned|Returned;id:GrindBuild|architect|Architect;id:GrindComfort|hearthkeeper|Hearthkeeper;id:GrindCook|cookfireveteran|Cookfire Veteran;id:GrindDays|survivor|Survivor;id:GrindEat|bottomless|Bottomless;id:GrindFish|fishmaster|Fishmaster;id:GrindForager|wildharvester|Wild Harvester;id:GrindHarvest|harvestlord|Harvest Lord;id:GrindHoney|beekeeper|Beekeeper;id:GrindLeviathan|leviathansbane|Leviathan's Bane;id:GrindSail|searover|Sea Rover;id:GrindSailHelm|helmsman|Helmsman;id:GrindTreasure|treasurehunter|Treasure Hunter;id:GrindTrees|woodsman|Woodsman;id:KillAllCreatures|beastbane|Beastbane;id:KillAllCreaturesHard|apexhunter|Apex Hunter;id:KingsQueens|crownbreaker|Crownbreaker;id:MultiplayerBoss|oathbound|Oathbound;id:SailEdge|edgewalker|Edgewalker;id:SoloBoss|lonewolf|Lone Wolf";
				ModLog.Info("Migrated the untouched 0.1.0 title catalog to the complete vanilla-achievement catalog.");
			}
			else if (text.StartsWith("id:AllBosses|forsakensbane|Forsaken's Bane;id:AllBossesHard|doomwalker|Doomwalker;id:AllBossesNormal|realmwarden|Realmwarden;id:AllBuildPieces|masterbuilder|Master Builder;id:AllFoodCooked|masterchef|Master Chef;id:AllFoodEaten|ironstomach|Iron Stomach;id:AllItemCraft|masterartisan|Master Artisan;id:AllMiniBosses|legendhunter|Legend Hunter;id:AllWeaponCraft|arsenalkeeper|Arsenal Keeper;id:Arrived|newblood|New Blood;id:BigFish|masterangler|Master Angler;id:Boss1Eikthyr|stormtouched|Stormtouched;id:Boss2Elder|rootbreaker|Rootbreaker;id:Boss3Bonemass|lightbringer|Lightbringer;id:Boss4Moder|dragonsbane|Dragonsbane;id:Boss5Yagluth|kingsbane|Kingsbane;id:Boss6Queen|hivebreaker|Hivebreaker;id:Boss7Fader|flamebreaker|Flamebreaker;id:Boss8FrozenKing|chainbreaker|Chainbreaker;id:BuildDyrnwyn|flameforged|Flameforged;id:BuildHigh|skybuilder|Skybuilder;id:BuildHighWorld|cloudpiercer|Cloudpiercer;id:BuildHouse|homesteader|Homesteader;id:BuildVillage|hearthjarl|Hearth Jarl;id:ChildOfOdin|odinschosen|Odin's Chosen;id:DeathByAllTypes|deathscompanion|Death's Companion;id:DeathByObliterator|atomized|Atomized;id:DeathByTree|timbered|Timbered;id:DeathByTreeAll|treemagnet|Tree Magnet;id:ExploreNSEW|wayfinder|Wayfinder;id:ExploreNSEWNoMap|pathless|Pathless;id:FindAllTrophies|trophykeeper|Trophy Keeper;id:FirstDeath|returned|Returned;id:GrindBuild|architect|Architect;id:GrindComfort|hearthkeeper|Hearthkeeper;id:GrindCook|cookfireveteran|Cookfire Veteran;id:GrindDays|survivor|Survivor;id:GrindEat|bottomless|Bottomless;id:GrindFish|fishmaster|Fishmaster;id:GrindForager|wildharvester|Wild Harvester;id:GrindHarvest|harvestlord|Harvest Lord;id:GrindHoney|beekeeper|Beekeeper;id:GrindLeviathan|leviathansbane|Leviathan's Bane;id:GrindSail|searover|Sea Rover;id:GrindSailHelm|helmsman|Helmsman;id:GrindTreasure|treasurehunter|Treasure Hunter;id:GrindTrees|woodsman|Woodsman;id:KillAllCreatures|beastbane|Beastbane;id:KillAllCreaturesHard|apexhunter|Apex Hunter;id:KingsQueens|crownbreaker|Crownbreaker;id:MultiplayerBoss|oathbound|Oathbound;id:SailEdge|edgewalker|Edgewalker;id:SoloBoss|lonewolf|Lone Wolf;pickupsum:", StringComparison.Ordinal) && text.IndexOf("allskills:100|allstar|Allstar", StringComparison.Ordinal) >= 0)
			{
				TitleRewards.Value = "id:AllBosses|forsakensbane|Forsaken's Bane;id:AllBossesHard|doomwalker|Doomwalker;id:AllBossesNormal|realmwarden|Realmwarden;id:AllBuildPieces|masterbuilder|Master Builder;id:AllFoodCooked|masterchef|Master Chef;id:AllFoodEaten|ironstomach|Iron Stomach;id:AllItemCraft|masterartisan|Master Artisan;id:AllMiniBosses|legendhunter|Legend Hunter;id:AllWeaponCraft|arsenalkeeper|Arsenal Keeper;id:Arrived|newblood|New Blood;id:BigFish|masterangler|Master Angler;id:Boss1Eikthyr|stormtouched|Stormtouched;id:Boss2Elder|rootbreaker|Rootbreaker;id:Boss3Bonemass|lightbringer|Lightbringer;id:Boss4Moder|dragonsbane|Dragonsbane;id:Boss5Yagluth|kingsbane|Kingsbane;id:Boss6Queen|hivebreaker|Hivebreaker;id:Boss7Fader|flamebreaker|Flamebreaker;id:Boss8FrozenKing|chainbreaker|Chainbreaker;id:BuildDyrnwyn|flameforged|Flameforged;id:BuildHigh|skybuilder|Skybuilder;id:BuildHighWorld|cloudpiercer|Cloudpiercer;id:BuildHouse|homesteader|Homesteader;id:BuildVillage|hearthjarl|Hearth Jarl;id:ChildOfOdin|odinschosen|Odin's Chosen;id:DeathByAllTypes|deathscompanion|Death's Companion;id:DeathByObliterator|atomized|Atomized;id:DeathByTree|timbered|Timbered;id:DeathByTreeAll|treemagnet|Tree Magnet;id:ExploreNSEW|wayfinder|Wayfinder;id:ExploreNSEWNoMap|pathless|Pathless;id:FindAllTrophies|trophykeeper|Trophy Keeper;id:FirstDeath|returned|Returned;id:GrindBuild|architect|Architect;id:GrindComfort|hearthkeeper|Hearthkeeper;id:GrindCook|cookfireveteran|Cookfire Veteran;id:GrindDays|survivor|Survivor;id:GrindEat|bottomless|Bottomless;id:GrindFish|fishmaster|Fishmaster;id:GrindForager|wildharvester|Wild Harvester;id:GrindHarvest|harvestlord|Harvest Lord;id:GrindHoney|beekeeper|Beekeeper;id:GrindLeviathan|leviathansbane|Leviathan's Bane;id:GrindSail|searover|Sea Rover;id:GrindSailHelm|helmsman|Helmsman;id:GrindTreasure|treasurehunter|Treasure Hunter;id:GrindTrees|woodsman|Woodsman;id:KillAllCreatures|beastbane|Beastbane;id:KillAllCreaturesHard|apexhunter|Apex Hunter;id:KingsQueens|crownbreaker|Crownbreaker;id:MultiplayerBoss|oathbound|Oathbound;id:SailEdge|edgewalker|Edgewalker;id:SoloBoss|lonewolf|Lone Wolf";
				ModLog.Info("Migrated the untouched 0.2.x catalog: removed mod-side pseudo-achievements and retained all 53 vanilla achievement title mappings.");
			}
		}

		private static void OnSynchronizedRewardConfigurationChanged()
		{
			TitleRewardCatalog.RebuildFromConfig();
			TitleRuntime.OnRewardConfigurationChanged();
			TitleSelectionGui.RequestRefresh();
			LocalPlayerTitleHud.RequestRefresh();
		}
	}
	internal static class ServerSyncBridge
	{
		private static object _syncObject;

		internal static void Initialize(string pluginGuid, string displayName, string version)
		{
			ConfigSync syncObject = new ConfigSync(pluginGuid)
			{
				DisplayName = displayName,
				CurrentVersion = version,
				MinimumRequiredVersion = version
			};
			_syncObject = syncObject;
		}

		internal static void AddLockingConfigEntry(ConfigEntry<bool> entry)
		{
			GetSync().AddLockingConfigEntry<bool>(entry);
		}

		internal static void AddConfigEntry<T>(ConfigEntry<T> entry)
		{
			SyncedConfigEntry<T> syncedConfigEntry = GetSync().AddConfigEntry<T>(entry);
			syncedConfigEntry.SynchronizedConfig = true;
		}

		internal static void Reset()
		{
			_syncObject = null;
		}

		private static ConfigSync GetSync()
		{
			if (!(_syncObject is ConfigSync result))
			{
				throw new InvalidOperationException("ServerSyncBridge used before initialization.");
			}
			return result;
		}
	}
}
namespace BalrondAchievementRewards.AchievementIntegration
{
	internal static class AchievementApi
	{
		internal static AchievementScanResult Scan()
		{
			AchievementScanResult achievementScanResult = new AchievementScanResult();
			try
			{
				Achievements instance = Achievements.m_instance;
				if ((Object)(object)instance == (Object)null)
				{
					achievementScanResult.ManagerAvailable = false;
					achievementScanResult.PlatformInitialized = Achievements.m_platformInitialized;
					achievementScanResult.Error = "Achievements.m_instance is not active yet.";
					return achievementScanResult;
				}
				achievementScanResult.ManagerAvailable = true;
				achievementScanResult.PlatformInitialized = Achievements.m_platformInitialized;
				if ((Object)(object)Player.m_localPlayer != (Object)null && (Object)(object)Game.instance != (Object)null && Game.instance.GetPlayerProfile() != null)
				{
					try
					{
						achievementScanResult.CanGetAchievements = Achievements.CanGetAchievements(false);
					}
					catch (Exception ex)
					{
						ModLog.Debug("ValheimAchievements.CanGetAchievements(false) was not ready: " + ex.Message);
					}
				}
				if (instance.m_achievementLists == null)
				{
					achievementScanResult.Error = "Achievements.m_achievementLists is null.";
					return achievementScanResult;
				}
				for (int i = 0; i < instance.m_achievementLists.Count; i++)
				{
					AchievementList val = instance.m_achievementLists[i];
					if ((Object)(object)val == (Object)null || val.m_achievements == null)
					{
						continue;
					}
					for (int j = 0; j < val.m_achievements.Count; j++)
					{
						Achievement val2 = val.m_achievements[j];
						if (!((Object)(object)val2 == (Object)null))
						{
							AchievementSnapshot achievementSnapshot = ReadAchievement(val2);
							if (achievementSnapshot != null)
							{
								achievementScanResult.Achievements.Add(achievementSnapshot);
							}
						}
					}
				}
				if (achievementScanResult.Achievements.Count == 0)
				{
					achievementScanResult.Error = "Achievements manager is active, but no achievement definitions are populated yet.";
				}
			}
			catch (Exception ex2)
			{
				achievementScanResult.Error = ex2.GetType().Name + ": " + ex2.Message;
				ModLog.ErrorOnce("achievement_scan", "Direct achievement scan failed: " + ex2);
			}
			return achievementScanResult;
		}

		internal static string Localize(string textId)
		{
			if (string.IsNullOrEmpty(textId))
			{
				return string.Empty;
			}
			try
			{
				if (Localization.instance != null)
				{
					string text = Localization.instance.Localize(textId);
					if (!string.IsNullOrEmpty(text))
					{
						return text;
					}
				}
			}
			catch
			{
			}
			return textId;
		}

		private static AchievementSnapshot ReadAchievement(Achievement achievement)
		{
			string text = achievement.m_id ?? string.Empty;
			string text2 = achievement.m_name ?? string.Empty;
			string text3 = achievement.m_description ?? string.Empty;
			string text4 = string.Empty;
			try
			{
				text4 = Achievements.GetAchievementInternalPlatformId(achievement) ?? string.Empty;
			}
			catch
			{
			}
			string text5 = Localize(text2);
			if (string.IsNullOrWhiteSpace(text5))
			{
				text5 = ((!string.IsNullOrWhiteSpace(text)) ? text : text4);
			}
			string stableKey = ((!string.IsNullOrWhiteSpace(text)) ? ("id:" + text.Trim()) : ((!string.IsNullOrWhiteSpace(text4)) ? ("platform:" + text4.Trim()) : ("name:" + text2.Trim())));
			return new AchievementSnapshot
			{
				StableKey = stableKey,
				InternalId = text,
				PlatformId = text4,
				NameTextId = text2,
				DisplayName = text5,
				DescriptionTextId = text3,
				DisplayDescription = Localize(text3),
				Unlocked = achievement.m_unlocked
			};
		}
	}
	internal static class AchievementWatcher
	{
		private static readonly Dictionary<string, bool> PreviousUnlocked = new Dictionary<string, bool>(StringComparer.Ordinal);

		private static bool _initialized;

		private static bool _hasSuccessfulScan;

		private static bool _dirty;

		private static float _nextScanTime;

		private static bool _initialDumpWritten;

		internal static AchievementScanResult LastScan { get; private set; }

		internal static void Initialize()
		{
			PreviousUnlocked.Clear();
			LastScan = null;
			_initialized = true;
			_hasSuccessfulScan = false;
			_dirty = true;
			_nextScanTime = 0f;
			_initialDumpWritten = false;
		}

		internal static void Shutdown()
		{
			PreviousUnlocked.Clear();
			LastScan = null;
			_initialized = false;
			_hasSuccessfulScan = false;
			_dirty = false;
		}

		internal static void MarkDirty(string reason)
		{
			if (_initialized)
			{
				_dirty = true;
				_nextScanTime = 0f;
				ModLog.Debug("Achievement rescan requested: " + reason);
			}
		}

		internal static void Tick(float now)
		{
			if (_initialized)
			{
				if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsDedicated())
				{
					_dirty = false;
					_nextScanTime = now + 60f;
				}
				else if ((Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)Game.instance == (Object)null || Game.instance.GetPlayerProfile() == null)
				{
					_dirty = true;
					_nextScanTime = now + 1f;
				}
				else if (_dirty || !(now < _nextScanTime))
				{
					_dirty = false;
					ScanNow(now);
				}
			}
		}

		internal static void ScanImmediately(string reason)
		{
			MarkDirty(reason);
			if (!((Object)(object)Player.m_localPlayer == (Object)null) && !((Object)(object)Game.instance == (Object)null) && Game.instance.GetPlayerProfile() != null)
			{
				ScanNow(Time.unscaledTime);
			}
		}

		internal static string WriteDump()
		{
			AchievementScanResult achievementScanResult = LastScan;
			if (achievementScanResult == null || achievementScanResult.Achievements == null || achievementScanResult.Achievements.Count == 0)
			{
				achievementScanResult = AchievementApi.Scan();
			}
			string text = Path.Combine(Paths.ConfigPath, "BalrondAchievementRewards");
			Directory.CreateDirectory(text);
			string text2 = Path.Combine(text, "achievement_dump.txt");
			StringBuilder stringBuilder = new StringBuilder(8192);
			stringBuilder.AppendLine("BalrondAchievementRewards vanilla achievement dump");
			stringBuilder.AppendLine("Generated: " + DateTime.UtcNow.ToString("u", CultureInfo.InvariantCulture));
			stringBuilder.AppendLine("ManagerAvailable=" + (achievementScanResul