diff --git a/examples/example.js b/examples/example.js index 690a5c6..54ba128 100644 --- a/examples/example.js +++ b/examples/example.js @@ -5,7 +5,7 @@ const { GoalBlock, GoalLookAt, GoalPlaceBlock } = goals; const { Vec3 } = require("vec3"); const rl = require('readline') const { default: loader, EntityState, EPhysicsCtx, EntityPhysics } = require("@nxg-org/mineflayer-physics-util"); -const { GoalMineBlock } = require("../dist/mineflayer-specific/goals"); +const { GoalMineBlock, GoalFollowEntity } = require("../dist/mineflayer-specific/goals"); const bot = createBot({ @@ -25,17 +25,17 @@ const pathfinder = createPlugin(); const validTypes = ["block" , "lookat", "place", "break"] let mode = "block" -function getGoal(world,x,y,z) { - const ret = _getGoal(world,x,y,z) +function getGoal(world,x,y,z,modes=mode) { + const ret = _getGoal(world,x,y,z,modes) bot.chat(`Going to: ${ret.x} ${ret.y} ${ret.z}`) console.log(ret) return ret; } -function _getGoal(world, x, y, z) { +function _getGoal(world, x, y, z,modes) { const block = bot.blockAt(new Vec3(x, y, z)); if (block === null) return new GoalBlock(x, y+1, z); let item; - switch (mode) { + switch (modes) { case "block": return new GoalBlock(x, y+1, z); case "lookat": @@ -54,8 +54,19 @@ function _getGoal(world, x, y, z) { bot.on("inject_allowed", () => {}); bot.once("spawn", async () => { + + bot.on('physicsTick', () => { + if (bot.getControlState('forward') && bot.getControlState('back')) { + throw new Error('both forward and back are true') + + } + }) bot.loadPlugin(pathfinder); bot.loadPlugin(loader); + + bot.pathfinder.setPathfinderOptions({ + partialPathProducer: true + }) // bot.physics.yawSpeed = 3; // bot.physics.pitchSpeed = 3 @@ -79,11 +90,11 @@ bot.once("spawn", async () => { const val = new EntityPhysics(bot.registry) const oldSim = bot.physics.simulatePlayer; bot.physics.simulatePlayer = (...args) => { - bot.jumpTicks = 0 - const ctx = EPhysicsCtx.FROM_BOT(val, bot) - ctx.state.jumpTicks = 0; // allow immediate jumping - // ctx.state.control.set('sneak', true) - return val.simulate(ctx, bot.world).applyToBot(bot); + // bot.jumpTicks = 0 + // const ctx = EPhysicsCtx.FROM_BOT(val, bot) + // ctx.state.jumpTicks = 0; // allow immediate jumping + // // ctx.state.control.set('sneak', true) + // return val.simulate(ctx, bot.world).applyToBot(bot); return oldSim(...args); }; @@ -94,7 +105,7 @@ bot.once("spawn", async () => { }) rlline.on('line', (line) => { - console.log('line!', line) + // console.log('line!', line) if (line === "exit") { bot.quit() process.exit() @@ -111,7 +122,7 @@ bot.once("spawn", async () => { let lastStart = null; async function cmdHandler(username, msg) { - console.log(msg) + // console.log(msg) if (username === bot.username) return; const [cmd1, ...args] = msg.split(" "); @@ -120,6 +131,15 @@ async function cmdHandler(username, msg) { const cmd = cmd1.toLowerCase().replace(prefix, ""); switch (cmd) { + + case "followme": { + if (!author) return bot.whisper(username, "failed to find player"); + const dist = parseInt(args[0]) || 1; + const goal = GoalFollowEntity.fromEntity(author, dist, {neverfinish: true}); + await bot.pathfinder.goto(goal); + break; + } + case "blockatme": { if (!author) return bot.whisper(username, "failed to find player"); const block = bot.blockAt(author.position); @@ -295,26 +315,40 @@ async function cmdHandler(username, msg) { } while (test.done === false) const endTime = performance.now(); - bot.whisper( - username, - `took ${(endTime - startTime).toFixed(3)} ms, ${Math.ceil((endTime - startTime) / 50)} ticks, ${( - (endTime - startTime) / - 1000 - ).toFixed(3)} seconds` - ); - if (args[0] === "debug") { + if (args.find(val=>val==='debug')) { console.log('hey') console.log(test1.value.result.path.map((v) => `(${v.moveType.constructor.name}: ${v.toPlace.length} ${v.toBreak.length} | ${v.entryPos} ${v.exitPos})`).join("\n")); - if (args[1] === 'trail') { + + const poses = []; + const listener = () => { + for (const pos of poses) { + bot.chat('/particle minecraft:flame ' + (pos.entryPos.x+0.5) + ' ' + (pos.entryPos.y +0.5) + ' ' + (pos.entryPos.z+0.5) + ' 0 0 0 0 1 force') + } + } + const interval = setInterval(listener, 500) + if (args.find(val=>val==='trail')) { + const stagger = 2 for (const pos of test1.value.result.path) { - bot.chat('/particle minecraft:flame ' + pos.entryPos.x + ' ' + pos.entryPos.y + ' ' + pos.entryPos.z + ' 0 0.5 0 0 10 force') + poses.push(pos) + bot.chat('/particle minecraft:flame ' + (pos.entryPos.x+0.5) + ' ' + (pos.entryPos.y +0.5) + ' ' + (pos.entryPos.z+0.5) + ' 0 0 0 0 1 force') + await bot.waitForTicks(stagger); } + clearInterval(interval) } } + + bot.whisper( + username, + `took ${(endTime - startTime).toFixed(3)} ms, ${Math.ceil((endTime - startTime) / 50)} ticks, ${( + (endTime - startTime) / + 1000 + ).toFixed(3)} seconds` + ); + bot.whisper(username, bot.pathfinder.world.getCacheSize()); console.log(test1.length); break; @@ -325,31 +359,64 @@ async function cmdHandler(username, msg) { let rayBlock; let info = new Vec3(0, 0, 0); - if (args.length === 3) { + if (args.length >= 3) { info = new Vec3(parseInt(args[0]), parseInt(args[1]), parseInt(args[2])); rayBlock = bot.blockAt(info); - } else if (args.length === 0) { + } else { if (!author) return bot.whisper(username, "failed to find player."); rayBlock = await rayTraceEntitySight({ entity: author }); if (!rayBlock) return bot.whisper(username, "No block in sight"); info = rayBlock.position; - } else { - bot.whisper(username, "pathtothere | pathtothere"); - return; - } + } + + // else { + // bot.whisper(username, "pathtothere | pathtothere"); + // return; + // } bot.whisper(username, `pathing to ${info.x} ${info.y} ${info.z}`); const goal = getGoal(bot.world, info.x, info.y, info.z); const res1 = bot.pathfinder.getPathTo(goal); + let test; let test1; const test2 = []; - while ((test1 = await res1.next()).done === false) { - test2.concat(test1.value.result.path); - } + do { + test = await res1.next() + if (!test.done) test1=test + // console.log(test1) + if (test1.value) test2.push(...test1.value.result.path); + } while (test.done === false) + const endTime = performance.now(); + + + + if (args.find(val=>val==='debug')) { + console.log('hey') + console.log(test1.value.result.path.map((v) => `(${v.moveType.constructor.name}: ${v.toPlace.length} ${v.toBreak.length} | ${v.entryPos} ${v.exitPos})`).join("\n")); + + + const poses = []; + const listener = () => { + for (const pos of poses) { + bot.chat('/particle minecraft:flame ' + (pos.entryPos.x+0.5) + ' ' + (pos.entryPos.y +0.5) + ' ' + (pos.entryPos.z+0.5) + ' 0 0 0 0 1 force') + } + } + const interval = setInterval(listener, 500) + if (args.find(val=>val==='trail')) { + const stagger = 2 + for (const pos of test1.value.result.path) { + poses.push(pos) + bot.chat('/particle minecraft:flame ' + (pos.entryPos.x+0.5) + ' ' + (pos.entryPos.y +0.5) + ' ' + (pos.entryPos.z+0.5) + ' 0 0 0 0 1 force') + await bot.waitForTicks(stagger); + } + clearInterval(interval) + } + } + bot.whisper( username, `took ${(endTime - startTime).toFixed(3)} ms, ${Math.ceil((endTime - startTime) / 50)} ticks, ${( @@ -357,6 +424,7 @@ async function cmdHandler(username, msg) { 1000 ).toFixed(3)} seconds` ); + bot.whisper(username, bot.pathfinder.world.getCacheSize()); console.log(test2.length); break; diff --git a/src/ThePathfinder.ts b/src/ThePathfinder.ts index be78e09..cef02b0 100644 --- a/src/ThePathfinder.ts +++ b/src/ThePathfinder.ts @@ -38,10 +38,12 @@ import { Block, ResetReason } from './types' export interface PathfinderOptions { partialPathProducer: boolean + partialPathLength: number } const DEFAULT_PATHFINDER_OPTS: PathfinderOptions = { - partialPathProducer: false + partialPathProducer: false, + partialPathLength: 50 } const EMPTY_VEC = new Vec3(0, 0, 0) @@ -97,6 +99,7 @@ interface PathGeneratorResult { interface PerformOpts { errorOnRetry?: boolean + errorOnCancel?: boolean } /** @@ -119,13 +122,14 @@ export class ThePathfinder { public executing = false public cancelCalculation = false + private userCancelled = false private currentGoal?: goals.Goal private currentExecutingPath?: Move[] private currentMove?: Move private currentExecutor?: MovementExecutor - private allowRetry = false + private resetReason?: ResetReason private currentProducer?: PathProducer public get currentAStar (): AStar | undefined { @@ -161,22 +165,43 @@ export class ThePathfinder { this.setupListeners() } - async cancel (allowRetry = false, timeout = 1000): Promise { + async cancel (): Promise { + await this._cancel(1000, true) + } + + async _cancel (timeout = 1000, userCalled = false, reasonStr?: ResetReason): Promise { + if (this.currentProducer == null) return console.log('no producer') this.cancelCalculation = true - this.allowRetry = allowRetry + this.userCancelled = userCalled if (this.currentExecutor == null) return console.log('no executor') + // if (this.currentExecutor.aborted) return console.trace('already aborted') if (this.currentMove == null) throw new Error('No current move, but there is a current executor.') - await this.currentExecutor.abort(this.currentMove, { timeout, resetting: allowRetry }) + let reason + if (reasonStr != null) { + switch (reasonStr) { + case 'blockUpdate': + reason = new ResetError('blockUpdate') + break + case 'chunkLoad': + reason = new ResetError('chunkLoad') + break + case 'goalUpdated': + reason = new ResetError('goalUpdated') + break + } + } + + await this.currentExecutor.abort(this.currentMove, { timeout, reason }) - console.log('tried cancel frfr') + console.trace('tried cancel frfr') // calling cleanupAll is not necessary as the end of goto already calls it. } async reset (reason: ResetReason, cancelTimeout = 1000): Promise { this.bot.emit('resetPath', reason) - await this.cancel(true, cancelTimeout) + await this._cancel(cancelTimeout, false, reason) } setupListeners (): void { @@ -328,7 +353,7 @@ export class ThePathfinder { async * getPathFromTo (startPos: Vec3, startVel: Vec3, goal: goals.Goal, settings = this.defaultMoveSettings): PathGenerator { this.cancelCalculation = false - this.allowRetry = false + delete this.resetReason this.currentGoal = goal this.currentMove = Move.startMove(new IdleMovement(this.bot, this.world), startPos.clone(), startVel.clone(), this.getScaffoldCount()) @@ -343,21 +368,47 @@ export class ThePathfinder { this.currentProducer = new ContinuousPathProducer(this.currentMove, goal, settings, this.bot, this.world, this.movements) } - let { result, astarContext } = this.currentProducer.advance() - - yield { result, astarContext } - let ticked = false + const listener = (): void => { ticked = true } - this.bot.on('physicsTick', listener) + let cleanup = (): void => { + this.bot.off('physicsTick', listener) + } + + if (goal instanceof goals.GoalDynamic && goal.dynamic) { + const bounded = goal.hasChanged.bind(goal) + const old = cleanup + const list1 = (...args: any[]): void => { + const ret = bounded(...args) + if (ret) { + void this.reset('goalUpdated') + this.bot.off(goal.eventKey, list1) + } + } + + this.bot.on(goal.eventKey, list1) + goal.cleanup = (reason) => { + this.bot.off(goal.eventKey, list1) + delete goal.cleanup + } + + cleanup = () => { + old() + this.bot.off(goal.eventKey, list1) + } + } + + let { result, astarContext } = this.currentProducer.advance() + + yield { result, astarContext } while (result.status === 'partial') { if (this.cancelCalculation) { console.log('cancelling!') - this.bot.off('physicsTick', listener) + cleanup() return null } @@ -365,7 +416,7 @@ export class ThePathfinder { result = result2 if (result.status === 'success') { - this.bot.off('physicsTick', listener) + cleanup() yield { result, astarContext } return { result, astarContext } } @@ -379,7 +430,7 @@ export class ThePathfinder { } } - this.bot.off('physicsTick', listener) + cleanup() return { result, astarContext @@ -402,31 +453,61 @@ export class ThePathfinder { */ async goto (goal: goals.Goal, performOpts: PerformOpts = {}): Promise { if (this.executing || goal == null) { - await this.cancel() + await this._cancel() } this.executing = true - do { - for await (const res of this.getPathTo(goal)) { - if (res.result.status !== 'success') { - if (res.result.status === 'noPath' || res.result.status === 'timeout') break - } else { - console.log('sup gang!') - const newPath = await this.postProcess(res.result) - await this.perform(newPath, goal) - - if (performOpts.errorOnRetry != null && this.allowRetry) { - throw new Error('Goto: Purposefully cancelled due to recalculation of path occurring.') - } + const doForever = !!(goal instanceof goals.GoalDynamic && goal.neverfinish && goal.dynamic) - if (!this.allowRetry) { - console.log('finished!', this.bot.entity.position, goal) - break + do { + do { + console.log('finding path!') + for await (const res of this.getPathTo(goal)) { + 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) + + if (performOpts.errorOnRetry != null && this.resetReason != null) { + throw new Error('Goto: Purposefully cancelled due to recalculation of path occurring.') + } + + if (performOpts.errorOnCancel != null && this.cancelCalculation) { + throw new Error('Goto: Purposefully cancelled by user.') + } + + if (this.resetReason == null && !this.cancelCalculation) { + console.log('finished!', this.bot.entity.position, this.bot.listenerCount('physicsTick')) + break + } } } + } while (this.resetReason != null && !this.cancelCalculation) + + if (doForever) { + await this.cleanupBot() + const refGoal = goal + + if (this.resetReason == null) { + await new Promise((resolve, reject) => { + const bounded = refGoal.hasChanged.bind(refGoal) + const listener = (...args: any[]): void => { + if (this.userCancelled || bounded(...args)) { + console.log('hi', bounded(...args), this.bot.listenerCount(refGoal.eventKey)) + refGoal.update() + this.bot.off(refGoal.eventKey, listener) + resolve() + } + } + this.bot.on(refGoal.eventKey, listener) + }) + } } - } while (this.allowRetry) + // eslint-disable-next-line no-unmodified-loop-condition + } while (doForever && !this.userCancelled) + console.log('sup gang!!1', doForever, this.userCancelled) await this.cleanupAll(goal, this.currentExecutor) } @@ -435,8 +516,6 @@ export class ThePathfinder { optimizer.loadPath(pathInfo.path) - console.log('hi') - const res = await optimizer.compute() const ret = { ...pathInfo } @@ -445,6 +524,16 @@ export class ThePathfinder { return ret } + private check (): void { + if (this.userCancelled) { + throw new AbortError('User cancelled.') + } + + if (this.resetReason != null) { + throw new ResetError(this.resetReason) + } + } + /** * Do not mind the absolutely horrendous code here right now. * It will be fixed, just very busy right now. @@ -459,9 +548,7 @@ export class ThePathfinder { const movementHandler = path.context.movementProvider as MovementHandler const movements = movementHandler.getMovements() - console.log('sup gang') - // eslint-disable-next-line no-labels - outer: while (currentIndex < path.path.length) { + 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) @@ -486,7 +573,7 @@ export class ThePathfinder { continue } - console.log('performing', move.moveType.constructor.name, 'at index', currentIndex, 'of', path.path.length, goal) + console.log('performing', move.moveType.constructor.name, 'at index', currentIndex, 'of', path.path.length) console.log( 'toPlace', move.toPlace.map((p) => p.vec), @@ -502,7 +589,8 @@ export class ThePathfinder { // wrap this code in a try-catch as we intentionally throw errors. try { - while (!(await executor._align(move, tickCount++, goal)) && tickCount < 999) { + while (!(await executor.align(move, tickCount++, goal)) && tickCount < 999) { + this.check() await this.bot.waitForTicks(1) } @@ -511,33 +599,34 @@ export class ThePathfinder { // allow the initial execution of this code. await executor._performInit(move, currentIndex, path.path) + this.check() let adding = await executor._performPerTick(move, tickCount++, currentIndex, path.path) while (!(adding as boolean) && tickCount < 999) { + this.check() await this.bot.waitForTicks(1) adding = await executor._performPerTick(move, tickCount++, currentIndex, path.path) } currentIndex += adding as number + console.log('done with move', move.exitPos, this.bot.entity.position, this.bot.entity.position.distanceTo(move.exitPos)) } catch (err) { // immediately exit since we want to abort the entire path. if (err instanceof AbortError) { - this.currentExecutor.reset() - - // eslint-disable-next-line no-labels - break outer + console.log('hi, aborted') + executor.reset() + delete this.resetReason + return } else if (err instanceof ResetError) { - this.currentExecutor.reset() - this.allowRetry = true + this.resetReason = err.reason + executor.reset() - // eslint-disable-next-line no-labels - break outer + console.log('fuck', err.reason) + return } else if (err instanceof CancelError) { // allow recovery if movement intentionall canceled. await this.recovery(move, path, goal, entry) - - // eslint-disable-next-line no-labels - break outer + return } else throw err } } @@ -595,14 +684,19 @@ export class ThePathfinder { } async cleanupAll (goal: goals.Goal, lastMove?: MovementExecutor): Promise { + if (goal instanceof goals.GoalDynamic && goal.dynamic) { + goal.cleanup?.('normal') + } + await this.cleanupBot() if (lastMove != null && !this.cancelCalculation) await goal.onFinish(lastMove) await this.cleanupBot() this.bot.chat(this.world.getCacheSize()) this.world.clearCache() this.cancelCalculation = false + this.userCancelled = false this.executing = false - this.allowRetry = false + delete this.resetReason delete this.currentGoal delete this.currentExecutingPath diff --git a/src/abstract/algorithms/astar.ts b/src/abstract/algorithms/astar.ts index c7559fc..f8dddec 100644 --- a/src/abstract/algorithms/astar.ts +++ b/src/abstract/algorithms/astar.ts @@ -69,7 +69,7 @@ export class AStar implements Algorithm makeResult (status: string, node: PathNode): Path> { console.log( status, - this.goal, + // this.goal, performance.now() - this.startTime, node.g, this.closedDataSet.size, diff --git a/src/mineflayer-specific/exceptions.ts b/src/mineflayer-specific/exceptions.ts index 2345119..8fef232 100644 --- a/src/mineflayer-specific/exceptions.ts +++ b/src/mineflayer-specific/exceptions.ts @@ -1,3 +1,5 @@ +import { ResetReason } from '../types' + export class CancelError extends Error { constructor (...args: any[]) { console.log('CancelError', args) @@ -13,8 +15,8 @@ export class AbortError extends Error { } export class ResetError extends Error { - constructor (...args: any[]) { - console.log('ResetError', args) + constructor (public readonly reason: ResetReason, ...args: any[]) { + console.log('ResetError', reason, args) super('Movement timed out: ' + args.join(' ')) } } diff --git a/src/mineflayer-specific/goals.ts b/src/mineflayer-specific/goals.ts index 0b57156..aeddff1 100644 --- a/src/mineflayer-specific/goals.ts +++ b/src/mineflayer-specific/goals.ts @@ -7,6 +7,8 @@ import { PlaceHandler } from './movements/interactionUtils' import type { Item } from 'prismarine-item' import { MovementExecutor } from './movements' import { Block } from '../types' +import { BotEvents } from 'mineflayer' +import type { Entity } from 'prismarine-entity' /** * The abstract goal definition used by the pathfinder. @@ -17,6 +19,14 @@ export abstract class Goal implements AGoal { async onFinish (node: MovementExecutor): Promise {} } +export abstract class GoalDynamic extends Goal { + dynamic = true + neverfinish = false + readonly abstract eventKey: Key + abstract hasChanged (...args: Parameters): boolean + abstract update (): void + cleanup?: (reason: 'cancel' | 'normal') => void // will be assigned later. +} /** * A goal to be directly at a specific coordinate. */ @@ -226,6 +236,13 @@ export class GoalPlaceBlock extends GoalLookAt { return new GoalPlaceBlock(world, bPos, item, distance, height) } + /** + * Prevent overlap. + */ + isEnd (node: Move): boolean { + return super.isEnd(node) && node.x !== this.x && node.y !== this.y && node.z !== this.z + } + override async onFinish (node: MovementExecutor): Promise { const bot = node.bot this.handler.loadMove(node) @@ -244,3 +261,67 @@ export class GoalPlaceBlock extends GoalLookAt { await this.handler.perform(bot, item) } } + +interface GoalFollowEntityOpts { + neverfinish?: boolean + dynamic?: boolean + +} +export class GoalFollowEntity extends GoalDynamic<'entityMoved'> { + readonly eventKey = 'entityMoved' as const + + public x: number + public y: number + public z: number + public sqDist: number + + constructor (public readonly refVec: Vec3, distance: number, opts: GoalFollowEntityOpts = {}) { + super() + this.x = refVec.x + this.y = refVec.y + this.z = refVec.z + this.sqDist = Math.pow(distance, 2) + this.neverfinish = opts.neverfinish ?? false + this.dynamic = opts.dynamic ?? true + } + + static fromEntity (entity: { position: Vec3 }, distance: number, opts: GoalFollowEntityOpts): GoalFollowEntity { + return new GoalFollowEntity(entity.position, distance, opts) + } + + isEnd (node: Move): boolean { + const dx = this.x - node.x + const dy = this.y - node.y + const dz = this.z - node.z + + return dx * dx + dy * dy + dz * dz <= this.sqDist + } + + heuristic (node: Move): number { + const dx = this.x - node.x + const dy = this.y - node.y + const dz = this.z - node.z + + return Math.sqrt(dx * dx + dy * dy + dz * dz) + } + + hasChanged (e: Entity): boolean { + // if (e.position !== this.refVec) return false; + const dx = this.x - this.refVec.x + const dy = this.y - this.refVec.y + const dz = this.z - this.refVec.z + + const ret = Math.abs(dx * dx) + Math.abs(dy * dy) + Math.abs(dz * dz) > this.sqDist + if (ret) { + this.update() + } + + return ret + } + + update (): void { + this.x = this.refVec.x + this.y = this.refVec.y + this.z = this.refVec.z + } +} diff --git a/src/mineflayer-specific/movements/controls.ts b/src/mineflayer-specific/movements/controls.ts index d7d18fd..7659cd4 100644 --- a/src/mineflayer-specific/movements/controls.ts +++ b/src/mineflayer-specific/movements/controls.ts @@ -4,27 +4,27 @@ import { Vec3 } from 'vec3' // const ZERO = (0 * Math.PI) / 12 // const PI_OVER_TWELVE = (1 * Math.PI) / 12 -// const TWO_PI_OVER_TWELVE = (2 * Math.PI) / 12 +const TWO_PI_OVER_TWELVE = (2 * Math.PI) / 12 // const THREE_PI_OVER_TWELVE = (3 * Math.PI) / 12 -// const FOUR_PI_OVER_TWELVE = (4 * Math.PI) / 12 -const FIVE_PI_OVER_TWELVE = (5 * Math.PI) / 12 +const FOUR_PI_OVER_TWELVE = (4 * Math.PI) / 12 +// const FIVE_PI_OVER_TWELVE = (5 * Math.PI) / 12 // const SIX_PI_OVER_TWELVE = (6 * Math.PI) / 12 -const SEVEN_PI_OVER_TWELVE = (7 * Math.PI) / 12 -// const EIGHT_PI_OVER_TWELVE = (8 * Math.PI) / 12 +// const SEVEN_PI_OVER_TWELVE = (7 * Math.PI) / 12 +const EIGHT_PI_OVER_TWELVE = (8 * Math.PI) / 12 // const NINE_PI_OVER_TWELVE = (9 * Math.PI) / 12 -// const TEN_PI_OVER_TWELVE = (10 * Math.PI) / 12 +const TEN_PI_OVER_TWELVE = (10 * Math.PI) / 12 // const ELEVEN_PI_OVER_TWELVE = (11 * Math.PI) / 12 // const TWELVE_PI_OVER_TWELVE = (12 * Math.PI) / 12 // const THIRTEEN_PI_OVER_TWELVE = (13 * Math.PI) / 12 -// const FOURTEEN_PI_OVER_TWELVE = (14 * Math.PI) / 12 +const FOURTEEN_PI_OVER_TWELVE = (14 * Math.PI) / 12 // const FIFTEEN_PI_OVER_TWELVE = (15 * Math.PI) / 12 -// const SIXTEEN_PI_OVER_TWELVE = (16 * Math.PI) / 12 -const SEVENTEEN_PI_OVER_TWELVE = (17 * Math.PI) / 12 +const SIXTEEN_PI_OVER_TWELVE = (16 * Math.PI) / 12 +// const SEVENTEEN_PI_OVER_TWELVE = (17 * Math.PI) / 12 // const EIGHTEEN_PI_OVER_TWELVE = (18 * Math.PI) / 12 -const NINETEEN_PI_OVER_TWELVE = (19 * Math.PI) / 12 -// const TWENTY_PI_OVER_TWELVE = (20 * Math.PI) / 12 +// const NINETEEN_PI_OVER_TWELVE = (19 * Math.PI) / 12 +const TWENTY_PI_OVER_TWELVE = (20 * Math.PI) / 12 // const TWENTY_ONE_PI_OVER_TWELVE = (21 * Math.PI) / 12 -// const TWENTY_TWO_PI_OVER_TWELVE = (22 * Math.PI) / 12 +const TWENTY_TWO_PI_OVER_TWELVE = (22 * Math.PI) / 12 // const TWENTY_THREE_PI_OVER_TWELVE = (23 * Math.PI) / 12 // const TWENTY_FOUR_PI_OVER_TWELVE = (24 * Math.PI) / 12 const TWO_PI = 2 * Math.PI @@ -54,7 +54,7 @@ function findDiff (position: Vec3, velocity: Vec3, yaw: number, pitch: number, n // another method of doing this is vel.y > 0 ? 2 : 1 // const offset = bot.entity.position.plus(bot.entity.velocity.scaled(amt > 0.15 ? 2 : 1)); let scale = onGround ? 0 : 1 - if (amt > 0.16) scale = 2 + if (amt > 0.17) scale = 2 if (position.distanceTo(nextPoint) < 0.3) scale = 0 if (amt < 0.02) scale = 0 const offset = position.plus(velocity.scaled(scale)) @@ -163,15 +163,13 @@ export function strafeMovement (ctx: EntityState, nextPoint: Vec3): void { */ // currentPoint, export function botStrafeMovement (bot: Bot, nextPoint: Vec3): void { - // const diff = findDiff(bot.entity.position, bot.entity.velocity, bot.entity.yaw, bot.entity.pitch, nextPoint, bot.entity.onGround) + const diff = findDiff(bot.entity.position, bot.entity.velocity, bot.entity.yaw, bot.entity.pitch, nextPoint, bot.entity.onGround) - // bot.entity.position.distanceTo(nextPoint) < 0.3 || - // if (true) { + if (bot.entity.position.distanceTo(nextPoint) < 0.1) { // console.log('stopping since near goal') - bot.setControlState('left', false) - bot.setControlState('right', false) - - // } + bot.setControlState('left', false) + bot.setControlState('right', false) + } // const lookDiff = wrapRadians(wrapRadians(bot.entity.yaw)) @@ -179,19 +177,19 @@ export function botStrafeMovement (bot: Bot, nextPoint: Vec3): void { // console.log('strafe diff', diff, diff / Math.PI * 12) - // if (PI_OVER_TWELVE < diff && diff < ELEVEN_PI_OVER_TWELVE) { - // // console.log('going left') - // bot.setControlState('left', false) // are these reversed? tf - // bot.setControlState('right', true) - // } else if (THIRTEEN_PI_OVER_TWELVE < diff && diff < TWENTY_THREE_PI_OVER_TWELVE) { - // // console.log('going right') - // bot.setControlState('left', true) - // bot.setControlState('right', false) - // } else { - // // console.log('going neither strafe') - // bot.setControlState('left', false) - // bot.setControlState('right', false) - // } + if (FOURTEEN_PI_OVER_TWELVE < diff && diff < TWENTY_TWO_PI_OVER_TWELVE) { + // console.log('going left') + bot.setControlState('left', false) // are these reversed? tf + bot.setControlState('right', true) + } else if (TWO_PI_OVER_TWELVE < diff && diff < TEN_PI_OVER_TWELVE) { + // console.log('going right') + bot.setControlState('left', true) + bot.setControlState('right', false) + } else { + // console.log('going neither strafe') + bot.setControlState('left', false) + bot.setControlState('right', false) + } } /** @@ -217,16 +215,16 @@ export function smartMovement (ctx: EntityState, nextPoint: Vec3, sprint = true) // diff = wrapRadians(diff + lookDiff) - // console.log('forward/back diff', diff, diff / Math.PI * 12) + console.log('forward/back diff', diff, diff / Math.PI * 12) - if (SEVEN_PI_OVER_TWELVE < diff && diff < SEVENTEEN_PI_OVER_TWELVE) { + if (EIGHT_PI_OVER_TWELVE < diff && diff < SIXTEEN_PI_OVER_TWELVE) { // console.log('going back') ctx.control.set('forward', false) ctx.control.set('sprint', false) ctx.control.set('back', true) // console.log("back"); - } else if (NINETEEN_PI_OVER_TWELVE < diff || diff < FIVE_PI_OVER_TWELVE) { + } else if (TWENTY_PI_OVER_TWELVE < diff || diff < FOUR_PI_OVER_TWELVE) { // console.log('going forward') ctx.control.set('forward', true) ctx.control.set('sprint', sprint) @@ -251,7 +249,7 @@ export function botSmartMovement (bot: Bot, nextPoint: Vec3, sprint: boolean): v const diff = findDiff(bot.entity.position, bot.entity.velocity, bot.entity.yaw, bot.entity.pitch, nextPoint, bot.entity.onGround) if (bot.entity.position.distanceTo(nextPoint) < 0.1) { - console.log('stopping since near goal') + // console.log('stopping since near goal') bot.setControlState('forward', false) bot.setControlState('back', false) return @@ -265,14 +263,14 @@ export function botSmartMovement (bot: Bot, nextPoint: Vec3, sprint: boolean): v // console.log('forward/back diff', diff, diff / Math.PI * 12) - if (SEVEN_PI_OVER_TWELVE < diff && diff < SEVENTEEN_PI_OVER_TWELVE) { + if (EIGHT_PI_OVER_TWELVE < diff && diff < SIXTEEN_PI_OVER_TWELVE) { // console.log('going back') bot.setControlState('forward', false) bot.setControlState('sprint', false) bot.setControlState('back', true) // console.log("back"); - } else if (NINETEEN_PI_OVER_TWELVE < diff || diff < FIVE_PI_OVER_TWELVE) { + } else if (TWENTY_PI_OVER_TWELVE < diff || diff < FOUR_PI_OVER_TWELVE) { // console.log('going forward') bot.setControlState('forward', true) bot.setControlState('sprint', sprint) diff --git a/src/mineflayer-specific/movements/movementExecutor.ts b/src/mineflayer-specific/movements/movementExecutor.ts index 22eca24..4d725b3 100644 --- a/src/mineflayer-specific/movements/movementExecutor.ts +++ b/src/mineflayer-specific/movements/movementExecutor.ts @@ -6,16 +6,21 @@ import { World } from '../world/worldInterface' import { BreakHandler, InteractHandler, InteractOpts, PlaceHandler, RayType } from './interactionUtils' import { AbortError, CancelError, ResetError } from '../exceptions' import { Movement, MovementOptions } from './movement' -import { AABB, AABBUtils } from '@nxg-org/mineflayer-util-plugin' +import { AABB, AABBUtils, Task } from '@nxg-org/mineflayer-util-plugin' import { BaseSimulator, Controller, EPhysicsCtx, EntityPhysics, EntityState, SimulationGoal } from '@nxg-org/mineflayer-physics-util' import { botStrafeMovement, botSmartMovement } from './controls' // temp typing interface AbortOpts { - resetting?: boolean + reason?: ResetError | AbortError timeout?: number } +export interface CompleteOpts { + ticks?: number + entry?: boolean +} + export abstract class MovementExecutor extends Movement { /** * Current move being executed. @@ -49,12 +54,14 @@ export abstract class MovementExecutor extends Movement { /** * Whether or not this movement has been cancelled/aborted. */ - public cancelled = false + public aborted = false /** * Whether or not this movement is asking for a reset. */ - public resetting = false + public resetReason?: AbortOpts['reason'] + + private task: Task = new Task() public constructor (bot: Bot, world: World, settings: Partial = {}) { super(bot, world, settings) @@ -64,62 +71,81 @@ export abstract class MovementExecutor extends Movement { } public reset (): void { - this.cancelled = false - this.resetting = false + this.aborted = false + delete this.resetReason } /** * TODO: Implement. */ public async abort (move: Move = this.currentMove, settings: AbortOpts = {}): Promise { - if (this.cancelled || this.resetting) return + if (this.aborted || this.resetReason != null) return + + const resetting = settings.reason - const timeout = settings.timeout ?? 1000 - const resetting = settings.resetting ?? false + this.aborted = true + + this.resetReason = resetting + + await this.task.promise + + this.task = new Task() + } - this.cancelled = true + private async holdUntilAborted (move: Move, task: Task, timeout = 1000): Promise { + if (!this.aborted && this.resetReason == null) return - this.resetting = resetting + console.log('aborting') + let start = performance.now() for (const breakTarget of move.toBreak) { await breakTarget._abort(this.bot) } + console.log('aborted breaks', performance.now() - start) + start = performance.now() + for (const place of move.toPlace) { await place._abort(this.bot) } + console.log('aborted places', performance.now() - start) + start = performance.now() + // TODO: handle bug (nextMove not included). await new Promise((resolve, reject) => { const listener = (): void => { if (this.safeToCancel(move)) { this.bot.off('physicsTick', listener) + task.finish() resolve() } } this.bot.on('physicsTick', listener) setTimeout(() => { this.bot.off('physicsTick', listener) + task.finish() reject(new Error('Movement failed to abort properly.')) }, timeout) }) + + console.log('aborted all', performance.now() - start) + if (this.resetReason != null) throw this.resetReason // new ResetError('Movement is resetting.') + if (this.aborted) throw new AbortError('Movement aborted.') } - public _performInit (thisMove: Move, currentIndex: number, path: Move[]): void | Promise { - if (this.resetting) throw new ResetError('Movement is resetting.') - if (this.cancelled) throw new AbortError('Movement aborted.') - return this.performInit(thisMove, currentIndex, path) + public async _performInit (thisMove: Move, currentIndex: number, path: Move[]): Promise { + await this.holdUntilAborted(thisMove, this.task) + return await this.performInit(thisMove, currentIndex, path) } public async _performPerTick (thisMove: Move, tickCount: number, currentIndex: number, path: Move[]): Promise { - if (this.resetting) throw new ResetError('Movement is resetting.') - if (this.cancelled) throw new AbortError('Movement aborted.') + await this.holdUntilAborted(thisMove, this.task) return await this.performPerTick(thisMove, tickCount, currentIndex, path) } public async _align (thisMove: Move, tickCount: number, goal: goals.Goal): Promise { - if (this.resetting) throw new ResetError('Movement is resetting.') - if (this.cancelled) throw new AbortError('Movement aborted.') + await this.holdUntilAborted(thisMove, this.task) return await this.align(thisMove, tickCount, goal) } @@ -154,54 +180,10 @@ export abstract class MovementExecutor extends Movement { */ align (thisMove: Move, tickCount?: number, goal?: goals.Goal, lookTarget?: Vec3): boolean | Promise { const target = lookTarget ?? thisMove.entryPos - if (lookTarget != null) void this.alignToPath(thisMove, { lookAt: target }) - else void this.alignToPath(thisMove) - - const off0 = thisMove.exitPos.minus(this.bot.entity.position) - const off1 = thisMove.exitPos.minus(target) + if (lookTarget != null) void this.postInitAlignToPath(thisMove, { lookAt: target }) + else void this.postInitAlignToPath(thisMove) - off0.translate(0, -off0.y, 0) - off1.translate(0, -off1.y, 0) - - const similarDirection = off0.normalize().dot(off1.normalize()) > 0.95 - - const bb0 = AABBUtils.getEntityAABBRaw({ position: this.bot.entity.position, width: 0.6, height: 1.8 }) - - let bb1: AABB[] - let bb2: AABB[] - let bb1Good - let bb2Good - if ((this.bot.entity as any).isInWater as boolean) { - console.log('in water') - const bb1bl = this.getBlockInfo(target, 0, 0, 0) - bb1 = [AABB.fromBlock(bb1bl.position)] - bb1Good = bb1bl.liquid - - const bb2bl = this.getBlockInfo(thisMove.exitPos.floored(), 0, 0, 0) - bb2 = [AABB.fromBlock(bb2bl.position)] - bb2Good = bb2bl.liquid - } else { - console.log('on land') - const bb1bl = this.getBlockInfo(target, 0, -1, 0) - bb1 = bb1bl.getBBs() - // if (bb1.length === 0) bb1.push(AABB.fromBlock(bb1bl.position)) - bb1Good = bb1bl.physical - - const bb2bl = thisMove.moveType.getBlockInfo(thisMove.exitPos.floored(), 0, -1, 0) - bb2 = bb2bl.getBBs() - // if (bb2.length === 0) bb2.push(AABB.fromBlock(bb1bl.position)) - bb2Good = bb2bl.physical - } - - console.log(bb0, bb1, bb2, bb1.some((b) => b.collides(bb0)), bb1Good, bb2.some((b) => b.collides(bb0)), bb2Good) - - if ((bb1.some((b) => b.collides(bb0)) && bb1Good) || (bb2.some((b) => b.collides(bb0)) && bb2Good)) { - console.log('hi', similarDirection, this.bot.entity.position.xzDistanceTo(target)) - if (similarDirection) return true - else if (this.bot.entity.position.xzDistanceTo(target) < 0.2) return true// this.isLookingAtYaw(target) - } - - return false + return this.isInitAligned(thisMove, target) } /** @@ -221,7 +203,7 @@ export abstract class MovementExecutor extends Movement { * Does so via velocity direction check (heading towards the block) * and bounding box check (touching OR slightly above block). */ - protected isComplete (startMove: Move, endMove: Move = startMove, ticks = 1): boolean { + protected isComplete (startMove: Move, endMove: Move = startMove, opts: CompleteOpts = {}): boolean { // console.log('isComplete:', this.toBreakLen(), this.toPlaceLen()) if (this.toBreakLen() > 0) return false if (this.toPlaceLen() > 0) return false @@ -230,6 +212,9 @@ export abstract class MovementExecutor extends Movement { if (!this.cI.allowExit) return false } + const ticks = opts.ticks ?? 1 + + const target = endMove.exitPos const offset = endMove.exitPos.minus(this.bot.entity.position) const dir = endMove.exitPos.minus(startMove.entryPos) @@ -263,24 +248,35 @@ export abstract class MovementExecutor extends Movement { let bbCheckCond = false let weGood = false - const aboveWater = !ectx.state.isInWater && !ectx.state.onGround && this.bot.pathfinder.world.getBlockInfo(this.bot.entity.position.floored().translate(0, -0.6, 0)).liquid + const aboveWater = + !ectx.state.isInWater && + !ectx.state.onGround && + this.bot.pathfinder.world.getBlockInfo(this.bot.entity.position.floored().translate(0, -0.6, 0)).liquid if (aboveWater) { - bb1bl = this.bot.pathfinder.world.getBlockInfo(endMove.exitPos.floored()) + bb1bl = this.bot.pathfinder.world.getBlockInfo(target.floored()) bbCheckCond = bb1bl.safe const bb1s = AABB.fromBlockPos(bb1bl.position) weGood = bb1s.collides(bb0) && bbCheckCond // && !(this.bot.entity as any).isCollidedHorizontally; } else if (ectx.state.isInWater) { - bb1bl = this.bot.pathfinder.world.getBlockInfo(endMove.exitPos.floored()) + bb1bl = this.bot.pathfinder.world.getBlockInfo(target.floored()) bbCheckCond = bb1bl.liquid const bb1s = AABB.fromBlock(bb1bl.position) weGood = bb1s.collides(bb0) && bbCheckCond // && !(this.bot.entity as any).isCollidedHorizontally; // console.log('water check', bb1bl.block?.type, bb1s, bb0, bbCheckCond) } else { - bb1bl = this.bot.pathfinder.world.getBlockInfo(endMove.exitPos.floored().translate(0, -1, 0)) + bb1bl = this.bot.pathfinder.world.getBlockInfo(target.floored().translate(0, -1, 0)) bbCheckCond = bb1bl.physical const bb1s = bb1bl.getBBs() weGood = bb1s.some((b) => b.collides(bb0)) && bbCheckCond && pos.y >= bb1bl.height // && !(this.bot.entity as any).isCollidedHorizontally; - // console.log('land check', endMove.exitPos, bb1bl.block, bb1s, bb0, bbCheckCond) + // console.log( + // "land check", + // endMove.exitPos, + // bb1bl.block?.name, + // bb1s, + // bb0, + // bbCheckCond, + // bb1s.some((b) => b.collides(bb0)) + // ); } // const bbOff = new Vec3(0, ectx.state.isInWater ? 0 : -1, 0) @@ -290,10 +286,10 @@ export abstract class MovementExecutor extends Movement { // startMove.moveType.getBlockInfo(endMove.exitPos.floored(), 0, -1, 0).physical; // console.info('bb0', bb0, 'bb1s', bb1s) - console.log(weGood, similarDirection, offset.y <= 0, this.bot.entity.position) + // console.log(weGood, similarDirection, offset.y <= 0, this.bot.entity.position); // console.info('end move exit pos', endMove.exitPos.toString()) if (weGood) { - console.log(offset.normalize().dot(dir.normalize()), similarDirection, headingThatWay, ectx.state.isCollidedHorizontally, ectx.state.isCollidedVertically) + // console.log(offset.normalize().dot(dir.normalize()), similarDirection, headingThatWay, ectx.state.isCollidedHorizontally, ectx.state.isCollidedVertically) if (similarDirection && headingThatWay) return !ectx.state.isCollidedHorizontally else if (dist < 0.2) return true @@ -318,11 +314,66 @@ export abstract class MovementExecutor extends Movement { ) } + public isInitAligned (thisMove: Move, target: Vec3 = thisMove.entryPos): boolean { + target = thisMove.entryPos + const off0 = thisMove.exitPos.minus(this.bot.entity.position) + const off1 = thisMove.exitPos.minus(target) + + if (this.bot.entity.position.y < thisMove.entryPos.y - 1) throw new CancelError('MovementExecutor: bot is too low.') + // const xzVel = this.bot.entity.velocity.offset(0, -this.bot.entity.velocity.y, 0); + + // console.log(off0.dot(off1), off0, off1) + + off0.translate(0, -off0.y, 0) + off1.translate(0, -off1.y, 0) + + const similarDirection = off0.normalize().dot(off1.normalize()) > 0.95 + console.log(similarDirection, thisMove.moveType.constructor.name, target, thisMove.entryPos, thisMove.exitPos) + // if (!similarDirection) { + const bb0 = AABBUtils.getEntityAABBRaw({ position: this.bot.entity.position, width: 0.6, height: 1.8 }) + + const bb1bl = this.getBlockInfo(target, 0, -1, 0) + const bb1 = bb1bl.getBBs() + if (bb1.length === 0) bb1.push(AABB.fromBlock(bb1bl.position)) + const bb1physical = bb1bl.physical || bb1bl.liquid + + const bb2bl = this.getBlockInfo(thisMove.exitPos.floored(), 0, -1, 0) + const bb2 = bb2bl.getBBs() + if (bb2.length === 0) bb2.push(AABB.fromBlock(bb2bl.position)) + const bb2physical = bb2bl.physical || bb2bl.liquid + + console.log( + this.toPlaceLen(), + bb1bl.block?.name, + bb1, + bb2bl.block?.name, + bb2, + 'test', + bb0, + bb1.some((b) => b.collides(bb0)), + bb1physical, + bb2.some((b) => b.collides(bb0)), + bb2physical, + bb2bl + ) + // console.log(bb0.collides(bb1), bb0, bb1, this.bot.entity.position.distanceTo(thisMove.entryPos)) + if ((bb1.some((b) => b.collides(bb0)) && bb1physical) || (bb2.some((b) => b.collides(bb0)) && bb2physical)) { + console.log('yay', similarDirection, this.bot.entity.position.xzDistanceTo(target)) + if (similarDirection) return true + else { + if (this.bot.entity.position.xzDistanceTo(target) < 0.2) return true // this.isLookingAtYaw(target); + if (bb2.some((b) => b.collides(bb0)) && bb2physical) return true + } + } + + return false + } + /** * Lazy code. */ public safeToCancel (startMove: Move, endMove: Move = startMove): boolean { - return this.bot.entity.onGround || (this.bot.entity as any).isInWater as boolean + return this.bot.entity.onGround || ((this.bot.entity as any).isInWater as boolean) } /** @@ -392,10 +443,13 @@ export abstract class MovementExecutor extends Movement { // const dy = vec3.y - this.bot.entity.position.y // const dz = vec3.z - this.bot.entity.position.z - // console.log("lookAt", Math.atan2(-dx, -dz), Math.atan2(dy, Math.sqrt(dx * dx + dz * dz))) - if (this.isLookingAt(vec3, 0.001)) return - return await this.bot.lookAt(vec3, force) + await this.bot.lookAt(vec3, force) + + // console.log("lookAt", this.bot.entity.yaw, Math.atan2(-dx, -dz), Math.atan2(dy, Math.sqrt(dx * dx + dz * dz))); + + // this.bot.entity.yaw = Math.atan2(-dx, -dz) + // this.bot.entity.pitch = Math.atan2(dy, Math.sqrt(dx * dx + dz * dz)) - Math.PI / 2 } public isLookingAt (vec3: Vec3, limit = 0.01): boolean { @@ -441,16 +495,16 @@ export abstract class MovementExecutor extends Movement { // const yaw = wrapRadians(Math.atan2(-dx, -dz)) // fuck it, I'm being lazy. - const bl = this.bot.blockAtCursor(256) as unknown as RayType | null - // console.log(bl) - if (bl == null) return false + // const bl = this.bot.blockAtCursor(256) as unknown as RayType | null; + // if (bl == null) return false; // const blPosXZ = bl.position.offset(0, -bl.position, 0) // const vec3XZ = vec3.offset(0, -vec3.y, 0) + const inter = this.bot.util.getViewDir() const eyePos = this.bot.entity.position.offset(0, 1.62, 0) - const inter = bl.intersect.minus(eyePos) - inter.translate(0, -inter.y, 0) + // const inter = bl.intersect.minus(eyePos); + // inter.translate(0, -inter.y, 0); const pos1 = vec3.minus(eyePos) pos1.translate(0, -pos1.y, 0) @@ -505,13 +559,16 @@ export abstract class MovementExecutor extends Movement { return this.sim.simulateUntil(goal, () => {}, controller, this.simCtx, this.world, maxTicks) } - protected async alignToPath (startMove: Move, opts?: { handleBack?: boolean, lookAt?: Vec3, lookAtYaw?: Vec3, sprint?: boolean }): Promise - protected async alignToPath ( + protected async postInitAlignToPath ( + startMove: Move, + opts?: { handleBack?: boolean, lookAt?: Vec3, lookAtYaw?: Vec3, sprint?: boolean } + ): Promise + protected async postInitAlignToPath ( startMove: Move, endMove?: Move, opts?: { handleBack?: boolean, lookAt?: Vec3, lookAtYaw?: Vec3, sprint?: boolean } ): Promise - protected async alignToPath (startMove: Move, endMove?: any, opts?: any): Promise { + protected async postInitAlignToPath (startMove: Move, endMove?: any, opts?: any): Promise { if (endMove === undefined) { endMove = startMove opts = {} @@ -542,17 +599,29 @@ export abstract class MovementExecutor extends Movement { // console.log("target", target, opts) - let task - if (target !== endMove.exitPos) task = await this.lookAt(target) - else task = await this.lookAtPathPos(target) + if (target !== endMove.exitPos) { + await this.lookAt(target) + if (!this.isLookingAt(target, 0.01)) return + } else { + await this.lookAtPathPos(target) + if (!this.isLookingAtYaw(target, 0.01)) { + console.log('failed yaw check') + return + } + } // this.bot.chat(`/particle flame ${endMove.exitPos.x} ${endMove.exitPos.y} ${endMove.exitPos.z} 0 0.5 0 0 10 force`); botStrafeMovement(this.bot, endMove.exitPos) botSmartMovement(this.bot, endMove.exitPos, sprint) - console.trace( - this.bot.entity.position.distanceTo(endMove.exitPos), this.bot.entity.position.distanceTo(endMove.exitPos.offset(0, 1, 0)), - ' | ', + console.log(this.bot.entity.yaw) + console.log( + target, + startMove.entryPos, + endMove.exitPos, + startMove === endMove, + this.bot.entity.position.distanceTo(endMove.exitPos), + '\n | ', this.bot.getControlState('forward'), this.bot.getControlState('back'), ' | ', @@ -563,7 +632,7 @@ export abstract class MovementExecutor extends Movement { this.bot.getControlState('jump'), this.bot.getControlState('sneak') ) - await task + // await task; // if (this.bot.entity.position.xzDistanceTo(target) > 0.3) // // botSmartMovement(this.bot, endMove.exitPos, true); // this.bot.setControlState("forward", true); diff --git a/src/mineflayer-specific/movements/movementExecutors.ts b/src/mineflayer-specific/movements/movementExecutors.ts index a33e2ab..1534e23 100644 --- a/src/mineflayer-specific/movements/movementExecutors.ts +++ b/src/mineflayer-specific/movements/movementExecutors.ts @@ -5,7 +5,7 @@ import { CancelError } from '../exceptions' import { BlockInfo } from '../world/cacheWorld' import { PlaceHandler, RayType } from './interactionUtils' import { AABB, AABBUtils } from '@nxg-org/mineflayer-util-plugin' -import { MovementExecutor } from './movementExecutor' +import { CompleteOpts, MovementExecutor } from './movementExecutor' import { JumpCalculator, ParkourJumpHelper, getUnderlyingBBs, leavingBlockLevel, stateLookAt } from './movementUtils' import { EPhysicsCtx } from '@nxg-org/mineflayer-physics-util' @@ -24,12 +24,15 @@ export class NewForwardExecutor extends MovementExecutor { const placementVecs = this.toPlace().map((p) => AABB.fromBlock(p.vec)) const near = placementVecs.some((p) => p.distanceToVec(eyePos) < PlaceHandler.reach + 2) + // console.log('this.currentMove?.toPlace.length === 0 || !near', this.currentMove?.toPlace.length === 0 || !near) + return this.currentMove?.toPlace.length === 0 || !near } override async align (thisMove: Move, tickCount: number, goal: goals.Goal): Promise { - const faceForward = await this.faceForward() + if (this.doWaterLogic()) return await super.align(thisMove, tickCount, goal) + const faceForward = await this.faceForward() let target if (faceForward) { target = thisMove.entryPos.floored().translate(0.5, 0, 0.5) @@ -38,7 +41,34 @@ export class NewForwardExecutor extends MovementExecutor { target = offset } - return await super.align(thisMove, tickCount, goal, target) + await this.postInitAlignToPath(thisMove, { lookAt: target }) + return this.isInitAligned(thisMove, target) + } + + async landAlign (thisMove: Move, tickCount: number, goal: goals.Goal): Promise { + const faceForward = await this.faceForward() + + const target = thisMove.entryPos.floored().translate(0.5, 0, 0.5) + if (faceForward) { + void this.postInitAlignToPath(thisMove, { lookAt: target }) + // void this.lookAt(target); + // this.bot.setControlState("forward", true); + // if (this.bot.food <= 6) this.bot.setControlState("sprint", false); + // else this.bot.setControlState("sprint", true); + } else { + const offset = this.bot.entity.position.minus(target).plus(this.bot.entity.position) + void this.postInitAlignToPath(thisMove, { lookAt: offset }) + // void this.lookAt(offset); + // this.bot.setControlState("forward", false); + // this.bot.setControlState("sprint", false); + // this.bot.setControlState("back", true); + } + + // return this.isComplete(thisMove, thisMove, {entry: true}) + // console.log("align", this.bot.entity.position, thisMove.exitPos, this.bot.entity.position.xzDistanceTo(thisMove.exitPos), this.bot.entity.onGround) + // return this.bot.entity.position.distanceTo(thisMove.entryPos) < 0.2 && this.bot.entity.onGround; + + return this.isInitAligned(thisMove, target) } async performInit (thisMove: Move, currentIndex: number, path: Move[]): Promise { @@ -49,11 +79,11 @@ export class NewForwardExecutor extends MovementExecutor { const faceForward = await this.faceForward() if (faceForward) { - await this.alignToPath(thisMove) + await this.postInitAlignToPath(thisMove) } else { const offset = this.bot.entity.position.minus(thisMove.exitPos).plus(this.bot.entity.position) console.log('here!', thisMove.exitPos, this.bot.entity.position, offset) - await this.alignToPath(thisMove, { lookAt: offset, sprint: true }) + await this.postInitAlignToPath(thisMove, { lookAt: offset, sprint: true }) } } @@ -101,7 +131,7 @@ export class NewForwardExecutor extends MovementExecutor { if (ctx.state.pos.y > thisMove.entryPos.y) return false const nextPos = path[++currentIndex] - let offset = 0.3 + let offset = 0.4 if (currentIndex < path.length) { if (nextPos.toPlace.length > 0 || nextPos.toBreak.length > 0) offset = 0.8 @@ -109,6 +139,10 @@ export class NewForwardExecutor extends MovementExecutor { if (nextPos.exitPos.y > thisMove.entryPos.y) { offset = 0.8 } + + if (nextPos.exitPos.y - thisMove.entryPos.y > 2) { + offset = 0.8 + } } if (thisMove.entryPos.xzDistanceTo(ctx.state.pos) > thisMove.entryPos.xzDistanceTo(thisMove.exitPos) - offset) { @@ -126,7 +160,7 @@ export class NewForwardExecutor extends MovementExecutor { return false } else if (this.cI == null) { // const start = performance.now() - const test = await this.interactPossible(15) + const test = await this.interactPossible(5) if (test != null) { void this.performInteraction(test) return false @@ -136,12 +170,12 @@ export class NewForwardExecutor extends MovementExecutor { // if (tickCount > 160) throw new CancelError("ForwardMove: tickCount > 160"); if ( - !this.bot.entity.onGround && - this.bot.entity.position.y < thisMove.entryPos.y && // + (!this.bot.entity.onGround && !this.bot.getControlState('jump') && - !this.doWaterLogic() + !this.doWaterLogic() && + this.canJump(thisMove, currentIndex, path)) || this.bot.entity.position.y < (Math.round(thisMove.entryPos.y) - 0.6) ) { - // console.log(this.bot.entity.position, this.bot.entity.velocity); + console.log(this.bot.entity.position, thisMove.entryPos) throw new CancelError('ForwardMove: not on ground') } @@ -150,11 +184,11 @@ export class NewForwardExecutor extends MovementExecutor { if (faceForward) { const jump = this.canJump(thisMove, currentIndex, path) this.bot.setControlState('jump', jump) - void this.alignToPath(thisMove) + void this.postInitAlignToPath(thisMove) return this.isComplete(thisMove) } else { const offset = this.bot.entity.position.minus(thisMove.exitPos).plus(this.bot.entity.position) - void this.alignToPath(thisMove, { lookAt: offset }) + void this.postInitAlignToPath(thisMove, { lookAt: offset }) return this.isComplete(thisMove) } } @@ -219,19 +253,21 @@ export class ForwardExecutor extends MovementExecutor { const target = thisMove.entryPos.floored().translate(0.5, 0, 0.5) if (faceForward) { - void this.alignToPath(thisMove, { lookAt: target }) + void this.postInitAlignToPath(thisMove, { lookAtYaw: target }) // void this.lookAt(target); // this.bot.setControlState("forward", true); // if (this.bot.food <= 6) this.bot.setControlState("sprint", false); // else this.bot.setControlState("sprint", true); } else { const offset = this.bot.entity.position.minus(target).plus(this.bot.entity.position) - void this.alignToPath(thisMove, { lookAt: offset }) + void this.postInitAlignToPath(thisMove, { lookAt: offset }) // void this.lookAt(offset); // this.bot.setControlState("forward", false); // this.bot.setControlState("sprint", false); // this.bot.setControlState("back", true); } + + // return this.isComplete(thisMove, thisMove, {entry: true}) // console.log("align", this.bot.entity.position, thisMove.exitPos, this.bot.entity.position.xzDistanceTo(thisMove.exitPos), this.bot.entity.onGround) // return this.bot.entity.position.distanceTo(thisMove.entryPos) < 0.2 && this.bot.entity.onGround; @@ -278,7 +314,7 @@ export class ForwardExecutor extends MovementExecutor { const faceForward = await this.faceForward() if (faceForward) { - await this.alignToPath(thisMove) + await this.postInitAlignToPath(thisMove) // void this.lookAt(thisMove.entryPos); // this.bot.setControlState("forward", true); // if (this.bot.food <= 6) this.bot.setControlState("sprint", false); @@ -287,7 +323,7 @@ export class ForwardExecutor extends MovementExecutor { const offset = this.bot.entity.position.minus(thisMove.exitPos).plus(this.bot.entity.position) console.log('here!', thisMove.exitPos, this.bot.entity.position, offset) // void this.lookAt(offset); - await this.alignToPath(thisMove, { lookAt: offset, sprint: true }) + await this.postInitAlignToPath(thisMove, { lookAt: offset, sprint: true }) // this.bot.setControlState('forward', false) // this.bot.setControlState('sprint', false) // this.bot.setControlState('back', true) @@ -441,12 +477,12 @@ export class ForwardExecutor extends MovementExecutor { const nextMove = path[this.currentIndex] if (currentIndex !== this.currentIndex && nextMove !== undefined) { // this.lookAt(nextMove.exitPos, true); - void this.alignToPath(thisMove, nextMove) + void this.postInitAlignToPath(thisMove, nextMove) if (this.isComplete(thisMove, nextMove)) return this.currentIndex - currentIndex // if (this.bot.entity.position.xzDistanceTo(nextMove.exitPos) < 0.2) return this.currentIndex - currentIndex; } else { // this.lookAt(thisMove.exitPos, true); - void this.alignToPath(thisMove) + void this.postInitAlignToPath(thisMove) return this.isComplete(thisMove) // return this.bot.entity.position.xzDistanceTo(thisMove.exitPos) < 0.2; } @@ -454,13 +490,13 @@ export class ForwardExecutor extends MovementExecutor { const jump = this.canJump(thisMove, currentIndex, path) // console.log("should jump", jump); this.bot.setControlState('jump', jump) - void this.alignToPath(thisMove) + void this.postInitAlignToPath(thisMove) return this.isComplete(thisMove) // return this.bot.entity.position.xzDistanceTo(thisMove.exitPos) < 0.2; } } else { const offset = this.bot.entity.position.minus(thisMove.exitPos).plus(this.bot.entity.position) - void this.alignToPath(thisMove, { lookAt: offset }) + void this.postInitAlignToPath(thisMove, { lookAt: offset }) return this.isComplete(thisMove) } @@ -477,7 +513,7 @@ export class ForwardJumpExecutor extends MovementExecutor { private flag = false protected isComplete (startMove: Move, endMove?: Move): boolean { - return super.isComplete(startMove, endMove, 0) + return super.isComplete(startMove, endMove, { ticks: 0 }) } override async align (thisMove: Move, tickCount: number, goal: goals.Goal): Promise { @@ -538,7 +574,7 @@ export class ForwardJumpExecutor extends MovementExecutor { let info = await thisMove.toPlace[0].performInfo(this.bot, 0) if (info.raycasts.length === 0) { console.log('info') - void this.alignToPath(thisMove, { lookAt: thisMove.entryPos }) + void this.postInitAlignToPath(thisMove, { lookAt: thisMove.entryPos }) await this.performInteraction(thisMove.toPlace[0]) } else { console.log('no info') @@ -549,20 +585,20 @@ export class ForwardJumpExecutor extends MovementExecutor { console.log('did first place!') this.bot.setControlState('jump', true) - await this.alignToPath(thisMove, { lookAt: thisMove.entryPos }) + await this.postInitAlignToPath(thisMove, { lookAt: thisMove.entryPos }) // this.bot.setControlState('back', false) // this.bot.setControlState('sprint', false) // this.bot.setControlState('forward', true) // this.bot.setControlState('jump', true) while (this.bot.entity.position.y - thisMove.exitPos.y < 0) { - await this.alignToPath(thisMove, { lookAt: thisMove.entryPos }) + await this.postInitAlignToPath(thisMove, { lookAt: thisMove.entryPos }) await this.bot.waitForTicks(1) // console.log('loop 0') } info = await thisMove.toPlace[1].performInfo(this.bot) while (info.raycasts.length === 0) { - await this.alignToPath(thisMove, { lookAt: thisMove.entryPos }) + await this.postInitAlignToPath(thisMove, { lookAt: thisMove.entryPos }) await this.bot.waitForTicks(1) info = await thisMove.toPlace[1].performInfo(this.bot) @@ -635,7 +671,7 @@ export class ForwardJumpExecutor extends MovementExecutor { } } - void this.alignToPath(thisMove) + void this.postInitAlignToPath(thisMove) // void this.lookAtPathPos(thisMove.exitPos); // this.bot.setControlState("forward", true); @@ -682,7 +718,7 @@ export class NewForwardJumpExecutor extends ForwardJumpExecutor { override async performPerTick (thisMove: Move, tickCount: number, currentIndex: number, path: Move[]): Promise { if ((this.bot.entity as any).isInWater as boolean) { this.bot.setControlState('jump', this.bot.entity.position.y < thisMove.exitPos.y) - void this.alignToPath(thisMove) + void this.postInitAlignToPath(thisMove) return this.isComplete(thisMove) } else { return await super.performPerTick(thisMove, tickCount, currentIndex, path) @@ -704,7 +740,7 @@ export class ForwardDropDownExecutor extends MovementExecutor { async performInit (thisMove: Move, currentIndex: number, path: Move[]): Promise { this.currentIndex = currentIndex - await this.alignToPath(thisMove, { sprint: true }) + await this.postInitAlignToPath(thisMove) // console.log(thisMove.exitPos, thisMove.x, thisMove.y, thisMove.z); // this.bot.setControlState("forward", true); @@ -763,22 +799,22 @@ export class ForwardDropDownExecutor extends MovementExecutor { // off0.dot(off1) > 0.85 && if (currentIndex !== this.currentIndex && nextMove !== undefined) { // TODO: perform fall damage check to ensure this is allowed. - void this.alignToPath(thisMove, nextMove) + void this.postInitAlignToPath(thisMove, nextMove) // console.log("hi", this.bot.entity.position, nextMove.exitPos, this.bot.entity.position.xzDistanceTo(nextMove.exitPos), this.bot.entity.position.y, nextMove.exitPos.y) // if (this.bot.entity.position.xzDistanceTo(nextMove.exitPos) < 0.2 && this.bot.entity.position.y === nextMove.exitPos.y) if (this.isComplete(thisMove, nextMove)) return this.currentIndex - currentIndex // } } else { - void this.alignToPath(thisMove, thisMove) + void this.postInitAlignToPath(thisMove, thisMove) // console.log(this.bot.entity.position, thisMove.exitPos, thisMove.entryPos, thisMove.exitPos.xzDistanceTo(this.bot.entity.position), thisMove.entryPos.xzDistanceTo(this.bot.entity.position)) // if (this.bot.entity.position.xzDistanceTo(thisMove.exitPos) < 0.2 && this.bot.entity.position.y === thisMove.exitPos.y) return true; if (this.isComplete(thisMove, thisMove)) return true } } else { - const nextMove = path[++currentIndex] - if (currentIndex < path.length) void this.alignToPath(thisMove, { lookAtYaw: nextMove.entryPos, sprint: true }) - else void this.alignToPath(thisMove, { sprint: true }) + // const nextMove = path[++currentIndex] + if (currentIndex < path.length) void this.postInitAlignToPath(thisMove) + else void this.postInitAlignToPath(thisMove) if (this.isComplete(thisMove)) return true // if (this.bot.entity.position.xzDistanceTo(thisMove.exitPos) < 0.2 && this.bot.entity.position.y === thisMove.exitPos.y) return true; @@ -867,7 +903,7 @@ export class StraightUpExecutor extends MovementExecutor { this.bot.setControlState('jump', true) const target = thisMove.exitPos.floored().translate(0.5, 0, 0.5) - void this.alignToPath(thisMove, { lookAt: target }) + void this.postInitAlignToPath(thisMove, { lookAt: target }) const off0 = thisMove.exitPos.minus(this.bot.entity.position) const off1 = thisMove.exitPos.minus(target) @@ -1011,8 +1047,8 @@ export class ParkourForwardExecutor extends MovementExecutor { private stepAmt = 2 - protected isComplete (startMove: Move, endMove?: Move, ticks?: number): boolean { - return super.isComplete(startMove, endMove, 0) + protected isComplete (startMove: Move, endMove?: Move, opts: CompleteOpts = {}): boolean { + return super.isComplete(startMove, endMove, opts) } async align (thisMove: Move, tickCount: number, goal: goals.Goal): Promise { @@ -1156,7 +1192,7 @@ export class ParkourForwardExecutor extends MovementExecutor { async performInit (thisMove: Move, currentIndex: number, path: Move[]): Promise { delete this.backUpTarget this.reachedBackup = false - await this.alignToPath(thisMove) + await this.postInitAlignToPath(thisMove) // await this.lookAtPathPos(thisMove.exitPos); // this.bot.chat(`/particle flame ${thisMove.exitPos.x} ${thisMove.exitPos.y} ${thisMove.exitPos.z} 0 0.5 0 0 10 force`) @@ -1172,7 +1208,7 @@ export class ParkourForwardExecutor extends MovementExecutor { this.bot.clearControlStates() if (this.executing) { this.bot.setControlState('jump', false) - void this.alignToPath(thisMove) + void this.postInitAlignToPath(thisMove) return this.isComplete(thisMove) } @@ -1184,7 +1220,7 @@ export class ParkourForwardExecutor extends MovementExecutor { bbs.push(AABB.fromBlockPos(thisMove.entryPos)) } - void this.alignToPath(thisMove) + void this.postInitAlignToPath(thisMove) // this.lookAtPathPos(thisMove.exitPos) // console.log('CALLED TEST IN PER TICK', this.bot.entity.position, this.shitterTwo.getUnderlyingBBs(this.bot.entity.position,0.6)) diff --git a/src/mineflayer-specific/movements/movementProviders.ts b/src/mineflayer-specific/movements/movementProviders.ts index 18b8db0..f6de33e 100644 --- a/src/mineflayer-specific/movements/movementProviders.ts +++ b/src/mineflayer-specific/movements/movementProviders.ts @@ -204,7 +204,7 @@ export class ForwardJump extends MovementProvider { if ((cost += this.safeOrBreak(blockA, toBreak)) > 100) return if ((cost += this.safeOrBreak(blockB, toBreak)) > 100) return if ((cost += this.safeOrBreak(blockH, toBreak)) > 100) return - // if (toPlace.length === 2) return; + if (toPlace.length > 0) return // set cachedVec to center of block we want. neighbors.push(Move.fromPrevious(cost, blockB.position.offset(0.5, 0, 0.5), node, this, toPlace, toBreak)) diff --git a/src/mineflayer-specific/pathProducers/partialPathProducer.ts b/src/mineflayer-specific/pathProducers/partialPathProducer.ts index ff6b85e..5c25e8a 100644 --- a/src/mineflayer-specific/pathProducers/partialPathProducer.ts +++ b/src/mineflayer-specific/pathProducers/partialPathProducer.ts @@ -21,7 +21,11 @@ export class PartialPathProducer implements PathProducer { private readonly gcInterval: number = 10 private readonly lastGc: number = 0 - private readonly maxPathLen: number = 50 + // private readonly maxPathLen: number = 30 + + public get maxPathLength (): number { + return this.bot.pathfinder.pathfinderSettings.partialPathLength + } private lastAstarContext: AStar | undefined constructor (start: Move, goal: goals.Goal, settings: MovementOptions, bot: Bot, world: World, movements: ExecutorMap) { @@ -37,7 +41,7 @@ export class PartialPathProducer implements PathProducer { return this.lastAstarContext } - private handleAstarContext (foundPathLen: number, maxPathLen = this.maxPathLen): AStar | undefined { + private handleAstarContext (foundPathLen: number, maxPathLen = this.maxPathLength): AStar | undefined { // if the path length is less than 50, return the previous astar context. // otherwise, return a new one. @@ -74,7 +78,7 @@ export class PartialPathProducer implements PathProducer { const lastNode = result.path[result.path.length - 1] if (lastNode != null) { this.latestCost = this.latestCost + result.cost - console.info('Partial Path cost increased by', lastNode.cost, 'to', this.latestCost, 'total') + console.info('Partial Path cost increased by', lastNode.cost, 'to', this.latestCost, 'total', this.latestMove?.vec, 'pos') } if (result.status === 'noPath') { @@ -92,11 +96,13 @@ export class PartialPathProducer implements PathProducer { astarContext } } - } else { + } + + if (result.path.length > this.maxPathLength || result.status === 'success') { this.latestMove = result.path[result.path.length - 1] this.latestMoves.push(this.latestMove) + this.lastPath = [...this.lastPath, ...result.path] } - this.lastPath = [...this.lastPath, ...result.path] // console.log(result.path.length, 'found path length', this.lastPath.length, 'total length', this.lastPath.map(p => p.entryPos.toString()), this.lastPath[this.lastPath.length - 1].entryPos) const ret = { diff --git a/src/mineflayer-specific/world/cacheWorld.ts b/src/mineflayer-specific/world/cacheWorld.ts index ee468cc..48b9d89 100644 --- a/src/mineflayer-specific/world/cacheWorld.ts +++ b/src/mineflayer-specific/world/cacheWorld.ts @@ -175,7 +175,7 @@ export class BlockInfo { // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions b1.replaceable = BlockInfo.replaceables.has(b.type) && !b1.physical - b1.liquid = BlockInfo.liquids.has(b.type) || ((b as any)._properties?.waterlogged as boolean && b.boundingBox === 'empty') + b1.liquid = BlockInfo.liquids.has(b.type) || (Boolean((b as any)._properties?.waterlogged) && b.boundingBox === 'empty') b1.height = b.position.y b1.canFall = BlockInfo.gravityBlocks.has(b.type) b1.openable = BlockInfo.openable.has(b.type) diff --git a/src/types.ts b/src/types.ts index d0f8e3f..e44e78a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -5,7 +5,7 @@ export interface Vec3Properties { z: number } -export type ResetReason = 'blockUpdate' | 'chunkLoad' +export type ResetReason = 'blockUpdate' | 'chunkLoad' | 'goalUpdated' export type BlockType = ReturnType export type Block = import('prismarine-block').Block