mirror of
https://github.com/kolbytn/mindcraft.git
synced 2025-06-08 10:15:55 +02:00
init build goals
This commit is contained in:
parent
e0c43d5c90
commit
ca7f08d345
8 changed files with 239 additions and 69 deletions
src/agent
|
@ -174,7 +174,7 @@ export class Agent {
|
||||||
console.log('Agent died: ', message);
|
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.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.on('idle', () => {
|
||||||
this.bot.modes.unPauseAll();
|
this.bot.modes.unPauseAll();
|
||||||
this.coder.executeResume();
|
this.coder.executeResume();
|
||||||
|
|
|
@ -25,11 +25,12 @@ export function getNearestFreeSpace(bot, size=1, distance=8) {
|
||||||
for (let z = 0; z < size; z++) {
|
for (let z = 0; z < size; z++) {
|
||||||
let top = bot.blockAt(empty_pos[i].offset(x, 0, z));
|
let top = bot.blockAt(empty_pos[i].offset(x, 0, z));
|
||||||
let bottom = bot.blockAt(empty_pos[i].offset(x, -1, 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;
|
empty = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!empty) break;
|
||||||
}
|
}
|
||||||
if (empty) {
|
if (empty) {
|
||||||
return empty_pos[i];
|
return empty_pos[i];
|
||||||
|
|
80
src/agent/npc/build_goal.js
Normal file
80
src/agent/npc/build_goal.js
Normal 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};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
43
src/agent/npc/construction/shelter.json
Normal file
43
src/agent/npc/construction/shelter.json
Normal 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"]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,17 +1,32 @@
|
||||||
|
import { readdirSync, readFileSync } from 'fs';
|
||||||
import { NPCData } from './data.js';
|
import { NPCData } from './data.js';
|
||||||
import { ItemGoal } from './item_goal.js';
|
import { ItemGoal } from './item_goal.js';
|
||||||
|
import { BuildGoal } from './build_goal.js';
|
||||||
|
import { itemSatisfied } from './utils.js';
|
||||||
|
|
||||||
|
|
||||||
export class NPCContoller {
|
export class NPCContoller {
|
||||||
constructor(agent) {
|
constructor(agent) {
|
||||||
this.agent = agent;
|
this.agent = agent;
|
||||||
this.data = NPCData.fromObject(agent.prompter.prompts.npc);
|
this.data = NPCData.fromObject(agent.prompter.prompts.npc);
|
||||||
|
this.temp_goals = [];
|
||||||
this.item_goal = new ItemGoal(agent);
|
this.item_goal = new ItemGoal(agent);
|
||||||
|
this.build_goal = new BuildGoal(agent);
|
||||||
|
this.constructions = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
if (this.data === null) return;
|
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 () => {
|
this.agent.bot.on('idle', async () => {
|
||||||
// Wait a while for inputs before acting independently
|
// Wait a while for inputs before acting independently
|
||||||
|
@ -19,8 +34,40 @@ export class NPCContoller {
|
||||||
if (!this.agent.isIdle()) return;
|
if (!this.agent.isIdle()) return;
|
||||||
|
|
||||||
// Persue goal
|
// Persue goal
|
||||||
if (this.agent.coder.resume_func === null)
|
if (!this.agent.coder.resume_func)
|
||||||
this.item_goal.executeNext();
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,18 +1,23 @@
|
||||||
export class NPCData {
|
export class NPCData {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.goals = [];
|
this.goals = [];
|
||||||
|
this.built = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
toObject() {
|
toObject() {
|
||||||
return {
|
return {
|
||||||
goals: this.goals
|
goals: this.goals,
|
||||||
|
built: this.built
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromObject(obj) {
|
static fromObject(obj) {
|
||||||
if (!obj) return null;
|
if (!obj) return null;
|
||||||
let npc = new NPCData();
|
let npc = new NPCData();
|
||||||
|
if (obj.goals)
|
||||||
npc.goals = obj.goals;
|
npc.goals = obj.goals;
|
||||||
|
if (obj.built)
|
||||||
|
npc.built = obj.built;
|
||||||
return npc;
|
return npc;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import * as skills from '../library/skills.js';
|
import * as skills from '../library/skills.js';
|
||||||
import * as world from '../library/world.js';
|
import * as world from '../library/world.js';
|
||||||
import * as mc from '../../utils/mcdata.js';
|
import * as mc from '../../utils/mcdata.js';
|
||||||
|
import { itemSatisfied } from './utils.js';
|
||||||
|
|
||||||
|
|
||||||
const blacklist = [
|
const blacklist = [
|
||||||
|
@ -103,36 +104,9 @@ class ItemNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
isDone(quantity=1) {
|
isDone(quantity=1) {
|
||||||
let qualifying = [this.name];
|
if (this.manager.goal.name === 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;
|
return false;
|
||||||
|
return itemSatisfied(this.manager.agent.bot, this.name, quantity);
|
||||||
}
|
}
|
||||||
|
|
||||||
getDepth(q=1) {
|
getDepth(q=1) {
|
||||||
|
@ -304,37 +278,24 @@ class ItemWrapper {
|
||||||
|
|
||||||
|
|
||||||
export class ItemGoal {
|
export class ItemGoal {
|
||||||
constructor(agent, timeout=-1) {
|
constructor(agent) {
|
||||||
this.agent = agent;
|
this.agent = agent;
|
||||||
this.timeout = timeout;
|
this.goal = null;
|
||||||
this.goals = [];
|
|
||||||
this.nodes = {};
|
this.nodes = {};
|
||||||
this.failed = [];
|
this.failed = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
setGoals(goals) {
|
async executeNext(item_name) {
|
||||||
this.goals = []
|
if (this.nodes[item_name] === undefined)
|
||||||
for (let goal of goals) {
|
this.nodes[item_name] = new ItemWrapper(this, null, item_name);
|
||||||
this.goals.push({name: goal, quantity: 1})
|
this.goal = this.nodes[item_name];
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
// Get next goal to execute
|
// 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 next = next_info.node;
|
||||||
let quantity = next_info.quantity;
|
let quantity = next_info.quantity;
|
||||||
|
|
||||||
|
@ -346,11 +307,9 @@ export class ItemGoal {
|
||||||
// If the bot has failed to obtain the block before, explore
|
// If the bot has failed to obtain the block before, explore
|
||||||
if (this.failed.includes(next.name)) {
|
if (this.failed.includes(next.name)) {
|
||||||
this.failed = this.failed.filter((item) => item !== next.name);
|
this.failed = this.failed.filter((item) => item !== next.name);
|
||||||
this.agent.coder.interruptible = true;
|
|
||||||
await this.agent.coder.execute(async () => {
|
await this.agent.coder.execute(async () => {
|
||||||
await skills.moveAway(this.agent.bot, 8);
|
await skills.moveAway(this.agent.bot, 8);
|
||||||
}, this.timeout);
|
});
|
||||||
this.agent.coder.interruptible = false;
|
|
||||||
} else {
|
} else {
|
||||||
this.failed.push(next.name);
|
this.failed.push(next.name);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||||
|
@ -365,18 +324,16 @@ export class ItemGoal {
|
||||||
|
|
||||||
// Execute the next goal
|
// Execute the next goal
|
||||||
let init_quantity = world.getInventoryCounts(this.agent.bot)[next.name] || 0;
|
let init_quantity = world.getInventoryCounts(this.agent.bot)[next.name] || 0;
|
||||||
this.agent.coder.interruptible = true;
|
|
||||||
await this.agent.coder.execute(async () => {
|
await this.agent.coder.execute(async () => {
|
||||||
await next.execute(quantity);
|
await next.execute(quantity);
|
||||||
}, this.timeout);
|
});
|
||||||
this.agent.coder.interruptible = false;
|
|
||||||
let final_quantity = world.getInventoryCounts(this.agent.bot)[next.name] || 0;
|
let final_quantity = world.getInventoryCounts(this.agent.bot)[next.name] || 0;
|
||||||
|
|
||||||
// Log the result of the goal attempt
|
// Log the result of the goal attempt
|
||||||
if (final_quantity > init_quantity) {
|
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 {
|
} 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
37
src/agent/npc/utils.js
Normal 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;
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue