// ─── Fabricator Crafting Helpers ────────────────────────────────────────────── // Separate file to avoid exceeding the 400-line limit in craftingStore.ts. import type { EquipmentCraftingProgress } from './types'; import type { ElementState } from './types'; import type { FabricatorRecipe } from './data/fabricator-recipes'; import { FABRICATOR_RECIPES } from './data/fabricator-recipes'; import { useManaStore } from './stores/manaStore'; import { computeDisciplineEffects } from './effects/discipline-effects'; // ─── Lookup ─────────────────────────────────────────────────────────────────── export function getFabricatorRecipe(recipeId: string): FabricatorRecipe | undefined { return FABRICATOR_RECIPES.find(r => r.id === recipeId); } // ─── Crafting Cost Reduction ────────────────────────────────────────────────── /** * Returns the current crafting cost reduction percentage from discipline effects. * Spec: actualCost = baseCost × (1 - craftingCostReduction / 100), capped at 75%. */ export function getCraftingCostReduction(): number { const disciplineEffects = computeDisciplineEffects(); const raw = disciplineEffects.bonuses.craftingCostReduction || 0; return Math.min(75, raw); } /** * Apply cost reduction to a material amount, rounding up to ensure * the player never pays more than the displayed reduced cost. */ export function applyCostReduction(baseAmount: number, costReduction: number): number { if (costReduction <= 0) return baseAmount; return Math.max(1, Math.ceil(baseAmount * (1 - costReduction / 100))); } // ─── Cost Check ─────────────────────────────────────────────────────────────── export interface FabricatorCostCheck { canCraft: boolean; missingMana: number; missingMaterials: Record; } export function checkFabricatorCosts( recipe: FabricatorRecipe, materials: Record, rawMana: number, elements: Record, costReduction?: number, ): FabricatorCostCheck { const reduction = costReduction ?? getCraftingCostReduction(); const missingMaterials: Record = {}; let canCraft = true; for (const [matId, rawRequired] of Object.entries(recipe.materials)) { const required = applyCostReduction(rawRequired, reduction); const available = materials[matId] || 0; if (available < required) { missingMaterials[matId] = required - available; canCraft = false; } } const manaAvailable = recipe.manaType === 'raw' ? rawMana : (elements[recipe.manaType]?.current ?? 0); const missingMana = Math.max(0, recipe.manaCost - manaAvailable); if (missingMana > 0) canCraft = false; return { canCraft, missingMana, missingMaterials }; } // ─── Mana Deduction ─────────────────────────────────────────────────────────── export interface ManaDeduction { rawMana: number; elements: Record; } export function deductFabricatorMana( recipe: FabricatorRecipe, rawMana: number, elements: Record, ): ManaDeduction | null { if (recipe.manaType === 'raw') { if (rawMana < recipe.manaCost) return null; return { rawMana: rawMana - recipe.manaCost, elements }; } const elemState = elements[recipe.manaType]; if (!elemState || !elemState.unlocked || elemState.current < recipe.manaCost) return null; return { rawMana, elements: { ...elements, [recipe.manaType]: { ...elements[recipe.manaType], current: elements[recipe.manaType].current - recipe.manaCost, }, }, }; } // ─── Mana Refund (on cancel) ────────────────────────────────────────────────── export interface ManaRefund { rawMana: number; elements: Record; } export function refundFabricatorMana( recipe: FabricatorRecipe, refundAmount: number, rawMana: number, elements: Record, ): ManaRefund { if (recipe.manaType === 'raw') { return { rawMana: rawMana + refundAmount, elements }; } return { rawMana, elements: { ...elements, [recipe.manaType]: { ...elements[recipe.manaType], current: Math.min( elements[recipe.manaType].max, elements[recipe.manaType].current + refundAmount, ), }, }, }; } // ─── Material Deduction ─────────────────────────────────────────────────────── export function deductMaterials( recipe: FabricatorRecipe, materials: Record, costReduction?: number, ): Record { const reduction = costReduction ?? getCraftingCostReduction(); const newMaterials = { ...materials }; for (const [matId, rawAmount] of Object.entries(recipe.materials)) { const amount = applyCostReduction(rawAmount, reduction); newMaterials[matId] = (newMaterials[matId] || 0) - amount; if (newMaterials[matId] <= 0) delete newMaterials[matId]; } return newMaterials; } // ─── Progress Init ──────────────────────────────────────────────────────────── export function makeFabricatorProgress( recipeId: string, equipmentTypeId: string, craftTime: number, manaCost: number, ): EquipmentCraftingProgress { return { blueprintId: `fabricator-${recipeId}`, equipmentTypeId, progress: 0, required: craftTime, manaSpent: manaCost, }; } // ─── Material Crafting ────────────────────────────────────────────────────── export interface CraftMaterialResult { success: boolean; newMaterials: Record; newRawMana: number; newElements: Record; logMessage: string; } export function executeMaterialCraft( recipe: FabricatorRecipe, materials: Record, ): CraftMaterialResult | null { if (recipe.recipeType !== 'material' || !recipe.resultMaterial || !recipe.resultAmount) return null; const rawMana = useManaStore.getState().rawMana; const elements = useManaStore.getState().elements; const deducted = deductFabricatorMana(recipe, rawMana, elements); if (!deducted) return null; const newMaterials = deductMaterials(recipe, materials); newMaterials[recipe.resultMaterial] = (newMaterials[recipe.resultMaterial] || 0) + recipe.resultAmount; return { success: true, newMaterials, newRawMana: deducted.rawMana, newElements: deducted.elements, logMessage: `✨ Crafted ${recipe.resultAmount}x ${recipe.name}!`, }; }