feat: implement Active Disciplines system
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 31s

This commit is contained in:
2026-05-16 19:17:12 +02:00
parent c8341f79f3
commit e462bfcc13
17 changed files with 992 additions and 100 deletions
+35 -98
View File
@@ -1,13 +1,14 @@
// ─── 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)
// - Direct skill bonuses (from skill levels)
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
@@ -17,10 +18,6 @@ export type { ComputedEffects } from './upgrade-effects.types';
// ─── Equipment Effect Computation ────────────────────────────────────────────
/**
* Compute all effects from equipped enchantments
* @param enchantmentPowerMultiplier - Multiplier applied to all enchantment effect values (default 1.0)
*/
export function computeEquipmentEffects(
equipmentInstances: Record<string, EquipmentInstance>,
equippedInstances: Record<string, string | null>,
@@ -34,24 +31,18 @@ export function computeEquipmentEffects(
const multipliers: Record<string, number> = {};
const specials = new Set<string>();
// 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
// Apply enchantmentPowerMultiplier to the effect value
const adjustedValue = effect.value * enchantmentPowerMultiplier;
// Handle per-element capacity bonuses (stat format: elementCap_fire, elementCap_water, etc.)
if (effect.stat.startsWith('elementCap_')) {
const element = effect.stat.replace('elementCap_', '');
bonuses[`elementCap_${element}`] = (bonuses[`elementCap_${element}`] || 0) + adjustedValue * ench.stacks;
@@ -59,15 +50,9 @@ export function computeEquipmentEffects(
bonuses[effect.stat] = (bonuses[effect.stat] || 0) + adjustedValue * 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
// Apply enchantmentPowerMultiplier to the effect value
const adjustedValue = effect.value * enchantmentPowerMultiplier;
const key = effect.stat;
if (!multipliers[key]) {
multipliers[key] = 1;
}
// Each stack applies the multiplier
if (!multipliers[key]) multipliers[key] = 1;
for (let i = 0; i < ench.stacks; i++) {
multipliers[key] *= adjustedValue;
}
@@ -80,35 +65,34 @@ export function computeEquipmentEffects(
return { bonuses, multipliers, specials };
}
// ─── Discipline Effects Integration ──────────────────────────────────────────
export function getDisciplineEffects(state: GameState) {
return computeDisciplineEffects(state);
}
// ─── Unified Computed Effects ─────────────────────────────────────────────────
export interface UnifiedEffects extends ComputedEffects {
// Equipment bonuses
equipmentBonuses: Record<string, number>;
equipmentMultipliers: Record<string, number>;
equipmentSpecials: Set<string>;
disciplineBonuses: Record<string, number>;
disciplineMultipliers: Record<string, number>;
disciplineSpecials: Set<string>;
}
/**
* Compute all effects from all sources: skill upgrades + equipment enchantments
*/
export function computeAllEffects(
skillUpgrades: Record<string, string[]>,
skillTiers: Record<string, number>,
equipmentInstances: Record<string, EquipmentInstance>,
equippedInstances: Record<string, string | null>
equippedInstances: Record<string, string | null>,
gameState: GameState
): UnifiedEffects {
// Get skill upgrade effects
const upgradeEffects = computeEffects(skillUpgrades, skillTiers);
const equipmentEffects = computeEquipmentEffects(equipmentInstances, equippedInstances, upgradeEffects.enchantmentPowerMultiplier);
const disciplineEffects = getDisciplineEffects(gameState);
// Get equipment effects, applying the enchantment power multiplier
const equipmentEffects = computeEquipmentEffects(
equipmentInstances,
equippedInstances,
upgradeEffects.enchantmentPowerMultiplier
);
// Extract per-element capacity bonuses from equipment effects
const perElementCapBonus: Record<string, number> = { ...upgradeEffects.perElementCapBonus };
for (const [key, value] of Object.entries(equipmentEffects.bonuses)) {
if (key.startsWith('elementCap_')) {
@@ -117,92 +101,65 @@ export function computeAllEffects(
}
}
// 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),
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,
// 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([...Array.from(upgradeEffects.specials), ...Array.from(equipmentEffects.specials)]),
// Store equipment effects for reference
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,
};
// 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<GameState, 'skillUpgrades' | 'skillTiers' | 'equipmentInstances' | 'equippedInstances'>): UnifiedEffects {
export function getUnifiedEffects(state: Pick<GameState, 'skillUpgrades' | 'skillTiers' | 'equipmentInstances' | 'equippedInstances' | 'disciplines'>): UnifiedEffects {
return computeAllEffects(
state.skillUpgrades || {},
state.skillTiers || {},
state.equipmentInstances || {},
state.equippedInstances || {}
state.equippedInstances || {},
state as GameState
);
}
// ─── Stat Computation with All Effects ───────────────────────────────────────
/**
* Compute max mana with all effect sources
*/
export function computeTotalMaxMana(
state: Pick<GameState, 'skills' | 'prestigeUpgrades' | 'skillUpgrades' | 'skillTiers' | 'equipmentInstances' | 'equippedInstances'>,
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);
}
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);
}
/**
* Compute regen with all effect sources
*/
export function computeTotalRegen(
state: Pick<GameState, 'skills' | 'prestigeUpgrades' | 'skillUpgrades' | 'skillTiers' | 'equipmentInstances' | 'equippedInstances'>,
effects?: UnifiedEffects
@@ -210,39 +167,19 @@ export function computeTotalRegen(
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;
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);
}
if (!effects) effects = getUnifiedEffects(state as any);
regen = (regen + effects.regenBonus + effects.permanentRegenBonus) * effects.regenMultiplier;
return regen;
}
/**
* Compute click mana with all effect sources
*/
export function computeTotalClickMana(
state: Pick<GameState, 'skills' | 'skillUpgrades' | 'skillTiers' | 'equipmentInstances' | 'equippedInstances'>,
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);
}
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);
}
}