update: enhance bot functionality with new item handling and environment variable support

This commit is contained in:
ZareMate 2026-03-13 17:47:30 +01:00
parent 7976d46d05
commit 2241108f97
3 changed files with 204 additions and 21 deletions

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
/node_modules
package-lock.json
package-lock.json
.env

218
index.js
View File

@ -3,12 +3,22 @@ const fs = require("fs");
const path = require("path");
const mineflayer = require("mineflayer");
const gui = require("mineflayer-gui");
const Vec3 = require("vec3");
require("dotenv").config();
const logs = [];
const MAX_LOGS = 200;
const LOOP_DELAY_MS = 30_000;
const LOOP_DELAY_MS = 10_000;
const MAX_ALLOWED_MOVE_BLOCKS = 10;
const REJOIN_DELAY_MS = 60_000;
const BULB_VERIFY_RETRIES = 3;
const ITEM_MODE = normalizeItemMode(
process.env.ITEM_MODE || process.env.SPAWNER_MODE || "bones",
);
const BUTTON_POSITION = parseEnvPosition("BUTTON");
const BULB_POSITION = parseEnvPosition("BULB");
let spawnerLoopRunning = false;
let joinPosition = null;
@ -16,6 +26,102 @@ let hasJoined = false;
let spawnerPosition = null;
let reconnectTimer = null;
let bot = null;
let bothModeStep = 0;
function normalizeItemMode(value) {
const input = String(value || "")
.trim()
.toLowerCase();
if (input === "both") return "both";
if (input === "arrow" || input === "arrows") return "arrows";
return "bones";
}
function parseEnvPosition(prefix) {
const compact = process.env[`${prefix}_POS`];
if (compact) {
const parsed = compact
.split(/[\s,]+/)
.filter(Boolean)
.map((v) => Number(v));
if (parsed.length === 3 && parsed.every(Number.isFinite)) {
return new Vec3(parsed[0], parsed[1], parsed[2]);
}
}
const x = Number(process.env[`${prefix}_X`]);
const y = Number(process.env[`${prefix}_Y`]);
const z = Number(process.env[`${prefix}_Z`]);
if ([x, y, z].every(Number.isFinite)) {
return new Vec3(x, y, z);
}
return null;
}
function isBulbOn(block) {
if (!block) return null;
const props =
typeof block.getProperties === "function" ? block.getProperties() : null;
if (props && typeof props.lit === "boolean") return props.lit;
if (props && typeof props.powered === "boolean") return props.powered;
const name = String(block.name || "").toLowerCase();
if (name.includes("lit")) return true;
if (name.includes("unlit")) return false;
if (name.includes("redstone_lamp")) return false;
return null;
}
async function ensureBulbModeBeforeOpen(mode) {
if (!BULB_POSITION || !BUTTON_POSITION) {
pushLog("missing BULB_POS/BUTTON_POS in .env; skipping mode check");
return false;
}
const desiredBulbOn = mode === "arrows";
let bulbBlock = bot.blockAt(BULB_POSITION);
if (!bulbBlock || bulbBlock.name === "air") {
pushLog("bulb block not found at BULB_POS");
return false;
}
let currentBulbOn = isBulbOn(bulbBlock);
if (currentBulbOn === desiredBulbOn) {
return false;
}
const buttonBlock = bot.blockAt(BUTTON_POSITION);
if (!buttonBlock || buttonBlock.name === "air") {
pushLog("button block not found at BUTTON_POS");
return false;
}
for (let attempt = 1; attempt <= BULB_VERIFY_RETRIES; attempt += 1) {
try {
await bot.lookAt(buttonBlock.position.offset(0.5, 0.5, 0.5), true);
await bot.activateBlock(buttonBlock);
pushLog(
`clicked button to set bulb ${desiredBulbOn ? "on" : "off"} for ${mode} (attempt ${attempt}/${BULB_VERIFY_RETRIES})`,
);
await sleep(900);
bulbBlock = bot.blockAt(BULB_POSITION);
currentBulbOn = isBulbOn(bulbBlock);
if (currentBulbOn === desiredBulbOn) {
return true;
}
} catch (err) {
pushLog(`failed to click button: ${err.message}`);
}
}
pushLog(
`bulb state still incorrect after ${BULB_VERIFY_RETRIES} attempts (wanted ${desiredBulbOn ? "on" : "off"})`,
);
return false;
}
function createBotInstance() {
hasJoined = false;
@ -237,23 +343,32 @@ function waitForWindowOpen(timeoutMs = 5000) {
});
}
async function openSpawnerGui() {
const targetBlock = await rotateToSpawner();
if (!targetBlock || targetBlock.name === "air") {
pushLog("unable to target spawner block");
return;
async function openSpawnerGui(maxAttempts = 2) {
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
const targetBlock = await rotateToSpawner();
if (!targetBlock || targetBlock.name === "air") {
pushLog("unable to target spawner block");
return false;
}
const openPromise = waitForWindowOpen(5000);
closeCurrentWindow();
await bot.activateBlock(targetBlock);
const opened = await openPromise;
if (opened) {
return true;
}
pushLog(
`no gui opened from block '${targetBlock.name}' (attempt ${attempt}/${maxAttempts})`,
);
if (attempt < maxAttempts) {
await sleep(700);
}
}
const openPromise = waitForWindowOpen(5000);
await bot.activateBlock(targetBlock);
const opened = await openPromise;
if (!opened) {
pushLog(`no gui opened from block '${targetBlock.name}'`);
return false;
}
return true;
return false;
}
function getWindowItems() {
@ -302,6 +417,43 @@ async function dropAllBonesFromCurrentWindow() {
pushLog(`dropped bones total: ${dropped}`);
}
async function dropAllArrowsFromCurrentWindow() {
if (!bot.currentWindow) {
pushLog("no window open to drop arrows");
return;
}
let dropped = 0;
while (true) {
const arrowStacks = getWindowItems().filter(
(item) => item && String(item.name || "").toLowerCase() === "arrow",
);
const arrowCount = arrowStacks.reduce(
(sum, item) => sum + (item.count || 0),
0,
);
if (arrowCount === 0) break;
const ok = await bot.gui
.Query()
.Window(comparator)
.Drop("Arrow", 64)
.end()
.run();
if (!ok) {
pushLog("failed to drop arrows from window");
break;
}
dropped += Math.min(64, arrowCount);
await sleep(150);
}
pushLog(`dropped arrows total: ${dropped}`);
}
function closeCurrentWindow() {
if (!bot.currentWindow) return;
if (typeof bot.closeWindow === "function") {
@ -313,13 +465,32 @@ function closeCurrentWindow() {
}
}
async function runSpawnerCycle() {
async function runSpawnerPhase(mode) {
const toggled = await ensureBulbModeBeforeOpen(mode);
await sleep(toggled ? 1300 : 500);
const opened = await openSpawnerGui();
if (!opened) return;
await dropAllBonesFromCurrentWindow();
if (mode === "arrows") {
await dropAllArrowsFromCurrentWindow();
} else {
await dropAllBonesFromCurrentWindow();
}
closeCurrentWindow();
}
async function runSpawnerCycle() {
if (ITEM_MODE === "both") {
const phase = bothModeStep < 2 ? "bones" : "arrows";
await runSpawnerPhase(phase);
bothModeStep = (bothModeStep + 1) % 4;
return;
}
await runSpawnerPhase(ITEM_MODE);
}
function startSpawnerLoop() {
if (spawnerLoopRunning) {
pushLog("spawner loop already running");
@ -332,6 +503,7 @@ function startSpawnerLoop() {
}
spawnerLoopRunning = true;
bothModeStep = 0;
pushLog("spawner loop started");
(async () => {
@ -343,7 +515,7 @@ function startSpawnerLoop() {
}
if (!spawnerLoopRunning) break;
pushLog("waiting 30s for next cycle");
pushLog("waiting 10s for next cycle");
await sleep(LOOP_DELAY_MS);
}
})();
@ -398,3 +570,11 @@ function itemToString(item) {
}
createBotInstance();
if (BULB_POSITION && BUTTON_POSITION) {
pushLog(
`mode=${ITEM_MODE}, bulb=${BULB_POSITION.x},${BULB_POSITION.y},${BULB_POSITION.z}, button=${BUTTON_POSITION.x},${BUTTON_POSITION.y},${BUTTON_POSITION.z}`,
);
} else {
pushLog("set BULB_POS and BUTTON_POS in .env to enable mode checking");
}

View File

@ -10,7 +10,9 @@
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"dotenv": "^17.3.1",
"mineflayer": "^4.35.0",
"mineflayer-gui": "^4.0.2"
"mineflayer-gui": "^4.0.2",
"vec3": "^0.1.10"
}
}