Decompiled source of RunicAwareness v1.0.0

RunicAwareness.dll

Decompiled 14 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using RunicAwareness.Core;
using RunicAwareness.Integration;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Runic Awareness")]
[assembly: AssemblyDescription("Bounded, display-only explanations of already-known Valheim state.")]
[assembly: AssemblyCompany("Chazman")]
[assembly: AssemblyProduct("Runic Awareness")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: InternalsVisibleTo("RunicAwareness.Tests")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace RunicAwareness
{
	public enum AwarenessTimerPrecision
	{
		WholeSecond = 1,
		FiveSeconds = 5,
		TenSeconds = 10,
		WholeMinute = 60
	}
	public enum AwarenessOverlayAnchor
	{
		TopRight,
		TopLeft,
		BottomRight,
		BottomLeft,
		MiddleLeft
	}
	internal static class AwarenessConfig
	{
		internal const int HardMaximumFoodScan = 8;

		internal const int HardMaximumEffectScan = 32;

		internal const int HardMaximumComfortPieces = 64;

		internal const int HardMaximumPanelLines = 40;

		internal const int HardMaximumPanelCharacters = 4096;

		internal static ConfigEntry<bool> Enabled { get; private set; }

		internal static ConfigEntry<bool> ShowFoodTimers { get; private set; }

		internal static ConfigEntry<bool> ShowEffectTimers { get; private set; }

		internal static ConfigEntry<bool> ShowComfort { get; private set; }

		internal static ConfigEntry<bool> ShowItemComparison { get; private set; }

		internal static ConfigEntry<bool> ShowProduction { get; private set; }

		internal static ConfigEntry<bool> ShowAgriculture { get; private set; }

		internal static ConfigEntry<bool> ShowBuilding { get; private set; }

		internal static ConfigEntry<bool> ShowTamedAnimals { get; private set; }

		internal static ConfigEntry<AwarenessTimerPrecision> TimerPrecision { get; private set; }

		internal static ConfigEntry<float> RefreshInterval { get; private set; }

		internal static ConfigEntry<int> MaximumEffectRows { get; private set; }

		internal static ConfigEntry<int> MaximumComfortRows { get; private set; }

		internal static ConfigEntry<int> MaximumContextLines { get; private set; }

		internal static ConfigEntry<float> UiScale { get; private set; }

		internal static ConfigEntry<float> ControllerScaleMultiplier { get; private set; }

		internal static ConfigEntry<AwarenessOverlayAnchor> Anchor { get; private set; }

		internal static ConfigEntry<bool> MiddleLeftAnchorMigrationApplied { get; private set; }

		internal static ConfigEntry<bool> VerboseLogging { get; private set; }

		internal static void Bind(ConfigFile config)
		{
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Expected O, but got Unknown
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Expected O, but got Unknown
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_0171: Expected O, but got Unknown
			//IL_0193: Unknown result type (might be due to invalid IL or missing references)
			//IL_019d: Expected O, but got Unknown
			//IL_01cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d5: Expected O, but got Unknown
			//IL_0203: Unknown result type (might be due to invalid IL or missing references)
			//IL_020d: Expected O, but got Unknown
			Enabled = config.Bind<bool>("General", "Enabled", true, "Enable Runic Awareness. This module only observes and renders already-known local state.");
			ShowFoodTimers = Panel(config, "FoodTimers", defaultValue: true, "Show remaining time and the local food item that supplied each active food slot.");
			ShowEffectTimers = Panel(config, "EffectTimers", defaultValue: true, "Show locally active HUD status effects and their remaining time.");
			ShowComfort = Panel(config, "ComfortBreakdown", defaultValue: true, "Show current comfort, Rested time, and bounded category results from vanilla's own comfort pass.");
			ShowItemComparison = Panel(config, "ItemComparison", defaultValue: true, "Retain bounded item-comparison capture for compatibility; inventory UI suppression takes precedence, so this capture is not rendered as an Awareness panel while inventory is open.");
			ShowProduction = Panel(config, "ProductionContext", defaultValue: true, "Show the currently hovered station's visible state and optional Runic Production status when its runtime contract is available.");
			ShowAgriculture = Panel(config, "AgricultureContext", defaultValue: true, "Show bounded text already exposed by the currently hovered crop or beehive.");
			ShowBuilding = Panel(config, "BuildingContext", defaultValue: true, "Show the currently selected or hovered build piece's known transform, health, station level, and vanilla support legend.");
			ShowTamedAnimals = Panel(config, "TamedAnimalContext", defaultValue: true, "Show bounded text already exposed by the currently hovered tameable animal.");
			TimerPrecision = config.Bind<AwarenessTimerPrecision>("Display", "TimerPrecision", AwarenessTimerPrecision.WholeSecond, "Round remaining timers upward to whole seconds, five seconds, ten seconds, or whole minutes.");
			RefreshInterval = config.Bind<float>("Display", "RefreshIntervalSeconds", 0.35f, new ConfigDescription("Low-frequency state sampling interval. Text is rebuilt only when a bounded state signature changes.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.2f, 2f), Array.Empty<object>()));
			MaximumEffectRows = config.Bind<int>("Display", "MaximumEffectRows", 5, new ConfigDescription("Maximum visible HUD-effect rows. The scan itself is hard-capped at 32 local effects.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8), Array.Empty<object>()));
			MaximumComfortRows = config.Bind<int>("Display", "MaximumComfortWinnerRows", 6, new ConfigDescription("Maximum named comfort winners. Oversized vanilla comfort sets fail closed to a level-only explanation.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8), Array.Empty<object>()));
			MaximumContextLines = config.Bind<int>("Display", "MaximumContextLines", 5, new ConfigDescription("Maximum lines copied from already-visible current-hover text.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 6), Array.Empty<object>()));
			UiScale = config.Bind<float>("Display", "UiScale", 1f, new ConfigDescription("Overlay text and panel scale.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.75f, 2f), Array.Empty<object>()));
			ControllerScaleMultiplier = config.Bind<float>("Display", "ControllerScaleMultiplier", 1.15f, new ConfigDescription("Additional readability scale while Valheim reports controller navigation as active.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 1.5f), Array.Empty<object>()));
			Anchor = config.Bind<AwarenessOverlayAnchor>("Display", "Anchor", AwarenessOverlayAnchor.MiddleLeft, "Place the non-interactive overlay at a safe-area anchor. The default is left-middle.");
			MiddleLeftAnchorMigrationApplied = config.Bind<bool>("Migrations", "DefaultAnchorMovedToMiddleLeft", false, "Internal one-time migration marker. Once true, later Anchor choices are never rewritten by this migration.");
			if (!MiddleLeftAnchorMigrationApplied.Value)
			{
				AwarenessOverlayAnchor awarenessOverlayAnchor = MigrateLegacyAnchor(Anchor.Value, migrationApplied: false);
				if (awarenessOverlayAnchor != Anchor.Value)
				{
					Anchor.Value = awarenessOverlayAnchor;
				}
				MiddleLeftAnchorMigrationApplied.Value = true;
			}
			VerboseLogging = config.Bind<bool>("Diagnostics", "VerboseLogging", false, "Log Awareness configuration refreshes. Never logs hidden world state.");
		}

		internal static AwarenessOverlayAnchor MigrateLegacyAnchor(AwarenessOverlayAnchor anchor, bool migrationApplied)
		{
			if (migrationApplied || anchor != AwarenessOverlayAnchor.TopRight)
			{
				return anchor;
			}
			return AwarenessOverlayAnchor.MiddleLeft;
		}

		private static ConfigEntry<bool> Panel(ConfigFile config, string key, bool defaultValue, string description)
		{
			return config.Bind<bool>("Panels", key, defaultValue, description);
		}
	}
	[BepInPlugin("chazman.RunicAwareness", "Runic Awareness", "1.0.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "chazman.RunicAwareness";

		public const string Name = "Runic Awareness";

		public const string Version = "1.0.0";

		private Harmony _harmony;

		private AwarenessRuntime _runtime;

		private bool _languageSubscribed;

		internal static Plugin Instance { get; private set; }

		internal AwarenessRuntime Runtime => _runtime;

		private void Awake()
		{
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Expected O, but got Unknown
			Instance = this;
			AwarenessConfig.Bind(((BaseUnityPlugin)this).Config);
			((BaseUnityPlugin)this).Config.SettingChanged += OnSettingChanged;
			try
			{
				ValheimContracts.VerifyInstalledSignatures();
				_runtime = new AwarenessRuntime(((BaseUnityPlugin)this).Logger, Application.isBatchMode);
				Localization.OnLanguageChange = (Action)Delegate.Combine(Localization.OnLanguageChange, new Action(OnLanguageChange));
				_languageSubscribed = true;
				if (!Application.isBatchMode)
				{
					_harmony = new Harmony("chazman.RunicAwareness");
					_harmony.PatchAll(typeof(Plugin).Assembly);
				}
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Runic Awareness v1.0.0 ready: bounded local timers, comfort explanation, equipment comparison, and current-context display panels; gameplay state is never changed.");
				if (Application.isBatchMode)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)"Dedicated/batch process detected; the display runtime is intentionally inert.");
				}
			}
			catch (Exception ex)
			{
				Cleanup();
				((BaseUnityPlugin)this).Logger.LogError((object)("Runic Awareness failed closed during startup; vanilla UI and gameplay remain unchanged. " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private void Update()
		{
			try
			{
				_runtime?.Update();
			}
			catch (Exception exception)
			{
				_runtime?.FailClosed("update", exception);
			}
		}

		private void OnGUI()
		{
			try
			{
				_runtime?.Draw();
			}
			catch (Exception exception)
			{
				_runtime?.FailClosed("draw", exception);
			}
		}

		private void OnSettingChanged(object sender, SettingChangedEventArgs arguments)
		{
			try
			{
				AwarenessRuntime runtime = _runtime;
				if (runtime != null)
				{
					object definition;
					if (arguments == null)
					{
						definition = null;
					}
					else
					{
						ConfigEntryBase changedSetting = arguments.ChangedSetting;
						definition = ((changedSetting != null) ? changedSetting.Definition : null);
					}
					runtime.OnConfigurationChanged((ConfigDefinition)definition);
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Awareness configuration refresh failed closed: " + ex.Message));
			}
		}

		private void OnLanguageChange()
		{
			try
			{
				ComfortCapture.Reset();
				ContextCapture.Reset();
				HoverItemCapture.Reset();
				_runtime?.OnLocalizationChanged();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Awareness language refresh failed closed: " + ex.Message));
			}
		}

		private void OnDestroy()
		{
			((BaseUnityPlugin)this).Config.SettingChanged -= OnSettingChanged;
			Cleanup();
			Instance = null;
		}

		private void Cleanup()
		{
			if (_languageSubscribed)
			{
				Localization.OnLanguageChange = (Action)Delegate.Remove(Localization.OnLanguageChange, new Action(OnLanguageChange));
				_languageSubscribed = false;
			}
			_runtime?.Dispose();
			_runtime = null;
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			_harmony = null;
			ContextCapture.Reset();
			HoverItemCapture.Reset();
			ComfortCapture.Reset();
		}
	}
}
namespace RunicAwareness.Integration
{
	internal sealed class AwarenessRuntime : IDisposable
	{
		private const int MaximumFoodRows = 3;

		private const int MaximumContextCharacters = 768;

		private readonly ManualLogSource _log;

		private readonly bool _inert;

		private readonly PanelCache _panels = new PanelCache();

		private readonly StringBuilder _builder = new StringBuilder(1024);

		private readonly OptionalPortalPanelAdapter _portalPanels = new OptionalPortalPanelAdapter();

		private readonly GUIContent _content = new GUIContent();

		private GUIStyle _textStyle;

		private GUIStyle _boxStyle;

		private float _nextRefresh;

		private float _nextStationLevelRefresh;

		private bool _failed;

		private bool _layoutDirty = true;

		private int _styleFontSize;

		private float _cachedHeight;

		private float _lastInnerWidth;

		private float _lastMaximumHeight;

		private int _hookFailureCount;

		private ulong _foodSignature;

		private ulong _effectSignature;

		private ulong _comfortSignature;

		private ulong _itemSignature;

		private ulong _contextSignature;

		private Piece _lastBuildingPiece;

		private int _cachedStationLevel;

		private bool _hadFoodSignature;

		private bool _hadEffectSignature;

		private bool _hadComfortSignature;

		private bool _hadItemSignature;

		private bool _hadContextSignature;

		internal AwarenessRuntime(ManualLogSource log, bool inert)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Expected O, but got Unknown
			_log = log ?? throw new ArgumentNullException("log");
			_inert = inert;
		}

		internal void Update()
		{
			if (_inert || _failed)
			{
				return;
			}
			float unscaledTime = Time.unscaledTime;
			if (unscaledTime < _nextRefresh && unscaledTime >= 0f)
			{
				return;
			}
			_nextRefresh = unscaledTime + FiniteClamp(AwarenessConfig.RefreshInterval.Value, 0.2f, 2f, 0.35f);
			if (!AwarenessConfig.Enabled.Value)
			{
				ClearAll();
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				ClearAll();
				return;
			}
			if (InventoryGui.IsVisible())
			{
				UpdateItemComparison(localPlayer, unscaledTime);
				return;
			}
			_panels.Clear(AwarenessPanel.ItemComparison);
			UpdateFood(localPlayer);
			UpdateEffects(localPlayer);
			UpdateComfort(localPlayer);
			UpdateContext(localPlayer, unscaledTime);
		}

		internal void Draw()
		{
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Invalid comparison between Unknown and I4
			//IL_0220: Unknown result type (might be due to invalid IL or missing references)
			//IL_0238: Unknown result type (might be due to invalid IL or missing references)
			//IL_023d: Unknown result type (might be due to invalid IL or missing references)
			//IL_027f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0284: Unknown result type (might be due to invalid IL or missing references)
			if (_inert || _failed || !AwarenessConfig.Enabled.Value || Event.current == null || (int)Event.current.type != 7 || ShouldSuppressOverlay())
			{
				return;
			}
			bool inventoryOnly = InventoryGui.IsVisible();
			if (_panels.Compose(inventoryOnly))
			{
				_content.text = _panels.ComposedText;
				_layoutDirty = true;
			}
			if (!string.IsNullOrEmpty(_content.text))
			{
				bool flag = false;
				try
				{
					flag = ZInput.IsGamepadActive();
				}
				catch (Exception)
				{
				}
				float num = Mathf.Clamp(FiniteClamp(AwarenessConfig.UiScale.Value, 0.75f, 2f, 1f) * (flag ? FiniteClamp(AwarenessConfig.ControllerScaleMultiplier.Value, 1f, 1.5f, 1.15f) : 1f), 0.75f, 3f);
				int num2 = Mathf.Clamp(Mathf.RoundToInt(14f * num), 11, 42);
				EnsureStyles(num2);
				Rect safeArea = Screen.safeArea;
				float num3 = Mathf.Max(10f, 14f * num);
				float num4 = Mathf.Max(120f, ((Rect)(ref safeArea)).width - num3 * 2f);
				float num5 = Mathf.Min(280f, num4);
				float num6 = Mathf.Clamp(430f * num, num5, num4);
				float num7 = Mathf.Max(120f, num6 - num3 * 1.6f);
				float num8 = Mathf.Max((float)num2 + num3, ((Rect)(ref safeArea)).height - num3 * 2f);
				if (Mathf.Abs(num7 - _lastInnerWidth) > 0.5f)
				{
					_lastInnerWidth = num7;
					_layoutDirty = true;
				}
				if (Mathf.Abs(num8 - _lastMaximumHeight) > 0.5f)
				{
					_lastMaximumHeight = num8;
					_layoutDirty = true;
				}
				if (_layoutDirty)
				{
					_cachedHeight = Mathf.Min(num8, _textStyle.CalcHeight(_content, num7) + num3 * 1.4f);
					_layoutDirty = false;
				}
				float height = Mathf.Min(num8, Mathf.Max((float)num2 + num3, _cachedHeight));
				Rect val = AnchoredRect(safeArea, Screen.height, num6, height, num3, AwarenessConfig.Anchor.Value);
				Rect val2 = new Rect(((Rect)(ref val)).x + num3 * 0.8f, ((Rect)(ref val)).y + num3 * 0.65f, ((Rect)(ref val)).width - num3 * 1.6f, ((Rect)(ref val)).height - num3 * 1.3f);
				GUI.Box(val, GUIContent.none, _boxStyle);
				GUI.Label(val2, _content, _textStyle);
			}
		}

		internal void OnConfigurationChanged(ConfigDefinition definition)
		{
			_nextRefresh = 0f;
			ResetSignatures();
			if (definition == (ConfigDefinition)null || (definition.Section == "Display" && definition.Key == "MaximumComfortWinnerRows"))
			{
				ComfortCapture.Reset();
			}
			_styleFontSize = 0;
			_layoutDirty = true;
			if (!AwarenessConfig.ShowFoodTimers.Value)
			{
				_panels.Clear(AwarenessPanel.Food);
			}
			if (!AwarenessConfig.ShowEffectTimers.Value)
			{
				_panels.Clear(AwarenessPanel.Effects);
			}
			if (!AwarenessConfig.ShowComfort.Value)
			{
				_panels.Clear(AwarenessPanel.Comfort);
			}
			if (!AwarenessConfig.ShowComfort.Value)
			{
				ComfortCapture.Reset();
			}
			if (!AwarenessConfig.ShowItemComparison.Value)
			{
				_panels.Clear(AwarenessPanel.ItemComparison);
				HoverItemCapture.Reset();
			}
			if (!AwarenessConfig.ShowProduction.Value)
			{
				_panels.Clear(AwarenessPanel.Production);
			}
			if (!AwarenessConfig.ShowAgriculture.Value)
			{
				_panels.Clear(AwarenessPanel.Agriculture);
			}
			if (!AwarenessConfig.ShowBuilding.Value)
			{
				_panels.Clear(AwarenessPanel.Building);
			}
			if (!AwarenessConfig.ShowTamedAnimals.Value)
			{
				_panels.Clear(AwarenessPanel.TamedAnimal);
			}
			if (!AwarenessConfig.ShowProduction.Value && !AwarenessConfig.ShowAgriculture.Value && !AwarenessConfig.ShowBuilding.Value && !AwarenessConfig.ShowTamedAnimals.Value)
			{
				ContextCapture.Reset();
			}
			if (!AwarenessConfig.Enabled.Value)
			{
				ComfortCapture.Reset();
				HoverItemCapture.Reset();
				ContextCapture.Reset();
				ClearAll();
			}
			if (AwarenessConfig.VerboseLogging.Value)
			{
				_log.LogInfo((object)("Awareness configuration refreshed: " + ((definition == (ConfigDefinition)null) ? "unknown" : (definition.Section + "/" + definition.Key))));
			}
		}

		internal void OnLocalizationChanged()
		{
			_nextRefresh = 0f;
			ResetSignatures();
			ClearAll();
			if (AwarenessConfig.VerboseLogging.Value)
			{
				_log.LogInfo((object)"Awareness display cache cleared for a language change.");
			}
		}

		internal void FailClosed(string operation, Exception exception)
		{
			if (!_failed)
			{
				_failed = true;
				ClearAll();
				_log.LogError((object)("Runic Awareness display runtime stopped after " + operation + "; gameplay and vanilla UI remain unchanged. " + exception.GetType().Name + ": " + exception.Message));
			}
		}

		internal void FailHook(string hook, Exception exception)
		{
			if (_hookFailureCount < 3)
			{
				_hookFailureCount++;
				_log.LogWarning((object)("Awareness ignored a display capture failure in " + hook + ": " + exception.GetType().Name + "."));
			}
		}

		public void Dispose()
		{
			ClearAll();
			_textStyle = null;
			_boxStyle = null;
		}

		private void UpdateFood(Player player)
		{
			if (!AwarenessConfig.ShowFoodTimers.Value)
			{
				_panels.Clear(AwarenessPanel.Food);
				return;
			}
			List<Food> foods = player.GetFoods();
			int num = foods?.Count ?? 0;
			ulong num2 = HashStart(num, (int)AwarenessConfig.TimerPrecision.Value);
			if (num > 8)
			{
				num2 = HashMix(num2, num);
				if (!SameSignature(ref _foodSignature, ref _hadFoodSignature, num2))
				{
					_panels.Set(AwarenessPanel.Food, "Details withheld: local food list exceeds the hard limit of " + 8 + ".");
				}
				return;
			}
			for (int i = 0; i < num; i++)
			{
				Food val = foods[i];
				num2 = HashMix(num2, TimerFormatter.Bucket(val?.m_time ?? 0f, AwarenessConfig.TimerPrecision.Value));
				num2 = HashMix(num2, BoundedText.HashBounded(FoodName(val), 96));
			}
			if (SameSignature(ref _foodSignature, ref _hadFoodSignature, num2))
			{
				return;
			}
			if (num == 0)
			{
				_panels.Clear(AwarenessPanel.Food);
				return;
			}
			_builder.Clear();
			int num3 = Math.Min(num, 3);
			for (int j = 0; j < num3; j++)
			{
				if (j > 0)
				{
					_builder.Append('\n');
				}
				Food val2 = foods[j];
				_builder.Append(BoundedLocalization.Label(FoodName(val2))).Append(": ");
				TimerFormatter.Append(_builder, TimerFormatter.Bucket(val2?.m_time ?? 0f, AwarenessConfig.TimerPrecision.Value));
			}
			if (num > num3)
			{
				_builder.Append("\n+").Append(num - num3).Append(" more local food slots");
			}
			_panels.Set(AwarenessPanel.Food, BoundedText.Sanitize(_builder.ToString(), 512, 4));
		}

		private void UpdateEffects(Player player)
		{
			if (!AwarenessConfig.ShowEffectTimers.Value)
			{
				_panels.Clear(AwarenessPanel.Effects);
				return;
			}
			SEMan sEMan = ((Character)player).GetSEMan();
			List<StatusEffect> list = ((sEMan != null) ? sEMan.GetStatusEffects() : null);
			int num = list?.Count ?? 0;
			ulong hash = HashStart(num, (int)AwarenessConfig.TimerPrecision.Value);
			if (num > 32)
			{
				hash = HashMix(hash, num);
				if (!SameSignature(ref _effectSignature, ref _hadEffectSignature, hash))
				{
					_panels.Set(AwarenessPanel.Effects, "Details withheld: local effect list exceeds the hard limit of " + 32 + ".");
				}
				return;
			}
			int num2 = 0;
			for (int i = 0; i < num; i++)
			{
				StatusEffect val = list[i];
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val.m_icon == (Object)null))
				{
					num2++;
					int value = ((val.m_ttl > 0f) ? TimerFormatter.Bucket(val.GetRemaningTime(), AwarenessConfig.TimerPrecision.Value) : (-1));
					hash = HashMix(hash, value);
					hash = HashMix(hash, BoundedText.HashBounded(val.m_name, 96));
				}
			}
			hash = HashMix(hash, num2);
			if (SameSignature(ref _effectSignature, ref _hadEffectSignature, hash))
			{
				return;
			}
			if (num2 == 0)
			{
				_panels.Clear(AwarenessPanel.Effects);
				return;
			}
			_builder.Clear();
			int num3 = Math.Max(1, Math.Min(8, AwarenessConfig.MaximumEffectRows.Value));
			int num4 = 0;
			for (int j = 0; j < num; j++)
			{
				if (num4 >= num3)
				{
					break;
				}
				StatusEffect val2 = list[j];
				if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2.m_icon == (Object)null))
				{
					if (num4 > 0)
					{
						_builder.Append('\n');
					}
					_builder.Append(BoundedLocalization.Label(val2.m_name)).Append(": ");
					if (val2.m_ttl > 0f)
					{
						TimerFormatter.Append(_builder, TimerFormatter.Bucket(val2.GetRemaningTime(), AwarenessConfig.TimerPrecision.Value));
					}
					else
					{
						_builder.Append("active");
					}
					num4++;
				}
			}
			if (num2 > num4)
			{
				_builder.Append("\n+").Append(num2 - num4).Append(" more HUD effects");
			}
			_panels.Set(AwarenessPanel.Effects, BoundedText.Sanitize(_builder.ToString(), 768, num3 + 1));
		}

		private void UpdateComfort(Player player)
		{
			if (!AwarenessConfig.ShowComfort.Value)
			{
				_panels.Clear(AwarenessPanel.Comfort);
				return;
			}
			int num = Math.Max(0, player.GetComfortLevel());
			bool flag = player.InShelter();
			ComfortCapture.TryGet(player, num, flag, Time.unscaledTime, out var captured);
			SEMan sEMan = ((Character)player).GetSEMan();
			StatusEffect val = ((sEMan != null) ? sEMan.GetStatusEffect(SEMan.s_statusEffectRested) : null);
			int num2 = (((Object)(object)val != (Object)null && val.m_ttl > 0f) ? TimerFormatter.Bucket(val.GetRemaningTime(), AwarenessConfig.TimerPrecision.Value) : (-1));
			ulong hash = HashStart(num, flag ? 1 : 0);
			hash = HashMix(hash, captured.Version);
			hash = HashMix(hash, num2);
			if (!SameSignature(ref _comfortSignature, ref _hadComfortSignature, hash))
			{
				_builder.Clear();
				_builder.Append("Level ").Append(num).Append(flag ? " (sheltered)" : " (not sheltered)");
				_builder.Append("\nRested: ");
				if (num2 >= 0)
				{
					TimerFormatter.Append(_builder, num2);
				}
				else
				{
					_builder.Append("inactive");
				}
				if (!string.IsNullOrEmpty(captured.Detail))
				{
					_builder.Append('\n').Append(captured.Detail);
				}
				else
				{
					_builder.Append("\nCategory winners appear after vanilla's next comfort check.");
				}
				_panels.Set(AwarenessPanel.Comfort, BoundedText.Sanitize(_builder.ToString(), 1024, 12));
			}
		}

		private void UpdateItemComparison(Player player, float now)
		{
			if (!AwarenessConfig.ShowItemComparison.Value || !HoverItemCapture.TryGet(player, now, out var item) || item?.m_shared == null)
			{
				_panels.Clear(AwarenessPanel.ItemComparison);
				_hadItemSignature = false;
				return;
			}
			string text = ((object)Unsafe.As<ItemType, ItemType>(ref item.m_shared.m_itemType)/*cast due to .constrained prefix*/).ToString();
			ItemComparisonDisposition itemComparisonDisposition = ItemTypePolicy.Classify(text);
			if (itemComparisonDisposition == ItemComparisonDisposition.HideKnownNonEquipment)
			{
				_panels.Clear(AwarenessPanel.ItemComparison);
				_hadItemSignature = false;
				return;
			}
			try
			{
				ItemData val = ((itemComparisonDisposition == ItemComparisonDisposition.CompareKnownEquipment) ? ValheimContracts.EquippedFor(player, item) : null);
				ItemMetrics itemMetrics = ReadMetrics(player, item, text);
				ItemMetrics? itemMetrics2 = ((val?.m_shared == null) ? ((ItemMetrics?)null) : new ItemMetrics?(ReadMetrics(player, val, ((object)Unsafe.As<ItemType, ItemType>(ref val.m_shared.m_itemType)/*cast due to .constrained prefix*/).ToString())));
				ulong current = ItemSignature(item, val, itemMetrics, itemMetrics2);
				if (!SameSignature(ref _itemSignature, ref _hadItemSignature, current))
				{
					_panels.Set(AwarenessPanel.ItemComparison, ItemComparisonFormatter.Format(itemMetrics, itemMetrics2, item == val));
				}
			}
			catch (Exception exception)
			{
				_panels.Clear(AwarenessPanel.ItemComparison);
				_hadItemSignature = false;
				FailHook("item comparison", exception);
			}
		}

		private void UpdateContext(Player player, float now)
		{
			Piece val = (AwarenessConfig.ShowBuilding.Value ? player.GetHoveringPiece() : null);
			if ((Object)(object)val != (Object)null)
			{
				ClearContextExcept(AwarenessPanel.Building);
				if (AllowsDetailedDisclosure(player, (Component)(object)val))
				{
					UpdateBuilding(val, now);
				}
				else
				{
					UpdateBuildingUnavailable();
				}
				return;
			}
			if (!ContextCapture.TryGet(((Humanoid)player).GetHoverObject(), now, out var context) || !ContextEnabled(context.Kind))
			{
				ClearContextPanels();
				if (_hadContextSignature)
				{
					_hadContextSignature = false;
					_contextSignature = 0uL;
				}
				return;
			}
			AwarenessPanel awarenessPanel = PanelFor(context.Kind);
			ClearContextExcept(awarenessPanel);
			ulong hash = HashStart((int)context.Kind, (!((Object)(object)context.Subject == (Object)null)) ? ((Object)context.Subject).GetInstanceID() : 0);
			hash = HashMix(hash, BoundedText.HashBounded(context.VisibleText, 768));
			if (!SameSignature(ref _contextSignature, ref _hadContextSignature, hash))
			{
				string text = BoundedText.Sanitize(context.VisibleText, 768, Math.Max(1, Math.Min(6, AwarenessConfig.MaximumContextLines.Value)));
				if (string.IsNullOrEmpty(text))
				{
					_panels.Clear(awarenessPanel);
				}
				else
				{
					_panels.Set(awarenessPanel, text);
				}
			}
		}

		private void UpdateBuilding(Piece piece, float now)
		{
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c1: Unknown result type (might be due to invalid IL or missing references)
			WearNTear componentInParent = ((Component)piece).GetComponentInParent<WearNTear>();
			float num = (((Object)(object)componentInParent == (Object)null) ? (-1f) : (componentInParent.GetHealthPercentage() * 100f));
			CraftingStation craftingStation = piece.m_craftingStation;
			if (piece != _lastBuildingPiece || now >= _nextStationLevelRefresh)
			{
				_lastBuildingPiece = piece;
				_cachedStationLevel = ((!((Object)(object)craftingStation == (Object)null)) ? Math.Max(0, craftingStation.GetLevel(false)) : 0);
				_nextStationLevelRefresh = now + 2f;
			}
			Vector3 position = ((Component)piece).transform.position;
			Vector3 eulerAngles = ((Component)piece).transform.eulerAngles;
			ulong hash = HashStart(((Object)piece).GetInstanceID(), Quantize(num));
			hash = HashMix(hash, Quantize(position.x));
			hash = HashMix(hash, Quantize(position.y));
			hash = HashMix(hash, Quantize(position.z));
			hash = HashMix(hash, Quantize(eulerAngles.x));
			hash = HashMix(hash, Quantize(eulerAngles.y));
			hash = HashMix(hash, Quantize(eulerAngles.z));
			hash = HashMix(hash, _cachedStationLevel);
			if (!SameSignature(ref _contextSignature, ref _hadContextSignature, hash))
			{
				_builder.Clear();
				_builder.Append(BoundedLocalization.Label(piece.m_name));
				if (num >= 0f)
				{
					_builder.Append(" | health ").Append(num.ToString("0", CultureInfo.InvariantCulture)).Append('%');
				}
				_builder.Append("\nPosition: ").Append(VectorText(position));
				_builder.Append("\nRotation: ").Append(VectorText(eulerAngles));
				if (_cachedStationLevel > 0)
				{
					_builder.Append("\nStation level: ").Append(_cachedStationLevel);
				}
				_builder.Append("\nSupport colors: blue grounded; green/yellow/orange supported; red weakest.");
				_panels.Set(AwarenessPanel.Building, BoundedText.Sanitize(_builder.ToString(), 768, 6));
			}
		}

		private void UpdateBuildingUnavailable()
		{
			ulong current = HashStart(6, 1431191894);
			if (!SameSignature(ref _contextSignature, ref _hadContextSignature, current))
			{
				_lastBuildingPiece = null;
				_cachedStationLevel = 0;
				_nextStationLevelRefresh = 0f;
				_panels.Set(AwarenessPanel.Building, "Building details unavailable outside avatar interaction reach or without current ward access.");
			}
		}

		private unsafe static ItemMetrics ReadMetrics(Player player, ItemData item, string itemType)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			SkillType skillType = item.m_shared.m_skillType;
			float skillFactor = ((Character)player).GetSkillFactor(skillType);
			float skillLevel = ((Character)player).GetSkillLevel(skillType);
			string name = BoundedLocalization.Label(item.m_shared.m_name);
			int quality = item.m_quality;
			DamageTypes damage = item.GetDamage();
			return new ItemMetrics(name, itemType, quality, ((DamageTypes)(ref damage)).GetTotalDamage(), item.GetArmor(), item.GetBlockPower(skillFactor), item.m_shared.m_movementModifier * 100f, item.GetNonStackedWeight(), item.m_durability, item.GetMaxDurability(), ((object)(*(SkillType*)(&skillType))/*cast due to .constrained prefix*/).ToString(), skillLevel);
		}

		private static ulong ItemSignature(ItemData selected, ItemData equipped, ItemMetrics selectedMetrics, ItemMetrics? equippedMetrics)
		{
			ulong signature = HashStart(RuntimeHelpers.GetHashCode(selected), (equipped != null) ? RuntimeHelpers.GetHashCode(equipped) : 0);
			signature = HashMetrics(signature, selectedMetrics);
			if (equippedMetrics.HasValue)
			{
				signature = HashMix(signature, 1);
				signature = HashMetrics(signature, equippedMetrics.Value);
			}
			return signature;
		}

		private static ulong HashMetrics(ulong signature, ItemMetrics metrics)
		{
			signature = HashMix(signature, BoundedText.HashBounded(metrics.Name, 96));
			signature = HashMix(signature, BoundedText.HashBounded(metrics.ItemType, 96));
			signature = HashMix(signature, metrics.Quality);
			signature = HashMix(signature, Quantize(metrics.Damage));
			signature = HashMix(signature, Quantize(metrics.Armor));
			signature = HashMix(signature, Quantize(metrics.Block));
			signature = HashMix(signature, Quantize(metrics.MovementPercent));
			signature = HashMix(signature, Quantize(metrics.Weight));
			signature = HashMix(signature, Quantize(metrics.Durability));
			signature = HashMix(signature, Quantize(metrics.MaximumDurability));
			signature = HashMix(signature, BoundedText.HashBounded(metrics.SkillName, 96));
			return HashMix(signature, Quantize(metrics.SkillLevel));
		}

		private static string FoodName(Food food)
		{
			return food?.m_item?.m_shared?.m_name ?? food?.m_name ?? string.Empty;
		}

		private static bool AllowsDetailedDisclosure(Player player, Component subject)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player == (Object)null || (Object)(object)subject == (Object)null)
			{
				return false;
			}
			Transform transform = subject.transform;
			Transform transform2 = ((Component)player).transform;
			if ((Object)(object)transform == (Object)null || (Object)(object)transform2 == (Object)null)
			{
				return false;
			}
			Vector3 position = transform.position;
			Vector3 val = position - transform2.position;
			if (ContextDisclosurePolicy.AllowsDetailedDisclosure(((Vector3)(ref val)).sqrMagnitude, player.m_maxInteractDistance))
			{
				return StrictWardDisclosure.Allows(position);
			}
			return false;
		}

		private bool ContextEnabled(AwarenessContextKind kind)
		{
			return kind switch
			{
				AwarenessContextKind.Production => AwarenessConfig.ShowProduction.Value, 
				AwarenessContextKind.Agriculture => AwarenessConfig.ShowAgriculture.Value, 
				AwarenessContextKind.Building => AwarenessConfig.ShowBuilding.Value, 
				AwarenessContextKind.TamedAnimal => AwarenessConfig.ShowTamedAnimals.Value, 
				_ => false, 
			};
		}

		private static AwarenessPanel PanelFor(AwarenessContextKind kind)
		{
			return kind switch
			{
				AwarenessContextKind.Production => AwarenessPanel.Production, 
				AwarenessContextKind.Agriculture => AwarenessPanel.Agriculture, 
				AwarenessContextKind.Building => AwarenessPanel.Building, 
				AwarenessContextKind.TamedAnimal => AwarenessPanel.TamedAnimal, 
				_ => throw new ArgumentOutOfRangeException("kind"), 
			};
		}

		private void ClearContextPanels()
		{
			_panels.Clear(AwarenessPanel.Production);
			_panels.Clear(AwarenessPanel.Agriculture);
			_panels.Clear(AwarenessPanel.Building);
			_panels.Clear(AwarenessPanel.TamedAnimal);
		}

		private void ClearContextExcept(AwarenessPanel retained)
		{
			if (retained != AwarenessPanel.Production)
			{
				_panels.Clear(AwarenessPanel.Production);
			}
			if (retained != AwarenessPanel.Agriculture)
			{
				_panels.Clear(AwarenessPanel.Agriculture);
			}
			if (retained != AwarenessPanel.Building)
			{
				_panels.Clear(AwarenessPanel.Building);
			}
			if (retained != AwarenessPanel.TamedAnimal)
			{
				_panels.Clear(AwarenessPanel.TamedAnimal);
			}
		}

		private void ClearAll()
		{
			for (int i = 0; i <= 7; i++)
			{
				_panels.Clear((AwarenessPanel)i);
			}
			_content.text = string.Empty;
			_layoutDirty = true;
			ResetSignatures();
			_lastBuildingPiece = null;
			_cachedStationLevel = 0;
			_nextStationLevelRefresh = 0f;
		}

		private void ResetSignatures()
		{
			_foodSignature = (_effectSignature = (_comfortSignature = (_itemSignature = (_contextSignature = 0uL))));
			_hadFoodSignature = (_hadEffectSignature = (_hadComfortSignature = (_hadItemSignature = (_hadContextSignature = false))));
		}

		private bool ShouldSuppressOverlay()
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || Menu.IsVisible() || Console.IsVisible() || TextInput.IsVisible() || StoreGui.IsVisible() || InventoryGui.IsVisible() || Minimap.IsOpen() || Hud.IsPieceSelectionVisible() || PlayerCustomizaton.IsBarberGuiVisible() || Feedback.IsVisible() || UnifiedPopup.IsVisible() || ConnectPanel.IsVisible() || ZInput.VirtualKeyboardOpen)
			{
				return true;
			}
			TextViewer instance = TextViewer.instance;
			if ((Object)(object)instance != (Object)null && instance.IsVisible())
			{
				return true;
			}
			Chat instance2 = Chat.instance;
			if ((Object)(object)instance2 != (Object)null && (instance2.HasFocus() || instance2.IsChatDialogWindowVisible()))
			{
				return true;
			}
			if (Game.IsPaused() || (Object)(object)Hud.instance == (Object)null || Hud.instance.m_userHidden)
			{
				return true;
			}
			return _portalPanels.SetupPanelVisible(localPlayer);
		}

		private void EnsureStyles(int fontSize)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Expected O, but got Unknown
			//IL_005a: Expected O, but got Unknown
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Expected O, but got Unknown
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			if (_textStyle == null || _boxStyle == null || _styleFontSize != fontSize)
			{
				_textStyle = new GUIStyle(GUI.skin.label)
				{
					richText = true,
					wordWrap = true,
					fontSize = fontSize,
					alignment = (TextAnchor)0,
					padding = new RectOffset(0, 0, 0, 0)
				};
				_textStyle.normal.textColor = new Color(0.92f, 0.95f, 1f, 1f);
				_boxStyle = new GUIStyle(GUI.skin.box);
				_boxStyle.normal.textColor = Color.clear;
				_styleFontSize = fontSize;
				_lastInnerWidth = 0f;
				_lastMaximumHeight = 0f;
				_layoutDirty = true;
			}
		}

		internal static Rect AnchoredRect(Rect safe, float screenHeight, float width, float height, float margin, AwarenessOverlayAnchor anchor)
		{
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			bool flag = anchor == AwarenessOverlayAnchor.MiddleLeft;
			bool num = anchor == AwarenessOverlayAnchor.TopRight || anchor == AwarenessOverlayAnchor.BottomRight;
			bool flag2 = anchor == AwarenessOverlayAnchor.BottomRight || anchor == AwarenessOverlayAnchor.BottomLeft;
			float num2 = (num ? (((Rect)(ref safe)).xMax - width - margin) : (((Rect)(ref safe)).xMin + margin));
			float num3 = screenHeight - ((Rect)(ref safe)).yMax;
			float num4 = screenHeight - ((Rect)(ref safe)).yMin;
			float num5 = (flag ? (num3 + (((Rect)(ref safe)).height - height) * 0.5f) : (flag2 ? (num4 - height - margin) : (num3 + margin)));
			return new Rect(num2, num5, width, height);
		}

		private static bool SameSignature(ref ulong stored, ref bool hasStored, ulong current)
		{
			if (hasStored && stored == current)
			{
				return true;
			}
			stored = current;
			hasStored = true;
			return false;
		}

		private static ulong HashStart(int left, int right)
		{
			return HashMix(HashMix(1469598103934665603uL, left), right);
		}

		private static ulong HashMix(ulong hash, int value)
		{
			hash ^= (uint)value;
			return hash * 1099511628211L;
		}

		private static ulong HashMix(ulong hash, ulong value)
		{
			hash ^= value;
			return hash * 1099511628211L;
		}

		private static int Quantize(float value)
		{
			if (float.IsNaN(value) || float.IsInfinity(value))
			{
				return 0;
			}
			double num = Math.Round((double)value * 10.0, MidpointRounding.AwayFromZero);
			if (!(num > 2147483647.0))
			{
				if (!(num < -2147483648.0))
				{
					return (int)num;
				}
				return int.MinValue;
			}
			return int.MaxValue;
		}

		private static float FiniteClamp(float value, float minimum, float maximum, float fallback)
		{
			if (float.IsNaN(value) || float.IsInfinity(value))
			{
				value = fallback;
			}
			return Math.Max(minimum, Math.Min(maximum, value));
		}

		private static string VectorText(Vector3 value)
		{
			return value.x.ToString("0.0", CultureInfo.InvariantCulture) + ", " + value.y.ToString("0.0", CultureInfo.InvariantCulture) + ", " + value.z.ToString("0.0", CultureInfo.InvariantCulture);
		}
	}
	internal static class BoundedLocalization
	{
		internal const int MaximumTokenCharacters = 128;

		private static readonly FieldInfo Translations = ResolveTranslations();

		internal static bool IsSupported => Translations != null;

		internal static string Label(string raw)
		{
			if (string.IsNullOrEmpty(raw))
			{
				return string.Empty;
			}
			if (!TryGetTranslationKey(raw, out var key))
			{
				return BoundedText.Label(raw);
			}
			try
			{
				Localization instance = Localization.instance;
				string value = key;
				if (instance != null && Translations != null && Translations.GetValue(instance) is Dictionary<string, string> dictionary && dictionary.TryGetValue(key, out var value2) && value2 != null)
				{
					value = value2;
				}
				return BoundedText.Label(value);
			}
			catch
			{
				return BoundedText.Label(key);
			}
		}

		internal static bool TryGetTranslationKey(string raw, out string key)
		{
			key = null;
			if (string.IsNullOrEmpty(raw) || raw.Length < 2 || raw.Length > 128 || raw[0] != '$')
			{
				return false;
			}
			int i;
			for (i = 1; i < raw.Length && raw[i] == '$'; i++)
			{
			}
			if (i == raw.Length)
			{
				return false;
			}
			for (int j = i; j < raw.Length; j++)
			{
				char c = raw[j];
				if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9') && c != '_' && c != '-' && c != '.')
				{
					return false;
				}
			}
			key = raw.Substring(i);
			if (key.StartsWith("KEY_", StringComparison.Ordinal))
			{
				key = null;
				return false;
			}
			return true;
		}

		private static FieldInfo ResolveTranslations()
		{
			try
			{
				FieldInfo field = typeof(Localization).GetField("m_translations", BindingFlags.Instance | BindingFlags.NonPublic);
				return (field != null && field.FieldType == typeof(Dictionary<string, string>) && !field.IsStatic) ? field : null;
			}
			catch
			{
				return null;
			}
		}
	}
	internal readonly struct CapturedComfort
	{
		internal int Level { get; }

		internal bool Sheltered { get; }

		internal string Detail { get; }

		internal ulong Signature { get; }

		internal int Version { get; }

		internal int SourceInstanceId { get; }

		internal float CapturedAt { get; }

		internal CapturedComfort(int level, bool sheltered, string detail, ulong signature, int version)
			: this(level, sheltered, detail, signature, version, 0, 0f)
		{
		}

		internal CapturedComfort(int level, bool sheltered, string detail, ulong signature, int version, int sourceInstanceId, float capturedAt)
		{
			Level = level;
			Sheltered = sheltered;
			Detail = detail ?? string.Empty;
			Signature = signature;
			Version = version;
			SourceInstanceId = sourceInstanceId;
			CapturedAt = capturedAt;
		}
	}
	internal static class ComfortCapture
	{
		internal const float MaximumAgeSeconds = 5f;

		private static readonly StringBuilder Builder = new StringBuilder(512);

		private static CapturedComfort _current;

		internal static CapturedComfort Current => _current;

		internal static bool TryGet(Player player, int level, bool sheltered, float now, out CapturedComfort captured)
		{
			captured = _current;
			if ((Object)(object)player != (Object)null && captured.Version > 0 && captured.SourceInstanceId == ((Object)player).GetInstanceID() && captured.Level == level && captured.Sheltered == sheltered && now >= captured.CapturedAt && now - captured.CapturedAt <= 5f)
			{
				return true;
			}
			captured = default(CapturedComfort);
			return false;
		}

		internal static void Record(Player player, int level)
		{
			if (Plugin.Instance?.Runtime == null || !AwarenessConfig.Enabled.Value || !AwarenessConfig.ShowComfort.Value || (Object)(object)player == (Object)null || player != Player.m_localPlayer)
			{
				return;
			}
			bool flag = player.InShelter();
			FieldInfo comfortPiecesField = ValheimContracts.ComfortPiecesField;
			List<Piece> list = (flag ? (comfortPiecesField.GetValue(null) as List<Piece>) : null);
			int num = list?.Count ?? 0;
			int instanceID = ((Object)player).GetInstanceID();
			ulong hash = Hash(level, flag, list, num);
			hash = Mix(hash, instanceID);
			if (_current.Version > 0 && _current.Signature == hash)
			{
				_current = new CapturedComfort(_current.Level, _current.Sheltered, _current.Detail, _current.Signature, _current.Version, _current.SourceInstanceId, Time.unscaledTime);
				return;
			}
			Builder.Clear();
			if (num > 64)
			{
				Builder.Append("Nearby category details withheld: vanilla returned ").Append(num).Append(" comfort pieces (hard limit ")
					.Append(64)
					.Append(").");
			}
			else
			{
				AppendWinners(Builder, list, num, flag);
				if (!flag)
				{
					if (Builder.Length > 0)
					{
						Builder.Append('\n');
					}
					Builder.Append("Known opportunity: shelter is not currently counted.");
				}
			}
			_current = new CapturedComfort(Math.Max(0, level), flag, BoundedText.Sanitize(Builder.ToString(), 768, 10), hash, _current.Version + 1, instanceID, Time.unscaledTime);
		}

		internal static void Reset()
		{
			_current = default(CapturedComfort);
			Builder.Clear();
		}

		private static void AppendWinners(StringBuilder builder, List<Piece> pieces, int count, bool sheltered)
		{
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			int num = Math.Max(1, Math.Min(8, AwarenessConfig.MaximumComfortRows.Value));
			int num2 = 0;
			int num3 = 0;
			Piece val = null;
			builder.Append("Counted: base +1");
			if (sheltered)
			{
				builder.Append(", shelter +1");
			}
			for (int i = 0; i < count; i++)
			{
				Piece val2 = pieces[i];
				if ((Object)(object)val2 == (Object)null)
				{
					continue;
				}
				int num4;
				if ((Object)(object)val != (Object)null)
				{
					if (((object)Unsafe.As<ComfortGroup, ComfortGroup>(ref val2.m_comfortGroup)/*cast due to .constrained prefix*/).ToString() != "None")
					{
						ref ComfortGroup comfortGroup = ref val2.m_comfortGroup;
						object obj = val.m_comfortGroup;
						if (((object)Unsafe.As<ComfortGroup, ComfortGroup>(ref comfortGroup)/*cast due to .constrained prefix*/).Equals(obj))
						{
							num4 = 1;
							goto IL_00b6;
						}
					}
					num4 = (string.Equals(val2.m_name, val.m_name, StringComparison.Ordinal) ? 1 : 0);
				}
				else
				{
					num4 = 0;
				}
				goto IL_00b6;
				IL_00b6:
				int comfort = val2.GetComfort();
				if (num4 != 0 || comfort <= 0)
				{
					num3++;
				}
				else
				{
					num2++;
					if (num2 <= num)
					{
						builder.Append(", ").Append(BoundedLocalization.Label(val2.m_name)).Append(" +")
							.Append(comfort);
					}
				}
				val = val2;
			}
			if (num2 > num)
			{
				builder.Append(" (+").Append(num2 - num).Append(" more winners)");
			}
			if (num3 > 0)
			{
				builder.Append("\nNearby alternatives not counted: ").Append(num3).Append(" (same category/name or zero comfort).");
			}
		}

		private static ulong Hash(int level, bool sheltered, List<Piece> pieces, int count)
		{
			ulong hash = 1469598103934665603uL;
			hash = Mix(hash, level);
			hash = Mix(hash, sheltered ? 1 : 0);
			hash = Mix(hash, count);
			if (pieces == null || count > 64)
			{
				return hash;
			}
			for (int i = 0; i < count; i++)
			{
				Piece val = pieces[i];
				if ((Object)(object)val == (Object)null)
				{
					hash = Mix(hash, -1);
					continue;
				}
				hash = Mix(hash, ((object)Unsafe.As<ComfortGroup, ComfortGroup>(ref val.m_comfortGroup)/*cast due to .constrained prefix*/).GetHashCode());
				hash = Mix(hash, val.GetComfort());
				hash ^= BoundedText.HashBounded(val.m_name, 96);
				hash *= 1099511628211L;
			}
			return hash;
		}

		private static ulong Mix(ulong hash, int value)
		{
			hash ^= (uint)value;
			return hash * 1099511628211L;
		}
	}
	internal enum AwarenessContextKind
	{
		None,
		Production,
		Agriculture,
		Building,
		TamedAnimal
	}
	internal readonly struct CapturedContext
	{
		internal AwarenessContextKind Kind { get; }

		internal Component Source { get; }

		internal Component Subject { get; }

		internal string VisibleText { get; }

		internal float CapturedAt { get; }

		internal CapturedContext(AwarenessContextKind kind, Component source, Component subject, string visibleText, float capturedAt)
		{
			Kind = kind;
			Source = source;
			Subject = subject;
			VisibleText = visibleText ?? string.Empty;
			CapturedAt = capturedAt;
		}
	}
	internal static class ContextCapture
	{
		internal const float MaximumAgeSeconds = 0.8f;

		internal const int MaximumVisibleTextCharacters = 8192;

		private static CapturedContext _current;

		internal static void Record(AwarenessContextKind kind, Component source, Component subject, string visibleText)
		{
			if (Plugin.Instance?.Runtime != null && AwarenessConfig.Enabled.Value && IsEnabled(kind) && !((Object)(object)source == (Object)null) && !((Object)(object)subject == (Object)null) && kind != AwarenessContextKind.None)
			{
				if (visibleText != null && visibleText.Length > 8192)
				{
					_current = default(CapturedContext);
				}
				else
				{
					_current = new CapturedContext(kind, source, subject, visibleText, Time.unscaledTime);
				}
			}
		}

		internal static bool TryGet(GameObject hoverObject, float now, out CapturedContext context)
		{
			context = _current;
			if ((Object)(object)hoverObject == (Object)null || (Object)(object)context.Source == (Object)null || (Object)(object)context.Subject == (Object)null || now < context.CapturedAt || now - context.CapturedAt > 0.8f)
			{
				_current = default(CapturedContext);
				return false;
			}
			Transform transform = hoverObject.transform;
			if (!SameHierarchy(transform, context.Source.transform))
			{
				return SameHierarchy(transform, context.Subject.transform);
			}
			return true;
		}

		internal static void Reset()
		{
			_current = default(CapturedContext);
		}

		private static bool IsEnabled(AwarenessContextKind kind)
		{
			return kind switch
			{
				AwarenessContextKind.Production => AwarenessConfig.ShowProduction.Value, 
				AwarenessContextKind.Agriculture => AwarenessConfig.ShowAgriculture.Value, 
				AwarenessContextKind.Building => AwarenessConfig.ShowBuilding.Value, 
				AwarenessContextKind.TamedAnimal => AwarenessConfig.ShowTamedAnimals.Value, 
				_ => false, 
			};
		}

		private static bool SameHierarchy(Transform left, Transform right)
		{
			if ((Object)(object)left == (Object)null || (Object)(object)right == (Object)null)
			{
				return false;
			}
			Transform val = left;
			int num = 0;
			while ((Object)(object)val != (Object)null && num < 16)
			{
				if ((Object)(object)val == (Object)(object)right)
				{
					return true;
				}
				num++;
				val = val.parent;
			}
			val = right;
			int num2 = 0;
			while ((Object)(object)val != (Object)null && num2 < 16)
			{
				if ((Object)(object)val == (Object)(object)left)
				{
					return true;
				}
				num2++;
				val = val.parent;
			}
			return false;
		}
	}
	internal static class HoverItemCapture
	{
		internal const float MaximumAgeSeconds = 0.8f;

		private static ItemData _item;

		private static float _capturedAt;

		private static int _sourcePlayerInstanceId;

		internal static void Record(ItemData item)
		{
			Player localPlayer = Player.m_localPlayer;
			if (Plugin.Instance?.Runtime != null && AwarenessConfig.Enabled.Value && AwarenessConfig.ShowItemComparison.Value && item != null && !((Object)(object)localPlayer == (Object)null))
			{
				_item = item;
				_capturedAt = Time.unscaledTime;
				_sourcePlayerInstanceId = ((Object)localPlayer).GetInstanceID();
			}
		}

		internal static bool TryGet(Player player, float now, out ItemData item)
		{
			item = _item;
			if ((Object)(object)player != (Object)null && item != null && _sourcePlayerInstanceId == ((Object)player).GetInstanceID() && now >= _capturedAt && now - _capturedAt <= 0.8f)
			{
				return true;
			}
			Reset();
			item = null;
			return false;
		}

		internal static void Reset()
		{
			_item = null;
			_capturedAt = 0f;
			_sourcePlayerInstanceId = 0;
		}
	}
	[HarmonyPatch(typeof(InventoryGrid), "CreateItemTooltip", new Type[]
	{
		typeof(ItemData),
		typeof(UITooltip)
	})]
	internal static class InventoryGridCreateItemTooltipPatch
	{
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void Postfix(ItemData __0)
		{
			try
			{
				HoverItemCapture.Record(__0);
			}
			catch (Exception exception)
			{
				Plugin.Instance?.Runtime?.FailHook("item tooltip", exception);
			}
		}
	}
	[HarmonyPatch(typeof(SE_Rested), "CalculateComfortLevel", new Type[] { typeof(Player) })]
	internal static class RestedCalculateComfortPatch
	{
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void Postfix(Player __0, int __result)
		{
			try
			{
				ComfortCapture.Record(__0, __result);
			}
			catch (Exception exception)
			{
				Plugin.Instance?.Runtime?.FailHook("comfort capture", exception);
			}
		}
	}
	[HarmonyPatch(typeof(CraftingStation), "GetHoverText", new Type[] { })]
	internal static class CraftingStationHoverPatch
	{
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void Postfix(CraftingStation __instance, string __result)
		{
			SafeContext.Record(AwarenessContextKind.Building, (Component)(object)__instance, (Component)(object)__instance, __result, "crafting station hover");
		}
	}
	[HarmonyPatch(typeof(CookingStation), "GetHoverText", new Type[] { })]
	internal static class CookingStationHoverPatch
	{
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void Postfix(CookingStation __instance, string __result)
		{
			SafeContext.Record(AwarenessContextKind.Production, (Component)(object)__instance, (Component)(object)__instance, __result, "cooking station hover");
		}
	}
	[HarmonyPatch(typeof(Fermenter), "GetHoverText", new Type[] { })]
	internal static class FermenterHoverPatch
	{
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void Postfix(Fermenter __instance, string __result)
		{
			SafeContext.Record(AwarenessContextKind.Production, (Component)(object)__instance, (Component)(object)__instance, __result, "fermenter hover");
		}
	}
	[HarmonyPatch(typeof(Plant), "GetHoverText", new Type[] { })]
	internal static class PlantHoverPatch
	{
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void Postfix(Plant __instance, string __result)
		{
			SafeContext.Record(AwarenessContextKind.Agriculture, (Component)(object)__instance, (Component)(object)__instance, __result, "plant hover");
		}
	}
	[HarmonyPatch(typeof(Beehive), "GetHoverText", new Type[] { })]
	internal static class BeehiveHoverPatch
	{
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void Postfix(Beehive __instance, string __result)
		{
			SafeContext.Record(AwarenessContextKind.Agriculture, (Component)(object)__instance, (Component)(object)__instance, __result, "beehive hover");
		}
	}
	[HarmonyPatch(typeof(Tameable), "GetHoverText", new Type[] { })]
	internal static class TameableHoverPatch
	{
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void Postfix(Tameable __instance, string __result)
		{
			SafeContext.Record(AwarenessContextKind.TamedAnimal, (Component)(object)__instance, (Component)(object)__instance, __result, "tameable hover");
		}
	}
	[HarmonyPatch(typeof(Switch), "GetHoverText", new Type[] { })]
	internal static class ProductionSwitchHoverPatch
	{
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void Postfix(Switch __instance, string __result)
		{
			try
			{
				if ((Object)(object)__instance == (Object)null)
				{
					return;
				}
				Smelter componentInParent = ((Component)__instance).GetComponentInParent<Smelter>();
				if ((Object)(object)componentInParent != (Object)null)
				{
					ContextCapture.Record(AwarenessContextKind.Production, (Component)(object)__instance, (Component)(object)componentInParent, __result);
					return;
				}
				CookingStation componentInParent2 = ((Component)__instance).GetComponentInParent<CookingStation>();
				if ((Object)(object)componentInParent2 != (Object)null)
				{
					ContextCapture.Record(AwarenessContextKind.Production, (Component)(object)__instance, (Component)(object)componentInParent2, __result);
					return;
				}
				Fermenter componentInParent3 = ((Component)__instance).GetComponentInParent<Fermenter>();
				if ((Object)(object)componentInParent3 != (Object)null)
				{
					ContextCapture.Record(AwarenessContextKind.Production, (Component)(object)__instance, (Component)(object)componentInParent3, __result);
				}
			}
			catch (Exception exception)
			{
				Plugin.Instance?.Runtime?.FailHook("production switch hover", exception);
			}
		}
	}
	internal static class SafeContext
	{
		internal static void Record(AwarenessContextKind kind, Component source, Component subject, string text, string hook)
		{
			try
			{
				ContextCapture.Record(kind, source, subject, text);
			}
			catch (Exception exception)
			{
				Plugin.Instance?.Runtime?.FailHook(hook, exception);
			}
		}
	}
	internal sealed class OptionalPortalPanelAdapter
	{
		internal const string PortalPluginGuid = "chazman.RunicPortals";

		private BaseUnityPlugin _plugin;

		private ConfigEntry<bool> _enabled;

		private ConfigEntry<bool> _universalRouting;

		private ConfigEntry<bool> _showSetupPanel;

		private int _lastPortalHoverFrame = -10;

		internal bool SetupPanelVisible(Player player)
		{
			if ((Object)(object)player == (Object)null)
			{
				_lastPortalHoverFrame = -10;
				return false;
			}
			if (!TryBind())
			{
				return false;
			}
			if (!_enabled.Value || !_universalRouting.Value || !_showSetupPanel.Value)
			{
				_lastPortalHoverFrame = -10;
				return false;
			}
			GameObject hoverObject = ((Humanoid)player).GetHoverObject();
			if ((Object)(object)hoverObject != (Object)null && (Object)(object)hoverObject.GetComponentInParent<TeleportWorld>() != (Object)null)
			{
				_lastPortalHoverFrame = Time.frameCount;
			}
			int num = Time.frameCount - _lastPortalHoverFrame;
			if (num >= 0)
			{
				return num <= 1;
			}
			return false;
		}

		private bool TryBind()
		{
			try
			{
				if (!Chainloader.PluginInfos.TryGetValue("chazman.RunicPortals", out var value) || (Object)(object)((value != null) ? value.Instance : null) == (Object)null)
				{
					Clear();
					return false;
				}
				BaseUnityPlugin instance = value.Instance;
				if (_plugin == instance && _enabled != null && _universalRouting != null && _showSetupPanel != null)
				{
					return true;
				}
				ConfigFile config = instance.Config;
				ConfigEntry<bool> enabled = default(ConfigEntry<bool>);
				ConfigEntry<bool> universalRouting = default(ConfigEntry<bool>);
				ConfigEntry<bool> showSetupPanel = default(ConfigEntry<bool>);
				if (config == null || !config.TryGetEntry<bool>("General", "Enabled", ref enabled) || !config.TryGetEntry<bool>("Features", "UniversalRouting", ref universalRouting) || !config.TryGetEntry<bool>("Display", "ShowSetupPanel", ref showSetupPanel))
				{
					Clear();
					return false;
				}
				_plugin = instance;
				_enabled = enabled;
				_universalRouting = universalRouting;
				_showSetupPanel = showSetupPanel;
				return true;
			}
			catch (Exception)
			{
				Clear();
				return false;
			}
		}

		private void Clear()
		{
			_plugin = null;
			_enabled = null;
			_universalRouting = null;
			_showSetupPanel = null;
			_lastPortalHoverFrame = -10;
		}
	}
	internal static class StrictWardDisclosure
	{
		private delegate bool WardStateDelegate(PrivateArea area);

		private delegate bool WardContainsDelegate(PrivateArea area, Vector3 point, float radius);

		internal const int MaximumWardAreasExamined = 4096;

		private static readonly List<PrivateArea> WardAreas = ResolveWardAreas();

		private static readonly WardStateDelegate WardEnabled = ResolveWardState("IsEnabled");

		private static readonly WardStateDelegate WardLocalAccess = ResolveWardState("HaveLocalAccess");

		private static readonly WardContainsDelegate WardContains = ResolveWardContains();

		internal static bool IsSupported
		{
			get
			{
				if (WardAreas != null && WardEnabled != null && WardLocalAccess != null)
				{
					return WardContains != null;
				}
				return false;
			}
		}

		internal static bool Allows(Vector3 position)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			if (!IsSupported || !IsFinite(position))
			{
				return false;
			}
			try
			{
				int count = WardAreas.Count;
				if (count > 4096)
				{
					return false;
				}
				for (int i = 0; i < count; i++)
				{
					PrivateArea val = WardAreas[i];
					if (!((Object)(object)val == (Object)null))
					{
						bool num = WardEnabled(val);
						bool flag = num && WardContains(val, position, 0f);
						bool localAccess = !flag || WardLocalAccess(val);
						if (IsHostileOverlap(num, flag, localAccess))
						{
							return false;
						}
					}
				}
				return true;
			}
			catch
			{
				return false;
			}
		}

		internal static bool IsHostileOverlap(bool enabled, bool containsTarget, bool localAccess)
		{
			if (enabled && containsTarget)
			{
				return !localAccess;
			}
			return false;
		}

		private static bool IsFinite(Vector3 value)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			if (!float.IsNaN(value.x) && !float.IsInfinity(value.x) && !float.IsNaN(value.y) && !float.IsInfinity(value.y) && !float.IsNaN(value.z))
			{
				return !float.IsInfinity(value.z);
			}
			return false;
		}

		private static List<PrivateArea> ResolveWardAreas()
		{
			try
			{
				FieldInfo field = typeof(PrivateArea).GetField("m_allAreas", BindingFlags.Static | BindingFlags.NonPublic);
				return (field != null && field.FieldType == typeof(List<PrivateArea>)) ? (field.GetValue(null) as List<PrivateArea>) : null;
			}
			catch
			{
				return null;
			}
		}

		private static WardStateDelegate ResolveWardState(string name)
		{
			try
			{
				MethodInfo method = typeof(PrivateArea).GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
				return (method != null && method.ReturnType == typeof(bool) && !method.IsStatic) ? ((WardStateDelegate)method.CreateDelegate(typeof(WardStateDelegate))) : null;
			}
			catch
			{
				return null;
			}
		}

		private static WardContainsDelegate ResolveWardContains()
		{
			try
			{
				MethodInfo method = typeof(PrivateArea).GetMethod("IsInside", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2]
				{
					typeof(Vector3),
					typeof(float)
				}, null);
				return (method != null && method.ReturnType == typeof(bool) && !method.IsStatic) ? ((WardContainsDelegate)method.CreateDelegate(typeof(WardContainsDelegate))) : null;
			}
			catch
			{
				return null;
			}
		}
	}
	internal static class ValheimContracts
	{
		private const BindingFlags InstanceFields = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		private static FieldInfo _rightItem;

		private static FieldInfo _leftItem;

		private static FieldInfo _helmetItem;

		private static FieldInfo _chestItem;

		private static FieldInfo _legItem;

		private static FieldInfo _shoulderItem;

		private static FieldInfo _utilityItem;

		private static FieldInfo _trinketItem;

		private static FieldInfo _ammoItem;

		private static FieldInfo _comfortPieces;

		private static bool _verified;

		internal static FieldInfo ComfortPiecesField => _comfortPieces ?? throw new InvalidOperationException("Valheim contracts are not initialized.");

		internal static void VerifyInstalledSignatures()
		{
			if (!_verified)
			{
				RequireMethod(typeof(Player), "GetFoods", typeof(List<Food>), false);
				RequireMethod(typeof(Player), "GetComfortLevel", typeof(int), false);
				RequireMethod(typeof(Player), "GetHoverObject", typeof(GameObject), false);
				RequireMethod(typeof(Player), "GetHoveringPiece", typeof(Piece), false);
				RequireMethod(typeof(Player), "GetSEMan", typeof(SEMan), false);
				RequireMethod(typeof(Player), "InShelter", typeof(bool), false);
				RequireMethod(typeof(Minimap), "IsOpen", typeof(bool), true);
				RequireMethod(typeof(Hud), "IsPieceSelectionVisible", typeof(bool), true);
				RequireMethod(typeof(Chat), "HasFocus", typeof(bool), false);
				RequireMethod(typeof(Chat), "IsChatDialogWindowVisible", typeof(bool), false);
				RequireMethod(typeof(PlayerCustomizaton), "IsBarberGuiVisible", typeof(bool), true);
				RequireMethod(typeof(Feedback), "IsVisible", typeof(bool), true);
				RequireMethod(typeof(UnifiedPopup), "IsVisible", typeof(bool), true);
				RequireMethod(typeof(ConnectPanel), "IsVisible", typeof(bool), true);
				RequireMethod(typeof(TextViewer), "IsVisible", typeof(bool), false);
				RequireMethod(typeof(SEMan), "GetStatusEffects", typeof(List<StatusEffect>), false);
				RequireMethod(typeof(SEMan), "GetStatusEffect", typeof(StatusEffect), false, typeof(int));
				RequireMethod(typeof(StatusEffect), "GetRemaningTime", typeof(float), false);
				RequireField(typeof(Localization), "m_translations", typeof(Dictionary<string, string>));
				if (!BoundedLocalization.IsSupported)
				{
					throw new MissingFieldException(typeof(Localization).FullName, "m_translations");
				}
				RequireMethod(typeof(InventoryGrid), "CreateItemTooltip", typeof(void), false, typeof(ItemData), typeof(UITooltip));
				RequireMethod(typeof(SE_Rested), "CalculateComfortLevel", typeof(int), true, typeof(Player));
				RequireMethod(typeof(CraftingStation), "GetHoverText", typeof(string), false);
				RequireMethod(typeof(CookingStation), "GetHoverText", typeof(string), false);
				RequireMethod(typeof(Fermenter), "GetHoverText", typeof(string), false);
				RequireMethod(typeof(Plant), "GetHoverText", typeof(string), false);
				RequireMethod(typeof(Beehive), "GetHoverText", typeof(string), false);
				RequireMethod(typeof(Tameable), "GetHoverText", typeof(string), false);
				RequireMethod(typeof(Switch), "GetHoverText", typeof(string), false);
				RequireMethod(typeof(WearNTear), "GetHealthPercentage", typeof(float), false);
				RequireMethod(typeof(CraftingStation), "GetLevel", typeof(int), false, typeof(bool));
				RequireField(typeof(PrivateArea), "m_allAreas", typeof(List<PrivateArea>), BindingFlags.Static | BindingFlags.NonPublic);
				RequireMethod(typeof(PrivateArea), "IsEnabled", typeof(bool), false);
				RequireMethod(typeof(PrivateArea), "HaveLocalAccess", typeof(bool), false);
				RequireMethod(typeof(PrivateArea), "IsInside", typeof(bool), false, typeof(Vector3), typeof(float));
				if (!StrictWardDisclosure.IsSupported)
				{
					throw new MissingMethodException(typeof(PrivateArea).FullName, "strict disclosure delegates");
				}
				_rightItem = RequireField(typeof(Humanoid), "m_rightItem", typeof(ItemData));
				_leftItem = RequireField(typeof(Humanoid), "m_leftItem", typeof(ItemData));
				_helmetItem = RequireField(typeof(Humanoid), "m_helmetItem", typeof(ItemData));
				_chestItem = RequireField(typeof(Humanoid), "m_chestItem", typeof(ItemData));
				_legItem = RequireField(typeof(Humanoid), "m_legItem", typeof(ItemData));
				_shoulderItem = RequireField(typeof(Humanoid), "m_shoulderItem", typeof(ItemData));
				_utilityItem = RequireField(typeof(Humanoid), "m_utilityItem", typeof(ItemData));
				_trinketItem = RequireField(typeof(Humanoid), "m_trinketItem", typeof(ItemData));
				_ammoItem = RequireField(typeof(Humanoid), "m_ammoItem", typeof(ItemData));
				_comfortPieces = RequireField(typeof(SE_Rested), "s_tempPieces", typeof(List<Piece>), BindingFlags.Static | BindingFlags.NonPublic);
				_verified = true;
			}
		}

		internal static ItemData EquippedFor(Player player, ItemData selected)
		{
			if (!_verified || (Object)(object)player == (Object)null || selected?.m_shared == null)
			{
				return null;
			}
			string text = ((object)Unsafe.As<ItemType, ItemType>(ref selected.m_shared.m_itemType)/*cast due to .constrained prefix*/).ToString();
			switch (text)
			{
			case "Helmet":
				return ReadItem(_helmetItem, (Humanoid)(object)player);
			case "Chest":
				return ReadItem(_chestItem, (Humanoid)(object)player);
			case "Legs":
				return ReadItem(_legItem, (Humanoid)(object)player);
			case "Shoulder":
				return ReadItem(_shoulderItem, (Humanoid)(object)player);
			case "Utility":
				return ReadItem(_utilityItem, (Humanoid)(object)player);
			case "Trinket":
				return ReadItem(_trinketItem, (Humanoid)(object)player);
			case "Ammo":
				return ReadItem(_ammoItem, (Humanoid)(object)player);
			case "Shield":
				return ReadItem(_leftItem, (Humanoid)(object)player);
			case "TwoHandedWeaponLeft":
				return ReadItem(_leftItem, (Humanoid)(object)player);
			default:
			{
				ItemData val = ReadItem(_rightItem, (Humanoid)(object)player);
				if (val != null && val.m_shared != null && string.Equals(((object)Unsafe.As<ItemType, ItemType>(ref val.m_shared.m_itemType)/*cast due to .constrained prefix*/).ToString(), text, StringComparison.Ordinal))
				{
					return val;
				}
				ItemData val2 = ReadItem(_leftItem, (Humanoid)(object)player);
				if (val2 == null || val2.m_shared == null || !string.Equals(((object)Unsafe.As<ItemType, ItemType>(ref val2.m_shared.m_itemType)/*cast due to .constrained prefix*/).ToString(), text, StringComparison.Ordinal))
				{
					return null;
				}
				return val2;
			}
			}
		}

		private static ItemData ReadItem(FieldInfo field, Humanoid player)
		{
			object? obj = field?.GetValue(player);
			return (ItemData)((obj is ItemData) ? obj : null);
		}

		private static MethodInfo RequireMethod(Type type, string name, Type returnType, bool isStatic, params Type[] parameters)
		{
			MethodInfo method = type.GetMethod(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, parameters ?? Type.EmptyTypes, null);
			if (method == null || method.ReturnType != returnType || method.IsStatic != isStatic)
			{
				throw new MissingMethodException(type.FullName, name);
			}
			return method;
		}

		private static FieldInfo RequireField(Type type, string name, Type fieldType)
		{
			return RequireField(type, name, fieldType, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		}

		private static FieldInfo RequireField(Type type, string name, Type fieldType, BindingFlags flags)
		{
			FieldInfo field = type.GetField(name, flags);
			if (field == null || field.FieldType != fieldType)
			{
				throw new MissingFieldException(type.FullName, name);
			}
			return field;
		}
	}
}
namespace RunicAwareness.Core
{
	internal static class BoundedText
	{
		internal const int MaximumLabelCharacters = 96;

		internal static string Sanitize(string value, int maximumCharacters, int maximumLines)
		{
			maximumCharacters = Math.Max(1, maximumCharacters);
			maximumLines = Math.Max(1, maximumLines);
			if (string.IsNullOrEmpty(value))
			{
				return string.Empty;
			}
			StringBuilder stringBuilder = new StringBuilder(Math.Min(maximumCharacters, value.Length));
			int num = 1;
			bool flag = false;
			int num2 = Math.Min(value.Length, Math.Max(256, maximumCharacters * 4));
			for (int i = 0; i < num2 && stringBuilder.Length < maximumCharacters; i++)
			{
				char c = value[i];
				switch (c)
				{
				case '<':
				{
					int num3 = FindTagEnd(value, i, 64);
					if (num3 >= 0)
					{
						i = num3;
						continue;
					}
					c = '[';
					goto default;
				}
				case '>':
					c = ']';
					goto default;
				default:
					switch (c)
					{
					case '\n':
						break;
					default:
					{
						UnicodeCategory unicodeCategory = char.GetUnicodeCategory(c);
						if (unicodeCategory == UnicodeCategory.Control || unicodeCategory == UnicodeCategory.Format || IsBidiControl(c))
						{
							continue;
						}
						if (c == '\t' || char.IsWhiteSpace(c))
						{
							if (!flag && stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] != '\n')
							{
								stringBuilder.Append(' ');
								flag = true;
							}
						}
						else
						{
							stringBuilder.Append(c);
							flag = false;
						}
						continue;
					}
					case '\r':
						continue;
					}
					if (num < maximumLines)
					{
						TrimTrailingSpace(stringBuilder);
						if (stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] != '\n')
						{
							stringBuilder.Append('\n');
							num++;
						}
						flag = false;
						continue;
					}
					break;
				}
				break;
			}
			TrimTrailingSpace(stringBuilder);
			while (stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] == '\n')
			{
				stringBuilder.Length--;
			}
			if ((stringBuilder.Length == maximumCharacters && value.Length > maximumCharacters) || num2 < value.Length)
			{
				AddEllipsis(stringBuilder, maximumCharacters);
			}
			return stringBuilder.ToString();
		}

		internal static string Label(string value)
		{
			return Sanitize(value, 96, 1);
		}

		internal static ulong HashBounded(string value, int maximumCharacters)
		{
			ulong num = 1469598103934665603uL;
			if (value == null)
			{
				return num;
			}
			int num2 = Math.Min(Math.Max(0, maximumCharacters), value.Length);
			for (int i = 0; i < num2; i++)
			{
				num ^= value[i];
				num *= 1099511628211L;
			}
			num ^= (ulong)Math.Min(value.Length, maximumCharacters + 1);
			return num * 1099511628211L;
		}

		internal static int CountLines(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return 0;
			}
			int num = 1;
			for (int i = 0; i < value.Length; i++)
			{
				if (value[i] == '\n')
				{
					num++;
				}
			}
			return num;
		}

		private static int FindTagEnd(string value, int start, int maximumDistance)
		{
			int num = Math.Min(value.Length, start + maximumDistance + 1);
			for (int i = start + 1; i < num; i++)
			{
				if (value[i] == '>')
				{
					return i;
				}
			}
			return -1;
		}

		private static bool IsBidiControl(char value)
		{
			if ((value < '\u202a' || value > '\u202e') && (value < '\u2066' || value > '\u2069') && (value < '\u200b' || value > '\u200f') && value != '\u061c')
			{
				return value == '\ufeff';
			}
			return true;
		}

		private static void TrimTrailingSpace(StringBuilder builder)
		{
			while (builder.Length > 0 && builder[builder.Length - 1] == ' ')
			{
				builder.Length--;
			}
		}

		private static void AddEllipsis(StringBuilder builder, int maximumCharacters)
		{
			if (maximumCharacters == 1)
			{
				builder[0] = '…';
				return;
			}
			builder.Length = Math.Min(builder.Length, maximumCharacters - 1);
			builder.Append('…');
		}
	}
	internal static class ContextDisclosurePolicy
	{
		internal const float HardMaximumDetailReachMeters = 10f;

		internal static bool AllowsDetailedDisclosure(float squaredAvatarDistance, float vanillaInteractDistance)
		{
			if (float.IsNaN(squaredAvatarDistance) || float.IsInfinity(squaredAvatarDistance) || float.IsNaN(vanillaInteractDistance) || float.IsInfinity(vanillaInteractDistance) || squaredAvatarDistance < 0f || vanillaInteractDistance <= 0f)
			{
				return false;
			}
			float num = Math.Min(vanillaInteractDistance, 10f);
			return squaredAvatarDistance <= num * num;
		}
	}
	internal readonly struct ItemMetrics
	{
		internal string Name { get; }

		internal string ItemType { get; }

		internal int Quality { get; }

		internal float Damage { get; }

		internal float Armor { get; }

		internal float Block { get; }

		internal float MovementPercent { get; }

		internal float Weight { get; }

		internal float Durability { get; }

		internal float MaximumDurability { get; }

		internal string SkillName { get; }

		internal float SkillLevel { get; }

		internal ItemMetrics(string name, string itemType, int quality, float damage, float armor, float block, float movementPercent, float weight, float durability, float maximumDurability, string skillName, float skillLevel)
		{
			Name = name ?? string.Empty;
			ItemType = itemType ?? string.Empty;
			Quality = Math.Max(0, quality);
			Damage = FiniteOrZero(damage);
			Armor = FiniteOrZero(armor);
			Block = FiniteOrZero(block);
			MovementPercent = FiniteOrZero(movementPercent);
			Weight = FiniteOrZero(weight);
			Durability = FiniteOrZero(durability);
			MaximumDurability = FiniteOrZero(maximumDurability);
			SkillName = skillName ?? string.Empty;
			SkillLevel = FiniteOrZero(skillLevel);
		}

		private static float FiniteOrZero(float value)
		{
			if (!float.IsNaN(value) && !float.IsInfinity(value))
			{
				return value;
			}
			return 0f;
		}
	}
	internal static class ItemComparisonFormatter
	{
		internal const int MaximumLines = 8;

		internal static string Format(ItemMetrics selected, ItemMetrics? equipped, bool isEquipped)
		{
			StringBuilder stringBuilder = new StringBuilder(384);
			stringBuilder.Append(BoundedText.Label(selected.Name));
			if (isEquipped)
			{
				stringBuilder.Append(" (equipped)");
			}
			else if (equipped.HasValue)
			{
				stringBuilder.Append(" vs ").Append(BoundedText.Label(equipped.Value.Name));
			}
			else
			{
				stringBuilder.Append(" vs empty slot");
			}
			int lines = 1;
			AppendText(stringBuilder, ref lines, "Type", BoundedText.Label(selected.ItemType));
			AppendInteger(stringBuilder, ref lines, "Quality", selected.Quality, equipped?.Quality);
			AppendNumber(stringBuilder, ref lines, "Damage (item)", selected.Damage, equipped?.Damage, selected.Damage > 0f || (equipped?.Damage ?? 0f) > 0f);
			AppendNumber(stringBuilder, ref lines, "Armor", selected.Armor, equipped?.Armor, selected.Armor > 0f || (equipped?.Armor ?? 0f) > 0f);
			AppendNumber(stringBuilder, ref lines, "Block (skill)", selected.Block, equipped?.Block, selected.Block > 0f || (equipped?.Block ?? 0f) > 0f);
			AppendNumber(stringBuilder, ref lines, "Move", selected.MovementPercent, equipped?.MovementPercent, include: true, "%");
			if (lines < 8 && !string.IsNullOrEmpty(selected.SkillName))
			{
				stringBuilder.Append("\nSkill: ").Append(BoundedText.Label(selected.SkillName)).Append(' ')
					.Append(FormatNumber(selected.SkillLevel));
				lines++;
			}
			AppendNumber(stringBuilder, ref lines, "Weight", selected.Weight, equipped?.Weight, include: true);
			if (lines < 8 && selected.MaximumDurability > 0f)
			{
				stringBuilder.Append("\nDurability: ").Append(FormatNumber(selected.Durability)).Append('/')
					.Append(FormatNumber(selected.MaximumDurability));
				lines++;
			}
			return BoundedText.Sanitize(stringBuilder.ToString(), 640, 8);
		}

		private static void AppendText(StringBuilder builder, ref int lines, string label, string value)
		{
			if (lines < 8 && !string.IsNullOrEmpty(value))
			{
				builder.Append('\n').Append(label).Append(": ")
					.Append(value);
				lines++;
			}
		}

		private static void AppendInteger(StringBuilder builder, ref int lines, string label, int current, int? comparison)
		{
			if (lines < 8)
			{
				builder.Append('\n').Append(label).Append(": ")
					.Append(current);
				if (comparison.HasValue)
				{
					AppendDelta(builder, current - comparison.Value, string.Empty);
				}
				lines++;
			}
		}

		private static void AppendNumber(StringBuilder builder, ref int lines, string label, float current, float? comparison, bool include, string suffix = "")
		{
			if (include && lines < 8)
			{
				builder.Append('\n').Append(label).Append(": ")
					.Append(FormatNumber(current))
					.Append(suffix);
				if (comparison.HasValue)
				{
					AppendDelta(builder, current - comparison.Value, suffix);
				}
				lines++;
			}
		}

		private static void AppendDelta(StringBuilder builder, float delta, string suffix)
		{
			if (!(Math.Abs(delta) < 0.05f))
			{
				builder.Append(" (");
				if (delta > 0f)
				{
					builder.Append('+');
				}
				builder.Append(FormatNumber(delta)).Append(suffix).Append(')');
			}
		}

		private static string FormatNumber(float value)
		{
			return value.ToString("0.#", CultureInfo.InvariantCulture);
		}
	}
	internal enum ItemComparisonDisposition
	{
		HideKnownNonEquipment,
		CompareKnownEquipment,
		ShowRawUnsupported
	}
	internal static class ItemTypePolicy
	{
		internal static ItemComparisonDisposition Classify(string type)
		{
			switch (type)
			{
			case "Helmet":
			case "Shield":
			case "Chest":
			case "Hands":
			case "Torch":
			case "Legs":
			case "Ammo":
			case "Tool":
			case "Shoulder":
			case "Trinket":
			case "Utility":
			case "OneHandedWeapon":
			case "TwoHandedWeapon":
			case "Attach_Atgeir":
			case "TwoHandedWeaponLeft":
			case "Bow":
				return ItemComparisonDisposition.CompareKnownEquipment;
			case "Trophy":
			case "None":
			case "Misc":
			case "Fish":
			case "Material":
			case "Customization":
			case "Consumable":
			case "AmmoNonEquipable":
				return ItemComparisonDisposition.HideKnownNonEquipment;
			default:
				if (!string.IsNullOrEmpty(type))
				{
					return ItemComparisonDisposition.ShowRawUnsupported;
				}
				return ItemComparisonDisposition.HideKnownNonEquipment;
			}
		}
	}
	internal enum AwarenessPanel
	{
		Food,
		Effects,
		Comfort,
		ItemComparison,
		Production,
		Agriculture,
		Building,
		TamedAnimal
	}
	internal sealed class PanelCache
	{
		private static readonly string[] Titles = new string[8] { "Food", "Effects", "Comfort", "Item comparison", "Production", "Agriculture", "Building", "Tamed animal" };

		private readonly string[] _bodies = new string[Titles.Length];

		private readonly int[] _bodyLines = new int[Titles.Length];

		private readonly StringBuilder _builder = new StringBuilder(4096);

		private int _version;

		private int _composedVersion = -1;

		private bool _composedInventoryOnly;

		private string _composed = string.Empty;

		private int _composedLines;

		internal int Version => _version;

		internal string ComposedText => _composed;

		internal int ComposedLines => _composedLines;

		internal bool Set(AwarenessPanel panel, string body)
		{
			body = body ?? string.Empty;
			if (string.Equals(_bodies[(int)panel], body, StringComparison.Ordinal))
			{
				return false;
			}
			_bodies[(int)panel] = body;
			_bodyLines[(int)panel] = BoundedText.CountLines(body);
			_version++;
			return true;
		}

		internal bool Clear(AwarenessPanel panel)
		{
			return Set(panel, string.Empty);
		}

		internal bool Compose(bool inventoryOnly)
		{
			if (_composedVersion == _version && _composedInventoryOnly == inventoryOnly)
			{
				return false;
			}
			_builder.Clear();
			int num = 0;
			for (int i = 0; i < _bodies.Length; i++)
			{
				bool flag = i == 3;
				if (inventoryOnly == flag && !string.IsNullOrEmpty(_bodies[i]))
				{
					int num2 = _bodyLines[i] + 1 + ((_builder.Length != 0) ? 1 : 0);
					if (num + num2 > 40)
					{
						break;
					}
					int num3 = ((_builder.Length != 0) ? 2 : 0) + 3 + Titles[i].Length + 5 + _bodies[i].Length;
					if (_builder.Length + num3 > 4096)
					{
						break;
					}
					if (_builder.Length > 0)
					{
						_builder.Append("\n\n");
						num++;
					}
					_builder.Append("<b>").Append(Titles[i]).Append("</b>\n");
					_builder.Append(_bodies[i]);
					num += _bodyLines[i] + 1;
				}
			}
			_composed = _builder.ToString();
			_composedLines = num;
			_composedVersion = _version;
			_composedInventoryOnly = inventoryOnly;
			return true;
		}
	}
	internal static class TimerFormatter
	{
		internal const int MaximumDisplaySeconds = 359999;

		internal static int Bucket(float remainingSeconds, AwarenessTimerPrecision precision)
		{
			if (float.IsNaN(remainingSeconds) || float.IsInfinity(remainingSeconds) || remainingSeconds <= 0f)
			{
				return 0;
			}
			int num = Math.Max(1, (int)precision);
			double num2 = Math.Min(359999f, remainingSeconds);
			return Math.Min(359999, checked((int)Math.Ceiling(num2 / (double)num) * num));
		}

		internal static void Append(StringBuilder builder, int bucketSeconds)
		{
			int num = Math.Max(0, Math.Min(359999, bucketSeconds));
			int num2 = num / 3600;
			int value = num / 60 % 60;
			int value2 = num % 60;
			if (num2 > 0)
			{
				builder.Append(num2).Append(':');
				AppendTwoDigits(builder, value);
				builder.Append(':');
				AppendTwoDigits(builder, value2);
			}
			else
			{
				builder.Append(value).Append(':');
				AppendTwoDigits(builder, value2);
			}
		}

		internal static string Format(int bucketSeconds)
		{
			StringBuilder stringBuilder = new StringBuilder(10);
			Append(stringBuilder, bucketSeconds);
			return stringBuilder.ToString();
		}

		private static void AppendTwoDigits(StringBuilder builder, int value)
		{
			if (value < 10)
			{
				builder.Append('0');
			}
			builder.Append(value);
		}
	}
}