fix: fabricator recipes now use correct elemental mana type
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m37s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m37s
- 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
This commit is contained in:
@@ -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<string, number>;
|
||||
}
|
||||
|
||||
export function checkFabricatorCosts(
|
||||
recipe: FabricatorRecipe,
|
||||
materials: Record<string, number>,
|
||||
rawMana: number,
|
||||
elements: Record<string, { current: number; max: number; unlocked: boolean }>,
|
||||
): FabricatorCostCheck {
|
||||
const missingMaterials: Record<string, number> = {};
|
||||
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<string, { current: number; max: number; unlocked: boolean }>;
|
||||
}
|
||||
|
||||
export function deductFabricatorMana(
|
||||
recipe: FabricatorRecipe,
|
||||
rawMana: number,
|
||||
elements: Record<string, { current: number; max: number; unlocked: boolean }>,
|
||||
): 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<string, { current: number; max: number; unlocked: boolean }>;
|
||||
}
|
||||
|
||||
export function refundFabricatorMana(
|
||||
recipe: FabricatorRecipe,
|
||||
refundAmount: number,
|
||||
rawMana: number,
|
||||
elements: Record<string, { current: number; max: number; unlocked: boolean }>,
|
||||
): 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<string, number>,
|
||||
): Record<string, number> {
|
||||
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,
|
||||
};
|
||||
}
|
||||
@@ -206,6 +206,7 @@ export function canCraftRecipe(
|
||||
recipe: FabricatorRecipe,
|
||||
materials: Record<string, number>,
|
||||
manaAmount: number,
|
||||
manaType?: string,
|
||||
): { canCraft: boolean; missingMaterials: Record<string, number>; missingMana: number } {
|
||||
const missingMaterials: Record<string, number> = {};
|
||||
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;
|
||||
|
||||
@@ -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<CraftingStore>()(
|
||||
persist(
|
||||
@@ -235,29 +241,39 @@ export const useCraftingStore = create<CraftingStore>()(
|
||||
},
|
||||
|
||||
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 }}));
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user