结构(旧 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
124 lines
4.9 KiB
TypeScript
124 lines
4.9 KiB
TypeScript
import type { World } from '../World'
|
|
import { Character } from '../../../types/domain'
|
|
import { produceOffspring } from './cultivation'
|
|
import { yearGrowth } from './diplomacy'
|
|
import { MALE_GIVEN, FEMALE_GIVEN } from '../../kernel/names'
|
|
import { aspirationById } from '../../../data/aspirations'
|
|
|
|
export function yearStartMarriage(w: World): void {
|
|
yearGrowth(w)
|
|
const s = w.state
|
|
const fam = s.family
|
|
const zongci = fam.buildings['zongci'] ?? 0
|
|
const birthBase = 0.6 + zongci * 0.04 + (fam.difficulty === 'easy' ? 0.06 : fam.difficulty === 'hard' ? -0.06 : 0)
|
|
|
|
const couples = buildCouples(w)
|
|
|
|
// 族内夫妇
|
|
for (const couple of couples.internal) {
|
|
const [a, b] = couple
|
|
const father = a.gender === 'male' ? a : b
|
|
const mother = a.gender === 'male' ? b : a
|
|
const fatherAge = w.ageOf(father)
|
|
const motherAge = w.ageOf(mother)
|
|
if (fatherAge < 18 || fatherAge > 52 || motherAge < 16 || motherAge > 46) continue
|
|
const offspringBonus = Object.values(s.members)
|
|
.filter((m) => m.alive && aspirationById(m.aspiration)?.effect.type === 'offspring')
|
|
.length * 0.05
|
|
const p = birthBase * (0.75 + mother.physique * 0.05) + offspringBonus
|
|
if (!w.rng.chance(p)) continue
|
|
const gen = Math.max(father.generation, mother.generation) + 1
|
|
const first = produceOffspring(w, { father, mother, generation: gen, bornYear: s.year, surname: fam.surname })
|
|
w.addMember(first, father, mother)
|
|
let note = `${fam.surname}氏新增一员,名唤${first.name},生年 ${s.year}。`
|
|
if (w.rng.chance(0.06)) {
|
|
const twin = produceOffspring(w, { father, mother, generation: gen, bornYear: s.year, surname: fam.surname })
|
|
w.addMember(twin, father, mother)
|
|
w.state.yearStats.births += 1
|
|
note += ` 双生之喜!双子名唤${twin.name}。`
|
|
}
|
|
w.chronicle('birth', note, first.id, true)
|
|
w.state.yearStats.births += 1
|
|
w.log('good', `${fam.surname}家诞下新丁:${first.name}。`)
|
|
}
|
|
|
|
// 联姻外孙来投(男方娶亲:妻室携子来归;或女儿携子回门)
|
|
for (const member of Object.values(s.members)) {
|
|
if (!member.alive || !member.spouseHouse) continue
|
|
const age = w.ageOf(member)
|
|
if (age < 18 || age > 44) continue
|
|
if (!w.rng.chance(0.15)) continue
|
|
const gen = member.generation + 1
|
|
const house = member.spouseHouse
|
|
const child = produceOffspring(w, { father: null, mother: null, generation: gen, bornYear: s.year, surname: fam.surname })
|
|
child.spouseHouse = house
|
|
w.addMember(child)
|
|
w.chronicle('birth', `${member.name}自${house}携幼子归庄,名唤${child.name}。`, child.id, true)
|
|
w.state.yearStats.births += 1
|
|
w.log('info', `${member.name} 领着小辈回门投亲。`)
|
|
}
|
|
|
|
// 媒人撮合族内婚(含续弦与再醮)
|
|
const isWidowed = (c: Character): boolean => {
|
|
if (!c.spouseId) return false
|
|
const sp = w.state.members[c.spouseId]
|
|
return !!sp && !sp.alive
|
|
}
|
|
const men = w
|
|
.aliveMembers()
|
|
.filter((c) => c.gender === 'male' && c.state !== 'expedition' && (isWidowed(c) || !c.spouseId))
|
|
.filter((c) => {
|
|
const age = w.ageOf(c)
|
|
return age >= 18 && age <= 48
|
|
})
|
|
const women = w
|
|
.aliveMembers()
|
|
.filter((c) => c.gender === 'female' && c.state !== 'expedition' && (isWidowed(c) || !c.spouseId))
|
|
.filter((c) => {
|
|
const age = w.ageOf(c)
|
|
return age >= 16 && age <= 42
|
|
})
|
|
for (const m of men) {
|
|
if (w.rng.chance(0.55) && women.length > 0) {
|
|
const candidate = women.filter(
|
|
(x) =>
|
|
!(x.fatherId && x.fatherId === m.fatherId) &&
|
|
!(x.motherId && x.motherId === m.motherId) &&
|
|
x !== m &&
|
|
!x.children.includes(m.id) &&
|
|
!m.children.includes(x.id)
|
|
)
|
|
if (candidate.length === 0) continue
|
|
const bride = w.rng.pick(candidate)
|
|
m.spouseId = bride.id
|
|
bride.spouseId = m.id
|
|
w.chronicle('marriage', `${m.name}与${bride.name}缔结连理。`, m.id, true)
|
|
w.log('info', `${m.name} 与 ${bride.name} 成婚。`)
|
|
const idx = women.indexOf(bride)
|
|
if (idx >= 0) women.splice(idx, 1)
|
|
}
|
|
}
|
|
|
|
fam.generation = Math.max(
|
|
fam.generation,
|
|
...Object.values(s.members).filter((c) => c.alive).map((c) => c.generation)
|
|
)
|
|
}
|
|
|
|
function buildCouples(w: World): { internal: [Character, Character][]; cross: Character[] } {
|
|
const internal: [Character, Character][] = []
|
|
const cross: Character[] = []
|
|
for (const member of Object.values(w.state.members)) {
|
|
if (!member.alive || !member.spouseId) continue
|
|
const spouse = w.state.members[member.spouseId]
|
|
if (!spouse?.alive) continue
|
|
const key = [member.id, spouse.id].sort().join('|')
|
|
if (internal.some(([a, b]) => [a.id, b.id].sort().join('|') === key)) continue
|
|
internal.push([member, spouse])
|
|
}
|
|
for (const member of Object.values(w.state.members)) {
|
|
if (member.alive && member.spouseHouse) cross.push(member)
|
|
}
|
|
return { internal, cross }
|
|
}
|