feat: discipline UI improvements - stat labels, prerequisites, mana type tab
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m23s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m23s
- Add player-friendly label field to statBonus in DisciplineDefinition - Show prerequisite requirements on locked discipline cards - Disable activate button for locked disciplines - Restructure elemental attunement into dedicated 'Mana Types' tab - Add checkDisciplinePrerequisites utility function - Update store to enforce prerequisite checking on activation - Split discipline-prerequisites tests into separate file
This commit is contained in:
@@ -4,12 +4,14 @@ import type { DisciplineDefinition } from '@/lib/game/types/disciplines';
|
||||
import type { ManaType } from '@/lib/game/types/elements';
|
||||
import { ELEMENTS } from '@/lib/game/constants/elements';
|
||||
import { baseDisciplines } from '@/lib/game/data/disciplines/base';
|
||||
import { elementalAttunementDisciplines } from '@/lib/game/data/disciplines/elemental';
|
||||
import { elementalRegenDisciplines } from '@/lib/game/data/disciplines/elemental-regen';
|
||||
import { elementalRegenAdvancedDisciplines } from '@/lib/game/data/disciplines/elemental-regen-advanced';
|
||||
import { enchanterDisciplines } from '@/lib/game/data/disciplines/enchanter';
|
||||
import { fabricatorDisciplines } from '@/lib/game/data/disciplines/fabricator';
|
||||
import { invokerDisciplines } from '@/lib/game/data/disciplines/invoker';
|
||||
import { calculateStatBonus, calculateManaDrain } from '@/lib/game/utils/discipline-math';
|
||||
import { calculateStatBonus, calculateManaDrain, checkDisciplinePrerequisites } from '@/lib/game/utils/discipline-math';
|
||||
import { ALL_DISCIPLINES } from '@/lib/game/data/disciplines';
|
||||
import clsx from 'clsx';
|
||||
|
||||
// ─── Attunement Tabs ─────────────────────────────────────────────────────────
|
||||
@@ -22,6 +24,7 @@ interface AttunementTab {
|
||||
|
||||
const ATTUNEMENT_TABS: AttunementTab[] = [
|
||||
{ key: 'base', label: 'Base', items: baseDisciplines },
|
||||
{ key: 'elements', label: 'Mana Types', items: elementalAttunementDisciplines },
|
||||
{ key: 'elemental-regen', label: 'Elemental Regen', items: elementalRegenDisciplines },
|
||||
{ key: 'elemental-regen-advanced', label: 'Advanced Regen', items: elementalRegenAdvancedDisciplines },
|
||||
{ key: 'enchanter', label: 'Enchanter', items: enchanterDisciplines },
|
||||
@@ -41,6 +44,8 @@ export interface DisciplineCardDefinition {
|
||||
perkValues?: number[];
|
||||
perkTypes?: string[];
|
||||
statBonus: string;
|
||||
statBonusLabel: string;
|
||||
requires?: string[];
|
||||
baseValue: number;
|
||||
drainBase: number;
|
||||
difficultyFactor: number;
|
||||
@@ -51,6 +56,8 @@ export interface DisciplineCardRuntime {
|
||||
xp: number;
|
||||
paused: boolean;
|
||||
concurrentLimit: number;
|
||||
isLocked: boolean;
|
||||
missingPrereqs: string[];
|
||||
}
|
||||
|
||||
export interface DisciplineCardCallbacks {
|
||||
@@ -68,9 +75,9 @@ interface DisciplineCardProps {
|
||||
const DisciplineCard: React.FC<DisciplineCardProps> = ({ definition, runtime, callbacks }) => {
|
||||
const {
|
||||
id, name, description, manaType, baseCost, perkThresholds, perkValues, perkTypes,
|
||||
statBonus, baseValue, drainBase, difficultyFactor, scalingFactor,
|
||||
statBonus, statBonusLabel, baseValue, drainBase, difficultyFactor, scalingFactor,
|
||||
} = definition;
|
||||
const { xp, paused: isPaused, concurrentLimit } = runtime;
|
||||
const { xp, paused: isPaused, concurrentLimit, isLocked, missingPrereqs } = runtime;
|
||||
const { onToggle } = callbacks;
|
||||
|
||||
const displayXp = xp;
|
||||
@@ -102,7 +109,7 @@ const DisciplineCard: React.FC<DisciplineCardProps> = ({ definition, runtime, ca
|
||||
};
|
||||
|
||||
return (
|
||||
<div key={id} className="border rounded-lg p-4 shadow-sm space-y-3">
|
||||
<div key={id} className={clsx('border rounded-lg p-4 shadow-sm space-y-3', isLocked && 'opacity-60 border-gray-600')}>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<h3 className="text-lg font-medium">{name}</h3>
|
||||
<span
|
||||
@@ -143,7 +150,7 @@ const DisciplineCard: React.FC<DisciplineCardProps> = ({ definition, runtime, ca
|
||||
</div>
|
||||
|
||||
<div className="mt-2 text-sm">
|
||||
<strong>Stat Bonus:</strong> {activeStatBonus.toFixed(2)} on {statBonus}
|
||||
<strong>Stat Bonus:</strong> {activeStatBonus.toFixed(2)} on {statBonusLabel}
|
||||
</div>
|
||||
|
||||
<div className="mt-2">
|
||||
@@ -159,17 +166,26 @@ const DisciplineCard: React.FC<DisciplineCardProps> = ({ definition, runtime, ca
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{isLocked && missingPrereqs.length > 0 && (
|
||||
<div className="mt-2 text-xs text-red-400">
|
||||
<strong>Requires:</strong> {missingPrereqs.join(', ')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-4 flex justify-end">
|
||||
<button
|
||||
onClick={toggleAction}
|
||||
disabled={isLocked}
|
||||
className={clsx(
|
||||
'rounded px-3 py-1 text-sm font-medium',
|
||||
isPaused
|
||||
? 'bg-yellow-600 text-white hover:bg-yellow-500'
|
||||
: 'bg-blue-600 text-white hover:bg-blue-500',
|
||||
isLocked
|
||||
? 'bg-gray-600 text-gray-400 cursor-not-allowed'
|
||||
: isPaused
|
||||
? 'bg-yellow-600 text-white hover:bg-yellow-500'
|
||||
: 'bg-blue-600 text-white hover:bg-blue-500',
|
||||
)}
|
||||
>
|
||||
{isPaused ? 'Start Practicing' : 'Stop Practicing'}
|
||||
{isLocked ? 'Locked' : isPaused ? 'Start Practicing' : 'Stop Practicing'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -235,6 +251,7 @@ export const DisciplinesTab: React.FC = () => {
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{activeTab?.items.map((disc) => {
|
||||
const discState = disciplines[disc.id] ?? { xp: 0, paused: true };
|
||||
const prereqCheck = checkDisciplinePrerequisites(disc, disciplines, ALL_DISCIPLINES);
|
||||
return (
|
||||
<DisciplineCard
|
||||
key={disc.id}
|
||||
@@ -248,6 +265,8 @@ export const DisciplinesTab: React.FC = () => {
|
||||
manaType: disc.manaType,
|
||||
baseCost: disc.baseCost,
|
||||
statBonus: disc.statBonus.stat,
|
||||
statBonusLabel: disc.statBonus.label,
|
||||
requires: disc.requires,
|
||||
baseValue: disc.statBonus.baseValue,
|
||||
drainBase: disc.drainBase,
|
||||
difficultyFactor: disc.difficultyFactor,
|
||||
@@ -257,6 +276,8 @@ export const DisciplinesTab: React.FC = () => {
|
||||
xp: discState.xp,
|
||||
paused: discState.paused,
|
||||
concurrentLimit,
|
||||
isLocked: !prereqCheck.canProceed,
|
||||
missingPrereqs: prereqCheck.missingPrereqs,
|
||||
}}
|
||||
callbacks={{
|
||||
onToggle: handleToggle,
|
||||
|
||||
Reference in New Issue
Block a user