42773b7e-7147-4ad5-828d-da7fcf0290b8
This commit is contained in:
+116
-17
@@ -42,6 +42,14 @@ import {
|
||||
EXPEDITION_CONFIG,
|
||||
} from "@/lib/game/expedition";
|
||||
import { ACHIEVEMENTS, type Achievement } from "@/lib/game/achievements";
|
||||
import {
|
||||
TIDE_CONFIG,
|
||||
rollTide,
|
||||
getTideModifiers,
|
||||
computeSilenceCompensation,
|
||||
type StarTide,
|
||||
type TideType,
|
||||
} from "@/lib/game/starTide";
|
||||
|
||||
interface GameActions {
|
||||
// 生命周期
|
||||
@@ -53,6 +61,10 @@ interface GameActions {
|
||||
tick: (now: number) => void;
|
||||
pulse: () => { gain: number; combo: number } | null;
|
||||
|
||||
// 星潮
|
||||
tickTide: (now: number) => { started?: TideType; ended?: TideType; silenceCompensation?: number } | null;
|
||||
consumeTideEvents: () => { started?: TideType; ended?: TideType; silenceCompensation?: number }[];
|
||||
|
||||
// 解码
|
||||
startDecode: (crystalId: string) => void;
|
||||
clickNode: (nodeId: number) => { ok: boolean; finished: boolean; solvable?: boolean; failReason?: string };
|
||||
@@ -92,6 +104,7 @@ type Store = GameState & GameActions & {
|
||||
_combo: number;
|
||||
_lastPulse: number;
|
||||
_achievementQueue: Achievement[];
|
||||
_tideEvents: { started?: TideType; ended?: TideType; silenceCompensation?: number }[];
|
||||
};
|
||||
|
||||
/** 计算并写回产能字段 */
|
||||
@@ -129,6 +142,7 @@ export const useGameStore = create<Store>()(
|
||||
_combo: 0,
|
||||
_lastPulse: 0,
|
||||
_achievementQueue: [],
|
||||
_tideEvents: [],
|
||||
|
||||
init: () => {
|
||||
const s = get();
|
||||
@@ -148,8 +162,13 @@ export const useGameStore = create<Store>()(
|
||||
pendingCrystals: [...s.pendingCrystals, crystal].slice(-CRYSTAL_SPAWN.maxPending),
|
||||
});
|
||||
}
|
||||
// 兼容旧存档:补全 achievements 字段
|
||||
// 兼容旧存档:补全 achievements / 星潮 字段
|
||||
const achievements = s.achievements ?? {};
|
||||
const activeTide = s.activeTide ?? null;
|
||||
// lastTideEnd:0 表示从未触发过星潮(首次游玩),保留 0 让 firstDelay 生效
|
||||
const lastTideEndRaw = s.lastTideEnd ?? 0;
|
||||
// 若旧存档有已过期的星潮,清掉
|
||||
const tide = activeTide && activeTide.endsAt > now ? activeTide : null;
|
||||
// 首次进入:补发离线收益
|
||||
const elapsed = Math.max(0, (now - s.lastTick) / 1000);
|
||||
if (elapsed > 5) {
|
||||
@@ -161,20 +180,81 @@ export const useGameStore = create<Store>()(
|
||||
lastTick: now,
|
||||
activePuzzle,
|
||||
achievements,
|
||||
...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements }),
|
||||
activeTide: tide,
|
||||
lastTideEnd: tide ? lastTideEndRaw : lastTideEndRaw,
|
||||
...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements, activeTide: tide }),
|
||||
});
|
||||
} else {
|
||||
set({ lastTick: now, activePuzzle, achievements, ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements }) });
|
||||
set({ lastTick: now, activePuzzle, achievements, activeTide: tide, lastTideEnd: tide ? lastTideEndRaw : lastTideEndRaw, ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements, activeTide: tide }) });
|
||||
}
|
||||
},
|
||||
|
||||
loadOnline: () => {
|
||||
const s = get();
|
||||
set({ ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements }) });
|
||||
set({ ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: s.activeTide }) });
|
||||
},
|
||||
|
||||
hardReset: () => {
|
||||
set({ ...createInitialState(), _lastAutoDecode: Date.now(), _lastSpawn: Date.now(), _combo: 0, _lastPulse: 0, _achievementQueue: [] });
|
||||
set({ ...createInitialState(), _lastAutoDecode: Date.now(), _lastSpawn: Date.now(), _combo: 0, _lastPulse: 0, _achievementQueue: [], _tideEvents: [] });
|
||||
},
|
||||
|
||||
tickTide: (now) => {
|
||||
const s = get();
|
||||
const tide = s.activeTide;
|
||||
// 1) 检查当前星潮是否结束
|
||||
if (tide && now >= tide.endsAt) {
|
||||
const endedType = tide.type;
|
||||
// 寂静期补偿洞见
|
||||
let silenceCompensation = 0;
|
||||
if (tide.type === "silence") {
|
||||
silenceCompensation = computeSilenceCompensation(tide);
|
||||
}
|
||||
const newInsights = s.insights + silenceCompensation;
|
||||
const event: { started?: TideType; ended?: TideType; silenceCompensation?: number } = { ended: endedType };
|
||||
if (silenceCompensation > 0) event.silenceCompensation = silenceCompensation;
|
||||
set({
|
||||
activeTide: null,
|
||||
lastTideEnd: now,
|
||||
insights: newInsights,
|
||||
// 星潮结束后重算 stats(移除 contactRate/insight 修饰)
|
||||
...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: null }),
|
||||
_tideEvents: [...s._tideEvents, event],
|
||||
});
|
||||
return event;
|
||||
}
|
||||
// 2) 检查是否该触发新星潮(间隙已过)
|
||||
if (!tide) {
|
||||
const since = now - s.lastTideEnd;
|
||||
// 首次:以 createdAt 起算 firstDelay;之后以 lastTideEnd 起算 gap
|
||||
const firstStart = s.lastTideEnd === 0 || s.lastTideEnd <= s.createdAt + 1000;
|
||||
const need = firstStart ? TIDE_CONFIG.firstDelay : TIDE_CONFIG.gap;
|
||||
if (since >= need) {
|
||||
const type = rollTide();
|
||||
const newTide: StarTide = {
|
||||
id: `tide_${now}_${Math.random().toString(36).slice(2, 7)}`,
|
||||
type,
|
||||
startedAt: now,
|
||||
endsAt: now + TIDE_CONFIG.duration,
|
||||
};
|
||||
const event: { started?: TideType; ended?: TideType; silenceCompensation?: number } = { started: type };
|
||||
set({
|
||||
activeTide: newTide,
|
||||
// 星潮开始后重算 stats(应用 contactRate/insight 修饰)
|
||||
...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: newTide }),
|
||||
_tideEvents: [...s._tideEvents, event],
|
||||
});
|
||||
return event;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
consumeTideEvents: () => {
|
||||
const s = get();
|
||||
if (s._tideEvents.length === 0) return [];
|
||||
const items = s._tideEvents;
|
||||
set({ _tideEvents: [] });
|
||||
return items;
|
||||
},
|
||||
|
||||
tick: (now) => {
|
||||
@@ -182,11 +262,14 @@ export const useGameStore = create<Store>()(
|
||||
const dt = Math.max(0, (now - s.lastTick) / 1000);
|
||||
if (dt <= 0) return;
|
||||
|
||||
// 星潮产能修饰(即时乘)
|
||||
const tideMod = getTideModifiers(s.activeTide);
|
||||
const effCps = s.crystalsPerSec * tideMod.crystalsPerSecMult;
|
||||
// 产能累加(仅闲置产能受仓库上限;探险奖励可超出)
|
||||
const newCrystals =
|
||||
s.crystals >= s.crystalCap
|
||||
? s.crystals // 已达/超上限,不再自动产出
|
||||
: Math.min(s.crystalCap, s.crystals + s.crystalsPerSec * dt);
|
||||
: Math.min(s.crystalCap, s.crystals + effCps * dt);
|
||||
|
||||
// 自动产出待解码晶体(每 baseInterval 秒一颗,受技术/飞升略提速)
|
||||
const bpBoost = 1 + s.blueprints.length * 0.03;
|
||||
@@ -238,7 +321,9 @@ export const useGameStore = create<Store>()(
|
||||
combo = Math.min(10, s._combo + 1);
|
||||
}
|
||||
const mult = 1 + (combo - 1) * 0.15;
|
||||
const gain = s.pulsePower * mult;
|
||||
// 星潮脉冲威力修饰
|
||||
const tideMod = getTideModifiers(s.activeTide);
|
||||
const gain = s.pulsePower * mult * tideMod.pulsePowerMult;
|
||||
set({
|
||||
crystals: Math.min(s.crystalCap, s.crystals + gain),
|
||||
_combo: combo,
|
||||
@@ -266,8 +351,14 @@ export const useGameStore = create<Store>()(
|
||||
const res = tryClickNode(puzzle, nodeId);
|
||||
if (res.ok) {
|
||||
if (res.finished) {
|
||||
// 结算奖励
|
||||
const rewards = decodeRewards(puzzle.tier, s.insightMult, s.contactRateMult);
|
||||
// 结算奖励(星潮解码奖励修饰)
|
||||
const tideMod = getTideModifiers(s.activeTide);
|
||||
const base = decodeRewards(puzzle.tier, s.insightMult, s.contactRateMult);
|
||||
const rewards = {
|
||||
crystals: Math.round(base.crystals * tideMod.decodeRewardMult),
|
||||
insights: Math.round(base.insights * tideMod.decodeRewardMult),
|
||||
contact: +(base.contact * tideMod.decodeRewardMult).toFixed(2),
|
||||
};
|
||||
const newTotal = s.totalDecoded + 1;
|
||||
const newContact = Math.min(100, s.contact + rewards.contact);
|
||||
const newInsights = s.insights + rewards.insights;
|
||||
@@ -336,7 +427,13 @@ export const useGameStore = create<Store>()(
|
||||
const idx = s.pendingCrystals.findIndex((c) => c.tier === 1);
|
||||
if (idx < 0) return;
|
||||
const crystal = s.pendingCrystals[idx];
|
||||
const rewards = decodeRewards(1, s.insightMult, s.contactRateMult);
|
||||
const tideMod = getTideModifiers(s.activeTide);
|
||||
const base = decodeRewards(1, s.insightMult, s.contactRateMult);
|
||||
const rewards = {
|
||||
crystals: Math.round(base.crystals * tideMod.decodeRewardMult),
|
||||
insights: Math.round(base.insights * tideMod.decodeRewardMult),
|
||||
contact: +(base.contact * tideMod.decodeRewardMult).toFixed(2),
|
||||
};
|
||||
const newTotal = s.totalDecoded + 1;
|
||||
const tentative: GameState = { ...s, totalDecoded: newTotal, fragments: { ...s.fragments } };
|
||||
checkFragments(tentative);
|
||||
@@ -362,7 +459,7 @@ export const useGameStore = create<Store>()(
|
||||
set({
|
||||
insights: s.insights - node.cost,
|
||||
tech: newTech,
|
||||
...syncStats({ tech: newTech, blueprints: s.blueprints }),
|
||||
...syncStats({ tech: newTech, blueprints: s.blueprints, achievements: s.achievements, activeTide: s.activeTide }),
|
||||
});
|
||||
return true;
|
||||
},
|
||||
@@ -376,8 +473,9 @@ export const useGameStore = create<Store>()(
|
||||
if (s.energy < EXPEDITION_CONFIG.energyCost) {
|
||||
return { ok: false, reason: "能量不足" };
|
||||
}
|
||||
const power = computeExpeditionPower(s);
|
||||
const hp = computeExpeditionHp(s);
|
||||
const tideMod = getTideModifiers(s.activeTide);
|
||||
const power = computeExpeditionPower(s) + tideMod.expeditionPowerBonus;
|
||||
const hp = computeExpeditionHp(s) + tideMod.expeditionHpBonus;
|
||||
const seed = Math.floor(Math.random() * 1e9);
|
||||
const expedition = generateExpedition(seed, power, hp);
|
||||
set({
|
||||
@@ -476,11 +574,12 @@ export const useGameStore = create<Store>()(
|
||||
const next = performPrestige(s);
|
||||
set({
|
||||
...next,
|
||||
...syncStats({ tech: next.tech, blueprints: next.blueprints, achievements: next.achievements }),
|
||||
...syncStats({ tech: next.tech, blueprints: next.blueprints, achievements: next.achievements, activeTide: next.activeTide }),
|
||||
_lastAutoDecode: Date.now(),
|
||||
_lastSpawn: Date.now(),
|
||||
_combo: 0,
|
||||
_lastPulse: 0,
|
||||
_tideEvents: [],
|
||||
});
|
||||
return { newBp };
|
||||
},
|
||||
@@ -512,7 +611,7 @@ export const useGameStore = create<Store>()(
|
||||
insights,
|
||||
contact,
|
||||
...(statsDirty
|
||||
? syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: updated })
|
||||
? syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: updated, activeTide: s.activeTide })
|
||||
: {}),
|
||||
_achievementQueue: [...s._achievementQueue, ...newlyUnlocked],
|
||||
});
|
||||
@@ -537,8 +636,8 @@ export const useGameStore = create<Store>()(
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
// 不持久化临时字段
|
||||
partialize: (s) => {
|
||||
const { _lastAutoDecode, _lastSpawn, _combo, _lastPulse, _achievementQueue, ...rest } = s;
|
||||
void _lastAutoDecode; void _lastSpawn; void _combo; void _lastPulse; void _achievementQueue;
|
||||
const { _lastAutoDecode, _lastSpawn, _combo, _lastPulse, _achievementQueue, _tideEvents, ...rest } = s;
|
||||
void _lastAutoDecode; void _lastSpawn; void _combo; void _lastPulse; void _achievementQueue; void _tideEvents;
|
||||
return rest as GameState;
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user