AI works here

This commit is contained in:
2026-04-30 12:48:29 -04:00
parent 92024c8a64
commit 6f4519894b
16 changed files with 1793 additions and 303 deletions
+138 -137
View File
@@ -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){