6f5491c0-6c94-4f8e-8ad3-136d04ab862f
This commit is contained in:
+136
-5
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user