103 lines
3.9 KiB
TypeScript
103 lines
3.9 KiB
TypeScript
/**
|
|
* Type-1 (DS1 monster/NPC spawn) resolution.
|
|
*
|
|
* DS1 entries come in two flavours: `type` 2 is an `Objects.txt` object, `type` 1
|
|
* is a spawn whose `id` indexes `MonPreset.txt` filtered by act. Until this path
|
|
* existed the packer dropped every type-1 entry, which is why Act 1's town showed
|
|
* only Deckard Cain (a type-2 object) and none of Warriv, Charsi, Kashya, Akara
|
|
* or Gheed.
|
|
*/
|
|
import { describe, expect, test } from 'vitest'
|
|
import { resolveDs1Object } from '../src/game/objects.ts'
|
|
import type { MonstersTable, ObjectsTable } from '../src/game/objects.ts'
|
|
import { OBJECT_TYPE_MONSTER } from '../src/game/object-lookup.ts'
|
|
|
|
/** The type-1 path never consults `Objects.txt`, so an empty table is sufficient. */
|
|
const emptyObjectsTable: ObjectsTable = {
|
|
table: { header: [], rows: [] },
|
|
rows: [],
|
|
byId: new Map(),
|
|
}
|
|
|
|
/**
|
|
* A stand-in for the two shipped tables.
|
|
*
|
|
* Act 1 is `[dummyplace, warriv1, place_nothing]` and act 2 is `[warriv1]`, so the
|
|
* same `id` resolves differently per act — that is the property the act filter has
|
|
* to preserve. `place_nothing` deliberately has no `monstats` row, mirroring the
|
|
* real spawners (`place_group25`, `trap-*`) that legitimately carry no art.
|
|
* `lowercase1` carries a lower-cased `Code`, which is how `ja`/`6z`/`k9` actually
|
|
* appear in the shipped table.
|
|
*/
|
|
function mockMonsters(): MonstersTable {
|
|
const presetPlaceByAct = new Map<string, readonly string[]>([
|
|
['1', ['dummyplace', 'warriv1', 'place_nothing', 'lowercase1']],
|
|
['2', ['warriv1']],
|
|
])
|
|
|
|
const stats = {
|
|
header: ['Id', 'NameStr', 'Code'],
|
|
rows: [
|
|
['warriv1', 'Warriv', 'WA'],
|
|
['dummyplace', '', 'DM'],
|
|
['lowercase1', 'Jamella', 'ja'],
|
|
],
|
|
}
|
|
|
|
const statsById = new Map<string, readonly string[]>([
|
|
['warriv1', stats.rows[0]!],
|
|
['dummyplace', stats.rows[1]!],
|
|
['lowercase1', stats.rows[2]!],
|
|
])
|
|
|
|
return { stats, preset: { header: ['Act', 'Place'], rows: [] }, presetPlaceByAct, statsById }
|
|
}
|
|
|
|
describe('resolveDs1Object, type-1 spawns', () => {
|
|
test('resolves a named NPC to its art token and name', () => {
|
|
const res = resolveDs1Object(emptyObjectsTable, 1, OBJECT_TYPE_MONSTER, 1, mockMonsters())
|
|
expect(res.kind).toBe('npc')
|
|
expect(res.token).toBe('WA')
|
|
expect(res.name).toBe('Warriv')
|
|
expect(res.artless).toBe(false)
|
|
})
|
|
|
|
test('a Place with no monstats row is artless, not a guess', () => {
|
|
const res = resolveDs1Object(emptyObjectsTable, 1, OBJECT_TYPE_MONSTER, 2, mockMonsters())
|
|
expect(res.kind).toBe('npc')
|
|
expect(res.token).toBe('')
|
|
expect(res.name).toBe('place_nothing')
|
|
expect(res.artless).toBe(true)
|
|
})
|
|
|
|
test('an out-of-range id falls back to a plain monster spawn', () => {
|
|
const res = resolveDs1Object(emptyObjectsTable, 1, OBJECT_TYPE_MONSTER, 999, mockMonsters())
|
|
expect(res.kind).toBe('monster')
|
|
expect(res.token).toBe('')
|
|
expect(res.artless).toBe(true)
|
|
})
|
|
|
|
test('the same id resolves differently per act', () => {
|
|
const monsters = mockMonsters()
|
|
expect(resolveDs1Object(emptyObjectsTable, 1, OBJECT_TYPE_MONSTER, 0, monsters).token).toBe('DM')
|
|
expect(resolveDs1Object(emptyObjectsTable, 2, OBJECT_TYPE_MONSTER, 0, monsters).token).toBe('WA')
|
|
})
|
|
|
|
test('omitting the monsters table restores the legacy drop behaviour', () => {
|
|
const res = resolveDs1Object(emptyObjectsTable, 1, OBJECT_TYPE_MONSTER, 1, undefined)
|
|
expect(res.kind).toBe('monster')
|
|
expect(res.token).toBe('')
|
|
expect(res.artless).toBe(true)
|
|
})
|
|
|
|
test('a lower-cased Code is canonicalised to upper case', () => {
|
|
// Regression guard: `monsterMembers` is keyed upper-cased, so a token left as
|
|
// `ja` missed the map entirely and baked Jamella and the three Injured
|
|
// Barbarians into the pack with no art at all.
|
|
const res = resolveDs1Object(emptyObjectsTable, 1, OBJECT_TYPE_MONSTER, 3, mockMonsters())
|
|
expect(res.token).toBe('JA')
|
|
expect(res.artless).toBe(false)
|
|
expect(res.name).toBe('Jamella')
|
|
})
|
|
})
|