mirror of
https://github.com/kolbytn/mindcraft.git
synced 2025-04-21 21:52:07 +02:00
commit
65aa0ba35a
13 changed files with 1235 additions and 1 deletions
165
andy_npc.json
Normal file
165
andy_npc.json
Normal file
|
@ -0,0 +1,165 @@
|
|||
{
|
||||
"name": "andy_npc",
|
||||
|
||||
"model": "gpt-3.5-turbo",
|
||||
|
||||
"conversing": "You are a playful Minecraft bot named $NAME that can converse with players, see, move, mine, build, and interact with the world by using commands. Act human-like as if you were a typical Minecraft player, rather than an AI. Be very brief in your responses, don't apologize constantly, don't give instructions or make lists unless asked, and don't refuse requests. Don't pretend to act, use commands immediately when requested. Do NOT say this: 'Sure, I've stopped.', instead say this: 'Sure, I'll stop. !stop'. Do NOT say this: 'On my way! Give me a moment.', instead say this: 'On my way! !goToPlayer('playername', 3)'. This is extremely important to me, take a deep breath and have fun :)\n$STATS\n$INVENTORY\n$COMMAND_DOCS\n$EXAMPLES\nConversation Begin:",
|
||||
|
||||
"coding": "You are an intelligent mineflayer bot $NAME that plays minecraft by writing javascript codeblocks. Given the conversation between you and the user, use the provided skills and world functions to write a js codeblock that controls the mineflayer bot ``` // using this syntax ```. The code will be executed and you will recieve it's output. If you are satisfied with the response, respond without a codeblock in a conversational way. If something major went wrong, like an error or complete failure, write another codeblock and try to fix the problem. Minor mistakes are acceptable. Be maximally efficient, creative, and clear. Do not use commands !likeThis, only use codeblocks. Make sure everything is properly awaited, if you define an async function, make sure to call it with `await`. Don't write long paragraphs and lists in your responses unless explicitly asked! Only summarize the code you write with a sentence or two when done. This is extremely important to me, take a deep breath and good luck! \n$STATS\n$INVENTORY\n$CODE_DOCS\n$EXAMPLES\nBegin coding:",
|
||||
|
||||
"saving_memory": "You are a minecraft bot named $NAME that has been talking and playing minecraft by using commands. Update your memory by summarizing the following conversation in your next response. Store information that will help you improve as a Minecraft bot. Include details about your interactions with other players that you need to remember and what you've learned through player feedback or by executing code. Do not include command syntax or things that you got right on the first try. Be extremely brief and use as few words as possible.\nOld Memory: '$MEMORY'\nRecent conversation: \n$TO_SUMMARIZE\nSummarize your old memory and recent conversation into a new memory, and respond only with the memory text: ",
|
||||
|
||||
"npc": {
|
||||
"goals": [
|
||||
"wooden_pickaxe",
|
||||
"hole",
|
||||
"stone_axe",
|
||||
"stone_pickaxe",
|
||||
"stone_axe",
|
||||
"house",
|
||||
"furnace",
|
||||
"iron_pickaxe",
|
||||
"iron_axe",
|
||||
"iron_sword",
|
||||
"iron_armor"
|
||||
]
|
||||
},
|
||||
|
||||
"conversation_examples": [
|
||||
[
|
||||
{"role": "user", "content": "miner_32: Hey! What are you up to?"},
|
||||
{"role": "assistant", "content": "Nothing much miner_32, what do you need?"}
|
||||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "grombo_Xx: What do you see?"},
|
||||
{"role": "assistant", "content": "Let me see... !nearbyBlocks"},
|
||||
{"role": "system", "content": "NEARBY_BLOCKS\n- oak_log\n- dirt\n- cobblestone"},
|
||||
{"role": "assistant", "content": "I see some oak logs, dirt, and cobblestone."}
|
||||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "greg: Collect 10 wood"},
|
||||
{"role": "assistant", "content": "Let me see what's nearby... !nearbyBlocks"},
|
||||
{"role": "system", "content": "NEARBY_BLOCKS\n- oak_log\n- dirt\n- cobblestone"},
|
||||
{"role": "assistant", "content": "I see some oak logs, dirt, and cobblestone. I'll collect oak logs. !collectBlocks('oak_log', 10)"}
|
||||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "bobby: Craft a plank"},
|
||||
{"role": "assistant", "content": "Okay! !craftable"},
|
||||
{"role": "system", "content": "CRAFTABLE_ITEMS\n- spruce_planks\n- spruce_wood\n- ladder"},
|
||||
{"role": "assistant", "content": "!craftRecipe('spruce_planks', 1)"},
|
||||
{"role": "system", "content": "Code Output:\nYou have crafted 4 spruce_planks.\nCode execution finished successfully."},
|
||||
{"role": "assistant", "content": "I've crafted 4 spruce planks!"}
|
||||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "zZZn98: come here"},
|
||||
{"role": "assistant", "content": "On my way! !goToPlayer('zZZn98', 3)"},
|
||||
{"role": "system", "content": "Arrived at player."},
|
||||
{"role": "assistant", "content": "Here!"},
|
||||
{"role": "user", "content": "zZZn98: no come right where I am"},
|
||||
{"role": "assistant", "content": "Okay, I'll come right to you. !goToPlayer('zZZn98', 0)"}
|
||||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "hanky: collect some sand and give it to me please"},
|
||||
{"role": "assistant", "content": "Collecting sand... !newAction"},
|
||||
{"role": "system", "content": "Code Output:\nYou have reached player hanky.\nCode execution finished successfully."},
|
||||
{"role": "assistant", "content": "Here!"}
|
||||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "joe: Follow me!"},
|
||||
{"role": "assistant", "content": "Sure! !followPlayer('joe', 4)"},
|
||||
{"role": "user", "content": "joe: Not so close!"},
|
||||
{"role": "assistant", "content": "Okay, I'll keep my distance. !followPlayer('joe', 3)"}
|
||||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "hanky: kill that zombie!"},
|
||||
{"role": "assistant", "content": "I'm attacking! !attack('zombie')"},
|
||||
{"role": "system", "content": "Code Output:\nNo zombie nearby"},
|
||||
{"role": "assistant", "content": "I could not find a zombie nearby."}
|
||||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "abc: stop"},
|
||||
{"role": "assistant", "content": "Sure. !stop"},
|
||||
{"role": "system", "content": "Agent action stopped."},
|
||||
{"role": "assistant", "content": "I've stopped! What next?"}
|
||||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "maya: go to the nearest oak log"},
|
||||
{"role": "assistant", "content": "!newAction`"},
|
||||
{"role": "system", "content": "Code output:\n!!Code threw exception!! Error: ReferenceError: nearestOakLog is not defined\n Write code to fix the problem and try again."},
|
||||
{"role": "assistant", "content": "Apologies, I made an error. Would you like me to try again?"}
|
||||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "trixy88: craft some sticks"},
|
||||
{"role": "assistant", "content": "!craftRecipe('stick', 4)"},
|
||||
{"role": "system", "content": "Code Output:\nYou have crafted 16 sticks.\nCode execution finished successfully."},
|
||||
{"role": "assistant", "content": "I've crafted 16 sticks!"}
|
||||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "poppy: build a cobblestone house"},
|
||||
{"role": "assistant", "content": "Sure, I'll to build a small cobblestone house where I am. !newAction"}
|
||||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "zib: build a dirt tower"},
|
||||
{"role": "assistant", "content": "Sure, I'll build a dirt tower 5 tall where I am. !newAction"}
|
||||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "umpire_man: follow me without hunting"},
|
||||
{"role": "assistant", "content": "Okay, first I'll turn off hunting mode !setMode('hunting', false)"},
|
||||
{"role": "system", "content": "'hunting' mode disabled."},
|
||||
{"role": "assistant", "content": "Now I'll follow you !followPlayer('umpire_man')"}
|
||||
]
|
||||
],
|
||||
|
||||
"coding_examples": [
|
||||
[
|
||||
{"role": "user", "content": "greg: Collect 10 wood"},
|
||||
{"role": "assistant", "content": "```await skills.collectBlock(bot, 'oak_log', 10);\n```"},
|
||||
{"role": "system", "content": "Successfully collected 9 wood."},
|
||||
{"role": "assistant", "content": "I collected 9 oak logs, what next?"}
|
||||
],
|
||||
[
|
||||
{"role": "user", "content": "bobby: cook some chicken"},
|
||||
{"role": "assistant", "content": "```\nawait skills.smeltItem(bot, 'chicken', 8);\n```"},
|
||||
{"role": "system", "content": "Successfully smelted 8 chicken into 8 cooked_chicken."},
|
||||
{"role": "assistant", "content": "I have cooked 8 chicken."}
|
||||
],
|
||||
[
|
||||
{"role": "user", "content": "zZZn98: come here"},
|
||||
{"role": "assistant", "content": "```await skills.goToPlayer(bot, 'zZZn98');\n```"},
|
||||
{"role": "system", "content": "Code execution finished successfully."},
|
||||
{"role": "assistant", "content": "Here!"}
|
||||
],
|
||||
[
|
||||
{"role": "user", "content": "maya: go to the nearest oak log"},
|
||||
{"role": "assistant", "content": "```await skills.goToPosition(bot, nearestOakLog.x, nearestOakLog.y, nearestOakLog.z);\n```"},
|
||||
{"role": "system", "content": "Code output:\n!!Code threw exception!! Error: ReferenceError: nearestOakLog is not defined\n Write code to fix the problem and try again."},
|
||||
{"role": "assistant", "content": "Apologies, I made an error. Let me try that again.```\n// I am navigating to the nearest oak log.\nlet position = world.getNearestBlock(bot, 'oak_log', 20);\nawait skills.goToPosition(bot, position.x, position.y, position.z);\n```"},
|
||||
{"role": "system", "content": "Arrived at location."},
|
||||
{"role": "assistant", "content": "I found an oak log and I am now standing next to it. What next?"}
|
||||
],
|
||||
[
|
||||
{"role": "user", "content": "234jeb: build a little tower"},
|
||||
{"role": "assistant", "content": "```let pos = bot.entity.position;\nfor (let i = 0; i < 5; i++) {\n await skills.placeBlock(bot, 'dirt', pos.x, pos.y + i, pos.z);\n}\n```"},
|
||||
{"role": "system", "content": "Successfully placed 5 dirt."},
|
||||
{"role": "assistant", "content": "I built a little tower!"}
|
||||
],
|
||||
[
|
||||
{"role": "user", "content": "brug: build a dirt house"},
|
||||
{"role": "assistant", "content": "```const position = world.getPosition(bot);\nconst startX = position.x;\nconst startY = position.y;\nconst startZ = position.z;\nconst width = 7;\nconst depth = 7;\nconst height = 4;\n\n// Build the walls\nfor (let x = startX; x < startX + width; x++) {\n for (let y = startY; y < startY + height; y++) {\n for (let z = startZ; z < startZ + depth; z++) {\n if (x === startX || x === startX + width - 1 || y === startY || y === startY + height - 1 || z === startZ || z === startZ + depth - 1) {\n await skills.placeBlock(bot, 'oak_planks', x, y, z); \n }\n }\n }\n}\n```"}
|
||||
]
|
||||
]
|
||||
|
||||
}
|
|
@ -4,6 +4,7 @@ import { Prompter } from './prompter.js';
|
|||
import { initModes } from './modes.js';
|
||||
import { initBot } from '../utils/mcdata.js';
|
||||
import { containsCommand, commandExists, executeCommand, truncCommandMessage } from './commands/index.js';
|
||||
import { NPCContoller } from './npc/controller.js';
|
||||
|
||||
|
||||
export class Agent {
|
||||
|
@ -12,6 +13,8 @@ export class Agent {
|
|||
this.name = this.prompter.getName();
|
||||
this.history = new History(this);
|
||||
this.coder = new Coder(this);
|
||||
this.npc = new NPCContoller(this);
|
||||
|
||||
await this.prompter.initExamples();
|
||||
|
||||
if (load_mem)
|
||||
|
@ -177,6 +180,9 @@ export class Agent {
|
|||
this.coder.executeResume();
|
||||
});
|
||||
|
||||
// Init NPC controller
|
||||
this.npc.init();
|
||||
|
||||
// This update loop ensures that each update() is called one at a time, even if it takes longer than the interval
|
||||
const INTERVAL = 300;
|
||||
setTimeout(async () => {
|
||||
|
@ -189,6 +195,8 @@ export class Agent {
|
|||
}
|
||||
}
|
||||
}, INTERVAL);
|
||||
|
||||
this.bot.emit('idle');
|
||||
}
|
||||
|
||||
isIdle() {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { writeFileSync, readFileSync } from 'fs';
|
||||
import { NPCData } from './npc/data.js';
|
||||
|
||||
|
||||
export class History {
|
||||
|
@ -52,6 +53,8 @@ export class History {
|
|||
'memory': this.memory,
|
||||
'turns': this.turns
|
||||
};
|
||||
if (this.agent.npc.data !== null)
|
||||
data.npc = this.agent.npc.data.toObject();
|
||||
const json_data = JSON.stringify(data, null, 4);
|
||||
writeFileSync(this.memory_fp, json_data, (err) => {
|
||||
if (err) {
|
||||
|
@ -67,6 +70,7 @@ export class History {
|
|||
const data = readFileSync(this.memory_fp, 'utf8');
|
||||
const obj = JSON.parse(data);
|
||||
this.memory = obj.memory;
|
||||
this.agent.npc.data = NPCData.fromObject(obj.npc);
|
||||
this.turns = obj.turns;
|
||||
} catch (err) {
|
||||
console.error(`No memory file '${this.memory_fp}' for agent ${this.name}.`);
|
||||
|
|
|
@ -777,6 +777,49 @@ export async function stay(bot) {
|
|||
return true;
|
||||
}
|
||||
|
||||
export async function useDoor(bot, door_pos=null) {
|
||||
/**
|
||||
* Use the door at the given position.
|
||||
* @param {MinecraftBot} bot, reference to the minecraft bot.
|
||||
* @param {Vec3} door_pos, the position of the door to use. If null, the nearest door will be used.
|
||||
* @returns {Promise<boolean>} true if the door was used, false otherwise.
|
||||
* @example
|
||||
* let door = world.getNearestBlock(bot, "oak_door", 16).position;
|
||||
* await skills.useDoor(bot, door);
|
||||
**/
|
||||
if (!door_pos) {
|
||||
for (let door_type of ['oak_door', 'spruce_door', 'birch_door', 'jungle_door', 'acacia_door', 'dark_oak_door',
|
||||
'mangrove_door', 'cherry_door', 'bamboo_door', 'crimson_door', 'warped_door']) {
|
||||
door_pos = world.getNearestBlock(bot, door_type, 16).position;
|
||||
if (door_pos) break;
|
||||
}
|
||||
} else {
|
||||
door_pos = Vec3(door_pos.x, door_pos.y, door_pos.z);
|
||||
}
|
||||
if (!door_pos) {
|
||||
log(bot, `Could not find a door to use.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
bot.pathfinder.setGoal(new pf.goals.GoalNear(door_pos.x, door_pos.y, door_pos.z, 1));
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
while (bot.pathfinder.isMoving()) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
let door_block = bot.blockAt(door_pos);
|
||||
await bot.lookAt(door_pos);
|
||||
if (!door_block._properties.open)
|
||||
await bot.activateBlock(door_block);
|
||||
|
||||
bot.setControlState("forward", true);
|
||||
await new Promise((resolve) => setTimeout(resolve, 600));
|
||||
bot.setControlState("forward", false);
|
||||
await bot.activateBlock(door_block);
|
||||
|
||||
log(bot, `Used door at ${door_pos}.`);
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function goToBed(bot) {
|
||||
/**
|
||||
|
|
|
@ -266,4 +266,4 @@ export function getBiomeName(bot) {
|
|||
**/
|
||||
const biomeId = bot.world.getBiome(bot.entity.position);
|
||||
return mc.getAllBiomes()[biomeId].name;
|
||||
}
|
||||
}
|
||||
|
|
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';
|
||||
import { blockSatisfied, getTypeOfGeneric, rotateXZ } from './utils.js';
|
||||
|
||||
|
||||
export class BuildGoal {
|
||||
constructor(agent) {
|
||||
this.agent = agent;
|
||||
}
|
||||
|
||||
async wrapSkill(func) {
|
||||
if (!this.agent.isIdle())
|
||||
return false;
|
||||
let res = await this.agent.coder.execute(func);
|
||||
return !res.interrupted;
|
||||
}
|
||||
|
||||
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] = 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 !== null && !blockSatisfied(block_name, current_block)) {
|
||||
acted = true;
|
||||
|
||||
if (current_block.name !== 'air') {
|
||||
res = await this.wrapSkill(async () => {
|
||||
await skills.breakBlockAt(this.agent.bot, world_pos.x, world_pos.y, world_pos.z);
|
||||
});
|
||||
if (!res) return {missing: missing, acted: acted, position: position, orientation: orientation};
|
||||
}
|
||||
|
||||
if (block_name !== 'air') {
|
||||
let block_typed = getTypeOfGeneric(this.agent.bot, block_name);
|
||||
if (inventory[block_typed] > 0) {
|
||||
res = await this.wrapSkill(async () => {
|
||||
await skills.placeBlock(this.agent.bot, block_typed, world_pos.x, world_pos.y, world_pos.z);
|
||||
});
|
||||
if (!res) return {missing: missing, acted: acted, position: position, orientation: orientation};
|
||||
} else {
|
||||
if (missing[block_typed] === undefined)
|
||||
missing[block_typed] = 0;
|
||||
missing[block_typed]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {missing: missing, acted: acted, position: position, orientation: orientation};
|
||||
}
|
||||
|
||||
}
|
38
src/agent/npc/construction/hole.json
Normal file
38
src/agent/npc/construction/hole.json
Normal file
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"name": "hole",
|
||||
"offset": -2,
|
||||
"blocks": [
|
||||
[
|
||||
["", "", "", "", ""],
|
||||
["", "dirt", "dirt", "dirt", ""],
|
||||
["", "dirt", "dirt", "dirt", ""],
|
||||
["", "dirt", "dirt", "dirt", ""],
|
||||
["", "", "dirt", "", ""],
|
||||
["", "", "dirt", "", ""]
|
||||
],
|
||||
[
|
||||
["dirt", "dirt", "dirt", "dirt", "dirt"],
|
||||
["dirt", "chest", "bed", "air", "dirt"],
|
||||
["dirt", "air", "bed", "air", "dirt"],
|
||||
["dirt", "air", "air", "air", "dirt"],
|
||||
["dirt", "dirt", "door", "dirt", "dirt"],
|
||||
["dirt", "dirt", "air", "dirt", "dirt"]
|
||||
],
|
||||
[
|
||||
["dirt", "dirt", "dirt", "dirt", "dirt"],
|
||||
["dirt", "air", "air", "air", "dirt"],
|
||||
["dirt", "torch", "air", "air", "dirt"],
|
||||
["dirt", "air", "air", "air", "dirt"],
|
||||
["dirt", "dirt", "door", "dirt", "dirt"],
|
||||
["air", "air", "air", "air", "air"]
|
||||
],
|
||||
[
|
||||
["air", "air", "air", "air", "air"],
|
||||
["dirt", "dirt", "dirt", "dirt", "dirt"],
|
||||
["dirt", "dirt", "dirt", "dirt", "dirt"],
|
||||
["dirt", "dirt", "dirt", "dirt", "dirt"],
|
||||
["air", "air", "air", "air", "air"],
|
||||
["air", "air", "air", "air", "air"]
|
||||
]
|
||||
]
|
||||
}
|
42
src/agent/npc/construction/house.json
Normal file
42
src/agent/npc/construction/house.json
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"name": "shelter",
|
||||
"offset": -1,
|
||||
"blocks": [
|
||||
[
|
||||
["", "", "", "", ""],
|
||||
["", "planks", "planks", "planks", ""],
|
||||
["", "planks", "planks", "planks", ""],
|
||||
["", "planks", "planks", "planks", ""],
|
||||
["", "planks", "planks", "planks", ""],
|
||||
["", "", "planks", "", ""],
|
||||
["", "", "", "", ""]
|
||||
],
|
||||
[
|
||||
["log", "planks", "planks", "planks", "log"],
|
||||
["planks", "chest", "bed", "air", "planks"],
|
||||
["planks", "air", "bed", "air", "planks"],
|
||||
["planks", "air", "air", "air", "planks"],
|
||||
["planks", "air", "air", "air", "planks"],
|
||||
["log", "planks", "door", "planks", "log"],
|
||||
["", "air", "air", "air", ""]
|
||||
],
|
||||
[
|
||||
["log", "planks", "planks", "planks", "log"],
|
||||
["planks", "torch", "air", "torch", "planks"],
|
||||
["planks", "air", "air", "air", "planks"],
|
||||
["planks", "air", "air", "air", "planks"],
|
||||
["planks", "torch", "air", "torch", "planks"],
|
||||
["log", "planks", "door", "planks", "log"],
|
||||
["", "air", "air", "air", ""]
|
||||
],
|
||||
[
|
||||
["air", "air", "air", "air", "air"],
|
||||
["air", "planks", "planks", "planks", "air"],
|
||||
["planks", "planks", "planks", "planks", "planks"],
|
||||
["planks", "planks", "planks", "planks", "planks"],
|
||||
["air", "planks", "planks", "planks", "air"],
|
||||
["air", "air", "air", "air", "air"],
|
||||
["", "air", "air", "air", ""]
|
||||
]
|
||||
]
|
||||
}
|
223
src/agent/npc/controller.js
Normal file
223
src/agent/npc/controller.js
Normal file
|
@ -0,0 +1,223 @@
|
|||
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.prompts.npc);
|
||||
this.temp_goals = [];
|
||||
this.item_goal = new ItemGoal(agent, this.data);
|
||||
this.build_goal = new BuildGoal(agent);
|
||||
this.constructions = {};
|
||||
}
|
||||
|
||||
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() {
|
||||
if (this.data === null) return;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 () => {
|
||||
// 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.coder.resume_func) {
|
||||
this.executeNext();
|
||||
this.agent.history.save();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async executeNext() {
|
||||
if (!this.agent.isIdle()) return;
|
||||
await this.agent.coder.execute(async () => {
|
||||
await skills.moveAway(this.agent.bot, 2);
|
||||
});
|
||||
|
||||
if (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.coder.execute(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 {
|
||||
// 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.coder.execute(async () => {
|
||||
await skills.useDoor(this.agent.bot, door_pos);
|
||||
});
|
||||
}
|
||||
|
||||
// Go to bed
|
||||
await this.agent.coder.execute(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);
|
||||
this.temp_goals = [];
|
||||
|
||||
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)) {
|
||||
await this.item_goal.executeNext(goal.name, goal.quantity);
|
||||
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) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
37
src/agent/npc/data.js
Normal file
37
src/agent/npc/data.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
export class NPCData {
|
||||
constructor() {
|
||||
this.goals = [];
|
||||
this.built = {};
|
||||
this.home = null;
|
||||
}
|
||||
|
||||
toObject() {
|
||||
let obj = {};
|
||||
if (this.goals.length > 0)
|
||||
obj.goals = this.goals;
|
||||
if (Object.keys(this.built).length > 0)
|
||||
obj.built = this.built;
|
||||
if (this.home)
|
||||
obj.home = this.home;
|
||||
return obj;
|
||||
}
|
||||
|
||||
static fromObject(obj) {
|
||||
if (!obj) return null;
|
||||
let npc = new NPCData();
|
||||
if (obj.goals) {
|
||||
npc.goals = [];
|
||||
for (let goal of obj.goals) {
|
||||
if (typeof goal === 'string')
|
||||
npc.goals.push({name: goal, quantity: 1});
|
||||
else
|
||||
npc.goals.push({name: goal.name, quantity: goal.quantity});
|
||||
}
|
||||
}
|
||||
if (obj.built)
|
||||
npc.built = obj.built;
|
||||
if (obj.home)
|
||||
npc.home = obj.home;
|
||||
return npc;
|
||||
}
|
||||
}
|
354
src/agent/npc/item_goal.js
Normal file
354
src/agent/npc/item_goal.js
Normal file
|
@ -0,0 +1,354 @@
|
|||
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 = [
|
||||
'coal_block',
|
||||
'iron_block',
|
||||
'gold_block',
|
||||
'diamond_block',
|
||||
'deepslate',
|
||||
'blackstone',
|
||||
'netherite',
|
||||
'_wood',
|
||||
'stripped_',
|
||||
'crimson',
|
||||
'warped',
|
||||
'dye'
|
||||
]
|
||||
|
||||
|
||||
class ItemNode {
|
||||
constructor(manager, wrapper, name) {
|
||||
this.manager = manager;
|
||||
this.wrapper = wrapper;
|
||||
this.name = name;
|
||||
this.type = '';
|
||||
this.source = null;
|
||||
this.prereq = null;
|
||||
this.recipe = [];
|
||||
this.fails = 0;
|
||||
}
|
||||
|
||||
setRecipe(recipe) {
|
||||
this.type = 'craft';
|
||||
let size = 0;
|
||||
this.recipe = [];
|
||||
for (let [key, value] of Object.entries(recipe)) {
|
||||
if (this.manager.nodes[key] === undefined)
|
||||
this.manager.nodes[key] = new ItemWrapper(this.manager, this.wrapper, key);
|
||||
this.recipe.push({node: this.manager.nodes[key], quantity: value});
|
||||
size += value;
|
||||
}
|
||||
if (size > 4) {
|
||||
if (this.manager.nodes['crafting_table'] === undefined)
|
||||
this.manager.nodes['crafting_table'] = new ItemWrapper(this.manager, this.wrapper, 'crafting_table');
|
||||
this.prereq = this.manager.nodes['crafting_table'];
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
setCollectable(source=null, tool=null) {
|
||||
this.type = 'block';
|
||||
if (source)
|
||||
this.source = source;
|
||||
else
|
||||
this.source = this.name;
|
||||
if (tool) {
|
||||
if (this.manager.nodes[tool] === undefined)
|
||||
this.manager.nodes[tool] = new ItemWrapper(this.manager, this.wrapper, tool);
|
||||
this.prereq = this.manager.nodes[tool];
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
setSmeltable(source_item) {
|
||||
this.type = 'smelt';
|
||||
if (this.manager.nodes['furnace'] === undefined)
|
||||
this.manager.nodes['furnace'] = new ItemWrapper(this.manager, this.wrapper, 'furnace');
|
||||
this.prereq = this.manager.nodes['furnace'];
|
||||
|
||||
if (this.manager.nodes[source_item] === undefined)
|
||||
this.manager.nodes[source_item] = new ItemWrapper(this.manager, this.wrapper, source_item);
|
||||
if (this.manager.nodes['coal'] === undefined)
|
||||
this.manager.nodes['coal'] = new ItemWrapper(this.manager, this.wrapper, 'coal');
|
||||
this.recipe = [
|
||||
{node: this.manager.nodes[source_item], quantity: 1},
|
||||
{node: this.manager.nodes['coal'], quantity: 1}
|
||||
];
|
||||
return this;
|
||||
}
|
||||
|
||||
setHuntable(animal_source) {
|
||||
this.type = 'hunt';
|
||||
this.source = animal_source;
|
||||
return this;
|
||||
}
|
||||
|
||||
getChildren() {
|
||||
let children = [...this.recipe];
|
||||
if (this.prereq) {
|
||||
children.push({node: this.prereq, quantity: 1});
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
isReady() {
|
||||
for (let child of this.getChildren()) {
|
||||
if (!child.node.isDone(child.quantity)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
isDone(quantity=1) {
|
||||
if (this.manager.goal.name === this.name)
|
||||
return false;
|
||||
return itemSatisfied(this.manager.agent.bot, this.name, quantity);
|
||||
}
|
||||
|
||||
getDepth(q=1) {
|
||||
if (this.isDone(q)) {
|
||||
return 0;
|
||||
}
|
||||
let depth = 0;
|
||||
for (let child of this.getChildren()) {
|
||||
depth = Math.max(depth, child.node.getDepth(child.quantity));
|
||||
}
|
||||
return depth + 1;
|
||||
}
|
||||
|
||||
getFails(q=1) {
|
||||
if (this.isDone(q)) {
|
||||
return 0;
|
||||
}
|
||||
let fails = 0;
|
||||
for (let child of this.getChildren()) {
|
||||
fails += child.node.getFails(child.quantity);
|
||||
}
|
||||
return fails + this.fails;
|
||||
}
|
||||
|
||||
getNext(q=1) {
|
||||
if (this.isDone(q))
|
||||
return null;
|
||||
if (this.isReady())
|
||||
return {node: this, quantity: q};
|
||||
for (let child of this.getChildren()) {
|
||||
let res = child.node.getNext(child.quantity);
|
||||
if (res)
|
||||
return res;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async execute(quantity=1) {
|
||||
if (!this.isReady()) {
|
||||
this.fails += 1;
|
||||
return;
|
||||
}
|
||||
let inventory = world.getInventoryCounts(this.manager.agent.bot);
|
||||
let init_quantity = inventory[this.name] || 0;
|
||||
if (this.type === 'block') {
|
||||
await skills.collectBlock(this.manager.agent.bot, this.source, quantity, this.manager.agent.npc.getBuiltPositions());
|
||||
} else if (this.type === 'smelt') {
|
||||
let to_smelt_name = this.recipe[0].node.name;
|
||||
let to_smelt_quantity = Math.min(quantity, inventory[to_smelt_name] || 1);
|
||||
await skills.smeltItem(this.manager.agent.bot, to_smelt_name, to_smelt_quantity);
|
||||
} else if (this.type === 'hunt') {
|
||||
for (let i=0; i<quantity; i++) {
|
||||
res = await skills.attackNearest(this.manager.agent.bot, this.source);
|
||||
if (!res || this.manager.agent.bot.interrupt_code)
|
||||
break;
|
||||
}
|
||||
} else if (this.type === 'craft') {
|
||||
await skills.craftRecipe(this.manager.agent.bot, this.name, quantity);
|
||||
}
|
||||
let final_quantity = world.getInventoryCounts(this.manager.agent.bot)[this.name] || 0;
|
||||
if (final_quantity <= init_quantity) {
|
||||
this.fails += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ItemWrapper {
|
||||
constructor(manager, parent, name) {
|
||||
this.manager = manager;
|
||||
this.name = name;
|
||||
this.parent = parent;
|
||||
this.methods = [];
|
||||
|
||||
let blacklisted = false;
|
||||
for (let match of blacklist) {
|
||||
if (name.includes(match)) {
|
||||
blacklisted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!blacklisted && !this.containsCircularDependency()) {
|
||||
this.createChildren();
|
||||
}
|
||||
}
|
||||
|
||||
add_method(method) {
|
||||
for (let child of method.getChildren()) {
|
||||
if (child.node.methods.length === 0)
|
||||
return;
|
||||
}
|
||||
this.methods.push(method);
|
||||
}
|
||||
|
||||
createChildren() {
|
||||
let recipes = mc.getItemCraftingRecipes(this.name);
|
||||
if (recipes) {
|
||||
for (let recipe of recipes) {
|
||||
let includes_blacklisted = false;
|
||||
for (let ingredient in recipe) {
|
||||
for (let match of blacklist) {
|
||||
if (ingredient.includes(match)) {
|
||||
includes_blacklisted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (includes_blacklisted) break;
|
||||
}
|
||||
if (includes_blacklisted) continue;
|
||||
this.add_method(new ItemNode(this.manager, this, this.name).setRecipe(recipe))
|
||||
}
|
||||
}
|
||||
|
||||
let block_sources = mc.getItemBlockSources(this.name);
|
||||
if (block_sources.length > 0 && this.name !== 'torch' && !this.name.includes('bed')) { // Do not collect placed torches or beds
|
||||
for (let block_source of block_sources) {
|
||||
if (block_source === 'grass_block') continue; // Dirt nodes will collect grass blocks
|
||||
let tool = mc.getBlockTool(block_source);
|
||||
this.add_method(new ItemNode(this.manager, this, this.name).setCollectable(block_source, tool));
|
||||
}
|
||||
}
|
||||
|
||||
let smeltingIngredient = mc.getItemSmeltingIngredient(this.name);
|
||||
if (smeltingIngredient) {
|
||||
this.add_method(new ItemNode(this.manager, this, this.name).setSmeltable(smeltingIngredient));
|
||||
}
|
||||
|
||||
let animal_source = mc.getItemAnimalSource(this.name);
|
||||
if (animal_source) {
|
||||
this.add_method(new ItemNode(this.manager, this, this.name).setHuntable(animal_source));
|
||||
}
|
||||
}
|
||||
|
||||
containsCircularDependency() {
|
||||
let p = this.parent;
|
||||
while (p) {
|
||||
if (p.name === this.name) {
|
||||
return true;
|
||||
}
|
||||
p = p.parent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getBestMethod(q=1) {
|
||||
let best_cost = -1;
|
||||
let best_method = null;
|
||||
for (let method of this.methods) {
|
||||
let cost = method.getDepth(q) + method.getFails(q);
|
||||
if (best_cost == -1 || cost < best_cost) {
|
||||
best_cost = cost;
|
||||
best_method = method;
|
||||
}
|
||||
}
|
||||
return best_method
|
||||
}
|
||||
|
||||
isDone(q=1) {
|
||||
if (this.methods.length === 0)
|
||||
return false;
|
||||
return this.getBestMethod(q).isDone(q);
|
||||
}
|
||||
|
||||
getDepth(q=1) {
|
||||
if (this.methods.length === 0)
|
||||
return 0;
|
||||
return this.getBestMethod(q).getDepth(q);
|
||||
}
|
||||
|
||||
getFails(q=1) {
|
||||
if (this.methods.length === 0)
|
||||
return 0;
|
||||
return this.getBestMethod(q).getFails(q);
|
||||
}
|
||||
|
||||
getNext(q=1) {
|
||||
if (this.methods.length === 0)
|
||||
return null;
|
||||
return this.getBestMethod(q).getNext(q);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class ItemGoal {
|
||||
constructor(agent) {
|
||||
this.agent = agent;
|
||||
this.goal = null;
|
||||
this.nodes = {};
|
||||
this.failed = [];
|
||||
}
|
||||
|
||||
async executeNext(item_name, item_quantity=1) {
|
||||
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 = this.goal.getNext(item_quantity);
|
||||
if (!next_info) {
|
||||
console.log(`Invalid item goal ${this.goal.name}`);
|
||||
return;
|
||||
}
|
||||
let next = next_info.node;
|
||||
let quantity = next_info.quantity;
|
||||
|
||||
// Prevent unnecessary attempts to obtain blocks that are not nearby
|
||||
if (next.type === 'block' && !world.getNearbyBlockTypes(this.agent.bot).includes(next.source) ||
|
||||
next.type === 'hunt' && !world.getNearbyEntityTypes(this.agent.bot).includes(next.source)) {
|
||||
next.fails += 1;
|
||||
|
||||
// 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);
|
||||
await this.agent.coder.execute(async () => {
|
||||
await skills.moveAway(this.agent.bot, 8);
|
||||
});
|
||||
} else {
|
||||
this.failed.push(next.name);
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
this.agent.bot.emit('idle');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for the bot to be idle before attempting to execute the next goal
|
||||
if (!this.agent.isIdle())
|
||||
return;
|
||||
|
||||
// Execute the next goal
|
||||
let init_quantity = world.getInventoryCounts(this.agent.bot)[next.name] || 0;
|
||||
await this.agent.coder.execute(async () => {
|
||||
await next.execute(quantity);
|
||||
});
|
||||
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 ${this.goal.name}`);
|
||||
} else {
|
||||
console.log(`Failed to obtain ${next.name} for goal ${this.goal.name}`);
|
||||
}
|
||||
}
|
||||
}
|
126
src/agent/npc/utils.js
Normal file
126
src/agent/npc/utils.js
Normal file
|
@ -0,0 +1,126 @@
|
|||
import * as world from '../library/world.js';
|
||||
import * as mc from '../../utils/mcdata.js';
|
||||
|
||||
|
||||
export function getTypeOfGeneric(bot, block_name) {
|
||||
// Get type of wooden block
|
||||
if (mc.MATCHING_WOOD_BLOCKS.includes(block_name)) {
|
||||
|
||||
// Return most common wood type in inventory
|
||||
let type_count = {};
|
||||
let max_count = 0;
|
||||
let max_type = null;
|
||||
let inventory = world.getInventoryCounts(bot);
|
||||
for (const item in inventory) {
|
||||
for (const wood of mc.WOOD_TYPES) {
|
||||
if (item.includes(wood)) {
|
||||
if (type_count[wood] === undefined)
|
||||
type_count[wood] = 0;
|
||||
type_count[wood] += inventory[item];
|
||||
if (type_count[wood] > max_count) {
|
||||
max_count = type_count[wood];
|
||||
max_type = wood;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (max_type !== null)
|
||||
return max_type + '_' + block_name;
|
||||
|
||||
// Return nearest wood type
|
||||
let log_types = mc.WOOD_TYPES.map((wood) => wood + '_log');
|
||||
let blocks = world.getNearestBlocks(bot, log_types, 16, 1);
|
||||
if (blocks.length > 0) {
|
||||
let wood = blocks[0].name.split('_')[0];
|
||||
return wood + '_' + block_name;
|
||||
}
|
||||
|
||||
// Return oak
|
||||
return 'oak_' + block_name;
|
||||
}
|
||||
|
||||
// Get type of bed
|
||||
if (block_name === 'bed') {
|
||||
|
||||
// Return most common wool type in inventory
|
||||
let type_count = {};
|
||||
let max_count = 0;
|
||||
let max_type = null;
|
||||
let inventory = world.getInventoryCounts(bot);
|
||||
for (const item in inventory) {
|
||||
for (const color of mc.WOOL_COLORS) {
|
||||
if (item === color + '_wool') {
|
||||
if (type_count[color] === undefined)
|
||||
type_count[color] = 0;
|
||||
type_count[color] += inventory[item];
|
||||
if (type_count[color] > max_count) {
|
||||
max_count = type_count[color];
|
||||
max_type = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (max_type !== null)
|
||||
return max_type + '_' + block_name;
|
||||
|
||||
// Return white
|
||||
return 'white_' + block_name;
|
||||
}
|
||||
return block_name;
|
||||
}
|
||||
|
||||
|
||||
export function blockSatisfied(target_name, block) {
|
||||
if (target_name == 'dirt') {
|
||||
return block.name == 'dirt' || block.name == 'grass_block';
|
||||
} else if (mc.MATCHING_WOOD_BLOCKS.includes(target_name)) {
|
||||
return block.name.endsWith(target_name);
|
||||
} else if (target_name == 'bed') {
|
||||
return block.name.endsWith('bed');
|
||||
} else if (target_name == 'torch') {
|
||||
return block.name.includes('torch');
|
||||
}
|
||||
return block.name == target_name;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
export function 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];
|
||||
}
|
|
@ -12,6 +12,41 @@ const mc_version = settings.minecraft_version;
|
|||
const mcdata = minecraftData(mc_version);
|
||||
|
||||
|
||||
export const WOOD_TYPES = ['oak', 'spruce', 'birch', 'jungle', 'acacia', 'dark_oak'];
|
||||
export const MATCHING_WOOD_BLOCKS = [
|
||||
'log',
|
||||
'planks',
|
||||
'sign',
|
||||
'boat',
|
||||
'fence_gate',
|
||||
'door',
|
||||
'fence',
|
||||
'slab',
|
||||
'stairs',
|
||||
'button',
|
||||
'pressure_plate',
|
||||
'trapdoor'
|
||||
]
|
||||
export const WOOL_COLORS = [
|
||||
'white',
|
||||
'orange',
|
||||
'magenta',
|
||||
'light_blue',
|
||||
'yellow',
|
||||
'lime',
|
||||
'pink',
|
||||
'gray',
|
||||
'light_gray',
|
||||
'cyan',
|
||||
'purple',
|
||||
'blue',
|
||||
'brown',
|
||||
'green',
|
||||
'red',
|
||||
'black'
|
||||
]
|
||||
|
||||
|
||||
export function initBot(username) {
|
||||
let bot = createBot({
|
||||
username: username,
|
||||
|
@ -122,4 +157,83 @@ export function getAllBlockIds(ignore) {
|
|||
|
||||
export function getAllBiomes() {
|
||||
return mcdata.biomes;
|
||||
}
|
||||
|
||||
export function getItemCraftingRecipes(itemName) {
|
||||
let itemId = getItemId(itemName);
|
||||
if (!mcdata.recipes[itemId]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let recipes = [];
|
||||
for (let r of mcdata.recipes[itemId]) {
|
||||
let recipe = {};
|
||||
let ingredients = [];
|
||||
if (r.ingredients) {
|
||||
ingredients = r.ingredients;
|
||||
} else if (r.inShape) {
|
||||
ingredients = r.inShape.flat();
|
||||
}
|
||||
for (let ingredient of ingredients) {
|
||||
let ingredientName = getItemName(ingredient);
|
||||
if (ingredientName === null) continue;
|
||||
if (!recipe[ingredientName])
|
||||
recipe[ingredientName] = 0;
|
||||
recipe[ingredientName]++;
|
||||
}
|
||||
recipes.push(recipe);
|
||||
}
|
||||
|
||||
return recipes;
|
||||
}
|
||||
|
||||
export function getItemSmeltingIngredient(itemName) {
|
||||
return {
|
||||
baked_potato: 'potato',
|
||||
steak: 'raw_beef',
|
||||
cooked_chicken: 'raw_chicken',
|
||||
cooked_cod: 'raw_cod',
|
||||
cooked_mutton: 'raw_mutton',
|
||||
cooked_porkchop: 'raw_porkchop',
|
||||
cooked_rabbit: 'raw_rabbit',
|
||||
cooked_salmon: 'raw_salmon',
|
||||
dried_kelp: 'kelp',
|
||||
iron_ingot: 'raw_iron',
|
||||
gold_ingot: 'raw_gold',
|
||||
copper_ingot: 'raw_copper',
|
||||
glass: 'sand'
|
||||
}[itemName];
|
||||
}
|
||||
|
||||
export function getItemBlockSources(itemName) {
|
||||
let itemId = getItemId(itemName);
|
||||
let sources = [];
|
||||
for (let block of getAllBlocks()) {
|
||||
if (block.drops.includes(itemId)) {
|
||||
sources.push(block.name);
|
||||
}
|
||||
}
|
||||
return sources;
|
||||
}
|
||||
|
||||
export function getItemAnimalSource(itemName) {
|
||||
return {
|
||||
raw_beef: 'cow',
|
||||
raw_chicken: 'chicken',
|
||||
raw_cod: 'cod',
|
||||
raw_mutton: 'sheep',
|
||||
raw_porkchop: 'pig',
|
||||
raw_rabbit: 'rabbit',
|
||||
raw_salmon: 'salmon',
|
||||
leather: 'cow',
|
||||
wool: 'sheep'
|
||||
}[itemName];
|
||||
}
|
||||
|
||||
export function getBlockTool(blockName) {
|
||||
let block = mcdata.blocksByName[blockName];
|
||||
if (!block || !block.harvestTools) {
|
||||
return null;
|
||||
}
|
||||
return getItemName(Object.keys(block.harvestTools)[0]); // Double check first tool is always simplest
|
||||
}
|
Loading…
Add table
Reference in a new issue