From 00a5dc8bcb980a522768fe08d76c2190d7d49c01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=91=E5=8A=B2=E5=AE=87?= <137690584+isaacJinyu@users.noreply.github.com> Date: Wed, 27 Sep 2023 02:06:23 +0800 Subject: [PATCH] Add files via upload --- .../control_primitives/givePlacedItemBack.js | 38 +++++ .../minecraft/control_primitives/killMob.js | 51 +++++++ .../minecraft/control_primitives/mineBlock.js | 37 +++++ .../minecraft/control_primitives/placeItem.js | 79 +++++++++++ .../minecraft/control_primitives/shoot.js | 34 +++++ .../minecraft/control_primitives/smeltItem.js | 68 +++++++++ .../minecraft/control_primitives/useChest.js | 133 ++++++++++++++++++ .../control_primitives/waitForMobRemoved.js | 84 +++++++++++ 8 files changed, 524 insertions(+) create mode 100644 metagpt/actions/minecraft/control_primitives/givePlacedItemBack.js create mode 100644 metagpt/actions/minecraft/control_primitives/killMob.js create mode 100644 metagpt/actions/minecraft/control_primitives/mineBlock.js create mode 100644 metagpt/actions/minecraft/control_primitives/placeItem.js create mode 100644 metagpt/actions/minecraft/control_primitives/shoot.js create mode 100644 metagpt/actions/minecraft/control_primitives/smeltItem.js create mode 100644 metagpt/actions/minecraft/control_primitives/useChest.js create mode 100644 metagpt/actions/minecraft/control_primitives/waitForMobRemoved.js diff --git a/metagpt/actions/minecraft/control_primitives/givePlacedItemBack.js b/metagpt/actions/minecraft/control_primitives/givePlacedItemBack.js new file mode 100644 index 000000000..57d3537f4 --- /dev/null +++ b/metagpt/actions/minecraft/control_primitives/givePlacedItemBack.js @@ -0,0 +1,38 @@ +async function givePlacedItemBack(bot, name, position) { + await bot.chat("/gamerule doTileDrops false"); + // iterate name and position + const history = []; + for (let i = 0; i < name.length; i++) { + await givePlacedItemBackSingle(bot, name[i], position[i]); + } + await bot.chat("/gamerule doTileDrops true"); + + async function givePlacedItemBackSingle(bot, name, position) { + bot.chat(`/give bot ${name} 1`); + const x = Math.floor(position.x); + const y = Math.floor(position.y); + const z = Math.floor(position.z); + // loop through 125 blocks around the block + const size = 3; + for (let dx = -size; dx <= size; dx++) { + for (let dy = -size; dy <= size; dy++) { + for (let dz = -size; dz <= size; dz++) { + const block = bot.blockAt(new Vec3(x + dx, y + dy, z + dz)); + if ( + block?.name === name && + !history.includes(block.position) + ) { + await bot.chat( + `/setblock ${x + dx} ${y + dy} ${ + z + dz + } air destroy` + ); + history.push(block.position); + await bot.waitForTicks(20); + return; + } + } + } + } + } +} diff --git a/metagpt/actions/minecraft/control_primitives/killMob.js b/metagpt/actions/minecraft/control_primitives/killMob.js new file mode 100644 index 000000000..3466077b2 --- /dev/null +++ b/metagpt/actions/minecraft/control_primitives/killMob.js @@ -0,0 +1,51 @@ +async function killMob(bot, mobName, timeout = 300) { + // return if mobName is not string + if (typeof mobName !== "string") { + throw new Error(`mobName for killMob must be a string`); + } + // return if timeout is not number + if (typeof timeout !== "number") { + throw new Error(`timeout for killMob must be a number`); + } + + const weaponsForShooting = [ + "bow", + "crossbow", + "snowball", + "ender_pearl", + "egg", + "splash_potion", + "trident", + ]; + const mainHandItem = bot.inventory.slots[bot.getEquipmentDestSlot("hand")]; + + const entity = bot.nearestEntity( + (entity) => + entity.name === mobName && + // kill mob distance should be slightly bigger than explore distance + entity.position.distanceTo(bot.entity.position) < 48 + ); + if (!entity) { + bot.chat(`No ${mobName} nearby, please explore first`); + _killMobFailCount++; + if (_killMobFailCount > 10) { + throw new Error( + `killMob failed too many times, make sure you explore before calling killMob` + ); + } + return; + } + + let droppedItem; + if (mainHandItem && weaponsForShooting.includes(mainHandItem.name)) { + bot.hawkEye.autoAttack(entity, mainHandItem.name); + droppedItem = await waitForMobShot(bot, entity, timeout); + } else { + await bot.pvp.attack(entity); + droppedItem = await waitForMobRemoved(bot, entity, timeout); + } + if (droppedItem) { + await bot.collectBlock.collect(droppedItem, { ignoreNoPath: true }); + } + bot.save(`${mobName}_killed`); +} diff --git a/metagpt/actions/minecraft/control_primitives/mineBlock.js b/metagpt/actions/minecraft/control_primitives/mineBlock.js new file mode 100644 index 000000000..5746091f4 --- /dev/null +++ b/metagpt/actions/minecraft/control_primitives/mineBlock.js @@ -0,0 +1,37 @@ +async function mineBlock(bot, name, count = 1) { + // return if name is not string + if (typeof name !== "string") { + throw new Error(`name for mineBlock must be a string`); + } + if (typeof count !== "number") { + throw new Error(`count for mineBlock must be a number`); + } + const blockByName = mcData.blocksByName[name]; + if (!blockByName) { + throw new Error(`No block named ${name}`); + } + const blocks = bot.findBlocks({ + matching: [blockByName.id], + maxDistance: 32, + count: 1024, + }); + if (blocks.length === 0) { + bot.chat(`No ${name} nearby, please explore first`); + _mineBlockFailCount++; + if (_mineBlockFailCount > 10) { + throw new Error( + "mineBlock failed too many times, make sure you explore before calling mineBlock" + ); + } + return; + } + const targets = []; + for (let i = 0; i < blocks.length; i++) { + targets.push(bot.blockAt(blocks[i])); + } + await bot.collectBlock.collect(targets, { + ignoreNoPath: true, + count: count, + }); + bot.save(`${name}_mined`); +} diff --git a/metagpt/actions/minecraft/control_primitives/placeItem.js b/metagpt/actions/minecraft/control_primitives/placeItem.js new file mode 100644 index 000000000..90175a7ca --- /dev/null +++ b/metagpt/actions/minecraft/control_primitives/placeItem.js @@ -0,0 +1,79 @@ +async function placeItem(bot, name, position) { + // return if name is not string + if (typeof name !== "string") { + throw new Error(`name for placeItem must be a string`); + } + // return if position is not Vec3 + if (!(position instanceof Vec3)) { + throw new Error(`position for placeItem must be a Vec3`); + } + const itemByName = mcData.itemsByName[name]; + if (!itemByName) { + throw new Error(`No item named ${name}`); + } + const item = bot.inventory.findInventoryItem(itemByName.id); + if (!item) { + bot.chat(`No ${name} in inventory`); + return; + } + const item_count = item.count; + // find a reference block + const faceVectors = [ + new Vec3(0, 1, 0), + new Vec3(0, -1, 0), + new Vec3(1, 0, 0), + new Vec3(-1, 0, 0), + new Vec3(0, 0, 1), + new Vec3(0, 0, -1), + ]; + let referenceBlock = null; + let faceVector = null; + for (const vector of faceVectors) { + const block = bot.blockAt(position.minus(vector)); + if (block?.name !== "air") { + referenceBlock = block; + faceVector = vector; + bot.chat(`Placing ${name} on ${block.name} at ${block.position}`); + break; + } + } + if (!referenceBlock) { + bot.chat( + `No block to place ${name} on. You cannot place a floating block.` + ); + _placeItemFailCount++; + if (_placeItemFailCount > 10) { + throw new Error( + `placeItem failed too many times. You cannot place a floating block.` + ); + } + return; + } + + // You must use try catch to placeBlock + try { + // You must first go to the block position you want to place + await bot.pathfinder.goto(new GoalPlaceBlock(position, bot.world, {})); + // You must equip the item right before calling placeBlock + await bot.equip(item, "hand"); + await bot.placeBlock(referenceBlock, faceVector); + bot.chat(`Placed ${name}`); + bot.save(`${name}_placed`); + } catch (err) { + const item = bot.inventory.findInventoryItem(itemByName.id); + if (item?.count === item_count) { + bot.chat( + `Error placing ${name}: ${err.message}, please find another position to place` + ); + _placeItemFailCount++; + if (_placeItemFailCount > 10) { + throw new Error( + `placeItem failed too many times, please find another position to place.` + ); + } + } else { + bot.chat(`Placed ${name}`); + bot.save(`${name}_placed`); + } + } +} diff --git a/metagpt/actions/minecraft/control_primitives/shoot.js b/metagpt/actions/minecraft/control_primitives/shoot.js new file mode 100644 index 000000000..c0f862a0c --- /dev/null +++ b/metagpt/actions/minecraft/control_primitives/shoot.js @@ -0,0 +1,34 @@ +// shoot 1 pig with a bow: shoot(bot, "bow", "pig"); +async function shoot(bot, weapon, target) { + const validWeapons = [ + "bow", + "crossbow", + "snowball", + "ender_pearl", + "egg", + "splash_potion", + "trident", + ]; + if (!validWeapons.includes(weapon)) { + bot.chat(`${weapon} is not a valid weapon for shooting`); + return; + } + + const weaponItem = mcData.itemsByName[weapon]; + if (!bot.inventory.findInventoryItem(weaponItem.id, null)) { + bot.chat(`No ${weapon} in inventory for shooting`); + return; + } + + const targetEntity = bot.nearestEntity( + (entity) => + entity.name === target + ); + if (!targetEntity) { + bot.chat(`No ${target} nearby`); + return; + } + bot.hawkEye.autoAttack(targetEntity, "bow"); + bot.on('auto_shot_stopped', (target) => { + }) +} diff --git a/metagpt/actions/minecraft/control_primitives/smeltItem.js b/metagpt/actions/minecraft/control_primitives/smeltItem.js new file mode 100644 index 000000000..4f06817ad --- /dev/null +++ b/metagpt/actions/minecraft/control_primitives/smeltItem.js @@ -0,0 +1,68 @@ +async function smeltItem(bot, itemName, fuelName, count = 1) { + // return if itemName or fuelName is not string + if (typeof itemName !== "string" || typeof fuelName !== "string") { + throw new Error("itemName or fuelName for smeltItem must be a string"); + } + // return if count is not a number + if (typeof count !== "number") { + throw new Error("count for smeltItem must be a number"); + } + const item = mcData.itemsByName[itemName]; + const fuel = mcData.itemsByName[fuelName]; + if (!item) { + throw new Error(`No item named ${itemName}`); + } + if (!fuel) { + throw new Error(`No item named ${fuelName}`); + } + const furnaceBlock = bot.findBlock({ + matching: mcData.blocksByName.furnace.id, + maxDistance: 32, + }); + if (!furnaceBlock) { + throw new Error("No furnace nearby"); + } else { + await bot.pathfinder.goto( + new GoalLookAtBlock(furnaceBlock.position, bot.world) + ); + } + const furnace = await bot.openFurnace(furnaceBlock); + let success_count = 0; + for (let i = 0; i < count; i++) { + if (!bot.inventory.findInventoryItem(item.id, null)) { + bot.chat(`No ${itemName} to smelt in inventory`); + break; + } + if (furnace.fuelSeconds < 15 && furnace.fuelItem()?.name !== fuelName) { + if (!bot.inventory.findInventoryItem(fuel.id, null)) { + bot.chat(`No ${fuelName} as fuel in inventory`); + break; + } + await furnace.putFuel(fuel.id, null, 1); + await bot.waitForTicks(20); + if (!furnace.fuel && furnace.fuelItem()?.name !== fuelName) { + throw new Error(`${fuelName} is not a valid fuel`); + } + } + await furnace.putInput(item.id, null, 1); + await bot.waitForTicks(12 * 20); + if (!furnace.outputItem()) { + throw new Error(`${itemName} is not a valid input`); + } + await furnace.takeOutput(); + success_count++; + } + furnace.close(); + if (success_count > 0) bot.chat(`Smelted ${success_count} ${itemName}.`); + else { + bot.chat( + `Failed to smelt ${itemName}, please check the fuel and input.` + ); + _smeltItemFailCount++; + if (_smeltItemFailCount > 10) { + throw new Error( + `smeltItem failed too many times, please check the fuel and input.` + ); + } + } +} diff --git a/metagpt/actions/minecraft/control_primitives/useChest.js b/metagpt/actions/minecraft/control_primitives/useChest.js new file mode 100644 index 000000000..64f02da80 --- /dev/null +++ b/metagpt/actions/minecraft/control_primitives/useChest.js @@ -0,0 +1,133 @@ +async function getItemFromChest(bot, chestPosition, itemsToGet) { + // return if chestPosition is not Vec3 + if (!(chestPosition instanceof Vec3)) { + bot.chat("chestPosition for getItemFromChest must be a Vec3"); + return; + } + await moveToChest(bot, chestPosition); + const chestBlock = bot.blockAt(chestPosition); + const chest = await bot.openContainer(chestBlock); + for (const name in itemsToGet) { + const itemByName = mcData.itemsByName[name]; + if (!itemByName) { + bot.chat(`No item named ${name}`); + continue; + } + + const item = chest.findContainerItem(itemByName.id); + if (!item) { + bot.chat(`I don't see ${name} in this chest`); + continue; + } + try { + await chest.withdraw(item.type, null, itemsToGet[name]); + } catch (err) { + bot.chat(`Not enough ${name} in chest.`); + } + } + await closeChest(bot, chestBlock); +} + +async function depositItemIntoChest(bot, chestPosition, itemsToDeposit) { + // return if chestPosition is not Vec3 + if (!(chestPosition instanceof Vec3)) { + throw new Error( + "chestPosition for depositItemIntoChest must be a Vec3" + ); + } + await moveToChest(bot, chestPosition); + const chestBlock = bot.blockAt(chestPosition); + const chest = await bot.openContainer(chestBlock); + for (const name in itemsToDeposit) { + const itemByName = mcData.itemsByName[name]; + if (!itemByName) { + bot.chat(`No item named ${name}`); + continue; + } + const item = bot.inventory.findInventoryItem(itemByName.id); + if (!item) { + bot.chat(`No ${name} in inventory`); + continue; + } + try { + await chest.deposit(item.type, null, itemsToDeposit[name]); + } catch (err) { + bot.chat(`Not enough ${name} in inventory.`); + } + } + await closeChest(bot, chestBlock); +} + +async function checkItemInsideChest(bot, chestPosition) { + // return if chestPosition is not Vec3 + if (!(chestPosition instanceof Vec3)) { + throw new Error( + "chestPosition for depositItemIntoChest must be a Vec3" + ); + } + await moveToChest(bot, chestPosition); + const chestBlock = bot.blockAt(chestPosition); + await bot.openContainer(chestBlock); + await closeChest(bot, chestBlock); +} + +async function moveToChest(bot, chestPosition) { + if (!(chestPosition instanceof Vec3)) { + throw new Error( + "chestPosition for depositItemIntoChest must be a Vec3" + ); + } + if (chestPosition.distanceTo(bot.entity.position) > 32) { + bot.chat( + `/tp ${chestPosition.x} ${chestPosition.y} ${chestPosition.z}` + ); + await bot.waitForTicks(20); + } + const chestBlock = bot.blockAt(chestPosition); + if (chestBlock.name !== "chest") { + bot.emit("removeChest", chestPosition); + throw new Error( + `No chest at ${chestPosition}, it is ${chestBlock.name}` + ); + } + await bot.pathfinder.goto( + new GoalLookAtBlock(chestBlock.position, bot.world, {}) + ); + return chestBlock; +} + +async function listItemsInChest(bot, chestBlock) { + const chest = await bot.openContainer(chestBlock); + const items = chest.containerItems(); + if (items.length > 0) { + const itemNames = items.reduce((acc, obj) => { + if (acc[obj.name]) { + acc[obj.name] += obj.count; + } else { + acc[obj.name] = obj.count; + } + return acc; + }, {}); + bot.emit("closeChest", itemNames, chestBlock.position); + } else { + bot.emit("closeChest", {}, chestBlock.position); + } + return chest; +} + +async function closeChest(bot, chestBlock) { + try { + const chest = await listItemsInChest(bot, chestBlock); + await chest.close(); + } catch (err) { + await bot.closeWindow(chestBlock); + } +} + +function itemByName(items, name) { + for (let i = 0; i < items.length; ++i) { + const item = items[i]; + if (item && item.name === name) return item; + } + return null; +} diff --git a/metagpt/actions/minecraft/control_primitives/waitForMobRemoved.js b/metagpt/actions/minecraft/control_primitives/waitForMobRemoved.js new file mode 100644 index 000000000..fa83d4310 --- /dev/null +++ b/metagpt/actions/minecraft/control_primitives/waitForMobRemoved.js @@ -0,0 +1,84 @@ +function waitForMobRemoved(bot, entity, timeout = 300) { + return new Promise((resolve, reject) => { + let success = false; + let droppedItem = null; + // Set up timeout + const timeoutId = setTimeout(() => { + success = false; + bot.pvp.stop(); + }, timeout * 1000); + + // Function to handle entityRemoved event + function onEntityGone(e) { + if (e === entity) { + success = true; + clearTimeout(timeoutId); + bot.chat(`Killed ${entity.name}!`); + bot.pvp.stop(); + } + } + + function onItemDrop(item) { + if (entity.position.distanceTo(item.position) <= 1) { + droppedItem = item; + } + } + + function onStoppedAttacking() { + clearTimeout(timeoutId); + bot.removeListener("entityGone", onEntityGone); + bot.removeListener("stoppedAttacking", onStoppedAttacking); + bot.removeListener("itemDrop", onItemDrop); + if (!success) reject(new Error(`Failed to kill ${entity.name}.`)); + else resolve(droppedItem); + } + + // Listen for entityRemoved event + bot.on("entityGone", onEntityGone); + bot.on("stoppedAttacking", onStoppedAttacking); + bot.on("itemDrop", onItemDrop); + }); +} + + +function waitForMobShot(bot, entity, timeout = 300) { + return new Promise((resolve, reject) => { + let success = false; + let droppedItem = null; + // Set up timeout + const timeoutId = setTimeout(() => { + success = false; + bot.hawkEye.stop(); + }, timeout * 1000); + + // Function to handle entityRemoved event + function onEntityGone(e) { + if (e === entity) { + success = true; + clearTimeout(timeoutId); + bot.chat(`Shot ${entity.name}!`); + bot.hawkEye.stop(); + } + } + + function onItemDrop(item) { + if (entity.position.distanceTo(item.position) <= 1) { + droppedItem = item; + } + } + + function onAutoShotStopped() { + clearTimeout(timeoutId); + bot.removeListener("entityGone", onEntityGone); + bot.removeListener("auto_shot_stopped", onAutoShotStopped); + bot.removeListener("itemDrop", onItemDrop); + if (!success) reject(new Error(`Failed to shoot ${entity.name}.`)); + else resolve(droppedItem); + } + + // Listen for entityRemoved event + bot.on("entityGone", onEntityGone); + bot.on("auto_shot_stopped", onAutoShotStopped); + bot.on("itemDrop", onItemDrop); + }); +}