130 lines
5.5 KiB
TypeScript
130 lines
5.5 KiB
TypeScript
import { describe, expect, test } from 'vitest'
|
|
import {
|
|
readSuperUniques,
|
|
CANONICAL_SUPERUNIQUES_TABLE,
|
|
CANONICAL_SUPER_UNIQUE_LANDMARKS,
|
|
CANONICAL_SUPER_UNIQUES_BY_LEVEL,
|
|
} from '../src/game/monsters.ts'
|
|
import {
|
|
MONSTER_ART_MAP,
|
|
resolveMonsterArtSpec,
|
|
} from '../src/game/monster-mapping.ts'
|
|
import { MpqArchive } from '../src/mpq/archive.ts'
|
|
import { fileSource } from '../src/mpq/file-source.ts'
|
|
import { MountedArchives } from '../src/mpq/mount.ts'
|
|
import { parseTable } from '../src/game/acts.ts'
|
|
|
|
describe('SuperUniques Fidelity & Art Packaging (Issue #115)', () => {
|
|
const d2DataDir = '/usr/local/google/home/taodao/d2-data'
|
|
|
|
test('CANONICAL_SUPER_UNIQUE_LANDMARKS contains valid and corrected minion IDs', () => {
|
|
// 10 previously mistyped minions must match correct IDs
|
|
const landmarkMap = new Map(CANONICAL_SUPER_UNIQUE_LANDMARKS.map(l => [l.id, l]))
|
|
|
|
expect(landmarkMap.get('Beetleburst')?.minionMonsterId).toBe('scarab2')
|
|
expect(landmarkMap.get('Dark Elder')?.minionMonsterId).toBe('zombie5')
|
|
expect(landmarkMap.get('Bloodwitch the Wild')?.minionMonsterId).toBe('pantherwoman1')
|
|
expect(landmarkMap.get('Sszark the Burning')?.minionMonsterId).toBe('arach4')
|
|
expect(landmarkMap.get('Witch Doctor Endugu')?.minionMonsterId).toBe('fetish4')
|
|
expect(landmarkMap.get('Battlemaid Sarina')?.minionMonsterId).toBe('corruptrogue5')
|
|
expect(landmarkMap.get('Dac Farren')?.minionMonsterId).toBe('imp3')
|
|
expect(landmarkMap.get('Shenk the Overseer')?.minionMonsterId).toBe('minion1')
|
|
expect(landmarkMap.get('Frozenstein')?.minionMonsterId).toBe('snowyeti4')
|
|
expect(landmarkMap.get('Nihlathak')?.minionMonsterId).toBe('minion6')
|
|
})
|
|
|
|
test('CANONICAL_SUPERUNIQUES_TABLE parses with corrected Class entries', () => {
|
|
const parsed = readSuperUniques(CANONICAL_SUPERUNIQUES_TABLE)
|
|
const map = new Map(parsed.map(p => [p.id, p]))
|
|
|
|
expect(map.get('Beetleburst')?.monsterId).toBe('scarab2')
|
|
expect(map.get('Dark Elder')?.monsterId).toBe('zombie5')
|
|
expect(map.get('Bloodwitch the Wild')?.monsterId).toBe('pantherwoman1')
|
|
expect(map.get('Sszark the Burning')?.monsterId).toBe('arach4')
|
|
expect(map.get('Witch Doctor Endugu')?.monsterId).toBe('fetishshaman4')
|
|
expect(map.get('Battlemaid Sarina')?.monsterId).toBe('corruptrogue5')
|
|
expect(map.get('Dac Farren')?.monsterId).toBe('imp3')
|
|
expect(map.get('Frozenstein')?.monsterId).toBe('snowyeti4')
|
|
})
|
|
|
|
test('MONSTER_ART_MAP correctly maps critical SuperUnique boss tokens for each Act', () => {
|
|
// Act 1: Griswold (GZ), The Smith (5P)
|
|
expect(MONSTER_ART_MAP.griswold?.token).toBe('GZ')
|
|
expect(MONSTER_ART_MAP.smith?.token).toBe('5P')
|
|
|
|
// Act 2: Radament (RD), Coldworm (MQ), The Summoner (SU)
|
|
expect(MONSTER_ART_MAP.radament?.token).toBe('RD')
|
|
expect(MONSTER_ART_MAP.maggotqueen1?.token).toBe('MQ')
|
|
expect(MONSTER_ART_MAP.summoner?.token).toBe('SU')
|
|
|
|
// Act 3: High Council members (HP)
|
|
expect(MONSTER_ART_MAP.councilmember1?.token).toBe('HP')
|
|
expect(MONSTER_ART_MAP.councilmember2?.token).toBe('HP')
|
|
expect(MONSTER_ART_MAP.councilmember3?.token).toBe('HP')
|
|
|
|
// Act 4: Hephasto the Armorer (5P)
|
|
expect(MONSTER_ART_MAP.hephasto?.token).toBe('5P')
|
|
|
|
// Act 5: Nihlathak (XU), Baal Subject 5 (43)
|
|
expect(MONSTER_ART_MAP.nihlathakboss?.token).toBe('XU')
|
|
expect(MONSTER_ART_MAP.baalminion1?.token).toBe('43')
|
|
})
|
|
|
|
test('All 66 SuperUniques in game data verify boss class and minion resolution', async () => {
|
|
const archives = new MountedArchives()
|
|
for (const name of ['d2data.mpq', 'd2exp.mpq', 'Patch_D2.mpq']) {
|
|
try {
|
|
const archive = await MpqArchive.open(await fileSource(`${d2DataDir}/${name}`))
|
|
archives.add(name, archive)
|
|
} catch {
|
|
// If data dir unavailable in test runner, skip archive loading portion
|
|
return
|
|
}
|
|
}
|
|
|
|
const suData = await archives.read('data\\global\\excel\\SuperUniques.txt')
|
|
const suTable = parseTable(suData)
|
|
const superUniques = readSuperUniques(suTable)
|
|
|
|
expect(superUniques.length).toBe(66)
|
|
|
|
for (const su of superUniques) {
|
|
expect(su.id).toBeTruthy()
|
|
expect(su.monsterId).toBeTruthy()
|
|
|
|
// Boss class must resolve to an authentic art spec (including minion1 -> token 'XX')
|
|
const bossArt = resolveMonsterArtSpec(su.monsterId)
|
|
expect(bossArt).not.toBeNull()
|
|
expect(bossArt?.token).toBeTruthy()
|
|
if (su.monsterId === 'minion1') {
|
|
expect(bossArt).toEqual({ token: 'XX', weapon: 'hth' })
|
|
} else {
|
|
expect(bossArt?.token.toLowerCase()).not.toBe('xx')
|
|
}
|
|
}
|
|
})
|
|
|
|
test('Act 5 Minion of Destruction (minion1..11) resolve to token XX while unknown/empty IDs resolve to null', () => {
|
|
// minion1..minion11 have authentic D2exp.mpq art under data\global\monsters\XX\
|
|
for (let i = 1; i <= 11; i += 1) {
|
|
const art = resolveMonsterArtSpec(`minion${i}`)
|
|
expect(art).toEqual({ token: 'XX', weapon: 'hth' })
|
|
}
|
|
// Unknown or empty IDs
|
|
expect(resolveMonsterArtSpec('xx')).toBeNull()
|
|
expect(resolveMonsterArtSpec('')).toBeNull()
|
|
})
|
|
|
|
test('CANONICAL_SUPER_UNIQUES_BY_LEVEL builds without undefined errors', () => {
|
|
expect(Object.keys(CANONICAL_SUPER_UNIQUES_BY_LEVEL).length).toBeGreaterThan(0)
|
|
for (const [lvl, specOrSpecs] of Object.entries(CANONICAL_SUPER_UNIQUES_BY_LEVEL)) {
|
|
const specs = Array.isArray(specOrSpecs) ? specOrSpecs : [specOrSpecs]
|
|
for (const s of specs) {
|
|
expect(s.id).toBeTruthy()
|
|
expect(s.monsterId).toBeTruthy()
|
|
expect(s.minionMonsterId).toBeTruthy()
|
|
}
|
|
}
|
|
})
|
|
})
|