From 2f8248955e1b3c963e42bbb0cf6c9b5139ee9641 Mon Sep 17 00:00:00 2001 From: Ayush Maniar Date: Sun, 23 Feb 2025 02:41:51 -0800 Subject: [PATCH 1/5] Add some example cooking tasks --- example_tasks.json | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/example_tasks.json b/example_tasks.json index 69ab550..d8fa295 100644 --- a/example_tasks.json +++ b/example_tasks.json @@ -54,6 +54,7 @@ } }, "target": "stone_pickaxe", + "goal": "Build a stone pickaxe", "number_of_target": 1, "type": "techtree", "timeout": 300 @@ -71,5 +72,38 @@ "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": "Collaborate to make 1 bread, 1 cooked_mutton" + } } \ No newline at end of file From 1ba5f130f3392bb243e663af49de4cd7658c0fc7 Mon Sep 17 00:00:00 2001 From: Ayush Maniar Date: Sun, 23 Feb 2025 02:50:11 -0800 Subject: [PATCH 2/5] Improve task.js with better modularized code --- src/agent/tasks.js | 314 +++++++++++++++++++++++++++++---------------- 1 file changed, 203 insertions(+), 111 deletions(-) diff --git a/src/agent/tasks.js b/src/agent/tasks.js index a51bb0c..1ca8373 100644 --- a/src/agent/tasks.js +++ b/src/agent/tasks.js @@ -1,42 +1,116 @@ 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 { CraftTaskInitiator } from './task_types/crafting_tasks.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 + }; } } - export class Task { constructor(agent, task_path, task_id) { this.agent = agent; @@ -49,7 +123,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; + } + this.blocked_actions = this.data.blocked_actions || []; this.restrict_to_inventory = !!this.data.restrict_to_inventory; if (this.data.goal) @@ -57,6 +142,9 @@ 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); } loadTask(task_path, task_id) { @@ -79,13 +167,9 @@ export class Task { } isDone() { - if (this.validator && this.validator.validate()) + if (this.validator && this.validator()) return {"message": 'Task successful', "code": 2}; - // TODO check for other terminal conditions - // if (this.task.goal && !this.self_prompter.on) - // return {"message": 'Agent ended goal', "code": 3}; - // if (this.task.conversation && !inConversation()) - // return {"message": 'Agent ended conversation', "code": 3}; + if (this.taskTimeout) { const elapsedTime = (Date.now() - this.taskStartTime) / 1000; if (elapsedTime >= this.taskTimeout) { @@ -97,89 +181,62 @@ 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.`); - //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 ("initial_inventory" in this.data) { - 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 - await new Promise((resolve) => setTimeout(resolve, 500)); - console.log("Done giving inventory items."); - } - // Function to generate random numbers - - 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 - - // 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)) { - console.log('Found human player:', player.username); - human_player_name = player.username - break; - } + + if (this.data === null) + return; + + if (this.task_type === 'techtree') { + this.initiator = new CraftTaskInitiator(this.data, this.agent); + } else if (this.task_type === 'cooking') { + this.initiator = new CookingTaskInitiator(this.data, this.agent); + } else { + this.initiator = null; } - // 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 - + 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..."); + 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)); } - 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)}`); - await new Promise((resolve) => setTimeout(resolve, 200)); + + 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 (available_agents.length < this.data.agent_count) { - console.log(`Missing ${this.data.agent_count - available_agents.length} bot(s).`); + 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(); } } @@ -189,8 +246,43 @@ export class Task { } if (this.data.conversation && this.agent.count_id === 0) { - let other_name = available_agents.filter(n => n !== name)[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 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 (!this.available_agents.some((n) => n === playerName)) { + console.log('Found human player:', player.username); + human_player_name = player.username + break; + } + } + + if (human_player_name) { + 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)); + + if (this.data.type !== 'construction') { + const pos = getPosition(bot); + const xOffset = getRandomOffset(5); + const zOffset = getRandomOffset(5); + 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)); + } + } +} \ No newline at end of file From 786bee36d39b223d1e42c21a7f13e1e62af6c1e2 Mon Sep 17 00:00:00 2001 From: Ayush Maniar Date: Sun, 23 Feb 2025 02:53:45 -0800 Subject: [PATCH 3/5] Added different initialization for cooking_tasks --- src/agent/task_types/cooking_tasks.js | 383 ++++++++++++++++++++++++++ src/agent/tasks.js | 7 +- 2 files changed, 385 insertions(+), 5 deletions(-) create mode 100644 src/agent/task_types/cooking_tasks.js diff --git a/src/agent/task_types/cooking_tasks.js b/src/agent/task_types/cooking_tasks.js new file mode 100644 index 0000000..9f97d9b --- /dev/null +++ b/src/agent/task_types/cooking_tasks.js @@ -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(); + } + } +} \ No newline at end of file diff --git a/src/agent/tasks.js b/src/agent/tasks.js index 1ca8373..dc3814e 100644 --- a/src/agent/tasks.js +++ b/src/agent/tasks.js @@ -2,7 +2,6 @@ import { readFileSync } from 'fs'; import { executeCommand } from './commands/index.js'; import { getPosition } from './library/world.js'; import settings from '../../settings.js'; -import { CraftTaskInitiator } from './task_types/crafting_tasks.js'; import { CookingTaskInitiator } from './task_types/cooking_tasks.js'; /** @@ -189,10 +188,8 @@ export class Task { if (this.data === null) return; - - if (this.task_type === 'techtree') { - this.initiator = new CraftTaskInitiator(this.data, this.agent); - } else if (this.task_type === 'cooking') { + + if (this.task_type === 'cooking') { this.initiator = new CookingTaskInitiator(this.data, this.agent); } else { this.initiator = null; From 57c47c0bcf576f30b5ebf43113c1d61d91416fe9 Mon Sep 17 00:00:00 2001 From: Ayush Maniar Date: Sun, 23 Feb 2025 03:21:59 -0800 Subject: [PATCH 4/5] Provide agent specific goals in task, added more example tasks --- example_tasks.json | 48 +++++++++++++++++++++++++++------------------- src/agent/tasks.js | 25 ++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/example_tasks.json b/example_tasks.json index 44d8943..a1287c2 100644 --- a/example_tasks.json +++ b/example_tasks.json @@ -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": { @@ -90,6 +98,25 @@ "type": "techtree", "timeout": 300 }, + "multiagent_smelt_ingot": { + "conversation": "Let's collaborate to smelt ingots", + "goal": "Smelt 1 iron ingot and 1 copper ingot, use star emojis in every response", + "agent_count": 2, + "initial_inventory": { + "0": { + "furnace": 1, + "coal": 2 + }, + "1": { + "raw_iron": 1, + "raw_copper": 1 + } + }, + "target": "copper_ingot", + "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, @@ -122,24 +149,5 @@ }, "blocked_access_to_recipe": [], "goal": "Collaborate to make 1 bread, 1 cooked_mutton" - }, - "multiagent_smelt_ingot": { - "conversation": "Let's collaborate to smelt ingots", - "goal": "Smelt 1 iron ingot and 1 copper ingot, use star emojis in every response", - "agent_count": 2, - "initial_inventory": { - "0": { - "furnace": 1, - "coal": 2 - }, - "1": { - "raw_iron": 1, - "raw_copper": 1 - } - }, - "target": "copper_ingot", - "number_of_target": 1, - "type": "techtree", - "timeout": 300 - } + } } \ No newline at end of file diff --git a/src/agent/tasks.js b/src/agent/tasks.js index 96b3849..770a939 100644 --- a/src/agent/tasks.js +++ b/src/agent/tasks.js @@ -150,6 +150,25 @@ export class Task { this.available_agents = settings.profiles.map((p) => JSON.parse(readFileSync(p, 'utf8')).name); } + getAgentGoal() { + if (!this.data || !this.data.goal) { + return null; + } + + // If goal is a string, all agents share the same goal + if (typeof this.data.goal === 'string') { + return this.data.goal; + } + + // 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] || null; + } + + return null; + } + loadTask(task_path, task_id) { try { const tasksFile = readFileSync(task_path, 'utf8'); @@ -242,8 +261,10 @@ export class Task { } } - if (this.data.goal) { - await executeCommand(this.agent, `!goal("${this.data.goal}")`); + const agentGoal = this.getAgentGoal(); + 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) { From fd4f952a2c90b58445cd2fe863b6a67426315582 Mon Sep 17 00:00:00 2001 From: Ayush Maniar Date: Tue, 25 Feb 2025 22:47:43 -0800 Subject: [PATCH 5/5] Added example tasks for different goals per agent --- example_tasks.json | 5 ++++- src/agent/tasks.js | 11 +++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/example_tasks.json b/example_tasks.json index a1287c2..f90ad7c 100644 --- a/example_tasks.json +++ b/example_tasks.json @@ -148,6 +148,9 @@ ] }, "blocked_access_to_recipe": [], - "goal": "Collaborate to make 1 bread, 1 cooked_mutton" + "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." + } } } \ No newline at end of file diff --git a/src/agent/tasks.js b/src/agent/tasks.js index 770a939..dab9680 100644 --- a/src/agent/tasks.js +++ b/src/agent/tasks.js @@ -155,15 +155,21 @@ export class Task { 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; + 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] || null; + return (this.data.goal[agentId] || '') + add_string; } return null; @@ -262,6 +268,7 @@ export class Task { } 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}")`);