From 02600754e782fb348a458836957ec838956def89 Mon Sep 17 00:00:00 2001 From: n8n-gitea Date: Tue, 26 May 2026 20:58:55 +0200 Subject: [PATCH] feat: Add Mana Circulation discipline with regen multiplier and meditation cap perks --- docs/circular-deps.txt | 2 +- docs/dependency-graph.json | 2 +- src/app/components/LeftPanel.tsx | 4 ++- src/app/page.tsx | 2 +- src/lib/game/data/disciplines/base.ts | 32 ++++++++++++++++++++++ src/lib/game/effects/discipline-effects.ts | 23 ++++++++++++++-- src/lib/game/hooks/useGameDerived.ts | 6 ++-- src/lib/game/stores/gameHooks.ts | 2 +- src/lib/game/stores/gameStore.ts | 4 +-- src/lib/game/types/disciplines.ts | 5 ++++ src/lib/game/utils/mana-utils.ts | 23 ++++++++++++---- 11 files changed, 88 insertions(+), 17 deletions(-) diff --git a/docs/circular-deps.txt b/docs/circular-deps.txt index 4626e97..495a4c0 100644 --- a/docs/circular-deps.txt +++ b/docs/circular-deps.txt @@ -1,5 +1,5 @@ # Circular Dependencies -Generated: 2026-05-26T16:39:59.755Z +Generated: 2026-05-26T18:40:16.686Z Found: 7 circular chain(s) — these MUST be fixed before modifying involved files. 1. Processed 135 files (1.5s) (2 warnings) diff --git a/docs/dependency-graph.json b/docs/dependency-graph.json index 9c7e120..1158fb2 100644 --- a/docs/dependency-graph.json +++ b/docs/dependency-graph.json @@ -1,6 +1,6 @@ { "_meta": { - "generated": "2026-05-26T16:39:58.077Z", + "generated": "2026-05-26T18:40:14.879Z", "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." }, diff --git a/src/app/components/LeftPanel.tsx b/src/app/components/LeftPanel.tsx index 15bc323..36cd009 100644 --- a/src/app/components/LeftPanel.tsx +++ b/src/app/components/LeftPanel.tsx @@ -13,6 +13,7 @@ import { useGameStore, useManaStore, useCombatStore, useCraftingStore, usePresti import { getUnifiedEffects } from '@/lib/game/effects'; import { getMeditationBonus, getIncursionStrength } from '@/lib/game/stores'; import { computeTotalMaxMana, computeTotalRegen, computeTotalClickMana } from '@/lib/game/effects'; +import { computeDisciplineEffects } from '@/lib/game/effects/discipline-effects'; export function LeftPanel() { const [isGathering, setIsGathering] = useState(false); @@ -53,10 +54,11 @@ export function LeftPanel() { }, [isGathering, gatherMana]); const upgradeEffects = getUnifiedEffects({ skillUpgrades: {}, skillTiers: {}, equippedInstances, equipmentInstances }); + const disciplineEffects = computeDisciplineEffects(); const maxMana = computeTotalMaxMana({ skills: {}, prestigeUpgrades, skillUpgrades: {}, skillTiers: {}, equippedInstances, equipmentInstances }, upgradeEffects); const baseRegen = computeTotalRegen({ skills: {}, prestigeUpgrades, skillUpgrades: {}, skillTiers: {}, equippedInstances, equipmentInstances }, upgradeEffects); const clickMana = computeTotalClickMana({ skills: {}, skillUpgrades: {}, skillTiers: {}, equippedInstances, equipmentInstances }, upgradeEffects); - const meditationMultiplier = getMeditationBonus(meditateTicks, {}, upgradeEffects.meditationEfficiency); + const meditationMultiplier = getMeditationBonus(meditateTicks, {}, upgradeEffects.meditationEfficiency, disciplineEffects.meditationCapBonus); const incursionStrength = getIncursionStrength(useGameStore((s) => s.day), useGameStore((s) => s.hour)); const effectiveRegen = baseRegen * (1 - incursionStrength) * meditationMultiplier; diff --git a/src/app/page.tsx b/src/app/page.tsx index de813be..3ca6f36 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -90,7 +90,7 @@ function useGameDerivedStats() { }, upgradeEffects, disciplineEffects); const clickMana = computeClickMana({}, disciplineEffects); - const meditationMultiplier = getMeditationBonus(meditateTicks, {}, upgradeEffects.meditationEfficiency); + const meditationMultiplier = getMeditationBonus(meditateTicks, {}, upgradeEffects.meditationEfficiency, disciplineEffects.meditationCapBonus); const incursionStrength = getIncursionStrength(day, hour); const effectiveRegenWithSpecials = baseRegen * (1 - incursionStrength); diff --git a/src/lib/game/data/disciplines/base.ts b/src/lib/game/data/disciplines/base.ts index 5d72006..653059d 100644 --- a/src/lib/game/data/disciplines/base.ts +++ b/src/lib/game/data/disciplines/base.ts @@ -35,4 +35,36 @@ export const baseDisciplines: DisciplineDefinition[] = [ }, ], }, + { + id: 'mana-circulation', + name: 'Mana Circulation', + attunement: DisciplinesAttunementType.BASE, + manaType: 'raw', + baseCost: 5, + description: + 'Cultivate and circulate raw mana through the body — the foundation upon which all elemental work is built.', + statBonus: { stat: 'regenBonus', baseValue: 0.2, label: 'Raw Mana Regen' }, + difficultyFactor: 100, + scalingFactor: 50, + drainBase: 1, + perks: [ + { + id: 'mana-circulation-regen', + type: 'infinite', + threshold: 100, + value: 100, + description: 'Every 100 XP: +10% raw mana regen', + bonus: { stat: 'regenMultiplier', amount: 0.10 }, + }, + { + id: 'mana-circulation-meditation', + type: 'capped', + threshold: 100, + value: 100, + description: 'Every 100 XP: +0.5 max meditation multiplier (7 tiers, up to +3.5)', + bonus: { stat: 'meditationCapBonus', amount: 0.5 }, + maxTier: 7, + }, + ], + }, ]; \ No newline at end of file diff --git a/src/lib/game/effects/discipline-effects.ts b/src/lib/game/effects/discipline-effects.ts index 1307a2e..ab65a1b 100644 --- a/src/lib/game/effects/discipline-effects.ts +++ b/src/lib/game/effects/discipline-effects.ts @@ -18,15 +18,22 @@ import { const KNOWN_BONUS_STATS = new Set([ 'maxManaBonus', 'regenBonus', + 'regenMultiplier', 'clickManaBonus', 'baseDamageBonus', 'elementCapBonus', + 'meditationCapBonus', ]); export interface DisciplineEffectsResult { bonuses: Record; multipliers: Record; specials: Set; + /** + * Bonus to the meditation multiplier cap from disciplines. + * Each point of meditationCapBonus adds +0.5 to the max meditation multiplier. + */ + meditationCapBonus: number; /** * Conversion entries: for each active discipline with a conversionRate, * maps target mana type → { rate, sourceManaTypes }. @@ -45,9 +52,18 @@ export function computeDisciplineEffects(_state?: DisciplineStoreState): Discipl const bonuses: Record = {}; const multipliers: Record = {}; const specials = new Set(); + let meditationCapBonus = 0; const conversions: Record = {}; function addBonus(stat: string, amount: number) { + if (stat === 'meditationCapBonus') { + meditationCapBonus += amount; + return; + } + if (stat === 'regenMultiplier') { + multipliers[stat] = (multipliers[stat] || 0) + amount; + return; + } bonuses[stat] = (bonuses[stat] || 0) + amount; } @@ -90,7 +106,10 @@ export function computeDisciplineEffects(_state?: DisciplineStoreState): Discipl } } else if (perk.type === 'capped') { if (perk.bonus) { - const tier = calculatePerkTier(disc.xp, perk.threshold, perk.value); + let tier = calculatePerkTier(disc.xp, perk.threshold, perk.value); + if (tier > 0 && perk.maxTier !== undefined) { + tier = Math.min(tier, perk.maxTier); + } if (tier > 0) { addBonus(perk.bonus.stat, tier * perk.bonus.amount); } @@ -101,5 +120,5 @@ export function computeDisciplineEffects(_state?: DisciplineStoreState): Discipl } } - return { bonuses, multipliers, specials, conversions }; + return { bonuses, multipliers, specials, meditationCapBonus, conversions }; } diff --git a/src/lib/game/hooks/useGameDerived.ts b/src/lib/game/hooks/useGameDerived.ts index 364c92d..3982f3c 100644 --- a/src/lib/game/hooks/useGameDerived.ts +++ b/src/lib/game/hooks/useGameDerived.ts @@ -21,6 +21,7 @@ import { computePactMultiplier, computePactInsightMultiplier } from '../utils/pa import { ELEMENTS, SPELLS_DEF, getStudySpeedMultiplier, getStudyCostMultiplier, HOURS_PER_TICK, TICK_MS } from '../constants'; import { getGuardianForFloor } from '../data/guardian-encounters'; import { hasSpecial, SPECIAL_EFFECTS } from '../effects/special-effects'; +import { computeDisciplineEffects } from '../effects/discipline-effects'; /** * Hook for all mana-related derived stats @@ -52,9 +53,10 @@ export function useManaStats() { [] ); + const disciplineEffects = computeDisciplineEffects(); const meditationMultiplier = useMemo( - () => getMeditationBonus(meditateTicks, {}, upgradeEffects.meditationEfficiency), - [meditateTicks, upgradeEffects.meditationEfficiency] + () => getMeditationBonus(meditateTicks, {}, upgradeEffects.meditationEfficiency, disciplineEffects.meditationCapBonus), + [meditateTicks, upgradeEffects.meditationEfficiency, disciplineEffects.meditationCapBonus] ); const incursionStrength = useMemo( diff --git a/src/lib/game/stores/gameHooks.ts b/src/lib/game/stores/gameHooks.ts index 11451d8..8e40923 100644 --- a/src/lib/game/stores/gameHooks.ts +++ b/src/lib/game/stores/gameHooks.ts @@ -75,7 +75,7 @@ export function useManaStats() { const clickMana = computeClickMana({}, disciplineEffects); - const meditationMultiplier = getMeditationBonus(meditateTicks, {}, upgradeEffects.meditationEfficiency); + const meditationMultiplier = getMeditationBonus(meditateTicks, {}, upgradeEffects.meditationEfficiency, disciplineEffects.meditationCapBonus); const incursionStrength = getIncursionStrength(day, hour); const effectiveRegenWithSpecials = baseRegen * (1 - incursionStrength); diff --git a/src/lib/game/stores/gameStore.ts b/src/lib/game/stores/gameStore.ts index 273a99c..04e9101 100644 --- a/src/lib/game/stores/gameStore.ts +++ b/src/lib/game/stores/gameStore.ts @@ -121,7 +121,7 @@ export const useGameStore = create()( { skills: {}, prestigeUpgrades: ctx.prestige.prestigeUpgrades, skillUpgrades: {}, skillTiers: {}, attunements: {} }, undefined, disciplineEffects, - ); + ) * (1 + (disciplineEffects.multipliers.regenMultiplier || 0)); // Time progression let hour = ctx.game.hour + HOURS_PER_TICK; @@ -172,7 +172,7 @@ export const useGameStore = create()( if (ctx.combat.currentAction === 'meditate') { meditateTicks++; - meditationMultiplier = getMeditationBonus(meditateTicks, {}, 1); + meditationMultiplier = getMeditationBonus(meditateTicks, {}, 1, disciplineEffects.meditationCapBonus); } else { meditateTicks = 0; } diff --git a/src/lib/game/types/disciplines.ts b/src/lib/game/types/disciplines.ts index 5bdc8f5..91ca269 100644 --- a/src/lib/game/types/disciplines.ts +++ b/src/lib/game/types/disciplines.ts @@ -26,6 +26,11 @@ export interface DisciplinePerk { description: string; unlocksEffects?: string[]; bonus?: PerkBonus; + /** + * For capped perks: maximum number of tiers. If unset, capped perks + * behave like infinite (no cap on tier count). + */ + maxTier?: number; } // ─── Discipline Definition ──────────────────────────────────────────────────── diff --git a/src/lib/game/utils/mana-utils.ts b/src/lib/game/utils/mana-utils.ts index 0b1bd03..b1384ce 100644 --- a/src/lib/game/utils/mana-utils.ts +++ b/src/lib/game/utils/mana-utils.ts @@ -124,29 +124,40 @@ export function computeClickMana( // ─── Meditation Bonus ───────────────────────────────────────────────────────── -export function getMeditationBonus(meditateTicks: number, skills: Record, meditationEfficiency: number = 1): number { +export function getMeditationBonus( + meditateTicks: number, + skills: Record, + meditationEfficiency: number = 1, + disciplineMeditationCap: number = 0, +): number { const hasMeditation = skills.meditation === 1; const hasDeepTrance = skills.deepTrance === 1; const hasVoidMeditation = skills.voidMeditation === 1; const hours = meditateTicks * HOURS_PER_TICK; - // Base meditation: ramps up over 4 hours to 1.5x + // Determine the hard cap for this meditation session. + // disciplineMeditationCap adds +0.5 per point (e.g. from Mana Circulation discipline). + // Base max is 5.0 (Void Meditation), each discipline bonus adds +0.5. + const maxMultiplier = 5.0 + disciplineMeditationCap; + + // Base meditation: ramps up over 4 hours, capped at 1.5x or discipline cap let bonus = 1 + Math.min(hours / 4, 0.5); + bonus = Math.min(bonus, maxMultiplier); // With Meditation Focus: up to 2.5x after 4 hours if (hasMeditation && hours >= 4) { - bonus = 2.5; + bonus = Math.min(2.5, maxMultiplier); } // With Deep Trance: up to 3.0x after 6 hours if (hasDeepTrance && hours >= 6) { - bonus = 3.0; + bonus = Math.min(3.0, maxMultiplier); } - // With Void Meditation: up to 5.0x after 8 hours + // With Void Meditation: up to maxMultiplier after 8 hours if (hasVoidMeditation && hours >= 8) { - bonus = 5.0; + bonus = maxMultiplier; } // Apply meditation efficiency from upgrades