3d8566bc-063f-41e8-8934-68c9ac5b8211
This commit is contained in:
+59
-7
@@ -41,6 +41,7 @@ import {
|
||||
computeEnergyRegen,
|
||||
EXPEDITION_CONFIG,
|
||||
} from "@/lib/game/expedition";
|
||||
import { ACHIEVEMENTS, type Achievement } from "@/lib/game/achievements";
|
||||
|
||||
interface GameActions {
|
||||
// 生命周期
|
||||
@@ -73,6 +74,10 @@ interface GameActions {
|
||||
// 飞升
|
||||
doPrestige: () => { newBp: number } | null;
|
||||
|
||||
// 成就
|
||||
checkAchievements: () => Achievement[];
|
||||
consumeAchievementQueue: () => Achievement[];
|
||||
|
||||
// 设置
|
||||
toggleTheme: () => void;
|
||||
toggleSound: () => void;
|
||||
@@ -86,6 +91,7 @@ type Store = GameState & GameActions & {
|
||||
_lastSpawn: number;
|
||||
_combo: number;
|
||||
_lastPulse: number;
|
||||
_achievementQueue: Achievement[];
|
||||
};
|
||||
|
||||
/** 计算并写回产能字段 */
|
||||
@@ -122,6 +128,7 @@ export const useGameStore = create<Store>()(
|
||||
_lastSpawn: Date.now(),
|
||||
_combo: 0,
|
||||
_lastPulse: 0,
|
||||
_achievementQueue: [],
|
||||
|
||||
init: () => {
|
||||
const s = get();
|
||||
@@ -141,6 +148,8 @@ export const useGameStore = create<Store>()(
|
||||
pendingCrystals: [...s.pendingCrystals, crystal].slice(-CRYSTAL_SPAWN.maxPending),
|
||||
});
|
||||
}
|
||||
// 兼容旧存档:补全 achievements 字段
|
||||
const achievements = s.achievements ?? {};
|
||||
// 首次进入:补发离线收益
|
||||
const elapsed = Math.max(0, (now - s.lastTick) / 1000);
|
||||
if (elapsed > 5) {
|
||||
@@ -151,20 +160,21 @@ export const useGameStore = create<Store>()(
|
||||
crystals: Math.min(s.crystalCap, s.crystals + gain),
|
||||
lastTick: now,
|
||||
activePuzzle,
|
||||
...syncStats({ tech: s.tech, blueprints: s.blueprints }),
|
||||
achievements,
|
||||
...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements }),
|
||||
});
|
||||
} else {
|
||||
set({ lastTick: now, activePuzzle, ...syncStats({ tech: s.tech, blueprints: s.blueprints }) });
|
||||
set({ lastTick: now, activePuzzle, achievements, ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements }) });
|
||||
}
|
||||
},
|
||||
|
||||
loadOnline: () => {
|
||||
const s = get();
|
||||
set({ ...syncStats({ tech: s.tech, blueprints: s.blueprints }) });
|
||||
set({ ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements }) });
|
||||
},
|
||||
|
||||
hardReset: () => {
|
||||
set({ ...createInitialState(), _lastAutoDecode: Date.now(), _lastSpawn: Date.now(), _combo: 0, _lastPulse: 0 });
|
||||
set({ ...createInitialState(), _lastAutoDecode: Date.now(), _lastSpawn: Date.now(), _combo: 0, _lastPulse: 0, _achievementQueue: [] });
|
||||
},
|
||||
|
||||
tick: (now) => {
|
||||
@@ -466,7 +476,7 @@ export const useGameStore = create<Store>()(
|
||||
const next = performPrestige(s);
|
||||
set({
|
||||
...next,
|
||||
...syncStats({ tech: next.tech, blueprints: next.blueprints }),
|
||||
...syncStats({ tech: next.tech, blueprints: next.blueprints, achievements: next.achievements }),
|
||||
_lastAutoDecode: Date.now(),
|
||||
_lastSpawn: Date.now(),
|
||||
_combo: 0,
|
||||
@@ -475,6 +485,48 @@ export const useGameStore = create<Store>()(
|
||||
return { newBp };
|
||||
},
|
||||
|
||||
checkAchievements: () => {
|
||||
const s = get();
|
||||
const newlyUnlocked: Achievement[] = [];
|
||||
const updated = { ...s.achievements };
|
||||
let crystals = s.crystals;
|
||||
let insights = s.insights;
|
||||
let contact = s.contact;
|
||||
let statsDirty = false;
|
||||
for (const a of ACHIEVEMENTS) {
|
||||
if (updated[a.id]) continue;
|
||||
if (a.check(s)) {
|
||||
updated[a.id] = true;
|
||||
newlyUnlocked.push(a);
|
||||
// 发放即时奖励
|
||||
if (a.reward.crystals) crystals += a.reward.crystals;
|
||||
if (a.reward.insights) insights += a.reward.insights;
|
||||
if (a.reward.contact) contact = Math.min(100, contact + a.reward.contact);
|
||||
if (a.reward.crystalsPerSecPct || a.reward.insightPct) statsDirty = true;
|
||||
}
|
||||
}
|
||||
if (newlyUnlocked.length === 0) return [];
|
||||
set({
|
||||
achievements: updated,
|
||||
crystals,
|
||||
insights,
|
||||
contact,
|
||||
...(statsDirty
|
||||
? syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: updated })
|
||||
: {}),
|
||||
_achievementQueue: [...s._achievementQueue, ...newlyUnlocked],
|
||||
});
|
||||
return newlyUnlocked;
|
||||
},
|
||||
|
||||
consumeAchievementQueue: () => {
|
||||
const s = get();
|
||||
if (s._achievementQueue.length === 0) return [];
|
||||
const items = s._achievementQueue;
|
||||
set({ _achievementQueue: [] });
|
||||
return items;
|
||||
},
|
||||
|
||||
canPrestige: () => get().contact >= CONTACT.prestigeMin,
|
||||
|
||||
toggleTheme: () => set({ theme: get().theme === "dark" ? "light" : "dark" }),
|
||||
@@ -485,8 +537,8 @@ export const useGameStore = create<Store>()(
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
// 不持久化临时字段
|
||||
partialize: (s) => {
|
||||
const { _lastAutoDecode, _lastSpawn, _combo, _lastPulse, ...rest } = s;
|
||||
void _lastAutoDecode; void _lastSpawn; void _combo; void _lastPulse;
|
||||
const { _lastAutoDecode, _lastSpawn, _combo, _lastPulse, _achievementQueue, ...rest } = s;
|
||||
void _lastAutoDecode; void _lastSpawn; void _combo; void _lastPulse; void _achievementQueue;
|
||||
return rest as GameState;
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user