Files
echo-nexus/src/lib/game/constellation.ts
T

216 lines
9.4 KiB
TypeScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 回响星核 / Echo Nexus — 星图天文台:飞升后元进程(Meta-Progression
// 设计动机:回应 Issue #1「就一直连连看?」——飞升后玩家从 3 个随机星座天赋中选 1,
// 跨周目永久生效。增加策略选择 + 视觉变化(动态星图)。
import type { GameState } from "./types";
/** 星座天赋类别(6 类,每类 3 个天赋 = 18 个) */
export type ConstellationCategory =
| "mining" // 翠 - 采矿
| "decoding" // 玫 - 解码
| "expedition" // 琥 - 探险
| "contact" // 紫 - 接触
| "economy" // 灰 - 经济
| "cosmic"; // 金 - 宇宙
/** 天赋效果(聚合到 recomputeStats 或具体动作处) */
export interface ConstellationPerk {
id: string;
category: ConstellationCategory;
name: string;
desc: string;
/** 顺序序号(同类别 1/2/3,决定星座内的位置) */
order: number;
}
/** 类别可视化(用于 Canvas 与 CSS */
export const CONSTELLATION_CATEGORY_META: Record<
ConstellationCategory,
{ name: string; color: string; glow: string; hex: string; icon: string; desc: string }
> = {
mining: { name: "永动矿脉", color: "emerald", glow: "rgba(52,211,153,0.55)", hex: "#34d399", icon: "Pickaxe", desc: "晶体产能与仓储" },
decoding: { name: "光谱矩阵", color: "rose", glow: "rgba(251,113,133,0.55)", hex: "#fb7185", icon: "ScanLine", desc: "解码效率与脉冲" },
expedition: { name: "远征星图", color: "amber", glow: "rgba(251,191,36,0.55)", hex: "#fbbf24", icon: "Rocket", desc: "探险力与生命" },
contact: { name: "接触回响", color: "fuchsia", glow: "rgba(232,121,249,0.55)", hex: "#e879f9", icon: "Sparkles", desc: "接触进度与飞升" },
economy: { name: "虚空市场", color: "slate", glow: "rgba(148,163,184,0.55)", hex: "#94a3b8", icon: "Coins", desc: "经济与离线" },
cosmic: { name: "宇宙回响", color: "amber", glow: "rgba(251,191,36,0.55)", hex: "#fcd34d", icon: "Star", desc: "全局综合增益" },
};
/** 18 个星座天赋(6 类 × 3) */
export const CONSTELLATION_PERKS: ConstellationPerk[] = [
// 翠 · 永动矿脉
{ id: "c_min_1", category: "mining", order: 1, name: "永动钻头", desc: "晶体/秒 +20%" },
{ id: "c_min_2", category: "mining", order: 2, name: "深井网络", desc: "仓库上限 +50%" },
{ id: "c_min_3", category: "mining", order: 3, name: "谐振熔炉", desc: "主动脉冲威力 +50%" },
// 玫 · 光谱矩阵
{ id: "c_dec_1", category: "decoding", order: 1, name: "光谱记忆", desc: "洞见倍率 +15%" },
{ id: "c_dec_2", category: "decoding", order: 2, name: "步幅延展", desc: "解码步数上限 +1" },
{ id: "c_dec_3", category: "decoding", order: 3, name: "自动校准", desc: "自动解码周期 -3s" },
// 琥 · 远征星图
{ id: "c_exp_1", category: "expedition", order: 1, name: "维生护盾", desc: "探险生命 +25%" },
{ id: "c_exp_2", category: "expedition", order: 2, name: "信标矩阵", desc: "探险力 +20%" },
{ id: "c_exp_3", category: "expedition", order: 3, name: "能量共振", desc: "能量上限 +1" },
// 紫 · 接触回响
{ id: "c_con_1", category: "contact", order: 1, name: "接触共鸣", desc: "接触进度率 +20%" },
{ id: "c_con_2", category: "contact", order: 2, name: "飞升加速", desc: "飞升所需接触 -10" },
{ id: "c_con_3", category: "contact", order: 3, name: "蓝图回响", desc: "每个蓝图额外 +2% 全属性" },
// 灰 · 虚空市场
{ id: "c_eco_1", category: "economy", order: 1, name: "离线缓存", desc: "离线效率 +15%" },
{ id: "c_eco_2", category: "economy", order: 2, name: "晶体富集", desc: "T2/T3 晶体出现率 +8%" },
{ id: "c_eco_3", category: "economy", order: 3, name: "星潮引导", desc: "星潮间隙 -10s" },
// 金 · 宇宙回响
{ id: "c_cos_1", category: "cosmic", order: 1, name: "飞升礼包", desc: "每次飞升后获得 +30 晶体 / +5 洞见" },
{ id: "c_cos_2", category: "cosmic", order: 2, name: "二周目经验", desc: "解码奖励 +10%" },
{ id: "c_cos_3", category: "cosmic", order: 3, name: "全息共振", desc: "所有产能与脉冲 +8%" },
];
/** 由 ID 取天赋 */
export function getPerk(id: string): ConstellationPerk | undefined {
return CONSTELLATION_PERKS.find((p) => p.id === id);
}
/** 聚合玩家已解锁天赋的总修饰器(供 recomputeStats / 具体动作使用) */
export interface ConstellationModifiers {
crystalsPerSecMult: number;
crystalCapMult: number;
pulsePowerMult: number;
insightMultAdd: number;
decodeStepsBonus: number;
autoDecodeIntervalDeltaSec: number;
expeditionHpMult: number;
expeditionPowerMult: number;
energyMaxBonus: number;
contactRateMult: number;
prestigeContactMinDelta: number;
perBlueprintAllStatsPct: number;
offlineEffBonus: number;
t2t3BonusRate: number;
tideGapDeltaSec: number;
prestigeStartCrystals: number;
prestigeStartInsights: number;
decodeRewardMult: number;
allProductionMult: number;
}
export function constellationBonuses(perks: string[]): ConstellationModifiers {
const owned = new Set(perks);
const m: ConstellationModifiers = {
crystalsPerSecMult: 1,
crystalCapMult: 1,
pulsePowerMult: 1,
insightMultAdd: 0,
decodeStepsBonus: 0,
autoDecodeIntervalDeltaSec: 0,
expeditionHpMult: 1,
expeditionPowerMult: 1,
energyMaxBonus: 0,
contactRateMult: 1,
prestigeContactMinDelta: 0,
perBlueprintAllStatsPct: 0,
offlineEffBonus: 0,
t2t3BonusRate: 0,
tideGapDeltaSec: 0,
prestigeStartCrystals: 0,
prestigeStartInsights: 0,
decodeRewardMult: 1,
allProductionMult: 1,
};
if (owned.has("c_min_1")) m.crystalsPerSecMult += 0.2;
if (owned.has("c_min_2")) m.crystalCapMult += 0.5;
if (owned.has("c_min_3")) m.pulsePowerMult += 0.5;
if (owned.has("c_dec_1")) m.insightMultAdd += 0.15;
if (owned.has("c_dec_2")) m.decodeStepsBonus += 1;
if (owned.has("c_dec_3")) m.autoDecodeIntervalDeltaSec -= 3;
if (owned.has("c_exp_1")) m.expeditionHpMult += 0.25;
if (owned.has("c_exp_2")) m.expeditionPowerMult += 0.2;
if (owned.has("c_exp_3")) m.energyMaxBonus += 1;
if (owned.has("c_con_1")) m.contactRateMult += 0.2;
if (owned.has("c_con_2")) m.prestigeContactMinDelta -= 10;
if (owned.has("c_con_3")) m.perBlueprintAllStatsPct += 2;
if (owned.has("c_eco_1")) m.offlineEffBonus += 0.15;
if (owned.has("c_eco_2")) m.t2t3BonusRate += 0.08;
if (owned.has("c_eco_3")) m.tideGapDeltaSec -= 10;
if (owned.has("c_cos_1")) {
m.prestigeStartCrystals += 30;
m.prestigeStartInsights += 5;
}
if (owned.has("c_cos_2")) m.decodeRewardMult += 0.1;
if (owned.has("c_cos_3")) m.allProductionMult += 0.08;
return m;
}
/** 生成 3 个可选天赋(从未拥有的中随机抽取,保证类别多样性) */
export function rollPerkChoices(
owned: string[],
rng: () => number = Math.random
): string[] {
const ownedSet = new Set(owned);
const pool = CONSTELLATION_PERKS.filter((p) => !ownedSet.has(p.id));
if (pool.length === 0) return [];
// 洗牌
const shuffled = [...pool].sort(() => rng() - 0.5);
// 取前 3 个,但尽量保证不同类别
const picked: ConstellationPerk[] = [];
const usedCats = new Set<ConstellationCategory>();
// 第一轮:每类最多 1 个
for (const p of shuffled) {
if (picked.length >= 3) break;
if (!usedCats.has(p.category)) {
picked.push(p);
usedCats.add(p.category);
}
}
// 第二轮:补齐
for (const p of shuffled) {
if (picked.length >= 3) break;
if (!picked.includes(p)) picked.push(p);
}
return picked.slice(0, 3).map((p) => p.id);
}
/** 给定类别取该类别内已解锁的天赋 order 数 */
export function countUnlockedInCategory(perks: string[], cat: ConstellationCategory): number {
const owned = new Set(perks);
return CONSTELLATION_PERKS.filter((p) => p.category === cat && owned.has(p.id)).length;
}
/** 计算总进度(已解锁 / 18) */
export function constellationProgress(perks: string[]): { unlocked: number; total: number } {
return { unlocked: perks.length, total: CONSTELLATION_PERKS.length };
}
/** 飞升所需接触进度(受天赋影响) */
export function computePrestigeContactMin(state: GameState): number {
const c = constellationBonuses(state.constellation ?? []);
return Math.max(60, 100 + c.prestigeContactMinDelta);
}
/** 星图布局:6 个星座中心点(极坐标转笛卡尔) */
export function getConstellationLayout(centerX: number, centerY: number, radius: number) {
const cats: ConstellationCategory[] = ["mining", "decoding", "expedition", "contact", "economy", "cosmic"];
return cats.map((cat, i) => {
// 六边形布局:每 60° 一个
const angle = (Math.PI / 3) * i - Math.PI / 2; // 顶部为第一个
return {
category: cat,
cx: centerX + radius * Math.cos(angle),
cy: centerY + radius * Math.sin(angle),
meta: CONSTELLATION_CATEGORY_META[cat],
};
});
}
/** 取某类别内 3 个星点的局部坐标(相对星座中心) */
export function getStarsInCategory(cat: ConstellationCategory, cx: number, cy: number, scale = 1) {
const perks = CONSTELLATION_PERKS.filter((p) => p.category === cat).sort((a, b) => a.order - b.order);
return perks.map((p, i) => {
// 三角形排布:order 1 顶点,2/3 底部
const localY = i === 0 ? -22 * scale : 14 * scale;
const localX = i === 0 ? 0 : (i === 1 ? -18 * scale : 18 * scale);
return {
perk: p,
x: cx + localX,
y: cy + localY,
};
});
}