feat: Add Mana Circulation discipline with regen multiplier and meditation cap perks
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m23s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m23s
This commit is contained in:
@@ -35,4 +35,36 @@ export const baseDisciplines: DisciplineDefinition[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'mana-circulation',
|
||||
name: 'Mana Circulation',
|
||||
attunement: DisciplinesAttunementType.BASE,
|
||||
manaType: 'raw',
|
||||
baseCost: 5,
|
||||
description:
|
||||
'Cultivate and circulate raw mana through the body — the foundation upon which all elemental work is built.',
|
||||
statBonus: { stat: 'regenBonus', baseValue: 0.2, label: 'Raw Mana Regen' },
|
||||
difficultyFactor: 100,
|
||||
scalingFactor: 50,
|
||||
drainBase: 1,
|
||||
perks: [
|
||||
{
|
||||
id: 'mana-circulation-regen',
|
||||
type: 'infinite',
|
||||
threshold: 100,
|
||||
value: 100,
|
||||
description: 'Every 100 XP: +10% raw mana regen',
|
||||
bonus: { stat: 'regenMultiplier', amount: 0.10 },
|
||||
},
|
||||
{
|
||||
id: 'mana-circulation-meditation',
|
||||
type: 'capped',
|
||||
threshold: 100,
|
||||
value: 100,
|
||||
description: 'Every 100 XP: +0.5 max meditation multiplier (7 tiers, up to +3.5)',
|
||||
bonus: { stat: 'meditationCapBonus', amount: 0.5 },
|
||||
maxTier: 7,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -18,15 +18,22 @@ import {
|
||||
const KNOWN_BONUS_STATS = new Set([
|
||||
'maxManaBonus',
|
||||
'regenBonus',
|
||||
'regenMultiplier',
|
||||
'clickManaBonus',
|
||||
'baseDamageBonus',
|
||||
'elementCapBonus',
|
||||
'meditationCapBonus',
|
||||
]);
|
||||
|
||||
export interface DisciplineEffectsResult {
|
||||
bonuses: Record<string, number>;
|
||||
multipliers: Record<string, number>;
|
||||
specials: Set<string>;
|
||||
/**
|
||||
* Bonus to the meditation multiplier cap from disciplines.
|
||||
* Each point of meditationCapBonus adds +0.5 to the max meditation multiplier.
|
||||
*/
|
||||
meditationCapBonus: number;
|
||||
/**
|
||||
* Conversion entries: for each active discipline with a conversionRate,
|
||||
* maps target mana type → { rate, sourceManaTypes }.
|
||||
@@ -45,9 +52,18 @@ export function computeDisciplineEffects(_state?: DisciplineStoreState): Discipl
|
||||
const bonuses: Record<string, number> = {};
|
||||
const multipliers: Record<string, number> = {};
|
||||
const specials = new Set<string>();
|
||||
let meditationCapBonus = 0;
|
||||
const conversions: Record<string, { rate: number; sourceManaTypes: string[] }> = {};
|
||||
|
||||
function addBonus(stat: string, amount: number) {
|
||||
if (stat === 'meditationCapBonus') {
|
||||
meditationCapBonus += amount;
|
||||
return;
|
||||
}
|
||||
if (stat === 'regenMultiplier') {
|
||||
multipliers[stat] = (multipliers[stat] || 0) + amount;
|
||||
return;
|
||||
}
|
||||
bonuses[stat] = (bonuses[stat] || 0) + amount;
|
||||
}
|
||||
|
||||
@@ -90,7 +106,10 @@ export function computeDisciplineEffects(_state?: DisciplineStoreState): Discipl
|
||||
}
|
||||
} else if (perk.type === 'capped') {
|
||||
if (perk.bonus) {
|
||||
const tier = calculatePerkTier(disc.xp, perk.threshold, perk.value);
|
||||
let tier = calculatePerkTier(disc.xp, perk.threshold, perk.value);
|
||||
if (tier > 0 && perk.maxTier !== undefined) {
|
||||
tier = Math.min(tier, perk.maxTier);
|
||||
}
|
||||
if (tier > 0) {
|
||||
addBonus(perk.bonus.stat, tier * perk.bonus.amount);
|
||||
}
|
||||
@@ -101,5 +120,5 @@ export function computeDisciplineEffects(_state?: DisciplineStoreState): Discipl
|
||||
}
|
||||
}
|
||||
|
||||
return { bonuses, multipliers, specials, conversions };
|
||||
return { bonuses, multipliers, specials, meditationCapBonus, conversions };
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import { computePactMultiplier, computePactInsightMultiplier } from '../utils/pa
|
||||
import { ELEMENTS, SPELLS_DEF, getStudySpeedMultiplier, getStudyCostMultiplier, HOURS_PER_TICK, TICK_MS } from '../constants';
|
||||
import { getGuardianForFloor } from '../data/guardian-encounters';
|
||||
import { hasSpecial, SPECIAL_EFFECTS } from '../effects/special-effects';
|
||||
import { computeDisciplineEffects } from '../effects/discipline-effects';
|
||||
|
||||
/**
|
||||
* Hook for all mana-related derived stats
|
||||
@@ -52,9 +53,10 @@ export function useManaStats() {
|
||||
[]
|
||||
);
|
||||
|
||||
const disciplineEffects = computeDisciplineEffects();
|
||||
const meditationMultiplier = useMemo(
|
||||
() => getMeditationBonus(meditateTicks, {}, upgradeEffects.meditationEfficiency),
|
||||
[meditateTicks, upgradeEffects.meditationEfficiency]
|
||||
() => getMeditationBonus(meditateTicks, {}, upgradeEffects.meditationEfficiency, disciplineEffects.meditationCapBonus),
|
||||
[meditateTicks, upgradeEffects.meditationEfficiency, disciplineEffects.meditationCapBonus]
|
||||
);
|
||||
|
||||
const incursionStrength = useMemo(
|
||||
|
||||
@@ -75,7 +75,7 @@ export function useManaStats() {
|
||||
|
||||
const clickMana = computeClickMana({}, disciplineEffects);
|
||||
|
||||
const meditationMultiplier = getMeditationBonus(meditateTicks, {}, upgradeEffects.meditationEfficiency);
|
||||
const meditationMultiplier = getMeditationBonus(meditateTicks, {}, upgradeEffects.meditationEfficiency, disciplineEffects.meditationCapBonus);
|
||||
const incursionStrength = getIncursionStrength(day, hour);
|
||||
const effectiveRegenWithSpecials = baseRegen * (1 - incursionStrength);
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ export const useGameStore = create<GameCoordinatorStore>()(
|
||||
{ skills: {}, prestigeUpgrades: ctx.prestige.prestigeUpgrades, skillUpgrades: {}, skillTiers: {}, attunements: {} },
|
||||
undefined,
|
||||
disciplineEffects,
|
||||
);
|
||||
) * (1 + (disciplineEffects.multipliers.regenMultiplier || 0));
|
||||
|
||||
// Time progression
|
||||
let hour = ctx.game.hour + HOURS_PER_TICK;
|
||||
@@ -172,7 +172,7 @@ export const useGameStore = create<GameCoordinatorStore>()(
|
||||
|
||||
if (ctx.combat.currentAction === 'meditate') {
|
||||
meditateTicks++;
|
||||
meditationMultiplier = getMeditationBonus(meditateTicks, {}, 1);
|
||||
meditationMultiplier = getMeditationBonus(meditateTicks, {}, 1, disciplineEffects.meditationCapBonus);
|
||||
} else {
|
||||
meditateTicks = 0;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@ export interface DisciplinePerk {
|
||||
description: string;
|
||||
unlocksEffects?: string[];
|
||||
bonus?: PerkBonus;
|
||||
/**
|
||||
* For capped perks: maximum number of tiers. If unset, capped perks
|
||||
* behave like infinite (no cap on tier count).
|
||||
*/
|
||||
maxTier?: number;
|
||||
}
|
||||
|
||||
// ─── Discipline Definition ────────────────────────────────────────────────────
|
||||
|
||||
@@ -124,29 +124,40 @@ export function computeClickMana(
|
||||
|
||||
// ─── Meditation Bonus ─────────────────────────────────────────────────────────
|
||||
|
||||
export function getMeditationBonus(meditateTicks: number, skills: Record<string, number>, meditationEfficiency: number = 1): number {
|
||||
export function getMeditationBonus(
|
||||
meditateTicks: number,
|
||||
skills: Record<string, number>,
|
||||
meditationEfficiency: number = 1,
|
||||
disciplineMeditationCap: number = 0,
|
||||
): number {
|
||||
const hasMeditation = skills.meditation === 1;
|
||||
const hasDeepTrance = skills.deepTrance === 1;
|
||||
const hasVoidMeditation = skills.voidMeditation === 1;
|
||||
|
||||
const hours = meditateTicks * HOURS_PER_TICK;
|
||||
|
||||
// Base meditation: ramps up over 4 hours to 1.5x
|
||||
// Determine the hard cap for this meditation session.
|
||||
// disciplineMeditationCap adds +0.5 per point (e.g. from Mana Circulation discipline).
|
||||
// Base max is 5.0 (Void Meditation), each discipline bonus adds +0.5.
|
||||
const maxMultiplier = 5.0 + disciplineMeditationCap;
|
||||
|
||||
// Base meditation: ramps up over 4 hours, capped at 1.5x or discipline cap
|
||||
let bonus = 1 + Math.min(hours / 4, 0.5);
|
||||
bonus = Math.min(bonus, maxMultiplier);
|
||||
|
||||
// With Meditation Focus: up to 2.5x after 4 hours
|
||||
if (hasMeditation && hours >= 4) {
|
||||
bonus = 2.5;
|
||||
bonus = Math.min(2.5, maxMultiplier);
|
||||
}
|
||||
|
||||
// With Deep Trance: up to 3.0x after 6 hours
|
||||
if (hasDeepTrance && hours >= 6) {
|
||||
bonus = 3.0;
|
||||
bonus = Math.min(3.0, maxMultiplier);
|
||||
}
|
||||
|
||||
// With Void Meditation: up to 5.0x after 8 hours
|
||||
// With Void Meditation: up to maxMultiplier after 8 hours
|
||||
if (hasVoidMeditation && hours >= 8) {
|
||||
bonus = 5.0;
|
||||
bonus = maxMultiplier;
|
||||
}
|
||||
|
||||
// Apply meditation efficiency from upgrades
|
||||
|
||||
Reference in New Issue
Block a user