// ─── 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, 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 './upgrade-effects'; import { hasSpecial, SPECIAL_EFFECTS } from './special-effects'; import type { ComputedEffects } from './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; getAvailableCapacity: (instanceId: string) => number; }; // ─── Crafting Store Slice ─────────────────────────────────────────────────── export function createCraftingSlice( set: (fn: (state: GameState) => Partial) => 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 { const { rawMana, log } = effects; let updates: Partial = {}; 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, 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)]; }