mirror of
https://github.com/kolbytn/mindcraft.git
synced 2025-08-22 15:13:45 +02:00
261 lines
No EOL
10 KiB
JavaScript
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
|
|
};
|
|
}
|
|
} |