2025年8月13日水曜日

Mineflayer で bot.dig がうまく動作しない時の対処方法

Mineflayer で bot.dig がうまく動作しない時の対処方法

概要

Minecraft が 1.21.4 で Mineflayer が 4.31.0 の場合に bot.dig が動かないバグがあるようです
https://github.com/PrismarineJS/mineflayer/issues/3717

手にしているアイテムのエンハンス情報を取得しにいく際にバグがあるようです
今回はその回避方法を紹介します
具体的には bot.dig ではなく採掘するためのローレルベルAPIを使います

環境

  • macOS 15.6
  • Minecraft 1.21.4
  • nodejs 22.15.1
  • mineflayer 4.31.0

lib/command/dig/low_level_api.js

ポイントは digWithoutEnchant です
ここで mineflayer のローレベル API (block_dig) を使って自力で採掘処理を実装しています

const { goals } = require("mineflayer-pathfinder");
const { GoalBlock } = goals;

// ボットにピッケルを装備させる
async function equipPickaxe(bot) {
  const pickaxe = bot.inventory
    .items()
    .find((item) => item.name.includes("pickaxe"));
  if (pickaxe) {
    await bot.equip(pickaxe, "hand");
    bot.chat(`ピッケル ${pickaxe.name} を装備しました`);
  } else {
    bot.chat("ピッケルがありません! 掘削できません!");
  }
}

// bot.dig のバグ対応版 dig、低レベルAPIを使用して自前で掘る処理
// https://github.com/PrismarineJS/mineflayer/issues/3717
function digWithoutEnchant(bot, block) {
  return new Promise((resolve, reject) => {
    if (!block) return reject(new Error("No block"));

    bot._client.write("block_dig", {
      status: 0, // start digging
      location: block.position,
      face: 1, // top
    });

    setTimeout(() => {
      bot._client.write("block_dig", {
        status: 2, // finish digging
        location: block.position,
        face: 1,
      });
      resolve();
    }, 500); // 適当に0.5秒待つ
  });
}

// 周りにブロックが囲まれている場合に掘削して抜け出す処理
async function tryRescue(bot, targetPos) {
  bot.chat("動けません…周囲を掘ってみます");
  const offsets = [
    { x: 1, y: 0, z: 0 },
    { x: -1, y: 0, z: 0 },
    { x: 0, y: 1, z: 0 },
    { x: 0, y: -1, z: 0 },
    { x: 0, y: 0, z: 1 },
    { x: 0, y: 0, z: -1 },
  ];
  for (const off of offsets) {
    const blockPos = bot.entity.position.offset(off.x, off.y, off.z);
    const block = bot.blockAt(blockPos);
    if (block && block.type !== 0 && bot.canDigBlock(block)) {
      try {
        await equipPickaxe(bot);
        await digWithoutEnchant(bot, block);
        bot.chat(`掘削: ${block.name}`);
        break;
      } catch (err) {
        bot.chat(`掘削失敗: ${err.message}`);
      }
    }
  }
  bot.pathfinder.setGoal(new GoalBlock(targetPos.x, targetPos.y, targetPos.z));
}

module.exports = { tryRescue };

lib/command/commands.js

過去に紹介した comeon コマンドを修正します
ブロックに囲われている際には自力で脱出して目的の場所まで到着するようになります

const { goals, Movements } = require("mineflayer-pathfinder");
const { GoalBlock } = goals;
const {
  buildBaseBlock,
  buildFloorFromBase,
  buildWallsFromBase,
  buildRoofFlatFromBase,
  moveBotToCenter,
} = require("./build/house");
const { tryRescue } = require("./dig/low_level_api");

const mcDataLoader = require("minecraft-data");

// 指定の座標に移動するコマンド
function moveCmd(bot, username, args) {
  if (args.length < 3) {
    bot.chat(`Usage: move <x> <y> <z>`);
    return;
  }

  const [x, y, z] = args.map(Number);
  if ([x, y, z].some(isNaN)) {
    bot.chat("座標は数値で指定してください");
    return;
  }

  bot.chat(`${username} の指示で (${x}, ${y}, ${z}) に移動します`);
  bot.pathfinder.setGoal(new GoalBlock(x, y, z));
}

