From 1632a78fe2247a0e35462705806e85714449dd7f Mon Sep 17 00:00:00 2001 From: lorisj-elefant Date: Mon, 8 Sep 2025 07:49:49 -0700 Subject: [PATCH 1/9] added some code gen logic --- .../altoclef/player2api/AgentSideEffects.java | 30 +- .../player2api/ConversationHistory.java | 2 +- .../altoclef/player2api/EventQueueData.java | 34 +- .../player2api/EventQueueManager.java | 65 +- .../altoclef/player2api/LLMCompleter.java | 76 + .../altoclef/player2api/LockManager.java | 30 + .../adris/altoclef/player2api/Prompts.java | 355 ++++- .../adris/altoclef/player2api/TTSManager.java | 13 +- .../pseudocommands/PseudoCommands.java | 54 + .../codegen/BuildStructure.java | 30 + .../codegen/BuildStructureFromCode.java | 1239 +++++++++++++++++ 11 files changed, 1826 insertions(+), 102 deletions(-) create mode 100644 src/autoclef/java/adris/altoclef/player2api/LLMCompleter.java create mode 100644 src/autoclef/java/adris/altoclef/player2api/LockManager.java create mode 100644 src/autoclef/java/adris/altoclef/player2api/pseudocommands/PseudoCommands.java create mode 100644 src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java create mode 100644 src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructureFromCode.java diff --git a/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java b/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java index fc7193f9..8842a59d 100644 --- a/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java +++ b/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java @@ -1,5 +1,6 @@ package adris.altoclef.player2api; + import java.util.function.Consumer; import org.apache.logging.log4j.LogManager; @@ -7,6 +8,7 @@ import adris.altoclef.AltoClefController; import adris.altoclef.commandsystem.CommandExecutor; +import adris.altoclef.player2api.pseudocommands.PseudoCommands; import net.minecraft.network.chat.Component; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; @@ -14,7 +16,6 @@ public class AgentSideEffects { private static final Logger LOGGER = LogManager.getLogger(); - public sealed interface CommandExecutionStopReason permits CommandExecutionStopReason.Cancelled, CommandExecutionStopReason.Finished, @@ -35,14 +36,16 @@ public static void onEntityMessage(MinecraftServer server, Event.CharacterMessag // message part: if (characterMessage.message() != null && !characterMessage.message().isBlank()) { EventQueueData sendingCharacterData = characterMessage.sendingCharacterData(); - String message = String.format("<%s> %s", sendingCharacterData.getName(), characterMessage.message()); - for(ServerPlayer player : server.getPlayerList().getPlayers()){ + String message = String.format("<%s> %s", sendingCharacterData.getName(), characterMessage.message()); + for (ServerPlayer player : server.getPlayerList().getPlayers()) { // if you are an owner, or close, send to player. - // if(sendingCharacterData.isOwner(player.getUUID()) || isClose(sendingCharacterData, player) ){ - broadcastChatToPlayer(server, message, player); + // if(sendingCharacterData.isOwner(player.getUUID()) || + // isClose(sendingCharacterData, player) ){ + broadcastChatToPlayer(server, message, player); // } } - TTSManager.TTS(characterMessage.message(), sendingCharacterData.getCharacter(), sendingCharacterData.getPlayer2apiService()); + TTSManager.TTS(characterMessage.message(), sendingCharacterData.getCharacter(), + sendingCharacterData.getPlayer2apiService()); EventQueueManager.onAICharacterMessage(characterMessage, characterMessage.sendingCharacterData().getUUID()); } @@ -67,6 +70,19 @@ public static void onCommandListGenerated(AltoClefController mod, String command } else { mod.isStopping = false; } + + PseudoCommands.getPseudocommandOption(commandWithPrefix).ifPresentOrElse( + (pseudoCommand) -> { + PseudoCommands.process(pseudoCommand, commandWithPrefix); + }, + () -> { + executeNormalCommand(mod, commandWithPrefix, onStop); + }); + } + + private static void executeNormalCommand(AltoClefController mod, String commandWithPrefix, + Consumer onStop) { + CommandExecutor cmdExecutor = mod.getCommandExecutor(); cmdExecutor.execute(commandWithPrefix, () -> { if (mod.isStopping) { System.out.printf( @@ -82,7 +98,7 @@ public static void onCommandListGenerated(AltoClefController mod, String command }); } - private static void broadcastChatToPlayer(MinecraftServer server, String message, ServerPlayer player){ + private static void broadcastChatToPlayer(MinecraftServer server, String message, ServerPlayer player) { player.displayClientMessage(Component.literal(message), false); } diff --git a/src/autoclef/java/adris/altoclef/player2api/ConversationHistory.java b/src/autoclef/java/adris/altoclef/player2api/ConversationHistory.java index 35d087e6..f13c018d 100644 --- a/src/autoclef/java/adris/altoclef/player2api/ConversationHistory.java +++ b/src/autoclef/java/adris/altoclef/player2api/ConversationHistory.java @@ -34,7 +34,7 @@ public ConversationHistory(String initialSystemPrompt, String characterName, Str } } - private ConversationHistory(String initialSystemPrompt) { + public ConversationHistory(String initialSystemPrompt) { this.historyFile = null; this.setBaseSystemPrompt(initialSystemPrompt); this.loadedFromFile = false; diff --git a/src/autoclef/java/adris/altoclef/player2api/EventQueueData.java b/src/autoclef/java/adris/altoclef/player2api/EventQueueData.java index f6ffc621..98fbce34 100644 --- a/src/autoclef/java/adris/altoclef/player2api/EventQueueData.java +++ b/src/autoclef/java/adris/altoclef/player2api/EventQueueData.java @@ -1,4 +1,5 @@ package adris.altoclef.player2api; + import java.util.Deque; import java.util.Optional; import java.util.UUID; @@ -58,7 +59,7 @@ public long getPriority() { public void process( Consumer onCharacterEvent, Consumer extOnErrMsg, - EventQueueManager.LLMCompleter completer) { + LLMCompleter completer) { if (isProcessing) { LOGGER.warn("Called queueData.process even though it was already processing! this should not happen"); @@ -78,21 +79,24 @@ public void process( this.isProcessing = true; // prepare conversation history for LLM call - Event lastEvent = mod.getAIPersistantData().dumpEventQueueToConversationHistoryAndReturnLastEvent(eventQueue, mod.getPlayer2APIService()); + Event lastEvent = mod.getAIPersistantData().dumpEventQueueToConversationHistoryAndReturnLastEvent(eventQueue, + mod.getPlayer2APIService()); Optional reminderString = getReminderStringFromLastEvent(lastEvent); String agentStatus = AgentStatus.fromMod(this.mod).toString(); String worldStatus = WorldStatus.fromMod(this.mod).toString(); String altoClefDebugMsgs = this.altoClefMsgBuffer.dumpAndGetString(); ConversationHistory historyWithWrappedStatus = mod.getAIPersistantData() - .getConversationHistoryWrappedWithStatus(worldStatus, agentStatus, altoClefDebugMsgs, mod.getPlayer2APIService(), reminderString); + .getConversationHistoryWrappedWithStatus(worldStatus, agentStatus, altoClefDebugMsgs, + mod.getPlayer2APIService(), reminderString); LOGGER.info("[AICommandBridge/processChatWithAPI]: Calling LLM: history={}", new Object[] { historyWithWrappedStatus.toString() }); Consumer onLLMResponse = jsonResp -> { String llmMessage = Utils.getStringJsonSafely(jsonResp, "message"); - String command = this.isGreetingResponse? "bodylang greeting": Utils.getStringJsonSafely(jsonResp, "command"); + String command = this.isGreetingResponse ? "bodylang greeting" + : Utils.getStringJsonSafely(jsonResp, "command"); this.isGreetingResponse = false; LOGGER.info("[AICommandBridge/processCharWithAPI]: Processed LLM repsonse: message={} command={}", llmMessage, command); @@ -134,11 +138,13 @@ private void addEventToQueue(Event event) { eventQueue.add(event); } - private Optional getReminderStringFromLastEvent(Event lastEvent){ - if(lastEvent instanceof Event.UserMessage){ - return Optional.of(((Event.UserMessage) lastEvent).userName().equals(getMod().getOwnerUsername()) ? Prompts.reminderOnOwnerMsg : Prompts.reminderOnOtherUSerMsg); + private Optional getReminderStringFromLastEvent(Event lastEvent) { + if (lastEvent instanceof Event.UserMessage) { + return Optional.of(((Event.UserMessage) lastEvent).userName().equals(getMod().getOwnerUsername()) + ? Prompts.reminderOnOwnerMsg + : Prompts.reminderOnOtherUSerMsg); } - if(lastEvent instanceof Event.CharacterMessage){ + if (lastEvent instanceof Event.CharacterMessage) { return Optional.of(Prompts.reminderOnAIMsg); } return Optional.empty(); @@ -171,7 +177,7 @@ public void onGreeting() { public void onCommandFinish(AgentSideEffects.CommandExecutionStopReason stopReason) { if (stopReason instanceof CommandExecutionStopReason.Finished) { - if(shouldIgnoreGreetingDance){ + if (shouldIgnoreGreetingDance) { // ignore first greeting command finish: shouldIgnoreGreetingDance = false; return; @@ -214,15 +220,17 @@ public LivingEntity getEntity() { public void setEnabled(boolean enabled) { this.enabled = enabled; } - public Character getCharacter(){ + + public Character getCharacter() { return mod.getAIPersistantData().getCharacter(); } - public Player2APIService getPlayer2apiService(){ + + public Player2APIService getPlayer2apiService() { return mod.getPlayer2APIService(); } - public String getName(){ + + public String getName() { return getCharacter().shortName(); } - } \ No newline at end of file diff --git a/src/autoclef/java/adris/altoclef/player2api/EventQueueManager.java b/src/autoclef/java/adris/altoclef/player2api/EventQueueManager.java index 3d211e0b..abaeb41a 100644 --- a/src/autoclef/java/adris/altoclef/player2api/EventQueueManager.java +++ b/src/autoclef/java/adris/altoclef/player2api/EventQueueManager.java @@ -5,8 +5,6 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Stream; @@ -14,8 +12,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import com.google.gson.JsonObject; - import adris.altoclef.AltoClefController; import adris.altoclef.player2api.Event.UserMessage; import adris.altoclef.player2api.status.StatusUtils; @@ -43,63 +39,6 @@ public static void init() { } } - public static class LLMCompleter { - private boolean isProcessing = false; - - private static final ExecutorService llmThread = Executors.newSingleThreadExecutor(); - - public void process( - Player2APIService player2apiService, - ConversationHistory history, - Consumer extOnLLMResponse, - Consumer extOnErrMsg) { - if (isProcessing) { - LOGGER.warn("Called llmcompleter.process when it was already processing! This should not happen."); - return; - } - Consumer onLLMResponse = resp -> { - try { - extOnLLMResponse.accept(resp); - } catch (Exception e) { - LOGGER.error( - "[EventQueueManager/LLMCompleter/process/onLLMResponse]: Error in external llm resp, errMsg={} llmResp={}", - e.getMessage(), resp.toString()); - } finally { - LOGGER.info("Done processing, isprocessing -> false"); - isProcessing = false; - } - }; - Consumer onErrMsg = errMsg -> { - try { - extOnErrMsg.accept(errMsg); - } catch (Exception e) { - LOGGER.error( - "[EventQueueManager/LLMCompleter/process/onErrMsg]: Error in external onErrmsg, errMsgFromException={} errMsg={}", - e.getMessage(), errMsg); - } finally { - isProcessing = false; - } - }; - isProcessing = true; - llmThread.submit(() -> { - try { - JsonObject response = player2apiService.completeConversation(history); - LOGGER.info("LLMCompleter returned json={}", response); - onLLMResponse.accept(response); - } catch (Exception e) { - onErrMsg.accept( - e.getMessage() == null ? "Unknown error from CompleteConversation API" : e.getMessage()); - } - }); - } - - public boolean isAvailible() { - return !isProcessing; - } - } - - private static List llmCompleters = List.of(new LLMCompleter()); - // ## Utils public static EventQueueData getOrCreateEventQueueData(AltoClefController mod) { return queueData.computeIfAbsent(mod.getPlayer().getUUID(), k -> { @@ -144,7 +83,7 @@ private static void process(Consumer onCharacterEvent, C Optional dataToProcess = queueData.values().stream().filter(data -> { return data.getPriority() != 0; }).max(Comparator.comparingLong(EventQueueData::getPriority)); - llmCompleters.stream().filter(LLMCompleter::isAvailible).forEach(completer -> { + LLMCompleter.processUsingAvailibleCompleter(completer -> { dataToProcess.ifPresent(data -> { data.process(onCharacterEvent, onErrEvent, completer); }); @@ -163,7 +102,7 @@ public static void injectOnTick(MinecraftServer server) { Consumer onErrEvent = (errMsg) -> { AgentSideEffects.onError(server, errMsg); }; - if (!TTSManager.isLocked()) { + if (!LockManager.globalIsLocked()) { process(onCharacterEvent, onErrEvent); } TTSManager.injectOnTick(server); diff --git a/src/autoclef/java/adris/altoclef/player2api/LLMCompleter.java b/src/autoclef/java/adris/altoclef/player2api/LLMCompleter.java new file mode 100644 index 00000000..f5215ca6 --- /dev/null +++ b/src/autoclef/java/adris/altoclef/player2api/LLMCompleter.java @@ -0,0 +1,76 @@ +package adris.altoclef.player2api; + +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import com.google.gson.JsonObject; + +public class LLMCompleter { + public static final Logger LOGGER = LogManager.getLogger(); + private boolean isProcessing = false; + + private static final ExecutorService llmThread = Executors.newSingleThreadExecutor(); + private static List llmCompleters = List.of(new LLMCompleter()); + + public void process( + Player2APIService player2apiService, + ConversationHistory history, + Consumer extOnLLMResponse, + Consumer extOnErrMsg) { + if (isProcessing) { + LOGGER.warn("Called llmcompleter.process when it was already processing! This should not happen."); + return; + } + Consumer onLLMResponse = resp -> { + try { + extOnLLMResponse.accept(resp); + } catch (Exception e) { + LOGGER.error( + "[EventQueueManager/LLMCompleter/process/onLLMResponse]: Error in external llm resp, errMsg={} llmResp={}", + e.getMessage(), resp.toString()); + } finally { + LOGGER.info("Done processing, isprocessing -> false"); + isProcessing = false; + } + }; + Consumer onErrMsg = errMsg -> { + try { + extOnErrMsg.accept(errMsg); + } catch (Exception e) { + LOGGER.error( + "[EventQueueManager/LLMCompleter/process/onErrMsg]: Error in external onErrmsg, errMsgFromException={} errMsg={}", + e.getMessage(), errMsg); + } finally { + isProcessing = false; + } + }; + isProcessing = true; + llmThread.submit(() -> { + try { + JsonObject response = player2apiService.completeConversation(history); + LOGGER.info("LLMCompleter returned json={}", response); + onLLMResponse.accept(response); + } catch (Exception e) { + onErrMsg.accept( + e.getMessage() == null ? "Unknown error from CompleteConversation API" : e.getMessage()); + } + }); + } + + public boolean isAvailible() { + return !isProcessing; + } + + public static void processUsingAvailibleCompleter(Consumer processer) { + Stream availibles = llmCompleters.stream().filter(LLMCompleter::isAvailible); + if (availibles.toArray().length < 1) { + LOGGER.error("ALL LLM COMPLETERS BUSY, should not happen. Some locking error occured"); + } + llmCompleters.stream().filter(LLMCompleter::isAvailible).forEach(processer); + } +} diff --git a/src/autoclef/java/adris/altoclef/player2api/LockManager.java b/src/autoclef/java/adris/altoclef/player2api/LockManager.java new file mode 100644 index 00000000..f20a80d5 --- /dev/null +++ b/src/autoclef/java/adris/altoclef/player2api/LockManager.java @@ -0,0 +1,30 @@ +package adris.altoclef.player2api; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class LockManager { + private static Logger LOGGER = LogManager.getLogger(); + + private static boolean ttsLocked = false; + private static boolean codeGenLocked = false; + + + public static boolean isTTSLocked(){ + return ttsLocked; + } + + public static boolean globalIsLocked(){ + return ttsLocked || codeGenLocked; + } + + public static void setTTS(boolean onOrOff){ + LOGGER.info(String.format("TTS: %s lock", onOrOff ? "setting" : "releasing")); + ttsLocked = onOrOff; + } + + public static void setCodeGenLock(boolean onOrOff){ + LOGGER.info(String.format("CodeGen: %s lock", onOrOff ? "setting" : "releasing")); + codeGenLocked = onOrOff; + } +} diff --git a/src/autoclef/java/adris/altoclef/player2api/Prompts.java b/src/autoclef/java/adris/altoclef/player2api/Prompts.java index 11ecc53c..35300af5 100644 --- a/src/autoclef/java/adris/altoclef/player2api/Prompts.java +++ b/src/autoclef/java/adris/altoclef/player2api/Prompts.java @@ -4,6 +4,7 @@ import java.util.Map; import adris.altoclef.commandsystem.Command; +import adris.altoclef.player2api.pseudocommands.PseudoCommands; import adris.altoclef.player2api.utils.Utils; public class Prompts { @@ -46,23 +47,359 @@ public class Prompts { {{validCommands}} """; - public static String getAINPCSystemPrompt(Character character, Collection altoclefCommands, String ownerUsername) { - StringBuilder commandListBuilder = new StringBuilder(); + public static void addCommandToBuilder(StringBuilder builder, String name, String description) { int padSize = 10; + builder.append(name).append(": "); + int toAdd = padSize - name.length(); + builder.append(" ".repeat(Math.max(0, toAdd))); + builder.append(description).append("\n"); + } + + public static String getAINPCSystemPrompt(Character character, Collection altoclefCommands, + String ownerUsername) { + StringBuilder commandListBuilder = new StringBuilder(); for (Command c : altoclefCommands) { + // for each command this will give something like "commandName: " StringBuilder line = new StringBuilder(); - line.append(c.getName()).append(": "); - int toAdd = padSize - c.getName().length(); - line.append(" ".repeat(Math.max(0, toAdd))); - line.append(c.getDescription()).append("\n"); + addCommandToBuilder(commandListBuilder, c.getName(), c.getDescription()); commandListBuilder.append(line); } + + for (PseudoCommands.PseudoCommand c : PseudoCommands.pseudoCommands) { + StringBuilder line = new StringBuilder(); + addCommandToBuilder(commandListBuilder, c.getName(), c.getDescription()); + commandListBuilder.append(line); + } + String validCommandsFormatted = commandListBuilder.toString(); - + String newPrompt = Utils.replacePlaceholders(aiNPCPromptTemplate, - Map.of("characterDescription", character.description(), "characterName", character.name(), "validCommands", + Map.of("characterDescription", character.description(), "characterName", character.name(), + "validCommands", validCommandsFormatted, "ownerUsername", ownerUsername)); return newPrompt; } -} \ No newline at end of file + private static String buildStructurePrompt = """ + You are a code generator for a tiny construction DSL used by a Minecraft bot. + + ## Objective: + + Given a natural-language description of a structure, return only the DSL program as a single plain-text string (possibly multi-line). No explanations, no markdown, no code fences, no JSON, no Java wrappers. + + ### DSL Summary (what you can output) + + Declarations: let name = ; + + Strings use double quotes; integers only; booleans true|false. + + Arithmetic: + - * / % (integer math only). + + Comparisons/logic: == != < <= > >= && || ! + + Control flow: + + For loops: for (let i = 0; i < N; i = i + 1) { ... } + + Conditionals: if (cond) { ... } else { ... } + + Side effects: + + setBlock(x, y, z, blockName); — place a single block. + + Comments: // comment + + Forbidden: user-defined functions, imports, while/foreach, floats, external calls. + + Place blocks via setBlock(baseX + dx, baseY + dy, baseZ + dz, ); + + If materials are named in the description, use them (e.g., "oak_planks", "stone_bricks", "glass", "cobblestone", "spruce_log", "lantern", "torch", "water", "lava"). If unknown, fall back to "stone". + + ## Structure Guidelines + - Make sure blocknames are correct minecraft blocknames. + - Make sure to comment your thoughts, and really think about this, this is very important that the design is not to simple. + - Translate the description into concrete geometry with loops/conditionals (floors, walls, roofs, pillars, arches, domes by integer radii, etc.). + - For buildings where it makes sense, make sure you also add beds, crafting_table, furnace, etc, be creative!! Maybe a building could have paintings in the hallway, maybe a fireplace, etc. + - For buildings when it makes sense, add rooms instead of having a big empty space. Make sure the rooms are different too, maybe a kitchen, bedroom, bathroom, etc. Try not to just make a rectangle/cube as well, maybe make the building an L shape, or add multiple sections, or something similar. + - Make sure any torches are attached to a block, and not floating in the air. + - A player is 2x1, so make sure structures are the appropriate size. + ## Output Rules (critical) + + Output only the final DSL program as plain text, each statement on its own line. + + Every statement ends with ; (except }). + + Do not wrap the program in quotes, Java, JSON, or markdown. + + No extra commentary before or after. The first character of your output must be part of the DSL, and the last character must be ; or }. + + Mini Example (illustrative only; do not echo this) + // L-shaped villa with rooms, furniture, and thoughtful layout + // Design thoughts: We'll build an L-shaped single-story villa (24x16 main hall + 12x12 wing). + // Height = 8 (comfortable for 2-block-tall player). Interior walls create rooms: foyer/hall, kitchen, bedroom, study. + // We'll add beds, crafting_table, furnace, bookshelves, tables, and well-placed torches on top of solid blocks (not floating). + // Windows are spaced regularly; doors are 2 blocks tall. A stone-brick fireplace with a chimney and a campfire hearth adds flair. + + let baseX = 0; + let baseY = 64; + let baseZ = 0; + let dir = "north"; + let block = "stone_bricks"; + + // ====== FOUNDATION ====== + // Main rectangle: 24 x 16 + for (let x = 0; x < 24; x = x + 1) { + for (let z = 0; z < 16; z = z + 1) { + setBlock(baseX + x, baseY, baseZ + z, "stone"); + } + } + // Wing rectangle: 12 x 12, attached on the east side (from z=4..15) + for (let x = 24; x < 36; x = x + 1) { + for (let z = 4; z < 16; z = z + 1) { + setBlock(baseX + x, baseY, baseZ + z, "stone"); + } + } + + // ====== FLOORING ====== + // Main hall floor: oak_planks + for (let x = 0; x < 24; x = x + 1) { + for (let z = 0; z < 16; z = z + 1) { + setBlock(baseX + x, baseY + 1, baseZ + z, "oak_planks"); + } + } + // Wing floor: spruce_planks for contrast + for (let x = 24; x < 36; x = x + 1) { + for (let z = 4; z < 16; z = z + 1) { + setBlock(baseX + x, baseY + 1, baseZ + z, "spruce_planks"); + } + } + + // ====== OUTER WALLS (HEIGHT 8) ====== + for (let y = 2; y <= 9; y = y + 1) { + // Main rectangle perimeter + for (let x = 0; x < 24; x = x + 1) { + setBlock(baseX + x, baseY + y, baseZ + 0, "stone_bricks"); + setBlock(baseX + x, baseY + y, baseZ + 15, "stone_bricks"); + } + for (let z = 0; z < 16; z = z + 1) { + setBlock(baseX + 0, baseY + y, baseZ + z, "stone_bricks"); + setBlock(baseX + 23, baseY + y, baseZ + z, "stone_bricks"); + } + // Wing perimeter + for (let x = 24; x < 36; x = x + 1) { + setBlock(baseX + x, baseY + y, baseZ + 4, "stone_bricks"); + setBlock(baseX + x, baseY + y, baseZ + 15, "stone_bricks"); + } + for (let z = 4; z < 16; z = z + 1) { + setBlock(baseX + 24, baseY + y, baseZ + z, "stone_bricks"); + setBlock(baseX + 35, baseY + y, baseZ + z, "stone_bricks"); + } + } + + // ====== DOORWAYS ====== + // Main entrance centered on front (z=0) of main hall: width 3, height 3 + for (let dx = 10; dx <= 12; dx = dx + 1) { + for (let dy = 2; dy <= 4; dy = dy + 1) { + setBlock(baseX + dx, baseY + dy, baseZ + 0, "air"); + } + } + // Door from main hall to wing (opening on shared wall at x=23): 2x3 + for (let dz = 8; dz <= 9; dz = dz + 1) { + for (let dy = 2; dy <= 4; dy = dy + 1) { + setBlock(baseX + 23, baseY + dy, baseZ + dz, "air"); + } + } + + // ====== WINDOWS ====== + // Evenly spaced windows (2x2) around exterior walls, leaving corners + for (let y = 4; y <= 5; y = y + 1) { + for (let x = 3; x <= 21; x = x + 6) { + setBlock(baseX + x, baseY + y, baseZ + 0, "glass"); + setBlock(baseX + x + 1, baseY + y, baseZ + 0, "glass"); + setBlock(baseX + x, baseY + y, baseZ + 15, "glass"); + setBlock(baseX + x + 1, baseY + y, baseZ + 15, "glass"); + } + for (let z = 3; z <= 13; z = z + 5) { + setBlock(baseX + 0, baseY + y, baseZ + z, "glass"); + setBlock(baseX + 1, baseY + y, baseZ + z, "glass"); + setBlock(baseX + 23, baseY + y, baseZ + z, "glass"); + setBlock(baseX + 22, baseY + y, baseZ + z, "glass"); + } + // Wing windows + for (let x = 26; x <= 34; x = x + 8) { + setBlock(baseX + x, baseY + y, baseZ + 4, "glass"); + setBlock(baseX + x + 1, baseY + y, baseZ + 4, "glass"); + setBlock(baseX + x, baseY + y, baseZ + 15, "glass"); + setBlock(baseX + x + 1, baseY + y, baseZ + 15, "glass"); + } + for (let z = 6; z <= 14; z = z + 4) { + setBlock(baseX + 24, baseY + y, baseZ + z, "glass"); + setBlock(baseX + 35, baseY + y, baseZ + z, "glass"); + } + } + + // ====== ROOF (FLAT WITH BORDER) ====== + for (let x = 0; x < 24; x = x + 1) { + for (let z = 0; z < 16; z = z + 1) { + setBlock(baseX + x, baseY + 10, baseZ + z, "stone"); + } + } + for (let x = 24; x < 36; x = x + 1) { + for (let z = 4; z < 16; z = z + 1) { + setBlock(baseX + x, baseY + 10, baseZ + z, "stone"); + } + } + // Roof trim + for (let x = 0; x < 24; x = x + 1) { + setBlock(baseX + x, baseY + 10, baseZ + 0, "stone_bricks"); + setBlock(baseX + x, baseY + 10, baseZ + 15, "stone_bricks"); + } + for (let z = 0; z < 16; z = z + 1) { + setBlock(baseX + 0, baseY + 10, baseZ + z, "stone_bricks"); + setBlock(baseX + 23, baseY + 10, baseZ + z, "stone_bricks"); + } + for (let x = 24; x < 36; x = x + 1) { + setBlock(baseX + x, baseY + 10, baseZ + 4, "stone_bricks"); + setBlock(baseX + x, baseY + 10, baseZ + 15, "stone_bricks"); + } + for (let z = 4; z < 16; z = z + 1) { + setBlock(baseX + 24, baseY + 10, baseZ + z, "stone_bricks"); + setBlock(baseX + 35, baseY + 10, baseZ + z, "stone_bricks"); + } + + // ====== INTERIOR ROOMS ====== + // Partition main hall into foyer (front), corridor (middle), and living room (rear) + for (let x = 2; x <= 21; x = x + 1) { + for (let y = 2; y <= 7; y = y + 1) { + // Wall between foyer and corridor at z=5 + setBlock(baseX + x, baseY + y, baseZ + 5, "stone_bricks"); + // Wall between corridor and living room at z=10 + setBlock(baseX + x, baseY + y, baseZ + 10, "stone_bricks"); + } + } + // Doorways (2x2) in those partitions + for (let dy = 2; dy <= 3; dy = dy + 1) { + setBlock(baseX + 12, baseY + dy, baseZ + 5, "air"); + setBlock(baseX + 12, baseY + dy, baseZ + 10, "air"); + setBlock(baseX + 13, baseY + dy, baseZ + 5, "air"); + setBlock(baseX + 13, baseY + dy, baseZ + 10, "air"); + } + + // Wing: split into kitchen (north) and bedroom (south) + for (let x = 26; x <= 33; x = x + 1) { + for (let y = 2; y <= 7; y = y + 1) { + setBlock(baseX + x, baseY + y, baseZ + 10, "stone_bricks"); + } + } + // Wing doorways (2x2) + for (let dy = 2; dy <= 3; dy = dy + 1) { + setBlock(baseX + 30, baseY + dy, baseZ + 10, "air"); + setBlock(baseX + 31, baseY + dy, baseZ + 10, "air"); + } + + // ====== FIREPLACE & CHIMNEY (living room corner) ====== + // Hearth at (x=3..5, z=12..13) + for (let x = 3; x <= 5; x = x + 1) { + for (let z = 12; z <= 13; z = z + 1) { + setBlock(baseX + x, baseY + 1, baseZ + z, "cobblestone"); + } + } + // Campfire for safe flame + setBlock(baseX + 4, baseY + 2, baseZ + 12, "campfire"); + // Back wall cladding and chimney up + for (let y = 2; y <= 10; y = y + 1) { + setBlock(baseX + 4, baseY + y, baseZ + 14, "cobblestone"); + setBlock(baseX + 4, baseY + y, baseZ + 15, "cobblestone"); + } + for (let y = 11; y <= 13; y = y + 1) { + setBlock(baseX + 4, baseY + y, baseZ + 15, "cobblestone"); + } + + // ====== FURNITURE & UTILITIES ====== + // Corridor rug (carpet) + for (let x = 9; x <= 14; x = x + 1) { + for (let z = 6; z <= 9; z = z + 1) { + setBlock(baseX + x, baseY + 2, baseZ + z, "red_carpet"); + } + } + + // Living room: table (logs + slab top), bookshelves, torches on top of shelves + // Table legs + setBlock(baseX + 16, baseY + 2, baseZ + 12, "spruce_log"); + setBlock(baseX + 18, baseY + 2, baseZ + 12, "spruce_log"); + setBlock(baseX + 16, baseY + 2, baseZ + 14, "spruce_log"); + setBlock(baseX + 18, baseY + 2, baseZ + 14, "spruce_log"); + // Table top + for (let x = 16; x <= 18; x = x + 1) { + for (let z = 12; z <= 14; z = z + 1) { + setBlock(baseX + x, baseY + 3, baseZ + z, "oak_slab"); + } + } + // Bookshelf wall + for (let x = 19; x <= 21; x = x + 1) { + for (let y = 2; y <= 4; y = y + 1) { + setBlock(baseX + x, baseY + y, baseZ + 13, "bookshelf"); + } + } + // Torches on top of bookshelf (attached to solid block below) + for (let x = 19; x <= 21; x = x + 1) { + setBlock(baseX + x, baseY + 5, baseZ + 13, "torch"); + } + + // Kitchen (wing north): counters (stone), crafting_table, furnace, sink (water) + for (let x = 26; x <= 33; x = x + 1) { + setBlock(baseX + x, baseY + 2, baseZ + 6, "stone"); + } + setBlock(baseX + 27, baseY + 2, baseZ + 7, "crafting_table"); + setBlock(baseX + 28, baseY + 2, baseZ + 7, "furnace"); + // Simple sink basin + setBlock(baseX + 30, baseY + 2, baseZ + 7, "cauldron"); + setBlock(baseX + 30, baseY + 3, baseZ + 7, "water"); + + // Bedroom (wing south): double bed, side tables (barrels), chest + setBlock(baseX + 29, baseY + 2, baseZ + 12, "bed"); + setBlock(baseX + 30, baseY + 2, baseZ + 12, "bed"); + setBlock(baseX + 28, baseY + 2, baseZ + 12, "barrel"); + setBlock(baseX + 31, baseY + 2, baseZ + 12, "barrel"); + setBlock(baseX + 33, baseY + 2, baseZ + 13, "chest"); + + // Study (rear main hall): desk, chair, bookshelves, torches on desk corners + // Desk + for (let x = 7; x <= 9; x = x + 1) { + setBlock(baseX + x, baseY + 2, baseZ + 13, "oak_slab"); + } + setBlock(baseX + 8, baseY + 2, baseZ + 12, "stair"); + setBlock(baseX + 7, baseY + 3, baseZ + 13, "torch"); + setBlock(baseX + 9, baseY + 3, baseZ + 13, "torch"); + + // ====== INTERIOR LIGHTING (TORCHES ON TOP OF FLOOR BLOCKS) ====== + // Main hall grid, placed on floor tops (supported by floor below at y-1) + for (let x = 3; x <= 21; x = x + 6) { + for (let z = 3; z <= 13; z = z + 5) { + setBlock(baseX + x, baseY + 2, baseZ + z, "torch"); + } + } + // Wing lighting + for (let x = 26; x <= 34; x = x + 4) { + setBlock(baseX + x, baseY + 2, baseZ + 6, "torch"); + setBlock(baseX + x, baseY + 2, baseZ + 13, "torch"); + } + + // ====== FRONT PATH & GARDEN TOUCH ====== + // Small path leading from entrance + for (let z = -1; z >= -6; z = z - 1) { + for (let x = 10; x <= 12; x = x + 1) { + setBlock(baseX + x, baseY + 1, baseZ + z, "cobblestone"); + } + } + // Flower beds flanking the path + for (let z = -1; z >= -6; z = z - 1) { + setBlock(baseX + 9, baseY + 2, baseZ + z, "rose_bush"); + setBlock(baseX + 13, baseY + 2, baseZ + z, "peony"); + } + """; + + public static String getBuildStructurePrompt() { + return buildStructurePrompt; + } +} diff --git a/src/autoclef/java/adris/altoclef/player2api/TTSManager.java b/src/autoclef/java/adris/altoclef/player2api/TTSManager.java index 48178de5..e2687e9d 100644 --- a/src/autoclef/java/adris/altoclef/player2api/TTSManager.java +++ b/src/autoclef/java/adris/altoclef/player2api/TTSManager.java @@ -13,7 +13,6 @@ public class TTSManager { private static final Logger LOGGER = LogManager.getLogger(); private static final int TTScharactersPerSecond = 25; // approx how fast (characters/sec) does the TTS talk - private static boolean TTSLocked = false; private static long estimatedEndTime = 0; private static final ExecutorService ttsThread = Executors.newSingleThreadExecutor(); @@ -27,8 +26,8 @@ private static void setEstimatedEndTime(String message) { } public static void TTS(String message, Character character, Player2APIService player2apiService) { - TTSLocked = true; - LOGGER.info("Locking TTS based on msg={}", message); + LOGGER.info("TTS for msg={}", message); + LockManager.setTTS(true); estimatedEndTime = Long.MAX_VALUE; ttsThread.submit(() -> { @@ -38,16 +37,12 @@ public static void TTS(String message, Character character, Player2APIService pl }); } - public static boolean isLocked() { - return TTSLocked; - } public static void injectOnTick(MinecraftServer server) { // release lock if we think we have finished. server.execute(() -> { - if ((System.nanoTime() > estimatedEndTime) && TTSLocked) { - LOGGER.info("TTS releasing lock"); - TTSLocked = false; + if ((System.nanoTime() > estimatedEndTime) && LockManager.isTTSLocked()) { + LockManager.setTTS(false); } }); } diff --git a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/PseudoCommands.java b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/PseudoCommands.java new file mode 100644 index 00000000..ea2376af --- /dev/null +++ b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/PseudoCommands.java @@ -0,0 +1,54 @@ +package adris.altoclef.player2api.pseudocommands; + +import java.util.List; +import java.util.Optional; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import adris.altoclef.player2api.LockManager; + +public class PseudoCommands { + public static Logger LOGGER = LogManager.getLogger(); + + public static class PseudoCommand { + String name; + String description; + + public PseudoCommand(String name, String description) { + this.name = name; + this.description = description; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + } + + public static List pseudoCommands = List.of( + new PseudoCommand("build_structure", + "you provide a description, and the agent will build a structure matching that description. Example call would be `build_structure a gray modern house with a garden of roses in front of it`")); + + public static Optional getPseudocommandOption(String cmd) { + return pseudoCommands.stream().filter((p) -> cmd.contains(p.getName())).findFirst(); + } + + public static void process(PseudoCommand cmd, String commandWithPrefix) { + LOGGER.info("Processing PseudoCommand={}, commandWithPrefix={}", cmd, commandWithPrefix); + switch (cmd.name) { + case "build_structure": + processBuildStructure(commandWithPrefix.split("build_structure")[1].strip()); // after build_structure + // is desc + default: + LOGGER.error("for cmdWithPrefix={} could not find matching pseudocommand", commandWithPrefix); + } + } + + private static void processBuildStructure(String description) { + + } +} diff --git a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java new file mode 100644 index 00000000..91787716 --- /dev/null +++ b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java @@ -0,0 +1,30 @@ +package adris.altoclef.player2api.pseudocommands.codegen; + +import com.google.gson.JsonObject; + +import adris.altoclef.player2api.ConversationHistory; +import adris.altoclef.player2api.LLMCompleter; +import adris.altoclef.player2api.LockManager; +import adris.altoclef.player2api.Player2APIService; +import adris.altoclef.player2api.Prompts; + +public class BuildStructure { + + private static void onLLMResponse(JsonObject obj) { + + } + + private static void onLLMErrMsg(String errMsg) { + + } + + public static void buildStructure(String description, Player2APIService service) { + LockManager.setCodeGenLock(true); + // 1) get code from LLM + ConversationHistory history = new ConversationHistory(Prompts.getBuildStructurePrompt()); + LLMCompleter.processUsingAvailibleCompleter( + (cmp) -> { + cmp.process(service, history, BuildStructure::onLLMResponse, BuildStructure::onLLMErrMsg); + }); + } +} diff --git a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructureFromCode.java b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructureFromCode.java new file mode 100644 index 00000000..81e24617 --- /dev/null +++ b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructureFromCode.java @@ -0,0 +1,1239 @@ +package adris.altoclef.player2api.pseudocommands.codegen; + +import java.util.*; +import java.util.function.*; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * MiniBlocks: a tiny interpreter that parses and runs a small language with + * - let / assignment + * - if / else + * - while + * - for (init; condition; update) { ... } + * - setBlock(x, y, z, facing, blockname) + * + * As it executes, each setBlock call becomes a SetBlockCommand. Use + * Runner.next() + * to pull commands one-by-one until completion. + */ +public class BuildStructureFromCode { + public static final Logger LOGGER = LogManager.getLogger(); + + // ==== Public API ==== + + /** Represents a single emitted setBlock command. */ + public static final class SetBlockCommand { + public final int x, y, z; + public final String blockName; + + public SetBlockCommand(int x, int y, int z, String blockName) { + this.x = x; + this.y = y; + this.z = z; + this.blockName = blockName; + } + + @Override + public String toString() { + return "setBlock(" + x + ", " + y + ", " + z + "\", \"" + blockName + "\")"; + } + } + + /** Compiles source into an executable Program. */ + public static Program compile(String source) { + Lexer lex = new Lexer(source); + List tokens = lex.lex(); + Parser parser = new Parser(tokens); + List stmts = parser.parse(); + return new Program(stmts); + } + + /** Program runner that yields setBlock commands lazily, step-by-step. */ + public static final class Runner { + private final Interpreter interp; + private final Queue queue = new ArrayDeque<>(); + private boolean done = false; + + public Runner(Program program) { + this.interp = new Interpreter(program, queue::add); + } + + /** Execute until the next setBlock is produced or program ends. */ + public Optional next() { + if (done && queue.isEmpty()) + return Optional.empty(); + if (!queue.isEmpty()) + return Optional.of(queue.poll()); + while (queue.isEmpty() && !done) { + done = !interp.step(); // step returns false when finished + } + return queue.isEmpty() ? Optional.empty() : Optional.of(queue.poll()); + } + } + + /** Container for parsed program (AST). */ + public static final class Program { + final List statements; + + Program(List statements) { + this.statements = statements; + } + } + + // ==== Demo ==== + + public static void runCode(String code, Consumer onSetBlock) throws Exception { + Program program = compile(code); + Runner runner = new Runner(program); + Optional cmd; + while ((cmd = runner.next()).isPresent()) { + SetBlockCommand data = cmd.get(); + onSetBlock.accept(data); + } + } + + // ==== Lexer ==== + + enum TokenType { + // Single-char + LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, + // One or two char + BANG, BANG_EQUAL, EQUAL, EQUAL_EQUAL, GREATER, GREATER_EQUAL, LESS, LESS_EQUAL, + // Literals + IDENTIFIER, STRING, NUMBER, + // Keywords + LET, IF, ELSE, WHILE, FOR, TRUE, FALSE, NIL, SETBLOCK, + // End + EOF + } + + static final class Token { + final TokenType type; + final String lexeme; + final Object literal; + final int line, col; + + Token(TokenType type, String lexeme, Object literal, int line, int col) { + this.type = type; + this.lexeme = lexeme; + this.literal = literal; + this.line = line; + this.col = col; + } + + @Override + public String toString() { + return type + " '" + lexeme + "'" + (literal != null ? (" -> " + literal) : ""); + } + } + + static final class Lexer { + private final String src; + private final List tokens = new ArrayList<>(); + private int start = 0, current = 0, line = 1, col = 1; + + Lexer(String src) { + this.src = src; + } + + List lex() { + while (!isAtEnd()) { + start = current; + scanToken(); + } + tokens.add(new Token(TokenType.EOF, "", null, line, col)); + return tokens; + } + + private void scanToken() { + char c = advance(); + switch (c) { + case '(': + add(TokenType.LEFT_PAREN); + break; + case ')': + add(TokenType.RIGHT_PAREN); + break; + case '{': + add(TokenType.LEFT_BRACE); + break; + case '}': + add(TokenType.RIGHT_BRACE); + break; + case ',': + add(TokenType.COMMA); + break; + case '.': + add(TokenType.DOT); + break; + case '-': + add(TokenType.MINUS); + break; + case '+': + add(TokenType.PLUS); + break; + case ';': + add(TokenType.SEMICOLON); + break; + case '*': + add(TokenType.STAR); + break; + case '!': + add(match('=') ? TokenType.BANG_EQUAL : TokenType.BANG); + break; + case '=': + add(match('=') ? TokenType.EQUAL_EQUAL : TokenType.EQUAL); + break; + case '<': + add(match('=') ? TokenType.LESS_EQUAL : TokenType.LESS); + break; + case '>': + add(match('=') ? TokenType.GREATER_EQUAL : TokenType.GREATER); + break; + case '/': + if (match('/')) { // line comment + while (!isAtEnd() && peek() != '\n') + advance(); + } else if (match('*')) { // block comment + while (!isAtEnd() && !(peek() == '*' && peekNext() == '/')) { + if (peek() == '\n') { + line++; + col = 0; + } + advance(); + } + if (!isAtEnd()) { + advance(); + advance(); + } // consume */ + } else + add(TokenType.SLASH); + break; + case ' ': + case '\r': + case '\t': + break; + case '\n': + line++; + col = 0; + break; + case '"': + string(); + break; + default: + if (isDigit(c)) + number(); + else if (isAlpha(c)) + identifier(); + else + error("Unexpected character: " + c); + } + } + + private void string() { + StringBuilder sb = new StringBuilder(); + while (!isAtEnd() && peek() != '"') { + char c = advance(); + if (c == '\\') { + if (isAtEnd()) + break; + char n = advance(); + switch (n) { + case 'n': + sb.append('\n'); + break; + case 't': + sb.append('\t'); + break; + case '"': + sb.append('"'); + break; + case '\\': + sb.append('\\'); + break; + default: + sb.append(n); + break; + } + } else { + sb.append(c); + } + if (c == '\n') { + line++; + col = 0; + } + } + if (isAtEnd()) + error("Unterminated string."); + advance(); // closing " + add(TokenType.STRING, sb.toString()); + } + + private void number() { + while (isDigit(peek())) + advance(); + if (peek() == '.' && isDigit(peekNext())) { + advance(); + while (isDigit(peek())) + advance(); + } + double val = Double.parseDouble(src.substring(start, current)); + add(TokenType.NUMBER, val); + } + + private void identifier() { + while (isAlphaNumeric(peek())) + advance(); + String text = src.substring(start, current); + TokenType type = keywords.get(text); + if ("setBlock".equals(text)) + type = TokenType.SETBLOCK; + if (type == null) + type = TokenType.IDENTIFIER; + add(type); + } + + private void add(TokenType type) { + add(type, null); + } + + private void add(TokenType type, Object lit) { + tokens.add(new Token(type, src.substring(start, current), lit, line, col)); + } + + private boolean match(char expected) { + if (isAtEnd() || src.charAt(current) != expected) + return false; + advance(); + return true; + } + + private char peek() { + return isAtEnd() ? '\0' : src.charAt(current); + } + + private char peekNext() { + return (current + 1 >= src.length()) ? '\0' : src.charAt(current + 1); + } + + private char advance() { + current++; + col++; + return src.charAt(current - 1); + } + + private boolean isAtEnd() { + return current >= src.length(); + } + + private static boolean isDigit(char c) { + return c >= '0' && c <= '9'; + } + + private static boolean isAlpha(char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; + } + + private static boolean isAlphaNumeric(char c) { + return isAlpha(c) || isDigit(c); + } + + private void error(String msg) { + throw new RuntimeException("[Lexer] line " + line + ", col " + col + ": " + msg); + } + + private static final Map keywords = new HashMap<>(); + static { + keywords.put("let", TokenType.LET); + keywords.put("if", TokenType.IF); + keywords.put("else", TokenType.ELSE); + keywords.put("while", TokenType.WHILE); + keywords.put("for", TokenType.FOR); + keywords.put("true", TokenType.TRUE); + keywords.put("false", TokenType.FALSE); + keywords.put("nil", TokenType.NIL); + } + } + + // ==== AST ==== + + interface Expr { + R accept(ExprVisitor v); + } + + interface ExprVisitor { + R visitBinary(Binary e); + + R visitUnary(Unary e); + + R visitLiteral(Literal e); + + R visitGrouping(Grouping e); + + R visitVariable(Variable e); + + R visitAssign(Assign e); + } + + static final class Binary implements Expr { + final Expr left; + final Token op; + final Expr right; + + Binary(Expr l, Token o, Expr r) { + left = l; + op = o; + right = r; + } + + public R accept(ExprVisitor v) { + return v.visitBinary(this); + } + } + + static final class Unary implements Expr { + final Token op; + final Expr right; + + Unary(Token o, Expr r) { + op = o; + right = r; + } + + public R accept(ExprVisitor v) { + return v.visitUnary(this); + } + } + + static final class Literal implements Expr { + final Object value; + + Literal(Object v) { + value = v; + } + + public R accept(ExprVisitor v) { + return v.visitLiteral(this); + } + } + + static final class Grouping implements Expr { + final Expr expr; + + Grouping(Expr e) { + expr = e; + } + + public R accept(ExprVisitor v) { + return v.visitGrouping(this); + } + } + + static final class Variable implements Expr { + final Token name; + + Variable(Token n) { + name = n; + } + + public R accept(ExprVisitor v) { + return v.visitVariable(this); + } + } + + static final class Assign implements Expr { + final Token name; + final Expr value; + + Assign(Token n, Expr v) { + name = n; + value = v; + } + + public R accept(ExprVisitor v) { + return v.visitAssign(this); + } + } + + interface Stmt { + void accept(StmtVisitor v); + } + + interface StmtVisitor { + void visitExprStmt(ExprStmt s); + + void visitPrintStmt(PrintStmt s); // (not exposed, handy for debugging) + + void visitVarStmt(Var s); + + void visitBlockStmt(Block s); + + void visitIfStmt(If s); + + void visitWhileStmt(While s); + + void visitForStmt(For s); + + void visitSetBlockStmt(SetBlock s); + } + + static final class ExprStmt implements Stmt { + final Expr expr; + + ExprStmt(Expr e) { + expr = e; + } + + public void accept(StmtVisitor v) { + v.visitExprStmt(this); + } + } + + static final class PrintStmt implements Stmt { + final Expr expr; + + PrintStmt(Expr e) { + expr = e; + } + + public void accept(StmtVisitor v) { + v.visitPrintStmt(this); + } + } + + static final class Var implements Stmt { + final Token name; + final Expr initializer; + + Var(Token n, Expr init) { + name = n; + initializer = init; + } + + public void accept(StmtVisitor v) { + v.visitVarStmt(this); + } + } + + static final class Block implements Stmt { + final List statements; + + Block(List s) { + statements = s; + } + + public void accept(StmtVisitor v) { + v.visitBlockStmt(this); + } + } + + static final class If implements Stmt { + final Expr condition; + final Stmt thenBranch; + final Stmt elseBranch; + + If(Expr c, Stmt t, Stmt e) { + condition = c; + thenBranch = t; + elseBranch = e; + } + + public void accept(StmtVisitor v) { + v.visitIfStmt(this); + } + } + + static final class While implements Stmt { + final Expr condition; + final Stmt body; + + While(Expr c, Stmt b) { + condition = c; + body = b; + } + + public void accept(StmtVisitor v) { + v.visitWhileStmt(this); + } + } + + static final class For implements Stmt { + final Stmt initializer; + final Expr condition; + final Stmt increment; + final Stmt body; + + For(Stmt init, Expr cond, Stmt inc, Stmt body) { + initializer = init; + condition = cond; + increment = inc; + this.body = body; + } + + public void accept(StmtVisitor v) { + v.visitForStmt(this); + } + } + + static final class SetBlock implements Stmt { + final Expr x, y, z, block; + + SetBlock(Expr x, Expr y, Expr z, Expr block) { + this.x = x; + this.y = y; + this.z = z; + this.block = block; + } + + public void accept(StmtVisitor v) { + v.visitSetBlockStmt(this); + } + } + + // ==== Parser ==== + + static final class Parser { + private final List tokens; + private int current = 0; + + Parser(List tokens) { + this.tokens = tokens; + } + + List parse() { + List stmts = new ArrayList<>(); + while (!isAtEnd()) + stmts.add(declaration()); + return stmts; + } + + private Stmt declaration() { + if (match(TokenType.LET)) + return varDeclaration(); + return statement(); + } + + private Stmt varDeclaration() { + Token name = consume(TokenType.IDENTIFIER, "Expect variable name."); + Expr init = null; + if (match(TokenType.EQUAL)) + init = expression(); + consume(TokenType.SEMICOLON, "Expect ';' after variable declaration."); + return new Var(name, init == null ? new Literal(null) : init); + } + + private Stmt statement() { + if (match(TokenType.LEFT_BRACE)) + return new Block(block()); + if (match(TokenType.IF)) + return ifStatement(); + if (match(TokenType.WHILE)) + return whileStatement(); + if (match(TokenType.FOR)) + return forStatement(); + if (match(TokenType.SETBLOCK)) + return setBlockStatement(); + return exprStatement(); + } + + private Stmt setBlockStatement() { + consume(TokenType.LEFT_PAREN, "Expect '(' after setBlock."); + Expr x = expression(); + consume(TokenType.COMMA, "Expect ',' after x."); + Expr y = expression(); + consume(TokenType.COMMA, "Expect ',' after y."); + Expr z = expression(); + consume(TokenType.COMMA, "Expect ',' after z."); + Expr block = expression(); + consume(TokenType.RIGHT_PAREN, "Expect ')' after arguments."); + consume(TokenType.SEMICOLON, "Expect ';' after setBlock."); + return new SetBlock(x, y, z, block); + } + + private Stmt ifStatement() { + consume(TokenType.LEFT_PAREN, "Expect '(' after if."); + Expr cond = expression(); + consume(TokenType.RIGHT_PAREN, "Expect ')' after condition."); + Stmt thenB = statement(); + Stmt elseB = null; + if (match(TokenType.ELSE)) + elseB = statement(); + return new If(cond, thenB, elseB); + } + + private Stmt whileStatement() { + consume(TokenType.LEFT_PAREN, "Expect '(' after while."); + Expr cond = expression(); + consume(TokenType.RIGHT_PAREN, "Expect ')' after condition."); + Stmt body = statement(); + return new While(cond, body); + } + + private Stmt forStatement() { + consume(TokenType.LEFT_PAREN, "Expect '(' after for."); + + Stmt init; + if (match(TokenType.SEMICOLON)) { + init = null; + } else if (match(TokenType.LET)) { + init = varDeclarationNoSemi(); + consume(TokenType.SEMICOLON, "Expect ';' after for init."); + } else { + init = exprStatementNoSemi(); + consume(TokenType.SEMICOLON, "Expect ';' after for init."); + } + + Expr cond = null; + if (!check(TokenType.SEMICOLON)) + cond = expression(); + consume(TokenType.SEMICOLON, "Expect ';' after loop condition."); + + Stmt inc = null; + if (!check(TokenType.RIGHT_PAREN)) + inc = exprStatementNoSemi(); + consume(TokenType.RIGHT_PAREN, "Expect ')' after for clauses."); + + Stmt body = statement(); + + // Desugar to: { init; while (cond) { body; inc; } } + if (inc != null) + body = new Block(Arrays.asList(body, inc)); + if (cond == null) + cond = new Literal(true); + Stmt whileStmt = new While(cond, body); + if (init != null) + whileStmt = new Block(Arrays.asList(init, whileStmt)); + return whileStmt; + } + + private Stmt exprStatement() { + Expr e = expression(); + consume(TokenType.SEMICOLON, "Expect ';' after expression."); + return new ExprStmt(e); + } + + private Stmt exprStatementNoSemi() { + Expr e = expression(); + return new ExprStmt(e); + } + + private Stmt varDeclarationNoSemi() { + Token name = consume(TokenType.IDENTIFIER, "Expect variable name."); + Expr init = null; + if (match(TokenType.EQUAL)) + init = expression(); + return new Var(name, init == null ? new Literal(null) : init); + } + + private List block() { + List stmts = new ArrayList<>(); + while (!check(TokenType.RIGHT_BRACE) && !isAtEnd()) + stmts.add(declaration()); + consume(TokenType.RIGHT_BRACE, "Expect '}' after block."); + return stmts; + } + + // Expressions (precedence: equality > comparison > term > factor > unary > + // primary) + private Expr expression() { + return assignment(); + } + + private Expr assignment() { + Expr expr = equality(); + if (match(TokenType.EQUAL)) { + Token equals = previous(); + Expr value = assignment(); + if (expr instanceof Variable) { + Token name = ((Variable) expr).name; + return new Assign(name, value); + } + error(equals, "Invalid assignment target."); + } + return expr; + } + + private Expr equality() { + Expr expr = comparison(); + while (match(TokenType.BANG_EQUAL, TokenType.EQUAL_EQUAL)) { + Token op = previous(); + Expr right = comparison(); + expr = new Binary(expr, op, right); + } + return expr; + } + + private Expr comparison() { + Expr expr = term(); + while (match(TokenType.GREATER, TokenType.GREATER_EQUAL, TokenType.LESS, TokenType.LESS_EQUAL)) { + Token op = previous(); + Expr right = term(); + expr = new Binary(expr, op, right); + } + return expr; + } + + private Expr term() { + Expr expr = factor(); + while (match(TokenType.PLUS, TokenType.MINUS)) { + Token op = previous(); + Expr right = factor(); + expr = new Binary(expr, op, right); + } + return expr; + } + + private Expr factor() { + Expr expr = unary(); + while (match(TokenType.STAR, TokenType.SLASH)) { + Token op = previous(); + Expr right = unary(); + expr = new Binary(expr, op, right); + } + return expr; + } + + private Expr unary() { + if (match(TokenType.BANG, TokenType.MINUS)) { + Token op = previous(); + Expr right = unary(); + return new Unary(op, right); + } + return primary(); + } + + private Expr primary() { + if (match(TokenType.FALSE)) + return new Literal(false); + if (match(TokenType.TRUE)) + return new Literal(true); + if (match(TokenType.NIL)) + return new Literal(null); + if (match(TokenType.NUMBER)) + return new Literal(previous().literal); + if (match(TokenType.STRING)) + return new Literal(previous().literal); + if (match(TokenType.IDENTIFIER)) + return new Variable(previous()); + if (match(TokenType.LEFT_PAREN)) { + Expr e = expression(); + consume(TokenType.RIGHT_PAREN, "Expect ')' after expression."); + return new Grouping(e); + } + error(peek(), "Expect expression."); + return null; // unreachable + } + + // Helpers + private boolean match(TokenType... types) { + for (TokenType t : types) { + if (check(t)) { + advance(); + return true; + } + } + return false; + } + + private boolean check(TokenType t) { + return !isAtEnd() && peek().type == t; + } + + private Token advance() { + if (!isAtEnd()) + current++; + return previous(); + } + + private boolean isAtEnd() { + return peek().type == TokenType.EOF; + } + + private Token peek() { + return tokens.get(current); + } + + private Token previous() { + return tokens.get(current - 1); + } + + private Token consume(TokenType t, String msg) { + if (check(t)) + return advance(); + error(peek(), msg); + return null; // unreachable + } + + private void error(Token t, String msg) { + throw new RuntimeException( + "[Parser] line " + t.line + ", col " + t.col + ": " + msg + " Found '" + t.lexeme + "'"); + } + } + + // ==== Interpreter with step support ==== + + static final class Interpreter implements ExprVisitor, StmtVisitor { + private final Program program; + private final Emitter emitter; + private final Deque envStack = new ArrayDeque<>(); + private final Deque frames = new ArrayDeque<>(); + private boolean initialized = false; + + interface Emitter { + void emit(SetBlockCommand cmd); + } + + Interpreter(Program program, Emitter emitter) { + this.program = program; + this.emitter = emitter; + envStack.push(new Env(null)); + } + + /** + * Executes until either a setBlock is emitted, or the whole program finishes. + * + * @return true if there is more to run; false if finished. + */ + boolean step() { + if (!initialized) { + frames.push(new Frame(program.statements)); + initialized = true; + } + while (!frames.isEmpty()) { + Frame f = frames.peek(); + + // Handle WhileFrame specially + if (f instanceof WhileFrame) { + WhileFrame wf = (WhileFrame) f; + if (isTruthy(evaluate(wf.condition))) { + // Run one iteration body, then come back to this WhileFrame + frames.push(new Frame(singleton(wf.body))); + continue; + } else { + frames.pop(); // loop finished + continue; + } + } + + if (f.ip >= f.stmts.size()) { + frames.pop(); + if (f.onClose != null) + f.onClose.run(); + continue; + } + + Stmt s = f.stmts.get(f.ip++); + int emittedBefore = emittedCount; + s.accept(this); + if (emittedCount > emittedBefore) + return true; // yielded one setBlock + } + return false; // finished + } + + // Track emissions to know when to yield + private int emittedCount = 0; + + private void emit(SetBlockCommand cmd) { + emittedCount++; + emitter.emit(cmd); + } + + // ---- Statements ---- + + public void visitExprStmt(ExprStmt s) { + evaluate(s.expr); + } + + public void visitPrintStmt(PrintStmt s) { + System.out.println(stringify(evaluate(s.expr))); + } + + public void visitVarStmt(Var s) { + Object val = evaluate(s.initializer); + env().define(s.name.lexeme, val); + } + + public void visitBlockStmt(Block s) { + pushEnv(); + frames.push(new Frame(s.statements, () -> popEnv())); + } + + public void visitIfStmt(If s) { + if (isTruthy(evaluate(s.condition))) { + frames.push(new Frame(singleton(s.thenBranch))); + } else if (s.elseBranch != null) { + frames.push(new Frame(singleton(s.elseBranch))); + } + } + + public void visitWhileStmt(While s) { + frames.push(new WhileFrame(s.condition, s.body)); + } + + public void visitForStmt(For s) { + // Parser desugars 'for' into a block+while, so this isn't used. + frames.push(new Frame(singleton(s.body))); + } + + public void visitSetBlockStmt(SetBlock s) { + int x = toInt(evaluate(s.x)); + int y = toInt(evaluate(s.y)); + int z = toInt(evaluate(s.z)); + String block = String.valueOf(evaluate(s.block)); + emit(new SetBlockCommand(x, y, z, block)); + } + + // ---- Expressions ---- + + public Object visitBinary(Binary e) { + Object l = evaluate(e.left); + Object r = evaluate(e.right); + switch (e.op.type) { + case PLUS: + if (l instanceof Double && r instanceof Double) + return (Double) l + (Double) r; + return stringify(l) + stringify(r); + case MINUS: + checkNumber(l, e.op); + checkNumber(r, e.op); + return (Double) l - (Double) r; + case STAR: + checkNumber(l, e.op); + checkNumber(r, e.op); + return (Double) l * (Double) r; + case SLASH: + checkNumber(l, e.op); + checkNumber(r, e.op); + return (Double) l / (Double) r; + case GREATER: + checkNumber(l, e.op); + checkNumber(r, e.op); + return (Double) l > (Double) r; + case GREATER_EQUAL: + checkNumber(l, e.op); + checkNumber(r, e.op); + return (Double) l >= (Double) r; + case LESS: + checkNumber(l, e.op); + checkNumber(r, e.op); + return (Double) l < (Double) r; + case LESS_EQUAL: + checkNumber(l, e.op); + checkNumber(r, e.op); + return (Double) l <= (Double) r; + case EQUAL_EQUAL: + return isEqual(l, r); + case BANG_EQUAL: + return !isEqual(l, r); + default: + throw new RuntimeException("Unknown binary op: " + e.op.type); + } + } + + public Object visitUnary(Unary e) { + Object v = evaluate(e.right); + switch (e.op.type) { + case MINUS: + checkNumber(v, e.op); + return -((Double) v); + case BANG: + return !isTruthy(v); + default: + throw new RuntimeException("Unknown unary op: " + e.op.type); + } + } + + public Object visitLiteral(Literal e) { + return e.value; + } + + public Object visitGrouping(Grouping e) { + return evaluate(e.expr); + } + + public Object visitVariable(Variable e) { + return env().get(e.name); + } + + public Object visitAssign(Assign e) { + Object val = evaluate(e.value); + env().assign(e.name, val); + return val; + } + + private Object evaluate(Expr e) { + return e.accept(this); + } + + // ---- Utilities ---- + + private Env env() { + return envStack.peek(); + } + + private void pushEnv() { + envStack.push(new Env(env())); + } + + private void popEnv() { + envStack.pop(); + } + + private static boolean isTruthy(Object v) { + if (v == null) + return false; + if (v instanceof Boolean) + return (Boolean) v; + if (v instanceof Double) + return ((Double) v) != 0.0; + String s = String.valueOf(v); + return !s.isEmpty(); + } + + private static boolean isEqual(Object a, Object b) { + if (a == null && b == null) + return true; + if (a == null) + return false; + if (a instanceof Double && b instanceof Double) + return ((Double) a).doubleValue() == ((Double) b).doubleValue(); + return String.valueOf(a).equals(String.valueOf(b)); + } + + private static void checkNumber(Object v, Token at) { + if (!(v instanceof Double)) + throw new RuntimeException("Operand must be a number at token '" + at.lexeme + "'"); + } + + private static String stringify(Object v) { + if (v == null) + return "nil"; + if (v instanceof Double) { + double d = (Double) v; + if (d == Math.rint(d)) + return String.valueOf((long) d); + return String.valueOf(d); + } + return String.valueOf(v); + } + + private static int toInt(Object v) { + if (v instanceof Double) + return (int) Math.round((Double) v); + try { + return Integer.parseInt(String.valueOf(v)); + } catch (Exception e) { + throw new RuntimeException("Expected integer-like value, got: " + v); + } + } + + // ---- Frames for stepping ---- + + static class Frame { + final List stmts; + int ip = 0; + final Runnable onClose; // optional cleanup (e.g., pop env) + + Frame(List stmts) { + this(stmts, null); + } + + Frame(List stmts, Runnable onClose) { + this.stmts = stmts; + this.onClose = onClose; + } + } + + static final class WhileFrame extends Frame { + final Expr condition; + final Stmt body; + + WhileFrame(Expr condition, Stmt body) { + super(Collections.emptyList()); + this.condition = condition; + this.body = body; + } + } + + private static List singleton(Stmt s) { + return Collections.singletonList(s); + } + } + + // ==== Environment / Variables ==== + + static final class Env { + private final Env enclosing; + private final Map values = new HashMap<>(); + + Env(Env enclosing) { + this.enclosing = enclosing; + } + + void define(String name, Object value) { + values.put(name, value); + } + + Object get(Token nameTok) { + String name = nameTok.lexeme; + if (values.containsKey(name)) + return values.get(name); + if (enclosing != null) + return enclosing.get(nameTok); + throw new RuntimeException("Undefined variable '" + name + "'."); + } + + void assign(Token nameTok, Object value) { + String name = nameTok.lexeme; + if (values.containsKey(name)) { + values.put(name, value); + return; + } + if (enclosing != null) { + enclosing.assign(nameTok, value); + return; + } + throw new RuntimeException("Undefined variable '" + name + "'."); + } + } + + // static String code = String.join("\n", + // "let baseX = 10;", + // "let baseY = 64;", + // "let baseZ = 10;", + // "let dir = \"north\";", + // "let block = \"stone\";", + // "", + // "// Build a 3x2 wall:", + // "for (let i = 0; i < 3; i = i + 1) {", + // " for (let j = 0; j < 2; j = j + 1) {", + // " setBlock(baseX + i, baseY + j, baseZ, block);", + // " }", + // "}", + // "", + // "// If we want a cap:", + // "if (true) {", + // " setBlock(baseX + 1, baseY + 2, baseZ, \"glass\");", + // "}"); + + public static void buildStructureFromCode(String code, Consumer onSetBlock, + Consumer onErrString) { + try { + // run once to validate + BuildStructureFromCode.runCode(code, + (_unused) -> { + // set block is nothing + }); + LOGGER.info("Code validated, running code for real now."); + // code validated, can safely set block + BuildStructureFromCode.runCode(code, onSetBlock); + } catch (Exception e) { + LOGGER.error("LLM build structure err={} ", e.getMessage()); + onErrString.accept(e.getMessage()); + } + } + +} From 788049fafbb7a5b69fe46f88f118e153c1c756a4 Mon Sep 17 00:00:00 2001 From: lorisj-elefant Date: Mon, 8 Sep 2025 08:43:21 -0700 Subject: [PATCH 2/9] hooked up buildStructure --- .../altoclef/player2api/AgentSideEffects.java | 4 +- .../altoclef/player2api/EventQueueData.java | 3 +- .../player2api/EventQueueManager.java | 1 - .../altoclef/player2api/LLMCompleter.java | 49 ++++++++++++++++++- .../codegen/BuildStructure.java | 30 ++++++++++-- 5 files changed, 76 insertions(+), 11 deletions(-) diff --git a/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java b/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java index 8842a59d..aaa93885 100644 --- a/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java +++ b/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java @@ -76,11 +76,11 @@ public static void onCommandListGenerated(AltoClefController mod, String command PseudoCommands.process(pseudoCommand, commandWithPrefix); }, () -> { - executeNormalCommand(mod, commandWithPrefix, onStop); + executeAltoclefCommand(mod, commandWithPrefix, onStop); }); } - private static void executeNormalCommand(AltoClefController mod, String commandWithPrefix, + private static void executeAltoclefCommand(AltoClefController mod, String commandWithPrefix, Consumer onStop) { CommandExecutor cmdExecutor = mod.getCommandExecutor(); cmdExecutor.execute(commandWithPrefix, () -> { diff --git a/src/autoclef/java/adris/altoclef/player2api/EventQueueData.java b/src/autoclef/java/adris/altoclef/player2api/EventQueueData.java index 98fbce34..a4fb49fa 100644 --- a/src/autoclef/java/adris/altoclef/player2api/EventQueueData.java +++ b/src/autoclef/java/adris/altoclef/player2api/EventQueueData.java @@ -115,7 +115,8 @@ public void process( this.isProcessing = false; } }; - completer.process(mod.getPlayer2APIService(), historyWithWrappedStatus, onLLMResponse, onErrMsg); + completer.processWithJsonResponse(mod.getPlayer2APIService(), historyWithWrappedStatus, onLLMResponse, + onErrMsg); } private boolean isEventDuplicateOfLastMessage(Event evt) { diff --git a/src/autoclef/java/adris/altoclef/player2api/EventQueueManager.java b/src/autoclef/java/adris/altoclef/player2api/EventQueueManager.java index abaeb41a..3fc3744e 100644 --- a/src/autoclef/java/adris/altoclef/player2api/EventQueueManager.java +++ b/src/autoclef/java/adris/altoclef/player2api/EventQueueManager.java @@ -1,7 +1,6 @@ package adris.altoclef.player2api; import java.util.Comparator; -import java.util.List; import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; diff --git a/src/autoclef/java/adris/altoclef/player2api/LLMCompleter.java b/src/autoclef/java/adris/altoclef/player2api/LLMCompleter.java index f5215ca6..861ff96f 100644 --- a/src/autoclef/java/adris/altoclef/player2api/LLMCompleter.java +++ b/src/autoclef/java/adris/altoclef/player2api/LLMCompleter.java @@ -17,7 +17,52 @@ public class LLMCompleter { private static final ExecutorService llmThread = Executors.newSingleThreadExecutor(); private static List llmCompleters = List.of(new LLMCompleter()); - public void process( + public void processWithStringResponse( + Player2APIService player2apiService, + ConversationHistory history, + Consumer extOnLLMResponse, + Consumer extOnErrMsg) { + if (isProcessing) { + LOGGER.warn("Called llmcompleter.process when it was already processing! This should not happen."); + return; + } + Consumer onLLMResponse = resp -> { + try { + extOnLLMResponse.accept(resp); + } catch (Exception e) { + LOGGER.error( + "[EventQueueManager/LLMCompleter/process/onLLMResponse]: Error in external llm resp, errMsg={} llmResp={}", + e.getMessage(), resp.toString()); + } finally { + LOGGER.info("Done processing, isprocessing -> false"); + isProcessing = false; + } + }; + Consumer onErrMsg = errMsg -> { + try { + extOnErrMsg.accept(errMsg); + } catch (Exception e) { + LOGGER.error( + "[EventQueueManager/LLMCompleter/process/onErrMsg]: Error in external onErrmsg, errMsgFromException={} errMsg={}", + e.getMessage(), errMsg); + } finally { + isProcessing = false; + } + }; + isProcessing = true; + llmThread.submit(() -> { + try { + String response = player2apiService.completeConversationToString(history); + LOGGER.info("LLMCompleter returned as string={}", response); + onLLMResponse.accept(response); + } catch (Exception e) { + onErrMsg.accept( + e.getMessage() == null ? "Unknown error from CompleteConversation API" : e.getMessage()); + } + }); + } + + public void processWithJsonResponse( Player2APIService player2apiService, ConversationHistory history, Consumer extOnLLMResponse, @@ -69,7 +114,7 @@ public boolean isAvailible() { public static void processUsingAvailibleCompleter(Consumer processer) { Stream availibles = llmCompleters.stream().filter(LLMCompleter::isAvailible); if (availibles.toArray().length < 1) { - LOGGER.error("ALL LLM COMPLETERS BUSY, should not happen. Some locking error occured"); + LOGGER.error("ALL LLM COMPLETERS BUSY, should not happen. Some locking error has occured"); } llmCompleters.stream().filter(LLMCompleter::isAvailible).forEach(processer); } diff --git a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java index 91787716..d12bff58 100644 --- a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java +++ b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java @@ -1,6 +1,7 @@ package adris.altoclef.player2api.pseudocommands.codegen; -import com.google.gson.JsonObject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import adris.altoclef.player2api.ConversationHistory; import adris.altoclef.player2api.LLMCompleter; @@ -9,22 +10,41 @@ import adris.altoclef.player2api.Prompts; public class BuildStructure { + public static final Logger LOGGER = LogManager.getLogger(); - private static void onLLMResponse(JsonObject obj) { + private static String llmReponseToCode(String input) { + // remove formatting, markdown, etc. + return input; + } + private static void onLLMResponse(String llmResponse) { + LockManager.setCodeGenLock(false); + LOGGER.info("llm responsed with={}", llmResponse); + String code = llmReponseToCode(llmResponse); + LOGGER.info("processed code into={}", code); + BuildStructureFromCode.buildStructureFromCode(code, (setBlockData) -> { + LOGGER.info("Setblock(x={}, y={}, z={}, blockName={})", setBlockData.x, setBlockData.y, setBlockData.z, + setBlockData.blockName); + }, (errStr) -> { + LOGGER.error("Building err={}", errStr); + }); } private static void onLLMErrMsg(String errMsg) { - + LOGGER.error("Err calling LLM={}", errMsg); + LockManager.setCodeGenLock(false); } public static void buildStructure(String description, Player2APIService service) { LockManager.setCodeGenLock(true); - // 1) get code from LLM + // get code from LLM ConversationHistory history = new ConversationHistory(Prompts.getBuildStructurePrompt()); + history.addUserMessage(String.format("Generate the code given this description: %s", description), service); LLMCompleter.processUsingAvailibleCompleter( (cmp) -> { - cmp.process(service, history, BuildStructure::onLLMResponse, BuildStructure::onLLMErrMsg); + cmp.processWithStringResponse(service, history, + BuildStructure::onLLMResponse, + BuildStructure::onLLMErrMsg); }); } } From 14832f11b103427e83d2308e6ddced7840377ef5 Mon Sep 17 00:00:00 2001 From: lorisj-elefant Date: Mon, 8 Sep 2025 14:26:45 -0700 Subject: [PATCH 3/9] more fixes --- .../altoclef/player2api/AgentSideEffects.java | 9 ++-- .../altoclef/player2api/EventQueueData.java | 12 +++-- .../player2api/EventQueueManager.java | 16 ++++-- .../altoclef/player2api/LLMCompleter.java | 14 +++-- .../altoclef/player2api/LockManager.java | 11 ++-- .../player2api/Player2APIService.java | 10 ++-- .../pseudocommands/PseudoCommands.java | 16 +++--- .../codegen/BuildStructure.java | 52 +++++++++++++------ 8 files changed, 89 insertions(+), 51 deletions(-) diff --git a/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java b/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java index aaa93885..23d0b3ee 100644 --- a/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java +++ b/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java @@ -32,7 +32,8 @@ record Error(String commandName, String errMsg) implements CommandExecutionStopR } } - public static void onEntityMessage(MinecraftServer server, Event.CharacterMessage characterMessage) { + public static void onEntityMessage(MinecraftServer server, Event.CharacterMessage characterMessage, + LLMCompleter completer, Player2APIService service) { // message part: if (characterMessage.message() != null && !characterMessage.message().isBlank()) { EventQueueData sendingCharacterData = characterMessage.sendingCharacterData(); @@ -52,7 +53,7 @@ public static void onEntityMessage(MinecraftServer server, Event.CharacterMessag // command part: if (characterMessage.command() != null && !characterMessage.command().isBlank()) { onCommandListGenerated(characterMessage.sendingCharacterData().getMod(), characterMessage.command(), - characterMessage.sendingCharacterData()::onCommandFinish); + characterMessage.sendingCharacterData()::onCommandFinish, completer, service); } } @@ -61,7 +62,7 @@ public static void onError(MinecraftServer server, String errMsg) { } public static void onCommandListGenerated(AltoClefController mod, String command, - Consumer onStop) { + Consumer onStop, LLMCompleter completer, Player2APIService service) { CommandExecutor cmdExecutor = mod.getCommandExecutor(); String commandWithPrefix = cmdExecutor.isClientCommand(command) ? command : (cmdExecutor.getCommandPrefix() + command); @@ -73,7 +74,7 @@ public static void onCommandListGenerated(AltoClefController mod, String command PseudoCommands.getPseudocommandOption(commandWithPrefix).ifPresentOrElse( (pseudoCommand) -> { - PseudoCommands.process(pseudoCommand, commandWithPrefix); + PseudoCommands.process(pseudoCommand, commandWithPrefix, completer, service); }, () -> { executeAltoclefCommand(mod, commandWithPrefix, onStop); diff --git a/src/autoclef/java/adris/altoclef/player2api/EventQueueData.java b/src/autoclef/java/adris/altoclef/player2api/EventQueueData.java index a4fb49fa..6335b38d 100644 --- a/src/autoclef/java/adris/altoclef/player2api/EventQueueData.java +++ b/src/autoclef/java/adris/altoclef/player2api/EventQueueData.java @@ -4,10 +4,12 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.function.BiConsumer; import java.util.function.Consumer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.util.TriConsumer; import com.google.gson.JsonObject; @@ -57,7 +59,7 @@ public long getPriority() { // get LLM response and add to conversation history public void process( - Consumer onCharacterEvent, + TriConsumer onCharacterEvent, Consumer extOnErrMsg, LLMCompleter completer) { @@ -77,10 +79,10 @@ public void process( this.lastProcessTime = System.nanoTime(); this.isProcessing = true; - + Player2APIService service = mod.getPlayer2APIService(); // prepare conversation history for LLM call Event lastEvent = mod.getAIPersistantData().dumpEventQueueToConversationHistoryAndReturnLastEvent(eventQueue, - mod.getPlayer2APIService()); + service); Optional reminderString = getReminderStringFromLastEvent(lastEvent); String agentStatus = AgentStatus.fromMod(this.mod).toString(); @@ -93,7 +95,7 @@ public void process( LOGGER.info("[AICommandBridge/processChatWithAPI]: Calling LLM: history={}", new Object[] { historyWithWrappedStatus.toString() }); - Consumer onLLMResponse = jsonResp -> { + BiConsumer onLLMResponse = (jsonResp, cmp) -> { String llmMessage = Utils.getStringJsonSafely(jsonResp, "message"); String command = this.isGreetingResponse ? "bodylang greeting" : Utils.getStringJsonSafely(jsonResp, "command"); @@ -103,7 +105,7 @@ public void process( try { if (llmMessage != null || command != null) { mod.getAIPersistantData().addAssistantMessage(llmMessage, mod.getPlayer2APIService()); - onCharacterEvent.accept(new Event.CharacterMessage(llmMessage, command, this)); + onCharacterEvent.accept(new Event.CharacterMessage(llmMessage, command, this), completer, service); } else { LOGGER.warn( "[AICommandBridge/processChatWithAPI/onLLMResponse]: Generated null llm message and command"); diff --git a/src/autoclef/java/adris/altoclef/player2api/EventQueueManager.java b/src/autoclef/java/adris/altoclef/player2api/EventQueueManager.java index 3fc3744e..4a99a559 100644 --- a/src/autoclef/java/adris/altoclef/player2api/EventQueueManager.java +++ b/src/autoclef/java/adris/altoclef/player2api/EventQueueManager.java @@ -4,12 +4,14 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.util.TriConsumer; import adris.altoclef.AltoClefController; import adris.altoclef.player2api.Event.UserMessage; @@ -78,7 +80,8 @@ public static void onAICharacterMessage(Event.CharacterMessage msg, UUID senderI }); } - private static void process(Consumer onCharacterEvent, Consumer onErrEvent) { + private static void process(TriConsumer onCharacterEvent, + Consumer onErrEvent) { Optional dataToProcess = queueData.values().stream().filter(data -> { return data.getPriority() != 0; }).max(Comparator.comparingLong(EventQueueData::getPriority)); @@ -95,14 +98,19 @@ public static void injectOnTick(MinecraftServer server) { init(); } - Consumer onCharacterEvent = (data) -> { - AgentSideEffects.onEntityMessage(server, data); + TriConsumer onCharacterEvent = (data, completer, + service) -> { + AgentSideEffects.onEntityMessage(server, data, completer, service); }; Consumer onErrEvent = (errMsg) -> { AgentSideEffects.onError(server, errMsg); }; if (!LockManager.globalIsLocked()) { - process(onCharacterEvent, onErrEvent); + LLMCompleter.processUsingAvailibleCompleter( + (cmp) -> { + process(onCharacterEvent, onErrEvent); + + }); } TTSManager.injectOnTick(server); } diff --git a/src/autoclef/java/adris/altoclef/player2api/LLMCompleter.java b/src/autoclef/java/adris/altoclef/player2api/LLMCompleter.java index 861ff96f..056f4165 100644 --- a/src/autoclef/java/adris/altoclef/player2api/LLMCompleter.java +++ b/src/autoclef/java/adris/altoclef/player2api/LLMCompleter.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.stream.Stream; @@ -20,7 +21,7 @@ public class LLMCompleter { public void processWithStringResponse( Player2APIService player2apiService, ConversationHistory history, - Consumer extOnLLMResponse, + BiConsumer extOnLLMResponse, Consumer extOnErrMsg) { if (isProcessing) { LOGGER.warn("Called llmcompleter.process when it was already processing! This should not happen."); @@ -28,7 +29,7 @@ public void processWithStringResponse( } Consumer onLLMResponse = resp -> { try { - extOnLLMResponse.accept(resp); + extOnLLMResponse.accept(resp, this); } catch (Exception e) { LOGGER.error( "[EventQueueManager/LLMCompleter/process/onLLMResponse]: Error in external llm resp, errMsg={} llmResp={}", @@ -65,15 +66,16 @@ public void processWithStringResponse( public void processWithJsonResponse( Player2APIService player2apiService, ConversationHistory history, - Consumer extOnLLMResponse, + BiConsumer extOnLLMResponse, Consumer extOnErrMsg) { if (isProcessing) { LOGGER.warn("Called llmcompleter.process when it was already processing! This should not happen."); return; } + Consumer onLLMResponse = resp -> { try { - extOnLLMResponse.accept(resp); + extOnLLMResponse.accept(resp, this); } catch (Exception e) { LOGGER.error( "[EventQueueManager/LLMCompleter/process/onLLMResponse]: Error in external llm resp, errMsg={} llmResp={}", @@ -83,6 +85,7 @@ public void processWithJsonResponse( isProcessing = false; } }; + Consumer onErrMsg = errMsg -> { try { extOnErrMsg.accept(errMsg); @@ -112,6 +115,9 @@ public boolean isAvailible() { } public static void processUsingAvailibleCompleter(Consumer processer) { + if (LockManager.globalIsLocked()) { + return; + } Stream availibles = llmCompleters.stream().filter(LLMCompleter::isAvailible); if (availibles.toArray().length < 1) { LOGGER.error("ALL LLM COMPLETERS BUSY, should not happen. Some locking error has occured"); diff --git a/src/autoclef/java/adris/altoclef/player2api/LockManager.java b/src/autoclef/java/adris/altoclef/player2api/LockManager.java index f20a80d5..4c3e8ea4 100644 --- a/src/autoclef/java/adris/altoclef/player2api/LockManager.java +++ b/src/autoclef/java/adris/altoclef/player2api/LockManager.java @@ -5,25 +5,24 @@ public class LockManager { private static Logger LOGGER = LogManager.getLogger(); - + private static boolean ttsLocked = false; private static boolean codeGenLocked = false; - - public static boolean isTTSLocked(){ + public static boolean isTTSLocked() { return ttsLocked; } - public static boolean globalIsLocked(){ + public static boolean globalIsLocked() { return ttsLocked || codeGenLocked; } - public static void setTTS(boolean onOrOff){ + public static void setTTS(boolean onOrOff) { LOGGER.info(String.format("TTS: %s lock", onOrOff ? "setting" : "releasing")); ttsLocked = onOrOff; } - public static void setCodeGenLock(boolean onOrOff){ + public static void setCodeGenLock(boolean onOrOff) { LOGGER.info(String.format("CodeGen: %s lock", onOrOff ? "setting" : "releasing")); codeGenLocked = onOrOff; } diff --git a/src/autoclef/java/adris/altoclef/player2api/Player2APIService.java b/src/autoclef/java/adris/altoclef/player2api/Player2APIService.java index dc21cbc0..b0e3b165 100644 --- a/src/autoclef/java/adris/altoclef/player2api/Player2APIService.java +++ b/src/autoclef/java/adris/altoclef/player2api/Player2APIService.java @@ -12,18 +12,22 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; import java.util.HashMap; + public class Player2APIService { private static final Logger LOGGER = LogManager.getLogger(); private String player2GameID; - public Player2APIService(String player2GameID){ + public Player2APIService(String player2GameID) { this.player2GameID = player2GameID; } - private Map sendRequest(String endpoint, boolean postRequest, JsonObject requestBody) throws Exception{ + + private Map sendRequest(String endpoint, boolean postRequest, JsonObject requestBody) + throws Exception { Map headers = getHeaders(player2GameID); return HTTPUtils.sendRequest(endpoint, postRequest, requestBody, headers); } + public JsonObject completeConversation(ConversationHistory conversationHistory) throws Exception { JsonObject requestBody = new JsonObject(); JsonArray messagesArray = new JsonArray(); @@ -144,7 +148,7 @@ public void player2ProcessConnection(HttpURLConnection connection) { } } - public static Map getHeaders(String player2Apikey){ + public static Map getHeaders(String player2Apikey) { Map headers = new HashMap<>(); headers.put("player2-game-key", player2Apikey); return headers; diff --git a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/PseudoCommands.java b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/PseudoCommands.java index ea2376af..83ab2986 100644 --- a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/PseudoCommands.java +++ b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/PseudoCommands.java @@ -6,7 +6,9 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import adris.altoclef.player2api.LockManager; +import adris.altoclef.player2api.LLMCompleter; +import adris.altoclef.player2api.Player2APIService; +import adris.altoclef.player2api.pseudocommands.codegen.BuildStructure; public class PseudoCommands { public static Logger LOGGER = LogManager.getLogger(); @@ -37,18 +39,16 @@ public static Optional getPseudocommandOption(String cmd) { return pseudoCommands.stream().filter((p) -> cmd.contains(p.getName())).findFirst(); } - public static void process(PseudoCommand cmd, String commandWithPrefix) { + public static void process(PseudoCommand cmd, String commandWithPrefix, LLMCompleter completer, + Player2APIService service) { LOGGER.info("Processing PseudoCommand={}, commandWithPrefix={}", cmd, commandWithPrefix); switch (cmd.name) { case "build_structure": - processBuildStructure(commandWithPrefix.split("build_structure")[1].strip()); // after build_structure - // is desc + String description = commandWithPrefix.split("build_structure")[1].strip(); + BuildStructure.buildStructure(description, completer, service); + break; default: LOGGER.error("for cmdWithPrefix={} could not find matching pseudocommand", commandWithPrefix); } } - - private static void processBuildStructure(String description) { - - } } diff --git a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java index d12bff58..3fb9e9a7 100644 --- a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java +++ b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java @@ -11,40 +11,58 @@ public class BuildStructure { public static final Logger LOGGER = LogManager.getLogger(); + private static int numErrors = 0; + + private static final int maxNumErrors = 2; + + private static void onLLMErrMsg(String errMsg, LLMCompleter completer, Player2APIService service) { + if (numErrors < maxNumErrors) { + numErrors += 1; + LOGGER.error("Err calling LLM={}", errMsg); + buildStructureInternal(errMsg, completer, service); + return; + } + numErrors = 0; + LockManager.setCodeGenLock(false); + } private static String llmReponseToCode(String input) { // remove formatting, markdown, etc. return input; } - private static void onLLMResponse(String llmResponse) { - LockManager.setCodeGenLock(false); + private static void onLLMResponse(String llmResponse, LLMCompleter completer) { LOGGER.info("llm responsed with={}", llmResponse); String code = llmReponseToCode(llmResponse); - LOGGER.info("processed code into={}", code); + LOGGER.info("processed response into code={}", code); BuildStructureFromCode.buildStructureFromCode(code, (setBlockData) -> { - LOGGER.info("Setblock(x={}, y={}, z={}, blockName={})", setBlockData.x, setBlockData.y, setBlockData.z, + LOGGER.info("setblock(x={}, y={}, z={}, blockName={})", + setBlockData.x, + setBlockData.y, + setBlockData.z, setBlockData.blockName); }, (errStr) -> { - LOGGER.error("Building err={}", errStr); + LOGGER.error("While building got err={}", errStr); }); } - private static void onLLMErrMsg(String errMsg) { - LOGGER.error("Err calling LLM={}", errMsg); - LockManager.setCodeGenLock(false); - } - - public static void buildStructure(String description, Player2APIService service) { - LockManager.setCodeGenLock(true); + private static void buildStructureInternal(String description, LLMCompleter completer, Player2APIService service) { // get code from LLM ConversationHistory history = new ConversationHistory(Prompts.getBuildStructurePrompt()); history.addUserMessage(String.format("Generate the code given this description: %s", description), service); - LLMCompleter.processUsingAvailibleCompleter( - (cmp) -> { - cmp.processWithStringResponse(service, history, - BuildStructure::onLLMResponse, - BuildStructure::onLLMErrMsg); + completer.processWithStringResponse( + service, + history, + BuildStructure::onLLMResponse, + (errMsg) -> { + onLLMErrMsg(errMsg, completer, service); }); } + + // external + public static void buildStructure(String description, LLMCompleter completer, Player2APIService service) { + LockManager.setCodeGenLock(true); + numErrors = 0; + buildStructureInternal(description, completer, service); + } } From 3b5549c6f94a5e76eb377e1f54c3dbb6c3e78a86 Mon Sep 17 00:00:00 2001 From: lorisj-elefant Date: Mon, 8 Sep 2025 14:55:55 -0700 Subject: [PATCH 4/9] quick fix --- .../altoclef/player2api/LLMCompleter.java | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/autoclef/java/adris/altoclef/player2api/LLMCompleter.java b/src/autoclef/java/adris/altoclef/player2api/LLMCompleter.java index 056f4165..a3d30899 100644 --- a/src/autoclef/java/adris/altoclef/player2api/LLMCompleter.java +++ b/src/autoclef/java/adris/altoclef/player2api/LLMCompleter.java @@ -13,7 +13,7 @@ public class LLMCompleter { public static final Logger LOGGER = LogManager.getLogger(); - private boolean isProcessing = false; + private boolean isCallingLLM = false; private static final ExecutorService llmThread = Executors.newSingleThreadExecutor(); private static List llmCompleters = List.of(new LLMCompleter()); @@ -23,11 +23,12 @@ public void processWithStringResponse( ConversationHistory history, BiConsumer extOnLLMResponse, Consumer extOnErrMsg) { - if (isProcessing) { + if (isCallingLLM) { LOGGER.warn("Called llmcompleter.process when it was already processing! This should not happen."); return; } Consumer onLLMResponse = resp -> { + isCallingLLM = false; try { extOnLLMResponse.accept(resp, this); } catch (Exception e) { @@ -36,21 +37,19 @@ public void processWithStringResponse( e.getMessage(), resp.toString()); } finally { LOGGER.info("Done processing, isprocessing -> false"); - isProcessing = false; } }; Consumer onErrMsg = errMsg -> { + isCallingLLM = false; try { extOnErrMsg.accept(errMsg); } catch (Exception e) { LOGGER.error( "[EventQueueManager/LLMCompleter/process/onErrMsg]: Error in external onErrmsg, errMsgFromException={} errMsg={}", e.getMessage(), errMsg); - } finally { - isProcessing = false; } }; - isProcessing = true; + isCallingLLM = true; llmThread.submit(() -> { try { String response = player2apiService.completeConversationToString(history); @@ -68,7 +67,7 @@ public void processWithJsonResponse( ConversationHistory history, BiConsumer extOnLLMResponse, Consumer extOnErrMsg) { - if (isProcessing) { + if (isCallingLLM) { LOGGER.warn("Called llmcompleter.process when it was already processing! This should not happen."); return; } @@ -82,7 +81,7 @@ public void processWithJsonResponse( e.getMessage(), resp.toString()); } finally { LOGGER.info("Done processing, isprocessing -> false"); - isProcessing = false; + isCallingLLM = false; } }; @@ -94,10 +93,10 @@ public void processWithJsonResponse( "[EventQueueManager/LLMCompleter/process/onErrMsg]: Error in external onErrmsg, errMsgFromException={} errMsg={}", e.getMessage(), errMsg); } finally { - isProcessing = false; + isCallingLLM = false; } }; - isProcessing = true; + isCallingLLM = true; llmThread.submit(() -> { try { JsonObject response = player2apiService.completeConversation(history); @@ -111,7 +110,7 @@ public void processWithJsonResponse( } public boolean isAvailible() { - return !isProcessing; + return !isCallingLLM; } public static void processUsingAvailibleCompleter(Consumer processer) { From 776a631f8f4180bb3fcfe563faeb6f8997743b5d Mon Sep 17 00:00:00 2001 From: lorisj-elefant Date: Tue, 9 Sep 2025 11:44:44 -0700 Subject: [PATCH 5/9] fixed code gen but still 500 err --- .../altoclef/player2api/AgentSideEffects.java | 2 +- .../player2api/EventQueueManager.java | 3 +- .../altoclef/player2api/LLMCompleter.java | 33 +- .../altoclef/player2api/LockManager.java | 17 +- .../adris/altoclef/player2api/Prompts.java | 768 +++++++++--------- .../pseudocommands/PseudoCommands.java | 6 +- .../codegen/BuildStructure.java | 173 +++- 7 files changed, 564 insertions(+), 438 deletions(-) diff --git a/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java b/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java index 23d0b3ee..2ed7392c 100644 --- a/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java +++ b/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java @@ -74,7 +74,7 @@ public static void onCommandListGenerated(AltoClefController mod, String command PseudoCommands.getPseudocommandOption(commandWithPrefix).ifPresentOrElse( (pseudoCommand) -> { - PseudoCommands.process(pseudoCommand, commandWithPrefix, completer, service); + PseudoCommands.process(pseudoCommand, commandWithPrefix, completer, service, mod); }, () -> { executeAltoclefCommand(mod, commandWithPrefix, onStop); diff --git a/src/autoclef/java/adris/altoclef/player2api/EventQueueManager.java b/src/autoclef/java/adris/altoclef/player2api/EventQueueManager.java index 4a99a559..282374f1 100644 --- a/src/autoclef/java/adris/altoclef/player2api/EventQueueManager.java +++ b/src/autoclef/java/adris/altoclef/player2api/EventQueueManager.java @@ -105,11 +105,10 @@ public static void injectOnTick(MinecraftServer server) { Consumer onErrEvent = (errMsg) -> { AgentSideEffects.onError(server, errMsg); }; - if (!LockManager.globalIsLocked()) { + if (!LockManager.processingNextQueueLock()) { LLMCompleter.processUsingAvailibleCompleter( (cmp) -> { process(onCharacterEvent, onErrEvent); - }); } TTSManager.injectOnTick(server); diff --git a/src/autoclef/java/adris/altoclef/player2api/LLMCompleter.java b/src/autoclef/java/adris/altoclef/player2api/LLMCompleter.java index a3d30899..ccd98954 100644 --- a/src/autoclef/java/adris/altoclef/player2api/LLMCompleter.java +++ b/src/autoclef/java/adris/altoclef/player2api/LLMCompleter.java @@ -9,6 +9,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.util.TriConsumer; + import com.google.gson.JsonObject; public class LLMCompleter { @@ -21,25 +23,30 @@ public class LLMCompleter { public void processWithStringResponse( Player2APIService player2apiService, ConversationHistory history, - BiConsumer extOnLLMResponse, + TriConsumer extOnLLMResponse, Consumer extOnErrMsg) { if (isCallingLLM) { - LOGGER.warn("Called llmcompleter.process when it was already processing! This should not happen."); + LOGGER.error( + "Called llmcompleter.process when it was already processing! This should not happen. Cancelling call."); return; } + + LockManager.setOnLLMResponseLock(true); Consumer onLLMResponse = resp -> { + LOGGER.info("Done processing (string llm resp), isprocessing -> false"); isCallingLLM = false; try { - extOnLLMResponse.accept(resp, this); + extOnLLMResponse.accept(resp, this, player2apiService); } catch (Exception e) { LOGGER.error( "[EventQueueManager/LLMCompleter/process/onLLMResponse]: Error in external llm resp, errMsg={} llmResp={}", e.getMessage(), resp.toString()); } finally { - LOGGER.info("Done processing, isprocessing -> false"); + LockManager.setOnLLMResponseLock(false); } }; Consumer onErrMsg = errMsg -> { + LOGGER.info("Done processing (string err), isprocessing -> false"); isCallingLLM = false; try { extOnErrMsg.accept(errMsg); @@ -47,6 +54,8 @@ public void processWithStringResponse( LOGGER.error( "[EventQueueManager/LLMCompleter/process/onErrMsg]: Error in external onErrmsg, errMsgFromException={} errMsg={}", e.getMessage(), errMsg); + } finally { + LockManager.setOnLLMResponseLock(false); } }; isCallingLLM = true; @@ -72,7 +81,10 @@ public void processWithJsonResponse( return; } + LockManager.setOnLLMResponseLock(true); Consumer onLLMResponse = resp -> { + LOGGER.info("Done processing (json llm resp), isprocessing -> false"); + isCallingLLM = false; try { extOnLLMResponse.accept(resp, this); } catch (Exception e) { @@ -80,12 +92,13 @@ public void processWithJsonResponse( "[EventQueueManager/LLMCompleter/process/onLLMResponse]: Error in external llm resp, errMsg={} llmResp={}", e.getMessage(), resp.toString()); } finally { - LOGGER.info("Done processing, isprocessing -> false"); - isCallingLLM = false; + LockManager.setOnLLMResponseLock(false); } }; Consumer onErrMsg = errMsg -> { + LOGGER.info("Done processing (json err), isprocessing -> false"); + isCallingLLM = false; try { extOnErrMsg.accept(errMsg); } catch (Exception e) { @@ -93,10 +106,12 @@ public void processWithJsonResponse( "[EventQueueManager/LLMCompleter/process/onErrMsg]: Error in external onErrmsg, errMsgFromException={} errMsg={}", e.getMessage(), errMsg); } finally { - isCallingLLM = false; + LockManager.setOnLLMResponseLock(false); } }; + isCallingLLM = true; + llmThread.submit(() -> { try { JsonObject response = player2apiService.completeConversation(history); @@ -105,6 +120,8 @@ public void processWithJsonResponse( } catch (Exception e) { onErrMsg.accept( e.getMessage() == null ? "Unknown error from CompleteConversation API" : e.getMessage()); + } finally { + LockManager.setOnLLMResponseLock(false); } }); } @@ -114,7 +131,7 @@ public boolean isAvailible() { } public static void processUsingAvailibleCompleter(Consumer processer) { - if (LockManager.globalIsLocked()) { + if (LockManager.processingNextQueueLock()) { return; } Stream availibles = llmCompleters.stream().filter(LLMCompleter::isAvailible); diff --git a/src/autoclef/java/adris/altoclef/player2api/LockManager.java b/src/autoclef/java/adris/altoclef/player2api/LockManager.java index 4c3e8ea4..8e9fb7a9 100644 --- a/src/autoclef/java/adris/altoclef/player2api/LockManager.java +++ b/src/autoclef/java/adris/altoclef/player2api/LockManager.java @@ -6,15 +6,19 @@ public class LockManager { private static Logger LOGGER = LogManager.getLogger(); - private static boolean ttsLocked = false; - private static boolean codeGenLocked = false; + private static boolean ttsLocked = false; // make sure we dont start processing until tts has finished (including + // estimated wait) + private static boolean onLLMResponseLock = false; // make sure we dont start processing until onLLMResponse has + // finished + private static boolean codeGenLocked = false; // make sure we don't start processing until code gen has finished public static boolean isTTSLocked() { return ttsLocked; } - public static boolean globalIsLocked() { - return ttsLocked || codeGenLocked; + // should we wait before processing next queue element. + public static boolean processingNextQueueLock() { + return ttsLocked || codeGenLocked || onLLMResponseLock; } public static void setTTS(boolean onOrOff) { @@ -26,4 +30,9 @@ public static void setCodeGenLock(boolean onOrOff) { LOGGER.info(String.format("CodeGen: %s lock", onOrOff ? "setting" : "releasing")); codeGenLocked = onOrOff; } + + public static void setOnLLMResponseLock(boolean onOrOff) { + LOGGER.info(String.format("llmResponse: %s lock", onOrOff ? "setting" : "releasing")); + onLLMResponseLock = onOrOff; + } } diff --git a/src/autoclef/java/adris/altoclef/player2api/Prompts.java b/src/autoclef/java/adris/altoclef/player2api/Prompts.java index 35300af5..ec2d594d 100644 --- a/src/autoclef/java/adris/altoclef/player2api/Prompts.java +++ b/src/autoclef/java/adris/altoclef/player2api/Prompts.java @@ -9,397 +9,399 @@ public class Prompts { - public static final String reminderOnAIMsg = "Last message was from an AI. Think about whether or not to respond. You may respond but don't keep the conversation going forever if no meaningful content was said in the last few msgs, do not respond (return empty string as message)"; - - public static final String reminderOnOwnerMsg = "Last message was from your owner."; - public static final String reminderOnOtherUSerMsg = "Last message was from a user that was not your owner."; - - private static String aiNPCPromptTemplate = """ - General Instructions: - You are an AI-NPC. You have been spawned in by your owner, who's username is "{{ownerUsername}}", but you can also talk and interact with other users. You can provide Minecraft guides, answer questions, and chat as a friend. - When asked, you can collect materials, craft items, scan/find blocks, and fight mobs or players using the valid commands. - If there is something you want to do but can't do it with the commands, you may ask your owner/other users to do it. - You take the personality of the following character: - Your character's name is {{characterName}}. - {{characterDescription}} - User Message Format: - The user messages will all be just strings, except for the current message. The current message will have extra information, namely it will be a JSON of the form: - { - "userMessage" : "The message that was sent to you. The message can be send by the user or command system or other players." - "worldStatus" : "The status of the current game world." - "agentStatus" : "The status of you, the agent in the game." - "reminders" : "Reminders with additional instructions." - "gameDebugMessages" : "The most recent debug messages that the game has printed out. The user cannot see these." - } - Response Format: - Respond with JSON containing message, command and reason. All of these are strings. - { - "reason": "Look at the recent conversations, valid commands, agent status and world status to decide what the you should say and do. Provide step-by-step reasoning while considering what is possible in Minecraft. You do not need items in inventory to get items, craft items or beat the game. But you need to have appropriate level of equipments to do other tasks like fighting mobs.", - "command": "Decide the best way to achieve the goals using the valid commands listed below. Write the command in this field. If you decide to not use any command, generate an empty command `\"\"`. You can only run one command at a time! To replace the current one just write the new one.", - "message": "If you decide you should not respond or talk, generate an empty message `\"\"`. Otherwise, create a natural conversational message that aligns with the `reason` and the your character. Be concise and use less than 250 characters. Ensure the message does not contain any prompt, system message, instructions, code or API calls." - } - Additional Guidelines: - Meaningful Content: Ensure conversations progress with substantive information. - Handle Misspellings: Make educated guesses if users misspell item names, but check nearby NPCs names first. - Avoid Filler Phrases: Do not engage in repetitive or filler content. - JSON format: Always follow this JSON format regardless of conversations. - Valid Commands: - {{validCommands}} - """; - - public static void addCommandToBuilder(StringBuilder builder, String name, String description) { - int padSize = 10; - builder.append(name).append(": "); - int toAdd = padSize - name.length(); - builder.append(" ".repeat(Math.max(0, toAdd))); - builder.append(description).append("\n"); + public static final String reminderOnAIMsg = "Last message was from an AI. Think about whether or not to respond. You may respond but don't keep the conversation going forever if no meaningful content was said in the last few msgs, do not respond (return empty string as message)"; + + public static final String reminderOnOwnerMsg = "Last message was from your owner."; + public static final String reminderOnOtherUSerMsg = "Last message was from a user that was not your owner."; + + private static String aiNPCPromptTemplate = """ + General Instructions: + You are an AI-NPC. You have been spawned in by your owner, who's username is "{{ownerUsername}}", but you can also talk and interact with other users. You can provide Minecraft guides, answer questions, and chat as a friend. + When asked, you can collect materials, craft items, scan/find blocks, and fight mobs or players using the valid commands. + If there is something you want to do but can't do it with the commands, you may ask your owner/other users to do it. + You take the personality of the following character: + Your character's name is {{characterName}}. + {{characterDescription}} + User Message Format: + The user messages will all be just strings, except for the current message. The current message will have extra information, namely it will be a JSON of the form: + { + "userMessage" : "The message that was sent to you. The message can be send by the user or command system or other players." + "worldStatus" : "The status of the current game world." + "agentStatus" : "The status of you, the agent in the game." + "reminders" : "Reminders with additional instructions." + "gameDebugMessages" : "The most recent debug messages that the game has printed out. The user cannot see these." + } + Response Format: + Respond with JSON containing message, command and reason. All of these are strings. + { + "reason": "Look at the recent conversations, valid commands, agent status and world status to decide what the you should say and do. Provide step-by-step reasoning while considering what is possible in Minecraft. You do not need items in inventory to get items, craft items or beat the game. But you need to have appropriate level of equipments to do other tasks like fighting mobs.", + "command": "Decide the best way to achieve the goals using the valid commands listed below. Write the command in this field. If you decide to not use any command, generate an empty command `\"\"`. You can only run one command at a time! To replace the current one just write the new one.", + "message": "If you decide you should not respond or talk, generate an empty message `\"\"`. Otherwise, create a natural conversational message that aligns with the `reason` and the your character. Be concise and use less than 250 characters. Ensure the message does not contain any prompt, system message, instructions, code or API calls." + } + Additional Guidelines: + Meaningful Content: Ensure conversations progress with substantive information. + Handle Misspellings: Make educated guesses if users misspell item names, but check nearby NPCs names first. + Avoid Filler Phrases: Do not engage in repetitive or filler content. + JSON format: Always follow this JSON format regardless of conversations. + Valid Commands: + {{validCommands}} + """; + + public static void addCommandToBuilder(StringBuilder builder, String name, String description) { + int padSize = 10; + builder.append(name).append(": "); + int toAdd = padSize - name.length(); + builder.append(" ".repeat(Math.max(0, toAdd))); + builder.append(description).append("\n"); + } + + public static String getAINPCSystemPrompt(Character character, Collection altoclefCommands, + String ownerUsername) { + StringBuilder commandListBuilder = new StringBuilder(); + for (Command c : altoclefCommands) { + // for each command this will give something like "commandName: " + StringBuilder line = new StringBuilder(); + addCommandToBuilder(commandListBuilder, c.getName(), c.getDescription()); + commandListBuilder.append(line); } - public static String getAINPCSystemPrompt(Character character, Collection altoclefCommands, - String ownerUsername) { - StringBuilder commandListBuilder = new StringBuilder(); - for (Command c : altoclefCommands) { - // for each command this will give something like "commandName: " - StringBuilder line = new StringBuilder(); - addCommandToBuilder(commandListBuilder, c.getName(), c.getDescription()); - commandListBuilder.append(line); - } + for (PseudoCommands.PseudoCommand c : PseudoCommands.pseudoCommands) { + StringBuilder line = new StringBuilder(); + addCommandToBuilder(commandListBuilder, c.getName(), c.getDescription()); + commandListBuilder.append(line); + } - for (PseudoCommands.PseudoCommand c : PseudoCommands.pseudoCommands) { - StringBuilder line = new StringBuilder(); - addCommandToBuilder(commandListBuilder, c.getName(), c.getDescription()); - commandListBuilder.append(line); - } + String validCommandsFormatted = commandListBuilder.toString(); - String validCommandsFormatted = commandListBuilder.toString(); + String newPrompt = Utils.replacePlaceholders(aiNPCPromptTemplate, + Map.of("characterDescription", character.description(), "characterName", character.name(), + "validCommands", + validCommandsFormatted, "ownerUsername", ownerUsername)); + return newPrompt; + } - String newPrompt = Utils.replacePlaceholders(aiNPCPromptTemplate, - Map.of("characterDescription", character.description(), "characterName", character.name(), - "validCommands", - validCommandsFormatted, "ownerUsername", ownerUsername)); - return newPrompt; - } + private static String buildStructurePrompt = """ + You are a code generator for a tiny construction DSL used by a Minecraft bot. - private static String buildStructurePrompt = """ - You are a code generator for a tiny construction DSL used by a Minecraft bot. + ## Objective: - ## Objective: + Given a natural-language description of a structure, return only the DSL program as a single plain-text string (possibly multi-line). No explanations, no markdown, no code fences, no JSON, no Java wrappers. - Given a natural-language description of a structure, return only the DSL program as a single plain-text string (possibly multi-line). No explanations, no markdown, no code fences, no JSON, no Java wrappers. + ### DSL Summary (what you can output) - ### DSL Summary (what you can output) + Declarations: let name = ; - Declarations: let name = ; - - Strings use double quotes; integers only; booleans true|false. - - Arithmetic: + - * / % (integer math only). - - Comparisons/logic: == != < <= > >= && || ! - - Control flow: - - For loops: for (let i = 0; i < N; i = i + 1) { ... } - - Conditionals: if (cond) { ... } else { ... } - - Side effects: - - setBlock(x, y, z, blockName); — place a single block. - - Comments: // comment - - Forbidden: user-defined functions, imports, while/foreach, floats, external calls. - - Place blocks via setBlock(baseX + dx, baseY + dy, baseZ + dz, ); - - If materials are named in the description, use them (e.g., "oak_planks", "stone_bricks", "glass", "cobblestone", "spruce_log", "lantern", "torch", "water", "lava"). If unknown, fall back to "stone". - - ## Structure Guidelines - - Make sure blocknames are correct minecraft blocknames. - - Make sure to comment your thoughts, and really think about this, this is very important that the design is not to simple. - - Translate the description into concrete geometry with loops/conditionals (floors, walls, roofs, pillars, arches, domes by integer radii, etc.). - - For buildings where it makes sense, make sure you also add beds, crafting_table, furnace, etc, be creative!! Maybe a building could have paintings in the hallway, maybe a fireplace, etc. - - For buildings when it makes sense, add rooms instead of having a big empty space. Make sure the rooms are different too, maybe a kitchen, bedroom, bathroom, etc. Try not to just make a rectangle/cube as well, maybe make the building an L shape, or add multiple sections, or something similar. - - Make sure any torches are attached to a block, and not floating in the air. - - A player is 2x1, so make sure structures are the appropriate size. - ## Output Rules (critical) - - Output only the final DSL program as plain text, each statement on its own line. - - Every statement ends with ; (except }). - - Do not wrap the program in quotes, Java, JSON, or markdown. - - No extra commentary before or after. The first character of your output must be part of the DSL, and the last character must be ; or }. - - Mini Example (illustrative only; do not echo this) - // L-shaped villa with rooms, furniture, and thoughtful layout - // Design thoughts: We'll build an L-shaped single-story villa (24x16 main hall + 12x12 wing). - // Height = 8 (comfortable for 2-block-tall player). Interior walls create rooms: foyer/hall, kitchen, bedroom, study. - // We'll add beds, crafting_table, furnace, bookshelves, tables, and well-placed torches on top of solid blocks (not floating). - // Windows are spaced regularly; doors are 2 blocks tall. A stone-brick fireplace with a chimney and a campfire hearth adds flair. - - let baseX = 0; - let baseY = 64; - let baseZ = 0; - let dir = "north"; - let block = "stone_bricks"; - - // ====== FOUNDATION ====== - // Main rectangle: 24 x 16 - for (let x = 0; x < 24; x = x + 1) { - for (let z = 0; z < 16; z = z + 1) { - setBlock(baseX + x, baseY, baseZ + z, "stone"); - } - } - // Wing rectangle: 12 x 12, attached on the east side (from z=4..15) - for (let x = 24; x < 36; x = x + 1) { - for (let z = 4; z < 16; z = z + 1) { - setBlock(baseX + x, baseY, baseZ + z, "stone"); - } - } - - // ====== FLOORING ====== - // Main hall floor: oak_planks - for (let x = 0; x < 24; x = x + 1) { - for (let z = 0; z < 16; z = z + 1) { - setBlock(baseX + x, baseY + 1, baseZ + z, "oak_planks"); - } - } - // Wing floor: spruce_planks for contrast - for (let x = 24; x < 36; x = x + 1) { - for (let z = 4; z < 16; z = z + 1) { - setBlock(baseX + x, baseY + 1, baseZ + z, "spruce_planks"); - } - } - - // ====== OUTER WALLS (HEIGHT 8) ====== - for (let y = 2; y <= 9; y = y + 1) { - // Main rectangle perimeter - for (let x = 0; x < 24; x = x + 1) { - setBlock(baseX + x, baseY + y, baseZ + 0, "stone_bricks"); - setBlock(baseX + x, baseY + y, baseZ + 15, "stone_bricks"); - } - for (let z = 0; z < 16; z = z + 1) { - setBlock(baseX + 0, baseY + y, baseZ + z, "stone_bricks"); - setBlock(baseX + 23, baseY + y, baseZ + z, "stone_bricks"); - } - // Wing perimeter - for (let x = 24; x < 36; x = x + 1) { - setBlock(baseX + x, baseY + y, baseZ + 4, "stone_bricks"); - setBlock(baseX + x, baseY + y, baseZ + 15, "stone_bricks"); - } - for (let z = 4; z < 16; z = z + 1) { - setBlock(baseX + 24, baseY + y, baseZ + z, "stone_bricks"); - setBlock(baseX + 35, baseY + y, baseZ + z, "stone_bricks"); - } - } - - // ====== DOORWAYS ====== - // Main entrance centered on front (z=0) of main hall: width 3, height 3 - for (let dx = 10; dx <= 12; dx = dx + 1) { - for (let dy = 2; dy <= 4; dy = dy + 1) { - setBlock(baseX + dx, baseY + dy, baseZ + 0, "air"); - } - } - // Door from main hall to wing (opening on shared wall at x=23): 2x3 - for (let dz = 8; dz <= 9; dz = dz + 1) { - for (let dy = 2; dy <= 4; dy = dy + 1) { - setBlock(baseX + 23, baseY + dy, baseZ + dz, "air"); - } - } - - // ====== WINDOWS ====== - // Evenly spaced windows (2x2) around exterior walls, leaving corners - for (let y = 4; y <= 5; y = y + 1) { - for (let x = 3; x <= 21; x = x + 6) { - setBlock(baseX + x, baseY + y, baseZ + 0, "glass"); - setBlock(baseX + x + 1, baseY + y, baseZ + 0, "glass"); - setBlock(baseX + x, baseY + y, baseZ + 15, "glass"); - setBlock(baseX + x + 1, baseY + y, baseZ + 15, "glass"); - } - for (let z = 3; z <= 13; z = z + 5) { - setBlock(baseX + 0, baseY + y, baseZ + z, "glass"); - setBlock(baseX + 1, baseY + y, baseZ + z, "glass"); - setBlock(baseX + 23, baseY + y, baseZ + z, "glass"); - setBlock(baseX + 22, baseY + y, baseZ + z, "glass"); - } - // Wing windows - for (let x = 26; x <= 34; x = x + 8) { - setBlock(baseX + x, baseY + y, baseZ + 4, "glass"); - setBlock(baseX + x + 1, baseY + y, baseZ + 4, "glass"); - setBlock(baseX + x, baseY + y, baseZ + 15, "glass"); - setBlock(baseX + x + 1, baseY + y, baseZ + 15, "glass"); - } - for (let z = 6; z <= 14; z = z + 4) { - setBlock(baseX + 24, baseY + y, baseZ + z, "glass"); - setBlock(baseX + 35, baseY + y, baseZ + z, "glass"); - } - } - - // ====== ROOF (FLAT WITH BORDER) ====== - for (let x = 0; x < 24; x = x + 1) { - for (let z = 0; z < 16; z = z + 1) { - setBlock(baseX + x, baseY + 10, baseZ + z, "stone"); - } - } - for (let x = 24; x < 36; x = x + 1) { - for (let z = 4; z < 16; z = z + 1) { - setBlock(baseX + x, baseY + 10, baseZ + z, "stone"); - } - } - // Roof trim - for (let x = 0; x < 24; x = x + 1) { - setBlock(baseX + x, baseY + 10, baseZ + 0, "stone_bricks"); - setBlock(baseX + x, baseY + 10, baseZ + 15, "stone_bricks"); - } - for (let z = 0; z < 16; z = z + 1) { - setBlock(baseX + 0, baseY + 10, baseZ + z, "stone_bricks"); - setBlock(baseX + 23, baseY + 10, baseZ + z, "stone_bricks"); - } - for (let x = 24; x < 36; x = x + 1) { - setBlock(baseX + x, baseY + 10, baseZ + 4, "stone_bricks"); - setBlock(baseX + x, baseY + 10, baseZ + 15, "stone_bricks"); - } - for (let z = 4; z < 16; z = z + 1) { - setBlock(baseX + 24, baseY + 10, baseZ + z, "stone_bricks"); - setBlock(baseX + 35, baseY + 10, baseZ + z, "stone_bricks"); - } - - // ====== INTERIOR ROOMS ====== - // Partition main hall into foyer (front), corridor (middle), and living room (rear) - for (let x = 2; x <= 21; x = x + 1) { - for (let y = 2; y <= 7; y = y + 1) { - // Wall between foyer and corridor at z=5 - setBlock(baseX + x, baseY + y, baseZ + 5, "stone_bricks"); - // Wall between corridor and living room at z=10 - setBlock(baseX + x, baseY + y, baseZ + 10, "stone_bricks"); - } - } - // Doorways (2x2) in those partitions - for (let dy = 2; dy <= 3; dy = dy + 1) { - setBlock(baseX + 12, baseY + dy, baseZ + 5, "air"); - setBlock(baseX + 12, baseY + dy, baseZ + 10, "air"); - setBlock(baseX + 13, baseY + dy, baseZ + 5, "air"); - setBlock(baseX + 13, baseY + dy, baseZ + 10, "air"); - } - - // Wing: split into kitchen (north) and bedroom (south) - for (let x = 26; x <= 33; x = x + 1) { - for (let y = 2; y <= 7; y = y + 1) { - setBlock(baseX + x, baseY + y, baseZ + 10, "stone_bricks"); - } - } - // Wing doorways (2x2) - for (let dy = 2; dy <= 3; dy = dy + 1) { - setBlock(baseX + 30, baseY + dy, baseZ + 10, "air"); - setBlock(baseX + 31, baseY + dy, baseZ + 10, "air"); - } - - // ====== FIREPLACE & CHIMNEY (living room corner) ====== - // Hearth at (x=3..5, z=12..13) - for (let x = 3; x <= 5; x = x + 1) { - for (let z = 12; z <= 13; z = z + 1) { - setBlock(baseX + x, baseY + 1, baseZ + z, "cobblestone"); - } - } - // Campfire for safe flame - setBlock(baseX + 4, baseY + 2, baseZ + 12, "campfire"); - // Back wall cladding and chimney up - for (let y = 2; y <= 10; y = y + 1) { - setBlock(baseX + 4, baseY + y, baseZ + 14, "cobblestone"); - setBlock(baseX + 4, baseY + y, baseZ + 15, "cobblestone"); - } - for (let y = 11; y <= 13; y = y + 1) { - setBlock(baseX + 4, baseY + y, baseZ + 15, "cobblestone"); - } - - // ====== FURNITURE & UTILITIES ====== - // Corridor rug (carpet) - for (let x = 9; x <= 14; x = x + 1) { - for (let z = 6; z <= 9; z = z + 1) { - setBlock(baseX + x, baseY + 2, baseZ + z, "red_carpet"); - } - } - - // Living room: table (logs + slab top), bookshelves, torches on top of shelves - // Table legs - setBlock(baseX + 16, baseY + 2, baseZ + 12, "spruce_log"); - setBlock(baseX + 18, baseY + 2, baseZ + 12, "spruce_log"); - setBlock(baseX + 16, baseY + 2, baseZ + 14, "spruce_log"); - setBlock(baseX + 18, baseY + 2, baseZ + 14, "spruce_log"); - // Table top - for (let x = 16; x <= 18; x = x + 1) { - for (let z = 12; z <= 14; z = z + 1) { - setBlock(baseX + x, baseY + 3, baseZ + z, "oak_slab"); - } - } - // Bookshelf wall - for (let x = 19; x <= 21; x = x + 1) { - for (let y = 2; y <= 4; y = y + 1) { - setBlock(baseX + x, baseY + y, baseZ + 13, "bookshelf"); - } - } - // Torches on top of bookshelf (attached to solid block below) - for (let x = 19; x <= 21; x = x + 1) { - setBlock(baseX + x, baseY + 5, baseZ + 13, "torch"); - } - - // Kitchen (wing north): counters (stone), crafting_table, furnace, sink (water) - for (let x = 26; x <= 33; x = x + 1) { - setBlock(baseX + x, baseY + 2, baseZ + 6, "stone"); - } - setBlock(baseX + 27, baseY + 2, baseZ + 7, "crafting_table"); - setBlock(baseX + 28, baseY + 2, baseZ + 7, "furnace"); - // Simple sink basin - setBlock(baseX + 30, baseY + 2, baseZ + 7, "cauldron"); - setBlock(baseX + 30, baseY + 3, baseZ + 7, "water"); - - // Bedroom (wing south): double bed, side tables (barrels), chest - setBlock(baseX + 29, baseY + 2, baseZ + 12, "bed"); - setBlock(baseX + 30, baseY + 2, baseZ + 12, "bed"); - setBlock(baseX + 28, baseY + 2, baseZ + 12, "barrel"); - setBlock(baseX + 31, baseY + 2, baseZ + 12, "barrel"); - setBlock(baseX + 33, baseY + 2, baseZ + 13, "chest"); - - // Study (rear main hall): desk, chair, bookshelves, torches on desk corners - // Desk - for (let x = 7; x <= 9; x = x + 1) { - setBlock(baseX + x, baseY + 2, baseZ + 13, "oak_slab"); - } - setBlock(baseX + 8, baseY + 2, baseZ + 12, "stair"); - setBlock(baseX + 7, baseY + 3, baseZ + 13, "torch"); - setBlock(baseX + 9, baseY + 3, baseZ + 13, "torch"); - - // ====== INTERIOR LIGHTING (TORCHES ON TOP OF FLOOR BLOCKS) ====== - // Main hall grid, placed on floor tops (supported by floor below at y-1) - for (let x = 3; x <= 21; x = x + 6) { - for (let z = 3; z <= 13; z = z + 5) { - setBlock(baseX + x, baseY + 2, baseZ + z, "torch"); - } - } - // Wing lighting - for (let x = 26; x <= 34; x = x + 4) { - setBlock(baseX + x, baseY + 2, baseZ + 6, "torch"); - setBlock(baseX + x, baseY + 2, baseZ + 13, "torch"); - } - - // ====== FRONT PATH & GARDEN TOUCH ====== - // Small path leading from entrance - for (let z = -1; z >= -6; z = z - 1) { - for (let x = 10; x <= 12; x = x + 1) { - setBlock(baseX + x, baseY + 1, baseZ + z, "cobblestone"); - } - } - // Flower beds flanking the path - for (let z = -1; z >= -6; z = z - 1) { - setBlock(baseX + 9, baseY + 2, baseZ + z, "rose_bush"); - setBlock(baseX + 13, baseY + 2, baseZ + z, "peony"); - } - """; - - public static String getBuildStructurePrompt() { - return buildStructurePrompt; - } + Strings use double quotes; integers only; booleans true|false. + + Arithmetic: + - * / % (integer math only). + + Comparisons/logic: == != < <= > >= && || ! + + Control flow: + + For loops: for (let i = 0; i < N; i = i + 1) { ... } + + Conditionals: if (cond) { ... } else { ... } + + Side effects: + + setBlock(x, y, z, blockName); — place a single block. + + Comments: // comment + + Forbidden: user-defined functions, imports, while/foreach, floats, external calls. + + Place blocks via setBlock(baseX + dx, baseY + dy, baseZ + dz, ); + + If materials are named in the description, use them (e.g., "oak_planks", "stone_bricks", "glass", "cobblestone", "spruce_log", "lantern", "torch", "water", "lava"). If unknown, fall back to "stone". + + ## Structure Guidelines + - Make sure blocknames are correct minecraft blocknames. + - Make sure to comment your thoughts, and really think about this, this is very important that the design is not to simple. + - Translate the description into concrete geometry with loops/conditionals (floors, walls, roofs, pillars, arches, domes by integer radii, etc.). + - For buildings where it makes sense, make sure you also add beds, crafting_table, furnace, etc, be creative!! Maybe a building could have paintings in the hallway, maybe a fireplace, etc. + - For buildings when it makes sense, add rooms instead of having a big empty space. Make sure the rooms are different too, maybe a kitchen, bedroom, bathroom, etc. Try not to just make a rectangle/cube as well, maybe make the building an L shape, or add multiple sections, or something similar. + - Make sure any torches are attached to a block, and not floating in the air. + - A player is 2x1, so make sure structures are the appropriate size. + ## Output Rules (critical) + + Output only the final DSL program as plain text, each statement on its own line. + + Every statement ends with ; (except }). + + Do not wrap the program in quotes, Java, JSON, or markdown. + + IMPORTANT: conditional (ternary) operators are NOT ALLOWED! DO NOT include them in your code. + + No extra commentary before or after. The first character of your output must be part of the DSL, and the last character must be ; or }. + + Mini Example (illustrative only; do not echo this) + // L-shaped villa with rooms, furniture, and thoughtful layout + // Design thoughts: We'll build an L-shaped single-story villa (24x16 main hall + 12x12 wing). + // Height = 8 (comfortable for 2-block-tall player). Interior walls create rooms: foyer/hall, kitchen, bedroom, study. + // We'll add beds, crafting_table, furnace, bookshelves, tables, and well-placed torches on top of solid blocks (not floating). + // Windows are spaced regularly; doors are 2 blocks tall. A stone-brick fireplace with a chimney and a campfire hearth adds flair. + + let baseX = 0; + let baseY = 64; + let baseZ = 0; + let dir = "north"; + let block = "stone_bricks"; + + // ====== FOUNDATION ====== + // Main rectangle: 24 x 16 + for (let x = 0; x < 24; x = x + 1) { + for (let z = 0; z < 16; z = z + 1) { + setBlock(baseX + x, baseY, baseZ + z, "stone"); + } + } + // Wing rectangle: 12 x 12, attached on the east side (from z=4..15) + for (let x = 24; x < 36; x = x + 1) { + for (let z = 4; z < 16; z = z + 1) { + setBlock(baseX + x, baseY, baseZ + z, "stone"); + } + } + + // ====== FLOORING ====== + // Main hall floor: oak_planks + for (let x = 0; x < 24; x = x + 1) { + for (let z = 0; z < 16; z = z + 1) { + setBlock(baseX + x, baseY + 1, baseZ + z, "oak_planks"); + } + } + // Wing floor: spruce_planks for contrast + for (let x = 24; x < 36; x = x + 1) { + for (let z = 4; z < 16; z = z + 1) { + setBlock(baseX + x, baseY + 1, baseZ + z, "spruce_planks"); + } + } + + // ====== OUTER WALLS (HEIGHT 8) ====== + for (let y = 2; y <= 9; y = y + 1) { + // Main rectangle perimeter + for (let x = 0; x < 24; x = x + 1) { + setBlock(baseX + x, baseY + y, baseZ + 0, "stone_bricks"); + setBlock(baseX + x, baseY + y, baseZ + 15, "stone_bricks"); + } + for (let z = 0; z < 16; z = z + 1) { + setBlock(baseX + 0, baseY + y, baseZ + z, "stone_bricks"); + setBlock(baseX + 23, baseY + y, baseZ + z, "stone_bricks"); + } + // Wing perimeter + for (let x = 24; x < 36; x = x + 1) { + setBlock(baseX + x, baseY + y, baseZ + 4, "stone_bricks"); + setBlock(baseX + x, baseY + y, baseZ + 15, "stone_bricks"); + } + for (let z = 4; z < 16; z = z + 1) { + setBlock(baseX + 24, baseY + y, baseZ + z, "stone_bricks"); + setBlock(baseX + 35, baseY + y, baseZ + z, "stone_bricks"); + } + } + + // ====== DOORWAYS ====== + // Main entrance centered on front (z=0) of main hall: width 3, height 3 + for (let dx = 10; dx <= 12; dx = dx + 1) { + for (let dy = 2; dy <= 4; dy = dy + 1) { + setBlock(baseX + dx, baseY + dy, baseZ + 0, "air"); + } + } + // Door from main hall to wing (opening on shared wall at x=23): 2x3 + for (let dz = 8; dz <= 9; dz = dz + 1) { + for (let dy = 2; dy <= 4; dy = dy + 1) { + setBlock(baseX + 23, baseY + dy, baseZ + dz, "air"); + } + } + + // ====== WINDOWS ====== + // Evenly spaced windows (2x2) around exterior walls, leaving corners + for (let y = 4; y <= 5; y = y + 1) { + for (let x = 3; x <= 21; x = x + 6) { + setBlock(baseX + x, baseY + y, baseZ + 0, "glass"); + setBlock(baseX + x + 1, baseY + y, baseZ + 0, "glass"); + setBlock(baseX + x, baseY + y, baseZ + 15, "glass"); + setBlock(baseX + x + 1, baseY + y, baseZ + 15, "glass"); + } + for (let z = 3; z <= 13; z = z + 5) { + setBlock(baseX + 0, baseY + y, baseZ + z, "glass"); + setBlock(baseX + 1, baseY + y, baseZ + z, "glass"); + setBlock(baseX + 23, baseY + y, baseZ + z, "glass"); + setBlock(baseX + 22, baseY + y, baseZ + z, "glass"); + } + // Wing windows + for (let x = 26; x <= 34; x = x + 8) { + setBlock(baseX + x, baseY + y, baseZ + 4, "glass"); + setBlock(baseX + x + 1, baseY + y, baseZ + 4, "glass"); + setBlock(baseX + x, baseY + y, baseZ + 15, "glass"); + setBlock(baseX + x + 1, baseY + y, baseZ + 15, "glass"); + } + for (let z = 6; z <= 14; z = z + 4) { + setBlock(baseX + 24, baseY + y, baseZ + z, "glass"); + setBlock(baseX + 35, baseY + y, baseZ + z, "glass"); + } + } + + // ====== ROOF (FLAT WITH BORDER) ====== + for (let x = 0; x < 24; x = x + 1) { + for (let z = 0; z < 16; z = z + 1) { + setBlock(baseX + x, baseY + 10, baseZ + z, "stone"); + } + } + for (let x = 24; x < 36; x = x + 1) { + for (let z = 4; z < 16; z = z + 1) { + setBlock(baseX + x, baseY + 10, baseZ + z, "stone"); + } + } + // Roof trim + for (let x = 0; x < 24; x = x + 1) { + setBlock(baseX + x, baseY + 10, baseZ + 0, "stone_bricks"); + setBlock(baseX + x, baseY + 10, baseZ + 15, "stone_bricks"); + } + for (let z = 0; z < 16; z = z + 1) { + setBlock(baseX + 0, baseY + 10, baseZ + z, "stone_bricks"); + setBlock(baseX + 23, baseY + 10, baseZ + z, "stone_bricks"); + } + for (let x = 24; x < 36; x = x + 1) { + setBlock(baseX + x, baseY + 10, baseZ + 4, "stone_bricks"); + setBlock(baseX + x, baseY + 10, baseZ + 15, "stone_bricks"); + } + for (let z = 4; z < 16; z = z + 1) { + setBlock(baseX + 24, baseY + 10, baseZ + z, "stone_bricks"); + setBlock(baseX + 35, baseY + 10, baseZ + z, "stone_bricks"); + } + + // ====== INTERIOR ROOMS ====== + // Partition main hall into foyer (front), corridor (middle), and living room (rear) + for (let x = 2; x <= 21; x = x + 1) { + for (let y = 2; y <= 7; y = y + 1) { + // Wall between foyer and corridor at z=5 + setBlock(baseX + x, baseY + y, baseZ + 5, "stone_bricks"); + // Wall between corridor and living room at z=10 + setBlock(baseX + x, baseY + y, baseZ + 10, "stone_bricks"); + } + } + // Doorways (2x2) in those partitions + for (let dy = 2; dy <= 3; dy = dy + 1) { + setBlock(baseX + 12, baseY + dy, baseZ + 5, "air"); + setBlock(baseX + 12, baseY + dy, baseZ + 10, "air"); + setBlock(baseX + 13, baseY + dy, baseZ + 5, "air"); + setBlock(baseX + 13, baseY + dy, baseZ + 10, "air"); + } + + // Wing: split into kitchen (north) and bedroom (south) + for (let x = 26; x <= 33; x = x + 1) { + for (let y = 2; y <= 7; y = y + 1) { + setBlock(baseX + x, baseY + y, baseZ + 10, "stone_bricks"); + } + } + // Wing doorways (2x2) + for (let dy = 2; dy <= 3; dy = dy + 1) { + setBlock(baseX + 30, baseY + dy, baseZ + 10, "air"); + setBlock(baseX + 31, baseY + dy, baseZ + 10, "air"); + } + + // ====== FIREPLACE & CHIMNEY (living room corner) ====== + // Hearth at (x=3..5, z=12..13) + for (let x = 3; x <= 5; x = x + 1) { + for (let z = 12; z <= 13; z = z + 1) { + setBlock(baseX + x, baseY + 1, baseZ + z, "cobblestone"); + } + } + // Campfire for safe flame + setBlock(baseX + 4, baseY + 2, baseZ + 12, "campfire"); + // Back wall cladding and chimney up + for (let y = 2; y <= 10; y = y + 1) { + setBlock(baseX + 4, baseY + y, baseZ + 14, "cobblestone"); + setBlock(baseX + 4, baseY + y, baseZ + 15, "cobblestone"); + } + for (let y = 11; y <= 13; y = y + 1) { + setBlock(baseX + 4, baseY + y, baseZ + 15, "cobblestone"); + } + + // ====== FURNITURE & UTILITIES ====== + // Corridor rug (carpet) + for (let x = 9; x <= 14; x = x + 1) { + for (let z = 6; z <= 9; z = z + 1) { + setBlock(baseX + x, baseY + 2, baseZ + z, "red_carpet"); + } + } + + // Living room: table (logs + slab top), bookshelves, torches on top of shelves + // Table legs + setBlock(baseX + 16, baseY + 2, baseZ + 12, "spruce_log"); + setBlock(baseX + 18, baseY + 2, baseZ + 12, "spruce_log"); + setBlock(baseX + 16, baseY + 2, baseZ + 14, "spruce_log"); + setBlock(baseX + 18, baseY + 2, baseZ + 14, "spruce_log"); + // Table top + for (let x = 16; x <= 18; x = x + 1) { + for (let z = 12; z <= 14; z = z + 1) { + setBlock(baseX + x, baseY + 3, baseZ + z, "oak_slab"); + } + } + // Bookshelf wall + for (let x = 19; x <= 21; x = x + 1) { + for (let y = 2; y <= 4; y = y + 1) { + setBlock(baseX + x, baseY + y, baseZ + 13, "bookshelf"); + } + } + // Torches on top of bookshelf (attached to solid block below) + for (let x = 19; x <= 21; x = x + 1) { + setBlock(baseX + x, baseY + 5, baseZ + 13, "torch"); + } + + // Kitchen (wing north): counters (stone), crafting_table, furnace, sink (water) + for (let x = 26; x <= 33; x = x + 1) { + setBlock(baseX + x, baseY + 2, baseZ + 6, "stone"); + } + setBlock(baseX + 27, baseY + 2, baseZ + 7, "crafting_table"); + setBlock(baseX + 28, baseY + 2, baseZ + 7, "furnace"); + // Simple sink basin + setBlock(baseX + 30, baseY + 2, baseZ + 7, "cauldron"); + setBlock(baseX + 30, baseY + 3, baseZ + 7, "water"); + + // Bedroom (wing south): double bed, side tables (barrels), chest + setBlock(baseX + 29, baseY + 2, baseZ + 12, "bed"); + setBlock(baseX + 30, baseY + 2, baseZ + 12, "bed"); + setBlock(baseX + 28, baseY + 2, baseZ + 12, "barrel"); + setBlock(baseX + 31, baseY + 2, baseZ + 12, "barrel"); + setBlock(baseX + 33, baseY + 2, baseZ + 13, "chest"); + + // Study (rear main hall): desk, chair, bookshelves, torches on desk corners + // Desk + for (let x = 7; x <= 9; x = x + 1) { + setBlock(baseX + x, baseY + 2, baseZ + 13, "oak_slab"); + } + setBlock(baseX + 8, baseY + 2, baseZ + 12, "stair"); + setBlock(baseX + 7, baseY + 3, baseZ + 13, "torch"); + setBlock(baseX + 9, baseY + 3, baseZ + 13, "torch"); + + // ====== INTERIOR LIGHTING (TORCHES ON TOP OF FLOOR BLOCKS) ====== + // Main hall grid, placed on floor tops (supported by floor below at y-1) + for (let x = 3; x <= 21; x = x + 6) { + for (let z = 3; z <= 13; z = z + 5) { + setBlock(baseX + x, baseY + 2, baseZ + z, "torch"); + } + } + // Wing lighting + for (let x = 26; x <= 34; x = x + 4) { + setBlock(baseX + x, baseY + 2, baseZ + 6, "torch"); + setBlock(baseX + x, baseY + 2, baseZ + 13, "torch"); + } + + // ====== FRONT PATH & GARDEN TOUCH ====== + // Small path leading from entrance + for (let z = -1; z >= -6; z = z - 1) { + for (let x = 10; x <= 12; x = x + 1) { + setBlock(baseX + x, baseY + 1, baseZ + z, "cobblestone"); + } + } + // Flower beds flanking the path + for (let z = -1; z >= -6; z = z - 1) { + setBlock(baseX + 9, baseY + 2, baseZ + z, "rose_bush"); + setBlock(baseX + 13, baseY + 2, baseZ + z, "peony"); + } + """; + + public static String getBuildStructurePrompt() { + return buildStructurePrompt; + } } diff --git a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/PseudoCommands.java b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/PseudoCommands.java index 83ab2986..bdbc2750 100644 --- a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/PseudoCommands.java +++ b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/PseudoCommands.java @@ -6,9 +6,11 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import adris.altoclef.AltoClefController; import adris.altoclef.player2api.LLMCompleter; import adris.altoclef.player2api.Player2APIService; import adris.altoclef.player2api.pseudocommands.codegen.BuildStructure; +import net.minecraft.server.level.ServerLevel; public class PseudoCommands { public static Logger LOGGER = LogManager.getLogger(); @@ -40,12 +42,12 @@ public static Optional getPseudocommandOption(String cmd) { } public static void process(PseudoCommand cmd, String commandWithPrefix, LLMCompleter completer, - Player2APIService service) { + Player2APIService service, AltoClefController mod) { LOGGER.info("Processing PseudoCommand={}, commandWithPrefix={}", cmd, commandWithPrefix); switch (cmd.name) { case "build_structure": String description = commandWithPrefix.split("build_structure")[1].strip(); - BuildStructure.buildStructure(description, completer, service); + BuildStructure.buildStructure(description, completer, service, mod); break; default: LOGGER.error("for cmdWithPrefix={} could not find matching pseudocommand", commandWithPrefix); diff --git a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java index 3fb9e9a7..4eaa793a 100644 --- a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java +++ b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java @@ -3,66 +3,163 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import adris.altoclef.AltoClefController; import adris.altoclef.player2api.ConversationHistory; import adris.altoclef.player2api.LLMCompleter; import adris.altoclef.player2api.LockManager; import adris.altoclef.player2api.Player2APIService; import adris.altoclef.player2api.Prompts; +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; public class BuildStructure { public static final Logger LOGGER = LogManager.getLogger(); - private static int numErrors = 0; + private static int numErrors = 0; private static final int maxNumErrors = 2; - private static void onLLMErrMsg(String errMsg, LLMCompleter completer, Player2APIService service) { - if (numErrors < maxNumErrors) { - numErrors += 1; - LOGGER.error("Err calling LLM={}", errMsg); - buildStructureInternal(errMsg, completer, service); - return; - } - numErrors = 0; - LockManager.setCodeGenLock(false); - } - - private static String llmReponseToCode(String input) { - // remove formatting, markdown, etc. + private static String llmResponseToCode(String input) { + // TODO: later can strip markdown ```, language hints, etc. return input; } - private static void onLLMResponse(String llmResponse, LLMCompleter completer) { - LOGGER.info("llm responsed with={}", llmResponse); - String code = llmReponseToCode(llmResponse); - LOGGER.info("processed response into code={}", code); - BuildStructureFromCode.buildStructureFromCode(code, (setBlockData) -> { - LOGGER.info("setblock(x={}, y={}, z={}, blockName={})", - setBlockData.x, - setBlockData.y, - setBlockData.z, - setBlockData.blockName); - }, (errStr) -> { - LOGGER.error("While building got err={}", errStr); - }); + private static void appendUserRegenerationPrompt( + ConversationHistory history, + Player2APIService service, + String description, + String errorMsg) { + + StringBuilder sb = new StringBuilder(); + if (errorMsg != null && !errorMsg.isEmpty()) { + sb.append("The previous attempt failed with this error:\n") + .append(errorMsg) + .append("\n\n"); + sb.append("Try again and generate code using the same description: \"") + .append(description) + .append("\".\n") + .append("Only output valid executable code (no explanations, no markdown)."); + } else { + sb.append("Generate code using this description:\"") + .append(description) + .append("\""); + } + + history.addUserMessage(sb.toString(), service); } - private static void buildStructureInternal(String description, LLMCompleter completer, Player2APIService service) { - // get code from LLM - ConversationHistory history = new ConversationHistory(Prompts.getBuildStructurePrompt()); - history.addUserMessage(String.format("Generate the code given this description: %s", description), service); + private static void requestCodeFromLLM( + ConversationHistory history, + LLMCompleter completer, + Player2APIService service, + String description, + AltoClefController mod) { + LOGGER.info("Requesting code from llm: history={} description={}", history, description); completer.processWithStringResponse( service, history, - BuildStructure::onLLMResponse, - (errMsg) -> { - onLLMErrMsg(errMsg, completer, service); + (llmResponse, completerParam, serviceParam) -> onLLMResponse(llmResponse, completerParam, serviceParam, + description, history, mod), + (errMsg) -> onLLMTransportError(errMsg, completer, service, description, history)); + } + + private static void onLLMResponse( + String llmResponse, + LLMCompleter completer, + Player2APIService service, + String description, + ConversationHistory history, + AltoClefController mod) { + + LOGGER.info("LLM responded with code as string={}", llmResponse); + + String code = llmResponseToCode(llmResponse); + LOGGER.info("Processed response into code={}", code); + + history.addAssistantMessage(code, service); + // for now do it sync + BuildStructureFromCode.buildStructureFromCode( + code, + (setBlockData) -> { + LOGGER.info("setBlock(x={}, y={}, z={}, blockName={})"); + // setBlockData.x, setBlockData.y, setBlockData.z, setBlockData.blockName); + ResourceLocation id = new ResourceLocation("minecraft", setBlockData.blockName); + Block block = BuiltInRegistries.BLOCK.get(id); + // 3 means send to clients (2) and notify neighbors/update block states (1). + // maybe do 2 if you dont want + // redstone/etc updating/torches falling probably + mod.getWorld().setBlock(new BlockPos(setBlockData.x, setBlockData.y, setBlockData.z), + block.defaultBlockState(), 3); + + }, + (errStr) -> { + LOGGER.error("While building got err={}", errStr); + onCodeValidationError(errStr, code, completer, service, description, history, mod); }); + LockManager.setCodeGenLock(false); } - // external - public static void buildStructure(String description, LLMCompleter completer, Player2APIService service) { + private static void onLLMTransportError( + String errMsg, + LLMCompleter completer, + Player2APIService service, + String description, + ConversationHistory history) { + + LOGGER.error("LLM transport/call error={}", errMsg); + LockManager.setCodeGenLock(false); + } + + private static void onCodeValidationError( + String errMsg, + String lastCode, + LLMCompleter completer, + Player2APIService service, + String description, + ConversationHistory history, + AltoClefController mod) { + + if (numErrors < maxNumErrors) { + numErrors += 1; + + appendUserRegenerationPrompt(history, service, description, errMsg); + requestCodeFromLLM(history, completer, service, description, mod); + return; + } + + numErrors = 0; + LockManager.setCodeGenLock(false); + } + + private static void buildStructureInternal( + String description, + LLMCompleter completer, + Player2APIService service, + ConversationHistory history, + AltoClefController mod) { + appendUserRegenerationPrompt(history, service, description, null); + requestCodeFromLLM(history, completer, service, description, mod); + } + + public static void buildStructure( + String description, + LLMCompleter completer, + Player2APIService service, + AltoClefController mod) { + LockManager.setCodeGenLock(true); numErrors = 0; - buildStructureInternal(description, completer, service); + + ConversationHistory history = new ConversationHistory(Prompts.getBuildStructurePrompt()); + + history.addUserMessage( + "Generate code for the following description. " + + "Only output valid executable code (no explanations, no markdown). " + + "Description: \"" + description + "\"", + service); + + buildStructureInternal(description, completer, service, history, mod); } -} +} \ No newline at end of file From d51f214197c0d3bd0c662f5b3158237c9fdbddea Mon Sep 17 00:00:00 2001 From: lorisj-elefant Date: Tue, 9 Sep 2025 12:14:52 -0700 Subject: [PATCH 6/9] fixes --- .../java/adris/altoclef/player2api/Prompts.java | 2 ++ .../pseudocommands/codegen/BuildStructure.java | 10 +++++++--- .../pseudocommands/codegen/BuildStructureFromCode.java | 3 ++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/autoclef/java/adris/altoclef/player2api/Prompts.java b/src/autoclef/java/adris/altoclef/player2api/Prompts.java index ec2d594d..aaf5bd29 100644 --- a/src/autoclef/java/adris/altoclef/player2api/Prompts.java +++ b/src/autoclef/java/adris/altoclef/player2api/Prompts.java @@ -133,6 +133,8 @@ If materials are named in the description, use them (e.g., "oak_planks", "stone_ IMPORTANT: conditional (ternary) operators are NOT ALLOWED! DO NOT include them in your code. + IMPORTANT: binary operators, like &, |, etc are NOT ALLOWED! DO NOT include them either. + No extra commentary before or after. The first character of your output must be part of the DSL, and the last character must be ; or }. Mini Example (illustrative only; do not echo this) diff --git a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java index 4eaa793a..12b7dc2c 100644 --- a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java +++ b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java @@ -72,7 +72,6 @@ private static void onLLMResponse( String description, ConversationHistory history, AltoClefController mod) { - LOGGER.info("LLM responded with code as string={}", llmResponse); String code = llmResponseToCode(llmResponse); @@ -97,8 +96,12 @@ private static void onLLMResponse( (errStr) -> { LOGGER.error("While building got err={}", errStr); onCodeValidationError(errStr, code, completer, service, description, history, mod); + }, + () -> { + LOGGER.info("Building structure done, releasing code gen lock"); + LockManager.setCodeGenLock(false); }); - LockManager.setCodeGenLock(false); + } private static void onLLMTransportError( @@ -122,13 +125,14 @@ private static void onCodeValidationError( AltoClefController mod) { if (numErrors < maxNumErrors) { + LOGGER.info("onCodeValidationError: trying again, errMsg={}", errMsg); numErrors += 1; appendUserRegenerationPrompt(history, service, description, errMsg); requestCodeFromLLM(history, completer, service, description, mod); return; } - + LOGGER.info("Too many erorrs, exiting. Last errMsg={}", errMsg); numErrors = 0; LockManager.setCodeGenLock(false); } diff --git a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructureFromCode.java b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructureFromCode.java index 81e24617..4213bf6d 100644 --- a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructureFromCode.java +++ b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructureFromCode.java @@ -1220,7 +1220,7 @@ void assign(Token nameTok, Object value) { // "}"); public static void buildStructureFromCode(String code, Consumer onSetBlock, - Consumer onErrString) { + Consumer onErrString, Runnable onFinishSuccess) { try { // run once to validate BuildStructureFromCode.runCode(code, @@ -1230,6 +1230,7 @@ public static void buildStructureFromCode(String code, Consumer LOGGER.info("Code validated, running code for real now."); // code validated, can safely set block BuildStructureFromCode.runCode(code, onSetBlock); + onFinishSuccess.run(); } catch (Exception e) { LOGGER.error("LLM build structure err={} ", e.getMessage()); onErrString.accept(e.getMessage()); From 27646f45c325e80f67cb6722c13eaaa341a7efd0 Mon Sep 17 00:00:00 2001 From: lorisj-elefant Date: Tue, 9 Sep 2025 12:27:02 -0700 Subject: [PATCH 7/9] fix, now works with short code --- .../player2api/pseudocommands/codegen/BuildStructure.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java index 12b7dc2c..3140db7e 100644 --- a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java +++ b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java @@ -82,8 +82,8 @@ private static void onLLMResponse( BuildStructureFromCode.buildStructureFromCode( code, (setBlockData) -> { - LOGGER.info("setBlock(x={}, y={}, z={}, blockName={})"); - // setBlockData.x, setBlockData.y, setBlockData.z, setBlockData.blockName); + LOGGER.info("setBlock(x={}, y={}, z={}, blockName={})", + setBlockData.x, setBlockData.y, setBlockData.z, setBlockData.blockName); ResourceLocation id = new ResourceLocation("minecraft", setBlockData.blockName); Block block = BuiltInRegistries.BLOCK.get(id); // 3 means send to clients (2) and notify neighbors/update block states (1). From 405a82ae02c1aee64d79de95658328d921a4da77 Mon Sep 17 00:00:00 2001 From: lorisj-elefant Date: Wed, 10 Sep 2025 14:51:47 -0700 Subject: [PATCH 8/9] boolean, ternary, moved stop logic to mod, finish condition, etc --- .../adris/altoclef/AltoClefController.java | 66 +++++--- .../altoclef/player2api/AgentSideEffects.java | 1 + .../altoclef/player2api/LockManager.java | 4 + .../adris/altoclef/player2api/Prompts.java | 4 - .../pseudocommands/PseudoCommands.java | 7 +- .../codegen/BuildStructure.java | 14 +- .../codegen/BuildStructureFromCode.java | 159 +++++++++++++++--- .../player2api/status/StatusUtils.java | 66 +++++--- 8 files changed, 238 insertions(+), 83 deletions(-) diff --git a/src/autoclef/java/adris/altoclef/AltoClefController.java b/src/autoclef/java/adris/altoclef/AltoClefController.java index 295974e2..de861a65 100644 --- a/src/autoclef/java/adris/altoclef/AltoClefController.java +++ b/src/autoclef/java/adris/altoclef/AltoClefController.java @@ -18,7 +18,7 @@ import adris.altoclef.player2api.EventQueueManager; import adris.altoclef.player2api.AIPersistantData; import adris.altoclef.player2api.Player2APIService; - +import adris.altoclef.player2api.pseudocommands.PseudoCommands.PseudoCommand; import adris.altoclef.player2api.Character; import adris.altoclef.tasksystem.Task; import adris.altoclef.tasksystem.TaskRunner; @@ -49,7 +49,6 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.Item; - public class AltoClefController { private final IBaritone baritone; private AIPersistantData aiPersistantData; @@ -80,6 +79,8 @@ public class AltoClefController { private Task storedTask; public boolean isStopping = false; private Player owner; + private Optional currentPseudoCommandInfoAsString; + public boolean shouldStopPseudoCommand = false; public AltoClefController(IBaritone baritone, Character character, String player2GameId) { this.baritone = baritone; @@ -96,7 +97,8 @@ public AltoClefController(IBaritone baritone, Character character, String player new WorldSurvivalChain(this.taskRunner); this.foodChain = new FoodChain(this.taskRunner); new PlayerDefenseChain(this.taskRunner); - this.storageTracker = new ItemStorageTracker(this, this.trackerManager, container -> this.containerSubTracker = container); + this.storageTracker = new ItemStorageTracker(this, this.trackerManager, + container -> this.containerSubTracker = container); this.entityTracker = new EntityTracker(this.trackerManager); this.blockScanner = new BlockScanner(this); this.chunkTracker = new SimpleChunkTracker(this); @@ -111,23 +113,23 @@ public AltoClefController(IBaritone baritone, Character character, String player this.botBehaviour = new BotBehaviour(this); this.initializeCommands(); Settings.load( - newSettings -> { - this.settings = newSettings; - List baritoneCanPlace = Arrays.stream(this.settings.getThrowawayItems(this, true)).toList(); - this.getBaritoneSettings().acceptableThrowawayItems.get().addAll(baritoneCanPlace); - if ((!this.getUserTaskChain().isActive() || this.getUserTaskChain().isRunningIdleTask()) - && this.getModSettings().shouldRunIdleCommandWhenNotActive()) { - this.getUserTaskChain().signalNextTaskToBeIdleTask(); - this.getCommandExecutor().executeWithPrefix(this.getModSettings().getIdleCommand()); - } - - this.getExtraBaritoneSettings().avoidBlockBreak(this.userBlockRangeTracker::isNearUserTrackedBlock); - this.getExtraBaritoneSettings().avoidBlockPlace(this.entityStuckTracker::isBlockedByEntity); - } - ); + newSettings -> { + this.settings = newSettings; + List baritoneCanPlace = Arrays.stream(this.settings.getThrowawayItems(this, true)).toList(); + this.getBaritoneSettings().acceptableThrowawayItems.get().addAll(baritoneCanPlace); + if ((!this.getUserTaskChain().isActive() || this.getUserTaskChain().isRunningIdleTask()) + && this.getModSettings().shouldRunIdleCommandWhenNotActive()) { + this.getUserTaskChain().signalNextTaskToBeIdleTask(); + this.getCommandExecutor().executeWithPrefix(this.getModSettings().getIdleCommand()); + } + + this.getExtraBaritoneSettings().avoidBlockBreak(this.userBlockRangeTracker::isNearUserTrackedBlock); + this.getExtraBaritoneSettings().avoidBlockPlace(this.entityStuckTracker::isBlockedByEntity); + }); Playground.IDLE_TEST_INIT_FUNCTION(this); - // AI setup: (should be at end to ensure as many things are not null as possible) + // AI setup: (should be at end to ensure as many things are not null as + // possible) EventQueueManager.getOrCreateEventQueueData(this); this.aiPersistantData = new AIPersistantData(this, character); this.player2apiService = new Player2APIService(player2GameId); @@ -143,6 +145,7 @@ public void serverTick() { this.inputControls.onTickPost(); this.baritone.serverTick(); } + public static void staticServerTick(MinecraftServer server) { EventQueueManager.injectOnTick(server); } @@ -195,7 +198,8 @@ public void runUserTask(Task task, Runnable onFinish) { } public void runUserTask(Task task) { - this.runUserTask(task, () -> {}); + this.runUserTask(task, () -> { + }); } public void cancelUserTask() { @@ -227,7 +231,7 @@ public baritone.api.Settings getBaritoneSettings() { } public AltoClefSettings getExtraBaritoneSettings() { - return ((Baritone)this.baritone).getExtraBaritoneSettings(); + return ((Baritone) this.baritone).getExtraBaritoneSettings(); } public TaskRunner getTaskRunner() { @@ -362,29 +366,39 @@ public void setOwner(Player owner) { this.owner = owner; aiPersistantData.updateSystemPrompt(); } + public boolean isOwner(UUID playerToCheck) { return playerToCheck.equals(owner.getUUID()); } + public adris.altoclef.player2api.AIPersistantData getAIPersistantData() { return this.aiPersistantData; } - public adris.altoclef.player2api.Player2APIService getPlayer2APIService(){ + public adris.altoclef.player2api.Player2APIService getPlayer2APIService() { return this.player2apiService; } - public String getOwnerUsername(){ - if(getOwner() == null){ + public String getOwnerUsername() { + if (getOwner() == null) { return "UNKNOWN OWNER"; } return getOwner().getName().getString(); } - public Optional getClosestPlayer(){ - return this.getWorld().players().stream().sorted((a,b)-> { + public Optional getClosestPlayer() { + return this.getWorld().players().stream().sorted((a, b) -> { float adist = a.distanceTo(this.getEntity()); float bdist = b.distanceTo(this.getEntity()); return Float.compare(adist, bdist); - } ).findFirst(); + }).findFirst(); + } + + public void setPseudoCommandInfo(Optional ma) { + currentPseudoCommandInfoAsString = ma; + } + + public Optional getCurrentlyRunningPseudoCmd() { + return currentPseudoCommandInfoAsString; } } diff --git a/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java b/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java index 2ed7392c..63bafb28 100644 --- a/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java +++ b/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java @@ -68,6 +68,7 @@ public static void onCommandListGenerated(AltoClefController mod, String command : (cmdExecutor.getCommandPrefix() + command); if (commandWithPrefix.equals("@stop")) { mod.isStopping = true; + } else { mod.isStopping = false; } diff --git a/src/autoclef/java/adris/altoclef/player2api/LockManager.java b/src/autoclef/java/adris/altoclef/player2api/LockManager.java index 8e9fb7a9..d414c3c7 100644 --- a/src/autoclef/java/adris/altoclef/player2api/LockManager.java +++ b/src/autoclef/java/adris/altoclef/player2api/LockManager.java @@ -35,4 +35,8 @@ public static void setOnLLMResponseLock(boolean onOrOff) { LOGGER.info(String.format("llmResponse: %s lock", onOrOff ? "setting" : "releasing")); onLLMResponseLock = onOrOff; } + + public static boolean getCodeGenLock() { + return codeGenLocked; + } } diff --git a/src/autoclef/java/adris/altoclef/player2api/Prompts.java b/src/autoclef/java/adris/altoclef/player2api/Prompts.java index aaf5bd29..098a9e56 100644 --- a/src/autoclef/java/adris/altoclef/player2api/Prompts.java +++ b/src/autoclef/java/adris/altoclef/player2api/Prompts.java @@ -131,10 +131,6 @@ If materials are named in the description, use them (e.g., "oak_planks", "stone_ Do not wrap the program in quotes, Java, JSON, or markdown. - IMPORTANT: conditional (ternary) operators are NOT ALLOWED! DO NOT include them in your code. - - IMPORTANT: binary operators, like &, |, etc are NOT ALLOWED! DO NOT include them either. - No extra commentary before or after. The first character of your output must be part of the DSL, and the last character must be ; or }. Mini Example (illustrative only; do not echo this) diff --git a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/PseudoCommands.java b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/PseudoCommands.java index bdbc2750..e314ce0d 100644 --- a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/PseudoCommands.java +++ b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/PseudoCommands.java @@ -8,9 +8,9 @@ import adris.altoclef.AltoClefController; import adris.altoclef.player2api.LLMCompleter; +import adris.altoclef.player2api.LockManager; import adris.altoclef.player2api.Player2APIService; import adris.altoclef.player2api.pseudocommands.codegen.BuildStructure; -import net.minecraft.server.level.ServerLevel; public class PseudoCommands { public static Logger LOGGER = LogManager.getLogger(); @@ -35,7 +35,7 @@ public String getDescription() { public static List pseudoCommands = List.of( new PseudoCommand("build_structure", - "you provide a description, and the agent will build a structure matching that description. Example call would be `build_structure a gray modern house with a garden of roses in front of it`")); + "you provide a description, and the mod will build a structure matching that description. \nIMPORTANT: You must put a position into the description. If the player you are talking to doesn't give any hints on where to build it, put in that player's position into the description, or some positional information. You MUST give a coordiante to build at. If you don't know the player's position, then put your own position. \n Example call would be `build_structure a gray modern house with a garden of roses in front of it. Build at position (-305, 406, 72)`")); public static Optional getPseudocommandOption(String cmd) { return pseudoCommands.stream().filter((p) -> cmd.contains(p.getName())).findFirst(); @@ -43,7 +43,9 @@ public static Optional getPseudocommandOption(String cmd) { public static void process(PseudoCommand cmd, String commandWithPrefix, LLMCompleter completer, Player2APIService service, AltoClefController mod) { + mod.setPseudoCommandInfo(Optional.of(commandWithPrefix.substring(1))); LOGGER.info("Processing PseudoCommand={}, commandWithPrefix={}", cmd, commandWithPrefix); + mod.shouldStopPseudoCommand = false; switch (cmd.name) { case "build_structure": String description = commandWithPrefix.split("build_structure")[1].strip(); @@ -51,6 +53,7 @@ public static void process(PseudoCommand cmd, String commandWithPrefix, LLMCompl break; default: LOGGER.error("for cmdWithPrefix={} could not find matching pseudocommand", commandWithPrefix); + mod.setPseudoCommandInfo(Optional.empty()); } } } diff --git a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java index 3140db7e..8b43a62b 100644 --- a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java +++ b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java @@ -1,5 +1,7 @@ package adris.altoclef.player2api.pseudocommands.codegen; +import java.util.Optional; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -13,7 +15,6 @@ import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.BlockState; public class BuildStructure { public static final Logger LOGGER = LogManager.getLogger(); @@ -62,7 +63,7 @@ private static void requestCodeFromLLM( history, (llmResponse, completerParam, serviceParam) -> onLLMResponse(llmResponse, completerParam, serviceParam, description, history, mod), - (errMsg) -> onLLMTransportError(errMsg, completer, service, description, history)); + (errMsg) -> onLLMTransportError(errMsg, completer, service, description, history, mod)); } private static void onLLMResponse( @@ -99,9 +100,9 @@ private static void onLLMResponse( }, () -> { LOGGER.info("Building structure done, releasing code gen lock"); + mod.setPseudoCommandInfo(Optional.empty()); LockManager.setCodeGenLock(false); - }); - + }, mod); } private static void onLLMTransportError( @@ -109,9 +110,11 @@ private static void onLLMTransportError( LLMCompleter completer, Player2APIService service, String description, - ConversationHistory history) { + ConversationHistory history, + AltoClefController mod) { LOGGER.error("LLM transport/call error={}", errMsg); + mod.setPseudoCommandInfo(Optional.empty()); LockManager.setCodeGenLock(false); } @@ -134,6 +137,7 @@ private static void onCodeValidationError( } LOGGER.info("Too many erorrs, exiting. Last errMsg={}", errMsg); numErrors = 0; + mod.setPseudoCommandInfo(Optional.empty()); LockManager.setCodeGenLock(false); } diff --git a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructureFromCode.java b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructureFromCode.java index 4213bf6d..ae5be94b 100644 --- a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructureFromCode.java +++ b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructureFromCode.java @@ -6,6 +6,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import adris.altoclef.AltoClefController; + /** * MiniBlocks: a tiny interpreter that parses and runs a small language with * - let / assignment @@ -37,7 +39,7 @@ public SetBlockCommand(int x, int y, int z, String blockName) { @Override public String toString() { - return "setBlock(" + x + ", " + y + ", " + z + "\", \"" + blockName + "\")"; + return "setBlock(" + x + ", " + y + ", " + z + ", \"" + blockName + "\")"; } } @@ -82,13 +84,16 @@ public static final class Program { } } - // ==== Demo ==== - - public static void runCode(String code, Consumer onSetBlock) throws Exception { + public static void runCode(String code, Consumer onSetBlock, AltoClefController mod) + throws Exception { Program program = compile(code); Runner runner = new Runner(program); Optional cmd; while ((cmd = runner.next()).isPresent()) { + if (mod.shouldStopPseudoCommand) { + mod.shouldStopPseudoCommand = false; + return; + } SetBlockCommand data = cmd.get(); onSetBlock.accept(data); } @@ -101,6 +106,10 @@ enum TokenType { LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, // One or two char BANG, BANG_EQUAL, EQUAL, EQUAL_EQUAL, GREATER, GREATER_EQUAL, LESS, LESS_EQUAL, + + // Booleans and ternary + AND_AND, OR_OR, QUESTION, COLON, + // Literals IDENTIFIER, STRING, NUMBER, // Keywords @@ -192,11 +201,43 @@ private void scanToken() { case '>': add(match('=') ? TokenType.GREATER_EQUAL : TokenType.GREATER); break; + + case ' ': + case '\r': + case '\t': + break; + case '\n': + line++; + col = 0; + break; + case '"': + string(); + break; + case '&': + if (match('&')) + add(TokenType.AND_AND); + else + error("Unexpected character: & (did you mean &&?)"); + break; + case '|': + if (match('|')) + add(TokenType.OR_OR); + else + error("Unexpected character: | (did you mean ||?)"); + break; + + // NEW: ternary + case '?': + add(TokenType.QUESTION); + break; + case ':': + add(TokenType.COLON); + break; case '/': - if (match('/')) { // line comment + if (match('/')) { while (!isAtEnd() && peek() != '\n') advance(); - } else if (match('*')) { // block comment + } else if (match('*')) { /* …existing block comment logic… */ while (!isAtEnd() && !(peek() == '*' && peekNext() == '/')) { if (peek() == '\n') { line++; @@ -207,21 +248,10 @@ private void scanToken() { if (!isAtEnd()) { advance(); advance(); - } // consume */ + } } else add(TokenType.SLASH); break; - case ' ': - case '\r': - case '\t': - break; - case '\n': - line++; - col = 0; - break; - case '"': - string(); - break; default: if (isDigit(c)) number(); @@ -375,6 +405,24 @@ interface ExprVisitor { R visitVariable(Variable e); R visitAssign(Assign e); + + R visitConditional(Conditional e); + } + + static final class Conditional implements Expr { + final Expr condition; + final Expr thenExpr; + final Expr elseExpr; + + Conditional(Expr condition, Expr thenExpr, Expr elseExpr) { + this.condition = condition; + this.thenExpr = thenExpr; + this.elseExpr = elseExpr; + } + + public R accept(ExprVisitor v) { + return v.visitConditional(this); + } } static final class Binary implements Expr { @@ -742,7 +790,7 @@ private Expr expression() { } private Expr assignment() { - Expr expr = equality(); + Expr expr = conditional(); // CHANGED: used to be equality() if (match(TokenType.EQUAL)) { Token equals = previous(); Expr value = assignment(); @@ -755,6 +803,40 @@ private Expr assignment() { return expr; } + // NEW: ternary (right-associative) + private Expr conditional() { + Expr expr = or(); + if (match(TokenType.QUESTION)) { + Expr thenExpr = expression(); // allow comma/ops etc. + consume(TokenType.COLON, "Expect ':' in ternary expression."); + Expr elseExpr = conditional(); // right-associative + expr = new Conditional(expr, thenExpr, elseExpr); + } + return expr; + } + + // NEW: || precedence + private Expr or() { + Expr expr = and(); + while (match(TokenType.OR_OR)) { + Token op = previous(); + Expr right = and(); + expr = new Binary(expr, op, right); + } + return expr; + } + + // NEW: && precedence + private Expr and() { + Expr expr = equality(); + while (match(TokenType.AND_AND)) { + Token op = previous(); + Expr right = equality(); + expr = new Binary(expr, op, right); + } + return expr; + } + private Expr equality() { Expr expr = comparison(); while (match(TokenType.BANG_EQUAL, TokenType.EQUAL_EQUAL)) { @@ -987,8 +1069,21 @@ public void visitSetBlockStmt(SetBlock s) { } // ---- Expressions ---- - public Object visitBinary(Binary e) { + // Short-circuit for logical ops: + if (e.op.type == TokenType.OR_OR) { + Object l = evaluate(e.left); + if (isTruthy(l)) + return true; // short-circuit + return isTruthy(evaluate(e.right)); + } + if (e.op.type == TokenType.AND_AND) { + Object l = evaluate(e.left); + if (!isTruthy(l)) + return false; // short-circuit + return isTruthy(evaluate(e.right)); + } + Object l = evaluate(e.left); Object r = evaluate(e.right); switch (e.op.type) { @@ -1033,6 +1128,13 @@ public Object visitBinary(Binary e) { } } + public Object visitConditional(Conditional e) { + Object cond = evaluate(e.condition); + if (isTruthy(cond)) + return evaluate(e.thenExpr); + return evaluate(e.elseExpr); + } + public Object visitUnary(Unary e) { Object v = evaluate(e.right); switch (e.op.type) { @@ -1206,11 +1308,12 @@ void assign(Token nameTok, Object value) { // "let baseZ = 10;", // "let dir = \"north\";", // "let block = \"stone\";", + // "let t = true;", // "", // "// Build a 3x2 wall:", // "for (let i = 0; i < 3; i = i + 1) {", // " for (let j = 0; j < 2; j = j + 1) {", - // " setBlock(baseX + i, baseY + j, baseZ, block);", + // " setBlock(baseX + i, baseY + j, baseZ, (!t || j != 0)? block : \"a\");", // " }", // "}", // "", @@ -1219,17 +1322,25 @@ void assign(Token nameTok, Object value) { // " setBlock(baseX + 1, baseY + 2, baseZ, \"glass\");", // "}"); + // public static void main(String[] args) { + // try { + // runCode(code, cmd -> System.out.println(cmd)); + // } catch (Exception e) { + // // TODO: handle exception + // } + // } + public static void buildStructureFromCode(String code, Consumer onSetBlock, - Consumer onErrString, Runnable onFinishSuccess) { + Consumer onErrString, Runnable onFinishSuccess, AltoClefController mod) { try { // run once to validate BuildStructureFromCode.runCode(code, (_unused) -> { // set block is nothing - }); + }, mod); LOGGER.info("Code validated, running code for real now."); // code validated, can safely set block - BuildStructureFromCode.runCode(code, onSetBlock); + BuildStructureFromCode.runCode(code, onSetBlock, mod); onFinishSuccess.run(); } catch (Exception e) { LOGGER.error("LLM build structure err={} ", e.getMessage()); diff --git a/src/autoclef/java/adris/altoclef/player2api/status/StatusUtils.java b/src/autoclef/java/adris/altoclef/player2api/status/StatusUtils.java index 9188fc23..d9963d96 100644 --- a/src/autoclef/java/adris/altoclef/player2api/status/StatusUtils.java +++ b/src/autoclef/java/adris/altoclef/player2api/status/StatusUtils.java @@ -1,6 +1,8 @@ package adris.altoclef.player2api.status; import adris.altoclef.AltoClefController; +import adris.altoclef.player2api.pseudocommands.PseudoCommands; +import adris.altoclef.player2api.pseudocommands.PseudoCommands.PseudoCommand; import adris.altoclef.tasksystem.Task; import adris.altoclef.util.helpers.ItemHelper; import baritone.api.entity.IAutomatone; @@ -10,6 +12,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.UUID; import java.util.Map.Entry; import net.minecraft.core.BlockPos; @@ -51,7 +54,8 @@ public static String getDimensionString(AltoClefController mod) { public static String getWeatherString(AltoClefController mod) { boolean isRaining = mod.getWorld().isRaining(); boolean isThundering = mod.getWorld().isThundering(); - ObjectStatus status = new ObjectStatus().add("isRaining", String.valueOf(isRaining)).add("isThundering", String.valueOf(isThundering)); + ObjectStatus status = new ObjectStatus().add("isRaining", String.valueOf(isRaining)).add("isThundering", + String.valueOf(isThundering)); return status.toString(); } @@ -74,7 +78,8 @@ public static String getNearbyBlocksString(AltoClefController mod) { for (int dy = -radius; dy <= radius; dy++) { for (int dz = -radius; dz <= radius; dz++) { BlockPos pos = center.offset(dx, dy, dz); - String blockName = mod.getWorld().getBlockState(pos).getBlock().getDescriptionId().replace("block.minecraft.", ""); + String blockName = mod.getWorld().getBlockState(pos).getBlock().getDescriptionId() + .replace("block.minecraft.", ""); if (!blockName.equals("air")) { blockCounts.put(blockName, blockCounts.getOrDefault(blockName, 0) + 1); } @@ -109,8 +114,8 @@ public static String getNearbyHostileMobs(AltoClefController mod) { } return descriptions.isEmpty() - ? String.format("no nearby hostile mobs within %d", radius) - : "[" + String.join(",", descriptions.stream().map(s -> "\"" + s + "\"").toArray(String[]::new)) + "]"; + ? String.format("no nearby hostile mobs within %d", radius) + : "[" + String.join(",", descriptions.stream().map(s -> "\"" + s + "\"").toArray(String[]::new)) + "]"; } public static String getEquippedArmorStatusString(AltoClefController mod) { @@ -121,24 +126,37 @@ public static String getEquippedArmorStatusString(AltoClefController mod) { ItemStack legs = player.getItemBySlot(EquipmentSlot.LEGS); ItemStack feet = player.getItemBySlot(EquipmentSlot.FEET); ItemStack offhand = player.getItemBySlot(EquipmentSlot.OFFHAND); - status.add("helmet", !head.isEmpty() && head.getItem() instanceof ArmorItem ? head.getItem().getDescriptionId().replace("item.minecraft.", "") : "none"); + status.add("helmet", + !head.isEmpty() && head.getItem() instanceof ArmorItem + ? head.getItem().getDescriptionId().replace("item.minecraft.", "") + : "none"); status.add( - "chestplate", !chest.isEmpty() && chest.getItem() instanceof ArmorItem ? chest.getItem().getDescriptionId().replace("item.minecraft.", "") : "none" - ); - status.add("leggings", !legs.isEmpty() && legs.getItem() instanceof ArmorItem ? legs.getItem().getDescriptionId().replace("item.minecraft.", "") : "none"); - status.add("boots", !feet.isEmpty() && feet.getItem() instanceof ArmorItem ? feet.getItem().getDescriptionId().replace("item.minecraft.", "") : "none"); + "chestplate", + !chest.isEmpty() && chest.getItem() instanceof ArmorItem + ? chest.getItem().getDescriptionId().replace("item.minecraft.", "") + : "none"); + status.add("leggings", + !legs.isEmpty() && legs.getItem() instanceof ArmorItem + ? legs.getItem().getDescriptionId().replace("item.minecraft.", "") + : "none"); + status.add("boots", + !feet.isEmpty() && feet.getItem() instanceof ArmorItem + ? feet.getItem().getDescriptionId().replace("item.minecraft.", "") + : "none"); status.add( - "offhand_shield", - !offhand.isEmpty() && offhand.getItem() instanceof ShieldItem ? offhand.getItem().getDescriptionId().replace("item.minecraft.", "") : "none" - ); + "offhand_shield", + !offhand.isEmpty() && offhand.getItem() instanceof ShieldItem + ? offhand.getItem().getDescriptionId().replace("item.minecraft.", "") + : "none"); return status.toString(); } public static String getNearbyPlayers(AltoClefController mod) { + int maxDist = 70; List descriptions = new ArrayList<>(); for (Entity entity : mod.getEntityTracker().getCloseEntities()) { - if (entity instanceof Player player && entity.distanceTo(mod.getPlayer()) < 32.0F) { + if (entity instanceof Player player && entity.distanceTo(mod.getPlayer()) < maxDist) { String username = player.getName().getString(); String position = entity.position().align(EnumSet.allOf(Axis.class)).toString(); descriptions.add(username + " at " + position); @@ -146,8 +164,8 @@ public static String getNearbyPlayers(AltoClefController mod) { } return descriptions.isEmpty() - ? String.format("no nearby users within %d", 32) - : "[" + String.join(",", descriptions.stream().map(s -> "\"" + s + "\"").toArray(String[]::new)) + "]"; + ? String.format("no nearby users within %d", maxDist) + : "[" + String.join(",", descriptions.stream().map(s -> "\"" + s + "\"").toArray(String[]::new)) + "]"; } public static String getNearbyNPCs(AltoClefController mod) { @@ -164,8 +182,8 @@ public static String getNearbyNPCs(AltoClefController mod) { } return descriptions.isEmpty() - ? String.format("no nearby npcs within %d", 32) - : "[" + String.join(",", descriptions.stream().map(s -> "\"" + s + "\"").toArray(String[]::new)) + "]"; + ? String.format("no nearby npcs within %d", 32) + : "[" + String.join(",", descriptions.stream().map(s -> "\"" + s + "\"").toArray(String[]::new)) + "]"; } public static float getUserNameDistance(AltoClefController mod, String targetUsername) { @@ -196,17 +214,21 @@ public static String getGamemodeString(AltoClefController mod) { public static String getTaskTree(AltoClefController mod) { Task task = mod.getUserTaskChain().getCurrentTask(); + Optional currentPseudoCommand = mod.getCurrentlyRunningPseudoCmd(); + if (currentPseudoCommand.isPresent()) { + return currentPseudoCommand.get(); + } return task == null ? "Task tree is empty" : task.getTaskTree(); } public static float getDistanceToUUID(AltoClefController mod, UUID target) { // for (Player player : mod.getWorld().players()) { - // if (player.getUUID().equals(target)) { - // return player.distanceTo(mod.getPlayer()); - // } + // if (player.getUUID().equals(target)) { + // return player.distanceTo(mod.getPlayer()); + // } // } - for(Entity entity : mod.getWorld().getAllEntities()){ - if(entity.getUUID().equals(target)){ + for (Entity entity : mod.getWorld().getAllEntities()) { + if (entity.getUUID().equals(target)) { return entity.distanceTo(mod.getPlayer()); } } From 2b81c06ffe50a13bc691108d700353cd886be01d Mon Sep 17 00:00:00 2001 From: lorisj-elefant Date: Thu, 11 Sep 2025 14:28:11 -0700 Subject: [PATCH 9/9] more progress --- .../adris/altoclef/AltoClefController.java | 24 ++++++- .../altoclef/player2api/AgentSideEffects.java | 37 +++++++--- .../player2api/EventQueueManager.java | 2 +- .../altoclef/player2api/LLMCompleter.java | 58 ++++++++++------ .../altoclef/player2api/LockManager.java | 13 +--- .../pseudocommands/PseudoCommandExecutor.java | 67 +++++++++++++++++++ .../pseudocommands/PseudoCommands.java | 16 ----- .../codegen/BuildStructure.java | 1 - .../codegen/BuildStructureFromCode.java | 4 +- 9 files changed, 159 insertions(+), 63 deletions(-) create mode 100644 src/autoclef/java/adris/altoclef/player2api/pseudocommands/PseudoCommandExecutor.java diff --git a/src/autoclef/java/adris/altoclef/AltoClefController.java b/src/autoclef/java/adris/altoclef/AltoClefController.java index de861a65..3c79bed3 100644 --- a/src/autoclef/java/adris/altoclef/AltoClefController.java +++ b/src/autoclef/java/adris/altoclef/AltoClefController.java @@ -18,6 +18,7 @@ import adris.altoclef.player2api.EventQueueManager; import adris.altoclef.player2api.AIPersistantData; import adris.altoclef.player2api.Player2APIService; +import adris.altoclef.player2api.pseudocommands.PseudoCommandExecutor; import adris.altoclef.player2api.pseudocommands.PseudoCommands.PseudoCommand; import adris.altoclef.player2api.Character; import adris.altoclef.tasksystem.Task; @@ -79,8 +80,9 @@ public class AltoClefController { private Task storedTask; public boolean isStopping = false; private Player owner; - private Optional currentPseudoCommandInfoAsString; - public boolean shouldStopPseudoCommand = false; + private Optional currentPseudoCommandInfoAsString = Optional.empty(); + public boolean shouldCancelPseudoCommand = false; + private PseudoCommandExecutor pseudoCommandExecutor; public AltoClefController(IBaritone baritone, Character character, String player2GameId) { this.baritone = baritone; @@ -130,9 +132,11 @@ public AltoClefController(IBaritone baritone, Character character, String player // AI setup: (should be at end to ensure as many things are not null as // possible) + this.player2apiService = new Player2APIService(player2GameId); + this.pseudoCommandExecutor = new PseudoCommandExecutor(this, player2apiService); + EventQueueManager.getOrCreateEventQueueData(this); this.aiPersistantData = new AIPersistantData(this, character); - this.player2apiService = new Player2APIService(player2GameId); } public void serverTick() { @@ -401,4 +405,18 @@ public void setPseudoCommandInfo(Optional ma) { public Optional getCurrentlyRunningPseudoCmd() { return currentPseudoCommandInfoAsString; } + + public PseudoCommandExecutor getPseudoCommandExecutor() { + return this.pseudoCommandExecutor; + } + + public void resetStop() { + isStopping = false; + shouldCancelPseudoCommand = false; + } + + public void callOnStop() { + isStopping = true; + shouldCancelPseudoCommand = true; + } } diff --git a/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java b/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java index 63bafb28..86bf6e4d 100644 --- a/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java +++ b/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java @@ -1,6 +1,7 @@ package adris.altoclef.player2api; +import java.util.Optional; import java.util.function.Consumer; import org.apache.logging.log4j.LogManager; @@ -8,7 +9,9 @@ import adris.altoclef.AltoClefController; import adris.altoclef.commandsystem.CommandExecutor; +import adris.altoclef.player2api.pseudocommands.PseudoCommandExecutor; import adris.altoclef.player2api.pseudocommands.PseudoCommands; +import adris.altoclef.player2api.pseudocommands.PseudoCommands.PseudoCommand; import net.minecraft.network.chat.Component; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; @@ -67,18 +70,36 @@ public static void onCommandListGenerated(AltoClefController mod, String command String commandWithPrefix = cmdExecutor.isClientCommand(command) ? command : (cmdExecutor.getCommandPrefix() + command); if (commandWithPrefix.equals("@stop")) { - mod.isStopping = true; - + mod.callOnStop(); } else { - mod.isStopping = false; + mod.resetStop(); } + Optional ma = PseudoCommands + .getPseudocommandOption(commandWithPrefix.substring(1)); + ma.ifPresentOrElse( + (pcmd) -> executePseudoCommand(mod, pcmd, commandWithPrefix, onStop), + () -> executeAltoclefCommand(mod, commandWithPrefix, onStop)); + } - PseudoCommands.getPseudocommandOption(commandWithPrefix).ifPresentOrElse( - (pseudoCommand) -> { - PseudoCommands.process(pseudoCommand, commandWithPrefix, completer, service, mod); - }, + private static void executePseudoCommand(AltoClefController mod, PseudoCommand pcmd, String commandWithPrefix, + Consumer onStop) { + PseudoCommandExecutor executor = mod.getPseudoCommandExecutor(); + mod.shouldCancelPseudoCommand = false; + mod.setPseudoCommandInfo(Optional.of("running" + commandWithPrefix.substring(1))); + executor.execute(pcmd, + commandWithPrefix, () -> { - executeAltoclefCommand(mod, commandWithPrefix, onStop); + if (mod.shouldCancelPseudoCommand) { + mod.setPseudoCommandInfo( + Optional.of(String.format("Running psuedocommand %s", pcmd.getName()))); + LOGGER.info("{} was cancelled. Not adding finish event to queue.", + commandWithPrefix); + onStop.accept(new CommandExecutionStopReason.Cancelled(commandWithPrefix)); + } else { + onStop.accept(new CommandExecutionStopReason.Finished(commandWithPrefix)); + } + }, (errMsg) -> { + onStop.accept(new CommandExecutionStopReason.Error(commandWithPrefix, errMsg)); }); } diff --git a/src/autoclef/java/adris/altoclef/player2api/EventQueueManager.java b/src/autoclef/java/adris/altoclef/player2api/EventQueueManager.java index 282374f1..ff9e2031 100644 --- a/src/autoclef/java/adris/altoclef/player2api/EventQueueManager.java +++ b/src/autoclef/java/adris/altoclef/player2api/EventQueueManager.java @@ -105,7 +105,7 @@ public static void injectOnTick(MinecraftServer server) { Consumer onErrEvent = (errMsg) -> { AgentSideEffects.onError(server, errMsg); }; - if (!LockManager.processingNextQueueLock()) { + if (!LockManager.isConversationLocked()) { LLMCompleter.processUsingAvailibleCompleter( (cmp) -> { process(onCharacterEvent, onErrEvent); diff --git a/src/autoclef/java/adris/altoclef/player2api/LLMCompleter.java b/src/autoclef/java/adris/altoclef/player2api/LLMCompleter.java index ccd98954..3acdbbe0 100644 --- a/src/autoclef/java/adris/altoclef/player2api/LLMCompleter.java +++ b/src/autoclef/java/adris/altoclef/player2api/LLMCompleter.java @@ -1,15 +1,12 @@ package adris.altoclef.player2api; -import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.function.BiConsumer; import java.util.function.Consumer; -import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.util.TriConsumer; import com.google.gson.JsonObject; @@ -18,13 +15,29 @@ public class LLMCompleter { private boolean isCallingLLM = false; private static final ExecutorService llmThread = Executors.newSingleThreadExecutor(); - private static List llmCompleters = List.of(new LLMCompleter()); + private Player2APIService service; + + public LLMCompleter(Player2APIService service) { + this.service = service; + } + + public class StringResponseRequest { + ConversationHistory history; + Consumer extOnLLMResponse; + Consumer extOnErrMsg; + + public StringResponseRequest( + ConversationHistory history, + Consumer extOnLLMResponse, + Consumer extOnErrMsg) { + this.history = history; + this.extOnLLMResponse = extOnLLMResponse; + this.extOnErrMsg = extOnErrMsg; + } + } public void processWithStringResponse( - Player2APIService player2apiService, - ConversationHistory history, - TriConsumer extOnLLMResponse, - Consumer extOnErrMsg) { + StringResponseRequest req) { if (isCallingLLM) { LOGGER.error( "Called llmcompleter.process when it was already processing! This should not happen. Cancelling call."); @@ -36,7 +49,7 @@ public void processWithStringResponse( LOGGER.info("Done processing (string llm resp), isprocessing -> false"); isCallingLLM = false; try { - extOnLLMResponse.accept(resp, this, player2apiService); + req.extOnLLMResponse.accept(resp); } catch (Exception e) { LOGGER.error( "[EventQueueManager/LLMCompleter/process/onLLMResponse]: Error in external llm resp, errMsg={} llmResp={}", @@ -49,7 +62,7 @@ public void processWithStringResponse( LOGGER.info("Done processing (string err), isprocessing -> false"); isCallingLLM = false; try { - extOnErrMsg.accept(errMsg); + req.extOnErrMsg.accept(errMsg); } catch (Exception e) { LOGGER.error( "[EventQueueManager/LLMCompleter/process/onErrMsg]: Error in external onErrmsg, errMsgFromException={} errMsg={}", @@ -61,7 +74,7 @@ public void processWithStringResponse( isCallingLLM = true; llmThread.submit(() -> { try { - String response = player2apiService.completeConversationToString(history); + String response = service.completeConversationToString(req.history); LOGGER.info("LLMCompleter returned as string={}", response); onLLMResponse.accept(response); } catch (Exception e) { @@ -130,14 +143,17 @@ public boolean isAvailible() { return !isCallingLLM; } - public static void processUsingAvailibleCompleter(Consumer processer) { - if (LockManager.processingNextQueueLock()) { - return; - } - Stream availibles = llmCompleters.stream().filter(LLMCompleter::isAvailible); - if (availibles.toArray().length < 1) { - LOGGER.error("ALL LLM COMPLETERS BUSY, should not happen. Some locking error has occured"); - } - llmCompleters.stream().filter(LLMCompleter::isAvailible).forEach(processer); - } + // public static void processUsingAvailibleCompleter(Consumer + // processer) { + // if (LockManager.isConversationLocked()) { + // return; + // } + // Stream availibles = + // llmCompleters.stream().filter(LLMCompleter::isAvailible); + // if (availibles.toArray().length < 1) { + // LOGGER.error("ALL LLM COMPLETERS BUSY, should not happen. Some locking error + // has occured"); + // } + // llmCompleters.stream().filter(LLMCompleter::isAvailible).forEach(processer); + // } } diff --git a/src/autoclef/java/adris/altoclef/player2api/LockManager.java b/src/autoclef/java/adris/altoclef/player2api/LockManager.java index d414c3c7..9f0953dd 100644 --- a/src/autoclef/java/adris/altoclef/player2api/LockManager.java +++ b/src/autoclef/java/adris/altoclef/player2api/LockManager.java @@ -10,15 +10,14 @@ public class LockManager { // estimated wait) private static boolean onLLMResponseLock = false; // make sure we dont start processing until onLLMResponse has // finished - private static boolean codeGenLocked = false; // make sure we don't start processing until code gen has finished public static boolean isTTSLocked() { return ttsLocked; } // should we wait before processing next queue element. - public static boolean processingNextQueueLock() { - return ttsLocked || codeGenLocked || onLLMResponseLock; + public static boolean isConversationLocked() { + return ttsLocked || onLLMResponseLock; } public static void setTTS(boolean onOrOff) { @@ -26,17 +25,9 @@ public static void setTTS(boolean onOrOff) { ttsLocked = onOrOff; } - public static void setCodeGenLock(boolean onOrOff) { - LOGGER.info(String.format("CodeGen: %s lock", onOrOff ? "setting" : "releasing")); - codeGenLocked = onOrOff; - } - public static void setOnLLMResponseLock(boolean onOrOff) { LOGGER.info(String.format("llmResponse: %s lock", onOrOff ? "setting" : "releasing")); onLLMResponseLock = onOrOff; } - public static boolean getCodeGenLock() { - return codeGenLocked; - } } diff --git a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/PseudoCommandExecutor.java b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/PseudoCommandExecutor.java new file mode 100644 index 00000000..f54ca884 --- /dev/null +++ b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/PseudoCommandExecutor.java @@ -0,0 +1,67 @@ +package adris.altoclef.player2api.pseudocommands; + +import java.util.Optional; +import java.util.function.Consumer; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import adris.altoclef.AltoClefController; +import adris.altoclef.player2api.LLMCompleter; +import adris.altoclef.player2api.Player2APIService; +import adris.altoclef.player2api.pseudocommands.PseudoCommands.PseudoCommand; +import adris.altoclef.player2api.pseudocommands.codegen.BuildStructure; + +public class PseudoCommandExecutor { + + public interface PseudoCommandRunner { + public void onStart(); + + public void onStop(); + } + + public static Logger LOGGER = LogManager.getLogger(); + private AltoClefController mod; + private Player2APIService service; + + private Optional cmdStatus = Optional.empty(); + + public static LLMCompleter toolLLM; + + public static Optional tryToStartUsingLLM() { + if (!toolLLM.isAvailible()) { + return Optional.empty(); + } + return Optional.of(toolLLM); + } + + public PseudoCommandExecutor(AltoClefController mod, Player2APIService service) { + this.mod = mod; + this.service = service; + toolLLM = new LLMCompleter(service); + } + + public void setCmdStatus(String status) { + cmdStatus = Optional.of(status); + } + + public Optional getStatus() { + return cmdStatus; + } + + public void execute(PseudoCommands.PseudoCommand pcmd, String commandWithPrefix, Runnable onStopExt, + Consumer onErrMsg) { + LOGGER.info("Processing PseudoCommand={}, commandWithPrefix={}", pcmd.name, commandWithPrefix); + + Runnable onStop = () -> { + cmdStatus = Optional.empty(); + onStopExt.run(); + }; + + switch (pcmd.name) { + case "build_structure": + String description = commandWithPrefix.split("build_structure")[1].strip(); + BuildStructure.buildStructure(description, toolLLM, service, mod); + } + } +} diff --git a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/PseudoCommands.java b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/PseudoCommands.java index e314ce0d..98b2ef0f 100644 --- a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/PseudoCommands.java +++ b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/PseudoCommands.java @@ -8,7 +8,6 @@ import adris.altoclef.AltoClefController; import adris.altoclef.player2api.LLMCompleter; -import adris.altoclef.player2api.LockManager; import adris.altoclef.player2api.Player2APIService; import adris.altoclef.player2api.pseudocommands.codegen.BuildStructure; @@ -41,19 +40,4 @@ public static Optional getPseudocommandOption(String cmd) { return pseudoCommands.stream().filter((p) -> cmd.contains(p.getName())).findFirst(); } - public static void process(PseudoCommand cmd, String commandWithPrefix, LLMCompleter completer, - Player2APIService service, AltoClefController mod) { - mod.setPseudoCommandInfo(Optional.of(commandWithPrefix.substring(1))); - LOGGER.info("Processing PseudoCommand={}, commandWithPrefix={}", cmd, commandWithPrefix); - mod.shouldStopPseudoCommand = false; - switch (cmd.name) { - case "build_structure": - String description = commandWithPrefix.split("build_structure")[1].strip(); - BuildStructure.buildStructure(description, completer, service, mod); - break; - default: - LOGGER.error("for cmdWithPrefix={} could not find matching pseudocommand", commandWithPrefix); - mod.setPseudoCommandInfo(Optional.empty()); - } - } } diff --git a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java index 8b43a62b..d60102f7 100644 --- a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java +++ b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructure.java @@ -157,7 +157,6 @@ public static void buildStructure( Player2APIService service, AltoClefController mod) { - LockManager.setCodeGenLock(true); numErrors = 0; ConversationHistory history = new ConversationHistory(Prompts.getBuildStructurePrompt()); diff --git a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructureFromCode.java b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructureFromCode.java index ae5be94b..3dde157f 100644 --- a/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructureFromCode.java +++ b/src/autoclef/java/adris/altoclef/player2api/pseudocommands/codegen/BuildStructureFromCode.java @@ -90,8 +90,8 @@ public static void runCode(String code, Consumer onSetBlock, Al Runner runner = new Runner(program); Optional cmd; while ((cmd = runner.next()).isPresent()) { - if (mod.shouldStopPseudoCommand) { - mod.shouldStopPseudoCommand = false; + if (mod.shouldCancelPseudoCommand) { + mod.shouldCancelPseudoCommand = false; return; } SetBlockCommand data = cmd.get();