294 lines
9.3 KiB
TypeScript
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)',
|
|
])
|
|
})
|
|
})
|