// プレイヤーの目の前に移動するコマンド(canDig + 自動救出版)
function comeOnCmd(bot, username) {
  function getScaffoldBlocks(bot, mcData) {
    const usableItemIds = [
      mcData.itemsByName.dirt.id,
      mcData.itemsByName.cobblestone.id,
      mcData.itemsByName.sand.id,
    ];
    return bot.inventory
      .items()
      .filter((item) => usableItemIds.includes(item.type))
      .map((item) => item.type);
  }

  const player = bot.players[username]?.entity;
  if (!player) {
    bot.chat(`プレイヤー ${username} が見つかりません`);
    return;
  }

  const mcData = mcDataLoader(bot.version);
  const movements = new Movements(bot, mcData);

  // 各種設定
  movements.allowParkour = true;
  movements.allow1by1towers = true;
  movements.canDig = true;
  movements.scafoldingBlocks = getScaffoldBlocks(bot, mcData);

  bot.pathfinder.setMovements(movements);

  // プレイヤーの目の前の座標
  const pos = player.position.clone();
  const dx = Math.round(Math.cos(player.yaw));
  const dz = Math.round(Math.sin(player.yaw));
  const targetPos = pos.offset(dx, 0, dz);

  bot.chat(`${username} の所に行きます!`);
  bot.pathfinder.setGoal(new GoalBlock(targetPos.x, targetPos.y, targetPos.z));

  // 自動救出ループ
  let lastPos = bot.entity.position.clone();
  const checkInterval = setInterval(() => {
    const dist = bot.entity.position.distanceTo(targetPos);
    if (dist <= 1.5) {
      bot.chat(`到着しました!(距離 ${dist.toFixed(2)})`);
      clearInterval(checkInterval);
      return;
    }

    if (bot.entity.position.distanceTo(lastPos) < 0.01) {
      tryRescue(bot, targetPos);
    }
    lastPos = bot.entity.position.clone();
  }, 1500);
}

// ボットのインベントリーの中身を確認するコマンド
function inventoryCmd(bot, username) {
  const items = bot.inventory.items();
  if (items.length === 0) {
    bot.chat(`${username}、インベントリは空です。`);
    return;
  }
  const itemList = items
    .map((item) => `${item.count}x${item.name}(${item.type})`)
    .join(", ");
  bot.chat(`${username} の所持品: ${itemList}`);
}

// 四角形の家を建築するコマンド
async function buildEasyHouseCmd(bot, username) {
  const baseBlockPos = await buildBaseBlock(bot, "dirt");
  if (baseBlockPos) {
    await buildFloorFromBase(bot, baseBlockPos, "dirt");
    await moveBotToCenter(bot, baseBlockPos, 5);
    bot.chat("床を作りました!");
    await buildWallsFromBase(bot, baseBlockPos, "dirt", 5, 3);
    bot.chat("壁を作りました");
    await buildRoofFlatFromBase(bot, baseBlockPos, "dirt", 5, 3);
    bot.chat("屋根を作りました");
  }
}

module.exports = { moveCmd, comeOnCmd, inventoryCmd, buildEasyHouseCmd };

index.js

これは前回と同様です

const mineflayer = require("mineflayer");
const { pathfinder, Movements } = require("mineflayer-pathfinder");
const CommandParser = require("./lib/command/parser");
const {
  moveCmd,
  comeOnCmd,
  inventoryCmd,
  buildEasyHouseCmd,
} = require("./lib/command/commands");

class MinecraftBot {
  constructor(host, port, username) {
    this.bot = mineflayer.createBot({
      host,
      port,
      username,
      version: false,
    });

    this.bot.loadPlugin(pathfinder);
    this.registerEvents();
  }

  registerEvents() {
    this.bot.once("spawn", () => this.onSpawn());
    this.bot.on("chat", (username, message) => this.onChat(username, message));
    this.bot.on("kicked", (reason) =>
      console.error("キックされました:", reason),
    );
    this.bot.on("error", (err) => console.error("エラー:", err));
    this.bot.on("end", () => console.log("Bot が切断されました"));
  }

  onSpawn() {
    console.log("Bot がログインしました!");
    this.bot.chat("こんにちは!Botクラスです!");

    const defaultMovements = new Movements(this.bot, this.bot.registry);
    this.bot.pathfinder.setMovements(defaultMovements);
  }

  onChat(username, message) {
    if (username === this.bot.username) return;
    console.log(`[${username}]: ${message}`);

    const parser = new CommandParser(message);

    if (parser.isMoveCommand()) {
      moveCmd(this.bot, username, parser.getArgs());
    } else if (parser.isComeOnCommand()) {
      comeOnCmd(this.bot, username);
    } else if (parser.isInventoryCommand()) {
      inventoryCmd(this.bot, username);
    } else if (parser.isBuildEasyHouseCommand()) {
      buildEasyHouseCmd(this.bot, username);
    }
  }
}

const myBot = new MinecraftBot("localhost", 25565, "bot");

動作確認

  • npx node index.js

で動作確認しましょう
ボットには事前に何かしらのピッケルを持たせておいてください

  • /give bot diamond_pickaxe 1

またボット土ブロックで囲われています
comeon コマンドを実行すると自動で経路を見つけその先にブロックがある場合は自分で壊して来てくれます

最後に

bot.dig にバグがあるようなので自力で対処してみました
そのうち修正版が出るかなと思いますがそれまでのワークアラウンドとして使える方法かなと思います

参考サイト

0 件のコメント:

コメントを投稿