Decompiled source of WiresHelmetHider v0.2.4

WiresHelmetHider.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("WiresHelmetHider")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("WiresHelmetHider")]
[assembly: AssemblyTitle("WiresHelmetHider")]
[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 WiresHoodToggle
{
	internal static class HelmetToggleAnimation
	{
		private static Player _player;

		private static ZSyncAnimation _sync;

		private static float _remaining;

		private static bool _pending;

		private static bool _desiredHidden;

		internal static bool Pending => _pending;

		internal static void Start(Player player, bool desiredHidden)
		{
			Cancel(commit: true);
			_player = player;
			_sync = (((Object)(object)player != (Object)null) ? ((Component)player).GetComponent<ZSyncAnimation>() : null);
			if ((Object)(object)player == (Object)null || (Object)(object)_sync == (Object)null || !Plugin.PlayEquipAnimation.Value || player.GetActionQueueCount() != 0 || ((Character)player).InAttack() || ((Character)player).IsDead() || ((Character)player).IsStaggering() || !((Character)player).IsOnGround())
			{
				HoodVisuals.CommitToggle(player, desiredHidden);
				return;
			}
			_pending = true;
			_desiredHidden = desiredHidden;
			_remaining = Plugin.AnimationDuration.Value;
			_sync.SetBool("equipping", true);
		}

		internal static void Update(Player player)
		{
			if (!_pending)
			{
				return;
			}
			if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)_player || ((Character)player).IsDead() || ((Character)player).InAttack() || ((Character)player).IsStaggering() || !((Character)player).IsOnGround() || player.GetActionQueueCount() > 0 || !Plugin.PlayEquipAnimation.Value)
			{
				Cancel(commit: true);
				return;
			}
			_remaining -= Time.deltaTime;
			if (_remaining <= 0f)
			{
				Cancel(commit: true);
			}
		}

		internal static void Cancel(bool commit)
		{
			if (_pending)
			{
				Player player = _player;
				bool desiredHidden = _desiredHidden;
				ZSyncAnimation sync = _sync;
				_pending = false;
				_player = null;
				_sync = null;
				_remaining = 0f;
				if ((Object)(object)sync != (Object)null && ((Object)(object)player == (Object)null || player.GetActionQueueCount() == 0))
				{
					sync.SetBool("equipping", false);
				}
				if (commit && (Object)(object)player != (Object)null)
				{
					HoodVisuals.CommitToggle(player, desiredHidden);
				}
			}
		}
	}
	internal static class HoodInput
	{
		private const string EmoteButton = "OpenEmote";

		private static float _lastTapAt = -10f;

		private static bool _consumeCurrentPress;

		internal static bool UsingEmoteButton
		{
			get
			{
				//IL_000c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0011: Unknown result type (might be due to invalid IL or missing references)
				//IL_0014: Unknown result type (might be due to invalid IL or missing references)
				//IL_001a: Invalid comparison between Unknown and I4
				if (Plugin.CustomToggleKey != null)
				{
					KeyboardShortcut value = Plugin.CustomToggleKey.Value;
					return (int)((KeyboardShortcut)(ref value)).MainKey == 0;
				}
				return true;
			}
		}

		internal static void Update()
		{
			//IL_0038: 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)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				Reset();
				return;
			}
			bool flag = CanTakeInput(localPlayer);
			if (!UsingEmoteButton)
			{
				_lastTapAt = -10f;
				_consumeCurrentPress = false;
				KeyboardShortcut value = Plugin.CustomToggleKey.Value;
				if (flag && ((KeyboardShortcut)(ref value)).IsDown())
				{
					HoodVisuals.ToggleLocal(localPlayer);
				}
				return;
			}
			bool buttonDown = ZInput.GetButtonDown("OpenEmote");
			bool buttonUp = ZInput.GetButtonUp("OpenEmote");
			float unscaledTime = Time.unscaledTime;
			if (_lastTapAt > -1f && unscaledTime - _lastTapAt > Plugin.DoubleTapWindow.Value)
			{
				_lastTapAt = -10f;
			}
			if (buttonDown && flag)
			{
				if (_lastTapAt > -1f && unscaledTime - _lastTapAt <= Plugin.DoubleTapWindow.Value)
				{
					_lastTapAt = -10f;
					_consumeCurrentPress = true;
					HoodVisuals.ToggleLocal(localPlayer);
				}
				else
				{
					_lastTapAt = unscaledTime;
				}
			}
			if (buttonUp)
			{
				_consumeCurrentPress = false;
			}
		}

		internal static bool ShouldOpenEmoteRadial()
		{
			if (!UsingEmoteButton || _consumeCurrentPress || Plugin.EmoteHoldDuration == null)
			{
				return false;
			}
			if (ZInput.GetButton("OpenEmote"))
			{
				return ZInput.GetButtonPressedTimer("OpenEmote") >= Plugin.EmoteHoldDuration.Value;
			}
			return false;
		}

		internal static void Reset()
		{
			_lastTapAt = -10f;
			_consumeCurrentPress = false;
		}

		private static bool CanTakeInput(Player player)
		{
			if ((Object)(object)player == (Object)null || ((Character)player).IsDead() || ((Character)player).InCutscene() || ((Character)player).IsTeleporting() || InventoryGui.IsVisible() || Menu.IsVisible() || GameCamera.InFreeFly() || Minimap.IsOpen() || Hud.IsPieceSelectionVisible() || StoreGui.IsVisible() || Console.IsVisible() || TextInput.IsVisible() || PlayerCustomizaton.IsBarberGuiVisible() || Hud.InRadial())
			{
				return false;
			}
			if ((Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus())
			{
				return false;
			}
			return true;
		}
	}
	internal static class HoodVisuals
	{
		private sealed class VisualState
		{
			internal GameObject Helmet;

			internal int HelmetId;

			internal int HelmetHash;

			internal bool Hidden;
		}

		private static readonly int HiddenKey = StringExtensionMethods.GetStableHashCode("wire.hoodtoggle.lowered");

		private static readonly FieldInfo HelmetInstanceField = AccessTools.Field(typeof(VisEquipment), "m_helmetItemInstance");

		private static readonly FieldInfo CurrentHelmetHashField = AccessTools.Field(typeof(VisEquipment), "m_currentHelmetItemHash");

		private static readonly FieldInfo NViewField = AccessTools.Field(typeof(VisEquipment), "m_nview");

		private static readonly FieldInfo EquippedHelmetField = AccessTools.Field(typeof(Humanoid), "m_helmetItem");

		private static readonly MethodInfo UpdateEquipmentVisualsMethod = AccessTools.Method(typeof(VisEquipment), "UpdateEquipmentVisuals", (Type[])null, (Type[])null);

		private static readonly MethodInfo TriggerEquipEffectMethod = AccessTools.Method(typeof(Humanoid), "TriggerEquipEffect", (Type[])null, (Type[])null);

		private static readonly Dictionary<int, VisualState> States = new Dictionary<int, VisualState>();

		private static Player _trackedPlayer;

		private static ItemData _trackedHelmet;

		private static bool _restoredForPlayer;

		private static VisEquipment _menuPreviewVis;

		private static long _menuPreviewPlayerId;

		internal static void UpdateLocal()
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				HelmetToggleAnimation.Cancel(commit: false);
				_trackedPlayer = null;
				_trackedHelmet = null;
				_restoredForPlayer = false;
				return;
			}
			object? obj = EquippedHelmetField?.GetValue(localPlayer);
			ItemData val = (ItemData)((obj is ItemData) ? obj : null);
			if ((Object)(object)localPlayer != (Object)(object)_trackedPlayer)
			{
				HelmetToggleAnimation.Cancel(commit: false);
				_trackedPlayer = localPlayer;
				_trackedHelmet = val;
				_restoredForPlayer = false;
			}
			if (!_restoredForPlayer)
			{
				RestoreLocalSavedState(localPlayer);
			}
			if ((Object)(object)localPlayer == (Object)(object)_trackedPlayer && _restoredForPlayer && _trackedHelmet != val)
			{
				_trackedHelmet = val;
				HelmetToggleAnimation.Cancel(commit: false);
				if (Plugin.ResetOnHeadgearChange.Value)
				{
					ZNetView component = ((Component)localPlayer).GetComponent<ZNetView>();
					if ((Object)(object)component != (Object)null && component.IsValid() && component.IsOwner())
					{
						SavedHelmetStates.SetHidden(localPlayer.GetPlayerID(), hidden: false);
						ZDO zDO = component.GetZDO();
						if (zDO != null && zDO.GetBool(HiddenKey, false))
						{
							zDO.Set(HiddenKey, false);
							VisEquipment component2 = ((Component)localPlayer).GetComponent<VisEquipment>();
							if ((Object)(object)component2 != (Object)null)
							{
								RefreshHairAndBeard(component2);
								Apply(component2, force: true);
							}
						}
					}
				}
			}
			HelmetToggleAnimation.Update(localPlayer);
		}

		internal static void ToggleLocal(Player player)
		{
			if ((Object)(object)player == (Object)null)
			{
				return;
			}
			VisEquipment component = ((Component)player).GetComponent<VisEquipment>();
			ZNetView component2 = ((Component)player).GetComponent<ZNetView>();
			if ((Object)(object)component == (Object)null || (Object)(object)component2 == (Object)null || !component2.IsValid() || !component2.IsOwner())
			{
				return;
			}
			ZDO zDO = component2.GetZDO();
			if (zDO != null)
			{
				bool desiredHidden = !(HelmetToggleAnimation.Pending ? (!zDO.GetBool(HiddenKey, false)) : zDO.GetBool(HiddenKey, false));
				if (EquippedHelmetField?.GetValue(player) is ItemData)
				{
					HelmetToggleAnimation.Start(player, desiredHidden);
				}
			}
		}

		internal static void CommitToggle(Player player, bool hidden)
		{
			if ((Object)(object)player == (Object)null)
			{
				return;
			}
			VisEquipment component = ((Component)player).GetComponent<VisEquipment>();
			ZNetView component2 = ((Component)player).GetComponent<ZNetView>();
			if ((Object)(object)component == (Object)null || (Object)(object)component2 == (Object)null || !component2.IsValid() || !component2.IsOwner())
			{
				return;
			}
			ZDO zDO = component2.GetZDO();
			if (zDO != null)
			{
				object? obj = EquippedHelmetField?.GetValue(player);
				ItemData val = (ItemData)((obj is ItemData) ? obj : null);
				zDO.Set(HiddenKey, hidden);
				SavedHelmetStates.SetHidden(player.GetPlayerID(), hidden);
				RefreshHairAndBeard(component);
				Apply(component, force: true);
				PlayToggleEffects(player, component, val, hidden);
				if (val != null)
				{
					ShowNotification(hidden);
				}
			}
		}

		internal static bool IsHidden(VisEquipment vis)
		{
			if ((Object)(object)vis == (Object)null || !vis.m_isPlayer || vis.m_isArmorStand)
			{
				return false;
			}
			if ((Object)(object)vis == (Object)(object)_menuPreviewVis)
			{
				return SavedHelmetStates.IsHidden(_menuPreviewPlayerId);
			}
			ZNetView view = GetView(vis);
			ZDO val = (((Object)(object)view != (Object)null && view.IsValid()) ? view.GetZDO() : null);
			if (val != null)
			{
				return val.GetBool(HiddenKey, false);
			}
			return false;
		}

		internal static void Apply(VisEquipment vis, bool force = false)
		{
			if ((Object)(object)vis == (Object)null || !vis.m_isPlayer || vis.m_isArmorStand || HelmetInstanceField == null || CurrentHelmetHashField == null)
			{
				return;
			}
			int instanceID = ((Object)vis).GetInstanceID();
			if (!States.TryGetValue(instanceID, out var value))
			{
				value = new VisualState();
				States[instanceID] = value;
			}
			object? value2 = HelmetInstanceField.GetValue(vis);
			GameObject val = (GameObject)((value2 is GameObject) ? value2 : null);
			int num = (((Object)(object)val != (Object)null) ? ((Object)val).GetInstanceID() : 0);
			int num2 = (int)CurrentHelmetHashField.GetValue(vis);
			bool flag = IsHidden(vis);
			if (force || value.HelmetId != num || value.HelmetHash != num2 || value.Hidden != flag)
			{
				Restore(value);
				value.Helmet = val;
				value.HelmetId = num;
				value.HelmetHash = num2;
				value.Hidden = flag;
				if (flag && (Object)(object)val != (Object)null && num2 != 0)
				{
					val.SetActive(false);
				}
			}
		}

		internal static void Clear()
		{
			foreach (VisualState value in States.Values)
			{
				Restore(value);
			}
			States.Clear();
			_trackedPlayer = null;
			_trackedHelmet = null;
			_restoredForPlayer = false;
			_menuPreviewVis = null;
			_menuPreviewPlayerId = 0L;
		}

		internal static void RestoreLocalSavedState(Player player)
		{
			if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			if ((Object)(object)player != (Object)(object)_trackedPlayer)
			{
				_trackedPlayer = player;
				object? obj = EquippedHelmetField?.GetValue(player);
				_trackedHelmet = (ItemData)((obj is ItemData) ? obj : null);
				_restoredForPlayer = false;
			}
			if (_restoredForPlayer)
			{
				return;
			}
			long playerID = player.GetPlayerID();
			ZNetView component = ((Component)player).GetComponent<ZNetView>();
			if (playerID == 0L || (Object)(object)component == (Object)null || !component.IsValid() || !component.IsOwner())
			{
				return;
			}
			_restoredForPlayer = true;
			if (!SavedHelmetStates.IsHidden(playerID) || _trackedHelmet == null)
			{
				return;
			}
			ZDO zDO = component.GetZDO();
			if (zDO != null)
			{
				zDO.Set(HiddenKey, true);
				VisEquipment component2 = ((Component)player).GetComponent<VisEquipment>();
				if ((Object)(object)component2 != (Object)null)
				{
					RefreshHairAndBeard(component2);
					Apply(component2, force: true);
				}
			}
		}

		internal static void SetMenuPreview(Player preview, long playerId)
		{
			_menuPreviewVis = (((Object)(object)preview != (Object)null) ? ((Component)preview).GetComponent<VisEquipment>() : null);
			_menuPreviewPlayerId = playerId;
			if ((Object)(object)_menuPreviewVis != (Object)null)
			{
				RefreshHairAndBeard(_menuPreviewVis);
				Apply(_menuPreviewVis, force: true);
			}
		}

		private static void Restore(VisualState state)
		{
			if ((Object)(object)state?.Helmet != (Object)null)
			{
				state.Helmet.SetActive(true);
			}
		}

		private static ZNetView GetView(VisEquipment vis)
		{
			object? obj = NViewField?.GetValue(vis);
			ZNetView val = (ZNetView)((obj is ZNetView) ? obj : null);
			if ((Object)(object)val != (Object)null)
			{
				return val;
			}
			if ((Object)(object)vis.m_nViewOverride != (Object)null)
			{
				return vis.m_nViewOverride;
			}
			return ((Component)vis).GetComponent<ZNetView>();
		}

		private static void RefreshHairAndBeard(VisEquipment vis)
		{
			if (!((Object)(object)vis == (Object)null) && !(UpdateEquipmentVisualsMethod == null))
			{
				UpdateEquipmentVisualsMethod.Invoke(vis, null);
			}
		}

		private static void PlayToggleEffects(Player player, VisEquipment vis, ItemData helmetItem, bool hidden)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: 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_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)player == (Object)null) && helmetItem != null)
			{
				ZDOID zDOID = ((Character)player).GetZDOID();
				if (hidden)
				{
					helmetItem.m_shared.m_unequipEffect.Create(((Component)player).transform.position, Quaternion.identity, (Transform)null, 1f, -1, zDOID);
				}
				else
				{
					Transform val = (((Object)(object)vis != (Object)null) ? vis.m_helmet : null);
					Vector3 val2 = (((Object)(object)val != (Object)null) ? val.position : ((Component)player).transform.position);
					Quaternion val3 = (((Object)(object)val != (Object)null) ? val.rotation : ((Component)player).transform.rotation);
					helmetItem.m_shared.m_equipEffect.Create(val2, val3, (Transform)null, 1f, -1, zDOID);
				}
				TriggerEquipEffectMethod?.Invoke(player, new object[1] { helmetItem });
			}
		}

		private static void ShowNotification(bool hidden)
		{
			if (!((Object)(object)MessageHud.instance == (Object)null))
			{
				MessageHud.instance.ShowMessage((MessageType)1, hidden ? "Headpiece hidden" : "Headpiece visible", 0, (Sprite)null, false, true);
			}
		}
	}
	[HarmonyPatch(typeof(MaterialMan), "RegisterRenderers")]
	internal static class EarlyMaterialBlockPatch
	{
		private static readonly FieldInfo SharedBlock = AccessTools.Field(typeof(MaterialMan), "m_propertyBlock");

		private static bool _reportedMissingField;

		[HarmonyPrefix]
		private static void Prefix(MaterialMan __instance)
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Expected O, but got Unknown
			if ((Object)(object)__instance == (Object)null)
			{
				return;
			}
			if (SharedBlock == null)
			{
				if (!_reportedMissingField)
				{
					_reportedMissingField = true;
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogWarning((object)"MaterialMan private property-block field could not be found. Startup-order guard was not applied.");
					}
				}
			}
			else if (SharedBlock.GetValue(__instance) == null)
			{
				SharedBlock.SetValue(__instance, (object?)new MaterialPropertyBlock());
				ManualLogSource log2 = Plugin.Log;
				if (log2 != null)
				{
					log2.LogInfo((object)"MaterialMan property block initialized before early renderer registration.");
				}
			}
		}
	}
	[BepInPlugin("wire.valheim.hoodtoggle", "Wire's Helmet Hider", "0.2.4")]
	[BepInProcess("valheim.exe")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "wire.valheim.hoodtoggle";

		public const string Name = "Wire's Helmet Hider";

		public const string Version = "0.2.4";

		internal static ConfigEntry<KeyboardShortcut> CustomToggleKey;

		internal static ConfigEntry<float> DoubleTapWindow;

		internal static ConfigEntry<float> EmoteHoldDuration;

		internal static ConfigEntry<bool> PlayEquipAnimation;

		internal static ConfigEntry<float> AnimationDuration;

		internal static ConfigEntry<bool> ResetOnHeadgearChange;

		private Harmony _harmony;

		internal static ManualLogSource Log;

		private void Awake()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Expected O, but got Unknown
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Expected O, but got Unknown
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Expected O, but got Unknown
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Expected O, but got Unknown
			CustomToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Input", "Custom Toggle Key", new KeyboardShortcut((KeyCode)0, Array.Empty<KeyCode>()), "Optional direct toggle key. Leave None to use the default emote-button double tap.");
			DoubleTapWindow = ((BaseUnityPlugin)this).Config.Bind<float>("Input", "Double Tap Window", 0.3f, new ConfigDescription("Maximum time between two taps when using the emote button.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.15f, 0.6f), Array.Empty<object>()));
			EmoteHoldDuration = ((BaseUnityPlugin)this).Config.Bind<float>("Input", "Emote Hold Duration", 0.33f, new ConfigDescription("How long the emote button must be held before the emote radial opens when using the default emote-button control.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.15f, 1f), Array.Empty<object>()));
			PlayEquipAnimation = ((BaseUnityPlugin)this).Config.Bind<bool>("Animation", "Play equip animation", true, "Play a short, synced visual-only equipping animation when toggling helmet visibility. No equipment action, movement restriction or delay beyond the visual.");
			AnimationDuration = ((BaseUnityPlugin)this).Config.Bind<float>("Animation", "Animation duration", 0.22f, new ConfigDescription("Maximum time to play the cosmetic equip animation before applying the visual change. Cancelling the animation applies the change immediately.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.08f, 0.4f), Array.Empty<object>()));
			ResetOnHeadgearChange = ((BaseUnityPlugin)this).Config.Bind<bool>("Visibility", "Reset hidden state on headgear change", true, "Show the headpiece again when actually equipping, unequipping or swapping headgear. Disable to keep the hidden state until manually toggled.");
			Log = ((BaseUnityPlugin)this).Logger;
			SavedHelmetStates.Load();
			_harmony = new Harmony("wire.valheim.hoodtoggle");
			_harmony.PatchAll();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Wire's Helmet Hider 0.2.4 loaded.");
		}

		private void Update()
		{
			HoodVisuals.UpdateLocal();
			HoodInput.Update();
		}

		private void OnDestroy()
		{
			HoodInput.Reset();
			HelmetToggleAnimation.Cancel(commit: false);
			HoodVisuals.Clear();
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	[HarmonyPatch(typeof(Player), "CheckKeyboardRadialPressed")]
	internal static class EmoteButtonHoldPatch
	{
		[HarmonyPrefix]
		private static bool Prefix(ref bool __result)
		{
			if (!HoodInput.UsingEmoteButton)
			{
				return true;
			}
			if (ZInput.GetButtonDown("OpenRadial"))
			{
				__result = true;
				return false;
			}
			__result = HoodInput.ShouldOpenEmoteRadial();
			return false;
		}
	}
	[HarmonyPatch(typeof(VisEquipment), "CustomUpdate")]
	internal static class VisEquipmentUpdatePatch
	{
		[HarmonyPostfix]
		private static void Postfix(VisEquipment __instance)
		{
			HoodVisuals.Apply(__instance);
		}
	}
	[HarmonyPatch(typeof(VisEquipment), "GetHairItem")]
	internal static class HairVisibilityPatch
	{
		[HarmonyPostfix]
		private static void Postfix(VisEquipment __instance, int itemHash, ref int __result)
		{
			if (HoodVisuals.IsHidden(__instance))
			{
				__result = itemHash;
			}
		}
	}
	internal static class SavedHelmetStates
	{
		private const string FileName = "wire.valheim.helmethider.hidden.txt";

		private static readonly HashSet<long> HiddenIds = new HashSet<long>();

		private static string _path;

		private static bool _loaded;

		private static bool _dirty;

		internal static string PathToFile => _path;

		internal static void Load()
		{
			if (_loaded)
			{
				return;
			}
			_loaded = true;
			_path = Path.Combine(Paths.ConfigPath, "wire.valheim.helmethider.hidden.txt");
			HiddenIds.Clear();
			try
			{
				if (!File.Exists(_path))
				{
					File.WriteAllText(_path, string.Empty);
					return;
				}
				string[] array = File.ReadAllLines(_path);
				for (int i = 0; i < array.Length; i++)
				{
					string text = array[i].Trim();
					if (text.Length != 0 && !text.StartsWith("#") && long.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && result != 0L)
					{
						HiddenIds.Add(result);
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("Could not read helmet-hidden state file: " + ex.Message));
				}
			}
		}

		internal static bool IsHidden(long playerId)
		{
			Load();
			if (playerId != 0L)
			{
				return HiddenIds.Contains(playerId);
			}
			return false;
		}

		internal static void SetHidden(long playerId, bool hidden)
		{
			if (playerId != 0L)
			{
				Load();
				if (hidden ? HiddenIds.Add(playerId) : HiddenIds.Remove(playerId))
				{
					_dirty = true;
				}
			}
		}

		internal static void AfterCharacterSaved(PlayerProfile profile, bool saveSucceeded)
		{
			if (!saveSucceeded || !_dirty || profile == null || (Object)(object)Game.instance == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null || Game.instance.GetPlayerProfile() != profile || Player.m_localPlayer.GetPlayerID() != profile.GetPlayerID())
			{
				return;
			}
			try
			{
				string[] array = new string[HiddenIds.Count];
				List<long> list = new List<long>(HiddenIds);
				list.Sort();
				for (int i = 0; i < list.Count; i++)
				{
					array[i] = list[i].ToString(CultureInfo.InvariantCulture);
				}
				string text = _path + ".tmp";
				File.WriteAllLines(text, array);
				if (File.Exists(_path))
				{
					File.Replace(text, _path, null);
				}
				else
				{
					File.Move(text, _path);
				}
				_dirty = false;
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("Could not save helmet-hidden state file: " + ex.Message));
				}
			}
		}
	}
	[HarmonyPatch(typeof(PlayerProfile), "Save")]
	internal static class HelmetStatePlayerProfileSavePatch
	{
		[HarmonyPostfix]
		private static void Postfix(PlayerProfile __instance, bool __result)
		{
			SavedHelmetStates.AfterCharacterSaved(__instance, __result);
		}
	}
	[HarmonyPatch(typeof(Game), "SpawnPlayer")]
	internal static class HelmetStateSpawnPatch
	{
		[HarmonyPostfix]
		private static void Postfix(Player __result)
		{
			HoodVisuals.RestoreLocalSavedState(__result);
		}
	}
	[HarmonyPatch(typeof(FejdStartup), "SetupCharacterPreview")]
	internal static class HelmetStatePreviewPatch
	{
		[HarmonyPostfix]
		private static void Postfix(FejdStartup __instance, PlayerProfile profile)
		{
			HoodVisuals.SetMenuPreview(((Object)(object)__instance != (Object)null) ? __instance.GetPreviewPlayer() : null, (profile != null) ? profile.GetPlayerID() : 0);
		}
	}
}