245 lines
12 KiB
TypeScript
245 lines
12 KiB
TypeScript
/**
|
|
* tests/set-items-icons-dye.test.ts
|
|
*
|
|
* Verifies Diablo II v1.13c parity for:
|
|
* 1. M'avina's Battle Hymn set items off-white dyeing (whit colormap from invgreybrown.dat table 0).
|
|
* 2. Trang-Oul's Guise dedicated set icon (invbhmu inherited from uh9 setinvfile).
|
|
* 3. Griswold's Heart dedicated set icon (invxaru inherited from xar setinvfile).
|
|
* 4. Ground flippy item sprites for M'avina pieces.
|
|
* 5. Regression protection for existing dyed items (Harlequin Crest cgrn, Tal Rasha dpur, Magefist lgry, Arachnid Mesh blac).
|
|
*/
|
|
|
|
import { describe, it, expect } from 'vitest'
|
|
import { resolveItemSpriteRect, resolveGroundItemSpriteRect } from '../src/ui/inventory.ts'
|
|
import { BAKED_UI_MANIFEST } from '../src/ui/baked-ui-meta.ts'
|
|
import { CLASS_EXCLUSIVE_SETS } from '../src/game/class-starter-profiles.ts'
|
|
|
|
describe('Set Items Dedicated Icons & Color Dyeing Parity (D2 1.13c)', () => {
|
|
describe("Trang-Oul's Guise dedicated icon (invbhmu)", () => {
|
|
it('sets dedicated invFile invbhmu in Necromancer starter profile', () => {
|
|
const trangHelm = CLASS_EXCLUSIVE_SETS.nec.helm!
|
|
expect(trangHelm).toBeDefined()
|
|
expect(trangHelm.name).toBe("Trang-Oul's Guise")
|
|
expect(trangHelm.code).toBe('uh9')
|
|
expect(trangHelm.invFile).toBe('invbhmu')
|
|
})
|
|
|
|
it("maps Trang-Oul's Guise to invbhmu in BAKED_UI_MANIFEST.codeToInvFile", () => {
|
|
expect(BAKED_UI_MANIFEST.codeToInvFile["Trang-Oul's Guise"]).toBe('invbhmu')
|
|
})
|
|
|
|
it('resolves valid non-zero sprite rectangle for invbhmu', () => {
|
|
const rect = resolveItemSpriteRect(CLASS_EXCLUSIVE_SETS.nec.helm!)
|
|
expect(rect).toBeDefined()
|
|
expect(rect).toEqual(BAKED_UI_MANIFEST.itemRects['invbhmu'])
|
|
expect(rect!.w).toBeGreaterThan(0)
|
|
expect(rect!.h).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('does not resolve to generic base bone helm invbhm', () => {
|
|
const rect = resolveItemSpriteRect(CLASS_EXCLUSIVE_SETS.nec.helm!)
|
|
const baseRect = BAKED_UI_MANIFEST.itemRects['invbhm']
|
|
expect(rect).not.toEqual(baseRect)
|
|
})
|
|
|
|
it('resolves invbhmu by item name even if invFile is omitted', () => {
|
|
const rect = resolveItemSpriteRect({ name: "Trang-Oul's Guise" })
|
|
expect(rect).toEqual(BAKED_UI_MANIFEST.itemRects['invbhmu'])
|
|
})
|
|
})
|
|
|
|
describe("Griswold's Heart dedicated icon (invxaru)", () => {
|
|
it('sets dedicated invFile invxaru in Paladin starter profile', () => {
|
|
const grisArmor = CLASS_EXCLUSIVE_SETS.pal.armor!
|
|
expect(grisArmor).toBeDefined()
|
|
expect(grisArmor.name).toBe("Griswold's Heart")
|
|
expect(grisArmor.code).toBe('xar')
|
|
expect(grisArmor.invFile).toBe('invxaru')
|
|
})
|
|
|
|
it("maps Griswold's Heart to invxaru in BAKED_UI_MANIFEST.codeToInvFile", () => {
|
|
expect(BAKED_UI_MANIFEST.codeToInvFile["Griswold's Heart"]).toBe('invxaru')
|
|
})
|
|
|
|
it('resolves valid non-zero sprite rectangle for invxaru', () => {
|
|
const rect = resolveItemSpriteRect(CLASS_EXCLUSIVE_SETS.pal.armor!)
|
|
expect(rect).toBeDefined()
|
|
expect(rect).toEqual(BAKED_UI_MANIFEST.itemRects['invxaru'])
|
|
expect(rect!.w).toBeGreaterThan(0)
|
|
expect(rect!.h).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('does not resolve to generic base ornate plate invaar', () => {
|
|
const rect = resolveItemSpriteRect(CLASS_EXCLUSIVE_SETS.pal.armor!)
|
|
const baseRect = BAKED_UI_MANIFEST.itemRects['invaar']
|
|
expect(rect).not.toEqual(baseRect)
|
|
})
|
|
|
|
it('resolves invxaru by item name even if invFile is omitted', () => {
|
|
const rect = resolveItemSpriteRect({ name: "Griswold's Heart" })
|
|
expect(rect).toEqual(BAKED_UI_MANIFEST.itemRects['invxaru'])
|
|
})
|
|
})
|
|
|
|
describe("M'avina's Battle Hymn off-white (whit) dyeing parity", () => {
|
|
const mavinaPieces = [
|
|
{ slot: 'helm', name: "M'avina's True Sight", baseInv: 'invci3', dyedInv: 'invci3_whit', flp: 'flpci2_whit' },
|
|
{ slot: 'armor', name: "M'avina's Embrace", baseInv: 'invfld', dyedInv: 'invfld_whit', flp: 'flpfld_whit' },
|
|
{ slot: 'gloves', name: "M'avina's Icy Clutch", baseInv: 'invtgl', dyedInv: 'invtgl_whit', flp: 'flptgl_whit' },
|
|
{ slot: 'belt', name: "M'avina's Tenet", baseInv: 'invvbl', dyedInv: 'invvbl_whit', flp: 'flpvbl_whit' },
|
|
{ slot: 'weapon1', name: "M'avina's Caster", baseInv: 'invam2', dyedInv: 'invam2_whit', flp: 'flpam2_whit' },
|
|
] as const
|
|
|
|
it('has invtransform: whit on all 5 pieces in Amazon starter profile', () => {
|
|
const amaSet = CLASS_EXCLUSIVE_SETS.ama
|
|
for (const p of mavinaPieces) {
|
|
const item = (amaSet as any)[p.slot]
|
|
expect(item, `Amazon starter set must include ${p.slot}`).toBeDefined()
|
|
expect(item.invtransform, `${p.name} must have invtransform: 'whit'`).toBe('whit')
|
|
}
|
|
})
|
|
|
|
it('registers all 5 dyed variants in BAKED_UI_MANIFEST.itemRects with non-zero dimensions', () => {
|
|
for (const p of mavinaPieces) {
|
|
const rect = BAKED_UI_MANIFEST.itemRects[p.dyedInv]
|
|
expect(rect, `itemRects must contain ${p.dyedInv}`).toBeDefined()
|
|
expect(rect!.w).toBeGreaterThan(0)
|
|
expect(rect!.h).toBeGreaterThan(0)
|
|
}
|
|
})
|
|
|
|
it('maps all 5 piece names to dyed variants in codeToInvFile', () => {
|
|
for (const p of mavinaPieces) {
|
|
expect(BAKED_UI_MANIFEST.codeToInvFile[p.name], `codeToInvFile[${p.name}] must map to ${p.dyedInv}`).toBe(p.dyedInv)
|
|
}
|
|
})
|
|
|
|
it('resolves dyed rects from starter items via resolveItemSpriteRect', () => {
|
|
const amaSet = CLASS_EXCLUSIVE_SETS.ama
|
|
for (const p of mavinaPieces) {
|
|
const item = (amaSet as any)[p.slot]
|
|
const rect = resolveItemSpriteRect(item)
|
|
expect(rect, `${p.name} must resolve to ${p.dyedInv}`).toEqual(BAKED_UI_MANIFEST.itemRects[p.dyedInv])
|
|
}
|
|
})
|
|
|
|
it('resolves dyed rects when given base invFile + invtransform: whit', () => {
|
|
for (const p of mavinaPieces) {
|
|
const rect = resolveItemSpriteRect({ invFile: p.baseInv, invtransform: 'whit' })
|
|
expect(rect, `${p.baseInv} + whit must resolve to ${p.dyedInv}`).toEqual(BAKED_UI_MANIFEST.itemRects[p.dyedInv])
|
|
}
|
|
})
|
|
|
|
it('resolves dyed rects when given item name alone', () => {
|
|
for (const p of mavinaPieces) {
|
|
const rect = resolveItemSpriteRect({ name: p.name })
|
|
expect(rect, `${p.name} alone must resolve to ${p.dyedInv}`).toEqual(BAKED_UI_MANIFEST.itemRects[p.dyedInv])
|
|
}
|
|
})
|
|
|
|
it("resolves authentic ground flippy sprites for all 5 M'avina pieces", () => {
|
|
for (const p of mavinaPieces) {
|
|
const flpRect = resolveGroundItemSpriteRect({ name: p.name, invtransform: 'whit' })
|
|
expect(flpRect, `${p.name} ground item must resolve to ${p.flp}`).toEqual(BAKED_UI_MANIFEST.flippyRects[p.flp])
|
|
expect(flpRect!.w).toBeGreaterThan(0)
|
|
expect(flpRect!.h).toBeGreaterThan(0)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('Regression: existing dyed items remain fully intact', () => {
|
|
it('resolves Harlequin Crest to invcap_cgrn and flpcap_cgrn', () => {
|
|
expect(resolveItemSpriteRect({ invFile: 'invcap', invtransform: 'cgrn' })).toEqual(BAKED_UI_MANIFEST.itemRects['invcap_cgrn'])
|
|
expect(resolveItemSpriteRect({ name: 'Harlequin Crest' })).toEqual(BAKED_UI_MANIFEST.itemRects['invcap_cgrn'])
|
|
expect(resolveGroundItemSpriteRect({ name: 'Harlequin Crest', invtransform: 'cgrn' })).toEqual(BAKED_UI_MANIFEST.flippyRects['flpcap_cgrn'])
|
|
})
|
|
|
|
it("resolves Tal Rasha's Guardianship to invgth_dpur", () => {
|
|
expect(resolveItemSpriteRect({ invFile: 'invgth', invtransform: 'dpur' })).toEqual(BAKED_UI_MANIFEST.itemRects['invgth_dpur'])
|
|
expect(resolveItemSpriteRect({ name: "Tal Rasha's Guardianship" })).toEqual(BAKED_UI_MANIFEST.itemRects['invgth_dpur'])
|
|
})
|
|
|
|
it('resolves Magefist to invtgl_lgry', () => {
|
|
expect(resolveItemSpriteRect({ invFile: 'invtgl', invtransform: 'lgry' })).toEqual(BAKED_UI_MANIFEST.itemRects['invtgl_lgry'])
|
|
expect(resolveItemSpriteRect({ name: 'Magefist' })).toEqual(BAKED_UI_MANIFEST.itemRects['invtgl_lgry'])
|
|
})
|
|
|
|
it('resolves Arachnid Mesh to invvbl_blac', () => {
|
|
expect(resolveItemSpriteRect({ invFile: 'invvbl', invtransform: 'blac' })).toEqual(BAKED_UI_MANIFEST.itemRects['invvbl_blac'])
|
|
expect(resolveItemSpriteRect({ name: 'Arachnid Mesh' })).toEqual(BAKED_UI_MANIFEST.itemRects['invvbl_blac'])
|
|
})
|
|
})
|
|
|
|
describe('Starter Profiles Dedicated Set Icons Parity (D2 1.13c)', () => {
|
|
it("sets dedicated invFile invaaru for Immortal King's Soul Cage (Barbarian)", () => {
|
|
const ikArmor = CLASS_EXCLUSIVE_SETS.bar.armor!
|
|
expect(ikArmor).toBeDefined()
|
|
expect(ikArmor.code).toBe('uar')
|
|
expect(ikArmor.invFile).toBe('invaaru')
|
|
expect(BAKED_UI_MANIFEST.codeToInvFile[ikArmor.name!]).toBe('invaaru')
|
|
expect(resolveItemSpriteRect(ikArmor)).toEqual(BAKED_UI_MANIFEST.itemRects['invaaru'])
|
|
expect(resolveItemSpriteRect(ikArmor)).not.toEqual(BAKED_UI_MANIFEST.itemRects['invaar'])
|
|
})
|
|
|
|
it("sets dedicated invFile invmstu for Aldur's Rhythm (Druid)", () => {
|
|
const aldurWeapon = CLASS_EXCLUSIVE_SETS.dru.weapon1!
|
|
expect(aldurWeapon).toBeDefined()
|
|
expect(aldurWeapon.code).toBe('9mt')
|
|
expect(aldurWeapon.invFile).toBe('invmstu')
|
|
expect(BAKED_UI_MANIFEST.codeToInvFile[aldurWeapon.name!]).toBe('invmstu')
|
|
expect(resolveItemSpriteRect(aldurWeapon)).toEqual(BAKED_UI_MANIFEST.itemRects['invmstu'])
|
|
expect(resolveItemSpriteRect(aldurWeapon)).not.toEqual(BAKED_UI_MANIFEST.itemRects['invmst'])
|
|
})
|
|
|
|
it("sets dedicated invFile invbhmu for Natalya's Totem (Assassin)", () => {
|
|
const natHelm = CLASS_EXCLUSIVE_SETS.ass.helm!
|
|
expect(natHelm).toBeDefined()
|
|
expect(natHelm.code).toBe('xh9')
|
|
expect(natHelm.invFile).toBe('invbhmu')
|
|
expect(BAKED_UI_MANIFEST.codeToInvFile[natHelm.name!]).toBe('invbhmu')
|
|
expect(resolveItemSpriteRect(natHelm)).toEqual(BAKED_UI_MANIFEST.itemRects['invbhmu'])
|
|
expect(resolveItemSpriteRect(natHelm)).not.toEqual(BAKED_UI_MANIFEST.itemRects['invbhm'])
|
|
})
|
|
|
|
it("sets dedicated invFile invskru for Natalya's Mark (Assassin)", () => {
|
|
const natClaw = CLASS_EXCLUSIVE_SETS.ass.weapon1!
|
|
expect(natClaw).toBeDefined()
|
|
expect(natClaw.code).toBe('7qr')
|
|
expect(natClaw.invFile).toBe('invskru')
|
|
expect(BAKED_UI_MANIFEST.codeToInvFile[natClaw.name!]).toBe('invskru')
|
|
expect(resolveItemSpriteRect(natClaw)).toEqual(BAKED_UI_MANIFEST.itemRects['invskru'])
|
|
expect(resolveItemSpriteRect(natClaw)).not.toEqual(BAKED_UI_MANIFEST.itemRects['invskr'])
|
|
})
|
|
})
|
|
|
|
describe('String Table Display Name vs Internal Index Resolution (D2 1.13c)', () => {
|
|
it("resolves Aldur's Rhythm by both internal index and English display name", () => {
|
|
expect(BAKED_UI_MANIFEST.codeToInvFile["Aldur's Gauntlet"]).toBe('invmstu')
|
|
expect(BAKED_UI_MANIFEST.codeToInvFile["Aldur's Rhythm"]).toBe('invmstu')
|
|
expect(resolveItemSpriteRect({ name: "Aldur's Rhythm" })).toEqual(BAKED_UI_MANIFEST.itemRects['invmstu'])
|
|
})
|
|
|
|
it("resolves Bartuc's Cut-Throat by internal index (Cutthroat1) and display name", () => {
|
|
expect(BAKED_UI_MANIFEST.codeToInvFile['Cutthroat1']).toBe('invclw')
|
|
expect(BAKED_UI_MANIFEST.codeToInvFile["Bartuc's Cut-Throat"]).toBe('invclw')
|
|
expect(resolveItemSpriteRect({ name: "Bartuc's Cut-Throat" })).toEqual(BAKED_UI_MANIFEST.itemRects['invclw'])
|
|
})
|
|
|
|
it("resolves Death's Fathom by internal index (Fathom) and display name", () => {
|
|
expect(BAKED_UI_MANIFEST.codeToInvFile['Fathom']).toBe('invob5')
|
|
expect(BAKED_UI_MANIFEST.codeToInvFile["Death's Fathom"]).toBe('invob5')
|
|
expect(resolveItemSpriteRect({ name: "Death's Fathom" })).toEqual(BAKED_UI_MANIFEST.itemRects['invob5'])
|
|
})
|
|
|
|
it("resolves Griswold's Redemption by Blizzard index (with typo Griswolds's) and display name", () => {
|
|
expect(BAKED_UI_MANIFEST.codeToInvFile["Griswolds's Redemption"]).toBe('invwsp_dgld')
|
|
expect(BAKED_UI_MANIFEST.codeToInvFile["Griswold's Redemption"]).toBe('invwsp_dgld')
|
|
})
|
|
|
|
it("resolves Whitstan's Guard by Blizzard index (with typo Wihtstan's) and display name", () => {
|
|
expect(BAKED_UI_MANIFEST.codeToInvFile["Wihtstan's Guard"]).toBe('invxmlu')
|
|
expect(BAKED_UI_MANIFEST.codeToInvFile["Whitstan's Guard"]).toBe('invxmlu')
|
|
})
|
|
})
|
|
})
|