feat(inventory): support ring, amulet, jewel, and charm VarInvGfx graphic variants (Fixes #375)
This commit is contained in:
parent
609149fa5f
commit
43748eac53
Binary file not shown.
|
Before Width: | Height: | Size: 391 KiB After Width: | Height: | Size: 397 KiB |
File diff suppressed because it is too large
Load Diff
|
|
@ -472,6 +472,30 @@ async function main(): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
// Register authentic ring DC6 icons (invrin1..invrin5 per ItemTypes.txt VarInvGfx = 5)
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const ringInv = `invrin${i}`
|
||||
if (archives.has(`data/global/items/${ringInv}.dc6`)) {
|
||||
uniqueInvFiles.add(ringInv)
|
||||
}
|
||||
}
|
||||
|
||||
// Register authentic amulet DC6 icons (invamu1..invamu3 per ItemTypes.txt VarInvGfx = 3)
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
const amuInv = `invamu${i}`
|
||||
if (archives.has(`data/global/items/${amuInv}.dc6`)) {
|
||||
uniqueInvFiles.add(amuInv)
|
||||
}
|
||||
}
|
||||
|
||||
// Register authentic jewel DC6 icons (invjw1..invjw6 per ItemTypes.txt VarInvGfx = 6)
|
||||
for (let i = 1; i <= 6; i++) {
|
||||
const jwInv = `invjw${i}`
|
||||
if (archives.has(`data/global/items/${jwInv}.dc6`)) {
|
||||
uniqueInvFiles.add(jwInv)
|
||||
}
|
||||
}
|
||||
|
||||
// Identity mapping for every invfile: codeToInvFile[inv] = inv
|
||||
for (const inv of uniqueInvFiles) {
|
||||
codeToInvFile[inv] = inv
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -103,6 +103,8 @@ export interface ResolvableItem {
|
|||
readonly invFile?: string | undefined
|
||||
readonly invtransform?: string | undefined
|
||||
readonly chrtransform?: string | undefined
|
||||
readonly variant?: number | undefined
|
||||
readonly id?: string | undefined
|
||||
readonly invWidth?: number | undefined
|
||||
readonly invHeight?: number | undefined
|
||||
readonly width?: number | undefined
|
||||
|
|
@ -125,6 +127,10 @@ export function resolveItemSpriteRect(
|
|||
// Tier 0.5: Authentic Diablo II 1.13c Charm Resolution (cm1, cm2, cm3, Annihilus, Torch, Gheed's)
|
||||
if (item.code === 'cm1' || item.code === 'anni' || item.name?.includes('Annihilus')) {
|
||||
const validSmall = ['invch1', 'invch4', 'invch7', 'invmss']
|
||||
if (item.variant !== undefined && !item.name?.includes('Annihilus') && item.code !== 'anni') {
|
||||
const v = ['invch1', 'invch4', 'invch7'][Math.abs(Math.floor(item.variant)) % 3]
|
||||
if (itemRects[v]) return itemRects[v]
|
||||
}
|
||||
const file = item.invFile?.toLowerCase()
|
||||
const direct = file && validSmall.includes(file) ? (itemRects[item.invFile!] ?? itemRects[file]) : undefined
|
||||
if (item.code === 'anni' || item.name?.includes('Annihilus')) {
|
||||
|
|
@ -134,6 +140,10 @@ export function resolveItemSpriteRect(
|
|||
}
|
||||
if (item.code === 'cm2' || item.code === 'torch' || item.name?.includes('Torch')) {
|
||||
const validLarge = ['invch2', 'invch5', 'invch8', 'invtrch']
|
||||
if (item.variant !== undefined && !item.name?.includes('Torch') && item.code !== 'torch') {
|
||||
const v = ['invch2', 'invch5', 'invch8'][Math.abs(Math.floor(item.variant)) % 3]
|
||||
if (itemRects[v]) return itemRects[v]
|
||||
}
|
||||
const file = item.invFile?.toLowerCase()
|
||||
const direct = file && validLarge.includes(file) ? (itemRects[item.invFile!] ?? itemRects[file]) : undefined
|
||||
if (item.code === 'torch' || item.name?.includes('Torch')) {
|
||||
|
|
@ -143,11 +153,85 @@ export function resolveItemSpriteRect(
|
|||
}
|
||||
if (item.code === 'cm3' || item.code === 'gheeds' || item.name?.includes('Gheed')) {
|
||||
const validGrand = ['invch3', 'invch6', 'invch9']
|
||||
if (item.variant !== undefined) {
|
||||
const v = validGrand[Math.abs(Math.floor(item.variant)) % validGrand.length]
|
||||
if (itemRects[v]) return itemRects[v]
|
||||
}
|
||||
const file = item.invFile?.toLowerCase()
|
||||
const direct = file && validGrand.includes(file) ? (itemRects[item.invFile!] ?? itemRects[file]) : undefined
|
||||
return direct ?? itemRects['invch3'] ?? itemRects['invch6'] ?? itemRects['invch9'] ?? null
|
||||
}
|
||||
|
||||
// Tier 0.6: Authentic Diablo II 1.13c ItemTypes.txt VarInvGfx (Ring: 5, Amulet: 3, Jewel: 6)
|
||||
if (item.code === 'rin' || item.code === 'ring' || (item.invFile && /^invrin\d?$/i.test(item.invFile))) {
|
||||
const ringVariants = ['invrin1', 'invrin2', 'invrin3', 'invrin4', 'invrin5']
|
||||
if (item.variant !== undefined) {
|
||||
const idx = Math.abs(Math.floor(item.variant)) % ringVariants.length
|
||||
const key = ringVariants[idx]
|
||||
if (itemRects[key]) return itemRects[key]
|
||||
}
|
||||
if (item.invFile && item.invFile.toLowerCase() !== 'invrin' && itemRects[item.invFile]) {
|
||||
return itemRects[item.invFile]
|
||||
}
|
||||
if (item.id) {
|
||||
let hash = 0
|
||||
for (let i = 0; i < item.id.length; i++) {
|
||||
hash = ((hash << 5) - hash + item.id.charCodeAt(i)) | 0
|
||||
}
|
||||
const idx = Math.abs(hash) % ringVariants.length
|
||||
const key = ringVariants[idx]
|
||||
if (itemRects[key]) return itemRects[key]
|
||||
}
|
||||
return itemRects['invrin1'] ?? itemRects['invrin'] ?? null
|
||||
}
|
||||
|
||||
if (item.code === 'amu' || item.code === 'amul' || (item.invFile && /^invamu\d?$/i.test(item.invFile))) {
|
||||
const amuVariants = ['invamu1', 'invamu2', 'invamu3']
|
||||
if (item.variant !== undefined) {
|
||||
const idx = Math.abs(Math.floor(item.variant)) % amuVariants.length
|
||||
const key = amuVariants[idx]
|
||||
if (itemRects[key]) return itemRects[key]
|
||||
}
|
||||
if (item.invFile && item.invFile.toLowerCase() !== 'invamu' && itemRects[item.invFile]) {
|
||||
return itemRects[item.invFile]
|
||||
}
|
||||
if (item.name && BAKED_UI_MANIFEST.codeToInvFile[item.name] && itemRects[BAKED_UI_MANIFEST.codeToInvFile[item.name]]) {
|
||||
return itemRects[BAKED_UI_MANIFEST.codeToInvFile[item.name]]
|
||||
}
|
||||
if (item.id) {
|
||||
let hash = 0
|
||||
for (let i = 0; i < item.id.length; i++) {
|
||||
hash = ((hash << 5) - hash + item.id.charCodeAt(i)) | 0
|
||||
}
|
||||
const idx = Math.abs(hash) % amuVariants.length
|
||||
const key = amuVariants[idx]
|
||||
if (itemRects[key]) return itemRects[key]
|
||||
}
|
||||
return itemRects['invamu1'] ?? itemRects['invamu'] ?? null
|
||||
}
|
||||
|
||||
if (item.code === 'jew' || item.code === 'jewl' || (item.invFile && /^(invjw|invjew)\d?$/i.test(item.invFile))) {
|
||||
const jewelVariants = ['invjw1', 'invjw2', 'invjw3', 'invjw4', 'invjw5', 'invjw6']
|
||||
if (item.variant !== undefined) {
|
||||
const idx = Math.abs(Math.floor(item.variant)) % jewelVariants.length
|
||||
const key = jewelVariants[idx]
|
||||
if (itemRects[key]) return itemRects[key]
|
||||
}
|
||||
if (item.invFile && itemRects[item.invFile]) {
|
||||
return itemRects[item.invFile]
|
||||
}
|
||||
if (item.id) {
|
||||
let hash = 0
|
||||
for (let i = 0; i < item.id.length; i++) {
|
||||
hash = ((hash << 5) - hash + item.id.charCodeAt(i)) | 0
|
||||
}
|
||||
const idx = Math.abs(hash) % jewelVariants.length
|
||||
const key = jewelVariants[idx]
|
||||
if (itemRects[key]) return itemRects[key]
|
||||
}
|
||||
return itemRects['invjw1'] ?? null
|
||||
}
|
||||
|
||||
// Tier 1: Direct invFile (ignore misc.txt placeholder invfiles for charms)
|
||||
const isCharmPlaceholder =
|
||||
(item.code === 'cm3' && item.invFile?.toLowerCase() === 'invsst') ||
|
||||
|
|
|
|||
|
|
@ -302,5 +302,75 @@ describe('Milestone M4 (Issue #149): Base item invfile audit & charm icons', ()
|
|||
}
|
||||
}
|
||||
})
|
||||
|
||||
describe('Issue #375: ItemTypes.txt VarInvGfx variants for rings, amulets, jewels, and charms', () => {
|
||||
it('bakes all ring (5), amulet (3), and jewel (6) graphic variants into itemRects', () => {
|
||||
// Rings: invrin1 to invrin5
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const key = `invrin${i}`
|
||||
expect(itemRects[key], `Expected ${key} in itemRects`).toBeDefined()
|
||||
expect(itemRects[key]!.w).toBe(28)
|
||||
expect(itemRects[key]!.h).toBe(28)
|
||||
}
|
||||
|
||||
// Amulets: invamu1 to invamu3
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
const key = `invamu${i}`
|
||||
expect(itemRects[key], `Expected ${key} in itemRects`).toBeDefined()
|
||||
expect(itemRects[key]!.w).toBe(28)
|
||||
expect(itemRects[key]!.h).toBe(28)
|
||||
}
|
||||
|
||||
// Jewels: invjw1 to invjw6
|
||||
for (let i = 1; i <= 6; i++) {
|
||||
const key = `invjw${i}`
|
||||
expect(itemRects[key], `Expected ${key} in itemRects`).toBeDefined()
|
||||
expect(itemRects[key]!.w).toBe(28)
|
||||
expect(itemRects[key]!.h).toBe(28)
|
||||
}
|
||||
})
|
||||
|
||||
it('resolves all ring variants 0..4 correctly', () => {
|
||||
for (let v = 0; v < 5; v++) {
|
||||
const rect = resolveItemSpriteRect({ code: 'rin', variant: v }, itemRects)
|
||||
expect(rect).toEqual(itemRects[`invrin${v + 1}`])
|
||||
}
|
||||
// Direct invFile
|
||||
expect(resolveItemSpriteRect({ code: 'rin', invFile: 'invrin3' }, itemRects)).toEqual(itemRects['invrin3'])
|
||||
})
|
||||
|
||||
it('resolves all amulet variants 0..2 correctly', () => {
|
||||
for (let v = 0; v < 3; v++) {
|
||||
const rect = resolveItemSpriteRect({ code: 'amu', variant: v }, itemRects)
|
||||
expect(rect).toEqual(itemRects[`invamu${v + 1}`])
|
||||
}
|
||||
// Direct invFile
|
||||
expect(resolveItemSpriteRect({ code: 'amu', invFile: 'invamu2' }, itemRects)).toEqual(itemRects['invamu2'])
|
||||
})
|
||||
|
||||
it('resolves all jewel variants 0..5 correctly', () => {
|
||||
for (let v = 0; v < 6; v++) {
|
||||
const rect = resolveItemSpriteRect({ code: 'jew', variant: v }, itemRects)
|
||||
expect(rect).toEqual(itemRects[`invjw${v + 1}`])
|
||||
}
|
||||
// Direct invFile
|
||||
expect(resolveItemSpriteRect({ code: 'jew', invFile: 'invjw5' }, itemRects)).toEqual(itemRects['invjw5'])
|
||||
})
|
||||
|
||||
it('resolves charm variants when variant property is specified', () => {
|
||||
expect(resolveItemSpriteRect({ code: 'cm1', variant: 0 }, itemRects)).toEqual(itemRects['invch1'])
|
||||
expect(resolveItemSpriteRect({ code: 'cm1', variant: 1 }, itemRects)).toEqual(itemRects['invch4'])
|
||||
expect(resolveItemSpriteRect({ code: 'cm1', variant: 2 }, itemRects)).toEqual(itemRects['invch7'])
|
||||
|
||||
expect(resolveItemSpriteRect({ code: 'cm2', variant: 0 }, itemRects)).toEqual(itemRects['invch2'])
|
||||
expect(resolveItemSpriteRect({ code: 'cm2', variant: 1 }, itemRects)).toEqual(itemRects['invch5'])
|
||||
expect(resolveItemSpriteRect({ code: 'cm2', variant: 2 }, itemRects)).toEqual(itemRects['invch8'])
|
||||
|
||||
expect(resolveItemSpriteRect({ code: 'cm3', variant: 0 }, itemRects)).toEqual(itemRects['invch3'])
|
||||
expect(resolveItemSpriteRect({ code: 'cm3', variant: 1 }, itemRects)).toEqual(itemRects['invch6'])
|
||||
expect(resolveItemSpriteRect({ code: 'cm3', variant: 2 }, itemRects)).toEqual(itemRects['invch9'])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue