mirror of
https://github.com/kolbytn/mindcraft.git
synced 2025-08-03 22:05:35 +02:00
Merge pull request #3 from icwhite/cooking_tasks
Cooking tasks - Internal Merge
This commit is contained in:
commit
42c8062bcf
3 changed files with 649 additions and 103 deletions
|
@ -17,6 +17,14 @@
|
|||
},
|
||||
"type": "debug"
|
||||
},
|
||||
"debug_different_goal": {
|
||||
"goal": {
|
||||
"0": "Reply to all messages with star emojis when prompted",
|
||||
"1": "Reply to all messages with heart emojis when prompted"
|
||||
},
|
||||
"agent_count": 2,
|
||||
"type": "debug"
|
||||
},
|
||||
"debug_inventory_restriction": {
|
||||
"goal": "Place 1 oak plank, then place 1 stone brick",
|
||||
"initial_inventory": {
|
||||
|
@ -44,7 +52,6 @@
|
|||
},
|
||||
"multiagent_techtree_1_stone_pickaxe": {
|
||||
"conversation": "Let's collaborate to build a stone pickaxe",
|
||||
"goal": "Build a stone pickaxe",
|
||||
"agent_count": 2,
|
||||
"initial_inventory": {
|
||||
"0": {
|
||||
|
@ -55,6 +62,7 @@
|
|||
}
|
||||
},
|
||||
"target": "stone_pickaxe",
|
||||
"goal": "Build a stone pickaxe",
|
||||
"number_of_target": 1,
|
||||
"type": "techtree",
|
||||
"timeout": 300
|
||||
|
@ -108,5 +116,41 @@
|
|||
"number_of_target": 1,
|
||||
"type": "techtree",
|
||||
"timeout": 300
|
||||
}
|
||||
},
|
||||
"multiagent_cooking_1": {
|
||||
"conversation": "Let's collaborate to make dinner, I am going to search for 'potatoes' and make 1 'baked_potato', you on the other hand, search for cow and cook 1 beef. We have a furnace (fuel already present) nearby to help us cook, search for it over long distances to find it. Note : We only need one of each item, lets not waste time by collecting unnecessary resources.",
|
||||
"agent_count": 2,
|
||||
"target": {
|
||||
"baked_potato":1,
|
||||
"cooked_beef":1
|
||||
},
|
||||
"type": "cooking",
|
||||
"timeout": 300,
|
||||
"goal": "Make 1 baked potato, use a furnace nearby to cook which has fuel in it, let the other agent cook 1 beef"
|
||||
},
|
||||
"multiagent_cooking_2": {
|
||||
"conversation": "Let's collaborate to make bread and cooked_mutton. We can split up to gather ingredients and use the nearby furnace that's already fueled.",
|
||||
"agent_count": 2,
|
||||
"target": {
|
||||
"bread": 1,
|
||||
"cooked_mutton": 1
|
||||
},
|
||||
"type": "cooking",
|
||||
"timeout": 300,
|
||||
"recipes": {
|
||||
"bread": [
|
||||
"Step 1: Go to the farm and collect 3 wheat.",
|
||||
"Step 2: Go to the crafting table and use the wheat to craft bread."
|
||||
],
|
||||
"cooked_mutton": [
|
||||
"Step 1: Kill a sheep and pick up 1 mutton that is dropped.",
|
||||
"Step 2: Go to furnace and use it to cook the mutton."
|
||||
]
|
||||
},
|
||||
"blocked_access_to_recipe": [],
|
||||
"goal" : {
|
||||
"0": "Collaborate with randy to make 1 bread and 1 cooked mutton, you can divide the tasks among yourselves.\nThere is a furnace nearby that is already fueled, there is also a smoker and crafting table nearby, use them to your advantage. Crops of all different types are available in the farm where you are standing, you can use them to your advantage as well. The farm also includes animals like cows, pigs, sheep, and chickens that you can use to your advantage.\nSince the farm is huge, make sure to search for the resources over long distances to find them.",
|
||||
"1": "Collaborate with andy to make 1 bread and 1 cooked mutton, you can divide the tasks among yourselves.\nThere is a furnace nearby that is already fueled, there is also a smoker and crafting table nearby, use them to your advantage. Crops of all different types are available in the farm where you are standing, you can use them to your advantage as well. The farm also includes animals like cows, pigs, sheep, and chickens that you can use to your advantage.\nSince the farm is huge, make sure to search for the resources over long distances to find them."
|
||||
}
|
||||
}
|
||||
}
|
383
src/agent/task_types/cooking_tasks.js
Normal file
383
src/agent/task_types/cooking_tasks.js
Normal file
|
@ -0,0 +1,383 @@
|
|||
import { getPosition } from "../library/world.js";
|
||||
|
||||
export class CookingTaskInitiator {
|
||||
constructor(data, agent) {
|
||||
this.agent = agent;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
async init() {
|
||||
let bot = this.agent.bot;
|
||||
|
||||
//// Setting up the cooking world using minecraft cheats ////
|
||||
|
||||
// Only run the setup if the agent is the first one
|
||||
|
||||
if (this.agent.count_id === 0) {
|
||||
// Clear and prepare the base area
|
||||
await bot.chat(`/fill ~ ~-1 ~ ~50 ~-3 ~50 grass_block`);
|
||||
await bot.chat(`/fill ~ ~-1 ~ ~-50 ~-3 ~50 grass_block`);
|
||||
await bot.chat(`/fill ~ ~-1 ~ ~-50 ~-3 ~-50 grass_block`);
|
||||
await bot.chat(`/fill ~ ~-1 ~ ~50 ~-3 ~-50 grass_block`);
|
||||
await bot.chat(`/fill ~ ~ ~ ~50 ~10 ~50 air`);
|
||||
await bot.chat(`/fill ~ ~ ~ ~-50 ~10 ~50 air`);
|
||||
await bot.chat(`/fill ~ ~ ~ ~-50 ~10 ~-50 air`);
|
||||
await bot.chat(`/fill ~ ~ ~ ~50 ~10 ~-50 air`);
|
||||
|
||||
const position = getPosition(bot);
|
||||
const botX = Math.floor(position.x);
|
||||
const botZ = Math.floor(position.z);
|
||||
|
||||
// Region management system
|
||||
const isOverlapping = (newXMin, newXMax, newZMin, newZMax, occupiedRegions) => {
|
||||
for (const region of occupiedRegions) {
|
||||
if (newXMin < region.xMax && newXMax > region.xMin &&
|
||||
newZMin < region.zMax && newZMax > region.zMin) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const findValidPosition = (width, depth, occupiedRegions) => {
|
||||
const maxXStart = position.x + 25 - width; // Constrain to 50x50 area
|
||||
const minXStart = position.x - 25;
|
||||
const maxZStart = position.z + 25 - depth;
|
||||
const minZStart = position.z - 25;
|
||||
|
||||
let attempts = 0;
|
||||
while (attempts < 1000) {
|
||||
const xStart = Math.floor(minXStart + Math.random() * (maxXStart - minXStart + 1));
|
||||
const zStart = Math.floor(minZStart + Math.random() * (maxZStart - minZStart + 1));
|
||||
const xMin = xStart;
|
||||
const xMax = xStart + width - 1;
|
||||
const zMin = zStart;
|
||||
const zMax = zStart + depth - 1;
|
||||
|
||||
if (!isOverlapping(xMin, xMax, zMin, zMax, occupiedRegions)) {
|
||||
return { xStart, zStart };
|
||||
}
|
||||
attempts++;
|
||||
}
|
||||
throw new Error('Failed to find non-overlapping position after 1000 attempts');
|
||||
};
|
||||
|
||||
// Define all regions with their sizes
|
||||
const regionsToPlace = [
|
||||
{ type: 'wheat', width: 5, depth: 5 },
|
||||
{ type: 'beetroots', width: 2, depth: 5 },
|
||||
{ type: 'mushrooms', width: 2, depth: 5 },
|
||||
{ type: 'potatoes', width: 2, depth: 5 },
|
||||
{ type: 'carrots', width: 2, depth: 5 },
|
||||
{ type: 'sugar_cane', width: 3, depth: 3 },
|
||||
{ type: 'sugar_cane', width: 3, depth: 3 },
|
||||
{ type: 'pumpkins', width: 5, depth: 1 },
|
||||
{ type: 'house', width: 11, depth: 11 }
|
||||
];
|
||||
|
||||
// Expand the regions of each type to make sure they don't overlap
|
||||
|
||||
for (let i = 0; i < regionsToPlace.length; i++) {
|
||||
const region = regionsToPlace[i];
|
||||
const { width, depth } = region;
|
||||
regionsToPlace[i].width = width + 4;
|
||||
regionsToPlace[i].depth = depth + 4;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const occupiedRegions = [{
|
||||
xMin : botX - 1,
|
||||
xMax : botX + 1,
|
||||
zMin : botZ - 1,
|
||||
zMax : botZ + 1
|
||||
}];
|
||||
const regionPositions = {};
|
||||
|
||||
// Calculate positions for all regions
|
||||
for (const region of regionsToPlace) {
|
||||
const { xStart, zStart } = findValidPosition(region.width, region.depth, occupiedRegions);
|
||||
|
||||
occupiedRegions.push({
|
||||
xMin: xStart,
|
||||
xMax: xStart + region.width - 1,
|
||||
zMin: zStart,
|
||||
zMax: zStart + region.depth - 1
|
||||
});
|
||||
|
||||
if (region.type === 'sugar_cane') {
|
||||
if (!regionPositions.sugar_cane) regionPositions.sugar_cane = [];
|
||||
regionPositions.sugar_cane.push({ xStart, zStart });
|
||||
} else {
|
||||
regionPositions[region.type] = { xStart, zStart };
|
||||
}
|
||||
}
|
||||
|
||||
// Planting functions with dynamic positions
|
||||
const plantWheat = async (xStart, zStart) => {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
for (let j = 0; j < 5; j++) {
|
||||
const x = xStart + i;
|
||||
const z = zStart + j;
|
||||
await bot.chat(`/setblock ${x} ${position.y - 1} ${z} farmland`);
|
||||
await bot.chat(`/setblock ${x} ${position.y} ${z} wheat[age=7]`);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const plantBeetroots = async (xStart, zStart) => {
|
||||
for (let i = 0; i < 2; i++) {
|
||||
for (let j = 0; j < 5; j++) {
|
||||
const x = xStart + i;
|
||||
const z = zStart + j;
|
||||
await bot.chat(`/setblock ${x} ${position.y - 1} ${z} farmland`);
|
||||
await bot.chat(`/setblock ${x} ${position.y} ${z} beetroots[age=3]`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const plantMushrooms = async (xStart, zStart) => {
|
||||
for (let i = 0; i < 2; i++) {
|
||||
for (let j = 0; j < 5; j++) {
|
||||
const x = xStart + i;
|
||||
const z = zStart + j;
|
||||
await bot.chat(`/setblock ${x} ${position.y - 1} ${z} mycelium`);
|
||||
await bot.chat(`/setblock ${x} ${position.y} ${z} red_mushroom`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const plantPotatoes = async (xStart, zStart) => {
|
||||
for (let i = 0; i < 2; i++) {
|
||||
for (let j = 0; j < 5; j++) {
|
||||
const x = xStart + i;
|
||||
const z = zStart + j;
|
||||
await bot.chat(`/setblock ${x} ${position.y - 1} ${z} farmland`);
|
||||
await bot.chat(`/setblock ${x} ${position.y} ${z} potatoes[age=7]`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const plantCarrots = async (xStart, zStart) => {
|
||||
for (let i = 0; i < 2; i++) {
|
||||
for (let j = 0; j < 5; j++) {
|
||||
const x = xStart + i;
|
||||
const z = zStart + j;
|
||||
await bot.chat(`/setblock ${x} ${position.y - 1} ${z} farmland`);
|
||||
await bot.chat(`/setblock ${x} ${position.y} ${z} carrots[age=7]`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const plantSugarCane = async (patches) => {
|
||||
for (const patch of patches) {
|
||||
const xCenter = patch.xStart + 1;
|
||||
const zCenter = patch.zStart + 1;
|
||||
await bot.chat(`/setblock ${xCenter} ${position.y - 1} ${zCenter} water`);
|
||||
const offsets = [[1, 0], [-1, 0], [0, 1], [0, -1]];
|
||||
for (const [dx, dz] of offsets) {
|
||||
await bot.chat(`/setblock ${xCenter + dx} ${position.y} ${zCenter + dz} sugar_cane[age=15]`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const plantPumpkins = async (xStart, zStart) => {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const x = xStart + i;
|
||||
const z = zStart;
|
||||
await bot.chat(`/setblock ${x} ${position.y} ${z} pumpkin`);
|
||||
}
|
||||
};
|
||||
|
||||
// Execute all planting
|
||||
await plantWheat(regionPositions.wheat.xStart, regionPositions.wheat.zStart);
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
await plantBeetroots(regionPositions.beetroots.xStart, regionPositions.beetroots.zStart);
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
await plantMushrooms(regionPositions.mushrooms.xStart, regionPositions.mushrooms.zStart);
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
await plantPotatoes(regionPositions.potatoes.xStart, regionPositions.potatoes.zStart);
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
await plantCarrots(regionPositions.carrots.xStart, regionPositions.carrots.zStart);
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
await plantSugarCane(regionPositions.sugar_cane);
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
await plantPumpkins(regionPositions.pumpkins.xStart, regionPositions.pumpkins.zStart);
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
|
||||
// House construction
|
||||
const buildHouse = async (xStart, zStart) => {
|
||||
const startX = xStart;
|
||||
const startY = position.y;
|
||||
const startZ = zStart;
|
||||
const width = 10;
|
||||
const depth = 10;
|
||||
const height = 5;
|
||||
|
||||
// Foundation and walls
|
||||
for (let x = startX; x <= startX + depth; x++) {
|
||||
for (let y = startY; y <= startY + height; y++) {
|
||||
for (let z = startZ; z <= startZ + width; z++) {
|
||||
if (y === startY) {
|
||||
if (!(x === startX + depth - 1 && z === startZ + Math.floor(width / 2))) {
|
||||
await bot.chat(`/setblock ${x} ${y} ${z} stone_bricks`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (x === startX || x === startX + depth ||
|
||||
z === startZ || z === startZ + width ||
|
||||
y === startY + height) {
|
||||
|
||||
const isWindow = (
|
||||
(x === startX || x === startX + depth) &&
|
||||
(z === startZ + 3 || z === startZ + width - 3) &&
|
||||
(y === startY + 2 || y === startY + 3)
|
||||
) || (
|
||||
(z === startZ || z === startZ + width) &&
|
||||
(x === startX + 3 || x === startX + depth - 3) &&
|
||||
(y === startY + 2 || y === startY + 3)
|
||||
);
|
||||
|
||||
const isDoor = x === startX + depth &&
|
||||
z === startZ + Math.floor(width / 2) &&
|
||||
(y === startY + 1 || y === startY + 2);
|
||||
|
||||
if (!isWindow && !isDoor) {
|
||||
await bot.chat(`/setblock ${x} ${y} ${z} stone_bricks`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Entrance features
|
||||
const doorZ = startZ + Math.floor(width / 2);
|
||||
await bot.chat(`/setblock ${startX + depth - 1} ${startY} ${doorZ} stone_brick_stairs[facing=west]`);
|
||||
await bot.chat(`/setblock ${startX + depth - 1} ${startY} ${doorZ - 1} stone_bricks`);
|
||||
await bot.chat(`/setblock ${startX + depth - 1} ${startY} ${doorZ + 1} stone_bricks`);
|
||||
await bot.chat(`/setblock ${startX + depth} ${startY} ${doorZ} oak_door[half=lower,hinge=left,facing=west,powered=false]`);
|
||||
await bot.chat(`/setblock ${startX + depth} ${startY + 1} ${doorZ} oak_door[half=upper,hinge=left,facing=west,powered=false]`);
|
||||
|
||||
// Roof construction
|
||||
for (let i = 0; i < 3; i++) {
|
||||
for (let x = startX + i; x <= startX + depth - i; x++) {
|
||||
for (let z = startZ + i; z <= startZ + width - i; z++) {
|
||||
if (x === startX + i || x === startX + depth - i ||
|
||||
z === startZ + i || z === startZ + width - i) {
|
||||
await bot.chat(`/setblock ${x} ${startY + height + i} ${z} cobblestone`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Interior items
|
||||
await bot.chat(`/setblock ${startX + 4} ${startY + 1} ${startZ + 3} crafting_table`);
|
||||
await bot.chat(`/setblock ${startX + 4} ${startY + 1} ${startZ + 5} furnace`);
|
||||
// Add fuel to the furnace
|
||||
await bot.chat(`/data merge block ${startX + 4} ${startY + 1} ${startZ + 5} {Items:[{Slot:1b,id:"minecraft:coal",Count:64b}]}`)
|
||||
await bot.chat(`/setblock ${startX + 4} ${startY + 1} ${startZ + 7} smoker`);
|
||||
// Add fuel to the smoker
|
||||
await bot.chat(`/data merge block ${startX + 4} ${startY + 1} ${startZ + 7} {Items:[{Slot:1b,id:"minecraft:coal",Count:64b}]}`)
|
||||
await bot.chat(`/setblock ${startX + depth - 3} ${startY + 1} ${startZ + 2} bed`);
|
||||
};
|
||||
|
||||
await buildHouse(regionPositions.house.xStart, regionPositions.house.zStart);
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
|
||||
// Add a chest with cooking items near the bot
|
||||
const addChestWithItems = async () => {
|
||||
// Find a valid position near the bot (within 10 blocks)
|
||||
const findChestPosition = () => {
|
||||
const maxAttempts = 100;
|
||||
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
||||
const x = botX + Math.floor(Math.random() * 10 - 5); // Within ±5 blocks X
|
||||
const z = botZ + Math.floor(Math.random() * 10 - 5); // Within ±5 blocks Z
|
||||
const y = position.y;
|
||||
|
||||
// Check if the position is not overlapping with existing structures
|
||||
if (!isOverlapping(x, x, z, z, occupiedRegions)) {
|
||||
return { x, y, z };
|
||||
}
|
||||
}
|
||||
throw new Error('Failed to find valid chest position');
|
||||
};
|
||||
|
||||
const { x, y, z } = findChestPosition();
|
||||
|
||||
// Place the chest
|
||||
await bot.chat(`/setblock ${x} ${y} ${z} chest`);
|
||||
|
||||
const cookingItems = [
|
||||
['minecraft:milk_bucket', 1], // Non-stackable
|
||||
['minecraft:egg', 16], // Stacks to 16
|
||||
['minecraft:melon_slice', 64], // Stacks to 64
|
||||
['minecraft:sugar', 64],
|
||||
['minecraft:cocoa_beans', 64],
|
||||
['minecraft:apple', 64],
|
||||
['minecraft:cookie', 64],
|
||||
['minecraft:mutton', 64],
|
||||
['minecraft:salmon', 64],
|
||||
['minecraft:cod', 64],
|
||||
['minecraft:kelp', 64],
|
||||
['minecraft:dried_kelp', 64],
|
||||
['minecraft:sweet_berries', 64],
|
||||
['minecraft:honey_bottle', 1], // Non-stackable
|
||||
['minecraft:glow_berries', 64],
|
||||
['minecraft:bowl', 64],
|
||||
['minecraft:golden_carrot', 64],
|
||||
['minecraft:golden_apple', 64],
|
||||
['minecraft:enchanted_golden_apple', 64],
|
||||
['minecraft:cooked_mutton', 64],
|
||||
['minecraft:cooked_salmon', 64],
|
||||
['minecraft:cooked_cod', 64]
|
||||
];
|
||||
|
||||
// Fill the chest with random cooking items
|
||||
for (let slot = 0; slot < cookingItems.length; slot++) { // Chest has 27 slots
|
||||
const randomItem = cookingItems[slot];
|
||||
await bot.chat(`/item replace block ${x} ${y} ${z} container.${slot} with ${randomItem[0]} ${randomItem[1]}`);
|
||||
}
|
||||
|
||||
// Mark the chest area as occupied
|
||||
occupiedRegions.push({
|
||||
xMin: x,
|
||||
xMax: x,
|
||||
zMin: z,
|
||||
zMax: z
|
||||
});
|
||||
};
|
||||
|
||||
await addChestWithItems();
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
|
||||
// Animal management
|
||||
await bot.chat('/kill @e[type=item,distance=..50]');
|
||||
await bot.chat('/kill @e[type=chicken,distance=..50]');
|
||||
await bot.chat('/kill @e[type=cow,distance=..50]');
|
||||
await bot.chat('/kill @e[type=llama,distance=..50]');
|
||||
await bot.chat('/kill @e[type=mooshroom,distance=..50]');
|
||||
await bot.chat('/kill @e[type=pig,distance=..50]');
|
||||
await bot.chat('/kill @e[type=rabbit,distance=..50]');
|
||||
await bot.chat('/kill @e[type=sheep,distance=..50]');
|
||||
|
||||
await bot.chat(`/kill @e[type=item,distance=..50]`);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
|
||||
// Summon new animals
|
||||
const summonAnimals = async () => {
|
||||
const animals = ['chicken', 'cow', 'llama', 'mooshroom', 'pig', 'rabbit', 'sheep'];
|
||||
for (const animal of animals) {
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const x = position.x - 25 + Math.random() * 50;
|
||||
const z = position.z - 25 + Math.random() * 50;
|
||||
await bot.chat(`/summon ${animal} ${Math.floor(x)} ${position.y} ${Math.floor(z)}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
await summonAnimals();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,38 +1,112 @@
|
|||
import { readFileSync } from 'fs';
|
||||
import { executeCommand } from './commands/index.js';
|
||||
import { getPosition } from './library/world.js'
|
||||
import { getPosition } from './library/world.js';
|
||||
import settings from '../../settings.js';
|
||||
import { CookingTaskInitiator } from './task_types/cooking_tasks.js';
|
||||
|
||||
|
||||
export class TaskValidator {
|
||||
constructor(data, agent) {
|
||||
this.target = data.target;
|
||||
this.number_of_target = data.number_of_target;
|
||||
this.agent = agent;
|
||||
/**
|
||||
* Validates the presence of required items in an agent's inventory
|
||||
* @param {Object} data - Task data containing target and quantity information
|
||||
* @param {Object} agent - Agent object with bot inventory
|
||||
* @returns {Object} Validation result with success status and missing items
|
||||
*/
|
||||
function checkItemPresence(data, agent) {
|
||||
// Helper function to check if target is a dictionary with quantities
|
||||
function isTargetDictionaryWithQuantities(target) {
|
||||
return typeof target === 'object' &&
|
||||
!Array.isArray(target) &&
|
||||
target !== null &&
|
||||
Object.values(target).every(value => typeof value === 'number');
|
||||
}
|
||||
|
||||
validate() {
|
||||
try{
|
||||
let valid = false;
|
||||
let total_targets = 0;
|
||||
this.agent.bot.inventory.slots.forEach((slot) => {
|
||||
if (slot && slot.name.toLowerCase() === this.target) {
|
||||
total_targets += slot.count;
|
||||
}
|
||||
if (slot && slot.name.toLowerCase() === this.target && slot.count >= this.number_of_target) {
|
||||
valid = true;
|
||||
console.log('Task is complete');
|
||||
}
|
||||
});
|
||||
if (total_targets >= this.number_of_target) {
|
||||
valid = true;
|
||||
console.log('Task is complete');
|
||||
}
|
||||
return valid;
|
||||
} catch (error) {
|
||||
console.error('Error validating task:', error);
|
||||
return false;
|
||||
// Convert any target format into a standardized dictionary
|
||||
function normalizeTargets(target) {
|
||||
if (typeof target === 'string') {
|
||||
// Single target case
|
||||
return { [target]: 1 };
|
||||
} else if (Array.isArray(target)) {
|
||||
// Array case - convert to dictionary with default quantity 1
|
||||
return target.reduce((acc, item) => {
|
||||
acc[item] = 1;
|
||||
return acc;
|
||||
}, {});
|
||||
} else if (typeof target === 'object' && target !== null) {
|
||||
// Already a dictionary - return as is
|
||||
return target;
|
||||
}
|
||||
throw new Error('Invalid target format');
|
||||
}
|
||||
|
||||
// Normalize quantities to match target format
|
||||
function normalizeQuantities(targets, quantities) {
|
||||
if (quantities === undefined) {
|
||||
// If no quantities specified, default to 1 for each target
|
||||
return Object.keys(targets).reduce((acc, key) => {
|
||||
acc[key] = 1;
|
||||
return acc;
|
||||
}, {});
|
||||
} else if (typeof quantities === 'number') {
|
||||
// If single number provided, apply to all targets
|
||||
return Object.keys(targets).reduce((acc, key) => {
|
||||
acc[key] = quantities;
|
||||
return acc;
|
||||
}, {});
|
||||
} else if (typeof quantities === 'object' && quantities !== null) {
|
||||
// If quantities dictionary provided, use it directly
|
||||
return quantities;
|
||||
}
|
||||
throw new Error('Invalid number_of_target format');
|
||||
}
|
||||
|
||||
try {
|
||||
// First normalize targets to always have a consistent format
|
||||
const targets = normalizeTargets(data.target);
|
||||
|
||||
// Determine the required quantities
|
||||
const requiredQuantities = isTargetDictionaryWithQuantities(data.target)
|
||||
? data.target
|
||||
: normalizeQuantities(targets, data.number_of_target);
|
||||
|
||||
// Count items in inventory
|
||||
const inventoryCount = {};
|
||||
agent.bot.inventory.slots.forEach((slot) => {
|
||||
if (slot) {
|
||||
const itemName = slot.name.toLowerCase();
|
||||
inventoryCount[itemName] = (inventoryCount[itemName] || 0) + slot.count;
|
||||
}
|
||||
});
|
||||
|
||||
// Check if all required items are present in sufficient quantities
|
||||
const missingItems = [];
|
||||
let allTargetsMet = true;
|
||||
|
||||
for (const [item, requiredCount] of Object.entries(requiredQuantities)) {
|
||||
const itemName = item.toLowerCase();
|
||||
const currentCount = inventoryCount[itemName] || 0;
|
||||
|
||||
if (currentCount < requiredCount) {
|
||||
allTargetsMet = false;
|
||||
missingItems.push({
|
||||
item: itemName,
|
||||
required: requiredCount,
|
||||
current: currentCount,
|
||||
missing: requiredCount - currentCount
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: allTargetsMet,
|
||||
missingItems: missingItems
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error checking item presence:', error);
|
||||
return {
|
||||
success: false,
|
||||
missingItems: [],
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,7 +122,18 @@ export class Task {
|
|||
this.data = this.loadTask(task_path, task_id);
|
||||
this.taskTimeout = this.data.timeout || 300;
|
||||
this.taskStartTime = Date.now();
|
||||
this.validator = new TaskValidator(this.data, this.agent);
|
||||
this.task_type = this.data.type;
|
||||
|
||||
// Set validator based on task_type
|
||||
if (this.task_type === 'cooking' || this.task_type === 'techtree') {
|
||||
this.validator = () => {
|
||||
const result = checkItemPresence(this.data, this.agent);
|
||||
return result.success;
|
||||
};
|
||||
} else {
|
||||
this.validator = null;
|
||||
}
|
||||
|
||||
if (this.data.blocked_actions) {
|
||||
this.blocked_actions = this.data.blocked_actions[this.agent.count_id.toString()] || [];
|
||||
} else {
|
||||
|
@ -60,6 +145,34 @@ export class Task {
|
|||
if (this.data.conversation)
|
||||
this.blocked_actions.push('!endConversation');
|
||||
}
|
||||
|
||||
this.name = this.agent.name;
|
||||
this.available_agents = settings.profiles.map((p) => JSON.parse(readFileSync(p, 'utf8')).name);
|
||||
}
|
||||
|
||||
getAgentGoal() {
|
||||
if (!this.data || !this.data.goal) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let add_string = '';
|
||||
|
||||
if (this.task_type === 'cooking') {
|
||||
add_string = '\nIn the end, all the food should be given to Andy.';
|
||||
}
|
||||
|
||||
// If goal is a string, all agents share the same goal
|
||||
if (typeof this.data.goal === 'string') {
|
||||
return this.data.goal + add_string;
|
||||
}
|
||||
|
||||
// If goal is an object, get the goal for this agent's count_id
|
||||
if (typeof this.data.goal === 'object' && this.data.goal !== null) {
|
||||
const agentId = this.agent.count_id.toString();
|
||||
return (this.data.goal[agentId] || '') + add_string;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
loadTask(task_path, task_id) {
|
||||
|
@ -82,8 +195,9 @@ export class Task {
|
|||
}
|
||||
|
||||
isDone() {
|
||||
if (this.validator && this.validator.validate())
|
||||
if (this.validator && this.validator())
|
||||
return {"message": 'Task successful', "code": 2};
|
||||
|
||||
if (this.taskTimeout) {
|
||||
const elapsedTime = (Date.now() - this.taskStartTime) / 1000;
|
||||
if (elapsedTime >= this.taskTimeout) {
|
||||
|
@ -95,104 +209,109 @@ export class Task {
|
|||
}
|
||||
|
||||
async initBotTask() {
|
||||
if (this.data === null)
|
||||
return;
|
||||
let bot = this.agent.bot;
|
||||
let name = this.agent.name;
|
||||
await this.agent.bot.chat(`/clear ${this.name}`);
|
||||
console.log(`Cleared ${this.name}'s inventory.`);
|
||||
|
||||
bot.chat(`/clear ${name}`);
|
||||
console.log(`Cleared ${name}'s inventory.`);
|
||||
|
||||
//kill all drops
|
||||
if (this.agent.count_id === 0) {
|
||||
bot.chat(`/kill @e[type=item]`);
|
||||
}
|
||||
//wait for a bit so inventory is cleared
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
let initial_inventory = null;
|
||||
if (this.data.agent_count > 1) {
|
||||
initial_inventory = this.data.initial_inventory[this.agent.count_id.toString()];
|
||||
console.log("Initial inventory:", initial_inventory);
|
||||
} else if (this.data) {
|
||||
console.log("Initial inventory:", this.data.initial_inventory);
|
||||
initial_inventory = this.data.initial_inventory;
|
||||
|
||||
if (this.data === null)
|
||||
return;
|
||||
|
||||
if (this.task_type === 'cooking') {
|
||||
this.initiator = new CookingTaskInitiator(this.data, this.agent);
|
||||
} else {
|
||||
this.initiator = null;
|
||||
}
|
||||
|
||||
if ("initial_inventory" in this.data) {
|
||||
|
||||
await this.teleportBots();
|
||||
|
||||
//wait for a bit so bots are teleported
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||
|
||||
if (this.data.initial_inventory) {
|
||||
console.log("Setting inventory...");
|
||||
console.log("Inventory to set:", initial_inventory);
|
||||
for (let key of Object.keys(initial_inventory)) {
|
||||
console.log('Giving item:', key);
|
||||
bot.chat(`/give ${name} ${key} ${initial_inventory[key]}`);
|
||||
};
|
||||
//wait for a bit so inventory is set
|
||||
let initialInventory = {};
|
||||
|
||||
// Handle multi-agent inventory assignment
|
||||
if (this.data.agent_count > 1) {
|
||||
initialInventory = this.data.initial_inventory[this.agent.count_id.toString()] || {};
|
||||
console.log("Initial inventory for agent", this.agent.count_id, ":", initialInventory);
|
||||
} else {
|
||||
initialInventory = this.data.initial_inventory;
|
||||
console.log("Initial inventory:", initialInventory);
|
||||
}
|
||||
|
||||
// Assign inventory items
|
||||
for (let key of Object.keys(initialInventory)) {
|
||||
const itemName = key.toLowerCase();
|
||||
const quantity = initialInventory[key];
|
||||
await this.agent.bot.chat(`/give ${this.name} ${itemName} ${quantity}`);
|
||||
console.log(`Gave ${this.name} ${quantity} ${itemName}`);
|
||||
}
|
||||
|
||||
// Wait briefly for inventory commands to complete
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
console.log("Done giving inventory items.");
|
||||
}
|
||||
// Function to generate random numbers
|
||||
|
||||
if (this.initiator) {
|
||||
await this.initiator.init();
|
||||
}
|
||||
|
||||
if (this.data.agent_count && this.data.agent_count > 1) {
|
||||
// TODO wait for other bots to join
|
||||
await new Promise((resolve) => setTimeout(resolve, 10000));
|
||||
if (this.available_agents.length < this.data.agent_count) {
|
||||
console.log(`Missing ${this.data.agent_count - this.available_agents.length} bot(s).`);
|
||||
this.agent.killAll();
|
||||
}
|
||||
}
|
||||
|
||||
const agentGoal = this.getAgentGoal();
|
||||
console.log(`Agent goal for agent Id ${this.agent.count_id}: ${agentGoal}`);
|
||||
if (agentGoal) {
|
||||
console.log(`Setting goal for agent ${this.agent.count_id}: ${agentGoal}`);
|
||||
await executeCommand(this.agent, `!goal("${agentGoal}")`);
|
||||
}
|
||||
|
||||
if (this.data.conversation && this.agent.count_id === 0) {
|
||||
let other_name = this.available_agents.filter(n => n !== this.name)[0];
|
||||
await executeCommand(this.agent, `!startConversation("${other_name}", "${this.data.conversation}")`);
|
||||
}
|
||||
}
|
||||
|
||||
async teleportBots() {
|
||||
console.log('\n\n\n\n\nTeleporting bots');
|
||||
function getRandomOffset(range) {
|
||||
return Math.floor(Math.random() * (range * 2 + 1)) - range;
|
||||
}
|
||||
|
||||
|
||||
let human_player_name = null;
|
||||
let available_agents = settings.profiles.map((p) => JSON.parse(readFileSync(p, 'utf8')).name); // TODO this does not work with command line args
|
||||
|
||||
let bot = this.agent.bot;
|
||||
|
||||
// Finding if there is a human player on the server
|
||||
for (const playerName in bot.players) {
|
||||
const player = bot.players[playerName];
|
||||
if (!available_agents.some((n) => n === playerName)) {
|
||||
if (!this.available_agents.some((n) => n === playerName)) {
|
||||
console.log('Found human player:', player.username);
|
||||
human_player_name = player.username
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If there are multiple human players, teleport to the first one
|
||||
|
||||
// teleport near a human player if found by default
|
||||
|
||||
if (human_player_name) {
|
||||
console.log(`Teleporting ${name} to human ${human_player_name}`)
|
||||
bot.chat(`/tp ${name} ${human_player_name}`) // teleport on top of the human player
|
||||
|
||||
console.log(`Teleporting ${this.name} to human ${human_player_name}`)
|
||||
bot.chat(`/tp ${this.name} ${human_player_name}`)
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
// now all bots are teleport on top of each other (which kinda looks ugly)
|
||||
// Thus, we need to teleport them to random distances to make it look better
|
||||
|
||||
/*
|
||||
Note : We don't want randomness for construction task as the reference point matters a lot.
|
||||
Another reason for no randomness for construction task is because, often times the user would fly in the air,
|
||||
then set a random block to dirt and teleport the bot to stand on that block for starting the construction,
|
||||
This was done by MaxRobinson in one of the youtube videos.
|
||||
*/
|
||||
|
||||
|
||||
if (this.data.type !== 'construction') {
|
||||
const pos = getPosition(bot);
|
||||
const xOffset = getRandomOffset(5);
|
||||
const zOffset = getRandomOffset(5);
|
||||
bot.chat(`/tp ${name} ${Math.floor(pos.x + xOffset)} ${pos.y + 3} ${Math.floor(pos.z + zOffset)}`);
|
||||
bot.chat(`/tp ${this.name} ${Math.floor(pos.x + xOffset)} ${pos.y + 3} ${Math.floor(pos.z + zOffset)}`);
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
}
|
||||
|
||||
if (this.data.agent_count && this.data.agent_count > 1) {
|
||||
// TODO wait for other bots to join
|
||||
await new Promise((resolve) => setTimeout(resolve, 10000));
|
||||
if (available_agents.length < this.data.agent_count) {
|
||||
console.log(`Missing ${this.data.agent_count - available_agents.length} bot(s).`);
|
||||
this.agent.killAll();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.data.goal) {
|
||||
await executeCommand(this.agent, `!goal("${this.data.goal}")`);
|
||||
}
|
||||
|
||||
if (this.data.conversation && this.agent.count_id === 0) {
|
||||
let other_name = available_agents.filter(n => n !== name)[0];
|
||||
await executeCommand(this.agent, `!startConversation("${other_name}", "${this.data.conversation}")`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue