import assert from 'node:assert/strict'; import {EventEmitter} from 'node:events'; import {createRequire} from 'node:module'; import test from 'node:test'; import {tsImport} from 'tsx/esm/api'; const {create_function_manager} = await tsImport('../bot/function/init.ts', import.meta.url); const {init: Stop} = await tsImport('../bot/function/bot/stop_current_function.ts', import.meta.url); const require = createRequire(import.meta.url); const {Vec3} = createRequire(require.resolve('@wp2508/mineflayer'))('vec3'); const turn = () => new Promise(resolve => setImmediate(resolve)); async function fixture(t) { const bot = new EventEmitter(); bot.entity = {id: 1, position: new Vec3(0, 0, 0), height: 1.8, eyeHeight: 1.62, onGround: true}; bot._client = Object.assign(new EventEmitter(), {state: 'play'}); bot.controlState = {}; bot.getControlState = key => bot.controlState[key] === true; bot.setControlState = (key, value) => { bot.controlState[key] = value; }; bot.stopDigging = () => { bot.targetDigBlock = null; }; bot.deactivateItem = () => {}; bot.clearControlStates = () => { for (const key of Object.keys(bot.controlState)) bot.setControlState(key, false); }; const manager = await create_function_manager({bot_instance: bot, bot_status: 'online'}); t.after(() => manager.dispose()); return {bot, manager}; } function clean(bot) { assert.deepEqual(bot.eventNames(), []); assert.deepEqual(bot._client.eventNames(), []); } test('stop bypasses a full queue, releases direction and persistent sneak, and never restarts pending keys', async t => { const {bot, manager} = await fixture(t); await manager.run('bot', 'squat_continue_switch', [true]); const running = manager.run('bot', 'use_direction_key', ['forward', 5000]); await turn(); assert.equal(bot.controlState.forward, true); const queued = Array.from({length: 64}, () => manager.run('bot', 'use_direction_key', ['right', 5000])); let queuedChecks = 0; // Queue capacity applies to normal calls, never to the stop command. await assert.rejects(manager.run('bot', 'jump', [], () => queuedChecks++), /队列已满/); assert.equal(await manager.run('bot', 'stop_current_function', []), true); assert.equal(await running, false); assert.deepEqual(await Promise.all(queued), Array(64).fill(false)); await turn(); assert.equal(queuedChecks, 0); assert.deepEqual(bot.controlState, {sneak: false, forward: false}); clean(bot); const fresh = manager.run('bot', 'jump', []); await turn(); bot.emit('physicsTick'); assert.equal(await fresh, true); clean(bot); }); test('stop validates no arguments and attempts every stop API even when earlier calls throw', async () => { const stop = new Stop(); assert.deepEqual(stop.args, []); for (const failing of ['stopDigging', 'deactivateItem', 'clearControlStates', null]) { const calls = []; const bot = {}; for (const name of ['stopDigging', 'deactivateItem', 'clearControlStates']) bot[name] = () => { calls.push(name); if (name === failing) throw Error(name); }; assert.equal(await stop.run(bot, [1]), false); assert.deepEqual(calls, []); assert.equal(await stop.run(bot, []), failing === null); assert.deepEqual(calls, ['stopDigging', 'deactivateItem', 'clearControlStates']); } }); test('stop settles pending look immediately and late entity look cannot attack or restart queued work', async t => { const {bot, manager} = await fixture(t); const target = {id: 2, name: 'cow', position: new Vec3(2, 0, 0), width: 1, height: 2}; bot.entities = {2: target}; let finishLook; bot.lookAt = () => new Promise(resolve => { finishLook = resolve; }); let attacks = 0; bot.attack = () => { attacks++; }; const running = manager.run('entity', 'attack_nearest_entity', ['cow', 4, false, true]); await turn(); let before = 0; const queued = manager.run('bot', 'jump', [], () => before++); assert.equal(await manager.run('bot', 'stop_current_function', []), true); assert.equal(await running, false); assert.equal(await queued, false); clean(bot); finishLook(); await turn(); assert.equal(attacks, 0); assert.equal(before, 0); clean(bot); }); test('stop cancels container waits and block groups without fake disconnects or later group actions', async t => { const {bot, manager} = await fixture(t); let ends = 0; bot.on('end', () => ends++); bot.entities = {2: {id: 2, name: 'cow', position: new Vec3(2, 0, 0), width: 1, height: 2}}; bot.interactEntity = () => {}; const open = manager.run('entity', 'open_nearest_entity_container', ['cow', 4, false]); await turn(); assert.equal(bot.listenerCount('windowOpen'), 1); await manager.run('bot', 'stop_current_function', []); assert.equal(await open, false); assert.equal(bot.listenerCount('windowOpen'), 0); const block = {name: 'stone', type: 1, stateId: 1, position: new Vec3(2, 1, 0)}; bot.blockAt = () => block; let finishUse; let uses = 0; bot.activateBlock = () => { uses++; return new Promise(resolve => { finishUse = resolve; }); }; const group = manager.run('block', 'use_position_group_block', [[2, 1, 0, 1, 2, 1, 0, 1]]); await turn(); await manager.run('bot', 'stop_current_function', []); assert.equal(await group, false); finishUse(); await turn(); assert.equal(uses, 1); assert.equal(ends, 0); bot.removeAllListeners('end'); clean(bot); }); test('stop prevents remaining inventory clicks after an already submitted click settles', async t => { const {bot, manager} = await fixture(t); for (const name of ['slot_to_slot', 'slot_switch_slot']) { bot.inventory = {slots: [{name: 'stone'}, null], selectedItem: null}; let finishClick; const clicks = []; bot.clickWindow = slot => { clicks.push(slot); return new Promise(resolve => { finishClick = resolve; }); }; const running = manager.run('inventory', name, [0, 1]); await turn(); assert.deepEqual(clicks, [0]); await manager.run('bot', 'stop_current_function', []); assert.equal(await running, false); finishClick(); await turn(); assert.deepEqual(clicks, [0]); } clean(bot); }); test('stop releases dig ownership during pending aim and stops an already active dig', async t => { const {bot, manager} = await fixture(t); const block = {name: 'stone', type: 1, stateId: 1, position: new Vec3(2, 1, 0)}; bot.blockAt = () => block; bot.canDigBlock = () => true; let finishLook; bot.lookAt = () => new Promise(resolve => { finishLook = resolve; }); let digs = 0; let finishDig; bot.dig = () => { digs++; bot.targetDigBlock = block; return new Promise(resolve => { finishDig = resolve; }); }; const aiming = manager.run('block', 'destroy_position_block', [2, 1, 0, 1, true]); await turn(); await manager.run('bot', 'stop_current_function', []); assert.equal(await aiming, false); clean(bot); finishLook(); await turn(); assert.equal(digs, 0); const digging = manager.run('block', 'destroy_position_block', [2, 1, 0, 1]); await turn(); assert.equal(digs, 1); assert.equal(bot.targetDigBlock, block); await manager.run('bot', 'stop_current_function', []); assert.equal(await digging, false); assert.equal(bot.targetDigBlock, null); finishDig(); await turn(); clean(bot); });