42773b7e-7147-4ad5-828d-da7fcf0290b8

This commit is contained in:
2026-06-23 13:52:13 +00:00
parent da18f32df2
commit f3d2e53e4f
16 changed files with 562 additions and 34 deletions
+17
View File
@@ -17,6 +17,8 @@ type SfxName =
| "expeditionDefeat" // 探险失败(下行低音)
| "prestige" // 飞升(宏大扫频)
| "achievement" // 成就解锁(亮丽琶音)
| "tideStart" // 星潮降临(神秘扫频)
| "tideEnd" // 星潮结束(柔和消退)
| "uiHover" // 界面悬停(极轻)
| "uiClick"; // 界面点击(轻确认)
@@ -249,6 +251,21 @@ class AudioEngine {
this.tone(1318.51, 0.4, "triangle", 0.18, 0.24);
break;
}
case "tideStart": {
// 神秘扫频 + 泛音列
this.sweep(330, 660, 0.8, "sine", 0.16);
this.sweep(440, 880, 0.8, "triangle", 0.08, 0.04);
this.tone(523.25, 0.5, "sine", 0.1, 0.2);
this.tone(783.99, 0.6, "sine", 0.08, 0.3);
break;
}
case "tideEnd": {
// 柔和下行消退
this.tone(659.25, 0.3, "sine", 0.14, 0);
this.tone(523.25, 0.35, "sine", 0.12, 0.1);
this.tone(392, 0.5, "triangle", 0.1, 0.22);
break;
}
case "uiHover": {
this.tone(880, 0.05, "sine", 0.05);
break;
+2
View File
@@ -30,6 +30,8 @@ export const INITIAL_STATE = {
lastEnergyTick: Date.now(),
totalExpeditions: 0,
achievements: {},
activeTide: null,
lastTideEnd: 0,
theme: "dark" as const,
soundOn: true,
};
+13 -2
View File
@@ -8,8 +8,9 @@ import {
CONTACT,
} from "./config";
import { achievementBonuses } from "./achievements";
import { getTideModifiers, type StarTide } from "./starTide";
/** 由技术树 + 飞升蓝图 + 成就聚合计算产能字段 */
/** 由技术树 + 飞升蓝图 + 成就 + 星潮聚合计算产能字段 */
export function recomputeStats(state: Partial<GameState>): {
crystalsPerSec: number;
crystalCap: number;
@@ -23,6 +24,7 @@ export function recomputeStats(state: Partial<GameState>): {
const tech = state.tech ?? {};
const bp = state.blueprints?.length ?? 0;
const ach = achievementBonuses(state.achievements ?? {});
const tideMod = getTideModifiers((state.activeTide as StarTide | null) ?? null);
let crystalsPerSec = INITIAL_STATE.crystalsPerSec;
let crystalCap = INITIAL_STATE.crystalCap;
@@ -74,6 +76,10 @@ export function recomputeStats(state: Partial<GameState>): {
crystalsPerSec *= 1 + ach.crystalsPerSecPct / 100;
insightMult *= 1 + ach.insightPct / 100;
// 星潮瞬时修饰(contactRateMult 与 insightMult 进缓存;产能/脉冲/探险在用点处即时乘)
insightMult += tideMod.insightMultAdd;
contactRateMult *= tideMod.contactRateMult;
return {
crystalsPerSec,
crystalCap,
@@ -111,7 +117,7 @@ export function performPrestige(state: GameState): GameState {
].slice(0, PRESTIGE.maxBlueprints);
// 保留:fragments, totalDecoded(累计), ascensions+1, blueprints, achievements, theme/sound, expeditionLog
// 重置:资源、技术、产能、pendingCrystals、activePuzzle、activeExpedition、contact、lastTick、energy
// 重置:资源、技术、产能、pendingCrystals、activePuzzle、activeExpedition、contact、lastTick、energy、星潮
const fresh = createInitialState();
const stats = recomputeStats({ tech: {}, blueprints, achievements: state.achievements });
return {
@@ -128,6 +134,9 @@ export function performPrestige(state: GameState): GameState {
lastEnergyTick: Date.now(),
expeditionLog: state.expeditionLog,
totalExpeditions: state.totalExpeditions,
// 星潮:飞升后清空,lastTideEnd 设为现在,使首次星潮在 firstDelay 后触发
activeTide: null,
lastTideEnd: Date.now(),
...stats,
};
}
@@ -139,6 +148,8 @@ export function createInitialState(): GameState {
tech: {},
fragments: {},
achievements: {},
activeTide: null,
lastTideEnd: Date.now(),
pendingCrystals: [],
activePuzzle: null,
activeExpedition: null,
+209
View File
@@ -0,0 +1,209 @@
// 回响星核 / 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);
}
+4
View File
@@ -128,6 +128,10 @@ export interface GameState {
// 成就
achievements: Record<string, boolean>; // achievementId -> unlocked
// 星潮事件
activeTide: import("./starTide").StarTide | null;
lastTideEnd: number;
// 元
lastTick: number;
createdAt: number;