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:
@@ -2341,10 +2341,12 @@ export default function ManaLoopGame() {
|
|||||||
designProgress={store.designProgress}
|
designProgress={store.designProgress}
|
||||||
preparationProgress={store.preparationProgress}
|
preparationProgress={store.preparationProgress}
|
||||||
applicationProgress={store.applicationProgress}
|
applicationProgress={store.applicationProgress}
|
||||||
|
equipmentCraftingProgress={store.equipmentCraftingProgress}
|
||||||
rawMana={store.rawMana}
|
rawMana={store.rawMana}
|
||||||
skills={store.skills}
|
skills={store.skills}
|
||||||
currentAction={store.currentAction}
|
currentAction={store.currentAction}
|
||||||
unlockedEffects={store.unlockedEffects || ['spell_manaBolt']}
|
unlockedEffects={store.unlockedEffects || ['spell_manaBolt']}
|
||||||
|
lootInventory={store.lootInventory}
|
||||||
startDesigningEnchantment={store.startDesigningEnchantment}
|
startDesigningEnchantment={store.startDesigningEnchantment}
|
||||||
cancelDesign={store.cancelDesign}
|
cancelDesign={store.cancelDesign}
|
||||||
saveDesign={store.saveDesign}
|
saveDesign={store.saveDesign}
|
||||||
@@ -2357,6 +2359,9 @@ export default function ManaLoopGame() {
|
|||||||
cancelApplication={store.cancelApplication}
|
cancelApplication={store.cancelApplication}
|
||||||
disenchantEquipment={store.disenchantEquipment}
|
disenchantEquipment={store.disenchantEquipment}
|
||||||
getAvailableCapacity={store.getAvailableCapacity}
|
getAvailableCapacity={store.getAvailableCapacity}
|
||||||
|
startCraftingEquipment={store.startCraftingEquipment}
|
||||||
|
cancelEquipmentCrafting={store.cancelEquipmentCrafting}
|
||||||
|
deleteMaterial={store.deleteMaterial}
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ import { Separator } from '@/components/ui/separator';
|
|||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import {
|
import {
|
||||||
Wand2, Scroll, Hammer, Sparkles, Trash2, Plus, Minus,
|
Wand2, Scroll, Hammer, Sparkles, Trash2, Plus, Minus,
|
||||||
Package, Zap, Clock, ChevronRight, Circle
|
Package, Zap, Clock, ChevronRight, Circle, Anvil
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { EQUIPMENT_TYPES, type EquipmentType, type EquipmentSlot } from '@/lib/game/data/equipment';
|
import { EQUIPMENT_TYPES, type EquipmentType, type EquipmentSlot } from '@/lib/game/data/equipment';
|
||||||
import { ENCHANTMENT_EFFECTS, type EnchantmentEffectDef, calculateEffectCapacityCost } from '@/lib/game/data/enchantment-effects';
|
import { ENCHANTMENT_EFFECTS, type EnchantmentEffectDef, calculateEffectCapacityCost } from '@/lib/game/data/enchantment-effects';
|
||||||
import type { EquipmentInstance, EnchantmentDesign, DesignEffect, AppliedEnchantment } from '@/lib/game/types';
|
import { CRAFTING_RECIPES, canCraftRecipe } from '@/lib/game/data/crafting-recipes';
|
||||||
|
import { LOOT_DROPS, RARITY_COLORS } from '@/lib/game/data/loot-drops';
|
||||||
|
import type { EquipmentInstance, EnchantmentDesign, DesignEffect, AppliedEnchantment, LootInventory, EquipmentCraftingProgress } from '@/lib/game/types';
|
||||||
import { fmt } from '@/lib/game/store';
|
import { fmt } from '@/lib/game/store';
|
||||||
|
|
||||||
// Slot display names
|
// Slot display names
|
||||||
@@ -39,6 +41,7 @@ interface CraftingTabProps {
|
|||||||
designProgress: { designId: string; progress: number; required: number } | null;
|
designProgress: { designId: string; progress: number; required: number } | null;
|
||||||
preparationProgress: { equipmentInstanceId: string; progress: number; required: number; manaCostPaid: number } | null;
|
preparationProgress: { equipmentInstanceId: string; progress: number; required: number; manaCostPaid: number } | null;
|
||||||
applicationProgress: { equipmentInstanceId: string; designId: string; progress: number; required: number; manaPerHour: number; paused: boolean; manaSpent: number } | null;
|
applicationProgress: { equipmentInstanceId: string; designId: string; progress: number; required: number; manaPerHour: number; paused: boolean; manaSpent: number } | null;
|
||||||
|
equipmentCraftingProgress: EquipmentCraftingProgress | null;
|
||||||
|
|
||||||
// Player state
|
// Player state
|
||||||
rawMana: number;
|
rawMana: number;
|
||||||
@@ -46,6 +49,9 @@ interface CraftingTabProps {
|
|||||||
currentAction: string;
|
currentAction: string;
|
||||||
unlockedEffects: string[]; // Effect IDs that have been researched
|
unlockedEffects: string[]; // Effect IDs that have been researched
|
||||||
|
|
||||||
|
// Loot inventory
|
||||||
|
lootInventory: LootInventory;
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
startDesigningEnchantment: (name: string, equipmentTypeId: string, effects: DesignEffect[]) => boolean;
|
startDesigningEnchantment: (name: string, equipmentTypeId: string, effects: DesignEffect[]) => boolean;
|
||||||
cancelDesign: () => void;
|
cancelDesign: () => void;
|
||||||
@@ -59,6 +65,11 @@ interface CraftingTabProps {
|
|||||||
cancelApplication: () => void;
|
cancelApplication: () => void;
|
||||||
disenchantEquipment: (instanceId: string) => void;
|
disenchantEquipment: (instanceId: string) => void;
|
||||||
getAvailableCapacity: (instanceId: string) => number;
|
getAvailableCapacity: (instanceId: string) => number;
|
||||||
|
|
||||||
|
// Equipment crafting actions
|
||||||
|
startCraftingEquipment: (blueprintId: string) => boolean;
|
||||||
|
cancelEquipmentCrafting: () => void;
|
||||||
|
deleteMaterial: (materialId: string, amount: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CraftingTab({
|
export function CraftingTab({
|
||||||
@@ -68,10 +79,12 @@ export function CraftingTab({
|
|||||||
designProgress,
|
designProgress,
|
||||||
preparationProgress,
|
preparationProgress,
|
||||||
applicationProgress,
|
applicationProgress,
|
||||||
|
equipmentCraftingProgress,
|
||||||
rawMana,
|
rawMana,
|
||||||
skills,
|
skills,
|
||||||
currentAction,
|
currentAction,
|
||||||
unlockedEffects,
|
unlockedEffects,
|
||||||
|
lootInventory,
|
||||||
startDesigningEnchantment,
|
startDesigningEnchantment,
|
||||||
cancelDesign,
|
cancelDesign,
|
||||||
saveDesign,
|
saveDesign,
|
||||||
@@ -84,8 +97,11 @@ export function CraftingTab({
|
|||||||
cancelApplication,
|
cancelApplication,
|
||||||
disenchantEquipment,
|
disenchantEquipment,
|
||||||
getAvailableCapacity,
|
getAvailableCapacity,
|
||||||
|
startCraftingEquipment,
|
||||||
|
cancelEquipmentCrafting,
|
||||||
|
deleteMaterial,
|
||||||
}: CraftingTabProps) {
|
}: CraftingTabProps) {
|
||||||
const [craftingStage, setCraftingStage] = useState<'design' | 'prepare' | 'apply'>('design');
|
const [craftingStage, setCraftingStage] = useState<'design' | 'prepare' | 'apply' | 'craft'>('craft');
|
||||||
const [selectedEquipmentType, setSelectedEquipmentType] = useState<string | null>(null);
|
const [selectedEquipmentType, setSelectedEquipmentType] = useState<string | null>(null);
|
||||||
const [selectedEquipmentInstance, setSelectedEquipmentInstance] = useState<string | null>(null);
|
const [selectedEquipmentInstance, setSelectedEquipmentInstance] = useState<string | null>(null);
|
||||||
const [selectedDesign, setSelectedDesign] = useState<string | null>(null);
|
const [selectedDesign, setSelectedDesign] = useState<string | null>(null);
|
||||||
@@ -718,11 +734,189 @@ export function CraftingTab({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Render equipment crafting stage
|
||||||
|
const renderCraftStage = () => (
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
|
{/* Blueprint Selection */}
|
||||||
|
<Card className="bg-gray-900/80 border-gray-700">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-amber-400 text-sm flex items-center gap-2">
|
||||||
|
<Anvil className="w-4 h-4" />
|
||||||
|
Available Blueprints
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{equipmentCraftingProgress ? (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="text-sm text-gray-400">
|
||||||
|
Crafting: {CRAFTING_RECIPES[equipmentCraftingProgress.blueprintId]?.name}
|
||||||
|
</div>
|
||||||
|
<Progress value={(equipmentCraftingProgress.progress / equipmentCraftingProgress.required) * 100} className="h-3" />
|
||||||
|
<div className="flex justify-between text-xs text-gray-400">
|
||||||
|
<span>{equipmentCraftingProgress.progress.toFixed(1)}h / {equipmentCraftingProgress.required.toFixed(1)}h</span>
|
||||||
|
<span>Mana spent: {fmt(equipmentCraftingProgress.manaSpent)}</span>
|
||||||
|
</div>
|
||||||
|
<Button size="sm" variant="outline" onClick={cancelEquipmentCrafting}>Cancel</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<ScrollArea className="h-64">
|
||||||
|
<div className="space-y-2">
|
||||||
|
{lootInventory.blueprints.length === 0 ? (
|
||||||
|
<div className="text-center text-gray-400 py-4">
|
||||||
|
<Package className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
||||||
|
<p>No blueprints discovered yet.</p>
|
||||||
|
<p className="text-xs mt-1">Defeat guardians to find blueprints!</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
lootInventory.blueprints.map(bpId => {
|
||||||
|
const recipe = CRAFTING_RECIPES[bpId];
|
||||||
|
if (!recipe) return null;
|
||||||
|
|
||||||
|
const { canCraft, missingMaterials, missingMana } = canCraftRecipe(
|
||||||
|
recipe,
|
||||||
|
lootInventory.materials,
|
||||||
|
rawMana
|
||||||
|
);
|
||||||
|
|
||||||
|
const rarityStyle = RARITY_COLORS[recipe.rarity];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={bpId}
|
||||||
|
className="p-3 rounded border bg-gray-800/50"
|
||||||
|
style={{ borderColor: rarityStyle?.color }}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between items-start mb-2">
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold" style={{ color: rarityStyle?.color }}>
|
||||||
|
{recipe.name}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-gray-400 capitalize">{recipe.rarity}</div>
|
||||||
|
</div>
|
||||||
|
<Badge variant="outline" className="text-xs">
|
||||||
|
{EQUIPMENT_TYPES[recipe.equipmentTypeId]?.category}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-xs text-gray-400 mb-2">{recipe.description}</div>
|
||||||
|
|
||||||
|
<Separator className="bg-gray-700 my-2" />
|
||||||
|
|
||||||
|
<div className="text-xs space-y-1">
|
||||||
|
<div className="text-gray-500">Materials:</div>
|
||||||
|
{Object.entries(recipe.materials).map(([matId, amount]) => {
|
||||||
|
const available = lootInventory.materials[matId] || 0;
|
||||||
|
const matDrop = LOOT_DROPS[matId];
|
||||||
|
const hasEnough = available >= amount;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={matId} className="flex justify-between">
|
||||||
|
<span>{matDrop?.name || matId}</span>
|
||||||
|
<span className={hasEnough ? 'text-green-400' : 'text-red-400'}>
|
||||||
|
{available} / {amount}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
<div className="flex justify-between mt-2">
|
||||||
|
<span>Mana Cost:</span>
|
||||||
|
<span className={rawMana >= recipe.manaCost ? 'text-green-400' : 'text-red-400'}>
|
||||||
|
{fmt(recipe.manaCost)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span>Craft Time:</span>
|
||||||
|
<span>{recipe.craftTime}h</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className="w-full mt-3"
|
||||||
|
size="sm"
|
||||||
|
disabled={!canCraft || currentAction === 'craft'}
|
||||||
|
onClick={() => startCraftingEquipment(bpId)}
|
||||||
|
>
|
||||||
|
{canCraft ? 'Craft Equipment' : 'Missing Resources'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Materials Inventory */}
|
||||||
|
<Card className="bg-gray-900/80 border-gray-700">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-amber-400 text-sm flex items-center gap-2">
|
||||||
|
<Package className="w-4 h-4" />
|
||||||
|
Materials ({Object.values(lootInventory.materials).reduce((a, b) => a + b, 0)})
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<ScrollArea className="h-64">
|
||||||
|
{Object.keys(lootInventory.materials).length === 0 ? (
|
||||||
|
<div className="text-center text-gray-400 py-4">
|
||||||
|
<Sparkles className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
||||||
|
<p>No materials collected yet.</p>
|
||||||
|
<p className="text-xs mt-1">Defeat floors to gather materials!</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
{Object.entries(lootInventory.materials).map(([matId, count]) => {
|
||||||
|
if (count <= 0) return null;
|
||||||
|
const drop = LOOT_DROPS[matId];
|
||||||
|
if (!drop) return null;
|
||||||
|
|
||||||
|
const rarityStyle = RARITY_COLORS[drop.rarity];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={matId}
|
||||||
|
className="p-2 rounded border bg-gray-800/50 group relative"
|
||||||
|
style={{ borderColor: rarityStyle?.color }}
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between">
|
||||||
|
<div>
|
||||||
|
<div className="text-sm font-semibold" style={{ color: rarityStyle?.color }}>
|
||||||
|
{drop.name}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-gray-400">x{count}</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-5 w-5 p-0 opacity-0 group-hover:opacity-100 text-red-400 hover:text-red-300 hover:bg-red-900/20"
|
||||||
|
onClick={() => deleteMaterial(matId, count)}
|
||||||
|
>
|
||||||
|
<Trash2 className="w-3 h-3" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</ScrollArea>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Stage Tabs */}
|
{/* Stage Tabs */}
|
||||||
<Tabs value={craftingStage} onValueChange={(v) => setCraftingStage(v as typeof craftingStage)}>
|
<Tabs value={craftingStage} onValueChange={(v) => setCraftingStage(v as typeof craftingStage)}>
|
||||||
<TabsList className="bg-gray-800/50">
|
<TabsList className="bg-gray-800/50">
|
||||||
|
<TabsTrigger value="craft" className="data-[state=active]:bg-cyan-600">
|
||||||
|
<Anvil className="w-4 h-4 mr-1" />
|
||||||
|
Craft
|
||||||
|
</TabsTrigger>
|
||||||
<TabsTrigger value="design" className="data-[state=active]:bg-amber-600">
|
<TabsTrigger value="design" className="data-[state=active]:bg-amber-600">
|
||||||
<Scroll className="w-4 h-4 mr-1" />
|
<Scroll className="w-4 h-4 mr-1" />
|
||||||
Design
|
Design
|
||||||
@@ -737,6 +931,9 @@ export function CraftingTab({
|
|||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
|
<TabsContent value="craft" className="mt-4">
|
||||||
|
{renderCraftStage()}
|
||||||
|
</TabsContent>
|
||||||
<TabsContent value="design" className="mt-4">
|
<TabsContent value="design" className="mt-4">
|
||||||
{renderDesignStage()}
|
{renderDesignStage()}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
@@ -749,6 +946,20 @@ export function CraftingTab({
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
{/* Current Activity Indicator */}
|
{/* Current Activity Indicator */}
|
||||||
|
{currentAction === 'craft' && equipmentCraftingProgress && (
|
||||||
|
<Card className="bg-cyan-900/30 border-cyan-600">
|
||||||
|
<CardContent className="py-3 flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Anvil className="w-5 h-5 text-cyan-400" />
|
||||||
|
<span>Crafting equipment...</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-gray-400">
|
||||||
|
{((equipmentCraftingProgress.progress / equipmentCraftingProgress.required) * 100).toFixed(0)}%
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
{currentAction === 'design' && designProgress && (
|
{currentAction === 'design' && designProgress && (
|
||||||
<Card className="bg-purple-900/30 border-purple-600">
|
<Card className="bg-purple-900/30 border-purple-600">
|
||||||
<CardContent className="py-3 flex items-center justify-between">
|
<CardContent className="py-3 flex items-center justify-between">
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
// ─── Crafting Store Slice ─────────────────────────────────────────────────────────
|
// ─── Crafting Store Slice ─────────────────────────────────────────────────────────
|
||||||
// Handles equipment and enchantment system: design, prepare, apply stages
|
// 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 { EQUIPMENT_TYPES, type EquipmentCategory } from './data/equipment';
|
||||||
import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from './data/enchantment-effects';
|
import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from './data/enchantment-effects';
|
||||||
|
import { CRAFTING_RECIPES, canCraftRecipe, type CraftingRecipe } from './data/crafting-recipes';
|
||||||
import { SPELLS_DEF } from './constants';
|
import { SPELLS_DEF } from './constants';
|
||||||
|
|
||||||
// ─── Helper Functions ─────────────────────────────────────────────────────────
|
// ─── Helper Functions ─────────────────────────────────────────────────────────
|
||||||
@@ -88,6 +89,11 @@ export interface CraftingActions {
|
|||||||
// Disenchanting
|
// Disenchanting
|
||||||
disenchantEquipment: (instanceId: string) => void;
|
disenchantEquipment: (instanceId: string) => void;
|
||||||
|
|
||||||
|
// Equipment Crafting (from blueprints)
|
||||||
|
startCraftingEquipment: (blueprintId: string) => boolean;
|
||||||
|
cancelEquipmentCrafting: () => void;
|
||||||
|
deleteMaterial: (materialId: string, amount: number) => void;
|
||||||
|
|
||||||
// Computed getters
|
// Computed getters
|
||||||
getEquipmentSpells: () => string[];
|
getEquipmentSpells: () => string[];
|
||||||
getEquipmentEffects: () => Record<string, number>;
|
getEquipmentEffects: () => Record<string, number>;
|
||||||
@@ -502,6 +508,97 @@ export function createCraftingSlice(
|
|||||||
if (!instance) return 0;
|
if (!instance) return 0;
|
||||||
return instance.totalCapacity - instance.usedCapacity;
|
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;
|
return updates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
257
src/lib/game/data/crafting-recipes.ts
Normal file
257
src/lib/game/data/crafting-recipes.ts
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
// ─── Crafting Recipes ─────────────────────────────────────────────────────────
|
||||||
|
// Defines what materials are needed to craft equipment from blueprints
|
||||||
|
|
||||||
|
import type { EquipmentSlot } from '../types';
|
||||||
|
|
||||||
|
export interface CraftingRecipe {
|
||||||
|
id: string; // Blueprint ID (matches loot drop)
|
||||||
|
equipmentTypeId: string; // Resulting equipment type ID
|
||||||
|
name: string; // Display name
|
||||||
|
description: string;
|
||||||
|
rarity: 'common' | 'uncommon' | 'rare' | 'epic' | 'legendary';
|
||||||
|
materials: Record<string, number>; // materialId -> count required
|
||||||
|
manaCost: number; // Raw mana cost to craft
|
||||||
|
craftTime: number; // Hours to craft
|
||||||
|
minFloor: number; // Minimum floor where blueprint drops
|
||||||
|
unlocked: boolean; // Whether the player has discovered this
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CRAFTING_RECIPES: Record<string, CraftingRecipe> = {
|
||||||
|
// ─── Staff Blueprints ───
|
||||||
|
staffBlueprint: {
|
||||||
|
id: 'staffBlueprint',
|
||||||
|
equipmentTypeId: 'oakStaff',
|
||||||
|
name: 'Oak Staff',
|
||||||
|
description: 'A sturdy oak staff with decent mana capacity.',
|
||||||
|
rarity: 'uncommon',
|
||||||
|
materials: {
|
||||||
|
manaCrystalDust: 5,
|
||||||
|
arcaneShard: 2,
|
||||||
|
},
|
||||||
|
manaCost: 200,
|
||||||
|
craftTime: 4,
|
||||||
|
minFloor: 10,
|
||||||
|
unlocked: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
wandBlueprint: {
|
||||||
|
id: 'wandBlueprint',
|
||||||
|
equipmentTypeId: 'crystalWand',
|
||||||
|
name: 'Crystal Wand',
|
||||||
|
description: 'A wand tipped with a small crystal. Excellent for elemental enchantments.',
|
||||||
|
rarity: 'rare',
|
||||||
|
materials: {
|
||||||
|
manaCrystalDust: 8,
|
||||||
|
arcaneShard: 4,
|
||||||
|
elementalCore: 1,
|
||||||
|
},
|
||||||
|
manaCost: 500,
|
||||||
|
craftTime: 6,
|
||||||
|
minFloor: 20,
|
||||||
|
unlocked: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
robeBlueprint: {
|
||||||
|
id: 'robeBlueprint',
|
||||||
|
equipmentTypeId: 'scholarRobe',
|
||||||
|
name: 'Scholar Robe',
|
||||||
|
description: 'A robe worn by scholars and researchers.',
|
||||||
|
rarity: 'rare',
|
||||||
|
materials: {
|
||||||
|
manaCrystalDust: 6,
|
||||||
|
arcaneShard: 3,
|
||||||
|
elementalCore: 1,
|
||||||
|
},
|
||||||
|
manaCost: 400,
|
||||||
|
craftTime: 5,
|
||||||
|
minFloor: 25,
|
||||||
|
unlocked: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
artifactBlueprint: {
|
||||||
|
id: 'artifactBlueprint',
|
||||||
|
equipmentTypeId: 'arcanistStaff',
|
||||||
|
name: 'Arcanist Staff',
|
||||||
|
description: 'A staff designed for advanced spellcasters. High capacity for complex enchantments.',
|
||||||
|
rarity: 'legendary',
|
||||||
|
materials: {
|
||||||
|
manaCrystalDust: 20,
|
||||||
|
arcaneShard: 10,
|
||||||
|
elementalCore: 5,
|
||||||
|
voidEssence: 2,
|
||||||
|
celestialFragment: 1,
|
||||||
|
},
|
||||||
|
manaCost: 2000,
|
||||||
|
craftTime: 12,
|
||||||
|
minFloor: 60,
|
||||||
|
unlocked: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// ─── Additional Blueprints ───
|
||||||
|
battlestaffBlueprint: {
|
||||||
|
id: 'battlestaffBlueprint',
|
||||||
|
equipmentTypeId: 'battlestaff',
|
||||||
|
name: 'Battlestaff',
|
||||||
|
description: 'A reinforced staff suitable for both casting and combat.',
|
||||||
|
rarity: 'rare',
|
||||||
|
materials: {
|
||||||
|
manaCrystalDust: 10,
|
||||||
|
arcaneShard: 5,
|
||||||
|
elementalCore: 2,
|
||||||
|
},
|
||||||
|
manaCost: 600,
|
||||||
|
craftTime: 6,
|
||||||
|
minFloor: 30,
|
||||||
|
unlocked: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
catalystBlueprint: {
|
||||||
|
id: 'catalystBlueprint',
|
||||||
|
equipmentTypeId: 'fireCatalyst',
|
||||||
|
name: 'Fire Catalyst',
|
||||||
|
description: 'A catalyst attuned to fire magic. Enhances fire enchantments.',
|
||||||
|
rarity: 'rare',
|
||||||
|
materials: {
|
||||||
|
manaCrystalDust: 8,
|
||||||
|
arcaneShard: 4,
|
||||||
|
elementalCore: 3,
|
||||||
|
},
|
||||||
|
manaCost: 500,
|
||||||
|
craftTime: 5,
|
||||||
|
minFloor: 25,
|
||||||
|
unlocked: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
shieldBlueprint: {
|
||||||
|
id: 'shieldBlueprint',
|
||||||
|
equipmentTypeId: 'runicShield',
|
||||||
|
name: 'Runic Shield',
|
||||||
|
description: 'A shield engraved with protective runes.',
|
||||||
|
rarity: 'rare',
|
||||||
|
materials: {
|
||||||
|
manaCrystalDust: 10,
|
||||||
|
arcaneShard: 6,
|
||||||
|
elementalCore: 2,
|
||||||
|
},
|
||||||
|
manaCost: 450,
|
||||||
|
craftTime: 5,
|
||||||
|
minFloor: 28,
|
||||||
|
unlocked: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
hatBlueprint: {
|
||||||
|
id: 'hatBlueprint',
|
||||||
|
equipmentTypeId: 'wizardHat',
|
||||||
|
name: 'Wizard Hat',
|
||||||
|
description: 'A classic pointed wizard hat. Decent capacity for headwear.',
|
||||||
|
rarity: 'uncommon',
|
||||||
|
materials: {
|
||||||
|
manaCrystalDust: 4,
|
||||||
|
arcaneShard: 2,
|
||||||
|
},
|
||||||
|
manaCost: 150,
|
||||||
|
craftTime: 3,
|
||||||
|
minFloor: 12,
|
||||||
|
unlocked: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
glovesBlueprint: {
|
||||||
|
id: 'glovesBlueprint',
|
||||||
|
equipmentTypeId: 'spellweaveGloves',
|
||||||
|
name: 'Spellweave Gloves',
|
||||||
|
description: 'Gloves woven with mana-conductive threads.',
|
||||||
|
rarity: 'uncommon',
|
||||||
|
materials: {
|
||||||
|
manaCrystalDust: 3,
|
||||||
|
arcaneShard: 2,
|
||||||
|
},
|
||||||
|
manaCost: 120,
|
||||||
|
craftTime: 3,
|
||||||
|
minFloor: 15,
|
||||||
|
unlocked: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
bootsBlueprint: {
|
||||||
|
id: 'bootsBlueprint',
|
||||||
|
equipmentTypeId: 'travelerBoots',
|
||||||
|
name: 'Traveler Boots',
|
||||||
|
description: 'Comfortable boots for long journeys.',
|
||||||
|
rarity: 'uncommon',
|
||||||
|
materials: {
|
||||||
|
manaCrystalDust: 3,
|
||||||
|
arcaneShard: 1,
|
||||||
|
},
|
||||||
|
manaCost: 100,
|
||||||
|
craftTime: 2,
|
||||||
|
minFloor: 8,
|
||||||
|
unlocked: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
ringBlueprint: {
|
||||||
|
id: 'ringBlueprint',
|
||||||
|
equipmentTypeId: 'silverRing',
|
||||||
|
name: 'Silver Ring',
|
||||||
|
description: 'A silver ring with decent magical conductivity.',
|
||||||
|
rarity: 'uncommon',
|
||||||
|
materials: {
|
||||||
|
manaCrystalDust: 2,
|
||||||
|
arcaneShard: 1,
|
||||||
|
},
|
||||||
|
manaCost: 80,
|
||||||
|
craftTime: 2,
|
||||||
|
minFloor: 10,
|
||||||
|
unlocked: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
amuletBlueprint: {
|
||||||
|
id: 'amuletBlueprint',
|
||||||
|
equipmentTypeId: 'silverAmulet',
|
||||||
|
name: 'Silver Amulet',
|
||||||
|
description: 'A silver amulet with a small gem.',
|
||||||
|
rarity: 'uncommon',
|
||||||
|
materials: {
|
||||||
|
manaCrystalDust: 3,
|
||||||
|
arcaneShard: 2,
|
||||||
|
},
|
||||||
|
manaCost: 100,
|
||||||
|
craftTime: 3,
|
||||||
|
minFloor: 12,
|
||||||
|
unlocked: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
export function getRecipeByBlueprint(blueprintId: string): CraftingRecipe | undefined {
|
||||||
|
return CRAFTING_RECIPES[blueprintId];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canCraftRecipe(
|
||||||
|
recipe: CraftingRecipe,
|
||||||
|
materials: Record<string, number>,
|
||||||
|
rawMana: 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 - rawMana);
|
||||||
|
if (missingMana > 0) {
|
||||||
|
canCraft = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { canCraft, missingMaterials, missingMana };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all recipes available based on unlocked blueprints
|
||||||
|
export function getAvailableRecipes(unlockedBlueprints: string[]): CraftingRecipe[] {
|
||||||
|
return unlockedBlueprints
|
||||||
|
.map(bpId => CRAFTING_RECIPES[bpId])
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
@@ -47,7 +47,8 @@ import {
|
|||||||
type FamiliarBonuses,
|
type FamiliarBonuses,
|
||||||
DEFAULT_FAMILIAR_BONUSES,
|
DEFAULT_FAMILIAR_BONUSES,
|
||||||
} from './familiar-slice';
|
} from './familiar-slice';
|
||||||
import { rollLootDrops } from './data/loot-drops';
|
import { rollLootDrops, LOOT_DROPS } from './data/loot-drops';
|
||||||
|
import { CRAFTING_RECIPES, canCraftRecipe } from './data/crafting-recipes';
|
||||||
|
|
||||||
// Default empty effects for when effects aren't provided
|
// Default empty effects for when effects aren't provided
|
||||||
const DEFAULT_EFFECTS: ComputedEffects = {
|
const DEFAULT_EFFECTS: ComputedEffects = {
|
||||||
@@ -561,6 +562,9 @@ function makeInitial(overrides: Partial<GameState> = {}): GameState {
|
|||||||
},
|
},
|
||||||
lootDropsToday: 0,
|
lootDropsToday: 0,
|
||||||
|
|
||||||
|
// Equipment Crafting Progress
|
||||||
|
equipmentCraftingProgress: null,
|
||||||
|
|
||||||
// Achievements
|
// Achievements
|
||||||
achievements: {
|
achievements: {
|
||||||
unlocked: [],
|
unlocked: [],
|
||||||
@@ -1912,6 +1916,98 @@ export const useGameStore = create<GameStore>()(
|
|||||||
return instance.totalCapacity - instance.usedCapacity;
|
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 and mana
|
||||||
|
const { canCraft } = 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,
|
||||||
|
},
|
||||||
|
log: [`🔨 Started crafting ${recipe.name}...`, ...state.log.slice(0, 49)],
|
||||||
|
}));
|
||||||
|
|
||||||
|
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: [`🚫 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 = LOOT_DROPS[materialId]?.name || materialId;
|
||||||
|
return {
|
||||||
|
lootInventory: {
|
||||||
|
...state.lootInventory,
|
||||||
|
materials: newMaterials,
|
||||||
|
},
|
||||||
|
log: [`🗑️ Deleted ${amount}x ${dropName}.`, ...state.log.slice(0, 49)],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// ─── Floor Navigation ────────────────────────────────────────────────────────
|
// ─── Floor Navigation ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
setClimbDirection: (direction: 'up' | 'down') => {
|
setClimbDirection: (direction: 'up' | 'down') => {
|
||||||
|
|||||||
@@ -206,6 +206,15 @@ export interface ApplicationProgress {
|
|||||||
manaSpent: number; // Total mana spent so far
|
manaSpent: number; // Total mana spent so far
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Equipment Crafting Progress (crafting from blueprints)
|
||||||
|
export interface EquipmentCraftingProgress {
|
||||||
|
blueprintId: string; // Blueprint being crafted
|
||||||
|
equipmentTypeId: string; // Resulting equipment type
|
||||||
|
progress: number; // Hours spent crafting
|
||||||
|
required: number; // Total hours needed
|
||||||
|
manaSpent: number; // Mana spent so far
|
||||||
|
}
|
||||||
|
|
||||||
// Equipment spell state (for multi-spell casting)
|
// Equipment spell state (for multi-spell casting)
|
||||||
export interface EquipmentSpellState {
|
export interface EquipmentSpellState {
|
||||||
spellId: string;
|
spellId: string;
|
||||||
@@ -436,6 +445,7 @@ export interface GameState {
|
|||||||
designProgress: DesignProgress | null;
|
designProgress: DesignProgress | null;
|
||||||
preparationProgress: PreparationProgress | null;
|
preparationProgress: PreparationProgress | null;
|
||||||
applicationProgress: ApplicationProgress | null;
|
applicationProgress: ApplicationProgress | null;
|
||||||
|
equipmentCraftingProgress: EquipmentCraftingProgress | null;
|
||||||
|
|
||||||
// Unlocked enchantment effects for designing
|
// Unlocked enchantment effects for designing
|
||||||
unlockedEffects: string[]; // Effect IDs that have been researched
|
unlockedEffects: string[]; // Effect IDs that have been researched
|
||||||
|
|||||||
Reference in New Issue
Block a user