v0.7: CrystalOrb Canvas 粒子系统升级 + 角色属性系统(四维)
CrystalOrb 升级(P0 视觉震撼): - 从 CSS 动画重写为 Canvas 2D 粒子系统 - 环绕能量粒子(3层32个,emerald/fuchsia/rose,带拖尾) - 环境星尘(40个,闪烁漂移) - 点击爆发粒子 + 冲击波环(连击解锁更多层) - 晶核辉光呼吸 + 3层六边形纹理 + 鼠标视差 - VLM 视觉评分 8/10 角色属性系统(P1 RPG 深度): - 新增 attributes.ts 逻辑层 + AttributesPanel UI - 四维属性:探索力/智慧/勇气/灵感(emerald/fuchsia/amber/rose) - 两区加成公式(0-50线性,50-100递减) - 飞升获得属性点(ascensions×2+1),手动分配 - 完成活动获得属性经验,自动升级 - 接入 pulse/clickNode/resolveCurrentNode/grantCruiseReward/tickTide - 旧存档兼容(migrateAttributes) - 新增 2 项成就 + 第 8 个标签页「角色」 - VLM 视觉评分 7-8/10 lint 零错误;HTTP 200
This commit is contained in:
@@ -251,6 +251,45 @@ export const ACHIEVEMENTS: Achievement[] = [
|
||||
reward: { crystalsPerSecPct: 8, insightPct: 8 },
|
||||
rewardText: "产能 +8% · 洞见 +8%",
|
||||
},
|
||||
{
|
||||
id: "ach_attr_total_50",
|
||||
name: "四维觉醒",
|
||||
desc: "角色四维属性总和达到 50",
|
||||
icon: "🧭",
|
||||
color: "#e879f9",
|
||||
check: (s) => {
|
||||
const a = s.attributes;
|
||||
if (!a) return false;
|
||||
return (
|
||||
(a.exploration || 0) +
|
||||
(a.wisdom || 0) +
|
||||
(a.courage || 0) +
|
||||
(a.inspiration || 0) >=
|
||||
50
|
||||
);
|
||||
},
|
||||
reward: { crystalsPerSecPct: 6, insightPct: 6 },
|
||||
rewardText: "产能 +6% · 洞见 +6%",
|
||||
},
|
||||
{
|
||||
id: "ach_attr_max_100",
|
||||
name: "维度精通",
|
||||
desc: "任一角色属性达到满级 100",
|
||||
icon: "💫",
|
||||
color: "#fbbf24",
|
||||
check: (s) => {
|
||||
const a = s.attributes;
|
||||
if (!a) return false;
|
||||
return (
|
||||
a.exploration >= 100 ||
|
||||
a.wisdom >= 100 ||
|
||||
a.courage >= 100 ||
|
||||
a.inspiration >= 100
|
||||
);
|
||||
},
|
||||
reward: { crystalsPerSecPct: 12, insightPct: 10 },
|
||||
rewardText: "产能 +12% · 洞见 +10%",
|
||||
},
|
||||
];
|
||||
|
||||
/** 计算成就提供的永久加成(跨周目保留) */
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
// 回响星核 / Echo Nexus — 角色属性系统 (v0.7 P1)
|
||||
//
|
||||
// 无人机驾驶员的四维属性,为放置循环注入 RPG 深度:
|
||||
// • 探索力 Exploration(翠 emerald)— 探险力 / 飞船速度 / 星图发现
|
||||
// • 智慧 Wisdom (玫 fuchsia)— 解码步数 / 洞见 / 自动解码周期
|
||||
// • 勇气 Courage (琥 amber) — 探险生命 / BOSS 战胜率 / 巡航护盾
|
||||
// • 灵感 Inspiration (玫红 rose) — 接触率 / 星潮触发 / 脉冲连击加成
|
||||
//
|
||||
// 数值规则:
|
||||
// • 0-100 软上限(超过后收益递减)
|
||||
// • 0-50 线性区:每 10 点 = +5% 加成 → 即 0.5%/点
|
||||
// • 50-100 递减区:每点 = +2% 加成
|
||||
// • 属性通过「属性点」升级(飞升获得),亦可由「经验值」自动累积升级
|
||||
// • 每级经验需求 = 10 × 当前等级
|
||||
import type { GameState } from "./types";
|
||||
|
||||
/** 四维属性键 */
|
||||
export type AttributeKey = "exploration" | "wisdom" | "courage" | "inspiration";
|
||||
|
||||
/** 角色四维属性数值(0-100) */
|
||||
export interface CharacterAttributes extends Record<AttributeKey, number> {
|
||||
exploration: number;
|
||||
wisdom: number;
|
||||
courage: number;
|
||||
inspiration: number;
|
||||
}
|
||||
|
||||
/** 单个属性的经验进度 */
|
||||
export interface AttributeProgressEntry {
|
||||
/** 当前累计经验值 */
|
||||
exp: number;
|
||||
/** 当前等级(与 attributes[key] 同步) */
|
||||
level: number;
|
||||
}
|
||||
|
||||
/** 经验进度集合 */
|
||||
export type AttributeProgress = Record<AttributeKey, AttributeProgressEntry>;
|
||||
|
||||
/** 单属性元信息(供 UI 渲染) */
|
||||
export interface AttributeMeta {
|
||||
key: AttributeKey;
|
||||
name: string;
|
||||
enName: string;
|
||||
/** 主题色(Tailwind class 前缀,避免使用蓝/靛色) */
|
||||
color: "emerald" | "fuchsia" | "amber" | "rose";
|
||||
hex: string;
|
||||
glow: string;
|
||||
/** lucide-react 图标名 */
|
||||
icon: string;
|
||||
desc: string;
|
||||
/** 影响的游戏系统列表(用于 UI 展示) */
|
||||
effects: string[];
|
||||
}
|
||||
|
||||
/** 属性配置常量 */
|
||||
export const ATTRIBUTE_CONFIG: Record<AttributeKey, AttributeMeta> = {
|
||||
exploration: {
|
||||
key: "exploration",
|
||||
name: "探索力",
|
||||
enName: "Exploration",
|
||||
color: "emerald",
|
||||
hex: "#34d399",
|
||||
glow: "rgba(52,211,153,0.55)",
|
||||
icon: "Compass",
|
||||
desc: "足迹所至,皆是矿脉。提升探险力与巡航飞船速度。",
|
||||
effects: ["探险力 +X%", "巡航飞船速度 +X%", "星图发现概率"],
|
||||
},
|
||||
wisdom: {
|
||||
key: "wisdom",
|
||||
name: "智慧",
|
||||
enName: "Wisdom",
|
||||
color: "fuchsia",
|
||||
hex: "#e879f9",
|
||||
glow: "rgba(232,121,249,0.55)",
|
||||
icon: "Brain",
|
||||
desc: "谐振的回声唯有智者能解。强化解码与洞见。",
|
||||
effects: ["解码步数 +X", "洞见产出 +X%", "自动解码周期 -X%"],
|
||||
},
|
||||
courage: {
|
||||
key: "courage",
|
||||
name: "勇气",
|
||||
enName: "Courage",
|
||||
color: "amber",
|
||||
hex: "#fbbf24",
|
||||
glow: "rgba(251,191,36,0.55)",
|
||||
icon: "Swords",
|
||||
desc: "直面虚空者,虚空亦为之让路。增益战斗与护盾。",
|
||||
effects: ["探险生命 +X", "BOSS 战胜率 +X%", "巡航护盾 +X"],
|
||||
},
|
||||
inspiration: {
|
||||
key: "inspiration",
|
||||
name: "灵感",
|
||||
enName: "Inspiration",
|
||||
color: "rose",
|
||||
hex: "#fb7185",
|
||||
glow: "rgba(251,113,133,0.55)",
|
||||
icon: "Sparkles",
|
||||
desc: "在不期而至的回响之间,照见更高的维度。",
|
||||
effects: ["接触率 +X", "星潮触发概率 +X%", "脉冲连击加成 +X%"],
|
||||
},
|
||||
};
|
||||
|
||||
/** 属性键的固定顺序(UI 渲染用) */
|
||||
export const ATTRIBUTE_KEYS: AttributeKey[] = [
|
||||
"exploration",
|
||||
"wisdom",
|
||||
"courage",
|
||||
"inspiration",
|
||||
];
|
||||
|
||||
/** 属性数值上限(软上限,超过后收益递减但仍可继续投资) */
|
||||
export const ATTRIBUTE_HARD_CAP = 100;
|
||||
/** 线性区与递减区交界点 */
|
||||
export const ATTRIBUTE_LINEAR_CAP = 50;
|
||||
|
||||
/**
|
||||
* 计算单个属性当前的加成百分比(小数形式,1.0 = 100%)。
|
||||
* - 0-50 线性区:每点 +0.5% → 0-50 点对应 0-25%
|
||||
* - 50-100 递减区:每点 +0.2% → 50-100 点对应 25-35%
|
||||
* - >100 仍按 100 计算加成(软上限)
|
||||
*/
|
||||
export function getAttributeBonus(attr: number): number {
|
||||
const v = Math.max(0, Math.min(ATTRIBUTE_HARD_CAP, attr));
|
||||
if (v <= ATTRIBUTE_LINEAR_CAP) {
|
||||
// 0..50 → 0..25%
|
||||
return v * 0.005;
|
||||
}
|
||||
// 25% + (v - 50) * 0.2%
|
||||
return 0.25 + (v - ATTRIBUTE_LINEAR_CAP) * 0.002;
|
||||
}
|
||||
|
||||
/** 计算单个属性对脉冲连击的额外加成百分比(线性,灵感专属) */
|
||||
export function getInspirationComboBonus(attr: number): number {
|
||||
// 每点 +0.5% 连击伤害加成(无递减)
|
||||
return Math.max(0, Math.min(ATTRIBUTE_HARD_CAP, attr)) * 0.005;
|
||||
}
|
||||
|
||||
/** 属性聚合修饰器(聚合到 recomputeStats 与具体动作) */
|
||||
export interface AttributeModifiers {
|
||||
/** 探险力 +X% (探索力) */
|
||||
expeditionPowerMult: number;
|
||||
/** 巡航飞船速度 +X% (探索力) */
|
||||
cruiseShipSpeedMult: number;
|
||||
/** 解码步数 +X (智慧) — 每点 +0.1 步 → 0-10 步加成 */
|
||||
decodeStepsBonus: number;
|
||||
/** 洞见倍率 +X% (智慧) */
|
||||
insightMultAdd: number;
|
||||
/** 自动解码周期 -X% (智慧) */
|
||||
autoDecodeIntervalMult: number;
|
||||
/** 探险生命 +X (勇气) — 每点 +2 HP */
|
||||
expeditionHpBonus: number;
|
||||
/** BOSS 战胜率 +X% (勇气) */
|
||||
bossWinRateBonus: number;
|
||||
/** 巡航护盾 +X (勇气) — 每点 +1 护盾 */
|
||||
cruiseShieldBonus: number;
|
||||
/** 接触率倍率 (灵感) */
|
||||
contactRateMult: number;
|
||||
/** 星潮触发概率 +X% (灵感) */
|
||||
tideTriggerBonus: number;
|
||||
/** 脉冲连击加成 +X% (灵感) */
|
||||
pulseComboBonus: number;
|
||||
/** 综合产能加成(探索力 + 灵感 0.2%/点) */
|
||||
crystalsPerSecMult: number;
|
||||
}
|
||||
|
||||
/** 由四维属性计算所有修饰器 */
|
||||
export function getAllBonuses(attrs: Partial<CharacterAttributes>): AttributeModifiers {
|
||||
const exploration = attrs.exploration ?? 0;
|
||||
const wisdom = attrs.wisdom ?? 0;
|
||||
const courage = attrs.courage ?? 0;
|
||||
const inspiration = attrs.inspiration ?? 0;
|
||||
|
||||
return {
|
||||
expeditionPowerMult: 1 + getAttributeBonus(exploration),
|
||||
cruiseShipSpeedMult: 1 + getAttributeBonus(exploration) * 0.6, // 飞船速度加成减半
|
||||
decodeStepsBonus: Math.floor(wisdom * 0.1), // 每点 +0.1 步
|
||||
insightMultAdd: getAttributeBonus(wisdom),
|
||||
autoDecodeIntervalMult: 1 - Math.min(0.5, wisdom * 0.004), // 每点 -0.4%,上限 -50%
|
||||
expeditionHpBonus: Math.round(courage * 2),
|
||||
bossWinRateBonus: Math.min(0.3, courage * 0.003), // 每点 +0.3%,上限 +30%
|
||||
cruiseShieldBonus: Math.round(courage * 1),
|
||||
contactRateMult: 1 + getAttributeBonus(inspiration),
|
||||
tideTriggerBonus: Math.min(0.3, inspiration * 0.003),
|
||||
pulseComboBonus: getInspirationComboBonus(inspiration),
|
||||
crystalsPerSecMult:
|
||||
1 +
|
||||
getAttributeBonus(exploration) * 0.4 + // 探索力 0.4 倍系数加成产能
|
||||
getAttributeBonus(inspiration) * 0.2, // 灵感 0.2 倍系数加成产能
|
||||
};
|
||||
}
|
||||
|
||||
/** 每级所需经验 = 10 × 当前等级 */
|
||||
export function expRequiredForLevel(level: number): number {
|
||||
return Math.max(10, 10 * Math.max(0, level));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查属性经验是否足以升级。
|
||||
* 返回新的进度(可能跨多级)。
|
||||
*/
|
||||
export function levelUpCheck(
|
||||
progress: AttributeProgressEntry,
|
||||
maxValue: number = ATTRIBUTE_HARD_CAP
|
||||
): { leveledUp: boolean; newProgress: AttributeProgressEntry; levelsGained: number } {
|
||||
let { exp, level } = progress;
|
||||
let leveledUp = false;
|
||||
let levelsGained = 0;
|
||||
// 安全上限:避免异常数据循环
|
||||
let safety = 0;
|
||||
while (level < maxValue && exp >= expRequiredForLevel(level) && safety < 200) {
|
||||
exp -= expRequiredForLevel(level);
|
||||
level += 1;
|
||||
leveledUp = true;
|
||||
levelsGained += 1;
|
||||
safety += 1;
|
||||
}
|
||||
// 已达上限:保留多余经验但不升级
|
||||
if (level >= maxValue) {
|
||||
level = maxValue;
|
||||
}
|
||||
return {
|
||||
leveledUp,
|
||||
levelsGained,
|
||||
newProgress: { exp, level },
|
||||
};
|
||||
}
|
||||
|
||||
/** 创建初始的属性进度集合 */
|
||||
export function createInitialAttributeProgress(): AttributeProgress {
|
||||
return {
|
||||
exploration: { exp: 0, level: 0 },
|
||||
wisdom: { exp: 0, level: 0 },
|
||||
courage: { exp: 0, level: 0 },
|
||||
inspiration: { exp: 0, level: 0 },
|
||||
};
|
||||
}
|
||||
|
||||
/** 创建初始的属性数值集合 */
|
||||
export function createInitialAttributes(): CharacterAttributes {
|
||||
return {
|
||||
exploration: 0,
|
||||
wisdom: 0,
|
||||
courage: 0,
|
||||
inspiration: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 旧存档兼容:补全 attributes / attributeProgress / pendingAttrPoints 字段。
|
||||
* 若字段缺失或结构异常,使用初始值;若数值越界则夹紧。
|
||||
*/
|
||||
export function migrateAttributes(state: Partial<GameState>): {
|
||||
attributes: CharacterAttributes;
|
||||
attributeProgress: AttributeProgress;
|
||||
pendingAttrPoints: number;
|
||||
} {
|
||||
const init = createInitialAttributes();
|
||||
let attributes: CharacterAttributes;
|
||||
if (state.attributes && typeof state.attributes === "object") {
|
||||
attributes = {
|
||||
exploration: clampAttr(state.attributes.exploration),
|
||||
wisdom: clampAttr(state.attributes.wisdom),
|
||||
courage: clampAttr(state.attributes.courage),
|
||||
inspiration: clampAttr(state.attributes.inspiration),
|
||||
};
|
||||
} else {
|
||||
attributes = init;
|
||||
}
|
||||
let attributeProgress: AttributeProgress;
|
||||
if (state.attributeProgress && typeof state.attributeProgress === "object") {
|
||||
const base = createInitialAttributeProgress();
|
||||
attributeProgress = {
|
||||
exploration: normalizeProgress(
|
||||
state.attributeProgress.exploration,
|
||||
attributes.exploration
|
||||
),
|
||||
wisdom: normalizeProgress(
|
||||
state.attributeProgress.wisdom,
|
||||
attributes.wisdom
|
||||
),
|
||||
courage: normalizeProgress(
|
||||
state.attributeProgress.courage,
|
||||
attributes.courage
|
||||
),
|
||||
inspiration: normalizeProgress(
|
||||
state.attributeProgress.inspiration,
|
||||
attributes.inspiration
|
||||
),
|
||||
};
|
||||
void base;
|
||||
} else {
|
||||
// 同步 level 与 attributes 数值(保证一致)
|
||||
attributeProgress = {
|
||||
exploration: { exp: 0, level: attributes.exploration },
|
||||
wisdom: { exp: 0, level: attributes.wisdom },
|
||||
courage: { exp: 0, level: attributes.courage },
|
||||
inspiration: { exp: 0, level: attributes.inspiration },
|
||||
};
|
||||
}
|
||||
const pendingAttrPoints =
|
||||
typeof state.pendingAttrPoints === "number" && state.pendingAttrPoints >= 0
|
||||
? Math.floor(state.pendingAttrPoints)
|
||||
: 0;
|
||||
return { attributes, attributeProgress, pendingAttrPoints };
|
||||
}
|
||||
|
||||
function clampAttr(v: unknown): number {
|
||||
const n = typeof v === "number" && isFinite(v) ? v : 0;
|
||||
return Math.max(0, Math.min(ATTRIBUTE_HARD_CAP, Math.floor(n)));
|
||||
}
|
||||
|
||||
function normalizeProgress(
|
||||
p: unknown,
|
||||
level: number
|
||||
): AttributeProgressEntry {
|
||||
if (p && typeof p === "object") {
|
||||
const obj = p as { exp?: unknown; level?: unknown };
|
||||
const exp =
|
||||
typeof obj.exp === "number" && isFinite(obj.exp) && obj.exp >= 0
|
||||
? obj.exp
|
||||
: 0;
|
||||
const lvl =
|
||||
typeof obj.level === "number" && isFinite(obj.level) && obj.level >= 0
|
||||
? Math.floor(obj.level)
|
||||
: level;
|
||||
// level 字段以 attributes 数值为准(attributes 是事实之源)
|
||||
return { exp, level };
|
||||
}
|
||||
return { exp: 0, level };
|
||||
}
|
||||
|
||||
/** 计算本次飞升可获得的属性点 = 飞升次数 × 2 + 1(飞升前 ascensions) */
|
||||
export function computePrestigeAttrPoints(ascensionsBefore: number): number {
|
||||
return ascensionsBefore * 2 + 1;
|
||||
}
|
||||
|
||||
/** 计算四维属性总和 */
|
||||
export function totalAttributeLevel(attrs: CharacterAttributes): number {
|
||||
return (
|
||||
attrs.exploration + attrs.wisdom + attrs.courage + attrs.inspiration
|
||||
);
|
||||
}
|
||||
|
||||
/** 计算四维属性总加成百分比(用于 UI 概览) */
|
||||
export function totalAttributeBonusPct(attrs: CharacterAttributes): number {
|
||||
return (
|
||||
(getAttributeBonus(attrs.exploration) +
|
||||
getAttributeBonus(attrs.wisdom) +
|
||||
getAttributeBonus(attrs.courage) +
|
||||
getAttributeBonus(attrs.inspiration)) *
|
||||
100
|
||||
);
|
||||
}
|
||||
|
||||
/** 是否任一属性已达 100(用于成就) */
|
||||
export function anyAttributeMaxed(attrs: CharacterAttributes): boolean {
|
||||
return (
|
||||
attrs.exploration >= ATTRIBUTE_HARD_CAP ||
|
||||
attrs.wisdom >= ATTRIBUTE_HARD_CAP ||
|
||||
attrs.courage >= ATTRIBUTE_HARD_CAP ||
|
||||
attrs.inspiration >= ATTRIBUTE_HARD_CAP
|
||||
);
|
||||
}
|
||||
@@ -45,6 +45,20 @@ export const INITIAL_STATE = {
|
||||
starTidesEncountered: [] as string[],
|
||||
crystalsDecoded: 0,
|
||||
},
|
||||
// v0.7 角色属性系统
|
||||
attributes: {
|
||||
exploration: 0,
|
||||
wisdom: 0,
|
||||
courage: 0,
|
||||
inspiration: 0,
|
||||
} as import("./attributes").CharacterAttributes,
|
||||
attributeProgress: {
|
||||
exploration: { exp: 0, level: 0 },
|
||||
wisdom: { exp: 0, level: 0 },
|
||||
courage: { exp: 0, level: 0 },
|
||||
inspiration: { exp: 0, level: 0 },
|
||||
} as import("./attributes").AttributeProgress,
|
||||
pendingAttrPoints: 0,
|
||||
theme: "dark" as const,
|
||||
soundOn: true,
|
||||
};
|
||||
|
||||
+42
-2
@@ -14,6 +14,12 @@ import {
|
||||
buildChronicleEntry,
|
||||
createRunStartSnapshot,
|
||||
} from "./chronicle";
|
||||
import {
|
||||
getAllBonuses,
|
||||
migrateAttributes,
|
||||
computePrestigeAttrPoints,
|
||||
createInitialAttributeProgress,
|
||||
} from "./attributes";
|
||||
|
||||
/** 由技术树 + 飞升蓝图 + 成就 + 星图天赋 + 星潮聚合计算产能字段 */
|
||||
export function recomputeStats(state: Partial<GameState>): {
|
||||
@@ -96,6 +102,13 @@ export function recomputeStats(state: Partial<GameState>): {
|
||||
insightMult += tideMod.insightMultAdd;
|
||||
contactRateMult *= tideMod.contactRateMult;
|
||||
|
||||
// 角色属性加成(v0.7 P1 — 与现有所有加成叠加)
|
||||
const am = getAllBonuses(state.attributes ?? {});
|
||||
crystalsPerSec *= am.crystalsPerSecMult;
|
||||
insightMult += am.insightMultAdd;
|
||||
contactRateMult *= am.contactRateMult;
|
||||
decodeStepsBonus += am.decodeStepsBonus;
|
||||
|
||||
return {
|
||||
crystalsPerSec,
|
||||
crystalCap,
|
||||
@@ -142,11 +155,20 @@ export function performPrestige(state: GameState): GameState {
|
||||
const newChronicle = [...(state.chronicle || []), chronicleEntry].slice(-50); // 上限 50 条
|
||||
const freshRunStart = createRunStartSnapshot(state);
|
||||
|
||||
// v0.7 角色属性:保留 attributes 数值(跨周目永久),清空当前周目经验进度,
|
||||
// 发放待分配属性点 = 飞升次数 × 2 + 1(用飞升前的次数计算)
|
||||
const migrated = migrateAttributes(state);
|
||||
const attributes = migrated.attributes; // 保留数值
|
||||
const attributeProgress = createInitialAttributeProgress(); // 清空经验
|
||||
const earnedAttrPoints = computePrestigeAttrPoints(state.ascensions ?? 0);
|
||||
const pendingAttrPoints = migrated.pendingAttrPoints + earnedAttrPoints;
|
||||
|
||||
// 保留:fragments, totalDecoded(累计), ascensions+1, blueprints, achievements, constellation, theme/sound, expeditionLog
|
||||
// chronicle(新增), bossKills(累计), starTidesEncountered(累计), runStart(重置为新周目)
|
||||
// 重置:资源、技术、产能、pendingCrystals、activePuzzle、activeExpedition、contact、lastTick、energy、星潮
|
||||
// attributes(永久保留), pendingAttrPoints(累加)
|
||||
// 重置:资源、技术、产能、pendingCrystals、activePuzzle、activeExpedition、contact、lastTick、energy、星潮、attributeProgress
|
||||
const fresh = createInitialState();
|
||||
const stats = recomputeStats({ tech: {}, blueprints, achievements: state.achievements, constellation: state.constellation });
|
||||
const stats = recomputeStats({ tech: {}, blueprints, achievements: state.achievements, constellation: state.constellation, attributes });
|
||||
return {
|
||||
...fresh,
|
||||
fragments: state.fragments,
|
||||
@@ -174,6 +196,10 @@ export function performPrestige(state: GameState): GameState {
|
||||
// 星潮:飞升后清空,lastTideEnd 设为现在,使首次星潮在 firstDelay 后触发
|
||||
activeTide: null,
|
||||
lastTideEnd: Date.now(),
|
||||
// v0.7 角色属性
|
||||
attributes,
|
||||
attributeProgress,
|
||||
pendingAttrPoints,
|
||||
...stats,
|
||||
};
|
||||
}
|
||||
@@ -194,6 +220,20 @@ export function createInitialState(): GameState {
|
||||
lastEnergyTick: Date.now(),
|
||||
createdAt: Date.now(),
|
||||
lastTick: Date.now(),
|
||||
// v0.7 角色属性:每次创建都生成全新对象,避免引用共享
|
||||
attributes: {
|
||||
exploration: 0,
|
||||
wisdom: 0,
|
||||
courage: 0,
|
||||
inspiration: 0,
|
||||
},
|
||||
attributeProgress: {
|
||||
exploration: { exp: 0, level: 0 },
|
||||
wisdom: { exp: 0, level: 0 },
|
||||
courage: { exp: 0, level: 0 },
|
||||
inspiration: { exp: 0, level: 0 },
|
||||
},
|
||||
pendingAttrPoints: 0,
|
||||
} as GameState;
|
||||
}
|
||||
|
||||
|
||||
@@ -142,6 +142,11 @@ export interface GameState {
|
||||
chronicle: ChronicleEntry[];
|
||||
runStart: RunStartSnapshot;
|
||||
|
||||
// 角色属性(v0.7 P1 — 四维属性系统)
|
||||
attributes: import("./attributes").CharacterAttributes;
|
||||
attributeProgress: import("./attributes").AttributeProgress;
|
||||
pendingAttrPoints: number;
|
||||
|
||||
// 元
|
||||
lastTick: number;
|
||||
createdAt: number;
|
||||
|
||||
Reference in New Issue
Block a user