Gitea Issue #2 修复: Bug: 1. 探险只能一次 → 能量恢复 45→20s, 初始能量 3→5, 上限 5→8 新增 lastExpeditionSummary 显示上次结果 新增 dismissExpedition action 2. 元素偏移/遮挡 → 标签页 grid-cols-8 → 响应式 grid-cols-4/sm:8 3. 成就UI遮挡 → max-h 440→520, min-h 提高 4. 提示框遮挡 → toast top-0→top-14 避开 header, TOAST_LIMIT 1→3 玩法改进: 1. 中央晶体球 → 下方新增 4 指标收益面板(产能/脉冲/连击/仓库) 2. 技术树 12→20 节点 (每分支 3→5 级) 3. 成就解锁 → 全屏居中特效通知 (framer-motion 弹窗) 4. 叙事融入 → 新建 narrative.ts, 脉冲/技术树注入世界观文本
309 lines
8.4 KiB
TypeScript
Executable File
309 lines
8.4 KiB
TypeScript
Executable File
// 回响星核 / Echo Nexus — 成就系统(数据驱动)
|
|
import type { GameState } from "./types";
|
|
|
|
/** 成就奖励类型 */
|
|
export interface AchievementReward {
|
|
crystals?: number;
|
|
insights?: number;
|
|
contact?: number;
|
|
/** 永久产能加成(百分比,叠加到所有周目) */
|
|
crystalsPerSecPct?: number;
|
|
insightPct?: number;
|
|
}
|
|
|
|
export interface Achievement {
|
|
id: string;
|
|
name: string;
|
|
desc: string;
|
|
/** 图标 emoji(轻量,无需图标库) */
|
|
icon: string;
|
|
/** 颜色(用于徽章) */
|
|
color: string;
|
|
/** 判定函数:返回 true 表示达成 */
|
|
check: (s: GameState) => boolean;
|
|
reward: AchievementReward;
|
|
/** 奖励文本(展示用) */
|
|
rewardText: string;
|
|
}
|
|
|
|
export const ACHIEVEMENTS: Achievement[] = [
|
|
{
|
|
id: "ach_first_pulse",
|
|
name: "初次触碰",
|
|
desc: "发起第一次脉冲扫描",
|
|
icon: "✦",
|
|
color: "#34d399",
|
|
check: (s) => s.totalDecoded >= 0 || s.crystals > 0,
|
|
reward: { crystals: 5 },
|
|
rewardText: "+5 晶体",
|
|
},
|
|
{
|
|
id: "ach_first_decode",
|
|
name: "谐振初鸣",
|
|
desc: "成功解码第一颗记忆晶体",
|
|
icon: "◈",
|
|
color: "#fb7185",
|
|
check: (s) => s.totalDecoded >= 1,
|
|
reward: { insights: 3 },
|
|
rewardText: "+3 洞见",
|
|
},
|
|
{
|
|
id: "ach_decoded_10",
|
|
name: "回响解码者",
|
|
desc: "累计解码 10 颗晶体",
|
|
icon: "❖",
|
|
color: "#fbbf24",
|
|
check: (s) => s.totalDecoded >= 10,
|
|
reward: { insights: 15, crystalsPerSecPct: 5 },
|
|
rewardText: "+15 洞见 · 产能 +5%",
|
|
},
|
|
{
|
|
id: "ach_decoded_25",
|
|
name: "记忆织匠",
|
|
desc: "累计解码 25 颗晶体",
|
|
icon: "✺",
|
|
color: "#e879f9",
|
|
check: (s) => s.totalDecoded >= 25,
|
|
reward: { insights: 40, crystalsPerSecPct: 8 },
|
|
rewardText: "+40 洞见 · 产能 +8%",
|
|
},
|
|
{
|
|
id: "ach_decoded_50",
|
|
name: "星核解密师",
|
|
desc: "累计解码 50 颗晶体",
|
|
icon: "✷",
|
|
color: "#34d399",
|
|
check: (s) => s.totalDecoded >= 50,
|
|
reward: { insights: 100, crystalsPerSecPct: 12, insightPct: 10 },
|
|
rewardText: "+100 洞见 · 产能 +12% · 洞见 +10%",
|
|
},
|
|
{
|
|
id: "ach_tech_3",
|
|
name: "初窥门径",
|
|
desc: "解锁 3 项技术",
|
|
icon: "⚙",
|
|
color: "#fbbf24",
|
|
check: (s) => Object.values(s.tech).filter((v) => v > 0).length >= 3,
|
|
reward: { insights: 20 },
|
|
rewardText: "+20 洞见",
|
|
},
|
|
{
|
|
id: "ach_tech_all",
|
|
name: "全谱精通",
|
|
desc: "解锁全部 20 项技术",
|
|
icon: "⬡",
|
|
color: "#e879f9",
|
|
check: (s) => Object.values(s.tech).filter((v) => v > 0).length >= 20,
|
|
reward: { crystalsPerSecPct: 15, insightPct: 15 },
|
|
rewardText: "产能 +15% · 洞见 +15%",
|
|
},
|
|
{
|
|
id: "ach_frag_4",
|
|
name: "残篇拾遗",
|
|
desc: "拼凑 4 段记忆碎片",
|
|
icon: "▤",
|
|
color: "#fb7185",
|
|
check: (s) => Object.values(s.fragments).filter(Boolean).length >= 4,
|
|
reward: { contact: 10, insights: 25 },
|
|
rewardText: "+10 接触 · +25 洞见",
|
|
},
|
|
{
|
|
id: "ach_frag_all",
|
|
name: "回响全谱",
|
|
desc: "拼凑全部首纪元记忆碎片",
|
|
icon: "▦",
|
|
color: "#34d399",
|
|
check: (s) => Object.values(s.fragments).filter(Boolean).length >= 8,
|
|
reward: { contact: 25, crystalsPerSecPct: 10 },
|
|
rewardText: "+25 接触 · 产能 +10%",
|
|
},
|
|
{
|
|
id: "ach_exp_1",
|
|
name: "初探遗迹",
|
|
desc: "完成第一次遗迹探险",
|
|
icon: "▲",
|
|
color: "#fbbf24",
|
|
check: (s) => s.totalExpeditions >= 1,
|
|
reward: { insights: 10 },
|
|
rewardText: "+10 洞见",
|
|
},
|
|
{
|
|
id: "ach_exp_5",
|
|
name: "遗迹猎手",
|
|
desc: "累计出发 5 次探险",
|
|
icon: "⬢",
|
|
color: "#fb7185",
|
|
check: (s) => s.totalExpeditions >= 5,
|
|
reward: { insights: 30, crystalsPerSecPct: 5 },
|
|
rewardText: "+30 洞见 · 产能 +5%",
|
|
},
|
|
{
|
|
id: "ach_prestige_1",
|
|
name: "初次接触",
|
|
desc: "完成第一次飞升",
|
|
icon: "✧",
|
|
color: "#e879f9",
|
|
check: (s) => s.ascensions >= 1,
|
|
reward: { crystalsPerSecPct: 10, insightPct: 10 },
|
|
rewardText: "产能 +10% · 洞见 +10%",
|
|
},
|
|
{
|
|
id: "ach_prestige_3",
|
|
name: "维度行者",
|
|
desc: "累计飞升 3 次",
|
|
icon: "✶",
|
|
color: "#34d399",
|
|
check: (s) => s.ascensions >= 3,
|
|
reward: { crystalsPerSecPct: 20, insightPct: 20 },
|
|
rewardText: "产能 +20% · 洞见 +20%",
|
|
},
|
|
{
|
|
id: "ach_warehouse",
|
|
name: "满仓时刻",
|
|
desc: "晶体储量达到仓库上限",
|
|
icon: "▣",
|
|
color: "#fbbf24",
|
|
check: (s) => s.crystals >= s.crystalCap,
|
|
reward: { insights: 8 },
|
|
rewardText: "+8 洞见",
|
|
},
|
|
{
|
|
id: "ach_constellation_1",
|
|
name: "星图初绘",
|
|
desc: "觉醒第 1 道星图天赋",
|
|
icon: "✨",
|
|
color: "#e879f9",
|
|
check: (s) => (s.constellation?.length ?? 0) >= 1,
|
|
reward: { insights: 30, crystalsPerSecPct: 5 },
|
|
rewardText: "+30 洞见 · 产能 +5%",
|
|
},
|
|
{
|
|
id: "ach_constellation_6",
|
|
name: "六分星辉",
|
|
desc: "觉醒 6 道星图天赋(每类各 1 道)",
|
|
icon: "✺",
|
|
color: "#34d399",
|
|
check: (s) => {
|
|
const c = s.constellation ?? [];
|
|
const cats = new Set(c.map((id) => id.split("_")[1]));
|
|
return cats.size >= 6 && c.length >= 6;
|
|
},
|
|
reward: { crystalsPerSecPct: 12, insightPct: 12 },
|
|
rewardText: "产能 +12% · 洞见 +12%",
|
|
},
|
|
{
|
|
id: "ach_chronicle_1",
|
|
name: "首部编年",
|
|
desc: "完成首次飞升,开启编年史第一章",
|
|
icon: "📖",
|
|
color: "#e879f9",
|
|
check: (s) => (s.chronicle?.length ?? 0) >= 1,
|
|
reward: { insights: 40, crystalsPerSecPct: 6 },
|
|
rewardText: "+40 洞见 · 产能 +6%",
|
|
},
|
|
{
|
|
id: "ach_chronicle_3",
|
|
name: "三纪元回响",
|
|
desc: "完成 3 次飞升,编年史铭刻三章",
|
|
icon: "📜",
|
|
color: "#fbbf24",
|
|
check: (s) => (s.chronicle?.length ?? 0) >= 3,
|
|
reward: { crystalsPerSecPct: 10, insightPct: 10 },
|
|
rewardText: "产能 +10% · 洞见 +10%",
|
|
},
|
|
{
|
|
id: "ach_chronicle_5",
|
|
name: "五纪元闭环",
|
|
desc: "完成 5 次飞升,走完一轮纪元循环",
|
|
icon: "♾",
|
|
color: "#fcd34d",
|
|
check: (s) => (s.chronicle?.length ?? 0) >= 5,
|
|
reward: { crystalsPerSecPct: 15, insightPct: 15 },
|
|
rewardText: "产能 +15% · 洞见 +15%",
|
|
},
|
|
{
|
|
id: "ach_boss_1",
|
|
name: "首杀维度",
|
|
desc: "在遗迹探险中击破首个 BOSS",
|
|
icon: "⚔",
|
|
color: "#fb7185",
|
|
check: (s) => (s.bossKills ?? 0) >= 1,
|
|
reward: { insights: 25, crystalsPerSecPct: 4 },
|
|
rewardText: "+25 洞见 · 产能 +4%",
|
|
},
|
|
{
|
|
id: "ach_boss_5",
|
|
name: "维度猎手",
|
|
desc: "累计击破 5 个 BOSS",
|
|
icon: "⛧",
|
|
color: "#e879f9",
|
|
check: (s) => (s.bossKills ?? 0) >= 5,
|
|
reward: { crystalsPerSecPct: 10, insightPct: 8 },
|
|
rewardText: "产能 +10% · 洞见 +8%",
|
|
},
|
|
{
|
|
id: "ach_tides_all",
|
|
name: "星潮亲历者",
|
|
desc: "经历全部 9 种星潮事件",
|
|
icon: "🌊",
|
|
color: "#34d399",
|
|
check: (s) => (s.starTidesEncountered?.length ?? 0) >= 9,
|
|
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%",
|
|
},
|
|
];
|
|
|
|
/** 计算成就提供的永久加成(跨周目保留) */
|
|
export function achievementBonuses(unlocked: Record<string, boolean>): {
|
|
crystalsPerSecPct: number;
|
|
insightPct: number;
|
|
} {
|
|
let crystalsPerSecPct = 0;
|
|
let insightPct = 0;
|
|
for (const a of ACHIEVEMENTS) {
|
|
if (!unlocked[a.id]) continue;
|
|
crystalsPerSecPct += a.reward.crystalsPerSecPct ?? 0;
|
|
insightPct += a.reward.insightPct ?? 0;
|
|
}
|
|
return { crystalsPerSecPct, insightPct };
|
|
}
|