mirror of
https://github.com/kolbytn/mindcraft.git
synced 2025-04-23 22:52:06 +02:00
init build goals
This commit is contained in:
parent
e0c43d5c90
commit
ca7f08d345
8 changed files with 239 additions and 69 deletions
|
@ -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();
|
||||
|
|
|
@ -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];
|
||||
|
|
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 { 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
if (obj.goals)
|
||||
npc.goals = obj.goals;
|
||||
if (obj.built)
|
||||
npc.built = obj.built;
|
||||
return npc;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
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
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