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 { Badge } from '@/components/ui/badge';
|
||||
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 { LootInventoryDisplay } from '@/components/game/LootInventory';
|
||||
import { AchievementsDisplay } from '@/components/game/AchievementsDisplay';
|
||||
// Loot and Achievements moved to separate tabs
|
||||
import { DebugName } from '@/lib/game/debug-context';
|
||||
|
||||
export default function ManaLoopGame() {
|
||||
@@ -210,31 +209,7 @@ export default function ManaLoopGame() {
|
||||
/>
|
||||
</DebugName>
|
||||
|
||||
{/* Loot Inventory */}
|
||||
<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>
|
||||
{/* Loot and Achievements moved to tabs */}
|
||||
</div>
|
||||
|
||||
{/* 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="equipment" className="text-xs px-2 py-1">🛡️ Gear</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="stats" className="text-xs px-2 py-1">📊 Stats</TabsTrigger>
|
||||
<TabsTrigger value="debug" className="text-xs px-2 py-1">🔧 Debug</TabsTrigger>
|
||||
@@ -289,6 +266,18 @@ export default function ManaLoopGame() {
|
||||
</DebugName>
|
||||
</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">
|
||||
<DebugName name="LabTab">
|
||||
<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 */}
|
||||
<Card className="bg-gray-900/80 border-gray-700">
|
||||
<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>
|
||||
<CardContent>
|
||||
{preparationProgress ? (
|
||||
@@ -401,28 +401,36 @@ export function CraftingTab({ store }: CraftingTabProps) {
|
||||
) : (
|
||||
<ScrollArea className="h-64">
|
||||
<div className="space-y-2">
|
||||
{equippedItems.map(({ slot, instance }) => (
|
||||
<div
|
||||
key={instance.instanceId}
|
||||
className={`p-3 rounded border cursor-pointer transition-all ${
|
||||
selectedEquipmentInstance === instance.instanceId
|
||||
? 'border-amber-500 bg-amber-900/20'
|
||||
: 'border-gray-700 bg-gray-800/50 hover:border-gray-600'
|
||||
}`}
|
||||
onClick={() => setSelectedEquipmentInstance(instance.instanceId)}
|
||||
>
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<div className="font-semibold">{instance.name}</div>
|
||||
<div className="text-xs text-gray-400">{SLOT_NAMES[slot]}</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>
|
||||
{equippedItems.map(({ slot, instance }) => {
|
||||
const hasEnchantments = instance.enchantments.length > 0;
|
||||
return (
|
||||
<div
|
||||
key={instance.instanceId}
|
||||
className={`p-3 rounded border cursor-pointer transition-all ${
|
||||
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' : ''}`}
|
||||
onClick={() => setSelectedEquipmentInstance(instance.instanceId)}
|
||||
>
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<div className="font-semibold">{instance.name}</div>
|
||||
<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
|
||||
</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>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
{equippedItems.length === 0 && (
|
||||
<div className="text-center text-gray-400 py-4">No equipped items</div>
|
||||
)}
|
||||
@@ -440,43 +448,79 @@ export function CraftingTab({ store }: CraftingTabProps) {
|
||||
<CardContent>
|
||||
{!selectedEquipmentInstance ? (
|
||||
<div className="text-center text-gray-400 py-8">
|
||||
Select equipment to prepare
|
||||
Select equipment to prepare or disenchant
|
||||
</div>
|
||||
) : preparationProgress ? (
|
||||
<div className="text-gray-400">Preparation in progress...</div>
|
||||
) : (
|
||||
(() => {
|
||||
const instance = equipmentInstances[selectedEquipmentInstance];
|
||||
const hasEnchantments = instance.enchantments.length > 0;
|
||||
const prepTime = 2 + Math.floor(instance.totalCapacity / 50);
|
||||
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 (
|
||||
<div className="space-y-4">
|
||||
<div className="text-lg font-semibold">{instance.name}</div>
|
||||
<Separator className="bg-gray-700" />
|
||||
<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>
|
||||
|
||||
{/* Disenchant option for enchanted gear */}
|
||||
{hasEnchantments && (
|
||||
<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 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}
|
||||
onClick={() => startPreparing(selectedEquipmentInstance)}
|
||||
>
|
||||
Start Preparation ({prepTime}h, {fmt(manaCost)} mana)
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* 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>
|
||||
</div>
|
||||
<Button
|
||||
className="w-full"
|
||||
disabled={rawMana < manaCost}
|
||||
onClick={() => startPreparing(selectedEquipmentInstance)}
|
||||
>
|
||||
Start Preparation ({prepTime}h, {fmt(manaCost)} mana)
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})()
|
||||
@@ -517,10 +561,12 @@ export function CraftingTab({ store }: CraftingTabProps) {
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<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">
|
||||
<div className="space-y-1">
|
||||
{equippedItems.map(({ slot, instance }) => (
|
||||
{equippedItems
|
||||
.filter(({ instance }) => instance.enchantments.length === 0)
|
||||
.map(({ slot, instance }) => (
|
||||
<div
|
||||
key={instance.instanceId}
|
||||
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)
|
||||
</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>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
@@ -632,51 +683,6 @@ export function CraftingTab({ store }: CraftingTabProps) {
|
||||
)}
|
||||
</CardContent>
|
||||
</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>
|
||||
);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Switch } from '@/components/ui/switch';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import {
|
||||
RotateCcw, Bug, Plus, Minus, Lock, Unlock, Zap,
|
||||
Clock, Star, AlertTriangle, Sparkles, Settings, Eye
|
||||
Clock, Star, AlertTriangle, Sparkles, Settings, Eye, BookOpen
|
||||
} from 'lucide-react';
|
||||
import type { GameStore } from '@/lib/game/types';
|
||||
import { ATTUNEMENTS_DEF } from '@/lib/game/data/attunements';
|
||||
@@ -415,6 +415,285 @@ export function DebugTab({ store }: DebugTabProps) {
|
||||
</div>
|
||||
</CardContent>
|
||||
</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>
|
||||
);
|
||||
|
||||
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 { AttunementsTab } from './AttunementsTab';
|
||||
export { DebugTab } from './DebugTab';
|
||||
export { LootTab } from './LootTab';
|
||||
export { AchievementsTab } from './AchievementsTab';
|
||||
|
||||
Reference in New Issue
Block a user