Files
Mana-Loop/src/lib/game/crafting-equipment.ts
T

191 lines
7.5 KiB
TypeScript

// ─── Equipment Crafting System ──────────────────────────────────────────────
// Equipment crafting functions
import type { EquipmentInstance, EquipmentCraftingProgress, AppliedEnchantment } from './types';
import { CRAFTING_RECIPES, canCraftRecipe, type CraftingRecipe } from './data/crafting-recipes';
import { EQUIPMENT_TYPES } from './data/equipment';
import { ok, fail, ErrorCode } from './utils/result';
import type { Result } from './utils/result';
import { HOURS_PER_TICK } from './constants';
const MANA_REFUND_RATE = 0.5;
// ─── Equipment Crafting Validation ──────────────────────────────────────────
export function canStartEquipmentCrafting(
blueprintId: string,
hasBlueprint: boolean,
materials: Record<string, number>,
currentMana: number,
currentAction: string
): { canCraft: boolean; reason?: string; recipe?: CraftingRecipe; missingMaterials?: Record<string, number>; missingMana?: number } {
if (currentAction !== 'meditate') return { canCraft: false, reason: 'Must be in meditate state' };
const recipe = CRAFTING_RECIPES[blueprintId];
if (!recipe) return { canCraft: false, reason: 'Invalid blueprint' };
if (!hasBlueprint) return { canCraft: false, reason: 'Blueprint not acquired' };
const { canCraft, missingMaterials } = canCraftRecipe(recipe, materials, currentMana);
if (canCraft) return { canCraft: true, recipe };
const missingManaAmount = Math.max(0, recipe.manaCost - currentMana);
return {
canCraft: false,
reason: missingManaAmount > 0 ? 'Insufficient mana' : 'Missing materials',
recipe,
missingMaterials,
missingMana: missingManaAmount > 0 ? missingManaAmount : undefined,
};
}
// ─── Equipment Crafting Execution ───────────────────────────────────────────
export interface CraftingInitResult {
recipe: CraftingRecipe;
newMaterials: Record<string, number>;
manaCost: number;
progress: EquipmentCraftingProgress;
}
export function initializeEquipmentCrafting(
blueprintId: string,
materials: Record<string, number>,
_currentMana: number
): CraftingInitResult {
const recipe = CRAFTING_RECIPES[blueprintId];
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 {
recipe,
newMaterials,
manaCost: recipe.manaCost,
progress: {
blueprintId,
equipmentTypeId: recipe.equipmentTypeId,
progress: 0,
required: recipe.craftTime,
manaSpent: recipe.manaCost,
},
};
}
// ─── Crafting Progress ──────────────────────────────────────────────────────
export interface CraftingTickResult {
progress: number;
isComplete: boolean;
}
export function calculateCraftingTick(currentProgress: number, required: number): CraftingTickResult {
const progress = currentProgress + HOURS_PER_TICK;
return { progress, isComplete: progress >= required };
}
// ─── Crafting Completion ───────────────────────────────────────────────────
const BASE_EQUIPMENT_QUALITY = 100;
export function completeEquipmentCrafting(
blueprintId: string,
recipe: CraftingRecipe,
bonusEnchantments: AppliedEnchantment[] = []
): Result<{ instanceId: string; instance: EquipmentInstance; logMessage: string }> {
const equipType = EQUIPMENT_TYPES[recipe.equipmentTypeId];
if (!equipType) return fail(ErrorCode.INVALID_EQUIPMENT_TYPE, `Invalid equipment type: ${recipe.equipmentTypeId}`);
const instanceId = `equip_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const usedCapacity = bonusEnchantments.reduce((sum, e) => sum + e.actualCost, 0);
return ok({
instanceId,
instance: {
instanceId,
typeId: recipe.equipmentTypeId,
name: recipe.name,
enchantments: bonusEnchantments,
usedCapacity,
totalCapacity: equipType.baseCapacity,
rarity: recipe.rarity,
quality: BASE_EQUIPMENT_QUALITY,
tags: [],
},
logMessage: `🔨 Crafted ${recipe.name}!`,
});
}
// ─── Crafting Cancellation ──────────────────────────────────────────────────
export interface CraftingCancelResult {
manaRefund: number;
logMessage: string;
}
export function cancelEquipmentCrafting(blueprintId: string, manaSpent: number, currentProgress?: number, requiredProgress?: number): CraftingCancelResult {
const recipe = CRAFTING_RECIPES[blueprintId];
if (!recipe) return { manaRefund: 0, logMessage: 'Invalid crafting recipe.' };
// Refund proportional to remaining progress: unspent portion + half of spent portion
let refundRate: number;
if (currentProgress !== undefined && requiredProgress && requiredProgress > 0) {
const remainingFraction = Math.max(0, (requiredProgress - currentProgress) / requiredProgress);
// Full refund for unspent progress, flat 50% for spent progress
refundRate = remainingFraction + (1 - remainingFraction) * MANA_REFUND_RATE;
} else {
refundRate = MANA_REFUND_RATE;
}
const manaRefund = Math.floor(manaSpent * refundRate);
return { manaRefund, logMessage: `🚫 Equipment crafting cancelled. Refunded ${manaRefund} mana.` };
}
// ─── Recipe Information ─────────────────────────────────────────────────────
export function getRecipe(blueprintId: string): CraftingRecipe | null {
return CRAFTING_RECIPES[blueprintId] || null;
}
export function getCraftableRecipes(
blueprints: string[],
materials: Record<string, number>,
currentMana: number
): CraftingRecipe[] {
const craftable: CraftingRecipe[] = [];
for (const blueprintId of blueprints) {
const recipe = CRAFTING_RECIPES[blueprintId];
if (!recipe) continue;
if (canCraftRecipe(recipe, materials, currentMana).canCraft) craftable.push(recipe);
}
return craftable;
}
// ─── Material Management ────────────────────────────────────────────────────
export function deleteMaterials(materialId: string, amount: number, materials: Record<string, number>): {
newMaterials: Record<string, number>;
deleted: number;
} {
const currentAmount = materials[materialId] || 0;
const deleted = Math.min(amount, currentAmount);
const remaining = Math.max(0, currentAmount - amount);
const newMaterials = { ...materials };
if (remaining <= 0) {
delete newMaterials[materialId];
} else {
newMaterials[materialId] = remaining;
}
return { newMaterials, deleted };
}
export function getMaterialCount(materials: Record<string, number>, materialId: string): number {
return materials[materialId] || 0;
}
export function addMaterials(materials: Record<string, number>, materialId: string, amount: number): Record<string, number> {
const newMaterials = { ...materials };
newMaterials[materialId] = (newMaterials[materialId] || 0) + amount;
return newMaterials;
}