From 64b472572bc405d2e428314f153a4169a884c7fc Mon Sep 17 00:00:00 2001 From: n8n-gitea Date: Wed, 27 May 2026 11:06:24 +0200 Subject: [PATCH] fix: fabricator recipes now use correct elemental mana type - fabricator-recipes.ts: add optional manaType param to canCraftRecipe for clarity - FabricatorSubTab.tsx: read elemental mana from store based on recipe manaType instead of always using rawMana - craftingStore.ts: add startFabricatorCrafting action that deducts correct mana type - craftingStore.types.ts: add startFabricatorCrafting to CraftingActions interface - crafting-fabricator.ts: new helper file to keep craftingStore.ts under 400 lines Fixes #155 --- docs/circular-deps.txt | 2 +- docs/dependency-graph.json | 2 +- docs/project-structure.txt | 1 + .../tabs/CraftingTab/FabricatorSubTab.tsx | 26 ++-- src/lib/game/crafting-fabricator.ts | 141 ++++++++++++++++++ src/lib/game/data/fabricator-recipes.ts | 4 + src/lib/game/stores/craftingStore.ts | 48 ++++-- src/lib/game/stores/craftingStore.types.ts | 1 + 8 files changed, 198 insertions(+), 27 deletions(-) create mode 100644 src/lib/game/crafting-fabricator.ts diff --git a/docs/circular-deps.txt b/docs/circular-deps.txt index 80b8e88..2a00704 100644 --- a/docs/circular-deps.txt +++ b/docs/circular-deps.txt @@ -1,4 +1,4 @@ # Circular Dependencies -Generated: 2026-05-26T19:57:56.106Z +Generated: 2026-05-27T08:45:44.894Z No circular dependencies found. ✅ diff --git a/docs/dependency-graph.json b/docs/dependency-graph.json index 3854d17..6c2b6b4 100644 --- a/docs/dependency-graph.json +++ b/docs/dependency-graph.json @@ -1,6 +1,6 @@ { "_meta": { - "generated": "2026-05-26T19:57:54.313Z", + "generated": "2026-05-27T08:45:43.143Z", "description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.", "usage": "To find what a file affects, search for its path in the VALUES. To find what a file depends on, look at its KEY entry." }, diff --git a/docs/project-structure.txt b/docs/project-structure.txt index 283c02b..4b6be3f 100644 --- a/docs/project-structure.txt +++ b/docs/project-structure.txt @@ -369,6 +369,7 @@ Mana-Loop/ │ │ ├── crafting-attunements.ts │ │ ├── crafting-design.ts │ │ ├── crafting-equipment.ts +│ │ ├── crafting-fabricator.ts │ │ ├── crafting-loot.ts │ │ ├── crafting-prep.ts │ │ ├── crafting-utils.ts diff --git a/src/components/game/tabs/CraftingTab/FabricatorSubTab.tsx b/src/components/game/tabs/CraftingTab/FabricatorSubTab.tsx index c935462..ec959cb 100644 --- a/src/components/game/tabs/CraftingTab/FabricatorSubTab.tsx +++ b/src/components/game/tabs/CraftingTab/FabricatorSubTab.tsx @@ -27,20 +27,27 @@ const MANA_TYPE_LABELS: Record = { function RecipeCard({ recipe, materials, - manaAmount, + rawMana, + elementalMana, onCraft, isCrafting, }: { recipe: FabricatorRecipe; materials: Record; - manaAmount: number; + rawMana: number; + elementalMana: Record; onCraft: (recipe: FabricatorRecipe) => void; isCrafting: boolean; }) { + // Determine the correct mana amount based on the recipe's mana type + const pool = recipe.manaType === 'raw' + ? rawMana + : (elementalMana[recipe.manaType]?.current ?? 0); const { canCraft } = canCraftRecipe( recipe, materials, - manaAmount, + pool, + recipe.manaType, ); const rarityStyle = LOOT_RARITY_COLORS[recipe.rarity]; @@ -85,8 +92,8 @@ function RecipeCard({
{MANA_TYPE_LABELS[recipe.manaType]?.split(' ')[1] ?? recipe.manaType} Mana: - = recipe.manaCost ? 'text-green-400' : 'text-red-400'}> - {manaAmount} / {recipe.manaCost} + = recipe.manaCost ? 'text-green-400' : 'text-red-400'}> + {pool} / {recipe.manaCost}
@@ -114,7 +121,8 @@ export function FabricatorSubTab() { const lootInventory = useCraftingStore((s) => s.lootInventory); const equipmentCraftingProgress = useCraftingStore((s) => s.equipmentCraftingProgress); const rawMana = useManaStore((s) => s.rawMana); - const startCraftingEquipment = useCraftingStore((s) => s.startCraftingEquipment); + const elements = useManaStore((s) => s.elements); + const startFabricatorCrafting = useCraftingStore((s) => s.startFabricatorCrafting); const cancelEquipmentCrafting = useCraftingStore((s) => s.cancelEquipmentCrafting); const availableManaTypes = useMemo(() => { @@ -129,8 +137,7 @@ export function FabricatorSubTab() { const isCrafting = equipmentCraftingProgress !== null; const handleCraft = (recipe: FabricatorRecipe) => { - // Use the existing equipment crafting system with a fabricator-specific blueprint ID - startCraftingEquipment(`fabricator-${recipe.id}`); + startFabricatorCrafting(recipe.id); }; return ( @@ -197,7 +204,8 @@ export function FabricatorSubTab() { key={recipe.id} recipe={recipe} materials={lootInventory.materials} - manaAmount={rawMana} + rawMana={rawMana} + elementalMana={elements} onCraft={handleCraft} isCrafting={isCrafting} /> diff --git a/src/lib/game/crafting-fabricator.ts b/src/lib/game/crafting-fabricator.ts new file mode 100644 index 0000000..59103a0 --- /dev/null +++ b/src/lib/game/crafting-fabricator.ts @@ -0,0 +1,141 @@ +// ─── Fabricator Crafting Helpers ────────────────────────────────────────────── +// Separate file to avoid exceeding the 400-line limit in craftingStore.ts. + +import type { EquipmentCraftingProgress } from './types'; +import type { FabricatorRecipe } from './data/fabricator-recipes'; +import { FABRICATOR_RECIPES } from './data/fabricator-recipes'; + +// ─── Lookup ─────────────────────────────────────────────────────────────────── + +export function getFabricatorRecipe(recipeId: string): FabricatorRecipe | undefined { + return FABRICATOR_RECIPES.find(r => r.id === recipeId); +} + +// ─── Cost Check ─────────────────────────────────────────────────────────────── + +export interface FabricatorCostCheck { + canCraft: boolean; + missingMana: number; + missingMaterials: Record; +} + +export function checkFabricatorCosts( + recipe: FabricatorRecipe, + materials: Record, + rawMana: number, + elements: Record, +): FabricatorCostCheck { + const missingMaterials: Record = {}; + let canCraft = true; + + for (const [matId, required] of Object.entries(recipe.materials)) { + 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, +): Record { + const newMaterials = { ...materials }; + for (const [matId, amount] of Object.entries(recipe.materials)) { + 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, + }; +} diff --git a/src/lib/game/data/fabricator-recipes.ts b/src/lib/game/data/fabricator-recipes.ts index 04918c2..cbddb62 100644 --- a/src/lib/game/data/fabricator-recipes.ts +++ b/src/lib/game/data/fabricator-recipes.ts @@ -206,6 +206,7 @@ export function canCraftRecipe( recipe: FabricatorRecipe, materials: Record, manaAmount: number, + manaType?: string, ): { canCraft: boolean; missingMaterials: Record; missingMana: number } { const missingMaterials: Record = {}; let canCraft = true; @@ -218,6 +219,9 @@ export function canCraftRecipe( } } + // If manaType is provided, manaAmount is already the correct pool value. + // Otherwise fall back to treating manaAmount as raw mana (backward compat). + const effectiveManaType = manaType ?? recipe.manaType; const missingMana = Math.max(0, recipe.manaCost - manaAmount); if (missingMana > 0) { canCraft = false; diff --git a/src/lib/game/stores/craftingStore.ts b/src/lib/game/stores/craftingStore.ts index a7200eb..01d1c92 100644 --- a/src/lib/game/stores/craftingStore.ts +++ b/src/lib/game/stores/craftingStore.ts @@ -16,6 +16,12 @@ import { equipItem as equipItemAction, unequipItem as unequipItemAction } from ' import { ErrorCode } from '../utils/result'; import { createSafeStorage } from '../utils/safe-persist'; import { createInitialEquipmentInstances } from './crafting-initial-state'; +import { + getFabricatorRecipe, + deductFabricatorMana, + deductMaterials, + makeFabricatorProgress, +} from '../crafting-fabricator'; export const useCraftingStore = create()( persist( @@ -235,29 +241,39 @@ export const useCraftingStore = create()( }, cancelEquipmentCrafting: () => { - const state = get(); - const progress = state.equipmentCraftingProgress; + const progress = get().equipmentCraftingProgress; if (!progress) return; - - // Get cancel result (mana refund) - const cancelResult = CraftingEquipment.cancelEquipmentCrafting( - progress.blueprintId, - progress.manaSpent - ); - - // Update crafting store state + const cancelResult = CraftingEquipment.cancelEquipmentCrafting(progress.blueprintId, progress.manaSpent); set({ equipmentCraftingProgress: null }); - - // Refund mana to mana store useManaStore.setState((s) => ({ rawMana: s.rawMana + cancelResult.manaRefund })); - - // Update combat store (reset action) useCombatStore.setState({ currentAction: 'meditate' }); - - // Add log message to UI store useUIStore.getState().addLog(cancelResult.logMessage); }, + // Fabricator crafting — uses elemental mana instead of raw mana + startFabricatorCrafting: (recipeId: string) => { + const state = get(); + const currentAction = useCombatStore.getState().currentAction; + if (currentAction !== 'meditate') return false; + + const recipe = getFabricatorRecipe(recipeId); + if (!recipe) return false; + + const rawMana = useManaStore.getState().rawMana; + const elements = useManaStore.getState().elements; + + const deducted = deductFabricatorMana(recipe, rawMana, elements); + if (!deducted) return false; + + const newMaterials = deductMaterials(recipe, state.lootInventory.materials); + const progress = makeFabricatorProgress(recipeId, recipe.equipmentTypeId, recipe.craftTime, recipe.manaCost); + + useManaStore.setState({ rawMana: deducted.rawMana, elements: deducted.elements }); + set((s) => ({ lootInventory: { ...s.lootInventory, materials: newMaterials }, equipmentCraftingProgress: progress })); + useCombatStore.setState({ currentAction: 'craft' }); + return true; + }, + // Enchantment selection actions setSelectedEquipmentType: (type) => { set((s) => ({ enchantmentSelection: { ...s.enchantmentSelection, selectedEquipmentType: type }})); diff --git a/src/lib/game/stores/craftingStore.types.ts b/src/lib/game/stores/craftingStore.types.ts index 71a3f79..cb9e4cb 100644 --- a/src/lib/game/stores/craftingStore.types.ts +++ b/src/lib/game/stores/craftingStore.types.ts @@ -61,6 +61,7 @@ export interface CraftingActions { equipItem: (instanceId: string, slot: EquipmentSlot) => boolean; unequipItem: (slot: EquipmentSlot) => void; startCraftingEquipment: (blueprintId: string) => boolean; + startFabricatorCrafting: (recipeId: string) => boolean; cancelEquipmentCrafting: () => void; setSelectedEquipmentType: (type: string | null) => void; setSelectedEffects: (effects: DesignEffect[]) => void;