家族模拟器首版:修仙/经营/战斗/外交/叙事全套系统 - Electron + React + TS + MetonaSqlark(aria+OPFS) - 水墨中国风 UI - 引擎种子随机、确定性可回放 - 19 项单元测试、端到端冒烟验证
121 lines
3.9 KiB
TypeScript
121 lines
3.9 KiB
TypeScript
import { Character, Element, Gender, Realm, RealmMajor } from '../types/domain'
|
|
import { Rng } from '../core/rng'
|
|
import { ELEMENT_LIST, ROOT_GRADES } from '../data/elements'
|
|
import { MAJORS } from '../data/realms'
|
|
import { TRAIT_POOL, TRAITS } from '../data/traits'
|
|
|
|
export function rollRoots(rng: Rng, parents?: { m?: Character; f?: Character }): { grade: number; primary: Element; secondary: Element[] } {
|
|
let grade: number
|
|
if (parents && parents.m && parents.f) {
|
|
const mix = (parents.m.roots.grade + parents.f.roots.grade) / 2
|
|
const roll = rng.next()
|
|
if (roll < 0.3) grade = Math.round(mix)
|
|
else if (roll < 0.8) grade = Math.round(mix) + 1
|
|
else grade = Math.round(mix) - 1
|
|
if (rng.chance(0.15)) grade = Math.min(5, grade + 2)
|
|
} else {
|
|
const total = Object.entries(ROOT_GRADES).reduce((s, [k, v]) => s + v.drawWeight, 0)
|
|
let r = rng.next() * total
|
|
grade = 1
|
|
for (const [k, v] of Object.entries(ROOT_GRADES)) {
|
|
r -= v.drawWeight
|
|
if (r <= 0) {
|
|
grade = Number(k)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
grade = Math.max(0, Math.min(5, grade))
|
|
|
|
const primaryCandidate = parents && (parents.m || parents.f)
|
|
? [parents.m!.roots.primary, parents.f!.roots.primary]
|
|
: ELEMENT_LIST
|
|
const primary = rng.pick(primaryCandidate)
|
|
const secondaryCount = grade >= 3 ? rng.int(1, 2) : grade === 2 ? rng.int(0, 1) : 0
|
|
const rest = ELEMENT_LIST.filter((e) => e !== primary)
|
|
const secondary = rng.shuffle(rest).slice(0, secondaryCount)
|
|
return { grade, primary, secondary }
|
|
}
|
|
|
|
export function rollPersonality(rng: Rng): string[] {
|
|
const n = rng.chance(0.5) ? 2 : 1
|
|
return rng.shuffle([...TRAIT_POOL]).slice(0, n)
|
|
}
|
|
|
|
export function rollAttributes(rng: Rng, base: number, variance: number): number {
|
|
const v = Math.round(base + rng.between(-variance, variance))
|
|
return Math.max(1, Math.min(10, v))
|
|
}
|
|
|
|
export function calcLifespan(major: RealmMajor, physique: number): number {
|
|
const base = MAJORS[major].lifespan
|
|
return Math.round(base * (0.9 + physique * 0.02))
|
|
}
|
|
|
|
export function newCharacter(
|
|
rng: Rng,
|
|
opts: {
|
|
name: string
|
|
gender: Gender
|
|
generation: number
|
|
bornYear: number
|
|
age: number
|
|
mother?: Character
|
|
father?: Character
|
|
realm?: Realm
|
|
isHead?: boolean
|
|
isFounder?: boolean
|
|
fortuneBase?: number
|
|
}
|
|
): Character {
|
|
const realm: Realm = opts.realm ?? { major: 'mortal', minor: 0 }
|
|
const roots = rollRoots(rng, opts.father || opts.mother ? { m: opts.father, f: opts.mother } : undefined)
|
|
const levelBonus = MAJORS[realm.major as RealmMajor].minorLayers > 0 ? realm.minor * 0.4 : 0
|
|
const perception = rollAttributes(rng, 4.5 + roots.grade * 0.8 + levelBonus * 0.25, 1.8)
|
|
const physique = rollAttributes(rng, 4 + roots.grade * 0.5 + levelBonus * 0.3, 1.8)
|
|
const mind = rollAttributes(rng, 4.5 + roots.grade * 0.3, 1.8)
|
|
const charm = rollAttributes(rng, 4.5, 2.2)
|
|
const fortune = rollAttributes(rng, (opts.fortuneBase ?? 5) + roots.grade * 0.4, 2.2)
|
|
return {
|
|
id: '',
|
|
name: opts.name,
|
|
gender: opts.gender,
|
|
generation: opts.generation,
|
|
children: [],
|
|
bornYear: opts.bornYear,
|
|
age: opts.age,
|
|
realm,
|
|
realmProgress: 0,
|
|
roots,
|
|
perception,
|
|
physique,
|
|
mind,
|
|
charm,
|
|
fortune,
|
|
traits: rollPersonality(rng),
|
|
state: 'idle',
|
|
health: 100,
|
|
alive: true,
|
|
isHead: opts.isHead,
|
|
isFounder: opts.isFounder
|
|
}
|
|
}
|
|
|
|
export function traitBonuses(character: Character): { exp: number; breakBonus: number; windBonus: number; charmBonus: number; priceMult: number } {
|
|
let exp = 0
|
|
let breakBonus = 0
|
|
let windBonus = 0
|
|
let charmBonus = 0
|
|
let priceMult = 0
|
|
for (const t of character.traits) {
|
|
const def = TRAITS[t]
|
|
if (!def) continue
|
|
exp += def.expBonus ?? 0
|
|
breakBonus += def.breakBonus ?? 0
|
|
windBonus += def.windBonus ?? 0
|
|
charmBonus += def.charmBonus ?? 0
|
|
priceMult += def.priceMult ?? 0
|
|
}
|
|
return { exp: 1 + exp, breakBonus, windBonus, charmBonus, priceMult }
|
|
}
|