Phase 3: Extract sub-components from StatsTab.tsx
This commit is contained in:
+25
-18
@@ -1,6 +1,6 @@
|
|||||||
# Phase 3: Refactor Large Files - Progress
|
# Phase 3: Refactor Large Files - Progress #
|
||||||
|
|
||||||
## Completed Refactorings (All Committed & Pushed)
|
## Completed Refactorings (All Committed & Pushed!)
|
||||||
|
|
||||||
### 1. `types.ts` (516 lines) ✅
|
### 1. `types.ts` (516 lines) ✅
|
||||||
- **Commit**: `eb81ccb Phase 3: Split types.ts into domain-specific files`
|
- **Commit**: `eb81ccb Phase 3: Split types.ts into domain-specific files`
|
||||||
@@ -29,10 +29,20 @@
|
|||||||
|
|
||||||
### 6. `utils.ts` (372 lines) ✅
|
### 6. `utils.ts` (372 lines) ✅
|
||||||
- **Commit**: `23d0a12 Phase 3: Split utils.ts by responsibility`
|
- **Commit**: `23d0a12 Phase 3: Split utils.ts by responsibility`
|
||||||
- **Result**: Split into `utils/formatting.ts`, `floor-utils.ts`, `mana-utils.ts`, `combat-utils.ts`, `index.ts` (some overlap with computed-stats, but consistent)
|
- **Result**: Split into `utils/formatting.ts`, `floor-utils.ts`, `mana-utils.ts`, `combat-utils.ts`, `index.ts`
|
||||||
- **Build**: ✅ Passes
|
- **Build**: ✅ Passes
|
||||||
|
|
||||||
## Failed Refactorings
|
### 7. `DebugTab.tsx` (700 lines) ✅
|
||||||
|
- **Commit**: Phase 3: Split DebugTab.tsx into functional components`
|
||||||
|
- **Result**: Split into `debug/GameStateDebug.tsx`, `SkillDebug.tsx`, `ElementDebug.tsx`, `AttunementDebug.tsx`, `GolemDebug.tsx`, `index.tsx`
|
||||||
|
- **Build**: ✅ Passes
|
||||||
|
|
||||||
|
### 8. `page.tsx` (465 lines) ✅
|
||||||
|
- **Commit**: `eea5ed1 Phase 3: Lazy load tabs in page.tsx`
|
||||||
|
- **Result**: Lazy loads all tab components using React.lazy() and Suspense
|
||||||
|
- **Build**: ✅ Passes
|
||||||
|
|
||||||
|
## Failed Refactorings!
|
||||||
|
|
||||||
### 1. `store.ts` (2464 lines) ❌
|
### 1. `store.ts` (2464 lines) ❌
|
||||||
- **Issue**: Sub-agent made changes that broke build (`Cannot read properties of undefined (reading 'mainHand')`)
|
- **Issue**: Sub-agent made changes that broke build (`Cannot read properties of undefined (reading 'mainHand')`)
|
||||||
@@ -45,24 +55,21 @@
|
|||||||
|
|
||||||
### 3. `gameStore.ts` (509 lines) ❌
|
### 3. `gameStore.ts` (509 lines) ❌
|
||||||
- **Issue**: Sub-agent returned empty result (context limits or other issue)
|
- **Issue**: Sub-agent returned empty result (context limits or other issue)
|
||||||
- **Status**: Will try again with simpler prompt, or flag as "sub-agent unstable for this file"
|
- **Status**: Flagged as "unstable sub-agent behavior"
|
||||||
|
|
||||||
## Next Files to Refactor
|
## Next Files to Refactor!
|
||||||
|
|
||||||
### High Priority (Smaller, Likely to Work)
|
### High Priority (Smaller, Likely to Work)
|
||||||
1. `src/components/game/tabs/DebugTab.tsx` (700 lines) - Split by functional area
|
1. `src/components/game/StatsTab.tsx` (551 lines) - Extract sub-components (NEXT TARGET!)
|
||||||
2. `src/app/page.tsx` (465 lines) - Lazy load tabs
|
2. `src/components/game/tabs/StatsTab.tsx` (545 lines) - Extract sub-components
|
||||||
|
3. `src/components/game/tabs/CraftingTab.tsx` (already split) - done
|
||||||
### Medium Priority
|
|
||||||
3. `src/components/game/StatsTab.tsx` (551 lines) - Extract sub-components
|
|
||||||
4. `src/lib/game/stores/index.test.ts` (maybe not needed)
|
4. `src/lib/game/stores/index.test.ts` (maybe not needed)
|
||||||
|
|
||||||
## Build Status
|
## Build Status!
|
||||||
✅ Build passes after each successful refactoring
|
✅ Build passes after each successful refactoring
|
||||||
✅ All commits pushed to remote (`git push origin master` successful)
|
✅ All commits pushed to remote!
|
||||||
|
|
||||||
## Notes
|
## Notes!
|
||||||
- Sub-agents work best with files under ~1500 lines with focused prompts
|
- Sub-agents work best with files under ~1500 lines with focused prompts!
|
||||||
- Files over 2000 lines consistently fail (context limits)
|
- Files over 2000 lines consistently fail (context limits)!
|
||||||
- Some files around 500 lines also fail occasionally (unstable sub-agent behavior)
|
- When in doubt, flag it and move on (per user instructions)!
|
||||||
- When in doubt, flag it and move on (per user instructions)
|
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { GUARDIANS } from '@/lib/game/constants';
|
||||||
|
import { fmtDec } from '@/lib/game/store';
|
||||||
|
import type { GameStore } from '@/lib/game/types';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Swords } from 'lucide-react';
|
||||||
|
|
||||||
|
export interface CombatStatsSectionProps {
|
||||||
|
store: GameStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CombatStatsSection({ store }: CombatStatsSectionProps) {
|
||||||
|
return (
|
||||||
|
<Card className="bg-gray-900/80 border-gray-700">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-red-400 game-panel-title text-xs flex items-center gap-2">
|
||||||
|
<Swords className="w-4 h-4" />
|
||||||
|
Combat Stats
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Combat Training Bonus:</span>
|
||||||
|
<span className="text-red-300">+{(store.skills.combatTrain || 0) * 5}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Arcane Fury Multiplier:</span>
|
||||||
|
<span className="text-red-300">×{fmtDec(1 + (store.skills.arcaneFury || 0) * 0.1, 2)}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Elemental Mastery:</span>
|
||||||
|
<span className="text-red-300">×{fmtDec(1 + (store.skills.elementalMastery || 0) * 0.15, 2)}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Guardian Bane:</span>
|
||||||
|
<span className="text-red-300">×{fmtDec(1 + (store.skills.guardianBane || 0) * 0.2, 2)} (vs guardians)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Critical Hit Chance:</span>
|
||||||
|
<span className="text-amber-300">{((store.skills.precision || 0) * 5)}%</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Critical Multiplier:</span>
|
||||||
|
<span className="text-amber-300">1.5x</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Spell Echo Chance:</span>
|
||||||
|
<span className="text-amber-300">{((store.skills.spellEcho || 0) * 10)}%</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Pact Multiplier:</span>
|
||||||
|
<span className="text-amber-300">×{fmtDec(store.signedPacts.reduce((m, f) => m * (GUARDIANS[f]?.pact || 1), 1), 2)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { getTierMultiplier } from '@/lib/game/skill-evolution';
|
||||||
|
import { hasSpecial, SPECIAL_EFFECTS } from '@/lib/game/effects';
|
||||||
|
import { fmt, fmtDec } from '@/lib/game/store';
|
||||||
|
import type { GameStore, UnifiedEffects } from '@/lib/game/types';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Separator } from '@/components/ui/separator';
|
||||||
|
import { Droplet } from 'lucide-react';
|
||||||
|
|
||||||
|
export interface ManaStatsSectionProps {
|
||||||
|
store: GameStore;
|
||||||
|
upgradeEffects: UnifiedEffects;
|
||||||
|
maxMana: number;
|
||||||
|
baseRegen: number;
|
||||||
|
clickMana: number;
|
||||||
|
meditationMultiplier: number;
|
||||||
|
effectiveRegen: number;
|
||||||
|
incursionStrength: number;
|
||||||
|
manaCascadeBonus: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ManaStatsSection({
|
||||||
|
store,
|
||||||
|
upgradeEffects,
|
||||||
|
maxMana,
|
||||||
|
baseRegen,
|
||||||
|
clickMana,
|
||||||
|
meditationMultiplier,
|
||||||
|
effectiveRegen,
|
||||||
|
incursionStrength,
|
||||||
|
manaCascadeBonus,
|
||||||
|
}: ManaStatsSectionProps) {
|
||||||
|
return (
|
||||||
|
<Card className="bg-gray-900/80 border-gray-700">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-blue-400 game-panel-title text-xs flex items-center gap-2">
|
||||||
|
<Droplet className="w-4 h-4" />
|
||||||
|
Mana Stats
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Base Max Mana:</span>
|
||||||
|
<span className="text-gray-200">100</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Mana Well Bonus:</span>
|
||||||
|
<span className="text-blue-300">
|
||||||
|
{(() => {
|
||||||
|
const mw = store.skillTiers?.manaWell || 1;
|
||||||
|
const tieredSkillId = mw > 1 ? `manaWell_t${mw}` : 'manaWell';
|
||||||
|
const level = store.skills[tieredSkillId] || store.skills.manaWell || 0;
|
||||||
|
const tierMult = getTierMultiplier(tieredSkillId);
|
||||||
|
return `+${fmt(level * 100 * tierMult)} (${level} lvl × 100 × ${tierMult}x tier)`;
|
||||||
|
})()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Prestige Mana Well:</span>
|
||||||
|
<span className="text-blue-300">+{fmt((store.prestigeUpgrades.manaWell || 0) * 500)}</span>
|
||||||
|
</div>
|
||||||
|
{upgradeEffects.maxManaBonus > 0 && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-amber-400">Upgrade Mana Bonus:</span>
|
||||||
|
<span className="text-amber-300">+{fmt(upgradeEffects.maxManaBonus)}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{upgradeEffects.maxManaMultiplier > 1 && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-amber-400">Upgrade Mana Multiplier:</span>
|
||||||
|
<span className="text-amber-300">×{fmtDec(upgradeEffects.maxManaMultiplier, 2)}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex justify-between text-sm font-semibold border-t border-gray-700 pt-2">
|
||||||
|
<span className="text-gray-300">Total Max Mana:</span>
|
||||||
|
<span className="text-blue-400">{fmt(maxMana)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Base Regen:</span>
|
||||||
|
<span className="text-gray-200">2/hr</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Mana Flow Bonus:</span>
|
||||||
|
<span className="text-blue-300">
|
||||||
|
{(() => {
|
||||||
|
const mf = store.skillTiers?.manaFlow || 1;
|
||||||
|
const tieredSkillId = mf > 1 ? `manaFlow_t${mf}` : 'manaFlow';
|
||||||
|
const level = store.skills[tieredSkillId] || store.skills.manaFlow || 0;
|
||||||
|
const tierMult = getTierMultiplier(tieredSkillId);
|
||||||
|
return `+${fmtDec(level * 1 * tierMult)}/hr (${level} lvl × 1 × ${tierMult}x tier)`;
|
||||||
|
})()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Mana Spring Bonus:</span>
|
||||||
|
<span className="text-blue-300">+{(store.skills.manaSpring || 0) * 2}/hr</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Prestige Mana Flow:</span>
|
||||||
|
<span className="text-blue-300">+{fmtDec((store.prestigeUpgrades.manaFlow || 0) * 0.5)}/hr</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Temporal Echo:</span>
|
||||||
|
<span className="text-blue-300">×{fmtDec(1 + (store.prestigeUpgrades.temporalEcho || 0) * 0.1, 2)}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm font-semibold border-t border-gray-700 pt-2">
|
||||||
|
<span className="text-gray-300">Base Regen:</span>
|
||||||
|
<span className="text-blue-400">{fmtDec(baseRegen, 2)}/hr</span>
|
||||||
|
</div>
|
||||||
|
{upgradeEffects.regenBonus > 0 && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-amber-400">Upgrade Regen Bonus:</span>
|
||||||
|
<span className="text-amber-300">+{fmtDec(upgradeEffects.regenBonus, 2)}/hr</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{upgradeEffects.permanentRegenBonus > 0 && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-amber-400">Permanent Regen Bonus:</span>
|
||||||
|
<span className="text-amber-300">+{fmtDec(upgradeEffects.permanentRegenBonus, 2)}/hr</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{upgradeEffects.regenMultiplier > 1 && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-amber-400">Upgrade Regen Multiplier:</span>
|
||||||
|
<span className="text-amber-300">×{fmtDec(upgradeEffects.regenMultiplier, 2)}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Separator className="bg-gray-700 my-3" />
|
||||||
|
{upgradeEffects.activeUpgrades.length > 0 && (
|
||||||
|
<>
|
||||||
|
<div className="mb-2">
|
||||||
|
<span className="text-xs text-amber-400 game-panel-title">Active Skill Upgrades</span>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 mb-3">
|
||||||
|
{upgradeEffects.activeUpgrades.map((upgrade, idx) => (
|
||||||
|
<div key={idx} className="flex justify-between text-xs bg-gray-800/50 rounded px-2 py-1">
|
||||||
|
<span className="text-gray-300">{upgrade.name}</span>
|
||||||
|
<span className="text-gray-400">{upgrade.desc}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<Separator className="bg-gray-700 my-3" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Click Mana Value:</span>
|
||||||
|
<span className="text-purple-300">+{clickMana}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Mana Tap Bonus:</span>
|
||||||
|
<span className="text-purple-300">+{store.skills.manaTap || 0}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Mana Surge Bonus:</span>
|
||||||
|
<span className="text-purple-300">+{(store.skills.manaSurge || 0) * 3}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Mana Overflow:</span>
|
||||||
|
<span className="text-purple-300">×{fmtDec(1 + (store.skills.manaOverflow || 0) * 0.25, 2)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Meditation Multiplier:</span>
|
||||||
|
<span className={`font-semibold ${meditationMultiplier > 1.5 ? 'text-purple-400' : 'text-gray-300'}`}>
|
||||||
|
{fmtDec(meditationMultiplier, 2)}x
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Effective Regen:</span>
|
||||||
|
<span className="text-green-400 font-semibold">{fmtDec(effectiveRegen, 2)}/hr</span>
|
||||||
|
</div>
|
||||||
|
{incursionStrength > 0 && !hasSpecial(upgradeEffects, SPECIAL_EFFECTS.STEADY_STREAM) && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-red-400">Incursion Penalty:</span>
|
||||||
|
<span className="text-red-400">-{Math.round(incursionStrength * 100)}%</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{hasSpecial(upgradeEffects, SPECIAL_EFFECTS.STEADY_STREAM) && incursionStrength > 0 && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-green-400">Steady Stream:</span>
|
||||||
|
<span className="text-green-400">Immune to incursion</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{manaCascadeBonus > 0 && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-cyan-400">Mana Cascade Bonus:</span>
|
||||||
|
<span className="text-cyan-400">+{fmtDec(manaCascadeBonus, 2)}/hr</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{hasSpecial(upgradeEffects, SPECIAL_EFFECTS.MANA_TORRENT) && store.rawMana > maxMana * 0.75 && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-cyan-400">Mana Torrent:</span>
|
||||||
|
<span className="text-cyan-400">+50% regen (high mana)</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{hasSpecial(upgradeEffects, SPECIAL_EFFECTS.DESPERATE_WELLS) && store.rawMana < maxMana * 0.25 && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-cyan-400">Desperate Wells:</span>
|
||||||
|
<span className="text-cyan-400">+50% regen (low mana)</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { fmtDec } from '@/lib/game/store';
|
||||||
|
import type { GameStore } from '@/lib/game/types';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { BookOpen } from 'lucide-react';
|
||||||
|
|
||||||
|
export interface StudyStatsSectionProps {
|
||||||
|
store: GameStore;
|
||||||
|
studySpeedMult: number;
|
||||||
|
studyCostMult: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StudyStatsSection({ store, studySpeedMult, studyCostMult }: StudyStatsSectionProps) {
|
||||||
|
return (
|
||||||
|
<Card className="bg-gray-900/80 border-gray-700">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-purple-400 game-panel-title text-xs flex items-center gap-2">
|
||||||
|
<BookOpen className="w-4 h-4" />
|
||||||
|
Study Stats
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Study Speed:</span>
|
||||||
|
<span className="text-purple-300">×{fmtDec(studySpeedMult, 2)}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Quick Learner Bonus:</span>
|
||||||
|
<span className="text-purple-300">+{((store.skills.quickLearner || 0) * 10)}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Study Cost:</span>
|
||||||
|
<span className="text-purple-300">{Math.round(studyCostMult * 100)}%</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Focused Mind Bonus:</span>
|
||||||
|
<span className="text-purple-300">-{((store.skills.focusedMind || 0) * 5)}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-400">Progress Retention:</span>
|
||||||
|
<span className="text-purple-300">{Math.round((1 + (store.skills.knowledgeRetention || 0) * 0.2) * 100)}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { SKILL_EVOLUTION_PATHS, getTierMultiplier } from '@/lib/game/skill-evolution';
|
||||||
|
import { SKILLS_DEF } from '@/lib/game/constants';
|
||||||
|
import type { GameStore, SkillUpgradeChoice } from '@/lib/game/types';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { Star } from 'lucide-react';
|
||||||
|
|
||||||
|
export interface UpgradeEffectsSectionProps {
|
||||||
|
store: GameStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to get all selected skill upgrades
|
||||||
|
function getAllSelectedUpgrades(store: GameStore) {
|
||||||
|
const upgrades: { skillId: string; upgrade: SkillUpgradeChoice }[] = [];
|
||||||
|
for (const [skillId, selectedIds] of Object.entries(store.skillUpgrades)) {
|
||||||
|
const baseSkillId = skillId.includes('_t') ? skillId.split('_t')[0] : skillId;
|
||||||
|
const path = SKILL_EVOLUTION_PATHS[baseSkillId];
|
||||||
|
if (!path) continue;
|
||||||
|
for (const tier of path.tiers) {
|
||||||
|
if (tier.skillId === skillId) {
|
||||||
|
for (const upgradeId of selectedIds) {
|
||||||
|
const upgrade = tier.upgrades.find(u => u.id === upgradeId);
|
||||||
|
if (upgrade) {
|
||||||
|
upgrades.push({ skillId, upgrade });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return upgrades;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function UpgradeEffectsSection({ store }: UpgradeEffectsSectionProps) {
|
||||||
|
const selectedUpgrades = getAllSelectedUpgrades(store);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="bg-gray-900/80 border-gray-700">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-amber-400 game-panel-title text-xs flex items-center gap-2">
|
||||||
|
<Star className="w-4 h-4" />
|
||||||
|
Active Skill Upgrades ({selectedUpgrades.length})
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{selectedUpgrades.length === 0 ? (
|
||||||
|
<div className="text-gray-500 text-sm">No skill upgrades selected yet. Level skills to 5 or 10 to choose upgrades.</div>
|
||||||
|
) : (
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||||
|
{selectedUpgrades.map(({ skillId, upgrade }) => (
|
||||||
|
<div key={upgrade.id} className="p-2 rounded border border-amber-600/30 bg-amber-900/10">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-amber-300 text-sm font-semibold">{upgrade.name}</span>
|
||||||
|
<Badge variant="outline" className="text-xs text-gray-400">
|
||||||
|
{SKILLS_DEF[skillId]?.name || skillId}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-gray-400 mt-1">{upgrade.desc}</div>
|
||||||
|
{upgrade.effect.type === 'multiplier' && (
|
||||||
|
<div className="text-xs text-green-400 mt-1">
|
||||||
|
+{Math.round((upgrade.effect.value! - 1) * 100)}% {upgrade.effect.stat}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{upgrade.effect.type === 'bonus' && (
|
||||||
|
<div className="text-xs text-blue-400 mt-1">
|
||||||
|
+{upgrade.effect.value} {upgrade.effect.stat}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{upgrade.effect.type === 'special' && (
|
||||||
|
<div className="text-xs text-cyan-400 mt-1">
|
||||||
|
⚡ {upgrade.effect.specialDesc || 'Special effect active'}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export { ManaStatsSection } from './ManaStatsSection';
|
||||||
|
export type { ManaStatsSectionProps } from './ManaStatsSection';
|
||||||
|
|
||||||
|
export { CombatStatsSection } from './CombatStatsSection';
|
||||||
|
export type { CombatStatsSectionProps } from './CombatStatsSection';
|
||||||
|
|
||||||
|
export { StudyStatsSection } from './StudyStatsSection';
|
||||||
|
export type { StudyStatsSectionProps } from './StudyStatsSection';
|
||||||
|
|
||||||
|
export { UpgradeEffectsSection } from './UpgradeEffectsSection';
|
||||||
|
export type { UpgradeEffectsSectionProps } from './UpgradeEffectsSection';
|
||||||
@@ -1,14 +1,17 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { ELEMENTS, GUARDIANS, SKILLS_DEF } from '@/lib/game/constants';
|
import { ELEMENTS, GUARDIANS } from '@/lib/game/constants';
|
||||||
import { SKILL_EVOLUTION_PATHS, getTierMultiplier } from '@/lib/game/skill-evolution';
|
import { getTierMultiplier } from '@/lib/game/skill-evolution';
|
||||||
import { hasSpecial, SPECIAL_EFFECTS } from '@/lib/game/effects';
|
import { hasSpecial, SPECIAL_EFFECTS } from '@/lib/game/effects';
|
||||||
import { fmt, fmtDec } from '@/lib/game/store';
|
import { fmt, fmtDec } from '@/lib/game/store';
|
||||||
import type { SkillUpgradeChoice, GameStore, UnifiedEffects } from '@/lib/game/types';
|
import type { GameStore, UnifiedEffects } from '@/lib/game/types';
|
||||||
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 { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { Droplet, Swords, BookOpen, FlaskConical, Trophy, RotateCcw, Star } from 'lucide-react';
|
import { FlaskConical, Trophy, RotateCcw } from 'lucide-react';
|
||||||
|
import { ManaStatsSection } from '../stats/ManaStatsSection';
|
||||||
|
import { CombatStatsSection } from '../stats/CombatStatsSection';
|
||||||
|
import { StudyStatsSection } from '../stats/StudyStatsSection';
|
||||||
|
import { UpgradeEffectsSection } from '../stats/UpgradeEffectsSection';
|
||||||
|
|
||||||
export interface StatsTabProps {
|
export interface StatsTabProps {
|
||||||
store: GameStore;
|
store: GameStore;
|
||||||
@@ -46,303 +49,30 @@ export function StatsTab({
|
|||||||
return 10 + level * 50 * tierMult + (store.prestigeUpgrades.elementalAttune || 0) * 25;
|
return 10 + level * 50 * tierMult + (store.prestigeUpgrades.elementalAttune || 0) * 25;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// Get all selected skill upgrades
|
|
||||||
const getAllSelectedUpgrades = () => {
|
|
||||||
const upgrades: { skillId: string; upgrade: SkillUpgradeChoice }[] = [];
|
|
||||||
for (const [skillId, selectedIds] of Object.entries(store.skillUpgrades)) {
|
|
||||||
const baseSkillId = skillId.includes('_t') ? skillId.split('_t')[0] : skillId;
|
|
||||||
const path = SKILL_EVOLUTION_PATHS[baseSkillId];
|
|
||||||
if (!path) continue;
|
|
||||||
for (const tier of path.tiers) {
|
|
||||||
if (tier.skillId === skillId) {
|
|
||||||
for (const upgradeId of selectedIds) {
|
|
||||||
const upgrade = tier.upgrades.find(u => u.id === upgradeId);
|
|
||||||
if (upgrade) {
|
|
||||||
upgrades.push({ skillId, upgrade });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return upgrades;
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectedUpgrades = getAllSelectedUpgrades();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Mana Stats */}
|
{/* Mana Stats */}
|
||||||
<Card className="bg-gray-900/80 border-gray-700">
|
<ManaStatsSection
|
||||||
<CardHeader className="pb-2">
|
store={store}
|
||||||
<CardTitle className="text-blue-400 game-panel-title text-xs flex items-center gap-2">
|
upgradeEffects={upgradeEffects}
|
||||||
<Droplet className="w-4 h-4" />
|
maxMana={maxMana}
|
||||||
Mana Stats
|
baseRegen={baseRegen}
|
||||||
</CardTitle>
|
clickMana={clickMana}
|
||||||
</CardHeader>
|
meditationMultiplier={meditationMultiplier}
|
||||||
<CardContent>
|
effectiveRegen={effectiveRegen}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
incursionStrength={incursionStrength}
|
||||||
<div className="space-y-2">
|
manaCascadeBonus={manaCascadeBonus}
|
||||||
<div className="flex justify-between text-sm">
|
/>
|
||||||
<span className="text-gray-400">Base Max Mana:</span>
|
|
||||||
<span className="text-gray-200">100</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Mana Well Bonus:</span>
|
|
||||||
<span className="text-blue-300">
|
|
||||||
{(() => {
|
|
||||||
const mw = store.skillTiers?.manaWell || 1;
|
|
||||||
const tieredSkillId = mw > 1 ? `manaWell_t${mw}` : 'manaWell';
|
|
||||||
const level = store.skills[tieredSkillId] || store.skills.manaWell || 0;
|
|
||||||
const tierMult = getTierMultiplier(tieredSkillId);
|
|
||||||
return `+${fmt(level * 100 * tierMult)} (${level} lvl × 100 × ${tierMult}x tier)`;
|
|
||||||
})()}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Prestige Mana Well:</span>
|
|
||||||
<span className="text-blue-300">+{fmt((store.prestigeUpgrades.manaWell || 0) * 500)}</span>
|
|
||||||
</div>
|
|
||||||
{upgradeEffects.maxManaBonus > 0 && (
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-amber-400">Upgrade Mana Bonus:</span>
|
|
||||||
<span className="text-amber-300">+{fmt(upgradeEffects.maxManaBonus)}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{upgradeEffects.maxManaMultiplier > 1 && (
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-amber-400">Upgrade Mana Multiplier:</span>
|
|
||||||
<span className="text-amber-300">×{fmtDec(upgradeEffects.maxManaMultiplier, 2)}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="flex justify-between text-sm font-semibold border-t border-gray-700 pt-2">
|
|
||||||
<span className="text-gray-300">Total Max Mana:</span>
|
|
||||||
<span className="text-blue-400">{fmt(maxMana)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Base Regen:</span>
|
|
||||||
<span className="text-gray-200">2/hr</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Mana Flow Bonus:</span>
|
|
||||||
<span className="text-blue-300">
|
|
||||||
{(() => {
|
|
||||||
const mf = store.skillTiers?.manaFlow || 1;
|
|
||||||
const tieredSkillId = mf > 1 ? `manaFlow_t${mf}` : 'manaFlow';
|
|
||||||
const level = store.skills[tieredSkillId] || store.skills.manaFlow || 0;
|
|
||||||
const tierMult = getTierMultiplier(tieredSkillId);
|
|
||||||
return `+${fmtDec(level * 1 * tierMult)}/hr (${level} lvl × 1 × ${tierMult}x tier)`;
|
|
||||||
})()}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Mana Spring Bonus:</span>
|
|
||||||
<span className="text-blue-300">+{(store.skills.manaSpring || 0) * 2}/hr</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Prestige Mana Flow:</span>
|
|
||||||
<span className="text-blue-300">+{fmtDec((store.prestigeUpgrades.manaFlow || 0) * 0.5)}/hr</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Temporal Echo:</span>
|
|
||||||
<span className="text-blue-300">×{fmtDec(1 + (store.prestigeUpgrades.temporalEcho || 0) * 0.1, 2)}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between text-sm font-semibold border-t border-gray-700 pt-2">
|
|
||||||
<span className="text-gray-300">Base Regen:</span>
|
|
||||||
<span className="text-blue-400">{fmtDec(baseRegen, 2)}/hr</span>
|
|
||||||
</div>
|
|
||||||
{upgradeEffects.regenBonus > 0 && (
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-amber-400">Upgrade Regen Bonus:</span>
|
|
||||||
<span className="text-amber-300">+{fmtDec(upgradeEffects.regenBonus, 2)}/hr</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{upgradeEffects.permanentRegenBonus > 0 && (
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-amber-400">Permanent Regen Bonus:</span>
|
|
||||||
<span className="text-amber-300">+{fmtDec(upgradeEffects.permanentRegenBonus, 2)}/hr</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{upgradeEffects.regenMultiplier > 1 && (
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-amber-400">Upgrade Regen Multiplier:</span>
|
|
||||||
<span className="text-amber-300">×{fmtDec(upgradeEffects.regenMultiplier, 2)}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Separator className="bg-gray-700 my-3" />
|
|
||||||
{upgradeEffects.activeUpgrades.length > 0 && (
|
|
||||||
<>
|
|
||||||
<div className="mb-2">
|
|
||||||
<span className="text-xs text-amber-400 game-panel-title">Active Skill Upgrades</span>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 mb-3">
|
|
||||||
{upgradeEffects.activeUpgrades.map((upgrade, idx) => (
|
|
||||||
<div key={idx} className="flex justify-between text-xs bg-gray-800/50 rounded px-2 py-1">
|
|
||||||
<span className="text-gray-300">{upgrade.name}</span>
|
|
||||||
<span className="text-gray-400">{upgrade.desc}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<Separator className="bg-gray-700 my-3" />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Click Mana Value:</span>
|
|
||||||
<span className="text-purple-300">+{clickMana}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Mana Tap Bonus:</span>
|
|
||||||
<span className="text-purple-300">+{store.skills.manaTap || 0}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Mana Surge Bonus:</span>
|
|
||||||
<span className="text-purple-300">+{(store.skills.manaSurge || 0) * 3}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Mana Overflow:</span>
|
|
||||||
<span className="text-purple-300">×{fmtDec(1 + (store.skills.manaOverflow || 0) * 0.25, 2)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Meditation Multiplier:</span>
|
|
||||||
<span className={`font-semibold ${meditationMultiplier > 1.5 ? 'text-purple-400' : 'text-gray-300'}`}>
|
|
||||||
{fmtDec(meditationMultiplier, 2)}x
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Effective Regen:</span>
|
|
||||||
<span className="text-green-400 font-semibold">{fmtDec(effectiveRegen, 2)}/hr</span>
|
|
||||||
</div>
|
|
||||||
{incursionStrength > 0 && !hasSpecial(upgradeEffects, SPECIAL_EFFECTS.STEADY_STREAM) && (
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-red-400">Incursion Penalty:</span>
|
|
||||||
<span className="text-red-400">-{Math.round(incursionStrength * 100)}%</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{hasSpecial(upgradeEffects, SPECIAL_EFFECTS.STEADY_STREAM) && incursionStrength > 0 && (
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-green-400">Steady Stream:</span>
|
|
||||||
<span className="text-green-400">Immune to incursion</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{manaCascadeBonus > 0 && (
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-cyan-400">Mana Cascade Bonus:</span>
|
|
||||||
<span className="text-cyan-400">+{fmtDec(manaCascadeBonus, 2)}/hr</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{hasSpecial(upgradeEffects, SPECIAL_EFFECTS.MANA_TORRENT) && store.rawMana > maxMana * 0.75 && (
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-cyan-400">Mana Torrent:</span>
|
|
||||||
<span className="text-cyan-400">+50% regen (high mana)</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{hasSpecial(upgradeEffects, SPECIAL_EFFECTS.DESPERATE_WELLS) && store.rawMana < maxMana * 0.25 && (
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-cyan-400">Desperate Wells:</span>
|
|
||||||
<span className="text-cyan-400">+50% regen (low mana)</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Combat Stats */}
|
{/* Combat Stats */}
|
||||||
<Card className="bg-gray-900/80 border-gray-700">
|
<CombatStatsSection store={store} />
|
||||||
<CardHeader className="pb-2">
|
|
||||||
<CardTitle className="text-red-400 game-panel-title text-xs flex items-center gap-2">
|
|
||||||
<Swords className="w-4 h-4" />
|
|
||||||
Combat Stats
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Combat Training Bonus:</span>
|
|
||||||
<span className="text-red-300">+{(store.skills.combatTrain || 0) * 5}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Arcane Fury Multiplier:</span>
|
|
||||||
<span className="text-red-300">×{fmtDec(1 + (store.skills.arcaneFury || 0) * 0.1, 2)}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Elemental Mastery:</span>
|
|
||||||
<span className="text-red-300">×{fmtDec(1 + (store.skills.elementalMastery || 0) * 0.15, 2)}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Guardian Bane:</span>
|
|
||||||
<span className="text-red-300">×{fmtDec(1 + (store.skills.guardianBane || 0) * 0.2, 2)} (vs guardians)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Critical Hit Chance:</span>
|
|
||||||
<span className="text-amber-300">{((store.skills.precision || 0) * 5)}%</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Critical Multiplier:</span>
|
|
||||||
<span className="text-amber-300">1.5x</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Spell Echo Chance:</span>
|
|
||||||
<span className="text-amber-300">{((store.skills.spellEcho || 0) * 10)}%</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Pact Multiplier:</span>
|
|
||||||
<span className="text-amber-300">×{fmtDec(store.signedPacts.reduce((m, f) => m * (GUARDIANS[f]?.pact || 1), 1), 2)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Study Stats */}
|
{/* Study Stats */}
|
||||||
<Card className="bg-gray-900/80 border-gray-700">
|
<StudyStatsSection
|
||||||
<CardHeader className="pb-2">
|
store={store}
|
||||||
<CardTitle className="text-purple-400 game-panel-title text-xs flex items-center gap-2">
|
studySpeedMult={studySpeedMult}
|
||||||
<BookOpen className="w-4 h-4" />
|
studyCostMult={studyCostMult}
|
||||||
Study Stats
|
/>
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Study Speed:</span>
|
|
||||||
<span className="text-purple-300">×{fmtDec(studySpeedMult, 2)}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Quick Learner Bonus:</span>
|
|
||||||
<span className="text-purple-300">+{((store.skills.quickLearner || 0) * 10)}%</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Study Cost:</span>
|
|
||||||
<span className="text-purple-300">{Math.round(studyCostMult * 100)}%</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Focused Mind Bonus:</span>
|
|
||||||
<span className="text-purple-300">-{((store.skills.focusedMind || 0) * 5)}%</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-400">Progress Retention:</span>
|
|
||||||
<span className="text-purple-300">{Math.round((1 + (store.skills.knowledgeRetention || 0) * 0.2) * 100)}%</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Element Stats */}
|
{/* Element Stats */}
|
||||||
<Card className="bg-gray-900/80 border-gray-700">
|
<Card className="bg-gray-900/80 border-gray-700">
|
||||||
@@ -406,48 +136,7 @@ export function StatsTab({
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Active Upgrades */}
|
{/* Active Upgrades */}
|
||||||
<Card className="bg-gray-900/80 border-gray-700">
|
<UpgradeEffectsSection store={store} />
|
||||||
<CardHeader className="pb-2">
|
|
||||||
<CardTitle className="text-amber-400 game-panel-title text-xs flex items-center gap-2">
|
|
||||||
<Star className="w-4 h-4" />
|
|
||||||
Active Skill Upgrades ({selectedUpgrades.length})
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
{selectedUpgrades.length === 0 ? (
|
|
||||||
<div className="text-gray-500 text-sm">No skill upgrades selected yet. Level skills to 5 or 10 to choose upgrades.</div>
|
|
||||||
) : (
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
|
||||||
{selectedUpgrades.map(({ skillId, upgrade }) => (
|
|
||||||
<div key={upgrade.id} className="p-2 rounded border border-amber-600/30 bg-amber-900/10">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<span className="text-amber-300 text-sm font-semibold">{upgrade.name}</span>
|
|
||||||
<Badge variant="outline" className="text-xs text-gray-400">
|
|
||||||
{SKILLS_DEF[skillId]?.name || skillId}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-gray-400 mt-1">{upgrade.desc}</div>
|
|
||||||
{upgrade.effect.type === 'multiplier' && (
|
|
||||||
<div className="text-xs text-green-400 mt-1">
|
|
||||||
+{Math.round((upgrade.effect.value! - 1) * 100)}% {upgrade.effect.stat}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{upgrade.effect.type === 'bonus' && (
|
|
||||||
<div className="text-xs text-blue-400 mt-1">
|
|
||||||
+{upgrade.effect.value} {upgrade.effect.stat}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{upgrade.effect.type === 'special' && (
|
|
||||||
<div className="text-xs text-cyan-400 mt-1">
|
|
||||||
⚡ {upgrade.effect.specialDesc || 'Special effect active'}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Pact Bonuses */}
|
{/* Pact Bonuses */}
|
||||||
<Card className="bg-gray-900/80 border-gray-700">
|
<Card className="bg-gray-900/80 border-gray-700">
|
||||||
|
|||||||
Reference in New Issue
Block a user