From 39cf663c56107b7c0d27adb879c7acd5183203cc Mon Sep 17 00:00:00 2001 From: GenerelSchwerz Date: Thu, 8 Feb 2024 16:58:43 -0500 Subject: [PATCH] provide options to switch producers --- src/ThePathfinder.ts | 363 +++++++++++++++++++++++-------------------- 1 file changed, 192 insertions(+), 171 deletions(-) diff --git a/src/ThePathfinder.ts b/src/ThePathfinder.ts index 4d3b8ba..ca79d4d 100644 --- a/src/ThePathfinder.ts +++ b/src/ThePathfinder.ts @@ -1,12 +1,12 @@ -import { Bot } from 'mineflayer' -import { AStar as AAStar } from './abstract/algorithms/astar' -import { AStar } from './mineflayer-specific/algs' -import * as goals from './mineflayer-specific/goals' -import { Vec3 } from 'vec3' -import { Move } from './mineflayer-specific/move' -import { Path, Algorithm } from './abstract' -import { BlockInfo, CacheSyncWorld } from './mineflayer-specific/world/cacheWorld' -import { AbortError, CancelError } from './mineflayer-specific/movements/exceptions' +import { Bot } from "mineflayer"; +import { AStar as AAStar } from "./abstract/algorithms/astar"; +import { AStar } from "./mineflayer-specific/algs"; +import * as goals from "./mineflayer-specific/goals"; +import { Vec3 } from "vec3"; +import { Move } from "./mineflayer-specific/move"; +import { Path, Algorithm } from "./abstract"; +import { BlockInfo, CacheSyncWorld } from "./mineflayer-specific/world/cacheWorld"; +import { AbortError, CancelError } from "./mineflayer-specific/movements/exceptions"; import { BuildableMoveExecutor, BuildableMoveProvider, @@ -29,13 +29,21 @@ import { ForwardJumpExecutor, StraightDownExecutor, StraightUpExecutor, - DEFAULT_MOVEMENT_OPTS -} from './mineflayer-specific/movements' -import { DropDownOpt, ForwardJumpUpOpt, StraightAheadOpt } from './mineflayer-specific/post/optimizers' -import { BuildableOptimizer, OptimizationSetup, MovementOptimizer, OptimizationMap, Optimizer } from './mineflayer-specific/post' -import { ContinuousPathProducer } from './mineflayer-specific/pathProducers' + DEFAULT_MOVEMENT_OPTS, +} from "./mineflayer-specific/movements"; +import { DropDownOpt, ForwardJumpUpOpt, StraightAheadOpt } from "./mineflayer-specific/post/optimizers"; +import { BuildableOptimizer, OptimizationSetup, MovementOptimizer, OptimizationMap, Optimizer } from "./mineflayer-specific/post"; +import { ContinuousPathProducer, PartialPathProducer } from "./mineflayer-specific/pathProducers"; + +export interface PathfinderOpts { + partialPathProducer: boolean; +} + +const DEFAULT_PATHFINDER_OPTS: PathfinderOpts = { + partialPathProducer: false, +}; -const EMPTY_VEC = new Vec3(0, 0, 0) +const EMPTY_VEC = new Vec3(0, 0, 0); /** * These are the default movement types and their respective executors. @@ -47,8 +55,8 @@ const DEFAULT_PROVIDER_EXECUTORS = [ [Diagonal, ForwardExecutor], [StraightDown, StraightDownExecutor], [StraightUp, StraightUpExecutor], - [ParkourForward, ParkourForwardExecutor] -] as Array<[BuildableMoveProvider, BuildableMoveExecutor]> + [ParkourForward, ParkourForwardExecutor], +] as Array<[BuildableMoveProvider, BuildableMoveExecutor]>; /** * Due to locality caching of blocks being implemented, @@ -58,7 +66,7 @@ const DEFAULT_PROVIDER_EXECUTORS = [ * Human logic keeps simple at front, complicated at back, * So for simplicity I reverse the array. */ -DEFAULT_PROVIDER_EXECUTORS.reverse() +DEFAULT_PROVIDER_EXECUTORS.reverse(); /** * This is the default optimization setup. @@ -71,23 +79,23 @@ const DEFAULT_OPTIMIZERS = [ [Forward, StraightAheadOpt], [Diagonal, StraightAheadOpt], [ForwardDropDown, DropDownOpt], - [ForwardJump, ForwardJumpUpOpt] -] as Array<[BuildableMoveProvider, BuildableOptimizer]> + [ForwardJump, ForwardJumpUpOpt], +] as Array<[BuildableMoveProvider, BuildableOptimizer]>; -const DEFAULT_SETUP = new Map(DEFAULT_PROVIDER_EXECUTORS) +const DEFAULT_SETUP = new Map(DEFAULT_PROVIDER_EXECUTORS); -const DEFAULT_OPTIMIZATION = new Map(DEFAULT_OPTIMIZERS) +const DEFAULT_OPTIMIZATION = new Map(DEFAULT_OPTIMIZERS); // Temp typing. -type PathInfo = Path> +type PathInfo = Path>; type PathGenerator = AsyncGenerator< -{ - result: PathInfo - astarContext: AAStar -}, -void, -unknown -> + { + result: PathInfo; + astarContext: AAStar; + }, + void, + unknown +>; /** * Eventually, I want all pathfinder logic entirely off thread. @@ -100,176 +108,189 @@ unknown * That will be a while, but remember to code this with that in mind. */ export class ThePathfinder { - astar: AStar | null - world: CacheSyncWorld - movements: ExecutorMap - optimizers: OptimizationMap - defaultSettings: MovementOptions + astar: AStar | null; + world: CacheSyncWorld; + movements: ExecutorMap; + optimizers: OptimizationMap; + defaultMoveSettings: MovementOptions; + + pathfinderSettings: PathfinderOpts; - public executing = false - private currentMove?: Move - public currentExecutor?: MovementExecutor + public executing = false; + private currentMove?: Move; + public currentExecutor?: MovementExecutor; - constructor ( + constructor( private readonly bot: Bot, movements?: MovementSetup, optimizers?: OptimizationSetup, - settings: MovementOptions = DEFAULT_MOVEMENT_OPTS + moveSettings: MovementOptions = DEFAULT_MOVEMENT_OPTS, + pathfinderSettings: PathfinderOpts = DEFAULT_PATHFINDER_OPTS ) { - this.world = new CacheSyncWorld(bot, this.bot.world) + this.world = new CacheSyncWorld(bot, this.bot.world); // set up executors, map them to providers. - const moves = new Map() + const moves = new Map(); for (const [providerType, ExecutorType] of movements ?? DEFAULT_SETUP) { - moves.set(providerType, new ExecutorType(bot, this.world, settings)) + moves.set(providerType, new ExecutorType(bot, this.world, moveSettings)); } // set up optimizers, map them to providers. - const opts = new Map() + const opts = new Map(); for (const [providerType, ExecutorType] of optimizers ?? DEFAULT_OPTIMIZATION) { - opts.set(providerType, new ExecutorType(bot, this.world)) + opts.set(providerType, new ExecutorType(bot, this.world)); } - this.movements = moves - this.optimizers = opts - this.defaultSettings = settings - this.astar = null + this.movements = moves; + this.optimizers = opts; + this.defaultMoveSettings = moveSettings; + this.pathfinderSettings = pathfinderSettings; + this.astar = null; } - async cancel (timeout = 1000): Promise { - if (this.currentExecutor == null) return - if (this.currentMove == null) throw new Error('No current move, but there is a current executor.') + async cancel(timeout = 1000): Promise { + if (this.currentExecutor == null) return; + if (this.currentMove == null) throw new Error("No current move, but there is a current executor."); - await this.currentExecutor.abort(this.currentMove, timeout) + await this.currentExecutor.abort(this.currentMove, timeout); } - getCacheSize (): string { - return this.world.getCacheSize() + getCacheSize(): string { + return this.world.getCacheSize(); } - setCacheEnabled (enabled: boolean): void { - this.world.setEnabled(enabled) + setCacheEnabled(enabled: boolean): void { + this.world.setEnabled(enabled); } - isCacheEnabled (): boolean { - return this.world.enabled + isCacheEnabled(): boolean { + return this.world.enabled; } - dropMovment (provider: BuildableMoveProvider): void { - this.movements.delete(provider) + dropMovment(provider: BuildableMoveProvider): void { + this.movements.delete(provider); // will keep in optimizers as that has no effect. // this.optimizers.delete(provider); } - setExecutor (provider: BuildableMoveProvider, Executor: BuildableMoveExecutor | MovementExecutor): void { + setExecutor(provider: BuildableMoveProvider, Executor: BuildableMoveExecutor | MovementExecutor): void { if (Executor instanceof MovementExecutor) { - this.movements.set(provider, Executor) + this.movements.set(provider, Executor); } else { - this.movements.set(provider, new Executor(this.bot, this.world, this.defaultSettings)) + this.movements.set(provider, new Executor(this.bot, this.world, this.defaultMoveSettings)); } } - setOptimizer (provider: BuildableMoveProvider, Optimizer: BuildableOptimizer | MovementOptimizer): void { + setOptimizer(provider: BuildableMoveProvider, Optimizer: BuildableOptimizer | MovementOptimizer): void { if (Optimizer instanceof MovementOptimizer) { - this.optimizers.set(provider, Optimizer) + this.optimizers.set(provider, Optimizer); } else { - this.optimizers.set(provider, new Optimizer(this.bot, this.world)) + this.optimizers.set(provider, new Optimizer(this.bot, this.world)); } } - setDefaultOptions (settings: Partial): void { - this.defaultSettings = Object.assign({}, DEFAULT_MOVEMENT_OPTS, settings) + setDefaultMoveOptions(settings: Partial): void { + this.defaultMoveSettings = Object.assign({}, DEFAULT_MOVEMENT_OPTS, settings); for (const [, executor] of this.movements) { - executor.settings = this.defaultSettings + executor.settings = this.defaultMoveSettings; } } - getPathTo (goal: goals.Goal, settings = this.defaultSettings): PathGenerator { - return this.getPathFromTo(this.bot.entity.position, this.bot.entity.velocity, goal, settings) + setPathfinderOptions(settings: Partial): void { + this.pathfinderSettings = Object.assign({}, DEFAULT_PATHFINDER_OPTS, settings); + } + + getPathTo(goal: goals.Goal, settings = this.defaultMoveSettings): PathGenerator { + return this.getPathFromTo(this.bot.entity.position, this.bot.entity.velocity, goal, settings); } - getScaffoldCount (): number { - return this.bot.inventory.items().reduce((acc, item) => (BlockInfo.scaffoldingBlockItems.has(item.type) ? item.count + acc : acc), 0) + getScaffoldCount(): number { + return this.bot.inventory.items().reduce((acc, item) => (BlockInfo.scaffoldingBlockItems.has(item.type) ? item.count + acc : acc), 0); } - async * getPathFromTo (startPos: Vec3, startVel: Vec3, goal: goals.Goal, settings = this.defaultSettings): PathGenerator { - const move = Move.startMove(new IdleMovement(this.bot, this.world), startPos.clone(), startVel.clone(), this.getScaffoldCount()) + async *getPathFromTo(startPos: Vec3, startVel: Vec3, goal: goals.Goal, settings = this.defaultMoveSettings): PathGenerator { + const move = Move.startMove(new IdleMovement(this.bot, this.world), startPos.clone(), startVel.clone(), this.getScaffoldCount()); // technically introducing a bug here, where resetting the pathingUtil fucks up. - this.bot.pathingUtil.refresh() + this.bot.pathingUtil.refresh(); - const foo = new ContinuousPathProducer(move, goal, settings, this.bot, this.world, this.movements) - // const foo = new PartialPathProducer(move, goal, settings, this.bot, this.world, this.movements); + let foo; - let { result, astarContext } = foo.advance() + if (this.pathfinderSettings.partialPathProducer) { + foo = new PartialPathProducer(move, goal, settings, this.bot, this.world, this.movements); + } else { + foo = new ContinuousPathProducer(move, goal, settings, this.bot, this.world, this.movements); + } - yield { result, astarContext } + let { result, astarContext } = foo.advance(); - let ticked = false + yield { result, astarContext }; + + let ticked = false; const listener = (): void => { - ticked = true - } - this.bot.on('physicsTick', listener) - - while (result.status === 'partial') { - const { result: result2, astarContext } = foo.advance() - result = result2 - if (result.status === 'success') { - yield { result, astarContext } - break + ticked = true; + }; + this.bot.on("physicsTick", listener); + + while (result.status === "partial") { + const { result: result2, astarContext } = foo.advance(); + result = result2; + if (result.status === "success") { + yield { result, astarContext }; + break; } - yield { result, astarContext } + yield { result, astarContext }; // allow bot to function even while calculating. // Note: if we already ticked, there is no point in waiting. Our packets are already desynced. if (!ticked) { - await this.bot.waitForTicks(1) - ticked = false + await this.bot.waitForTicks(1); + ticked = false; } } - this.bot.off('physicsTick', listener) + this.bot.off("physicsTick", listener); } - async getPathFromToRaw (startPos: Vec3, startVel: Vec3, goal: goals.Goal): Promise { + async getPathFromToRaw(startPos: Vec3, startVel: Vec3, goal: goals.Goal): Promise { for await (const res of this.getPathFromTo(startPos, startVel, goal)) { - if (res.result.status !== 'success') { - if (res.result.status === 'noPath' || res.result.status === 'timeout') return null + if (res.result.status !== "success") { + if (res.result.status === "noPath" || res.result.status === "timeout") return null; } else { - return res.result + return res.result; } } - return null + return null; } - async goto (goal: goals.Goal): Promise { + async goto(goal: goals.Goal): Promise { // if (this.executing) throw new Error('Already executing!') if (this.executing) { - await this.cancel() + await this.cancel(); } - this.executing = true + this.executing = true; for await (const res of this.getPathTo(goal)) { - if (res.result.status !== 'success') { - if (res.result.status === 'noPath' || res.result.status === 'timeout') break + if (res.result.status !== "success") { + if (res.result.status === "noPath" || res.result.status === "timeout") break; } else { - const newPath = await this.postProcess(res.result) - await this.perform(newPath, goal) + const newPath = await this.postProcess(res.result); + await this.perform(newPath, goal); } } - await this.cleanupAll(goal) + await this.cleanupAll(goal); } - private async postProcess (pathInfo: Path>): Promise>> { - const optimizer = new Optimizer(this.bot, this.world, this.optimizers) + private async postProcess(pathInfo: Path>): Promise>> { + const optimizer = new Optimizer(this.bot, this.world, this.optimizers); - optimizer.loadPath(pathInfo.path) + optimizer.loadPath(pathInfo.path); - const res = await optimizer.compute() + const res = await optimizer.compute(); - const ret = { ...pathInfo } + const ret = { ...pathInfo }; - ret.path = res - return ret + ret.path = res; + return ret; } /** @@ -279,129 +300,129 @@ export class ThePathfinder { * @param goal * @param entry */ - async perform (path: Path>, goal: goals.Goal, entry = 0): Promise { - if (entry > 10) throw new Error('Too many failures, exiting performing.') + async perform(path: Path>, goal: goals.Goal, entry = 0): Promise { + if (entry > 10) throw new Error("Too many failures, exiting performing."); - let currentIndex = 0 - const movementHandler = path.context.movementProvider as MovementHandler - const movements = movementHandler.getMovements() + let currentIndex = 0; + const movementHandler = path.context.movementProvider as MovementHandler; + const movements = movementHandler.getMovements(); // eslint-disable-next-line no-labels outer: while (currentIndex < path.path.length) { - const move = path.path[currentIndex] - const executor = movements.get(move.moveType.constructor as BuildableMoveProvider) - if (executor == null) throw new Error('No executor for movement type ' + move.moveType.constructor.name) + const move = path.path[currentIndex]; + const executor = movements.get(move.moveType.constructor as BuildableMoveProvider); + if (executor == null) throw new Error("No executor for movement type " + move.moveType.constructor.name); - this.currentMove = move - this.currentExecutor = executor + this.currentMove = move; + this.currentExecutor = executor; - let tickCount = 0 + let tickCount = 0; // TODO: could move this to physicsTick to be performant, but meh who cares. // reset bot. - await this.cleanupBot() + await this.cleanupBot(); // provide current move to executor as a reference. - executor.loadMove(move) + executor.loadMove(move); // if the movement has already been completed (another movement has already completed it), skip it. if (executor.isAlreadyCompleted(move, tickCount, goal)) { - currentIndex++ - continue + currentIndex++; + continue; } // wrap this code in a try-catch as we intentionally throw errors. try { while (!(await executor._align(move, tickCount++, goal)) && tickCount < 999) { - await this.bot.waitForTicks(1) + await this.bot.waitForTicks(1); } - tickCount = 0 + tickCount = 0; // allow the initial execution of this code. - await executor._performInit(move, currentIndex, path.path) + await executor._performInit(move, currentIndex, path.path); - let adding = await executor._performPerTick(move, tickCount++, currentIndex, path.path) + let adding = await executor._performPerTick(move, tickCount++, currentIndex, path.path); while (!(adding as boolean) && tickCount < 999) { - await this.bot.waitForTicks(1) - adding = await executor._performPerTick(move, tickCount++, currentIndex, path.path) + await this.bot.waitForTicks(1); + adding = await executor._performPerTick(move, tickCount++, currentIndex, path.path); } - currentIndex += adding as number + currentIndex += adding as number; } catch (err) { // immediately exit since we want to abort the entire path. if (err instanceof AbortError) { - this.currentExecutor.reset() + this.currentExecutor.reset(); // eslint-disable-next-line no-labels - break outer + break outer; } else if (err instanceof CancelError) { // allow recovery if movement intentionall canceled. - await this.recovery(move, path, goal, entry) + await this.recovery(move, path, goal, entry); // eslint-disable-next-line no-labels - break outer - } else throw err + break outer; + } else throw err; } } } // TODO: implement recovery for any movement and goal. - async recovery (move: Move, path: Path>, goal: goals.Goal, entry = 0): Promise { - await this.cleanupBot() + async recovery(move: Move, path: Path>, goal: goals.Goal, entry = 0): Promise { + await this.cleanupBot(); - const ind = path.path.indexOf(move) + const ind = path.path.indexOf(move); if (ind === -1) { - return // done + return; // done } - let newGoal + let newGoal; - const pos = this.bot.entity.position - let bad = false - let nextMove = path.path.sort((a, b) => a.entryPos.distanceTo(pos) - b.entryPos.distanceTo(pos))[0] + const pos = this.bot.entity.position; + let bad = false; + let nextMove = path.path.sort((a, b) => a.entryPos.distanceTo(pos) - b.entryPos.distanceTo(pos))[0]; if (path.path.indexOf(nextMove) === ind) { - nextMove = path.path[ind + 1] + nextMove = path.path[ind + 1]; } else if (path.path.indexOf(nextMove) < ind) { - bad = true + bad = true; } - const no = entry > 0 || bad + const no = entry > 0 || bad; if (no) { - newGoal = goal + newGoal = goal; } else { - newGoal = goals.GoalBlock.fromVec(nextMove.toVec()) + newGoal = goals.GoalBlock.fromVec(nextMove.toVec()); } - const path1 = await this.getPathFromToRaw(this.bot.entity.position, EMPTY_VEC, newGoal) + const path1 = await this.getPathFromToRaw(this.bot.entity.position, EMPTY_VEC, newGoal); if (path1 === null) { // done } else if (no) { // execution of past recoveries failed or not easily saveable, so full recovery needed. - await this.perform(path1, goal, entry + 1) + await this.perform(path1, goal, entry + 1); } else { // attempt recovery to nearby node. - await this.perform(path1, newGoal, entry + 1) - path.path.splice(0, ind) - await this.perform(path, goal, 0) + await this.perform(path1, newGoal, entry + 1); + path.path.splice(0, ind); + await this.perform(path, goal, 0); } } - async cleanupBot (): Promise { - this.bot.clearControlStates() + async cleanupBot(): Promise { + this.bot.clearControlStates(); // await this.bot.waitForTicks(1); } - async cleanupAll (goal: goals.Goal): Promise { - await this.cleanupBot() - await goal.onFinish(this.bot) - this.bot.chat(this.world.getCacheSize()) - this.world.clearCache() - this.executing = false + async cleanupAll(goal: goals.Goal): Promise { + await this.cleanupBot(); + await goal.onFinish(this.bot); + this.bot.chat(this.world.getCacheSize()); + this.world.clearCache(); + this.executing = false; - delete this.currentMove - delete this.currentExecutor + delete this.currentMove; + delete this.currentExecutor; } }