// ─── Unified Effect System ───────────────────────────────────────────────────────── // This module consolidates ALL effect sources into a single computation: // - Skill upgrade effects (from milestone upgrades) // - Equipment enchantment effects (from enchanted gear) // - Direct skill bonuses (from skill levels) import type { GameState, EquipmentInstance } from './types'; import { ENCHANTMENT_EFFECTS } from './data/enchantment-effects'; import { computeEffects, hasSpecial, SPECIAL_EFFECTS, type ComputedEffects } from './upgrade-effects'; // Re-export for convenience export { computeEffects, hasSpecial, SPECIAL_EFFECTS, type ComputedEffects }; // ─── Equipment Effect Computation ──────────────────────────────────────────────── /** * Compute all effects from equipped enchantments */ export function computeEquipmentEffects( equipmentInstances: Record, equippedInstances: Record ): { bonuses: Record; multipliers: Record; specials: Set; } { const bonuses: Record = {}; const multipliers: Record = {}; const specials = new Set(); // Iterate through all equipped items for (const instanceId of Object.values(equippedInstances)) { if (!instanceId) continue; const instance = equipmentInstances[instanceId]; if (!instance) continue; // Process each enchantment on the item 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) { // Bonus effects add to the stat bonuses[effect.stat] = (bonuses[effect.stat] || 0) + effect.value * ench.stacks; } else if (effect.type === 'multiplier' && effect.stat && effect.value) { // Multiplier effects multiply together // For multipliers, we need to track them separately and apply as product const key = effect.stat; if (!multipliers[key]) { multipliers[key] = 1; } // Each stack applies the multiplier for (let i = 0; i < ench.stacks; i++) { multipliers[key] *= effect.value; } } else if (effect.type === 'special' && effect.specialId) { specials.add(effect.specialId); } } } return { bonuses, multipliers, specials }; } // ─── Unified Computed Effects ──────────────────────────────────────────────────── export interface UnifiedEffects extends ComputedEffects { // Equipment bonuses equipmentBonuses: Record; equipmentMultipliers: Record; equipmentSpecials: Set; } /** * Compute all effects from all sources: skill upgrades + equipment enchantments */ export function computeAllEffects( skillUpgrades: Record, skillTiers: Record, equipmentInstances: Record, equippedInstances: Record ): UnifiedEffects { // Get skill upgrade effects const upgradeEffects = computeEffects(skillUpgrades, skillTiers); // Get equipment effects const equipmentEffects = computeEquipmentEffects(equipmentInstances, equippedInstances); // Merge the effects const merged: UnifiedEffects = { ...upgradeEffects, // Merge equipment bonuses with upgrade bonuses maxManaBonus: upgradeEffects.maxManaBonus + (equipmentEffects.bonuses.maxMana || 0), regenBonus: upgradeEffects.regenBonus + (equipmentEffects.bonuses.regen || 0), clickManaBonus: upgradeEffects.clickManaBonus + (equipmentEffects.bonuses.clickMana || 0), baseDamageBonus: upgradeEffects.baseDamageBonus + (equipmentEffects.bonuses.baseDamage || 0), elementCapBonus: upgradeEffects.elementCapBonus + (equipmentEffects.bonuses.elementCap || 0), // Merge equipment multipliers with upgrade multipliers 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), // Merge specials specials: new Set([...upgradeEffects.specials, ...equipmentEffects.specials]), // Store equipment effects for reference equipmentBonuses: equipmentEffects.bonuses, equipmentMultipliers: equipmentEffects.multipliers, equipmentSpecials: equipmentEffects.specials, }; // Handle special stats that are equipment-only if (equipmentEffects.bonuses.critChance) { merged.critChanceBonus += equipmentEffects.bonuses.critChance; } if (equipmentEffects.bonuses.meditationEfficiency) { // This is a multiplier in equipment, convert to additive for simplicity // Equipment gives +10% per stack, so add it to the base merged.meditationEfficiency *= (equipmentEffects.multipliers.meditationEfficiency || 1); } if (equipmentEffects.bonuses.studySpeed) { merged.studySpeedMultiplier *= (equipmentEffects.multipliers.studySpeed || 1); } if (equipmentEffects.bonuses.insightGain) { // Store separately - insight multiplier (merged as any).insightGainMultiplier = (equipmentEffects.multipliers.insightGain || 1); } if (equipmentEffects.bonuses.guardianDamage) { (merged as any).guardianDamageMultiplier = (equipmentEffects.multipliers.guardianDamage || 1); } return merged; } /** * Helper to get unified effects from game state */ export function getUnifiedEffects(state: Pick): UnifiedEffects { return computeAllEffects( state.skillUpgrades || {}, state.skillTiers || {}, state.equipmentInstances, state.equippedInstances ); } // ─── Stat Computation with All Effects ─────────────────────────────────────────── /** * Compute max mana with all effect sources */ export function computeTotalMaxMana( state: Pick, effects?: UnifiedEffects ): number { const pu = state.prestigeUpgrades; const base = 100 + (state.skills.manaWell || 0) * 100 + (pu.manaWell || 0) * 500; if (!effects) { effects = getUnifiedEffects(state); } return Math.floor((base + effects.maxManaBonus) * effects.maxManaMultiplier); } /** * Compute regen with all effect sources */ export function computeTotalRegen( state: Pick, effects?: UnifiedEffects ): number { const pu = state.prestigeUpgrades; const temporalBonus = 1 + (pu.temporalEcho || 0) * 0.1; const base = 2 + (state.skills.manaFlow || 0) * 1 + (state.skills.manaSpring || 0) * 2 + (pu.manaFlow || 0) * 0.5; let regen = base * temporalBonus; if (!effects) { effects = getUnifiedEffects(state); } regen = (regen + effects.regenBonus + effects.permanentRegenBonus) * effects.regenMultiplier; return regen; } /** * Compute click mana with all effect sources */ export function computeTotalClickMana( state: Pick, effects?: UnifiedEffects ): number { const base = 1 + (state.skills.manaTap || 0) * 1 + (state.skills.manaSurge || 0) * 3; if (!effects) { effects = getUnifiedEffects(state as any); } return Math.floor((base + effects.clickManaBonus) * effects.clickManaMultiplier); }