'use strict'; /** * 协议自检:核对 mineflayer 写出的移动相关包在目标版本上的字段名与结构。 * * 背景:部分服务端在收到位置包后会持续重发 `position`,直到客户端回一个 * `teleport_confirm` 确认;若 mineflayer 因为版本字段名不一致而写不出这个包, * 就会形成「服务端一直传送、客户端一直走不动」的死循环。 * * 注意 `minecraft-data` 的结构:`types.packet[1][1].type[1].fields[包名]` 拿到的是 * **类型名字符串**(如 `packet_position_look`),要再去 `types[名字]` 取 * `['container', [字段...]]` 才能看到字段。早先的版本直接对它 `.map()`, * 所以这个脚本一直跑不通。 */ const fs = require('fs'); const path = require('path'); const mcData = require('minecraft-data')('1.21.4'); const VERSION = '1.21.4'; const toServer = mcData.protocol.play.toServer; const toClient = mcData.protocol.play.toClient; /** 取某一方向「包名 → 类型名」的映射 */ function packetNameMap(direction) { const paramsField = direction.types.packet[1][1]; return paramsField.type[1].fields; } /** 把类型名解析成字段列表;解析不到返回 null */ function resolveFields(direction, typeName) { const type = direction.types[typeName]; if (!Array.isArray(type) || type[0] !== 'container') return null; return type[1]; } /** 打印一个包的字段定义 */ function dumpFields(direction, label, packetName) { const typeName = packetNameMap(direction)[packetName]; if (!typeName) { console.log(` ${packetName.padEnd(18)} (该版本没有这个包)`); return; } const fields = resolveFields(direction, typeName); if (!fields) { console.log(` ${packetName.padEnd(18)} ${typeName}(不是 container,无法展开)`); return; } const text = fields.map((f) => `${f.name}:${JSON.stringify(f.type)}`).join(', '); console.log(` ${packetName.padEnd(18)} ${text}`); } console.log(`=== ${VERSION} 客户端 → 服务端:移动 / 传送相关包 ===`); const serverNames = packetNameMap(toServer); for (const name of Object.keys(serverNames).sort()) { if (/teleport|position|input|flying|^look$/i.test(name)) console.log(` ${name}`); } console.log(`\n=== ${VERSION} 客户端 → 服务端:字段定义 ===`); for (const name of ['position', 'position_look', 'look', 'flying', 'player_input', 'teleport_confirm', 'tick_end']) { dumpFields(toServer, 'toServer', name); } console.log('\n=== 关键位标志类型的展开 ==='); for (const [name, def] of Object.entries(toServer.types)) { if (Array.isArray(def) && def[0] === 'bitflags' && /MovementFlags|Input/i.test(name)) { console.log(` ${name} = ${JSON.stringify(def)}`); } } console.log(`\n=== ${VERSION} 服务端 → 客户端:position 包字段 ===`); dumpFields(toClient, 'toClient', 'position'); for (const [name, def] of Object.entries(toClient.types)) { if (Array.isArray(def) && def[0] === 'bitflags' && /PositionUpdateRelatives/i.test(name)) { console.log(` ${name} = ${JSON.stringify(def)}`); } } console.log('\n=== mineflayer 实际写出的包 / 用到的字段名 ==='); const physicsPath = path.join(__dirname, '..', 'node_modules', 'mineflayer', 'lib', 'plugins', 'physics.js'); try { const lines = fs.readFileSync(physicsPath, 'utf8').split('\n'); lines.forEach((line, index) => { if (/teleport_confirm|teleportId|_client\.write\(/.test(line)) { console.log(` physics.js:${index + 1} ${line.trim()}`); } }); } catch (err) { console.error(' 读不到 mineflayer/physics.js:' + (err && err.message ? err.message : err)); process.exitCode = 1; }