Merge pull request #20 from kolbytn/clearpath

Clearpath
This commit is contained in:
Max Robinson 2024-01-29 23:17:45 -06:00 committed by GitHub
commit 4f284eeb71
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 83 additions and 20 deletions

View file

@ -75,6 +75,10 @@ export class Agent {
const user_command_name = containsCommand(message); const user_command_name = containsCommand(message);
if (user_command_name) { if (user_command_name) {
if (!commandExists(user_command_name)) {
this.bot.chat(`Command '${user_command_name}' does not exist.`);
return;
}
this.bot.chat(`*${source} used ${user_command_name.substring(1)}*`); this.bot.chat(`*${source} used ${user_command_name.substring(1)}*`);
let execute_res = await executeCommand(this, message); let execute_res = await executeCommand(this, message);
if (user_command_name === '!newAction') { if (user_command_name === '!newAction') {
@ -165,10 +169,18 @@ export class Agent {
} }
}); });
// set interval every 300ms to update the bot's state // This update loop ensures that each update() is called one at a time, even if it takes longer than the interval
this.update_interval = setInterval(async () => { const INTERVAL = 300;
this.bot.modes.update(); setTimeout(async () => {
}, 300); while (true) {
let start = Date.now();
await this.bot.modes.update();
let remaining = INTERVAL - (Date.now() - start);
if (remaining > 0) {
await new Promise((resolve) => setTimeout(resolve, remaining));
}
}
}, INTERVAL);
} }
isIdle() { isIdle() {

View file

@ -29,6 +29,21 @@ export const actionsList = [
return 'Agent stopped.'; return 'Agent stopped.';
} }
}, },
{
name: '!restart',
description: 'Restart the agent process.',
perform: async function (agent) {
process.exit(1);
}
},
{
name: '!clear',
description: 'Clear the chat history.',
perform: async function (agent) {
agent.history.clear();
return agent.name + "'s chat history was cleared, starting new conversation from scratch.";
}
},
{ {
name: '!setMode', name: '!setMode',
description: 'Set a mode to on or off. A mode is an automatic behavior that constantly checks and responds to the environment. Ex: !setMode("hunting", true)', description: 'Set a mode to on or off. A mode is an automatic behavior that constantly checks and responds to the environment. Ex: !setMode("hunting", true)',

View file

@ -112,4 +112,9 @@ export class History {
console.error(`No file for profile '${load_path}' for agent ${this.name}.`); console.error(`No file for profile '${load_path}' for agent ${this.name}.`);
} }
} }
clear() {
this.turns = [];
this.memory = '';
}
} }

View file

