init build goals

This commit is contained in:
Kolby Nottingham 2024-03-05 16:40:06 -08:00
parent e0c43d5c90
commit ca7f08d345
8 changed files with 239 additions and 69 deletions

View file

@ -174,7 +174,7 @@ export class Agent {
console.log('Agent died: ', message);
this.handleMessage('system', `You died with the final message: '${message}'. Previous actions were stopped and you have respawned. Notify the user and perform any necessary actions.`);
}
})
});
this.bot.on('idle', () => {
this.bot.modes.unPauseAll();
this.coder.executeResume();

View file

@ -25,11 +25,12 @@ export function getNearestFreeSpace(bot, size=1, distance=8) {
for (let z = 0; z < size; z++) {
let top = bot.blockAt(empty_pos[i].offset(x, 0, z));
let bottom = bot.blockAt(empty_pos[i].offset(x, -1, z));
if (!top || !top.name == 'air' || !bottom || !bottom.diggable) {
if (!top || !top.name == 'air' || !bottom || bottom.drops.length == 0 || !bottom.diggable) {
empty = false;
break;
}
}
if (!empty) break;
}
if (empty) {
return empty_pos[i];

View file

@ -0,0 +1,80 @@
import { Vec3 } from 'vec3';
import * as skills from '../library/skills.js';
import * as world from '../library/world.js';
import * as mc from '../../utils/mcdata.js';
export class BuildGoal {
constructor(agent) {
this.agent = agent;
}
rotateXZ(x, z, orientation, sizex, sizez) {
if (orientation === 0) return [x, z];
if (orientation === 1) return [z, sizex-x-1];
if (orientation === 2) return [sizex-x-1, sizez-z-1];
if (orientation === 3) return [sizez-z-1, x];
}
async executeNext(goal, position=null, orientation=null) {
let sizex = goal.blocks[0][0].length;
let sizez = goal.blocks[0].length;
let sizey = goal.blocks.length;
if (!position) {
for (let x = 0; x < sizex - 1; x++) {
position = world.getNearestFreeSpace(this.agent.bot, sizex - x, 16);
if (position) break;
}
}
if (orientation === null) {
orientation = Math.floor(Math.random() * 4);
}
let inventory = world.getInventoryCounts(this.agent.bot);
let missing = [];
let acted = false;
for (let y = goal.offset; y < sizey+goal.offset; y++) {
for (let z = 0; z < sizez; z++) {
for (let x = 0; x < sizex; x++) {
let [rx, rz] = this.rotateXZ(x, z, orientation, sizex, sizez);
let ry = y - goal.offset;
let block_name = goal.blocks[ry][rz][rx];
if (block_name === null || block_name === '') continue;
let world_pos = new Vec3(position.x + x, position.y + y, position.z + z);
let current_block = this.agent.bot.blockAt(world_pos);
let res = null;
if (current_block.name !== block_name) {
acted = true;
if (!this.agent.isIdle())
return {missing: missing, acted: acted, position: position, orientation: orientation};
res = await this.agent.coder.execute(async () => {
await skills.breakBlockAt(this.agent.bot, world_pos.x, world_pos.y, world_pos.z);
});
if (res.interrupted)
return {missing: missing, acted: acted, position: position, orientation: orientation};
if (inventory[block_name] > 0) {
if (!this.agent.isIdle())
return {missing: missing, acted: acted, position: position, orientation: orientation};
await this.agent.coder.execute(async () => {
await skills.placeBlock(this.agent.bot, block_name, world_pos.x, world_pos.y, world_pos.z);
});
if (res.interrupted)
return {missing: missing, acted: acted, position: position, orientation: orientation};
} else {
missing.push(block_name);
}
}
}
}
}
return {missing: missing, acted: acted, position: position, orientation: orientation};
}
}

View file

@ -0,0 +1,43 @@
{
"name": "shelter",
"offset": -1,
"placement": [
[1, 1, 1],
[1, 2, 1],
[1, 3, 1],
[2, 3, 1],
[3, 1, 1],
[3, 2, 1],
[3, 3, 1]
],
"blocks": [
[
["dirt", "dirt", "dirt", "dirt", "dirt"],
["dirt", "dirt", "dirt", "dirt", "dirt"],
["dirt", "dirt", "dirt", "dirt", "dirt"],
["dirt", "dirt", "dirt", "dirt", "dirt"],
["dirt", "dirt", "dirt", "dirt", "dirt"]
],
[
["dirt", "dirt", "dirt", "dirt", "dirt"],
["dirt", "air", "air", "air", "dirt"],
["dirt", "air", "air", "air", "dirt"],
["dirt", "air", "air", "air", "dirt"],
["dirt", "dirt", "dirt", "dirt", "dirt"]
],
[
["dirt", "dirt", "dirt", "dirt", "dirt"],
["dirt", "air", "air", "air", "dirt"],
["dirt", "air", "air", "air", "dirt"],
["dirt", "air", "air", "air", "dirt"],
["dirt", "dirt", "dirt", "dirt", "dirt"]
],
[
["air", "air", "air", "air", "air"],
["air", "dirt", "dirt", "dirt", "air"],
["air", "dirt", "dirt", "dirt", "air"],
["air", "dirt", "dirt", "dirt", "air"],
["air", "air", "air", "air", "air"]
]
]
}

View file

@ -1,17 +1,32 @@
import { readdirSync, readFileSync } from 'fs';
import { NPCData } from './data.js';
import { ItemGoal } from './item_goal.js';
import { BuildGoal } from './build_goal.js';
import { itemSatisfied } from './utils.js';
export class NPCContoller {
constructor(agent) {
this.agent = agent;
this.data = NPCData.fromObject(agent.prompter.prompts.npc);
this.temp_goals = [];
this.item_goal = new ItemGoal(agent);
this.build_goal = new BuildGoal(agent);
this.constructions = {};
}
init() {
if (this.data === null) return;
this.item_goal.setGoals(this.data.goals);
for (let file of readdirSync('src/agent/npc/construction')) {
if (file.endsWith('.json')) {
try {
this.constructions[file.slice(0, -5)] = JSON.parse(readFileSync('src/agent/npc/construction/' + file, 'utf8'));
} catch (e) {
console.log('Error reading construction file: ', file);
}
}
}
this.agent.bot.on('idle', async () => {
// Wait a while for inputs before acting independently
@ -19,8 +34,40 @@ export class NPCContoller {
if (!this.agent.isIdle()) return;
// Persue goal
if (this.agent.coder.resume_func === null)
this.item_goal.executeNext();
if (!this.agent.coder.resume_func)
this.executeNext();
});
}
async executeNext() {
let goals = this.data.goals;
if (this.temp_goals !== null && this.temp_goals.length > 0) {
goals = this.temp_goals.concat(goals);
}
for (let goal of goals) {
if (this.constructions[goal] === undefined && !itemSatisfied(this.agent.bot, goal)) {
await this.item_goal.executeNext(goal);
break;
} else if (this.constructions[goal]) {
let res = null;
if (this.data.built.hasOwnProperty(goal)) {
res = await this.build_goal.executeNext(
this.constructions[goal],
this.data.built[goal].position,
this.data.built[goal].orientation
);
} else {
res = await this.build_goal.executeNext(this.constructions[goal]);
this.data.built[goal] = {
name: goal,
position: res.position,
orientation: res.orientation
};
}
this.temp_goals = res.missing;
if (res.acted) break;
}
}
}
}

View file

@ -1,18 +1,23 @@
export class NPCData {
constructor() {
this.goals = [];
this.built = {};
}
toObject() {
return {
goals: this.goals
goals: this.goals,
built: this.built
}
}
static fromObject(obj) {
if (!obj) return null;
let npc = new NPCData();
npc.goals = obj.goals;
if (obj.goals)
npc.goals = obj.goals;
if (obj.built)
npc.built = obj.built;
return npc;
}
}

View file

@ -1,6 +1,7 @@
import * as skills from '../library/skills.js';
import * as world from '../library/world.js';
import * as mc from '../../utils/mcdata.js';
import { itemSatisfied } from './utils.js';
const blacklist = [
@ -103,36 +104,9 @@ class ItemNode {
}
isDone(quantity=1) {
let qualifying = [this.name];
if (this.name.includes('pickaxe') ||
this.name.includes('axe') ||
this.name.includes('shovel') ||
this.name.includes('hoe') ||
this.name.includes('sword')) {
let material = this.name.split('_')[0];
let type = this.name.split('_')[1];
if (material === 'wooden') {
qualifying.push('stone_' + type);
qualifying.push('iron_' + type);
qualifying.push('gold_' + type);
qualifying.push('diamond_' + type);
} else if (material === 'stone') {
qualifying.push('iron_' + type);
qualifying.push('gold_' + type);
qualifying.push('diamond_' + type);
} else if (material === 'iron') {
qualifying.push('gold_' + type);
qualifying.push('diamond_' + type);
} else if (material === 'gold') {
qualifying.push('diamond_' + type);
}
}
for (let item of qualifying) {
if (world.getInventoryCounts(this.manager.agent.bot)[item] >= quantity) {
return true;
}
}
return false;
if (this.manager.goal.name === this.name)
return false;
return itemSatisfied(this.manager.agent.bot, this.name, quantity);
}
getDepth(q=1) {
@ -304,37 +278,24 @@ class ItemWrapper {
export class ItemGoal {
constructor(agent, timeout=-1) {
constructor(agent) {
this.agent = agent;
this.timeout = timeout;
this.goals = [];
this.goal = null;
this.nodes = {};
this.failed = [];
}
setGoals(goals) {
this.goals = []
for (let goal of goals) {
this.goals.push({name: goal, quantity: 1})
}
}
async executeNext() {
// Get goal by priority
let goal = null;
for (let g of this.goals) {
if (this.nodes[g.name] === undefined)
this.nodes[g.name] = new ItemWrapper(this, null, g.name);
if (!this.nodes[g.name].isDone(g.quantity)) {
goal = this.nodes[g.name];
break;
}
}
if (goal === null)
return;
async executeNext(item_name) {
if (this.nodes[item_name] === undefined)
this.nodes[item_name] = new ItemWrapper(this, null, item_name);
this.goal = this.nodes[item_name];
// Get next goal to execute
let next_info = goal.getNext();
let next_info = this.goal.getNext();
if (!next_info) {
console.log(`Invalid item goal ${this.goal.name}`);
return;
}
let next = next_info.node;
let quantity = next_info.quantity;
@ -346,11 +307,9 @@ export class ItemGoal {
// If the bot has failed to obtain the block before, explore
if (this.failed.includes(next.name)) {
this.failed = this.failed.filter((item) => item !== next.name);
this.agent.coder.interruptible = true;
await this.agent.coder.execute(async () => {
await skills.moveAway(this.agent.bot, 8);
}, this.timeout);
this.agent.coder.interruptible = false;
});
} else {
this.failed.push(next.name);
await new Promise((resolve) => setTimeout(resolve, 500));
@ -365,18 +324,16 @@ export class ItemGoal {
// Execute the next goal
let init_quantity = world.getInventoryCounts(this.agent.bot)[next.name] || 0;
this.agent.coder.interruptible = true;
await this.agent.coder.execute(async () => {
await next.execute(quantity);
}, this.timeout);
this.agent.coder.interruptible = false;
});
let final_quantity = world.getInventoryCounts(this.agent.bot)[next.name] || 0;
// Log the result of the goal attempt
if (final_quantity > init_quantity) {
console.log(`Successfully obtained ${next.name} for goal ${goal.name}`);
console.log(`Successfully obtained ${next.name} for goal ${this.goal.name}`);
} else {
console.log(`Failed to obtain ${next.name} for goal ${goal.name}`);
console.log(`Failed to obtain ${next.name} for goal ${this.goal.name}`);
}
}
}

37
src/agent/npc/utils.js Normal file
View file

@ -0,0 +1,37 @@
import * as skills from '../library/skills.js';
import * as world from '../library/world.js';
import * as mc from '../../utils/mcdata.js';
export function itemSatisfied(bot, item, quantity=1) {
let qualifying = [item];
if (item.includes('pickaxe') ||
item.includes('axe') ||
item.includes('shovel') ||
item.includes('hoe') ||
item.includes('sword')) {
let material = item.split('_')[0];
let type = item.split('_')[1];
if (material === 'wooden') {
qualifying.push('stone_' + type);
qualifying.push('iron_' + type);
qualifying.push('gold_' + type);
qualifying.push('diamond_' + type);
} else if (material === 'stone') {
qualifying.push('iron_' + type);
qualifying.push('gold_' + type);
qualifying.push('diamond_' + type);
} else if (material === 'iron') {
qualifying.push('gold_' + type);
qualifying.push('diamond_' + type);
} else if (material === 'gold') {
qualifying.push('diamond_' + type);
}
}
for (let item of qualifying) {
if (world.getInventoryCounts(bot)[item] >= quantity) {
return true;
}
}
return false;
}