185 lines
7.9 KiB
JavaScript
185 lines
7.9 KiB
JavaScript
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 {init: InventoryInfo} = await tsImport('../bot/info/bot/inventory.ts', import.meta.url);
|
|
const {create_info_manager} = await tsImport('../bot/info/init.ts', import.meta.url);
|
|
const require = createRequire(import.meta.url);
|
|
const mf = createRequire(require.resolve('@wp2508/mineflayer'));
|
|
const registry = mf('prismarine-registry')('1.21.4');
|
|
const windows = mf('prismarine-windows')(registry);
|
|
const Item = mf('prismarine-item')(registry);
|
|
|
|
function createBot() {
|
|
const bot = new EventEmitter();
|
|
bot._client = new EventEmitter();
|
|
bot.inventory = windows.createWindow(0, 'minecraft:inventory', 'Inventory');
|
|
bot.currentWindow = null;
|
|
bot.quickBarSlot = 2;
|
|
bot.registry = registry;
|
|
return bot;
|
|
}
|
|
|
|
test('inventory snapshots cover all nine window kinds and special properties without operations', t => {
|
|
t.mock.timers.enable({apis: ['setTimeout']});
|
|
const bot = createBot();
|
|
const info = new InventoryInfo(bot);
|
|
t.after(() => info.dispose());
|
|
const tick = () => t.mock.timers.tick(50);
|
|
const open = (type, id = 1) => {
|
|
const window = windows.createWindow(id, `minecraft:${type}`, type);
|
|
bot.currentWindow = window;
|
|
bot.emit('windowOpen', window);
|
|
tick();
|
|
return window;
|
|
};
|
|
const property = (id, value) => bot._client.emit('craft_progress_bar', {
|
|
windowId: bot.currentWindow.id, property: id, value,
|
|
});
|
|
|
|
assert.equal(info.get_info().window.kind, 'inventory');
|
|
assert.equal(info.get_info().handSlot, 38);
|
|
assert.equal(bot.listenerCount('end'), 0);
|
|
for (const [type, kind, handSlot] of [
|
|
['generic_9x3', 'chest', 56],
|
|
['generic_9x6', 'large_chest', 83],
|
|
['crafting', 'crafting', 39],
|
|
['enchantment', 'enchantment', 31],
|
|
['stonecutter', 'stonecutter', 31],
|
|
['furnace', 'furnace', 32],
|
|
['merchant', 'merchant', 32],
|
|
['lectern', 'lectern', null],
|
|
]) {
|
|
const previous = bot.currentWindow;
|
|
const window = open(type);
|
|
assert.equal(info.get_info().window.kind, kind);
|
|
assert.equal(info.get_info().handSlot, handSlot);
|
|
if (previous) assert.equal(previous.listenerCount('updateSlot'), 0);
|
|
if (kind === 'lectern') {
|
|
assert.equal(info.get_info().window.hasPlayerInventory, false);
|
|
assert.equal(info.get_info().window.inventoryStart, null);
|
|
assert.equal(info.get_info().window.slots.length, 1);
|
|
}
|
|
assert.equal(window.listenerCount('updateSlot'), 1);
|
|
}
|
|
|
|
open('furnace');
|
|
assert.equal(info.get_info().special.furnace.fuelRatio, null);
|
|
property(0, 40);
|
|
property(1, 80);
|
|
property(2, 50);
|
|
property(3, 200);
|
|
tick();
|
|
assert.equal(info.get_info().special.furnace.fuelRatio, 0.5);
|
|
assert.equal(info.get_info().special.furnace.progressRatio, 0.25);
|
|
open('furnace');
|
|
assert.deepEqual(info.get_info().window.properties, {});
|
|
|
|
// Properties can arrive before windowOpen, while Mineflayer awaits slot contents.
|
|
bot.currentWindow = windows.createWindow(2, 'minecraft:enchantment', 'Enchant');
|
|
property(0, 30);
|
|
property(4, 5);
|
|
property(7, 3);
|
|
bot.emit('windowOpen', bot.currentWindow);
|
|
tick();
|
|
const option = info.get_info().special.enchantment.options[0];
|
|
assert.deepEqual(option, {index: 0, requiredLevel: 30, levelCost: 1, lapisCost: 1, enchantmentId: 5, enchantmentLevel: 3});
|
|
assert.equal(info.get_info().special.enchantment.options[1].requiredLevel, null);
|
|
|
|
const merchant = open('merchant');
|
|
assert.equal(info.get_info().special.merchant.ready, false);
|
|
merchant.trades = [{
|
|
inputItem1: new Item(registry.itemsByName.emerald.id, 2),
|
|
outputItem: new Item(registry.itemsByName.bread.id, 1),
|
|
hasItem2: false, realPrice: 1, tradeDisabled: false, nbTradeUses: 0, maximumNbTradeUses: 12,
|
|
}];
|
|
bot._client.emit('trade_list');
|
|
tick();
|
|
const offers = info.get_info().special.merchant.offers;
|
|
assert.equal(offers[0].input1.type, registry.itemsByName.emerald.id);
|
|
assert.equal(offers[0].realPrice, 1);
|
|
assert.equal(offers[0].input2, null);
|
|
|
|
const lectern = open('lectern');
|
|
const book = new Item(registry.itemsByName.written_book.id, 1);
|
|
book.components = [{type: 'written_book_content', data: {
|
|
rawTitle: 'Title', author: 'Author', pages: [{content: {
|
|
type: 'compound', value: {text: {type: 'string', value: 'Page'}, color: {type: 'string', value: 'red'}},
|
|
}}],
|
|
}}];
|
|
lectern.updateSlot(0, book);
|
|
property(0, 0);
|
|
tick();
|
|
assert.equal(info.get_info().special.lectern.book.title, 'Title');
|
|
assert.equal(info.get_info().special.lectern.book.pages[0][0].message, 'Page');
|
|
assert.equal(info.get_info().special.lectern.book.pages[0][0].font_color, '#FF5555');
|
|
assert.equal(info.get_info().special.lectern.page, 0);
|
|
const oldBook = {
|
|
type: book.type, name: 'written_book', displayName: 'Written Book', count: 1, metadata: 0,
|
|
nbt: {type: 'compound', value: {
|
|
title: {type: 'string', value: 'Legacy'}, author: {type: 'string', value: 'Writer'},
|
|
pages: {type: 'list', value: {type: 'string', value: ['{"text":"Old page"}']}},
|
|
long: {type: 'long', value: 123n}, bytes: Buffer.from([1, 2]),
|
|
}},
|
|
};
|
|
lectern.slots[0] = oldBook;
|
|
lectern.emit('updateSlot');
|
|
tick();
|
|
assert.equal(info.get_info().special.lectern.book.pages[0][0].message, 'Old page');
|
|
assert.equal(info.get_info().window.slots[0].details.extra.legacy.long, '123');
|
|
assert.deepEqual(info.get_info().window.slots[0].details.extra.legacy.bytes, [1, 2]);
|
|
assert.equal('nbt' in info.get_info().window.slots[0], false);
|
|
assert.doesNotThrow(() => JSON.stringify(info.get_info()));
|
|
assert.equal('icon' in info.get_info().window.slots[0], false);
|
|
|
|
const cyclic = {number: 1n};
|
|
cyclic.self = cyclic;
|
|
Object.defineProperty(cyclic, 'accessor', {enumerable: true, get() {throw new Error('must not invoke');}});
|
|
oldBook.components = [{type: 'custom_data', data: cyclic}];
|
|
lectern.emit('updateSlot');
|
|
tick();
|
|
const data = info.get_info().window.slots[0].details.extra.custom_data;
|
|
assert.deepEqual(data, {number: '1', self: null, accessor: null});
|
|
assert.equal(info.get_info().window.slots[0].truncated, true);
|
|
|
|
bot.currentWindow = null;
|
|
bot.emit('windowClose', lectern);
|
|
tick();
|
|
assert.equal(info.get_info().window.kind, 'inventory');
|
|
assert.deepEqual(info.get_info().special, {});
|
|
assert.equal(lectern.listenerCount('updateSlot'), 0);
|
|
let updates = 0;
|
|
const unsubscribe = info.on_update(() => updates++);
|
|
bot.inventory.selectedItem = new Item(registry.itemsByName.stone.id, 3);
|
|
bot._client.emit('set_slot');
|
|
tick();
|
|
assert.equal(info.get_info().cursorItem.count, 3);
|
|
assert.equal(info.get_info().cursorItem.slot, null);
|
|
bot.emit('physicsTick');
|
|
tick();
|
|
assert.equal(updates, 1);
|
|
unsubscribe();
|
|
bot.emit('physicsTick');
|
|
info.dispose();
|
|
tick();
|
|
assert.equal(bot.eventNames().length, 0);
|
|
assert.equal(bot._client.eventNames().length, 0);
|
|
assert.equal(bot.inventory.listenerCount('updateSlot'), 0);
|
|
assert.throws(() => info.on_update(() => {}), /已销毁/);
|
|
});
|
|
|
|
test('the real info manager loads inventory and owns its cleanup', async () => {
|
|
const bot = createBot();
|
|
const manager = await create_info_manager(bot);
|
|
assert.equal(bot.listenerCount('end'), 1);
|
|
assert.equal(manager.get_info('inventory').value.window.kind, 'inventory');
|
|
assert.ok(manager.get_info('inventory').i18n.fields.window.fields.slots.fields.type);
|
|
bot.emit('end');
|
|
manager.dispose();
|
|
assert.equal(bot.eventNames().length, 0);
|
|
assert.equal(bot._client.eventNames().length, 0);
|
|
assert.equal(bot.inventory.listenerCount('updateSlot'), 0);
|
|
});
|