2024-12-10 15:39:57 -08:00
import { readFileSync } from 'fs' ;
import { executeCommand } from './commands/index.js' ;
2025-02-23 02:50:11 -08:00
import { getPosition } from './library/world.js' ;
2024-12-10 15:39:57 -08:00
import settings from '../../settings.js' ;
2024-12-27 20:42:34 -06:00
import { Vec3 } from 'vec3' ;
2025-03-03 21:41:38 -08:00
import { ConstructionTaskValidator , Blueprint } from './task_types/construction_tasks.js' ;
2025-02-23 02:50:11 -08:00
import { CookingTaskInitiator } from './task_types/cooking_tasks.js' ;
2024-12-10 15:39:57 -08:00
2024-12-29 10:56:20 -06:00
//todo: modify validator code to return an object with valid and score -> do more testing hahah
//todo: figure out how to log these things to the same place as bots/histories
2025-03-03 17:06:24 -08:00
// export class CraftTaskValidator {
// constructor(data, agent) {
// this.target = data.target;
// this.number_of_target = data.number_of_target;
// this.agent = agent;
2025-02-23 02:50:11 -08:00
/ * *
* 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 ) &&
2025-03-04 11:10:42 -08:00
2025-02-23 02:50:11 -08:00
target !== null &&
Object . values ( target ) . every ( value => typeof value === 'number' ) ;
2024-12-10 15:39:57 -08:00
}
2025-02-23 02:50:11 -08:00
// 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' ) ;
2024-12-10 15:39:57 -08:00
}
2025-02-23 02:50:11 -08:00
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
} ) ;
2024-12-10 15:39:57 -08:00
}
}
2025-02-23 02:50:11 -08:00
return {
success : allTargetsMet ,
missingItems : missingItems
} ;
} catch ( error ) {
console . error ( 'Error checking item presence:' , error ) ;
return {
success : false ,
missingItems : [ ] ,
error : error . message
} ;
2024-12-10 15:39:57 -08:00
}
}
2025-03-03 17:06:24 -08:00
class CookingCraftingTaskValidator {
constructor ( data , agent ) {
this . data = data ;
this . agent = agent ;
}
2025-03-20 12:56:29 -07:00
validate ( ) {
const result = checkItemPresence ( this . data , this . agent ) ;
let score = 0 ;
if ( result . success ) {
score = 1 ;
2025-03-05 10:10:24 -08:00
}
2025-03-20 12:56:29 -07:00
return {
"valid" : result . success ,
"score" : score ,
} ;
2024-12-10 15:39:57 -08:00
}
}
export class Task {
2025-03-16 20:31:30 -07:00
constructor ( agent , task _path , task _id , taskStartTime = null ) {
2024-12-10 15:39:57 -08:00
this . agent = agent ;
this . data = null ;
2025-03-16 20:31:30 -07:00
console . log ( "task start time" , taskStartTime ) ;
if ( taskStartTime !== null )
this . taskStartTime = taskStartTime ;
else
this . taskStartTime = Date . now ( ) ;
console . log ( this . taskStartTime ) ;
2024-12-10 15:39:57 -08:00
this . validator = null ;
2025-01-07 19:29:50 -08:00
this . reset _function = null ;
2024-12-10 15:39:57 -08:00
this . blocked _actions = [ ] ;
2025-02-28 14:42:00 -08:00
this . task _id = task _id ;
2025-03-09 13:01:54 -07:00
console . log ( 'Task ID:' , task _id ) ;
2024-12-10 15:39:57 -08:00
if ( task _path && task _id ) {
this . data = this . loadTask ( task _path , task _id ) ;
2024-12-23 11:34:56 -06:00
this . task _type = this . data . type ;
if ( this . task _type === 'construction' && this . data . blueprint ) {
this . blueprint = new Blueprint ( this . data . blueprint ) ;
2024-12-26 18:12:11 -06:00
this . goal = this . data . goal + ' \n' + this . blueprint . explain ( ) + " \n" + "make sure to place the lower levels of the blueprint first" ;
this . conversation = this . data . conversation + ' \n' + this . blueprint . explain ( ) ;
2024-12-23 11:34:56 -06:00
} else {
this . goal = this . data . goal ;
2024-12-26 18:12:11 -06:00
this . conversation = this . data . conversation ;
2024-12-23 11:34:56 -06:00
}
2024-12-10 15:39:57 -08:00
this . taskTimeout = this . data . timeout || 300 ;
this . taskStartTime = Date . now ( ) ;
2025-02-23 02:50:11 -08:00
// Set validator based on task_type
2025-02-27 21:00:41 -08:00
2024-12-26 18:12:11 -06:00
if ( this . task _type === 'construction' ) {
this . validator = new ConstructionTaskValidator ( this . data , this . agent ) ;
2025-03-03 17:06:24 -08:00
} else if ( this . task _type === 'cooking' || this . task _type === 'techtree' ) {
this . validator = new CookingCraftingTaskValidator ( this . data , this . agent ) ;
2025-03-04 11:10:42 -08:00
2025-02-23 02:50:11 -08:00
} else {
this . validator = null ;
2024-12-26 18:12:11 -06:00
}
2025-02-23 02:59:46 -08:00
2025-02-20 21:45:29 -08:00
if ( this . data . blocked _actions ) {
this . blocked _actions = this . data . blocked _actions [ this . agent . count _id . toString ( ) ] || [ ] ;
} else {
this . blocked _actions = [ ] ;
}
2025-01-09 15:15:25 -06:00
this . restrict _to _inventory = ! ! this . data . restrict _to _inventory ;
2024-12-10 15:39:57 -08:00
if ( this . data . goal )
this . blocked _actions . push ( '!endGoal' ) ;
2024-12-26 18:12:11 -06:00
if ( this . conversation )
2024-12-10 15:39:57 -08:00
this . blocked _actions . push ( '!endConversation' ) ;
}
2025-03-03 11:53:57 -08:00
2025-02-23 02:50:11 -08:00
this . name = this . agent . name ;
this . available _agents = settings . profiles . map ( ( p ) => JSON . parse ( readFileSync ( p , 'utf8' ) ) . name ) ;
2024-12-10 15:39:57 -08:00
}
2025-02-23 03:21:59 -08:00
getAgentGoal ( ) {
if ( ! this . data || ! this . data . goal ) {
return null ;
}
2025-02-25 22:47:43 -08:00
let add _string = '' ;
if ( this . task _type === 'cooking' ) {
2025-03-25 11:05:56 -07:00
if ( this . data . agent _count > 2 ) {
if ( this . name . toLowerCase ( ) . startsWith ( 'andy' ) ) {
add _string = '\nIn the end, all the food items should be given to you by other bots. Make sure to talk to all the agents using startConversation command to coordinate the task instead of talking to just one agent. You can even end current conversation with any agent using endConversation command and then talk to a new agent using startConversation command.' ;
}
else {
add _string = '\nIn the end, all the food items should be given to one single bot whose name starts with andy or Andy. Make sure to talk to all the agents using startConversation command to coordinate the task instead of talking to just one agent. You can even end current conversation with any agent using endConversation command and then talk to a new agent using startConversation command.' ;
}
}
else {
add _string = '\nIn the end, all the food items should be given to one single bot.' ;
}
2025-02-25 22:47:43 -08:00
}
2025-02-23 03:21:59 -08:00
// If goal is a string, all agents share the same goal
if ( typeof this . data . goal === 'string' ) {
2025-02-25 22:47:43 -08:00
return this . data . goal + add _string ;
2025-02-23 03:21:59 -08:00
}
// 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 ( ) ;
2025-02-25 22:47:43 -08:00
return ( this . data . goal [ agentId ] || '' ) + add _string ;
2025-02-23 03:21:59 -08:00
}
return null ;
2024-12-10 15:39:57 -08:00
}
loadTask ( task _path , task _id ) {
try {
const tasksFile = readFileSync ( task _path , 'utf8' ) ;
const tasks = JSON . parse ( tasksFile ) ;
2025-02-28 16:29:37 -08:00
let task = tasks [ task _id ] ;
2025-03-09 13:01:54 -07:00
console . log ( task ) ;
console . log ( this . agent . count _id ) ;
2024-12-10 15:39:57 -08:00
if ( ! task ) {
throw new Error ( ` Task ${ task _id } not found ` ) ;
}
2025-03-09 13:01:54 -07:00
// if ((!task.agent_count || task.agent_count <= 1) && this.agent.count_id > 0) {
// task = null;
// }
2024-12-12 12:33:00 -08:00
2024-12-10 15:39:57 -08:00
return task ;
} catch ( error ) {
console . error ( 'Error loading task:' , error ) ;
process . exit ( 1 ) ;
}
}
isDone ( ) {
2025-03-03 17:06:24 -08:00
let res = null ;
if ( this . validator )
2025-03-20 12:56:29 -07:00
res = this . validator . validate ( ) ;
2025-03-03 17:06:24 -08:00
if ( res && res . valid ) {
2025-03-20 12:56:29 -07:00
// Find all the agents and clear their inventories
for ( let agent of this . available _agents ) {
this . agent . bot . chat ( ` /clear ${ agent } ` ) ;
}
2025-03-03 17:06:24 -08:00
return { "message" : 'Task successful' , "score" : res . score } ;
}
2025-02-28 16:59:56 -08:00
let other _names = this . available _agents . filter ( n => n !== this . name ) ;
const elapsedTime = ( Date . now ( ) - this . taskStartTime ) / 1000 ;
2025-02-28 18:31:19 -08:00
if ( elapsedTime >= 30 && this . available _agents . length !== this . data . agent _count ) {
2025-02-28 16:59:56 -08:00
console . log ( 'No other agents found. Task unsuccessful.' ) ;
2025-03-03 17:06:24 -08:00
return { "message" : 'No other agents found' , "score" : 0 } ;
2025-02-28 16:59:56 -08:00
}
2025-02-23 02:50:11 -08:00
2024-12-10 15:39:57 -08:00
if ( this . taskTimeout ) {
if ( elapsedTime >= this . taskTimeout ) {
console . log ( 'Task timeout reached. Task unsuccessful.' ) ;
2025-03-03 17:06:24 -08:00
if ( res ) {
return { "message" : 'Task timeout reached' , "score" : res . score } ;
} else {
return { "message" : 'Task timeout reached' , "score" : 0 } ;
}
2024-12-10 15:39:57 -08:00
}
}
return false ;
}
async initBotTask ( ) {
2025-02-23 02:50:11 -08:00
await this . agent . bot . chat ( ` /clear ${ this . name } ` ) ;
console . log ( ` Cleared ${ this . name } 's inventory. ` ) ;
2025-01-07 19:29:50 -08:00
2024-12-10 15:39:57 -08:00
//wait for a bit so inventory is cleared
await new Promise ( ( resolve ) => setTimeout ( resolve , 500 ) ) ;
2025-03-02 12:53:09 -08:00
2024-12-10 15:39:57 -08:00
if ( this . data === null )
return ;
2025-02-23 02:53:45 -08:00
if ( this . task _type === 'cooking' ) {
2025-02-23 02:50:11 -08:00
this . initiator = new CookingTaskInitiator ( this . data , this . agent ) ;
} else {
this . initiator = null ;
2024-12-10 15:39:57 -08:00
}
2025-02-12 15:39:57 -08:00
2025-02-23 02:50:11 -08:00
//wait for a bit so bots are teleported
await new Promise ( ( resolve ) => setTimeout ( resolve , 3000 ) ) ;
if ( this . data . initial _inventory ) {
2024-12-10 15:39:57 -08:00
console . log ( "Setting inventory..." ) ;
2025-02-23 02:50:11 -08:00
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 ) ;
}
2025-03-04 11:54:09 -08:00
console . log ( this . data . initial _inventory ) ;
2025-02-23 02:50:11 -08:00
// 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
2024-12-10 15:39:57 -08:00
await new Promise ( ( resolve ) => setTimeout ( resolve , 500 ) ) ;
}
2025-02-23 02:50:11 -08:00
if ( this . initiator ) {
await this . initiator . init ( ) ;
}
2025-03-21 02:18:07 -07:00
await this . teleportBots ( ) ;
2025-02-23 02:50:11 -08:00
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 ( ) ;
}
}
2025-02-28 18:31:19 -08:00
if ( this . data . conversation && this . agent . count _id === 0 ) {
let other _name = this . available _agents . filter ( n => n !== this . name ) [ 0 ] ;
let waitCount = 0 ;
while ( other _name === undefined && waitCount < 20 ) {
other _name = this . available _agents . filter ( n => n !== this . name ) [ 0 ] ;
await new Promise ( ( resolve ) => setTimeout ( resolve , 1000 ) ) ;
waitCount ++ ;
}
if ( other _name === undefined ) {
console . log ( 'No other agents found. Task unsuccessful.' ) ;
this . agent . killAll ( ) ;
}
await executeCommand ( this . agent , ` !startConversation(" ${ other _name } ", " ${ this . data . conversation } ") ` ) ;
}
2025-03-20 16:34:23 -07:00
let agentGoal = this . getAgentGoal ( ) ;
2025-02-23 03:21:59 -08:00
if ( agentGoal ) {
2025-03-20 16:34:23 -07:00
agentGoal += "You have to collaborate with other agents/bots, namely " + this . available _agents . filter ( n => n !== this . name ) . join ( ', ' ) + " to complete the task as soon as possible by dividing the work among yourselves." ;
2025-02-23 03:21:59 -08:00
console . log ( ` Setting goal for agent ${ this . agent . count _id } : ${ agentGoal } ` ) ;
await executeCommand ( this . agent , ` !goal(" ${ agentGoal } ") ` ) ;
2025-02-23 02:50:11 -08:00
}
}
2024-12-10 15:39:57 -08:00
2025-02-23 02:50:11 -08:00
async teleportBots ( ) {
2025-03-05 10:10:24 -08:00
console . log ( '\n\nTeleporting bots' ) ;
2024-12-10 15:39:57 -08:00
function getRandomOffset ( range ) {
return Math . floor ( Math . random ( ) * ( range * 2 + 1 ) ) - range ;
}
2025-02-23 02:50:11 -08:00
2024-12-10 15:39:57 -08:00
let human _player _name = null ;
2025-02-23 02:50:11 -08:00
let bot = this . agent . bot ;
2025-03-03 11:53:57 -08:00
2024-12-10 15:39:57 -08:00
// Finding if there is a human player on the server
for ( const playerName in bot . players ) {
const player = bot . players [ playerName ] ;
2025-02-23 02:50:11 -08:00
if ( ! this . available _agents . some ( ( n ) => n === playerName ) ) {
2024-12-10 15:39:57 -08:00
console . log ( 'Found human player:' , player . username ) ;
human _player _name = player . username
break ;
}
2024-12-12 12:33:00 -08:00
}
2024-12-10 15:39:57 -08:00
if ( human _player _name ) {
2025-02-23 02:50:11 -08:00
console . log ( ` Teleporting ${ this . name } to human ${ human _player _name } ` )
bot . chat ( ` /tp ${ this . name } ${ human _player _name } ` )
2024-12-10 15:39:57 -08:00
}
2025-03-21 02:18:07 -07:00
else {
console . log ( ` Teleporting ${ this . name } to ${ this . available _agents [ 0 ] } ` )
bot . chat ( ` /tp ${ this . name } ${ this . available _agents [ 0 ] } ` ) ;
}
2024-12-10 15:39:57 -08:00
await new Promise ( ( resolve ) => setTimeout ( resolve , 200 ) ) ;
2025-02-23 02:50:11 -08:00
2024-12-10 15:39:57 -08:00
// 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
2025-03-03 11:53:57 -08:00
2024-12-10 15:39:57 -08:00
/ *
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 ,
* /
2025-02-19 18:25:59 -08:00
2025-02-23 02:50:11 -08:00
2024-12-10 15:39:57 -08:00
if ( this . data . type !== 'construction' ) {
const pos = getPosition ( bot ) ;
const xOffset = getRandomOffset ( 5 ) ;
const zOffset = getRandomOffset ( 5 ) ;
2025-02-23 02:50:11 -08:00
bot . chat ( ` /tp ${ this . name } ${ Math . floor ( pos . x + xOffset ) } ${ pos . y + 3 } ${ Math . floor ( pos . z + zOffset ) } ` ) ;
2024-12-10 15:39:57 -08:00
await new Promise ( ( resolve ) => setTimeout ( resolve , 200 ) ) ;
}
2024-12-12 18:53:24 -08:00
2024-12-10 15:39:57 -08:00
if ( this . data . agent _count && this . data . agent _count > 1 ) {
2024-12-12 18:53:24 -08:00
// TODO wait for other bots to join
2024-12-10 15:39:57 -08:00
await new Promise ( ( resolve ) => setTimeout ( resolve , 10000 ) ) ;
2025-03-03 17:06:24 -08:00
if ( this . available _agents . length < this . data . agent _count ) {
console . log ( ` Missing ${ this . data . agent _count - this . available _agents . length } bot(s). ` ) ;
2024-12-13 10:42:41 -06:00
this . agent . killAll ( ) ;
2024-12-10 15:39:57 -08:00
}
}
2024-12-12 18:53:24 -08:00
2025-02-19 18:25:59 -08:00
if ( this . data . type === 'construction' ) {
//Ensures construction is cleaned out first. -> relies on cheats which are turned off?
if ( this . blueprint ) {
const result = this . blueprint . autoDelete ( ) ;
const commands = result . commands ;
const nearbyPosition = result . nearbyPosition ;
2025-03-10 23:08:35 -07:00
console . log ( "nearby position" , nearbyPosition ) ;
2025-03-22 17:13:14 -05:00
bot . chat ( ` /tp @a ${ nearbyPosition . x } ${ nearbyPosition . y } ${ nearbyPosition . z } ` ) ;
2025-02-19 18:25:59 -08:00
for ( const command of commands ) {
bot . chat ( command ) ;
}
}
else {
console . log ( 'no construction blueprint?' )
}
}
2025-01-26 11:26:11 -08:00
}
2025-02-23 02:50:11 -08:00
}