fix: deactivate all disciplines when entering/exiting the Spire
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m20s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m20s
- Add deactivateAll() action to discipline-slice.ts - Call deactivateAll() in createEnterSpireMode() (combat-descent-actions.ts) - Add spireMode guard in gameStore.ts tick() to skip discipline processTick - Call deactivateAll() in exitSpireMode() (combatStore.ts) as safety measure - Extract buildConversionParams to utils/conversion-params.ts to keep gameStore.ts under 400 lines - Add regression tests (5 tests, all 1136 passing) Fixes #347
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
# Circular Dependencies
|
# Circular Dependencies
|
||||||
Generated: 2026-06-10T08:50:57.213Z
|
Generated: 2026-06-10T09:19:26.381Z
|
||||||
Found: 2 circular chain(s) — these MUST be fixed before modifying involved files.
|
Found: 3 circular chain(s) — these MUST be fixed before modifying involved files.
|
||||||
|
|
||||||
1. 1) stores/golem-combat-actions.ts > stores/golem-combat-helpers.ts
|
1. 1) stores/golem-combat-actions.ts > stores/golem-combat-helpers.ts
|
||||||
2. 2) stores/combatStore.ts > stores/combat-descent-actions.ts > stores/attunementStore.ts
|
2. 2) stores/attunementStore.ts > stores/combatStore.ts > stores/combat-descent-actions.ts
|
||||||
|
3. 3) stores/attunementStore.ts > stores/combatStore.ts > stores/combat-descent-actions.ts > stores/non-combat-room-actions.ts
|
||||||
|
|
||||||
## How to fix
|
## How to fix
|
||||||
1. Identify which import in the chain can be extracted to a shared types/utils file.
|
1. Identify which import in the chain can be extracted to a shared types/utils file.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"generated": "2026-06-10T08:50:54.740Z",
|
"generated": "2026-06-10T09:19:24.358Z",
|
||||||
"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."
|
||||||
},
|
},
|
||||||
@@ -529,6 +529,7 @@
|
|||||||
"effects/discipline-effects.ts",
|
"effects/discipline-effects.ts",
|
||||||
"effects/special-effects.ts",
|
"effects/special-effects.ts",
|
||||||
"effects/upgrade-effects.ts",
|
"effects/upgrade-effects.ts",
|
||||||
|
"stores/attunementStore.ts",
|
||||||
"stores/combatStore.ts",
|
"stores/combatStore.ts",
|
||||||
"stores/gameStore.ts",
|
"stores/gameStore.ts",
|
||||||
"stores/manaStore.ts",
|
"stores/manaStore.ts",
|
||||||
|
|||||||
@@ -213,6 +213,7 @@ Mana-Loop/
|
|||||||
│ │ │ │ ├── cross-module-prestige-discipline.test.ts
|
│ │ │ │ ├── cross-module-prestige-discipline.test.ts
|
||||||
│ │ │ │ ├── curse-amplification.test.ts
|
│ │ │ │ ├── curse-amplification.test.ts
|
||||||
│ │ │ │ ├── design-validation-perk-gating.test.ts
|
│ │ │ │ ├── design-validation-perk-gating.test.ts
|
||||||
|
│ │ │ │ ├── discipline-deactivate-on-spire-entry.test.ts
|
||||||
│ │ │ │ ├── discipline-math.test.ts
|
│ │ │ │ ├── discipline-math.test.ts
|
||||||
│ │ │ │ ├── discipline-prerequisites.test.ts
|
│ │ │ │ ├── discipline-prerequisites.test.ts
|
||||||
│ │ │ │ ├── discipline-reactivate-bug.test.ts
|
│ │ │ │ ├── discipline-reactivate-bug.test.ts
|
||||||
@@ -410,6 +411,7 @@ Mana-Loop/
|
|||||||
│ │ │ ├── utils/
|
│ │ │ ├── utils/
|
||||||
│ │ │ │ ├── activity-log.ts
|
│ │ │ │ ├── activity-log.ts
|
||||||
│ │ │ │ ├── combat-utils.ts
|
│ │ │ │ ├── combat-utils.ts
|
||||||
|
│ │ │ │ ├── conversion-params.ts
|
||||||
│ │ │ │ ├── conversion-rates.ts
|
│ │ │ │ ├── conversion-rates.ts
|
||||||
│ │ │ │ ├── discipline-math.ts
|
│ │ │ │ ├── discipline-math.ts
|
||||||
│ │ │ │ ├── element-cap-bonus.ts
|
│ │ │ │ ├── element-cap-bonus.ts
|
||||||
|
|||||||
@@ -0,0 +1,229 @@
|
|||||||
|
import { describe, it, expect, beforeEach } from 'vitest';
|
||||||
|
import { useDisciplineStore } from '../stores/discipline-slice';
|
||||||
|
import { useCombatStore } from '../stores/combatStore';
|
||||||
|
import { useGameStore } from '../stores/gameStore';
|
||||||
|
import { useManaStore } from '../stores/manaStore';
|
||||||
|
import { usePrestigeStore } from '../stores/prestigeStore';
|
||||||
|
import { useUIStore } from '../stores/uiStore';
|
||||||
|
import { useAttunementStore } from '../stores/attunementStore';
|
||||||
|
import { useCraftingStore } from '../stores/craftingStore';
|
||||||
|
|
||||||
|
function resetAllStores() {
|
||||||
|
useUIStore.setState({
|
||||||
|
paused: false,
|
||||||
|
gameOver: false,
|
||||||
|
victory: false,
|
||||||
|
logs: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
useGameStore.setState({
|
||||||
|
day: 1,
|
||||||
|
hour: 0,
|
||||||
|
incursionStrength: 0,
|
||||||
|
containmentWards: 0,
|
||||||
|
initialized: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
useManaStore.setState({
|
||||||
|
rawMana: 1000,
|
||||||
|
meditateTicks: 0,
|
||||||
|
totalManaGathered: 0,
|
||||||
|
elements: {
|
||||||
|
transference: { unlocked: true, current: 50, max: 50, baseMax: 50 },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useCombatStore.setState({
|
||||||
|
currentFloor: 1,
|
||||||
|
floorHP: 0,
|
||||||
|
floorMaxHP: 0,
|
||||||
|
maxFloorReached: 1,
|
||||||
|
activeSpell: 'manaBolt',
|
||||||
|
currentAction: 'meditate',
|
||||||
|
castProgress: 0,
|
||||||
|
spireMode: false,
|
||||||
|
currentRoom: { roomType: 'combat', enemies: [] },
|
||||||
|
clearedFloors: {},
|
||||||
|
climbDirection: null,
|
||||||
|
isDescending: false,
|
||||||
|
startFloor: 1,
|
||||||
|
exitFloor: 1,
|
||||||
|
currentRoomIndex: 0,
|
||||||
|
roomsPerFloor: 5,
|
||||||
|
runId: 0,
|
||||||
|
descentPeak: null,
|
||||||
|
roomResetState: {},
|
||||||
|
clearedRooms: {},
|
||||||
|
isDescentComplete: false,
|
||||||
|
golemancy: { activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [] },
|
||||||
|
equipmentSpellStates: [],
|
||||||
|
weaponCastProgress: {},
|
||||||
|
comboHitCount: 0,
|
||||||
|
floorHitCount: 0,
|
||||||
|
spells: { manaBolt: { learned: true, level: 1, studyProgress: 0 } },
|
||||||
|
activityLog: [],
|
||||||
|
achievements: { unlocked: [], progress: {} },
|
||||||
|
totalSpellsCast: 0,
|
||||||
|
totalDamageDealt: 0,
|
||||||
|
totalCraftsCompleted: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
usePrestigeStore.setState({
|
||||||
|
loopCount: 0,
|
||||||
|
insight: 0,
|
||||||
|
totalInsight: 0,
|
||||||
|
loopInsight: 0,
|
||||||
|
prestigeUpgrades: {},
|
||||||
|
pactSlots: 1,
|
||||||
|
defeatedGuardians: [],
|
||||||
|
signedPacts: [],
|
||||||
|
signedPactDetails: {},
|
||||||
|
pactRitualFloor: null,
|
||||||
|
pactRitualProgress: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
useDisciplineStore.setState({
|
||||||
|
disciplines: {},
|
||||||
|
activeIds: [],
|
||||||
|
concurrentLimit: 1,
|
||||||
|
totalXP: 0,
|
||||||
|
processedPerks: [],
|
||||||
|
practicingCallbacks: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
useAttunementStore.setState({
|
||||||
|
attunements: {
|
||||||
|
enchanter: { active: true, level: 1, xp: 0 },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useCraftingStore.setState({
|
||||||
|
designProgress: null,
|
||||||
|
designProgress2: null,
|
||||||
|
preparationProgress: null,
|
||||||
|
applicationProgress: null,
|
||||||
|
equipmentCraftingProgress: null,
|
||||||
|
enchantmentDesigns: [],
|
||||||
|
unlockedEffects: [],
|
||||||
|
equippedInstances: {},
|
||||||
|
equipmentInstances: {},
|
||||||
|
lootInventory: { materials: {}, blueprints: [] },
|
||||||
|
enchantmentSelection: {
|
||||||
|
selectedEquipmentType: null,
|
||||||
|
selectedEffects: [],
|
||||||
|
designName: '',
|
||||||
|
selectedDesign: null,
|
||||||
|
selectedEquipmentInstance: null,
|
||||||
|
},
|
||||||
|
lastError: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Issue #347 — disciplines deactivate on spire entry', () => {
|
||||||
|
beforeEach(resetAllStores);
|
||||||
|
|
||||||
|
it('should deactivate all active disciplines when entering the Spire', () => {
|
||||||
|
// Set up sufficient mana and activate a discipline
|
||||||
|
useManaStore.setState({ rawMana: 1000, elements: {} });
|
||||||
|
|
||||||
|
// Activate Raw Mana Mastery
|
||||||
|
useDisciplineStore.getState().activate('raw-mastery');
|
||||||
|
expect(useDisciplineStore.getState().activeIds).toContain('raw-mastery');
|
||||||
|
expect(useDisciplineStore.getState().activeIds.length).toBe(1);
|
||||||
|
|
||||||
|
// Verify discipline is draining mana / accumulating XP
|
||||||
|
useDisciplineStore.getState().processTick({ rawMana: 1000, elements: {} });
|
||||||
|
expect(useDisciplineStore.getState().disciplines['raw-mastery'].xp).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Enter the Spire
|
||||||
|
useCombatStore.getState().enterSpireMode();
|
||||||
|
|
||||||
|
// ASSERT: All disciplines should be deactivated
|
||||||
|
expect(useDisciplineStore.getState().activeIds.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not accrue discipline XP during spire mode', () => {
|
||||||
|
useManaStore.setState({ rawMana: 1000, elements: {} });
|
||||||
|
|
||||||
|
// Activate and build up some XP
|
||||||
|
useDisciplineStore.getState().activate('raw-mastery');
|
||||||
|
useDisciplineStore.getState().processTick({ rawMana: 1000, elements: {} });
|
||||||
|
useDisciplineStore.getState().processTick({ rawMana: 1000, elements: {} });
|
||||||
|
const xpBefore = useDisciplineStore.getState().disciplines['raw-mastery'].xp;
|
||||||
|
expect(xpBefore).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Enter the Spire
|
||||||
|
useCombatStore.getState().enterSpireMode();
|
||||||
|
|
||||||
|
// Confirm spire mode is active
|
||||||
|
expect(useCombatStore.getState().spireMode).toBe(true);
|
||||||
|
|
||||||
|
// Tick the game — discipline should NOT accrue XP because spireMode guard skips processTick
|
||||||
|
useGameStore.getState().tick();
|
||||||
|
useGameStore.getState().tick();
|
||||||
|
useGameStore.getState().tick();
|
||||||
|
|
||||||
|
// XP should remain unchanged from before entering the spire
|
||||||
|
expect(useDisciplineStore.getState().disciplines['raw-mastery'].xp).toBe(xpBefore);
|
||||||
|
expect(useDisciplineStore.getState().activeIds.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deactivate all disciplines on spire exit as a safety measure', () => {
|
||||||
|
useManaStore.setState({ rawMana: 1000, elements: {} });
|
||||||
|
|
||||||
|
// Activate a discipline before entering
|
||||||
|
useDisciplineStore.getState().activate('raw-mastery');
|
||||||
|
|
||||||
|
// Enter spire (deactivates disciplines)
|
||||||
|
useCombatStore.getState().enterSpireMode();
|
||||||
|
expect(useCombatStore.getState().spireMode).toBe(true);
|
||||||
|
expect(useDisciplineStore.getState().activeIds.length).toBe(0);
|
||||||
|
|
||||||
|
// Simulate a discipline somehow reactivated during spire (edge case)
|
||||||
|
useDisciplineStore.getState().disciplines['raw-mastery'] = { id: 'raw-mastery', xp: 0, paused: false };
|
||||||
|
useDisciplineStore.setState({ activeIds: ['raw-mastery'] });
|
||||||
|
expect(useDisciplineStore.getState().activeIds).toContain('raw-mastery');
|
||||||
|
|
||||||
|
// Exit spire — should still clean up disciplines
|
||||||
|
useCombatStore.getState().exitSpireMode();
|
||||||
|
expect(useDisciplineStore.getState().activeIds.length).toBe(0);
|
||||||
|
expect(useCombatStore.getState().spireMode).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deactivateAll() should preserve XP and pause state', () => {
|
||||||
|
useManaStore.setState({ rawMana: 1000, elements: {} });
|
||||||
|
|
||||||
|
useDisciplineStore.getState().activate('raw-mastery');
|
||||||
|
useDisciplineStore.getState().processTick({ rawMana: 1000, elements: {} });
|
||||||
|
useDisciplineStore.getState().processTick({ rawMana: 1000, elements: {} });
|
||||||
|
const xpBefore = useDisciplineStore.getState().disciplines['raw-mastery'].xp;
|
||||||
|
|
||||||
|
// Call deactivateAll directly
|
||||||
|
useDisciplineStore.getState().deactivateAll();
|
||||||
|
|
||||||
|
expect(useDisciplineStore.getState().activeIds.length).toBe(0);
|
||||||
|
expect(useDisciplineStore.getState().disciplines['raw-mastery'].xp).toBe(xpBefore);
|
||||||
|
expect(useDisciplineStore.getState().disciplines['raw-mastery'].paused).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deactivateAll() should work with multiple active disciplines', () => {
|
||||||
|
useManaStore.setState({
|
||||||
|
rawMana: 1000,
|
||||||
|
elements: {
|
||||||
|
transference: { unlocked: true, current: 100, max: 100, baseMax: 100 },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Temporarily raise concurrent limit for this test
|
||||||
|
useDisciplineStore.setState({ concurrentLimit: 3 });
|
||||||
|
|
||||||
|
useDisciplineStore.getState().activate('raw-mastery');
|
||||||
|
useDisciplineStore.getState().activate('attune-transference');
|
||||||
|
|
||||||
|
expect(useDisciplineStore.getState().activeIds.length).toBe(2);
|
||||||
|
|
||||||
|
useDisciplineStore.getState().deactivateAll();
|
||||||
|
|
||||||
|
expect(useDisciplineStore.getState().activeIds.length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -10,6 +10,7 @@ import { useManaStore } from './manaStore';
|
|||||||
import { summonGolemsOnRoomEntry } from './golem-combat-actions';
|
import { summonGolemsOnRoomEntry } from './golem-combat-actions';
|
||||||
import { computeDisciplineEffects } from '../effects/discipline-effects';
|
import { computeDisciplineEffects } from '../effects/discipline-effects';
|
||||||
import { useAttunementStore } from './attunementStore';
|
import { useAttunementStore } from './attunementStore';
|
||||||
|
import { useDisciplineStore } from './discipline-slice';
|
||||||
import {
|
import {
|
||||||
onEnterLibraryRoom,
|
onEnterLibraryRoom,
|
||||||
onEnterRecoveryRoom,
|
onEnterRecoveryRoom,
|
||||||
@@ -278,6 +279,9 @@ export function createEnterSpireMode(get: GetFn, set: SetFn) {
|
|||||||
golemancy: { activeGolems: [], lastSummonFloor: 0, golemDesigns: get().golemancy?.golemDesigns ?? {}, golemLoadout: [] },
|
golemancy: { activeGolems: [], lastSummonFloor: 0, golemDesigns: get().golemancy?.golemDesigns ?? {}, golemLoadout: [] },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Deactivate all active disciplines when entering the Spire
|
||||||
|
useDisciplineStore.getState().deactivateAll();
|
||||||
|
|
||||||
get().addActivityLog('floor_transition',
|
get().addActivityLog('floor_transition',
|
||||||
`Entered the Spire at Floor ${startFloor}`);
|
`Entered the Spire at Floor ${startFloor}`);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
onEnterLibraryRoom, tickNonCombatRoom, skipNonCombatRoom, stayLongerInRoom,
|
onEnterLibraryRoom, tickNonCombatRoom, skipNonCombatRoom, stayLongerInRoom,
|
||||||
} from './non-combat-room-actions';
|
} from './non-combat-room-actions';
|
||||||
|
import { useDisciplineStore } from './discipline-slice';
|
||||||
import {
|
import {
|
||||||
addGolemDesign, removeGolemDesign, toggleGolemLoadoutEntry,
|
addGolemDesign, removeGolemDesign, toggleGolemLoadoutEntry,
|
||||||
} from './golemancy-actions';
|
} from './golemancy-actions';
|
||||||
@@ -210,6 +211,8 @@ export const useCombatStore = create<CombatStore>()(
|
|||||||
maxFloorReached: Math.max(s.maxFloorReached, 1),
|
maxFloorReached: Math.max(s.maxFloorReached, 1),
|
||||||
golemancy: { ...s.golemancy, activeGolems: [] as RuntimeActiveGolem[] },
|
golemancy: { ...s.golemancy, activeGolems: [] as RuntimeActiveGolem[] },
|
||||||
});
|
});
|
||||||
|
// Deactivate all disciplines on spire exit for safety
|
||||||
|
useDisciplineStore.getState().deactivateAll();
|
||||||
get().addActivityLog('floor_transition', 'Exited the Spire');
|
get().addActivityLog('floor_transition', 'Exited the Spire');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export interface DisciplineStoreState {
|
|||||||
export interface DisciplineStoreActions {
|
export interface DisciplineStoreActions {
|
||||||
activate: (id: string, gameStateOverrides?: { elements?: Record<string, ElementState>; rawMana?: number; signedPacts?: number[] }) => void;
|
activate: (id: string, gameStateOverrides?: { elements?: Record<string, ElementState>; rawMana?: number; signedPacts?: number[] }) => void;
|
||||||
deactivate: (id: string) => void;
|
deactivate: (id: string) => void;
|
||||||
|
deactivateAll: () => void;
|
||||||
processTick: (mana: { rawMana: number; elements: Record<string, ElementState> }) => {
|
processTick: (mana: { rawMana: number; elements: Record<string, ElementState> }) => {
|
||||||
rawMana: number;
|
rawMana: number;
|
||||||
elements: Record<string, ElementState>;
|
elements: Record<string, ElementState>;
|
||||||
@@ -161,6 +162,22 @@ export const useDisciplineStore = create<DisciplineStore>()(
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
deactivateAll() {
|
||||||
|
set((s) => {
|
||||||
|
const newDisciplines = { ...s.disciplines };
|
||||||
|
for (const id of s.activeIds) {
|
||||||
|
if (newDisciplines[id]) {
|
||||||
|
newDisciplines[id] = { ...newDisciplines[id], paused: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get().practicingCallbacks?.onStopPracticing?.();
|
||||||
|
return {
|
||||||
|
activeIds: [],
|
||||||
|
disciplines: newDisciplines,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
setPracticingCallbacks(callbacks) {
|
setPracticingCallbacks(callbacks) {
|
||||||
set({ practicingCallbacks: callbacks });
|
set({ practicingCallbacks: callbacks });
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import { useCombatStore } from './combatStore';
|
|||||||
import { useAttunementStore } from './attunementStore';
|
import { useAttunementStore } from './attunementStore';
|
||||||
import { useCraftingStore } from './craftingStore';
|
import { useCraftingStore } from './craftingStore';
|
||||||
import { useDisciplineStore } from './discipline-slice';
|
import { useDisciplineStore } from './discipline-slice';
|
||||||
import { ATTUNEMENTS_DEF } from '../data/attunements';
|
|
||||||
import { createResetGame, createGatherMana } from './gameActions';
|
import { createResetGame, createGatherMana } from './gameActions';
|
||||||
import { createSafeStorage } from '../utils/safe-persist';
|
import { createSafeStorage } from '../utils/safe-persist';
|
||||||
import { createStartNewLoop } from './gameLoopActions';
|
import { createStartNewLoop } from './gameLoopActions';
|
||||||
@@ -27,6 +26,7 @@ import { buildTickContext, applyTickWrites } from './tick-pipeline';
|
|||||||
import { processEnchantingTicks } from './pipelines/enchanting-tick';
|
import { processEnchantingTicks } from './pipelines/enchanting-tick';
|
||||||
import { buildGolemCombatPipeline } from './pipelines/golem-combat';
|
import { buildGolemCombatPipeline } from './pipelines/golem-combat';
|
||||||
import { getGuardianForFloor } from '../data/guardian-encounters';
|
import { getGuardianForFloor } from '../data/guardian-encounters';
|
||||||
|
import { buildConversionParams } from '../utils/conversion-params';
|
||||||
|
|
||||||
import type { TickContext, TickWrites } from './tick-pipeline';
|
import type { TickContext, TickWrites } from './tick-pipeline';
|
||||||
import type { GameCoordinatorState } from './gameStore.types';
|
import type { GameCoordinatorState } from './gameStore.types';
|
||||||
@@ -228,7 +228,10 @@ export const useGameStore = create<GameCoordinatorStore>()(
|
|||||||
usePrestigeStore.getState().completePactRitual(addLog);
|
usePrestigeStore.getState().completePactRitual(addLog);
|
||||||
}
|
}
|
||||||
|
|
||||||
const dr = useDisciplineStore.getState().processTick({ rawMana, elements });
|
// Skip discipline processing during spire runs (defense-in-depth)
|
||||||
|
const dr = ctx.combat.spireMode
|
||||||
|
? { rawMana, elements, unlockedEffects: [], unlockedRecipes: [], autoPausedNames: [] }
|
||||||
|
: useDisciplineStore.getState().processTick({ rawMana, elements });
|
||||||
rawMana = dr.rawMana; elements = dr.elements;
|
rawMana = dr.rawMana; elements = dr.elements;
|
||||||
rawMana = Math.min(rawMana, computeMaxMana({ prestigeUpgrades: ctx.prestige.prestigeUpgrades }, undefined, computeDisciplineEffects()));
|
rawMana = Math.min(rawMana, computeMaxMana({ prestigeUpgrades: ctx.prestige.prestigeUpgrades }, undefined, computeDisciplineEffects()));
|
||||||
|
|
||||||
@@ -373,27 +376,4 @@ export const useGameStore = create<GameCoordinatorStore>()(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
/** Build pact element map and gross regen for the unified conversion system */
|
|
||||||
function buildConversionParams(
|
|
||||||
signedPacts: number[],
|
|
||||||
attunements: Record<string, { active: boolean; level: number }>,
|
|
||||||
): { pactElementMap: Record<number, string>; grossRegen: Record<string, number> } {
|
|
||||||
const pactElementMap: Record<number, string> = {};
|
|
||||||
for (const floor of signedPacts) {
|
|
||||||
const guardian = getGuardianForFloor(floor);
|
|
||||||
if (guardian?.element?.length) {
|
|
||||||
pactElementMap[floor] = guardian.element[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const grossRegen: Record<string, number> = {};
|
|
||||||
for (const [id, state] of Object.entries(attunements)) {
|
|
||||||
if (!state.active) continue;
|
|
||||||
const def = ATTUNEMENTS_DEF[id];
|
|
||||||
if (def?.primaryManaType && def.rawManaRegen) {
|
|
||||||
const levelMult = Math.pow(1.5, (state.level || 1) - 1);
|
|
||||||
grossRegen[def.primaryManaType] = (grossRegen[def.primaryManaType] || 0)
|
|
||||||
+ def.rawManaRegen * levelMult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { pactElementMap, grossRegen };
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { ATTUNEMENTS_DEF } from '../data/attunements';
|
||||||
|
import { getGuardianForFloor } from '../data/guardian-encounters';
|
||||||
|
|
||||||
|
/** Build pact element map and gross regen for the unified conversion system */
|
||||||
|
export function buildConversionParams(
|
||||||
|
signedPacts: number[],
|
||||||
|
attunements: Record<string, { active: boolean; level: number }>,
|
||||||
|
): { pactElementMap: Record<number, string>; grossRegen: Record<string, number> } {
|
||||||
|
const pactElementMap: Record<number, string> = {};
|
||||||
|
for (const floor of signedPacts) {
|
||||||
|
const guardian = getGuardianForFloor(floor);
|
||||||
|
if (guardian?.element?.length) {
|
||||||
|
pactElementMap[floor] = guardian.element[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const grossRegen: Record<string, number> = {};
|
||||||
|
for (const [id, state] of Object.entries(attunements)) {
|
||||||
|
if (!state.active) continue;
|
||||||
|
const def = ATTUNEMENTS_DEF[id];
|
||||||
|
if (def?.primaryManaType && def.rawManaRegen) {
|
||||||
|
const levelMult = Math.pow(1.5, (state.level || 1) - 1);
|
||||||
|
grossRegen[def.primaryManaType] = (grossRegen[def.primaryManaType] || 0)
|
||||||
|
+ def.rawManaRegen * levelMult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { pactElementMap, grossRegen };
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ export { createSafeStorage } from './safe-persist';
|
|||||||
export { ok, okVoid, fail, failTyped, unwrapOr, isErrorCode, ErrorCode } from './result';
|
export { ok, okVoid, fail, failTyped, unwrapOr, isErrorCode, ErrorCode } from './result';
|
||||||
export type { Result, ErrorCodeType } from './result';
|
export type { Result, ErrorCodeType } from './result';
|
||||||
export { getFloorMaxHP, getFloorElement, getFloorElements } from './floor-utils';
|
export { getFloorMaxHP, getFloorElement, getFloorElements } from './floor-utils';
|
||||||
|
export { buildConversionParams } from './conversion-params';
|
||||||
export {
|
export {
|
||||||
computeMaxMana,
|
computeMaxMana,
|
||||||
computeRegen,
|
computeRegen,
|
||||||
|
|||||||
Reference in New Issue
Block a user