mirror of
https://github.com/kolbytn/mindcraft.git
synced 2025-08-09 00:35:33 +02:00
init item goals
This commit is contained in:
parent
aabd1a9ac2
commit
21a9995f62
5 changed files with 406 additions and 8 deletions
|
@ -5,6 +5,7 @@ import { Examples } from '../utils/examples.js';
|
||||||
import { initBot } from '../utils/mcdata.js';
|
import { initBot } from '../utils/mcdata.js';
|
||||||
import { sendRequest } from '../utils/gpt.js';
|
import { sendRequest } from '../utils/gpt.js';
|
||||||
import { containsCommand, commandExists, executeCommand } from './commands/index.js';
|
import { containsCommand, commandExists, executeCommand } from './commands/index.js';
|
||||||
|
import { ItemGoal } from './item_goal.js';
|
||||||
|
|
||||||
|
|
||||||
export class Agent {
|
export class Agent {
|
||||||
|
@ -13,6 +14,7 @@ export class Agent {
|
||||||
this.examples = new Examples();
|
this.examples = new Examples();
|
||||||
this.history = new History(this);
|
this.history = new History(this);
|
||||||
this.coder = new Coder(this);
|
this.coder = new Coder(this);
|
||||||
|
this.item_goal = new ItemGoal(this);
|
||||||
|
|
||||||
console.log('Loading examples...');
|
console.log('Loading examples...');
|
||||||
|
|
||||||
|
@ -171,9 +173,13 @@ export class Agent {
|
||||||
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();
|
if (this.coder.resume_func != null)
|
||||||
|
this.coder.executeResume();
|
||||||
|
else
|
||||||
|
this.item_goal.executeNext();
|
||||||
});
|
});
|
||||||
|
|
||||||
// This update loop ensures that each update() is called one at a time, even if it takes longer than the interval
|
// This update loop ensures that each update() is called one at a time, even if it takes longer than the interval
|
||||||
|
@ -188,6 +194,8 @@ export class Agent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, INTERVAL);
|
}, INTERVAL);
|
||||||
|
|
||||||
|
this.bot.emit('idle');
|
||||||
}
|
}
|
||||||
|
|
||||||
isIdle() {
|
isIdle() {
|
||||||
|
|
298
src/agent/item_goal.js
Normal file
298
src/agent/item_goal.js
Normal file
|
@ -0,0 +1,298 @@
|
||||||
|
import * as skills from './library/skills.js';
|
||||||
|
import * as world from './library/world.js';
|
||||||
|
import * as mc from '../utils/mcdata.js';
|
||||||
|
|
||||||
|
|
||||||
|
class ItemNode {
|
||||||
|
constructor(bot, name, quantity, wrapper) {
|
||||||
|
this.bot = bot;
|
||||||
|
this.name = name;
|
||||||
|
this.quantity = quantity;
|
||||||
|
this.wrapper = wrapper;
|
||||||
|
this.type = '';
|
||||||
|
this.source = null;
|
||||||
|
this.prereq = null;
|
||||||
|
this.recipe = [];
|
||||||
|
this.fails = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
setRecipe(recipe) {
|
||||||
|
this.type = 'craft';
|
||||||
|
let size = 0;
|
||||||
|
for (let [key, value] of Object.entries(recipe)) {
|
||||||
|
this.recipe.push(new ItemWrapper(this.bot, key, value * this.quantity, this.wrapper));
|
||||||
|
size += value;
|
||||||
|
}
|
||||||
|
if (size > 4) {
|
||||||
|
this.prereq = new ItemWrapper(this.bot, 'crafting_table', 1, this.wrapper);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCollectable(source=null, tool=null) {
|
||||||
|
this.type = 'block';
|
||||||
|
if (source)
|
||||||
|
this.source = source;
|
||||||
|
else
|
||||||
|
this.source = this.name;
|
||||||
|
if (tool)
|
||||||
|
this.prereq = new ItemWrapper(this.bot, tool, 1, this.wrapper);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSmeltable(source) {
|
||||||
|
this.type = 'smelt';
|
||||||
|
this.prereq = new ItemWrapper(this.bot, 'furnace', 1, this.wrapper);
|
||||||
|
this.source = new ItemWrapper(this.bot, source, this.quantity, this.wrapper);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setHuntable(animal_source) {
|
||||||
|
this.type = 'hunt';
|
||||||
|
this.source = animal_source;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
getChildren() {
|
||||||
|
let children = [];
|
||||||
|
for (let child of this.recipe) {
|
||||||
|
if (child instanceof ItemWrapper && child.methods.length > 0) {
|
||||||
|
children.push(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.prereq && this.prereq instanceof ItemWrapper && this.prereq.methods.length > 0) {
|
||||||
|
children.push(this.prereq);
|
||||||
|
}
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
isReady() {
|
||||||
|
for (let child of this.getChildren()) {
|
||||||
|
if (!child.isDone()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
isDone() {
|
||||||
|
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.bot)[item] >= this.quantity) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDepth() {
|
||||||
|
if (this.isDone()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let depth = 0;
|
||||||
|
for (let child of this.getChildren()) {
|
||||||
|
depth = Math.max(depth, child.getDepth());
|
||||||
|
}
|
||||||
|
return depth + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFails() {
|
||||||
|
if (this.isDone()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let fails = 0;
|
||||||
|
for (let child of this.getChildren()) {
|
||||||
|
fails += child.getFails();
|
||||||
|
}
|
||||||
|
return fails + this.fails;
|
||||||
|
}
|
||||||
|
|
||||||
|
getNext() {
|
||||||
|
if (this.isReady()) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
let furthest_depth = -1;
|
||||||
|
let furthest_child = null;
|
||||||
|
for (let child of this.getChildren()) {
|
||||||
|
let depth = child.getDepth();
|
||||||
|
if (depth > furthest_depth) {
|
||||||
|
furthest_depth = depth;
|
||||||
|
furthest_child = child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return furthest_child.getNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute() {
|
||||||
|
if (!this.isReady()) {
|
||||||
|
this.fails += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.type === 'block') {
|
||||||
|
await skills.collectBlock(this.bot, this.source, this.quantity);
|
||||||
|
} else if (this.type === 'smelt') {
|
||||||
|
await skills.smeltItem(this.bot, this.name, this.quantity);
|
||||||
|
} else if (this.type === 'hunt') {
|
||||||
|
for (let i = 0; i < this.quantity; i++) {
|
||||||
|
let res = await skills.attackNearest(this.bot, this.source);
|
||||||
|
if (!res) break;
|
||||||
|
}
|
||||||
|
} else if (this.type === 'craft') {
|
||||||
|
await skills.craftRecipe(this.bot, this.name, this.quantity);
|
||||||
|
}
|
||||||
|
if (!this.isDone()) {
|
||||||
|
this.fails += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ItemWrapper {
|
||||||
|
constructor(bot, name, quantity, parent=null) {
|
||||||
|
this.bot = bot;
|
||||||
|
this.name = name;
|
||||||
|
this.quantity = quantity;
|
||||||
|
this.parent = parent;
|
||||||
|
this.methods = [];
|
||||||
|
|
||||||
|
if (!this.containsCircularDependency()) {
|
||||||
|
this.createChildren();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createChildren() {
|
||||||
|
let recipes = mc.getItemCraftingRecipes(this.name);
|
||||||
|
if (recipes) {
|
||||||
|
for (let recipe of recipes) {
|
||||||
|
this.methods.push(new ItemNode(this.bot, this.name, this.quantity, this).setRecipe(recipe));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let block_source = mc.getItemBlockSource(this.name);
|
||||||
|
if (block_source) {
|
||||||
|
let tool = mc.getBlockTool(block_source);
|
||||||
|
this.methods.push(new ItemNode(this.bot, this.name, this.quantity, this).setCollectable(block_source, tool));
|
||||||
|
}
|
||||||
|
|
||||||
|
let smeltingIngredient = mc.getItemSmeltingIngredient(this.name);
|
||||||
|
if (smeltingIngredient) {
|
||||||
|
this.methods.push(new ItemNode(this.bot, this.name, this.quantity, this).setSmeltable(smeltingIngredient));
|
||||||
|
}
|
||||||
|
|
||||||
|
let animal_source = mc.getItemAnimalSource(this.name);
|
||||||
|
if (animal_source) {
|
||||||
|
this.methods.push(new ItemNode(this.bot, this.name, this.quantity, this).setHuntable(animal_source));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
containsCircularDependency() {
|
||||||
|
let p = this.parent;
|
||||||
|
while (p) {
|
||||||
|
if (p.name === this.name) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
p = p.parent;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getBestMethod() {
|
||||||
|
let best_cost = -1;
|
||||||
|
let best_method = null;
|
||||||
|
for (let method of this.methods) {
|
||||||
|
let cost = method.getDepth() + method.getFails();
|
||||||
|
if (best_cost == -1 || cost < best_cost) {
|
||||||
|
best_cost = cost;
|
||||||
|
best_method = method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best_method
|
||||||
|
}
|
||||||
|
|
||||||
|
getChildren() {
|
||||||
|
if (this.methods.length === 0)
|
||||||
|
return [];
|
||||||
|
return this.getBestMethod().getChildren();
|
||||||
|
}
|
||||||
|
|
||||||
|
isReady() {
|
||||||
|
if (this.methods.length === 0)
|
||||||
|
return false;
|
||||||
|
return this.getBestMethod().isReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
isDone() {
|
||||||
|
if (this.methods.length === 0)
|
||||||
|
return true;
|
||||||
|
return this.getBestMethod().isDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
getDepth() {
|
||||||
|
if (this.methods.length === 0)
|
||||||
|
return 0;
|
||||||
|
return this.getBestMethod().getDepth();
|
||||||
|
}
|
||||||
|
|
||||||
|
getFails() {
|
||||||
|
if (this.methods.length === 0)
|
||||||
|
return 0;
|
||||||
|
return this.getBestMethod().getFails();
|
||||||
|
}
|
||||||
|
|
||||||
|
getNext() {
|
||||||
|
if (this.methods.length === 0)
|
||||||
|
return null;
|
||||||
|
return this.getBestMethod().getNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class ItemGoal {
|
||||||
|
constructor(agent, timeout=-1) {
|
||||||
|
this.agent = agent;
|
||||||
|
this.timeout = timeout;
|
||||||
|
this.goal = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setGoal(goal, quantity=1) {
|
||||||
|
this.goal = new ItemWrapper(this.agent.bot, goal, quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeNext() {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
let next = this.goal.getNext();
|
||||||
|
|
||||||
|
await this.agent.coder.execute(async () => {
|
||||||
|
await next.execute();
|
||||||
|
}, this.timeout);
|
||||||
|
|
||||||
|
if (next.isDone()) {
|
||||||
|
console.log(`Successfully obtained ${next.quantity} ${next.name} for goal ${this.goal.name}`);
|
||||||
|
} else {
|
||||||
|
console.log(`Failed to obtain ${next.quantity} ${next.name} for goal ${this.goal.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,7 +35,7 @@ function equipHighestAttack(bot) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function craftRecipe(bot, itemName) {
|
export async function craftRecipe(bot, itemName, num=1) {
|
||||||
/**
|
/**
|
||||||
* Attempt to craft the given item name from a recipe. May craft many items.
|
* Attempt to craft the given item name from a recipe. May craft many items.
|
||||||
* @param {MinecraftBot} bot, reference to the minecraft bot.
|
* @param {MinecraftBot} bot, reference to the minecraft bot.
|
||||||
|
@ -85,7 +85,7 @@ export async function craftRecipe(bot, itemName) {
|
||||||
|
|
||||||
const recipe = recipes[0];
|
const recipe = recipes[0];
|
||||||
console.log('crafting...');
|
console.log('crafting...');
|
||||||
await bot.craft(recipe, 1, craftingTable);
|
await bot.craft(recipe, num, craftingTable);
|
||||||
log(bot, `Successfully crafted ${itemName}, you now have ${world.getInventoryCounts(bot)[itemName]} ${itemName}.`);
|
log(bot, `Successfully crafted ${itemName}, you now have ${world.getInventoryCounts(bot)[itemName]} ${itemName}.`);
|
||||||
if (placedTable) {
|
if (placedTable) {
|
||||||
await collectBlock(bot, 'crafting_table', 1);
|
await collectBlock(bot, 'crafting_table', 1);
|
||||||
|
@ -114,7 +114,16 @@ export async function smeltItem(bot, itemName, num=1) {
|
||||||
let furnaceBlock = undefined;
|
let furnaceBlock = undefined;
|
||||||
furnaceBlock = world.getNearestBlock(bot, 'furnace', 6);
|
furnaceBlock = world.getNearestBlock(bot, 'furnace', 6);
|
||||||
if (!furnaceBlock){
|
if (!furnaceBlock){
|
||||||
log(bot, `There is no furnace nearby.`)
|
// Try to place furnace
|
||||||
|
let hasFurnace = world.getInventoryCounts(bot)['furnace'] > 0;
|
||||||
|
if (hasFurnace) {
|
||||||
|
let pos = world.getNearestFreeSpace(bot, 1, 6);
|
||||||
|
await placeBlock(bot, 'furnace', pos.x, pos.y, pos.z);
|
||||||
|
furnaceBlock = world.getNearestBlock(bot, 'furnace', 6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!furnaceBlock){
|
||||||
|
log(bot, `There is no furnace nearby and you have no furnace.`)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
await bot.lookAt(furnaceBlock.position);
|
await bot.lookAt(furnaceBlock.position);
|
||||||
|
|
|
@ -172,7 +172,6 @@ export function getInventoryCounts(bot) {
|
||||||
inventory[item.name] += item.count;
|
inventory[item.name] += item.count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log(inventory)
|
|
||||||
return inventory;
|
return inventory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,12 +42,20 @@ export function isHostile(mob) {
|
||||||
return (mob.type === 'mob' || mob.type === 'hostile') && mob.name !== 'iron_golem' && mob.name !== 'snow_golem';
|
return (mob.type === 'mob' || mob.type === 'hostile') && mob.name !== 'iron_golem' && mob.name !== 'snow_golem';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getItemId(item) {
|
export function getItemId(itemName) {
|
||||||
return mcdata.itemsByName[item].id;
|
let item = mcdata.itemsByName[itemName];
|
||||||
|
if (item) {
|
||||||
|
return item.id;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getItemName(itemId) {
|
export function getItemName(itemId) {
|
||||||
return mcdata.items[itemId].name;
|
let item = mcdata.items[itemId]
|
||||||
|
if (item) {
|
||||||
|
return item.name;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAllItems(ignore) {
|
export function getAllItems(ignore) {
|
||||||
|
@ -99,3 +107,79 @@ export function getAllBlockIds(ignore) {
|
||||||
export function getAllBiomes() {
|
export function getAllBiomes() {
|
||||||
return mcdata.biomes;
|
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 getItemBlockSource(itemName) {
|
||||||
|
let itemId = getItemId(itemName);
|
||||||
|
for (let block of getAllBlocks()) {
|
||||||
|
if (block.drops.includes(itemId)) {
|
||||||
|
return block.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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'
|
||||||
|
}[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