'use strict'; const test = require('node:test'); const assert = require('node:assert'); const { AltitudeGoal, PathingService, PathEvent } = require('../src/bot/pathingService'); const { GoalXZ, GoalNearXZ } = require('mineflayer-pathfinder').goals; const { defaultConfig } = require('../src/config/expansionConfig'); const { FakeBot } = require('./helpers/fakeBot'); test('AltitudeGoal 的到达判定与内层目标一致', () => { const inner = new GoalXZ(100, 200); const goal = new AltitudeGoal(inner, 0.1, 64); assert.strictEqual(goal.isEnd({ x: 100, y: 70, z: 200 }), true, '不约束高度'); assert.strictEqual(goal.isEnd({ x: 101, y: 70, z: 200 }), false); }); test('AltitudeGoal 对高处给更低启发值、对低处给更高启发值', () => { const goal = new AltitudeGoal(new GoalXZ(0, 0), 0.1, 64); const high = goal.heuristic({ x: 10, y: 100, z: 10 }); const low = goal.heuristic({ x: 10, y: 20, z: 10 }); assert.ok(high < low, `高处启发值 ${high} 应小于低处 ${low}`); // 与 GoalXZ 的差值应等于 weight × y const base = new GoalXZ(0, 0).heuristic({ x: 10, y: 100, z: 10 }); assert.ok(Math.abs((base - high) - 0.1 * 100) < 1e-9); }); test('AltitudeGoal 启发值被钳制为非负', () => { const goal = new AltitudeGoal(new GoalXZ(0, 0), 10, 0); assert.ok(goal.heuristic({ x: 0, y: 300, z: 0 }) >= 0); }); test('AltitudeGoal 使用 GoalNearXZ 时保留其到达范围', () => { const goal = new AltitudeGoal(new GoalNearXZ(100, 200, 2), 0.1, 64); assert.strictEqual(goal.isEnd({ x: 100, y: 70, z: 201 }), true, '范围内算到达'); }); test('configure 施加 Grim 合规约束(默认禁止挖 / 放 / 跑酷 / 搭塔)', () => { const bot = new FakeBot(); const movements = {}; bot.pathfinder = { setMovements: (m) => { movements.value = m; }, thinkTimeout: 0, tickTimeout: 0, searchRadius: 0 }; const config = defaultConfig(); const service = new PathingService(bot, config, null); assert.strictEqual(service.configure(), true); const m = movements.value; assert.strictEqual(m.canDig, false, '默认不允许挖方块'); assert.deepStrictEqual(m.scafoldingBlocks, [], '默认不允许放方块'); assert.strictEqual(m.allowParkour, false); assert.strictEqual(m.allow1by1towers, false); assert.strictEqual(m.allowFreeMotion, false); assert.strictEqual(m.maxDropDown, 3); assert.strictEqual(m.infiniteLiquidDropdownDistance, false); assert.strictEqual(m.dontMineUnderFallingBlock, true); // waterMode=avoid 时液体代价被推高 assert.ok(m.liquidCost >= 200, 'avoid 模式下液体代价应被推高'); }); test('configure 在 allowBreak / allowPlace 打开时放行对应能力', () => { const bot = new FakeBot(); const movements = {}; bot.pathfinder = { setMovements: (m) => { movements.value = m; }, thinkTimeout: 0, tickTimeout: 0, searchRadius: 0 }; const config = defaultConfig(); config.allowBreak = true; config.allowPlace = true; config.waterMode = 'swim'; const service = new PathingService(bot, config, null); service.configure(); assert.strictEqual(movements.value.canDig, true); assert.ok(movements.value.scafoldingBlocks.length > 0); assert.strictEqual(movements.value.liquidCost, config.pathing.liquidCost); }); test('pathTo 下发目标区块中心并触发 CALC_STARTED', () => { const bot = new FakeBot(); let goal = null; bot.pathfinder = { setMovements: () => {}, setGoal: (g) => { goal = g; }, thinkTimeout: 0, tickTimeout: 0, searchRadius: 0 }; const events = []; const service = new PathingService(bot, defaultConfig(), null); service.configure(); service.onPathEvent = (event, detail) => events.push({ event, detail }); assert.strictEqual(service.pathTo({ x: 3, z: -2 }), true); assert.ok(goal, '应下发目标'); assert.strictEqual(goal.isEnd({ x: 3 * 16 + 8, y: 64, z: -2 * 16 + 8 }), true, '目标应为区块中心附近'); assert.strictEqual(events[0].event, PathEvent.CALC_STARTED); assert.strictEqual(service.isPathing(), true); }); test('关闭高度偏好时不包装目标', () => { const bot = new FakeBot(); let goal = null; bot.pathfinder = { setMovements: () => {}, setGoal: (g) => { goal = g; }, thinkTimeout: 0, tickTimeout: 0, searchRadius: 0 }; const config = defaultConfig(); config.altitudePreference = false; const service = new PathingService(bot, config, null); service.configure(); service.pathTo({ x: 0, z: 0 }); assert.ok(!(goal instanceof AltitudeGoal), '未启用高度偏好时不应包装'); }); test('cancel 清空目标并停止寻路状态', () => { const bot = new FakeBot(); let cleared = false; bot.pathfinder = { setMovements: () => {}, setGoal: (g) => { if (g === null) cleared = true; }, thinkTimeout: 0, tickTimeout: 0, searchRadius: 0 }; const service = new PathingService(bot, defaultConfig(), null); service.configure(); service.pathTo({ x: 1, z: 1 }); service.cancel(); assert.strictEqual(cleared, true); assert.strictEqual(service.isPathing(), false); assert.strictEqual(service.currentTarget, null); }); test('pathfinder 未加载时 configure 返回 false 且 pathTo 不抛异常', () => { const bot = new FakeBot(); const service = new PathingService(bot, defaultConfig(), null); assert.strictEqual(service.configure(), false); assert.strictEqual(service.isAvailable(), false); assert.strictEqual(service.pathTo({ x: 0, z: 0 }), false); service.cancel(); // 不应抛异常 });