refactor: remove legacy store.ts and crafting-slice.ts, complete modular store migration
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m18s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m18s
- Delete store.ts (355 LOC monolithic store, zero imports) - Delete crafting-slice.ts (379 LOC legacy crafting module) - Inline createStartingEquipment() into craftingStore.ts - Remove legacy equipment/inventory fields from GameState - Remove EquipmentDef from game.ts imports (unused) - Fix duplicate EquipmentSpellState export in types.ts - Fix bluePrintId typo in craftingStore.ts - Update stores/index.ts to import CraftingState/CraftingActions from craftingStore.types - Update EquipmentTab.test.ts to test store state instead of deleted module - Clean up stale comments referencing crafting-slice.ts - Reduce TS errors from 83 to 72 by removing conflicting legacy types
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// ─── Crafting Application System ────────────────────────────────────────────
|
||||
// Application system functions extracted from crafting-slice.ts
|
||||
// Application system functions
|
||||
|
||||
import type { EquipmentInstance, AppliedEnchantment, EnchantmentDesign, ApplicationProgress } from './types';
|
||||
import { calculateApplicationTime, calculateApplicationManaPerHour } from './crafting-utils';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// ─── Attunement System ──────────────────────────────────────────────────────
|
||||
// Attunement system functions extracted from crafting-slice.ts
|
||||
// Attunement system functions
|
||||
|
||||
import type { AttunementState } from './types';
|
||||
import { calculateEnchantingXP, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL } from './data/attunements';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// ─── Equipment Crafting System ──────────────────────────────────────────────
|
||||
// Equipment crafting functions extracted from crafting-slice.ts
|
||||
// Equipment crafting functions
|
||||
|
||||
import type { EquipmentInstance, EquipmentCraftingProgress } from './types';
|
||||
import { CRAFTING_RECIPES, canCraftRecipe, type CraftingRecipe } from './data/crafting-recipes';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// ─── Crafting Loot Inventory System ────────────────────────────────────────────
|
||||
// Loot inventory functions extracted from crafting-slice.ts
|
||||
// Loot inventory functions
|
||||
|
||||
import type { LootInventory } from './types';
|
||||
import type { CraftingRecipe } from './data/crafting-recipes';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// ─── Crafting Preparation System ────────────────────────────────────────────
|
||||
// Preparation system functions extracted from crafting-slice.ts
|
||||
// Preparation system functions
|
||||
|
||||
import type { EquipmentInstance, PreparationProgress } from './types';
|
||||
import { calculatePrepTime, calculatePrepManaCost, calculateManaPerHourForPrep } from './crafting-utils';
|
||||
|
||||
@@ -1,379 +0,0 @@
|
||||
// ─── Crafting Store Slice ─────────────────────────────────────────────────────
|
||||
// Core slice logic for equipment and enchantment system. Extracted logic lives
|
||||
// in focused modules: crafting-utils, crafting-design, crafting-prep,
|
||||
// crafting-apply, crafting-equipment, crafting-loot, crafting-attunements.
|
||||
|
||||
import type { GameState, EquipmentInstance, AppliedEnchantment, EnchantmentDesign, DesignEffect, EquipmentCraftingProgress, LootInventory, AttunementState } from './types';
|
||||
import { EQUIPMENT_TYPES } from './data/equipment';
|
||||
import type { EquipmentSlot } from './data/equipment';
|
||||
import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from './data/enchantment-effects';
|
||||
import { CRAFTING_RECIPES, canCraftRecipe, type CraftingRecipe } from './data/crafting-recipes';
|
||||
import { SPELLS_DEF } from './constants';
|
||||
import { calculateEnchantingXP, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL } from './data/attunements';
|
||||
import { computeEffects } from './effects/upgrade-effects';
|
||||
import { hasSpecial, SPECIAL_EFFECTS } from './effects/special-effects';
|
||||
import type { ComputedEffects } from './effects/upgrade-effects.types';
|
||||
|
||||
// ─── Crafting Modules ───────────────────────────────────────────────────────
|
||||
|
||||
import * as CraftingUtils from './crafting-utils';
|
||||
import * as CraftingDesign from './crafting-design';
|
||||
import * as CraftingPrep from './crafting-prep';
|
||||
import * as CraftingApply from './crafting-apply';
|
||||
import * as CraftingEquipment from './crafting-equipment';
|
||||
import * as CraftingLoot from './crafting-loot';
|
||||
import * as CraftingAttunements from './crafting-attunements';
|
||||
import * as CraftingActions from './crafting-actions';
|
||||
|
||||
// ─── Initial Equipment Setup ─────────────────────────────────────────────────
|
||||
|
||||
export function createStartingEquipment() {
|
||||
const staffId = CraftingUtils.generateInstanceId();
|
||||
const staffInstance: EquipmentInstance = {
|
||||
instanceId: staffId,
|
||||
typeId: 'basicStaff',
|
||||
name: 'Basic Staff',
|
||||
enchantments: [{ effectId: 'spell_manaBolt', stacks: 1, actualCost: 50 }],
|
||||
usedCapacity: 50,
|
||||
totalCapacity: 50,
|
||||
rarity: 'common',
|
||||
quality: 100,
|
||||
tags: [],
|
||||
};
|
||||
|
||||
const shirtId = CraftingUtils.generateInstanceId();
|
||||
const shirtInstance: EquipmentInstance = {
|
||||
instanceId: shirtId,
|
||||
typeId: 'civilianShirt',
|
||||
name: 'Civilian Shirt',
|
||||
enchantments: [],
|
||||
usedCapacity: 0,
|
||||
totalCapacity: 30,
|
||||
rarity: 'common',
|
||||
quality: 100,
|
||||
tags: [],
|
||||
};
|
||||
|
||||
const shoesId = CraftingUtils.generateInstanceId();
|
||||
const shoesInstance: EquipmentInstance = {
|
||||
instanceId: shoesId,
|
||||
typeId: 'civilianShoes',
|
||||
name: 'Civilian Shoes',
|
||||
enchantments: [],
|
||||
usedCapacity: 0,
|
||||
totalCapacity: 15,
|
||||
rarity: 'common',
|
||||
quality: 100,
|
||||
tags: [],
|
||||
};
|
||||
|
||||
return {
|
||||
equippedInstances: {
|
||||
mainHand: staffId,
|
||||
offHand: null,
|
||||
head: null,
|
||||
body: shirtId,
|
||||
hands: null,
|
||||
feet: shoesId,
|
||||
accessory1: null,
|
||||
accessory2: null,
|
||||
},
|
||||
equipmentInstances: {
|
||||
[staffId]: staffInstance,
|
||||
[shirtId]: shirtInstance,
|
||||
[shoesId]: shoesInstance,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ─── Crafting Actions Interface ─────────────────────────────────────────────
|
||||
|
||||
export type CraftingActions = {
|
||||
createEquipmentInstance: (typeId: string) => string | null;
|
||||
equipItem: (instanceId: string, slot: EquipmentSlot) => boolean;
|
||||
unequipItem: (slot: EquipmentSlot) => void;
|
||||
deleteEquipmentInstance: (instanceId: string) => void;
|
||||
startDesigningEnchantment: (name: string, equipmentTypeId: string, effects: DesignEffect[]) => boolean;
|
||||
cancelDesign: () => void;
|
||||
saveDesign: (design: EnchantmentDesign) => void;
|
||||
deleteDesign: (designId: string) => void;
|
||||
startPreparing: (equipmentInstanceId: string) => boolean;
|
||||
cancelPreparation: () => void;
|
||||
startApplying: (equipmentInstanceId: string, designId: string) => boolean;
|
||||
pauseApplication: () => void;
|
||||
resumeApplication: () => void;
|
||||
cancelApplication: () => void;
|
||||
disenchantEquipment: (instanceId: string) => void;
|
||||
startCraftingEquipment: (blueprintId: string) => boolean;
|
||||
cancelEquipmentCrafting: () => void;
|
||||
deleteMaterial: (materialId: string, amount: number) => void;
|
||||
getEquipmentSpells: () => string[];
|
||||
getEquipmentEffects: () => Record<string, number>;
|
||||
getAvailableCapacity: (instanceId: string) => number;
|
||||
};
|
||||
|
||||
// ─── Crafting Store Slice ───────────────────────────────────────────────────
|
||||
|
||||
export function createCraftingSlice(
|
||||
set: (fn: (state: GameState) => Partial<GameState>) => void,
|
||||
get: () => GameState & CraftingActions
|
||||
): CraftingActions {
|
||||
return {
|
||||
createEquipmentInstance: (typeId) => CraftingActions.createEquipmentInstance(typeId, set),
|
||||
equipItem: (instanceId, slot) => CraftingActions.equipItem(instanceId, slot, get, set),
|
||||
unequipItem: (slot) => CraftingActions.unequipItem(slot, set),
|
||||
deleteEquipmentInstance: (instanceId) => CraftingActions.deleteEquipmentInstance(instanceId, get, set),
|
||||
startDesigningEnchantment: (name, equipmentTypeId, effects) =>
|
||||
CraftingActions.startDesigningEnchantment(name, equipmentTypeId, effects, get, set),
|
||||
cancelDesign: () => CraftingActions.cancelDesign(get, set),
|
||||
saveDesign: (design) => CraftingActions.saveDesign(design, get, set),
|
||||
deleteDesign: (designId) => CraftingActions.deleteDesign(designId, set),
|
||||
startPreparing: (equipmentInstanceId) => CraftingActions.startPreparing(equipmentInstanceId, get, set),
|
||||
cancelPreparation: () => CraftingActions.cancelPreparation(set),
|
||||
startApplying: (equipmentInstanceId, designId) =>
|
||||
CraftingActions.startApplying(equipmentInstanceId, designId, get, set),
|
||||
pauseApplication: () => CraftingActions.pauseApplication(get, set),
|
||||
resumeApplication: () => CraftingActions.resumeApplication(get, set),
|
||||
cancelApplication: () => CraftingActions.cancelApplication(set),
|
||||
disenchantEquipment: (instanceId) => CraftingActions.disenchantEquipment(instanceId, get, set),
|
||||
startCraftingEquipment: (blueprintId) => CraftingActions.startCraftingEquipment(blueprintId, get, set),
|
||||
cancelEquipmentCrafting: () => CraftingActions.cancelEquipmentCrafting(get, set),
|
||||
deleteMaterial: (materialId, amount) => CraftingActions.deleteMaterial(materialId, amount, get, set),
|
||||
getEquipmentSpells: () => CraftingActions.getEquipmentSpells(get),
|
||||
getEquipmentEffects: () => CraftingActions.getEquipmentEffects(get),
|
||||
getAvailableCapacity: (instanceId) => CraftingActions.getAvailableCapacity(instanceId, get),
|
||||
};
|
||||
}
|
||||
|
||||
// ─── Tick Processing for Crafting ────────────────────────────────────────────
|
||||
|
||||
export function processCraftingTick(
|
||||
state: GameState,
|
||||
effects: { rawMana: number; log: string[] }
|
||||
): Partial<GameState> {
|
||||
const { rawMana, log } = effects;
|
||||
let updates: Partial<GameState> = {};
|
||||
|
||||
const computedEffects = computeEffects(state.skillUpgrades || {}, state.skillTiers || {});
|
||||
|
||||
// Process design progress (slot 1)
|
||||
if (state.currentAction === 'design' && state.designProgress) {
|
||||
const designResult = CraftingDesign.calculateDesignProgress(
|
||||
state.designProgress.progress,
|
||||
state.designProgress.required,
|
||||
computedEffects,
|
||||
false
|
||||
);
|
||||
|
||||
if (designResult.isComplete) {
|
||||
const completedDesign = CraftingDesign.createCompletedDesignFromProgress(
|
||||
{
|
||||
designId: state.designProgress.designId,
|
||||
name: state.designProgress.name,
|
||||
equipmentType: state.designProgress.equipmentType,
|
||||
effects: state.designProgress.effects,
|
||||
required: state.designProgress.required,
|
||||
},
|
||||
((state.skillUpgrades || {})['efficientEnchant'] || []).length * 0.05
|
||||
);
|
||||
updates = {
|
||||
...updates,
|
||||
designProgress: null,
|
||||
currentAction: 'meditate' as const,
|
||||
enchantmentDesigns: [...state.enchantmentDesigns, completedDesign],
|
||||
log: [`✅ Enchantment design "${completedDesign.name}" complete!`, ...log],
|
||||
};
|
||||
} else {
|
||||
updates = {
|
||||
...updates,
|
||||
designProgress: { ...state.designProgress, progress: designResult.progress },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Process second design progress (slot 2)
|
||||
if (state.designProgress2) {
|
||||
const designResult2 = CraftingDesign.calculateSecondDesignProgress(
|
||||
state.designProgress2.progress,
|
||||
state.designProgress2.required,
|
||||
computedEffects,
|
||||
false
|
||||
);
|
||||
if (designResult2.isComplete) {
|
||||
const completedDesign = CraftingDesign.createCompletedDesignFromProgress(
|
||||
{
|
||||
designId: state.designProgress2.designId,
|
||||
name: state.designProgress2.name,
|
||||
equipmentType: state.designProgress2.equipmentType,
|
||||
effects: state.designProgress2.effects,
|
||||
required: state.designProgress2.required,
|
||||
},
|
||||
((state.skillUpgrades || {})['efficientEnchant'] || []).length * 0.05
|
||||
);
|
||||
const shouldTransitionToMeditate = !state.designProgress;
|
||||
updates = {
|
||||
...updates,
|
||||
designProgress2: null,
|
||||
currentAction: shouldTransitionToMeditate ? 'meditate' as const : state.currentAction,
|
||||
enchantmentDesigns: [...state.enchantmentDesigns, completedDesign],
|
||||
log: [`✅ Enchantment design "${completedDesign.name}" complete! (2nd slot)`, ...log],
|
||||
};
|
||||
} else {
|
||||
updates = {
|
||||
...updates,
|
||||
designProgress2: { ...state.designProgress2, progress: designResult2.progress },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Process preparation progress
|
||||
if (state.currentAction === 'prepare' && state.preparationProgress) {
|
||||
const instance = state.equipmentInstances[state.preparationProgress.equipmentInstanceId];
|
||||
const manaPerTick = instance ? CraftingPrep.getPreparationManaCostForTick(instance) : 0;
|
||||
|
||||
if (rawMana >= manaPerTick) {
|
||||
const tickResult = CraftingPrep.calculatePreparationTick(
|
||||
state.preparationProgress.progress,
|
||||
state.preparationProgress.required,
|
||||
manaPerTick
|
||||
);
|
||||
|
||||
if (tickResult.isComplete) {
|
||||
if (instance) {
|
||||
const completeResult = CraftingPrep.completePreparation(instance, state.preparationProgress.manaCostPaid);
|
||||
updates = {
|
||||
...updates,
|
||||
rawMana: rawMana - tickResult.manaConsumed + completeResult.manaRecovered,
|
||||
preparationProgress: null,
|
||||
currentAction: 'meditate' as const,
|
||||
equipmentInstances: {
|
||||
...state.equipmentInstances,
|
||||
[instance.instanceId]: completeResult.updatedInstance,
|
||||
},
|
||||
log: [completeResult.logMessage, ...log],
|
||||
};
|
||||
} else {
|
||||
updates = {
|
||||
...updates,
|
||||
preparationProgress: null,
|
||||
currentAction: 'meditate' as const,
|
||||
rawMana: rawMana - tickResult.manaConsumed,
|
||||
log: ['✅ Preparation complete!', ...log],
|
||||
};
|
||||
}
|
||||
} else {
|
||||
updates = {
|
||||
...updates,
|
||||
rawMana: rawMana - tickResult.manaConsumed,
|
||||
preparationProgress: {
|
||||
...state.preparationProgress,
|
||||
progress: tickResult.progress,
|
||||
manaCostPaid: tickResult.manaCostPaid,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process application progress
|
||||
if (state.currentAction === 'enchant' && state.applicationProgress && !state.applicationProgress.paused) {
|
||||
const app = state.applicationProgress;
|
||||
const manaPerTick = CraftingApply.getApplicationManaCostForTick(app.manaPerHour);
|
||||
|
||||
if (rawMana >= manaPerTick) {
|
||||
const tickResult = CraftingApply.calculateApplicationTick(
|
||||
app.progress,
|
||||
app.required,
|
||||
app.manaSpent,
|
||||
manaPerTick,
|
||||
computedEffects
|
||||
);
|
||||
|
||||
if (tickResult.isComplete) {
|
||||
const instance = state.equipmentInstances[app.equipmentInstanceId];
|
||||
const design = state.enchantmentDesigns.find(d => d.id === app.designId);
|
||||
if (instance && design) {
|
||||
const applyResult = CraftingApply.applyEnchantments(instance, design, computedEffects);
|
||||
const xpGain = CraftingAttunements.gainEnchantingXP(state.attunements, applyResult.xpGained);
|
||||
updates = {
|
||||
...updates,
|
||||
rawMana: rawMana - tickResult.manaConsumed,
|
||||
applicationProgress: null,
|
||||
currentAction: 'meditate' as const,
|
||||
attunements: {
|
||||
...state.attunements,
|
||||
enchanter: xpGain.attunements.enchanter,
|
||||
},
|
||||
equipmentInstances: {
|
||||
...state.equipmentInstances,
|
||||
[instance.instanceId]: applyResult.updatedInstance,
|
||||
},
|
||||
log: [applyResult.logMessage, ...log],
|
||||
};
|
||||
}
|
||||
} else {
|
||||
updates = {
|
||||
...updates,
|
||||
rawMana: rawMana - tickResult.manaConsumed,
|
||||
applicationProgress: { ...app, progress: tickResult.progress, manaSpent: tickResult.manaSpent },
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process equipment crafting progress
|
||||
if (state.currentAction === 'craft' && state.equipmentCraftingProgress) {
|
||||
const craft = state.equipmentCraftingProgress;
|
||||
const tickResult = CraftingEquipment.calculateCraftingTick(craft.progress, craft.required);
|
||||
|
||||
if (tickResult.isComplete) {
|
||||
const recipe = CraftingEquipment.getRecipe(craft.blueprintId);
|
||||
if (recipe) {
|
||||
const craftResult = CraftingEquipment.completeEquipmentCrafting(craft.blueprintId, recipe);
|
||||
updates = {
|
||||
...updates,
|
||||
equipmentCraftingProgress: null,
|
||||
currentAction: 'meditate' as const,
|
||||
equipmentInstances: {
|
||||
...state.equipmentInstances,
|
||||
[craftResult.instanceId]: craftResult.instance,
|
||||
},
|
||||
totalCraftsCompleted: (state.totalCraftsCompleted || 0) + 1,
|
||||
log: [craftResult.logMessage, ...log],
|
||||
};
|
||||
} else {
|
||||
updates = {
|
||||
...updates,
|
||||
equipmentCraftingProgress: null,
|
||||
currentAction: 'meditate' as const,
|
||||
log: ['⚠️ Crafting failed - invalid recipe!', ...log],
|
||||
};
|
||||
}
|
||||
} else {
|
||||
updates = {
|
||||
...updates,
|
||||
equipmentCraftingProgress: { ...craft, progress: tickResult.progress },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return updates;
|
||||
}
|
||||
|
||||
// ─── Export helper to get equipment instance spells ─────────────────────────
|
||||
|
||||
export function getSpellsFromEquipment(instances: Record<string, EquipmentInstance>, equippedIds: (string | null)[]): string[] {
|
||||
const spells: string[] = [];
|
||||
for (const id of equippedIds) {
|
||||
if (!id) continue;
|
||||
const instance = instances[id];
|
||||
if (!instance) continue;
|
||||
for (const ench of instance.enchantments) {
|
||||
const effectDef = ENCHANTMENT_EFFECTS[ench.effectId];
|
||||
if (effectDef?.effect.type === 'spell' && effectDef.effect.spellId) {
|
||||
spells.push(effectDef.effect.spellId);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [...new Set(spells)];
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// ─── Crafting Helper Utilities ─────────────────────────────────────────────────
|
||||
// Instance/ID generation and helper functions extracted from crafting-slice.ts
|
||||
// Instance/ID generation and helper functions
|
||||
|
||||
import type { EquipmentInstance, EnchantmentDesign, DesignEffect } from './types';
|
||||
import { EQUIPMENT_TYPES, type EquipmentCategory, type EquipmentSlot } from './data/equipment';
|
||||
|
||||
@@ -1,355 +0,0 @@
|
||||
// ─── Game Store (Refactored) ──────────────────────────────────────────────
|
||||
// Main entry point - imports from modular store components
|
||||
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import type { GameState, GameAction, ActivityLogEntry } from './types';
|
||||
|
||||
import { addActivityLogEntry } from './utils/activity-log';
|
||||
import {
|
||||
computeMaxMana, computeRegen, computeClickMana,
|
||||
getMeditationBonus,
|
||||
} from './utils/mana-utils';
|
||||
import {
|
||||
calcDamage, calcInsight, getIncursionStrength, canAffordSpellCost, deductSpellCost,
|
||||
} from './utils/combat-utils';
|
||||
import { generateFloorState } from './utils/room-utils';
|
||||
|
||||
// Re-export formatting functions for backward compatibility
|
||||
export { fmt, fmtDec } from './utils/formatting';
|
||||
export { getFloorMaxHP, getFloorElement } from './utils/floor-utils';
|
||||
|
||||
// Re-export computed stats functions for backward compatibility and tests
|
||||
export {
|
||||
computeMaxMana, computeRegen, computeClickMana,
|
||||
getMeditationBonus,
|
||||
} from './utils/mana-utils';
|
||||
export {
|
||||
calcDamage, calcInsight, getIncursionStrength, canAffordSpellCost, deductSpellCost,
|
||||
} from './utils/combat-utils';
|
||||
|
||||
// ─── Initial State ───────────────────────────────────────────────────────
|
||||
|
||||
interface MakeInitialOptions {
|
||||
loopCount?: number;
|
||||
totalInsight?: number;
|
||||
insight?: number;
|
||||
prestigeUpgrades?: Record<string, number>;
|
||||
}
|
||||
|
||||
export function makeInitial(opts?: MakeInitialOptions): GameState {
|
||||
return {
|
||||
day: 1,
|
||||
hour: 0,
|
||||
rawMana: 100,
|
||||
maxMana: 100,
|
||||
elements: {},
|
||||
skills: {},
|
||||
skillUpgrades: {},
|
||||
skillTiers: {},
|
||||
spells: {},
|
||||
currentAction: 'meditate' as GameAction,
|
||||
currentStudyTarget: null,
|
||||
parallelStudyTarget: null,
|
||||
activeSpell: null,
|
||||
currentFloor: 100,
|
||||
floorHP: 1000,
|
||||
floorMaxHP: 1000,
|
||||
currentRoom: generateFloorState(100),
|
||||
maxFloorReached: 100,
|
||||
paused: false,
|
||||
gameOver: false,
|
||||
victory: false,
|
||||
loopCount: opts?.loopCount ?? 0,
|
||||
totalInsight: opts?.totalInsight ?? 0,
|
||||
insight: opts?.insight ?? 0,
|
||||
loopInsight: 0,
|
||||
prestigeUpgrades: opts?.prestigeUpgrades ?? {},
|
||||
signedPacts: [],
|
||||
attunements: {},
|
||||
golemancy: { enabledGolems: [] },
|
||||
memories: [],
|
||||
memorySlots: 0,
|
||||
log: [],
|
||||
activityLog: [],
|
||||
meditateTicks: 0,
|
||||
totalManaGathered: 0,
|
||||
equippedInstances: {},
|
||||
equipmentInstances: {},
|
||||
lootInventory: {},
|
||||
blueprints: {},
|
||||
spireMode: false,
|
||||
};
|
||||
}
|
||||
|
||||
// ─── Game Store Interface ─────────────────────────────────────────────────
|
||||
|
||||
export interface GameStore extends GameState {
|
||||
tick: () => void;
|
||||
gatherMana: () => void;
|
||||
setAction: (action: GameAction) => void;
|
||||
addActivityLog: (eventType: string, message: string, details?: ActivityLogEntry['details']) => void;
|
||||
setSpell: (spellId: string) => void;
|
||||
cancelStudy: () => void;
|
||||
convertMana: (element: string, amount: number) => void;
|
||||
unlockElement: (element: string) => void;
|
||||
doPrestige: (id: string, selectedManaType?: string) => void;
|
||||
startNewLoop: () => void;
|
||||
togglePause: () => void;
|
||||
resetGame: () => void;
|
||||
addLog: (message: string) => void;
|
||||
addAttunementXP: (attunementId: string, amount: number) => void;
|
||||
toggleGolem: (golemId: string) => void;
|
||||
setEnabledGolems: (golemIds: string[]) => void;
|
||||
debugUnlockAttunement: (attunementId: string) => void;
|
||||
debugAddElementalMana: (element: string, amount: number) => void;
|
||||
debugSetTime: (day: number, hour: number) => void;
|
||||
debugAddAttunementXP: (attunementId: string, amount: number) => void;
|
||||
debugSetFloor: (floor: number) => void;
|
||||
resetFloorHP: () => void;
|
||||
getMaxMana: () => number;
|
||||
getRegen: () => number;
|
||||
getClickMana: () => number;
|
||||
getDamage: (spellId: string) => number;
|
||||
getMeditationMultiplier: () => number;
|
||||
canCastSpell: (spellId: string) => boolean;
|
||||
enterSpireMode: () => void;
|
||||
climbDownFloor: () => void;
|
||||
exitSpireMode: () => void;
|
||||
}
|
||||
|
||||
// ─── Store Implementation ────────────────────────────────────────────────
|
||||
|
||||
export const useGameStore = create<GameStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
...makeInitial(),
|
||||
|
||||
getMaxMana: () => computeMaxMana(get()),
|
||||
getRegen: () => computeRegen(get()),
|
||||
getClickMana: () => computeClickMana(get()),
|
||||
getDamage: (spellId: string) => calcDamage(get(), spellId),
|
||||
getMeditationMultiplier: () => getMeditationBonus(get().meditateTicks, {}),
|
||||
|
||||
canCastSpell: (spellId: string) => {
|
||||
const state = get();
|
||||
const spell = state.spells?.[spellId];
|
||||
if (!spell) return false;
|
||||
return true;
|
||||
},
|
||||
|
||||
addLog: (message: string) => {
|
||||
set((state) => ({
|
||||
log: [message, ...(state.log || []).slice(0, 49)],
|
||||
}));
|
||||
},
|
||||
|
||||
addActivityLog: (eventType: string, message: string, details?: ActivityLogEntry['details']) => {
|
||||
set((state) => ({
|
||||
activityLog: addActivityLogEntry(state, eventType, message, details),
|
||||
}));
|
||||
},
|
||||
|
||||
tick: () => {
|
||||
const state = get();
|
||||
if (state.gameOver || state.paused) return;
|
||||
|
||||
const maxMana = computeMaxMana(state);
|
||||
const baseRegen = computeRegen(state);
|
||||
|
||||
let hour = state.hour + 1;
|
||||
let day = state.day;
|
||||
if (hour >= 24) { hour -= 24; day += 1; }
|
||||
|
||||
if (day > 100) {
|
||||
const insightGained = calcInsight(state);
|
||||
set({
|
||||
day, hour, gameOver: true, victory: false, loopInsight: insightGained,
|
||||
log: [`⏰ The loop ends. Gained ${insightGained} Insight.`, ...state.log.slice(0, 49)],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let rawMana = state.rawMana + baseRegen;
|
||||
rawMana = Math.min(rawMana, maxMana);
|
||||
|
||||
set({
|
||||
day, hour, rawMana,
|
||||
meditateTicks: state.currentAction === 'meditate' ? state.meditateTicks + 1 : 0,
|
||||
});
|
||||
},
|
||||
|
||||
gatherMana: () => {
|
||||
const state = get();
|
||||
const clickMana = computeClickMana(state);
|
||||
const maxMana = computeMaxMana(state);
|
||||
set((s) => ({
|
||||
rawMana: Math.min(s.rawMana + clickMana, maxMana),
|
||||
totalManaGathered: s.totalManaGathered + clickMana,
|
||||
}));
|
||||
},
|
||||
|
||||
setAction: (action: GameAction) => {
|
||||
set({ currentAction: action });
|
||||
},
|
||||
|
||||
setSpell: (spellId: string) => {
|
||||
set({ activeSpell: spellId });
|
||||
},
|
||||
|
||||
cancelStudy: () => {
|
||||
set({ currentStudyTarget: null, currentAction: 'meditate' });
|
||||
},
|
||||
|
||||
convertMana: (element: string, amount: number) => {
|
||||
set((s) => {
|
||||
const elem = s.elements?.[element];
|
||||
if (!elem || !elem.unlocked) return s;
|
||||
const canConvert = Math.min(amount, Math.floor(s.rawMana / 10), elem.max - elem.current);
|
||||
if (canConvert > 0) {
|
||||
return {
|
||||
rawMana: s.rawMana - canConvert * 10,
|
||||
elements: { ...s.elements, [element]: { ...elem, current: elem.current + canConvert } },
|
||||
};
|
||||
}
|
||||
return s;
|
||||
});
|
||||
},
|
||||
|
||||
unlockElement: (element: string) => {
|
||||
set((s) => ({
|
||||
elements: { ...s.elements, [element]: { ...s.elements[element], unlocked: true } },
|
||||
}));
|
||||
},
|
||||
|
||||
doPrestige: (id: string, selectedManaType?: string) => {
|
||||
set((s) => ({
|
||||
prestigeUpgrades: { ...s.prestigeUpgrades, [id]: (s.prestigeUpgrades[id] || 0) + 1 },
|
||||
}));
|
||||
},
|
||||
|
||||
startNewLoop: () => {
|
||||
const state = get();
|
||||
const insightGained = state.loopInsight || 0;
|
||||
set({
|
||||
...makeInitial({
|
||||
loopCount: state.loopCount + 1,
|
||||
totalInsight: (state.totalInsight || 0) + insightGained,
|
||||
insight: (state.insight || 0) + insightGained,
|
||||
prestigeUpgrades: state.prestigeUpgrades,
|
||||
}),
|
||||
});
|
||||
},
|
||||
|
||||
togglePause: () => {
|
||||
set((s) => ({ paused: !s.paused }));
|
||||
},
|
||||
|
||||
resetGame: () => {
|
||||
set(makeInitial());
|
||||
},
|
||||
|
||||
addAttunementXP: (attunementId: string, amount: number) => {
|
||||
set((s) => {
|
||||
const attState = s.attunements?.[attunementId];
|
||||
if (!attState) return s;
|
||||
return {
|
||||
attunements: { ...s.attunements, [attunementId]: { ...attState, experience: attState.experience + amount } },
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
toggleGolem: (golemId: string) => {
|
||||
set((s) => {
|
||||
const enabledGolems = s.golemancy?.enabledGolems || [];
|
||||
const isEnabled = enabledGolems.includes(golemId);
|
||||
return {
|
||||
golemancy: { ...s.golemancy, enabledGolems: isEnabled ? enabledGolems.filter(id => id !== golemId) : [...enabledGolems, golemId] },
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
setEnabledGolems: (golemIds: string[]) => {
|
||||
set((s) => ({
|
||||
golemancy: { ...s.golemancy, enabledGolems: golemIds },
|
||||
}));
|
||||
},
|
||||
|
||||
debugUnlockAttunement: (attunementId: string) => {
|
||||
set((s) => ({
|
||||
attunements: { ...s.attunements, [attunementId]: { id: attunementId, active: true, level: 1, experience: 0 } },
|
||||
}));
|
||||
},
|
||||
|
||||
debugAddElementalMana: (element: string, amount: number) => {
|
||||
set((s) => {
|
||||
const elem = s.elements?.[element];
|
||||
if (!elem) return s;
|
||||
return {
|
||||
elements: { ...s.elements, [element]: { ...elem, current: Math.min(elem.current + amount, elem.max) } },
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
debugSetTime: (day: number, hour: number) => {
|
||||
set({ day, hour });
|
||||
},
|
||||
|
||||
debugAddAttunementXP: (attunementId: string, amount: number) => {
|
||||
get().addAttunementXP(attunementId, amount);
|
||||
},
|
||||
|
||||
debugSetFloor: (floor: number) => {
|
||||
set((s) => ({
|
||||
currentFloor: floor,
|
||||
currentRoom: generateFloorState(floor),
|
||||
floorMaxHP: 100 + floor * 50,
|
||||
floorHP: 100 + floor * 50,
|
||||
}));
|
||||
},
|
||||
|
||||
resetFloorHP: () => {
|
||||
set((s) => ({
|
||||
floorHP: s.floorMaxHP,
|
||||
currentRoom: generateFloorState(s.currentFloor),
|
||||
}));
|
||||
},
|
||||
|
||||
enterSpireMode: () => {
|
||||
set({ spireMode: true });
|
||||
},
|
||||
|
||||
climbDownFloor: () => {
|
||||
set((s) => {
|
||||
if (s.currentFloor <= 1) return s;
|
||||
const newFloor = s.currentFloor - 1;
|
||||
return {
|
||||
currentFloor: newFloor,
|
||||
currentRoom: generateFloorState(newFloor),
|
||||
floorMaxHP: 100 + newFloor * 50,
|
||||
floorHP: 100 + newFloor * 50,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
exitSpireMode: () => {
|
||||
set({ spireMode: false });
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: 'mana-loop-game-store',
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// ─── Game Loop Hook ───────────────────────────────────────────────────────────
|
||||
|
||||
export function useGameLoop() {
|
||||
const tick = useGameStore((s) => s.tick);
|
||||
return {
|
||||
start: () => {
|
||||
const interval = setInterval(tick, 1000);
|
||||
return () => clearInterval(interval);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import * as CraftingUtils from '../crafting-utils';
|
||||
import * as CraftingDesign from '../crafting-design';
|
||||
import { useManaStore } from './manaStore';
|
||||
import { useCombatStore } from './combatStore';
|
||||
import { createStartingEquipment } from '../crafting-slice';
|
||||
|
||||
import { useUIStore } from './uiStore';
|
||||
import * as ApplicationActions from '../crafting-actions/application-actions';
|
||||
import * as PreparationActions from '../crafting-actions/preparation-actions';
|
||||
@@ -16,7 +16,45 @@ import * as CraftingEquipment from '../crafting-equipment';
|
||||
export const useCraftingStore = create<CraftingStore>()(
|
||||
persist(
|
||||
(set, get) => {
|
||||
const startingEquipment = createStartingEquipment();
|
||||
const staffId = CraftingUtils.generateInstanceId();
|
||||
const staffInstance = {
|
||||
instanceId: staffId,
|
||||
typeId: 'basicStaff',
|
||||
name: 'Basic Staff',
|
||||
enchantments: [{ effectId: 'spell_manaBolt', stacks: 1, actualCost: 50 }],
|
||||
usedCapacity: 50,
|
||||
totalCapacity: 50,
|
||||
rarity: 'common',
|
||||
quality: 100,
|
||||
tags: [],
|
||||
};
|
||||
|
||||
const shirtId = CraftingUtils.generateInstanceId();
|
||||
const shirtInstance = {
|
||||
instanceId: shirtId,
|
||||
typeId: 'civilianShirt',
|
||||
name: 'Civilian Shirt',
|
||||
enchantments: [],
|
||||
usedCapacity: 0,
|
||||
totalCapacity: 30,
|
||||
rarity: 'common',
|
||||
quality: 100,
|
||||
tags: [],
|
||||
};
|
||||
|
||||
const shoesId = CraftingUtils.generateInstanceId();
|
||||
const shoesInstance = {
|
||||
instanceId: shoesId,
|
||||
typeId: 'civilianShoes',
|
||||
name: 'Civilian Shoes',
|
||||
enchantments: [],
|
||||
usedCapacity: 0,
|
||||
totalCapacity: 15,
|
||||
rarity: 'common',
|
||||
quality: 100,
|
||||
tags: [],
|
||||
};
|
||||
|
||||
return {
|
||||
// Initial state
|
||||
designProgress: null,
|
||||
@@ -26,7 +64,21 @@ export const useCraftingStore = create<CraftingStore>()(
|
||||
equipmentCraftingProgress: null,
|
||||
enchantmentDesigns: [],
|
||||
unlockedEffects: [],
|
||||
...startingEquipment,
|
||||
equippedInstances: {
|
||||
mainHand: staffId,
|
||||
offHand: null,
|
||||
head: null,
|
||||
body: shirtId,
|
||||
hands: null,
|
||||
feet: shoesId,
|
||||
accessory1: null,
|
||||
accessory2: null,
|
||||
},
|
||||
equipmentInstances: {
|
||||
[staffId]: staffInstance,
|
||||
[shirtId]: shirtInstance,
|
||||
[shoesId]: shoesInstance,
|
||||
},
|
||||
lootInventory: {
|
||||
materials: {},
|
||||
blueprints: [],
|
||||
@@ -177,7 +229,7 @@ export const useCraftingStore = create<CraftingStore>()(
|
||||
|
||||
// Check if we can start crafting
|
||||
const check = CraftingEquipment.canStartEquipmentCrafting(
|
||||
bluePrintId,
|
||||
blueprintId,
|
||||
state.lootInventory.blueprints.includes(blueprintId),
|
||||
state.lootInventory.materials,
|
||||
rawMana,
|
||||
@@ -188,7 +240,7 @@ export const useCraftingStore = create<CraftingStore>()(
|
||||
|
||||
// Initialize crafting
|
||||
const result = CraftingEquipment.initializeEquipmentCrafting(
|
||||
bluePrintId,
|
||||
blueprintId,
|
||||
state.lootInventory.materials,
|
||||
rawMana
|
||||
);
|
||||
|
||||
@@ -15,7 +15,7 @@ export { useCombatStore, makeInitialSpells } from './combatStore';
|
||||
export type { CombatState } from './combat-state.types';
|
||||
|
||||
export { useCraftingStore } from './craftingStore';
|
||||
export type { CraftingState, CraftingActions } from './craftingStore';
|
||||
export type { CraftingState, CraftingActions } from './craftingStore.types';
|
||||
|
||||
export { useAttunementStore } from './attunementStore';
|
||||
export type { AttunementStoreState } from './attunementStore';
|
||||
|
||||
@@ -55,7 +55,6 @@ export type {
|
||||
GameActionType,
|
||||
ActivityEventType,
|
||||
ActivityLogEntry,
|
||||
EquipmentSpellState,
|
||||
} from './types/game';
|
||||
|
||||
// ─── New: Memory Type Definition ─────────────────────────────────────────────
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import type { AttunementState } from './attunements';
|
||||
import type { ElementState } from './elements';
|
||||
import type { SpellState } from './spells';
|
||||
import type { EquipmentInstance, EnchantmentDesign, DesignProgress, PreparationProgress, ApplicationProgress, EquipmentCraftingProgress, EquipmentDef, BlueprintDef, LootInventory, EquipmentSpellState } from './equipment';
|
||||
import type { EquipmentInstance, EnchantmentDesign, DesignProgress, PreparationProgress, ApplicationProgress, EquipmentCraftingProgress, BlueprintDef, LootInventory, EquipmentSpellState } from './equipment';
|
||||
|
||||
// ─── Activity Log Types ─────────────────────────────────────────────────
|
||||
export type ActivityEventType =
|
||||
@@ -182,9 +182,6 @@ export interface GameState {
|
||||
// Equipment spell states for multi-casting
|
||||
equipmentSpellStates: EquipmentSpellState[];
|
||||
|
||||
// Legacy Equipment (for backward compatibility)
|
||||
equipment: Record<string, EquipmentDef | null>;
|
||||
inventory: EquipmentDef[];
|
||||
|
||||
// Blueprints
|
||||
blueprints: Record<string, BlueprintDef>;
|
||||
|
||||
@@ -29,9 +29,7 @@ export function computeMaxMana(
|
||||
return base;
|
||||
}
|
||||
|
||||
// computeElementMax is now in ../store.ts with support for unlockedManaTypeUpgrades
|
||||
// This file no longer exports computeElementMax to avoid duplicate export issues
|
||||
// Import computeElementMax from '../store' instead
|
||||
// computeElementMax has been removed — element max is computed in manaStore.ts
|
||||
|
||||
export function computeRegen(
|
||||
state: Pick<GameState, 'skills' | 'prestigeUpgrades' | 'skillUpgrades' | 'skillTiers' | 'attunements'>,
|
||||
|
||||
Reference in New Issue
Block a user