119 lines
4.6 KiB
TypeScript
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')
|
|
})
|
|
})
|