refactor: remove skill system leftovers, migrate click mana to discipline perk
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m30s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m30s
- Simplified getMeditationBonus() to continuous ramp formula - Added click-mana capped perk to Mana Circulation discipline - Removed manaWell/manaFlow/manaSpring skill reads and prestige upgrades - Removed all skill fields from GameState and GameActionType - Updated all call sites and tests (916 tests passing) Closes #174
This commit is contained in:
@@ -1,11 +1,4 @@
|
|||||||
# Circular Dependencies
|
# Circular Dependencies
|
||||||
Generated: 2026-05-28T07:32:48.513Z
|
Generated: 2026-05-28T07:47:51.159Z
|
||||||
Found: 1 circular chain(s) — these MUST be fixed before modifying involved files.
|
|
||||||
|
|
||||||
1. 1) effects/discipline-effects.ts > stores/discipline-slice.ts
|
No circular dependencies found. ✅
|
||||||
|
|
||||||
## How to fix
|
|
||||||
1. Identify which import in the chain can be extracted to a shared types/utils file.
|
|
||||||
2. Move the shared type or function there.
|
|
||||||
3. Both files import from the new shared module instead of each other.
|
|
||||||
4. Run: bunx madge --circular src/lib/game (should return clean)
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"generated": "2026-05-28T07:32:46.775Z",
|
"generated": "2026-05-28T07:47:49.204Z",
|
||||||
"description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.",
|
"description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.",
|
||||||
"usage": "To find what a file affects, search for its path in the VALUES. To find what a file depends on, look at its KEY entry."
|
"usage": "To find what a file affects, search for its path in the VALUES. To find what a file depends on, look at its KEY entry."
|
||||||
},
|
},
|
||||||
@@ -528,7 +528,6 @@
|
|||||||
"data/disciplines/enchanter.ts",
|
"data/disciplines/enchanter.ts",
|
||||||
"data/disciplines/fabricator.ts",
|
"data/disciplines/fabricator.ts",
|
||||||
"data/disciplines/invoker.ts",
|
"data/disciplines/invoker.ts",
|
||||||
"effects/discipline-effects.ts",
|
|
||||||
"types.ts",
|
"types.ts",
|
||||||
"types/disciplines.ts",
|
"types/disciplines.ts",
|
||||||
"utils/discipline-math.ts",
|
"utils/discipline-math.ts",
|
||||||
|
|||||||
@@ -28,9 +28,9 @@ describe('Tab barrel export', () => {
|
|||||||
// ─── Test: Prestige upgrade definitions ────────────────────────────────────────
|
// ─── Test: Prestige upgrade definitions ────────────────────────────────────────
|
||||||
|
|
||||||
describe('Prestige upgrade definitions', () => {
|
describe('Prestige upgrade definitions', () => {
|
||||||
it('has exactly 13 prestige upgrades', async () => {
|
it('has exactly 11 prestige upgrades', async () => {
|
||||||
const { PRESTIGE_DEF } = await import('@/lib/game/constants/prestige');
|
const { PRESTIGE_DEF } = await import('@/lib/game/constants/prestige');
|
||||||
expect(Object.keys(PRESTIGE_DEF).length).toBe(13);
|
expect(Object.keys(PRESTIGE_DEF).length).toBe(11);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('all upgrades have required fields', async () => {
|
it('all upgrades have required fields', async () => {
|
||||||
@@ -43,10 +43,10 @@ describe('Prestige upgrade definitions', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('all 14 expected upgrade IDs are present', async () => {
|
it('all 11 expected upgrade IDs are present', async () => {
|
||||||
const { PRESTIGE_DEF } = await import('@/lib/game/constants/prestige');
|
const { PRESTIGE_DEF } = await import('@/lib/game/constants/prestige');
|
||||||
const expectedIds = [
|
const expectedIds = [
|
||||||
'manaWell', 'manaFlow', 'insightAmp', 'spireKey',
|
'insightAmp', 'spireKey',
|
||||||
'temporalEcho', 'steadyHand', 'ancientKnowledge', 'elementalAttune',
|
'temporalEcho', 'steadyHand', 'ancientKnowledge', 'elementalAttune',
|
||||||
'spellMemory', 'guardianPact', 'quickStart', 'elemStart',
|
'spellMemory', 'guardianPact', 'quickStart', 'elemStart',
|
||||||
'unlockedManaTypeCapacity',
|
'unlockedManaTypeCapacity',
|
||||||
|
|||||||
@@ -162,12 +162,9 @@ describe('deductSpellCost', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('computeMaxMana', () => {
|
describe('computeMaxMana', () => {
|
||||||
it('should return base 100 with no skills or upgrades', () => {
|
it('should return base 100 with no upgrades', () => {
|
||||||
const state = {
|
const state = {
|
||||||
skills: {} as Record<string, number>,
|
|
||||||
prestigeUpgrades: {},
|
prestigeUpgrades: {},
|
||||||
skillUpgrades: {},
|
|
||||||
skillTiers: {},
|
|
||||||
};
|
};
|
||||||
const effects = { maxManaBonus: 0, maxManaMultiplier: 1 } as any;
|
const effects = { maxManaBonus: 0, maxManaMultiplier: 1 } as any;
|
||||||
const result = computeMaxMana(state, effects);
|
const result = computeMaxMana(state, effects);
|
||||||
@@ -176,10 +173,7 @@ describe('computeMaxMana', () => {
|
|||||||
|
|
||||||
it('should include manaWell prestige upgrade', () => {
|
it('should include manaWell prestige upgrade', () => {
|
||||||
const state = {
|
const state = {
|
||||||
skills: {} as Record<string, number>,
|
|
||||||
prestigeUpgrades: { manaWell: 5 },
|
prestigeUpgrades: { manaWell: 5 },
|
||||||
skillUpgrades: {},
|
|
||||||
skillTiers: {},
|
|
||||||
};
|
};
|
||||||
const effects = { maxManaBonus: 0, maxManaMultiplier: 1 } as any;
|
const effects = { maxManaBonus: 0, maxManaMultiplier: 1 } as any;
|
||||||
const result = computeMaxMana(state, effects);
|
const result = computeMaxMana(state, effects);
|
||||||
@@ -188,10 +182,7 @@ describe('computeMaxMana', () => {
|
|||||||
|
|
||||||
it('should apply multiplier from effects', () => {
|
it('should apply multiplier from effects', () => {
|
||||||
const state = {
|
const state = {
|
||||||
skills: {} as Record<string, number>,
|
|
||||||
prestigeUpgrades: {},
|
prestigeUpgrades: {},
|
||||||
skillUpgrades: {},
|
|
||||||
skillTiers: {},
|
|
||||||
};
|
};
|
||||||
const effects = { maxManaBonus: 0, maxManaMultiplier: 1.5 } as any;
|
const effects = { maxManaBonus: 0, maxManaMultiplier: 1.5 } as any;
|
||||||
const result = computeMaxMana(state, effects);
|
const result = computeMaxMana(state, effects);
|
||||||
@@ -200,10 +191,7 @@ describe('computeMaxMana', () => {
|
|||||||
|
|
||||||
it('should apply bonus from effects', () => {
|
it('should apply bonus from effects', () => {
|
||||||
const state = {
|
const state = {
|
||||||
skills: {} as Record<string, number>,
|
|
||||||
prestigeUpgrades: {},
|
prestigeUpgrades: {},
|
||||||
skillUpgrades: {},
|
|
||||||
skillTiers: {},
|
|
||||||
};
|
};
|
||||||
const effects = { maxManaBonus: 50, maxManaMultiplier: 1 } as any;
|
const effects = { maxManaBonus: 50, maxManaMultiplier: 1 } as any;
|
||||||
const result = computeMaxMana(state, effects);
|
const result = computeMaxMana(state, effects);
|
||||||
@@ -212,43 +200,54 @@ describe('computeMaxMana', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('computeRegen', () => {
|
describe('computeRegen', () => {
|
||||||
it('should return base regen with no skills', () => {
|
it('should return base regen with no upgrades', () => {
|
||||||
const state = {
|
const state = {
|
||||||
skills: {} as Record<string, number>,
|
|
||||||
prestigeUpgrades: {} as Record<string, number>,
|
prestigeUpgrades: {} as Record<string, number>,
|
||||||
skillUpgrades: {} as Record<string, string[]>,
|
|
||||||
skillTiers: {} as Record<string, number>,
|
|
||||||
attunements: {} as Record<string, { active: boolean; level: number; experience: number }>,
|
attunements: {} as Record<string, { active: boolean; level: number; experience: number }>,
|
||||||
};
|
};
|
||||||
const effects = { regenBonus: 0, regenMultiplier: 1, permanentRegenBonus: 0 } as any;
|
const effects = { regenBonus: 0, regenMultiplier: 1, permanentRegenBonus: 0 } as any;
|
||||||
const result = computeRegen(state as any, effects);
|
const result = computeRegen(state as any, effects);
|
||||||
// Base regen is 2 (this test provides effects, so no attunement bonus)
|
|
||||||
expect(result).toBe(2);
|
expect(result).toBe(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('computeClickMana', () => {
|
describe('computeClickMana', () => {
|
||||||
it('should return base click mana with no skills', () => {
|
it('should return base click mana with no discipline', () => {
|
||||||
const state = {
|
const result = computeClickMana();
|
||||||
skills: {} as Record<string, number>,
|
expect(result).toBe(1);
|
||||||
};
|
});
|
||||||
const discipline = { bonuses: {}, multipliers: {} };
|
|
||||||
const result = computeClickMana(state.skills, discipline);
|
it('should return click mana with discipline bonus', () => {
|
||||||
expect(result).toBeGreaterThanOrEqual(1);
|
const discipline = { bonuses: { clickManaBonus: 3 }, multipliers: {} };
|
||||||
|
const result = computeClickMana(discipline);
|
||||||
|
expect(result).toBe(4);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getMeditationBonus', () => {
|
describe('getMeditationBonus', () => {
|
||||||
it('should return 1.0 with zero ticks', () => {
|
it('should return 1.0 with zero ticks', () => {
|
||||||
const result = getMeditationBonus(0, {});
|
const result = getMeditationBonus(0);
|
||||||
expect(result).toBe(1.0);
|
expect(result).toBe(1.0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should increase with more ticks', () => {
|
it('should increase with more ticks', () => {
|
||||||
const result1 = getMeditationBonus(10, {});
|
const result1 = getMeditationBonus(10);
|
||||||
const result2 = getMeditationBonus(100, {});
|
const result2 = getMeditationBonus(100);
|
||||||
expect(result2).toBeGreaterThan(result1);
|
expect(result2).toBeGreaterThan(result1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should follow continuous ramp formula', () => {
|
||||||
|
// At 4 hours: 1 + (4/8)*4 = 3.0
|
||||||
|
const ticksFor4Hours = 4 / 0.04;
|
||||||
|
const result = getMeditationBonus(ticksFor4Hours);
|
||||||
|
expect(result).toBeCloseTo(3.0, 5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should cap at 5.0', () => {
|
||||||
|
const ticksFor8Hours = 8 / 0.04;
|
||||||
|
const result = getMeditationBonus(ticksFor8Hours);
|
||||||
|
expect(result).toBeCloseTo(5.0, 5);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getIncursionStrength', () => {
|
describe('getIncursionStrength', () => {
|
||||||
|
|||||||
@@ -12,25 +12,14 @@ import { HOURS_PER_TICK } from '../constants';
|
|||||||
|
|
||||||
describe('computeMaxMana', () => {
|
describe('computeMaxMana', () => {
|
||||||
const baseState = {
|
const baseState = {
|
||||||
skills: {},
|
|
||||||
prestigeUpgrades: {},
|
prestigeUpgrades: {},
|
||||||
skillUpgrades: {},
|
|
||||||
skillTiers: {},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
it('should return base 100 with no skills or upgrades', () => {
|
it('should return base 100 with no upgrades', () => {
|
||||||
const result = computeMaxMana(baseState);
|
const result = computeMaxMana(baseState);
|
||||||
expect(result).toBe(100);
|
expect(result).toBe(100);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add 100 per manaWell skill level', () => {
|
|
||||||
const result = computeMaxMana({
|
|
||||||
...baseState,
|
|
||||||
skills: { manaWell: 3 },
|
|
||||||
});
|
|
||||||
expect(result).toBe(100 + 3 * 100);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add 500 per manaWell prestige upgrade', () => {
|
it('should add 500 per manaWell prestige upgrade', () => {
|
||||||
const result = computeMaxMana({
|
const result = computeMaxMana({
|
||||||
...baseState,
|
...baseState,
|
||||||
@@ -85,34 +74,15 @@ describe('computeMaxMana', () => {
|
|||||||
|
|
||||||
describe('computeRegen', () => {
|
describe('computeRegen', () => {
|
||||||
const baseState = {
|
const baseState = {
|
||||||
skills: {},
|
|
||||||
prestigeUpgrades: {},
|
prestigeUpgrades: {},
|
||||||
skillUpgrades: {},
|
|
||||||
skillTiers: {},
|
|
||||||
attunements: {},
|
attunements: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
it('should return base regen of 2 with no skills', () => {
|
it('should return base regen of 2 with no upgrades', () => {
|
||||||
const result = computeRegen(baseState);
|
const result = computeRegen(baseState);
|
||||||
expect(result).toBe(2);
|
expect(result).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add 1 per manaFlow skill level', () => {
|
|
||||||
const result = computeRegen({
|
|
||||||
...baseState,
|
|
||||||
skills: { manaFlow: 3 },
|
|
||||||
});
|
|
||||||
expect(result).toBe(5); // 2 + 3
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add 2 per manaSpring skill level', () => {
|
|
||||||
const result = computeRegen({
|
|
||||||
...baseState,
|
|
||||||
skills: { manaSpring: 2 },
|
|
||||||
});
|
|
||||||
expect(result).toBe(6); // 2 + 4
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add 0.5 per manaFlow prestige upgrade', () => {
|
it('should add 0.5 per manaFlow prestige upgrade', () => {
|
||||||
const result = computeRegen({
|
const result = computeRegen({
|
||||||
...baseState,
|
...baseState,
|
||||||
@@ -169,29 +139,14 @@ describe('computeRegen', () => {
|
|||||||
// ─── computeClickMana ─────────────────────────────────────────────────────────
|
// ─── computeClickMana ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
describe('computeClickMana', () => {
|
describe('computeClickMana', () => {
|
||||||
it('should return 1 with no skills', () => {
|
it('should return 1 with no discipline bonus', () => {
|
||||||
const result = computeClickMana({});
|
const result = computeClickMana();
|
||||||
expect(result).toBe(1);
|
expect(result).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add 1 per manaTap skill level', () => {
|
it('should add discipline click bonus', () => {
|
||||||
const result = computeClickMana({ manaTap: 3 });
|
const result = computeClickMana({
|
||||||
expect(result).toBe(4); // 1 + 3
|
bonuses: { clickManaBonus: 5 },
|
||||||
});
|
|
||||||
|
|
||||||
it('should add 3 per manaSurge skill level', () => {
|
|
||||||
const result = computeClickMana({ manaSurge: 2 });
|
|
||||||
expect(result).toBe(7); // 1 + 6
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should combine manaTap and manaSurge', () => {
|
|
||||||
const result = computeClickMana({ manaTap: 2, manaSurge: 1 });
|
|
||||||
expect(result).toBe(6); // 1 + 2 + 3
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add discipline click multiplier', () => {
|
|
||||||
const result = computeClickMana({}, {
|
|
||||||
bonuses: { clickManaMultiplier: 5 },
|
|
||||||
multipliers: {},
|
multipliers: {},
|
||||||
});
|
});
|
||||||
expect(result).toBe(6);
|
expect(result).toBe(6);
|
||||||
@@ -202,72 +157,49 @@ describe('computeClickMana', () => {
|
|||||||
|
|
||||||
describe('getMeditationBonus', () => {
|
describe('getMeditationBonus', () => {
|
||||||
it('should return 1.0 with zero ticks', () => {
|
it('should return 1.0 with zero ticks', () => {
|
||||||
expect(getMeditationBonus(0, {})).toBe(1.0);
|
expect(getMeditationBonus(0)).toBe(1.0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should increase with more ticks', () => {
|
it('should increase with more ticks', () => {
|
||||||
const low = getMeditationBonus(10, {});
|
const low = getMeditationBonus(10);
|
||||||
const high = getMeditationBonus(100, {});
|
const high = getMeditationBonus(100);
|
||||||
expect(high).toBeGreaterThan(low);
|
expect(high).toBeGreaterThan(low);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should cap at 1.5x without meditation skill', () => {
|
it('should follow continuous ramp: 1 + (hours/8)*4', () => {
|
||||||
// After 4 hours: 1 + min(4/4, 0.5) = 1.5
|
// At 2 hours: 1 + (2/8)*4 = 2.0
|
||||||
const ticksFor4Hours = 4 / HOURS_PER_TICK;
|
const ticksFor2Hours = 2 / HOURS_PER_TICK;
|
||||||
const result = getMeditationBonus(ticksFor4Hours, {});
|
const result = getMeditationBonus(ticksFor2Hours);
|
||||||
expect(result).toBeCloseTo(1.5, 5);
|
expect(result).toBeCloseTo(2.0, 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reach 2.5x with meditation skill after 4 hours', () => {
|
it('should cap at 5.0 with no discipline bonus', () => {
|
||||||
const ticksFor4Hours = 4 / HOURS_PER_TICK;
|
// At 8+ hours: cap at 5.0
|
||||||
const result = getMeditationBonus(ticksFor4Hours, { meditation: 1 });
|
|
||||||
expect(result).toBeCloseTo(2.5, 5);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reach 3.0x with deep trance after 6 hours', () => {
|
|
||||||
const ticksFor6Hours = 6 / HOURS_PER_TICK;
|
|
||||||
const result = getMeditationBonus(ticksFor6Hours, {
|
|
||||||
meditation: 1,
|
|
||||||
deepTrance: 1,
|
|
||||||
});
|
|
||||||
expect(result).toBeCloseTo(3.0, 5);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reach 5.0x with void meditation after 8 hours', () => {
|
|
||||||
const ticksFor8Hours = 8 / HOURS_PER_TICK;
|
const ticksFor8Hours = 8 / HOURS_PER_TICK;
|
||||||
const result = getMeditationBonus(ticksFor8Hours, {
|
const result = getMeditationBonus(ticksFor8Hours);
|
||||||
meditation: 1,
|
|
||||||
deepTrance: 1,
|
|
||||||
voidMeditation: 1,
|
|
||||||
});
|
|
||||||
expect(result).toBeCloseTo(5.0, 5);
|
expect(result).toBeCloseTo(5.0, 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reach cap at 2 hours (min(2/4, 0.5) = 0.5)', () => {
|
it('should ramp linearly: 1 hour → 1.5', () => {
|
||||||
// At 2 hours: min(2/4, 0.5) = min(0.5, 0.5) = 0.5 → 1.5x
|
|
||||||
const ticksFor2Hours = 2 / HOURS_PER_TICK;
|
|
||||||
const result = getMeditationBonus(ticksFor2Hours, {});
|
|
||||||
expect(result).toBeCloseTo(1.5, 5);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be below cap at 1 hour', () => {
|
|
||||||
const ticksFor1Hour = 1 / HOURS_PER_TICK;
|
const ticksFor1Hour = 1 / HOURS_PER_TICK;
|
||||||
const result = getMeditationBonus(ticksFor1Hour, {});
|
const result = getMeditationBonus(ticksFor1Hour);
|
||||||
expect(result).toBeLessThan(1.5);
|
expect(result).toBeCloseTo(1.5, 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should apply meditation efficiency multiplier', () => {
|
it('should apply meditation efficiency multiplier', () => {
|
||||||
const ticksFor4Hours = 4 / HOURS_PER_TICK;
|
const ticksFor4Hours = 4 / HOURS_PER_TICK;
|
||||||
const withoutEff = getMeditationBonus(ticksFor4Hours, {}, 1);
|
const withoutEff = getMeditationBonus(ticksFor4Hours, 1);
|
||||||
const withEff = getMeditationBonus(ticksFor4Hours, {}, 2);
|
const withEff = getMeditationBonus(ticksFor4Hours, 2);
|
||||||
expect(withEff).toBeCloseTo(withoutEff * 2, 5);
|
expect(withEff).toBeCloseTo(withoutEff * 2, 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should ramp linearly before cap', () => {
|
it('should respect discipline meditation cap bonus', () => {
|
||||||
// At 1 hour: 1 + min(1/4, 0.5) = 1 + 0.25 = 1.25
|
// With +3.5 discipline cap, max = 8.5
|
||||||
const ticksFor1Hour = 1 / HOURS_PER_TICK;
|
// Need more than 8 hours for the higher cap to matter
|
||||||
const result = getMeditationBonus(ticksFor1Hour, {});
|
// At 16 hours without cap: 1 + (16/8)*4 = 9.0, capped to 8.5
|
||||||
expect(result).toBeCloseTo(1.25, 5);
|
const ticksFor16Hours = 16 / HOURS_PER_TICK;
|
||||||
|
const result = getMeditationBonus(ticksFor16Hours, 1, 3.5);
|
||||||
|
expect(result).toBeCloseTo(8.5, 5);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -275,10 +207,7 @@ describe('getMeditationBonus', () => {
|
|||||||
|
|
||||||
describe('computeEffectiveRegenForDisplay', () => {
|
describe('computeEffectiveRegenForDisplay', () => {
|
||||||
const baseState = {
|
const baseState = {
|
||||||
skills: {},
|
|
||||||
prestigeUpgrades: {},
|
prestigeUpgrades: {},
|
||||||
skillUpgrades: {},
|
|
||||||
skillTiers: {},
|
|
||||||
attunements: {},
|
attunements: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -186,15 +186,16 @@ describe('PrestigeStore', () => {
|
|||||||
|
|
||||||
describe('doPrestige', () => {
|
describe('doPrestige', () => {
|
||||||
it('should purchase upgrade when affordable', () => {
|
it('should purchase upgrade when affordable', () => {
|
||||||
const result = usePrestigeStore.getState().doPrestige('manaWell');
|
usePrestigeStore.setState({ insight: 2000 });
|
||||||
|
const result = usePrestigeStore.getState().doPrestige('insightAmp');
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
expect(usePrestigeStore.getState().prestigeUpgrades.manaWell).toBe(1);
|
expect(usePrestigeStore.getState().prestigeUpgrades.insightAmp).toBe(1);
|
||||||
expect(usePrestigeStore.getState().insight).toBeLessThan(500);
|
expect(usePrestigeStore.getState().insight).toBeLessThan(2000);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false when cannot afford', () => {
|
it('should return false when cannot afford', () => {
|
||||||
usePrestigeStore.setState({ insight: 0 });
|
usePrestigeStore.setState({ insight: 0 });
|
||||||
const result = usePrestigeStore.getState().doPrestige('manaWell');
|
const result = usePrestigeStore.getState().doPrestige('insightAmp');
|
||||||
expect(result.success).toBe(false);
|
expect(result.success).toBe(false);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
expect(result.code).toBe(ErrorCode.INSUFFICIENT_INSIGHT);
|
expect(result.code).toBe(ErrorCode.INSUFFICIENT_INSIGHT);
|
||||||
@@ -320,9 +321,9 @@ describe('PrestigeStore', () => {
|
|||||||
|
|
||||||
describe('resetPrestigeForNewLoop', () => {
|
describe('resetPrestigeForNewLoop', () => {
|
||||||
it('should preserve insight and upgrades, reset loop state', () => {
|
it('should preserve insight and upgrades, reset loop state', () => {
|
||||||
usePrestigeStore.getState().resetPrestigeForNewLoop(200, { manaWell: 2 });
|
usePrestigeStore.getState().resetPrestigeForNewLoop(200, { insightAmp: 2 });
|
||||||
expect(usePrestigeStore.getState().insight).toBe(200);
|
expect(usePrestigeStore.getState().insight).toBe(200);
|
||||||
expect(usePrestigeStore.getState().prestigeUpgrades).toEqual({ manaWell: 2 });
|
expect(usePrestigeStore.getState().prestigeUpgrades).toEqual({ insightAmp: 2 });
|
||||||
|
|
||||||
expect(usePrestigeStore.getState().defeatedGuardians).toEqual([]);
|
expect(usePrestigeStore.getState().defeatedGuardians).toEqual([]);
|
||||||
});
|
});
|
||||||
@@ -330,7 +331,7 @@ describe('PrestigeStore', () => {
|
|||||||
|
|
||||||
describe('resetPrestige', () => {
|
describe('resetPrestige', () => {
|
||||||
it('should reset everything to initial state', () => {
|
it('should reset everything to initial state', () => {
|
||||||
usePrestigeStore.setState({ insight: 1000, loopCount: 5, prestigeUpgrades: { manaWell: 3 } });
|
usePrestigeStore.setState({ insight: 1000, loopCount: 5, prestigeUpgrades: { insightAmp: 3 } });
|
||||||
usePrestigeStore.getState().resetPrestige();
|
usePrestigeStore.getState().resetPrestige();
|
||||||
expect(usePrestigeStore.getState().insight).toBe(0);
|
expect(usePrestigeStore.getState().insight).toBe(0);
|
||||||
expect(usePrestigeStore.getState().loopCount).toBe(0);
|
expect(usePrestigeStore.getState().loopCount).toBe(0);
|
||||||
|
|||||||
@@ -16,12 +16,11 @@ export const RARITY_COLORS: Record<string, string> = {
|
|||||||
mythic: '#EF4444',
|
mythic: '#EF4444',
|
||||||
};
|
};
|
||||||
|
|
||||||
// ─── Study Speed Formula ─────────────────────────────────────────────────────
|
// ─── Study Multipliers (skill system removed, always base value) ────────────
|
||||||
export function getStudySpeedMultiplier(skills: Record<string, number>): number {
|
export function getStudySpeedMultiplier(): number {
|
||||||
return 1 + (skills.quickLearner || 0) * 0.1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Study Cost Formula ───────────────────────────────────────────────────────
|
export function getStudyCostMultiplier(): number {
|
||||||
export function getStudyCostMultiplier(skills: Record<string, number>): number {
|
return 1;
|
||||||
return 1 - (skills.focusedMind || 0) * 0.05;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
import type { PrestigeDef } from '../types';
|
import type { PrestigeDef } from '../types';
|
||||||
|
|
||||||
export const PRESTIGE_DEF: Record<string, PrestigeDef> = {
|
export const PRESTIGE_DEF: Record<string, PrestigeDef> = {
|
||||||
manaWell: { name: "Mana Well", desc: "+500 starting max mana", max: 5, cost: 500 },
|
|
||||||
manaFlow: { name: "Mana Flow", desc: "+0.5 regen/sec permanently", max: 10, cost: 750 },
|
|
||||||
insightAmp: { name: "Insight Amp", desc: "+25% insight gain", max: 4, cost: 1500 },
|
insightAmp: { name: "Insight Amp", desc: "+25% insight gain", max: 4, cost: 1500 },
|
||||||
spireKey: { name: "Spire Key", desc: "Start at floor +2", max: 5, cost: 4000 },
|
spireKey: { name: "Spire Key", desc: "Start at floor +2", max: 5, cost: 4000 },
|
||||||
temporalEcho: { name: "Temporal Echo", desc: "+10% mana generation", max: 5, cost: 3000 },
|
temporalEcho: { name: "Temporal Echo", desc: "+10% mana generation", max: 5, cost: 3000 },
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ import { hasSpecial, SPECIAL_EFFECTS } from '../effects/special-effects';
|
|||||||
|
|
||||||
export interface DesignActionsParams {
|
export interface DesignActionsParams {
|
||||||
skills: Record<string, number>;
|
skills: Record<string, number>;
|
||||||
skillUpgrades: Record<string, string[]>;
|
|
||||||
skillTiers: Record<string, number>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function startDesigningEnchantment(
|
export function startDesigningEnchantment(
|
||||||
@@ -33,14 +31,13 @@ export function startDesigningEnchantment(
|
|||||||
const equipType = CraftingUtils.getEquipmentType(equipmentTypeId);
|
const equipType = CraftingUtils.getEquipmentType(equipmentTypeId);
|
||||||
if (!equipType) return false;
|
if (!equipType) return false;
|
||||||
|
|
||||||
const efficiencyBonus = ((params.skillUpgrades || {})['efficientEnchant'] || [])?.length * 0.05 || 0;
|
const totalCapacityCost = CraftingDesign.calculateDesignCapacityCost(effects, 0);
|
||||||
const totalCapacityCost = CraftingDesign.calculateDesignCapacityCost(effects, efficiencyBonus);
|
|
||||||
|
|
||||||
if (totalCapacityCost > equipType.baseCapacity) {
|
if (totalCapacityCost > equipType.baseCapacity) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const computedEffects = computeEffects(params.skillUpgrades || {}, params.skillTiers || {});
|
const computedEffects = computeEffects();
|
||||||
const hasEnchantMastery = hasSpecial(computedEffects, SPECIAL_EFFECTS.ENCHANT_MASTERY);
|
const hasEnchantMastery = hasSpecial(computedEffects, SPECIAL_EFFECTS.ENCHANT_MASTERY);
|
||||||
|
|
||||||
let updates: Partial<CraftingState> = {};
|
let updates: Partial<CraftingState> = {};
|
||||||
|
|||||||
@@ -61,10 +61,19 @@ export const baseDisciplines: DisciplineDefinition[] = [
|
|||||||
type: 'capped',
|
type: 'capped',
|
||||||
threshold: 100,
|
threshold: 100,
|
||||||
value: 100,
|
value: 100,
|
||||||
description: 'Every 100 XP: +0.5 max meditation cap (7 tiers, up to +3.5). Raises Void Meditation ceiling.',
|
description: 'Every 100 XP: +0.5 to meditation multiplier (7 tiers, up to +3.5). Adds directly to the meditation multiplier cap.',
|
||||||
bonus: { stat: 'meditationCapBonus', amount: 0.5 },
|
bonus: { stat: 'meditationCapBonus', amount: 0.5 },
|
||||||
maxTier: 7,
|
maxTier: 7,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'mana-circulation-click',
|
||||||
|
type: 'capped',
|
||||||
|
threshold: 500,
|
||||||
|
value: 500,
|
||||||
|
description: 'Every 500 XP: +1 mana per click (5 tiers, up to +5).',
|
||||||
|
bonus: { stat: 'clickManaBonus', amount: 1 },
|
||||||
|
maxTier: 5,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+4
-25
@@ -88,12 +88,10 @@ export interface UnifiedEffects extends ComputedEffects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function computeAllEffects(
|
export function computeAllEffects(
|
||||||
skillUpgrades: Record<string, string[]>,
|
|
||||||
skillTiers: Record<string, number>,
|
|
||||||
equipmentInstances: Record<string, EquipmentInstance>,
|
equipmentInstances: Record<string, EquipmentInstance>,
|
||||||
equippedInstances: Record<string, string | null>,
|
equippedInstances: Record<string, string | null>,
|
||||||
): UnifiedEffects {
|
): UnifiedEffects {
|
||||||
const upgradeEffects = computeEffects(skillUpgrades, skillTiers);
|
const upgradeEffects = computeEffects();
|
||||||
const equipmentEffects = computeEquipmentEffects(equipmentInstances, equippedInstances, upgradeEffects.enchantmentPowerMultiplier);
|
const equipmentEffects = computeEquipmentEffects(equipmentInstances, equippedInstances, upgradeEffects.enchantmentPowerMultiplier);
|
||||||
const disciplineEffects = computeDisciplineEffects();
|
const disciplineEffects = computeDisciplineEffects();
|
||||||
|
|
||||||
@@ -105,9 +103,6 @@ export function computeAllEffects(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 };
|
const perElementRegenBonus: Record<string, number> = { ...upgradeEffects.perElementRegenBonus };
|
||||||
|
|
||||||
// Merge per-element cap bonuses from discipline effects (elementCap_{element})
|
// Merge per-element cap bonuses from discipline effects (elementCap_{element})
|
||||||
@@ -158,14 +153,10 @@ export function computeAllEffects(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getUnifiedEffects(state: {
|
export function getUnifiedEffects(state: {
|
||||||
skillUpgrades?: Record<string, string[]>;
|
|
||||||
skillTiers?: Record<string, number>;
|
|
||||||
equipmentInstances?: Record<string, EquipmentInstance>;
|
equipmentInstances?: Record<string, EquipmentInstance>;
|
||||||
equippedInstances?: Record<string, string | null>;
|
equippedInstances?: Record<string, string | null>;
|
||||||
}): UnifiedEffects {
|
}): UnifiedEffects {
|
||||||
return computeAllEffects(
|
return computeAllEffects(
|
||||||
state.skillUpgrades || {},
|
|
||||||
state.skillTiers || {},
|
|
||||||
state.equipmentInstances || {},
|
state.equipmentInstances || {},
|
||||||
state.equippedInstances || {},
|
state.equippedInstances || {},
|
||||||
);
|
);
|
||||||
@@ -175,28 +166,21 @@ export function getUnifiedEffects(state: {
|
|||||||
|
|
||||||
export function computeTotalMaxMana(
|
export function computeTotalMaxMana(
|
||||||
state: {
|
state: {
|
||||||
skills?: Record<string, number>;
|
|
||||||
prestigeUpgrades?: Record<string, number>;
|
prestigeUpgrades?: Record<string, number>;
|
||||||
skillUpgrades?: Record<string, string[]>;
|
|
||||||
skillTiers?: Record<string, number>;
|
|
||||||
equipmentInstances?: Record<string, EquipmentInstance>;
|
equipmentInstances?: Record<string, EquipmentInstance>;
|
||||||
equippedInstances?: Record<string, string | null>;
|
equippedInstances?: Record<string, string | null>;
|
||||||
},
|
},
|
||||||
effects?: UnifiedEffects
|
effects?: UnifiedEffects
|
||||||
): number {
|
): number {
|
||||||
const pu = state.prestigeUpgrades;
|
const pu = state.prestigeUpgrades;
|
||||||
const skillMult = effects?.skillLevelMultiplier || 1;
|
const base = 100 + ((pu || {}).manaWell || 0) * 500;
|
||||||
const base = 100 + ((state.skills || {}).manaWell || 0) * 100 * skillMult + ((pu || {}).manaWell || 0) * 500;
|
|
||||||
const resolvedEffects = effects || getUnifiedEffects(state);
|
const resolvedEffects = effects || getUnifiedEffects(state);
|
||||||
return Math.floor((base + resolvedEffects.maxManaBonus) * resolvedEffects.maxManaMultiplier);
|
return Math.floor((base + resolvedEffects.maxManaBonus) * resolvedEffects.maxManaMultiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function computeTotalRegen(
|
export function computeTotalRegen(
|
||||||
state: {
|
state: {
|
||||||
skills?: Record<string, number>;
|
|
||||||
prestigeUpgrades?: Record<string, number>;
|
prestigeUpgrades?: Record<string, number>;
|
||||||
skillUpgrades?: Record<string, string[]>;
|
|
||||||
skillTiers?: Record<string, number>;
|
|
||||||
equipmentInstances?: Record<string, EquipmentInstance>;
|
equipmentInstances?: Record<string, EquipmentInstance>;
|
||||||
equippedInstances?: Record<string, string | null>;
|
equippedInstances?: Record<string, string | null>;
|
||||||
},
|
},
|
||||||
@@ -204,8 +188,7 @@ export function computeTotalRegen(
|
|||||||
): number {
|
): number {
|
||||||
const pu = state.prestigeUpgrades;
|
const pu = state.prestigeUpgrades;
|
||||||
const temporalBonus = 1 + ((pu?.temporalEcho || 0)) * 0.1;
|
const temporalBonus = 1 + ((pu?.temporalEcho || 0)) * 0.1;
|
||||||
const skillMult = effects?.skillLevelMultiplier || 1;
|
const base = 2 + (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;
|
let regen = base * temporalBonus;
|
||||||
const resolvedEffects = effects || getUnifiedEffects(state);
|
const resolvedEffects = effects || getUnifiedEffects(state);
|
||||||
regen = (regen + resolvedEffects.regenBonus + resolvedEffects.permanentRegenBonus) * resolvedEffects.regenMultiplier;
|
regen = (regen + resolvedEffects.regenBonus + resolvedEffects.permanentRegenBonus) * resolvedEffects.regenMultiplier;
|
||||||
@@ -214,16 +197,12 @@ export function computeTotalRegen(
|
|||||||
|
|
||||||
export function computeTotalClickMana(
|
export function computeTotalClickMana(
|
||||||
state: {
|
state: {
|
||||||
skills?: Record<string, number>;
|
|
||||||
skillUpgrades?: Record<string, string[]>;
|
|
||||||
skillTiers?: Record<string, number>;
|
|
||||||
equipmentInstances?: Record<string, EquipmentInstance>;
|
equipmentInstances?: Record<string, EquipmentInstance>;
|
||||||
equippedInstances?: Record<string, string | null>;
|
equippedInstances?: Record<string, string | null>;
|
||||||
},
|
},
|
||||||
effects?: UnifiedEffects
|
effects?: UnifiedEffects
|
||||||
): number {
|
): number {
|
||||||
const skillMult = effects?.skillLevelMultiplier || 1;
|
const base = 1;
|
||||||
const base = 1 + (state.skills?.manaTap || 0) * 1 * skillMult + (state.skills?.manaSurge || 0) * 3 * skillMult;
|
|
||||||
const resolvedEffects = effects || getUnifiedEffects(state);
|
const resolvedEffects = effects || getUnifiedEffects(state);
|
||||||
return Math.floor((base + resolvedEffects.clickManaBonus) * resolvedEffects.clickManaMultiplier);
|
return Math.floor((base + resolvedEffects.clickManaBonus) * resolvedEffects.clickManaMultiplier);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,10 +18,7 @@ function _buildUpgradeCache(): void {
|
|||||||
* Get all selected upgrades with their full effect definitions.
|
* Get all selected upgrades with their full effect definitions.
|
||||||
* Since skills are removed, always returns empty array.
|
* Since skills are removed, always returns empty array.
|
||||||
*/
|
*/
|
||||||
export function getActiveUpgrades(
|
export function getActiveUpgrades(): ActiveUpgradeEffect[] {
|
||||||
_skillUpgrades: Record<string, string[]>,
|
|
||||||
_skillTiers: Record<string, number>
|
|
||||||
): ActiveUpgradeEffect[] {
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,10 +26,7 @@ export function getActiveUpgrades(
|
|||||||
* Compute all active effects from selected upgrades.
|
* Compute all active effects from selected upgrades.
|
||||||
* Since skills are removed, returns base values with no upgrades applied.
|
* Since skills are removed, returns base values with no upgrades applied.
|
||||||
*/
|
*/
|
||||||
export function computeEffects(
|
export function computeEffects(): ComputedEffects {
|
||||||
_skillUpgrades: Record<string, string[]>,
|
|
||||||
_skillTiers: Record<string, number>
|
|
||||||
): ComputedEffects {
|
|
||||||
return {
|
return {
|
||||||
maxManaMultiplier: 1,
|
maxManaMultiplier: 1,
|
||||||
maxManaBonus: 0,
|
maxManaBonus: 0,
|
||||||
|
|||||||
@@ -39,17 +39,17 @@ export function useManaStats() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const maxMana = useMemo(
|
const maxMana = useMemo(
|
||||||
() => computeMaxMana({ skills: {}, prestigeUpgrades, skillUpgrades: {}, skillTiers: {} }, upgradeEffects),
|
() => computeMaxMana({ prestigeUpgrades }, upgradeEffects),
|
||||||
[prestigeUpgrades, upgradeEffects]
|
[prestigeUpgrades, upgradeEffects]
|
||||||
);
|
);
|
||||||
|
|
||||||
const baseRegen = useMemo(
|
const baseRegen = useMemo(
|
||||||
() => computeRegen({ skills: {} as Record<string, number>, prestigeUpgrades, attunements: {} } as any, upgradeEffects),
|
() => computeRegen({ prestigeUpgrades, attunements: {} } as any, upgradeEffects),
|
||||||
[prestigeUpgrades, upgradeEffects]
|
[prestigeUpgrades, upgradeEffects]
|
||||||
);
|
);
|
||||||
|
|
||||||
const clickMana = useMemo(
|
const clickMana = useMemo(
|
||||||
() => computeClickMana({}),
|
() => computeClickMana(),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ export function useCombatStats() {
|
|||||||
const attackSpeedMult = upgradeEffects.attackSpeedMultiplier;
|
const attackSpeedMult = upgradeEffects.attackSpeedMultiplier;
|
||||||
const totalCastSpeed = spellCastSpeed * quickCastBonus * attackSpeedMult;
|
const totalCastSpeed = spellCastSpeed * quickCastBonus * attackSpeedMult;
|
||||||
|
|
||||||
const damagePerCast = calcDamage({ skills: {}, signedPacts }, activeSpell, floorElem);
|
const damagePerCast = calcDamage({ signedPacts }, activeSpell, floorElem);
|
||||||
const castsPerSecond = totalCastSpeed * HOURS_PER_TICK / (TICK_MS / 1000);
|
const castsPerSecond = totalCastSpeed * HOURS_PER_TICK / (TICK_MS / 1000);
|
||||||
|
|
||||||
return damagePerCast * castsPerSecond;
|
return damagePerCast * castsPerSecond;
|
||||||
@@ -195,7 +195,7 @@ export function useCombatStats() {
|
|||||||
precisionChance,
|
precisionChance,
|
||||||
elemBonus,
|
elemBonus,
|
||||||
elemBonusText,
|
elemBonusText,
|
||||||
total: calcDamage({ skills: {}, signedPacts }, activeSpell, floorElem),
|
total: calcDamage({ signedPacts }, activeSpell, floorElem),
|
||||||
};
|
};
|
||||||
}, [activeSpellDef, signedPacts, activeSpell, floorElem, isGuardianFloor, pactMultiplier]);
|
}, [activeSpellDef, signedPacts, activeSpell, floorElem, isGuardianFloor, pactMultiplier]);
|
||||||
|
|
||||||
@@ -217,17 +217,17 @@ export function useCombatStats() {
|
|||||||
*/
|
*/
|
||||||
export function useStudyStats() {
|
export function useStudyStats() {
|
||||||
const studySpeedMult = useMemo(
|
const studySpeedMult = useMemo(
|
||||||
() => getStudySpeedMultiplier({}),
|
() => getStudySpeedMultiplier(),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const studyCostMult = useMemo(
|
const studyCostMult = useMemo(
|
||||||
() => getStudyCostMultiplier({}),
|
() => getStudyCostMultiplier(),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const upgradeEffects = useMemo(
|
const upgradeEffects = useMemo(
|
||||||
() => computeEffects({}, {}),
|
() => computeEffects(),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ export function processCombatTick(
|
|||||||
// Calculate base damage
|
// Calculate base damage
|
||||||
const floorElement = getFloorElement(currentFloor);
|
const floorElement = getFloorElement(currentFloor);
|
||||||
const damage = calcDamage(
|
const damage = calcDamage(
|
||||||
{ skills: {}, signedPacts },
|
{ signedPacts },
|
||||||
spellId,
|
spellId,
|
||||||
floorElement,
|
floorElement,
|
||||||
disciplineEffects,
|
disciplineEffects,
|
||||||
@@ -151,7 +151,7 @@ export function processCombatTick(
|
|||||||
// Calculate damage
|
// Calculate damage
|
||||||
const eFloorElement = getFloorElement(currentFloor);
|
const eFloorElement = getFloorElement(currentFloor);
|
||||||
const eDamage = calcDamage(
|
const eDamage = calcDamage(
|
||||||
{ skills: {}, signedPacts },
|
{ signedPacts },
|
||||||
eSpell.spellId,
|
eSpell.spellId,
|
||||||
eFloorElement,
|
eFloorElement,
|
||||||
disciplineEffects,
|
disciplineEffects,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export const createResetGame = (set: (state: Partial<GameCoordinatorState>) => v
|
|||||||
|
|
||||||
useUIStore.getState().reset();
|
useUIStore.getState().reset();
|
||||||
usePrestigeStore.getState().resetPrestige();
|
usePrestigeStore.getState().resetPrestige();
|
||||||
useManaStore.getState().resetMana({}, {}, {}, {});
|
useManaStore.getState().resetMana({});
|
||||||
useCombatStore.getState().resetCombat(startFloor);
|
useCombatStore.getState().resetCombat(startFloor);
|
||||||
|
|
||||||
set({
|
set({
|
||||||
@@ -43,19 +43,11 @@ export const createGatherMana = () => () => {
|
|||||||
const prestigeState = usePrestigeStore.getState();
|
const prestigeState = usePrestigeStore.getState();
|
||||||
const disciplineEffects = computeDisciplineEffects();
|
const disciplineEffects = computeDisciplineEffects();
|
||||||
|
|
||||||
// Compute click mana with discipline bonuses (mana-channeling → clickManaMultiplier)
|
// Compute click mana with discipline bonuses
|
||||||
const cm = computeClickMana(
|
const cm = computeClickMana(disciplineEffects);
|
||||||
{},
|
|
||||||
disciplineEffects,
|
|
||||||
);
|
|
||||||
|
|
||||||
const max = computeMaxMana(
|
const max = computeMaxMana(
|
||||||
{
|
{ prestigeUpgrades: prestigeState.prestigeUpgrades },
|
||||||
skills: {},
|
|
||||||
prestigeUpgrades: prestigeState.prestigeUpgrades,
|
|
||||||
skillUpgrades: {},
|
|
||||||
skillTiers: {}
|
|
||||||
},
|
|
||||||
undefined,
|
undefined,
|
||||||
disciplineEffects,
|
disciplineEffects,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ export function useUnifiedEffects() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...getUnifiedEffects({
|
...getUnifiedEffects({
|
||||||
skillUpgrades: {},
|
|
||||||
skillTiers: {},
|
|
||||||
equippedInstances,
|
equippedInstances,
|
||||||
equipmentInstances,
|
equipmentInstances,
|
||||||
}),
|
}),
|
||||||
@@ -55,27 +53,25 @@ export function useManaStats() {
|
|||||||
const disciplineEffects = computeDisciplineEffects();
|
const disciplineEffects = computeDisciplineEffects();
|
||||||
|
|
||||||
const upgradeEffects = getUnifiedEffects({
|
const upgradeEffects = getUnifiedEffects({
|
||||||
skillUpgrades: {},
|
|
||||||
skillTiers: {},
|
|
||||||
equippedInstances,
|
equippedInstances,
|
||||||
equipmentInstances,
|
equipmentInstances,
|
||||||
});
|
});
|
||||||
|
|
||||||
const maxMana = computeMaxMana(
|
const maxMana = computeMaxMana(
|
||||||
{ skills: {}, prestigeUpgrades, skillUpgrades: {}, skillTiers: {} },
|
{ prestigeUpgrades },
|
||||||
upgradeEffects,
|
upgradeEffects,
|
||||||
disciplineEffects,
|
disciplineEffects,
|
||||||
);
|
);
|
||||||
|
|
||||||
const baseRegen = computeRegen(
|
const baseRegen = computeRegen(
|
||||||
{ skills: {}, prestigeUpgrades, skillUpgrades: {}, skillTiers: {}, attunements: {} },
|
{ prestigeUpgrades, attunements: {} },
|
||||||
upgradeEffects,
|
upgradeEffects,
|
||||||
disciplineEffects,
|
disciplineEffects,
|
||||||
);
|
);
|
||||||
|
|
||||||
const clickMana = computeClickMana({}, disciplineEffects);
|
const clickMana = computeClickMana(disciplineEffects);
|
||||||
|
|
||||||
const meditationMultiplier = getMeditationBonus(meditateTicks, {}, upgradeEffects.meditationEfficiency, disciplineEffects.meditationCapBonus);
|
const meditationMultiplier = getMeditationBonus(meditateTicks, upgradeEffects.meditationEfficiency, disciplineEffects.meditationCapBonus);
|
||||||
const incursionStrength = getIncursionStrength(day, hour);
|
const incursionStrength = getIncursionStrength(day, hour);
|
||||||
const effectiveRegenWithSpecials = baseRegen * (1 - incursionStrength);
|
const effectiveRegenWithSpecials = baseRegen * (1 - incursionStrength);
|
||||||
|
|
||||||
@@ -113,8 +109,6 @@ export function useCombatStats() {
|
|||||||
const equipmentInstances = useCraftingStore((s) => s.equipmentInstances);
|
const equipmentInstances = useCraftingStore((s) => s.equipmentInstances);
|
||||||
|
|
||||||
const upgradeEffects = getUnifiedEffects({
|
const upgradeEffects = getUnifiedEffects({
|
||||||
skillUpgrades: {},
|
|
||||||
skillTiers: {},
|
|
||||||
equippedInstances,
|
equippedInstances,
|
||||||
equipmentInstances,
|
equipmentInstances,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ export const createStartNewLoop = (set: (state: Partial<GameCoordinatorState>) =
|
|||||||
totalManaGathered: manaState.totalManaGathered,
|
totalManaGathered: manaState.totalManaGathered,
|
||||||
signedPacts: prestigeState.signedPacts,
|
signedPacts: prestigeState.signedPacts,
|
||||||
prestigeUpgrades: prestigeState.prestigeUpgrades,
|
prestigeUpgrades: prestigeState.prestigeUpgrades,
|
||||||
skills: {},
|
|
||||||
}, disciplineEffects);
|
}, disciplineEffects);
|
||||||
|
|
||||||
const total = prestigeState.insight + insightGained;
|
const total = prestigeState.insight + insightGained;
|
||||||
@@ -41,7 +40,7 @@ export const createStartNewLoop = (set: (state: Partial<GameCoordinatorState>) =
|
|||||||
);
|
);
|
||||||
usePrestigeStore.getState().incrementLoopCount();
|
usePrestigeStore.getState().incrementLoopCount();
|
||||||
|
|
||||||
useManaStore.getState().resetMana(pu, {}, {}, {});
|
useManaStore.getState().resetMana(pu);
|
||||||
|
|
||||||
// Reset combat with starting floor and any spells from prestige upgrades
|
// Reset combat with starting floor and any spells from prestige upgrades
|
||||||
const startSpells = makeInitialSpells();
|
const startSpells = makeInitialSpells();
|
||||||
|
|||||||
@@ -105,12 +105,12 @@ export const useGameStore = create<GameCoordinatorStore>()(
|
|||||||
const effects = { specials: allSpecials } as ComputedEffects;
|
const effects = { specials: allSpecials } as ComputedEffects;
|
||||||
|
|
||||||
const maxMana = computeMaxMana(
|
const maxMana = computeMaxMana(
|
||||||
{ skills: {}, prestigeUpgrades: ctx.prestige.prestigeUpgrades, skillUpgrades: {}, skillTiers: {} },
|
{ prestigeUpgrades: ctx.prestige.prestigeUpgrades },
|
||||||
undefined,
|
undefined,
|
||||||
disciplineEffects,
|
disciplineEffects,
|
||||||
);
|
);
|
||||||
const baseRegen = computeRegen(
|
const baseRegen = computeRegen(
|
||||||
{ skills: {}, prestigeUpgrades: ctx.prestige.prestigeUpgrades, skillUpgrades: {}, skillTiers: {}, attunements: {} },
|
{ prestigeUpgrades: ctx.prestige.prestigeUpgrades, attunements: {} },
|
||||||
undefined,
|
undefined,
|
||||||
disciplineEffects,
|
disciplineEffects,
|
||||||
) * (1 + (disciplineEffects.multipliers.regenMultiplier || 0));
|
) * (1 + (disciplineEffects.multipliers.regenMultiplier || 0));
|
||||||
@@ -129,7 +129,6 @@ export const useGameStore = create<GameCoordinatorStore>()(
|
|||||||
totalManaGathered: ctx.mana.totalManaGathered,
|
totalManaGathered: ctx.mana.totalManaGathered,
|
||||||
signedPacts: ctx.prestige.signedPacts,
|
signedPacts: ctx.prestige.signedPacts,
|
||||||
prestigeUpgrades: ctx.prestige.prestigeUpgrades,
|
prestigeUpgrades: ctx.prestige.prestigeUpgrades,
|
||||||
skills: {} as Record<string, number>,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check for loop end
|
// Check for loop end
|
||||||
@@ -163,7 +162,7 @@ export const useGameStore = create<GameCoordinatorStore>()(
|
|||||||
|
|
||||||
if (ctx.combat.currentAction === 'meditate') {
|
if (ctx.combat.currentAction === 'meditate') {
|
||||||
meditateTicks++;
|
meditateTicks++;
|
||||||
meditationMultiplier = getMeditationBonus(meditateTicks, {}, 1, disciplineEffects.meditationCapBonus);
|
meditationMultiplier = getMeditationBonus(meditateTicks, 1, disciplineEffects.meditationCapBonus);
|
||||||
} else {
|
} else {
|
||||||
meditateTicks = 0;
|
meditateTicks = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,9 +45,6 @@ export interface ManaActions {
|
|||||||
// Reset
|
// Reset
|
||||||
resetMana: (
|
resetMana: (
|
||||||
prestigeUpgrades: Record<string, number>,
|
prestigeUpgrades: Record<string, number>,
|
||||||
skills?: Record<string, number>,
|
|
||||||
skillUpgrades?: Record<string, string[]>,
|
|
||||||
skillTiers?: Record<string, number>
|
|
||||||
) => void;
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,9 +190,6 @@ export const useManaStore = create<ManaStore>()(
|
|||||||
|
|
||||||
resetMana: (
|
resetMana: (
|
||||||
prestigeUpgrades: Record<string, number>,
|
prestigeUpgrades: Record<string, number>,
|
||||||
_skills: Record<string, number> = {},
|
|
||||||
_skillUpgrades: Record<string, string[]> = {},
|
|
||||||
_skillTiers: Record<string, number> = {}
|
|
||||||
) => {
|
) => {
|
||||||
const elementMax = 10 + (prestigeUpgrades.elemMax || 0) * 5;
|
const elementMax = 10 + (prestigeUpgrades.elemMax || 0) * 5;
|
||||||
const startingMana = 10 + (prestigeUpgrades.manaStart || 0) * 10;
|
const startingMana = 10 + (prestigeUpgrades.manaStart || 0) * 10;
|
||||||
|
|||||||
@@ -166,12 +166,6 @@ export interface GameState {
|
|||||||
// Spells
|
// Spells
|
||||||
spells: Record<string, SpellState>;
|
spells: Record<string, SpellState>;
|
||||||
|
|
||||||
// Skills
|
|
||||||
skills: Record<string, number>;
|
|
||||||
skillProgress: Record<string, number>; // Saved study progress for skills
|
|
||||||
skillUpgrades: Record<string, string[]>; // Selected upgrade IDs per skill
|
|
||||||
skillTiers: Record<string, number>; // Current tier for each base skill
|
|
||||||
|
|
||||||
// Equipment System (new instance-based system)
|
// Equipment System (new instance-based system)
|
||||||
equippedInstances: Record<string, string | null>; // slot -> instanceId
|
equippedInstances: Record<string, string | null>; // slot -> instanceId
|
||||||
equipmentInstances: Record<string, EquipmentInstance>; // instanceId -> instance
|
equipmentInstances: Record<string, EquipmentInstance>; // instanceId -> instance
|
||||||
@@ -290,7 +284,6 @@ export type GameActionType =
|
|||||||
| { type: 'SET_ACTION'; action: GameAction }
|
| { type: 'SET_ACTION'; action: GameAction }
|
||||||
| { type: 'SET_SPELL'; spellId: string }
|
| { type: 'SET_SPELL'; spellId: string }
|
||||||
| { type: 'LEARN_SPELL'; spellId: string }
|
| { type: 'LEARN_SPELL'; spellId: string }
|
||||||
| { type: 'START_STUDYING_SKILL'; skillId: string }
|
|
||||||
| { type: 'START_STUDYING_SPELL'; spellId: string }
|
| { type: 'START_STUDYING_SPELL'; spellId: string }
|
||||||
| { type: 'CANCEL_STUDY' }
|
| { type: 'CANCEL_STUDY' }
|
||||||
| { type: 'CONVERT_MANA'; element: string; amount: number }
|
| { type: 'CONVERT_MANA'; element: string; amount: number }
|
||||||
@@ -299,6 +292,5 @@ export type GameActionType =
|
|||||||
| { type: 'DO_PRESTIGE'; id: string }
|
| { type: 'DO_PRESTIGE'; id: string }
|
||||||
| { type: 'START_NEW_LOOP' }
|
| { type: 'START_NEW_LOOP' }
|
||||||
| { type: 'TOGGLE_PAUSE' }
|
| { type: 'TOGGLE_PAUSE' }
|
||||||
| { type: 'RESET_GAME' }
|
| { type: 'RESET_GAME' };
|
||||||
| { type: 'SELECT_SKILL_UPGRADE'; skillId: string; upgradeId: string }
|
|
||||||
| { type: 'TIER_UP_SKILL'; skillId: string };
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { BASE_GUARDIANS } from '../data/guardian-data';
|
|||||||
// ─── Damage Calculation Params ──────────────────────────────────────────────
|
// ─── Damage Calculation Params ──────────────────────────────────────────────
|
||||||
|
|
||||||
export interface DamageCalcParams {
|
export interface DamageCalcParams {
|
||||||
skills: Record<string, number>;
|
|
||||||
signedPacts: number[];
|
signedPacts: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,13 +20,11 @@ export interface InsightCalcParams {
|
|||||||
totalManaGathered: number;
|
totalManaGathered: number;
|
||||||
signedPacts: number[];
|
signedPacts: number[];
|
||||||
prestigeUpgrades: Record<string, number>;
|
prestigeUpgrades: Record<string, number>;
|
||||||
skills: Record<string, number>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── DPS Calculation Params ─────────────────────────────────────────────────
|
// ─── DPS Calculation Params ─────────────────────────────────────────────────
|
||||||
|
|
||||||
export interface DPSCalcParams {
|
export interface DPSCalcParams {
|
||||||
skills: Record<string, number>;
|
|
||||||
signedPacts: number[];
|
signedPacts: number[];
|
||||||
equippedInstances: Record<string, string | null>;
|
equippedInstances: Record<string, string | null>;
|
||||||
equipmentInstances: Record<string, EquipmentInstance>;
|
equipmentInstances: Record<string, EquipmentInstance>;
|
||||||
@@ -150,24 +147,21 @@ export function calcDamage(
|
|||||||
): number {
|
): number {
|
||||||
const sp = SPELLS_DEF[spellId];
|
const sp = SPELLS_DEF[spellId];
|
||||||
if (!sp) return 5;
|
if (!sp) return 5;
|
||||||
const skills = state.skills;
|
|
||||||
|
|
||||||
// Base damage: spell base + skill bonus + discipline bonus
|
// Base damage: spell base + discipline bonus
|
||||||
const discBaseDmg = discipline?.bonuses?.baseDamageBonus || 0;
|
const discBaseDmg = discipline?.bonuses?.baseDamageBonus || 0;
|
||||||
const baseDmg = sp.dmg + (skills.combatTrain || 0) * 5 + discBaseDmg;
|
const baseDmg = sp.dmg + discBaseDmg;
|
||||||
|
|
||||||
// Percentage multiplier
|
// Percentage multiplier
|
||||||
const discDmgMult = discipline?.bonuses?.baseDamageMultiplier || 0;
|
const discDmgMult = discipline?.bonuses?.baseDamageMultiplier || 0;
|
||||||
const pct = 1 + (skills.arcaneFury || 0) * 0.1 + discDmgMult;
|
const pct = 1 + discDmgMult;
|
||||||
|
|
||||||
// Elemental mastery bonus
|
// Elemental mastery bonus
|
||||||
const elemMasteryBonus = 1 + (skills.elementalMastery || 0) * 0.15;
|
const elemMasteryBonus = 1;
|
||||||
|
|
||||||
// Guardian bane bonus
|
// Guardian bane bonus
|
||||||
const isGuardianFloor = floorElem && Object.values(BASE_GUARDIANS).some(g => g.element === floorElem);
|
const isGuardianFloor = floorElem && Object.values(BASE_GUARDIANS).some(g => g.element === floorElem);
|
||||||
const guardianBonus = isGuardianFloor
|
const guardianBonus = 1;
|
||||||
? 1 + (skills.guardianBane || 0) * 0.2
|
|
||||||
: 1;
|
|
||||||
|
|
||||||
// Get boon bonuses from pacts
|
// Get boon bonuses from pacts
|
||||||
const boons = getBoonBonuses(state.signedPacts);
|
const boons = getBoonBonuses(state.signedPacts);
|
||||||
@@ -177,7 +171,7 @@ export function calcDamage(
|
|||||||
const elemDamageMult = 1 + boons.elementalDamage / 100;
|
const elemDamageMult = 1 + boons.elementalDamage / 100;
|
||||||
|
|
||||||
// Apply crit chance and damage from boons
|
// Apply crit chance and damage from boons
|
||||||
const critChance = (skills.precision || 0) * 0.05 + boons.critChance / 100;
|
const critChance = boons.critChance / 100;
|
||||||
const critDamageMult = 1.5 + boons.critDamage / 100;
|
const critDamageMult = 1.5 + boons.critDamage / 100;
|
||||||
|
|
||||||
let damage = baseDmg * pct * elemMasteryBonus * guardianBonus * rawDamageMult * elemDamageMult;
|
let damage = baseDmg * pct * elemMasteryBonus * guardianBonus * rawDamageMult * elemDamageMult;
|
||||||
@@ -200,7 +194,7 @@ export function calcDamage(
|
|||||||
export function calcInsight(state: InsightCalcParams, discipline?: DisciplineBonuses): number {
|
export function calcInsight(state: InsightCalcParams, discipline?: DisciplineBonuses): number {
|
||||||
const pu = state.prestigeUpgrades;
|
const pu = state.prestigeUpgrades;
|
||||||
const discInsightBonus = discipline?.bonuses?.insightGainBonus || 0;
|
const discInsightBonus = discipline?.bonuses?.insightGainBonus || 0;
|
||||||
const skillBonus = 1 + (state.skills.insightHarvest || 0) * 0.1 + discInsightBonus;
|
const skillBonus = 1 + discInsightBonus;
|
||||||
|
|
||||||
// Get boon bonuses for insight gain
|
// Get boon bonuses for insight gain
|
||||||
const boons = getBoonBonuses(state.signedPacts);
|
const boons = getBoonBonuses(state.signedPacts);
|
||||||
@@ -323,7 +317,7 @@ export function getTotalDPS(
|
|||||||
|
|
||||||
// Get cast speed (spells per second)
|
// Get cast speed (spells per second)
|
||||||
const baseCastTime = spellDef.baseCastTime || 1.0;
|
const baseCastTime = spellDef.baseCastTime || 1.0;
|
||||||
const castingSpeedBonus = 1 + (state.skills.castingSpeed || 0) * 0.1;
|
const castingSpeedBonus = 1;
|
||||||
const equipmentAttackSpeed = upgradeEffects.attackSpeedMultiplier || 1;
|
const equipmentAttackSpeed = upgradeEffects.attackSpeedMultiplier || 1;
|
||||||
const castTime = baseCastTime / (castingSpeedBonus * equipmentAttackSpeed);
|
const castTime = baseCastTime / (castingSpeedBonus * equipmentAttackSpeed);
|
||||||
|
|
||||||
|
|||||||
@@ -13,10 +13,7 @@ export interface DisciplineBonuses {
|
|||||||
// ─── Mana Params ────────────────────────────────────────────────────────────
|
// ─── Mana Params ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export interface ManaComputeParams {
|
export interface ManaComputeParams {
|
||||||
skills?: Record<string, number>;
|
|
||||||
prestigeUpgrades?: Record<string, number>;
|
prestigeUpgrades?: Record<string, number>;
|
||||||
skillUpgrades?: Record<string, string[]>;
|
|
||||||
skillTiers?: Record<string, number>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RegenComputeParams extends ManaComputeParams {
|
export interface RegenComputeParams extends ManaComputeParams {
|
||||||
@@ -38,7 +35,6 @@ export function computeMaxMana(
|
|||||||
const pu = state.prestigeUpgrades || {};
|
const pu = state.prestigeUpgrades || {};
|
||||||
const base =
|
const base =
|
||||||
100 +
|
100 +
|
||||||
((state.skills || {}).manaWell || 0) * 100 +
|
|
||||||
(pu.manaWell || 0) * 500 +
|
(pu.manaWell || 0) * 500 +
|
||||||
(discipline?.bonuses?.maxManaBonus || 0);
|
(discipline?.bonuses?.maxManaBonus || 0);
|
||||||
|
|
||||||
@@ -51,7 +47,7 @@ export function computeMaxMana(
|
|||||||
// ─── Regen ────────────────────────────────────────────────────────────────────
|
// ─── Regen ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export function computeRegen(
|
export function computeRegen(
|
||||||
state: Pick<RegenComputeParams, 'skills' | 'prestigeUpgrades' | 'attunements'> & Partial<Pick<RegenComputeParams, 'skillUpgrades' | 'skillTiers'>>,
|
state: Pick<RegenComputeParams, 'prestigeUpgrades' | 'attunements'>,
|
||||||
effects?: ComputedEffects,
|
effects?: ComputedEffects,
|
||||||
discipline?: DisciplineBonuses,
|
discipline?: DisciplineBonuses,
|
||||||
): number {
|
): number {
|
||||||
@@ -59,8 +55,6 @@ export function computeRegen(
|
|||||||
const temporalBonus = 1 + (pu.temporalEcho || 0) * 0.1;
|
const temporalBonus = 1 + (pu.temporalEcho || 0) * 0.1;
|
||||||
const base =
|
const base =
|
||||||
2 +
|
2 +
|
||||||
((state.skills || {}).manaFlow || 0) * 1 +
|
|
||||||
((state.skills || {}).manaSpring || 0) * 2 +
|
|
||||||
((pu || {}).manaFlow || 0) * 0.5;
|
((pu || {}).manaFlow || 0) * 0.5;
|
||||||
|
|
||||||
let regen = base * temporalBonus;
|
let regen = base * temporalBonus;
|
||||||
@@ -85,7 +79,7 @@ export function computeRegen(
|
|||||||
// ─── Effective Regen for Display ──────────────────────────────────────────────
|
// ─── Effective Regen for Display ──────────────────────────────────────────────
|
||||||
|
|
||||||
export function computeEffectiveRegenForDisplay(
|
export function computeEffectiveRegenForDisplay(
|
||||||
state: Pick<RegenComputeParams, 'skills' | 'prestigeUpgrades' | 'attunements'>,
|
state: Pick<RegenComputeParams, 'prestigeUpgrades' | 'attunements'>,
|
||||||
effects?: ComputedEffects,
|
effects?: ComputedEffects,
|
||||||
discipline?: DisciplineBonuses,
|
discipline?: DisciplineBonuses,
|
||||||
): { rawRegen: number; conversionDrain: number; effectiveRegen: number } {
|
): { rawRegen: number; conversionDrain: number; effectiveRegen: number } {
|
||||||
@@ -99,7 +93,7 @@ export function computeEffectiveRegenForDisplay(
|
|||||||
// ─── Effective Regen (dynamic) ────────────────────────────────────────────────
|
// ─── Effective Regen (dynamic) ────────────────────────────────────────────────
|
||||||
|
|
||||||
export function computeEffectiveRegen(
|
export function computeEffectiveRegen(
|
||||||
state: Pick<RegenComputeParams, 'skills' | 'prestigeUpgrades' | 'attunements'> & { rawMana: number; incursionStrength: number },
|
state: Pick<RegenComputeParams, 'prestigeUpgrades' | 'attunements'> & { rawMana: number; incursionStrength: number },
|
||||||
effects?: ComputedEffects,
|
effects?: ComputedEffects,
|
||||||
discipline?: DisciplineBonuses,
|
discipline?: DisciplineBonuses,
|
||||||
): number {
|
): number {
|
||||||
@@ -112,56 +106,25 @@ export function computeEffectiveRegen(
|
|||||||
// ─── Click Mana ───────────────────────────────────────────────────────────────
|
// ─── Click Mana ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export function computeClickMana(
|
export function computeClickMana(
|
||||||
skills: Record<string, number>,
|
|
||||||
discipline?: DisciplineBonuses,
|
discipline?: DisciplineBonuses,
|
||||||
): number {
|
): number {
|
||||||
const skillTap = (skills.manaTap || 0) * 1;
|
const discClickBonus = discipline?.bonuses?.clickManaBonus || 0;
|
||||||
const skillSurge = (skills.manaSurge || 0) * 3;
|
return 1 + discClickBonus;
|
||||||
const discClickMult = discipline?.bonuses?.clickManaMultiplier || 0;
|
|
||||||
|
|
||||||
return 1 + skillTap + skillSurge + discClickMult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Meditation Bonus ─────────────────────────────────────────────────────────
|
// ─── Meditation Bonus ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export function getMeditationBonus(
|
export function getMeditationBonus(
|
||||||
meditateTicks: number,
|
meditateTicks: number,
|
||||||
skills: Record<string, number>,
|
|
||||||
meditationEfficiency: number = 1,
|
meditationEfficiency: number = 1,
|
||||||
disciplineMeditationCap: number = 0,
|
disciplineMeditationCap: number = 0,
|
||||||
): number {
|
): number {
|
||||||
const hasMeditation = skills.meditation === 1;
|
|
||||||
const hasDeepTrance = skills.deepTrance === 1;
|
|
||||||
const hasVoidMeditation = skills.voidMeditation === 1;
|
|
||||||
|
|
||||||
const hours = meditateTicks * HOURS_PER_TICK;
|
const hours = meditateTicks * HOURS_PER_TICK;
|
||||||
|
|
||||||
// Determine the hard cap for this meditation session.
|
// Continuous ramp: 1 + (hours / 8) * 4, capped at 5.0 + disciplineMeditationCap
|
||||||
// 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;
|
const maxMultiplier = 5.0 + disciplineMeditationCap;
|
||||||
|
const bonus = Math.min(1 + (hours / 8) * 4, maxMultiplier);
|
||||||
// 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 = Math.min(2.5, maxMultiplier);
|
|
||||||
}
|
|
||||||
|
|
||||||
// With Deep Trance: up to 3.0x after 6 hours
|
|
||||||
if (hasDeepTrance && hours >= 6) {
|
|
||||||
bonus = Math.min(3.0, maxMultiplier);
|
|
||||||
}
|
|
||||||
|
|
||||||
// With Void Meditation: up to maxMultiplier after 8 hours
|
|
||||||
if (hasVoidMeditation && hours >= 8) {
|
|
||||||
bonus = maxMultiplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply meditation efficiency from upgrades
|
// Apply meditation efficiency from upgrades
|
||||||
bonus *= meditationEfficiency;
|
return bonus * meditationEfficiency;
|
||||||
|
|
||||||
return bonus;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user