Decompiled source of BetterWheel v1.1.0

BetterWheel.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
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;
using Valheim.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("BetterWheel")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0+3feb34069b1268942b9e19f9e9606b36cae9de17")]
[assembly: AssemblyProduct("BetterWheel")]
[assembly: AssemblyTitle("BetterWheel")]
[assembly: AssemblyVersion("1.1.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 BetterWheel
{
	[HarmonyPatch(typeof(OpenRadialConfig), "InitRadialConfig")]
	internal static class OpenWheelPatch
	{
		private static readonly Func<OpenRadialConfig, RadialBase, bool> TryOpenHoverMenu = AccessTools.MethodDelegate<Func<OpenRadialConfig, RadialBase, bool>>(AccessTools.Method(typeof(OpenRadialConfig), "TryOpenNonDefaultRadials", (Type[])null, (Type[])null), (object)null, true);

		private static bool Prefix(OpenRadialConfig __instance, RadialBase radial)
		{
			ItemGroupConfig config;
			bool flag = Wheels.TryTakeRequest(out config);
			Wheels.ApplyRelease(flag);
			if (!flag && WheelConfig.Main.Value == MainWheel.Categories)
			{
				return true;
			}
			radial.OnInteractionDelay = delegate(float delay)
			{
				PlayerController.SetTakeInputDelay(delay);
			};
			radial.ShouldAnimateIn = true;
			string text;
			if (flag)
			{
				text = "wheel \"" + config.GroupName + "\"";
				radial.Open((IRadialConfig)(object)config, (IRadialConfig)null);
			}
			else if (ZInput.GetButton("OpenEmote"))
			{
				text = "emotes";
				radial.Open((IRadialConfig)(object)RadialData.SO.EmoteGroupConfig, (IRadialConfig)null);
			}
			else if (TryOpenHoverMenu(__instance, radial))
			{
				text = "hover menu";
			}
			else
			{
				text = "all items";
				radial.Open((IRadialConfig)(object)Wheels.AllItems(), (IRadialConfig)null);
			}
			WheelLog.Write("Opened " + text + ", release to use " + (RadialData.SO.EnableReleaseToUseMode ? "on" : "off"));
			return false;
		}
	}
	[HarmonyPatch(/*Could not decode attribute arguments.*/)]
	internal static class BuildModePatch
	{
		private static void Postfix(ref bool __result)
		{
			if (__result && Wheels.OpensInBuildMode)
			{
				__result = false;
			}
		}
	}
	[HarmonyPatch(typeof(RadialConfigHelper), "SetItemInteractionControls")]
	internal static class WheelControlsPatch
	{
		private static void Postfix(RadialBase radial)
		{
			Func<bool> close = radial.GetClose;
			radial.GetClose = delegate
			{
				if (!Wheels.Released(radial))
				{
					Func<bool> func = close;
					if (func == null || !func())
					{
						return Wheels.CloseRequested(radial);
					}
				}
				return true;
			};
		}
	}
	[HarmonyPatch(typeof(ItemGroupConfig), "AddElement")]
	internal static class KeepOpenPatch
	{
		private static void Postfix(List<RadialMenuElement> elements, RadialBase radial)
		{
			if (WheelConfig.AfterUse.Value == KeepOpen.UntilClosed && !radial.IsHoverMenu && elements.Count != 0)
			{
				RadialMenuElement obj = elements[elements.Count - 1];
				obj.AdvancedCloseOnInteract = null;
				obj.CloseOnInteract = () => false;
			}
		}
	}
	[BepInPlugin("nexuschip.BetterWheel", "Better Wheel", "1.1.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "nexuschip.BetterWheel";

		public const string PluginName = "Better Wheel";

		public const string PluginVersion = "1.1.0";

		private readonly Harmony _harmony = new Harmony("nexuschip.BetterWheel");

		private void Awake()
		{
			WheelConfig.Bind(((BaseUnityPlugin)this).Config);
			WheelLog.Bind(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger);
			_harmony.PatchAll(typeof(Plugin).Assembly);
		}

		private void Update()
		{
			Wheels.Update();
		}

		private void OnDestroy()
		{
			_harmony.UnpatchSelf();
		}
	}
	public enum MainWheel
	{
		AllItems,
		Categories
	}
	public enum KeepOpen
	{
		UntilClosed,
		Vanilla
	}
	public enum ReleaseToUse
	{
		On,
		Off,
		Game
	}
	public static class WheelConfig
	{
		public const int WheelSlots = 4;

		public const string GearTypes = "OneHandedWeapon, TwoHandedWeapon, TwoHandedWeaponLeft, Bow, Shield, Torch, Tool, Attach_Atgeir, Ammo, Helmet, Chest, Legs, Shoulder, Hands, Utility, Trinket";

		public static ConfigEntry<MainWheel> Main;

		public static ConfigEntry<KeepOpen> AfterUse;

		public static ConfigEntry<ReleaseToUse> MainRelease;

		public static ConfigEntry<float> ReleaseHold;

		public static ConfigEntry<bool> InBuildMode;

		public static readonly ConfigEntry<ReleaseToUse>[] Release = new ConfigEntry<ReleaseToUse>[4];

		public static readonly ConfigEntry<string>[] Names = new ConfigEntry<string>[4];

		public static readonly ConfigEntry<KeyboardShortcut>[] Keys = new ConfigEntry<KeyboardShortcut>[4];

		public static readonly ConfigEntry<string>[] Items = new ConfigEntry<string>[4];

		public static void Bind(ConfigFile cfg)
		{
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Expected O, but got Unknown
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			Main = cfg.Bind<MainWheel>("1 - General", "WheelKeyOpens", MainWheel.AllItems, "What the wheel key (G) opens. AllItems: straight into all your items, no category wheel; looking at a fermenter, item stand and the like still opens the items it takes, and the emote key still opens emotes. Categories: the game's category wheel.");
			AfterUse = cfg.Bind<KeepOpen>("1 - General", "AfterUse", KeepOpen.UntilClosed, "After you use something from a wheel. UntilClosed: the wheel stays open for the next pick until you close it (its key again, Esc or right-click). Vanilla: gear and food keep it open, other things close it. Letting go of a held key with ReleaseToUse on always closes it.");
			MainRelease = cfg.Bind<ReleaseToUse>("1 - General", "ReleaseToUse", ReleaseToUse.On, ReleaseText("the wheel key (G) and the emote key (T)"));
			ReleaseHold = cfg.Bind<float>("1 - General", "ReleaseHoldTime", 0.2f, new ConfigDescription("Seconds a wheel key must be held before letting go counts as a release (uses what you point at, or closes). A shorter press is a tap: the wheel stays open for clicking.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 1f), Array.Empty<object>()));
			InBuildMode = cfg.Bind<bool>("1 - General", "InBuildMode", true, "The wheel keys open the wheel with a building tool out (hammer, hoe, cultivator). Keyboard and mouse only: with a gamepad the game's rule stays (its wheel trigger also turns pieces). Off: as in the game, no wheel while building.");
			string[] array = new string[4] { "Food & gear", "Food", "Gear", "" };
			string[] array2 = new string[4] { "Consumable, OneHandedWeapon, TwoHandedWeapon, TwoHandedWeaponLeft, Bow, Shield, Torch, Tool, Attach_Atgeir, Ammo, Helmet, Chest, Legs, Shoulder, Hands, Utility, Trinket", "Consumable", "OneHandedWeapon, TwoHandedWeapon, TwoHandedWeaponLeft, Bow, Shield, Torch, Tool, Attach_Atgeir, Ammo, Helmet, Chest, Legs, Shoulder, Hands, Utility, Trinket", "" };
			for (int i = 0; i < 4; i++)
			{
				string text = $"{i + 2} - Wheel {i + 1}";
				Names[i] = cfg.Bind<string>(text, "Name", array[i], "Shown in the middle of this wheel.");
				Keys[i] = cfg.Bind<KeyboardShortcut>(text, "Key", KeyboardShortcut.Empty, "Opens this wheel (press again to close). Any key, with modifiers if you like, or a side mouse button (Mouse3, Mouse4). Empty: no key, the wheel is off.");
				Release[i] = cfg.Bind<ReleaseToUse>(text, "ReleaseToUse", ReleaseToUse.On, ReleaseText("this wheel's key"));
				Items[i] = cfg.Bind<string>(text, "ItemTypes", array2[i], "Item types on this wheel, separated by commas: Consumable (food, meads, potions), OneHandedWeapon, TwoHandedWeapon, TwoHandedWeaponLeft, Bow, Shield, Torch, Tool, Attach_Atgeir, Ammo, Helmet, Chest, Legs, Shoulder, Hands, Utility, Trinket, Material, Trophy, Fish, Misc.");
			}
		}

		private static string ReleaseText(string keys)
		{
			return "Holding " + keys + ", pointing at something and letting go. On: uses what the cursor points at and closes the wheel (a quick tap still opens it for clicking). Off: letting go just closes it. Game: the game's own Release to use setting.";
		}
	}
	public static class WheelLog
	{
		public static ConfigEntry<bool> Enabled;

		private static ManualLogSource _log;

		public static void Bind(ConfigFile cfg, ManualLogSource log)
		{
			_log = log;
			Enabled = cfg.Bind<bool>("9 - Debug", "Log", false, "Writes what the wheel does (openings, key releases, uses) to the BepInEx log, for bug reports.");
		}

		public static void Write(string text)
		{
			if (Enabled != null && Enabled.Value)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogInfo((object)$"[frame {Time.frameCount}] {text}");
				}
			}
		}

		public static string State(RadialBase radial)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			Traverse val = Traverse.Create((object)radial);
			Vector2 value = val.Field("m_previousInput").GetValue<Vector2>();
			string text = string.Format("selected {0} (index {1}, layer {2} of ", Describe(radial.Selected), val.Field("m_index").GetValue<int>(), val.Field("m_currentLayer").GetValue<int>());
			string text2 = string.Format("{0}), pointer input {1}, ", val.Field("m_nrOfLayers").GetValue<int>() + 1, (value == Vector2.zero) ? "none" : ((Vector2)(ref value)).ToString("0.00"));
			object arg = radial.IsHoverMenu;
			IRadialConfig currentConfig = radial.CurrentConfig;
			return text + text2 + $"hover menu {arg}, page {((currentConfig != null) ? currentConfig.LocalizedName : null)}";
		}

		public static int QueuedActions(Player player)
		{
			if (!((Object)(object)player != (Object)null))
			{
				return 0;
			}
			return Traverse.Create((object)player).Field("m_actionQueue").Property("Count", (object[])null)
				.GetValue<int>();
		}

		public static string Describe(RadialMenuElement element)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)element == (Object)null)
			{
				return "nothing";
			}
			ItemElement val = (ItemElement)(object)((element is ItemElement) ? element : null);
			if (val != null && val.m_data != null)
			{
				return string.Format("{0} ({1}{2})", val.m_data.m_shared.m_name, val.m_data.m_shared.m_itemType, val.m_data.m_equipped ? ", equipped" : "");
			}
			return ((object)element).GetType().Name;
		}
	}
	[HarmonyPatch(typeof(RadialBase), "OnInteract")]
	internal static class InteractLogPatch
	{
		private static void Prefix(RadialBase __instance)
		{
			WheelLog.Write("Use: " + WheelLog.State(__instance));
		}

		private static void Postfix(RadialBase __instance)
		{
			if (WheelLog.Enabled.Value)
			{
				RadialMenuElement selected = __instance.Selected;
				ItemElement val = (ItemElement)(object)((selected is ItemElement) ? selected : null);
				if (val != null && val.m_data != null)
				{
					string arg = WheelLog.Describe((RadialMenuElement)(object)val);
					Player localPlayer = Player.m_localPlayer;
					WheelLog.Write($"  after use: {arg}, still in inventory {((localPlayer != null) ? new bool?(((Humanoid)localPlayer).GetInventory().ContainsItem(val.m_data)) : ((bool?)null))}, " + $"queued actions {WheelLog.QueuedActions(Player.m_localPlayer)}");
				}
			}
		}
	}
	[HarmonyPatch(typeof(Player), "ClearActionQueue")]
	internal static class ActionQueueLogPatch
	{
		private static void Prefix(Player __instance)
		{
			if (WheelLog.Enabled.Value && (Object)(object)__instance == (Object)(object)Player.m_localPlayer && WheelLog.QueuedActions(__instance) > 0)
			{
				WheelLog.Write($"The game cancelled {WheelLog.QueuedActions(__instance)} queued action(s) (running {((Character)__instance).IsRunning()}, " + $"on ground {((Character)__instance).IsOnGround()}, wheel open {Hud.InRadial()})");
			}
		}
	}
	public static class Wheels
	{
		private static readonly Func<Player, bool> TakeInput = AccessTools.MethodDelegate<Func<Player, bool>>(AccessTools.Method(typeof(Player), "TakeInput", (Type[])null, (Type[])null), (object)null, true);

		private static readonly Action<RadialBase> Use = AccessTools.MethodDelegate<Action<RadialBase>>(AccessTools.Method(typeof(RadialBase), "OnInteract", (Type[])null, (Type[])null), (object)null, true);

		private static int _requested = -1;

		private static bool _releaseOverridden;

		private static bool _releaseUses;

		private static string _openButton;

		private static int _open = -1;

		private static int _openedFrame;

		private static float _pressedAt;

		public static bool OpensInBuildMode
		{
			get
			{
				if (WheelConfig.InBuildMode.Value)
				{
					return !ZInput.IsGamepadActive();
				}
				return false;
			}
		}

		public static void Update()
		{
			Player localPlayer = Player.m_localPlayer;
			Hud instance = Hud.instance;
			if ((Object)(object)localPlayer == (Object)null || (Object)(object)instance == (Object)null || (Object)(object)instance.m_radialMenu == (Object)null || !Hud.InRadial())
			{
				_open = -1;
				RestoreRelease();
			}
			if (!((Object)(object)localPlayer == (Object)null) && !((Object)(object)instance == (Object)null) && !((Object)(object)instance.m_radialMenu == (Object)null) && !Hud.InRadial())
			{
				int num = PressedWheel();
				if (num >= 0 && HasItemTypes(num) && CanOpen(localPlayer))
				{
					_requested = num;
					_pressedAt = Time.time;
					RadialBase radialMenu = instance.m_radialMenu;
					radialMenu.CanOpen = true;
					radialMenu.Open((IRadialConfig)(object)instance.m_config, (IRadialConfig)null);
					_requested = -1;
				}
			}
		}

		public static bool TryTakeRequest(out ItemGroupConfig config)
		{
			config = null;
			if (_requested < 0)
			{
				return false;
			}
			config = Create(_requested);
			_open = _requested;
			_openedFrame = Time.frameCount;
			_requested = -1;
			return (Object)(object)config != (Object)null;
		}

		public static void ApplyRelease(bool fromWheelKey)
		{
			ReleaseToUse releaseToUse = ((fromWheelKey && _open >= 0) ? WheelConfig.Release[_open].Value : WheelConfig.MainRelease.Value);
			_releaseUses = ((releaseToUse == ReleaseToUse.Game) ? GameRelease() : (releaseToUse == ReleaseToUse.On));
			_openButton = (fromWheelKey ? null : (ZInput.GetButton("OpenEmote") ? "OpenEmote" : "OpenRadial"));
			RadialData.SO.EnableReleaseToUseMode = false;
			_releaseOverridden = true;
		}

		private static void RestoreRelease()
		{
			if (_releaseOverridden && (Object)(object)RadialData.SO != (Object)null)
			{
				RadialData.SO.EnableReleaseToUseMode = GameRelease();
				_releaseOverridden = false;
			}
		}

		private static bool GameRelease()
		{
			return PlatformPrefs.GetInt("RadialReleaseToUse", 0) != 0;
		}

		public static ItemGroupConfig AllItems()
		{
			ItemGroupConfig obj = Object.Instantiate<ItemGroupConfig>(RadialData.SO.ItemGroupConfig);
			obj.GroupName = "allitems";
			return obj;
		}

		public static bool Released(RadialBase radial)
		{
			if (!TryGetRelease(out var held) || held < WheelConfig.ReleaseHold.Value)
			{
				return false;
			}
			WheelLog.Write(string.Format("Let go after {0:0.00}s: {1}, {2}", held, _releaseUses ? "use" : "close", WheelLog.State(radial)));
			if (_releaseUses)
			{
				Use(radial);
			}
			return true;
		}

		public static bool CloseRequested(RadialBase radial)
		{
			if (_open < 0 || Time.frameCount <= _openedFrame)
			{
				return false;
			}
			int num = PressedWheel();
			if (num < 0)
			{
				return false;
			}
			if (num == _open)
			{
				return true;
			}
			ItemGroupConfig val = Create(num);
			if ((Object)(object)val != (Object)null)
			{
				_open = num;
				_openedFrame = Time.frameCount;
				_pressedAt = Time.time;
				ApplyRelease(fromWheelKey: true);
				radial.QueuedOpen((IRadialConfig)(object)val, (IRadialConfig)null);
			}
			return false;
		}

		private static bool TryGetRelease(out float held)
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			held = 0f;
			if (_openButton != null)
			{
				if (ZInput.GetButtonUp(_openButton))
				{
					held = ZInput.GetButtonLastPressedTimer(_openButton);
					return true;
				}
				if (ZInput.GetButtonUp("JoyRadial"))
				{
					held = ZInput.GetButtonLastPressedTimer("JoyRadial");
					return true;
				}
				return false;
			}
			if (_open >= 0 && Time.frameCount > _openedFrame && KeyUp(WheelConfig.Keys[_open].Value))
			{
				held = Time.time - _pressedAt;
				return true;
			}
			return false;
		}

		private static bool HasItemTypes(int slot)
		{
			return ParseTypes(WheelConfig.Items[slot].Value).Length != 0;
		}

		private static ItemGroupConfig Create(int slot)
		{
			ItemType[] array = ParseTypes(WheelConfig.Items[slot].Value);
			if (array.Length == 0)
			{
				return null;
			}
			ItemGroupConfig obj = Object.Instantiate<ItemGroupConfig>(RadialData.SO.ItemGroupConfig);
			string value = WheelConfig.Names[slot].Value;
			obj.GroupName = (string.IsNullOrWhiteSpace(value) ? $"Wheel {slot + 1}" : value.Trim());
			obj.ItemTypes = array;
			return obj;
		}

		private static ItemType[] ParseTypes(string list)
		{
			//IL_0038: 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_0045: Unknown result type (might be due to invalid IL or missing references)
			List<ItemType> list2 = new List<ItemType>();
			string[] array = (list ?? "").Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				if (Enum.TryParse<ItemType>(array[i].Trim(), ignoreCase: true, out ItemType result) && (int)result != 0 && !list2.Contains(result))
				{
					list2.Add(result);
				}
			}
			return list2.ToArray();
		}

		private static bool CanOpen(Player player)
		{
			if (!((Character)player).IsDead() && TakeInput(player) && (!((Character)player).InPlaceMode() || OpensInBuildMode))
			{
				return !Hud.IsPieceSelectionVisible();
			}
			return false;
		}

		private static int PressedWheel()
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			for (int i = 0; i < 4; i++)
			{
				if (KeyDown(WheelConfig.Keys[i].Value))
				{
					return i;
				}
			}
			return -1;
		}

		private static bool KeyDown(KeyboardShortcut key)
		{
			//IL_0002: 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)
			if ((int)((KeyboardShortcut)(ref key)).MainKey != 0 && Down(((KeyboardShortcut)(ref key)).MainKey))
			{
				return ((KeyboardShortcut)(ref key)).Modifiers.All(Held);
			}
			return false;
		}

		private static bool KeyUp(KeyboardShortcut key)
		{
			//IL_0002: 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_001d: Unknown result type (might be due to invalid IL or missing references)
			if ((int)((KeyboardShortcut)(ref key)).MainKey == 0)
			{
				return false;
			}
			if (!IsMouse(((KeyboardShortcut)(ref key)).MainKey, out var button))
			{
				return ZInput.GetKeyUp(((KeyboardShortcut)(ref key)).MainKey, false);
			}
			return ZInput.GetMouseButtonUp(button);
		}

		private static bool Down(KeyCode key)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			if (!IsMouse(key, out var button))
			{
				return ZInput.GetKeyDown(key, false);
			}
			return ZInput.GetMouseButtonDown(button);
		}

		private static bool Held(KeyCode key)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			if (!IsMouse(key, out var button))
			{
				return ZInput.GetKey(key, false);
			}
			return ZInput.GetMouseButton(button);
		}

		private static bool IsMouse(KeyCode key, out int button)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Expected I4, but got Unknown
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Invalid comparison between Unknown and I4
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Invalid comparison between Unknown and I4
			button = key - 323;
			if ((int)key >= 323)
			{
				return (int)key <= 329;
			}
			return false;
		}
	}
}