1aea72c013
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m21s
- Replace generic spell-casting/void-manipulation with pact-focused disciplines - Add Pact Attunement (light): reduces pact signing time, boosts pact affinity - Add Guardian's Boon (dark): amplifies all guardian unique perks - Add pactAffinityBonus and guardianBoonMultiplier stat keys to effect system - Apply pactAffinityBonus in pact signing time calculation (gameStore) - Scale guardian boon values by guardianBoonMultiplier (combat-utils) - Guard Invoker discipline activation behind signedPacts.length > 0 - Add 'Signed guardian pact' prerequisite display in discipline-math
230 lines
11 KiB
TypeScript
230 lines
11 KiB
TypeScript
// ─── 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 { EquipmentInstance } from './types';
|
|
import { ENCHANTMENT_EFFECTS } from './data/enchantment-effects';
|
|
import { computeEffects } from './effects/upgrade-effects';
|
|
import { computeDisciplineEffects } from './effects/discipline-effects';
|
|
import type { ComputedEffects } from './effects/upgrade-effects.types';
|
|
|
|
// Re-export for convenience
|
|
export { computeEffects } from './effects/upgrade-effects';
|
|
export { hasSpecial, SPECIAL_EFFECTS } from './effects/special-effects';
|
|
export type { ComputedEffects } from './effects/upgrade-effects.types';
|
|
|
|
// ─── Equipment Effect Computation ────────────────────────────────────────────
|
|
|
|
export function computeEquipmentEffects(
|
|
equipmentInstances: Record<string, EquipmentInstance>,
|
|
equippedInstances: Record<string, string | null>,
|
|
enchantmentPowerMultiplier: number = 1.0
|
|
): {
|
|
bonuses: Record<string, number>;
|
|
multipliers: Record<string, number>;
|
|
specials: Set<string>;
|
|
} {
|
|
const bonuses: Record<string, number> = {};
|
|
const multipliers: Record<string, number> = {};
|
|
const specials = new Set<string>();
|
|
|
|
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 };
|
|
}
|
|
|
|
// ─── Unified Computed Effects ─────────────────────────────────────────────────
|
|
|
|
export interface UnifiedEffects extends ComputedEffects {
|
|
equipmentBonuses: Record<string, number>;
|
|
equipmentMultipliers: Record<string, number>;
|
|
equipmentSpecials: Set<string>;
|
|
disciplineBonuses: Record<string, number>;
|
|
disciplineMultipliers: Record<string, number>;
|
|
disciplineSpecials: Set<string>;
|
|
/**
|
|
* 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(
|
|
skillUpgrades: Record<string, string[]>,
|
|
skillTiers: Record<string, number>,
|
|
equipmentInstances: Record<string, EquipmentInstance>,
|
|
equippedInstances: Record<string, string | null>,
|
|
): UnifiedEffects {
|
|
const upgradeEffects = computeEffects(skillUpgrades, skillTiers);
|
|
const equipmentEffects = computeEquipmentEffects(equipmentInstances, equippedInstances, upgradeEffects.enchantmentPowerMultiplier);
|
|
const disciplineEffects = computeDisciplineEffects();
|
|
|
|
const perElementCapBonus: Record<string, number> = { ...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;
|
|
}
|
|
}
|
|
|
|
// Per-element regen from discipline effects (regen_{element}) — no longer produced
|
|
// by elemental disciplines (they now use conversion instead).
|
|
// Kept for backward compatibility with any upgrade effects that may provide per-element regen.
|
|
const perElementRegenBonus: Record<string, number> = { ...upgradeEffects.perElementRegenBonus };
|
|
|
|
// Merge per-element cap bonuses from discipline effects (elementCap_{element})
|
|
for (const [key, value] of Object.entries(disciplineEffects.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,
|
|
perElementRegenBonus,
|
|
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,
|
|
pactAffinityBonus: Math.min(0.9, disciplineEffects.bonuses.pactAffinityBonus || 0),
|
|
guardianBoonMultiplier: 1 + (disciplineEffects.bonuses.guardianBoonMultiplier || 0),
|
|
};
|
|
|
|
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: {
|
|
skillUpgrades?: Record<string, string[]>;
|
|
skillTiers?: Record<string, number>;
|
|
equipmentInstances?: Record<string, EquipmentInstance>;
|
|
equippedInstances?: Record<string, string | null>;
|
|
}): UnifiedEffects {
|
|
return computeAllEffects(
|
|
state.skillUpgrades || {},
|
|
state.skillTiers || {},
|
|
state.equipmentInstances || {},
|
|
state.equippedInstances || {},
|
|
);
|
|
}
|
|
|
|
// ─── Stat Computation with All Effects ───────────────────────────────────────
|
|
|
|
export function computeTotalMaxMana(
|
|
state: {
|
|
skills?: Record<string, number>;
|
|
prestigeUpgrades?: Record<string, number>;
|
|
skillUpgrades?: Record<string, string[]>;
|
|
skillTiers?: Record<string, number>;
|
|
equipmentInstances?: Record<string, EquipmentInstance>;
|
|
equippedInstances?: Record<string, string | null>;
|
|
},
|
|
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;
|
|
const resolvedEffects = effects || getUnifiedEffects(state);
|
|
return Math.floor((base + resolvedEffects.maxManaBonus) * resolvedEffects.maxManaMultiplier);
|
|
}
|
|
|
|
export function computeTotalRegen(
|
|
state: {
|
|
skills?: Record<string, number>;
|
|
prestigeUpgrades?: Record<string, number>;
|
|
skillUpgrades?: Record<string, string[]>;
|
|
skillTiers?: Record<string, number>;
|
|
equipmentInstances?: Record<string, EquipmentInstance>;
|
|
equippedInstances?: Record<string, string | null>;
|
|
},
|
|
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;
|
|
const resolvedEffects = effects || getUnifiedEffects(state);
|
|
regen = (regen + resolvedEffects.regenBonus + resolvedEffects.permanentRegenBonus) * resolvedEffects.regenMultiplier;
|
|
return regen;
|
|
}
|
|
|
|
export function computeTotalClickMana(
|
|
state: {
|
|
skills?: Record<string, number>;
|
|
skillUpgrades?: Record<string, string[]>;
|
|
skillTiers?: Record<string, number>;
|
|
equipmentInstances?: Record<string, EquipmentInstance>;
|
|
equippedInstances?: Record<string, string | null>;
|
|
},
|
|
effects?: UnifiedEffects
|
|
): number {
|
|
const skillMult = effects?.skillLevelMultiplier || 1;
|
|
const base = 1 + (state.skills?.manaTap || 0) * 1 * skillMult + (state.skills?.manaSurge || 0) * 3 * skillMult;
|
|
const resolvedEffects = effects || getUnifiedEffects(state);
|
|
return Math.floor((base + resolvedEffects.clickManaBonus) * resolvedEffects.clickManaMultiplier);
|
|
}
|