Mostly works
This commit is contained in:
+44
-144
@@ -6,6 +6,17 @@ const { pathfinder, Movements, goals: { GoalNear } } = require('mineflayer-pathf
|
||||
const Vec3 = require('vec3');
|
||||
const {sleep} = require('../utils');
|
||||
|
||||
// The server sends entity_velocity packets with undefined fields (e.g.
|
||||
// packet.velocity.x is undefined). This causes fromNotchVelocity to return
|
||||
// Vec3(NaN,NaN,NaN), which corrupts the bot's velocity, which the physics tick
|
||||
// uses to update position → NaN position → physics.js deadlocks permanently.
|
||||
const _conv = require('mineflayer/lib/conversions');
|
||||
const _fromNotchVelocity = _conv.fromNotchVelocity;
|
||||
_conv.fromNotchVelocity = function(vel) {
|
||||
if (!Number.isFinite(vel.x) || !Number.isFinite(vel.y) || !Number.isFinite(vel.z))
|
||||
return new Vec3(0, 0, 0);
|
||||
return _fromNotchVelocity(vel);
|
||||
};
|
||||
|
||||
class CJbot{
|
||||
isReady = false;
|
||||
@@ -60,6 +71,7 @@ class CJbot{
|
||||
this._taskQueue = [];
|
||||
this._connecting = false;
|
||||
this._idleTimeout = args.idleTimeout || 30000;
|
||||
this._goToLock = false;
|
||||
|
||||
// If we want the be always connected, kick off the function to auto
|
||||
// reconnect
|
||||
@@ -125,6 +137,7 @@ class CJbot{
|
||||
this.mcData = minecraftData(this.bot.version);
|
||||
this.defaultMove = new Movements(this.bot, this.mcData);
|
||||
this.defaultMove.canDig = false;
|
||||
this.defaultMove.scafoldingBlocks = [];
|
||||
|
||||
/*// Make pathfinder avoid routing through chests/shulkers
|
||||
if (!this.defaultMove.blocksCost) this.defaultMove.blocksCost = {};
|
||||
@@ -155,8 +168,7 @@ class CJbot{
|
||||
this.defaultMove.allowEntityDetection = true;
|
||||
|
||||
this.bot.pathfinder.setMovements(this.defaultMove);
|
||||
this._setupAntiStuck();
|
||||
|
||||
|
||||
// Add the listeners to the bot. We do this so if the bot loses
|
||||
// connection, the mineflayer instance will also be lost.
|
||||
this.isReady = true;
|
||||
@@ -563,154 +575,42 @@ playerWithinBlock(player, block, range){
|
||||
return distance < range;
|
||||
}
|
||||
|
||||
// Global anti-stuck system: monitors every physics tick while pathfinder is
|
||||
// moving. If the bot hasn't moved for ~600ms (12 ticks), it stops the
|
||||
// pathfinder and nudges the bot in an alternating direction (back/left/right)
|
||||
// to free it from corners. This works for ALL pathfinder movement globally.
|
||||
_antiStuckNudging = false;
|
||||
|
||||
_antiStuckNudging = false;
|
||||
|
||||
_setupAntiStuck() {
|
||||
let lastPos = null;
|
||||
let stuckTicks = 0;
|
||||
|
||||
this.bot.on('physicsTick', () => {
|
||||
if (!this.bot.pathfinder.isMoving() || this._antiStuckNudging) return;
|
||||
|
||||
const pos = this.bot.entity.position;
|
||||
if (!lastPos) { lastPos = pos.clone(); return; }
|
||||
|
||||
const dist = lastPos.distanceTo(pos);
|
||||
|
||||
// On 20 TPS / LAN, any distance under 0.01 is a hard collision
|
||||
if (dist < 0.01) {
|
||||
stuckTicks++;
|
||||
|
||||
if (stuckTicks >= 15) { // 750ms is plenty of time on a 20 TPS server
|
||||
console.log(`[AntiStuck] LAN-Precision Reset at ${pos.x.toFixed(2)}, ${pos.z.toFixed(2)}`);
|
||||
|
||||
// KILL THE VIBRATION:
|
||||
// If we don't clear control states, the bot keeps 'pushing' into the corner
|
||||
this.bot.clearControlStates();
|
||||
this.bot.pathfinder.stop();
|
||||
this.bot.entity.velocity.set(0, 0, 0);
|
||||
|
||||
// SNAP TO ABSOLUTE CENTER
|
||||
const newX = Math.floor(pos.x) + 0.5;
|
||||
const newZ = Math.floor(pos.z) + 0.5;
|
||||
|
||||
// Use a 'hard' teleport to break the physics loop
|
||||
this.bot.entity.position.x = newX;
|
||||
this.bot.entity.position.z = newZ;
|
||||
|
||||
this._antiStuckNudging = true;
|
||||
setTimeout(() => { this._antiStuckNudging = false; }, 300);
|
||||
|
||||
stuckTicks = 0;
|
||||
lastPos = null;
|
||||
}
|
||||
} else {
|
||||
stuckTicks = 0;
|
||||
lastPos = pos.clone();
|
||||
}
|
||||
});
|
||||
}
|
||||
// Interrupt current movement so trade/storage commands can run
|
||||
async interruptTask(from) {
|
||||
this.bot.clearControlStates();
|
||||
}
|
||||
|
||||
async goTo(options) {
|
||||
let range = options.range || 2;
|
||||
let block = this.__blockOrVec(options.where);
|
||||
let retries = 0;
|
||||
let lastPos = this.bot.entity.position.clone();
|
||||
while (this._goToLock) await new Promise(r => setTimeout(r, 50));
|
||||
this._goToLock = true;
|
||||
try {
|
||||
let range = options.range || 2;
|
||||
let block = this.__blockOrVec(options.where);
|
||||
console.log('[goTo] moving to', block.position, 'range', range);
|
||||
|
||||
console.log(`[goTo] Starting path to ${block.position} with range ${range}`);
|
||||
while(!this.isWithinRange(block.position, range)){
|
||||
try{
|
||||
console.log('[goTo] loop: isMoving=', this.bot.pathfinder.isMoving(), 'inRange=', this.isWithinRange(block.position, range));
|
||||
if(this.bot.pathfinder.isMoving()){
|
||||
await sleep(500);
|
||||
continue;
|
||||
}
|
||||
await this.bot.pathfinder.goto(
|
||||
new GoalNear(...block.position.toArray(), range)
|
||||
);
|
||||
}catch(error){
|
||||
console.log('CJbot.goTo while loop error:', error)
|
||||
await sleep(500);
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for path updates to detect partial paths
|
||||
const pathUpdateHandler = (results) => {
|
||||
console.log(`[Pathfinder] New path found. Length: ${results.path.length} | Status: ${results.status}`);
|
||||
};
|
||||
this.bot.on('path_update', pathUpdateHandler);
|
||||
return true;
|
||||
} finally {
|
||||
this._goToLock = false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
while (!this.isWithinRange(block.position, range)) {
|
||||
try {
|
||||
await this.bot.pathfinder.goto(new GoalNear(...block.position.toArray(), range));
|
||||
console.log(`[goTo] Successfully reached goal.`);
|
||||
break;
|
||||
} catch (error) {
|
||||
retries++;
|
||||
const errorMsg = error.message || error;
|
||||
console.log(`%c[goTo] Error on Attempt ${retries}: ${errorMsg}`, "color: red;");
|
||||
|
||||
this.bot.pathfinder.setGoal(null);
|
||||
|
||||
const botPos = this.bot.entity.position;
|
||||
const dist = botPos.distanceTo(block.position);
|
||||
console.log(`[Debug] Target Block: ${this.bot.blockAt(block.position)?.name} | Bot Pos: ${botPos} | Distance: ${dist.toFixed(2)}`);
|
||||
|
||||
// If we're within extended range on a partial path, call it good enough
|
||||
if (dist <= range + 4) {
|
||||
console.log(`[goTo] Close enough (${dist.toFixed(2)} <= ${range + 4}), accepting partial path`);
|
||||
break;
|
||||
}
|
||||
|
||||
// Detect stuck-ness: compare position to last attempt
|
||||
const moved = botPos.distanceTo(lastPos);
|
||||
console.log(`[goTo] Moved ${moved.toFixed(2)} blocks this attempt`);
|
||||
lastPos = botPos.clone();
|
||||
|
||||
// Visual refresh — look around to reveal more blocks
|
||||
console.log(`[goTo] Performing 360 refresh...`);
|
||||
for (let i = 0; i < 4; i++) {
|
||||
await this.bot.look(this.bot.entity.yaw + Math.PI / 2, 0, true);
|
||||
await sleep(150);
|
||||
}
|
||||
|
||||
// Every 3 retries, try unblocking via perpendicular movement
|
||||
if (retries % 3 === 0) {
|
||||
console.log(`[goTo] Attempting to unstick via perpendicular waypoint`);
|
||||
this.bot.pathfinder.setGoal(null);
|
||||
|
||||
const yaw = Math.atan2(
|
||||
block.position.z - botPos.z,
|
||||
block.position.x - botPos.x
|
||||
);
|
||||
const perpAngles = [yaw + Math.PI / 2, yaw - Math.PI / 2, yaw + Math.PI];
|
||||
for (const perpYaw of perpAngles) {
|
||||
const dx = Math.cos(perpYaw) * 3;
|
||||
const dz = Math.sin(perpYaw) * 3;
|
||||
const waypoint = botPos.offset(dx, 0, dz);
|
||||
try {
|
||||
this.bot.pathfinder.setGoal(new GoalNear(waypoint.x, waypoint.y, waypoint.z, 1), true);
|
||||
await sleep(1500);
|
||||
this.bot.pathfinder.setGoal(null);
|
||||
console.log(`[goTo] Perpendicular waypoint reached`);
|
||||
break;
|
||||
} catch (e) {
|
||||
// Try next direction
|
||||
}
|
||||
}
|
||||
|
||||
// Small backward nudge
|
||||
this.bot.setControlState('back', true);
|
||||
await sleep(300);
|
||||
this.bot.clearControlStates();
|
||||
await sleep(200);
|
||||
}
|
||||
|
||||
await sleep(200);
|
||||
|
||||
if (retries >= 20) {
|
||||
console.log(`[goTo] Failed after ${retries} attempts — giving up`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.bot.removeListener('path_update', pathUpdateHandler);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async goToReturn(options){
|
||||
let here = this.bot.entity.position;
|
||||
|
||||
Reference in New Issue
Block a user