import type { World } from '../World' import { MissionState } from '../../../types/domain' import { FormationId } from '../../../data/formations' import { missionById, MissionDef, ENEMIES } from '../../../data/secrets' import { resolveEncounter, rollWarbooty } from './combat' import { techniqueById } from '../../../data/techniques' import { describeRealm } from '../../../data/realms' export function missionTick(w: World): void { const active = w.state.missions.filter((m) => !m.done) for (const m of active) { settleSquad(w, m) if (m.done) continue m.stageMonth++ if (m.stageMonth < 2) continue const def = missionById(m.defId) const stage = def.stages[m.stage] if (!stage || m.stageMonth < stage.months) continue if (stage.kind === 'event') { const good = w.rng.chance(0.6) if (good) { m.log.push(`${m.stageMonth}月:${stage.title}——${stage.text?.safe ?? '安然无事。'}`) if (w.rng.chance(0.2)) { const squad = squadOf(w, m) squad.forEach((c) => (c.realmProgress = Math.min(100, c.realmProgress + 4))) m.log.push('途中参悟,众人皆有精进。') } } else { m.log.push(`${m.stageMonth}月:${stage.title}——${stage.text?.bad ?? '遭遇凶险。'}`) const squad = squadOf(w, m) const victim = w.rng.pick(squad) victim.health = Math.max(1, victim.health - 25 - w.rng.int(0, 15)) if (victim.health < 35) victim.state = 'wounded' } } else if (stage.kind === 'resource') { const loot = rollWarbooty(w, stage.loot ?? def.completionLoot) m.log.push(`${stage.title}:收获 ${lootText(loot)}。`) } else if (stage.kind === 'combat' || stage.kind === 'boss') { const enemy = ENEMIES.find((e) => e.id === stage.enemyId) ?? ENEMIES[0] const squad = squadOf(w, m) const res = resolveEncounter(w, { title: `${def.name} · ${stage.title}`, enemy, risk: def.risk * (stage.kind === 'boss' ? 1.15 : 1), team: squad, kind: 'scout', year: w.state.year, month: w.state.month, formation: m.formation as FormationId | undefined }) const line = res.win ? '战而胜之,征程继续!' : res.draw ? '僵持之后双方罢手,队伍休整再进。' : '不敌,只得暂避锋芒。' m.log.push(line) if (!res.win) { if (stage.kind === 'boss') { m.done = true m.result = res.draw ? 'stalemate' : 'retreat' m.log.join(' ') w.chronicle('exploration', `${def.name}探路不遂,${resultText(m)}。`, undefined, false) } } } m.stage++ m.stageMonth = 0 if (m.stage >= def.stages.length && !m.done) { m.done = true m.result = 'success' releaseSquad(w, m) const total = rollWarbooty(w, def.completionLoot) m.log.push(`凯旋而归,清点战利:${lootText(total)}。`) const survivors = squadOf(w, m).filter((c) => c.alive).map((c) => c.name).join('、') w.chronicle( 'exploration', `${survivors} 圆满完成【${def.name}】之行。`, undefined, true ) w.log('good', `${def.name} 探索归来,获得丰厚收获。`) } } } function squadOf(w: World, m: MissionState) { return m.memberIds.map((id) => w.memberById(id)).filter((c) => c.alive) } function lootText(loot: Record): string { const names: Record = { lingcao: '灵草', lingkuang: '灵矿', beastcore: '兽核', stones: '灵石', 'weapon-fan': '凡器', 'weapon-qi': '法器', 'weapon-ling': '灵器', 'pill-qiyuan': '聚气丹', 'pill-ningyuan': '凝元丹', tech: '功法' } return Object.entries(loot) .map(([k, v]) => `${names[k] ?? k}×${v}`) .join('、') } function resultText(m: MissionState): string { if (m.result === 'success') return '平安返回' if (m.result === 'retreat') return '败退而回' return '铩羽归来' } export function canSendMission(w: World, def: MissionDef, members: string[]): boolean { if (members.length < def.minMembers || members.length > def.maxMembers) return false for (const id of members) { const c = w.state.members[id] if (!c || !c.alive || c.state === 'expedition' || c.state === 'apprentice') return false } return w.state.missions.filter((m) => !m.done).length < 3 } export function sendMission(w: World, defId: string, members: string[], formation?: FormationId): boolean { const def = missionById(defId) if (!canSendMission(w, def, members)) return false const m: MissionState = { id: w.seq(), defId, memberIds: members, startYear: w.state.year, startMonth: w.state.month, stage: 0, stageMonth: 0, log: [`冬衣已备,饯行酒干,众人于 ${w.state.year} 年 ${w.state.month} 月出发。`], done: false, formation } members.forEach((id) => { const c = w.memberById(id) c.state = 'expedition' }) // 世界秘境灵气消耗(有 sim 时) if (w.state.worldSim?.secretQi) { const qi = w.state.worldSim.secretQi[def.id] as number | undefined if (qi !== undefined) { w.state.worldSim.secretQi[def.id] = Math.max(0, qi - 6) } else { w.state.worldSim.secretQi[def.id] = 44 } } w.state.missions.push(m) w.state.family.missionIds.push(m.id) w.log('info', `队伍出发探索【${def.name}】。`) return true } export function recallAll(w: World, missionId: string): void { const m = w.state.missions.find((x) => x.id === missionId) if (!m || m.done) return m.done = true m.result = 'recall' releaseSquad(w, m) w.log('info', '探索队伍奉命返家。') } /** 队伍成员状态归位:伤者保持养伤,其余恢复闲居 */ function releaseSquad(w: World, m: MissionState): void { for (const id of m.memberIds) { const c = w.memberById(id) if (!c.alive) continue if (c.state === 'wounded') continue c.state = 'idle' } } /** 月度巡查:亡者除名、重伤者离队疗伤;人数不足则提前收队 */ function settleSquad(w: World, m: MissionState): void { const def = missionById(m.defId) let removed = 0 for (let i = m.memberIds.length - 1; i >= 0; i--) { const c = w.memberById(m.memberIds[i]) if (!c.alive) { m.memberIds.splice(i, 1) removed++ } else if (c.state === 'wounded' || c.health < 30) { c.state = 'wounded' m.memberIds.splice(i, 1) removed++ m.log.push(`${c.name} 身负重伤,离队回庄疗养。`) } } if (removed > 0) w.log('info', `${def.name}队中有人离队。`) if (m.memberIds.length < def.minMembers) { m.done = true m.result = 'disband' releaseSquad(w, m) w.log('bad', `${def.name}人手不足,队伍提前收队。`) } }