Move Loot/Achievements to separate tabs, add skill debug options, fix enchanting system
All checks were successful
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 2m15s
All checks were successful
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 2m15s
- Move LootInventoryDisplay to new LootTab (better mobile UX) - Move AchievementsDisplay to new AchievementsTab (better mobile UX) - Add skill research debug options to DebugTab (level up skills, enchanting skills) - Fix enchanting: disenchant only possible in Prepare stage - Fix enchanting: cannot apply enchantments to already enchanted gear - Remove recover buttons from Apply stage (disenchant is only in Prepare stage now)
This commit is contained in:
@@ -13,10 +13,9 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { RotateCcw } from 'lucide-react';
|
import { RotateCcw } from 'lucide-react';
|
||||||
import { CraftingTab, SpireTab, SpellsTab, LabTab, SkillsTab, StatsTab, EquipmentTab, AttunementsTab, DebugTab } from '@/components/game/tabs';
|
import { CraftingTab, SpireTab, SpellsTab, LabTab, SkillsTab, StatsTab, EquipmentTab, AttunementsTab, DebugTab, LootTab, AchievementsTab } from '@/components/game/tabs';
|
||||||
import { ActionButtons, CalendarDisplay, ManaDisplay, TimeDisplay } from '@/components/game';
|
import { ActionButtons, CalendarDisplay, ManaDisplay, TimeDisplay } from '@/components/game';
|
||||||
import { LootInventoryDisplay } from '@/components/game/LootInventory';
|
// Loot and Achievements moved to separate tabs
|
||||||
import { AchievementsDisplay } from '@/components/game/AchievementsDisplay';
|
|
||||||
import { DebugName } from '@/lib/game/debug-context';
|
import { DebugName } from '@/lib/game/debug-context';
|
||||||
|
|
||||||
export default function ManaLoopGame() {
|
export default function ManaLoopGame() {
|
||||||
@@ -210,31 +209,7 @@ export default function ManaLoopGame() {
|
|||||||
/>
|
/>
|
||||||
</DebugName>
|
</DebugName>
|
||||||
|
|
||||||
{/* Loot Inventory */}
|
{/* Loot and Achievements moved to tabs */}
|
||||||
<DebugName name="LootInventoryDisplay">
|
|
||||||
<LootInventoryDisplay
|
|
||||||
inventory={store.lootInventory}
|
|
||||||
elements={store.elements}
|
|
||||||
equipmentInstances={store.equipmentInstances}
|
|
||||||
onDeleteMaterial={store.deleteMaterial}
|
|
||||||
onDeleteEquipment={store.deleteEquipmentInstance}
|
|
||||||
/>
|
|
||||||
</DebugName>
|
|
||||||
|
|
||||||
{/* Achievements */}
|
|
||||||
<DebugName name="AchievementsDisplay">
|
|
||||||
<AchievementsDisplay
|
|
||||||
achievements={store.achievements}
|
|
||||||
gameState={{
|
|
||||||
maxFloorReached: store.maxFloorReached,
|
|
||||||
totalManaGathered: store.totalManaGathered,
|
|
||||||
signedPacts: store.signedPacts,
|
|
||||||
totalSpellsCast: store.totalSpellsCast,
|
|
||||||
totalDamageDealt: store.totalDamageDealt,
|
|
||||||
totalCraftsCompleted: store.totalCraftsCompleted,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</DebugName>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right Panel - Tabs */}
|
{/* Right Panel - Tabs */}
|
||||||
@@ -247,6 +222,8 @@ export default function ManaLoopGame() {
|
|||||||
<TabsTrigger value="spells" className="text-xs px-2 py-1">🔮 Spells</TabsTrigger>
|
<TabsTrigger value="spells" className="text-xs px-2 py-1">🔮 Spells</TabsTrigger>
|
||||||
<TabsTrigger value="equipment" className="text-xs px-2 py-1">🛡️ Gear</TabsTrigger>
|
<TabsTrigger value="equipment" className="text-xs px-2 py-1">🛡️ Gear</TabsTrigger>
|
||||||
<TabsTrigger value="crafting" className="text-xs px-2 py-1">🔧 Craft</TabsTrigger>
|
<TabsTrigger value="crafting" className="text-xs px-2 py-1">🔧 Craft</TabsTrigger>
|
||||||
|
<TabsTrigger value="loot" className="text-xs px-2 py-1">💎 Loot</TabsTrigger>
|
||||||
|
<TabsTrigger value="achievements" className="text-xs px-2 py-1">🏆 Achieve</TabsTrigger>
|
||||||
<TabsTrigger value="lab" className="text-xs px-2 py-1">🔬 Lab</TabsTrigger>
|
<TabsTrigger value="lab" className="text-xs px-2 py-1">🔬 Lab</TabsTrigger>
|
||||||
<TabsTrigger value="stats" className="text-xs px-2 py-1">📊 Stats</TabsTrigger>
|
<TabsTrigger value="stats" className="text-xs px-2 py-1">📊 Stats</TabsTrigger>
|
||||||
<TabsTrigger value="debug" className="text-xs px-2 py-1">🔧 Debug</TabsTrigger>
|
<TabsTrigger value="debug" className="text-xs px-2 py-1">🔧 Debug</TabsTrigger>
|
||||||
@@ -289,6 +266,18 @@ export default function ManaLoopGame() {
|
|||||||
</DebugName>
|
</DebugName>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="loot">
|
||||||
|
<DebugName name="LootTab">
|
||||||
|
<LootTab store={store} />
|
||||||
|
</DebugName>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="achievements">
|
||||||
|
<DebugName name="AchievementsTab">
|
||||||
|
<AchievementsTab store={store} />
|
||||||
|
</DebugName>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="lab">
|
<TabsContent value="lab">
|
||||||
<DebugName name="LabTab">
|
<DebugName name="LabTab">
|
||||||
<LabTab store={store} />
|
<LabTab store={store} />
|
||||||
|
|||||||
43
src/components/game/tabs/AchievementsTab.tsx
Normal file
43
src/components/game/tabs/AchievementsTab.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import type { GameStore } from '@/lib/game/store';
|
||||||
|
import { AchievementsDisplay } from '@/components/game/AchievementsDisplay';
|
||||||
|
|
||||||
|
export interface AchievementsTabProps {
|
||||||
|
store: GameStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AchievementsTab({ store }: AchievementsTabProps) {
|
||||||
|
const achievements = store.achievements;
|
||||||
|
const unlockedCount = achievements.unlocked.length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<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">
|
||||||
|
🏆 Achievements
|
||||||
|
<Badge className="ml-auto bg-amber-900/50 text-amber-300">
|
||||||
|
{unlockedCount} unlocked
|
||||||
|
</Badge>
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<AchievementsDisplay
|
||||||
|
achievements={achievements}
|
||||||
|
gameState={{
|
||||||
|
maxFloorReached: store.maxFloorReached,
|
||||||
|
totalManaGathered: store.totalManaGathered,
|
||||||
|
signedPacts: store.signedPacts,
|
||||||
|
totalSpellsCast: store.totalSpellsCast,
|
||||||
|
totalDamageDealt: store.totalDamageDealt,
|
||||||
|
totalCraftsCompleted: store.totalCraftsCompleted,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -383,7 +383,7 @@ export function CraftingTab({ store }: CraftingTabProps) {
|
|||||||
{/* Equipment Selection */}
|
{/* Equipment Selection */}
|
||||||
<Card className="bg-gray-900/80 border-gray-700">
|
<Card className="bg-gray-900/80 border-gray-700">
|
||||||
<CardHeader className="pb-2">
|
<CardHeader className="pb-2">
|
||||||
<CardTitle className="text-amber-400 text-sm">Select Equipment to Prepare</CardTitle>
|
<CardTitle className="text-amber-400 text-sm">Select Equipment to Prepare or Disenchant</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{preparationProgress ? (
|
{preparationProgress ? (
|
||||||
@@ -401,28 +401,36 @@ export function CraftingTab({ store }: CraftingTabProps) {
|
|||||||
) : (
|
) : (
|
||||||
<ScrollArea className="h-64">
|
<ScrollArea className="h-64">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{equippedItems.map(({ slot, instance }) => (
|
{equippedItems.map(({ slot, instance }) => {
|
||||||
<div
|
const hasEnchantments = instance.enchantments.length > 0;
|
||||||
key={instance.instanceId}
|
return (
|
||||||
className={`p-3 rounded border cursor-pointer transition-all ${
|
<div
|
||||||
selectedEquipmentInstance === instance.instanceId
|
key={instance.instanceId}
|
||||||
? 'border-amber-500 bg-amber-900/20'
|
className={`p-3 rounded border cursor-pointer transition-all ${
|
||||||
: 'border-gray-700 bg-gray-800/50 hover:border-gray-600'
|
selectedEquipmentInstance === instance.instanceId
|
||||||
}`}
|
? 'border-amber-500 bg-amber-900/20'
|
||||||
onClick={() => setSelectedEquipmentInstance(instance.instanceId)}
|
: 'border-gray-700 bg-gray-800/50 hover:border-gray-600'
|
||||||
>
|
} ${hasEnchantments ? 'border-l-4 border-l-red-600' : ''}`}
|
||||||
<div className="flex justify-between">
|
onClick={() => setSelectedEquipmentInstance(instance.instanceId)}
|
||||||
<div>
|
>
|
||||||
<div className="font-semibold">{instance.name}</div>
|
<div className="flex justify-between">
|
||||||
<div className="text-xs text-gray-400">{SLOT_NAMES[slot]}</div>
|
<div>
|
||||||
</div>
|
<div className="font-semibold">{instance.name}</div>
|
||||||
<div className="text-right text-sm">
|
<div className="text-xs text-gray-400">{SLOT_NAMES[slot]}</div>
|
||||||
<div className="text-green-400">{instance.usedCapacity}/{instance.totalCapacity} cap</div>
|
{hasEnchantments && (
|
||||||
<div className="text-xs text-gray-400">{instance.enchantments.length} enchants</div>
|
<div className="text-xs text-red-400 mt-1">
|
||||||
|
⚠️ {instance.enchantments.length} enchantments - Disenchant to apply new
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="text-right text-sm">
|
||||||
|
<div className="text-green-400">{instance.usedCapacity}/{instance.totalCapacity} cap</div>
|
||||||
|
<div className="text-xs text-gray-400">{instance.enchantments.length} enchants</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
))}
|
})}
|
||||||
{equippedItems.length === 0 && (
|
{equippedItems.length === 0 && (
|
||||||
<div className="text-center text-gray-400 py-4">No equipped items</div>
|
<div className="text-center text-gray-400 py-4">No equipped items</div>
|
||||||
)}
|
)}
|
||||||
@@ -440,43 +448,79 @@ export function CraftingTab({ store }: CraftingTabProps) {
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
{!selectedEquipmentInstance ? (
|
{!selectedEquipmentInstance ? (
|
||||||
<div className="text-center text-gray-400 py-8">
|
<div className="text-center text-gray-400 py-8">
|
||||||
Select equipment to prepare
|
Select equipment to prepare or disenchant
|
||||||
</div>
|
</div>
|
||||||
) : preparationProgress ? (
|
) : preparationProgress ? (
|
||||||
<div className="text-gray-400">Preparation in progress...</div>
|
<div className="text-gray-400">Preparation in progress...</div>
|
||||||
) : (
|
) : (
|
||||||
(() => {
|
(() => {
|
||||||
const instance = equipmentInstances[selectedEquipmentInstance];
|
const instance = equipmentInstances[selectedEquipmentInstance];
|
||||||
|
const hasEnchantments = instance.enchantments.length > 0;
|
||||||
const prepTime = 2 + Math.floor(instance.totalCapacity / 50);
|
const prepTime = 2 + Math.floor(instance.totalCapacity / 50);
|
||||||
const manaCost = instance.totalCapacity * 10;
|
const manaCost = instance.totalCapacity * 10;
|
||||||
|
|
||||||
|
// Calculate disenchant recovery
|
||||||
|
const disenchantLevel = skills.disenchanting || 0;
|
||||||
|
const recoveryRate = 0.1 + disenchantLevel * 0.2;
|
||||||
|
const totalRecoverable = instance.enchantments.reduce(
|
||||||
|
(sum, e) => sum + Math.floor(e.actualCost * recoveryRate),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="text-lg font-semibold">{instance.name}</div>
|
<div className="text-lg font-semibold">{instance.name}</div>
|
||||||
<Separator className="bg-gray-700" />
|
<Separator className="bg-gray-700" />
|
||||||
<div className="space-y-2 text-sm">
|
|
||||||
<div className="flex justify-between">
|
{/* Disenchant option for enchanted gear */}
|
||||||
<span className="text-gray-400">Capacity:</span>
|
{hasEnchantments && (
|
||||||
<span>{instance.usedCapacity}/{instance.totalCapacity}</span>
|
<div className="p-3 rounded border border-red-600/50 bg-red-900/20 space-y-3">
|
||||||
|
<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>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<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>
|
</div>
|
||||||
<div className="flex justify-between">
|
)}
|
||||||
<span className="text-gray-400">Prep Time:</span>
|
|
||||||
<span>{prepTime}h</span>
|
{/* Prepare option for non-enchanted gear */}
|
||||||
</div>
|
{!hasEnchantments && (
|
||||||
<div className="flex justify-between">
|
<>
|
||||||
<span className="text-gray-400">Mana Cost:</span>
|
<div className="space-y-2 text-sm">
|
||||||
<span className={rawMana < manaCost ? 'text-red-400' : 'text-green-400'}>
|
<div className="flex justify-between">
|
||||||
{fmt(manaCost)}
|
<span className="text-gray-400">Capacity:</span>
|
||||||
</span>
|
<span>{instance.usedCapacity}/{instance.totalCapacity}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="flex justify-between">
|
||||||
<Button
|
<span className="text-gray-400">Prep Time:</span>
|
||||||
className="w-full"
|
<span>{prepTime}h</span>
|
||||||
disabled={rawMana < manaCost}
|
</div>
|
||||||
onClick={() => startPreparing(selectedEquipmentInstance)}
|
<div className="flex justify-between">
|
||||||
>
|
<span className="text-gray-400">Mana Cost:</span>
|
||||||
Start Preparation ({prepTime}h, {fmt(manaCost)} mana)
|
<span className={rawMana < manaCost ? 'text-red-400' : 'text-green-400'}>
|
||||||
</Button>
|
{fmt(manaCost)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
className="w-full"
|
||||||
|
disabled={rawMana < manaCost}
|
||||||
|
onClick={() => startPreparing(selectedEquipmentInstance)}
|
||||||
|
>
|
||||||
|
Start Preparation ({prepTime}h, {fmt(manaCost)} mana)
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})()
|
})()
|
||||||
@@ -517,10 +561,12 @@ export function CraftingTab({ store }: CraftingTabProps) {
|
|||||||
) : (
|
) : (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm text-gray-400 mb-2">Equipment:</div>
|
<div className="text-sm text-gray-400 mb-2">Equipment (without enchantments):</div>
|
||||||
<ScrollArea className="h-32">
|
<ScrollArea className="h-32">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
{equippedItems.map(({ slot, instance }) => (
|
{equippedItems
|
||||||
|
.filter(({ instance }) => instance.enchantments.length === 0)
|
||||||
|
.map(({ slot, instance }) => (
|
||||||
<div
|
<div
|
||||||
key={instance.instanceId}
|
key={instance.instanceId}
|
||||||
className={`p-2 rounded border cursor-pointer text-sm ${
|
className={`p-2 rounded border cursor-pointer text-sm ${
|
||||||
@@ -533,6 +579,11 @@ export function CraftingTab({ store }: CraftingTabProps) {
|
|||||||
{instance.name} ({instance.usedCapacity}/{instance.totalCapacity} cap)
|
{instance.name} ({instance.usedCapacity}/{instance.totalCapacity} cap)
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
{equippedItems.filter(({ instance }) => instance.enchantments.length === 0).length === 0 && (
|
||||||
|
<div className="text-center text-gray-500 text-xs py-2">
|
||||||
|
No unenchanted equipment available. Disenchant in Prepare stage first.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</div>
|
</div>
|
||||||
@@ -632,51 +683,6 @@ export function CraftingTab({ store }: CraftingTabProps) {
|
|||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Disenchant Section */}
|
|
||||||
<Card className="bg-gray-900/80 border-gray-700 lg:col-span-2">
|
|
||||||
<CardHeader className="pb-2">
|
|
||||||
<CardTitle className="text-amber-400 text-sm">Disenchant Equipment</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
|
|
||||||
{equippedItems
|
|
||||||
.filter(({ instance }) => instance.enchantments.length > 0)
|
|
||||||
.map(({ slot, instance }) => {
|
|
||||||
const disenchantLevel = skills.disenchanting || 0;
|
|
||||||
const recoveryRate = 0.1 + disenchantLevel * 0.2;
|
|
||||||
const totalRecoverable = instance.enchantments.reduce(
|
|
||||||
(sum, e) => sum + Math.floor(e.actualCost * recoveryRate),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={instance.instanceId} className="p-3 rounded border border-gray-700 bg-gray-800/50">
|
|
||||||
<div className="flex justify-between items-start">
|
|
||||||
<div>
|
|
||||||
<div className="font-semibold">{instance.name}</div>
|
|
||||||
<div className="text-xs text-gray-400">{instance.enchantments.length} enchantments</div>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => disenchantEquipment(instance.instanceId)}
|
|
||||||
>
|
|
||||||
<Trash2 className="w-4 h-4 mr-1" />
|
|
||||||
Recover {fmt(totalRecoverable)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{equippedItems.filter(({ instance }) => instance.enchantments.length > 0).length === 0 && (
|
|
||||||
<div className="col-span-full text-center text-gray-400 py-4">
|
|
||||||
No enchanted equipment to disenchant
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { Switch } from '@/components/ui/switch';
|
|||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import {
|
import {
|
||||||
RotateCcw, Bug, Plus, Minus, Lock, Unlock, Zap,
|
RotateCcw, Bug, Plus, Minus, Lock, Unlock, Zap,
|
||||||
Clock, Star, AlertTriangle, Sparkles, Settings, Eye
|
Clock, Star, AlertTriangle, Sparkles, Settings, Eye, BookOpen
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import type { GameStore } from '@/lib/game/types';
|
import type { GameStore } from '@/lib/game/types';
|
||||||
import { ATTUNEMENTS_DEF } from '@/lib/game/data/attunements';
|
import { ATTUNEMENTS_DEF } from '@/lib/game/data/attunements';
|
||||||
@@ -415,6 +415,285 @@ export function DebugTab({ store }: DebugTabProps) {
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* Skill Research Debug */}
|
||||||
|
<Card className="bg-gray-900/80 border-gray-700 md:col-span-2">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-purple-400 text-sm flex items-center gap-2">
|
||||||
|
<BookOpen className="w-4 h-4" />
|
||||||
|
Skill Research Debug
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{/* Enchanting Skills */}
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-gray-400 mb-2">Enchanting Skills:</div>
|
||||||
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
// Level up all enchanting skills by 1
|
||||||
|
const enchantSkills = ['enchanting', 'efficientEnchant', 'disenchanting', 'enchantSpeed', 'scrollCrafting', 'essenceRefining'];
|
||||||
|
enchantSkills.forEach(skillId => {
|
||||||
|
if (store.skills[skillId] !== undefined) {
|
||||||
|
store.skills[skillId] = Math.min((store.skills[skillId] || 0) + 1, 10);
|
||||||
|
} else {
|
||||||
|
store.skills[skillId] = 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
+1 All Enchanting
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
// Max all enchanting skills
|
||||||
|
const enchantSkills = ['enchanting', 'efficientEnchant', 'disenchanting', 'enchantSpeed', 'scrollCrafting', 'essenceRefining'];
|
||||||
|
enchantSkills.forEach(skillId => {
|
||||||
|
store.skills[skillId] = 10;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Max All Enchanting
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mana Skills */}
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-gray-400 mb-2">Mana Skills:</div>
|
||||||
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
const manaSkills = ['manaWell', 'manaFlow', 'elemAttune', 'manaOverflow'];
|
||||||
|
manaSkills.forEach(skillId => {
|
||||||
|
if (store.skills[skillId] !== undefined) {
|
||||||
|
store.skills[skillId] = Math.min((store.skills[skillId] || 0) + 1, 10);
|
||||||
|
} else {
|
||||||
|
store.skills[skillId] = 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
+1 All Mana
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
const manaSkills = ['manaWell', 'manaFlow', 'elemAttune', 'manaOverflow'];
|
||||||
|
manaSkills.forEach(skillId => {
|
||||||
|
store.skills[skillId] = 10;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Max All Mana
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Study Skills */}
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-gray-400 mb-2">Study Skills:</div>
|
||||||
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
const studySkills = ['quickLearner', 'focusedMind', 'meditation', 'knowledgeRetention'];
|
||||||
|
studySkills.forEach(skillId => {
|
||||||
|
if (store.skills[skillId] !== undefined) {
|
||||||
|
store.skills[skillId] = Math.min((store.skills[skillId] || 0) + 1, 10);
|
||||||
|
} else {
|
||||||
|
store.skills[skillId] = 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
+1 All Study
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
const studySkills = ['quickLearner', 'focusedMind', 'meditation', 'knowledgeRetention'];
|
||||||
|
studySkills.forEach(skillId => {
|
||||||
|
store.skills[skillId] = 10;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Max All Study
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Crafting Skills */}
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-gray-400 mb-2">Crafting Skills:</div>
|
||||||
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
const craftSkills = ['effCrafting', 'fieldRepair', 'elemCrafting'];
|
||||||
|
craftSkills.forEach(skillId => {
|
||||||
|
if (store.skills[skillId] !== undefined) {
|
||||||
|
store.skills[skillId] = Math.min((store.skills[skillId] || 0) + 1, 10);
|
||||||
|
} else {
|
||||||
|
store.skills[skillId] = 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
+1 All Crafting
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
const craftSkills = ['effCrafting', 'fieldRepair', 'elemCrafting'];
|
||||||
|
craftSkills.forEach(skillId => {
|
||||||
|
store.skills[skillId] = 10;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Max All Crafting
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Research Effects */}
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-gray-400 mb-2">Research Effects:</div>
|
||||||
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
// Unlock all spell research
|
||||||
|
const researchSkills = [
|
||||||
|
'researchManaSpells', 'researchFireSpells', 'researchWaterSpells',
|
||||||
|
'researchAirSpells', 'researchEarthSpells', 'researchLightSpells',
|
||||||
|
'researchDarkSpells', 'researchLifeDeathSpells',
|
||||||
|
'researchAdvancedFire', 'researchAdvancedWater', 'researchAdvancedAir',
|
||||||
|
'researchAdvancedEarth', 'researchAdvancedLight', 'researchAdvancedDark',
|
||||||
|
'researchMasterFire', 'researchMasterWater', 'researchMasterEarth',
|
||||||
|
'researchDamageEffects', 'researchCombatEffects',
|
||||||
|
'researchManaEffects', 'researchAdvancedManaEffects', 'researchUtilityEffects'
|
||||||
|
];
|
||||||
|
researchSkills.forEach(skillId => {
|
||||||
|
store.skills[skillId] = 1;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Unlock All Research
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
// Add all unlocked effects to unlockedEffects
|
||||||
|
const effectIds = Object.keys(store.unlockedEffects || {});
|
||||||
|
// Add spell effects, mana effects, combat effects, utility effects
|
||||||
|
const allEffectIds = [
|
||||||
|
// Spell effects
|
||||||
|
'spell_manaBolt', 'spell_manaStrike', 'spell_fireball', 'spell_emberShot',
|
||||||
|
'spell_waterJet', 'spell_iceShard', 'spell_gust', 'spell_windSlash',
|
||||||
|
'spell_stoneBullet', 'spell_rockSpike', 'spell_lightLance', 'spell_radiance',
|
||||||
|
'spell_shadowBolt', 'spell_darkPulse', 'spell_drain',
|
||||||
|
// Tier 2 spells
|
||||||
|
'spell_inferno', 'spell_flameWave', 'spell_tidalWave', 'spell_iceStorm',
|
||||||
|
'spell_hurricane', 'spell_windBlade', 'spell_earthquake', 'spell_stoneBarrage',
|
||||||
|
'spell_solarFlare', 'spell_divineSmite', 'spell_voidRift', 'spell_shadowStorm',
|
||||||
|
// Tier 3 spells
|
||||||
|
'spell_pyroclasm', 'spell_tsunami', 'spell_meteorStrike',
|
||||||
|
// Lightning
|
||||||
|
'spell_spark', 'spell_lightningBolt', 'spell_chainLightning',
|
||||||
|
'spell_stormCall', 'spell_thunderStrike',
|
||||||
|
// Metal and Sand
|
||||||
|
'spell_metalShard', 'spell_ironFist', 'spell_steelTempest', 'spell_furnaceBlast',
|
||||||
|
'spell_sandBlast', 'spell_sandstorm', 'spell_desertWind', 'spell_duneCollapse',
|
||||||
|
// Mana effects
|
||||||
|
'mana_cap_50', 'mana_cap_100', 'mana_regen_1', 'mana_regen_2', 'mana_regen_5',
|
||||||
|
'click_mana_1', 'click_mana_3',
|
||||||
|
// Combat effects
|
||||||
|
'damage_5', 'damage_10', 'damage_pct_10', 'crit_5', 'attack_speed_10',
|
||||||
|
// Utility effects
|
||||||
|
'meditate_10', 'study_10', 'insight_5',
|
||||||
|
// Special
|
||||||
|
'spell_echo_10', 'guardian_dmg_10', 'overpower_80',
|
||||||
|
// Weapon mana
|
||||||
|
'weapon_mana_cap_20', 'weapon_mana_cap_50', 'weapon_mana_cap_100',
|
||||||
|
'weapon_mana_regen_1', 'weapon_mana_regen_2', 'weapon_mana_regen_5',
|
||||||
|
// Sword enchants
|
||||||
|
'sword_fire', 'sword_frost', 'sword_lightning', 'sword_void'
|
||||||
|
];
|
||||||
|
allEffectIds.forEach(id => {
|
||||||
|
if (!store.unlockedEffects.includes(id)) {
|
||||||
|
store.unlockedEffects.push(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Unlock All Effects
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Max All */}
|
||||||
|
<Separator className="bg-gray-700" />
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
className="bg-purple-600 hover:bg-purple-700"
|
||||||
|
onClick={() => {
|
||||||
|
// Max all skills
|
||||||
|
Object.keys(store.skills).forEach(skillId => {
|
||||||
|
const current = store.skills[skillId] || 0;
|
||||||
|
if (current < 10) {
|
||||||
|
store.skills[skillId] = 10;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Unlock all effects
|
||||||
|
const allEffectIds = [
|
||||||
|
'spell_manaBolt', 'spell_manaStrike', 'spell_fireball', 'spell_emberShot',
|
||||||
|
'spell_waterJet', 'spell_iceShard', 'spell_gust', 'spell_windSlash',
|
||||||
|
'spell_stoneBullet', 'spell_rockSpike', 'spell_lightLance', 'spell_radiance',
|
||||||
|
'spell_shadowBolt', 'spell_darkPulse', 'spell_drain', 'spell_inferno',
|
||||||
|
'spell_flameWave', 'spell_tidalWave', 'spell_iceStorm', 'spell_hurricane',
|
||||||
|
'spell_windBlade', 'spell_earthquake', 'spell_stoneBarrage', 'spell_solarFlare',
|
||||||
|
'spell_divineSmite', 'spell_voidRift', 'spell_shadowStorm', 'spell_pyroclasm',
|
||||||
|
'spell_tsunami', 'spell_meteorStrike', 'spell_spark', 'spell_lightningBolt',
|
||||||
|
'spell_chainLightning', 'spell_stormCall', 'spell_thunderStrike', 'spell_metalShard',
|
||||||
|
'spell_ironFist', 'spell_steelTempest', 'spell_furnaceBlast', 'spell_sandBlast',
|
||||||
|
'spell_sandstorm', 'spell_desertWind', 'spell_duneCollapse',
|
||||||
|
'mana_cap_50', 'mana_cap_100', 'mana_regen_1', 'mana_regen_2', 'mana_regen_5',
|
||||||
|
'click_mana_1', 'click_mana_3', 'damage_5', 'damage_10', 'damage_pct_10',
|
||||||
|
'crit_5', 'attack_speed_10', 'meditate_10', 'study_10', 'insight_5',
|
||||||
|
'spell_echo_10', 'guardian_dmg_10', 'overpower_80',
|
||||||
|
'weapon_mana_cap_20', 'weapon_mana_cap_50', 'weapon_mana_cap_100',
|
||||||
|
'weapon_mana_regen_1', 'weapon_mana_regen_2', 'weapon_mana_regen_5',
|
||||||
|
'sword_fire', 'sword_frost', 'sword_lightning', 'sword_void'
|
||||||
|
];
|
||||||
|
allEffectIds.forEach(id => {
|
||||||
|
if (!store.unlockedEffects.includes(id)) {
|
||||||
|
store.unlockedEffects.push(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
🚀 Max Everything
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
46
src/components/game/tabs/LootTab.tsx
Normal file
46
src/components/game/tabs/LootTab.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import type { GameStore } from '@/lib/game/store';
|
||||||
|
import { LootInventoryDisplay } from '@/components/game/LootInventory';
|
||||||
|
|
||||||
|
export interface LootTabProps {
|
||||||
|
store: GameStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LootTab({ store }: LootTabProps) {
|
||||||
|
const inventory = store.lootInventory;
|
||||||
|
const elements = store.elements;
|
||||||
|
const equipmentInstances = store.equipmentInstances;
|
||||||
|
|
||||||
|
// Count items for badge
|
||||||
|
const materialCount = Object.values(inventory.materials).reduce((a, b) => a + b, 0);
|
||||||
|
const blueprintCount = inventory.blueprints.length;
|
||||||
|
const equipmentCount = Object.keys(equipmentInstances).length;
|
||||||
|
const totalItems = materialCount + blueprintCount + equipmentCount;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<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">
|
||||||
|
💎 Loot Inventory
|
||||||
|
<Badge className="ml-auto bg-gray-800 text-gray-300">
|
||||||
|
{totalItems} items
|
||||||
|
</Badge>
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<LootInventoryDisplay
|
||||||
|
inventory={inventory}
|
||||||
|
elements={elements}
|
||||||
|
equipmentInstances={equipmentInstances}
|
||||||
|
onDeleteMaterial={store.deleteMaterial}
|
||||||
|
onDeleteEquipment={store.deleteEquipmentInstance}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -10,3 +10,5 @@ export { StatsTab } from './StatsTab';
|
|||||||
export { EquipmentTab } from './EquipmentTab';
|
export { EquipmentTab } from './EquipmentTab';
|
||||||
export { AttunementsTab } from './AttunementsTab';
|
export { AttunementsTab } from './AttunementsTab';
|
||||||
export { DebugTab } from './DebugTab';
|
export { DebugTab } from './DebugTab';
|
||||||
|
export { LootTab } from './LootTab';
|
||||||
|
export { AchievementsTab } from './AchievementsTab';
|
||||||
|
|||||||
Reference in New Issue
Block a user