From 80d439e7c8b8a8992270e19c0ea920c6409ba5b7 Mon Sep 17 00:00:00 2001 From: uukelele-scratch Date: Sat, 27 Sep 2025 09:31:19 +0100 Subject: [PATCH 1/4] start working on !buildLavaPortal --- andy.json | 2 +- settings.js | 6 +- src/agent/commands/actions.js | 8 ++ src/agent/library/skills.js | 177 +++++++++++++++++++++++++++++++++- src/utils/mcdata.js | 4 + 5 files changed, 190 insertions(+), 7 deletions(-) diff --git a/andy.json b/andy.json index 94e561617..cd3ba9251 100644 --- a/andy.json +++ b/andy.json @@ -1,6 +1,6 @@ { "name": "andy", - "model": "gpt-5-nano" + "model": "gemini-2.0-flash" } \ No newline at end of file diff --git a/settings.js b/settings.js index b41f1df2e..d5eaed4b1 100644 --- a/settings.js +++ b/settings.js @@ -6,7 +6,7 @@ const settings = { // the mindserver manages all agents and hosts the UI "mindserver_port": 8080, - "auto_open_ui": true, // opens UI in browser on startup + "auto_open_ui": false, // opens UI in browser on startup "base_profile": "assistant", // survival, assistant, creative, or god_mode "profiles": [ @@ -27,7 +27,7 @@ const settings = { ], "load_memory": false, // load memory from previous session - "init_message": "Respond with hello world and your name", // sends to all on spawn + // "init_message": "Respond with hello world and your name", // sends to all on spawn "only_chat_with": [], // users that the bots listen to and send general messages to. if empty it will chat publicly "speak": false, @@ -40,7 +40,7 @@ const settings = { "language": "en", // translate to/from this language. Supports these language names: https://cloud.google.com/translate/docs/languages "render_bot_view": false, // show bot's view in browser at localhost:3000, 3001... - "allow_insecure_coding": false, // allows newAction command and model can write/run code on your computer. enable at own risk + "allow_insecure_coding": true, // allows newAction command and model can write/run code on your computer. enable at own risk "allow_vision": false, // allows vision model to interpret screenshots as inputs "blocked_actions" : ["!checkBlueprint", "!checkBlueprintLevel", "!getBlueprint", "!getBlueprintLevel"] , // commands to disable and remove from docs. Ex: ["!setMode"] "code_timeout_mins": -1, // minutes code is allowed to run. -1 for no timeout diff --git a/src/agent/commands/actions.js b/src/agent/commands/actions.js index 14d62dec7..2a3aaedf0 100644 --- a/src/agent/commands/actions.js +++ b/src/agent/commands/actions.js @@ -491,4 +491,12 @@ export const actionsList = [ await skills.useToolOn(agent.bot, tool_name, target); }) }, + { + name: '!buildLavaPortal', + description: 'Use a nearby lava pool and a water bucket to build a nether portal.', + params: { }, + perform: runAsAction(async (agent) => { + await skills.buildLavaPortal(agent.bot); + }) + } ]; diff --git a/src/agent/library/skills.js b/src/agent/library/skills.js index e2506d52c..7b0e84421 100644 --- a/src/agent/library/skills.js +++ b/src/agent/library/skills.js @@ -2020,9 +2020,9 @@ export async function useToolOn(bot, toolName, targetName) { } return true; - } +} - export async function useToolOnBlock(bot, toolName, block) { +export async function useToolOnBlock(bot, toolName, block) { /** * Use a tool on a specific block. * @param {MinecraftBot} bot @@ -2071,4 +2071,175 @@ export async function useToolOn(bot, toolName, targetName) { } log(bot, `Used ${toolName} on ${block.name}.`); return true; - } +} + + +export async function buildLavaPortal(bot) { + /** + * Attempt to build a nether portal if a nearby lava pool is found and a water bucket is in the inventory. + * @param {MinecraftBot} bot + * @returns {Promise} true if action succeeded + */ + const equipped = await equip(bot, 'water_bucket'); + if (!equipped) { + log(bot, `Water bucket not found.`); + return false; + } + + // needs 12 lava source blocks. + let blocks = world.getNearestBlocks(bot, 'lava'); + + if (blocks.length < 12) { + if (blocks.length === 0) { + log(bot, "No lava found nearby."); + } else { + log(bot, "Nearby lava pool is not large enough."); + } + return false; + } + + // Find 4 consecutive lava blocks in a line + let consecutiveLavaBlocks = null; + const directions = [ + new Vec3(1, 0, 0), // East + new Vec3(-1, 0, 0), // West + new Vec3(0, 0, 1), // South + new Vec3(0, 0, -1) // North + ]; + + const adjacentDirections = [ + new Vec3(1, 0, 0), + new Vec3(-1, 0, 0), + new Vec3(0, 0, 1), + new Vec3(0, 0, -1), + // exclude y directions. + ]; + + const mcdata = mc.getMcData(); + + const isBlockValid = (block) => { + if (!block || block.name !== 'lava' || block.metadata !== 0) { + return false; + } + + for (const adjDirection of adjacentDirections) { + const adjacentBlock = bot.blockAt(block.position.plus(adjDirection)); + if (!adjacentBlock) { + continue; + } + + const adjacentBlockData = mcdata.blocks[adjacentBlock.type]; + if (adjacentBlockData && adjacentBlockData.boundingBox === 'block' && adjacentBlock.name !== 'lava') { + return true; + } + } + + return false; + }; + + function findNonFlammableBlocks() { + const mc = mcdata; + if (!mc) { + log(bot, "mcData not initialized, cannot find non-flammable blocks."); + return { totalCount: 0, items: [] }; + } + + const inventoryItems = bot.inventory.items(); + const foundBlocks = []; + let totalCount = 0; + + for (const item of inventoryItems) { + // Get the block's properties from minecraft-data using the item's name + const blockData = mc.blocksByName[item.name]; + + // A block is considered valid if: + // 1. It's actually a block (blockData exists). + // 2. It's a "full" solid block (boundingBox === 'block'). This excludes things like signs or torches. + // 3. It is not flammable (!blockData.flammable). + if (blockData && blockData.boundingBox === 'block' && !blockData.flammable) { + foundBlocks.push(item); + totalCount += item.count; + } + } + + return { totalCount, items: foundBlocks }; + } + + const availableBlocks = findNonFlammableBlocks() + + if (availableBlocks.totalCount < 8) { + log(bot, "Not enough solid non-flammable blocks in inventory. You need at least 8.") + return; + } + + const highestYLevel = Math.max(...blocks.map(block => block.position.y)); + + for (const startBlock of blocks) { + const startPos = startBlock.position; + + if (startPos.y !== highestYLevel) { + continue; + } + + if (!isBlockValid(startBlock)) { + continue; + } + + for (const direction of directions) { + let line = [startBlock]; + let allBlocksInLineAreValid = true; + + for (let i = 1; i < 4; i++) { + const nextPos = startPos.plus(direction.scaled(i)); + const nextBlock = bot.blockAt(nextPos); + + if (isBlockValid(nextBlock)) { + line.push(nextBlock); + } else { + allBlocksInLineAreValid = false; + break; + } + } + + if (line.length === 4 && allBlocksInLineAreValid) { + consecutiveLavaBlocks = line; + break; + } + } + if (consecutiveLavaBlocks) break; + } + + if (!consecutiveLavaBlocks) { + log(bot, "Could not find 4 consecutive lava blocks in a line, with each adjacent to a solid block."); + return false; + } + + // log(bot, `${consecutiveLavaBlocks.map(b => b.position).join(', ')}`); + + const basePosition1 = consecutiveLavaBlocks[1].position; + const buildingBlock = availableBlocks.items[0]; + + let success; // success is used multiple times + + success = await placeBlock(bot, buildingBlock.name, basePosition1.x, basePosition1.y, basePosition1.z); + if (!success) { + log(bot, "Failed to build the first part of the portal base. Aborting."); + return false; + } + + const waterTargetPosition = consecutiveLavaBlocks[2].position; + bot.chat(`Placing water at ${waterTargetPosition}`) + await equip(bot, 'water_bucket'); + // success = await placeBlock(bot, 'water', waterTargetPosition.x, waterTargetPosition.y, waterTargetPosition.z); + // for some reason, this doesn't tell the bot to place off the block we just placed down, which we should. + const referenceBlockObject = bot.blockAt(basePosition1); + const faceVec = waterTargetPosition.minus(basePosition1); + await bot.activateBlock(referenceBlockObject, faceVec); + + // wait for obsidian + await new Promise(resolve => setTimeout(resolve, 500)); // 0.5-second delay is like the maximum for the most laggiest servers + + // TODO: Complete + + return true; +} \ No newline at end of file diff --git a/src/utils/mcdata.js b/src/utils/mcdata.js index c33e6475f..b30e7c7a1 100644 --- a/src/utils/mcdata.js +++ b/src/utils/mcdata.js @@ -12,6 +12,10 @@ let mc_version = null; let mcdata = null; let Item = null; +export function getMcData() { + return mcdata; +} + /** * @typedef {string} ItemName * @typedef {string} BlockName From 0e5355b891fea5884b82a703fed42df9713f376e Mon Sep 17 00:00:00 2001 From: uukelele-scratch Date: Sun, 28 Sep 2025 16:24:42 +0100 Subject: [PATCH 2/4] more stuffs --- src/agent/library/skills.js | 73 +++++++++++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 7 deletions(-) diff --git a/src/agent/library/skills.js b/src/agent/library/skills.js index 7b0e84421..af122ccf8 100644 --- a/src/agent/library/skills.js +++ b/src/agent/library/skills.js @@ -494,6 +494,7 @@ export async function collectBlock(bot, blockType, num=1, exclude=null) { try { let success = false; if (isLiquid) { + await goToPosition(bot, block.position.x, block.position.y, block.position.z, 2); success = await useToolOnBlock(bot, 'bucket', block); } else if (mc.mustCollectManually(blockType)) { @@ -2118,6 +2119,7 @@ export async function buildLavaPortal(bot) { const mcdata = mc.getMcData(); const isBlockValid = (block) => { + // if no block or block isn't lava or block is flowing if (!block || block.name !== 'lava' || block.metadata !== 0) { return false; } @@ -2216,7 +2218,7 @@ export async function buildLavaPortal(bot) { // log(bot, `${consecutiveLavaBlocks.map(b => b.position).join(', ')}`); - const basePosition1 = consecutiveLavaBlocks[1].position; + const basePosition1 = consecutiveLavaBlocks[2].position; const buildingBlock = availableBlocks.items[0]; let success; // success is used multiple times @@ -2227,18 +2229,75 @@ export async function buildLavaPortal(bot) { return false; } - const waterTargetPosition = consecutiveLavaBlocks[2].position; - bot.chat(`Placing water at ${waterTargetPosition}`) + await new Promise(resolve => setTimeout(resolve, 100)) + + const waterTargetPosition = consecutiveLavaBlocks[1].position; await equip(bot, 'water_bucket'); // success = await placeBlock(bot, 'water', waterTargetPosition.x, waterTargetPosition.y, waterTargetPosition.z); // for some reason, this doesn't tell the bot to place off the block we just placed down, which we should. - const referenceBlockObject = bot.blockAt(basePosition1); + // const referenceBlockObject = bot.blockAt(basePosition1); const faceVec = waterTargetPosition.minus(basePosition1); - await bot.activateBlock(referenceBlockObject, faceVec); + // await bot.activateBlock(referenceBlockObject, faceVec); + await goToPosition(bot, basePosition1.x, basePosition1.y, basePosition1.z, 2); + const blockCenter = basePosition1.offset(0.5, 0.5, 0.5); + const lookAtTarget = blockCenter.plus(faceVec.scaled(0.5)); + await bot.lookAt(lookAtTarget); + await new Promise(resolve => setTimeout(resolve, 100)); + await bot.activateItem(); + + // await useToolOnBlock(bot, 'water_bucket', referenceBlockObject); // wait for obsidian - await new Promise(resolve => setTimeout(resolve, 500)); // 0.5-second delay is like the maximum for the most laggiest servers - + await new Promise(resolve => setTimeout(resolve, 200)); + + await breakBlockAt(bot, basePosition1.x, basePosition1.y, basePosition1.z) + + const bottomPositions = [ + consecutiveLavaBlocks[1], + consecutiveLavaBlocks[2], + ].map(block => block.position); + + const originalMode = bot.modes.isOn('unstuck') + bot.modes.setOn('unstuck', false) + + let safePos = null; + for (const pos of bottomPositions) { + if (safePos) await goToPosition(bot, safePos.x, safePos.y, safePos.z); + await new Promise(resolve => setTimeout(resolve, 1000)); + success = await breakBlockAt(bot, pos.x, pos.y - 1, pos.z); + if (!success) { + log(bot, "Failed to break block."); + return false; + } + // await bot.chat(`Broken!`) + // await bot.chat(`Pos: ${pos}`) + if (!safePos) safePos = bot.entity.position; + await bot.chat(`SafePos: ${safePos}`) + await new Promise(resolve => setTimeout(resolve, 1000)); + if (safePos) await goToPosition(bot, safePos.x, safePos.y, safePos.z); + success = await collectBlock(bot, 'lava', 1); + if (!success) { + log(bot, "Failed to retrieve more lava."); + return false; + } + // await bot.chat(`Collected!`); + // await new Promise(resolve => setTimeout(resolve, 1000)); + await goToPosition(bot, pos.x, pos.y, pos.z, 4) + await new Promise(resolve => setTimeout(resolve, 1000)); + success = placeBlock(bot, 'lava', pos.x, pos.y-1, pos.z) + if (!success) { + log(bot, "Failed to place lava."); + return false; + } + // await useToolOnBlock(bot, 'lava_bucket', bot.blockAt(new Vec3(pos.x, pos.y-1, pos.z))); + await bot.chat(`Placed!`) + await new Promise(resolve => setTimeout(resolve, 1000)); + } + + bot.modes.setOn('unstuck', originalMode) + + + // TODO: Complete return true; From 1f27c3de3d2c9fa6a52e6ae10c400aefeddc765d Mon Sep 17 00:00:00 2001 From: uukelele-scratch Date: Wed, 8 Oct 2025 16:13:36 +0100 Subject: [PATCH 3/4] even more stuff --- src/agent/library/skills.js | 59 ++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/src/agent/library/skills.js b/src/agent/library/skills.js index af122ccf8..b14213e18 100644 --- a/src/agent/library/skills.js +++ b/src/agent/library/skills.js @@ -2088,7 +2088,7 @@ export async function buildLavaPortal(bot) { } // needs 12 lava source blocks. - let blocks = world.getNearestBlocks(bot, 'lava'); + let blocks = world.getNearestBlocks(bot, 'lava', 64); if (blocks.length < 12) { if (blocks.length === 0) { @@ -2216,6 +2216,9 @@ export async function buildLavaPortal(bot) { return false; } + // block state: + /* L L L L */ + // log(bot, `${consecutiveLavaBlocks.map(b => b.position).join(', ')}`); const basePosition1 = consecutiveLavaBlocks[2].position; @@ -2229,6 +2232,9 @@ export async function buildLavaPortal(bot) { return false; } + // block state: + /* L L S L */ + await new Promise(resolve => setTimeout(resolve, 100)) const waterTargetPosition = consecutiveLavaBlocks[1].position; @@ -2250,8 +2256,14 @@ export async function buildLavaPortal(bot) { // wait for obsidian await new Promise(resolve => setTimeout(resolve, 200)); + // block state: + /* O W S L */ + await breakBlockAt(bot, basePosition1.x, basePosition1.y, basePosition1.z) + // block state: + /* O W W O */ + const bottomPositions = [ consecutiveLavaBlocks[1], consecutiveLavaBlocks[2], @@ -2296,7 +2308,52 @@ export async function buildLavaPortal(bot) { bot.modes.setOn('unstuck', originalMode) + // block state: + /* G G G G */ + /* O W W O */ + + consecutiveLavaBlocks[0] + + // desired block state: + /* G G G D */ // (D: dirt / other building block in inventory, G: grass, or other block behind the 4 lava blocks) + /* O W W O */ + + // also can be seen like + + /* G G G G G G */ + /* G O W W O G */ + /* L L L L L L */ + + // direction of rest of portal base + const portalDirection = consecutiveLavaBlocks[1].position.minus(consecutiveLavaBlocks[0].position); + + // find the two blocks on the side of that + const side1Direction = new Vec3(portalDirection.z, 0, -portalDirection.x); + const side2Direction = new Vec3(-portalDirection.z, 0, portalDirection.x); + + // check which block isn't lava block + const side1BlockPos = consecutiveLavaBlocks[0].position.plus(side1Direction); + const side1Block = bot.blockAt(side1BlockPos); + + let buildSideDirection; + if (side1Block && side1Block.name !== 'lava') { + buildSideDirection = side1Direction; + } else { + buildSideDirection = side2Direction; + } + + await bot.chat(`Building on the side with direction: ${buildSideDirection}`); + + // build the pillar + const rightMostGPos = consecutiveLavaBlocks[0].position.plus(buildSideDirection); + const pillarBase = rightMostGPos; + const buildingBlockName = availableBlocks.items[0].name; + + if (!await placeBlock(bot, buildingBlockName, pillarBase.x, pillarBase.y + 1, pillarBase.z)) return false; + if (!await placeBlock(bot, buildingBlockName, pillarBase.x, pillarBase.y + 2, pillarBase.z)) return false; + if (!await placeBlock(bot, buildingBlockName, pillarBase.x, pillarBase.y + 3, pillarBase.z)) return false; + if (!await placeBlock(bot, buildingBlockName, consecutiveLavaBlocks[0].position.x, pillarBase.y + 3, consecutiveLavaBlocks[0].position.z)) return false; // TODO: Complete From 9277ccd30201ca69ea841d913266cc78c5246f10 Mon Sep 17 00:00:00 2001 From: uukelele-scratch Date: Wed, 29 Oct 2025 10:40:57 +0000 Subject: [PATCH 4/4] enough work for today --- src/agent/library/skills.js | 88 +++++++++++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 14 deletions(-) diff --git a/src/agent/library/skills.js b/src/agent/library/skills.js index b14213e18..e9c81a289 100644 --- a/src/agent/library/skills.js +++ b/src/agent/library/skills.js @@ -494,7 +494,7 @@ export async function collectBlock(bot, blockType, num=1, exclude=null) { try { let success = false; if (isLiquid) { - await goToPosition(bot, block.position.x, block.position.y, block.position.z, 2); + // await goToPosition(bot, block.position.x, block.position.y, block.position.z, 2); success = await useToolOnBlock(bot, 'bucket', block); } else if (mc.mustCollectManually(blockType)) { @@ -2296,13 +2296,32 @@ export async function buildLavaPortal(bot) { // await new Promise(resolve => setTimeout(resolve, 1000)); await goToPosition(bot, pos.x, pos.y, pos.z, 4) await new Promise(resolve => setTimeout(resolve, 1000)); - success = placeBlock(bot, 'lava', pos.x, pos.y-1, pos.z) + // const targetPos = new Vec3(pos.x, pos.y-1, pos.z) + // while (bot.blockAt(targetPos) != 'lava') { + // await placeBlock(bot, 'lava', targetPos.x, targetPos.y, targetPos.z); + // // will need a timeout of sorts to stop infinite loops + // } + success = await placeBlock(bot, 'lava', pos.x, pos.y-1, pos.z) if (!success) { - log(bot, "Failed to place lava."); - return false; - } + // log(bot, "Failed to place lava."); + // return false; + // ignore this, it will always return false because it placed obsidian rather than lava + } + if (bot.blockAt(new Vec3(pos.x, pos.y-1, pos.z)) != 'lava') { + // await bot.chat(`.... oh no ....`) + // here is the real error check + // no, it is not, because during testing he keeps firing this even though he placed it correct + // oh right i'm supposed to check for obsidian, not lava, my bad + } + if (bot.blockAt(new Vec3(pos.x, pos.y-1, pos.z)) != 'obsidian') { + // await bot.chat(`.... oh no ....`); + // for some reason even this triggers + // i'm going to leave it alone for now + } + /*** IGNORE MY MONOLOGUES (I WILL CLEAN THEM UP LATER) ***/ + // await useToolOnBlock(bot, 'lava_bucket', bot.blockAt(new Vec3(pos.x, pos.y-1, pos.z))); - await bot.chat(`Placed!`) + // await bot.chat(`Placed!`) await new Promise(resolve => setTimeout(resolve, 1000)); } @@ -2320,7 +2339,7 @@ export async function buildLavaPortal(bot) { // also can be seen like - /* G G G G G G */ + /* G G G G D G */ /* G O W W O G */ /* L L L L L L */ @@ -2342,18 +2361,59 @@ export async function buildLavaPortal(bot) { buildSideDirection = side2Direction; } - await bot.chat(`Building on the side with direction: ${buildSideDirection}`); - - // build the pillar + // build the pillar: const rightMostGPos = consecutiveLavaBlocks[0].position.plus(buildSideDirection); const pillarBase = rightMostGPos; const buildingBlockName = availableBlocks.items[0].name; - if (!await placeBlock(bot, buildingBlockName, pillarBase.x, pillarBase.y + 1, pillarBase.z)) return false; - if (!await placeBlock(bot, buildingBlockName, pillarBase.x, pillarBase.y + 2, pillarBase.z)) return false; - if (!await placeBlock(bot, buildingBlockName, pillarBase.x, pillarBase.y + 3, pillarBase.z)) return false; + // three blocks up... + await placeBlock(bot, buildingBlockName, pillarBase.x, pillarBase.y + 1, pillarBase.z); + await placeBlock(bot, buildingBlockName, pillarBase.x, pillarBase.y + 2, pillarBase.z); + await placeBlock(bot, buildingBlockName, pillarBase.x, pillarBase.y + 3, pillarBase.z); + + // ...and one block out + await placeBlock(bot, buildingBlockName, consecutiveLavaBlocks[0].position.x, pillarBase.y + 3, consecutiveLavaBlocks[0].position.z); + + // now we want: + /* G D D G D G */ + /* G O W W O G */ + /* L L L L L L */ + + // now the other two + for (const num of [2, 3]) { + const pos = consecutiveLavaBlocks[num].position.plus(buildSideDirection); + await placeBlock(bot, buildingBlockName, pos.x, pos.y + 1, pos.z); + } + + // 2-block air gap - if (!await placeBlock(bot, buildingBlockName, consecutiveLavaBlocks[0].position.x, pillarBase.y + 3, consecutiveLavaBlocks[0].position.z)) return false; + const air1 = consecutiveLavaBlocks[1].position.plus(buildSideDirection); + const air2 = air1.plus(buildSideDirection); + + for (const airPos of [air1, air2]) { + const airBlock = bot.blockAt(airPos); + if (airBlock && airBlock.name !== 'air') { + await breakBlockAt(bot, airPos.x, airPos.y + 1, airPos.z); + } + } + + await collectBlock(bot, 'water', 1) // pick up the water we placed earlier + + const waterPos = air1.plus(new Vec3(0, 3, 0)); + await equip(bot, 'water_bucket'); + // success = await placeBlock(bot, 'water', waterTargetPosition.x, waterTargetPosition.y, waterTargetPosition.z); + const referenceBlockObject = bot.blockAt(new Vec3(consecutiveLavaBlocks[0].position.x, pillarBase.y + 3, consecutiveLavaBlocks[0].position.z)); + const face = waterPos.minus(referenceBlockObject.position); + // await bot.activateBlock(referenceBlockObject, face); + await goToPosition(bot, referenceBlockObject.position.x, referenceBlockObject.position.y, referenceBlockObject.position.z, 2); + const center = referenceBlockObject.position.offset(0.5, 0.5, 0.5); + const target = center.plus(face.scaled(0.5)); + await bot.lookAt(target); + await new Promise(resolve => setTimeout(resolve, 100)); + await bot.activateItem(); + + // await useToolOnBlock(bot, 'water_bucket', referenceBlockObject); + // TODO: Complete