Merge pull request #18 from kolbytn/cleanup

Cleanup
This commit is contained in:
Kolby Nottingham 2024-01-26 14:30:52 -08:00 committed by GitHub
commit f18b0b88da
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 241 additions and 218 deletions

View file

@ -2,6 +2,5 @@
"name": "andy",
"bio": "You are playing minecraft and assisting other players in tasks.",
"memory": "",
"events": [],
"turns": []
}

View file

@ -1,5 +1,5 @@
import * as skills from '../../../src/agent/skills.js';
import * as world from '../../../src/agent/world.js';
import * as skills from '../../../src/agent/library/skills.js';
import * as world from '../../../src/agent/library/world.js';
import Vec3 from 'vec3';
const log = skills.log;

View file

@ -1,11 +1,10 @@
import { History } from './history.js';
import { Coder } from './coder.js';
import { initModes } from './modes.js';
import { Examples } from '../utils/examples.js';
import { initBot } from '../utils/mcdata.js';
import { sendRequest } from '../utils/gpt.js';
import { History } from './history.js';
import { Examples } from './examples.js';
import { Coder } from './coder.js';
import { containsCommand, commandExists, executeCommand } from './commands.js';
import { Events } from './events.js';
import { initModes } from './modes.js';
import { containsCommand, commandExists, executeCommand } from './commands/index.js';
export class Agent {
@ -21,10 +20,7 @@ export class Agent {
this.bot = initBot(name);
this.events = new Events(this, this.history.events);
initModes(this);
this.idle = true;
this.bot.on('login', async () => {
@ -63,7 +59,7 @@ export class Agent {
this.bot.emit('finished_executing');
}
this.startUpdateLoop();
this.startEvents();
});
}
@ -130,7 +126,24 @@ export class Agent {
this.bot.emit('finished_executing');
}
startUpdateLoop() {
startEvents() {
// Custom events
this.bot.on('time', () => {
if (this.bot.time.timeOfDay == 0)
this.bot.emit('sunrise');
else if (this.bot.time.timeOfDay == 6000)
this.bot.emit('noon');
else if (this.bot.time.timeOfDay == 12000)
this.bot.emit('sunset');
else if (this.bot.time.timeOfDay == 18000)
this.bot.emit('midnight');
});
this.bot.on('health', () => {
if (this.bot.health < 20)
this.bot.emit('damaged');
});
// Logging callbacks
this.bot.on('error' , (err) => {
console.error('Error event!', err);
});
@ -152,13 +165,13 @@ export class Agent {
}
});
this.self_defense = true;
this.defending = false;
this._pause_defending = false;
// set interval every 300ms to update the bot's state
this.update_interval = setInterval(async () => {
this.bot.modes.update();
}, 300);
}
isIdle() {
return !this.coder.executing && !this.coder.generating;
}
}

View file

