'use strict'; const { EventEmitter } = require('events'); const { Vec3 } = require('vec3'); /** 真实的 1.21.4 注册表(mineflayer-pathfinder 的 Movements 依赖它) */ let cachedRegistry = null; function defaultRegistry() { if (!cachedRegistry) { cachedRegistry = require('minecraft-data')('1.21.4'); } return cachedRegistry; } /** * 测试用的假 bot:只实现被测模块真正用到的接口, * 避免测试依赖真实 Minecraft 服务器。 */ class FakeBot extends EventEmitter { constructor(options) { super(); const opts = options || {}; this.username = opts.username || 'TestBot'; this.physicsEnabled = true; this.usingHeldItem = false; this.vehicle = null; this.food = 20; this.health = 20; this.game = { gameMode: opts.gameMode || 'survival' }; this.physics = Object.assign({ gravity: 0.08, playerSpeed: 0.1, sprintSpeed: 0.3, sneakSpeed: 0.3, stepHeight: 0.6, airborneInertia: 0.91, waterInertia: 0.8, yawSpeed: 3.0, pitchSpeed: 3.0 }, opts.physics || {}); this.entity = { position: new Vec3(opts.x || 0, opts.y || 64, opts.z || 0), velocity: new Vec3(0, 0, 0), yaw: 0, pitch: 0, eyeHeight: 1.62, isInWater: false, isInLava: false, onGround: true }; /** @type {Record} */ this.controls = {}; this.heldItem = opts.heldItem || null; this.inventory = { slots: opts.slots || [] }; this.entities = {}; this.world = opts.world || { raycast: () => null }; this.registry = opts.registry || defaultRegistry(); this.pathfinder = null; this.blockMap = opts.blockMap || new Map(); this.creative = { flying: false }; } getControlState(control) { return this.controls[control] === true; } setControlState(control, state) { this.controls[control] = state === true; this.emit('controlState', control, state); } blockAt(pos) { const key = `${Math.floor(pos.x)},${Math.floor(pos.y)},${Math.floor(pos.z)}`; if (this.blockMap.has(key)) return this.blockMap.get(key); return { name: 'air', boundingBox: 'empty' }; } look(yaw, pitch) { this.entity.yaw = yaw; this.entity.pitch = pitch === undefined ? this.entity.pitch : pitch; return Promise.resolve(); } lookAt(point) { return this.look(this.entity.yaw, this.entity.pitch); } attack(entity, swing) { this.lastAttack = { entity, swing }; } activateItem() { this.usingHeldItem = true; } deactivateItem() { this.usingHeldItem = false; } async waitForTicks(ticks) { for (let i = 0; i < (ticks || 1); i++) this.emit('physicsTick'); } chat(message) { this.lastChat = message; } equip() { return Promise.resolve(); } consume() { this.usingHeldItem = false; this.food = 20; return Promise.resolve(); } mount() { this.vehicle = { id: 99, position: this.entity.position.clone(), name: 'oak_boat' }; } dismount() { this.vehicle = null; } moveVehicle() { this.lastVehicleMove = Array.from(arguments); } /** 推进一个物理 tick(触发合规层的旋转推进) */ tick() { this.emit('physicsTick'); } /** 推进 n 个物理 tick */ tickTimes(n) { for (let i = 0; i < n; i++) this.emit('physicsTick'); } } module.exports = { FakeBot };