refactor(0.1.14-P1): 内核归位——game/ 按引擎架构重排(零行为漂移)
结构(旧 game/core+engine → 新 engine/ 域): - engine/kernel/ 时钟/随机/插件协议/fxqueue/timesense/format/urgency/guide/names(原 core) - engine/narrative/ legacy/报告/列传/谱系/年轴(原 core 叙事族) - engine/runtime/ World/creation/pcgen/ApiFacade/capabilities/pluginManager/boot/clocks + Systems/*(12 系统) - engine/sim/ Market(未来 WorldSim 同行) - 旧 game/core、engine/systems、engine/world.ts 等路径全部废弃(无 re-export 兼容层) 验证:35 套件/967 测试全绿(金钟罩三档零漂移=纯搬迁无行为变化) typecheck 0 error
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
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, number>): string {
|
||||
const names: Record<string, string> = {
|
||||
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'
|
||||
})
|
||||
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}人手不足,队伍提前收队。`)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user