import { describe, expect, it } from 'vitest' import { World } from '../src/renderer/game/engine/runtime/World' import { combatPowerOf, resolveEncounter, resolveRaid, rollWarbooty } from '../src/renderer/game/engine/runtime/Systems/combat' import { ENEMIES } from '../src/renderer/game/data/secrets' import { sendMission, recallAll } from '../src/renderer/game/engine/runtime/Systems/missions' import { missionById } from '../src/renderer/game/data/secrets' import { anyNpc } from './world.helpers' import { resetSaveBus, attachLogSink } from './world.helpers' function baseWorld(seed: string): World { const w = World.create({ seed, surname: '杭', familyName: '杭家', motto: 'm', difficulty: 'normal' }) resetSaveBus() attachLogSink(w) return w } describe('combat 战斗结算', () => { it('战力复数化:高装备>低装备,闭关者加成累计', () => { const w = baseWorld('cb-a') const c = w.state.members['x4'] const p0 = combatPowerOf(w, c) w.state.family.inventory['weapon-qi'] = 3 w.equip(c.id, 'weapon-qi') const p1 = combatPowerOf(w, c) expect(p1).toBeGreaterThan(p0) c.techniqueRank = 2 expect(combatPowerOf(w, c)).toBeGreaterThan(p1) }) it('重伤员战力折减(health 因子)', () => { const w = baseWorld('cb-b') const c = w.state.members['x3'] const p0 = combatPowerOf(w, c) c.health = 20 const p1 = combatPowerOf(w, c) expect(p1).toBeLessThan(p0) expect(p1).toBeGreaterThan(p0 * 0.3) }) it('家族战力随供奉职事提升', () => { const w = baseWorld('cb-c') const before = w.familyPower() w.assignPost('x3', 'guardian') w.assignPost('x4', 'guardian') expect(w.familyPower()).toBeGreaterThan(before) }) it('遭遇战必胜路径产出战利并写入战报', () => { const w = baseWorld('cb-d') const c = w.state.members['x4'] c.realm = { major: 'foundation', minor: 0 } c.health = 100 const res = resolveEncounter(w, { title: '模拟遭遇', enemy: ENEMIES.find((e) => e.id === 'e-huiyuan')!, risk: 0.2, team: [c], kind: 'scout', year: w.state.year, month: w.state.month }) expect(res.win).toBe(true) expect(res.loot).toBeTruthy() expect(w.state.battles.length).toBe(1) expect(w.state.battles[0].lines.length).toBeGreaterThan(2) }) it('遭遇战必败路径全体重伤/损失且不崩塌', () => { const w = baseWorld('cb-e') const c = w.state.members['x3'] c.realm = { major: 'mortal', minor: 0 } c.health = 50 const res = resolveEncounter(w, { title: '必败模拟', enemy: ENEMIES.find((e) => e.id === 'e-kuangzun')!, risk: 1, team: [c], kind: 'scout', year: w.state.year, month: w.state.month }) expect(res.win).toBe(false) expect(c.health).toBeLessThanOrEqual(50) expect(w.state.battles[w.state.battles.length - 1].winner).toBe('enemy') }) it('raid 后关系变化方向正确', () => { const w = baseWorld('cb-f') const npc = w.state.npcFamilies[anyNpc(w, ['n-nulei'])] npc.relation = -60 const npcId = anyNpc(w, ['n-nulei']) const pre = npc.power const team = [w.state.members['x4']] team[0].realm = { major: 'core', minor: 0 } const res = resolveRaid(w, npcId, team) if (res.win) { expect(npc.relation).toBeGreaterThan(-60) } else { expect(npc.relation).toBeLessThan(-60) expect(w.state.family.stones).toBeLessThanOrEqual(1000) } void pre }) it('rollWarbooty 概率性产物写入库存或功法阁', () => { const w = baseWorld('cb-g') const loot = rollWarbooty(w, { resources: { lingcao: [5, 5] }, artifactChance: 1, techniqueChance: 1 }) expect(loot.lingcao).toBe(5) const arti = Object.keys(loot).find((k) => k.startsWith('weapon')) expect(arti).toBeTruthy() expect(w.state.family.techniques.length).toBeGreaterThan(2) }) it('战斗人数攻击力线性叠加(阵型抖动内保持单调)', () => { const w = baseWorld('cb-h') const a = w.state.members['x3'] const b = w.state.members['x4'] const solo = combatPowerOf(w, a) const duo = combatPowerOf(w, a) + combatPowerOf(w, b) expect(duo).toBeGreaterThan(solo) }) }) describe('missions 探索任务', () => { it('派遣属性校验:人数不足/重复派遣/并发上限', () => { const w = baseWorld('ms-a') expect(sendMission(w, 'm-guzhan', ['x1'])).toBe(false) // 不足 min 2 expect(sendMission(w, 'm-guzhan', ['x3', 'x4'])).toBe(true) expect(sendMission(w, 'm-guzhan', ['x4', 'x5'])).toBe(false) // x4 已在途 expect(sendMission(w, 'm-anmoku', ['x4'])).toBe(false) // 在职者二次征召被拒 }) it('队伍出发后成员状态为出探,无法再被派', () => { const w = baseWorld('ms-b') sendMission(w, 'm-anmoku', ['x3']) expect(w.state.members['x3'].state).toBe('expedition') expect(sendMission(w, 'm-anmoku', ['x3', 'x5'])).toBe(false) }) it('任务按期推进并最终完成,战利入库且状态归位', () => { const w = baseWorld('ms-c') const c = w.state.members['x5'] c.realm = { major: 'qi', minor: 5 } // 保证能打 const squad = [c, w.state.members['x3']] sendMission(w, 'm-anmoku', squad.map((x) => x.id)) const m = w.state.missions[0] let guard = 0 while (!m.done && guard < 40) { if (w.state.gameOver) break w.advanceMonth() guard++ } expect(m.done).toBe(true) expect(['success', 'retreat', 'disband']).toContain(m.result) // 若成功则状态归位闲居 if (m.result === 'success') { expect(w.state.members['x3'].state).not.toBe('expedition') expect(w.state.members['x5'].state).not.toBe('expedition') } }) it('召回立刻释放队员', () => { const w = baseWorld('ms-d') sendMission(w, 'm-anmoku', ['x3']) const m = w.state.missions[0] recallAll(w, m.id) expect(m.done).toBe(true) expect(w.state.members['x3'].state).toBe('idle') }) it('全员告急:队员尽亡任务仍完成不挂死(保护)', () => { const w = baseWorld('ms-e') sendMission(w, 'm-anmoku', ['x3']) const m = w.state.missions[0] w.state.members['x3'].alive = false w.state.members['x3'].deathYear = w.state.year w.advanceMonth() expect(m.done).toBe(true) }) it('秘境阶段索引不会越界', () => { const w = baseWorld('ms-f') const def = missionById('m-anmoku') const c = w.state.members['x5'] c.realm = { major: 'foundation', minor: 0 } sendMission(w, 'm-anmoku', [c.id]) const m = w.state.missions[0] let guard = 0 while (!m.done && guard < 60) { w.advanceMonth() guard++ if (m.stage >= def.stages.length + 1 && !m.done) { // 不允许无限加厚 break } } expect(m.stage).toBeLessThanOrEqual(def.stages.length) }) })