@ -1,16 +1,16 @@
import { writeFile, readFile, mkdirSync } from 'fs';
import { sendRequest } from '../utils/gpt.js';
import { getSkillDocs } from './skill-library.js';
import { Examples } from './examples.js';
import { getSkillDocs } from './library/index.js';
import { Examples } from '../utils/examples.js';
export class Coder {
constructor(agent) {
this.agent = agent;
this.current_code = '';
this.file_counter = 0;
this.fp = '/bots/'+agent.name+'/action-code/';
this.executing = false;
this.generating = false;
this.code_template = '';
this.timedout = false;
}
@ -59,12 +59,11 @@ export class Coder {
console.error('Error writing code execution file: ' + result);
return null;
}
this.current_code = code;
return await import('../..' + this.fp + filename);
}
santitizeCode(code) {
const remove_strs = ['javascript', 'js']
const remove_strs = ['Javascript', 'javascript', 'js']
for (let r of remove_strs) {
if (code.startsWith(r)) {
code = code.slice(r.length);
@ -89,6 +88,15 @@ export class Coder {
async generateCode(agent_history) {
// wrapper to prevent overlapping code generation loops
await this.stop();
this.generating = true;
await this.generateCodeLoop(agent_history);
this.generating = false;
}
async generateCodeLoop(agent_history) {
let system_message = "You are a minecraft mineflayer bot that plays minecraft by writing javascript codeblocks. Given the conversation between you and the user, use the provided skills and world functions to write your code in a codeblock. Example response: ``` // your code here ``` You will then be given a response to your code. If you are satisfied with the response, respond without a codeblock in a conversational way. If something went wrong, write another codeblock and try to fix the problem.";
system_message += getSkillDocs();
@ -99,6 +107,8 @@ export class Coder {
let code_return = null;
let failures = 0;
for (let i=0; i<5; i++) {
if (this.agent.bot.interrupt_code)
return;
console.log(messages)
let res = await sendRequest(messages, system_message);
console.log('Code generation response:', res)
@ -144,12 +154,8 @@ export class Coder {
role: 'system',
content: code_return.message
});
if (this.agent.bot.interrupt_code)
return;
}
return
return;
}
// returns {success: bool, message: string, interrupted: bool, timedout: false}
@ -173,7 +179,6 @@ export class Coder {
let interrupted = this.agent.bot.interrupt_code;
let timedout = this.timedout;
this.clear();
this.agent.bot.emit("code_terminated");
return {success:true, message: output, interrupted, timedout};
} catch (err) {
this.executing = false;
@ -181,11 +186,10 @@ export class Coder {
console.error("Code execution triggered catch: " + err);
await this.stop();
let message = this.formatOutput(this.agent.bot);
message += '!!Code threw exception!! Error: ' + err;
let message = this.formatOutput(this.agent.bot) + '!!Code threw exception!! Error: ' + err;
let interrupted = this.agent.bot.interrupt_code;
this.clear();
this.agent.bot.emit("code_terminated");
return {success: false, message, interrupted, timedout: false};
}
}
@ -217,7 +221,6 @@ export class Coder {
}
clear() {
this.current_code = '';
this.agent.bot.output = '';
this.agent.bot.interrupt_code = false;
this.timedout = false;

View file

@ -1,15 +1,13 @@
import * as skills from '../skills.js';
import * as world from '../world.js';
import * as skills from '../library/skills.js';
function wrapExecution(func, timeout=-1) {
return async function (agent, ...args) {
agent.idle = false;
let code_return = await agent.coder.execute(async () => {
await func(agent, ...args);
}, timeout);
if (code_return.interrupted && !code_return.timedout)
return;
agent.idle = true;
return code_return.message;
}
}
@ -19,11 +17,7 @@ export const actionsList = [
name: '!newAction',
description: 'Perform new and unknown custom behaviors that are not available as a command by writing code.',
perform: async function (agent) {
agent.idle = false;
let res = await agent.coder.generateCode(agent.history);
agent.idle = true;
if (res)
return '\n' + res + '\n';
await agent.coder.generateCode(agent.history);
}
},
{
@ -32,7 +26,6 @@ export const actionsList = [
perform: async function (agent) {
await agent.coder.stop();
agent.coder.clear();
agent.idle = true;
return 'Agent stopped.';
}
},
@ -46,7 +39,7 @@ export const actionsList = [
perform: async function (agent, mode_name, on) {
const modes = agent.bot.modes;
if (!modes.exists(mode_name))
return `Mode ${mode_name} does not exist.` + modes.getDocs();
return `Mode ${mode_name} does not exist.` + modes.getStr();
if (modes.isOn(mode_name) === on)
return `Mode ${mode_name} is already ${on ? 'on' : 'off'}.`;
modes.setOn(mode_name, on);
@ -69,6 +62,18 @@ export const actionsList = [
await skills.followPlayer(agent.bot, player_name);
})
},
{
name: '!givePlayer',
description: 'Give the specified item to the given player. Ex: !givePlayer("steve", "stone_pickaxe", 1)',
params: {
'player_name': '(string) The name of the player to give the item to.',
'item_name': '(string) The name of the item to give.' ,
'num': '(number) The number of items to give.'
},
perform: wrapExecution(async (agent, player_name, item_name, num) => {
await skills.giveToPlayer(agent.bot, item_name, player_name, num);
})
},
{
name: '!collectBlocks',
description: 'Collect the nearest blocks of a given type.',

View file

@ -1,6 +1,6 @@
import { actionsList } from './actions.js';
import { queryList } from './queries.js';
import { actionsList } from './commands/actions.js';
import { queryList } from './commands/queries.js';
const commandList = queryList.concat(actionsList);
const commandMap = {};
@ -98,4 +98,4 @@ export function getCommandDocs() {
}
}
return docs + '*\n';
}
}

View file

@ -1,5 +1,6 @@
import { getNearestBlock, getNearbyEntityTypes, getNearbyPlayerNames, getNearbyBlockTypes, getInventoryCounts } from '../world.js';
import { getAllItems, getBiomeName } from '../../utils/mcdata.js';
import * as world from '../library/world.js';
import * as mc from '../../utils/mcdata.js';
const pad = (str) => {
return '\n' + str + '\n';
@ -18,7 +19,7 @@ export const queryList = [
res += `\n- Position: x: ${pos.x.toFixed(2)}, y: ${pos.y.toFixed(2)}, z: ${pos.z.toFixed(2)}`;
res += `\n- Health: ${Math.round(bot.health)} / 20`;
res += `\n- Hunger: ${Math.round(bot.food)} / 20`;
res += `\n- Biome: ${getBiomeName(bot)}`;
res += `\n- Biome: ${world.getBiomeName(bot)}`;
let weather = "clear";
if (bot.rainState > 0)
weather = "Rain";
@ -45,7 +46,7 @@ export const queryList = [
description: "Get your bot's inventory.",
perform: function (agent) {
let bot = agent.bot;
let inventory = getInventoryCounts(bot);
let inventory = world.getInventoryCounts(bot);
let res = 'INVENTORY';
for (const item in inventory) {
if (inventory[item] && inventory[item] > 0)
@ -63,7 +64,7 @@ export const queryList = [
perform: function (agent) {
let bot = agent.bot;
let res = 'NEARBY_BLOCKS';
let blocks = getNearbyBlockTypes(bot);
let blocks = world.getNearbyBlockTypes(bot);
for (let i = 0; i < blocks.length; i++) {
res += `\n- ${blocks[i]}`;
}
@ -78,9 +79,9 @@ export const queryList = [
description: "Get the craftable items with the bot's inventory.",
perform: function (agent) {
const bot = agent.bot;
const table = getNearestBlock(bot, 'crafting_table');
const table = world.getNearestBlock(bot, 'crafting_table');
let res = 'CRAFTABLE_ITEMS';
for (const item of getAllItems()) {
for (const item of mc.getAllItems()) {
let recipes = bot.recipesFor(item.id, null, 1, table);
if (recipes.length > 0) {
res += `\n- ${item.name}`;
@ -98,10 +99,10 @@ export const queryList = [
perform: function (agent) {
let bot = agent.bot;
let res = 'NEARBY_ENTITIES';
for (const entity of getNearbyPlayerNames(bot)) {
for (const entity of world.getNearbyPlayerNames(bot)) {
res += `\n- player: ${entity}`;
}
for (const entity of getNearbyEntityTypes(bot)) {
for (const entity of world.getNearbyEntityTypes(bot)) {
res += `\n- mob: ${entity}`;
}
if (res == 'NEARBY_ENTITIES') {
@ -114,14 +115,7 @@ export const queryList = [
name: "!modes",
description: "Get all available modes and see which are on/off.",
perform: function (agent) {
return agent.bot.modes.getDocs();
return agent.bot.modes.getStr();
}
},
{
name: "!currentAction",
description: "Get the currently executing code.",
perform: function (agent) {
return pad("Current code:\n`" + agent.coder.current_code +"`");
}
},
];
}
];

View file

@ -1,47 +0,0 @@
export class Events {
constructor(agent, events) {
this.events = events;
if (agent != null)
this.init(agent, events);
}
init(agent, events) {
this.events = events;
for (let [event, callback, params] of events) {
if (callback != null)
agent.bot.on(event, this[callback].bind(this, agent, params));
}
agent.bot.on('time', () => {
if (agent.bot.time.timeOfDay == 0)
agent.bot.emit('sunrise');
else if (agent.bot.time.timeOfDay == 6000)
agent.bot.emit('noon');
else if (agent.bot.time.timeOfDay == 12000)
agent.bot.emit('sunset');
else if (agent.bot.time.timeOfDay == 18000)
agent.bot.emit('midnight');
});
agent.bot.on('health', () => {
if (agent.bot.health < 20)
agent.bot.emit('damaged');
});
}
async executeCode(agent, code) {
console.log('responding to event with code.');
agent.coder.queueCode(code);
let code_return = await agent.coder.execute();
console.log('code return:', code_return.message);
agent.history.add('system', code_return.message);
}
sendThought(agent, message) {
agent.handleMessage(agent.name, message);
}
sendChat(agent, message) {
agent.bot.chat(message);
}
}

View file

@ -1,7 +1,7 @@
import { writeFileSync, readFileSync, mkdirSync } from 'fs';
import { getCommandDocs } from './commands.js';
import { sendRequest } from '../utils/gpt.js';
import { stringifyTurns } from '../utils/text.js';
import { sendRequest } from '../utils/gpt.js';
import { getCommandDocs } from './commands/index.js';
export class History {
@ -14,9 +14,6 @@ export class History {
this.bio = '';
this.memory = '';
// The bot's events
this.events = [];
// Variables for controlling the agent's memory and knowledge
this.max_messages = 20;
}
@ -91,7 +88,6 @@ export class History {
'name': this.name,
'bio': this.bio,
'memory': this.memory,
'events': this.events,
'turns': this.turns
};
const json_data = JSON.stringify(data, null, 4);
@ -111,7 +107,6 @@ export class History {
const obj = JSON.parse(data);
this.bio = obj.bio;
this.memory = obj.memory;
this.events = obj.events;
this.turns = obj.turns;
} catch (err) {
console.error(`No file for profile '${load_path}' for agent ${this.name}.`);

View file

@ -1,12 +1,6 @@
import * as skills from './skills.js';
import * as world from './world.js';
export function getSkillDocs() {
let docstring = "\n*SKILL DOCS\nThese skills are javascript functions that can be called when writing actions and skills.\n";
docstring += docHelper(Object.values(skills), 'skills');
docstring += docHelper(Object.values(world), 'world');
return docstring + '*\n';
}
export function docHelper(functions, module_name) {
let docstring = '';
@ -20,6 +14,9 @@ export function docHelper(functions, module_name) {
return docstring;
}
export function containsCodeBlock(message) {
return message.indexOf('```') !== -1;
export function getSkillDocs() {
let docstring = "\n*SKILL DOCS\nThese skills are javascript functions that can be called when writing actions and skills.\n";
docstring += docHelper(Object.values(skills), 'skills');
docstring += docHelper(Object.values(world), 'world');
return docstring + '*\n';
}

View file

@ -1,14 +1,39 @@
import { getItemId, getItemName } from "../utils/mcdata.js";
import { getNearestBlocks, getNearestBlock, getInventoryCounts, getNearestEntityWhere, getNearbyEntities, getNearbyBlocks } from "./world.js";
import * as mc from "../../utils/mcdata.js";
import * as world from "./world.js";
import pf from 'mineflayer-pathfinder';
import Vec3 from 'vec3';
export function log(bot, message, chat=false) {
bot.output += message + '\n';
if (chat)
bot.chat(message);
}
async function autoLight(bot) {
if (bot.modes.isOn('torch_placing') && !bot.interrupt_code) {
let nearest_torch = world.getNearestBlock(bot, 'torch', 8);
if (!nearest_torch) {
let has_torch = bot.inventory.items().find(item => item.name === 'torch');
if (has_torch) {
try {
log(bot, `Placing torch at ${bot.entity.position}.`);
await placeBlock(bot, 'torch', bot.entity.position.x, bot.entity.position.y, bot.entity.position.z);
return true;
} catch (err) {return true;}
}
}
}
return false;
}
function equipHighestAttack(bot) {
let weapons = bot.inventory.items().filter(item => item.name.includes('sword') || item.name.includes('axe') || item.name.includes('pickaxe') || item.name.includes('shovel'));
let weapon = weapons.sort((a, b) => b.attackDamage - a.attackDamage)[0];
if (weapon)
bot.equip(weapon, 'hand');
}
export async function craftRecipe(bot, itemName) {
/**
@ -19,25 +44,52 @@ export async function craftRecipe(bot, itemName) {
* @example
* await skills.craftRecipe(bot, "stick");
**/
let recipes = bot.recipesFor(getItemId(itemName), null, 1, null); // get recipes that don't require a crafting table
let placedTable = false;
// get recipes that don't require a crafting table
let recipes = bot.recipesFor(mc.getItemId(itemName), null, 1, null);
let craftingTable = null;
if (!recipes || recipes.length === 0) {
craftingTable = getNearestBlock(bot, 'crafting_table', 6);
// Look for crafting table
craftingTable = world.getNearestBlock(bot, 'crafting_table', 6);
if (craftingTable === null){
log(bot, `You either do not have enough resources to craft ${itemName} or it requires a crafting table, but there is none nearby.`)
return false;
// Try to place crafting table
let hasTable = world.getInventoryCounts(bot)['crafting_table'] > 0;
if (hasTable) {
let pos = world.getNearestFreeSpace(bot, 1, 6);
await placeBlock(bot, 'crafting_table', pos.x, pos.y, pos.z);
craftingTable = world.getNearestBlock(bot, 'crafting_table', 6);
if (craftingTable) {
recipes = bot.recipesFor(mc.getItemId(itemName), null, 1, craftingTable);
placedTable = true;
}
}
else {
log(bot, `You either do not have enough resources to craft ${itemName} or it requires a crafting table.`)
return false;
}
}
else {
recipes = bot.recipesFor(mc.getItemId(itemName), null, 1, craftingTable);
}
recipes = bot.recipesFor(getItemId(itemName), null, 1, craftingTable);
}
if (!recipes || recipes.length === 0) {
log(bot, `You do not have the resources to craft a ${itemName}.`);
if (placedTable) {
await collectBlock(bot, 'crafting_table', 1);
}
return false;
}
const recipe = recipes[0];
const recipe = recipes[0];
console.log('crafting...');
await bot.craft(recipe, 1, craftingTable);
log(bot, `Successfully crafted ${itemName}, you now have ${getInventoryCounts(bot)[itemName]} ${itemName}.`);
log(bot, `Successfully crafted ${itemName}, you now have ${world.getInventoryCounts(bot)[itemName]} ${itemName}.`);
if (placedTable) {
await collectBlock(bot, 'crafting_table', 1);
}
return true;
}
@ -60,7 +112,7 @@ export async function smeltItem(bot, itemName, num=1) {
} // TODO: allow cobblestone, sand, clay, etc.
let furnaceBlock = undefined;
furnaceBlock = getNearestBlock(bot, 'furnace', 6);
furnaceBlock = world.getNearestBlock(bot, 'furnace', 6);
if (!furnaceBlock){
log(bot, `There is no furnace nearby.`)
return false;
@ -71,14 +123,14 @@ export async function smeltItem(bot, itemName, num=1) {
const furnace = await bot.openFurnace(furnaceBlock);
// check if the furnace is already smelting something
let input_item = furnace.inputItem();
if (input_item && input_item.type !== getItemId(itemName) && input_item.count > 0) {
if (input_item && input_item.type !== mc.getItemId(itemName) && input_item.count > 0) {
// TODO: check if furnace is currently burning fuel. furnace.fuel is always null, I think there is a bug.
// This only checks if the furnace has an input item, but it may not be smelting it and should be cleared.
log(bot, `The furnace is currently smelting ${getItemName(input_item.type)}.`);
log(bot, `The furnace is currently smelting ${mc.getItemName(input_item.type)}.`);
return false;
}
// check if the bot has enough items to smelt
let inv_counts = getInventoryCounts(bot);
let inv_counts = world.getInventoryCounts(bot);
if (!inv_counts[itemName] || inv_counts[itemName] < num) {
log(bot, `You do not have enough ${itemName} to smelt.`);
return false;
@ -93,11 +145,11 @@ export async function smeltItem(bot, itemName, num=1) {
return false;
}
await furnace.putFuel(fuel.type, null, put_fuel);
log(bot, `Added ${put_fuel} ${getItemName(fuel.type)} to furnace fuel.`);
console.log(`Added ${put_fuel} ${getItemName(fuel.type)} to furnace fuel.`)
log(bot, `Added ${put_fuel} ${mc.getItemName(fuel.type)} to furnace fuel.`);
console.log(`Added ${put_fuel} ${mc.getItemName(fuel.type)} to furnace fuel.`)
}
// put the items in the furnace
await furnace.putInput(getItemId(itemName), null, num);
await furnace.putInput(mc.getItemId(itemName), null, num);
// wait for the items to smelt
let total = 0;
let collected_last = true;
@ -128,10 +180,10 @@ export async function smeltItem(bot, itemName, num=1) {
return false;
}
if (total < num) {
log(bot, `Only smelted ${total} ${getItemName(smelted_item.type)}.`);
log(bot, `Only smelted ${total} ${mc.getItemName(smelted_item.type)}.`);
return false;
}
log(bot, `Successfully smelted ${itemName}, got ${total} ${getItemName(smelted_item.type)}.`);
log(bot, `Successfully smelted ${itemName}, got ${total} ${mc.getItemName(smelted_item.type)}.`);
return true;
}
@ -143,7 +195,7 @@ export async function clearNearestFurnace(bot) {
* @example
* await skills.clearNearestFurnace(bot);
**/
let furnaceBlock = getNearestBlock(bot, 'furnace', 6);
let furnaceBlock = world.getNearestBlock(bot, 'furnace', 6);
if (!furnaceBlock){
log(bot, `There is no furnace nearby.`)
return false;
@ -170,14 +222,6 @@ export async function clearNearestFurnace(bot) {
}
function equipHighestAttack(bot) {
let weapons = bot.inventory.items().filter(item => item.name.includes('sword') || item.name.includes('axe') || item.name.includes('pickaxe') || item.name.includes('shovel'));
let weapon = weapons.sort((a, b) => b.attackDamage - a.attackDamage)[0];
if (weapon)
bot.equip(weapon, 'hand');
}
export async function attackNearest(bot, mobType, kill=true) {
/**
* Attack mob of the given type.
@ -221,7 +265,7 @@ export async function attackEntity(bot, entity, kill=true) {
}
else {
bot.pvp.attack(entity);
while (getNearbyEntities(bot, 16).includes(entity)) {
while (world.getNearbyEntities(bot, 16).includes(entity)) {
await new Promise(resolve => setTimeout(resolve, 1000));
if (bot.interrupt_code) {
bot.pvp.stop();
@ -245,7 +289,7 @@ export async function defendSelf(bot, range=8) {
* **/
bot.modes.pause('self_defense');
let attacked = false;
let enemy = getNearestEntityWhere(bot, entity => isHostile(entity), range);
let enemy = world.getNearestEntityWhere(bot, entity => mc.isHostile(entity), range);
while (enemy) {
equipHighestAttack(bot);
if (bot.entity.position.distanceTo(enemy.position) > 4 && enemy.name !== 'creeper' && enemy.name !== 'phantom') {
@ -257,7 +301,7 @@ export async function defendSelf(bot, range=8) {
bot.pvp.attack(enemy);
attacked = true;
await new Promise(resolve => setTimeout(resolve, 500));
enemy = getNearestEntityWhere(bot, entity => isHostile(entity), range);
enemy = world.getNearestEntityWhere(bot, entity => mc.isHostile(entity), range);
if (bot.interrupt_code) {
bot.pvp.stop();
return false;
@ -288,7 +332,7 @@ export async function collectBlock(bot, blockType, num=1) {
return false;
}
let collected = 0;
const blocks = getNearestBlocks(bot, blockType, 64, num);
const blocks = world.getNearestBlocks(bot, blockType, 64, num);
if (blocks.length === 0) {
log(bot, `Could not find any ${blockType} to collect.`);
return false;
@ -553,7 +597,7 @@ export async function goToPosition(bot, x, y, z, min_distance=2) {
* @param {number} distance, the distance to keep from the position. Defaults to 2.
* @returns {Promise<boolean>} true if the position was reached, false otherwise.
* @example
* let position = world.getNearestBlock(bot, "oak_log", 64).position;
* let position = world.world.getNearestBlock(bot, "oak_log", 64).position;
* await skills.goToPosition(bot, position.x, position.y, position.x + 20);
**/
if (x == null || y == null || z == null) {
@ -616,7 +660,7 @@ export async function followPlayer(bot, username) {
while (!bot.interrupt_code) {
let acted = false;
if (bot.modes.isOn('self_defense')) {
const enemy = getNearestEntityWhere(bot, entity => isHostile(entity), attack_distance);
const enemy = world.getNearestEntityWhere(bot, entity => mc.isHostile(entity), attack_distance);
if (enemy) {
log(bot, `Found ${enemy.name}, attacking!`, true);
await defendSelf(bot, 8);
@ -624,7 +668,7 @@ export async function followPlayer(bot, username) {
}
}
if (bot.modes.isOn('hunting')) {
const animal = getNearestEntityWhere(bot, entity => isHuntable(entity), attack_distance);
const animal = world.getNearestEntityWhere(bot, entity => mc.isHuntable(entity), attack_distance);
if (animal) {
log(bot, `Hunting ${animal.name}!`, true);
await attackEntity(bot, animal, true);
@ -676,34 +720,3 @@ export async function goToBed(bot) {
log(bot, `You have woken up.`);
return true;
}
export function isHuntable(mob) {
if (!mob || !mob.name) return false;
const animals = ['chicken', 'cod', 'cow', 'llama', 'mooshroom', 'pig', 'pufferfish', 'rabbit', 'salmon', 'sheep', 'squid', 'tropical_fish', 'turtle'];
return animals.includes(mob.name.toLowerCase()) && !mob.metadata[16]; // metadata 16 is not baby
}
export function isHostile(mob) {
if (!mob || !mob.name) return false;
return (mob.type === 'mob' || mob.type === 'hostile') && mob.name !== 'iron_golem' && mob.name !== 'snow_golem';
}
async function autoLight(bot) {
if (bot.modes.isOn('torch_placing') && !bot.interrupt_code) {
let nearest_torch = getNearestBlock(bot, 'torch', 8);
if (!nearest_torch) {
let has_torch = bot.inventory.items().find(item => item.name === 'torch');
if (has_torch) {
try {
log(bot, `Placing torch at ${bot.entity.position}.`);
await placeBlock(bot, 'torch', bot.entity.position.x, bot.entity.position.y, bot.entity.position.z);
return true;
} catch (err) {return true;}
}
}
}
return false;
}

View file

@ -1,4 +1,40 @@
import { getAllBlockIds } from '../utils/mcdata.js';
import { getAllBlockIds } from '../../utils/mcdata.js';
export function getNearestFreeSpace(bot, size=1, distance=8) {
/**
* Get the nearest empty space with solid blocks beneath it of the given size.
* @param {Bot} bot - The bot to get the nearest free space for.
* @param {number} size - The (size x size) of the space to find, default 1.
* @param {number} distance - The maximum distance to search, default 8.
* @returns {Vec3} - The south west corner position of the nearest free space.
* @example
* let position = world.getNearestFreeSpace(bot, 1, 8);
**/
let empty_pos = bot.findBlocks({
matching: (block) => {
return block && block.name == 'air';
},
maxDistance: distance,
count: 1000
});
for (let i = 0; i < empty_pos.length; i++) {
let empty = true;
for (let x = 0; x < size; x++) {
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) {
empty = false;
break;
}
}
}
if (empty) {
return empty_pos[i];
}
}
}
export function getNearestBlocks(bot, block_types, distance=16, count=1) {
@ -206,3 +242,16 @@ export function getNearbyBlockTypes(bot, distance=16) {
}
return found;
}
export function getBiomeName(bot) {
/**
* Get the name of the biome the bot is in.
* @param {Bot} bot - The bot to get the biome for.
* @returns {string} - The name of the biome.
* @example
* let biome = world.getBiomeName(bot);
**/
const biomeId = bot.world.getBiome(bot.entity.position);
return mcdata.biomes[biomeId].name;
}

View file

@ -1,5 +1,7 @@
import * as skills from './skills.js';
import * as world from './world.js';
import * as skills from './library/skills.js';
import * as world from './library/world.js';
import * as mc from '../utils/mcdata.js';
// a mode is a function that is called every tick to respond immediately to the world
// it has the following fields:
@ -17,7 +19,7 @@ const modes = [
active: false,
update: function (agent) {
if (this.active) return;
const enemy = world.getNearestEntityWhere(agent.bot, entity => skills.isHostile(entity), 8);
const enemy = world.getNearestEntityWhere(agent.bot, entity => mc.isHostile(entity), 8);
if (enemy) {
agent.bot.chat(`Fighting ${enemy.name}!`);
execute(this, agent, async () => {
@ -32,8 +34,8 @@ const modes = [
on: true,
active: false,
update: function (agent) {
if (agent.idle) {
const huntable = world.getNearestEntityWhere(agent.bot, entity => skills.isHuntable(entity), 8);
if (agent.isIdle()) {
const huntable = world.getNearestEntityWhere(agent.bot, entity => mc.isHuntable(entity), 8);
if (huntable) {
execute(this, agent, async () => {
agent.bot.chat(`Hunting ${huntable.name}!`);
@ -49,7 +51,7 @@ const modes = [
on: true,
active: false,
update: function (agent) {
if (agent.idle) {
if (agent.isIdle()) {
let item = world.getNearestEntityWhere(agent.bot, entity => entity.name === 'item', 8);
if (item) {
execute(this, agent, async () => {
@ -68,7 +70,7 @@ const modes = [
active: false,
update: function (agent) {
if (this.active) return;
if (agent.idle) {
if (agent.isIdle()) {
// TODO: check light level instead of nearby torches, block.light is broken
const near_torch = world.getNearestBlock(agent.bot, 'torch', 8);
if (!near_torch) {
@ -94,7 +96,7 @@ const modes = [
last_entity: null,
next_change: 0,
update: function (agent) {
if (agent.idle) {
if (agent.isIdle()) {
this.active = true;
const entity = agent.bot.nearestEntity();
let entity_in_view = entity && entity.position.distanceTo(agent.bot.entity.position) < 10 && entity.name !== 'enderman';
@ -129,13 +131,10 @@ const modes = [
async function execute(mode, agent, func, timeout=-1) {
mode.active = true;
await agent.coder.stop();
agent.idle = false;
let code_return = await agent.coder.execute(async () => {
await func();
}, timeout);
mode.active = false;
agent.idle = true;
console.log(`Mode ${mode.name} finished executing, code_return: ${code_return.message}`);
}
@ -165,7 +164,7 @@ class ModeController {
this.modes_map[mode_name].paused = true;
}
getDocs() {
getStr() {
let res = 'Available Modes:';
for (let mode of this.modes_list) {
let on = mode.on ? 'ON' : 'OFF';
@ -175,7 +174,7 @@ class ModeController {
}
update() {
if (this.agent.idle) {
if (this.agent.isIdle()) {
// other actions might pause a mode to override it
// when idle, unpause all modes
for (let mode of this.modes_list) {
@ -197,4 +196,4 @@ class ModeController {
export function initModes(agent) {
// the mode controller is added to the bot object so it is accessible from anywhere the bot is used
agent.bot.modes = new ModeController(agent);
}
}

View file

@ -1,6 +1,6 @@
import { readFileSync } from 'fs';
import { embed, cosineSimilarity } from '../utils/gpt.js';
import { stringifyTurns } from '../utils/text.js';
import { embed, cosineSimilarity } from './gpt.js';
import { stringifyTurns } from './text.js';
export class Examples {

View file

@ -33,6 +33,17 @@ export function initBot(username) {
return bot;
}
export function isHuntable(mob) {
if (!mob || !mob.name) return false;
const animals = ['chicken', 'cod', 'cow', 'llama', 'mooshroom', 'pig', 'pufferfish', 'rabbit', 'salmon', 'sheep', 'squid', 'tropical_fish', 'turtle'];
return animals.includes(mob.name.toLowerCase()) && !mob.metadata[16]; // metadata 16 is not baby
}
export function isHostile(mob) {
if (!mob || !mob.name) return false;
return (mob.type === 'mob' || mob.type === 'hostile') && mob.name !== 'iron_golem' && mob.name !== 'snow_golem';
}
export function getItemId(item) {
return mcdata.itemsByName[item].id;
}
@ -55,7 +66,6 @@ export function getAllItems(ignore) {
return items;
}
export function getAllItemIds(ignore) {
const items = getAllItems(ignore);
let itemIds = [];
@ -65,7 +75,6 @@ export function getAllItemIds(ignore) {
return itemIds;
}
export function getAllBlocks(ignore) {
if (!ignore) {
ignore = [];
@ -80,7 +89,6 @@ export function getAllBlocks(ignore) {
return blocks;
}
export function getAllBlockIds(ignore) {
const blocks = getAllBlocks(ignore);
let blockIds = [];
@ -89,8 +97,3 @@ export function getAllBlockIds(ignore) {
}
return blockIds;
}
export function getBiomeName(bot) {
const biomeId = bot.world.getBiome(bot.entity.position);
return mcdata.biomes[biomeId].name;
}