From a1b15cea74d8eda44e4dd2c8aa21ac7c164ab343 Mon Sep 17 00:00:00 2001 From: zhipu Date: Fri, 27 Mar 2026 18:51:13 +0000 Subject: [PATCH] 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 --- src/app/page.tsx | 14 +- src/components/game/ManaDisplay.tsx | 62 +++- src/components/game/tabs/AttunementsTab.tsx | 65 +++- src/components/game/tabs/DebugTab.tsx | 380 ++++++++++++++++++++ src/components/game/tabs/SpireTab.tsx | 5 +- src/components/game/tabs/index.ts | 1 + src/lib/game/data/attunements.ts | 33 +- 7 files changed, 531 insertions(+), 29 deletions(-) create mode 100644 src/components/game/tabs/DebugTab.tsx diff --git a/src/app/page.tsx b/src/app/page.tsx index bfa6dbd..3122dd2 100755 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -13,8 +13,8 @@ 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 } from '@/components/game/tabs'; -import { ComboMeter, ActionButtons, CalendarDisplay, ManaDisplay, TimeDisplay } from '@/components/game'; +import { CraftingTab, SpireTab, SpellsTab, LabTab, SkillsTab, StatsTab, EquipmentTab, AttunementsTab, DebugTab } from '@/components/game/tabs'; +import { ActionButtons, CalendarDisplay, ManaDisplay, TimeDisplay } from '@/components/game'; import { LootInventoryDisplay } from '@/components/game/LootInventory'; import { AchievementsDisplay } from '@/components/game/AchievementsDisplay'; @@ -166,8 +166,6 @@ export default function ManaLoopGame() { isPaused={store.isPaused} togglePause={store.togglePause} /> - - @@ -181,9 +179,12 @@ export default function ManaLoopGame() { rawMana={store.rawMana} maxMana={maxMana} effectiveRegen={effectiveRegen} + meditationMultiplier={meditationMultiplier} + clickMana={clickMana} isGathering={isGathering} onGatherStart={handleGatherStart} onGatherEnd={handleGatherEnd} + elements={store.elements} /> {/* Action Buttons */} @@ -238,6 +239,7 @@ export default function ManaLoopGame() { 🔧 Craft 🔬 Lab 📊 Stats + 🔧 Debug 📖 Grimoire @@ -288,6 +290,10 @@ export default function ManaLoopGame() { {renderGrimoireTab()} + + + + diff --git a/src/components/game/ManaDisplay.tsx b/src/components/game/ManaDisplay.tsx index 280049d..ed0dc67 100644 --- a/src/components/game/ManaDisplay.tsx +++ b/src/components/game/ManaDisplay.tsx @@ -3,8 +3,10 @@ import { Button } from '@/components/ui/button'; import { Progress } from '@/components/ui/progress'; import { Card, CardContent } from '@/components/ui/card'; -import { Zap } from 'lucide-react'; +import { Zap, ChevronDown, ChevronUp } from 'lucide-react'; import { fmt, fmtDec } from '@/lib/game/store'; +import { ELEMENTS } from '@/lib/game/constants'; +import { useState } from 'react'; interface ManaDisplayProps { rawMana: number; @@ -15,6 +17,7 @@ interface ManaDisplayProps { isGathering: boolean; onGatherStart: () => void; onGatherEnd: () => void; + elements: Record; } export function ManaDisplay({ @@ -26,10 +29,19 @@ export function ManaDisplay({ isGathering, onGatherStart, onGatherEnd, + elements, }: ManaDisplayProps) { + const [expanded, setExpanded] = useState(true); + + // Get unlocked elements sorted by current amount + const unlockedElements = Object.entries(elements) + .filter(([, state]) => state.unlocked) + .sort((a, b) => b[1].current - a[1].current); + return ( + {/* Raw Mana - Main Display */}
{fmt(rawMana)} @@ -57,6 +69,54 @@ export function ManaDisplay({ Gather +{clickMana} Mana {isGathering && (Holding...)} + + {/* Elemental Mana Pools */} + {unlockedElements.length > 0 && ( +
+ + + {expanded && ( +
+ {unlockedElements.map(([id, state]) => { + const elem = ELEMENTS[id]; + if (!elem) return null; + + return ( +
+
+ {elem.sym} + + {elem.name} + +
+
+
+
+
+ {fmt(state.current)}/{fmt(state.max)} +
+
+ ); + })} +
+ )} +
+ )} ); diff --git a/src/components/game/tabs/AttunementsTab.tsx b/src/components/game/tabs/AttunementsTab.tsx index 6797495..5530ab9 100644 --- a/src/components/game/tabs/AttunementsTab.tsx +++ b/src/components/game/tabs/AttunementsTab.tsx @@ -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) {

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.

@@ -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 ( - Active + Lv.{level} )}
@@ -134,20 +144,51 @@ export function AttunementsTab({ store }: AttunementsTabProps) { )}
- {/* Stats */} + {/* Stats with level scaling */}
Raw Regen
-
+{def.rawManaRegen}/hr
+
+ +{scaledRegen.toFixed(2)}/hr + {level > 1 && ({((levelMult - 1) * 100).toFixed(0)}% bonus)} +
Conversion
- {def.conversionRate > 0 ? `${def.conversionRate}/hr` : '—'} + {scaledConversion > 0 ? `${scaledConversion.toFixed(2)}/hr` : '—'} + {level > 1 && scaledConversion > 0 && ({((levelMult - 1) * 100).toFixed(0)}% bonus)}
+ {/* XP Progress Bar */} + {isUnlocked && state && !isMaxLevel && ( +
+
+ + + XP Progress + + {xp} / {xpNeeded} +
+ +
+ {isMaxLevel ? 'Max Level' : `${xpNeeded - xp} XP to Level ${level + 1}`} +
+
+ )} + + {/* Max Level Indicator */} + {isMaxLevel && ( +
+ ✨ MAX LEVEL ✨ +
+ )} + {/* Capabilities */}
Capabilities
@@ -156,14 +197,13 @@ export function AttunementsTab({ store }: AttunementsTabProps) { {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} ))} @@ -176,13 +216,6 @@ export function AttunementsTab({ store }: AttunementsTabProps) { 🔒 {def.unlockCondition}
)} - - {/* Level for unlocked attunements */} - {isUnlocked && state && ( -
- Level {state.level} • {state.experience} XP -
- )} ); diff --git a/src/components/game/tabs/DebugTab.tsx b/src/components/game/tabs/DebugTab.tsx new file mode 100644 index 0000000..9856402 --- /dev/null +++ b/src/components/game/tabs/DebugTab.tsx @@ -0,0 +1,380 @@ +'use client'; + +import { useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Separator } from '@/components/ui/separator'; +import { + RotateCcw, Bug, Plus, Minus, Lock, Unlock, Zap, + Clock, Star, AlertTriangle, Sparkles, Settings +} from 'lucide-react'; +import type { GameStore } from '@/lib/game/types'; +import { ATTUNEMENTS_DEF } from '@/lib/game/data/attunements'; +import { ELEMENTS } from '@/lib/game/constants'; +import { fmt } from '@/lib/game/store'; + +interface DebugTabProps { + store: GameStore; +} + +export function DebugTab({ store }: DebugTabProps) { + const [confirmReset, setConfirmReset] = useState(false); + + const handleReset = () => { + if (confirmReset) { + store.resetGame(); + setConfirmReset(false); + } else { + setConfirmReset(true); + setTimeout(() => setConfirmReset(false), 3000); + } + }; + + const handleAddMana = (amount: number) => { + // Use gatherMana multiple times to add mana + for (let i = 0; i < amount; i++) { + store.gatherMana(); + } + }; + + const handleUnlockAttunement = (id: string) => { + // Debug action to unlock attunements + if (store.debugUnlockAttunement) { + store.debugUnlockAttunement(id); + } + }; + + const handleUnlockElement = (element: string) => { + store.unlockElement(element); + }; + + const handleAddElementalMana = (element: string, amount: number) => { + const elem = store.elements[element]; + if (elem?.unlocked) { + // Add directly to element pool - need to implement in store + if (store.debugAddElementalMana) { + store.debugAddElementalMana(element, amount); + } + } + }; + + const handleSetTime = (day: number, hour: number) => { + if (store.debugSetTime) { + store.debugSetTime(day, hour); + } + }; + + const handleAddAttunementXP = (id: string, amount: number) => { + if (store.debugAddAttunementXP) { + store.debugAddAttunementXP(id, amount); + } + }; + + return ( +
+ {/* Warning Banner */} + + +
+ + Debug Mode +
+

+ These tools are for development and testing. Using them may break game balance or save data. +

+
+
+ +
+ {/* Game Reset */} + + + + + Game Reset + + + +

+ Reset all game progress and start fresh. This cannot be undone. +

+ +
+
+ + {/* Mana Debug */} + + + + + Mana Debug + + + +
+ Current: {fmt(store.rawMana)} / {fmt(store.getMaxMana())} +
+
+ + + + +
+ +
Fill to max:
+ +
+
+ + {/* Time Control */} + + + + + Time Control + + + +
+ Current: Day {store.day}, Hour {store.hour} +
+
+ + + + +
+ +
+ +
+
+
+ + {/* Attunement Unlock */} + + + + + Attunements + + + + {Object.entries(ATTUNEMENTS_DEF).map(([id, def]) => { + const isActive = store.attunements?.[id]?.active; + const level = store.attunements?.[id]?.level || 1; + const xp = store.attunements?.[id]?.experience || 0; + + return ( +
+
+ {def.icon} +
+
{def.name}
+ {isActive && ( +
Lv.{level} • {xp} XP
+ )} +
+
+
+ {!isActive && ( + + )} + {isActive && ( + <> + + + + )} +
+
+ ); + })} +
+
+ + {/* Element Unlock */} + + + + + Elemental Mana + + + +
+ {Object.entries(ELEMENTS).map(([id, def]) => { + const elem = store.elements[id]; + const isUnlocked = elem?.unlocked; + + return ( +
+
+ {def.sym} + {!isUnlocked && ( + + )} +
+
{def.name}
+ {isUnlocked && ( +
+ {elem.current.toFixed(0)}/{elem.max} +
+ )} + {isUnlocked && ( + + )} +
+ ); + })} +
+
+
+ + {/* Skills Debug */} + + + + + Quick Actions + + + +
+ + + +
+
+
+
+
+ ); +} diff --git a/src/components/game/tabs/SpireTab.tsx b/src/components/game/tabs/SpireTab.tsx index 595fe09..e1950e2 100755 --- a/src/components/game/tabs/SpireTab.tsx +++ b/src/components/game/tabs/SpireTab.tsx @@ -13,7 +13,7 @@ import { ELEMENTS, GUARDIANS, SPELLS_DEF, SKILLS_DEF } from '@/lib/game/constant import { fmt, fmtDec, getFloorElement, canAffordSpellCost } from '@/lib/game/store'; import { getActiveEquipmentSpells, getTotalDPS } from '@/lib/game/computed-stats'; import { formatSpellCost, getSpellCostColor, formatStudyTime } from '@/lib/game/formatting'; -import { ComboMeter, CraftingProgress, StudyProgress } from '@/components/game'; +import { CraftingProgress, StudyProgress } from '@/components/game'; import { getUnifiedEffects } from '@/lib/game/effects'; interface SpireTabProps { @@ -200,9 +200,6 @@ export function SpireTab({ store }: SpireTabProps) { - {/* Combo Meter - Shows when climbing or has active combo */} - - {/* Current Study (if any) */} {store.currentStudyTarget && ( diff --git a/src/components/game/tabs/index.ts b/src/components/game/tabs/index.ts index 02e6281..6315760 100644 --- a/src/components/game/tabs/index.ts +++ b/src/components/game/tabs/index.ts @@ -9,3 +9,4 @@ export { SkillsTab } from './SkillsTab'; export { StatsTab } from './StatsTab'; export { EquipmentTab } from './EquipmentTab'; export { AttunementsTab } from './AttunementsTab'; +export { DebugTab } from './DebugTab'; diff --git a/src/lib/game/data/attunements.ts b/src/lib/game/data/attunements.ts index fca00c1..ce38e20 100644 --- a/src/lib/game/data/attunements.ts +++ b/src/lib/game/data/attunements.ts @@ -31,7 +31,7 @@ export const ATTUNEMENTS_DEF: Record = { rawManaRegen: 0.5, conversionRate: 0.2, // Converts 0.2 raw mana to transference per hour unlocked: true, // Starting attunement - capabilities: ['enchanting', 'disenchanting', 'scrollCrafting'], + capabilities: ['enchanting', 'disenchanting'], skillCategories: ['enchant', 'effectResearch'], }, @@ -88,13 +88,38 @@ export function getUnlockedAttunements(attunements: Record): number { return Object.entries(attunements) - .filter(([id, state]) => state.active) - .reduce((total, [id]) => total + (ATTUNEMENTS_DEF[id]?.rawManaRegen || 0), 0); + .filter(([, state]) => state.active) + .reduce((total, [id, state]) => { + const def = ATTUNEMENTS_DEF[id]; + if (!def) return total; + // Exponential scaling: base * (1.5 ^ (level - 1)) + const levelMult = Math.pow(1.5, (state.level || 1) - 1); + return total + def.rawManaRegen * levelMult; + }, 0); } +// Get conversion rate with level scaling +export function getAttunementConversionRate(attunementId: string, level: number): number { + const def = ATTUNEMENTS_DEF[attunementId]; + if (!def || def.conversionRate <= 0) return 0; + // Exponential scaling: base * (1.5 ^ (level - 1)) + return def.conversionRate * Math.pow(1.5, (level || 1) - 1); +} + +// XP required for attunement level +export function getAttunementXPForLevel(level: number): number { + // Level 2: 100 XP, Level 3: 300 XP, Level 4: 900 XP, etc. + // Exponential: 100 * (3 ^ (level - 2)) + if (level <= 1) return 0; + return Math.floor(100 * Math.pow(3, level - 2)); +} + +// Max attunement level +export const MAX_ATTUNEMENT_LEVEL = 10; + // Helper function to get mana types from active attunements and pacts export function getAttunementManaTypes( attunements: Record,