import { describe, expect, test } from 'vitest'; import { resolveDs1Object, OBJECT_ROOT } from '../src/game/objects'; import type { ObjectsTable, MonstersTable } from '../src/game/objects'; import { OBJECT_TYPE_MONSTER, OBJECT_TYPE_OBJECT } from '../src/game/object-lookup'; describe('resolveDs1Object (type 1 rules)', () => { const emptyObjectsTable: ObjectsTable = { table: { header: [], rows: [] }, rows: [], byId: new Map() }; const mockMonsters = (): MonstersTable => { // Let's create a preset map and stats map. // We need Act 1, Id 0 -> 'dummyplace' // Act 1, Id 1 -> 'warriv1' -> has code WA // Act 2, Id 0 -> 'warriv1' -> has code NO_WA // Act 1, Id 2 -> 'place_nothing' -> has no row // We only need to populate `presetPlaceByAct` for Act 1 and 2. const presetPlaceByAct = new Map(); presetPlaceByAct.set('1', ['dummyplace', 'warriv1', 'place_nothing']); presetPlaceByAct.set('2', ['warriv1']); // same id but diff act const statsById = new Map(); // Assume columns are 0: Id, 8: Code, 5: NameStr? (Whatever cell reading does, wait, cell reads by column header offset!) // So we need proper D2Table for `monsters.stats`. const statsHeader = ['Id', 'NameStr', 'Code']; const statsRows = [ ['warriv1', 'Warriv', 'WA'], ['dummyplace', '', 'DM'], // has place and art // place_nothing is absent ]; statsById.set('warriv1', statsRows[0]); statsById.set('dummyplace', statsRows[1]); const statsTable = { header: statsHeader, rows: statsRows }; const presetTable = { header: ['Act', 'Place'], rows: [] }; // The array wasn't used in code if presetPlaceByAct is there, but cell needs headers! return { stats: statsTable, preset: presetTable, presetPlaceByAct, statsById }; }; test('resolves named NPC with code and name', () => { const monsters = mockMonsters(); const res = resolveDs1Object(emptyObjectsTable, 1, OBJECT_TYPE_MONSTER, 1, monsters); expect(res.kind).toBe('npc'); expect(res.token).toBe('WA'); expect(res.name).toBe('Warriv'); expect(res.artless).toBe(false); }); test('resolves place with no stats row as artless monster/npc', () => { const monsters = mockMonsters(); const res = resolveDs1Object(emptyObjectsTable, 1, OBJECT_TYPE_MONSTER, 2, monsters); // place_nothing is not in statsById -> code is '' -> artless is true expect(res.kind).toBe('npc'); expect(res.token).toBe(''); expect(res.name).toBe('place_nothing'); expect(res.artless).toBe(true); }); test('returns default monster if objectId out of range', () => { const monsters = mockMonsters(); const res = resolveDs1Object(emptyObjectsTable, 1, OBJECT_TYPE_MONSTER, 999, monsters); expect(res.kind).toBe('monster'); expect(res.token).toBe(''); expect(res.artless).toBe(true); }); test('act filtering produces different results for same id', () => { const monsters = mockMonsters(); // In Act 1, Id 0 -> dummyplace (token DM) const res1 = resolveDs1Object(emptyObjectsTable, 1, OBJECT_TYPE_MONSTER, 0, monsters); expect(res1.token).toBe('DM'); // In Act 2, Id 0 -> warriv1 (token WA) const res2 = resolveDs1Object(emptyObjectsTable, 2, OBJECT_TYPE_MONSTER, 0, monsters); expect(res2.token).toBe('WA'); }); test('omitted monsters parameter restores legacy behavior (dropping type 1)', () => { // If monsters is undefined, type 1 falls back to artless monster const res = resolveDs1Object(emptyObjectsTable, 1, OBJECT_TYPE_MONSTER, 1, undefined); expect(res.kind).toBe('monster'); expect(res.token).toBe(''); expect(res.artless).toBe(true); }); });