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-20 12:56:29 -07:00
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-23 02:50:11 -08:00
await this . teleportBots ( ) ;
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 ( ) ;
}
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
}
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 ,
This was done by MaxRobinson in one of the youtube videos .
* /
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 result = clearHouse(blueprint)
const commands = result . commands ;
const nearbyPosition = result . nearbyPosition ;
2025-03-10 23:08:35 -07:00
console . log ( "nearby position" , nearbyPosition ) ;
bot . chat ( ` /tp ${ this . name } ${ 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
}