mirror of
https://github.com/AutismDevelopment/Autism-Client.git
synced 2026-10-28 18:29:47 +00:00
Merge 1944f97084 into cb6910b424
This commit is contained in:
commit
5aa230438e
19 changed files with 8862 additions and 8476 deletions
521
src/main/java/autismclient/util/macro/MacroActionRunner.java
Normal file
521
src/main/java/autismclient/util/macro/MacroActionRunner.java
Normal 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 = MacroClientThread.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 MacroRender.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
MacroLifecycle.setCurrentStatus("Waiting target: " + action.getDisplayName());
|
||||||
|
while (MacroLifecycle.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 MacroClientThread.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 MacroClientThread.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());
|
||||||
|
MacroRunLoop.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 (MacroLifecycle.isCurrentRunActive()) {
|
||||||
|
Boolean ready = MacroClientThread.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)) MacroClientThread.runOnClientTick(mc, body);
|
||||||
|
else MacroClientThread.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 {
|
||||||
|
MacroLifecycle.setCurrentStatus(ia.getDisplayName());
|
||||||
|
java.util.List<Runnable> plan = MacroClientThread.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 (!MacroLifecycle.isCurrentRunActive()) break;
|
||||||
|
if (!first && delayMs > 0) Thread.sleep(delayMs);
|
||||||
|
first = false;
|
||||||
|
mc.execute(() -> MacroLifecycle.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 = () -> MacroLifecycle.withRunContext(run, () -> {
|
||||||
|
try { r.run(); }
|
||||||
|
catch (Throwable t) { AutismClientMessaging.sendPrefixed("\u00a7cAddon action (client task) error: " + (t.getMessage() == null ? t.getClass().getSimpleName() : t.getMessage())); }
|
||||||
|
});
|
||||||
|
try { MacroClientThread.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<>();
|
||||||
|
MacroLifecycle.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 MacroClientThread.callOnClientThread(Minecraft.getInstance(), safe, null); }
|
||||||
|
catch (InterruptedException e) { Thread.currentThread().interrupt(); return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void waitTicks(int ticks) {
|
||||||
|
MacroLifecycle.withRunContext(run, () -> {
|
||||||
|
for (int i = 0; i < ticks && MacroLifecycle.isCurrentRunActive(); i++) {
|
||||||
|
MacroRunLoop.waitForCondition(MacroConditionRegistry.waitForNextTick());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void awaitCondition(CompletableFuture<Void> future) {
|
||||||
|
if (future != null) MacroLifecycle.withRunContext(run, () -> MacroRunLoop.waitForCondition(future));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void awaitCondition(autismclient.api.macro.AddonCondition condition) {
|
||||||
|
if (condition != null) MacroLifecycle.withRunContext(run, () -> MacroRunLoop.waitForCondition(MacroConditionRegistry.await(condition)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean isActive() { return run != null && run.running.get() && !PackHideState.isHardLocked(); }
|
||||||
|
|
||||||
|
@Override public void setStatus(String status) { MacroLifecycle.withRunContext(run, () -> MacroLifecycle.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 && MacroLifecycle.isCurrentRunActive(); i++) {
|
||||||
|
|
||||||
|
MacroClientThread.runOnClientThread(mc, () -> {
|
||||||
|
try { action.sendUsePacket(mc); }
|
||||||
|
catch (Exception e) { AutismClientMessaging.sendPrefixed("\u00a7cError in Use phase: " + e.getMessage()); }
|
||||||
|
});
|
||||||
|
|
||||||
|
final boolean[] gateInstalled = { false };
|
||||||
|
if (action.shouldGate()) {
|
||||||
|
MacroClientThread.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 && MacroLifecycle.isCurrentRunActive(); t++) {
|
||||||
|
CompletableFuture<Void> tf = MacroConditionRegistry.waitForNextTick();
|
||||||
|
MacroRunLoop.waitForCondition(tf);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (gateInstalled[0]) {
|
||||||
|
final boolean wasOnThread = mc.isSameThread();
|
||||||
|
if (wasOnThread) {
|
||||||
|
action.removeGate();
|
||||||
|
} else {
|
||||||
|
MacroClientThread.runOnClientThread(mc, action::removeGate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!MacroLifecycle.isCurrentRunActive()) break;
|
||||||
|
|
||||||
|
MacroClientThread.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);
|
||||||
|
|
||||||
|
MacroClientThread.runOnClientThread(mc, () -> {
|
||||||
|
for (int c = 0; c < times && MacroLifecycle.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) {
|
||||||
|
MacroClientThread.runOnClientThread(mc, () -> action.doTransfer(mc));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Integer> slots = MacroClientThread.callOnClientThread(mc, () -> action.collectTransferSlots(mc), List.of());
|
||||||
|
for (int i = 0; i < slots.size() && MacroLifecycle.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) {
|
||||||
|
MacroClientThread.runOnClientThread(mc, () -> AutismGuiActions.closeCurrentScreen(mc, action.closeSendPkt, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void waitTicks(int ticks) {
|
||||||
|
for (int i = 0; i < ticks && MacroLifecycle.isCurrentRunActive(); i++) {
|
||||||
|
CompletableFuture<Void> tickFuture = MacroConditionRegistry.waitForNextTick();
|
||||||
|
MacroRunLoop.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) {
|
||||||
|
MacroClientThread.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 (!MacroLifecycle.isCurrentRunActive()) return;
|
||||||
|
MacroClientThread.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 && MacroLifecycle.isCurrentRunActive(); i++) {
|
||||||
|
MacroClientThread.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 && MacroLifecycle.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 && MacroLifecycle.isCurrentRunActive(); repeat++) {
|
||||||
|
for (int slot : slots) {
|
||||||
|
if (!MacroLifecycle.isCurrentRunActive()) return;
|
||||||
|
MacroClientThread.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 && MacroLifecycle.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 || !MacroLifecycle.isCurrentRunActive()) return;
|
||||||
|
|
||||||
|
if (!Boolean.TRUE.equals(MacroClientThread.callOnClientThread(mc,
|
||||||
|
() -> mc.player != null && mc.player.isUsingItem(),
|
||||||
|
Boolean.FALSE))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int remaining = MacroClientThread.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 && MacroLifecycle.isCurrentRunActive(); i++) {
|
||||||
|
if (!Boolean.TRUE.equals(MacroClientThread.callOnClientThread(mc,
|
||||||
|
() -> mc.player != null && mc.player.isUsingItem(),
|
||||||
|
Boolean.FALSE))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CompletableFuture<Void> tickFuture = MacroConditionRegistry.waitForNextTick();
|
||||||
|
MacroRunLoop.waitForCondition(tickFuture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
548
src/main/java/autismclient/util/macro/MacroBreak.java
Normal file
548
src/main/java/autismclient/util/macro/MacroBreak.java
Normal file
|
|
@ -0,0 +1,548 @@
|
||||||
|
package autismclient.util.macro;
|
||||||
|
|
||||||
|
import autismclient.mixin.accessor.AutismMultiPlayerGameModeAccessor;
|
||||||
|
import autismclient.modules.AutismModule;
|
||||||
|
import autismclient.util.AutismAutoTool;
|
||||||
|
import autismclient.util.AutismClientMessaging;
|
||||||
|
import autismclient.util.AutismInstaBreakRenderer;
|
||||||
|
import autismclient.util.AutismInventoryHelper;
|
||||||
|
import autismclient.util.AutismSharedState;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
|
||||||
|
import net.minecraft.tags.ItemTags;
|
||||||
|
import net.minecraft.world.InteractionHand;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.phys.BlockHitResult;
|
||||||
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
|
||||||
|
final class MacroBreak {
|
||||||
|
private MacroBreak() {
|
||||||
|
}
|
||||||
|
public static void runInstaBreakAction(Minecraft mc, InstaBreakAction action) throws InterruptedException {
|
||||||
|
if (mc == null || mc.player == null || mc.level == null || mc.getConnection() == null || mc.gameMode == null) {
|
||||||
|
AutismClientMessaging.sendPrefixed("\u00a7cInstaBreak: missing world or connection");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockPos target = action.blockPos == null ? BlockPos.ZERO : action.blockPos;
|
||||||
|
Direction direction = action.direction == null ? Direction.UP : action.direction;
|
||||||
|
int delayTicks = Math.max(0, action.delayTicks);
|
||||||
|
int targetTimes = Math.max(0, action.times);
|
||||||
|
int completed = 0;
|
||||||
|
boolean wasSolid = !isInstaBreakAir(mc, target);
|
||||||
|
|
||||||
|
if (!ensureInstaBreakPickaxe(mc, action)) return;
|
||||||
|
|
||||||
|
AutismInstaBreakRenderer.setTarget(target);
|
||||||
|
try {
|
||||||
|
long lastAttemptTick = AutismSharedState.get().getClientTickCounter() - (delayTicks + 1L);
|
||||||
|
long lastSneakTick = Long.MIN_VALUE;
|
||||||
|
while (MacroLifecycle.isCurrentRunActive() && isActionStillInCurrentMacro(action)) {
|
||||||
|
|
||||||
|
if (action.sneak) {
|
||||||
|
long sneakTick = AutismSharedState.get().getClientTickCounter();
|
||||||
|
if (sneakTick != lastSneakTick) {
|
||||||
|
lastSneakTick = sneakTick;
|
||||||
|
final String sneakMode = action.sneakMode;
|
||||||
|
MacroClientThread.runOnClientThread(mc, () -> holdMacroSneak(mc, sneakMode), action.isTickAligned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isAir = isInstaBreakAir(mc, target);
|
||||||
|
if (wasSolid && isAir) {
|
||||||
|
completed++;
|
||||||
|
MacroLifecycle.setCurrentStatus("InstaBreak " + completed + "/" + (targetTimes == 0 ? "\u221e" : targetTimes));
|
||||||
|
if (targetTimes > 0 && completed >= targetTimes) return;
|
||||||
|
wasSolid = false;
|
||||||
|
} else if (!isAir) {
|
||||||
|
wasSolid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isAir) {
|
||||||
|
long currentTick = AutismSharedState.get().getClientTickCounter();
|
||||||
|
if (currentTick != lastAttemptTick && (currentTick - lastAttemptTick) >= delayTicks) {
|
||||||
|
lastAttemptTick = currentTick;
|
||||||
|
if (!ensureInstaBreakPickaxe(mc, action)) return;
|
||||||
|
if (action.interact) {
|
||||||
|
|
||||||
|
InteractTiming t = action.interactTiming;
|
||||||
|
boolean interactFirst = t == InteractTiming.BEFORE || t == InteractTiming.WITH
|
||||||
|
|| (t == InteractTiming.CUSTOM && action.interactCustomMs < 0);
|
||||||
|
long ns = t == InteractTiming.CUSTOM
|
||||||
|
? Math.abs((long) action.interactCustomMs) * 1_000_000L : 0L;
|
||||||
|
if (interactFirst) {
|
||||||
|
MacroClientThread.runOnClientThread(mc, () -> sendBlockInteract(mc, target, direction), action.isTickAligned());
|
||||||
|
if (ns > 0) java.util.concurrent.locks.LockSupport.parkNanos(ns);
|
||||||
|
sendInstaBreakAttempt(mc, target, direction, action.isTickAligned());
|
||||||
|
} else if (t == InteractTiming.AFTER_PLUS) {
|
||||||
|
sendInstaBreakAttempt(mc, target, direction, action.isTickAligned());
|
||||||
|
MacroExecutor.waitOneTick();
|
||||||
|
MacroClientThread.runOnClientThread(mc, () -> sendBlockInteract(mc, target, direction), action.isTickAligned());
|
||||||
|
} else {
|
||||||
|
sendInstaBreakAttempt(mc, target, direction, action.isTickAligned());
|
||||||
|
if (ns > 0) java.util.concurrent.locks.LockSupport.parkNanos(ns);
|
||||||
|
MacroClientThread.runOnClientThread(mc, () -> sendBlockInteract(mc, target, direction), action.isTickAligned());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendInstaBreakAttempt(mc, target, direction, action.isTickAligned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MacroLifecycle.setCurrentStatus("InstaBreak " + target.getX() + "," + target.getY() + "," + target.getZ()
|
||||||
|
+ " " + completed + "/" + (targetTimes == 0 ? "\u221e" : targetTimes));
|
||||||
|
|
||||||
|
Thread.sleep(1);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (action.sneak) {
|
||||||
|
final String sneakMode = action.sneakMode;
|
||||||
|
try {
|
||||||
|
MacroClientThread.runOnClientThread(mc, () -> releaseMacroSneak(mc, sneakMode));
|
||||||
|
} catch (InterruptedException interrupted) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AutismInstaBreakRenderer.clearTarget(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isInstaBreakAir(Minecraft mc, BlockPos pos) {
|
||||||
|
|
||||||
|
return mc.level == null || mc.level.isOutsideBuildHeight(pos) || mc.level.getBlockState(pos).isAir();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isActionStillInCurrentMacro(MacroAction action) {
|
||||||
|
MacroExecutor.RunState run = MacroExecutor.CURRENT_RUN.get();
|
||||||
|
if (run == null || run.macro == null || run.macro.actions == null) return true;
|
||||||
|
return run.macro.actions.contains(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean ensureInstaBreakPickaxe(Minecraft mc, InstaBreakAction action) throws InterruptedException {
|
||||||
|
|
||||||
|
return MacroClientThread.callOnClientThread(mc, () -> {
|
||||||
|
if (mc.player == null) return false;
|
||||||
|
if (mc.player.getMainHandItem().is(ItemTags.PICKAXES)) return true;
|
||||||
|
if (!action.autoPickaxe) return false;
|
||||||
|
|
||||||
|
int preferred = Math.max(0, Math.min(8, mc.player.getInventory().getSelectedSlot()));
|
||||||
|
for (int slot = 0; slot < 9; slot++) {
|
||||||
|
if (mc.player.getInventory().getItem(slot).is(ItemTags.PICKAXES)) {
|
||||||
|
AutismInventoryHelper.selectHotbarSlot(mc, slot);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int slot = 9; slot < 36; slot++) {
|
||||||
|
if (mc.player.getInventory().getItem(slot).is(ItemTags.PICKAXES)) {
|
||||||
|
if (!AutismInventoryHelper.swapInventorySlots(mc, slot, preferred)) return false;
|
||||||
|
AutismInventoryHelper.selectHotbarSlot(mc, preferred);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}, false, action.isTickAligned());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void sendInstaBreakAttempt(Minecraft mc, BlockPos pos, Direction direction, boolean tickAligned) throws InterruptedException {
|
||||||
|
MacroClientThread.runOnClientThread(mc, () -> {
|
||||||
|
if (mc.player == null || mc.level == null || mc.gameMode == null || mc.getConnection() == null) return;
|
||||||
|
BlockPos blockPos = pos.immutable();
|
||||||
|
Direction dir = direction == null ? Direction.UP : direction;
|
||||||
|
|
||||||
|
if (mc.gameMode instanceof AutismMultiPlayerGameModeAccessor accessor) {
|
||||||
|
accessor.autism$startPrediction(mc.level, sequence -> new ServerboundPlayerActionPacket(
|
||||||
|
ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK, blockPos, dir, sequence));
|
||||||
|
accessor.autism$startPrediction(mc.level, sequence -> new ServerboundPlayerActionPacket(
|
||||||
|
ServerboundPlayerActionPacket.Action.STOP_DESTROY_BLOCK, blockPos, dir, sequence));
|
||||||
|
} else {
|
||||||
|
|
||||||
|
mc.getConnection().send(new ServerboundPlayerActionPacket(
|
||||||
|
ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK, blockPos, dir));
|
||||||
|
mc.getConnection().send(new ServerboundPlayerActionPacket(
|
||||||
|
ServerboundPlayerActionPacket.Action.STOP_DESTROY_BLOCK, blockPos, dir));
|
||||||
|
}
|
||||||
|
|
||||||
|
mc.player.swing(InteractionHand.MAIN_HAND);
|
||||||
|
}, tickAligned);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void runBreakAction(Minecraft mc, BreakAction action) throws InterruptedException {
|
||||||
|
if (mc == null || mc.player == null || mc.level == null || mc.getConnection() == null || mc.gameMode == null) {
|
||||||
|
AutismClientMessaging.sendPrefixed("\u00a7cBreak: missing world or connection");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final BlockPos target = action.blockPos == null ? BlockPos.ZERO : action.blockPos;
|
||||||
|
final Direction direction = action.direction == null ? Direction.UP : action.direction;
|
||||||
|
int startDelay = Math.max(0, action.delayTicks);
|
||||||
|
int targetTimes = Math.max(0, action.times);
|
||||||
|
int completed = 0;
|
||||||
|
|
||||||
|
if (startDelay > 0) waitBreakClientTicks(startDelay);
|
||||||
|
|
||||||
|
final boolean[] started = { false };
|
||||||
|
final boolean useNextSteps = action.interact && action.runNextSteps;
|
||||||
|
final boolean[] nextStepsFired = { false };
|
||||||
|
final boolean[] missingNextStepsWarned = { false };
|
||||||
|
AutismInstaBreakRenderer.setTarget(target);
|
||||||
|
long lastTick = -1L;
|
||||||
|
try {
|
||||||
|
while (MacroLifecycle.isCurrentRunActive() && isActionStillInCurrentMacro(action)) {
|
||||||
|
long now = AutismSharedState.get().getClientTickCounter();
|
||||||
|
if (now == lastTick) {
|
||||||
|
Thread.sleep(1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
lastTick = now;
|
||||||
|
|
||||||
|
int result;
|
||||||
|
if (!started[0]) {
|
||||||
|
result = useNextSteps
|
||||||
|
? startBreakWithNextSteps(mc, action, target, direction, started, nextStepsFired, missingNextStepsWarned)
|
||||||
|
: MacroClientThread.callOnClientThread(mc, () -> startBreakOnClient(mc, action, target, direction, started), -1, action.isTickAligned());
|
||||||
|
} else if (action.interact && action.interactTiming == InteractTiming.CUSTOM) {
|
||||||
|
result = useNextSteps
|
||||||
|
? breakCustomTickWithNextSteps(mc, action, target, direction, nextStepsFired, missingNextStepsWarned)
|
||||||
|
: breakCustomTick(mc, action, target, direction);
|
||||||
|
} else {
|
||||||
|
result = useNextSteps
|
||||||
|
? breakContinueWithNextSteps(mc, action, target, direction, nextStepsFired, missingNextStepsWarned)
|
||||||
|
: MacroClientThread.callOnClientThread(mc, () -> breakContinueOnClient(mc, action, target, direction), -1, action.isTickAligned());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result < 0) {
|
||||||
|
return;
|
||||||
|
} else if (result == 1) {
|
||||||
|
boolean completedStartedBreak = started[0];
|
||||||
|
if (useNextSteps && completedStartedBreak) {
|
||||||
|
runBreakNextStepsAfterCompletion(mc, action, nextStepsFired, missingNextStepsWarned);
|
||||||
|
} else if (action.interact && action.interactTiming == InteractTiming.AFTER_PLUS) {
|
||||||
|
MacroExecutor.waitOneTick();
|
||||||
|
MacroClientThread.runOnClientThread(mc, () -> sendBlockInteract(mc, target, direction), action.isTickAligned());
|
||||||
|
}
|
||||||
|
completed++;
|
||||||
|
MacroLifecycle.setCurrentStatus("Break " + completed + "/" + (targetTimes == 0 ? "\u221e" : targetTimes));
|
||||||
|
if (targetTimes > 0 && completed >= targetTimes) return;
|
||||||
|
started[0] = false;
|
||||||
|
nextStepsFired[0] = false;
|
||||||
|
waitForBreakSolid(mc, target);
|
||||||
|
} else {
|
||||||
|
MacroLifecycle.setCurrentStatus("Break " + target.getX() + "," + target.getY() + "," + target.getZ()
|
||||||
|
+ " " + completed + "/" + (targetTimes == 0 ? "\u221e" : targetTimes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
final boolean wasMining = started[0];
|
||||||
|
MacroClientThread.runOnClientThread(mc, () -> {
|
||||||
|
if (wasMining && mc.gameMode != null && mc.gameMode.isDestroying()) mc.gameMode.stopDestroyBlock();
|
||||||
|
if (action.sneak) releaseMacroSneak(mc, action.sneakMode);
|
||||||
|
}, action.isTickAligned());
|
||||||
|
AutismInstaBreakRenderer.clearTarget(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int startBreakWithNextSteps(Minecraft mc, BreakAction action, BlockPos pos, Direction dir,
|
||||||
|
boolean[] started, boolean[] nextStepsFired,
|
||||||
|
boolean[] missingNextStepsWarned) throws InterruptedException {
|
||||||
|
MacroExecutor.BreakStartPlan plan = MacroClientThread.callOnClientThread(mc, () -> prepareBreakStartOnClient(mc, action, pos), new MacroExecutor.BreakStartPlan(-1, false, false));
|
||||||
|
if (plan.result() != 0) return plan.result();
|
||||||
|
|
||||||
|
if (plan.instant() && plan.interactFirst()) {
|
||||||
|
runBreakNextStepsOnce(mc, action, nextStepsFired, missingNextStepsWarned);
|
||||||
|
if (!MacroLifecycle.isCurrentRunActive()) return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = MacroClientThread.callOnClientThread(mc, () -> startPreparedBreakOnClient(mc, action, pos, dir, started), -1, action.isTickAligned());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MacroExecutor.BreakStartPlan prepareBreakStartOnClient(Minecraft mc, BreakAction action, BlockPos pos) {
|
||||||
|
if (mc.level == null || mc.gameMode == null || mc.player == null || mc.getConnection() == null) {
|
||||||
|
return new MacroExecutor.BreakStartPlan(-1, false, false);
|
||||||
|
}
|
||||||
|
if (mc.level.isOutsideBuildHeight(pos)) return new MacroExecutor.BreakStartPlan(1, false, false);
|
||||||
|
BlockState state = mc.level.getBlockState(pos);
|
||||||
|
if (state.isAir()) return new MacroExecutor.BreakStartPlan(1, false, false);
|
||||||
|
|
||||||
|
if (action.sneak) holdMacroSneak(mc, action.sneakMode);
|
||||||
|
if (action.autoTool) {
|
||||||
|
AutismAutoTool.equipBestTool(mc, state, action.considerInventory, true);
|
||||||
|
state = mc.level.getBlockState(pos);
|
||||||
|
if (state.isAir()) return new MacroExecutor.BreakStartPlan(1, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
float startDelta = state.getDestroyProgress(mc.player, mc.player.level(), pos);
|
||||||
|
boolean instant = startDelta >= 1.0f;
|
||||||
|
boolean interactFirst = action.interact && (action.interactTiming == InteractTiming.BEFORE
|
||||||
|
|| action.interactTiming == InteractTiming.WITH
|
||||||
|
|| (action.interactTiming == InteractTiming.CUSTOM && action.interactCustomMs < 0));
|
||||||
|
return new MacroExecutor.BreakStartPlan(0, instant, interactFirst);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int startPreparedBreakOnClient(Minecraft mc, BreakAction action, BlockPos pos, Direction dir, boolean[] started) {
|
||||||
|
if (mc.level == null || mc.gameMode == null || mc.player == null || mc.getConnection() == null) return -1;
|
||||||
|
if (mc.level.isOutsideBuildHeight(pos) || mc.level.getBlockState(pos).isAir()) return 1;
|
||||||
|
if (action.sneak) holdMacroSneak(mc, action.sneakMode);
|
||||||
|
mc.gameMode.startDestroyBlock(pos, dir);
|
||||||
|
mc.player.swing(InteractionHand.MAIN_HAND);
|
||||||
|
started[0] = true;
|
||||||
|
return mc.level.getBlockState(pos).isAir() ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int startBreakOnClient(Minecraft mc, BreakAction action, BlockPos pos, Direction dir, boolean[] started) {
|
||||||
|
if (mc.level == null || mc.gameMode == null || mc.player == null || mc.getConnection() == null) return -1;
|
||||||
|
BlockState state = mc.level.getBlockState(pos);
|
||||||
|
if (state.isAir() || mc.level.isOutsideBuildHeight(pos)) return 1;
|
||||||
|
|
||||||
|
if (action.sneak) holdMacroSneak(mc, action.sneakMode);
|
||||||
|
if (action.autoTool) {
|
||||||
|
AutismAutoTool.equipBestTool(mc, state, action.considerInventory, true);
|
||||||
|
state = mc.level.getBlockState(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
float startDelta = state.getDestroyProgress(mc.player, mc.player.level(), pos);
|
||||||
|
boolean instant = startDelta >= 1.0f;
|
||||||
|
boolean interactFirst = action.interact && (action.interactTiming == InteractTiming.BEFORE
|
||||||
|
|| action.interactTiming == InteractTiming.WITH
|
||||||
|
|| (action.interactTiming == InteractTiming.CUSTOM && action.interactCustomMs < 0));
|
||||||
|
if (instant && interactFirst) sendBlockInteract(mc, pos, dir);
|
||||||
|
mc.gameMode.startDestroyBlock(pos, dir);
|
||||||
|
mc.player.swing(InteractionHand.MAIN_HAND);
|
||||||
|
started[0] = true;
|
||||||
|
if (instant && action.interact && !interactFirst && action.interactTiming != InteractTiming.AFTER_PLUS) sendBlockInteract(mc, pos, dir);
|
||||||
|
return mc.level.getBlockState(pos).isAir() ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int breakContinueWithNextSteps(Minecraft mc, BreakAction action, BlockPos pos, Direction dir,
|
||||||
|
boolean[] nextStepsFired,
|
||||||
|
boolean[] missingNextStepsWarned) throws InterruptedException {
|
||||||
|
MacroExecutor.BreakContinuePlan plan = MacroClientThread.callOnClientThread(mc, () -> inspectBreakContinueOnClient(mc, action, pos), new MacroExecutor.BreakContinuePlan(-1, false, false));
|
||||||
|
if (plan.result() != 0) return plan.result();
|
||||||
|
|
||||||
|
InteractTiming timing = action.interactTiming;
|
||||||
|
if (timing == InteractTiming.BEFORE && plan.breaksNextTick()) {
|
||||||
|
runBreakNextStepsOnce(mc, action, nextStepsFired, missingNextStepsWarned);
|
||||||
|
if (!MacroLifecycle.isCurrentRunActive()) return -1;
|
||||||
|
}
|
||||||
|
if (timing == InteractTiming.WITH && plan.breaksThisTick()) {
|
||||||
|
runBreakNextStepsOnce(mc, action, nextStepsFired, missingNextStepsWarned);
|
||||||
|
if (!MacroLifecycle.isCurrentRunActive()) return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = MacroClientThread.callOnClientThread(mc, () -> continueBreakOnClient(mc, action, pos, dir), -1, action.isTickAligned());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MacroExecutor.BreakContinuePlan inspectBreakContinueOnClient(Minecraft mc, BreakAction action, BlockPos pos) {
|
||||||
|
if (mc.level == null || mc.gameMode == null || mc.player == null || mc.getConnection() == null) {
|
||||||
|
return new MacroExecutor.BreakContinuePlan(-1, false, false);
|
||||||
|
}
|
||||||
|
BlockState state = mc.level.getBlockState(pos);
|
||||||
|
if (state.isAir()) return new MacroExecutor.BreakContinuePlan(1, false, false);
|
||||||
|
if (action.sneak) holdMacroSneak(mc, action.sneakMode);
|
||||||
|
|
||||||
|
boolean breaksThisTick = willBreakThisTick(mc, pos, state);
|
||||||
|
boolean breaksNextTick = !breaksThisTick && willBreakNextTick(mc, pos, state);
|
||||||
|
return new MacroExecutor.BreakContinuePlan(0, breaksThisTick, breaksNextTick);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int continueBreakOnClient(Minecraft mc, BreakAction action, BlockPos pos, Direction dir) {
|
||||||
|
if (mc.level == null || mc.gameMode == null || mc.player == null || mc.getConnection() == null) return -1;
|
||||||
|
if (mc.level.getBlockState(pos).isAir()) return 1;
|
||||||
|
if (action.sneak) holdMacroSneak(mc, action.sneakMode);
|
||||||
|
mc.gameMode.continueDestroyBlock(pos, dir);
|
||||||
|
mc.player.swing(InteractionHand.MAIN_HAND);
|
||||||
|
return mc.level.getBlockState(pos).isAir() ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int breakContinueOnClient(Minecraft mc, BreakAction action, BlockPos pos, Direction dir) {
|
||||||
|
if (mc.level == null || mc.gameMode == null || mc.player == null || mc.getConnection() == null) return -1;
|
||||||
|
BlockState state = mc.level.getBlockState(pos);
|
||||||
|
if (state.isAir()) return 1;
|
||||||
|
if (action.sneak) holdMacroSneak(mc, action.sneakMode);
|
||||||
|
|
||||||
|
boolean breaksThisTick = willBreakThisTick(mc, pos, state);
|
||||||
|
boolean breaksNextTick = !breaksThisTick && willBreakNextTick(mc, pos, state);
|
||||||
|
InteractTiming t = action.interactTiming;
|
||||||
|
if (action.interact && t == InteractTiming.BEFORE && breaksNextTick) sendBlockInteract(mc, pos, dir);
|
||||||
|
if (action.interact && t == InteractTiming.WITH && breaksThisTick) sendBlockInteract(mc, pos, dir);
|
||||||
|
|
||||||
|
mc.gameMode.continueDestroyBlock(pos, dir);
|
||||||
|
mc.player.swing(InteractionHand.MAIN_HAND);
|
||||||
|
|
||||||
|
if (action.interact && t == InteractTiming.AFTER && breaksThisTick) sendBlockInteract(mc, pos, dir);
|
||||||
|
return mc.level.getBlockState(pos).isAir() ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int breakCustomTickWithNextSteps(Minecraft mc, BreakAction action, BlockPos pos, Direction dir,
|
||||||
|
boolean[] nextStepsFired,
|
||||||
|
boolean[] missingNextStepsWarned) throws InterruptedException {
|
||||||
|
boolean willBreak = Boolean.TRUE.equals(MacroClientThread.callOnClientThread(mc, () -> {
|
||||||
|
if (mc.level == null || mc.player == null) return Boolean.FALSE;
|
||||||
|
BlockState state = mc.level.getBlockState(pos);
|
||||||
|
if (state.isAir()) return Boolean.FALSE;
|
||||||
|
return willBreakThisTick(mc, pos, state);
|
||||||
|
}, Boolean.FALSE));
|
||||||
|
|
||||||
|
if (!willBreak) {
|
||||||
|
return MacroClientThread.callOnClientThread(mc, () -> continueBreakOnClient(mc, action, pos, dir), -1, action.isTickAligned());
|
||||||
|
}
|
||||||
|
|
||||||
|
long ns = Math.abs((long) action.interactCustomMs) * 1_000_000L;
|
||||||
|
int broke;
|
||||||
|
if (action.interactCustomMs < 0) {
|
||||||
|
runBreakNextStepsOnce(mc, action, nextStepsFired, missingNextStepsWarned);
|
||||||
|
if (!MacroLifecycle.isCurrentRunActive()) return -1;
|
||||||
|
if (ns > 0) java.util.concurrent.locks.LockSupport.parkNanos(ns);
|
||||||
|
broke = MacroClientThread.callOnClientThread(mc, () -> breakAndDetectOnClient(mc, pos, dir), -1, action.isTickAligned());
|
||||||
|
} else {
|
||||||
|
broke = MacroClientThread.callOnClientThread(mc, () -> breakAndDetectOnClient(mc, pos, dir), -1, action.isTickAligned());
|
||||||
|
}
|
||||||
|
return broke;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int breakCustomTick(Minecraft mc, BreakAction action, BlockPos pos, Direction dir) throws InterruptedException {
|
||||||
|
boolean willBreak = Boolean.TRUE.equals(MacroClientThread.callOnClientThread(mc, () -> {
|
||||||
|
if (mc.level == null || mc.player == null) return Boolean.FALSE;
|
||||||
|
BlockState s = mc.level.getBlockState(pos);
|
||||||
|
if (s.isAir()) return Boolean.FALSE;
|
||||||
|
return willBreakThisTick(mc, pos, s);
|
||||||
|
}, Boolean.FALSE));
|
||||||
|
|
||||||
|
if (!willBreak) {
|
||||||
|
|
||||||
|
return MacroClientThread.callOnClientThread(mc, () -> {
|
||||||
|
if (mc.level == null || mc.gameMode == null || mc.player == null) return -1;
|
||||||
|
if (mc.level.getBlockState(pos).isAir()) return 1;
|
||||||
|
if (action.sneak) holdMacroSneak(mc, action.sneakMode);
|
||||||
|
mc.gameMode.continueDestroyBlock(pos, dir);
|
||||||
|
mc.player.swing(InteractionHand.MAIN_HAND);
|
||||||
|
return mc.level.getBlockState(pos).isAir() ? 1 : 0;
|
||||||
|
}, -1, action.isTickAligned());
|
||||||
|
}
|
||||||
|
|
||||||
|
long ns = Math.abs((long) action.interactCustomMs) * 1_000_000L;
|
||||||
|
int broke;
|
||||||
|
if (action.interactCustomMs < 0) {
|
||||||
|
|
||||||
|
MacroClientThread.runOnClientThread(mc, () -> sendBlockInteract(mc, pos, dir), action.isTickAligned());
|
||||||
|
if (ns > 0) java.util.concurrent.locks.LockSupport.parkNanos(ns);
|
||||||
|
broke = MacroClientThread.callOnClientThread(mc, () -> breakAndDetectOnClient(mc, pos, dir), -1, action.isTickAligned());
|
||||||
|
} else {
|
||||||
|
|
||||||
|
broke = MacroClientThread.callOnClientThread(mc, () -> breakAndDetectOnClient(mc, pos, dir), -1, action.isTickAligned());
|
||||||
|
if (ns > 0) java.util.concurrent.locks.LockSupport.parkNanos(ns);
|
||||||
|
MacroClientThread.runOnClientThread(mc, () -> sendBlockInteract(mc, pos, dir), action.isTickAligned());
|
||||||
|
}
|
||||||
|
return broke;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int breakAndDetectOnClient(Minecraft mc, BlockPos pos, Direction dir) {
|
||||||
|
if (mc.level == null || mc.gameMode == null || mc.player == null) return -1;
|
||||||
|
if (mc.level.getBlockState(pos).isAir()) return 1;
|
||||||
|
mc.gameMode.continueDestroyBlock(pos, dir);
|
||||||
|
mc.player.swing(InteractionHand.MAIN_HAND);
|
||||||
|
return mc.level.getBlockState(pos).isAir() ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean willBreakThisTick(Minecraft mc, BlockPos pos, BlockState state) {
|
||||||
|
float progress = (mc.gameMode instanceof AutismMultiPlayerGameModeAccessor acc) ? acc.autism$getDestroyProgress() : 0f;
|
||||||
|
float delta = state.getDestroyProgress(mc.player, mc.player.level(), pos);
|
||||||
|
return progress > 0f && (progress + delta) >= 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean willBreakNextTick(Minecraft mc, BlockPos pos, BlockState state) {
|
||||||
|
float progress = (mc.gameMode instanceof AutismMultiPlayerGameModeAccessor acc) ? acc.autism$getDestroyProgress() : 0f;
|
||||||
|
float delta = state.getDestroyProgress(mc.player, mc.player.level(), pos);
|
||||||
|
return progress > 0f && (progress + 2f * delta) >= 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void runBreakNextStepsOnce(Minecraft mc, BreakAction action, boolean[] fired, boolean[] missingWarned) throws InterruptedException {
|
||||||
|
if (fired != null && fired[0]) return;
|
||||||
|
java.util.List<MacroAction> actions = action.getNextActions();
|
||||||
|
if (actions == null || actions.isEmpty()) {
|
||||||
|
if (missingWarned != null && !missingWarned[0]) {
|
||||||
|
missingWarned[0] = true;
|
||||||
|
AutismClientMessaging.sendPrefixed("\u00a7cBreak: no next steps found. Add action(s) after this Break step.");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fired != null) fired[0] = true;
|
||||||
|
action.markNextActionsRan();
|
||||||
|
String names = actions.size() == 1 ? actions.get(0).getDisplayName() : actions.size() + " steps";
|
||||||
|
MacroLifecycle.setCurrentStatus("Break: next " + names);
|
||||||
|
AutismModule module = AutismModule.get();
|
||||||
|
boolean previousSuppressPostGui = Boolean.TRUE.equals(MacroExecutor.SUPPRESS_POST_GUI_AFTER.get());
|
||||||
|
try {
|
||||||
|
MacroExecutor.SUPPRESS_POST_GUI_AFTER.set(true);
|
||||||
|
for (MacroAction next : actions) {
|
||||||
|
if (next == null || !next.isEnabled() || !MacroLifecycle.isCurrentRunActive()) continue;
|
||||||
|
try {
|
||||||
|
MacroDispatcher.executeSingleActionWithWaits(mc, next, module);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
AutismClientMessaging.sendPrefixed("\u00a7cBreak next step failed (" + next.getDisplayName() + "): " + MacroRace.safeThrowableMessage(t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
MacroExecutor.SUPPRESS_POST_GUI_AFTER.set(previousSuppressPostGui);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void runBreakNextStepsAfterCompletion(Minecraft mc, BreakAction action, boolean[] fired,
|
||||||
|
boolean[] missingWarned) throws InterruptedException {
|
||||||
|
if (!action.interact || !action.runNextSteps || action.interactTiming == null) return;
|
||||||
|
switch (action.interactTiming) {
|
||||||
|
case AFTER -> runBreakNextStepsOnce(mc, action, fired, missingWarned);
|
||||||
|
case AFTER_PLUS -> {
|
||||||
|
MacroExecutor.waitOneTick();
|
||||||
|
runBreakNextStepsOnce(mc, action, fired, missingWarned);
|
||||||
|
}
|
||||||
|
case CUSTOM -> {
|
||||||
|
if (action.interactCustomMs >= 0) {
|
||||||
|
long ns = Math.abs((long) action.interactCustomMs) * 1_000_000L;
|
||||||
|
if (ns > 0) java.util.concurrent.locks.LockSupport.parkNanos(ns);
|
||||||
|
runBreakNextStepsOnce(mc, action, fired, missingWarned);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendBlockInteract(Minecraft mc, BlockPos pos, Direction dir) {
|
||||||
|
if (mc.gameMode == null || mc.player == null) return;
|
||||||
|
Vec3 hit = Vec3.atCenterOf(pos).add(dir.getStepX() * 0.5, dir.getStepY() * 0.5, dir.getStepZ() * 0.5);
|
||||||
|
BlockHitResult blockHit = new BlockHitResult(hit, dir, pos, false);
|
||||||
|
mc.gameMode.useItemOn(mc.player, InteractionHand.MAIN_HAND, blockHit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void waitForBreakSolid(Minecraft mc, BlockPos pos) throws InterruptedException {
|
||||||
|
while (MacroLifecycle.isCurrentRunActive()) {
|
||||||
|
if (!isInstaBreakAir(mc, pos)) return;
|
||||||
|
Thread.sleep(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void waitBreakClientTicks(int ticks) throws InterruptedException {
|
||||||
|
long start = AutismSharedState.get().getClientTickCounter();
|
||||||
|
while (MacroLifecycle.isCurrentRunActive() && (AutismSharedState.get().getClientTickCounter() - start) < ticks) {
|
||||||
|
Thread.sleep(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void holdMacroSneak(Minecraft mc, String mode) {
|
||||||
|
autismclient.util.AutismMacroSneak.hold(mc, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void releaseMacroSneak(Minecraft mc, String mode) {
|
||||||
|
autismclient.util.AutismMacroSneak.release(mc, mode);
|
||||||
|
if (!autismclient.util.AutismMacroSneak.isPacket(mode)) MacroLifecycle.applyAggregateSneakState(mc);
|
||||||
|
}
|
||||||
|
}
|
||||||
408
src/main/java/autismclient/util/macro/MacroCaptureAutofill.java
Normal file
408
src/main/java/autismclient/util/macro/MacroCaptureAutofill.java
Normal file
|
|
@ -0,0 +1,408 @@
|
||||||
|
package autismclient.util.macro;
|
||||||
|
|
||||||
|
import autismclient.util.AutismCommandSuggestionIds;
|
||||||
|
import autismclient.util.AutismNameHarvest;
|
||||||
|
import autismclient.util.AutismNotifications;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
final class MacroCaptureAutofill {
|
||||||
|
private MacroCaptureAutofill() {
|
||||||
|
}
|
||||||
|
private record CachedSuggestions(java.util.List<String> suggestions, Object connection) {}
|
||||||
|
private record PendingFetch(CompletableFuture<java.util.List<String>> future, Object connection) {}
|
||||||
|
private static final java.util.concurrent.ConcurrentHashMap<String, CachedSuggestions> SUGGESTION_CACHE =
|
||||||
|
new java.util.concurrent.ConcurrentHashMap<>();
|
||||||
|
private static final java.util.concurrent.ConcurrentHashMap<String, PendingFetch> PENDING_FETCHES =
|
||||||
|
new java.util.concurrent.ConcurrentHashMap<>();
|
||||||
|
private static final int SUGGESTION_CACHE_MAX = 256;
|
||||||
|
private static final long SUGGESTION_CACHE_IDLE_RESET_MS = 3000L;
|
||||||
|
|
||||||
|
private static final int TRUNCATED_REPLY_SIZE = 1000;
|
||||||
|
private static final int MIN_SWEEP_CAP = 8;
|
||||||
|
private static final int MAX_SWEEP_NAMES = 60_000;
|
||||||
|
private static final String DRILL_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789_";
|
||||||
|
static volatile long allRunsStoppedAtMs;
|
||||||
|
|
||||||
|
private static final class SuggestionUnion {
|
||||||
|
final Object connection;
|
||||||
|
final java.util.LinkedHashSet<String> names = new java.util.LinkedHashSet<>();
|
||||||
|
final java.util.ArrayDeque<String> sweepQueue = new java.util.ArrayDeque<>();
|
||||||
|
int alphabetCursor;
|
||||||
|
int cap;
|
||||||
|
|
||||||
|
SuggestionUnion(Object connection, String baseQuery) {
|
||||||
|
this.connection = connection;
|
||||||
|
sweepQueue.add(baseQuery);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final java.util.concurrent.ConcurrentHashMap<String, SuggestionUnion> SUGGESTION_UNIONS =
|
||||||
|
new java.util.concurrent.ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public static void expireSuggestionCacheAfterIdle() {
|
||||||
|
long stoppedAt = allRunsStoppedAtMs;
|
||||||
|
if (stoppedAt > 0 && System.currentTimeMillis() - stoppedAt > SUGGESTION_CACHE_IDLE_RESET_MS) {
|
||||||
|
SUGGESTION_CACHE.clear();
|
||||||
|
PENDING_FETCHES.clear();
|
||||||
|
SUGGESTION_UNIONS.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String buildAutofillQuery(String resolvedCommand, CaptureListSelector.Filter filter, String filterText) {
|
||||||
|
String query = resolvedCommand.startsWith("/") ? resolvedCommand : "/" + resolvedCommand;
|
||||||
|
|
||||||
|
if (!query.contains(" ")) query = query + " ";
|
||||||
|
String prefix = filter == CaptureListSelector.Filter.PREFIX && filterText != null ? filterText.trim() : "";
|
||||||
|
if (!prefix.isEmpty() && !prefix.contains(" ") && query.endsWith(" ")) {
|
||||||
|
|
||||||
|
int end = query.length();
|
||||||
|
while (end > 0 && query.charAt(end - 1) == ' ') end--;
|
||||||
|
query = query.substring(0, end) + " " + prefix;
|
||||||
|
}
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static java.util.List<String> capturePool(net.minecraft.client.multiplayer.ClientPacketListener connection,
|
||||||
|
CaptureValueAction action, String query, java.util.List<String> base) {
|
||||||
|
|
||||||
|
if (!action.autofillCacheList && base.size() < TRUNCATED_REPLY_SIZE) return base;
|
||||||
|
SuggestionUnion union = SUGGESTION_UNIONS.compute(query, (key, existing) ->
|
||||||
|
existing != null && existing.connection == connection ? existing : new SuggestionUnion(connection, query));
|
||||||
|
synchronized (union) {
|
||||||
|
union.names.addAll(base);
|
||||||
|
union.cap = Math.max(union.cap, base.size());
|
||||||
|
advanceSweep(union, connection, action.autofillTimeoutMs);
|
||||||
|
java.util.List<String> pool = new java.util.ArrayList<>(union.names);
|
||||||
|
boolean ordered = action.listSelection == CaptureListSelector.Selection.SEQUENTIAL
|
||||||
|
|| action.listSelection == CaptureListSelector.Selection.FIRST
|
||||||
|
|| action.listSelection == CaptureListSelector.Selection.LAST
|
||||||
|
|| action.listSelection == CaptureListSelector.Selection.POSITION;
|
||||||
|
|
||||||
|
if (ordered) pool.sort(String.CASE_INSENSITIVE_ORDER);
|
||||||
|
return pool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void advanceSweep(SuggestionUnion union, net.minecraft.client.multiplayer.ClientPacketListener connection, int timeoutMs) {
|
||||||
|
if (union.names.size() >= MAX_SWEEP_NAMES) { union.sweepQueue.clear(); return; }
|
||||||
|
while (!union.sweepQueue.isEmpty()) {
|
||||||
|
if (union.alphabetCursor >= DRILL_ALPHABET.length()) {
|
||||||
|
union.sweepQueue.poll();
|
||||||
|
union.alphabetCursor = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String target = union.sweepQueue.peek() + DRILL_ALPHABET.charAt(union.alphabetCursor);
|
||||||
|
CachedSuggestions cached = SUGGESTION_CACHE.get(target);
|
||||||
|
if (cached == null || cached.connection() != connection) {
|
||||||
|
fireSuggestionFetch(connection, target, timeoutMs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
union.names.addAll(cached.suggestions());
|
||||||
|
int size = cached.suggestions().size();
|
||||||
|
|
||||||
|
boolean truncated = size >= union.cap && union.cap >= MIN_SWEEP_CAP;
|
||||||
|
union.cap = Math.max(union.cap, size);
|
||||||
|
if (truncated) union.sweepQueue.add(target);
|
||||||
|
union.alphabetCursor++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applyCaptureSuggestions(MacroExecutor.RunState run, CaptureValueAction action,
|
||||||
|
java.util.List<String> suggestions, String query) {
|
||||||
|
CaptureValueAction.Preview preview = action.previewSuggestions(suggestions, query);
|
||||||
|
if (!preview.success()) return;
|
||||||
|
if (run != null) run.variables.setAll(preview.values());
|
||||||
|
else MacroVariables.setAll(preview.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PendingFetch fireSuggestionFetch(net.minecraft.client.multiplayer.ClientPacketListener connection,
|
||||||
|
String query, int timeoutMs) {
|
||||||
|
PendingFetch existing = PENDING_FETCHES.get(query);
|
||||||
|
if (existing != null && existing.connection() == connection && !existing.future().isDone()) return existing;
|
||||||
|
if (PENDING_FETCHES.size() > SUGGESTION_CACHE_MAX) return existing;
|
||||||
|
int requestId = autismclient.util.AutismCommandSuggestionIds.nextMacroId();
|
||||||
|
CompletableFuture<java.util.List<String>> future = new CompletableFuture<>();
|
||||||
|
MacroExecutor.OneShotPacketListener listener = MacroExecutor.awaitReceive(packet -> {
|
||||||
|
if (!(packet instanceof net.minecraft.network.protocol.game.ClientboundCommandSuggestionsPacket reply)
|
||||||
|
|| reply.id() != requestId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
java.util.List<String> texts = new java.util.ArrayList<>();
|
||||||
|
if (reply.suggestions() != null) {
|
||||||
|
for (var entry : reply.suggestions()) {
|
||||||
|
if (entry != null && entry.text() != null && !entry.text().isBlank()) texts.add(entry.text());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
future.complete(texts);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
PendingFetch pending = new PendingFetch(future, connection);
|
||||||
|
PENDING_FETCHES.put(query, pending);
|
||||||
|
future.completeOnTimeout(null, timeoutMs > 0 ? Math.max(1000, timeoutMs) : 30_000,
|
||||||
|
java.util.concurrent.TimeUnit.MILLISECONDS);
|
||||||
|
future.whenComplete((texts, error) -> {
|
||||||
|
listener.cancel();
|
||||||
|
PENDING_FETCHES.remove(query, pending);
|
||||||
|
|
||||||
|
if (texts != null) {
|
||||||
|
if (SUGGESTION_CACHE.size() >= SUGGESTION_CACHE_MAX) SUGGESTION_CACHE.clear();
|
||||||
|
SUGGESTION_CACHE.put(query, new CachedSuggestions(java.util.List.copyOf(texts), connection));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
connection.send(new net.minecraft.network.protocol.game.ServerboundCommandSuggestionPacket(requestId, query));
|
||||||
|
} catch (Throwable error) {
|
||||||
|
future.complete(null);
|
||||||
|
}
|
||||||
|
return pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void runCaptureCommandAutofill(Minecraft mc, CaptureValueAction action) {
|
||||||
|
MacroExecutor.RunState run = MacroExecutor.CURRENT_RUN.get();
|
||||||
|
MacroPrearm.clearCaptureOutputs(action);
|
||||||
|
MacroTemplate.Resolution resolved = MacroVariables.resolve(action.autofillCommand, mc);
|
||||||
|
if (!resolved.success() || resolved.value().isBlank()) return;
|
||||||
|
String query = buildAutofillQuery(resolved.value(), action.listFilter, action.listFilterText);
|
||||||
|
var connection = mc.getConnection();
|
||||||
|
if (connection == null) return;
|
||||||
|
|
||||||
|
if (action.autofillCacheList) {
|
||||||
|
CachedSuggestions listCache = SUGGESTION_CACHE.get(query);
|
||||||
|
if (listCache != null && listCache.connection() == connection && !listCache.suggestions().isEmpty()) {
|
||||||
|
MacroLifecycle.setCurrentStatus("Using cached suggestions");
|
||||||
|
applyCaptureSuggestions(run, action, listCache.suggestions(), query);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MacroLifecycle.setCurrentStatus("Sweeping command suggestions");
|
||||||
|
java.util.List<String> all = sweepAutofillCommand(connection, query);
|
||||||
|
if (all.isEmpty()) return;
|
||||||
|
if (SUGGESTION_CACHE.size() >= SUGGESTION_CACHE_MAX) SUGGESTION_CACHE.clear();
|
||||||
|
SUGGESTION_CACHE.put(query, new CachedSuggestions(java.util.List.copyOf(all), connection));
|
||||||
|
applyCaptureSuggestions(run, action, all, query);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CachedSuggestions cached = SUGGESTION_CACHE.get(query);
|
||||||
|
|
||||||
|
if (cached != null && (cached.connection() != connection || cached.suggestions().isEmpty())) cached = null;
|
||||||
|
|
||||||
|
if (cached != null) {
|
||||||
|
|
||||||
|
MacroLifecycle.setCurrentStatus("Using cached suggestions");
|
||||||
|
applyCaptureSuggestions(run, action, capturePool(connection, action, query, cached.suggestions()), query);
|
||||||
|
fireSuggestionFetch(connection, query, action.autofillTimeoutMs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MacroLifecycle.setCurrentStatus("Requesting command suggestions");
|
||||||
|
PendingFetch pending = fireSuggestionFetch(connection, query, action.autofillTimeoutMs);
|
||||||
|
if (pending == null) return;
|
||||||
|
if (!MacroRace.waitForFutureDone(pending.future(), 0)) return;
|
||||||
|
java.util.List<String> suggestions = pending.future().getNow(null);
|
||||||
|
if (suggestions == null || suggestions.isEmpty()) return;
|
||||||
|
applyCaptureSuggestions(run, action, capturePool(connection, action, query, suggestions), query);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static java.util.List<String> sweepAutofillCommand(net.minecraft.client.multiplayer.ClientPacketListener connection, String query) {
|
||||||
|
autismclient.util.AutismNameHarvest.Control control = new autismclient.util.AutismNameHarvest.Control() {
|
||||||
|
@Override public boolean cancelled() {
|
||||||
|
Minecraft mc = Minecraft.getInstance();
|
||||||
|
return !MacroLifecycle.isCurrentRunActive() || mc == null || mc.getConnection() != connection;
|
||||||
|
}
|
||||||
|
@Override public boolean paused() { return false; }
|
||||||
|
@Override public int limit() { return MAX_SWEEP_NAMES; }
|
||||||
|
@Override public int maxQueries() { return 50_000; }
|
||||||
|
@Override public boolean usernamesOnly() { return false; }
|
||||||
|
};
|
||||||
|
java.util.List<String> all = autismclient.util.AutismNameHarvest.sweepCommand(connection, query, control);
|
||||||
|
all.sort(String.CASE_INSENSITIVE_ORDER);
|
||||||
|
return all;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void runCaptureNamescrape(Minecraft mc, CaptureValueAction action) {
|
||||||
|
MacroExecutor.RunState run = MacroExecutor.CURRENT_RUN.get();
|
||||||
|
MacroPrearm.clearCaptureOutputs(action);
|
||||||
|
var connection = mc.getConnection();
|
||||||
|
if (connection == null) return;
|
||||||
|
String cacheKey = " | ||||||