forked from wmantly/mc-bot-town
AI works here
This commit is contained in:
+138
-137
@@ -125,9 +125,8 @@ class CJbot{
|
||||
this.mcData = minecraftData(this.bot.version);
|
||||
this.defaultMove = new Movements(this.bot, this.mcData);
|
||||
this.defaultMove.canDig = false;
|
||||
this.defaultMove.allowSprinting = false;
|
||||
|
||||
// Make pathfinder avoid routing through chests/shulkers
|
||||
/*// Make pathfinder avoid routing through chests/shulkers
|
||||
if (!this.defaultMove.blocksCost) this.defaultMove.blocksCost = {};
|
||||
const avoidBlocks = ['chest', 'trapped_chest', 'ender_chest',
|
||||
'shulker_box', 'white_shulker_box', 'orange_shulker_box',
|
||||
@@ -139,7 +138,21 @@ class CJbot{
|
||||
for (const name of avoidBlocks) {
|
||||
const block = this.mcData.blocksByName[name];
|
||||
if (block) this.defaultMove.blocksCost[block.id] = 100;
|
||||
}
|
||||
}*/
|
||||
|
||||
// 1. Disable sprinting globally. This is the #1 cause of clipping
|
||||
// on high-TPS servers because the bot moves too fast for its own
|
||||
// rotation speed.
|
||||
this.defaultMove.allowSprinting = false;
|
||||
|
||||
|
||||
this.defaultMove.entityCost = 100;
|
||||
this.defaultMove.allow1by1towers = false;
|
||||
|
||||
|
||||
// 3. Entity Intersections
|
||||
// Set this to true to make the bot more aware of collision boxes
|
||||
this.defaultMove.allowEntityDetection = true;
|
||||
|
||||
this.bot.pathfinder.setMovements(this.defaultMove);
|
||||
this._setupAntiStuck();
|
||||
@@ -535,7 +548,7 @@ class CJbot{
|
||||
return distance <= range+.9;
|
||||
}
|
||||
|
||||
playerWithinBlock(player, block, range){
|
||||
playerWithinBlock(player, block, range){
|
||||
let playerData = this.bot.players[player];
|
||||
if(!playerData || !playerData.entity) return; // Skip if no entity info
|
||||
|
||||
@@ -556,159 +569,147 @@ class CJbot{
|
||||
// to free it from corners. This works for ALL pathfinder movement globally.
|
||||
_antiStuckNudging = false;
|
||||
|
||||
_setupAntiStuck() {
|
||||
let lastPos = null;
|
||||
let stuckTicks = 0;
|
||||
let nudgeTicks = 0;
|
||||
let nudgeCount = 0;
|
||||
let idleTicks = 0;
|
||||
_antiStuckNudging = false;
|
||||
|
||||
this.bot.on('physicsTick', () => {
|
||||
// During a nudge, count ticks then clear controls
|
||||
if (this._antiStuckNudging) {
|
||||
nudgeTicks++;
|
||||
if (nudgeTicks >= 8) { // ~400ms of nudge movement
|
||||
this.bot.clearControlStates();
|
||||
this._antiStuckNudging = false;
|
||||
nudgeTicks = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
_setupAntiStuck() {
|
||||
let lastPos = null;
|
||||
let stuckTicks = 0;
|
||||
|
||||
// Track position regardless of pathfinder state — goTo clears
|
||||
// the goal between retries which would reset our counter
|
||||
const pos = this.bot.entity.position;
|
||||
this.bot.on('physicsTick', () => {
|
||||
if (!this.bot.pathfinder.isMoving() || this._antiStuckNudging) return;
|
||||
|
||||
if (!this.bot.pathfinder.isMoving()) {
|
||||
// Not pathfinding — only reset if we've been idle a while
|
||||
// (short gaps between goTo retries shouldn't reset)
|
||||
idleTicks++;
|
||||
if (idleTicks > 40) { // ~2 seconds of no pathfinding = truly idle
|
||||
stuckTicks = 0;
|
||||
lastPos = null;
|
||||
nudgeCount = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
const pos = this.bot.entity.position;
|
||||
if (!lastPos) { lastPos = pos.clone(); return; }
|
||||
|
||||
idleTicks = 0;
|
||||
const dist = lastPos.distanceTo(pos);
|
||||
|
||||
if (!lastPos) {
|
||||
lastPos = pos.clone();
|
||||
return;
|
||||
}
|
||||
// On 20 TPS / LAN, any distance under 0.01 is a hard collision
|
||||
if (dist < 0.01) {
|
||||
stuckTicks++;
|
||||
|
||||
if (lastPos.distanceTo(pos) < 0.05) {
|
||||
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);
|
||||
|
||||
if (stuckTicks >= 12) { // ~600ms with no movement
|
||||
nudgeCount++;
|
||||
const directions = ['back', 'left', 'right'];
|
||||
const dir = directions[nudgeCount % 3];
|
||||
console.log(`AntiStuck: No movement for ${stuckTicks} ticks, nudging ${dir}`);
|
||||
// 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;
|
||||
|
||||
// Stop pathfinder so our controls take effect
|
||||
this.bot.pathfinder.stop();
|
||||
|
||||
// Apply nudge direction
|
||||
this.bot.setControlState(dir, true);
|
||||
this._antiStuckNudging = true;
|
||||
nudgeTicks = 0;
|
||||
stuckTicks = 0;
|
||||
lastPos = null;
|
||||
}
|
||||
} else {
|
||||
stuckTicks = 0;
|
||||
lastPos = pos.clone();
|
||||
}
|
||||
});
|
||||
}
|
||||
this._antiStuckNudging = true;
|
||||
setTimeout(() => { this._antiStuckNudging = false; }, 300);
|
||||
|
||||
stuckTicks = 0;
|
||||
lastPos = null;
|
||||
}
|
||||
} else {
|
||||
stuckTicks = 0;
|
||||
lastPos = pos.clone();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async goTo(options) {
|
||||
let range = options.range || 2;
|
||||
let block = this.__blockOrVec(options.where);
|
||||
let retries = 0;
|
||||
let noPathCount = 0;
|
||||
const maxRetries = options.maxRetries || 5;
|
||||
const unjamDirections = ['back', 'left', 'right', 'forward'];
|
||||
let range = options.range || 2;
|
||||
let block = this.__blockOrVec(options.where);
|
||||
let retries = 0;
|
||||
let lastPos = this.bot.entity.position.clone();
|
||||
|
||||
while(!this.isWithinRange(this.__blockOrVec(options.where).position, range)){
|
||||
if(retries >= maxRetries){
|
||||
// All pathfinder attempts failed — clear goal, relocate, retry fresh
|
||||
console.log(`goTo: Pathfinder failed ${maxRetries} times, relocating and retrying`);
|
||||
console.log(`[goTo] Starting path to ${block.position} with range ${range}`);
|
||||
|
||||
this.bot.pathfinder.setGoal(null);
|
||||
await sleep(200);
|
||||
// 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);
|
||||
|
||||
// Walk backward and randomly strafe to a new position
|
||||
const side = Math.random() < 0.5 ? 'left' : 'right';
|
||||
this.bot.setControlState('back', true);
|
||||
this.bot.setControlState(side, true);
|
||||
await sleep(600);
|
||||
this.bot.clearControlStates();
|
||||
await sleep(500);
|
||||
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;");
|
||||
|
||||
// Reset retries and try again
|
||||
retries = 0;
|
||||
continue;
|
||||
}
|
||||
this.bot.pathfinder.setGoal(null);
|
||||
|
||||
try{
|
||||
// Timeout pathfinder after 30 seconds to prevent infinite hangs
|
||||
await Promise.race([
|
||||
this.bot.pathfinder.goto(
|
||||
new GoalNear(...block.position.toArray(), range)
|
||||
),
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => {
|
||||
this.bot.pathfinder.stop();
|
||||
reject(new Error('goTo: Pathfinder timed out after 30s'));
|
||||
}, 30000)
|
||||
)
|
||||
]);
|
||||
}catch(error){
|
||||
retries++;
|
||||
const msg = error.message || String(error);
|
||||
const target = block.position;
|
||||
console.log(`goTo: Attempt ${retries}/${maxRetries} to ${target} error: ${msg}`);
|
||||
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)}`);
|
||||
|
||||
// "No path" = pathfinder searched full graph, route doesn't exist.
|
||||
// Walking around won't help — skip this target after 2 attempts.
|
||||
if(msg.includes('No path')){
|
||||
noPathCount++;
|
||||
if(noPathCount >= 2){
|
||||
console.log(`goTo: No path to ${target} after ${noPathCount} attempts, skipping`);
|
||||
this.bot.pathfinder.setGoal(null);
|
||||
return false;
|
||||
}
|
||||
// One retry: increase range in case we're just barely blocked
|
||||
range = Math.min(range + 1, 5);
|
||||
console.log(`goTo: No path, increasing range to ${range} and retrying`);
|
||||
this.bot.pathfinder.setGoal(null);
|
||||
await sleep(500);
|
||||
continue;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Wait for any ongoing anti-stuck nudge to finish
|
||||
while(this._antiStuckNudging) await sleep(100);
|
||||
// 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();
|
||||
|
||||
// Clear pathfinder state
|
||||
this.bot.pathfinder.setGoal(null);
|
||||
await sleep(200);
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Unjam: cycle through different directions each retry
|
||||
const dir = unjamDirections[(retries - 1) % unjamDirections.length];
|
||||
const side = Math.random() < 0.5 ? 'left' : 'right';
|
||||
console.log(`goTo: Unjamming — walking ${dir} + ${side}`);
|
||||
this.bot.setControlState(dir, true);
|
||||
this.bot.setControlState(side, true);
|
||||
await sleep(800);
|
||||
this.bot.clearControlStates();
|
||||
await sleep(300);
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
|
||||
return true;
|
||||
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){
|
||||
|
||||
Reference in New Issue
Block a user