using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.System.Input; using FFXIVClientStructs.FFXIV.Client.System.String; using FFXIVClientStructs.FFXIV.Client.UI; using Microsoft.Extensions.Logging; using Questionable.Controller.Steps.Common; using Questionable.Model; using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Interactions; internal static class Dive { internal sealed class Factory : SimpleTaskFactory { public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step) { if (step.InteractionType != EInteractionType.Dive) { return null; } return new Task(); } } internal sealed class Task : ITask { public override string ToString() { return "Dive"; } } internal sealed class DoDive(ICondition condition, ILogger logger) : AbstractDelayedTaskExecutor(TimeSpan.FromSeconds(5L)) { private readonly Queue<(uint Type, nint Key)> _keysToPress = new Queue<(uint, nint)>(); private int _attempts; protected override bool StartInternal() { if (condition[ConditionFlag.Diving]) { return false; } if (condition[ConditionFlag.Mounted] || condition[ConditionFlag.Swimming]) { Descend(); return true; } throw new TaskException("You aren't swimming, so we can't dive."); } public unsafe override ETaskResult Update() { if (_keysToPress.TryDequeue(out (uint, nint) result)) { if (result.Item1 == 0) { return ETaskResult.StillRunning; } logger.LogDebug("{Action} key {KeyCode:X2}", (result.Item1 == 256) ? "Pressing" : "Releasing", result.Item2); NativeMethods.SendMessage((nint)Device.Instance()->hWnd, result.Item1, result.Item2, IntPtr.Zero); return ETaskResult.StillRunning; } return base.Update(); } public override bool ShouldInterruptOnDamage() { return false; } protected override ETaskResult UpdateInternal() { if (condition[ConditionFlag.Diving]) { return ETaskResult.TaskComplete; } if (_attempts >= 3) { throw new TaskException("Please dive manually."); } Descend(); _attempts++; return ETaskResult.StillRunning; } private unsafe void Descend() { UIInputData.Keybind keybind = default(UIInputData.Keybind); Utf8String* name = Utf8String.FromString("MOVE_DESCENT"); UIInputData.Instance()->GetKeybindByName(name, (Keybind*)(&keybind)); logger.LogInformation("Dive keybind: {Key1} + {Modifier1}, {Key2} + {Modifier2}", keybind.Key, keybind.Modifier, keybind.AltKey, keybind.AltModifier); int num = 2; List> list = new List>(num); CollectionsMarshal.SetCount(list, num); Span> span = CollectionsMarshal.AsSpan(list); int num2 = 0; span[num2] = GetKeysToPress(keybind.Key, keybind.Modifier); num2++; span[num2] = GetKeysToPress(keybind.AltKey, keybind.AltModifier); List list2 = (from x in list where x != null select (x)).MinBy((List x) => x.Count); if (list2 == null || list2.Count == 0) { throw new TaskException("No useable keybind found for diving"); } foreach (nint item in list2) { _keysToPress.Enqueue((256u, item)); _keysToPress.Enqueue((0u, 0)); _keysToPress.Enqueue((0u, 0)); } for (int num3 = 0; num3 < 5; num3++) { _keysToPress.Enqueue((0u, 0)); } list2.Reverse(); foreach (nint item2 in list2) { _keysToPress.Enqueue((257u, item2)); } } } private static class NativeMethods { public const uint WM_KEYUP = 257u; public const uint WM_KEYDOWN = 256u; [DllImport("user32.dll", CharSet = CharSet.Auto)] [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] public static extern nint SendMessage(nint hWnd, uint Msg, nint wParam, nint lParam); } private static List? GetKeysToPress(SeVirtualKey key, ModifierFlag modifier) { List list = new List(); if (modifier.HasFlag(ModifierFlag.Ctrl)) { list.Add(17); } if (modifier.HasFlag(ModifierFlag.Shift)) { list.Add(16); } if (modifier.HasFlag(ModifierFlag.Alt)) { list.Add(18); } nint num = (nint)key; if (num == 0) { return null; } list.Add(num); return list; } }