diablo2-web/tests/item-tooltip-123-125.test.ts

294 lines
9.3 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import type { ItemBase } from '../src/game/items.ts'
import { createDroppedItem } from '../src/game/drop-pipeline.ts'
import { formatItemTooltip, aggregateAndSortProperties } from '../src/game/item-tooltip.ts'
describe('Issue #123 & Issue #125: Enhanced vs Base Stats, Property Merging & Priority Sorting', () => {
it('Issue #123: calculates enhanced 1H/2H weapon damage, defense, requirements, and durability alongside original base stats', () => {
const weaponBase: ItemBase & Record<string, any> = {
id: 'btx',
name: 'Battle Axe',
kind: 'weapon',
tags: ['axe', 'weap'],
damage: 24,
defense: 0,
value: 500,
invWidth: 2,
invHeight: 3,
maxStack: 1,
level: 25,
mindam: 6,
maxdam: 12,
twoHandedMindam: 18,
twoHandedMaxdam: 24,
reqstr: 100,
reqdex: 50,
levelreq: 20,
durability: 40,
}
// 100% ED + Requirements -20%
const droppedWeapon = createDroppedItem(weaponBase, 'unique', {
ilvl: 85,
dwInitSeed: 42,
uniqueItem: {
id: 1,
index: 'The Chieftain',
version: 100,
enabled: true,
ladder: false,
rarity: 1,
nolimit: false,
lvl: 29,
levelreq: 35,
code: 'btx',
carry1: false,
costMult: 5,
costAdd: 5000,
props: [
{ code: 'dmg%', min: 100, max: 100 },
{ code: 'ease', min: -20, max: -20 },
],
},
})
const wTooltip = formatItemTooltip(droppedWeapon)
// Two-Hand Damage: 36 to 48 (18 to 24)
expect(wTooltip.twoHandDamage).toEqual({
min: 36,
max: 48,
baseMin: 18,
baseMax: 24,
isEnhanced: true,
})
// One-Hand Damage: 12 to 24 (6 to 12)
expect(wTooltip.oneHandDamage).toEqual({
min: 12,
max: 24,
baseMin: 6,
baseMax: 12,
isEnhanced: true,
})
// Requirements -20%: Str 100 -> 80 (100), Dex 50 -> 40 (50), Level 35 (20)
expect(wTooltip.reqStr).toBe(80)
expect(wTooltip.baseReqStr).toBe(100)
expect(wTooltip.reqDex).toBe(40)
expect(wTooltip.baseReqDex).toBe(50)
expect(wTooltip.reqLevel).toBe(35)
expect(wTooltip.baseReqLevel).toBe(20)
// Armor with +100% Enhanced Defense (Defense: 260 (130)) and 3x Rare Fallback Durability (258 of 258 (86))
const armorBase: ItemBase & Record<string, any> = {
id: 'uit',
name: 'Monarch',
kind: 'armor',
tags: ['shie', 'armo'],
damage: 0,
defense: 130,
value: 1000,
invWidth: 2,
invHeight: 3,
maxStack: 1,
level: 72,
reqstr: 156,
levelreq: 54,
durability: 86,
}
const droppedArmor = createDroppedItem(armorBase, 'unique', {
ilvl: 85,
dwInitSeed: 99,
durabilityMultiplier: 3,
uniqueItem: {
id: 2,
index: 'Stormshield',
version: 100,
enabled: true,
ladder: false,
rarity: 1,
nolimit: false,
lvl: 77,
levelreq: 73,
code: 'uit',
carry1: false,
costMult: 5,
costAdd: 5000,
props: [
{ code: 'ac%', min: 100, max: 100 },
{ code: 'ease', min: -20, max: -20 },
],
},
})
const aTooltip = formatItemTooltip(droppedArmor)
expect(aTooltip.defense).toBe(260)
expect(aTooltip.baseDefense).toBe(130)
expect(aTooltip.reqStr).toBe(124)
expect(aTooltip.baseReqStr).toBe(156)
expect(aTooltip.durability).toEqual({
current: 258,
max: 258,
baseMax: 86,
isEnhanced: true,
})
})
it('Issue #125: merges elemental and physical min/max damage pairs (e.g., fire-min +2 and fire-max +8 -> Adds 2-8 Fire Damage)', () => {
const merged = aggregateAndSortProperties([
{ code: 'fire-min', min: 2, max: 2, value: 2 },
{ code: 'fire-max', min: 8, max: 8, value: 8 },
{ code: 'ltng-min', min: 1, max: 1, value: 1 },
{ code: 'ltng-max', min: 20, max: 20, value: 20 },
{ code: 'cold-min', min: 6, max: 6, value: 6 },
{ code: 'cold-max', min: 14, max: 14, value: 14 },
{ code: 'dmg-min', min: 5, max: 5, value: 5 },
{ code: 'dmg-max', min: 10, max: 10, value: 10 },
])
const base: ItemBase = {
id: 'rin',
name: 'Ring',
kind: 'armor',
tags: ['ring'],
damage: 0,
defense: 0,
value: 100,
invWidth: 1,
invHeight: 1,
maxStack: 1,
level: 1,
}
const item = createDroppedItem(base, 'normal', {
ilvl: 50,
dwInitSeed: 1,
})
;(item as any).rolledProps = merged
const tooltip = formatItemTooltip(item)
const texts = tooltip.lines.map(l => l.text)
expect(texts).toContain('Adds 5-10 Damage')
expect(texts).toContain('Adds 2-8 Fire Damage')
expect(texts).toContain('Adds 1-20 Lightning Damage')
expect(texts).toContain('Adds 6-14 Cold Damage')
expect(texts).not.toContain('+2 to Minimum Fire Damage')
expect(texts).not.toContain('+8 to Maximum Fire Damage')
})
it('Issue #125 & #130: merges 4-element resistances (dgrp=2) into All Resistances and 4 attributes (dgrp=1) into All Attributes ONLY when all 4 are strictly equal', () => {
const base: ItemBase = {
id: 'amu',
name: 'Amulet',
kind: 'armor',
tags: ['amul'],
damage: 0,
defense: 0,
value: 100,
invWidth: 1,
invHeight: 1,
maxStack: 1,
level: 1,
}
// Case 1: All 4 resistances equal (+15) and all 4 attributes equal (+10) -> fold into All Attributes / All Resistances
const equalItem = createDroppedItem(base, 'normal', { ilvl: 80, dwInitSeed: 2 })
;(equalItem as any).rolledProps = [
{ code: 'res-fire', min: 15, max: 15, value: 15 },
{ code: 'res-cold', min: 15, max: 15, value: 15 },
{ code: 'res-ltng', min: 15, max: 15, value: 15 },
{ code: 'res-pois', min: 15, max: 15, value: 15 },
{ code: 'str', min: 10, max: 10, value: 10 },
{ code: 'dex', min: 10, max: 10, value: 10 },
{ code: 'vit', min: 10, max: 10, value: 10 },
{ code: 'enr', min: 10, max: 10, value: 10 },
]
const equalTooltip = formatItemTooltip(equalItem)
expect(equalTooltip.lines.map(l => l.text)).toEqual([
'+10 to All Attributes',
'All Resistances +15',
])
// Case 2: Unequal resistances (res-all: 15 + res-fire: 15 => fire=30, cold=15, ltng=15, pois=15) -> 4 independent lines, no partial All Resistances
const unequalItem = createDroppedItem(base, 'normal', { ilvl: 80, dwInitSeed: 22 })
;(unequalItem as any).rolledProps = [
{ code: 'res-all', min: 15, max: 15, value: 15 },
{ code: 'res-fire', min: 15, max: 15, value: 15 },
{ code: 'str', min: 10, max: 10, value: 10 },
{ code: 'dex', min: 10, max: 10, value: 10 },
{ code: 'vit', min: 10, max: 10, value: 10 },
{ code: 'enr', min: 10, max: 10, value: 10 },
]
const unequalTooltip = formatItemTooltip(unequalItem)
const unequalTexts = unequalTooltip.lines.map(l => l.text)
expect(unequalTexts).toEqual([
'+10 to All Attributes',
'Cold Resist +15%',
'Lightning Resist +15%',
'Fire Resist +30%',
'Poison Resist +15%',
])
expect(unequalTexts).not.toContain('All Resistances +15')
})
it('Issue #125: sorts properties in descending order of ItemStatCost.txt descpriority', () => {
const base: ItemBase = {
id: 'uap',
name: 'Shako',
kind: 'armor',
tags: ['helm', 'armo'],
damage: 0,
defense: 141,
value: 1000,
invWidth: 2,
invHeight: 2,
maxStack: 1,
level: 58,
}
const item = createDroppedItem(base, 'normal', { ilvl: 85, dwInitSeed: 3 })
// Deliberately provide properties in reverse/scrambled priority order
;(item as any).rolledProps = [
{ code: 'sock', min: 2, max: 2, value: 2 }, // prio 0
{ code: 'rep-dur', param: 5, min: 0, max: 0, value: 0 }, // prio 1
{ code: 'mag%', min: 50, max: 50, value: 50 }, // prio 8
{ code: 'res-fire', min: 30, max: 30, value: 30 }, // prio 36
{ code: 'mana', min: 40, max: 40, value: 40 }, // prio 55
{ code: 'hp', min: 60, max: 60, value: 60 }, // prio 59
{ code: 'str', min: 20, max: 20, value: 20 }, // prio 67
{ code: 'ac%', min: 150, max: 150, value: 150 }, // prio 74
{ code: 'lifesteal', min: 8, max: 8, value: 8 }, // prio 88
{ code: 'dmg-fire', min: 10, max: 20, value: 10 }, // prio 102
{ code: 'dmg%', min: 200, max: 200, value: 200 }, // prio 129
{ code: 'swing2', min: 20, max: 20, value: 20 }, // prio 145
{ code: 'allskills', min: 2, max: 2, value: 2 }, // prio 158
{ code: 'hit-skill', param: 197, min: 5, max: 10, value: 5 }, // prio 160
]
const tooltip = formatItemTooltip(item)
const texts = tooltip.lines.map(l => l.text)
expect(texts).toEqual([
'5% Chance to cast level 10 Firestorm on striking',
'+2 to All Skills',
'+20% Increased Attack Speed',
'+200% Enhanced Damage',
'Adds 10-20 Fire Damage',
'8% Life stolen per hit',
'+150% Enhanced Defense',
'+20 to Strength',
'+60 to Life',
'+40 to Mana',
'Fire Resist +30%',
'50% Better Chance of Getting Magic Items',
'Repairs 1 durability in 20 seconds',
'Socketed (2)',
])
})
})