feat: add attunement leveling system, debug tab, and UI improvements

- Add mana pools display to ManaDisplay component with collapsible elements
- Add Debug tab with reset game, mana debug, time control, attunement unlock, element unlock
- Remove ComboMeter from UI (header and SpireTab)
- Remove 'scrollCrafting' capability from Enchanter attunement
- Add attunement level scaling (exponential with level)
- Add XP progress bar and level display in AttunementsTab
- Add getAttunementConversionRate and getAttunementXPForLevel functions
- MAX_ATTUNEMENT_LEVEL = 10 with 3^level XP scaling
This commit is contained in:
2026-03-27 18:51:13 +00:00
parent e0a3d82dea
commit a1b15cea74
7 changed files with 531 additions and 29 deletions

View File

@@ -1,13 +1,13 @@
'use client';
import { ATTUNEMENTS_DEF, ATTUNEMENT_SLOT_NAMES, getTotalAttunementRegen, getAvailableSkillCategories } from '@/lib/game/data/attunements';
import { ATTUNEMENTS_DEF, ATTUNEMENT_SLOT_NAMES, getTotalAttunementRegen, getAvailableSkillCategories, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL, getAttunementConversionRate } from '@/lib/game/data/attunements';
import { ELEMENTS } from '@/lib/game/constants';
import type { GameStore, AttunementState } from '@/lib/game/types';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Progress } from '@/components/ui/progress';
import { Lock, Sparkles } from 'lucide-react';
import { Lock, Sparkles, TrendingUp } from 'lucide-react';
export interface AttunementsTabProps {
store: GameStore;
@@ -38,7 +38,7 @@ export function AttunementsTab({ store }: AttunementsTabProps) {
<CardContent>
<p className="text-sm text-gray-400 mb-3">
Attunements are magical bonds tied to specific body locations. Each attunement grants unique capabilities,
mana regeneration, and access to specialized skills.
mana regeneration, and access to specialized skills. Level them up to increase their power.
</p>
<div className="flex flex-wrap gap-2">
<Badge className="bg-teal-900/50 text-teal-300">
@@ -57,6 +57,11 @@ export function AttunementsTab({ store }: AttunementsTabProps) {
const state = attunements[id];
const isActive = state?.active;
const isUnlocked = state?.active || def.unlocked;
const level = state?.level || 1;
const xp = state?.experience || 0;
const xpNeeded = getAttunementXPForLevel(level + 1);
const xpProgress = xpNeeded > 0 ? (xp / xpNeeded) * 100 : 100;
const isMaxLevel = level >= MAX_ATTUNEMENT_LEVEL;
// Get primary mana element info
const primaryElem = def.primaryManaType ? ELEMENTS[def.primaryManaType] : null;
@@ -65,6 +70,11 @@ export function AttunementsTab({ store }: AttunementsTabProps) {
const currentMana = def.primaryManaType ? store.elements[def.primaryManaType]?.current || 0 : 0;
const maxMana = def.primaryManaType ? store.elements[def.primaryManaType]?.max || 50 : 50;
// Calculate level-scaled stats
const levelMult = Math.pow(1.5, level - 1);
const scaledRegen = def.rawManaRegen * levelMult;
const scaledConversion = getAttunementConversionRate(id, level);
return (
<Card
key={id}
@@ -98,7 +108,7 @@ export function AttunementsTab({ store }: AttunementsTabProps) {
)}
{isActive && (
<Badge className="text-xs" style={{ backgroundColor: `${def.color}30`, color: def.color }}>
Active
Lv.{level}
</Badge>
)}
</div>
@@ -134,20 +144,51 @@ export function AttunementsTab({ store }: AttunementsTabProps) {
)}
</div>
{/* Stats */}
{/* Stats with level scaling */}
<div className="grid grid-cols-2 gap-2 text-xs">
<div className="p-2 bg-gray-800/50 rounded">
<div className="text-gray-500">Raw Regen</div>
<div className="text-green-400 font-semibold">+{def.rawManaRegen}/hr</div>
<div className="text-green-400 font-semibold">
+{scaledRegen.toFixed(2)}/hr
{level > 1 && <span className="text-xs ml-1">({((levelMult - 1) * 100).toFixed(0)}% bonus)</span>}
</div>
</div>
<div className="p-2 bg-gray-800/50 rounded">
<div className="text-gray-500">Conversion</div>
<div className="text-cyan-400 font-semibold">
{def.conversionRate > 0 ? `${def.conversionRate}/hr` : '—'}
{scaledConversion > 0 ? `${scaledConversion.toFixed(2)}/hr` : '—'}
{level > 1 && scaledConversion > 0 && <span className="text-xs ml-1">({((levelMult - 1) * 100).toFixed(0)}% bonus)</span>}
</div>
</div>
</div>
{/* XP Progress Bar */}
{isUnlocked && state && !isMaxLevel && (
<div className="space-y-1">
<div className="flex items-center justify-between text-xs">
<span className="text-gray-500 flex items-center gap-1">
<TrendingUp className="w-3 h-3" />
XP Progress
</span>
<span className="text-amber-400">{xp} / {xpNeeded}</span>
</div>
<Progress
value={xpProgress}
className="h-2 bg-gray-800"
/>
<div className="text-xs text-gray-500">
{isMaxLevel ? 'Max Level' : `${xpNeeded - xp} XP to Level ${level + 1}`}
</div>
</div>
)}
{/* Max Level Indicator */}
{isMaxLevel && (
<div className="text-xs text-amber-400 text-center font-semibold">
MAX LEVEL
</div>
)}
{/* Capabilities */}
<div className="space-y-1">
<div className="text-xs text-gray-500">Capabilities</div>
@@ -156,14 +197,13 @@ export function AttunementsTab({ store }: AttunementsTabProps) {
<Badge key={cap} variant="outline" className="text-xs">
{cap === 'enchanting' && '✨ Enchanting'}
{cap === 'disenchanting' && '🔄 Disenchant'}
{cap === 'scrollCrafting' && '📜 Scrolls'}
{cap === 'pacts' && '🤝 Pacts'}
{cap === 'guardianPowers' && '💜 Guardian Powers'}
{cap === 'elementalMastery' && '🌟 Elem. Mastery'}
{cap === 'golemCrafting' && '🗿 Golems'}
{cap === 'gearCrafting' && '⚒️ Gear'}
{cap === 'earthShaping' && '⛰️ Earth Shaping'}
{!['enchanting', 'disenchanting', 'scrollCrafting', 'pacts', 'guardianPowers',
{!['enchanting', 'disenchanting', 'pacts', 'guardianPowers',
'elementalMastery', 'golemCrafting', 'gearCrafting', 'earthShaping'].includes(cap) && cap}
</Badge>
))}
@@ -176,13 +216,6 @@ export function AttunementsTab({ store }: AttunementsTabProps) {
🔒 {def.unlockCondition}
</div>
)}
{/* Level for unlocked attunements */}
{isUnlocked && state && (
<div className="text-xs text-gray-400">
Level {state.level} {state.experience} XP
</div>
)}
</CardContent>
</Card>
);