feat: recreate Crafting Tab with Fabricator and Enchanter sub-tabs
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 2m18s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 2m18s
- Add fabricator-recipes.ts with 12 recipes across earth/metal/crystal/sand mana types - Add FabricatorSubTab with mana-type filtering, recipe cards, materials inventory - Add EnchanterSubTab integrating existing 3-phase flow (Design → Prepare → Apply) - Add CraftingTab main component with clsx-based sub-tab system (matches DisciplinesTab pattern) - Wire into tabs barrel export and page.tsx with lazy loading + DebugName wrapper - Add 17 tests covering exports, displayNames, recipe data integrity, helpers, file sizes - All files under 400 lines
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
// ─── Fabricator Recipes ──────────────────────────────────────────────────────
|
||||
// Crafting recipes for the Fabricator attunement.
|
||||
// Each recipe is tied to a mana type the player has unlocked.
|
||||
|
||||
import type { EquipmentSlot } from './equipment/types';
|
||||
|
||||
export interface FabricatorRecipe {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
/** Mana type required to craft this recipe (must be unlocked) */
|
||||
manaType: string;
|
||||
/** Equipment type ID produced */
|
||||
equipmentTypeId: string;
|
||||
/** Which slot the resulting equipment occupies */
|
||||
slot: EquipmentSlot;
|
||||
/** Materials required: materialId -> count */
|
||||
materials: Record<string, number>;
|
||||
/** Mana cost in the recipe's mana type */
|
||||
manaCost: number;
|
||||
/** Craft time in hours */
|
||||
craftTime: number;
|
||||
/** Rarity tier */
|
||||
rarity: 'common' | 'uncommon' | 'rare' | 'epic' | 'legendary';
|
||||
/** Flavor text describing the gear's properties */
|
||||
gearTrait: string;
|
||||
}
|
||||
|
||||
export const FABRICATOR_RECIPES: FabricatorRecipe[] = [
|
||||
// ─── Earth Gear (Compacted Earth — high defense) ──────────────────────
|
||||
{
|
||||
id: 'earthHelm',
|
||||
name: 'Earthen Helm',
|
||||
description: 'A sturdy helm carved from compacted stone.',
|
||||
manaType: 'earth',
|
||||
equipmentTypeId: 'wizardHat',
|
||||
slot: 'head',
|
||||
materials: { manaCrystalDust: 4, arcaneShard: 2 },
|
||||
manaCost: 200,
|
||||
craftTime: 3,
|
||||
rarity: 'uncommon',
|
||||
gearTrait: '+15% physical resistance',
|
||||
},
|
||||
{
|
||||
id: 'earthChest',
|
||||
name: 'Stoneguard Armor',
|
||||
description: 'Heavy stone plates layered over leather. Slow but nearly impenetrable.',
|
||||
manaType: 'earth',
|
||||
equipmentTypeId: 'scholarRobe',
|
||||
slot: 'body',
|
||||
materials: { manaCrystalDust: 8, arcaneShard: 4, elementalCore: 1 },
|
||||
manaCost: 500,
|
||||
craftTime: 6,
|
||||
rarity: 'rare',
|
||||
gearTrait: '+25% physical resistance, -10% cast speed',
|
||||
},
|
||||
{
|
||||
id: 'earthBoots',
|
||||
name: 'Stonegreaves',
|
||||
description: 'Boots reinforced with compacted earth. Firm footing in any battle.',
|
||||
manaType: 'earth',
|
||||
equipmentTypeId: 'travelerBoots',
|
||||
slot: 'feet',
|
||||
materials: { manaCrystalDust: 3, arcaneShard: 1 },
|
||||
manaCost: 150,
|
||||
craftTime: 2,
|
||||
rarity: 'uncommon',
|
||||
gearTrait: '+10% physical resistance',
|
||||
},
|
||||
|
||||
// ─── Metal Gear (Fire+Earth — balanced offense/defense) ──────────────
|
||||
{
|
||||
id: 'metalBlade',
|
||||
name: 'Metal Blade',
|
||||
description: 'A blade forged from condensed metal mana. Sharp and durable.',
|
||||
manaType: 'metal',
|
||||
equipmentTypeId: 'steelBlade',
|
||||
slot: 'mainHand',
|
||||
materials: { manaCrystalDust: 6, arcaneShard: 3, elementalCore: 2 },
|
||||
manaCost: 400,
|
||||
craftTime: 5,
|
||||
rarity: 'rare',
|
||||
gearTrait: '+20% damage, +10% durability',
|
||||
},
|
||||
{
|
||||
id: 'metalShield',
|
||||
name: 'Metal Kite Shield',
|
||||
description: 'A polished metal shield. Reliable protection without excessive weight.',
|
||||
manaType: 'metal',
|
||||
equipmentTypeId: 'runicShield',
|
||||
slot: 'offHand',
|
||||
materials: { manaCrystalDust: 7, arcaneShard: 4, elementalCore: 1 },
|
||||
manaCost: 450,
|
||||
craftTime: 5,
|
||||
rarity: 'rare',
|
||||
gearTrait: '+20% block chance',
|
||||
},
|
||||
{
|
||||
id: 'metalGloves',
|
||||
name: 'Metalweave Gauntlets',
|
||||
description: 'Gauntlets woven with metal mana threads. Protective yet dexterous.',
|
||||
manaType: 'metal',
|
||||
equipmentTypeId: 'spellweaveGloves',
|
||||
slot: 'hands',
|
||||
materials: { manaCrystalDust: 4, arcaneShard: 2 },
|
||||
manaCost: 250,
|
||||
craftTime: 3,
|
||||
rarity: 'uncommon',
|
||||
gearTrait: '+10% damage, +10% physical resistance',
|
||||
},
|
||||
|
||||
// ─── Crystal Gear (Sand+Sand+Light — high enchantment capacity) ──────
|
||||
{
|
||||
id: 'crystalWand',
|
||||
name: 'Crystal Focus Wand',
|
||||
description: 'A wand with a pure crystal core. Exceptional mana conductivity.',
|
||||
manaType: 'crystal',
|
||||
equipmentTypeId: 'crystalWand',
|
||||
slot: 'mainHand',
|
||||
materials: { manaCrystalDust: 10, arcaneShard: 5, elementalCore: 3 },
|
||||
manaCost: 600,
|
||||
craftTime: 6,
|
||||
rarity: 'epic',
|
||||
gearTrait: '+40% enchantment capacity',
|
||||
},
|
||||
{
|
||||
id: 'crystalRing',
|
||||
name: 'Crystal Ring',
|
||||
description: 'A ring set with a mana crystal. Amplifies enchantment effects.',
|
||||
manaType: 'crystal',
|
||||
equipmentTypeId: 'silverRing',
|
||||
slot: 'accessory1',
|
||||
materials: { manaCrystalDust: 5, arcaneShard: 3, elementalCore: 1 },
|
||||
manaCost: 350,
|
||||
craftTime: 3,
|
||||
rarity: 'rare',
|
||||
gearTrait: '+15% enchantment capacity',
|
||||
},
|
||||
{
|
||||
id: 'crystalAmulet',
|
||||
name: 'Crystal Pendant',
|
||||
description: 'An amulet housing a crystal shard. Enhances all enchantments worn.',
|
||||
manaType: 'crystal',
|
||||
equipmentTypeId: 'silverAmulet',
|
||||
slot: 'accessory2',
|
||||
materials: { manaCrystalDust: 6, arcaneShard: 3, elementalCore: 2 },
|
||||
manaCost: 400,
|
||||
craftTime: 4,
|
||||
rarity: 'rare',
|
||||
gearTrait: '+10% all enchantment effects',
|
||||
},
|
||||
|
||||
// ─── Sand Gear (Earth+Water — lightweight, agile) ────────────────────
|
||||
{
|
||||
id: 'sandBoots',
|
||||
name: 'Sandstrider Boots',
|
||||
description: 'Boots infused with sand mana. Light as air, silent as dust.',
|
||||
manaType: 'sand',
|
||||
equipmentTypeId: 'travelerBoots',
|
||||
slot: 'feet',
|
||||
materials: { manaCrystalDust: 3, arcaneShard: 1 },
|
||||
manaCost: 120,
|
||||
craftTime: 2,
|
||||
rarity: 'uncommon',
|
||||
gearTrait: '+15% cast speed, +10% evasion',
|
||||
},
|
||||
{
|
||||
id: 'sandGloves',
|
||||
name: 'Sandweave Gloves',
|
||||
description: 'Gloves woven from sand mana. Nimble fingers for delicate enchanting.',
|
||||
manaType: 'sand',
|
||||
equipmentTypeId: 'spellweaveGloves',
|
||||
slot: 'hands',
|
||||
materials: { manaCrystalDust: 3, arcaneShard: 2 },
|
||||
manaCost: 140,
|
||||
craftTime: 2,
|
||||
rarity: 'uncommon',
|
||||
gearTrait: '+10% cast speed',
|
||||
},
|
||||
{
|
||||
id: 'sandVest',
|
||||
name: 'Sandcloth Vest',
|
||||
description: 'A light vest woven from sand mana. Offers minimal protection but maximum mobility.',
|
||||
manaType: 'sand',
|
||||
equipmentTypeId: 'scholarRobe',
|
||||
slot: 'body',
|
||||
materials: { manaCrystalDust: 5, arcaneShard: 2, elementalCore: 1 },
|
||||
manaCost: 300,
|
||||
craftTime: 4,
|
||||
rarity: 'rare',
|
||||
gearTrait: '+20% cast speed, +5% evasion',
|
||||
},
|
||||
];
|
||||
|
||||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export function getRecipesByManaType(manaType: string): FabricatorRecipe[] {
|
||||
return FABRICATOR_RECIPES.filter(r => r.manaType === manaType);
|
||||
}
|
||||
|
||||
export function getRecipeById(id: string): FabricatorRecipe | undefined {
|
||||
return FABRICATOR_RECIPES.find(r => r.id === id);
|
||||
}
|
||||
|
||||
export function canCraftRecipe(
|
||||
recipe: FabricatorRecipe,
|
||||
materials: Record<string, number>,
|
||||
manaAmount: number,
|
||||
): { canCraft: boolean; missingMaterials: Record<string, number>; missingMana: number } {
|
||||
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 missingMana = Math.max(0, recipe.manaCost - manaAmount);
|
||||
if (missingMana > 0) {
|
||||
canCraft = false;
|
||||
}
|
||||
|
||||
return { canCraft, missingMaterials, missingMana };
|
||||
}
|
||||
Reference in New Issue
Block a user