0.1.40: 天命系统 + NPC演化增强 + 建筑专精 (P0-1~P1-1)
This commit is contained in:
@@ -676,21 +676,82 @@ function evolveNpc(w: World, s: WorldSimState, noise: number): void {
|
||||
const cur = dyn.relationsWithOthers[oid] ?? 0
|
||||
dyn.relationsWithOthers[oid] = clamp(cur + (w.rng.chance(0.5) ? 1 : -1) * 5, -100, 100)
|
||||
}
|
||||
// 换代评估:寿终阈值 或 年首小概率(退隐/遇害)——约 50 年一代
|
||||
// 0.1.40 换代评估:四模式(退隐传贤/遇害暴毙/夺位篡权/寿终正寝)
|
||||
const chanceNow = dyn.leaderAge > lifespan * 0.9 ? 0.5 : 0.02
|
||||
if (w.rng.chance(chanceNow) && !w.rng.chance(0.25)) {
|
||||
dyn.leaderAge = 30 + w.rng.int(0, 25)
|
||||
dyn.leaderRealmIdx = Math.min(dyn.leaderRealmIdx + 1, 5)
|
||||
dyn.leaderName = `${def.name.replace('氏', '')}氏新主`
|
||||
dyn.prosperity = 55 + w.rng.int(0, 20)
|
||||
npc.power = Math.round(Math.max(40, npc.power + 15))
|
||||
dyn.lastEvent = '宗祧更替'
|
||||
const eraNow = (s.era ?? 'pingshi') as string
|
||||
const prosperityNow = dyn.prosperity ?? 50
|
||||
let mode: 'retire' | 'death' | 'usurp' | 'normal'
|
||||
if (eraNow === 'luanshi' || eraNow === 'mofa') {
|
||||
if (prosperityNow < 30 && w.rng.chance(0.4)) mode = 'death'
|
||||
else if (npc.power > 400 && w.rng.chance(0.25)) mode = 'usurp'
|
||||
else mode = 'normal'
|
||||
} else {
|
||||
if (prosperityNow > 70 && w.rng.chance(0.45)) mode = 'retire'
|
||||
else if (npc.power > 400 && w.rng.chance(0.15)) mode = 'usurp'
|
||||
else mode = 'normal'
|
||||
}
|
||||
switch (mode) {
|
||||
case 'retire': {
|
||||
// 退隐传贤:平盛世高景气——宗主功成身退,传位后辈
|
||||
dyn.leaderAge = 30 + w.rng.int(0, 20)
|
||||
dyn.leaderRealmIdx = Math.min(dyn.leaderRealmIdx + 1, 5)
|
||||
dyn.leaderName = `${def.name.replace('氏', '')}氏新主`
|
||||
dyn.prosperity = clamp(prosperityNow - 5, 8, 100)
|
||||
npc.power = Math.round(Math.max(40, npc.power - 5))
|
||||
dyn.lastEvent = '退隐传贤'
|
||||
break
|
||||
}
|
||||
case 'death': {
|
||||
// 遇害暴毙:乱世末法低景气——宗主陨落,群龙无首
|
||||
dyn.leaderAge = 25 + w.rng.int(0, 20)
|
||||
dyn.leaderRealmIdx = Math.max(0, dyn.leaderRealmIdx) // 境界不变或降
|
||||
dyn.leaderName = `${def.name.replace('氏', '')}氏少主`
|
||||
dyn.prosperity = clamp(prosperityNow - 20, 8, 100)
|
||||
npc.power = Math.round(Math.max(40, npc.power - 30))
|
||||
dyn.lastEvent = '宗主遇害'
|
||||
break
|
||||
}
|
||||
case 'usurp': {
|
||||
// 夺位篡权:高压家族——强人夺位,势力激增但人心不稳
|
||||
dyn.leaderAge = 35 + w.rng.int(0, 15)
|
||||
dyn.leaderRealmIdx = Math.min(dyn.leaderRealmIdx + (w.rng.chance(0.4) ? 2 : 1), 5)
|
||||
dyn.leaderName = `${def.name.replace('氏', '')}氏篡主`
|
||||
dyn.prosperity = clamp(prosperityNow + 8, 8, 100)
|
||||
npc.power = Math.round(Math.max(40, npc.power + 40))
|
||||
dyn.lastEvent = '夺位篡权'
|
||||
// 篡权者树敌:与邻家关系恶化
|
||||
for (const oid of ids) {
|
||||
if (oid === id) continue
|
||||
const cur = dyn.relationsWithOthers[oid] ?? 0
|
||||
dyn.relationsWithOthers[oid] = clamp(cur - 12, -100, 100)
|
||||
}
|
||||
break
|
||||
}
|
||||
default: {
|
||||
// 寿终正寝:常规换代
|
||||
dyn.leaderAge = 30 + w.rng.int(0, 25)
|
||||
dyn.leaderRealmIdx = Math.min(dyn.leaderRealmIdx + 1, 5)
|
||||
dyn.leaderName = `${def.name.replace('氏', '')}氏新主`
|
||||
dyn.prosperity = 55 + w.rng.int(0, 20)
|
||||
npc.power = Math.round(Math.max(40, npc.power + 15))
|
||||
dyn.lastEvent = '宗祧更替'
|
||||
}
|
||||
}
|
||||
dyn.lastEventYear = year
|
||||
s.npcSuccessions++
|
||||
pushNews(w, s, [id])
|
||||
w.log('info', `【天下】${def.name} 更易宗主,气象一新。`)
|
||||
const eventDesc = dyn.lastEvent === '退隐传贤' ? '宗主功成身退,后辈继位'
|
||||
: dyn.lastEvent === '宗主遇害' ? '宗主遇害身亡,少主仓促继位'
|
||||
: dyn.lastEvent === '夺位篡权' ? '强人夺位,人心浮动'
|
||||
: '更易宗主,气象一新'
|
||||
w.log('info', `【天下】${def.name} ${eventDesc}。`)
|
||||
w.emitFx('ripple', `succession:${id}`)
|
||||
}
|
||||
// 0.1.40 NPC 势力分裂:power>500 且景气<25 的家族,年首 3% 概率分裂
|
||||
if (w.rng.chance(0.03) && npc.power > 500 && (dyn.prosperity ?? 50) < 25 && !npc.allied) {
|
||||
splitNpcDynasty(w, s, id)
|
||||
}
|
||||
// B7 互攻:与世仇(关系<-50)年首相搏——败方伤筋动骨
|
||||
for (const oid of ids) {
|
||||
if (oid === id) continue
|
||||
@@ -773,27 +834,136 @@ function purgeNpc(w: World, s: WorldSimState, id: string): void {
|
||||
w.emitFx('ripple', `purge:${id}`)
|
||||
}
|
||||
|
||||
const NEWBORN_STYLES = ['新锐剑宗', '灵植世家', '商盟豪族', '兵修门阀', '符箓仙门'] as const
|
||||
const NEWBORN_REGIONS = ['南麓青泽', '西山雾谷', '东溪云汉', '北原古井'] as const
|
||||
const NEWBORN_STYLES = ['新锐剑宗', '灵植世家', '商盟豪族', '兵修门阀', '符箓仙门', '丹道世家', '阵法传承', '御兽门阀'] as const
|
||||
const NEWBORN_REGIONS = ['南麓青泽', '西山雾谷', '东溪云汉', '北原古井', '中州云台', '东海浮屿'] as const
|
||||
|
||||
/** 新贵补位:随机风格/区域/初始 power 的新家族(乱世更频) */
|
||||
/**
|
||||
* 0.1.40 NPC 势力分裂
|
||||
*
|
||||
* 触发条件:power>500 且景气<25 且非附庸,年首 3% 概率
|
||||
* 效果:
|
||||
* - 原家族 power 降 40%,景气降至 15
|
||||
* - 分裂出的新家族继承 35% power + 部分关系网
|
||||
* - 新家族注册为独立 NPC(registerNpcDef + npcFamilies + npcDyn)
|
||||
* - 落档 worldGen.npcs 保证读档幂等
|
||||
*/
|
||||
function splitNpcDynasty(w: World, s: WorldSimState, parentId: string): void {
|
||||
const parent = w.state.npcFamilies[parentId]
|
||||
const parentDef = npcById(parentId)
|
||||
const parentDyn = s.npcDyn[parentId]
|
||||
if (!parent || !parentDef || !parentDyn) return
|
||||
|
||||
const rng = w.rng
|
||||
const splitPower = Math.round(parent.power * 0.35)
|
||||
parent.power = Math.round(parent.power * 0.6)
|
||||
parentDyn.prosperity = 15
|
||||
parentDyn.lastEvent = '势力分裂'
|
||||
parentDyn.lastEventYear = w.state.year
|
||||
|
||||
// 分裂出的新家族
|
||||
let seedHash = 0
|
||||
for (let i = 0; i < w.state.seed.length; i++) seedHash = (seedHash * 31 + w.state.seed.charCodeAt(i)) >>> 0
|
||||
const newId = `n-split-${(seedHash % 4096).toString(36)}-${rng.int(1, 9999999)}`
|
||||
const newName = `${parent.name.slice(0, 2)}分支`
|
||||
const newStyle = rng.pick([...NEWBORN_STYLES])
|
||||
const newRegion = parent.region || rng.pick([...NEWBORN_REGIONS])
|
||||
const newDef = {
|
||||
id: newId,
|
||||
name: newName,
|
||||
region: newRegion,
|
||||
desc: `${parentDef.name}内乱分裂而出,另立门户。`,
|
||||
style: newStyle,
|
||||
leaderRealm: 'foundation' as const,
|
||||
initialPower: splitPower,
|
||||
powerGrowth: [3, 9] as [number, number],
|
||||
sells: parentDef.sells ? [...parentDef.sells] : [],
|
||||
buys: parentDef.buys ? [...parentDef.buys] : ['lingcao', 'lingkuang']
|
||||
}
|
||||
registerNpcDef(newDef)
|
||||
// 落档 worldGen.npcs
|
||||
if (w.state.worldGen) {
|
||||
if (!w.state.worldGen.npcs) w.state.worldGen.npcs = []
|
||||
w.state.worldGen.npcs.push({
|
||||
id: newDef.id, name: newDef.name, region: newDef.region, style: newDef.style,
|
||||
desc: newDef.desc, leaderRealm: newDef.leaderRealm, initialPower: newDef.initialPower,
|
||||
powerGrowth: newDef.powerGrowth, sells: newDef.sells, buys: newDef.buys
|
||||
})
|
||||
}
|
||||
w.state.npcFamilies[newId] = {
|
||||
id: newId, name: newName, region: newRegion,
|
||||
power: splitPower, relation: 0, allied: false, raidCount: 0, declineYears: 0
|
||||
}
|
||||
const newDyn = initDynFor(newId)
|
||||
newDyn.leaderName = `${newName.slice(0, 2)}氏少主`
|
||||
newDyn.leaderAge = 25 + rng.int(0, 15)
|
||||
newDyn.leaderRealmIdx = Math.max(0, parentDyn.leaderRealmIdx - 1)
|
||||
newDyn.prosperity = 40
|
||||
newDyn.lastEvent = '分裂自立'
|
||||
newDyn.lastEventYear = w.state.year
|
||||
// 继承部分关系网(与原家族的盟友关系减半、仇敌转为中立)
|
||||
for (const [oid, rel] of Object.entries(parentDyn.relationsWithOthers)) {
|
||||
if (oid === parentId) continue
|
||||
newDyn.relationsWithOthers[oid] = rel > 0 ? Math.round(rel * 0.5) : Math.round(rel * 0.3)
|
||||
// 分裂消息传至邻家:对原家族关系微降、对新家族态度不明
|
||||
const otherDyn = s.npcDyn[oid]
|
||||
if (otherDyn) {
|
||||
otherDyn.relationsWithOthers[parentId] = clamp((otherDyn.relationsWithOthers[parentId] ?? 0) - 5, -100, 100)
|
||||
otherDyn.relationsWithOthers[newId] = 0 // 中立起步
|
||||
}
|
||||
}
|
||||
// 父子反目
|
||||
newDyn.relationsWithOthers[parentId] = -40
|
||||
parentDyn.relationsWithOthers[newId] = -40
|
||||
s.npcDyn[newId] = newDyn
|
||||
|
||||
s.newsFeed.push({
|
||||
year: w.state.year, month: 1, src: '史官',
|
||||
text: `${parent.name} 内乱分裂——${newName} 另立门户,天下格局再变。`, kind: 'annal'
|
||||
})
|
||||
w.log('info', `【天下】${parent.name} 内乱分裂,${newName} 崛起自立。`)
|
||||
w.emitFx('ripple', `split:${newId}`)
|
||||
}
|
||||
|
||||
/** 新贵补位:随机风格/区域/初始 power 的新家族(乱世更频)
|
||||
* 0.1.40 增强:灰烬重生模式——灭亡家族的位置有概率被新势力填补
|
||||
*
|
||||
* 模式选取(rng 派生):
|
||||
* - 35% 灰烬重生:在已灭亡家族的原区域,以新风格重生(继承部分地缘)
|
||||
* - 65% 全新初立:原逻辑(随机风格/区域)
|
||||
*/
|
||||
function spawnNewbornDynasty(w: World, s: WorldSimState): void {
|
||||
const rng = w.rng
|
||||
const style = rng.pick([...NEWBORN_STYLES])
|
||||
const region = rng.pick([...NEWBORN_REGIONS])
|
||||
// 0.1.40 灰烬重生模式:检查 worldGen.npcs 中已被 purge 的家族区域
|
||||
let ashesRegion: string | undefined
|
||||
let ashesStyle: string | undefined
|
||||
if (w.rng.chance(0.35) && w.state.worldGen?.npcs) {
|
||||
// 从 worldGen.npcs 找到已不在 npcFamilies 中的灭亡家族
|
||||
const fallen = w.state.worldGen.npcs.filter((n) => !w.state.npcFamilies[n.id])
|
||||
if (fallen.length > 0) {
|
||||
const pick = fallen[rng.int(0, fallen.length - 1)]!
|
||||
ashesRegion = pick.region
|
||||
// 不继承原风格——灰烬中重生的新风格
|
||||
ashesStyle = rng.pick([...NEWBORN_STYLES])
|
||||
}
|
||||
}
|
||||
const style = ashesStyle ?? rng.pick([...NEWBORN_STYLES])
|
||||
const region = ashesRegion ?? rng.pick([...NEWBORN_REGIONS])
|
||||
// P1-10 id 掺 seed 指纹(防跨档碰撞)
|
||||
let seedHash = 0
|
||||
for (let i = 0; i < w.state.seed.length; i++) seedHash = (seedHash * 31 + w.state.seed.charCodeAt(i)) >>> 0
|
||||
const id = `n-new-${(seedHash % 4096).toString(36)}-${rng.int(1, 9999999)}` // 0.1.35 A-10 碰撞窗口 9999→9999999
|
||||
const name = `${region.slice(0, 2)}${NEWBORN_NAME_SUFFIX[rng.int(0, NEWBORN_NAME_SUFFIX.length - 1)]}`
|
||||
const isAshes = !!ashesRegion
|
||||
const def = {
|
||||
id,
|
||||
name,
|
||||
region,
|
||||
desc: `${style}初立,闷头搞了十年发展,如今渐攒起一份家业。`,
|
||||
desc: isAshes
|
||||
? `${style}于故地废墟上重建——前人虽去,灵脉犹存,新主收拾残局再起。`
|
||||
: `${style}初立,闷头搞了十年发展,如今渐攒起一份家业。`,
|
||||
style,
|
||||
leaderRealm: 'foundation' as const,
|
||||
initialPower: 100 + rng.int(0, 60),
|
||||
initialPower: isAshes ? 90 + rng.int(0, 50) : 100 + rng.int(0, 60), // 灰烬重生起步略低
|
||||
powerGrowth: [3, 9] as [number, number],
|
||||
sells: [],
|
||||
buys: ['lingcao', 'lingkuang']
|
||||
@@ -820,8 +990,13 @@ function spawnNewbornDynasty(w: World, s: WorldSimState): void {
|
||||
dyn.leaderName = `${name.slice(0, 2)}氏新主`
|
||||
s.npcDyn[id] = dyn
|
||||
const asr = s.era ?? 'pingshi'
|
||||
w.log('good', `【天下】新贵${name}在${region}崛起(${rgStyleName(def.style)}),天下格局松动。`)
|
||||
s.newsFeed.push({ year: w.state.year, month: 1, src: '史官', text: `新贵${name}崛起于${region}——天下格局松动。`, kind: 'annal' })
|
||||
if (isAshes) {
|
||||
w.log('good', `【天下】灰烬重生——${name}于${region}故地重建(${rgStyleName(def.style)}),旧地新主。`)
|
||||
s.newsFeed.push({ year: w.state.year, month: 1, src: '史官', text: `灰烬重生——${name}于${region}故地重建,旧地新主。`, kind: 'annal' })
|
||||
} else {
|
||||
w.log('good', `【天下】新贵${name}在${region}崛起(${rgStyleName(def.style)}),天下格局松动。`)
|
||||
s.newsFeed.push({ year: w.state.year, month: 1, src: '史官', text: `新贵${name}崛起于${region}——天下格局松动。`, kind: 'annal' })
|
||||
}
|
||||
void asr
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user