Fuzzy item matching + periodic inventory check for auto-equip

- equipBestWeapon uses fuzzy substring matching on item names
- equipBestArmor uses .includes() instead of .startsWith() for tiers
- Both strip minecraft: prefix before comparing
- Added periodic inventory check every 5 seconds (playerCollect
  doesn't fire on Bedrock) — detects new items and auto-equips
- evaluateEquipment logs full inventory contents for debugging
- All name comparisons are case-insensitive

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
roberts 2026-03-30 20:22:08 -05:00
parent aa0a937171
commit 9869181071

View file

@ -386,19 +386,36 @@ async function evaluateEquipment() {
if (Date.now() - lastEquipCheck < 3000) return; if (Date.now() - lastEquipCheck < 3000) return;
lastEquipCheck = Date.now(); lastEquipCheck = Date.now();
// Log what we have
const allItems = bot.inventory.items();
if (allItems.length > 0) {
log('client', 'INFO', `Inventory: ${allItems.map(i => i.name).join(', ')}`);
}
const weaponResult = await equipBestWeapon(); const weaponResult = await equipBestWeapon();
if (weaponResult.equipped) { if (weaponResult.equipped) {
log('client', 'INFO', `🛡 Auto-equipped weapon: ${weaponResult.item}`); log('client', 'INFO', `Auto-equipped weapon: ${weaponResult.item}`);
sendEvent('equipment_changed', { type: 'weapon', item: weaponResult.item }); sendEvent('equipment_changed', { type: 'weapon', item: weaponResult.item });
} }
const armorResult = await equipBestArmor(); const armorResult = await equipBestArmor();
if (armorResult.equipped) { if (armorResult.equipped) {
log('client', 'INFO', `🛡 Auto-equipped armor: ${armorResult.items.join(', ')}`); log('client', 'INFO', `Auto-equipped armor: ${armorResult.items.join(', ')}`);
sendEvent('equipment_changed', { type: 'armor', items: armorResult.items }); sendEvent('equipment_changed', { type: 'armor', items: armorResult.items });
} }
} }
// Also check inventory periodically (playerCollect may not fire on Bedrock)
setInterval(() => {
if (!spawned) return;
const items = bot.inventory.items();
const hash = items.map(i => `${i.name}:${i.count}`).sort().join(',');
if (hash !== lastInventoryHash && hash !== '') {
lastInventoryHash = hash;
evaluateEquipment();
}
}, 5000);
// --- Player-friendly name → Bedrock item ID mapping --- // --- Player-friendly name → Bedrock item ID mapping ---
const ITEM_ALIASES = { const ITEM_ALIASES = {
// Plural → singular // Plural → singular
@ -634,24 +651,41 @@ async function equipBestWeapon() {
let bestTier = -1; let bestTier = -1;
for (const item of items) { for (const item of items) {
const name = item.name.replace('minecraft:', ''); // Fuzzy match: strip prefix, try exact and partial matching
const tier = WEAPON_TIERS[name] || 0; const rawName = (item.name || '').replace('minecraft:', '').toLowerCase();
let tier = WEAPON_TIERS[rawName] || 0;
// Also try matching by substring (e.g. item.name contains "diamond_sword")
if (tier === 0) {
for (const [weaponName, weaponTier] of Object.entries(WEAPON_TIERS)) {
if (rawName.includes(weaponName) || weaponName.includes(rawName)) {
tier = weaponTier;
break;
}
}
}
if (tier > bestTier) { if (tier > bestTier) {
bestTier = tier; bestTier = tier;
bestItem = item; bestItem = item;
} }
} }
if (bestItem && bestItem !== bot.heldItem) { if (bestItem) {
// Check if already holding this item
const heldName = bot.heldItem?.name?.replace('minecraft:', '').toLowerCase() || '';
const bestName = bestItem.name.replace('minecraft:', '').toLowerCase();
if (heldName === bestName) {
return { equipped: false, reason: 'already_equipped' };
}
try { try {
await bot.equip(bestItem, 'hand'); await bot.equip(bestItem, 'hand');
log('client', 'INFO', `Equipped ${bestItem.name}`); log('client', 'INFO', `Equipped ${bestItem.name}`);
return { equipped: true, item: bestItem.name }; return { equipped: true, item: bestItem.name };
} catch (e) { } catch (e) {
log('client', 'WARN', `Failed to equip ${bestItem.name}: ${e.message}`);
return { equipped: false, error: e.message }; return { equipped: false, error: e.message };
} }
} }
return { equipped: false, reason: bestItem ? 'already_equipped' : 'no_weapons' }; return { equipped: false, reason: 'no_weapons' };
} }
async function equipBestTool(blockType) { async function equipBestTool(blockType) {
@ -704,9 +738,9 @@ async function equipBestArmor() {
let bestTierIdx = 999; let bestTierIdx = 999;
for (const item of items) { for (const item of items) {
const name = item.name.replace('minecraft:', ''); const name = (item.name || '').replace('minecraft:', '').toLowerCase();
if (!name.includes(armorPiece)) continue; if (!name.includes(armorPiece)) continue;
const tierIdx = ARMOR_TIERS.findIndex(t => name.startsWith(t)); const tierIdx = ARMOR_TIERS.findIndex(t => name.includes(t));
if (tierIdx >= 0 && tierIdx < bestTierIdx) { if (tierIdx >= 0 && tierIdx < bestTierIdx) {
bestTierIdx = tierIdx; bestTierIdx = tierIdx;
bestItem = item; bestItem = item;
@ -717,6 +751,7 @@ async function equipBestArmor() {
try { try {
await bot.equip(bestItem, slot); await bot.equip(bestItem, slot);
equipped.push(bestItem.name); equipped.push(bestItem.name);
log('client', 'INFO', `Equipped armor: ${bestItem.name}${slot}`);
} catch (e) { } catch (e) {
log('client', 'WARN', `Failed to equip ${bestItem.name}: ${e.message}`); log('client', 'WARN', `Failed to equip ${bestItem.name}: ${e.message}`);
} }