Mostly works

This commit is contained in:
2026-05-03 11:13:06 -04:00
parent 6f4519894b
commit 60663b8d4a
21 changed files with 3188 additions and 1706 deletions
+150
View File
@@ -0,0 +1,150 @@
'use strict';
const Vec3 = require('vec3');
const { goals: { GoalNear } } = require('mineflayer-pathfinder');
const { sleep } = require('../../utils');
class Navigation {
constructor(args) {
this.bot = args.bot;
this.commands = [
{
name: 'goto',
description: 'Move the bot to coordinates or a player position',
parameters: [
{ name: 'target', type: 'string', required: true, description: 'Coordinates "x y z" or player name' },
{ name: 'range', type: 'number', required: false, description: 'Stop distance from target (default: 0)' }
],
category: 'movement'
},
{
name: 'come',
description: 'Come to the requesting player with recovery on failure',
parameters: [
{ name: 'player', type: 'string', required: false, description: 'Player to come to (defaults to requester)' }
],
category: 'movement'
},
{
name: 'follow',
description: 'Go to a player and maintain distance',
parameters: [
{ name: 'target', type: 'string', required: true, description: 'Player to follow' },
{ name: 'range', type: 'number', required: false, description: 'Follow distance (default: 3)' }
],
category: 'movement'
},
{
name: 'stop',
description: 'Stop current pathfinding',
parameters: [],
category: 'movement'
}
];
}
async init() {}
async unload() {
try { this.bot.bot.pathfinder.stop(); } catch (e) { /* ignore */ }
return true;
}
async handleCommand(from, command, ...args) {
switch (command) {
case 'goto': {
const [target, rangeStr] = args;
const range = parseFloat(rangeStr) || 0;
if (!target) return 'Specify coordinates "x y z" or a player name';
// Parse coordinates
const parts = target.split(' ');
if (parts.length >= 3) {
const coords = parts.map(Number);
if (coords.every(c => !isNaN(c))) {
return this._goWithRecovery(new Vec3(coords[0], coords[1], coords[2]), range);
}
}
// Player target
const player = this.bot.bot.players[target];
if (player && player.entity) {
return this._goWithRecovery(player.entity.position, range);
}
return `Target not found: ${target}`;
}
case 'come': {
const [playerName] = args;
const target = playerName || from;
const player = this.bot.bot.players[target];
if (!player || !player.entity) return `Cannot find ${target}`;
return this._goWithRecovery(player.entity.position, 3);
}
case 'follow': {
const [target, rangeStr] = args;
const range = parseFloat(rangeStr) || 3;
const player = this.bot.bot.players[target];
if (!player || !player.entity) return `Cannot find ${target}`;
return this._goWithRecovery(player.entity.position, range);
}
case 'stop':
try { this.bot.bot.pathfinder.stop(); } catch (e) { /* ignore */ }
return 'Movement stopped';
default:
return `Unknown navigation command: ${command}`;
}
}
// Smart movement with recovery — retries with 360 scan and waypoint unstick when goTo fails
async _goWithRecovery(targetPos, range) {
let attempts = 0;
while (attempts < 3) {
const ok = await this.bot.goTo({ where: targetPos, range });
if (ok) return `Arrived at destination`;
attempts++;
console.log(`[Navigation] goTo failed (attempt ${attempts}/3), applying recovery...`);
// 360 visual refresh
const bot = this.bot.bot;
for (let i = 0; i < 4; i++) {
await bot.look(bot.entity.yaw + Math.PI / 2, 0, true);
await sleep(150);
}
// Perpendicular waypoint to unstick
const pos = bot.entity.position;
if (!isNaN(pos.x) && !isNaN(pos.z)) {
const yaw = Math.atan2(targetPos.z - pos.z, targetPos.x - pos.x);
const perps = [yaw + Math.PI / 2, yaw - Math.PI / 2, yaw + Math.PI];
for (const p of perps) {
const wx = pos.x + Math.cos(p) * 3;
const wz = pos.z + Math.sin(p) * 3;
try {
await bot.pathfinder.goto(
new GoalNear(wx, pos.y, wz, 1)
);
await sleep(1000);
bot.clearControlStates();
break;
} catch (e) { /* try next */ }
}
// Backward nudge
bot.setControlState('back', true);
await sleep(300);
bot.clearControlStates();
await sleep(200);
}
}
return 'Failed to reach destination after recovery attempts';
}
}
module.exports = Navigation;
+44 -24
View File
@@ -153,8 +153,17 @@ module.exports = {
const storage = this.plunginsLoaded['Storage'];
if (!storage) return;
storage._busy = true;
// Interrupt any active task and acquire operation lock
await this.interruptTask(from);
try {
await storage._acquireOperationLock(5000);
} catch (e) {
this.whisper(from, 'Storage is busy, try again in a moment.');
return;
}
try {
storage._busy = true;
const pending = storage.pendingWithdrawals.get(from);
await this.say('/trade accept');
@@ -220,32 +229,43 @@ module.exports = {
}
clearTimeout(timeoutCheck);
if (pending) {
// Withdrawal complete — clear pending
if (pending.timeoutId) clearTimeout(pending.timeoutId);
storage.pendingWithdrawals.delete(from);
this.whisper(from, `Withdrawal complete! Enjoy your ${pending.itemName}.`);
} else {
// Deposit — collect items from bot inventory and sort into storage
await sleep(500);
let tradeResult = null;
const hotbarNames = new Set((storage.config.hotbarItems || []).map(h => h.name));
const itemsReceived = [];
for (const item of this.bot.inventory.items()) {
if (hotbarNames.has(item.name)) continue;
itemsReceived.push({ name: item.name, id: item.type, count: item.count, nbt: item.nbt });
}
if (pending) {
// Withdrawal complete — clear pending
if (pending.timeoutId) clearTimeout(pending.timeoutId);
storage.pendingWithdrawals.delete(from);
this.whisper(from, `Withdrawal complete! Enjoy your ${pending.itemName}.`);
} else {
// Deposit — collect items from bot inventory and sort into storage
await sleep(500);
if (itemsReceived.length > 0) {
this.whisper(from, `Received ${itemsReceived.length} item type(s). Sorting into storage.`);
await storage.handleTrade(from, itemsReceived);
} else {
this.whisper(from, 'No items received.');
}
const hotbarNames = new Set((storage.config.hotbarItems || []).map(h => h.name));
const itemsReceived = [];
for (const item of this.bot.inventory.items()) {
if (hotbarNames.has(item.name)) continue;
itemsReceived.push({ name: item.name, id: item.type, count: item.count, nbt: item.nbt });
}
} finally {
storage._busy = false;
if (itemsReceived.length > 0) {
this.whisper(from, `Received ${itemsReceived.length} item type(s). Sorting into storage.`);
tradeResult = await storage.handleTrade(from, itemsReceived);
} else {
this.whisper(from, 'No items received.');
}
}
} finally {
storage._busy = false;
storage._releaseOperationLock();
}
// Organize after lock is released (handleTrade runs under the lock)
if (tradeResult && tradeResult.needsOrganize) {
try {
await storage.organizeLooseItems();
} catch (error) {
console.error('Storage: Post-trade organize failed:', error.message);
}
}
},
}
},
};
+6
View File
@@ -33,6 +33,9 @@ module.exports = {
// Check if bot has StoragePlugin
if (this.plunginsLoaded['Storage']) {
// Storage bot flow
if (this.plunginsLoaded['Ai']) {
this.plunginsLoaded['Ai']._expectingTradeWindow = true;
}
await this.say('/trade accept');
let window = await this.once('windowOpen');
@@ -80,6 +83,9 @@ module.exports = {
let chestBlock = findChestBySign(this, from);
if(!chestBlock) return this.whisper(from, `You aren't allowed to trade with me...`);
if (this.plunginsLoaded['Ai']) {
this.plunginsLoaded['Ai']._expectingTradeWindow = true;
}
await this.say('/trade accept');
let window = await this.once('windowOpen');