feat: add discipline and perks section to Stats tab
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m34s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m34s
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
# Circular Dependencies
|
# Circular Dependencies
|
||||||
Generated: 2026-05-27T09:40:41.085Z
|
Generated: 2026-05-27T09:44:00.635Z
|
||||||
|
|
||||||
No circular dependencies found. ✅
|
No circular dependencies found. ✅
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"generated": "2026-05-27T09:40:39.287Z",
|
"generated": "2026-05-27T09:43:58.768Z",
|
||||||
"description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.",
|
"description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.",
|
||||||
"usage": "To find what a file affects, search for its path in the VALUES. To find what a file depends on, look at its KEY entry."
|
"usage": "To find what a file affects, search for its path in the VALUES. To find what a file depends on, look at its KEY entry."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ Mana-Loop/
|
|||||||
│ │ │ │ │ └── index.ts
|
│ │ │ │ │ └── index.ts
|
||||||
│ │ │ │ ├── StatsTab/
|
│ │ │ │ ├── StatsTab/
|
||||||
│ │ │ │ │ ├── CombatStatsSection.tsx
|
│ │ │ │ │ ├── CombatStatsSection.tsx
|
||||||
|
│ │ │ │ │ ├── DisciplineStatsSection.tsx
|
||||||
│ │ │ │ │ ├── ElementStatsSection.tsx
|
│ │ │ │ │ ├── ElementStatsSection.tsx
|
||||||
│ │ │ │ │ ├── LoopStatsSection.tsx
|
│ │ │ │ │ ├── LoopStatsSection.tsx
|
||||||
│ │ │ │ │ ├── ManaStatsSection.tsx
|
│ │ │ │ │ ├── ManaStatsSection.tsx
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
|
|
||||||
import { usePrestigeStore } from '@/lib/game/stores';
|
import { usePrestigeStore } from '@/lib/game/stores';
|
||||||
import { useManaStats, useCombatStats, useStudyStats } from '@/lib/game/hooks/useGameDerived';
|
import { useManaStats, useCombatStats, useStudyStats } from '@/lib/game/hooks/useGameDerived';
|
||||||
|
import { computeDisciplineEffects } from '@/lib/game/effects/discipline-effects';
|
||||||
import { ManaStatsSection } from './StatsTab/ManaStatsSection';
|
import { ManaStatsSection } from './StatsTab/ManaStatsSection';
|
||||||
import { CombatStatsSection } from './StatsTab/CombatStatsSection';
|
import { CombatStatsSection } from './StatsTab/CombatStatsSection';
|
||||||
import { PactStatusSection } from './StatsTab/PactStatusSection';
|
import { PactStatusSection } from './StatsTab/PactStatusSection';
|
||||||
import { StudyStatsSection } from './StatsTab/StudyStatsSection';
|
import { StudyStatsSection } from './StatsTab/StudyStatsSection';
|
||||||
import { ElementStatsSection } from './StatsTab/ElementStatsSection';
|
import { ElementStatsSection } from './StatsTab/ElementStatsSection';
|
||||||
import { LoopStatsSection } from './StatsTab/LoopStatsSection';
|
import { LoopStatsSection } from './StatsTab/LoopStatsSection';
|
||||||
|
import { DisciplineStatsSection } from './StatsTab/DisciplineStatsSection';
|
||||||
|
|
||||||
export function StatsTab() {
|
export function StatsTab() {
|
||||||
const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades);
|
const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades);
|
||||||
@@ -15,6 +17,7 @@ export function StatsTab() {
|
|||||||
const manaStats = useManaStats();
|
const manaStats = useManaStats();
|
||||||
const combatStats = useCombatStats();
|
const combatStats = useCombatStats();
|
||||||
const studyStats = useStudyStats();
|
const studyStats = useStudyStats();
|
||||||
|
const disciplineEffects = computeDisciplineEffects();
|
||||||
|
|
||||||
// Compute element max (base + prestige only)
|
// Compute element max (base + prestige only)
|
||||||
const elemMax = 10 + (prestigeUpgrades.elementalAttune || 0) * 25;
|
const elemMax = 10 + (prestigeUpgrades.elementalAttune || 0) * 25;
|
||||||
@@ -37,6 +40,9 @@ export function StatsTab() {
|
|||||||
studySpeedMult={studyStats.studySpeedMult}
|
studySpeedMult={studyStats.studySpeedMult}
|
||||||
studyCostMult={studyStats.studyCostMult}
|
studyCostMult={studyStats.studyCostMult}
|
||||||
/>
|
/>
|
||||||
|
<DisciplineStatsSection
|
||||||
|
disciplineEffects={disciplineEffects}
|
||||||
|
/>
|
||||||
<ElementStatsSection
|
<ElementStatsSection
|
||||||
elemMax={elemMax}
|
elemMax={elemMax}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Brain } from 'lucide-react';
|
||||||
|
import { useDisciplineStore } from '@/lib/game/stores/discipline-slice';
|
||||||
|
import type { DisciplineEffectsResult } from '@/lib/game/effects/discipline-effects';
|
||||||
|
import { ALL_DISCIPLINES } from '@/lib/game/data/disciplines';
|
||||||
|
import { getUnlockedPerks } from '@/lib/game/utils/discipline-math';
|
||||||
|
|
||||||
|
interface DisciplineStatsSectionProps {
|
||||||
|
disciplineEffects: DisciplineEffectsResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DisciplineStatsSection({ disciplineEffects }: DisciplineStatsSectionProps) {
|
||||||
|
const disciplines = useDisciplineStore((s) => s.disciplines);
|
||||||
|
const activeIds = useDisciplineStore((s) => s.activeIds);
|
||||||
|
|
||||||
|
const disciplineEntries = useMemo(() => {
|
||||||
|
return ALL_DISCIPLINES.map((def) => {
|
||||||
|
const state = disciplines[def.id] ?? { xp: 0, paused: true };
|
||||||
|
const unlockedPerks = getUnlockedPerks(def, state.xp);
|
||||||
|
return { def, state, unlockedPerks };
|
||||||
|
}).filter(({ def, state }) => state.xp > 0 || activeIds.includes(def.id));
|
||||||
|
}, [disciplines, activeIds]);
|
||||||
|
|
||||||
|
if (disciplineEntries.length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="bg-[var(--bg-panel)] border-[var(--border-subtle)]">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-[var(--mana-crystal)] game-panel-title text-xs flex items-center gap-2">
|
||||||
|
<Brain className="w-4 h-4" />
|
||||||
|
Disciplines
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{disciplineEntries.map(({ def, state, unlockedPerks }) => {
|
||||||
|
const totalEffect = disciplineEffects.bonuses[def.statBonus.stat] ?? 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={def.id} className="border-b border-[var(--border-subtle)] pb-2 last:border-0">
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span style={{ color: 'var(--text-secondary)' }}>{def.name}</span>
|
||||||
|
<span style={{ color: activeIds.includes(def.id) ? 'var(--color-success)' : 'var(--text-muted)' }}>
|
||||||
|
{activeIds.includes(def.id) ? 'Active' : 'Paused'} • {state.xp} XP
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{totalEffect > 0 && (
|
||||||
|
<div className="text-xs mt-1" style={{ color: 'var(--text-muted)' }}>
|
||||||
|
Effect: +{totalEffect.toFixed(2)} {def.statBonus.label}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{unlockedPerks.length > 0 && (
|
||||||
|
<div className="mt-1">
|
||||||
|
<span className="text-xs" style={{ color: 'var(--text-muted)' }}>Perks:</span>
|
||||||
|
<ul className="mt-0.5 space-y-0.5">
|
||||||
|
{unlockedPerks.map((perk) => (
|
||||||
|
<li key={perk.id} className="text-xs text-green-500 list-none">• {perk.description}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user