From bff811d1c32fb6b96df7535eef885603b76bd239 Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Fri, 26 Jan 2024 13:18:30 -0600 Subject: [PATCH] modes are async, added clearpath check --- src/agent/agent.js | 20 ++++++++++++-------- src/agent/modes.js | 21 ++++++++++++--------- src/agent/world.js | 16 ++++++++++++++++ 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/agent/agent.js b/src/agent/agent.js index 730d584..7ad8ef8 100644 --- a/src/agent/agent.js +++ b/src/agent/agent.js @@ -152,13 +152,17 @@ 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); + // 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 () => { + 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); } } diff --git a/src/agent/modes.js b/src/agent/modes.js index a2ac1a2..c318b3c 100644 --- a/src/agent/modes.js +++ b/src/agent/modes.js @@ -8,17 +8,20 @@ import * as world from './world.js'; // 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) // 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 +// 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 = [ { name: 'self_defense', description: 'Automatically attack nearby enemies. Interrupts other actions.', on: true, active: false, - update: function (agent) { + update: async function (agent) { if (this.active) return; const enemy = world.getNearestEntityWhere(agent.bot, entity => skills.isHostile(entity), 8); - if (enemy) { + if (enemy && await world.isClearPath(agent.bot, enemy)) { agent.bot.chat(`Fighting ${enemy.name}!`); execute(this, agent, async () => { await skills.defendSelf(agent.bot, 8); @@ -31,10 +34,10 @@ const modes = [ description: 'Automatically hunt nearby animals when idle.', on: true, active: false, - update: function (agent) { + update: async function (agent) { if (agent.idle) { const huntable = world.getNearestEntityWhere(agent.bot, entity => skills.isHuntable(entity), 8); - if (huntable) { + if (huntable && await world.isClearPath(agent.bot, huntable)) { execute(this, agent, async () => { agent.bot.chat(`Hunting ${huntable.name}!`); await skills.attackEntity(agent.bot, huntable); @@ -48,10 +51,10 @@ const modes = [ description: 'Automatically collect nearby items when idle.', on: true, active: false, - update: function (agent) { + update: async function (agent) { if (agent.idle) { - let item = world.getNearestEntityWhere(agent.bot, entity => entity.name === 'item', 8); - if (item) { + let item = world.getNearestEntityWhere(agent.bot, entity => entity.name === 'item', 4); + if (item && await world.isClearPath(agent.bot, item)) { execute(this, agent, async () => { // wait 2 seconds for the item to settle await new Promise(resolve => setTimeout(resolve, 2000)); @@ -174,7 +177,7 @@ class ModeController { return res; } - update() { + async update() { if (this.agent.idle) { // other actions might pause a mode to override it // when idle, unpause all modes @@ -185,7 +188,7 @@ class ModeController { } for (let mode of this.modes_list) { if (mode.on && !mode.paused) { - mode.update(this.agent); + await mode.update(this.agent); if (mode.active) { break; } diff --git a/src/agent/world.js b/src/agent/world.js index cfb2e64..ea72f48 100644 --- a/src/agent/world.js +++ b/src/agent/world.js @@ -1,4 +1,5 @@ import { getAllBlockIds } from '../utils/mcdata.js'; +import pf from 'mineflayer-pathfinder'; export function getNearestBlocks(bot, block_types, distance=16, count=1) { @@ -206,3 +207,18 @@ export function getNearbyBlockTypes(bot, distance=16) { } 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'; +}