// ─── Unified Effect System ───────────────────────────────────────────────── // This module consolidates ALL effect sources into a single computation: // - Discipline effects (from active disciplines) // - Skill upgrade effects (from milestone upgrades) // - Equipment enchantment effects (from enchanted gear) import type { GameState, EquipmentInstance } from './types'; import { ENCHANTMENT_EFFECTS } from './data/enchantment-effects'; import { computeEffects } from './upgrade-effects'; import { hasSpecial, SPECIAL_EFFECTS } from './special-effects'; import { computeDisciplineEffects } from './effects/discipline-effects'; import type { ComputedEffects } from './upgrade-effects.types'; // Re-export for convenience export { computeEffects } from './upgrade-effects'; export { hasSpecial, SPECIAL_EFFECTS } from './special-effects'; export type { ComputedEffects } from './upgrade-effects.types'; // ─── Equipment Effect Computation ──────────────────────────────────────────── export function computeEquipmentEffects( equipmentInstances: Record, equippedInstances: Record, enchantmentPowerMultiplier: number = 1.0 ): { bonuses: Record; multipliers: Record; specials: Set; } { const bonuses: Record = {}; const multipliers: Record = {}; const specials = new Set(); for (const instanceId of Object.values(equippedInstances || {})) { if (!instanceId) continue; const instance = equipmentInstances[instanceId]; if (!instance) continue; for (const ench of instance.enchantments) { const effectDef = ENCHANTMENT_EFFECTS[ench.effectId]; if (!effectDef) continue; const { effect } = effectDef; if (effect.type === 'bonus' && effect.stat && effect.value) { const adjustedValue = effect.value * enchantmentPowerMultiplier; if (effect.stat.startsWith('elementCap_')) { const element = effect.stat.replace('elementCap_', ''); bonuses[`elementCap_${element}`] = (bonuses[`elementCap_${element}`] || 0) + adjustedValue * ench.stacks; } else { bonuses[effect.stat] = (bonuses[effect.stat] || 0) + adjustedValue * ench.stacks; } } else if (effect.type === 'multiplier' && effect.stat && effect.value) { const adjustedValue = effect.value * enchantmentPowerMultiplier; const key = effect.stat; if (!multipliers[key]) multipliers[key] = 1; for (let i = 0; i < ench.stacks; i++) { multipliers[key] *= adjustedValue; } } else if (effect.type === 'special' && effect.specialId) { specials.add(effect.specialId); } } } return { bonuses, multipliers, specials }; } // ─── Discipline Effects Integration ────────────────────────────────────────── export function getDisciplineEffects(state: GameState) { return computeDisciplineEffects(state); } // ─── Unified Computed Effects ───────────────────────────────────────────────── export interface UnifiedEffects extends ComputedEffects { equipmentBonuses: Record; equipmentMultipliers: Record; equipmentSpecials: Set; disciplineBonuses: Record; disciplineMultipliers: Record; disciplineSpecials: Set; } export function computeAllEffects( skillUpgrades: Record, skillTiers: Record, equipmentInstances: Record, equippedInstances: Record, gameState: GameState ): UnifiedEffects { const upgradeEffects = computeEffects(skillUpgrades, skillTiers); const equipmentEffects = computeEquipmentEffects(equipmentInstances, equippedInstances, upgradeEffects.enchantmentPowerMultiplier); const disciplineEffects = getDisciplineEffects(gameState); const perElementCapBonus: Record = { ...upgradeEffects.perElementCapBonus }; for (const [key, value] of Object.entries(equipmentEffects.bonuses)) { if (key.startsWith('elementCap_')) { const element = key.replace('elementCap_', ''); perElementCapBonus[element] = (perElementCapBonus[element] || 0) + value; } } const merged: UnifiedEffects = { ...upgradeEffects, maxManaBonus: upgradeEffects.maxManaBonus + (equipmentEffects.bonuses.maxMana || 0) + (disciplineEffects.bonuses.maxManaBonus || 0), regenBonus: upgradeEffects.regenBonus + (equipmentEffects.bonuses.regen || 0) + (disciplineEffects.bonuses.regenBonus || 0), clickManaBonus: upgradeEffects.clickManaBonus + (equipmentEffects.bonuses.clickMana || 0) + (disciplineEffects.bonuses.clickManaBonus || 0), baseDamageBonus: upgradeEffects.baseDamageBonus + (equipmentEffects.bonuses.baseDamage || 0) + (disciplineEffects.bonuses.baseDamageBonus || 0), elementCapBonus: upgradeEffects.elementCapBonus + (equipmentEffects.bonuses.elementCap || 0) + (disciplineEffects.bonuses.elementCapBonus || 0), perElementCapBonus, maxManaMultiplier: upgradeEffects.maxManaMultiplier * (equipmentEffects.multipliers.maxMana || 1), regenMultiplier: upgradeEffects.regenMultiplier * (equipmentEffects.multipliers.regen || 1), clickManaMultiplier: upgradeEffects.clickManaMultiplier * (equipmentEffects.multipliers.clickMana || 1), baseDamageMultiplier: upgradeEffects.baseDamageMultiplier * (equipmentEffects.multipliers.baseDamage || 1), attackSpeedMultiplier: upgradeEffects.attackSpeedMultiplier * (equipmentEffects.multipliers.attackSpeed || 1), elementCapMultiplier: upgradeEffects.elementCapMultiplier * (equipmentEffects.multipliers.elementCap || 1), specials: new Set([...Array.from(upgradeEffects.specials), ...Array.from(equipmentEffects.specials), ...Array.from(disciplineEffects.specials)]), equipmentBonuses: equipmentEffects.bonuses, equipmentMultipliers: equipmentEffects.multipliers, equipmentSpecials: equipmentEffects.specials, disciplineBonuses: disciplineEffects.bonuses, disciplineMultipliers: disciplineEffects.multipliers, disciplineSpecials: disciplineEffects.specials, }; if (equipmentEffects.bonuses.critChance) { merged.critChanceBonus += equipmentEffects.bonuses.critChance; } if (equipmentEffects.bonuses.meditationEfficiency) { merged.meditationEfficiency *= (equipmentEffects.multipliers.meditationEfficiency || 1); } if (equipmentEffects.bonuses.studySpeed) { merged.studySpeedMultiplier *= (equipmentEffects.multipliers.studySpeed || 1); } return merged; } export function getUnifiedEffects(state: Pick): UnifiedEffects { return computeAllEffects( state.skillUpgrades || {}, state.skillTiers || {}, state.equipmentInstances || {}, state.equippedInstances || {}, state as GameState ); } // ─── Stat Computation with All Effects ─────────────────────────────────────── export function computeTotalMaxMana( state: Pick, effects?: UnifiedEffects ): number { const pu = state.prestigeUpgrades; const skillMult = effects?.skillLevelMultiplier || 1; const base = 100 + ((state.skills || {}).manaWell || 0) * 100 * skillMult + ((pu || {}).manaWell || 0) * 500; if (!effects) effects = getUnifiedEffects(state as any); return Math.floor((base + effects.maxManaBonus) * effects.maxManaMultiplier); } export function computeTotalRegen( state: Pick, effects?: UnifiedEffects ): number { const pu = state.prestigeUpgrades; const temporalBonus = 1 + (pu.temporalEcho || 0) * 0.1; const skillMult = effects?.skillLevelMultiplier || 1; const base = 2 + (state.skills.manaFlow || 0) * 1 * skillMult + (state.skills.manaSpring || 0) * 2 * skillMult + (pu.manaFlow || 0) * 0.5; let regen = base * temporalBonus; if (!effects) effects = getUnifiedEffects(state as any); regen = (regen + effects.regenBonus + effects.permanentRegenBonus) * effects.regenMultiplier; return regen; } export function computeTotalClickMana( state: Pick, effects?: UnifiedEffects ): number { const skillMult = effects?.skillLevelMultiplier || 1; const base = 1 + (state.skills.manaTap || 0) * 1 * skillMult + (state.skills.manaSurge || 0) * 3 * skillMult; if (!effects) effects = getUnifiedEffects(state as any); return Math.floor((base + effects.clickManaBonus) * effects.clickManaMultiplier); }