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 ScrollToArms v0.2.2
ScrollToArms.dll
Decompiled 20 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.Audio; using UnityEngine.UI; [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("isimp")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright (c) 2026 isimp")] [assembly: AssemblyDescription("Pick the weapon or tool in your hand with the mouse wheel, while the game still decides when it can be changed.")] [assembly: AssemblyFileVersion("0.2.2.0")] [assembly: AssemblyInformationalVersion("0.2.2+92ddc2fbdfe32ba01f7294e316119c4ba1573a9d")] [assembly: AssemblyProduct("ScrollToArms")] [assembly: AssemblyTitle("ScrollToArms")] [assembly: AssemblyVersion("0.2.2.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 ScrollToArms { internal static class BuildToolHint { private enum Kind { None, KeepsTheWheel, WheelRotates } private static ItemData _watched; private static Kind _kind; private static float _watchUntil; private const float WatchTime = 5f; public static void Watch(ItemData item, float now) { _kind = KindOf(item); _watched = ((_kind == Kind.None) ? null : item); _watchUntil = now + 5f; } private static Kind KindOf(ItemData item) { if (item?.m_shared == null) { return Kind.None; } if (StepAside.Listed(item)) { return Kind.KeepsTheWheel; } if ((Object)(object)item.m_shared.m_buildPieces == (Object)null) { return Kind.None; } if (!WheelReads.PlacementReady) { return Kind.KeepsTheWheel; } if (Plugin.PlainScroll != PlainScroll.Hotbar) { return Kind.None; } return Kind.WheelRotates; } public static void Update(Player player, float now) { ItemData watched = _watched; if (watched != null) { if (now > _watchUntil) { _watched = null; } else if (watched.m_equipped) { _watched = null; Show(player, watched, _kind); } } } private static void Show(Player player, ItemData item, Kind kind) { if (!Plugin.ShowCenterHints) { return; } string text = ItemNameList.ShownName(item.m_shared.m_name); string text2 = KeyText.Marked(KeyText.Modifier); string text3; if (kind == Kind.WheelRotates) { text3 = text + " is out: the wheel rotates the piece. " + text2 + " with the wheel switches."; } else { string hide = KeyText.Hide; text3 = (string.IsNullOrEmpty(hide) ? (text + " keeps the wheel. Put it away to scroll the hotbar again.") : (text + " keeps the wheel. " + KeyText.Marked(hide) + " puts it away.")); if (Plugin.FlipBack) { text3 = text3 + " A tap on " + text2 + " goes back."; } } ((Character)player).Message((MessageType)2, text3, 0, (Sprite)null, false); } } internal static class HotbarView { private const string VanillaBarName = "HotKeyBar"; private static readonly FieldInfo ElementsField = AccessTools.Field(typeof(HotkeyBar), "m_elements"); private static readonly FieldInfo SelectedField = AccessTools.Field(typeof(HotkeyBar), "m_selected"); private static readonly Type ElementType = AccessTools.Inner(typeof(HotkeyBar), "ElementData"); private static readonly FieldInfo SelectionField = ((ElementType == null) ? null : AccessTools.Field(ElementType, "m_selection")); private static readonly FieldInfo QueuedField = ((ElementType == null) ? null : AccessTools.Field(ElementType, "m_queued")); private static readonly FieldInfo GoField = ((ElementType == null) ? null : AccessTools.Field(ElementType, "m_go")); private static readonly bool Available = CheckAvailable(); private static Hud _hud; private static HotkeyBar _bar; private static bool _drawn; private const float NameGap = 6f; private const float NameScale = 0.95f; private const float NameAlpha = 0.55f; private static TMP_Text _name; private const float FlashTime = 0.5f; private static readonly Color FlashColor = new Color(1f, 0.25f, 0.2f, 1f); private static GameObject _flashed; private static Graphic[] _flashedGraphics; private static Color[] _flashedColors; private const float PulseLow = 0.15f; private const float PulseRate = (float)Math.PI * 4f; private static CanvasGroup _pulsed; private static bool CheckAvailable() { if (ElementsField != null && SelectedField != null && SelectionField != null && QueuedField != null) { return true; } ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)"ScrollToArms: the hotbar's markers were not found. Scrolling still works, but the pick is not shown on the bar."); } return false; } public static void Draw() { if (!Available) { return; } Player localPlayer = Player.m_localPlayer; HotkeyBar val = FindBar(); if ((Object)(object)localPlayer == (Object)null || (Object)(object)val == (Object)null) { HideName(); _drawn = false; } else { if (!(ElementsField.GetValue(val) is IList list)) { return; } int num = (Picker.Choosing ? Picker.Cursor : Picker.PendingSlot); int pendingSlot = Picker.PendingSlot; float num2 = Time.time - Picker.LostAt; int num3 = ((Picker.LostSlot >= 0 && num2 < 0.5f && Picker.LostSlot != num) ? Picker.LostSlot : (-1)); if (num < 0 && pendingSlot < 0 && num3 < 0) { if (_drawn) { Restore(val, localPlayer); } _drawn = false; DrawLabel(val, localPlayer, list, -1, -1, 0f); return; } if (num3 < 0) { StopFlash(); } for (int i = 0; i < list.Count; i++) { object obj = list[i]; if (obj != null) { object? value = SelectionField.GetValue(obj); GameObject val2 = (GameObject)((value is GameObject) ? value : null); SetActive(val2, i == num || i == num3); if (i == num) { Pulse(val2, !Picker.Choosing && Picker.Waiting); } if (i == num3) { Flash(val2, num2 / 0.5f); } ItemData val3 = Picker.ItemAt(localPlayer, i); bool active = i == pendingSlot || (val3 != null && localPlayer.IsEquipActionQueued(val3)); object? value2 = QueuedField.GetValue(obj); SetActive((GameObject)((value2 is GameObject) ? value2 : null), active); } } DrawLabel(val, localPlayer, list, num, num3, num2 / 0.5f); _drawn = true; } } private static void DrawLabel(HotkeyBar bar, Player player, IList elements, int frame, int flash, float flashProgress) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0040: 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) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) int num = ((frame >= 0) ? frame : flash); ItemData val = ((num >= 0) ? Picker.ItemAt(player, num) : null); string text = null; Color color = Color.white; if (val != null) { if (Plugin.ShowItemName) { text = ItemNameList.ShownName(val.m_shared.m_name); } if (frame < 0) { color = FlashColor; color.a = 1f - Mathf.Clamp01(flashProgress); } } else { text = WheelMode.Text(player); } GameObject val2 = (GameObject)((text != null && GoField != null && elements.Count > 0) ? /*isinst with value type is only supported in some contexts*/: null); if ((Object)(object)val2 == (Object)null) { HideName(); return; } TMP_Text val3 = NameLabel(bar, val2); if (!((Object)(object)val3 == (Object)null)) { val3.text = text; color.a *= 0.55f; ((Graphic)val3).color = color; Vector3[] array = (Vector3[])(object)new Vector3[4]; ((RectTransform)val2.transform).GetWorldCorners(array); ((Transform)val3.rectTransform).position = array[1] + Vector3.up * (6f * val2.transform.lossyScale.y); if (!((Component)val3).gameObject.activeSelf) { ((Component)val3).gameObject.SetActive(true); } } } private static TMP_Text NameLabel(HotkeyBar bar, GameObject slot) { //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_name != (Object)null) { return _name; } TMP_Text val = null; Transform val2 = slot.transform.Find("binding"); if ((Object)(object)val2 != (Object)null) { val = ((Component)val2).GetComponent<TMP_Text>(); } if ((Object)(object)val == (Object)null) { val = slot.GetComponentInChildren<TMP_Text>(true); } if ((Object)(object)val == (Object)null) { return null; } GameObject obj = Object.Instantiate<GameObject>(((Component)val).gameObject, ((Component)bar).transform, false); ((Object)obj).name = "ScrollToArmsItemName"; _name = obj.GetComponent<TMP_Text>(); _name.alignment = (TextAlignmentOptions)1025; _name.textWrappingMode = (TextWrappingModes)0; _name.overflowMode = (TextOverflowModes)0; _name.fontSize = val.fontSize * 0.95f; ((Graphic)_name).raycastTarget = false; RectTransform rectTransform = _name.rectTransform; rectTransform.pivot = new Vector2(0f, 0f); rectTransform.sizeDelta = new Vector2(400f, rectTransform.sizeDelta.y); return _name; } private static void HideName() { if ((Object)(object)_name != (Object)null && ((Component)_name).gameObject.activeSelf) { ((Component)_name).gameObject.SetActive(false); } } private static void Flash(GameObject selection, float progress) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)selection == (Object)null) { return; } if ((Object)(object)_flashed != (Object)(object)selection) { StopFlash(); _flashed = selection; _flashedGraphics = selection.GetComponentsInChildren<Graphic>(true); _flashedColors = (Color[])(object)new Color[_flashedGraphics.Length]; for (int i = 0; i < _flashedGraphics.Length; i++) { _flashedColors[i] = _flashedGraphics[i].color; Color flashColor = FlashColor; flashColor.a = _flashedColors[i].a; _flashedGraphics[i].color = flashColor; } } CanvasGroup val = GroupOf(selection); if ((Object)(object)_pulsed == (Object)(object)val) { _pulsed = null; } val.alpha = 1f - Mathf.Clamp01(progress); } private static void StopFlash() { //IL_002e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_flashed == (Object)null) { return; } for (int i = 0; i < _flashedGraphics.Length; i++) { if ((Object)(object)_flashedGraphics[i] != (Object)null) { _flashedGraphics[i].color = _flashedColors[i]; } } CanvasGroup component = _flashed.GetComponent<CanvasGroup>(); if ((Object)(object)component != (Object)null) { component.alpha = 1f; } _flashed = null; _flashedGraphics = null; _flashedColors = null; } private static void Pulse(GameObject selection, bool on) { if ((Object)(object)selection == (Object)null) { return; } if (!on) { StopPulse(); return; } CanvasGroup val = GroupOf(selection); if ((Object)(object)_pulsed != (Object)(object)val) { StopPulse(); } _pulsed = val; float num = 0.5f - 0.5f * Mathf.Cos((Time.time - Picker.WaitingSince) * ((float)Math.PI * 4f)); val.alpha = Mathf.Lerp(0.15f, 1f, num); } private static CanvasGroup GroupOf(GameObject selection) { CanvasGroup val = selection.GetComponent<CanvasGroup>(); if ((Object)(object)val == (Object)null) { val = selection.AddComponent<CanvasGroup>(); } return val; } private static void StopPulse() { if ((Object)(object)_pulsed != (Object)null) { _pulsed.alpha = 1f; } _pulsed = null; } private static void Restore(HotkeyBar bar, Player player) { StopPulse(); StopFlash(); HideName(); if (!(ElementsField.GetValue(bar) is IList list)) { return; } bool flag = ZInput.IsGamepadActive(); int num = (int)SelectedField.GetValue(bar); for (int i = 0; i < list.Count; i++) { object obj = list[i]; if (obj != null) { object? value = SelectionField.GetValue(obj); SetActive((GameObject)((value is GameObject) ? value : null), flag && i == num); ItemData val = Picker.ItemAt(player, i); object? value2 = QueuedField.GetValue(obj); SetActive((GameObject)((value2 is GameObject) ? value2 : null), val != null && player.IsEquipActionQueued(val)); } } } private static HotkeyBar FindBar() { Hud instance = Hud.instance; if ((Object)(object)instance == (Object)null) { _hud = null; _bar = null; return null; } if ((Object)(object)instance == (Object)(object)_hud) { return _bar; } _hud = instance; _bar = null; HotkeyBar[] componentsInChildren = ((Component)instance).GetComponentsInChildren<HotkeyBar>(true); foreach (HotkeyBar val in componentsInChildren) { if (!(((Object)val).name != "HotKeyBar")) { _bar = val; break; } } return _bar; } private static void SetActive(GameObject go, bool active) { if ((Object)(object)go != (Object)null && go.activeSelf != active) { go.SetActive(active); } } } internal sealed class ItemNameList { private readonly Func<string> _source; private HashSet<string> _names = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private string _parsed; private static readonly Dictionary<string, string> Shown = new Dictionary<string, string>(); public ItemNameList(Func<string> source) { _source = source; } public bool Contains(ItemData item) { if (item?.m_shared == null) { return false; } Refresh(); if (_names.Count == 0) { return false; } GameObject dropPrefab = item.m_dropPrefab; if ((Object)(object)dropPrefab != (Object)null && _names.Contains(((Object)dropPrefab).name)) { return true; } string name = item.m_shared.m_name; if (string.IsNullOrEmpty(name)) { return false; } if (_names.Contains(name)) { return true; } return _names.Contains(ShownName(name)); } public static string ShownName(string token) { if (string.IsNullOrEmpty(token)) { return ""; } if (Shown.TryGetValue(token, out var value)) { return value; } Localization instance = Localization.instance; if (instance == null) { return token; } value = instance.Localize(token); Shown[token] = value; return value; } private void Refresh() { string text = _source() ?? ""; if (text == _parsed) { return; } _parsed = text; HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); string[] array = text.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length > 0) { hashSet.Add(text2); } } _names = hashSet; } } internal static class KeyText { public static string Modifier => Spaced(((object)Plugin.Modifier/*cast due to .constrained prefix*/).ToString()); public static string Hide { get { string text = ((ZInput.instance != null) ? ZInput.instance.GetBoundKeyString("Hide", true) : ""); Localization instance = Localization.instance; string text2; if (string.IsNullOrEmpty(text) || instance == null) { text2 = text; if (text2 == null) { return ""; } } else { text2 = instance.Localize(text); } return text2; } } public static string Marked(string label) { return "[<color=yellow><b>" + label + "</b></color>]"; } private static string Spaced(string raw) { StringBuilder stringBuilder = new StringBuilder(raw.Length + 4); for (int i = 0; i < raw.Length; i++) { if (i > 0 && char.IsUpper(raw[i]) && !char.IsUpper(raw[i - 1])) { stringBuilder.Append(' '); } stringBuilder.Append(raw[i]); } return stringBuilder.ToString(); } } public static class Picker { public const int Slots = 8; private static float _pendingUntil; private static bool _unequip; private static bool _asked; private static bool _queueSeen; private static string _lastBusy; private static float _lastNotch; private static float _wheel; private static ItemData _heldRight; private static ItemData _heldLeft; private static bool _warnedNotPatched; private static ItemData _current; private static ItemData _previous; private static float _tapStart; private static bool _tapSpoiled = true; private const float TapTime = 0.3f; private static readonly ItemNameList Skipped = new ItemNameList(() => Plugin.SkipItems); private const float NotchThreshold = 0.1f; private const float WheelMemory = 0.3f; private static readonly Func<Player, ItemData, bool> ToggleEquipped = ResolveToggleEquipped(); public static bool Choosing { get; private set; } public static int Cursor { get; private set; } = -1; public static ItemData Pending { get; private set; } public static int PendingSlot { get; private set; } = -1; public static bool Waiting { get { if (Pending != null) { return _lastBusy != null; } return false; } } public static float WaitingSince { get; private set; } public static int LostSlot { get; private set; } = -1; public static float LostAt { get; private set; } private static Func<Player, ItemData, bool> ResolveToggleEquipped() { try { MethodInfo methodInfo = AccessTools.Method(typeof(Player), "ToggleEquipped", (Type[])null, (Type[])null); if (methodInfo != null) { return AccessTools.MethodDelegate<Func<Player, ItemData, bool>>(methodInfo, (object)null, true); } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("ScrollToArms: Player.ToggleEquipped could not be resolved (" + ex.Message + ").")); } return null; } ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)"ScrollToArms: Player.ToggleEquipped was not found."); } return null; } public static bool HotbarOwnsWheel() { //IL_003a: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.Enabled || ToggleEquipped == null || !WheelReads.CameraReady) { return false; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || !CanPick(localPlayer)) { return false; } if (StepAside.Holding(localPlayer)) { return false; } bool key = Input.GetKey(Plugin.Modifier); if (((Character)localPlayer).InPlaceMode()) { if (key) { return WheelReads.PlacementReady; } return false; } if (Plugin.PlainScroll != PlainScroll.Zoom) { return !key; } return key; } public static void Update(float dt) { //IL_00c3: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || !Plugin.Enabled || ToggleEquipped == null) { Reset(); return; } if (!WheelReads.CameraRan && !_warnedNotPatched) { _warnedNotPatched = true; Plugin.Log.LogWarning((object)"ScrollToArms: the camera patch has not been applied yet, so hotbar scrolling waits for it."); } float time = Time.time; BuildToolHint.Update(localPlayer, time); TrackHands(localPlayer); bool flag = ReadTap(time); if (!CanPick(localPlayer)) { _tapSpoiled = true; Reset(); return; } if (Pending != null && Done(Pending)) { ClearPending(); RememberHands(localPlayer); } if ((Choosing || Pending != null) && HandsChanged(localPlayer)) { Reset(); return; } ReadWheel(localPlayer, time); if (Choosing) { bool num = Plugin.PlainScroll == PlainScroll.Zoom && !Input.GetKey(Plugin.Modifier); bool flag2 = Plugin.EffectiveCommit == Commit.AfterPause && time - _lastNotch >= Plugin.PauseTime; if (num || flag2) { CommitPick(localPlayer, time); } } else if (flag && Plugin.FlipBack) { Flip(localPlayer, time); } if (Pending != null) { TryEquipPending(localPlayer, time); } } private static void TrackHands(Player player) { ItemData val = ((Humanoid)player).RightItem ?? ((Humanoid)player).LeftItem; if (val != null && val != _current) { _previous = _current; _current = val; } } private static bool ReadTap(float now) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_001c: 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) KeyCode modifier = Plugin.Modifier; if (Input.GetKeyDown(modifier)) { _tapStart = now; _tapSpoiled = false; return false; } if (Input.GetKey(modifier)) { if (Input.anyKeyDown || !Application.isFocused || ZInput.GetMouseScrollWheel() != 0f) { _tapSpoiled = true; } return false; } if (!Input.GetKeyUp(modifier)) { return false; } bool result = !_tapSpoiled && now - _tapStart <= 0.3f; _tapSpoiled = true; return result; } private static void Flip(Player player, float now) { bool flag = ((Humanoid)player).RightItem == null && ((Humanoid)player).LeftItem == null; ItemData item = ((Pending != null || flag) ? _current : _previous); int num = SlotOf(player, item); if (num >= 0) { PickSlot(player, num, now, mayUnequip: false); } } private static void ReadWheel(Player player, float now) { if (!HotbarOwnsWheel()) { _wheel = 0f; return; } float mouseScrollWheel = ZInput.GetMouseScrollWheel(); if (mouseScrollWheel == 0f) { if (now - _lastNotch > 0.3f) { _wheel = 0f; } return; } _wheel += mouseScrollWheel; if (!(Mathf.Abs(_wheel) < 0.1f)) { int num = ((!(_wheel > 0f)) ? 1 : (-1)); if (Plugin.Invert) { num = -num; } _wheel = 0f; _lastNotch = now; Step(player, num); } } private static void Step(Player player, int step) { if (!Choosing) { int num = ((Pending != null) ? PendingSlot : SlotOfHeld(player)); if (NextStop(player, num, step) < 0) { return; } Choosing = true; Cursor = num; RememberHands(player); } _tapSpoiled = true; int num2 = NextStop(player, Cursor, step); if (num2 >= 0 && num2 != Cursor) { Cursor = num2; Sound.Play(Sound.Cue.Tick); } } private static void CommitPick(Player player, float now) { Choosing = false; int cursor = Cursor; Cursor = -1; if (cursor >= 0) { PickSlot(player, cursor, now, mayUnequip: true); } } private static void PickSlot(Player player, int slot, float now, bool mayUnequip) { ItemData val = ItemAt(player, slot); if (val == null || (val == Pending && (mayUnequip || !_unequip))) { return; } ItemData pending = Pending; if (pending != null) { if (player.IsEquipActionQueued(pending)) { ((Humanoid)player).RemoveEquipAction(pending); } ClearPending(); } if (!player.IsEquipActionQueued(val)) { bool equipped = val.m_equipped; if (!equipped || (mayUnequip && pending == null)) { Pending = val; PendingSlot = slot; _unequip = equipped; _pendingUntil = now + Plugin.WaitWhileBusy; _asked = false; _queueSeen = false; _lastBusy = null; RememberHands(player); } } } private static void TryEquipPending(Player player, float now) { ItemData pending = Pending; if (ItemAt(player, PendingSlot) != pending) { ClearPending(); return; } if (player.IsEquipActionQueued(pending)) { _queueSeen = true; _pendingUntil = now + Plugin.WaitWhileBusy; return; } string text = BusyReason(player, pending); if (text != null) { if (now >= _pendingUntil) { Lose(now); ClearPending(); return; } if (_lastBusy == null) { WaitingSince = now; } _lastBusy = text; return; } _lastBusy = null; if (_asked && !_queueSeen) { Lose(now); ClearPending(); return; } if (!_asked && !_unequip) { BuildToolHint.Watch(pending, now); } _asked = true; _queueSeen = false; if (Stow.IsStowed(player, pending)) { Stow.Unstow(player); } else { ToggleEquipped(player, pending); } if (Done(pending)) { ClearPending(); } else if (player.IsEquipActionQueued(pending)) { _queueSeen = true; } } private static string BusyReason(Player player, ItemData item) { if (((Character)player).InAttack()) { return "attacking"; } if (((Character)player).InDodge()) { return "dodging"; } if (((Character)player).IsSwimming() && !((Character)player).IsOnGround()) { return "swimming"; } if (item.m_shared.m_equipDuration > 0f && ((Character)player).IsRunning() && !Stow.IsStowed(player, item)) { return "running"; } return null; } public static bool Usable(Player player) { return CanPick(player); } private static bool CanPick(Player player) { if (((Character)player).IsDead() || ((Character)player).InCutscene() || ((Character)player).IsTeleporting()) { return false; } if (InventoryGui.IsVisible()) { return false; } if (StoreGui.IsVisible()) { return false; } if (Menu.IsVisible()) { return false; } if (Console.IsVisible()) { return false; } if (TextInput.IsVisible()) { return false; } if ((Object)(object)Minimap.instance != (Object)null && Minimap.IsOpen()) { return false; } if (Hud.IsPieceSelectionVisible()) { return false; } if (Hud.InRadial()) { return false; } if ((Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus()) { return false; } if (PlayerCustomizaton.IsBarberGuiVisible()) { return false; } if (GameCamera.InFreeFly()) { return false; } return true; } private static bool IsStop(ItemData item) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Invalid comparison between Unknown and I4 //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Invalid comparison between Unknown and I4 //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Invalid comparison between Unknown and I4 //IL_0024: 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_0029: Invalid comparison between Unknown and I4 if (item == null || !item.IsEquipable()) { return false; } ItemType itemType = item.m_shared.m_itemType; if ((int)itemType <= 15) { if (itemType - 3 > 2 && itemType - 14 > 1) { goto IL_0037; } } else if ((int)itemType != 19 && (int)itemType != 22) { goto IL_0037; } if (Plugin.SkipBuildTools && (Object)(object)item.m_shared.m_buildPieces != (Object)null) { return false; } if (Skipped.Contains(item)) { return false; } if (item.m_shared.m_useDurability) { return !(item.m_durability <= 0f); } return true; IL_0037: return false; } private static int Width(Player player) { return Mathf.Min(8, ((Humanoid)player).GetInventory().GetWidth()); } public static ItemData ItemAt(Player player, int slot) { if (slot < 0 || slot >= Width(player)) { return null; } return ((Humanoid)player).GetInventory().GetItemAt(slot, 0); } private static int NextStop(Player player, int from, int step) { int num = Width(player); if (num <= 0) { return -1; } int num2 = from; if (num2 < 0) { num2 = ((step > 0) ? (-1) : num); } for (int i = 0; i < num; i++) { num2 += step; if (num2 < 0 || num2 >= num) { if (!Plugin.Wrap) { if (from < 0) { return -1; } return from; } num2 = (num2 + num) % num; } if (IsStop(ItemAt(player, num2))) { return num2; } } if (from < 0 || !IsStop(ItemAt(player, from))) { return -1; } return from; } private static int SlotOf(Player player, ItemData item) { if (item == null) { return -1; } int num = Width(player); for (int i = 0; i < num; i++) { if (ItemAt(player, i) == item) { return i; } } return -1; } private static int SlotOfHeld(Player player) { if (((Humanoid)player).RightItem != null || ((Humanoid)player).LeftItem != null) { int num = SlotOf(player, ((Humanoid)player).RightItem); if (num < 0) { return SlotOf(player, ((Humanoid)player).LeftItem); } return num; } int num2 = SlotOf(player, Stow.Right(player)); if (num2 < 0) { return SlotOf(player, Stow.Left(player)); } return num2; } private static void RememberHands(Player player) { _heldRight = ((Humanoid)player).RightItem; _heldLeft = ((Humanoid)player).LeftItem; } private static bool HandsChanged(Player player) { if (((Humanoid)player).RightItem == _heldRight) { return ((Humanoid)player).LeftItem != _heldLeft; } return true; } private static void Lose(float now) { LostSlot = PendingSlot; LostAt = now; Sound.Play(Sound.Cue.Lost); } private static bool Done(ItemData item) { return item.m_equipped != _unequip; } private static void ClearPending() { Pending = null; PendingSlot = -1; _unequip = false; _lastBusy = null; } private static void Reset() { Choosing = false; Cursor = -1; ClearPending(); _wheel = 0f; } } public enum PlainScroll { Zoom, Hotbar } public enum Commit { OnRelease, AfterPause } [BepInPlugin("isimp.ScrollToArms", "ScrollToArms", "0.2.2")] [BepInProcess("valheim.exe")] public class Plugin : BaseUnityPlugin { public const string Guid = "isimp.ScrollToArms"; public const string Version = "0.2.2"; public static ManualLogSource Log; private static bool _broken; private static ConfigEntry<bool> _enabled; private static ConfigEntry<KeyCode> _modifier; private static ConfigEntry<PlainScroll> _plainScroll; private static ConfigEntry<Commit> _commit; private static ConfigEntry<float> _pauseTime; private static ConfigEntry<bool> _wrap; private static ConfigEntry<bool> _invert; private static ConfigEntry<bool> _skipBuildTools; private static ConfigEntry<string> _stepAsideTools; private static ConfigEntry<string> _skipItems; private static ConfigEntry<bool> _flipBack; private static ConfigEntry<bool> _showItemName; private static ConfigEntry<bool> _showWheelMode; private static ConfigEntry<bool> _showCenterHints; private static ConfigEntry<bool> _playSounds; private static ConfigEntry<float> _waitWhileBusy; private const string DefaultStepAsideTools = "BlueprintRune, PlanHammer"; private Harmony _harmony; public static bool Enabled { get { if (!_broken) { if (_enabled != null) { return _enabled.Value; } return true; } return false; } } public static KeyCode Modifier => (KeyCode)(((??)_modifier?.Value) ?? 308); public static PlainScroll PlainScroll => _plainScroll?.Value ?? PlainScroll.Zoom; public static Commit CommitSetting => _commit?.Value ?? Commit.OnRelease; public static float PauseTime => _pauseTime?.Value ?? 0.4f; public static bool Wrap { get { if (_wrap != null) { return _wrap.Value; } return true; } } public static bool Invert { get { if (_invert != null) { return _invert.Value; } return false; } } public static bool SkipBuildTools { get { if (_skipBuildTools != null) { return _skipBuildTools.Value; } return false; } } public static string StepAsideTools => _stepAsideTools?.Value ?? "BlueprintRune, PlanHammer"; public static string SkipItems => _skipItems?.Value ?? ""; public static bool FlipBack { get { if (_flipBack != null) { return _flipBack.Value; } return true; } } public static bool ShowItemName { get { if (_showItemName != null) { return _showItemName.Value; } return true; } } public static bool ShowWheelMode { get { if (_showWheelMode != null) { return _showWheelMode.Value; } return true; } } public static bool ShowCenterHints { get { if (_showCenterHints != null) { return _showCenterHints.Value; } return true; } } public static bool PlaySounds { get { if (_playSounds != null) { return _playSounds.Value; } return true; } } public static float WaitWhileBusy => _waitWhileBusy?.Value ?? 2f; public static Commit EffectiveCommit { get { if (PlainScroll != PlainScroll.Hotbar) { return CommitSetting; } return Commit.AfterPause; } } private void Awake() { //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Expected O, but got Unknown //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Expected O, but got Unknown //IL_0257: Unknown result type (might be due to invalid IL or missing references) //IL_0261: Expected O, but got Unknown //IL_02a4: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; _enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Turn hotbar scrolling off without removing the mod."); _modifier = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("General", "Modifier", (KeyCode)308, "Hold this while scrolling to use the other half of the wheel: the hotbar when PlainScroll is Zoom, the camera zoom when PlainScroll is Hotbar. In build mode the plain wheel rotates the piece being placed, so there this always picks from the hotbar."); _plainScroll = ((BaseUnityPlugin)this).Config.Bind<PlainScroll>("General", "PlainScroll", PlainScroll.Zoom, "What the mouse wheel does without the modifier. Zoom keeps the game's camera zoom on the wheel and scrolls the hotbar with the modifier held. Hotbar swaps the two. With a build tool out, the plain wheel rotates the piece either way and the modifier with the wheel picks from the hotbar; with Hotbar a message says so when the wheel takes a build tool out."); _commit = ((BaseUnityPlugin)this).Config.Bind<Commit>("Selection", "Commit", Commit.OnRelease, "When the picked slot is equipped. OnRelease equips it when you let go of the modifier. AfterPause equips it once the wheel has been still for PauseTime. When PlainScroll is Hotbar there is no modifier to release, so AfterPause is always used."); _pauseTime = ((BaseUnityPlugin)this).Config.Bind<float>("Selection", "PauseTime", 0.4f, new ConfigDescription("Seconds the wheel has to be still before the picked slot is equipped, when AfterPause applies.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 1.5f), Array.Empty<object>())); _wrap = ((BaseUnityPlugin)this).Config.Bind<bool>("Selection", "Wrap", true, "Scrolling past the last slot continues at the first, and the other way round."); _invert = ((BaseUnityPlugin)this).Config.Bind<bool>("Selection", "InvertDirection", false, "By default scrolling down moves right along the hotbar and scrolling up moves left. Turn this on to swap them."); _skipBuildTools = ((BaseUnityPlugin)this).Config.Bind<bool>("Selection", "SkipBuildTools", false, "Pass over the hammer, hoe, cultivator and other tools that open build mode. With them in hand the plain wheel rotates the piece being placed, and the modifier with the wheel still picks from the hotbar."); _skipItems = ((BaseUnityPlugin)this).Config.Bind<string>("Selection", "SkipItems", "", "Items the wheel passes over, separated by commas. Use the name shown in game, such as Bronze Pickaxe, or the item's prefab name."); _stepAsideTools = ((BaseUnityPlugin)this).Config.Bind<string>("Selection", "StepAsideTools", "BlueprintRune, PlanHammer", "Tools whose own controls use the modifier with the wheel. While one of them is in hand, the modifier and the wheel are left to the tool, and the Hide key, a number key or a tap on the modifier switches away. Names shown in game or prefab names, separated by commas. The defaults are the Blueprint Rune and Plan Hammer from PlanBuild."); _flipBack = ((BaseUnityPlugin)this).Config.Bind<bool>("Selection", "FlipBack", true, "Tap the modifier without scrolling to go back to the weapon or tool held before. A tap is a short press with no other key, mouse button or wheel in between. While a pick is still on its way, a tap cancels it and keeps what is in hand."); _showItemName = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowItemName", true, "Show the name of the item under the frame above the hotbar while picking."); _showWheelMode = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowWheelMode", true, "Show what the wheel does in the same spot above the hotbar when it is worth knowing: while the modifier is held, while a build tool takes the plain wheel for rotating the piece with PlainScroll on Hotbar, and while a tool on the StepAsideTools list keeps the wheel. Nothing is shown in ordinary play."); _showCenterHints = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowCenterHints", true, "Show a message in the middle of the screen when the wheel takes out a tool that changes what the wheel does."); _playSounds = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "PlaySounds", true, "Play a soft tick as the frame moves and a short sound when a pick is lost. Only you hear them, at the game's own sound effects volume."); _waitWhileBusy = ((BaseUnityPlugin)this).Config.Bind<float>("Rules", "WaitWhileBusy", 2f, new ConfigDescription("The game refuses to change what you hold while you attack, dodge or swim, and cancels an item's equip time while you run, jump or attack. A pick refused or cancelled that way is kept for this many seconds, with its slot pulsing on the hotbar, and equipped as soon as the game allows it. If the time runs out first, the slot flashes red and the pick is dropped. The game's own equip bar does not count against this time. 0 drops the pick at once, the way the number keys do.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>())); _harmony = new Harmony("isimp.ScrollToArms"); _broken = !WheelReads.Apply(_harmony); if (_broken) { Log.LogError((object)"ScrollToArms: hotbar scrolling is off for this session. The camera zoom is untouched."); } else { Log.LogInfo((object)string.Format("ScrollToArms {0}: plain scroll {1}, modifier {2}.", "0.2.2", PlainScroll, Modifier)); } } private void OnDestroy() { Sound.Mute(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } private void Update() { Sound.Prepare(); Picker.Update(Time.deltaTime); } private void LateUpdate() { HotbarView.Draw(); } } internal static class Sound { public enum Cue { Tick, Lost } private static readonly Dictionary<Cue, string> CueNames = new Dictionary<Cue, string> { { Cue.Tick, "sfx_gui_inventory_open" }, { Cue.Lost, "sfx_gui_inventory_close" } }; private const float Lifetime = 5f; private const float MinTickGap = 0.04f; private static readonly Dictionary<Cue, GameObject> Cues = new Dictionary<Cue, GameObject>(); private static bool _prepared; private static bool _muted; private static float _lastTick = -1f; private static GameObject _holder; private static Dictionary<string, GameObject> _effects; private static readonly Dictionary<Type, FieldInfo[]> EffectFieldCache = new Dictionary<Type, FieldInfo[]>(); public static void Prepare() { if (_prepared || !Plugin.PlaySounds || (Object)(object)ZNetScene.instance == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null) { return; } _prepared = true; foreach (KeyValuePair<Cue, string> cueName in CueNames) { GameObject val = Find(cueName.Value); Cues[cueName.Key] = val; if ((Object)(object)val == (Object)null) { Plugin.Log.LogInfo((object)("ScrollToArms could not find \"" + cueName.Value + "\"; that cue stays silent.")); } } } public static void Play(Cue cue) { if (_muted || !_prepared || !Plugin.PlaySounds) { return; } if (cue == Cue.Tick) { float unscaledTime = Time.unscaledTime; if (unscaledTime - _lastTick < 0.04f) { return; } _lastTick = unscaledTime; } if (Cues.TryGetValue(cue, out var value) && (Object)(object)value != (Object)null) { Play(value); } } public static void Mute() { _muted = true; } private static void Play(GameObject prefab) { //IL_00a8: 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) bool forceDisableInit = ZNetView.m_forceDisableInit; ZNetView.m_forceDisableInit = true; try { GameObject val = Object.Instantiate<GameObject>(prefab, Holder().transform, false); AudioMixerGroup val2 = (((Object)(object)AudioMan.instance != (Object)null) ? AudioMan.instance.m_guiMixer : null); AudioSource[] componentsInChildren = val.GetComponentsInChildren<AudioSource>(true); foreach (AudioSource val3 in componentsInChildren) { if ((Object)(object)val2 != (Object)null) { val3.outputAudioMixerGroup = val2; } val3.spatialBlend = 0f; } Camera mainCamera = Utils.GetMainCamera(); val.transform.SetParent((Transform)null, false); val.transform.position = (((Object)(object)mainCamera != (Object)null) ? ((Component)mainCamera).transform.position : Vector3.zero); Object.Destroy((Object)(object)val, 5f); } catch (Exception ex) { Plugin.Log.LogDebug((object)("ScrollToArms could not play a sound: " + ex.Message)); } finally { ZNetView.m_forceDisableInit = forceDisableInit; } } private static GameObject Holder() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown if ((Object)(object)_holder != (Object)null) { return _holder; } _holder = new GameObject("ScrollToArmsSounds"); _holder.SetActive(false); Object.DontDestroyOnLoad((Object)(object)_holder); return _holder; } private static GameObject Find(string name) { ZNetScene instance = ZNetScene.instance; if ((Object)(object)instance == (Object)null || string.IsNullOrEmpty(name)) { return null; } GameObject prefab = instance.GetPrefab(name); if ((Object)(object)prefab != (Object)null) { return prefab; } Dictionary<string, GameObject> dictionary = Effects(); if (dictionary == null || !dictionary.TryGetValue(name, out var value)) { return null; } return value; } private static Dictionary<string, GameObject> Effects() { if (_effects != null) { return _effects; } ZNetScene instance = ZNetScene.instance; if ((Object)(object)instance == (Object)null) { return null; } Stopwatch stopwatch = Stopwatch.StartNew(); Dictionary<string, GameObject> dictionary = new Dictionary<string, GameObject>(StringComparer.OrdinalIgnoreCase); try { foreach (string prefabName in instance.GetPrefabNames()) { GameObject prefab = instance.GetPrefab(prefabName); if ((Object)(object)prefab == (Object)null) { continue; } Component[] componentsInChildren = prefab.GetComponentsInChildren<Component>(true); foreach (Component val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } Gather(val, dictionary); SharedData val2 = ((ItemDrop)(((val is ItemDrop) ? val : null)?)).m_itemData?.m_shared; if (val2 != null) { Gather(val2, dictionary); if (val2.m_attack != null) { Gather(val2.m_attack, dictionary); } if (val2.m_secondaryAttack != null) { Gather(val2.m_secondaryAttack, dictionary); } } } } ObjectDB instance2 = ObjectDB.instance; if ((Object)(object)instance2 != (Object)null) { foreach (StatusEffect statusEffect in instance2.m_StatusEffects) { if ((Object)(object)statusEffect != (Object)null) { Gather(statusEffect, dictionary); } } } HashSet<Transform> hashSet = new HashSet<Transform>(); AddRoot((Component)(object)InventoryGui.instance, hashSet); AddRoot((Component)(object)Hud.instance, hashSet); AddRoot((Component)(object)StoreGui.instance, hashSet); AddRoot((Component)(object)Minimap.instance, hashSet); AddRoot((Component)(object)MessageHud.instance, hashSet); AddRoot((Component)(object)Menu.instance, hashSet); AddRoot((Component)(object)Chat.instance, hashSet); foreach (Transform item in hashSet) { Component[] componentsInChildren = ((Component)item).GetComponentsInChildren<Component>(true); foreach (Component val3 in componentsInChildren) { if ((Object)(object)val3 != (Object)null) { Gather(val3, dictionary); } } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("ScrollToArms could not finish indexing the game's effects: " + ex.Message)); } _effects = dictionary; Plugin.Log.LogInfo((object)$"ScrollToArms indexed {dictionary.Count} effect prefabs in {stopwatch.ElapsedMilliseconds} ms."); return _effects; } private static void AddRoot(Component part, HashSet<Transform> roots) { if ((Object)(object)part != (Object)null) { roots.Add(part.transform.root); } } private static void Gather(object owner, Dictionary<string, GameObject> index) { FieldInfo[] array = EffectFields(owner.GetType()); for (int i = 0; i < array.Length; i++) { object? value = array[i].GetValue(owner); EffectList val = (EffectList)((value is EffectList) ? value : null); if (val == null || val.m_effectPrefabs == null) { continue; } EffectData[] effectPrefabs = val.m_effectPrefabs; for (int j = 0; j < effectPrefabs.Length; j++) { GameObject val2 = effectPrefabs[j]?.m_prefab; if ((Object)(object)val2 != (Object)null && !index.ContainsKey(((Object)val2).name)) { index[((Object)val2).name] = val2; } } } } private static FieldInfo[] EffectFields(Type type) { if (EffectFieldCache.TryGetValue(type, out var value)) { return value; } List<FieldInfo> list = new List<FieldInfo>(); Type type2 = type; while (type2 != null && type2 != typeof(object)) { FieldInfo[] fields = type2.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType == typeof(EffectList)) { list.Add(fieldInfo); } } type2 = type2.BaseType; } value = list.ToArray(); EffectFieldCache[type] = value; return value; } } internal static class StepAside { private static readonly ItemNameList Tools = new ItemNameList(() => Plugin.StepAsideTools); public static bool Holding(Player player) { if (!Listed(((Humanoid)player).RightItem)) { return Listed(((Humanoid)player).LeftItem); } return true; } public static bool Listed(ItemData item) { return Tools.Contains(item); } } internal static class Stow { private static readonly FieldRef<Humanoid, ItemData> HiddenRight = Field("m_hiddenRightItem"); private static readonly FieldRef<Humanoid, ItemData> HiddenLeft = Field("m_hiddenLeftItem"); private static readonly Action<Humanoid, bool, bool> ShowHandItems = ResolveShow(); public static bool Available { get { if (HiddenRight != null && HiddenLeft != null) { return ShowHandItems != null; } return false; } } private static FieldRef<Humanoid, ItemData> Field(string name) { try { return AccessTools.FieldRefAccess<Humanoid, ItemData>(name); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("ScrollToArms: Humanoid." + name + " could not be read (" + ex.Message + "). Stowed items are treated as empty hands.")); } return null; } } private static Action<Humanoid, bool, bool> ResolveShow() { try { MethodInfo methodInfo = AccessTools.Method(typeof(Humanoid), "ShowHandItems", (Type[])null, (Type[])null); if (methodInfo != null) { return AccessTools.MethodDelegate<Action<Humanoid, bool, bool>>(methodInfo, (object)null, true); } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("ScrollToArms: Humanoid.ShowHandItems could not be resolved (" + ex.Message + "). Stowed items are treated as empty hands.")); } return null; } ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)"ScrollToArms: Humanoid.ShowHandItems was not found. Stowed items are treated as empty hands."); } return null; } public static ItemData Right(Player player) { if (!Available) { return null; } return HiddenRight.Invoke((Humanoid)(object)player); } public static ItemData Left(Player player) { if (!Available) { return null; } return HiddenLeft.Invoke((Humanoid)(object)player); } public static bool IsStowed(Player player, ItemData item) { if (!Available || item == null) { return false; } if (((Humanoid)player).RightItem != null || ((Humanoid)player).LeftItem != null) { return false; } if (item != HiddenRight.Invoke((Humanoid)(object)player)) { return item == HiddenLeft.Invoke((Humanoid)(object)player); } return true; } public static void Unstow(Player player) { ShowHandItems((Humanoid)(object)player, arg2: false, arg3: true); } } internal static class WheelMode { private enum State { None, ModifierPicks, ModifierZooms, BuildRotates, BuildStuck, ToolKeeps } private static State _state; private static ItemData _tool; private static KeyCode _modifier; private static bool _flipBack; private static string _text; public static string Text(Player player) { //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.ShowWheelMode || !Plugin.Enabled || !WheelReads.CameraReady || !Picker.Usable(player)) { return null; } ItemData val = (StepAside.Listed(((Humanoid)player).RightItem) ? ((Humanoid)player).RightItem : (StepAside.Listed(((Humanoid)player).LeftItem) ? ((Humanoid)player).LeftItem : null)); State state = StateOf(player, val); if (state == State.None) { return null; } if (state != _state || val != _tool || Plugin.Modifier != _modifier || Plugin.FlipBack != _flipBack || _text == null) { _state = state; _tool = val; _modifier = Plugin.Modifier; _flipBack = Plugin.FlipBack; _text = Build(state, val); } return _text; } private static State StateOf(Player player, ItemData tool) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (tool != null) { return State.ToolKeeps; } bool key = Input.GetKey(Plugin.Modifier); if (((Character)player).InPlaceMode()) { if (!WheelReads.PlacementReady) { return State.BuildStuck; } if (key) { return State.ModifierPicks; } if (Plugin.PlainScroll != PlainScroll.Hotbar) { return State.None; } return State.BuildRotates; } if (!key) { return State.None; } if (Plugin.PlainScroll != PlainScroll.Zoom) { return State.ModifierZooms; } return State.ModifierPicks; } private static string Build(State state, ItemData tool) { string text = "[" + KeyText.Modifier + "]"; string hide = KeyText.Hide; string text2 = (string.IsNullOrEmpty(hide) ? "" : (" | [" + hide + "] put away")); string text3 = (Plugin.FlipBack ? (" | tap " + text + " back") : ""); return state switch { State.ModifierPicks => "Wheel: hotbar", State.ModifierZooms => "Wheel: zoom", State.BuildRotates => "Wheel: rotate | " + text + " + wheel: hotbar", State.BuildStuck => "Wheel: rotate" + text2 + text3, State.ToolKeeps => ItemNameList.ShownName(tool.m_shared.m_name) + " keeps the wheel" + text2 + text3, _ => null, }; } } internal static class WheelReads { private static readonly MethodInfo Original = AccessTools.Method(typeof(ZInput), "GetMouseScrollWheel", (Type[])null, (Type[])null); private static readonly MethodInfo Replacement = AccessTools.Method(typeof(WheelReads), "Read", (Type[])null, (Type[])null); private static readonly MethodInfo Camera = AccessTools.Method(typeof(GameCamera), "UpdateCamera", (Type[])null, (Type[])null); private static readonly MethodInfo Placement = AccessTools.Method(typeof(Player), "UpdatePlacement", (Type[])null, (Type[])null); private static readonly Dictionary<MethodBase, int> Found = new Dictionary<MethodBase, int>(); private static readonly Dictionary<MethodBase, int> Reported = new Dictionary<MethodBase, int>(); public static bool CameraReady => ReadyFor(Camera); public static bool PlacementReady => ReadyFor(Placement); public static bool CameraRan { get { if (Camera != null) { return Found.ContainsKey(Camera); } return false; } } private static bool ReadyFor(MethodBase method) { if (method != null && Found.TryGetValue(method, out var value)) { return value > 0; } return false; } public static bool Apply(Harmony harmony) { if (Original == null || Camera == null) { Plugin.Log.LogError((object)"ScrollToArms: GameCamera.UpdateCamera or ZInput.GetMouseScrollWheel was not found."); return false; } if (!Patch(harmony, Camera, "GameCamera.UpdateCamera")) { return false; } if (Placement == null) { Plugin.Log.LogWarning((object)"ScrollToArms: Player.UpdatePlacement was not found. Hotbar scrolling stays off in build mode."); } else { Patch(harmony, Placement, "Player.UpdatePlacement"); } return true; } private static bool Patch(Harmony harmony, MethodBase target, string name) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown try { harmony.Patch(target, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(WheelReads), "Transpiler", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null); return true; } catch (Exception ex) { Plugin.Log.LogError((object)("ScrollToArms: could not patch " + name + " - " + ex.Message)); return false; } } private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase original) { int replaced = 0; foreach (CodeInstruction instruction in instructions) { if (CodeInstructionExtensions.Calls(instruction, Original)) { instruction.operand = Replacement; replaced++; } yield return instruction; } Found[original] = replaced; Report(original, replaced); } private static void Report(MethodBase method, int replaced) { if (!Reported.TryGetValue(method, out var value) || value != replaced) { Reported[method] = replaced; string arg = ((method == Camera) ? "camera zoom" : "piece rotation"); if (replaced > 0) { Plugin.Log.LogInfo((object)string.Format("ScrollToArms: {0} hands over the wheel at {1} {2}.", arg, replaced, (replaced == 1) ? "read" : "reads")); } else if (method == Camera) { Plugin.Log.LogError((object)"ScrollToArms: GameCamera.UpdateCamera no longer reads the mouse wheel where expected. Hotbar scrolling stays off; the camera zoom is untouched."); } else { Plugin.Log.LogWarning((object)"ScrollToArms: Player.UpdatePlacement no longer reads the mouse wheel where expected. Hotbar scrolling stays off in build mode."); } } } public static float Read() { float mouseScrollWheel = ZInput.GetMouseScrollWheel(); if (!Picker.HotbarOwnsWheel()) { return mouseScrollWheel; } return 0f; } } }