diff --git a/docs/circular-deps.txt b/docs/circular-deps.txt index 495a4c0..0aec896 100644 --- a/docs/circular-deps.txt +++ b/docs/circular-deps.txt @@ -1,8 +1,8 @@ # Circular Dependencies -Generated: 2026-05-26T18:40:16.686Z +Generated: 2026-05-26T18:59:01.066Z Found: 7 circular chain(s) — these MUST be fixed before modifying involved files. -1. Processed 135 files (1.5s) (2 warnings) +1. Processed 135 files (1.6s) (2 warnings) 2. 1) effects/discipline-effects.ts > stores/discipline-slice.ts > stores/combatStore.ts > stores/combat-actions.ts 3. 2) utils/floor-utils.ts > utils/room-utils.ts > utils/enemy-utils.ts 4. 3) utils/floor-utils.ts > utils/room-utils.ts diff --git a/docs/dependency-graph.json b/docs/dependency-graph.json index 1158fb2..1ea73ba 100644 --- a/docs/dependency-graph.json +++ b/docs/dependency-graph.json @@ -1,6 +1,6 @@ { "_meta": { - "generated": "2026-05-26T18:40:14.879Z", + "generated": "2026-05-26T18:58:59.230Z", "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." }, @@ -419,6 +419,7 @@ "hooks/useGameDerived.ts": [ "constants.ts", "data/guardian-encounters.ts", + "effects/discipline-effects.ts", "effects/special-effects.ts", "effects/upgrade-effects.ts", "stores/combatStore.ts", diff --git a/src/components/game/tabs/DisciplinesTab.tsx b/src/components/game/tabs/DisciplinesTab.tsx index 446cb91..38f7988 100644 --- a/src/components/game/tabs/DisciplinesTab.tsx +++ b/src/components/game/tabs/DisciplinesTab.tsx @@ -13,6 +13,7 @@ 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 { usePrestigeStore } from '@/lib/game/stores/prestigeStore'; import clsx from 'clsx'; // ─── Attunement Tabs ───────────────────────────────────────────────────────── @@ -216,10 +217,11 @@ export const DisciplinesTab: React.FC = () => { const [activeAttunement, setActiveAttunement] = useState('base'); const elements = useManaStore((s) => s.elements); + const signedPacts = usePrestigeStore((s) => s.signedPacts); const handleToggle = useCallback((id: string, paused: boolean) => { if (paused) { - activate(id, { elements }); + activate(id, { elements, signedPacts }); } else { deactivate(id); } diff --git a/src/lib/game/data/disciplines/invoker.ts b/src/lib/game/data/disciplines/invoker.ts index 7df9954..4548427 100644 --- a/src/lib/game/data/disciplines/invoker.ts +++ b/src/lib/game/data/disciplines/invoker.ts @@ -1,59 +1,82 @@ // ─── Invoker Discipline Files ───────────────────────────────────────────────── // Attunement-focused disciplines for Invoker role +// The Invoker forms pacts with guardians and channels their elemental boons. import { DisciplinesAttunementType } from '../../types/disciplines'; import type { DisciplineDefinition } from '../../types/disciplines'; export const invokerDisciplines: DisciplineDefinition[] = [ { - id: 'spell-casting', - name: 'Spell Casting', + id: 'pact-attunement', + name: 'Pact Attunement', attunement: DisciplinesAttunementType.INVOKER, manaType: 'light', - baseCost: 10, - description: 'Improve spell power and effectiveness.', - statBonus: { stat: 'baseDamageBonus', baseValue: 6, label: 'Base Damage' }, - difficultyFactor: 130, - scalingFactor: 65, - drainBase: 3, + baseCost: 12, + description: + 'Deepen your bond with guardian spirits. Reduces pact signing time and amplifies pact power.', + statBonus: { stat: 'pactAffinityBonus', baseValue: 0.05, label: 'Pact Affinity' }, + difficultyFactor: 150, + scalingFactor: 80, + drainBase: 4, + requires: ['signed_pact'], perks: [ { - id: 'spell-1', + id: 'pact-affinity-scaling', type: 'once', - threshold: 200, + threshold: 100, value: 0, - description: '+10 Base Damage', - bonus: { stat: 'baseDamageBonus', amount: 10 }, + description: 'Unlock pact affinity scaling — pact bonuses grow with XP.', }, { - id: 'spell-2', + id: 'pact-affinity-infinite', type: 'infinite', - threshold: 400, - value: 30, - description: 'Every 300 XP: +5 Base Damage', - bonus: { stat: 'baseDamageBonus', amount: 5 }, + threshold: 200, + value: 100, + description: 'Every 100 XP: +5% pact affinity', + bonus: { stat: 'pactAffinityBonus', amount: 0.05 }, + }, + { + id: 'pact-power-boost', + type: 'capped', + threshold: 500, + value: 200, + maxTier: 5, + description: 'Every 200 XP: +3% guardian pact boon strength (max +15%)', + bonus: { stat: 'guardianBoonMultiplier', amount: 0.03 }, }, ], }, { - id: 'void-manipulation', - name: 'Void Manipulation', + id: 'guardians-boon', + name: "Guardian's Boon", attunement: DisciplinesAttunementType.INVOKER, - manaType: 'void', - baseCost: 15, - description: 'Master the exotic void mana for devastating effects.', - statBonus: { stat: 'baseDamageMultiplier', baseValue: 0.15, label: 'Base Damage Multiplier' }, + manaType: 'dark', + baseCost: 18, + description: + 'Channel the unique blessings of every guardian you have bonded with. Amplifies all guardian unique perks.', + statBonus: { stat: 'guardianBoonMultiplier', baseValue: 0.10, label: 'Guardian Boon Power' }, difficultyFactor: 200, scalingFactor: 100, - drainBase: 7, + drainBase: 6, + requires: ['signed_pact'], perks: [ { - id: 'void-1', + id: 'boon-1', type: 'once', - threshold: 300, + threshold: 100, value: 0, - description: 'Unlock void damage multiplier', + description: 'Active guardian boons gain +10% effectiveness', + bonus: { stat: 'guardianBoonMultiplier', amount: 0.10 }, + }, + { + id: 'boon-2', + type: 'capped', + threshold: 200, + value: 350, + maxTier: 5, + description: 'Every 350 XP: +5% guardian boon effectiveness (max +25%)', + bonus: { stat: 'guardianBoonMultiplier', amount: 0.05 }, }, ], }, -]; \ No newline at end of file +]; diff --git a/src/lib/game/effects.ts b/src/lib/game/effects.ts index 64d1378..b7db66c 100644 --- a/src/lib/game/effects.ts +++ b/src/lib/game/effects.ts @@ -73,6 +73,18 @@ export interface UnifiedEffects extends ComputedEffects { disciplineBonuses: Record; disciplineMultipliers: Record; disciplineSpecials: Set; + /** + * Total pact affinity bonus from disciplines and prestige upgrades. + * Reduces pact signing time: effectiveTime = baseTime * (1 - pactAffinityBonus). + * Capped at 0.9 to prevent zero/negative times. + */ + pactAffinityBonus: number; + /** + * Total guardian boon multiplier from disciplines. + * Scales the effectiveness of all guardian unique perks. + * 1.0 = base, 1.15 = +15%, etc. + */ + guardianBoonMultiplier: number; } export function computeAllEffects( @@ -128,6 +140,8 @@ export function computeAllEffects( disciplineBonuses: disciplineEffects.bonuses, disciplineMultipliers: disciplineEffects.multipliers, disciplineSpecials: disciplineEffects.specials, + pactAffinityBonus: Math.min(0.9, disciplineEffects.bonuses.pactAffinityBonus || 0), + guardianBoonMultiplier: 1 + (disciplineEffects.bonuses.guardianBoonMultiplier || 0), }; if (equipmentEffects.bonuses.critChance) { diff --git a/src/lib/game/effects/discipline-effects.ts b/src/lib/game/effects/discipline-effects.ts index ab65a1b..3c44252 100644 --- a/src/lib/game/effects/discipline-effects.ts +++ b/src/lib/game/effects/discipline-effects.ts @@ -23,6 +23,8 @@ const KNOWN_BONUS_STATS = new Set([ 'baseDamageBonus', 'elementCapBonus', 'meditationCapBonus', + 'pactAffinityBonus', + 'guardianBoonMultiplier', ]); export interface DisciplineEffectsResult { diff --git a/src/lib/game/stores/discipline-slice.ts b/src/lib/game/stores/discipline-slice.ts index 74bc3e9..996772f 100644 --- a/src/lib/game/stores/discipline-slice.ts +++ b/src/lib/game/stores/discipline-slice.ts @@ -46,7 +46,7 @@ export interface DisciplineStoreState { } export interface DisciplineStoreActions { - activate: (id: string, gameState?: { elements?: Record }) => void; + activate: (id: string, gameState?: { elements?: Record; signedPacts?: number[] }) => void; deactivate: (id: string) => void; processTick: (mana: { rawMana: number; elements: Record }) => { rawMana: number; @@ -90,6 +90,12 @@ export const useDisciplineStore = create()( if (nonPaused >= s.concurrentLimit) return s; if (!canProceedDiscipline(def, existing, gameState)) return s; + // Invoker disciplines require at least one signed guardian pact + if (def.attunement === 'invoker') { + const signedPacts = gameState?.signedPacts || []; + if (signedPacts.length === 0) return s; + } + // Check discipline prerequisites (requires field → discipline XP) const prereqCheck = checkDisciplinePrerequisites(def, s.disciplines, ALL_DISCIPLINES); if (!prereqCheck.canProceed) return s; diff --git a/src/lib/game/stores/gameStore.ts b/src/lib/game/stores/gameStore.ts index 04e9101..e87cd8e 100644 --- a/src/lib/game/stores/gameStore.ts +++ b/src/lib/game/stores/gameStore.ts @@ -223,11 +223,11 @@ export const useGameStore = create()( if (ctx.prestige.pactRitualFloor !== null) { const guardian = getGuardianForFloor(ctx.prestige.pactRitualFloor); if (guardian) { - const pactAffinityBonus = 1 - (ctx.prestige.prestigeUpgrades.pactAffinity || 0) * 0.1; - const requiredTime = guardian.pactTime * pactAffinityBonus; - const newProgress = ctx.prestige.pactRitualProgress + HOURS_PER_TICK; + const pactAffinity = Math.min(0.9, + (ctx.prestige.prestigeUpgrades.pactAffinity || 0) * 0.1 + (disciplineEffects.bonuses.pactAffinityBonus || 0)); + const requiredTime = guardian.pactTime * (1 - pactAffinity); - if (newProgress >= requiredTime) { + if (ctx.prestige.pactRitualProgress + HOURS_PER_TICK >= requiredTime) { addLog(`📜 Pact signed with ${guardian.name}! You have gained their boons.`); // Unlock mana types granted by this guardian diff --git a/src/lib/game/utils/combat-utils.ts b/src/lib/game/utils/combat-utils.ts index 16c77cd..15d456b 100644 --- a/src/lib/game/utils/combat-utils.ts +++ b/src/lib/game/utils/combat-utils.ts @@ -71,7 +71,10 @@ export interface BoonBonuses { } // Helper to calculate total boon bonuses from signed pacts -export function getBoonBonuses(signedPacts: number[]): BoonBonuses { +export function getBoonBonuses( + signedPacts: number[], + guardianBoonMultiplier: number = 1.0, +): BoonBonuses { const bonuses: BoonBonuses = { maxMana: 0, manaRegen: 0, @@ -92,42 +95,43 @@ export function getBoonBonuses(signedPacts: number[]): BoonBonuses { if (!guardian) continue; for (const boon of guardian.boons) { + let value = boon.value * guardianBoonMultiplier; switch (boon.type) { case 'maxMana': - bonuses.maxMana += boon.value; + bonuses.maxMana += value; break; case 'manaRegen': - bonuses.manaRegen += boon.value; + bonuses.manaRegen += value; break; case 'castingSpeed': - bonuses.castingSpeed += boon.value; + bonuses.castingSpeed += value; break; case 'elementalDamage': - bonuses.elementalDamage += boon.value; + bonuses.elementalDamage += value; break; case 'rawDamage': - bonuses.rawDamage += boon.value; + bonuses.rawDamage += value; break; case 'critChance': - bonuses.critChance += boon.value; + bonuses.critChance += value; break; case 'critDamage': - bonuses.critDamage += boon.value; + bonuses.critDamage += value; break; case 'spellEfficiency': - bonuses.spellEfficiency += boon.value; + bonuses.spellEfficiency += value; break; case 'manaGain': - bonuses.manaGain += boon.value; + bonuses.manaGain += value; break; case 'insightGain': - bonuses.insightGain += boon.value; + bonuses.insightGain += value; break; case 'studySpeed': - bonuses.studySpeed += boon.value; + bonuses.studySpeed += value; break; case 'prestigeInsight': - bonuses.prestigeInsight += boon.value; + bonuses.prestigeInsight += value; break; } } diff --git a/src/lib/game/utils/discipline-math.ts b/src/lib/game/utils/discipline-math.ts index 964c1d4..84171e1 100644 --- a/src/lib/game/utils/discipline-math.ts +++ b/src/lib/game/utils/discipline-math.ts @@ -125,6 +125,12 @@ export function checkDisciplinePrerequisites( const missingPrereqs: string[] = []; for (const reqId of discipline.requires) { + // Special case: 'signed_pact' requires at least one guardian pact + if (reqId === 'signed_pact') { + missingPrereqs.push('Signed guardian pact'); + continue; + } + // Check if this is a discipline prerequisite (exists in definitions) const reqDef = allDefinitions.find((d) => d.id === reqId); if (reqDef) {