- 游戏开发
【免费下载链接】mineflayer
Create Minecraft bots with a powerful, stable, and high level JavaScript API.
Mineflayer 是一个用 JavaScript 编写 Minecraft 机器人(Bot)的高层 API 库,其核心由 andrewrk 创建,此后由大量社区贡献者持续改进。本文以仓库根目录下的 docs/CONTRIBUTING.md 为骨架,系统讲解向 Mineflayer 贡献代码的完整路径:如何用 Stage 标签组织 Issue、如何编写并运行覆盖多版本的内外部测试、如何从零创建第三方插件,以及提交 Pull Request 时必须遵守的错误处理与文档维护规范。读完本文,你将能独立完成"提 Issue → 写测试 → 跑测试 → 建插件 → 提交代码"的完整贡献闭环。
一、Issue 治理:用三个阶段标签组织问题
Mineflayer 仓库用一套"三阶段标签"(3 stage labels)来组织 Issue,目的是把"想法"逐步收敛为"可编码实现的任务",避免维护者面对一堆未经消化的需求:
| 阶段 | 含义 | 处理状态 |
|---|---|---|
| Stage 1 | 刚由项目新人创建,尚不确定是否值得实现/修复 | 待评估,可能被关闭 |
| Stage 2 | 想法有前景,但实现前还需要更多设计思考 | 待细化,进入讨论 |
| Stage 3 | 想法已被精确描述,只差编码落地 | 可直接认领开发 |
如果你想找"已经可以上手贡献"的任务,可以按 Stage 1 过滤掉早期议题,只保留已明确规格的 Stage 2/3 问题。这种三级流水线让贡献者能一眼判断某 Issue 是否"ready to code",也让维护者避免在未成熟的想法上过早投入实现成本。
二、双层测试体系:internal tests 与 external tests
Mineflayer 的测试分两类,二者互补,共同回答"某个功能在 Mineflayer 里到底能不能用":
- 内部测试(internal tests):位于 test/internalTest.js,针对一个用 node-minecraft-protocol 搭建的简易模拟服务器运行。它不依赖真实游戏服务器,能在毫秒级内构造登录包、区块包、实体生成包等网络数据,适合快速验证协议解析、实体跟踪、物理引擎、窗口操作等底层逻辑。
- 外部测试(external tests):位于 test/externalTests/,针对 vanilla(原版)服务器运行。它会真实下载对应版本的
minecraft_serverjar 并启动,让 Bot 以真实玩家的身份完成挖掘、放置、睡觉、交易等端到端操作,验证与真实服务器的兼容性。
内部测试与外部测试的最终目标一致:自动、持续地知道 Mineflayer 的哪些功能可用、哪些不可用,从而让库的兼容性改进变得可度量、可追踪。
2.1 内部测试的模拟服务器机制
从 test/internalTest.js 的源码结构可以看到内部测试的典型写法:为每个受支持的版本创建一个describe块,在beforeEach中用mc.createServer({ 'online-mode': false, version: supportedVersion, port })启动模拟服务器,再通过mineflayer.createBot(...)连接它,随后直接由服务端client.write('login', ...)、client.write('map_chunk', ...)等构造测试场景。例如chat用例中,模拟服务器向 Bot 写入一条来自gary的消息,断言 Bot 正确触发chat事件并回发hi;blockAt用例则在 chunk 中放置金块(gold_block),断言bot.blockAt(pos).type正确解析。这种"服务端驱动客户端"的模式让每个用例都能精确复现特定版本的协议行为。
2.2 外部测试的 vanilla 服务器启动流程
test/externalTest.js 展示了外部测试的自动化编排:通过minecraft-wrap下载并启动对应版本的官方服务端 jar,通过propOverrides注入'online-mode': 'false'、gamemode: '1'、'spawn-npcs': 'true'等属性来构造可控环境,并以pingUntilReady轮询服务端状态端口直至就绪,再让 Bot 登录并执行全部外部测试用例。测试还通过excludedTests数组显式排除digEverything、anvil、placeEntity等暂不稳定的用例,并在每次运行后清理服务端数据,保证可重复执行。
三、运行测试:跨版本执行与 mocha 的 -g 过滤
Mineflayer 的测试脚本定义在 package.json 中:
"scripts": { "mocha_test": "mocha --reporter ./test/common/durationsReporter.js --exit", "test": "npm run mocha_test", "pretest": "npm run lint", "lint": "standard && standard-markdown" }pretest会在正式跑测试前先执行standard(JS 代码规范检查)与standard-markdown(Markdown 规范检查),这意味着提交的代码必须通过 lint 才能进入测试阶段。运行方式分三档:
# 1. 在所有受支持版本上跑全部测试(内部 + 外部) npm run test # 2. 只跑 Minecraft 1.20.4 上的某个具体测试(exampleBee) npm run mocha_test -- -g "mineflayer_external 1.20.4v.*exampleBee" # 3. 只跑 1.20.4 一个版本的全部测试 npm run mocha_test -- -g "mineflayer_external 1.20.4v"其中-g是传给 mocha 的--grep参数,用来按用例名称过滤。因为内外部测试的 describe 块名称分别以mineflayer_internal <version>v和mineflayer_external <version>v开头,所以你可以精确地把过滤粒度控制到"版本 + 测试"两个维度,例如用npm run mocha_test -- -g "1.18.1.*BlockFinder"单独跑 1.18.1 的方块查找测试。这样做的价值在于:Mineflayer 横跨多个 Minecraft 版本,一次全量测试代价高昂,按需过滤可以大幅缩短开发反馈周期。
四、创建外部测试:从文件到断言
现在新增一个外部测试非常简单:只需在 test/externalTests 目录下新建一个.js文件,测试框架会自动扫描并注册它(加载与调度逻辑见 test/externalTest.js)。
4.1 导出格式要求
该文件需要导出一个函数,返回以下三者之一:
- 一个函数;
- 一个以函数为元素的数组。
每个函数接收两个参数:bot 对象和done 回调(mocha 的完成信号)。函数体内应包含assert断言,用来判定被测试功能是否失败。导出对象形式的文件(例如{ testA: () => async (bot) => {...}, testB: ... })还会被逐一注册为独立的it用例。
4.2 参考实现:digAndBuild.js
以 test/externalTests/digAndBuild.js 为模板,可以看到一个完整的外部测试骨架:
const { Vec3 } = require('vec3') const assert = require('assert') const { onceWithCleanup } = require('../../lib/promise_utils') module.exports = () => async (bot) => { const Item = require('prismarine-item')(bot.registry) await bot.test.setInventorySlot(36, new Item(bot.registry.itemsByName.dirt.id, 1, 0)) await bot.test.fly(new Vec3(0, 2, 0)) await bot.test.placeBlock(36, bot.entity.position.plus(new Vec3(0, -2, 0))) await bot.test.clearInventory() await bot.creative.stopFlying() await waitForFall() await bot.test.becomeSurvival() // 徒手挖掘脚下的泥土 await bot.dig(bot.blockAt(bot.entity.position.plus(new Vec3(0, -1, 0)))) // 掉落物有拾取延迟,等待物品栏槽位更新后再断言 const dirt = new Item(bot.registry.itemsByName.dirt.id, 1, 0) if (!Item.equal(bot.inventory.slots[36], dirt)) { await onceWithCleanup(bot.inventory, 'updateSlot', { timeout: 5000, checkCondition: (slot) => slot === 36 && Item.equal(bot.inventory.slots[36], dirt) }) } assert(Item.equal(bot.inventory.slots[36], dirt)) bot.test.sayEverywhere('dirt collect test: pass') // ... }这段代码展示了外部测试的完整生命周期:使用bot.test.*辅助方法(放置方块、飞行、切换生存模式等)构造场景 → 执行真实的bot.dig挖掘 → 用assert验证挖掘产物确实进入物品栏 → 通过bot.test.sayEverywhere在游戏内广播测试结果。注意它大量使用async/await与超时保护(onceWithCleanup),这是写健壮外部测试的推荐风格。
五、创建第三方插件:在 Mineflayer 之上叠加更高级的 API
Mineflayer 是**可插拔(pluggable)*设计的:任何人都可以创建一个插件,在 Mineflayer 之上提供更高层次的 API。仓库内已经涌现出 pathfinder(A寻路)、prismarine-viewer(浏览器可视化)、statemachine(状态机行为编排)等大量第三方插件,它们正是通过下面的机制实现的。
5.1 插件开发的四个步骤
按 docs/CONTRIBUTING.md 的指引,创建一个新插件需要:
- 新建一个独立的仓库(不放在 mineflayer 主仓库内);
- 在
index.js中导出一个init函数,它接收mineflayer(库本体)作为参数; - 该
init函数返回一个inject函数,inject接收bot 对象作为参数; - 在
inject函数内部为 bot 对象挂载新功能(方法、属性、事件监听等)。
因为 mineflayer 对象是以参数形式传入的,新插件包不需要在package.json中声明对 mineflayer 的依赖,这既避免了版本耦合,也方便插件针对不同 mineflayer 版本做兼容。
5.2 源码视角:plugin_loader 的注入机制
Mineflayer 主仓库内部的 lib/plugin_loader.js 正是这套机制的底层实现。它向 bot 暴露了三个 API:
bot.loadPlugin(plugin):加载单个插件(必须是函数,否则assert报错);bot.loadPlugins(plugins):批量加载插件数组(要求数组元素全部为函数);bot.hasPlugin(plugin):查询某插件是否已加载。
加载逻辑的关键点是:插件在收到inject_allowed事件(即 bot 完成初始化、允许注入)之前只被登记进pluginList,事件触发后才统一调用plugin(bot, options)完成实际注入。这保证了插件挂载时机不会破坏 bot 的初始化顺序——你创建第三方插件时导出的inject函数,最终就是被这段逻辑以plugin(bot, options)的形式调用的。
5.3 一个最小插件示例
// my-mineflayer-plugin/index.js module.exports = (mineflayer) => { return (bot) => { // 给 bot 挂载一个自定义方法 bot.sayHello = () => bot.chat('Hello from my plugin!') // 也可以监听 bot 生命周期事件 bot.once('spawn', () => bot.sayHello()) } }使用时在创建 bot 后调用bot.loadPlugin(require('my-mineflayer-plugin'))即可。这种"init 接收库、inject 接收 bot"的分层设计,是 Mineflayer 插件生态能够百花齐放的根本原因。
六、报告 Bug:四要素模板
Mineflayer 在多数场景下运行良好,但偶尔仍存在 bug。报告 Issue 时,请务必提供以下四项信息:
- 你想做什么(用英文描述目标);
- 你尝试了什么(贴出代码);
- 实际发生了什么;
- 你期望发生什么。
这个模板看似简单,却能极大提升 bug 的定位效率:目标让维护者判断是否属于 Mineflayer 的能力范围,代码让维护者复现路径,实际结果与期望结果的对照则直接划定了缺陷的边界。如果你的报告能附上对应的内部/外部测试复现用例,修复速度会更快。
七、Mineflayer 代码规范:提交 PR 前必读
7.1 错误处理:用 Node.js 回调约定,而不是 throw
Mineflayer 的核心原则之一是:在大多数情况下,bot 不应因某个功能失败而崩溃——即使某一步失败,bot 仍可走替代路线达成目标。因此,插件与核心代码不应使用throw new Error('error'),而应遵循 Node.js 的惯例:把错误作为第一个参数传给回调(callback)。
lib/plugins/bed.js 是这一约定的直接体现:sleep遇到"床太远""附近有怪物""不是夜晚"等情况时,并不是让 bot 崩溃,而是通过回调/Promise把Error抛给调用方处理(例如bot.sleep(bed).catch(err => ...)),bot 本体继续正常运行。标准写法示例:
function myfunction (param1, callback) { // do stuff let toDo = 1 toDo = 2 if (toDo === 2) { // everything worked callback() } else { callback(new Error('something failed')) } }提示:随着库的演进,Mineflayer 的新代码越来越多地采用 Promise/async 风格(如
onceWithCleanup、await),但其精神不变——错误应当被捕获并传递给调用方,而不是让整个进程退出。
7.2 更新文档:用 doctoc 维护目录
docs/api.md(完整 API 参考)的目录(Table of Contents)是用doctoc生成的。每次修改完该文件后,应运行:
doctoc docs/api.md来重新生成目录,确保 API 文档的章节锚点与正文保持一致。仓库的 devDependencies 中已声明doctoc,因此本地开发环境可以直接使用该命令。
八、从 Issue 到合入的完整路径小结
把上述内容串起来,一次完整的贡献流程是:
- 在 Issue 中按四要素模板描述需求或 bug,配合维护者推进到Stage 3(规格明确);
- 在 test/internalTest.js(协议层)或 test/externalTests(真实服务器层)新增或修改测试;
- 用
npm run mocha_test -- -g "<版本>.*<测试名>"快速验证目标版本; - 实现功能时遵守回调式错误处理,避免
throw导致 bot 崩溃; - 若改动涉及 docs/api.md,运行
doctoc docs/api.md同步目录; - 提交 PR,等待 CI 在全部受支持版本上验证(提交前
npm run lint会检查standard与standard-markdown)。
社区维护者与数千个下游项目共同受益于这套流程:它保证了 Mineflayer 在横跨多个 Minecraft 版本的前提下,既能持续扩展能力,又能稳定、可验证地演进——这正是本项目"powerful, stable, and high level JavaScript API"定位的基石。
- 游戏开发
【免费下载链接】mineflayer
Create Minecraft bots with a powerful, stable, and high level JavaScript API.
相关推荐
使用 Flutter Gen UI SDK 构建 A2UI 渲染客户端:genui 与 genui_a2a 集成指南
使用 Flutter Gen UI SDK 构建 A2UI 渲染客户端:genui 与 genui_a2a 集成指南 A2UI(Agent to UI)是一套让
游戏开发Flair 贡献指南全解析:从 Issue 到 PR 的开发流程、测试体系与代码规范
Flair 贡献指南全解析:从 Issue 到 PR 的开发流程、测试体系与代码规范 本篇技术指南以 Flair 仓库根目录的 CONTRIBUTING.md
NLP深度学习机器学习torchtune 贡献指南:从开发环境搭建、三层测试体系到文档与代码规范的完整实践
torchtune 贡献指南:从开发环境搭建、三层测试体系到文档与代码规范的完整实践 torchtune 是 PyTorch 原生的后训练(post train
大模型微调RLHF分布式训练模型量化
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考