Consolidate disenchanting into CraftingTab Prepare step (Bug 8)
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 3m32s

- Add 'tags' field to EquipmentInstance type
- Modify Prepare step to show warning and different button text when item has enchantments
- Consolidate disenchanting into Prepare step (remove separate disenchant UI)
- After successful preparation, item receives 'Ready for Enchantment' tag
- Modify Apply step to only allow applying enchantments to items tagged 'Ready for Enchantment'
- Update subtask_6_progress.md and todo.md to mark Sub-Task 6 as completed
This commit is contained in:
Refactoring Agent
2026-04-27 12:21:14 +02:00
parent beafa82789
commit 8261baab54
6 changed files with 173 additions and 66 deletions
@@ -6,7 +6,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Separator } from '@/components/ui/separator';
import { ENCHANTMENT_EFFECTS } from '@/lib/game/data/enchantment-effects';
import type { EquipmentInstance, EnchantmentDesign, AppliedEnchantment, LootInventory, EquipmentCraftingProgress } from '@/lib/game/types';
import type { EquipmentInstance, EnchantmentDesign, AppliedEnchantment, LootInventory, EquipmentCraftingProgress, EquipmentSlot } from '@/lib/game/types';
import { fmt, type GameStore } from '@/lib/game/store';
// Slot display names
@@ -46,7 +46,7 @@ export function EnchantmentApplier({
const resumeApplication = store.resumeApplication;
const cancelApplication = store.cancelApplication;
// Get equipped items as array
// Get equipped items as array - only show items tagged 'Ready for Enchantment'
const equippedItems = Object.entries(equippedInstances)
.filter(([, instanceId]) => instanceId && equipmentInstances[instanceId])
.map(([slot, instanceId]) => ({
@@ -84,11 +84,11 @@ export function EnchantmentApplier({
) : (
<div className="space-y-4">
<div>
<div className="text-sm text-gray-400 mb-2">Equipment (without enchantments):</div>
<div className="text-sm text-gray-400 mb-2">Equipment (Ready for Enchantment):</div>
<ScrollArea className="h-32">
<div className="space-y-1">
{equippedItems
.filter(({ instance }) => instance.enchantments.length === 0)
.filter(({ instance }) => instance.tags?.includes('Ready for Enchantment'))
.map(({ slot, instance }) => (
<div
key={instance.instanceId}
@@ -100,11 +100,12 @@ export function EnchantmentApplier({
onClick={() => setSelectedEquipmentInstance(instance.instanceId)}
>
{instance.name} ({instance.usedCapacity}/{instance.totalCapacity} cap)
<span className="text-xs text-green-400 ml-2"> Ready</span>
</div>
))}
{equippedItems.filter(({ instance }) => instance.enchantments.length === 0).length === 0 && (
{equippedItems.filter(({ instance }) => instance.tags?.includes('Ready for Enchantment')).length === 0 && (
<div className="text-center text-gray-500 text-xs py-2">
No unenchanted equipment available. Disenchant in Prepare stage first.
No equipment ready for enchantment. Prepare equipment first in the Prepare stage.
</div>
)}
</div>
@@ -151,6 +152,18 @@ export function EnchantmentApplier({
) : (
(() => {
const instance = equipmentInstances[selectedEquipmentInstance];
if (!instance) return null;
// Check if equipment is ready for enchantment
const isReady = instance.tags?.includes('Ready for Enchantment');
if (!isReady) {
return (
<div className="text-center text-red-400 py-8">
This equipment is not prepared for enchantment. Please prepare it in the Prepare stage first.
</div>
);
}
const design = enchantmentDesigns.find(d => d.id === selectedDesign);
if (!design) return null;
@@ -163,6 +176,7 @@ export function EnchantmentApplier({
<div className="space-y-4">
<div className="text-lg font-semibold">{design.name}</div>
<div className="text-sm text-gray-400"> {instance.name}</div>
<div className="text-xs text-green-400"> Ready for Enchantment</div>
<Separator className="bg-gray-700" />
<div className="space-y-2 text-sm">
@@ -7,7 +7,7 @@ import { ScrollArea } from '@/components/ui/scroll-area';
import { Separator } from '@/components/ui/separator';
import { Trash2 } from 'lucide-react';
import { EQUIPMENT_TYPES } from '@/lib/game/data/equipment';
import type { EquipmentInstance, AppliedEnchantment, LootInventory, EquipmentCraftingProgress } from '@/lib/game/types';
import type { EquipmentInstance, AppliedEnchantment, LootInventory, EquipmentCraftingProgress, EquipmentSlot } from '@/lib/game/types';
import { fmt, type GameStore } from '@/lib/game/store';
// Slot display names
@@ -40,7 +40,6 @@ export function EnchantmentPreparer({
const skills = store.skills;
const startPreparing = store.startPreparing;
const cancelPreparation = store.cancelPreparation;
const disenchantEquipment = store.disenchantEquipment;
// Get equipped items as array
const equippedItems = Object.entries(equippedInstances)
@@ -55,7 +54,7 @@ export function EnchantmentPreparer({
{/* Equipment Selection */}
<Card className="bg-gray-900/80 border-gray-700">
<CardHeader className="pb-2">
<CardTitle className="text-amber-400 text-sm">Select Equipment to Prepare or Disenchant</CardTitle>
<CardTitle className="text-amber-400 text-sm">Select Equipment to Prepare</CardTitle>
</CardHeader>
<CardContent>
{preparationProgress ? (
@@ -75,6 +74,7 @@ export function EnchantmentPreparer({
<div className="space-y-2">
{equippedItems.map(({ slot, instance }) => {
const hasEnchantments = instance.enchantments.length > 0;
const isReady = instance.tags?.includes('Ready for Enchantment');
return (
<div
key={instance.instanceId}
@@ -82,7 +82,7 @@ export function EnchantmentPreparer({
selectedEquipmentInstance === instance.instanceId
? 'border-amber-500 bg-amber-900/20'
: 'border-gray-700 bg-gray-800/50 hover:border-gray-600'
} ${hasEnchantments ? 'border-l-4 border-l-red-600' : ''}`}
} ${hasEnchantments ? 'border-l-4 border-l-red-600' : ''} ${isReady ? 'border-l-4 border-l-green-600' : ''}`}
onClick={() => setSelectedEquipmentInstance(instance.instanceId)}
>
<div className="flex justify-between">
@@ -91,7 +91,12 @@ export function EnchantmentPreparer({
<div className="text-xs text-gray-400">{SLOT_NAMES[slot]}</div>
{hasEnchantments && (
<div className="text-xs text-red-400 mt-1">
{instance.enchantments.length} enchantments - Disenchant to apply new
{instance.enchantments.length} enchantments - Preparation will remove them
</div>
)}
{isReady && (
<div className="text-xs text-green-400 mt-1">
Ready for Enchantment
</div>
)}
</div>
@@ -120,14 +125,16 @@ export function EnchantmentPreparer({
<CardContent>
{!selectedEquipmentInstance ? (
<div className="text-center text-gray-400 py-8">
Select equipment to prepare or disenchant
Select equipment to prepare
</div>
) : preparationProgress ? (
<div className="text-gray-400">Preparation in progress...</div>
) : (
(() => {
const instance = equipmentInstances[selectedEquipmentInstance];
if (!instance) return null;
const hasEnchantments = instance.enchantments.length > 0;
const isReady = instance.tags?.includes('Ready for Enchantment');
const prepTime = 2 + Math.floor(instance.totalCapacity / 50);
const manaCost = instance.totalCapacity * 10;
@@ -144,55 +151,61 @@ export function EnchantmentPreparer({
<div className="text-lg font-semibold">{instance.name}</div>
<Separator className="bg-gray-700" />
{/* Disenchant option for enchanted gear */}
{hasEnchantments && (
<div className="p-3 rounded border border-red-600/50 bg-red-900/20 space-y-3">
{/* Show warning if item has enchantments */}
{hasEnchantments && !isReady && (
<div className="p-3 rounded border border-red-600/50 bg-red-900/20">
<div className="text-sm font-semibold text-red-400"> Equipment has enchantments</div>
<div className="text-xs text-gray-400">
You must disenchant before applying new enchantments.
<div className="text-xs text-gray-400 mt-1">
Preparation will remove all existing enchantments and recover some mana.
</div>
<div className="flex justify-between text-sm">
<div className="flex justify-between text-sm mt-2">
<span className="text-gray-400">Recoverable Mana:</span>
<span className="text-green-400">{fmt(totalRecoverable)}</span>
</div>
<Button
className="w-full bg-red-600 hover:bg-red-700"
onClick={() => disenchantEquipment(instance.instanceId)}
>
<Trash2 className="w-4 h-4 mr-2" />
Disenchant & Recover {fmt(totalRecoverable)} Mana
</Button>
</div>
)}
{/* Prepare option for non-enchanted gear */}
{!hasEnchantments && (
<>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-gray-400">Capacity:</span>
<span>{instance.usedCapacity}/{instance.totalCapacity}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Prep Time:</span>
<span>{prepTime}h</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Mana Cost:</span>
<span className={rawMana < manaCost ? 'text-red-400' : 'text-green-400'}>
{fmt(manaCost)}
</span>
</div>
{/* Show ready status */}
{isReady && (
<div className="p-3 rounded border border-green-600/50 bg-green-900/20">
<div className="text-sm font-semibold text-green-400"> Ready for Enchantment</div>
<div className="text-xs text-gray-400 mt-1">
This item has been prepared and is ready for enchantment application.
</div>
<Button
className="w-full"
disabled={rawMana < manaCost}
onClick={() => startPreparing(selectedEquipmentInstance)}
>
Start Preparation ({prepTime}h, {fmt(manaCost)} mana)
</Button>
</>
</div>
)}
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-gray-400">Capacity:</span>
<span>{instance.usedCapacity}/{instance.totalCapacity}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Prep Time:</span>
<span>{prepTime}h</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Mana Cost:</span>
<span className={rawMana < manaCost ? 'text-red-400' : 'text-green-400'}>
{fmt(manaCost)}
</span>
</div>
</div>
<Button
className="w-full"
disabled={rawMana < manaCost || isReady}
onClick={() => startPreparing(selectedEquipmentInstance)}
>
{hasEnchantments ? (
<>
<Trash2 className="w-4 h-4 mr-2" />
Start Preparation this will remove existing enchantments ({prepTime}h, {fmt(manaCost)} mana)
</>
) : (
<>Start Preparation ({prepTime}h, {fmt(manaCost)} mana)</>
)}
</Button>
</div>
);
})()
+32 -4
View File
@@ -422,8 +422,8 @@ export function createCraftingSlice(
const instance = state.equipmentInstances[equipmentInstanceId];
if (!instance) return false;
// Don't allow preparing enchanted items - they need to be disenchanted first
if (instance.enchantments.length > 0) return false;
// Don't allow preparing an item that's already prepared
if (instance.tags?.includes('Ready for Enchantment')) return false;
const prepTime = calculatePrepTime(instance.totalCapacity);
const manaCost = calculatePrepManaCost(instance.totalCapacity);
@@ -459,6 +459,11 @@ export function createCraftingSlice(
if (!instance || !design) return false;
// Check if equipment is ready for enchantment
if (!instance.tags?.includes('Ready for Enchantment')) {
return false;
}
// Check capacity
if (instance.usedCapacity + design.totalCapacityUsed > instance.totalCapacity) {
return false;
@@ -818,12 +823,35 @@ export function processCraftingTick(
const manaCostPaid = prep.manaCostPaid + manaCost;
if (progress >= prep.required) {
// Preparation complete - clear enchantments, add tag, and recover some mana
const instance = state.equipmentInstances[prep.equipmentInstanceId];
let totalRecovered = 0;
if (instance) {
// Calculate mana recovery from disenchanting
const disenchantLevel = (state.skills as Record<string, number>).disenchanting || 0;
const recoveryRate = 0.1 + disenchantLevel * 0.2; // 10% base + 20% per level
for (const ench of instance.enchantments) {
totalRecovered += Math.floor(ench.actualCost * recoveryRate);
}
}
updates = {
...updates,
rawMana: rawMana - manaCost,
rawMana: rawMana - manaCost + totalRecovered,
preparationProgress: null,
currentAction: 'meditate',
log: ['✅ Equipment prepared for enchanting!', ...log],
equipmentInstances: instance ? {
...state.equipmentInstances,
[instance.instanceId]: {
...instance,
enchantments: [],
usedCapacity: 0,
rarity: 'common',
tags: [...(instance.tags || []), 'Ready for Enchantment'],
},
} : state.equipmentInstances,
log: [`✅ Equipment prepared for enchanting! Recovered ${totalRecovered} mana.`, ...log],
};
} else {
updates = {
+1
View File
@@ -22,6 +22,7 @@ export interface EquipmentInstance {
totalCapacity: number; // Base capacity + bonuses
rarity: 'common' | 'uncommon' | 'rare' | 'epic' | 'legendary' | 'mythic';
quality: number; // 0-100, affects capacity efficiency
tags: string[]; // Tags for item state (e.g., 'Ready for Enchantment')
weaponMana?: number; // Current mana stored in weapon (for weapon enchantments)
weaponManaMax?: number; // Max mana the weapon can store
weaponManaRegen?: number; // Mana regen per hour for weapon