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

210 lines
5.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 — 星潮事件系统(周期性全局玩法修饰)
//
// 深空中偶发的「星潮」会短暂改变物理法则,给放置循环注入动态变化。
// 每 ~40s 间隙后触发一次持续 ~75s 的星潮,6 种类型各有正负效果。
export type TideType =
| "crystal" // 晶体潮:产能 ×2
| "resonance" // 谐振风暴:解码奖励 +60%
| "ruins" // 遗迹共振:探险力 +5、生命 +30
| "void" // 虚空低语:洞见 ×2
| "core" // 星核悸动:接触率 ×3、脉冲 ×2
| "silence"; // 寂静期:产能 ×0.5,但结束时补偿洞见
export interface StarTide {
type: TideType;
startedAt: number;
endsAt: number;
/** 唯一 id,用于 React key */
id: string;
}
export interface TideModifiers {
crystalsPerSecMult: number;
decodeRewardMult: number;
insightMultAdd: number; // 加到 insightMult(百分比,0.5 = +50%
contactRateMult: number;
pulsePowerMult: number;
expeditionPowerBonus: number;
expeditionHpBonus: number;
/** 寂静期结束时补偿的洞见(按持续秒数) */
silenceInsightPerSec?: number;
}
export const TIDE_CONFIG = {
/** 星潮持续时间(毫秒) */
duration: 75000,
/** 星潮之间的间隙(毫秒) */
gap: 40000,
/** 首次星潮触发延迟(毫秒,从 init 起) */
firstDelay: 30000,
};
export const TIDE_EVENTS: Record<
TideType,
{
name: string;
desc: string;
color: string;
glow: string;
icon: string;
/** 是否负面事件(影响 UI 提示语气) */
negative: boolean;
}
> = {
crystal: {
name: "晶体潮",
desc: "深空晶体涌动,采矿产能翻倍",
color: "#34d399",
glow: "rgba(52,211,153,0.5)",
icon: "✺",
negative: false,
},
resonance: {
name: "谐振风暴",
desc: "共振序列被放大,解码奖励 +60%",
color: "#fb7185",
glow: "rgba(251,113,133,0.5)",
icon: "❖",
negative: false,
},
ruins: {
name: "遗迹共振",
desc: "远古遗迹苏醒,探险力 +5、生命 +30",
color: "#fbbf24",
glow: "rgba(251,191,36,0.5)",
icon: "⬢",
negative: false,
},
void: {
name: "虚空低语",
desc: "虚空传来回响,洞见获取翻倍",
color: "#e879f9",
glow: "rgba(232,121,249,0.5)",
icon: "✷",
negative: false,
},
core: {
name: "星核悸动",
desc: "星核震荡,接触率 ×3、脉冲威力 ×2",
color: "#a5f3fc",
glow: "rgba(165,243,252,0.5)",
icon: "✦",
negative: false,
},
silence: {
name: "寂静期",
desc: "虚空沉寂,产能减半;结束时按时长补偿洞见",
color: "#94a3b8",
glow: "rgba(148,163,184,0.5)",
icon: "◌",
negative: true,
},
};
/** 权重表(寂静期概率略低) */
const TIDE_WEIGHTS: Record<TideType, number> = {
crystal: 22,
resonance: 20,
ruins: 16,
void: 18,
core: 14,
silence: 10,
};
export function rollTide(rng: () => number = Math.random): TideType {
const total = Object.values(TIDE_WEIGHTS).reduce((a, b) => a + b, 0);
let r = rng() * total;
for (const k of Object.keys(TIDE_WEIGHTS) as TideType[]) {
r -= TIDE_WEIGHTS[k];
if (r <= 0) return k;
}
return "crystal";
}
/** 计算当前星潮的修饰器 */
export function getTideModifiers(tide: StarTide | null): TideModifiers {
if (!tide) {
return {
crystalsPerSecMult: 1,
decodeRewardMult: 1,
insightMultAdd: 0,
contactRateMult: 1,
pulsePowerMult: 1,
expeditionPowerBonus: 0,
expeditionHpBonus: 0,
};
}
switch (tide.type) {
case "crystal":
return {
crystalsPerSecMult: 2,
decodeRewardMult: 1,
insightMultAdd: 0,
contactRateMult: 1,
pulsePowerMult: 1,
expeditionPowerBonus: 0,
expeditionHpBonus: 0,
};
case "resonance":
return {
crystalsPerSecMult: 1,
decodeRewardMult: 1.6,
insightMultAdd: 0,
contactRateMult: 1,
pulsePowerMult: 1,
expeditionPowerBonus: 0,
expeditionHpBonus: 0,
};
case "ruins":
return {
crystalsPerSecMult: 1,
decodeRewardMult: 1,
insightMultAdd: 0,
contactRateMult: 1,
pulsePowerMult: 1,
expeditionPowerBonus: 5,
expeditionHpBonus: 30,
};
case "void":
return {
crystalsPerSecMult: 1,
decodeRewardMult: 1,
insightMultAdd: 1, // insightMult 翻倍(+100%
contactRateMult: 1,
pulsePowerMult: 1,
expeditionPowerBonus: 0,
expeditionHpBonus: 0,
};
case "core":
return {
crystalsPerSecMult: 1,
decodeRewardMult: 1,
insightMultAdd: 0,
contactRateMult: 3,
pulsePowerMult: 2,
expeditionPowerBonus: 0,
expeditionHpBonus: 0,
};
case "silence":
return {
crystalsPerSecMult: 0.5,
decodeRewardMult: 1,
insightMultAdd: 0,
contactRateMult: 1,
pulsePowerMult: 1,
expeditionPowerBonus: 0,
expeditionHpBonus: 0,
silenceInsightPerSec: 0.4,
};
}
}
/** 寂静期结束时的洞见补偿 */
export function computeSilenceCompensation(tide: StarTide): number {
const mod = getTideModifiers(tide);
if (!mod.silenceInsightPerSec) return 0;
const secs = (tide.endsAt - tide.startedAt) / 1000;
return Math.round(mod.silenceInsightPerSec * secs);
}