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
+130
View File
@@ -0,0 +1,130 @@
// ─── Discipline Math Utilities ────────────────────────────────────────────────
// Continuous scaling formulas for Active Disciplines
import type { DisciplineState, DisciplineDefinition } from '../types/disciplines';
/**
* Calculate continuous stat bonus from discipline XP
* StatBonus = BaseValue * (XP / ScalingFactor)^0.65
*/
export function calculateStatBonus(
baseValue: number,
xp: number,
scalingFactor: number
): number {
if (xp <= 0) return 0;
const ratio = xp / scalingFactor;
const power = Math.pow(ratio, 0.65);
return baseValue * power;
}
/**
* Calculate dynamic mana drain per tick
* ManaDrainPerTick = BaseDrain * (1 + (XP / DifficultyFactor)^0.4)
*/
export function calculateManaDrain(
baseDrain: number,
xp: number,
difficultyFactor: number
): number {
if (xp <= 0) return baseDrain;
const ratio = xp / difficultyFactor;
const power = Math.pow(ratio, 0.4);
return baseDrain * (1 + power);
}
/**
* Calculate infinite perk tier
* PerkTier = Math.max(0, Math.floor((XP - Threshold) / Interval) + 1)
*/
export function calculatePerkTier(
xp: number,
threshold: number,
interval: number
): number {
if (xp < threshold) return 0;
const excess = xp - threshold;
return Math.max(0, Math.floor(excess / interval) + 1);
}
/**
* Check if discipline can be activated (has required mana type)
*/
export function canActivateDiscipline(
discipline: DisciplineDefinition,
gameState: { elements?: Record<string, any> }
): boolean {
if (discipline.manaType === 'raw') return true;
const element = gameState.elements?.[discipline.manaType];
return element && element.unlocked;
}
/**
* Check if discipline can proceed (has sufficient mana for drain)
*/
export function canProceedDiscipline(
discipline: DisciplineDefinition,
disciplineState: DisciplineState,
gameState: { elements?: Record<string, any>; rawMana?: number }
): boolean {
if (disciplineState.paused) return false;
const drain = calculateManaDrain(
discipline.drainBase,
disciplineState.xp,
discipline.difficultyFactor
);
if (discipline.manaType === 'raw') {
return (gameState.rawMana || 0) >= drain;
}
const element = gameState.elements?.[discipline.manaType];
return element && element.current >= drain;
}
/**
* Get unlocked perks for a discipline
*/
export function getUnlockedPerks(
discipline: DisciplineDefinition,
xp: number
): DisciplinePerk[] {
return discipline.perks.filter(perk => {
if (perk.type === 'once') {
return xp >= perk.threshold;
} else if (perk.type === 'capped') {
const tier = calculatePerkTier(xp, perk.threshold, perk.value);
return tier > 0;
} else if (perk.type === 'infinite') {
return xp >= perk.threshold;
}
return false;
});
}
/**
* Calculate total stats from all active disciplines
*/
export function calculateDisciplineStats(
disciplines: DisciplineDefinition[],
states: DisciplineState[]
): Record<string, number> {
const stats: Record<string, number> = {};
disciplines.forEach((discipline, index) => {
const state = states[index];
if (!state || state.paused) return;
const bonus = calculateStatBonus(
discipline.statBonus.baseValue,
state.xp,
discipline.scalingFactor
);
const statKey = discipline.statBonus.stat;
stats[statKey] = (stats[statKey] || 0) + bonus;
});
return stats;
}