Add equipment crafting system

- Add crafting-recipes.ts with blueprint definitions and material requirements
- Update crafting-slice.ts with equipment crafting functions
- Add EquipmentCraftingProgress type and state
- Update CraftingTab.tsx with new Craft tab for equipment crafting
- Add material deletion functionality
- Update store.ts with equipment crafting methods
- Update page.tsx to pass new props to CraftingTab

Features:
- Players can craft equipment from discovered blueprints
- Crafting requires materials and mana
- Materials are obtained from loot drops
- New Craft tab in the crafting interface
- Shows blueprint details and material requirements
This commit is contained in:
2026-03-26 11:04:50 +00:00
parent 4f4cbeb527
commit 1d2dce75cc
6 changed files with 734 additions and 5 deletions

View File

@@ -1,9 +1,10 @@
// ─── Crafting Store Slice ─────────────────────────────────────────────────────────
// Handles equipment and enchantment system: design, prepare, apply stages
import type { GameState, EquipmentInstance, AppliedEnchantment, EnchantmentDesign, DesignEffect, EquipmentSlot } from './types';
import type { GameState, EquipmentInstance, AppliedEnchantment, EnchantmentDesign, DesignEffect, EquipmentSlot, EquipmentCraftingProgress, LootInventory } from './types';
import { EQUIPMENT_TYPES, type EquipmentCategory } from './data/equipment';
import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from './data/enchantment-effects';
import { CRAFTING_RECIPES, canCraftRecipe, type CraftingRecipe } from './data/crafting-recipes';
import { SPELLS_DEF } from './constants';
// ─── Helper Functions ─────────────────────────────────────────────────────────
@@ -88,6 +89,11 @@ export interface CraftingActions {
// Disenchanting
disenchantEquipment: (instanceId: string) => void;
// Equipment Crafting (from blueprints)
startCraftingEquipment: (blueprintId: string) => boolean;
cancelEquipmentCrafting: () => void;
deleteMaterial: (materialId: string, amount: number) => void;
// Computed getters
getEquipmentSpells: () => string[];
getEquipmentEffects: () => Record<string, number>;
@@ -502,6 +508,97 @@ export function createCraftingSlice(
if (!instance) return 0;
return instance.totalCapacity - instance.usedCapacity;
},
// ─── Equipment Crafting (from Blueprints) ───────────────────────────────────
startCraftingEquipment: (blueprintId: string) => {
const state = get();
const recipe = CRAFTING_RECIPES[blueprintId];
if (!recipe) return false;
// Check if player has the blueprint
if (!state.lootInventory.blueprints.includes(blueprintId)) return false;
// Check materials
const { canCraft, missingMaterials, missingMana } = canCraftRecipe(
recipe,
state.lootInventory.materials,
state.rawMana
);
if (!canCraft) return false;
// Deduct materials
const newMaterials = { ...state.lootInventory.materials };
for (const [matId, amount] of Object.entries(recipe.materials)) {
newMaterials[matId] = (newMaterials[matId] || 0) - amount;
if (newMaterials[matId] <= 0) {
delete newMaterials[matId];
}
}
// Start crafting progress
set((state) => ({
lootInventory: {
...state.lootInventory,
materials: newMaterials,
},
rawMana: state.rawMana - recipe.manaCost,
currentAction: 'craft',
equipmentCraftingProgress: {
blueprintId,
equipmentTypeId: recipe.equipmentTypeId,
progress: 0,
required: recipe.craftTime,
manaSpent: recipe.manaCost,
},
}));
return true;
},
cancelEquipmentCrafting: () => {
set((state) => {
const progress = state.equipmentCraftingProgress;
if (!progress) return {};
const recipe = CRAFTING_RECIPES[progress.blueprintId];
if (!recipe) return { currentAction: 'meditate', equipmentCraftingProgress: null };
// Refund 50% of mana
const manaRefund = Math.floor(progress.manaSpent * 0.5);
return {
currentAction: 'meditate',
equipmentCraftingProgress: null,
rawMana: state.rawMana + manaRefund,
log: [`🚫 Equipment crafting cancelled. Refunded ${manaRefund} mana.`, ...state.log.slice(0, 49)],
};
});
},
deleteMaterial: (materialId: string, amount: number) => {
set((state) => {
const currentAmount = state.lootInventory.materials[materialId] || 0;
const newAmount = Math.max(0, currentAmount - amount);
const newMaterials = { ...state.lootInventory.materials };
if (newAmount <= 0) {
delete newMaterials[materialId];
} else {
newMaterials[materialId] = newAmount;
}
const dropName = materialId; // Could look up in LOOT_DROPS for proper name
return {
lootInventory: {
...state.lootInventory,
materials: newMaterials,
},
log: [`🗑️ Deleted ${amount}x ${dropName}.`, ...state.log.slice(0, 49)],
};
});
},
};
}
@@ -620,6 +717,59 @@ export function processCraftingTick(
}
}
// Process equipment crafting progress
if (state.currentAction === 'craft' && state.equipmentCraftingProgress) {
const craft = state.equipmentCraftingProgress;
const progress = craft.progress + 0.04; // HOURS_PER_TICK
if (progress >= craft.required) {
// Crafting complete - create the equipment!
const recipe = CRAFTING_RECIPES[craft.blueprintId];
const equipType = recipe ? EQUIPMENT_TYPES[recipe.equipmentTypeId] : null;
if (recipe && equipType) {
const instanceId = generateInstanceId();
const newInstance: EquipmentInstance = {
instanceId,
typeId: recipe.equipmentTypeId,
name: recipe.name,
enchantments: [],
usedCapacity: 0,
totalCapacity: equipType.baseCapacity,
rarity: recipe.rarity,
quality: 100,
};
updates = {
...updates,
equipmentCraftingProgress: null,
currentAction: 'meditate',
equipmentInstances: {
...state.equipmentInstances,
[instanceId]: newInstance,
},
totalCraftsCompleted: (state.totalCraftsCompleted || 0) + 1,
log: [`🔨 Crafted ${recipe.name}!`, ...log],
};
} else {
updates = {
...updates,
equipmentCraftingProgress: null,
currentAction: 'meditate',
log: ['⚠️ Crafting failed - invalid recipe!', ...log],
};
}
} else {
updates = {
...updates,
equipmentCraftingProgress: {
...craft,
progress,
},
};
}
}
return updates;
}