diff --git a/src/main/java/autismclient/util/macro/MacroActionRunner.java b/src/main/java/autismclient/util/macro/MacroActionRunner.java new file mode 100644 index 0000000..6cc67bf --- /dev/null +++ b/src/main/java/autismclient/util/macro/MacroActionRunner.java @@ -0,0 +1,521 @@ +package autismclient.util.macro; + +import autismclient.modules.PackHideState; +import autismclient.util.AutismClientMessaging; +import autismclient.util.AutismGuiActions; +import autismclient.util.AutismInventoryClickHelper; +import autismclient.util.AutismSharedState; +import net.minecraft.client.Minecraft; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; +import java.util.List; +import java.util.Locale; + +final class MacroActionRunner { + private MacroActionRunner() { + } + public static void beginRaycastAim(Minecraft mc, MacroAction action) { + if (!(action instanceof RaycastAim aim) || !aim.isRaycast()) return; + autismclient.util.AutismRotationUtil.Rotation rotation; + try { + rotation = MacroExecutor.callOnClientThread(mc, () -> MacroRaycastAim.aimFor(mc, aim.raycastTarget(mc)), null); + } catch (InterruptedException interrupted) { + Thread.currentThread().interrupt(); + return; + } + if (rotation == null) return; + MacroRaycastAim.hold(rotation); + MacroExecutor.waitOneTick(); + } + + static boolean dynamicTextInputsAvailable(MacroAction action, Minecraft mc) { + if (action instanceof WaitForChatAction chat && chat.effectiveMatchMode() == MacroCapturePattern.Mode.MATCH) { + return MacroVariables.resolve(chat.pattern, mc).success(); + } + if (action instanceof WaitForGuiAction gui && gui.matchMode == MacroCapturePattern.Mode.MATCH) { + return MacroVariables.resolve(gui.guiTitle, mc).success(); + } + if (action instanceof WaitGuiTypeAction gui && gui.matchMode == MacroCapturePattern.Mode.MATCH) { + return MacroVariables.resolve(gui.title, mc).success(); + } + if (action instanceof WaitInventoryPredicateAction inventory) { + return MacroVariables.resolve(inventory.itemName, mc).success(); + } + if (action instanceof WaitDurabilityAction durability && durability.targetMode == WaitDurabilityAction.TargetMode.ITEM) { + return MacroVariables.resolve(durability.itemName, mc).success(); + } + if (action instanceof WaitEntityTargetAction entity) { + return MacroVariables.resolve(entity.entityId, mc).success(); + } + return true; + } + + public static java.util.List> preGeneratePacketsForRepeat( + int count, Minecraft mc) { + MacroExecutor.RepeatPacketContext context = MacroExecutor.REPEAT_PACKET_CONTEXT.get(); + if (context == null || context.macro() == null) return new java.util.ArrayList<>(); + return MacroExecutor.preGeneratePackets(context.macro(), context.startIdx(), count, context.endIdx()); + } + + public static boolean isTargetWaitInteractionAction(MacroAction action) { + return action instanceof OpenContainerAction || action instanceof InteractEntityAction; + } + + public static CompletableFuture runTargetWaitInteractionAction(Minecraft mc, MacroAction action) throws InterruptedException { + boolean waitForTarget = switch (action) { + case OpenContainerAction open -> open.waitForTarget; + case InteractEntityAction interact -> interact.waitForTarget; + default -> false; + }; + + if (!waitForTarget) { + CompletableFuture postGuiFuture = createPostGuiFuture(mc, action); + runTargetInteractionAttempt(mc, action); + return postGuiFuture; + } + + MacroExecutor.setCurrentStatus("Waiting target: " + action.getDisplayName()); + while (MacroExecutor.isCurrentRunActive()) { + if (!canRunTargetInteractionNow(mc, action)) { + java.util.concurrent.locks.LockSupport.parkNanos(MacroExecutor.MACRO_WAIT_POLL_NANOS); + if (Thread.interrupted()) throw new InterruptedException(); + continue; + } + + CompletableFuture postGuiFuture = createPostGuiFuture(mc, action); + if (runTargetInteractionAttempt(mc, action)) { + return postGuiFuture; + } + if (postGuiFuture != null) postGuiFuture.cancel(true); + java.util.concurrent.locks.LockSupport.parkNanos(MacroExecutor.MACRO_WAIT_POLL_NANOS); + if (Thread.interrupted()) throw new InterruptedException(); + } + return null; + } + + private static boolean canRunTargetInteractionNow(Minecraft mc, MacroAction action) throws InterruptedException { + return MacroExecutor.callOnClientThread(mc, () -> { + if (action instanceof OpenContainerAction open) return open.canExecuteNow(mc); + if (action instanceof InteractEntityAction interact) return interact.canExecuteNow(mc); + return true; + }, Boolean.FALSE); + } + + private static boolean runTargetInteractionAttempt(Minecraft mc, MacroAction action) throws InterruptedException { + return MacroExecutor.callOnClientThread(mc, () -> { + try { + if (action instanceof OpenContainerAction open) return open.tryExecute(mc); + if (action instanceof InteractEntityAction interact) return interact.tryExecute(mc); + action.execute(mc); + return true; + } catch (Exception e) { + AutismClientMessaging.sendPrefixed("\u00a7cError in " + action.getDisplayName() + ": " + e.getMessage()); + return true; + } + }, Boolean.FALSE, usesTickAlignedDispatch(action)); + } + + public static CompletableFuture createPostGuiFuture(Minecraft mc, MacroAction action) { + if (Boolean.TRUE.equals(MacroExecutor.SUPPRESS_POST_GUI_AFTER.get())) return null; + if (!(action instanceof WaitsForGui wfg) || !wfg.isWaitForGuiAfter()) return null; + return createWaitsForGuiFuture(mc, wfg); + } + + private static CompletableFuture createWaitsForGuiFuture(Minecraft mc, WaitsForGui wfg) { + MacroTemplate.Resolution guiName = MacroVariables.resolve(wfg.getWaitGuiName(), mc); + if (!guiName.success()) return CompletableFuture.completedFuture(null); + return wfg.isWaitForGuiChange() + ? MacroConditionRegistry.waitForGuiChange(mc.gui.screen()) + : MacroConditionRegistry.waitForGui(guiName.value()); + } + + public static boolean waitForGuiBeforeAction(Minecraft mc, MacroAction action) throws InterruptedException { + if (!(action instanceof WaitsForGui wfg)) return true; + return waitForGuiBeforeAction(mc, wfg); + } + + public static boolean waitForGuiBeforeAction(Minecraft mc, WaitsForGui action) throws InterruptedException { + if (mc == null || action == null || !action.isWaitForGuiBefore()) return true; + MacroTemplate.Resolution guiName = MacroVariables.resolve(action.getWaitGuiName(), mc); + if (!guiName.success()) return false; + if (isWaitGuiPresent(mc, guiName.value())) { + waitForCurrentGuiReady(mc); + return true; + } + CompletableFuture future = MacroConditionRegistry.waitForGui(guiName.value()); + MacroExecutor.waitForCondition(future); + waitForCurrentGuiReady(mc); + return true; + } + + private static boolean isWaitGuiPresent(Minecraft mc, String guiName) { + if (mc == null || mc.gui.screen() == null) return false; + if (MacroGuiMatcher.isOwnScreen(mc.gui.screen())) return false; + String expected = guiName == null ? "" : guiName.trim(); + if (expected.isEmpty()) return true; + String actual = mc.gui.screen().getTitle() == null ? "" : mc.gui.screen().getTitle().getString(); + return matchesWaitGuiTitle(expected, actual); + } + + private static boolean matchesWaitGuiTitle(String expected, String actual) { + if (expected == null || expected.isBlank() || actual == null || actual.isBlank()) return false; + String expectedLower = expected.toLowerCase(Locale.ROOT); + String actualLower = actual.toLowerCase(Locale.ROOT); + if (expectedLower.equals(actualLower) || actualLower.contains(expectedLower)) return true; + for (String word : expectedLower.split("\\s+")) { + if (!word.isBlank() && !actualLower.contains(word)) return false; + } + return true; + } + + private static void waitForCurrentGuiReady(Minecraft mc) throws InterruptedException { + + long deadline = System.nanoTime() + 500_000_000L; + while (MacroExecutor.isCurrentRunActive()) { + Boolean ready = MacroExecutor.callOnClientThread(mc, () -> { + if (mc.player == null || mc.gui.screen() == null) return false; + if (mc.player.containerMenu != null && mc.player.containerMenu != mc.player.inventoryMenu) { + return mc.player.containerMenu.getStateId() > 0; + } + return true; + }, Boolean.FALSE); + if (Boolean.TRUE.equals(ready)) return; + if (System.nanoTime() >= deadline) return; + java.util.concurrent.locks.LockSupport.parkNanos(MacroExecutor.MACRO_WAIT_POLL_NANOS); + if (Thread.interrupted()) return; + } + } + + public static void runMacroActionOnClientThread(Minecraft mc, MacroAction action, String label) throws InterruptedException { + Runnable body = () -> { + try { + action.execute(mc); + } catch (Throwable t) { + recordAddonActionError(action, "Action threw " + t.getClass().getSimpleName()); + AutismClientMessaging.sendPrefixed("\u00a7cError in " + label + ": " + + (t.getMessage() == null ? t.getClass().getSimpleName() : t.getMessage())); + } + }; + if (usesTickAlignedDispatch(action)) MacroExecutor.runOnClientTick(mc, body); + else MacroExecutor.runOnClientThread(mc, body); + } + + public static boolean usesTickAlignedDispatch(MacroAction action) { + return action instanceof PacketOrdered ordered && ordered.isTickAligned(); + } + + public static void runItemActionPaced(Minecraft mc, ItemAction ia) throws InterruptedException { + MacroExecutor.setCurrentStatus(ia.getDisplayName()); + java.util.List plan = MacroExecutor.callOnClientThread(mc, () -> ia.buildClickPlan(mc), java.util.Collections.emptyList()); + if (plan == null || plan.isEmpty()) return; + long delayMs = Math.max(0, ia.clickDelayMs); + MacroExecutor.RunState scheduledRun = MacroExecutor.CURRENT_RUN.get(); + boolean first = true; + for (Runnable op : plan) { + if (!MacroExecutor.isCurrentRunActive()) break; + if (!first && delayMs > 0) Thread.sleep(delayMs); + first = false; + mc.execute(() -> MacroExecutor.withRunContext(scheduledRun, () -> { + if (PackHideState.isHardLocked()) return; + try { + op.run(); + } catch (Throwable t) { + AutismClientMessaging.sendPrefixed("\u00a7cError in ItemAction: " + + (t.getMessage() == null ? t.getClass().getSimpleName() : t.getMessage())); + } + })); + } + } + + public static void runContextMacroAction(autismclient.api.macro.ContextMacroAction action) { + if (PackHideState.isHardLocked()) return; + if (action == null) return; + try { + action.run(new ApiExecutionContext(MacroExecutor.CURRENT_RUN.get())); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (Throwable t) { + recordAddonActionError(action, "Context action threw " + t.getClass().getSimpleName()); + AutismClientMessaging.sendPrefixed("\u00a7cAddon action error: " + + (t.getMessage() == null ? t.getClass().getSimpleName() : t.getMessage())); + } + } + + private static void recordAddonActionError(MacroAction action, String detail) { + if (action == null) return; + String owner = autismclient.api.macro.MacroActionRegistry.ownerOf(action.getTypeId()); + if (owner != null && !owner.isBlank()) autismclient.addons.AddonManager.recordRuntimeError(owner, detail); + } + + private static final class ApiExecutionContext implements autismclient.api.macro.MacroExecutionContext { + private final MacroExecutor.RunState run; + + private ApiExecutionContext(MacroExecutor.RunState run) { + this.run = run; + } + + @Override public Minecraft mc() { return Minecraft.getInstance(); } + + @Override public void runOnClientThread(Runnable r) { + if (r == null) return; + + Runnable safe = () -> MacroExecutor.withRunContext(run, () -> { + try { r.run(); } + catch (Throwable t) { AutismClientMessaging.sendPrefixed("\u00a7cAddon action (client task) error: " + (t.getMessage() == null ? t.getClass().getSimpleName() : t.getMessage())); } + }); + try { MacroExecutor.runOnClientThread(Minecraft.getInstance(), safe); } + catch (InterruptedException e) { Thread.currentThread().interrupt(); } + } + + @Override public T callOnClientThread(java.util.function.Supplier s) { + if (s == null) return null; + java.util.function.Supplier safe = () -> { + AtomicReference value = new AtomicReference<>(); + MacroExecutor.withRunContext(run, () -> { + try { value.set(s.get()); } + catch (Throwable t) { AutismClientMessaging.sendPrefixed("\u00a7cAddon action (client task) error: " + (t.getMessage() == null ? t.getClass().getSimpleName() : t.getMessage())); } + }); + return value.get(); + }; + try { return MacroExecutor.callOnClientThread(Minecraft.getInstance(), safe, null); } + catch (InterruptedException e) { Thread.currentThread().interrupt(); return null; } + } + + @Override public void waitTicks(int ticks) { + MacroExecutor.withRunContext(run, () -> { + for (int i = 0; i < ticks && MacroExecutor.isCurrentRunActive(); i++) { + MacroExecutor.waitForCondition(MacroConditionRegistry.waitForNextTick()); + } + }); + } + + @Override public void awaitCondition(CompletableFuture future) { + if (future != null) MacroExecutor.withRunContext(run, () -> MacroExecutor.waitForCondition(future)); + } + + @Override public void awaitCondition(autismclient.api.macro.AddonCondition condition) { + if (condition != null) MacroExecutor.withRunContext(run, () -> MacroExecutor.waitForCondition(MacroConditionRegistry.await(condition))); + } + + @Override public boolean isActive() { return run != null && run.running.get() && !PackHideState.isHardLocked(); } + + @Override public void setStatus(String status) { MacroExecutor.withRunContext(run, () -> MacroExecutor.setCurrentStatus(status)); } + + @Override public void sendPacket(net.minecraft.network.protocol.Packet packet) { + if (packet == null) return; + runOnClientThread(() -> { + Minecraft mc = Minecraft.getInstance(); + if (mc.getConnection() != null) mc.getConnection().send(packet); + }); + } + + @Override public java.util.Optional variable(String name) { + return run == null ? java.util.Optional.empty() : run.variables.get(name); + } + + @Override public void setVariable(String name, MacroValue value) { + if (run != null) run.variables.set(name, value); + } + + @Override public void removeVariable(String name) { + if (run != null) run.variables.remove(name); + } + + @Override public java.util.Map variables() { + return run == null ? java.util.Map.of() : run.variables.snapshot(); + } + + @Override public MacroTemplate.Resolution resolveTemplate(String template) { + return MacroTemplate.resolve(template, run == null ? null : run.variables, Minecraft.getInstance()); + } + } + + public static void runUseItemPhaseAction(Minecraft mc, UseItemPhaseAction action) throws InterruptedException { + int times = action.repeatTimes(); + for (int i = 0; i < times && MacroExecutor.isCurrentRunActive(); i++) { + + MacroExecutor.runOnClientThread(mc, () -> { + try { action.sendUsePacket(mc); } + catch (Exception e) { AutismClientMessaging.sendPrefixed("\u00a7cError in Use phase: " + e.getMessage()); } + }); + + final boolean[] gateInstalled = { false }; + if (action.shouldGate()) { + MacroExecutor.runOnClientThread(mc, () -> { + try { gateInstalled[0] = action.installGate(mc); } + catch (Exception e) { AutismClientMessaging.sendPrefixed("\u00a7cError in Use gate: " + e.getMessage()); } + }); + } + try { + + for (int t = 0; t < action.holdTicks && MacroExecutor.isCurrentRunActive(); t++) { + CompletableFuture tf = MacroConditionRegistry.waitForNextTick(); + MacroExecutor.waitForCondition(tf); + } + } finally { + if (gateInstalled[0]) { + final boolean wasOnThread = mc.isSameThread(); + if (wasOnThread) { + action.removeGate(); + } else { + MacroExecutor.runOnClientThread(mc, action::removeGate); + } + } + } + if (!MacroExecutor.isCurrentRunActive()) break; + + MacroExecutor.runOnClientThread(mc, () -> { + try { action.finishRelease(mc); } + catch (Exception e) { AutismClientMessaging.sendPrefixed("\u00a7cError in Use release: " + e.getMessage()); } + }); + } + } + + public static void runClickActionBurst(Minecraft mc, ClickAction action) throws InterruptedException { + int times = Math.max(1, action.clickCount); + + MacroExecutor.runOnClientThread(mc, () -> { + for (int c = 0; c < times && MacroExecutor.isCurrentRunActive(); c++) { + try { + action.execute(mc); + } catch (Exception e) { + AutismClientMessaging.sendPrefixed("\u00a7cError in Click: " + e.getMessage()); + break; + } + } + }, action.isTickAligned()); + } + + public static boolean isInstantInventoryAction(MacroAction action) { + if (action instanceof ItemAction + || action instanceof DropAction + || action instanceof SwapSlotsAction + || action instanceof ClickAction + || action instanceof XCarryAction) { + return true; + } + return action instanceof StoreItemAction storeItemAction && !storeItemAction.persistent; + } + + public static void runStoreItemAction(Minecraft mc, StoreItemAction action) throws InterruptedException { + if (action == null) return; + int delayTicks = StoreItemAction.clampDelayTicks(action.delayTicks); + if (delayTicks <= 0) { + MacroExecutor.runOnClientThread(mc, () -> action.doTransfer(mc)); + return; + } + + List slots = MacroExecutor.callOnClientThread(mc, () -> action.collectTransferSlots(mc), List.of()); + for (int i = 0; i < slots.size() && MacroExecutor.isCurrentRunActive(); i++) { + AutismInventoryClickHelper.click(mc, slots.get(i), 0, net.minecraft.world.inventory.ContainerInput.QUICK_MOVE); + if (i + 1 < slots.size()) waitTicks(delayTicks); + } + + if (action.closeAfter && !action.persistent) { + MacroExecutor.runOnClientThread(mc, () -> AutismGuiActions.closeCurrentScreen(mc, action.closeSendPkt, false)); + } + } + + private static void waitTicks(int ticks) { + for (int i = 0; i < ticks && MacroExecutor.isCurrentRunActive(); i++) { + CompletableFuture tickFuture = MacroConditionRegistry.waitForNextTick(); + MacroExecutor.waitForCondition(tickFuture); + } + } + + public static void runUseItemAction(Minecraft mc, UseItemAction action) throws InterruptedException { + if (action == null) return; + + boolean sneaking = action.sneak; + try { + if (action.useMode == UseItemAction.UseMode.CUSTOM_HOLD) { + MacroExecutor.runOnClientThread(mc, () -> { + try { + if (sneaking) autismclient.util.AutismMacroSneak.forceSneak(mc, true); + action.execute(mc); + } + catch (Exception e) { AutismClientMessaging.sendPrefixed("\u00a7cError in UseItem: " + e.getMessage()); } + }, action.isTickAligned()); + if (action.holdTicks > 0) { + waitTicks(action.holdTicks); + if (!MacroExecutor.isCurrentRunActive()) return; + MacroExecutor.runOnClientThread(mc, () -> { + try { action.sendRelease(mc); } + catch (Exception e) { AutismClientMessaging.sendPrefixed("\u00a7cError in UseItem release: " + e.getMessage()); } + }, action.isTickAligned()); + if (action.waitForFinish) waitForUseItemFinish(mc, action.holdTicks + 8); + } + return; + } + + int times = Math.max(1, action.useCount); + for (int i = 0; i < times && MacroExecutor.isCurrentRunActive(); i++) { + MacroExecutor.runOnClientThread(mc, () -> { + try { + if (sneaking) autismclient.util.AutismMacroSneak.forceSneak(mc, true); + action.execute(mc); + } + catch (Exception e) { AutismClientMessaging.sendPrefixed("\u00a7cError in UseItem: " + e.getMessage()); } + }, action.isTickAligned()); + if (action.waitForFinish) waitForUseItemFinish(mc, 0); + if (i + 1 < times && MacroExecutor.isCurrentRunActive()) MacroExecutor.waitOneTick(); + } + } finally { + if (sneaking) mc.execute(() -> autismclient.util.AutismMacroSneak.forceSneak(mc, false)); + } + } + + public static void runContainerClickSequenceAction(Minecraft mc, ContainerClickSequenceAction action) throws InterruptedException { + if (action == null) return; + List slots = action.resolvedSlots(); + int repeats = Math.max(1, action.repeatCount); + int delay = Math.max(0, action.delayTicks); + int total = repeats * slots.size(); + int sent = 0; + for (int repeat = 0; repeat < repeats && MacroExecutor.isCurrentRunActive(); repeat++) { + for (int slot : slots) { + if (!MacroExecutor.isCurrentRunActive()) return; + MacroExecutor.runOnClientThread(mc, () -> action.executeClick(mc, slot)); + sent++; + if (delay > 0 && sent < total) waitTicks(delay); + } + } + } + + public static void runPacketBurstAction(Minecraft mc, PacketBurstAction action) { + if (action == null || mc == null || mc.getConnection() == null) return; + if (action.flushBefore) AutismSharedState.get().flushDelayedPackets(mc.getConnection()); + int total = action.effectiveCount(); + int delay = Math.max(0, action.delayTicks); + for (int i = 0; i < total && MacroExecutor.isCurrentRunActive(); i++) { + action.sendOne(mc); + if (delay > 0 && i + 1 < total) waitTicks(delay); + } + } + + private static void waitForUseItemFinish(Minecraft mc, int fallbackTicks) throws InterruptedException { + if (mc == null || !MacroExecutor.isCurrentRunActive()) return; + + if (!Boolean.TRUE.equals(MacroExecutor.callOnClientThread(mc, + () -> mc.player != null && mc.player.isUsingItem(), + Boolean.FALSE))) { + return; + } + + int remaining = MacroExecutor.callOnClientThread(mc, + () -> mc.player == null ? 0 : Math.max(0, mc.player.getUseItemRemainingTicks()), + 0); + int maxTicks = Math.max(8, remaining > 0 ? remaining + 8 : fallbackTicks + 8); + maxTicks = Math.min(maxTicks, 72000); + for (int i = 0; i < maxTicks && MacroExecutor.isCurrentRunActive(); i++) { + if (!Boolean.TRUE.equals(MacroExecutor.callOnClientThread(mc, + () -> mc.player != null && mc.player.isUsingItem(), + Boolean.FALSE))) { + return; + } + CompletableFuture tickFuture = MacroConditionRegistry.waitForNextTick(); + MacroExecutor.waitForCondition(tickFuture); + } + } +} diff --git a/src/main/java/autismclient/util/macro/MacroExecutor.java b/src/main/java/autismclient/util/macro/MacroExecutor.java index 4bcc7d2..3437889 100644 --- a/src/main/java/autismclient/util/macro/MacroExecutor.java +++ b/src/main/java/autismclient/util/macro/MacroExecutor.java @@ -98,7 +98,7 @@ public class MacroExecutor { private static final java.util.concurrent.ConcurrentHashMap RUNS_BY_ID = new java.util.concurrent.ConcurrentHashMap<>(); private static final AtomicLong RUN_STATE_REVISION = new AtomicLong(); static final ThreadLocal CURRENT_RUN = new ThreadLocal<>(); - private static final ThreadLocal SUPPRESS_POST_GUI_AFTER = ThreadLocal.withInitial(() -> false); + static final ThreadLocal SUPPRESS_POST_GUI_AFTER = ThreadLocal.withInitial(() -> false); private static volatile RunState primaryRun = null; private static volatile boolean visibleRunActive; private static AutismMacro currentMacro = null; @@ -147,7 +147,7 @@ public class MacroExecutor { private static final java.util.concurrent.ConcurrentHashMap backgroundMoves = new java.util.concurrent.ConcurrentHashMap<>(); - private static final ThreadLocal REPEAT_PACKET_CONTEXT = new ThreadLocal<>(); + static final ThreadLocal REPEAT_PACKET_CONTEXT = new ThreadLocal<>(); private static final ThreadLocal REPEAT_PACKET_SKIP = ThreadLocal.withInitial(() -> 0); static final class RunState { @@ -237,7 +237,7 @@ public class MacroExecutor { } record ChatCapture(String sender, String message, String displayText, Component displayComponent, ChatSource source) {} - private record RepeatPacketContext(AutismMacro macro, int startIdx, int endIdx) {} + record RepeatPacketContext(AutismMacro macro, int startIdx, int endIdx) {} private record InlineNextActions(java.util.List actions, int lastIndex) { static InlineNextActions empty() { return new InlineNextActions(java.util.List.of(), -1); @@ -985,7 +985,7 @@ public class MacroExecutor { } if (!MacroDynamicBindings.apply(action, mc)) continue; - if (!dynamicTextInputsAvailable(action, mc)) continue; + if (!MacroActionRunner.dynamicTextInputsAvailable(action, mc)) continue; if (!MacroPrearm.hasPrearmedWait(action)) MacroPrearm.clearOwnCaptureOutput(action); if (action instanceof BranchAction branch) { @@ -1044,10 +1044,10 @@ public class MacroExecutor { EnumSet heldResources = acquireClientResources(action); try { - if (!waitForGuiBeforeAction(mc, action)) continue; - beginRaycastAim(mc, action); + if (!MacroActionRunner.waitForGuiBeforeAction(mc, action)) continue; + MacroActionRunner.beginRaycastAim(mc, action); java.util.concurrent.CompletableFuture postGuiFuture = - (action instanceof ItemAction || action instanceof DropAction || isTargetWaitInteractionAction(action)) ? null : createPostGuiFuture(mc, action); + (action instanceof ItemAction || action instanceof DropAction || MacroActionRunner.isTargetWaitInteractionAction(action)) ? null : MacroActionRunner.createPostGuiFuture(mc, action); if (burstMode && action instanceof SendPacketAction) { @@ -1719,7 +1719,7 @@ public class MacroExecutor { } } else if (action instanceof UseItemAction) { - runUseItemAction(mc, (UseItemAction) action); + MacroActionRunner.runUseItemAction(mc, (UseItemAction) action); } else if (action instanceof ItemAction) { ItemAction ia = (ItemAction) action; @@ -1728,18 +1728,18 @@ public class MacroExecutor { waitForItemActionTargets(mc, ia); } catch (java.util.concurrent.CancellationException e) { } } - postGuiFuture = createPostGuiFuture(mc, ia); + postGuiFuture = MacroActionRunner.createPostGuiFuture(mc, ia); if (ia.useClickDelay && ia.clickDelayMs > 0) { - runItemActionPaced(mc, ia); + MacroActionRunner.runItemActionPaced(mc, ia); } else { - runMacroActionOnClientThread(mc, action, "ItemAction"); + MacroActionRunner.runMacroActionOnClientThread(mc, action, "ItemAction"); } } else if (action instanceof DropAction || action instanceof SwapSlotsAction) { if (action instanceof DropAction dropAction) { - postGuiFuture = createPostGuiFuture(mc, dropAction); + postGuiFuture = MacroActionRunner.createPostGuiFuture(mc, dropAction); } - runMacroActionOnClientThread(mc, action, action.getDisplayName()); + MacroActionRunner.runMacroActionOnClientThread(mc, action, action.getDisplayName()); } else if (action instanceof CraftAction craftAction) { runCraftAction(mc, craftAction); @@ -1770,7 +1770,7 @@ public class MacroExecutor { if (sia.persistent) { while (isCurrentRunActive()) { - try { runStoreItemAction(mc, sia); } + try { MacroActionRunner.runStoreItemAction(mc, sia); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } catch (Exception e) { AutismClientMessaging.sendPrefixed("Store error: " + e.getMessage()); } @@ -1778,14 +1778,14 @@ public class MacroExecutor { waitForCondition(siTick); } } else { - try { runStoreItemAction(mc, sia); } + try { MacroActionRunner.runStoreItemAction(mc, sia); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (Exception e) { AutismClientMessaging.sendPrefixed("Store error: " + e.getMessage()); } } } else if (action instanceof ClickAction) { ClickAction ca = (ClickAction) action; - runClickActionBurst(mc, ca); + MacroActionRunner.runClickActionBurst(mc, ca); } else if (action instanceof PayAction) { runPayAction(mc, (PayAction) action); @@ -1829,10 +1829,10 @@ public class MacroExecutor { } } else if (action instanceof ContainerClickSequenceAction sequence) { - runContainerClickSequenceAction(mc, sequence); + MacroActionRunner.runContainerClickSequenceAction(mc, sequence); } else if (action instanceof PacketBurstAction burst) { - runPacketBurstAction(mc, burst); + MacroActionRunner.runPacketBurstAction(mc, burst); } else if (action instanceof BundleDupeV2Action bundleV2) { setCurrentStatus("Bundle Dupe V2"); @@ -1851,17 +1851,17 @@ public class MacroExecutor { else if (action instanceof PacketGateAction pga) { MacroRace.runPacketGateAction(mc, pga); } - else if (isTargetWaitInteractionAction(action)) { - postGuiFuture = runTargetWaitInteractionAction(mc, action); + else if (MacroActionRunner.isTargetWaitInteractionAction(action)) { + postGuiFuture = MacroActionRunner.runTargetWaitInteractionAction(mc, action); } else if (action instanceof MacroVariablesAction || action instanceof AssertAction) { action.execute(mc); } else if (action instanceof UseItemPhaseAction phaseAction) { - runUseItemPhaseAction(mc, phaseAction); + MacroActionRunner.runUseItemPhaseAction(mc, phaseAction); } else if (action instanceof autismclient.api.macro.ContextMacroAction contextAction) { - runContextMacroAction(contextAction); + MacroActionRunner.runContextMacroAction(contextAction); } else if (action instanceof PlaceAction placeInteract && placeInteract.interact) { @@ -1894,9 +1894,9 @@ public class MacroExecutor { } else { - if (usesTickAlignedDispatch(action)) { + if (MacroActionRunner.usesTickAlignedDispatch(action)) { - runMacroActionOnClientThread(mc, action, action.getDisplayName()); + MacroActionRunner.runMacroActionOnClientThread(mc, action, action.getDisplayName()); } else if (module != null && module.useInstantExecutionMode()) { java.util.concurrent.CountDownLatch latch = new java.util.concurrent.CountDownLatch(1); @@ -1918,7 +1918,7 @@ public class MacroExecutor { Thread.currentThread().interrupt(); } } else { - runMacroActionOnClientThread(mc, action, action.getDisplayName()); + MacroActionRunner.runMacroActionOnClientThread(mc, action, action.getDisplayName()); } } @@ -1935,7 +1935,7 @@ public class MacroExecutor { broadcastStepProgress(runState, i + 1, macro.actions.size(), macro.name); } - if (isCurrentRunActive() && !isInstantInventoryAction(action)) { + if (isCurrentRunActive() && !MacroActionRunner.isInstantInventoryAction(action)) { if (module != null && module.useInstantExecutionMode()) { int delayUs = module.getActionDelayUs(); if (delayUs > 0) { @@ -2122,15 +2122,15 @@ public class MacroExecutor { static void executeSingleActionWithWaits(Minecraft mc, MacroAction action, AutismModule module) throws InterruptedException { if (!MacroDynamicBindings.apply(action, mc)) return; - if (!dynamicTextInputsAvailable(action, mc)) return; + if (!MacroActionRunner.dynamicTextInputsAvailable(action, mc)) return; MacroPrearm.clearOwnCaptureOutput(action); EnumSet heldResources = acquireClientResources(action); try { - if (!waitForGuiBeforeAction(mc, action)) return; - beginRaycastAim(mc, action); + if (!MacroActionRunner.waitForGuiBeforeAction(mc, action)) return; + MacroActionRunner.beginRaycastAim(mc, action); java.util.concurrent.CompletableFuture postGuiFuture = - (action instanceof ItemAction || action instanceof DropAction || isTargetWaitInteractionAction(action)) ? null : createPostGuiFuture(mc, action); + (action instanceof ItemAction || action instanceof DropAction || MacroActionRunner.isTargetWaitInteractionAction(action)) ? null : MacroActionRunner.createPostGuiFuture(mc, action); if (action instanceof DelayAction) { DelayAction da = (DelayAction) action; @@ -2191,8 +2191,8 @@ public class MacroExecutor { MacroPrearm.commitStagedCapture(wgta); } else if (action instanceof PacketGateAction pga) { MacroRace.runPacketGateAction(mc, pga); - } else if (isTargetWaitInteractionAction(action)) { - postGuiFuture = runTargetWaitInteractionAction(mc, action); + } else if (MacroActionRunner.isTargetWaitInteractionAction(action)) { + postGuiFuture = MacroActionRunner.runTargetWaitInteractionAction(mc, action); } else if (action instanceof WaitForHealthAction) { WaitForHealthAction wh = (WaitForHealthAction) action; setCurrentStatus(wh.waitingStatusText()); @@ -2364,7 +2364,7 @@ public class MacroExecutor { } else if (action instanceof PlaceAction) { runPlaceAction(mc, (PlaceAction) action); } else if (action instanceof UseItemAction) { - runUseItemAction(mc, (UseItemAction) action); + MacroActionRunner.runUseItemAction(mc, (UseItemAction) action); } else if (action instanceof JumpAction) { JumpAction ja = (JumpAction) action; mc.execute(() -> ja.execute(mc)); @@ -2428,13 +2428,13 @@ public class MacroExecutor { waitForItemActionTargets(mc, ia); } catch (java.util.concurrent.CancellationException e) { } } - postGuiFuture = createPostGuiFuture(mc, ia); - runMacroActionOnClientThread(mc, action, "ItemAction"); + postGuiFuture = MacroActionRunner.createPostGuiFuture(mc, ia); + MacroActionRunner.runMacroActionOnClientThread(mc, action, "ItemAction"); } else if (action instanceof DropAction || action instanceof SwapSlotsAction) { if (action instanceof DropAction dropAction) { - postGuiFuture = createPostGuiFuture(mc, dropAction); + postGuiFuture = MacroActionRunner.createPostGuiFuture(mc, dropAction); } - runMacroActionOnClientThread(mc, action, action.getDisplayName()); + MacroActionRunner.runMacroActionOnClientThread(mc, action, action.getDisplayName()); } else if (action instanceof SendPacketAction) { if (module != null && module.useInstantExecutionMode()) { try { @@ -2453,7 +2453,7 @@ public class MacroExecutor { } } else if (action instanceof ClickAction) { ClickAction ca = (ClickAction) action; - runClickActionBurst(mc, ca); + MacroActionRunner.runClickActionBurst(mc, ca); } else if (action instanceof PayAction) { runPayAction(mc, (PayAction) action); } else if (action instanceof XCarryAction xsa) { @@ -2489,14 +2489,14 @@ public class MacroExecutor { + (sia.persistent ? " \u221e" : "")); if (sia.persistent) { while (isCurrentRunActive()) { - try { runStoreItemAction(mc, sia); } + try { MacroActionRunner.runStoreItemAction(mc, sia); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } catch (Exception e) { AutismClientMessaging.sendPrefixed("Store error: " + e.getMessage()); } CompletableFuture siTick = MacroConditionRegistry.waitForNextTick(); waitForCondition(siTick); } } else { - try { runStoreItemAction(mc, sia); } + try { MacroActionRunner.runStoreItemAction(mc, sia); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (Exception e) { AutismClientMessaging.sendPrefixed("Store error: " + e.getMessage()); } } @@ -2513,13 +2513,13 @@ public class MacroExecutor { + (t.getMessage() == null ? t.getClass().getSimpleName() : t.getMessage())); } } else if (action instanceof ContainerClickSequenceAction sequence) { - runContainerClickSequenceAction(mc, sequence); + MacroActionRunner.runContainerClickSequenceAction(mc, sequence); } else if (action instanceof PacketBurstAction burst) { - runPacketBurstAction(mc, burst); + MacroActionRunner.runPacketBurstAction(mc, burst); } else if (action instanceof TickSyncAction tsa) { if (mc.level == null) return; java.util.List> preGenerated = - preGeneratePacketsForRepeat(tsa.preGenCount, mc); + MacroActionRunner.preGeneratePacketsForRepeat(tsa.preGenCount, mc); long targetTick = mc.level.getGameTime() + tsa.tickOffset; setCurrentStatus("Tick Sync -> " + targetTick + " (" + preGenerated.size() + " pkts)"); while (isCurrentRunActive() && mc.level.getGameTime() < targetTick) { @@ -2534,7 +2534,7 @@ public class MacroExecutor { } else if (action instanceof RevisionSyncAction rsa) { if (mc.player == null || mc.player.containerMenu == null) return; java.util.List> preGenerated = - preGeneratePacketsForRepeat(rsa.preGenCount, mc); + MacroActionRunner.preGeneratePacketsForRepeat(rsa.preGenCount, mc); int baseRevision = mc.player.containerMenu.getStateId(); int targetRevision = baseRevision + rsa.revisionOffset; setCurrentStatus("Rev Sync -> " + targetRevision + " (" + preGenerated.size() + " pkts)"); @@ -2552,7 +2552,7 @@ public class MacroExecutor { } else if (action instanceof ServerTickSyncAction stsa) { if (mc.getConnection() == null) return; java.util.List> preGenerated = - preGeneratePacketsForRepeat(stsa.preGenCount, mc); + MacroActionRunner.preGeneratePacketsForRepeat(stsa.preGenCount, mc); int sampleWaitMs = 0; while (isCurrentRunActive() && !ServerTickTracker.isReady() && sampleWaitMs < stsa.maxWaitMs) { float progress = ServerTickTracker.getWarmupProgress(); @@ -2604,14 +2604,14 @@ public class MacroExecutor { if (stopAction.target == StopMacroAction.StopTarget.SELF) stopCurrentRun(); else stopAction.execute(mc); } else if (action instanceof UseItemPhaseAction phaseAction) { - runUseItemPhaseAction(mc, phaseAction); + MacroActionRunner.runUseItemPhaseAction(mc, phaseAction); } else if (action instanceof autismclient.api.macro.ContextMacroAction contextAction) { - runContextMacroAction(contextAction); + MacroActionRunner.runContextMacroAction(contextAction); } else { - if (usesTickAlignedDispatch(action)) { + if (MacroActionRunner.usesTickAlignedDispatch(action)) { - runMacroActionOnClientThread(mc, action, action.getDisplayName()); + MacroActionRunner.runMacroActionOnClientThread(mc, action, action.getDisplayName()); } else if (module != null && module.useInstantExecutionMode()) { java.util.concurrent.CountDownLatch latch = new java.util.concurrent.CountDownLatch(1); RunState scheduledRun = CURRENT_RUN.get(); @@ -2630,7 +2630,7 @@ public class MacroExecutor { Thread.currentThread().interrupt(); } } else { - runMacroActionOnClientThread(mc, action, action.getDisplayName()); + MacroActionRunner.runMacroActionOnClientThread(mc, action, action.getDisplayName()); } } @@ -2642,7 +2642,7 @@ public class MacroExecutor { } catch (java.util.concurrent.CancellationException e) { } } - if (isCurrentRunActive() && !isInstantInventoryAction(action) && module != null && module.useInstantExecutionMode()) { + if (isCurrentRunActive() && !MacroActionRunner.isInstantInventoryAction(action) && module != null && module.useInstantExecutionMode()) { int delayUs = module.getActionDelayUs(); if (delayUs > 0) java.util.concurrent.locks.LockSupport.parkNanos(delayUs * 1000L); else Thread.onSpinWait(); @@ -2653,509 +2653,6 @@ public class MacroExecutor { } } - private static void beginRaycastAim(Minecraft mc, MacroAction action) { - if (!(action instanceof RaycastAim aim) || !aim.isRaycast()) return; - autismclient.util.AutismRotationUtil.Rotation rotation; - try { - rotation = callOnClientThread(mc, () -> MacroRaycastAim.aimFor(mc, aim.raycastTarget(mc)), null); - } catch (InterruptedException interrupted) { - Thread.currentThread().interrupt(); - return; - } - if (rotation == null) return; - MacroRaycastAim.hold(rotation); - waitOneTick(); - } - - static boolean dynamicTextInputsAvailable(MacroAction action, Minecraft mc) { - if (action instanceof WaitForChatAction chat && chat.effectiveMatchMode() == MacroCapturePattern.Mode.MATCH) { - return MacroVariables.resolve(chat.pattern, mc).success(); - } - if (action instanceof WaitForGuiAction gui && gui.matchMode == MacroCapturePattern.Mode.MATCH) { - return MacroVariables.resolve(gui.guiTitle, mc).success(); - } - if (action instanceof WaitGuiTypeAction gui && gui.matchMode == MacroCapturePattern.Mode.MATCH) { - return MacroVariables.resolve(gui.title, mc).success(); - } - if (action instanceof WaitInventoryPredicateAction inventory) { - return MacroVariables.resolve(inventory.itemName, mc).success(); - } - if (action instanceof WaitDurabilityAction durability && durability.targetMode == WaitDurabilityAction.TargetMode.ITEM) { - return MacroVariables.resolve(durability.itemName, mc).success(); - } - if (action instanceof WaitEntityTargetAction entity) { - return MacroVariables.resolve(entity.entityId, mc).success(); - } - return true; - } - - private static java.util.List> preGeneratePacketsForRepeat( - int count, Minecraft mc) { - RepeatPacketContext context = REPEAT_PACKET_CONTEXT.get(); - if (context == null || context.macro() == null) return new java.util.ArrayList<>(); - return preGeneratePackets(context.macro(), context.startIdx(), count, context.endIdx()); - } - - private static boolean isTargetWaitInteractionAction(MacroAction action) { - return action instanceof OpenContainerAction || action instanceof InteractEntityAction; - } - - private static CompletableFuture runTargetWaitInteractionAction(Minecraft mc, MacroAction action) throws InterruptedException { - boolean waitForTarget = switch (action) { - case OpenContainerAction open -> open.waitForTarget; - case InteractEntityAction interact -> interact.waitForTarget; - default -> false; - }; - - if (!waitForTarget) { - CompletableFuture postGuiFuture = createPostGuiFuture(mc, action); - runTargetInteractionAttempt(mc, action); - return postGuiFuture; - } - - setCurrentStatus("Waiting target: " + action.getDisplayName()); - while (isCurrentRunActive()) { - if (!canRunTargetInteractionNow(mc, action)) { - java.util.concurrent.locks.LockSupport.parkNanos(MACRO_WAIT_POLL_NANOS); - if (Thread.interrupted()) throw new InterruptedException(); - continue; - } - - CompletableFuture postGuiFuture = createPostGuiFuture(mc, action); - if (runTargetInteractionAttempt(mc, action)) { - return postGuiFuture; - } - if (postGuiFuture != null) postGuiFuture.cancel(true); - java.util.concurrent.locks.LockSupport.parkNanos(MACRO_WAIT_POLL_NANOS); - if (Thread.interrupted()) throw new InterruptedException(); - } - return null; - } - - private static boolean canRunTargetInteractionNow(Minecraft mc, MacroAction action) throws InterruptedException { - return callOnClientThread(mc, () -> { - if (action instanceof OpenContainerAction open) return open.canExecuteNow(mc); - if (action instanceof InteractEntityAction interact) return interact.canExecuteNow(mc); - return true; - }, Boolean.FALSE); - } - - private static boolean runTargetInteractionAttempt(Minecraft mc, MacroAction action) throws InterruptedException { - return callOnClientThread(mc, () -> { - try { - if (action instanceof OpenContainerAction open) return open.tryExecute(mc); - if (action instanceof InteractEntityAction interact) return interact.tryExecute(mc); - action.execute(mc); - return true; - } catch (Exception e) { - AutismClientMessaging.sendPrefixed("\u00a7cError in " + action.getDisplayName() + ": " + e.getMessage()); - return true; - } - }, Boolean.FALSE, usesTickAlignedDispatch(action)); - } - - private static CompletableFuture createPostGuiFuture(Minecraft mc, MacroAction action) { - if (Boolean.TRUE.equals(SUPPRESS_POST_GUI_AFTER.get())) return null; - if (!(action instanceof WaitsForGui wfg) || !wfg.isWaitForGuiAfter()) return null; - return createWaitsForGuiFuture(mc, wfg); - } - - private static CompletableFuture createWaitsForGuiFuture(Minecraft mc, WaitsForGui wfg) { - MacroTemplate.Resolution guiName = MacroVariables.resolve(wfg.getWaitGuiName(), mc); - if (!guiName.success()) return CompletableFuture.completedFuture(null); - return wfg.isWaitForGuiChange() - ? MacroConditionRegistry.waitForGuiChange(mc.gui.screen()) - : MacroConditionRegistry.waitForGui(guiName.value()); - } - - private static boolean waitForGuiBeforeAction(Minecraft mc, MacroAction action) throws InterruptedException { - if (!(action instanceof WaitsForGui wfg)) return true; - return waitForGuiBeforeAction(mc, wfg); - } - - private static boolean waitForGuiBeforeAction(Minecraft mc, WaitsForGui action) throws InterruptedException { - if (mc == null || action == null || !action.isWaitForGuiBefore()) return true; - MacroTemplate.Resolution guiName = MacroVariables.resolve(action.getWaitGuiName(), mc); - if (!guiName.success()) return false; - if (isWaitGuiPresent(mc, guiName.value())) { - waitForCurrentGuiReady(mc); - return true; - } - CompletableFuture future = MacroConditionRegistry.waitForGui(guiName.value()); - waitForCondition(future); - waitForCurrentGuiReady(mc); - return true; - } - - private static boolean isWaitGuiPresent(Minecraft mc, String guiName) { - if (mc == null || mc.gui.screen() == null) return false; - if (MacroGuiMatcher.isOwnScreen(mc.gui.screen())) return false; - String expected = guiName == null ? "" : guiName.trim(); - if (expected.isEmpty()) return true; - String actual = mc.gui.screen().getTitle() == null ? "" : mc.gui.screen().getTitle().getString(); - return matchesWaitGuiTitle(expected, actual); - } - - private static boolean matchesWaitGuiTitle(String expected, String actual) { - if (expected == null || expected.isBlank() || actual == null || actual.isBlank()) return false; - String expectedLower = expected.toLowerCase(Locale.ROOT); - String actualLower = actual.toLowerCase(Locale.ROOT); - if (expectedLower.equals(actualLower) || actualLower.contains(expectedLower)) return true; - for (String word : expectedLower.split("\\s+")) { - if (!word.isBlank() && !actualLower.contains(word)) return false; - } - return true; - } - - private static void waitForCurrentGuiReady(Minecraft mc) throws InterruptedException { - - long deadline = System.nanoTime() + 500_000_000L; - while (isCurrentRunActive()) { - Boolean ready = callOnClientThread(mc, () -> { - if (mc.player == null || mc.gui.screen() == null) return false; - if (mc.player.containerMenu != null && mc.player.containerMenu != mc.player.inventoryMenu) { - return mc.player.containerMenu.getStateId() > 0; - } - return true; - }, Boolean.FALSE); - if (Boolean.TRUE.equals(ready)) return; - if (System.nanoTime() >= deadline) return; - java.util.concurrent.locks.LockSupport.parkNanos(MACRO_WAIT_POLL_NANOS); - if (Thread.interrupted()) return; - } - } - - private static void runMacroActionOnClientThread(Minecraft mc, MacroAction action, String label) throws InterruptedException { - Runnable body = () -> { - try { - action.execute(mc); - } catch (Throwable t) { - recordAddonActionError(action, "Action threw " + t.getClass().getSimpleName()); - AutismClientMessaging.sendPrefixed("\u00a7cError in " + label + ": " - + (t.getMessage() == null ? t.getClass().getSimpleName() : t.getMessage())); - } - }; - if (usesTickAlignedDispatch(action)) runOnClientTick(mc, body); - else runOnClientThread(mc, body); - } - - private static boolean usesTickAlignedDispatch(MacroAction action) { - return action instanceof PacketOrdered ordered && ordered.isTickAligned(); - } - - private static void runItemActionPaced(Minecraft mc, ItemAction ia) throws InterruptedException { - setCurrentStatus(ia.getDisplayName()); - java.util.List plan = callOnClientThread(mc, () -> ia.buildClickPlan(mc), java.util.Collections.emptyList()); - if (plan == null || plan.isEmpty()) return; - long delayMs = Math.max(0, ia.clickDelayMs); - RunState scheduledRun = CURRENT_RUN.get(); - boolean first = true; - for (Runnable op : plan) { - if (!isCurrentRunActive()) break; - if (!first && delayMs > 0) Thread.sleep(delayMs); - first = false; - mc.execute(() -> withRunContext(scheduledRun, () -> { - if (PackHideState.isHardLocked()) return; - try { - op.run(); - } catch (Throwable t) { - AutismClientMessaging.sendPrefixed("\u00a7cError in ItemAction: " - + (t.getMessage() == null ? t.getClass().getSimpleName() : t.getMessage())); - } - })); - } - } - - private static void runContextMacroAction(autismclient.api.macro.ContextMacroAction action) { - if (PackHideState.isHardLocked()) return; - if (action == null) return; - try { - action.run(new ApiExecutionContext(CURRENT_RUN.get())); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } catch (Throwable t) { - recordAddonActionError(action, "Context action threw " + t.getClass().getSimpleName()); - AutismClientMessaging.sendPrefixed("\u00a7cAddon action error: " - + (t.getMessage() == null ? t.getClass().getSimpleName() : t.getMessage())); - } - } - - private static void recordAddonActionError(MacroAction action, String detail) { - if (action == null) return; - String owner = autismclient.api.macro.MacroActionRegistry.ownerOf(action.getTypeId()); - if (owner != null && !owner.isBlank()) autismclient.addons.AddonManager.recordRuntimeError(owner, detail); - } - - private static final class ApiExecutionContext implements autismclient.api.macro.MacroExecutionContext { - private final RunState run; - - private ApiExecutionContext(RunState run) { - this.run = run; - } - - @Override public Minecraft mc() { return Minecraft.getInstance(); } - - @Override public void runOnClientThread(Runnable r) { - if (r == null) return; - - Runnable safe = () -> withRunContext(run, () -> { - try { r.run(); } - catch (Throwable t) { AutismClientMessaging.sendPrefixed("\u00a7cAddon action (client task) error: " + (t.getMessage() == null ? t.getClass().getSimpleName() : t.getMessage())); } - }); - try { MacroExecutor.runOnClientThread(Minecraft.getInstance(), safe); } - catch (InterruptedException e) { Thread.currentThread().interrupt(); } - } - - @Override public T callOnClientThread(java.util.function.Supplier s) { - if (s == null) return null; - java.util.function.Supplier safe = () -> { - AtomicReference value = new AtomicReference<>(); - withRunContext(run, () -> { - try { value.set(s.get()); } - catch (Throwable t) { AutismClientMessaging.sendPrefixed("\u00a7cAddon action (client task) error: " + (t.getMessage() == null ? t.getClass().getSimpleName() : t.getMessage())); } - }); - return value.get(); - }; - try { return MacroExecutor.callOnClientThread(Minecraft.getInstance(), safe, null); } - catch (InterruptedException e) { Thread.currentThread().interrupt(); return null; } - } - - @Override public void waitTicks(int ticks) { - withRunContext(run, () -> { - for (int i = 0; i < ticks && isCurrentRunActive(); i++) { - waitForCondition(MacroConditionRegistry.waitForNextTick()); - } - }); - } - - @Override public void awaitCondition(CompletableFuture future) { - if (future != null) withRunContext(run, () -> waitForCondition(future)); - } - - @Override public void awaitCondition(autismclient.api.macro.AddonCondition condition) { - if (condition != null) withRunContext(run, () -> waitForCondition(MacroConditionRegistry.await(condition))); - } - - @Override public boolean isActive() { return run != null && run.running.get() && !PackHideState.isHardLocked(); } - - @Override public void setStatus(String status) { withRunContext(run, () -> setCurrentStatus(status)); } - - @Override public void sendPacket(net.minecraft.network.protocol.Packet packet) { - if (packet == null) return; - runOnClientThread(() -> { - Minecraft mc = Minecraft.getInstance(); - if (mc.getConnection() != null) mc.getConnection().send(packet); - }); - } - - @Override public java.util.Optional variable(String name) { - return run == null ? java.util.Optional.empty() : run.variables.get(name); - } - - @Override public void setVariable(String name, MacroValue value) { - if (run != null) run.variables.set(name, value); - } - - @Override public void removeVariable(String name) { - if (run != null) run.variables.remove(name); - } - - @Override public java.util.Map variables() { - return run == null ? java.util.Map.of() : run.variables.snapshot(); - } - - @Override public MacroTemplate.Resolution resolveTemplate(String template) { - return MacroTemplate.resolve(template, run == null ? null : run.variables, Minecraft.getInstance()); - } - } - - private static void runUseItemPhaseAction(Minecraft mc, UseItemPhaseAction action) throws InterruptedException { - int times = action.repeatTimes(); - for (int i = 0; i < times && isCurrentRunActive(); i++) { - - runOnClientThread(mc, () -> { - try { action.sendUsePacket(mc); } - catch (Exception e) { AutismClientMessaging.sendPrefixed("\u00a7cError in Use phase: " + e.getMessage()); } - }); - - final boolean[] gateInstalled = { false }; - if (action.shouldGate()) { - runOnClientThread(mc, () -> { - try { gateInstalled[0] = action.installGate(mc); } - catch (Exception e) { AutismClientMessaging.sendPrefixed("\u00a7cError in Use gate: " + e.getMessage()); } - }); - } - try { - - for (int t = 0; t < action.holdTicks && isCurrentRunActive(); t++) { - CompletableFuture tf = MacroConditionRegistry.waitForNextTick(); - waitForCondition(tf); - } - } finally { - if (gateInstalled[0]) { - final boolean wasOnThread = mc.isSameThread(); - if (wasOnThread) { - action.removeGate(); - } else { - runOnClientThread(mc, action::removeGate); - } - } - } - if (!isCurrentRunActive()) break; - - runOnClientThread(mc, () -> { - try { action.finishRelease(mc); } - catch (Exception e) { AutismClientMessaging.sendPrefixed("\u00a7cError in Use release: " + e.getMessage()); } - }); - } - } - - private static void runClickActionBurst(Minecraft mc, ClickAction action) throws InterruptedException { - int times = Math.max(1, action.clickCount); - - runOnClientThread(mc, () -> { - for (int c = 0; c < times && isCurrentRunActive(); c++) { - try { - action.execute(mc); - } catch (Exception e) { - AutismClientMessaging.sendPrefixed("\u00a7cError in Click: " + e.getMessage()); - break; - } - } - }, action.isTickAligned()); - } - - private static boolean isInstantInventoryAction(MacroAction action) { - if (action instanceof ItemAction - || action instanceof DropAction - || action instanceof SwapSlotsAction - || action instanceof ClickAction - || action instanceof XCarryAction) { - return true; - } - return action instanceof StoreItemAction storeItemAction && !storeItemAction.persistent; - } - - private static void runStoreItemAction(Minecraft mc, StoreItemAction action) throws InterruptedException { - if (action == null) return; - int delayTicks = StoreItemAction.clampDelayTicks(action.delayTicks); - if (delayTicks <= 0) { - runOnClientThread(mc, () -> action.doTransfer(mc)); - return; - } - - List slots = callOnClientThread(mc, () -> action.collectTransferSlots(mc), List.of()); - for (int i = 0; i < slots.size() && isCurrentRunActive(); i++) { - AutismInventoryClickHelper.click(mc, slots.get(i), 0, net.minecraft.world.inventory.ContainerInput.QUICK_MOVE); - if (i + 1 < slots.size()) waitTicks(delayTicks); - } - - if (action.closeAfter && !action.persistent) { - runOnClientThread(mc, () -> AutismGuiActions.closeCurrentScreen(mc, action.closeSendPkt, false)); - } - } - - private static void waitTicks(int ticks) { - for (int i = 0; i < ticks && isCurrentRunActive(); i++) { - CompletableFuture tickFuture = MacroConditionRegistry.waitForNextTick(); - waitForCondition(tickFuture); - } - } - - private static void runUseItemAction(Minecraft mc, UseItemAction action) throws InterruptedException { - if (action == null) return; - - boolean sneaking = action.sneak; - try { - if (action.useMode == UseItemAction.UseMode.CUSTOM_HOLD) { - runOnClientThread(mc, () -> { - try { - if (sneaking) autismclient.util.AutismMacroSneak.forceSneak(mc, true); - action.execute(mc); - } - catch (Exception e) { AutismClientMessaging.sendPrefixed("\u00a7cError in UseItem: " + e.getMessage()); } - }, action.isTickAligned()); - if (action.holdTicks > 0) { - waitTicks(action.holdTicks); - if (!isCurrentRunActive()) return; - runOnClientThread(mc, () -> { - try { action.sendRelease(mc); } - catch (Exception e) { AutismClientMessaging.sendPrefixed("\u00a7cError in UseItem release: " + e.getMessage()); } - }, action.isTickAligned()); - if (action.waitForFinish) waitForUseItemFinish(mc, action.holdTicks + 8); - } - return; - } - - int times = Math.max(1, action.useCount); - for (int i = 0; i < times && isCurrentRunActive(); i++) { - runOnClientThread(mc, () -> { - try { - if (sneaking) autismclient.util.AutismMacroSneak.forceSneak(mc, true); - action.execute(mc); - } - catch (Exception e) { AutismClientMessaging.sendPrefixed("\u00a7cError in UseItem: " + e.getMessage()); } - }, action.isTickAligned()); - if (action.waitForFinish) waitForUseItemFinish(mc, 0); - if (i + 1 < times && isCurrentRunActive()) waitOneTick(); - } - } finally { - if (sneaking) mc.execute(() -> autismclient.util.AutismMacroSneak.forceSneak(mc, false)); - } - } - - private static void runContainerClickSequenceAction(Minecraft mc, ContainerClickSequenceAction action) throws InterruptedException { - if (action == null) return; - List slots = action.resolvedSlots(); - int repeats = Math.max(1, action.repeatCount); - int delay = Math.max(0, action.delayTicks); - int total = repeats * slots.size(); - int sent = 0; - for (int repeat = 0; repeat < repeats && isCurrentRunActive(); repeat++) { - for (int slot : slots) { - if (!isCurrentRunActive()) return; - runOnClientThread(mc, () -> action.executeClick(mc, slot)); - sent++; - if (delay > 0 && sent < total) waitTicks(delay); - } - } - } - - private static void runPacketBurstAction(Minecraft mc, PacketBurstAction action) { - if (action == null || mc == null || mc.getConnection() == null) return; - if (action.flushBefore) AutismSharedState.get().flushDelayedPackets(mc.getConnection()); - int total = action.effectiveCount(); - int delay = Math.max(0, action.delayTicks); - for (int i = 0; i < total && isCurrentRunActive(); i++) { - action.sendOne(mc); - if (delay > 0 && i + 1 < total) waitTicks(delay); - } - } - - private static void waitForUseItemFinish(Minecraft mc, int fallbackTicks) throws InterruptedException { - if (mc == null || !isCurrentRunActive()) return; - - if (!Boolean.TRUE.equals(callOnClientThread(mc, - () -> mc.player != null && mc.player.isUsingItem(), - Boolean.FALSE))) { - return; - } - - int remaining = callOnClientThread(mc, - () -> mc.player == null ? 0 : Math.max(0, mc.player.getUseItemRemainingTicks()), - 0); - int maxTicks = Math.max(8, remaining > 0 ? remaining + 8 : fallbackTicks + 8); - maxTicks = Math.min(maxTicks, 72000); - for (int i = 0; i < maxTicks && isCurrentRunActive(); i++) { - if (!Boolean.TRUE.equals(callOnClientThread(mc, - () -> mc.player != null && mc.player.isUsingItem(), - Boolean.FALSE))) { - return; - } - CompletableFuture tickFuture = MacroConditionRegistry.waitForNextTick(); - waitForCondition(tickFuture); - } - } private static void runXCarryClickPutIn(Minecraft mc, XCarryAction xsa) throws InterruptedException { if (mc == null || mc.player == null || mc.gameMode == null) return; @@ -4530,7 +4027,7 @@ public class MacroExecutor { if (!autismclient.util.AutismMacroSneak.isPacket(mode)) applyAggregateSneakState(mc); } - private static void runOnClientThread(Minecraft mc, Runnable runnable) throws InterruptedException { + static void runOnClientThread(Minecraft mc, Runnable runnable) throws InterruptedException { if (PackHideState.isHardLocked()) return; RunState scheduledRun = CURRENT_RUN.get(); Runnable contextual = () -> withRunContext(scheduledRun, runnable); @@ -4553,7 +4050,7 @@ public class MacroExecutor { if (!latch.await(2000, TimeUnit.MILLISECONDS)) pending.set(false); } - private static void runOnClientThread(Minecraft mc, Runnable runnable, boolean tickAligned) throws InterruptedException { + static void runOnClientThread(Minecraft mc, Runnable runnable, boolean tickAligned) throws InterruptedException { if (tickAligned) runOnClientTick(mc, runnable); else runOnClientThread(mc, runnable); } @@ -4571,7 +4068,7 @@ public class MacroExecutor { }); } - private static void runOnClientTick(Minecraft mc, Runnable runnable) throws InterruptedException { + static void runOnClientTick(Minecraft mc, Runnable runnable) throws InterruptedException { if (PackHideState.isHardLocked()) return; RunState scheduledRun = CURRENT_RUN.get(); Runnable contextual = () -> withRunContext(scheduledRun, runnable); @@ -4684,7 +4181,7 @@ public class MacroExecutor { if (!action.interact) { - runMacroActionOnClientThread(mc, action, "Place"); + MacroActionRunner.runMacroActionOnClientThread(mc, action, "Place"); return; } @@ -4698,7 +4195,7 @@ public class MacroExecutor { runOnClientThread(mc, () -> sendBlockInteract(mc, placePos, face), action.isTickAligned()); if (ns > 0) java.util.concurrent.locks.LockSupport.parkNanos(ns); - runMacroActionOnClientThread(mc, action, "Place"); + MacroActionRunner.runMacroActionOnClientThread(mc, action, "Place"); } else if (t == InteractTiming.WITH) { runOnClientThread(mc, () -> { @@ -4706,12 +4203,12 @@ public class MacroExecutor { sendBlockInteract(mc, placePos, face); }, action.isTickAligned()); } else if (t == InteractTiming.AFTER_PLUS) { - runMacroActionOnClientThread(mc, action, "Place"); + MacroActionRunner.runMacroActionOnClientThread(mc, action, "Place"); waitOneTick(); runOnClientThread(mc, () -> sendBlockInteract(mc, placePos, face), action.isTickAligned()); } else { - runMacroActionOnClientThread(mc, action, "Place"); + MacroActionRunner.runMacroActionOnClientThread(mc, action, "Place"); if (ns > 0) java.util.concurrent.locks.LockSupport.parkNanos(ns); runOnClientThread(mc, () -> sendBlockInteract(mc, placePos, face), action.isTickAligned()); } @@ -5935,13 +5432,13 @@ public class MacroExecutor { } } - private static java.util.List> preGeneratePackets(AutismMacro macro, int startIdx, int count) { + static java.util.List> preGeneratePackets(AutismMacro macro, int startIdx, int count) { java.util.List> result = new java.util.ArrayList<>(); int maxIdx = count < 0 ? macro.actions.size() : Math.min(startIdx + count, macro.actions.size()); return preGeneratePackets(macro, startIdx, count, maxIdx); } - private static java.util.List> preGeneratePackets(AutismMacro macro, int startIdx, int count, int endIdx) { + static java.util.List> preGeneratePackets(AutismMacro macro, int startIdx, int count, int endIdx) { java.util.List> result = new java.util.ArrayList<>(); int maxIdx = Math.min(endIdx, count < 0 ? macro.actions.size() : Math.min(startIdx + count, macro.actions.size())); diff --git a/src/main/java/autismclient/util/macro/MacroPrearm.java b/src/main/java/autismclient/util/macro/MacroPrearm.java index cc64d71..feb89c0 100644 --- a/src/main/java/autismclient/util/macro/MacroPrearm.java +++ b/src/main/java/autismclient/util/macro/MacroPrearm.java @@ -53,7 +53,7 @@ final class MacroPrearm { MacroAction next = actions.get(nextIndex); if (next == null || !next.isEnabled() || !next.listensDuringPreviousAction()) return; if (!MacroDynamicBindings.apply(next, mc)) return; - if (!MacroExecutor.dynamicTextInputsAvailable(next, mc)) return; + if (!MacroActionRunner.dynamicTextInputsAvailable(next, mc)) return; if (!isWaitConditionAction(next)) return; synchronized (run.prearmedWaits) { if (run.prearmedWaits.containsKey(next)) return;