Refactor: extract MacroActionRunner from MacroExecutor (upstream #4)

Move 28 single-action execution methods plus ApiExecutionContext
verbatim into MacroActionRunner: raycast/target interaction, GUI
gating, client-thread dispatch, paced item/use/click/store/container
actions and packet-burst helpers. Shared members referenced as
MacroExecutor.xxx (mechanical qualification only); self-calls to the
context interface methods left bare. All bodies verified identical
against HEAD modulo that qualification.
This commit is contained in:
sectersion 2026-10-11 16:18:00 +01:00
parent 57ca197042
commit 4c036f9c54
3 changed files with 582 additions and 564 deletions

View file

@ -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<net.minecraft.network.protocol.Packet<?>> 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<Void> 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<Void> 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<Void> 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<Void> 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<Void> 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<Void> 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<Runnable> plan = MacroExecutor.callOnClientThread(mc, () -> ia.buildClickPlan(mc), java.util.Collections.<Runnable>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> T callOnClientThread(java.util.function.Supplier<T> s) {
if (s == null) return null;
java.util.function.Supplier<T> safe = () -> {
AtomicReference<T> 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<Void> 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<MacroValue> 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<String, MacroValue> 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<Void> 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<Integer> 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<Void> 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<Integer> 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<Void> tickFuture = MacroConditionRegistry.waitForNextTick();
MacroExecutor.waitForCondition(tickFuture);
}
}
}

View file

@ -98,7 +98,7 @@ public class MacroExecutor {
private static final java.util.concurrent.ConcurrentHashMap<Long, RunState> RUNS_BY_ID = new java.util.concurrent.ConcurrentHashMap<>();
private static final AtomicLong RUN_STATE_REVISION = new AtomicLong();
static final ThreadLocal<RunState> CURRENT_RUN = new ThreadLocal<>();
private static final ThreadLocal<Boolean> SUPPRESS_POST_GUI_AFTER = ThreadLocal.withInitial(() -> false);
static final ThreadLocal<Boolean> 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<MoveAction, int[]> backgroundMoves =
new java.util.concurrent.ConcurrentHashMap<>();
private static final ThreadLocal<RepeatPacketContext> REPEAT_PACKET_CONTEXT = new ThreadLocal<>();
static final ThreadLocal<RepeatPacketContext> REPEAT_PACKET_CONTEXT = new ThreadLocal<>();
private static final ThreadLocal<Integer> 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<MacroAction> 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<SharedClientResource> 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<Void> 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<SharedClientResource> 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<Void> 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<Void> 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<net.minecraft.network.protocol.Packet<?>> 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<net.minecraft.network.protocol.Packet<?>> 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<net.minecraft.network.protocol.Packet<?>> 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<net.minecraft.network.protocol.Packet<?>> 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<Void> 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<Void> 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<Void> 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<Void> 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<Void> 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<Void> 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<Runnable> plan = callOnClientThread(mc, () -> ia.buildClickPlan(mc), java.util.Collections.<Runnable>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> T callOnClientThread(java.util.function.Supplier<T> s) {
if (s == null) return null;
java.util.function.Supplier<T> safe = () -> {
AtomicReference<T> 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<Void> 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<MacroValue> 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<String, MacroValue> 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<Void> 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<Integer> 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<Void> 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<Integer> 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<Void> 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<Packet<?>> preGeneratePackets(AutismMacro macro, int startIdx, int count) {
static java.util.List<Packet<?>> preGeneratePackets(AutismMacro macro, int startIdx, int count) {
java.util.List<Packet<?>> 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<Packet<?>> preGeneratePackets(AutismMacro macro, int startIdx, int count, int endIdx) {
static java.util.List<Packet<?>> preGeneratePackets(AutismMacro macro, int startIdx, int count, int endIdx) {
java.util.List<Packet<?>> result = new java.util.ArrayList<>();
int maxIdx = Math.min(endIdx, count < 0 ? macro.actions.size() : Math.min(startIdx + count, macro.actions.size()));

View file

@ -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;