diff --git a/Craftimizer/Configuration.cs b/Craftimizer/Configuration.cs index 24bb980..aacf279 100644 --- a/Craftimizer/Configuration.cs +++ b/Craftimizer/Configuration.cs @@ -181,8 +181,9 @@ public partial class Configuration public bool SuggestMacroAutomatically { get; set; } public bool ShowCommunityMacros { get; set; } = true; public bool SearchCommunityMacroAutomatically { get; set; } - public int SynthHelperStepCount { get; set; } = 5; + public int SynthHelperStepCount { get; set; } = 3; public bool SynthHelperDisplayOnlyFirstStep { get; set; } + public bool SynthHelperAbilityAnts { get; set; } public bool PinSynthHelperToWindow { get; set; } = true; public bool PinRecipeNoteToWindow { get; set; } = true; diff --git a/Craftimizer/Plugin.cs b/Craftimizer/Plugin.cs index e4cc773..f627468 100644 --- a/Craftimizer/Plugin.cs +++ b/Craftimizer/Plugin.cs @@ -113,7 +113,7 @@ public sealed class Plugin : IDalamudPlugin [Command(name: "/craftaction", description: "Execute the suggested action in the synthesis helper. Can also be run inside a macro. This command is useful for controller players.")] public void ExecuteSuggestedSynthHelperAction() => - SynthHelperWindow.QueueSuggestedActionExecution(); + SynthHelperWindow.ExecuteNextAction(); [Command(name: "/craftimizer", description: "Open the settings window.")] private void OpenSettingsWindowForced() => diff --git a/Craftimizer/Utils/Hooks.cs b/Craftimizer/Utils/Hooks.cs index c177d51..bd42bca 100644 --- a/Craftimizer/Utils/Hooks.cs +++ b/Craftimizer/Utils/Hooks.cs @@ -18,17 +18,24 @@ public sealed unsafe class Hooks : IDisposable public readonly Hook UseActionHook = null!; + public delegate byte IsActionHighlightedDelegate(ActionManager* manager, CSActionType actionType, uint actionId); + + public readonly Hook IsActionHighlightedHook = null!; + public Hooks() { UseActionHook = Service.GameInteropProvider.HookFromAddress((nint)ActionManager.MemberFunctionPointers.UseAction, UseActionDetour); + IsActionHighlightedHook = Service.GameInteropProvider.HookFromAddress((nint)ActionManager.MemberFunctionPointers.IsActionHighlighted, IsActionHighlightedDetour); + UseActionHook.Enable(); + IsActionHighlightedHook.Enable(); } private bool UseActionDetour(ActionManager* manager, CSActionType actionType, uint actionId, ulong targetId, uint param, uint useType, int pvp, nint a8) { var canCast = manager->GetActionStatus(actionType, actionId) == 0; var ret = UseActionHook.Original(manager, actionType, actionId, targetId, param, useType, pvp, a8); - if (canCast && ret && (actionType == CSActionType.CraftAction || actionType == CSActionType.Action)) + if (canCast && ret && actionType is CSActionType.CraftAction or CSActionType.Action) { var classJob = ClassJobUtils.GetClassJobFromIdx((byte)(Service.ClientState.LocalPlayer?.ClassJob.Id ?? 0)); if (classJob != null) @@ -41,8 +48,40 @@ public sealed unsafe class Hooks : IDisposable return ret; } + private byte IsActionHighlightedDetour(ActionManager* manager, CSActionType actionType, uint actionId) + { + var ret = IsActionHighlightedHook.Original(manager, actionType, actionId); + + if (!Service.Configuration.SynthHelperAbilityAnts) + return ret; + + if (!Service.Plugin.SynthHelperWindow.ShouldDrawAnts) + return ret; + + if (actionType is not (CSActionType.CraftAction or CSActionType.Action)) + return ret; + + var jobId = Service.ClientState.LocalPlayer?.ClassJob.Id; + if (jobId == null) + return ret; + + var classJob = ClassJobUtils.GetClassJobFromIdx((byte)jobId.Value); + if (classJob == null) + return ret; + + var simActionType = ActionUtils.GetActionTypeFromId(actionId, classJob.Value, actionType == CSActionType.CraftAction); + if (simActionType == null) + return ret; + + if (Service.Plugin.SynthHelperWindow.NextAction != simActionType) + return 0; + + return 1; + } + public void Dispose() { UseActionHook.Dispose(); + IsActionHighlightedHook.Dispose(); } } diff --git a/Craftimizer/Windows/Settings.cs b/Craftimizer/Windows/Settings.cs index 8adfe58..b8a1ac0 100644 --- a/Craftimizer/Windows/Settings.cs +++ b/Craftimizer/Windows/Settings.cs @@ -885,6 +885,16 @@ public sealed class Settings : Window, IDisposable ref isDirty ); + DrawOption( + "Draw Ability Ants", + "Turns your hotbar into a whack-a-mole game! Draws ants for " + + "the next action that should be executed. Also disables ants " + + "for things like combo actions and condition procs.", + Config.SynthHelperAbilityAnts, + v => Config.SynthHelperAbilityAnts = v, + ref isDirty + ); + DrawOption( "Step Count", "The minimum number of future steps to solve for during an in-game craft.", diff --git a/Craftimizer/Windows/SynthHelper.cs b/Craftimizer/Windows/SynthHelper.cs index af261ae..06fb0b9 100644 --- a/Craftimizer/Windows/SynthHelper.cs +++ b/Craftimizer/Windows/SynthHelper.cs @@ -46,6 +46,8 @@ public sealed unsafe class SynthHelper : Window, IDisposable public RecipeData? RecipeData { get; private set; } public CharacterStats? CharacterStats { get; private set; } public SimulationInput? SimulationInput { get; private set; } + public ActionType? NextAction => (ShouldOpen && Macro.Count > 0) ? Macro[0].Action : null; + public bool ShouldDrawAnts => ShouldOpen; public bool IsCrafting { get; private set; } private int CurrentActionCount { get; set; } @@ -65,8 +67,6 @@ public sealed unsafe class SynthHelper : Window, IDisposable private SimulationState currentState; private SimulatedMacro Macro { get; } = new(); - private bool IsSuggestedActionExecutionQueued { get; set; } - private CancellationTokenSource? HelperTaskTokenSource { get; set; } private Exception? HelperTaskException { get; set; } private Solver.Solver? HelperTaskObject { get; set; } @@ -133,9 +133,6 @@ public sealed unsafe class SynthHelper : Window, IDisposable } } - if (!ShouldCalculate) - IsSuggestedActionExecutionQueued = false; - if (!ShouldOpen) { StyleAlpha = LastAlpha = null; @@ -159,10 +156,24 @@ public sealed unsafe class SynthHelper : Window, IDisposable if (!Service.Configuration.EnableSynthHelper) return false; - if (Service.Configuration.DisableSynthHelperOnMacro && - RaptureShellModule.Instance()->MacroCurrentLine >= 0 && - !IsSuggestedActionExecutionQueued) - return false; + if (Service.Configuration.DisableSynthHelperOnMacro) + { + var module = RaptureShellModule.Instance(); + if (module->MacroCurrentLine >= 0) + { + var hasCraftAction = false; + foreach (ref var line in module->MacroLines) + { + if (line.EqualToString("/craftaction")) + { + hasCraftAction = true; + break; + } + } + if (!hasCraftAction) + return false; + } + } Addon = (AddonSynthesis*)Service.GameGui.GetAddonByName("Synthesis"); @@ -239,8 +250,6 @@ public sealed unsafe class SynthHelper : Window, IDisposable ImGui.PopStyleVar(); base.PostDraw(); - - IsSuggestedActionExecutionQueued = false; } public override void Draw() @@ -311,19 +320,16 @@ public sealed unsafe class SynthHelper : Window, IDisposable isPressed = ImGuiExtras.ButtonBehavior(bb, id, out isHovered, out isHeld, ImGuiButtonFlags.None); } ImGui.ImageButton(action.GetIcon(RecipeData!.ClassJob).ImGuiHandle, new(imageSize), default, Vector2.One, 0, default, failedAction ? new(1, 1, 1, ImGui.GetStyle().DisabledAlpha) : Vector4.One); - if (isPressed || IsSuggestedActionExecutionQueued) + if (isPressed && i == 0) { - if (canExecute && i == 0) - { - Service.Chat.SendMessage($"/ac \"{action.GetName(RecipeData.ClassJob)}\""); + if (ExecuteNextAction()) break; - } } if (isHovered) { ImGuiUtils.Tooltip($"{action.GetName(RecipeData!.ClassJob)}\n" + $"{actionBase.GetTooltip(CreateSim(lastState), true)}" + - $"{(canExecute && i == 0 ? "Click or run /craftaction to Execute" : string.Empty)}"); + $"{(canExecute && i == 0 ? "Click or run /craftaction to execute" : string.Empty)}"); hoveredState = state; } lastState = state; @@ -475,6 +481,18 @@ public sealed unsafe class SynthHelper : Window, IDisposable Service.Plugin.OpenMacroEditor(CharacterStats!, RecipeData!, new(Service.ClientState.LocalPlayer!.StatusList), [], null); } + public bool ExecuteNextAction() + { + var canExecute = !Service.Condition[ConditionFlag.Crafting40]; + var action = NextAction; + if (canExecute && action != null) + { + Service.Chat.SendMessage($"/ac \"{action.Value.GetName(RecipeData!.ClassJob)}\""); + return true; + } + return false; + } + private void OnStartCrafting(ushort recipeId) { var shouldUpdateInput = false; @@ -652,11 +670,6 @@ public sealed unsafe class SynthHelper : Window, IDisposable HelperTaskTokenSource?.Cancel(); } - public void QueueSuggestedActionExecution() - { - IsSuggestedActionExecutionQueued = true; - } - private static Sim CreateSim(in SimulationState state) => Service.Configuration.ConditionRandomness ? new Sim() { State = state } : new SimNoRandom() { State = state };