2024-12-10 15:39:57 -08:00
import { readFileSync } from 'fs' ;
import { executeCommand } from './commands/index.js' ;
import { getPosition } from './library/world.js'
import settings from '../../settings.js' ;
2024-12-27 20:42:34 -06:00
import { Vec3 } from 'vec3' ;
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
2024-12-26 18:12:11 -06:00
export class CraftTaskValidator {
2024-12-10 15:39:57 -08:00
constructor ( data , agent ) {
this . target = data . target ;
this . number _of _target = data . number _of _target ;
this . agent = agent ;
}
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 ;
}
}
}
2024-12-26 18:12:11 -06:00
export class ConstructionTaskValidator {
constructor ( data , agent ) {
this . blueprint = new Blueprint ( data . blueprint ) ;
this . agent = agent ;
}
validate ( ) {
try {
//todo: somehow make this more of a percentage or something
2024-12-29 10:56:20 -06:00
console . log ( 'Validating task...' ) ;
2024-12-26 18:12:11 -06:00
let valid = false ;
2024-12-27 20:42:34 -06:00
let score = 0 ;
2024-12-29 13:38:23 -06:00
let result = this . blueprint . check ( this . agent . bot ) ;
if ( result . mismatches . length === 0 ) {
valid = true ;
console . log ( 'Task is complete' ) ;
}
let total _blocks = result . mismatches . length + result . matches . length ;
score = ( result . matches . length / total _blocks ) * 100 ;
console . log ( ` Task is ${ score } % complete ` ) ;
2024-12-26 18:12:11 -06:00
return valid ;
} catch ( error ) {
console . error ( 'Error validating task:' , error ) ;
return false ;
}
}
}
2024-12-29 10:56:20 -06:00
export function checkLevelBlueprint ( agent , levelNum ) {
const blueprint = agent . task . blueprint ;
2024-12-26 18:12:11 -06:00
const bot = agent . bot ;
2024-12-29 10:56:20 -06:00
const result = blueprint . checkLevel ( bot , levelNum ) ;
if ( result . mismatches . length === 0 ) {
return ` Level ${ levelNum } is correct ` ;
} else {
let explanation = blueprint . explainLevelDifference ( bot , levelNum ) ;
return explanation ;
}
2024-12-26 18:12:11 -06:00
}
2024-12-29 10:56:20 -06:00
export function checkBlueprint ( agent ) {
console . log ( 'Checking blueprint...' ) ;
console . log ( agent ) ;
const blueprint = agent . task . blueprint ;
2024-12-26 18:12:11 -06:00
const bot = agent . bot ;
2024-12-29 10:56:20 -06:00
const result = blueprint . check ( bot ) ;
if ( result . mismatches . length === 0 ) {
return "Blueprint is correct" ;
} else {
let explanation = blueprint . explainBlueprintDifference ( bot ) ;
return explanation ;
}
2024-12-26 18:12:11 -06:00
}
2024-12-23 10:31:19 -06:00
export class Blueprint {
constructor ( blueprint ) {
2024-12-26 18:12:11 -06:00
this . data = blueprint ;
2024-12-23 10:31:19 -06:00
}
explain ( ) {
var explanation = "" ;
2024-12-26 18:12:11 -06:00
for ( let item of this . data . levels ) {
2024-12-23 10:31:19 -06:00
var coordinates = item . coordinates ;
explanation += ` Level ${ item . level } : ` ;
explanation += ` Start at coordinates X: ${ coordinates [ 0 ] } , Y: ${ coordinates [ 1 ] } , Z: ${ coordinates [ 2 ] } ` ;
let placement _string = this . _getPlacementString ( item . placement ) ;
explanation += ` \n ${ placement _string } \n ` ;
}
return explanation ;
}
_getPlacementString ( placement ) {
var placement _string = "[\n" ;
for ( let row of placement ) {
placement _string += "[" ;
for ( let i = 0 ; i < row . length - 1 ; i ++ ) {
let item = row [ i ] ;
placement _string += ` ${ item } , ` ;
}
let final _item = row [ row . length - 1 ] ;
placement _string += ` ${ final _item } ], \n ` ;
}
placement _string += "]" ;
return placement _string ;
}
2024-12-26 18:12:11 -06:00
explainLevel ( levelNum ) {
const levelData = this . data . levels [ levelNum ] ;
var explanation = ` Level ${ levelData . level } ` ;
2024-12-27 20:42:34 -06:00
explanation += ` starting at coordinates X: ${ levelData . coordinates [ 0 ] } , Y: ${ levelData . coordinates [ 1 ] } , Z: ${ levelData . coordinates [ 2 ] } ` ;
2024-12-26 18:12:11 -06:00
let placement _string = this . _getPlacementString ( levelData . placement ) ;
explanation += ` \n ${ placement _string } \n ` ;
2024-12-27 20:42:34 -06:00
return explanation ;
2024-12-26 18:12:11 -06:00
}
2024-12-29 10:56:20 -06:00
explainBlueprintDifference ( bot ) {
2024-12-26 18:12:11 -06:00
var explanation = "" ;
2024-12-27 20:42:34 -06:00
const levels = this . data . levels ;
for ( let i = 0 ; i < levels . length ; i ++ ) {
2024-12-29 10:56:20 -06:00
let level _explanation = this . explainLevelDifference ( bot , i ) ;
2024-12-26 18:12:11 -06:00
explanation += level _explanation + "\n" ;
}
return explanation ;
}
2024-12-29 10:56:20 -06:00
explainLevelDifference ( bot , levelNum ) {
const results = this . checkLevel ( bot , levelNum ) ;
2024-12-26 18:12:11 -06:00
const mismatches = results . mismatches ;
2024-12-27 20:42:34 -06:00
const levelData = this . data . levels [ levelNum ] ;
2024-12-26 18:12:11 -06:00
if ( mismatches . length === 0 ) {
2024-12-29 13:38:23 -06:00
return ` Level ${ levelData . level } is complete ` ;
2024-12-26 18:12:11 -06:00
}
var explanation = ` Level ${ levelData . level } ` ;
2024-12-27 20:42:34 -06:00
// explanation += `at coordinates X: ${levelData.coordinates[0]}, Y: ${levelData.coordinates[1]}, Z: ${levelData.coordinates[2]}`;
2024-12-29 13:38:23 -06:00
explanation += " requires the following fixes:\n" ;
2024-12-26 18:12:11 -06:00
for ( let item of mismatches ) {
2024-12-29 13:38:23 -06:00
if ( item . actual === 'air' ) {
explanation += ` Place ${ item . expected } at coordinates X: ${ item . coordinates [ 0 ] } , Y: ${ item . coordinates [ 1 ] } , Z: ${ item . coordinates [ 2 ] } \n ` ;
} else if ( item . expected === 'air' ) {
explanation += ` Remove the ${ item . actual } at coordinates X: ${ item . coordinates [ 0 ] } , Y: ${ item . coordinates [ 1 ] } , Z: ${ item . coordinates [ 2 ] } \n ` ;
} else {
explanation += ` Replace the ${ item . actual } with a ${ item . expected } at coordinates X: ${ item . coordinates [ 0 ] } , Y: ${ item . coordinates [ 1 ] } , Z: ${ item . coordinates [ 2 ] } \n ` ;
}
2024-12-26 18:12:11 -06:00
}
return explanation ;
}
2024-12-29 10:56:20 -06:00
check ( bot ) {
2024-12-29 13:38:23 -06:00
if ( ! bot || typeof bot !== 'object' || ! bot . hasOwnProperty ( 'blockAt' ) ) {
throw new Error ( 'Invalid bot object. Expected a mineflayer bot.' ) ;
}
2024-12-26 18:12:11 -06:00
const levels = this . data . levels ;
const mismatches = [ ] ;
const matches = [ ] ;
for ( let i = 0 ; i < levels . length ; i ++ ) {
2024-12-29 10:56:20 -06:00
const result = this . checkLevel ( bot , i ) ;
2024-12-26 18:12:11 -06:00
mismatches . push ( ... result . mismatches ) ;
matches . push ( ... result . matches ) ;
}
return {
"mismatches" : mismatches ,
"matches" : matches
} ;
}
2024-12-29 10:56:20 -06:00
checkLevel ( bot , levelNum ) {
2024-12-27 20:42:34 -06:00
const levelData = this . data . levels [ levelNum ] ;
2024-12-26 18:12:11 -06:00
const startCoords = levelData . coordinates ;
const placement = levelData . placement ;
const mismatches = [ ] ;
const matches = [ ] ;
for ( let zOffset = 0 ; zOffset < placement . length ; zOffset ++ ) {
const row = placement [ zOffset ] ;
for ( let xOffset = 0 ; xOffset < row . length ; xOffset ++ ) {
const blockName = row [ xOffset ] ;
const x = startCoords [ 0 ] + xOffset ;
const y = startCoords [ 1 ] ;
const z = startCoords [ 2 ] + zOffset ;
try {
2024-12-29 10:56:20 -06:00
const blockAtLocation = bot . blockAt ( new Vec3 ( x , y , z ) ) ;
2024-12-26 18:12:11 -06:00
if ( ! blockAtLocation || blockAtLocation . name !== blockName ) {
mismatches . push ( {
level : levelData . level ,
coordinates : [ x , y , z ] ,
expected : blockName ,
actual : blockAtLocation ? bot . registry . blocks [ blockAtLocation . type ] . name : 'air' // Assuming air if no block
} ) ;
} else {
matches . push ( {
level : levelData . level ,
coordinates : [ x , y , z ] ,
expected : blockName ,
actual : blockAtLocation ? bot . registry . blocks [ blockAtLocation . type ] . name : 'air' // Assuming air if no block
} ) ;
}
} catch ( err ) {
console . error ( ` Error getting block at ( ${ x } , ${ y } , ${ z } ): ` , err ) ;
return false ; // Stop checking if there's an issue getting blocks
}
}
}
return {
"mismatches" : mismatches ,
"matches" : matches
} ;
}
2024-12-23 10:31:19 -06:00
}
2024-12-10 15:39:57 -08:00
export class Task {
constructor ( agent , task _path , task _id ) {
this . agent = agent ;
this . data = null ;
this . taskTimeout = 300 ;
this . taskStartTime = Date . now ( ) ;
this . validator = null ;
this . blocked _actions = [ ] ;
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 ( ) ;
2024-12-26 18:12:11 -06:00
if ( this . task _type === 'construction' ) {
this . validator = new ConstructionTaskValidator ( this . data , this . agent ) ;
} else if ( this . task _type === 'techtree' ) {
this . validator = new CraftTaskValidator ( this . data , this . agent ) ;
}
2024-12-10 15:39:57 -08:00
this . blocked _actions = this . data . blocked _actions || [ ] ;
2024-12-23 11:34:56 -06:00
if ( this . goal )
2024-12-10 15:39:57 -08:00
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' ) ;
2024-12-26 18:12:11 -06:00
console . log ( 'Task loaded:' , this . data ) ;
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 ) ;
const task = tasks [ task _id ] ;
2024-12-26 18:12:11 -06:00
console . log ( 'Loaded task:' , task ) ;
2024-12-10 15:39:57 -08:00
if ( ! task ) {
throw new Error ( ` Task ${ task _id } not found ` ) ;
}
2024-12-12 12:33:00 -08:00
if ( ( ! task . agent _count || task . agent _count <= 1 ) && this . agent . count _id > 0 ) {
task = null ;
}
2024-12-10 15:39:57 -08:00
return task ;
} catch ( error ) {
console . error ( 'Error loading task:' , error ) ;
process . exit ( 1 ) ;
}
}
isDone ( ) {
if ( this . validator && this . validator . validate ( ) )
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 ) {
console . log ( 'Task timeout reached. Task unsuccessful.' ) ;
return { "message" : 'Task timeout reached' , "code" : 4 } ;
}
}
return false ;
}
async initBotTask ( ) {
if ( this . data === null )
return ;
let bot = this . agent . bot ;
let name = this . agent . name ;
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 ) ) ;
2024-12-12 12:33:00 -08:00
if ( this . data . agent _count > 1 ) {
2024-12-10 15:39:57 -08:00
var 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 ) ;
var 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 ] ;
2024-12-12 12:33:00 -08:00
if ( ! 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 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 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 ) ) ;
}
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 ) ) ;
if ( available _agents . length < this . data . agent _count ) {
console . log ( ` Missing ${ this . data . agent _count - available _agents . length } bot(s). ` ) ;
this . agent . cleanKill ( 'Not all required players/bots are present in the world. Exiting.' , 4 ) ;
}
}
2024-12-12 18:53:24 -08:00
2024-12-23 11:34:56 -06:00
if ( this . goal ) {
console . log ( 'Setting goal:' , this . goal ) ;
await executeCommand ( this . agent , ` !goal(" ${ this . goal } ") ` ) ;
2024-12-10 15:39:57 -08:00
}
2024-12-26 18:12:11 -06:00
if ( this . conversation && this . agent . count _id === 0 ) {
2024-12-10 15:39:57 -08:00
let other _name = available _agents . filter ( n => n !== name ) [ 0 ] ;
2024-12-26 18:12:11 -06:00
await executeCommand ( this . agent , ` !startConversation(" ${ other _name } ", " ${ this . conversation } ") ` ) ;
2024-12-10 15:39:57 -08:00
}
}
}
2024-12-23 10:31:19 -06:00
export function giveBlueprint ( agent , blueprint ) {
let bot = agent . bot ;
let name = agent . name ;
let blueprint _name = blueprint . name ;
let blueprint _count = blueprint . count ;
bot . chat ( ` /clear ${ name } ` ) ;
console . log ( ` Cleared ${ name } 's inventory. ` ) ;
bot . chat ( ` /give ${ name } ${ blueprint _name } ${ blueprint _count } ` ) ;
console . log ( ` Gave ${ name } ${ blueprint _count } ${ blueprint _name } (s). ` ) ;
}