6f5491c0-6c94-4f8e-8ad3-136d04ab862f

This commit is contained in:
2026-06-23 13:04:15 +00:00
parent f73b96026c
commit 674775be0e
9 changed files with 1007 additions and 26 deletions
+136 -5
View File
@@ -7,6 +7,7 @@ import type {
Crystal,
CrystalTier,
DecodePuzzle,
ExpeditionResult,
} from "@/lib/game/types";
import {
INITIAL_STATE,
@@ -31,6 +32,15 @@ import {
isSolvable,
resetPuzzle as resetPuz,
} from "@/lib/game/decode";
import {
generateExpedition,
resolveNode,
advanceExpedition,
computeExpeditionPower,
computeExpeditionHp,
computeEnergyRegen,
EXPEDITION_CONFIG,
} from "@/lib/game/expedition";
interface GameActions {
// 生命周期
@@ -51,6 +61,12 @@ interface GameActions {
/** 自动解码 T1(技术解锁后由 tick 调用) */
autoDecodeTick: () => void;
// 探险
startExpedition: () => { ok: boolean; reason?: string };
resolveCurrentNode: () => ExpeditionResult | null;
advanceNode: () => void;
abortExpedition: () => void;
// 技术
buyTech: (techId: string) => boolean;
@@ -156,11 +172,11 @@ export const useGameStore = create<Store>()(
const dt = Math.max(0, (now - s.lastTick) / 1000);
if (dt <= 0) return;
// 产能累加(受仓库上限
const newCrystals = Math.min(
s.crystalCap,
s.crystals + s.crystalsPerSec * dt
);
// 产能累加(仅闲置产能受仓库上限;探险奖励可超出
const newCrystals =
s.crystals >= s.crystalCap
? s.crystals // 已达/超上限,不再自动产出
: Math.min(s.crystalCap, s.crystals + s.crystalsPerSec * dt);
// 自动产出待解码晶体(每 baseInterval 秒一颗,受技术/飞升略提速)
const bpBoost = 1 + s.blueprints.length * 0.03;
@@ -182,11 +198,24 @@ export const useGameStore = create<Store>()(
lastSpawn = now;
}
// 能量恢复(探险系统)
let energy = s.energy;
let lastEnergyTick = s.lastEnergyTick;
if (energy < s.energyMax) {
const regen = computeEnergyRegen(lastEnergyTick, now, energy, s.energyMax);
energy = regen.energy;
lastEnergyTick = regen.lastTick;
} else {
lastEnergyTick = now;
}
set({
crystals: newCrystals,
lastTick: now,
pendingCrystals: pending,
_lastSpawn: lastSpawn,
energy,
lastEnergyTick,
});
},
@@ -328,6 +357,108 @@ export const useGameStore = create<Store>()(
return true;
},
// ============ 探险系统 ============
startExpedition: () => {
const s = get();
if (s.activeExpedition && !s.activeExpedition.finished) {
return { ok: false, reason: "已有进行中的探险" };
}
if (s.energy < EXPEDITION_CONFIG.energyCost) {
return { ok: false, reason: "能量不足" };
}
const power = computeExpeditionPower(s);
const hp = computeExpeditionHp(s);
const seed = Math.floor(Math.random() * 1e9);
const expedition = generateExpedition(seed, power, hp);
set({
activeExpedition: expedition,
energy: s.energy - EXPEDITION_CONFIG.energyCost,
totalExpeditions: s.totalExpeditions + 1,
});
return { ok: true };
},
resolveCurrentNode: () => {
const s = get();
if (!s.activeExpedition || s.activeExpedition.finished) return null;
// 深拷贝
const exp = JSON.parse(JSON.stringify(s.activeExpedition));
const result = resolveNode(exp);
// 累计奖励
if (result.crystals) exp.rewards.crystals += result.crystals;
if (result.insights) exp.rewards.insights += result.insights;
if (result.contact) exp.rewards.contact += result.contact;
if (result.fragments) exp.rewards.fragments.push(...result.fragments);
if (result.hpDelta) exp.hp = Math.max(0, Math.min(exp.maxHp, exp.hp + result.hpDelta));
// 实时入账(玩家立即获得)
const newCrystals = s.crystals + (result.crystals || 0);
const newInsights = s.insights + (result.insights || 0);
const newContact = Math.min(100, s.contact + (result.contact || 0));
// 碎片解锁
const newFragments = { ...s.fragments };
if (result.fragments) {
for (const fid of result.fragments) newFragments[fid] = true;
}
// 日志
const logEntry = {
expeditionId: exp.id,
nodeType: exp.nodes[exp.currentNode]?.type || "combat",
result: result.log,
rewards: [
result.crystals ? `+${result.crystals}晶体` : "",
result.insights ? `+${result.insights}洞见` : "",
result.contact ? `+${result.contact.toFixed(1)}接触` : "",
result.hpDelta ? `${result.hpDelta > 0 ? "+" : ""}${result.hpDelta}生命` : "",
].filter(Boolean).join(" "),
timestamp: Date.now(),
};
const newLog = [logEntry, ...s.expeditionLog].slice(0, 30);
if (result.ended) {
// 探险结束(胜利或失败)
exp.finished = true;
}
set({
activeExpedition: exp,
crystals: newCrystals,
insights: newInsights,
contact: newContact,
fragments: newFragments,
expeditionLog: newLog,
});
return result;
},
advanceNode: () => {
const s = get();
if (!s.activeExpedition || s.activeExpedition.finished) return;
const exp = JSON.parse(JSON.stringify(s.activeExpedition));
const node = exp.nodes[exp.currentNode];
if (!node || !node.cleared) return; // 当前节点未结算不能前进
if (exp.currentNode >= exp.nodes.length - 1) return;
exp.currentNode++;
set({ activeExpedition: exp });
},
abortExpedition: () => {
const s = get();
if (!s.activeExpedition) return;
const exp = JSON.parse(JSON.stringify(s.activeExpedition));
exp.finished = true;
const logEntry = {
expeditionId: exp.id,
nodeType: "rest" as const,
result: "探险队主动撤退,保留已获奖励。",
rewards: "",
timestamp: Date.now(),
};
set({
activeExpedition: exp,
expeditionLog: [logEntry, ...s.expeditionLog].slice(0, 30),
});
},
doPrestige: () => {
const s = get();
if (s.contact < CONTACT.prestigeMin) return null;