fix: multiple bug fixes - infinite loop crash, enchant tick handlers, discipline crash, Plasma symbol, desync
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m24s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m24s
- #236: Fix Climb the Spire React #185 infinite loop - removed redundant set() in processCombatTick that caused double Zustand writes per tick - #235: Add enchanting design/prepare/apply tick handlers - extracted to pipelines/enchanting-tick.ts - #235: Fix startApplying not setting currentAction to 'enchant' - #243: Guard discipline store against undefined activeIds/processedPerks from corrupted persisted state - #245: Change Plasma symbol from ⚡ (conflicts with Lightning) to 🔴 - #241: Fix combat store maxFloorReached desync - initialize to 0, reset on exitSpireMode - #239: Fix EffectSelector not rendering when unlockedEffects is empty (fresh game) - Created pipelines/enchanting-tick.ts to keep gameStore.ts under 400 lines
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
// ─── Enchanting Tick Handlers ─────────────────────────────────────────────────
|
||||
// Design → Prepare → Application tick processing for the enchanting pipeline.
|
||||
// Extracted from gameStore.ts to keep the coordinator under the 400-line limit.
|
||||
|
||||
import { HOURS_PER_TICK } from '../../constants';
|
||||
import type { ComputedEffects } from '../../effects/upgrade-effects.types';
|
||||
import { calculateDesignProgress, createCompletedDesignFromProgress } from '../../crafting-design';
|
||||
import { calculatePreparationTick, completePreparation } from '../../crafting-prep';
|
||||
import { calculateApplicationTick, applyEnchantments, updateEnchanterAttunement } from '../../crafting-apply';
|
||||
import { useCraftingStore } from '../craftingStore';
|
||||
import type { TickContext, TickWrites } from '../tick-pipeline';
|
||||
|
||||
interface EnchantingTickParams {
|
||||
ctx: TickContext;
|
||||
effects: ComputedEffects;
|
||||
rawMana: number;
|
||||
addLog: (msg: string) => void;
|
||||
}
|
||||
|
||||
export function processEnchantingTicks(
|
||||
params: EnchantingTickParams,
|
||||
): { rawMana: number; writes: Partial<TickWrites> } {
|
||||
const { ctx, effects, rawMana: initialRawMana, addLog } = params;
|
||||
let rawMana = initialRawMana;
|
||||
const writes: Partial<TickWrites> = {};
|
||||
const currentAction = ctx.combat.currentAction;
|
||||
|
||||
// ── Phase 1: Design ──────────────────────────────────────────────────────
|
||||
if (currentAction === 'design') {
|
||||
const designProgress = ctx.crafting.designProgress;
|
||||
const designProgress2 = ctx.crafting.designProgress2;
|
||||
if (!designProgress && !designProgress2) {
|
||||
writes.combat = { ...(writes.combat || {}), currentAction: 'meditate' };
|
||||
} else {
|
||||
const activeProgress = designProgress || designProgress2!;
|
||||
const isRepeatDesign = ctx.crafting.enchantmentDesigns.some(
|
||||
(d) => d.name === activeProgress.name,
|
||||
);
|
||||
const designResult = calculateDesignProgress(
|
||||
activeProgress.progress,
|
||||
activeProgress.required,
|
||||
effects,
|
||||
isRepeatDesign,
|
||||
);
|
||||
if (designResult.isComplete) {
|
||||
const completedDesign = createCompletedDesignFromProgress(
|
||||
{
|
||||
designId: activeProgress.designId,
|
||||
name: activeProgress.name,
|
||||
equipmentType: activeProgress.equipmentType,
|
||||
effects: activeProgress.effects,
|
||||
required: activeProgress.required,
|
||||
},
|
||||
0,
|
||||
);
|
||||
useCraftingStore.getState().saveDesign(completedDesign);
|
||||
addLog('Design "' + completedDesign.name + '" completed!');
|
||||
writes.combat = { ...(writes.combat || {}), currentAction: 'meditate' };
|
||||
} else {
|
||||
if (designProgress) {
|
||||
useCraftingStore.getState().setDesignProgress({
|
||||
...designProgress,
|
||||
progress: designResult.progress,
|
||||
});
|
||||
} else if (designProgress2) {
|
||||
useCraftingStore.getState().setDesignProgress2({
|
||||
...designProgress2,
|
||||
progress: designResult.progress,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Phase 2: Preparation ─────────────────────────────────────────────────
|
||||
if (currentAction === 'prepare') {
|
||||
const prepProgress = ctx.crafting.preparationProgress;
|
||||
if (!prepProgress) {
|
||||
writes.combat = { ...(writes.combat || {}), currentAction: 'meditate' };
|
||||
} else {
|
||||
const instance = ctx.crafting.equipmentInstances[prepProgress.equipmentInstanceId];
|
||||
if (!instance) {
|
||||
useCraftingStore.getState().setPreparationProgress(null);
|
||||
writes.combat = { ...(writes.combat || {}), currentAction: 'meditate' };
|
||||
addLog('Preparation failed: equipment not found.');
|
||||
} else {
|
||||
const manaPerTick = (instance.totalCapacity * 10) / (2 + instance.totalCapacity / 50) * HOURS_PER_TICK;
|
||||
const prepResult = calculatePreparationTick(
|
||||
prepProgress.progress,
|
||||
prepProgress.required,
|
||||
prepProgress.manaCostPaid,
|
||||
manaPerTick,
|
||||
);
|
||||
if (prepResult.manaConsumed > 0) {
|
||||
rawMana = Math.max(0, rawMana - prepResult.manaConsumed);
|
||||
}
|
||||
if (prepResult.isComplete) {
|
||||
const completionResult = completePreparation(instance);
|
||||
useCraftingStore.setState((s) => ({
|
||||
equipmentInstances: {
|
||||
...s.equipmentInstances,
|
||||
[prepProgress.equipmentInstanceId]: completionResult.updatedInstance,
|
||||
},
|
||||
preparationProgress: null,
|
||||
}));
|
||||
writes.combat = { ...(writes.combat || {}), currentAction: 'meditate' };
|
||||
addLog(completionResult.logMessage);
|
||||
} else {
|
||||
useCraftingStore.getState().setPreparationProgress({
|
||||
...prepProgress,
|
||||
progress: prepResult.progress,
|
||||
manaCostPaid: prepResult.manaCostPaid,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Phase 3: Application ─────────────────────────────────────────────────
|
||||
if (currentAction === 'enchant') {
|
||||
const appProgress = ctx.crafting.applicationProgress;
|
||||
if (!appProgress || appProgress.paused) {
|
||||
if (!appProgress) {
|
||||
writes.combat = { ...(writes.combat || {}), currentAction: 'meditate' };
|
||||
}
|
||||
} else {
|
||||
const instance = ctx.crafting.equipmentInstances[appProgress.equipmentInstanceId];
|
||||
const design = ctx.crafting.enchantmentDesigns.find((d) => d.id === appProgress.designId);
|
||||
if (!instance || !design) {
|
||||
useCraftingStore.getState().setApplicationProgress(null);
|
||||
writes.combat = { ...(writes.combat || {}), currentAction: 'meditate' };
|
||||
addLog('Enchantment failed: equipment or design not found.');
|
||||
} else {
|
||||
const totalStacks = design.effects.reduce((s, e) => s + e.stacks, 0);
|
||||
const manaPerTick = (20 + 5 * totalStacks) * HOURS_PER_TICK;
|
||||
const appResult = calculateApplicationTick(
|
||||
appProgress.progress,
|
||||
appProgress.required,
|
||||
appProgress.manaSpent,
|
||||
manaPerTick,
|
||||
effects,
|
||||
);
|
||||
if (appResult.manaConsumed > 0) {
|
||||
rawMana = Math.max(0, rawMana - appResult.manaConsumed);
|
||||
}
|
||||
if (appResult.isComplete) {
|
||||
const applyResult = applyEnchantments(instance, design, effects);
|
||||
useCraftingStore.setState((s) => ({
|
||||
equipmentInstances: {
|
||||
...s.equipmentInstances,
|
||||
[appProgress.equipmentInstanceId]: applyResult.updatedInstance,
|
||||
},
|
||||
applicationProgress: null,
|
||||
}));
|
||||
const updatedAttunements = updateEnchanterAttunement(
|
||||
ctx.attunement.attunements,
|
||||
applyResult.xpGained,
|
||||
);
|
||||
writes.attunement = { ...(writes.attunement || {}), attunements: updatedAttunements };
|
||||
writes.combat = { ...(writes.combat || {}), currentAction: 'meditate' };
|
||||
addLog(applyResult.logMessage);
|
||||
if (appResult.triggeredFreeEnchant) {
|
||||
addLog('Free enchantment triggered! No mana consumed this tick.');
|
||||
}
|
||||
} else {
|
||||
useCraftingStore.getState().setApplicationProgress({
|
||||
...appProgress,
|
||||
progress: appResult.progress,
|
||||
manaSpent: appResult.manaSpent,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { rawMana, writes };
|
||||
}
|
||||
Reference in New Issue
Block a user