Files
Mana-Loop/src/lib/game/crafting-fabricator.ts
T
n8n-gitea 0e1e506213
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m18s
fix: apply Crafting Efficiency cost reduction to all fabrication paths
- Add getCraftingCostReduction() and applyCostReduction() helpers in crafting-fabricator.ts
- Apply cost reduction in deductMaterials() and checkFabricatorCosts()
- Apply cost reduction in startFabricatorCrafting() and cancelEquipmentCrafting() pipeline
- Update canCraftRecipe() in fabricator-recipes.ts to accept costReduction param
- Update FabricatorSubTab and MaterialRecipeCard UIs to display discounted costs
- Spec formula: actualCost = ceil(baseCost × (1 - craftingCostReduction / 100))

Fixes #316
2026-06-07 23:15:55 +02:00

206 lines
7.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ─── 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<string, number>;
}
export function checkFabricatorCosts(
recipe: FabricatorRecipe,
materials: Record<string, number>,
rawMana: number,
elements: Record<string, ElementState>,
costReduction?: number,
): FabricatorCostCheck {
const reduction = costReduction ?? getCraftingCostReduction();
const missingMaterials: Record<string, number> = {};
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<string, ElementState>;
}
export function deductFabricatorMana(
recipe: FabricatorRecipe,
rawMana: number,
elements: Record<string, ElementState>,
): 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, ElementState>;
}
export function refundFabricatorMana(
recipe: FabricatorRecipe,
refundAmount: number,
rawMana: number,
elements: Record<string, ElementState>,
): 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>,
costReduction?: number,
): Record<string, number> {
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<string, number>;
newRawMana: number;
newElements: Record<string, ElementState>;
logMessage: string;
}
export function executeMaterialCraft(
recipe: FabricatorRecipe,
materials: Record<string, number>,
): 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}!`,
};
}