Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions src/mineflayer-specific/movements/movementExecutors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ export class IdleMovementExecutor extends MovementExecutor {
}

export class NewForwardExecutor extends MovementExecutor {
// Stall guard state: how many ticks in a row the bot has barely moved, and the
// position we last measured from. Used to detect being wedged against a block
// corner (see performPerTick) so the path executor can replan instead of
// grinding forever. Number of "no progress" ticks before we give up.
private stuckTicks = 0
private stuckLastPos: Vec3 | null = null
private static readonly STUCK_UNSTICK_AT = 12 // ticks of no progress before we try to unstick
private static readonly STUCK_TICK_LIMIT = 40 // ticks of no progress before we give up and replan
private static readonly STUCK_MIN_STEP = 0.05 // XZ blocks/tick below which we count as "no progress"

protected isComplete (startMove: Move, endMove?: Move, opts?: CompleteOpts): boolean {
return super.isComplete(startMove, endMove, { ticks: 2 })
}
Expand Down Expand Up @@ -201,6 +211,49 @@ export class NewForwardExecutor extends MovementExecutor {
throw new CancelError(`ForwardMove: not on ground. Target pos: ${thisMove.exitPos}, us: ${this.bot.entity.position}`)
}

// Stall guard. Walking along a wall, the bot can wedge its hitbox against a
// block corner: it keeps pressing forward (mostly along the path, into the
// wall) and stops moving. Mining/placing returned above, so reaching here
// means we expect to be travelling.
//
// When we notice no XZ progress, first try to UNSTICK: aim straight at the
// centre of the target block and stop sprinting. Slowing down and steering
// at the centre adds the sideways push needed to slide off the corner,
// instead of ramming the wall. Only if that fails for a whole second do we
// give up with a CancelError so the path executor can replan from here.
if (tickCount === 0) {
this.stuckTicks = 0
this.stuckLastPos = null
}
const here = this.bot.entity.position
// Mining/placing legitimately keeps the bot still, and a slow dig can outlast
// the stall window. An interaction in progress (this.cI != null) is NOT a
// movement stall — the allowExternalInfluence check above lets a reachable dig
// fall through to here — so don't count those ticks against the stall budget.
if (this.cI != null) {
this.stuckTicks = 0
} else if (this.stuckLastPos != null && here.xzDistanceTo(this.stuckLastPos) < NewForwardExecutor.STUCK_MIN_STEP) {
this.stuckTicks++
} else if (this.stuckTicks > 0) {
this.stuckTicks-- // made progress: ease back out of unstick mode
}
this.stuckLastPos = here.clone()

if (this.stuckTicks >= NewForwardExecutor.STUCK_TICK_LIMIT) {
this.stuckTicks = 0
this.stuckLastPos = null
throw new CancelError(`ForwardMove: stuck (no progress) near ${thisMove.exitPos}`)
Comment on lines +242 to +245

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Don't treat long block breaking as a movement stall

When a Forward/Diagonal move starts a dig that takes more than 40 ticks, this.cI remains set while the block is still being broken, and BreakHandler.performInfo returns finite ticks: 0 for any reachable block, so the allowExternalInfluence check at the top falls through into this stall guard instead of returning. The bot is commonly pressed against the still-solid target block during that dig, so XZ progress stays below the threshold and this throws CancelError before slow-but-valid mining, such as stone by hand or obsidian with a pickaxe, can finish.

Useful? React with 👍 / 👎.

}

if (this.stuckTicks >= NewForwardExecutor.STUCK_UNSTICK_AT) {
const centre = thisMove.exitPos.floored().translate(0.5, 0, 0.5)
this.bot.setControlState('jump', false)
this.bot.setControlState('sprint', false)
botSmartMovement(this.bot, centre, false)
botStrafeMovement(this.bot, centre)
return this.isComplete(thisMove)
}

const faceForward = await this.faceForward()

if (faceForward) {
Expand Down
Loading