// ─── Crafting Store ───────────────────────────────────────────────────── import { create } from 'zustand'; import { persist } from 'zustand/middleware'; import type { EquipmentSlot } from '../types/equipmentSlot'; import type { CraftingStore, CraftingState } from './craftingStore.types'; import * as CraftingUtils from '../crafting-utils'; import * as CraftingDesign from '../crafting-design'; import { useManaStore } from './manaStore'; import { useCombatStore } from './combatStore'; import { useUIStore } from './uiStore'; import { getEnchantingEfficiencyBonus } from '../effects/discipline-effects'; import * as ApplicationActions from '../crafting-actions/application-actions'; import * as PreparationActions from '../crafting-actions/preparation-actions'; import { equipItem as equipItemAction, unequipItem as unequipItemAction } from '../crafting-actions/equipment-actions'; import { ErrorCode } from '../utils/result'; import { createSafeStorage } from '../utils/safe-persist'; import { createDefaultCraftingState } from './crafting-initial-state'; import { craftMaterial as craftMaterialAction } from '../crafting-actions/crafting-material-actions'; import { processEquipmentCraftingTick } from './crafting-equipment-tick'; import { startCraftingEquipment, cancelEquipmentCrafting, startFabricatorCrafting } from './pipelines/equipment-crafting'; export const useCraftingStore = create()( persist( (set, get) => { const defaultState = createDefaultCraftingState(); return { ...defaultState, // Actions setDesignProgress: (progress) => set({ designProgress: progress }), setDesignProgress2: (progress) => set({ designProgress2: progress }), setPreparationProgress: (progress) => set({ preparationProgress: progress }), setApplicationProgress: (progress) => set({ applicationProgress: progress }), setEquipmentCraftingProgress: (progress) => set({ equipmentCraftingProgress: progress }), // Enchantment design actions startDesigningEnchantment: (name, equipmentTypeId, effects) => { const state = get(); // crafting state const efficiencyBonus = getEnchantingEfficiencyBonus(); const validation = CraftingDesign.validateDesignEffects(effects, equipmentTypeId, 0, state.unlockedEffects); if (!validation.valid) return false; const equipType = CraftingUtils.getEquipmentType(equipmentTypeId); if (!equipType) return false; const totalCapacityCost = CraftingDesign.calculateDesignCapacityCost(effects, efficiencyBonus); if (totalCapacityCost > equipType.baseCapacity) return false; let updates: Partial = {}; if (!state.designProgress) { updates = { designProgress: { designId: CraftingUtils.generateDesignId(), progress: 0, required: CraftingDesign.calculateDesignTime(effects), name, equipmentType: equipmentTypeId, effects, }, }; // Update currentAction in combatStore useCombatStore.setState({ currentAction: 'design' }); } else if (!state.designProgress2) { updates = { designProgress2: { designId: CraftingUtils.generateDesignId(), progress: 0, required: CraftingDesign.calculateDesignTime(effects), name, equipmentType: equipmentTypeId, effects, }, }; useCombatStore.setState({ currentAction: 'design' }); } else { return false; } set(updates); return true; }, cancelDesign: (slot?: 1 | 2) => { const state = get(); if (slot === 2) { if (state.designProgress2) { set({ designProgress2: null }); } } else if (slot === 1) { if (state.designProgress) { set({ designProgress: null }); useCombatStore.setState({ currentAction: 'meditate' }); } } else if (state.designProgress) { set({ designProgress: null }); useCombatStore.setState({ currentAction: 'meditate' }); } else if (state.designProgress2) { set({ designProgress2: null }); } }, deleteDesign: (designId) => { set((state) => ({ enchantmentDesigns: state.enchantmentDesigns.filter(d => d.id !== designId), })); }, // Enchantment design save saveDesign: (design) => { const state = get(); if (state.designProgress2 && state.designProgress2.designId === design.id) { set((s) => ({ enchantmentDesigns: [...s.enchantmentDesigns, design], designProgress2: null, })); } else { set((s) => ({ enchantmentDesigns: [...s.enchantmentDesigns, design], designProgress: null, })); useCombatStore.setState({ currentAction: 'meditate' }); } }, // Enchantment application actions startApplying: (equipmentInstanceId, designId) => { const currentAction = useCombatStore.getState().currentAction; const result = ApplicationActions.startApplying( equipmentInstanceId, designId, get, set as unknown as (partial: Partial) => void, currentAction ); if (result) { useCombatStore.setState({ currentAction: 'enchant' }); } return result; }, pauseApplication: () => { ApplicationActions.pauseApplication(get, set as unknown as (partial: Partial) => void); }, resumeApplication: () => { ApplicationActions.resumeApplication(get, set as unknown as (partial: Partial) => void); }, cancelApplication: () => { ApplicationActions.cancelApplication(get, set as unknown as (partial: Partial) => void); useCombatStore.setState({ currentAction: 'meditate' }); }, // Preparation actions startPreparing: (equipmentInstanceId) => { const rawMana = useManaStore.getState().rawMana; const result = PreparationActions.startPreparing( equipmentInstanceId, rawMana, get, set ); if (result) { useCombatStore.setState({ currentAction: 'prepare' }); set({ lastError: null }); } else { const state = get(); const instance = state.equipmentInstances[equipmentInstanceId]; let message = 'Cannot start preparation'; if (!instance) { message = `Equipment instance not found: ${equipmentInstanceId}`; } else if (instance.tags?.includes('Ready for Enchantment')) { message = 'Equipment is already prepared'; } else { message = 'Insufficient mana for preparation'; } set({ lastError: { code: ErrorCode.INVALID_INPUT, message, timestamp: Date.now() } }); } return result; }, cancelPreparation: () => { PreparationActions.cancelPreparation(get, set); useCombatStore.setState({ currentAction: 'meditate' }); }, startCraftingEquipment: (blueprintId: string) => startCraftingEquipment(blueprintId, get, set), cancelEquipmentCrafting: () => cancelEquipmentCrafting(get, set), // Fabricator crafting — uses elemental mana instead of raw mana startFabricatorCrafting: (recipeId: string) => startFabricatorCrafting(recipeId, get, set), // Material crafting — instant crafting of materials craftMaterial: (recipeId: string) => { const state = get(); const result = craftMaterialAction(recipeId, state.lootInventory.materials); if (!result.success) return false; if (result.newMaterials) { set((s) => ({ lootInventory: { ...s.lootInventory, materials: result.newMaterials! } })); } return true; }, // Enchantment selection actions setSelectedEquipmentType: (type) => { set((s) => ({ enchantmentSelection: { ...s.enchantmentSelection, selectedEquipmentType: type }})); }, setSelectedEffects: (effects) => { set((s) => ({ enchantmentSelection: { ...s.enchantmentSelection, selectedEffects: effects } })); }, setDesignName: (name) => { set((s) => ({ enchantmentSelection: { ...s.enchantmentSelection, designName: name } })); }, setSelectedDesign: (id) => { set((s) => ({ enchantmentSelection: { ...s.enchantmentSelection, selectedDesign: id } })); }, setSelectedEquipmentInstance: (id) => { set((s) => ({ enchantmentSelection: { ...s.enchantmentSelection, selectedEquipmentInstance: id } })); }, resetEnchantmentSelection: () => { set((s) => ({ enchantmentSelection: { selectedEquipmentType: null, selectedEffects: [], designName: '', selectedDesign: null, selectedEquipmentInstance: null, }, })); }, // Loot inventory actions deleteMaterial: (materialId: string, amount: number) => { set((state) => { const newMaterials = { ...state.lootInventory.materials }; const currentAmount = newMaterials[materialId] || 0; const newAmount = Math.max(0, currentAmount - amount); if (newAmount <= 0) { delete newMaterials[materialId]; } else { newMaterials[materialId] = newAmount; } return { lootInventory: { ...state.lootInventory, materials: newMaterials, }, }; }); }, deleteEquipmentInstance: (instanceId: string) => { set((state) => { let newEquipped = { ...state.equippedInstances }; for (const [slot, id] of Object.entries(newEquipped)) { if (id === instanceId) { newEquipped[slot as EquipmentSlot] = null; } } const newInstances = { ...state.equipmentInstances }; delete newInstances[instanceId]; return { equippedInstances: newEquipped, equipmentInstances: newInstances, }; }); }, equipItem: (instanceId: string, slot: EquipmentSlot) => { return equipItemAction(instanceId, slot, get, set); }, unequipItem: (slot: EquipmentSlot) => { unequipItemAction(slot, set); }, clearLastError: () => set({ lastError: null }), unlockEffects: (effectIds: string[]) => { set((state) => { const existing = new Set(state.unlockedEffects); let changed = false; for (const id of effectIds) { if (!existing.has(id)) { existing.add(id); changed = true; } } if (!changed) return state; return { unlockedEffects: Array.from(existing) }; }); }, unlockRecipes: (recipeIds: string[]) => { set((state) => { const existing = new Set(state.unlockedRecipes); let changed = false; for (const id of recipeIds) { if (!existing.has(id)) { existing.add(id); changed = true; } } if (!changed) return state; return { unlockedRecipes: Array.from(existing) }; }); }, processEquipmentCraftingTick: (): { completed: boolean; logMessage?: string } => { const state = get(); return processEquipmentCraftingTick(state, set as unknown as (partial: Partial) => void); }, resetCrafting: () => { set(createDefaultCraftingState()); }, }; }, { storage: createSafeStorage(), name: 'mana-loop-crafting', version: 1, partialize: (state) => ({ designProgress: state.designProgress, designProgress2: state.designProgress2, preparationProgress: state.preparationProgress, applicationProgress: state.applicationProgress, equipmentCraftingProgress: state.equipmentCraftingProgress, enchantmentDesigns: state.enchantmentDesigns, unlockedEffects: state.unlockedEffects, unlockedRecipes: state.unlockedRecipes, equipmentInstances: state.equipmentInstances, equippedInstances: state.equippedInstances, lootInventory: state.lootInventory, enchantmentSelection: state.enchantmentSelection, lastError: state.lastError, }), } ) );