Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of BetterWheel v1.1.0
BetterWheel.dll
Decompiled 2 days agousing 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; } } }