v0.1.4: 百年定局(终局/大比/寄读/春秋原)
- 望气定鼎:stats 追踪四维(人兴/道兴/威名/香火)+ 十二称号分档 + 铭文诗 春秋原页预演称号,落印定局(青册留档,不阻继续游历) - 太虚大比:每五年征召,点将三关(初试/拔擢/问鼎)复用战斗结算, 名次四档红利(声望/灵石/全场好感),史书战报自动落笔 - 宗门寄读:随机宗门收徒(适龄少年),寄读 2-4 年期间每季寄资源, 期满三选:归来精进(可触发突破+随机机运)或续读深造;低概率择师不回 - 求经台:藏书阁≥3 解锁,两年一访得稀有功法 - 春秋原复盘页:气数四维图/岁末族簿汇编/生卒与突破年表/大比年表 - 修复:大比征召曾因月份条件错过整年;抢在百年庆典前的优先级倒置 - 测试 161→176(评分边界/称号分档/大比三轨/寄读全链/求经冷却)
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import { GameState } from '../types/domain'
|
||||
import { MAJOR_ORDER } from '../data/realms'
|
||||
|
||||
export interface LegacyDims {
|
||||
renXing: number
|
||||
daoXing: number
|
||||
weiMing: number
|
||||
xiangHuo: number
|
||||
total: number
|
||||
}
|
||||
|
||||
export interface LegacyArch {
|
||||
title: string
|
||||
poem: string[]
|
||||
dims: LegacyDims
|
||||
rank: number
|
||||
verdict: string
|
||||
}
|
||||
|
||||
export const VERDICTS: Record<string, { title: string; poem: string[] }> = {
|
||||
feisheng: {
|
||||
title: '飞升之资',
|
||||
poem: ['海内升龙真气象,仙班亦录旧香名。', '遥看青山埋骨处,一脉烟霞送飞鸿。']
|
||||
},
|
||||
celestial: {
|
||||
title: '仙朝遗脉',
|
||||
poem: ['祖庭灯火三千里,曾照中天玉斗垂。', '后世儿孙开卷处,犹闻钟鼎旧清音。']
|
||||
},
|
||||
noble: {
|
||||
title: '中州望族',
|
||||
poem: ['钟鸣鼎食三百年,莫道朱门无圣贤。', '一炷心香传世绪,青灯犹自照芸编。']
|
||||
},
|
||||
warlord: {
|
||||
title: '一方枭雄',
|
||||
poem: ['剑气纵横镇八荒,家声不堕尽金汤。', '而今若问兴亡事,半壁江山问晚霜。']
|
||||
},
|
||||
recluse: {
|
||||
title: '隐世大族',
|
||||
poem: ['不向尘沙争利名,青山深处课桑耕。', '子孙自有莲舟意,一棹烟波到洞庭。']
|
||||
},
|
||||
modest: {
|
||||
title: '积余之家',
|
||||
poem: ['檐下燕飞春复秋,园蔬新雨补梁楸。', '粗茶淡饭家声在,犹胜浮云逐海流。']
|
||||
},
|
||||
dust: {
|
||||
title: '冢中枯骨',
|
||||
poem: ['一度花开一度尘,纸灰飞作旧人魂。', '若教重理当年事,且把残篇问故园。']
|
||||
}
|
||||
}
|
||||
|
||||
const RANK_MAP: [number, string][] = [
|
||||
[160, 'celestial'],
|
||||
[120, 'noble'],
|
||||
[85, 'warlord'],
|
||||
[55, 'recluse'],
|
||||
[30, 'modest']
|
||||
]
|
||||
|
||||
export function computeLegacy(s: GameState): LegacyDims {
|
||||
const st = s.stats
|
||||
const gens = Math.max(
|
||||
...Object.values(s.members).map((c) => c.generation).concat(1)
|
||||
)
|
||||
const renXing = Math.round(gens * 18 + Math.min(st.popPeak, 40) * 1.6)
|
||||
const daoXing = Math.round(st.maxRealmIdx * 26 + st.techniqueGrand * 4)
|
||||
const weiMing = Math.round(st.repPeak * 1.4 + (st.tourneyBest ? (6 - st.tourneyBest) * 12 : 0))
|
||||
const xiangHuo = Math.round(Math.min(s.year, 300) * 0.9 + st.feishengCount * 60)
|
||||
return { renXing, daoXing, weiMing, xiangHuo, total: renXing + daoXing + weiMing + xiangHuo }
|
||||
}
|
||||
|
||||
export function resolveLegacy(s: GameState): LegacyArch {
|
||||
const dims = computeLegacy(s)
|
||||
let key = 'dust'
|
||||
if (s.stats.feishengCount > 0) key = 'feisheng'
|
||||
else {
|
||||
for (const [need, k] of RANK_MAP) {
|
||||
if (dims.total >= need) {
|
||||
key = k
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
const verdict = VERDICTS[key]
|
||||
return { title: verdict.title, poem: verdict.poem, dims, rank: dims.total, verdict: key }
|
||||
}
|
||||
|
||||
export function peakRealmIndex(members: GameState['members']): number {
|
||||
let max = -1
|
||||
for (const c of Object.values(members)) {
|
||||
const idx = MAJOR_ORDER.indexOf(c.realm.major) * 10 + c.realm.minor
|
||||
if (idx > max) max = idx
|
||||
}
|
||||
return max
|
||||
}
|
||||
|
||||
export function grandTechniqueCount(members: GameState['members']): number {
|
||||
let n = 0
|
||||
for (const c of Object.values(members)) {
|
||||
if ((c.techniqueRank ?? 0) >= 2) n++
|
||||
}
|
||||
return n
|
||||
}
|
||||
@@ -41,6 +41,8 @@ export interface EffectDef {
|
||||
artifactChance?: number
|
||||
addTech?: string
|
||||
feisheng?: { stay: boolean }
|
||||
tournament?: boolean
|
||||
apprentice?: { build: boolean }
|
||||
}
|
||||
|
||||
export interface EventOptionDef {
|
||||
|
||||
@@ -76,7 +76,15 @@ export function createWorldState(opts: NewGameOptions): GameState {
|
||||
totalTicks: 0,
|
||||
finance: { accum: 0 },
|
||||
yearStats: { births: 0, deaths: 0 },
|
||||
yearlyReports: []
|
||||
yearlyReports: [],
|
||||
stats: {
|
||||
repPeak: 5,
|
||||
popPeak: 5,
|
||||
maxRealmIdx: 25,
|
||||
techniqueGrand: 0,
|
||||
tourneyHistory: [],
|
||||
feishengCount: 0
|
||||
}
|
||||
}
|
||||
|
||||
const w = new World(state, [])
|
||||
|
||||
@@ -49,7 +49,7 @@ export function monthlyRate(w: World, c: Character): number {
|
||||
|
||||
export function cultivationTick(w: World): void {
|
||||
for (const c of Object.values(w.state.members)) {
|
||||
if (!c.alive) continue
|
||||
if (!c.alive || c.state === 'apprentice') continue
|
||||
const rate = monthlyRate(w, c)
|
||||
if (rate <= 0) continue
|
||||
c.realmProgress = Math.min(100, c.realmProgress + rate)
|
||||
|
||||
@@ -11,11 +11,11 @@ import { findInheritor } from '../creation'
|
||||
|
||||
const ALL_EVENTS: EventDef[] = [...EVENTS]
|
||||
|
||||
export function findEvent(id: string): EventDef | undefined {
|
||||
return ALL_EVENTS.find((e) => e.id === id) ?? dynamicEventFor(id)
|
||||
export function findEvent(id: string, world?: World): EventDef | undefined {
|
||||
return ALL_EVENTS.find((e) => e.id === id) ?? dynamicEventFor(id, world)
|
||||
}
|
||||
|
||||
export function dynamicEventFor(id: string): EventDef | undefined {
|
||||
export function dynamicEventFor(id: string, w?: World): EventDef | undefined {
|
||||
if (id.startsWith('ev-raid-')) {
|
||||
const npcId = id.replace('ev-raid-', '')
|
||||
const npc = npcById(npcId)
|
||||
@@ -47,6 +47,52 @@ export function dynamicEventFor(id: string): EventDef | undefined {
|
||||
]
|
||||
}
|
||||
}
|
||||
if (id.startsWith('ev-tournament-')) {
|
||||
const year = Number(id.replace('ev-tournament-', ''))
|
||||
return {
|
||||
id,
|
||||
name: '太虚大比',
|
||||
category: 'major',
|
||||
weight: 0,
|
||||
text: `太虚仙盟十年一会,新一期大比于祖庭开擂。观礼之众云集,百族竞锋——可遣嫡系赴赛,亦可称病让席。`,
|
||||
options: [
|
||||
{ label: '点将赴赛', hint: '战三关,位次定声望', eff: { tournament: true, flag: { [`tournamentYear-${year}`]: true } } },
|
||||
{ label: '献礼买名', hint: '灵石-200,名次垫底,各族好感略升', eff: { res: { stones: -200 }, relation: { 'n-xuanying': 5, 'n-danxin': 5, 'n-sihai': 5, 'n-nulei': 5 } } },
|
||||
{ label: '称病不出', hint: '声望小幅受挫', eff: { rep: -3 } }
|
||||
]
|
||||
}
|
||||
}
|
||||
if (id.startsWith('ev-apprentice-')) {
|
||||
const rest = id.replace('ev-apprentice-', '')
|
||||
const memberId = rest.split('-')[0]
|
||||
const member = w?.state.members[memberId] ?? undefined
|
||||
const sectName = member?.apprentice?.sect ?? '师门'
|
||||
const name = member?.name ?? '弟子'
|
||||
return {
|
||||
id,
|
||||
name: '寄读还乡',
|
||||
category: 'major',
|
||||
weight: 0,
|
||||
text: `${name} 在${sectName}寄读期满。宗门遣人传书:或归家,或续练。`,
|
||||
options: [
|
||||
{ label: '接其归家', hint: '其悟道大进,或携功法而归', eff: { flag: { apprenticeRet: memberId } } },
|
||||
{ label: '令其再拜师门', hint: '寄读二年,资源更厚', eff: { flag: { apprenticeStay: memberId } } }
|
||||
]
|
||||
}
|
||||
}
|
||||
if (id === 'ev-recruit') {
|
||||
return {
|
||||
id,
|
||||
name: '宗门收徒',
|
||||
category: 'major',
|
||||
weight: 0,
|
||||
text: '远方宗门遣师来访,观族中少年灵根不俗,欲收为记名弟子寄读。',
|
||||
options: [
|
||||
{ label: '送子寄读', hint: '其人外派,归时精进', eff: { apprentice: { build: true } } },
|
||||
{ label: '婉拒', hint: '无', eff: {} }
|
||||
]
|
||||
}
|
||||
}
|
||||
if (id === 'ev-feisheng') {
|
||||
return {
|
||||
id,
|
||||
@@ -64,6 +110,23 @@ export function dynamicEventFor(id: string): EventDef | undefined {
|
||||
return undefined
|
||||
}
|
||||
|
||||
function asFlagString(flag: Record<string, number | boolean | string> | undefined, key: string): string | null {
|
||||
if (!flag) return null
|
||||
const v = flag[key]
|
||||
return typeof v === 'string' ? v : null
|
||||
}
|
||||
|
||||
import {
|
||||
runTournament as _runTournament,
|
||||
buildApprentice as _buildApprentice,
|
||||
finalizeApprentice as _finalizeApprentice,
|
||||
checkApprenticeExpiry,
|
||||
recruitCheck
|
||||
} from './tournament'
|
||||
export const runTournament = _runTournament
|
||||
export const buildApprentice = _buildApprentice
|
||||
export const finalizeApprentice = _finalizeApprentice
|
||||
|
||||
export function matchesCond(w: World, cond?: Cond): boolean {
|
||||
if (!cond) return true
|
||||
const s = w.state
|
||||
@@ -112,7 +175,7 @@ export function eventRoll(w: World): void {
|
||||
return
|
||||
}
|
||||
|
||||
// 里程碑检测(优先于日常事件)
|
||||
// 命运里程碑最优先(百年庆典 > 飞升之路)
|
||||
if (s.year >= 100 && !s.completedEvents.includes('ev-centennial')) {
|
||||
fire(w, 'ev-centennial')
|
||||
return
|
||||
@@ -122,6 +185,15 @@ export function eventRoll(w: World): void {
|
||||
fire(w, 'ev-feisheng')
|
||||
return
|
||||
}
|
||||
checkApprenticeExpiry(w)
|
||||
recruitCheck(w)
|
||||
|
||||
// 五年一会的太虚大比(该年任一时刻)
|
||||
const tourneyNext = Math.ceil(s.year / 5) * 5
|
||||
if (s.year >= 10 && s.year === tourneyNext && !s.completedEvents.includes(`ev-tournament-${tourneyNext}`)) {
|
||||
fire(w, `ev-tournament-${tourneyNext}`)
|
||||
return
|
||||
}
|
||||
|
||||
const roll = w.rng.next()
|
||||
const category: 'daily' | 'major' | 'fate' | undefined = roll < 0.5 ? 'daily' : roll < 0.78 ? 'major' : roll < 0.86 ? 'fate' : undefined
|
||||
@@ -151,7 +223,7 @@ export function fire(w: World, id: string): void {
|
||||
|
||||
export function applyEventChoice(w: World, eventId: string, optionIdx: number, squad?: string[]): void {
|
||||
const s = w.state
|
||||
const def = findEvent(eventId)
|
||||
const def = findEvent(eventId, w)
|
||||
if (!def) {
|
||||
s.pendingEvent = undefined
|
||||
return
|
||||
@@ -248,6 +320,22 @@ function applyEffect(w: World, eff: EffectDef, squad?: string[]): void {
|
||||
if (eff.addTech) {
|
||||
if (!fam.techniques.includes(eff.addTech)) fam.techniques.push(eff.addTech)
|
||||
}
|
||||
if (eff.tournament) {
|
||||
runTournament(w)
|
||||
}
|
||||
if (eff.apprentice?.build) {
|
||||
buildApprentice(w)
|
||||
}
|
||||
const retId = asFlagString(eff.flag, 'apprenticeRet')
|
||||
const stayId = asFlagString(eff.flag, 'apprenticeStay')
|
||||
if (retId) {
|
||||
finalizeApprentice(w, retId, 'return')
|
||||
delete fam.flag['apprenticeRet']
|
||||
}
|
||||
if (stayId) {
|
||||
finalizeApprentice(w, stayId, 'stay')
|
||||
delete fam.flag['apprenticeStay']
|
||||
}
|
||||
if (eff.feisheng) {
|
||||
const s2 = w.state
|
||||
const immortal = w
|
||||
|
||||
@@ -112,7 +112,7 @@ export function canSendMission(w: World, def: MissionDef, members: string[]): bo
|
||||
if (members.length < def.minMembers || members.length > def.maxMembers) return false
|
||||
for (const id of members) {
|
||||
const c = w.memberById(id)
|
||||
if (!c.alive || c.state === 'expedition') return false
|
||||
if (!c.alive || c.state === 'expedition' || c.state === 'apprentice') return false
|
||||
}
|
||||
return w.state.missions.filter((m) => !m.done).length < 3
|
||||
}
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
import { World } from '../world'
|
||||
import { Character } from '../../types/domain'
|
||||
import { ENEMIES, EnemyDef } from '../../data/secrets'
|
||||
import { resolveEncounter } from './combat'
|
||||
import { rankPower } from './events'
|
||||
import { resolveBreakthrough } from './cultivation'
|
||||
|
||||
export const SECTS = ['云栖宗', '太谷书院', '玄微剑阁', '丹霞洞天']
|
||||
|
||||
const GATE_ENEMIES: string[] = ['e-muche', 'e-huanhan', 'e-yeshen']
|
||||
|
||||
export function gateEnemy(idx: number, year: number): EnemyDef {
|
||||
const enemies = ENEMIES.filter((e) => ['qi', 'foundation', 'core', 'nascent'].includes(e.realm))
|
||||
const pick = enemies[(idx + Math.floor(year / 5)) % enemies.length] ?? enemies[0]
|
||||
return {
|
||||
...pick,
|
||||
name: idx === 2 ? `问鼎擂主·${pick.name}` : `第${idx + 1}关·${pick.name}`,
|
||||
strength: pick.strength * (0.85 + idx * 0.35) * (1 + Math.floor(year / 50) * 0.3)
|
||||
}
|
||||
}
|
||||
|
||||
export function runTournament(w: World, squad?: string[]): void {
|
||||
const s = w.state
|
||||
const eligible = w
|
||||
.aliveMembers()
|
||||
.filter((c) => w.ageOf(c) >= 16 && (c.state === 'idle' || c.state === 'meditation') && c.realm.major !== 'mortal')
|
||||
.sort((a, b) => rankPower(w, b) - rankPower(w, a))
|
||||
let team: Character[]
|
||||
if (squad && squad.length > 0) {
|
||||
team = squad.map((id) => w.memberById(id)).filter((c) => c.alive && c.state !== 'expedition' && w.ageOf(c) >= 16)
|
||||
} else {
|
||||
team = eligible.slice(0, 3)
|
||||
}
|
||||
if (team.length === 0) {
|
||||
w.log('bad', '太虚大比:族中无一嫡系可遣,只得告假缺席。')
|
||||
return
|
||||
}
|
||||
|
||||
let wins = 0
|
||||
for (let g = 0; g < 3; g++) {
|
||||
// 中途全灭/重伤离场则提前止步
|
||||
const aliveTeam = team.filter((c) => c.alive)
|
||||
if (aliveTeam.length === 0) break
|
||||
const res = resolveEncounter(w, {
|
||||
title: `太虚大比·第${g + 1}关`,
|
||||
enemy: gateEnemy(g, s.year),
|
||||
risk: 0.6,
|
||||
team: aliveTeam,
|
||||
kind: 'scout',
|
||||
year: s.year,
|
||||
month: s.month
|
||||
})
|
||||
if (!res.win) break
|
||||
wins++
|
||||
}
|
||||
|
||||
const rank = Math.max(1, 4 - wins)
|
||||
const rewards = [
|
||||
{ rep: 18, stones: 300, rel: 8 },
|
||||
{ rep: 12, stones: 200, rel: 5 },
|
||||
{ rep: 8, stones: 120, rel: 3 },
|
||||
{ rep: 4, stones: 60, rel: 1 }
|
||||
][rank - 1]!
|
||||
|
||||
s.family.reputation += rewards.rep
|
||||
s.family.stones += rewards.stones
|
||||
for (const npc of Object.values(s.npcFamilies)) {
|
||||
npc.relation = Math.min(100, npc.relation + rewards.rel)
|
||||
}
|
||||
s.stats.tourneyHistory.push({ year: s.year, rank })
|
||||
if (!s.stats.tourneyBest || rank < s.stats.tourneyBest) s.stats.tourneyBest = rank
|
||||
|
||||
const teamNames = team.map((c) => c.name).join('、')
|
||||
w.chronicle('battle', `太虚大比:${teamNames} 最终位列第${rank}名,赏灵石${rewards.stones}、声望+${rewards.rep}。`, undefined, true)
|
||||
w.log('good', `太虚大比落下帷幕,本族第${rank}名。`)
|
||||
}
|
||||
|
||||
export function apprenticeCandidates(w: World): Character[] {
|
||||
return w
|
||||
.aliveMembers()
|
||||
.filter((c) => w.ageOf(c) >= 12 && w.ageOf(c) <= 19 && c.state === 'idle')
|
||||
.sort((a, b) => b.perception - a.perception)
|
||||
}
|
||||
|
||||
export function buildApprentice(w: World): void {
|
||||
const cand = apprenticeCandidates(w)
|
||||
const c = w.rng.pick(cand)
|
||||
if (!c) {
|
||||
w.log('bad', '宗门来使失望而归:族中无适龄儿郎。')
|
||||
return
|
||||
}
|
||||
const sect = w.rng.pick(SECTS)
|
||||
const years = w.rng.int(2, 4)
|
||||
c.state = 'apprentice'
|
||||
c.apprentice = { sect, untilYear: w.state.year + years, quiet: false }
|
||||
w.state.family.stones += 60
|
||||
w.chronicle('event', `${c.name} 拜入${sect}门下寄读${years}载,宗门赠礼灵石六十。`, c.id, true)
|
||||
w.log('info', `${c.name} 离家赴${sect}修学。`)
|
||||
}
|
||||
|
||||
export function finalizeApprentice(w: World, memberId: string, mode: 'return' | 'stay'): void {
|
||||
const c = w.memberById(memberId)
|
||||
if (!c.apprentice) return
|
||||
if (mode === 'stay') {
|
||||
c.apprentice.untilYear = Math.max(c.apprentice.untilYear, w.state.year + 2)
|
||||
c.apprentice.quiet = false
|
||||
w.state.family.stones += 40
|
||||
w.log('info', `${c.name} 择师深造二年,族中资其膏火。`)
|
||||
w.chronicle('event', `${c.name} 续入${c.apprentice.sect}修习,寄回灵石四十。`, c.id, false)
|
||||
return
|
||||
}
|
||||
// 归来:境界推进+随机机遇
|
||||
const sect = c.apprentice.sect
|
||||
c.state = 'idle'
|
||||
c.apprentice = undefined
|
||||
const boost = w.rng.chance(0.75)
|
||||
c.realmProgress = Math.min(100, c.realmProgress + 60)
|
||||
if (boost && c.realmProgress >= 80) {
|
||||
c.realmProgress = 100
|
||||
resolveBreakthrough(w, c, 0.35)
|
||||
}
|
||||
c.fortune = Math.min(12, c.fortune + 1)
|
||||
w.chronicle('event', `${c.name} 自${sect}学成归家,携带新得与见识归来。`, c.id, true)
|
||||
w.log('good', `${c.name} 自${sect}归来,境界精进。`)
|
||||
}
|
||||
|
||||
export function checkApprenticeExpiry(w: World): void {
|
||||
for (const c of Object.values(w.state.members)) {
|
||||
if (!c.alive || !c.apprentice || c.apprentice.quiet) continue
|
||||
if (w.state.year >= c.apprentice.untilYear) {
|
||||
const evId = `ev-apprentice-${c.id}-${c.apprentice.untilYear}`
|
||||
if (!w.state.completedEvents.includes(evId)) {
|
||||
w.state.eventQueue.push(evId)
|
||||
w.state.completedEvents.push(evId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function recruitCheck(w: World): void {
|
||||
const s = w.state
|
||||
if (s.year % 4 !== 0) return
|
||||
const key = `recruitDone-${s.year}`
|
||||
if (s.family.flag[key]) return
|
||||
if (apprenticeCandidates(w).length === 0) return
|
||||
if (w.rng.chance(0.5)) {
|
||||
s.family.flag[key] = true
|
||||
s.eventQueue.push('ev-recruit')
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
import { Rng } from '../core/rng'
|
||||
import { BUILDINGS } from '../data/buildings'
|
||||
import { POSTS } from '../data/posts'
|
||||
import { TECHNIQUES } from '../data/techniques'
|
||||
import { computeLegacy, resolveLegacy, peakRealmIndex, grandTechniqueCount, LegacyArch } from '../core/legacy'
|
||||
import { productionTick } from './systems/production'
|
||||
import { deathTick, woundHealTick } from './systems/lifecycle'
|
||||
import { cultivationTick, resolveBreakthrough } from './systems/cultivation'
|
||||
@@ -37,6 +39,16 @@ export function normalizeGameState(state: GameState): GameState {
|
||||
if (!state.finance) state.finance = { accum: 0 }
|
||||
if (!state.yearStats) state.yearStats = { births: 0, deaths: 0 }
|
||||
if (!state.yearlyReports) state.yearlyReports = []
|
||||
if (!state.stats) {
|
||||
state.stats = {
|
||||
repPeak: state.family?.reputation ?? 0,
|
||||
popPeak: Object.values(state.members).filter((c) => c.alive).length,
|
||||
maxRealmIdx: peakRealmIndex(state.members ?? {}),
|
||||
techniqueGrand: grandTechniqueCount(state.members ?? {}),
|
||||
tourneyHistory: [],
|
||||
feishengCount: 0
|
||||
}
|
||||
}
|
||||
if (typeof state.totalTicks !== 'number') state.totalTicks = 0
|
||||
if (typeof state.seq !== 'number') state.seq = 10
|
||||
if (!state.battles) state.battles = []
|
||||
@@ -144,6 +156,33 @@ export class World {
|
||||
this.checkHead()
|
||||
const stonesEnd = s.family.stones
|
||||
s.finance.accum += stonesEnd - stonesStart
|
||||
this.trackStats()
|
||||
}
|
||||
|
||||
private trackStats(): void {
|
||||
const s = this.state
|
||||
const st = s.stats
|
||||
if (s.family.reputation > st.repPeak) st.repPeak = s.family.reputation
|
||||
const pop = this.aliveMembers().length
|
||||
if (pop > st.popPeak) st.popPeak = pop
|
||||
const idx = peakRealmIndex(s.members)
|
||||
if (idx > st.maxRealmIdx) st.maxRealmIdx = idx
|
||||
const g = grandTechniqueCount(s.members)
|
||||
if (g > st.techniqueGrand) st.techniqueGrand = g
|
||||
}
|
||||
|
||||
legacyPreview() {
|
||||
return computeLegacy(this.state)
|
||||
}
|
||||
|
||||
resolveLegacyNow(): LegacyArch {
|
||||
const arch = resolveLegacy(this.state)
|
||||
this.state.stats.resolvedYear = this.state.year
|
||||
this.state.stats.resolveTitle = arch.title
|
||||
this.state.family.flag['resolved'] = true
|
||||
this.chronicle('event', `望气观澜,本族百年气数终有定论——「${arch.title}」。开卷盖印,史入青册。`, undefined, true)
|
||||
this.log('good', `定鼎:${arch.title}。`)
|
||||
return arch
|
||||
}
|
||||
|
||||
private yearStart(): void {
|
||||
@@ -343,12 +382,12 @@ export class World {
|
||||
}
|
||||
|
||||
postCount(def: string): number {
|
||||
return this.aliveMembers().filter((c) => c.post === def).length
|
||||
return this.aliveMembers().filter((c) => c.post === def && c.state !== 'apprentice').length
|
||||
}
|
||||
|
||||
assignPost(memberId: Id, postId: string | undefined): boolean {
|
||||
const c = this.memberById(memberId)
|
||||
if (!c.alive) return false
|
||||
if (!c.alive || c.state === 'apprentice') return false
|
||||
if (postId === undefined || postId === '') {
|
||||
c.post = undefined
|
||||
return true
|
||||
@@ -402,6 +441,27 @@ export class World {
|
||||
return true
|
||||
}
|
||||
|
||||
seekSutra(): boolean {
|
||||
const fam = this.state.family
|
||||
if ((fam.buildings['cangshu'] ?? 0) < 3) return false
|
||||
const last = (fam.flag['sutraCD'] as number | undefined) ?? -999
|
||||
if (this.state.year - last < 2) return false
|
||||
if (fam.stones < 150) return false
|
||||
fam.stones -= 150
|
||||
fam.flag['sutraCD'] = this.state.year
|
||||
const pool = TECHNIQUES.filter((t) => t.grade >= 2 && !fam.techniques.includes(t.id))
|
||||
if (pool.length === 0) {
|
||||
this.log('info', '求经访道:天下典籍已入庶几,无可再得。')
|
||||
this.chronicle('event', '求经台广搜天下,经卷已穷。', undefined, false)
|
||||
return true
|
||||
}
|
||||
const t = this.rng.pick(pool)
|
||||
fam.techniques.push(t.id)
|
||||
this.chronicle('event', `遣人下江南求经,携回《${t.name}》。`, undefined, true)
|
||||
this.log('good', `求经台访得《${t.name}》!`)
|
||||
return true
|
||||
}
|
||||
|
||||
tauntNpc(npcId: string): boolean {
|
||||
const fam = this.state.family
|
||||
const npc = this.state.npcFamilies[npcId]
|
||||
@@ -425,7 +485,7 @@ export class World {
|
||||
const me = this.memberById(id)
|
||||
const meG = me.gender
|
||||
return this.aliveMembers()
|
||||
.filter((c) => c.gender !== meG && c.state !== 'expedition')
|
||||
.filter((c) => c.gender !== meG && c.state !== 'expedition' && c.state !== 'apprentice')
|
||||
.filter((c) => w2age(this, c) >= 16 && w2age(this, c) <= 46)
|
||||
.filter((c) => !c.spouseId || this.isWidowed(c))
|
||||
.filter(
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface Realm {
|
||||
minor: number
|
||||
}
|
||||
|
||||
export type CharState = 'idle' | 'meditation' | 'expedition' | 'wounded' | 'closed'
|
||||
export type CharState = 'idle' | 'meditation' | 'expedition' | 'wounded' | 'closed' | 'apprentice'
|
||||
|
||||
export interface SpiritRoots {
|
||||
grade: number
|
||||
@@ -53,6 +53,7 @@ export interface Character {
|
||||
isFounder?: boolean
|
||||
lastBreakthroughAttempt?: number
|
||||
monthProgress?: number
|
||||
apprentice?: { sect: string; untilYear: number; quiet: boolean }
|
||||
}
|
||||
|
||||
export interface FamilyState {
|
||||
@@ -133,6 +134,18 @@ export interface GameOver {
|
||||
reason: string
|
||||
}
|
||||
|
||||
export interface FamilyStats {
|
||||
repPeak: number
|
||||
popPeak: number
|
||||
maxRealmIdx: number
|
||||
techniqueGrand: number
|
||||
tourneyBest?: number
|
||||
tourneyHistory: { year: number; rank: number }[]
|
||||
feishengCount: number
|
||||
resolvedYear?: number
|
||||
resolveTitle?: string
|
||||
}
|
||||
|
||||
export interface YearlyReport {
|
||||
year: number
|
||||
nets: number
|
||||
@@ -164,6 +177,7 @@ export interface GameState {
|
||||
finance: { accum: number }
|
||||
yearStats: { births: number; deaths: number }
|
||||
yearlyReports: YearlyReport[]
|
||||
stats: FamilyStats
|
||||
}
|
||||
|
||||
export interface LogItem {
|
||||
|
||||
Reference in New Issue
Block a user