diablo2-web/tests/p0-520-player-state.test.ts

119 lines
4.6 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import { GameEngine } from '../src/game/engine.ts'
import { CANONICAL_CHARSTATS, type CharacterClassCode } from '../src/game/classes.ts'
import { HudManager } from '../src/ui/hud-manager.ts'
import { serializeSaveData, deserializeSaveData, applySaveDataToRuntime } from '../src/game/save.ts'
import type { Item } from '../src/game/items.ts'
const ALL_CLASSES: CharacterClassCode[] = ['ama', 'sor', 'nec', 'pal', 'bar', 'dru', 'ass']
describe('Issue #520: Authoritative Player State, Per-Class Gains, and Save/Load Round-Trip', () => {
it('initializes all 7 classes with authentic CharStats.txt base attributes and vitals', () => {
for (const cls of ALL_CLASSES) {
const engine = new GameEngine(cls)
const cStats = CANONICAL_CHARSTATS[cls]
const p = engine.world.player
expect(p.strength).toBe(cStats.baseAttributes.strength)
expect(p.dexterity).toBe(cStats.baseAttributes.dexterity)
expect(p.vitality).toBe(cStats.baseAttributes.vitality)
expect(p.energy).toBe(cStats.baseAttributes.energy)
expect(p.maxHp).toBe(cStats.baseVitals.life)
expect(p.hp).toBe(cStats.baseVitals.life)
expect(p.maxMana).toBe(cStats.baseVitals.mana)
expect(p.mana).toBe(cStats.baseVitals.mana)
expect(p.maxStamina).toBe(cStats.baseVitals.stamina)
expect(p.stamina).toBe(cStats.baseVitals.stamina)
}
})
it('allocates vitality and energy using per-class CharStats.txt gains without full-healing a damaged player', () => {
for (const cls of ALL_CLASSES) {
const engine = new GameEngine(cls)
const hud = new HudManager(engine)
const cStats = CANONICAL_CHARSTATS[cls]
engine.world.player.statPoints = 10
const baseHp = engine.world.player.maxHp
const baseMana = engine.world.player.maxMana
const baseStam = engine.world.player.maxStamina ?? cStats.baseVitals.stamina
// Damage player before allocating vitality
hud.takeDamage(20)
const damagedHp = engine.world.player.hp
expect(damagedHp).toBe(baseHp - 20)
expect(hud.allocateStat('vitality')).toBe(true)
expect(engine.world.player.maxHp).toBe(Math.floor(baseHp + cStats.perStatPoint.lifePerVitality))
expect(engine.world.player.hp).toBe(Math.floor(damagedHp + cStats.perStatPoint.lifePerVitality))
expect(engine.world.player.hp).toBeLessThan(engine.world.player.maxHp)
expect(engine.world.player.maxStamina).toBe(Math.floor(baseStam + cStats.perStatPoint.staminaPerVitality))
expect(hud.allocateStat('energy')).toBe(true)
expect(engine.world.player.maxMana).toBe(Math.floor(baseMana + cStats.perStatPoint.manaPerEnergy))
expect(hud.charSheet.attrs.baseMaxHp).toBe(engine.world.player.maxHp)
expect(hud.charSheet.attrs.baseMaxMana).toBe(engine.world.player.maxMana)
}
})
it('Syncs HUD state with GameEngine across level-ups and save/load round-trips', () => {
const engine = new GameEngine('bar')
const hud = new HudManager(engine)
const res = hud.grantExperience(500)
expect(res.levelsGained).toBe(1)
expect(res.newLevel).toBe(2)
expect(hud.charSheet.attrs.level).toBe(2)
expect(hud.charSheet.attrs.statPoints).toBe(5)
expect(hud.skillTree.unspentSkillPoints).toBe(1)
expect(hud.maxHp).toBe(engine.world.player.maxHp)
hud.allocateStat('strength')
hud.allocateStat('vitality')
engine.bag.gold = 4321
const potion: Item = {
id: 9001,
code: 'hp1',
name: 'Minor Healing Potion',
type: 'potion',
quality: 'normal',
level: 1,
identified: true,
invWidth: 1,
invHeight: 1,
stats: [],
baseCost: 30,
} as unknown as Item
engine.belt.setSlot(0, 0, potion)
const raw = serializeSaveData({
classCode: engine.classCode,
player: engine.world.player,
bag: engine.bag,
belt: engine.belt,
skillPoints: engine.skillPoints,
})
const engine2 = new GameEngine('sor')
const parsed = deserializeSaveData(raw)
expect(parsed).not.toBeNull()
applySaveDataToRuntime(parsed!, {
player: engine2.world.player,
bag: engine2.bag,
belt: engine2.belt,
skillPoints: engine2.skillPoints,
setClassCode: c => {
engine2.classCode = c
},
})
expect(engine2.classCode).toBe('bar')
expect(engine2.world.player.level).toBe(2)
expect(engine2.world.player.strength).toBe(engine.world.player.strength)
expect(engine2.world.player.vitality).toBe(engine.world.player.vitality)
expect(engine2.bag.gold).toBe(4321)
expect(engine2.belt.getSlot(0, 0)?.code).toBe('hp1')
})
})