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 = { 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 = { 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: merges 4-element resistances (dgrp=2) into All Resistances and 4 attributes (dgrp=1) into All Attributes', () => { 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, } const item = createDroppedItem(base, 'normal', { ilvl: 80, dwInitSeed: 2 }) ;(item as any).rolledProps = [ { code: 'res-fire', min: 35, max: 35, value: 35 }, { 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 tooltip = formatItemTooltip(item) const texts = tooltip.lines.map(l => l.text) // 4 attributes of +10 merge into "+10 to All Attributes" // 4 resists of 15 + extra 20 fire merge into "All Resistances +15" and "Fire Resist +20%" expect(texts).toEqual(['+10 to All Attributes', 'All Resistances +15', 'Fire Resist +20%']) }) 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)', ]) }) })