mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-06-17 15:35:21 +02:00
Add files via upload
This commit is contained in:
parent
28d1c7ad49
commit
00a5dc8bcb
8 changed files with 524 additions and 0 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
51
metagpt/actions/minecraft/control_primitives/killMob.js
Normal file
51
metagpt/actions/minecraft/control_primitives/killMob.js
Normal file
|
|
@ -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`);
|
||||
}
|
||||
37
metagpt/actions/minecraft/control_primitives/mineBlock.js
Normal file
37
metagpt/actions/minecraft/control_primitives/mineBlock.js
Normal file
|
|
@ -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`);
|
||||
}
|
||||
79
metagpt/actions/minecraft/control_primitives/placeItem.js
Normal file
79
metagpt/actions/minecraft/control_primitives/placeItem.js
Normal file
|
|
@ -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`);
|
||||
}
|
||||
}
|
||||
}
|
||||
34
metagpt/actions/minecraft/control_primitives/shoot.js
Normal file
34
metagpt/actions/minecraft/control_primitives/shoot.js
Normal file
|
|
@ -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) => {
|
||||
})
|
||||
}
|
||||
68
metagpt/actions/minecraft/control_primitives/smeltItem.js
Normal file
68
metagpt/actions/minecraft/control_primitives/smeltItem.js
Normal file
|
|
@ -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.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
133
metagpt/actions/minecraft/control_primitives/useChest.js
Normal file
133
metagpt/actions/minecraft/control_primitives/useChest.js
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue