mindcraft/src/agent/npc/controller.js
2024-11-05 16:28:12 -08:00

261 lines
No EOL
10 KiB
JavaScript

import { readdirSync, readFileSync } from 'fs';
import { NPCData } from './data.js';
import { ItemGoal } from './item_goal.js';
import { BuildGoal } from './build_goal.js';
import { itemSatisfied, rotateXZ } from './utils.js';
import * as skills from '../library/skills.js';
import * as world from '../library/world.js';
import * as mc from '../../utils/mcdata.js';
export class NPCContoller {
constructor(agent) {
this.agent = agent;
this.data = NPCData.fromObject(agent.prompter.profile.npc);
this.temp_goals = [];
this.item_goal = new ItemGoal(agent, this.data);
this.build_goal = new BuildGoal(agent);
this.constructions = {};
this.last_goals = {};
}
getBuiltPositions() {
let positions = [];
for (let name in this.data.built) {
let position = this.data.built[name].position;
let offset = this.constructions[name].offset;
let sizex = this.constructions[name].blocks[0][0].length;
let sizez = this.constructions[name].blocks[0].length;
let sizey = this.constructions[name].blocks.length;
for (let y = offset; y < sizey+offset; y++) {
for (let z = 0; z < sizez; z++) {
for (let x = 0; x < sizex; x++) {
positions.push({x: position.x + x, y: position.y + y, z: position.z + z});
}
}
}
}
return positions;
}
init() {
try {
for (let file of readdirSync('src/agent/npc/construction')) {
if (file.endsWith('.json')) {
this.constructions[file.slice(0, -5)] = JSON.parse(readFileSync('src/agent/npc/construction/' + file, 'utf8'));
}
}
} catch (e) {
console.log('Error reading construction file');
}
for (let name in this.constructions) {
let sizez = this.constructions[name].blocks[0].length;
let sizex = this.constructions[name].blocks[0][0].length;
let max_size = Math.max(sizex, sizez);
for (let y = 0; y < this.constructions[name].blocks.length; y++) {
for (let z = 0; z < max_size; z++) {
if (z >= this.constructions[name].blocks[y].length)
this.constructions[name].blocks[y].push([]);
for (let x = 0; x < max_size; x++) {
if (x >= this.constructions[name].blocks[y][z].length)
this.constructions[name].blocks[y][z].push('');
}
}
}
}
this.agent.bot.on('idle', async () => {
if (this.data.goals.length === 0 && !this.data.curr_goal) return;
// Wait a while for inputs before acting independently
await new Promise((resolve) => setTimeout(resolve, 5000));
if (!this.agent.isIdle()) return;
// Persue goal
if (!this.agent.actions.resume_func) {
this.executeNext();
this.agent.history.save();
}
});
}
async setGoal(name=null, quantity=1) {
this.data.curr_goal = null;
this.last_goals = {};
if (name) {
this.data.curr_goal = {name: name, quantity: quantity};
return;
}
if (!this.data.do_set_goal) return;
let past_goals = {...this.last_goals};
for (let goal in this.data.goals) {
if (past_goals[goal.name] === undefined) past_goals[goal.name] = true;
}
let res = await this.agent.prompter.promptGoalSetting(this.agent.history.getHistory(), past_goals);
if (res) {
this.data.curr_goal = res;
console.log('Set new goal: ', res.name, ' x', res.quantity);
} else {
console.log('Error setting new goal.');
}
}
async executeNext() {
if (!this.agent.isIdle()) return;
await this.agent.actions.runAction('npc:moveAway', async () => {
await skills.moveAway(this.agent.bot, 2);
});
if (!this.data.do_routine || this.agent.bot.time.timeOfDay < 13000) {
// Exit any buildings
let building = this.currentBuilding();
if (building == this.data.home) {
let door_pos = this.getBuildingDoor(building);
if (door_pos) {
await this.agent.actions.runAction('npc:exitBuilding', async () => {
await skills.useDoor(this.agent.bot, door_pos);
await skills.moveAway(this.agent.bot, 2); // If the bot is too close to the building it will try to enter again
});
}
}
// Work towards goals
await this.executeGoal();
} else {
// Reset goal at the end of the day
this.data.curr_goal = null;
// Return to home
let building = this.currentBuilding();
if (this.data.home !== null && (building === null || building != this.data.home)) {
let door_pos = this.getBuildingDoor(this.data.home);
await this.agent.actions.runAction('npc:returnHome', async () => {
await skills.useDoor(this.agent.bot, door_pos);
});
}
// Go to bed
await this.agent.actions.runAction('npc:bed', async () => {
await skills.goToBed(this.agent.bot);
});
}
if (this.agent.isIdle())
this.agent.bot.emit('idle');
}
async executeGoal() {
// If we need more blocks to complete a building, get those first
let goals = this.temp_goals.concat(this.data.goals);
if (this.data.curr_goal)
goals = goals.concat([this.data.curr_goal])
this.temp_goals = [];
let acted = false;
for (let goal of goals) {
// Obtain goal item or block
if (this.constructions[goal.name] === undefined) {
if (!itemSatisfied(this.agent.bot, goal.name, goal.quantity)) {
let res = await this.item_goal.executeNext(goal.name, goal.quantity);
this.last_goals[goal.name] = res;
acted = true;
break;
}
}
// Build construction goal
else {
let res = null;
if (this.data.built.hasOwnProperty(goal.name)) {
res = await this.build_goal.executeNext(
this.constructions[goal.name],
this.data.built[goal.name].position,
this.data.built[goal.name].orientation
);
} else {
res = await this.build_goal.executeNext(this.constructions[goal.name]);
this.data.built[goal.name] = {
name: goal.name,
position: res.position,
orientation: res.orientation
};
}
if (Object.keys(res.missing).length === 0) {
this.data.home = goal.name;
}
for (let block_name in res.missing) {
this.temp_goals.push({
name: block_name,
quantity: res.missing[block_name]
})
}
if (res.acted) {
acted = true;
this.last_goals[goal.name] = Object.keys(res.missing).length === 0;
break;
}
}
}
if (!acted && this.data.do_set_goal)
await this.setGoal();
}
currentBuilding() {
let bot_pos = this.agent.bot.entity.position;
for (let name in this.data.built) {
let pos = this.data.built[name].position;
let offset = this.constructions[name].offset;
let sizex = this.constructions[name].blocks[0][0].length;
let sizez = this.constructions[name].blocks[0].length;
let sizey = this.constructions[name].blocks.length;
if (this.data.built[name].orientation % 2 === 1) [sizex, sizez] = [sizez, sizex];
if (bot_pos.x >= pos.x && bot_pos.x < pos.x + sizex &&
bot_pos.y >= pos.y + offset && bot_pos.y < pos.y + sizey + offset &&
bot_pos.z >= pos.z && bot_pos.z < pos.z + sizez) {
return name;
}
}
return null;
}
getBuildingDoor(name) {
if (name === null || this.data.built[name] === undefined) return null;
let door_x = null;
let door_z = null;
let door_y = null;
for (let y = 0; y < this.constructions[name].blocks.length; y++) {
for (let z = 0; z < this.constructions[name].blocks[y].length; z++) {
for (let x = 0; x < this.constructions[name].blocks[y][z].length; x++) {
if (this.constructions[name].blocks[y][z][x] !== null &&
this.constructions[name].blocks[y][z][x].includes('door')) {
door_x = x;
door_z = z;
door_y = y;
break;
}
}
if (door_x !== null) break;
}
if (door_x !== null) break;
}
if (door_x === null) return null;
let sizex = this.constructions[name].blocks[0][0].length;
let sizez = this.constructions[name].blocks[0].length;
let orientation = 4 - this.data.built[name].orientation; // this conversion is opposite
if (orientation == 4) orientation = 0;
[door_x, door_z] = rotateXZ(door_x, door_z, orientation, sizex, sizez);
door_y += this.constructions[name].offset;
return {
x: this.data.built[name].position.x + door_x,
y: this.data.built[name].position.y + door_y,
z: this.data.built[name].position.z + door_z
};
}
}