@ -331,8 +331,12 @@ export async function collectBlock(bot, blockType, num=1) {
log(bot, `Invalid number of blocks to collect: ${num}.`); log(bot, `Invalid number of blocks to collect: ${num}.`);
return false; return false;
} }
let blocktypes = [blockType];
if (blockType.endsWith('ore'))
blocktypes.push('deepslate_'+blockType);
let collected = 0; let collected = 0;
const blocks = world.getNearestBlocks(bot, blockType, 64, num); const blocks = world.getNearestBlocks(bot, blocktypes, 64, num);
if (blocks.length === 0) { if (blocks.length === 0) {
log(bot, `Could not find any ${blockType} to collect.`); log(bot, `Could not find any ${blockType} to collect.`);
return false; return false;

View file

@ -1,3 +1,4 @@
import pf from 'mineflayer-pathfinder';
import { getAllBlockIds } from '../../utils/mcdata.js'; import { getAllBlockIds } from '../../utils/mcdata.js';
@ -48,9 +49,12 @@ export function getNearestBlocks(bot, block_types, distance=16, count=1) {
* @example * @example
* let woodBlocks = world.getNearestBlocks(bot, ['oak_log', 'birch_log'], 16, 1); * let woodBlocks = world.getNearestBlocks(bot, ['oak_log', 'birch_log'], 16, 1);
**/ **/
// if blocktypes is not a list, make it a list
if (!Array.isArray(block_types))
block_types = [block_types];
let block_locs = bot.findBlocks({ let block_locs = bot.findBlocks({
matching: (block) => { matching: (block) => {
return block && block_types.includes(block.name); return block && block_types.some(name => name === block.name);
}, },
maxDistance: distance, maxDistance: distance,
count: count count: count
@ -243,6 +247,20 @@ export function getNearbyBlockTypes(bot, distance=16) {
return found; return found;
} }
export async function isClearPath(bot, target) {
/**
* Check if there is a path to the target that requires no digging or placing blocks.
* @param {Bot} bot - The bot to get the path for.
* @param {Entity} target - The target to path to.
* @returns {boolean} - True if there is a clear path, false otherwise.
*/
let movements = new pf.Movements(bot)
movements.canDig = false;
movements.canPlaceOn = false;
let goal = new pf.goals.GoalNear(target.position.x, target.position.y, target.position.z, 1);
let path = await bot.pathfinder.getPathTo(movements, goal, 100);
return path.status === 'success';
}
export function getBiomeName(bot) { export function getBiomeName(bot) {
/** /**

View file

@ -10,17 +10,20 @@ import * as mc from '../utils/mcdata.js';
// paused: whether the mode is paused by another action that overrides the behavior (eg followplayer implements its own self defense) // paused: whether the mode is paused by another action that overrides the behavior (eg followplayer implements its own self defense)
// update: the function that is called every tick (if on is true) // update: the function that is called every tick (if on is true)
// when a mode is active, it will trigger an action to be performed but won't wait for it to return output // when a mode is active, it will trigger an action to be performed but won't wait for it to return output
// the order of this list matters! first modes will be prioritized // the order of this list matters! first modes will be prioritized
// while update functions are async, they should *not* be awaited longer than ~100ms as it will block the update loop
// to perform longer actions, use the execute function which won't block the update loop
const modes = [ const modes = [
{ {
name: 'self_defense', name: 'self_defense',
description: 'Automatically attack nearby enemies. Interrupts other actions.', description: 'Automatically attack nearby enemies. Interrupts other actions.',
on: true, on: true,
active: false, active: false,
update: function (agent) { update: async function (agent) {
if (this.active) return; if (this.active) return;
const enemy = world.getNearestEntityWhere(agent.bot, entity => mc.isHostile(entity), 8); const enemy = world.getNearestEntityWhere(agent.bot, entity => mc.isHostile(entity), 8);
if (enemy) { if (enemy && await world.isClearPath(agent.bot, enemy)) {
agent.bot.chat(`Fighting ${enemy.name}!`); agent.bot.chat(`Fighting ${enemy.name}!`);
execute(this, agent, async () => { execute(this, agent, async () => {
await skills.defendSelf(agent.bot, 8); await skills.defendSelf(agent.bot, 8);
@ -33,10 +36,10 @@ const modes = [
description: 'Automatically hunt nearby animals when idle.', description: 'Automatically hunt nearby animals when idle.',
on: true, on: true,
active: false, active: false,
update: function (agent) { update: async function (agent) {
if (agent.isIdle()) { if (agent.isIdle()) {
const huntable = world.getNearestEntityWhere(agent.bot, entity => mc.isHuntable(entity), 8); const huntable = world.getNearestEntityWhere(agent.bot, entity => mc.isHuntable(entity), 8);
if (huntable) { if (huntable && await world.isClearPath(agent.bot, huntable)) {
execute(this, agent, async () => { execute(this, agent, async () => {
agent.bot.chat(`Hunting ${huntable.name}!`); agent.bot.chat(`Hunting ${huntable.name}!`);
await skills.attackEntity(agent.bot, huntable); await skills.attackEntity(agent.bot, huntable);
@ -50,10 +53,10 @@ const modes = [
description: 'Automatically collect nearby items when idle.', description: 'Automatically collect nearby items when idle.',
on: true, on: true,
active: false, active: false,
update: function (agent) { update: async function (agent) {
if (agent.isIdle()) { if (agent.isIdle()) {
let item = world.getNearestEntityWhere(agent.bot, entity => entity.name === 'item', 8); let item = world.getNearestEntityWhere(agent.bot, entity => entity.name === 'item', 8);
if (item) { if (item && await world.isClearPath(agent.bot, item)) {
execute(this, agent, async () => { execute(this, agent, async () => {
// wait 2 seconds for the item to settle // wait 2 seconds for the item to settle
await new Promise(resolve => setTimeout(resolve, 2000)); await new Promise(resolve => setTimeout(resolve, 2000));
@ -173,7 +176,7 @@ class ModeController {
return res; return res;
} }
update() { async update() {
if (this.agent.isIdle()) { if (this.agent.isIdle()) {
// other actions might pause a mode to override it // other actions might pause a mode to override it
// when idle, unpause all modes // when idle, unpause all modes
@ -184,7 +187,7 @@ class ModeController {
} }
for (let mode of this.modes_list) { for (let mode of this.modes_list) {
if (mode.on && !mode.paused) { if (mode.on && !mode.paused) {
mode.update(this.agent); await mode.update(this.agent);
if (mode.active) { if (mode.active) {
break; break;
} }

View file

@ -78,7 +78,9 @@
], ],
[ [
{"role": "user", "content": "reter: place a crafting table"}, {"role": "user", "content": "umpire_man: follow me without hunting"},
{"role": "assistant", "content": "Okay! !placeHere('crafting_table')"} {"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')"}
] ]
] ]

View file

@ -6,10 +6,10 @@
{"role": "assistant", "content": "I collected 9 oak logs, what next?"} {"role": "assistant", "content": "I collected 9 oak logs, what next?"}
], ],
[ [
{"role": "user", "content": "bobby: Craft a plank"}, {"role": "user", "content": "bobby: cook some chicken"},
{"role": "assistant", "content": "```\nawait skills.craftRecipe(bot, 'spruce_planks');\n```"}, {"role": "assistant", "content": "```\nawait skills.smeltItem(bot, 'chicken', 8);\n```"},
{"role": "system", "content": "Successfully crafted 4 spruce_planks."}, {"role": "system", "content": "Successfully smelted 8 chicken into 8 cooked_chicken."},
{"role": "assistant", "content": "I crafted 4 spruce planks!"} {"role": "assistant", "content": "I have cooked 8 chicken."}
], ],
[ [
{"role": "user", "content": "zZZn98: come here"}, {"role": "user", "content": "zZZn98: come here"},
@ -30,5 +30,9 @@
{"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": "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": "system", "content": "Successfully placed 5 dirt."},
{"role": "assistant", "content": "I built a little tower!"} {"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// place a torch in the middle of the room\nawait skills.placeBlock(bot, 'torch', startX + width / 2, startY + height / 2, startZ + 1);```"}
] ]
] ]