From 026800738bea67447be33cd67a680ecaf42e9d66 Mon Sep 17 00:00:00 2001 From: sectersion Date: Sun, 11 Oct 2026 16:01:35 +0100 Subject: [PATCH] Refactor: extract MacroPacketWait from MacroExecutor (upstream #4) Move 9 packet match/wait methods (normalizePacketKey, packetKeyMatches, packetNameMatches, ParsedPacketTarget, parsedPacketTarget, matchesPacketTarget, awaitPacket, awaitChat, awaitCaptureValueChat, captureSelectionState) verbatim into MacroPacketWait. Shared MacroExecutor members referenced as MacroExecutor.xxx (mechanical qualification only); same-package captureSelectionState kept as delegate. All bodies verified identical against HEAD modulo that qualification. --- .../util/macro/MacroExecutor.java | 244 +++--------------- .../util/macro/MacroPacketWait.java | 197 ++++++++++++++ 2 files changed, 229 insertions(+), 212 deletions(-) create mode 100644 src/main/java/autismclient/util/macro/MacroPacketWait.java diff --git a/src/main/java/autismclient/util/macro/MacroExecutor.java b/src/main/java/autismclient/util/macro/MacroExecutor.java index dd04bdc..102b0f3 100644 --- a/src/main/java/autismclient/util/macro/MacroExecutor.java +++ b/src/main/java/autismclient/util/macro/MacroExecutor.java @@ -97,29 +97,29 @@ public class MacroExecutor { private static final java.util.concurrent.ConcurrentHashMap RUNS_BY_ID = new java.util.concurrent.ConcurrentHashMap<>(); private static final AtomicLong RUN_STATE_REVISION = new AtomicLong(); - private static final ThreadLocal CURRENT_RUN = new ThreadLocal<>(); + static final ThreadLocal CURRENT_RUN = new ThreadLocal<>(); private static final ThreadLocal SUPPRESS_POST_GUI_AFTER = ThreadLocal.withInitial(() -> false); private static volatile RunState primaryRun = null; private static volatile boolean visibleRunActive; private static AutismMacro currentMacro = null; private static Thread macroThread = null; - private static final AtomicReference lastReceivedPacket = new AtomicReference<>(""); + static final AtomicReference lastReceivedPacket = new AtomicReference<>(""); private static String currentStatus = ""; private static volatile int currentStepIndex = -1; private static volatile int totalSteps = 0; - private static final AtomicBoolean isWaitingForPacket = new AtomicBoolean(false); - private static final AtomicReference waitingPacketName = new AtomicReference<>(""); - private static final Object packetWaitLock = new Object(); + static final AtomicBoolean isWaitingForPacket = new AtomicBoolean(false); + static final AtomicReference waitingPacketName = new AtomicReference<>(""); + static final Object packetWaitLock = new Object(); - private static final AtomicBoolean isWaitingForChat = new AtomicBoolean(false); + static final AtomicBoolean isWaitingForChat = new AtomicBoolean(false); static final AtomicReference waitingChatPattern = new AtomicReference<>(""); static final AtomicBoolean waitingChatIsRegex = new AtomicBoolean(false); static final AtomicReference waitingChatPatternJson = new AtomicReference<>(""); static final java.util.concurrent.atomic.AtomicInteger waitingChatFuzzyPercent = new java.util.concurrent.atomic.AtomicInteger(100); - private static final AtomicReference lastMatchedChat = new AtomicReference<>(""); + static final AtomicReference lastMatchedChat = new AtomicReference<>(""); static final Object recentChatLock = new Object(); static final Deque recentChatMessages = new ArrayDeque<>(); static final int MAX_RECENT_CHAT_MESSAGES = 256; @@ -129,8 +129,8 @@ public class MacroExecutor { static String recentChatServerKey = ""; static volatile String recentChatServerKeyMirror = ""; - private static volatile CompletableFuture activePacketFuture = null; - private static volatile CompletableFuture activeChatFuture = null; + static volatile CompletableFuture activePacketFuture = null; + static volatile CompletableFuture activeChatFuture = null; private static volatile CompletableFuture activeBaritoneGoalFuture = null; private static final AtomicReference BARITONE_OWNER = new AtomicReference<>(); @@ -150,7 +150,7 @@ public class MacroExecutor { private static final ThreadLocal REPEAT_PACKET_CONTEXT = new ThreadLocal<>(); private static final ThreadLocal REPEAT_PACKET_SKIP = ThreadLocal.withInitial(() -> 0); - private static final class RunState { + static final class RunState { final long id; final AutismMacro sourceMacro; final AutismMacro macro; @@ -345,7 +345,7 @@ public class MacroExecutor { return run != null ? run.backgroundMoves : backgroundMoves; } - private static void setCurrentStatus(String status) { + static void setCurrentStatus(String status) { RunState run = CURRENT_RUN.get(); if (run != null) run.status = status == null ? "" : status; currentStatus = status == null ? "" : status; @@ -1168,11 +1168,11 @@ public class MacroExecutor { } } if (targets.isEmpty()) { - if (prearmed == null) awaitPacket(""); + if (prearmed == null) MacroPacketWait.awaitPacket(""); } else { for (int targetIndex = Math.min(startTarget, targets.size()); targetIndex < targets.size(); targetIndex++) { if (!isCurrentRunActive()) break; - awaitPacket(targets.get(targetIndex)); + MacroPacketWait.awaitPacket(targets.get(targetIndex)); } } copyLastMatchedPacket(packetAction); @@ -1470,7 +1470,7 @@ public class MacroExecutor { else if (action instanceof WaitForChatAction) { WaitForChatAction wca = (WaitForChatAction) action; setCurrentStatus("Waiting for chat: " + wca.pattern); - if (!waitForPrearmed(wca, wca.timeoutMs)) awaitChat(wca); + if (!waitForPrearmed(wca, wca.timeoutMs)) MacroPacketWait.awaitChat(wca); } else if (action instanceof WaitForEntityAction) { WaitForEntityAction wea = (WaitForEntityAction) action; @@ -2012,191 +2012,11 @@ public class MacroExecutor { } } - private static String normalizePacketKey(String value) { - - return AutismPacketNamer.normalizePacketKey(value); - } - - private static boolean packetKeyMatches(String normalizedExpected, String candidate) { - if (normalizedExpected.isEmpty() || candidate == null || candidate.isEmpty()) return false; - - String normalizedCandidate = normalizePacketKey(candidate); - if (normalizedCandidate.isEmpty()) return false; - if (normalizedExpected.equals(normalizedCandidate)) return true; - - if (normalizedCandidate.endsWith("packet")) { - String strippedCandidate = normalizedCandidate.substring(0, normalizedCandidate.length() - "packet".length()); - if (normalizedExpected.equals(strippedCandidate)) return true; - } - - return normalizedExpected.endsWith(normalizedCandidate) || normalizedCandidate.endsWith(normalizedExpected); - } - - private static boolean packetNameMatches(String expected, String candidate) { - if (expected == null || expected.isEmpty()) return false; - return packetKeyMatches(normalizePacketKey(expected), candidate); - } - - private record ParsedPacketTarget(String direction, String expectedName, String normalizedExpected) {} - private static final java.util.concurrent.ConcurrentHashMap PARSED_TARGET_CACHE = - new java.util.concurrent.ConcurrentHashMap<>(); - - private static final ParsedPacketTarget EMPTY_PACKET_TARGET = new ParsedPacketTarget("", "", ""); - - private static ParsedPacketTarget parsedPacketTarget(String target) { - if (target == null) return EMPTY_PACKET_TARGET; - if (PARSED_TARGET_CACHE.size() > 256) PARSED_TARGET_CACHE.clear(); - return PARSED_TARGET_CACHE.computeIfAbsent(target, t -> { - String expectedName = WaitForPacketAction.getPacketName(t); - return new ParsedPacketTarget(WaitForPacketAction.getDirection(t), expectedName, normalizePacketKey(expectedName)); - }); - } - - private static boolean matchesPacketTarget(String target, Packet packet, String direction) { - if (packet == null) return false; - - ParsedPacketTarget parsed = parsedPacketTarget(target); - if (!parsed.direction().isEmpty() && !parsed.direction().equalsIgnoreCase(direction)) return false; - if (parsed.expectedName().isEmpty()) return true; - - String normalizedExpected = parsed.normalizedExpected(); - if (packetKeyMatches(normalizedExpected, AutismPacketNamer.getFriendlyName(packet, direction))) return true; - if (packetKeyMatches(normalizedExpected, AutismPacketNamer.getFriendlyName(packet))) return true; - if (packetKeyMatches(normalizedExpected, packet.getClass().getSimpleName())) return true; - - @SuppressWarnings("unchecked") - String fullName = AutismPacketRegistry.getName((Class>) packet.getClass()); - return packetKeyMatches(normalizedExpected, fullName); - } - - private static void awaitPacket(String target) { - RunState run = CURRENT_RUN.get(); - if (run != null) { - CompletableFuture future = new CompletableFuture<>(); - String normalizedTarget = WaitForPacketAction.normalizeTarget(target); - synchronized (run.packetWaitLock) { - run.waitingPacketName.set(normalizedTarget); - run.packetFuture = future; - run.waitingForPacket.set(true); - } - try { - setCurrentStatus(normalizedTarget.isEmpty() - ? "Waiting for packet: Any" - : "Waiting for packet: " + WaitForPacketAction.getDisplayLabel(normalizedTarget)); - waitForCondition(future); - } finally { - synchronized (run.packetWaitLock) { - if (run.packetFuture == future) { - run.waitingForPacket.set(false); - run.waitingPacketName.set(""); - run.packetFuture = null; - } - } - } - return; - } - CompletableFuture future = new CompletableFuture<>(); - String normalizedTarget = WaitForPacketAction.normalizeTarget(target); - synchronized (packetWaitLock) { - waitingPacketName.set(normalizedTarget); - lastReceivedPacket.set(""); - activePacketFuture = future; - isWaitingForPacket.set(true); - } - try { - setCurrentStatus(normalizedTarget.isEmpty() - ? "Waiting for packet: Any" - : "Waiting for packet: " + WaitForPacketAction.getDisplayLabel(normalizedTarget)); - waitForCondition(future); - } finally { - synchronized (packetWaitLock) { - if (activePacketFuture == future) { - isWaitingForPacket.set(false); - waitingPacketName.set(""); - activePacketFuture = null; - } - } - } - } - - private static void awaitChat(WaitForChatAction wca) { - RunState run = CURRENT_RUN.get(); - if (run != null) { - clearCaptureOutputs(wca); - CompletableFuture future = new CompletableFuture<>(); - run.chatFuture = future; - run.waitingChatAction = wca; - run.waitingForChat.set(true); - try { - if (wca.timeoutMs > 0) future.completeOnTimeout(null, wca.timeoutMs, java.util.concurrent.TimeUnit.MILLISECONDS); - waitForCondition(future); - commitStagedCapture(wca); - } finally { - if (run.chatFuture == future) { - run.waitingForChat.set(false); - run.waitingChatAction = null; - run.chatFuture = null; - } - } - return; - } - CompletableFuture future = new CompletableFuture<>(); - activeChatFuture = future; - isWaitingForChat.set(true); - waitingChatPattern.set(MacroChatText.normalizeManualText(wca.pattern)); - waitingChatPatternJson.set(wca.patternJson == null ? "" : wca.patternJson); - waitingChatIsRegex.set(wca.useRegex); - waitingChatFuzzyPercent.set(WaitForChatAction.clampFuzzyPercent(wca.fuzzyPercent)); - lastMatchedChat.set(""); - try { - if (wca.timeoutMs > 0) future.completeOnTimeout(null, wca.timeoutMs, java.util.concurrent.TimeUnit.MILLISECONDS); - waitForCondition(future); - } finally { - isWaitingForChat.set(false); - waitingChatPattern.set(""); - waitingChatPatternJson.set(""); - waitingChatIsRegex.set(false); - waitingChatFuzzyPercent.set(100); - activeChatFuture = null; - } - } - - private static void awaitCaptureValueChat(CaptureValueAction action) { - if (action == null) return; - RunState run = CURRENT_RUN.get(); - clearCaptureOutputs(action); - OneShotPacketListener listener = awaitReceive(packet -> { - ChatCapture capture = MacroChatObserve.extractIncomingChat(packet); - if (capture == null) return false; - RecentChatMessage message = new RecentChatMessage( - capture.sender() == null ? "" : capture.sender(), - capture.message() == null ? "" : capture.message(), - capture.displayText() == null ? "" : capture.displayText(), - capture.displayComponent(), - capture.source(), - System.currentTimeMillis() - ); - CaptureValueAction.Preview preview = action.previewChat(message); - if (!preview.success()) return false; - if (run != null) run.stagedCaptures.put(action, preview.values()); - else MacroVariables.setAll(preview.values()); - return true; - }); - try { - if (waitForFutureDone(listener.future, 0) && run != null) commitStagedCapture(action); - } finally { - listener.cancel(); - } - } - static CaptureListSelector.State captureSelectionState(MacroAction action) { - RunState run = CURRENT_RUN.get(); - if (run == null || action == null) return null; - synchronized (run.captureSelection) { - return run.captureSelection.computeIfAbsent(action, a -> new CaptureListSelector.State()); - } + return MacroPacketWait.captureSelectionState(action); } + private record CachedSuggestions(java.util.List suggestions, Object connection) {} private record PendingFetch(CompletableFuture> future, Object connection) {} private static final java.util.concurrent.ConcurrentHashMap SUGGESTION_CACHE = @@ -2472,7 +2292,7 @@ public class MacroExecutor { if (action.waitForTrigger) { CaptureValueAction.Source source = action.source == null ? CaptureValueAction.Source.GUI_TITLE : action.source; setCurrentStatus("Waiting for capture: " + source.name().toLowerCase(Locale.ROOT).replace('_', ' ')); - if (source == CaptureValueAction.Source.RECENT_CHAT) awaitCaptureValueChat(action); + if (source == CaptureValueAction.Source.RECENT_CHAT) MacroPacketWait.awaitCaptureValueChat(action); else if (source == CaptureValueAction.Source.SCOREBOARD) awaitCaptureValueScoreboard(mc, action); else awaitCaptureValueState(mc, action); return; @@ -2592,7 +2412,7 @@ public class MacroExecutor { } } - private static void waitForCondition(CompletableFuture future) { + static void waitForCondition(CompletableFuture future) { Thread t = Thread.currentThread(); future.whenComplete((v, e) -> java.util.concurrent.locks.LockSupport.unpark(t)); @@ -3168,7 +2988,7 @@ public class MacroExecutor { action.matchedDirection = run.lastMatchedPacketDirection; } - private static void clearCaptureOutputs(MacroAction action) { + static void clearCaptureOutputs(MacroAction action) { RunState run = CURRENT_RUN.get(); if (run == null || action == null) return; run.stagedCaptures.remove(action); @@ -3357,7 +3177,7 @@ public class MacroExecutor { run.stagedCaptures.put(action, java.util.Map.copyOf(values)); } - private static void commitStagedCapture(MacroAction action) { + static void commitStagedCapture(MacroAction action) { RunState run = CURRENT_RUN.get(); if (run == null || action == null) return; java.util.Map values = run.stagedCaptures.remove(action); @@ -3401,14 +3221,14 @@ public class MacroExecutor { OneShotPacketListener send = null; OneShotPacketListener recv = null; if (!"S2C".equalsIgnoreCase(direction)) { - send = awaitSend(packet -> matchesPacketTarget(normalizedTarget, packet, "C2S")); + send = awaitSend(packet -> MacroPacketWait.matchesPacketTarget(normalizedTarget, packet, "C2S")); send.future.thenAccept(packet -> { recordMatchedPacket(action, packet, "C2S"); matched.complete(packet); }); } if (!"C2S".equalsIgnoreCase(direction)) { - recv = awaitReceive(packet -> matchesPacketTarget(normalizedTarget, packet, "S2C")); + recv = awaitReceive(packet -> MacroPacketWait.matchesPacketTarget(normalizedTarget, packet, "S2C")); recv.future.thenAccept(packet -> { recordMatchedPacket(action, packet, "S2C"); matched.complete(packet); @@ -4080,14 +3900,14 @@ public class MacroExecutor { TimedOneShotPacketListener recv = awaitTimedReceive((packet, direction, now) -> { if (targets.isEmpty()) return true; for (String target : targets) { - if (matchesPacketTarget(target, packet, "S2C")) return true; + if (MacroPacketWait.matchesPacketTarget(target, packet, "S2C")) return true; } return false; }); TimedOneShotPacketListener send = awaitTimedSend((packet, direction, now) -> { if (targets.isEmpty()) return true; for (String target : targets) { - if (matchesPacketTarget(target, packet, "C2S")) return true; + if (MacroPacketWait.matchesPacketTarget(target, packet, "C2S")) return true; } return false; }); @@ -4248,14 +4068,14 @@ public class MacroExecutor { OneShotPacketListener recv = awaitReceive(packet -> { if (targets.isEmpty()) return true; for (String target : targets) { - if (matchesPacketTarget(target, packet, "S2C")) return true; + if (MacroPacketWait.matchesPacketTarget(target, packet, "S2C")) return true; } return false; }); OneShotPacketListener send = awaitSend(packet -> { if (targets.isEmpty()) return true; for (String target : targets) { - if (matchesPacketTarget(target, packet, "C2S")) return true; + if (MacroPacketWait.matchesPacketTarget(target, packet, "C2S")) return true; } return false; }); @@ -4372,7 +4192,7 @@ public class MacroExecutor { return null; } - private static boolean waitForFutureDone(CompletableFuture future, long timeoutMs) { + static boolean waitForFutureDone(CompletableFuture future, long timeoutMs) { Thread t = Thread.currentThread(); long start = System.nanoTime(); long timeoutNanos = timeoutMs <= 0 ? Long.MAX_VALUE : timeoutMs * 1_000_000L; @@ -4420,11 +4240,11 @@ public class MacroExecutor { } java.util.List targets = packetAction.effectiveList(); if (targets.isEmpty()) { - awaitPacket(""); + MacroPacketWait.awaitPacket(""); } else { for (String target : targets) { if (!isCurrentRunActive()) break; - awaitPacket(target); + MacroPacketWait.awaitPacket(target); } } copyLastMatchedPacket(packetAction); @@ -4511,7 +4331,7 @@ public class MacroExecutor { } else if (action instanceof WaitForChatAction) { WaitForChatAction wca = (WaitForChatAction) action; setCurrentStatus("Waiting for chat: " + wca.pattern); - awaitChat(wca); + MacroPacketWait.awaitChat(wca); } else if (action instanceof WaitForEntityAction) { WaitForEntityAction wea = (WaitForEntityAction) action; setCurrentStatus(wea.getDisplayName()); @@ -7976,7 +7796,7 @@ public class MacroExecutor { if (!run.running.get() || !run.waitingForPacket.get()) continue; CompletableFuture f = null; synchronized (run.packetWaitLock) { - if (run.waitingForPacket.get() && matchesPacketTarget(run.waitingPacketName.get(), packet, direction)) { + if (run.waitingForPacket.get() && MacroPacketWait.matchesPacketTarget(run.waitingPacketName.get(), packet, direction)) { run.lastMatchedPacket = packet; run.lastMatchedPacketDirection = direction; f = run.packetFuture; @@ -7992,7 +7812,7 @@ public class MacroExecutor { if (!isWaitingForPacket.get()) return; String target = waitingPacketName.get(); - if (matchesPacketTarget(target, packet, direction)) { + if (MacroPacketWait.matchesPacketTarget(target, packet, direction)) { lastReceivedPacket.set(direction + " " + AutismPacketNamer.getFriendlyName(packet, direction)); f = activePacketFuture; } diff --git a/src/main/java/autismclient/util/macro/MacroPacketWait.java b/src/main/java/autismclient/util/macro/MacroPacketWait.java new file mode 100644 index 0000000..fc31a5d --- /dev/null +++ b/src/main/java/autismclient/util/macro/MacroPacketWait.java @@ -0,0 +1,197 @@ +package autismclient.util.macro; + +import autismclient.util.AutismPacketNamer; +import autismclient.util.AutismPacketRegistry; +import net.minecraft.client.Minecraft; +import net.minecraft.network.protocol.Packet; + +import java.util.concurrent.CompletableFuture; + +final class MacroPacketWait { + private MacroPacketWait() { + } + private static String normalizePacketKey(String value) { + + return AutismPacketNamer.normalizePacketKey(value); + } + + private static boolean packetKeyMatches(String normalizedExpected, String candidate) { + if (normalizedExpected.isEmpty() || candidate == null || candidate.isEmpty()) return false; + + String normalizedCandidate = normalizePacketKey(candidate); + if (normalizedCandidate.isEmpty()) return false; + if (normalizedExpected.equals(normalizedCandidate)) return true; + + if (normalizedCandidate.endsWith("packet")) { + String strippedCandidate = normalizedCandidate.substring(0, normalizedCandidate.length() - "packet".length()); + if (normalizedExpected.equals(strippedCandidate)) return true; + } + + return normalizedExpected.endsWith(normalizedCandidate) || normalizedCandidate.endsWith(normalizedExpected); + } + + private static boolean packetNameMatches(String expected, String candidate) { + if (expected == null || expected.isEmpty()) return false; + return packetKeyMatches(normalizePacketKey(expected), candidate); + } + + private record ParsedPacketTarget(String direction, String expectedName, String normalizedExpected) {} + private static final java.util.concurrent.ConcurrentHashMap PARSED_TARGET_CACHE = + new java.util.concurrent.ConcurrentHashMap<>(); + + private static final ParsedPacketTarget EMPTY_PACKET_TARGET = new ParsedPacketTarget("", "", ""); + + private static ParsedPacketTarget parsedPacketTarget(String target) { + if (target == null) return EMPTY_PACKET_TARGET; + if (PARSED_TARGET_CACHE.size() > 256) PARSED_TARGET_CACHE.clear(); + return PARSED_TARGET_CACHE.computeIfAbsent(target, t -> { + String expectedName = WaitForPacketAction.getPacketName(t); + return new ParsedPacketTarget(WaitForPacketAction.getDirection(t), expectedName, normalizePacketKey(expectedName)); + }); + } + + public static boolean matchesPacketTarget(String target, Packet packet, String direction) { + if (packet == null) return false; + + ParsedPacketTarget parsed = parsedPacketTarget(target); + if (!parsed.direction().isEmpty() && !parsed.direction().equalsIgnoreCase(direction)) return false; + if (parsed.expectedName().isEmpty()) return true; + + String normalizedExpected = parsed.normalizedExpected(); + if (packetKeyMatches(normalizedExpected, AutismPacketNamer.getFriendlyName(packet, direction))) return true; + if (packetKeyMatches(normalizedExpected, AutismPacketNamer.getFriendlyName(packet))) return true; + if (packetKeyMatches(normalizedExpected, packet.getClass().getSimpleName())) return true; + + @SuppressWarnings("unchecked") + String fullName = AutismPacketRegistry.getName((Class>) packet.getClass()); + return packetKeyMatches(normalizedExpected, fullName); + } + + public static void awaitPacket(String target) { + MacroExecutor.RunState run = MacroExecutor.CURRENT_RUN.get(); + if (run != null) { + CompletableFuture future = new CompletableFuture<>(); + String normalizedTarget = WaitForPacketAction.normalizeTarget(target); + synchronized (run.packetWaitLock) { + run.waitingPacketName.set(normalizedTarget); + run.packetFuture = future; + run.waitingForPacket.set(true); + } + try { + MacroExecutor.setCurrentStatus(normalizedTarget.isEmpty() + ? "Waiting for packet: Any" + : "Waiting for packet: " + WaitForPacketAction.getDisplayLabel(normalizedTarget)); + MacroExecutor.waitForCondition(future); + } finally { + synchronized (run.packetWaitLock) { + if (run.packetFuture == future) { + run.waitingForPacket.set(false); + run.waitingPacketName.set(""); + run.packetFuture = null; + } + } + } + return; + } + CompletableFuture future = new CompletableFuture<>(); + String normalizedTarget = WaitForPacketAction.normalizeTarget(target); + synchronized (MacroExecutor.packetWaitLock) { + MacroExecutor.waitingPacketName.set(normalizedTarget); + MacroExecutor.lastReceivedPacket.set(""); + MacroExecutor.activePacketFuture = future; + MacroExecutor.isWaitingForPacket.set(true); + } + try { + MacroExecutor.setCurrentStatus(normalizedTarget.isEmpty() + ? "Waiting for packet: Any" + : "Waiting for packet: " + WaitForPacketAction.getDisplayLabel(normalizedTarget)); + MacroExecutor.waitForCondition(future); + } finally { + synchronized (MacroExecutor.packetWaitLock) { + if (MacroExecutor.activePacketFuture == future) { + MacroExecutor.isWaitingForPacket.set(false); + MacroExecutor.waitingPacketName.set(""); + MacroExecutor.activePacketFuture = null; + } + } + } + } + + public static void awaitChat(WaitForChatAction wca) { + MacroExecutor.RunState run = MacroExecutor.CURRENT_RUN.get(); + if (run != null) { + MacroExecutor.clearCaptureOutputs(wca); + CompletableFuture future = new CompletableFuture<>(); + run.chatFuture = future; + run.waitingChatAction = wca; + run.waitingForChat.set(true); + try { + if (wca.timeoutMs > 0) future.completeOnTimeout(null, wca.timeoutMs, java.util.concurrent.TimeUnit.MILLISECONDS); + MacroExecutor.waitForCondition(future); + MacroExecutor.commitStagedCapture(wca); + } finally { + if (run.chatFuture == future) { + run.waitingForChat.set(false); + run.waitingChatAction = null; + run.chatFuture = null; + } + } + return; + } + CompletableFuture future = new CompletableFuture<>(); + MacroExecutor.activeChatFuture = future; + MacroExecutor.isWaitingForChat.set(true); + MacroExecutor.waitingChatPattern.set(MacroChatText.normalizeManualText(wca.pattern)); + MacroExecutor.waitingChatPatternJson.set(wca.patternJson == null ? "" : wca.patternJson); + MacroExecutor.waitingChatIsRegex.set(wca.useRegex); + MacroExecutor.waitingChatFuzzyPercent.set(WaitForChatAction.clampFuzzyPercent(wca.fuzzyPercent)); + MacroExecutor.lastMatchedChat.set(""); + try { + if (wca.timeoutMs > 0) future.completeOnTimeout(null, wca.timeoutMs, java.util.concurrent.TimeUnit.MILLISECONDS); + MacroExecutor.waitForCondition(future); + } finally { + MacroExecutor.isWaitingForChat.set(false); + MacroExecutor.waitingChatPattern.set(""); + MacroExecutor.waitingChatPatternJson.set(""); + MacroExecutor.waitingChatIsRegex.set(false); + MacroExecutor.waitingChatFuzzyPercent.set(100); + MacroExecutor.activeChatFuture = null; + } + } + + public static void awaitCaptureValueChat(CaptureValueAction action) { + if (action == null) return; + MacroExecutor.RunState run = MacroExecutor.CURRENT_RUN.get(); + MacroExecutor.clearCaptureOutputs(action); + MacroExecutor.OneShotPacketListener listener = MacroExecutor.awaitReceive(packet -> { + MacroExecutor.ChatCapture capture = MacroChatObserve.extractIncomingChat(packet); + if (capture == null) return false; + MacroExecutor.RecentChatMessage message = new MacroExecutor.RecentChatMessage( + capture.sender() == null ? "" : capture.sender(), + capture.message() == null ? "" : capture.message(), + capture.displayText() == null ? "" : capture.displayText(), + capture.displayComponent(), + capture.source(), + System.currentTimeMillis() + ); + CaptureValueAction.Preview preview = action.previewChat(message); + if (!preview.success()) return false; + if (run != null) run.stagedCaptures.put(action, preview.values()); + else MacroVariables.setAll(preview.values()); + return true; + }); + try { + if (MacroExecutor.waitForFutureDone(listener.future, 0) && run != null) MacroExecutor.commitStagedCapture(action); + } finally { + listener.cancel(); + } + } + + static CaptureListSelector.State captureSelectionState(MacroAction action) { + MacroExecutor.RunState run = MacroExecutor.CURRENT_RUN.get(); + if (run == null || action == null) return null; + synchronized (run.captureSelection) { + return run.captureSelection.computeIfAbsent(action, a -> new CaptureListSelector.State()); + } + } +}