ab3afae2a6
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 1m2s
- New files: element-distance.ts, conversion-costs.ts, conversion-rates.ts - All conversion types (discipline, attunement, pact) use unified formula - Conversion costs scale exponentially by element tier (10^(d+1) raw, 10*(d+1) per component) - Costs deducted from regen, not from mana pool - Auto-pause on insufficient regen with UI warning - Meditation boosts conversion rates (reduced by distance) - Attunement levels provide +50% multiplicative bonus per level - Guardian pacts provide +0.15/hr base rate + invoker level bonus - Removed convertMana, processConvertAction, craftComposite from manaStore - Stats tab shows per-element conversion breakdown with formulas - ManaDisplay shows per-element net regen rates - All 916 tests pass, all files under 400 lines
128 lines
4.9 KiB
TypeScript
128 lines
4.9 KiB
TypeScript
// ─── Mana & Regen Utilities ──────────────────────────────────────────────────
|
|
|
|
import type { AttunementState } from '../types';
|
|
import type { ComputedEffects } from '../effects/upgrade-effects.types';
|
|
import { HOURS_PER_TICK } from '../constants';
|
|
import { getTotalAttunementRegen } from '../data/attunements';
|
|
|
|
export interface DisciplineBonuses {
|
|
bonuses: Record<string, number>;
|
|
multipliers: Record<string, number>;
|
|
}
|
|
|
|
// ─── Mana Params ────────────────────────────────────────────────────────────
|
|
|
|
export interface ManaComputeParams {
|
|
prestigeUpgrades?: Record<string, number>;
|
|
}
|
|
|
|
export interface RegenComputeParams extends ManaComputeParams {
|
|
attunements: Record<string, AttunementState>;
|
|
}
|
|
|
|
export interface EffectiveRegenParams extends RegenComputeParams {
|
|
rawMana: number;
|
|
incursionStrength: number;
|
|
}
|
|
|
|
// ─── Max Mana ────────────────────────────────────────────────────────────────
|
|
|
|
export function computeMaxMana(
|
|
state: Partial<ManaComputeParams>,
|
|
effects?: ComputedEffects,
|
|
discipline?: DisciplineBonuses,
|
|
): number {
|
|
const pu = state.prestigeUpgrades || {};
|
|
const base =
|
|
100 +
|
|
(pu.manaWell || 0) * 500 +
|
|
(discipline?.bonuses?.maxManaBonus || 0);
|
|
|
|
if (effects) {
|
|
return Math.floor((base + effects.maxManaBonus) * effects.maxManaMultiplier);
|
|
}
|
|
return base;
|
|
}
|
|
|
|
// ─── Regen ────────────────────────────────────────────────────────────────────
|
|
|
|
export function computeRegen(
|
|
state: Pick<RegenComputeParams, 'prestigeUpgrades' | 'attunements'>,
|
|
effects?: ComputedEffects,
|
|
discipline?: DisciplineBonuses,
|
|
): number {
|
|
const pu = state.prestigeUpgrades || {};
|
|
const temporalBonus = 1 + (pu.temporalEcho || 0) * 0.1;
|
|
const base =
|
|
2 +
|
|
((pu || {}).manaFlow || 0) * 0.5;
|
|
|
|
let regen = base * temporalBonus;
|
|
|
|
// Add attunement raw mana regen
|
|
const attunementRegen = getTotalAttunementRegen(state.attunements || {});
|
|
regen += attunementRegen;
|
|
|
|
// Apply discipline regen bonus
|
|
if (discipline?.bonuses?.regenBonus) {
|
|
regen += discipline.bonuses.regenBonus;
|
|
}
|
|
|
|
// Apply upgrade effects if provided
|
|
if (effects) {
|
|
regen = (regen + effects.regenBonus + effects.permanentRegenBonus) * effects.regenMultiplier;
|
|
}
|
|
|
|
return regen;
|
|
}
|
|
|
|
// ─── Effective Regen for Display ──────────────────────────────────────────────
|
|
|
|
export function computeEffectiveRegenForDisplay(
|
|
state: Pick<RegenComputeParams, 'prestigeUpgrades' | 'attunements'>,
|
|
effects?: ComputedEffects,
|
|
discipline?: DisciplineBonuses,
|
|
): { rawRegen: number; conversionDrain: number; effectiveRegen: number } {
|
|
const rawRegen = computeRegen(state, effects, discipline);
|
|
return { rawRegen, conversionDrain: 0, effectiveRegen: rawRegen };
|
|
}
|
|
|
|
// ─── Effective Regen (dynamic) ────────────────────────────────────────────────
|
|
|
|
export function computeEffectiveRegen(
|
|
state: Pick<RegenComputeParams, 'prestigeUpgrades' | 'attunements'> & { rawMana: number; incursionStrength: number },
|
|
effects?: ComputedEffects,
|
|
discipline?: DisciplineBonuses,
|
|
): number {
|
|
let regen = computeRegen(state, effects, discipline);
|
|
const incursionStrength = state.incursionStrength || 0;
|
|
regen *= (1 - incursionStrength);
|
|
return regen;
|
|
}
|
|
|
|
// ─── Click Mana ───────────────────────────────────────────────────────────────
|
|
|
|
export function computeClickMana(
|
|
discipline?: DisciplineBonuses,
|
|
): number {
|
|
const discClickBonus = discipline?.bonuses?.clickManaBonus || 0;
|
|
return 1 + discClickBonus;
|
|
}
|
|
|
|
// ─── Meditation Bonus ─────────────────────────────────────────────────────────
|
|
|
|
export function getMeditationBonus(
|
|
meditateTicks: number,
|
|
meditationEfficiency: number = 1,
|
|
disciplineMeditationCap: number = 0,
|
|
): number {
|
|
const hours = meditateTicks * HOURS_PER_TICK;
|
|
|
|
// Continuous ramp: 1 + (hours / 8) * 4, capped at 2.5 + disciplineMeditationCap
|
|
const maxMultiplier = 2.5 + disciplineMeditationCap;
|
|
const bonus = Math.min(1 + (hours / 8) * 4, maxMultiplier);
|
|
|
|
// Apply meditation efficiency from upgrades
|
|
return bonus * meditationEfficiency;
|
|
}
|