refactor: Replace natural-regen disciplines with mana conversion speed disciplines
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m25s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m25s
- Add conversionRate + sourceManaTypes fields to DisciplineDefinition
- Rewrite elemental-regen.ts: 8 base disciplines now convert raw→element
- Rewrite elemental-regen-advanced.ts: 6 composite/exotic disciplines with proper source recipes
- Update discipline-effects.ts: produce conversion entries instead of regen bonuses
- Update gameStore.ts tick: drain source mana types, add to target element
- Update discipline-slice.ts: gate activation on source mana type access
- Update discipline-math.ts: resolve mana type IDs to 'X mana' display names
- Update DisciplinesTab.tsx: show conversion info, source requirements, and lock state
- Update DisciplineDebugSection.tsx: pass elements to activate()
- Update effects.ts: remove regen_{element} merge (no longer produced)
This commit is contained in:
@@ -12,6 +12,7 @@ import { fabricatorDisciplines } from '@/lib/game/data/disciplines/fabricator';
|
||||
import { invokerDisciplines } from '@/lib/game/data/disciplines/invoker';
|
||||
import { calculateStatBonus, calculateManaDrain, checkDisciplinePrerequisites } from '@/lib/game/utils/discipline-math';
|
||||
import { ALL_DISCIPLINES } from '@/lib/game/data/disciplines';
|
||||
import { useManaStore } from '@/lib/game/stores/manaStore';
|
||||
import clsx from 'clsx';
|
||||
|
||||
// ─── Attunement Tabs ─────────────────────────────────────────────────────────
|
||||
@@ -25,8 +26,8 @@ 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: 'elemental-regen', label: 'Elemental Flow', items: elementalRegenDisciplines },
|
||||
{ key: 'elemental-regen-advanced', label: 'Advanced Flow', items: elementalRegenAdvancedDisciplines },
|
||||
{ key: 'enchanter', label: 'Enchanter', items: enchanterDisciplines },
|
||||
{ key: 'fabricator', label: 'Fabricator', items: fabricatorDisciplines },
|
||||
{ key: 'invoker', label: 'Invoker', items: invokerDisciplines },
|
||||
@@ -50,6 +51,8 @@ export interface DisciplineCardDefinition {
|
||||
drainBase: number;
|
||||
difficultyFactor: number;
|
||||
scalingFactor: number;
|
||||
sourceManaTypes?: ManaType[];
|
||||
conversionRate?: number;
|
||||
}
|
||||
|
||||
export interface DisciplineCardRuntime {
|
||||
@@ -58,6 +61,7 @@ export interface DisciplineCardRuntime {
|
||||
concurrentLimit: number;
|
||||
isLocked: boolean;
|
||||
missingPrereqs: string[];
|
||||
missingSourceMana: string[];
|
||||
}
|
||||
|
||||
export interface DisciplineCardCallbacks {
|
||||
@@ -77,7 +81,7 @@ const DisciplineCard: React.FC<DisciplineCardProps> = ({ definition, runtime, ca
|
||||
id, name, description, manaType, baseCost, perkThresholds, perkValues, perkTypes,
|
||||
statBonusLabel, baseValue, drainBase, difficultyFactor, scalingFactor,
|
||||
} = definition;
|
||||
const { xp, paused: isPaused, concurrentLimit, isLocked, missingPrereqs } = runtime;
|
||||
const { xp, paused: isPaused, concurrentLimit, isLocked, missingPrereqs, missingSourceMana } = runtime;
|
||||
const { onToggle } = callbacks;
|
||||
|
||||
const displayXp = xp;
|
||||
@@ -91,6 +95,8 @@ const DisciplineCard: React.FC<DisciplineCardProps> = ({ definition, runtime, ca
|
||||
const manaIcon = elementDef?.sym ?? '✦';
|
||||
const manaName = elementDef?.name ?? manaType;
|
||||
|
||||
const effectiveIsLocked = isLocked || missingSourceMana.length > 0;
|
||||
|
||||
const unlockedPerks = perkTypes?.reduce<string[]>((acc, typ, idx) => {
|
||||
const threshold = perkThresholds?.[idx];
|
||||
if (threshold === undefined) return acc;
|
||||
@@ -109,7 +115,7 @@ const DisciplineCard: React.FC<DisciplineCardProps> = ({ definition, runtime, ca
|
||||
};
|
||||
|
||||
return (
|
||||
<div key={id} className={clsx('border rounded-lg p-4 shadow-sm space-y-3', isLocked && 'opacity-60 border-gray-600')}>
|
||||
<div key={id} className={clsx('border rounded-lg p-4 shadow-sm space-y-3', effectiveIsLocked && 'opacity-60 border-gray-600')}>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<h3 className="text-lg font-medium">{name}</h3>
|
||||
<span
|
||||
@@ -149,6 +155,12 @@ const DisciplineCard: React.FC<DisciplineCardProps> = ({ definition, runtime, ca
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{definition.conversionRate != null && definition.sourceManaTypes && (
|
||||
<div className="mt-2 text-xs text-gray-400">
|
||||
<strong>Converts:</strong> {definition.sourceManaTypes.map(s => s === 'raw' ? 'raw' : ELEMENTS[s]?.name ?? s).join(' + ')} → {manaName}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-2 text-sm">
|
||||
<strong>Stat Bonus:</strong> {activeStatBonus.toFixed(2)} on {statBonusLabel}
|
||||
</div>
|
||||
@@ -166,16 +178,16 @@ const DisciplineCard: React.FC<DisciplineCardProps> = ({ definition, runtime, ca
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{isLocked && missingPrereqs.length > 0 && (
|
||||
{effectiveIsLocked && (missingPrereqs.length > 0 || missingSourceMana.length > 0) && (
|
||||
<div className="mt-2 text-xs text-red-400">
|
||||
<strong>Requires:</strong> {missingPrereqs.join(', ')}
|
||||
<strong>Requires:</strong> {[...missingPrereqs, ...missingSourceMana].join(', ')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-4 flex justify-end">
|
||||
<button
|
||||
onClick={toggleAction}
|
||||
disabled={isLocked}
|
||||
disabled={effectiveIsLocked}
|
||||
className={clsx(
|
||||
'rounded px-3 py-1 text-sm font-medium',
|
||||
isLocked
|
||||
@@ -185,7 +197,7 @@ const DisciplineCard: React.FC<DisciplineCardProps> = ({ definition, runtime, ca
|
||||
: 'bg-blue-600 text-white hover:bg-blue-500',
|
||||
)}
|
||||
>
|
||||
{isLocked ? 'Locked' : isPaused ? 'Start Practicing' : 'Stop Practicing'}
|
||||
{effectiveIsLocked ? 'Locked' : isPaused ? 'Start Practicing' : 'Stop Practicing'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -203,13 +215,15 @@ export const DisciplinesTab: React.FC = () => {
|
||||
|
||||
const [activeAttunement, setActiveAttunement] = useState<string>('base');
|
||||
|
||||
const elements = useManaStore((s) => s.elements);
|
||||
|
||||
const handleToggle = useCallback((id: string, paused: boolean) => {
|
||||
if (paused) {
|
||||
activate(id);
|
||||
activate(id, { elements });
|
||||
} else {
|
||||
deactivate(id);
|
||||
}
|
||||
}, [activate, deactivate]);
|
||||
}, [activate, deactivate, elements]);
|
||||
|
||||
const activeTab = ATTUNEMENT_TABS.find((t) => t.key === activeAttunement);
|
||||
|
||||
@@ -254,6 +268,8 @@ export const DisciplinesTab: React.FC = () => {
|
||||
statBonus: disc.statBonus.stat,
|
||||
statBonusLabel: disc.statBonus.label,
|
||||
requires: disc.requires,
|
||||
sourceManaTypes: disc.sourceManaTypes,
|
||||
conversionRate: disc.conversionRate,
|
||||
baseValue: disc.statBonus.baseValue,
|
||||
drainBase: disc.drainBase,
|
||||
difficultyFactor: disc.difficultyFactor,
|
||||
@@ -265,6 +281,11 @@ export const DisciplinesTab: React.FC = () => {
|
||||
concurrentLimit,
|
||||
isLocked: !prereqCheck.canProceed,
|
||||
missingPrereqs: prereqCheck.missingPrereqs,
|
||||
missingSourceMana: disc.sourceManaTypes
|
||||
? disc.sourceManaTypes.filter(
|
||||
(src) => src !== 'raw' && (!elements[src] || !elements[src].unlocked),
|
||||
).map((src) => `${src} mana`)
|
||||
: [],
|
||||
}}
|
||||
callbacks={{
|
||||
onToggle: handleToggle,
|
||||
|
||||
Reference in New Issue
Block a user