/** * 世界种子生成器(0.1.24《寰宇初构》) * 游戏种子 → 确定性世界布局:NPC 势力(4~8 家)/初始关系网/开局时代/区域风味/市场偏移。 * 关键纪律:使用独立派生 rng(seed + '::worldgen')——World.rng 主序列零消耗, * 玩家玩法随机序列不被生成器移动(金钟罩玩法序保护);同 seed 世界可重放。 */ import { Rng, seedToRng } from '../kernel/rng' import { NpcFamilyDef } from '../../data/npcs' import { registerNpcDef } from '../../data/npcs' /** 老牌世家模板(创世第一轮种子池核心;worldgen 决定/谁出现) */ export const LEGACY_TEMPLATES: NpcFamilyDef[] = [ { id: 'n-xuanying', name: '玄影沈氏', region: '北岳玄影峰', style: '剑修世家', desc: '隐于北岳的剑修沈氏,剑意凛冽,最为孤傲。', leaderRealm: 'foundation', initialPower: 240, powerGrowth: [4, 10], sells: ['weapon-qi', 'weapon-ling'], buys: ['lingcao', 'lingkuang'] }, { id: 'n-danxin', name: '丹心木氏', region: '西川药谷', style: '丹道世家', desc: '悬壶济世的丹道世家,人脉广博,风格和缓。', leaderRealm: 'foundation', initialPower: 200, powerGrowth: [3, 9], sells: ['pill-qiyuan', 'pill-ningyuan', 'pill-pojing'], buys: ['lingcao', 'beastcore'] }, { id: 'n-sihai', name: '四海王氏', region: '南都连港', style: '商盟世族', desc: '商通四海,富可敌国,只认灵石不认人。', leaderRealm: 'foundation', initialPower: 160, powerGrowth: [5, 12], sells: ['lingcao', 'lingkuang'], buys: ['beastcore', 'lingkuang'] }, { id: 'n-nulei', name: '怒雷祝氏', region: '东丘雷泽', style: '兵修蛮门', desc: '雷泽蛮族的世仇,性情火爆,最易生衅。', leaderRealm: 'core', initialPower: 300, powerGrowth: [5, 13], sells: [], buys: ['lingkuang', 'beastcore'] } ] /** 新贵词库(默认池——MOD/插件可注入扩展;默认不变 → 基准世界不变) */ const DEFAULT_GEN_STYLES = ['剑修世家', '丹道世家', '商盟世族', '兵修蛮门', '符箓仙门', '灵植谷户', '阵道门阀', '散修聚落'] const DEFAULT_GEN_REGIONS = ['北岳玄影峰', '西川药谷', '南都连港', '东丘雷泽', '南麓青泽', '西山雾谷', '东溪云汉', '北原古井'] const DEFAULT_GEN_SUFFIX = ['氏', '氏', '宗', '寨', '门'] const DEFAULT_GEN_FAM = ['玄', '墨', '楚', '白', '萧', '洛', '燕', '秦', '顾', '周', '华', '苏'] /** 聚合词库池(MOD/插件注入端;读取端 worldgen 消费) */ export const WorldGenPools = { styles: [...DEFAULT_GEN_STYLES], regions: [...DEFAULT_GEN_REGIONS], suffixes: [...DEFAULT_GEN_SUFFIX], fams: [...DEFAULT_GEN_FAM], add(part: { styles?: string[]; regions?: string[]; suffixes?: string[]; fams?: string[] }): void { if (part.styles) for (const s of part.styles) if (!this.styles.includes(s)) this.styles.push(s) if (part.regions) for (const r of part.regions) if (!this.regions.includes(r)) this.regions.push(r) if (part.suffixes) for (const s of part.suffixes) if (!this.suffixes.includes(s)) this.suffixes.push(s) if (part.fams) for (const f of part.fams) if (!this.fams.includes(f)) this.fams.push(f) }, reset(): void { this.styles = [...DEFAULT_GEN_STYLES] this.regions = [...DEFAULT_GEN_REGIONS] this.suffixes = [...DEFAULT_GEN_SUFFIX] this.fams = [...DEFAULT_GEN_FAM] } } export interface WorldGenResult { seed: string npcs: NpcFamilyDef[] relations: Record> eras: 'shengshi' | 'pingshi' | 'luanshi' regionFlavor: Record marketOffset: Record alliances: Array<[string, string]> feuds: Array<[string, string]> } /** 世界生成(确定性;独立 rng) */ export function generateWorld(seed: string): WorldGenResult { const rng = new Rng(seedToRng(`${seed}::worldgen`)) // 家数 4~8(乱世开局偏少);老牌世家参与率 60~100% const count = 4 + rng.int(0, 4) const legacyPick = 1 + rng.int(0, 4) // 1~4 家老牌 const legacy: NpcFamilyDef[] = [] const pool = [...LEGACY_TEMPLATES] while (legacy.length < legacyPick && pool.length > 0) { const i = rng.int(0, pool.length - 1) legacy.push(pool[i]!) pool.splice(i, 1) } const usedRegions = new Set(legacy.map((l) => l.region)) const usedNames = new Set(legacy.map((l) => l.name)) const npcs: NpcFamilyDef[] = [...legacy] const frontier = count - legacy.length for (let k = 0; k < frontier; k++) { const region = WorldGenPools.regions.filter((r) => !usedRegions.has(r)) const regionPick = region.length > 0 ? region[rng.int(0, region.length - 1)]! : WorldGenPools.regions[rng.int(0, WorldGenPools.regions.length - 1)]! const stylePick = WorldGenPools.styles[rng.int(0, WorldGenPools.styles.length - 1)]! usedRegions.add(regionPick) // 去重:家名组合回溯(同 seed 确定性;至多 8 次重抽) let fam = WorldGenPools.fams[rng.int(0, WorldGenPools.fams.length - 1)]! let suffix = WorldGenPools.suffixes[rng.int(0, WorldGenPools.suffixes.length - 1)]! let name = `${fam}${suffix}` for (let t = 0; t < 8 && usedNames.has(name); t++) { fam = WorldGenPools.fams[rng.int(0, WorldGenPools.fams.length - 1)]! suffix = WorldGenPools.suffixes[rng.int(0, WorldGenPools.suffixes.length - 1)]! name = `${fam}${suffix}` } usedNames.add(name) const power = 90 + rng.int(0, 130) npcs.push({ id: `n-g${k + 1}-${fam}`, name: `${fam}${suffix}`, region: regionPick, style: stylePick, desc: `${stylePick}新立足${regionPick},渐成气候。`, leaderRealm: power > 180 ? 'core' : 'foundation', initialPower: power, powerGrowth: [3, 10], sells: rng.chance(0.5) ? ['lingcao'] : [], buys: ['lingcao', 'lingkuang'] }) } // 注册动态 def(npcById 双源命中) for (const n of npcs) registerNpcDef(n) // 初始关系网:随机世仇 1-2 对 + 盟友 1 对 + 其余 ±20 漂移 const relations: Record> = {} for (const a of npcs) relations[a.id] = {} const pairAll = (a: string, b: string, v: number) => { relations[a]![b] = v relations[b]![a] = v } const shuffle = [...npcs] for (let i = shuffle.length - 1; i > 0; i--) { const j = rng.int(0, i) ;[shuffle[i], shuffle[j]] = [shuffle[j]!, shuffle[i]!] } const feuds: Array<[string, string]> = [] const alliances: Array<[string, string]> = [] if (shuffle.length >= 2) { pairAll(shuffle[0]!.id, shuffle[1]!.id, -60 - rng.int(0, 20)) feuds.push([shuffle[0]!.id, shuffle[1]!.id]) if (shuffle.length >= 3) { pairAll(shuffle[2]!.id, shuffle[3]!.id, 45 + rng.int(0, 20)) alliances.push([shuffle[2]!.id, shuffle[3]!.id]) } else { pairAll(shuffle[0]!.id, shuffle[1]!.id, -60 - rng.int(0, 20)) } } for (let i = 0; i < npcs.length; i++) { for (let j = i + 1; j < npcs.length; j++) { const a = npcs[i]!.id const b = npcs[j]!.id if (relations[a]?.[b] === undefined) { pairAll(a, b, rng.int(-25, 25)) } } } // 开局 era:盛世/平世/乱世(乱世世界凶兆与繁华初现的分野) const eras = ['shengshi', 'pingshi', 'luanshi'] as const const eraPick = eras[rng.int(0, eras.length < 2 ? 0 : 2)]! // 区域风味(每 region 商品偏移 ±15%对称) const regionFlavor: Record = {} for (const n of npcs) { regionFlavor[n.id] = (rng.next() - 0.5) * 0.3 } const marketOffset: Record = { lingcao: (rng.next() - 0.5) * 0.24, lingkuang: (rng.next() - 0.5) * 0.24, beastcore: (rng.next() - 0.5) * 0.24, 'pill-qiyuan': (rng.next() - 0.5) * 0.18, 'pill-ningyuan': (rng.next() - 0.5) * 0.18 } return { seed, npcs, relations, eras: eraPick, regionFlavor, marketOffset, alliances, feuds } }