fix: resolve critical bugs - disciplines, debug reset, floating point, spire loop
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m21s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m21s
Fixes:
- Issue 193: Remove unnecessary useEffect that set activeTab when spireMode is true, and redundant setAction('climb') in SpireCombatPage
- Issue 194: Fix signed_pact prerequisite check in checkDisciplinePrerequisites by accepting signedPacts param; add 'At Limit' feedback on discipline button when concurrent limit reached
- Issue 195: Add resetDisciplines(), resetAttunements(), resetCrafting() calls to createResetGame; add resetCrafting action to crafting store
- Issue 196: Fix floating point display in ElementStatsSection (mana pools) and GameStateDebug (time); fix duplicate 'Base Regen' label in ManaStatsSection
All 917 tests pass. Files stay under 400-line limit.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
# Circular Dependencies
|
# Circular Dependencies
|
||||||
Generated: 2026-05-28T19:01:33.787Z
|
Generated: 2026-05-28T19:24:11.154Z
|
||||||
|
|
||||||
No circular dependencies found. ✅
|
No circular dependencies found. ✅
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"generated": "2026-05-28T19:01:32.036Z",
|
"generated": "2026-05-28T19:24:09.393Z",
|
||||||
"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."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -171,12 +171,6 @@ export default function ManaLoopGame() {
|
|||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
useEffect(() => { setMounted(true); }, []); // eslint-disable-line react-hooks/set-state-in-effect
|
useEffect(() => { setMounted(true); }, []); // eslint-disable-line react-hooks/set-state-in-effect
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (spireMode) {
|
|
||||||
setActiveTab('spells'); // eslint-disable-line react-hooks/set-state-in-effect
|
|
||||||
}
|
|
||||||
}, [spireMode]);
|
|
||||||
|
|
||||||
if (gameOver) {
|
if (gameOver) {
|
||||||
return <GameOverScreen day={day} hour={hour} insightGained={loopInsight} totalInsight={insight} />;
|
return <GameOverScreen day={day} hour={hour} insightGained={loopInsight} totalInsight={insight} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ function TimeControlSection({ day, hour, paused, onSetDay, onTogglePause }: {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-3">
|
<CardContent className="space-y-3">
|
||||||
<div className="text-xs text-gray-400">
|
<div className="text-xs text-gray-400">
|
||||||
Current: Day {day}, Hour {hour}
|
Current: Day {day}, Hour {Number.isFinite(hour) ? hour.toFixed(2) : '0.00'}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 flex-wrap">
|
<div className="flex gap-2 flex-wrap">
|
||||||
<Button size="sm" variant="outline" onClick={() => onSetDay(1)}>Day 1</Button>
|
<Button size="sm" variant="outline" onClick={() => onSetDay(1)}>Day 1</Button>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export interface DisciplineCardProps {
|
|||||||
definition: DisciplineDefinition;
|
definition: DisciplineDefinition;
|
||||||
xp: number;
|
xp: number;
|
||||||
paused: boolean;
|
paused: boolean;
|
||||||
|
activeIds: string[];
|
||||||
concurrentLimit: number;
|
concurrentLimit: number;
|
||||||
isLocked: boolean;
|
isLocked: boolean;
|
||||||
missingPrereqs: string[];
|
missingPrereqs: string[];
|
||||||
@@ -23,7 +24,7 @@ export interface DisciplineCardProps {
|
|||||||
// ─── Component ────────────────────────────────────────────────────────────────
|
// ─── Component ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export const DisciplineCard: React.FC<DisciplineCardProps> = ({
|
export const DisciplineCard: React.FC<DisciplineCardProps> = ({
|
||||||
definition, xp, paused: isPaused, concurrentLimit,
|
definition, xp, paused: isPaused, activeIds, concurrentLimit,
|
||||||
isLocked, missingPrereqs, missingSourceMana, onToggle,
|
isLocked, missingPrereqs, missingSourceMana, onToggle,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
@@ -41,6 +42,12 @@ export const DisciplineCard: React.FC<DisciplineCardProps> = ({
|
|||||||
const manaColor = elementDef?.color ?? '#888888';
|
const manaColor = elementDef?.color ?? '#888888';
|
||||||
const manaIcon = elementDef?.sym ?? '✦';
|
const manaIcon = elementDef?.sym ?? '✦';
|
||||||
const manaName = elementDef?.name ?? manaType;
|
const manaName = elementDef?.name ?? manaType;
|
||||||
|
const isActive = activeIds.includes(id);
|
||||||
|
const activeNotPaused = activeIds.filter((aid) => {
|
||||||
|
// Count how many active disciplines are not paused
|
||||||
|
return aid === id ? !isPaused : true;
|
||||||
|
}).length;
|
||||||
|
const atConcurrentLimit = !isActive && activeIds.length >= concurrentLimit;
|
||||||
const effectiveIsLocked = isLocked || missingSourceMana.length > 0;
|
const effectiveIsLocked = isLocked || missingSourceMana.length > 0;
|
||||||
const statBonusLabel = statBonus.label;
|
const statBonusLabel = statBonus.label;
|
||||||
|
|
||||||
@@ -140,9 +147,16 @@ export const DisciplineCard: React.FC<DisciplineCardProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Lock Reasons */}
|
{/* Lock Reasons */}
|
||||||
{effectiveIsLocked && (missingPrereqs.length > 0 || missingSourceMana.length > 0) && (
|
{(effectiveIsLocked || atConcurrentLimit) && (missingPrereqs.length > 0 || missingSourceMana.length > 0 || atConcurrentLimit) && (
|
||||||
<div className="mt-2 text-xs text-red-400">
|
<div className="mt-2 text-xs text-red-400">
|
||||||
<strong>Requires:</strong> {[...missingPrereqs, ...missingSourceMana].join(', ')}
|
{atConcurrentLimit && <div><strong>At limit:</strong> {activeIds.length}/{concurrentLimit} disciplines active</div>}
|
||||||
|
{missingPrereqs.length > 0 && <div><strong>Requires:</strong> {missingPrereqs.join(', ')}</div>}
|
||||||
|
{missingSourceMana.length > 0 && <div><strong>Missing mana:</strong> {missingSourceMana.join(', ')}</div>}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{atConcurrentLimit && missingPrereqs.length === 0 && missingSourceMana.length === 0 && (
|
||||||
|
<div className="mt-2 text-xs text-amber-400">
|
||||||
|
<strong>At limit:</strong> {activeIds.length}/{concurrentLimit} disciplines active. Gain XP to unlock more slots.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -150,17 +164,23 @@ export const DisciplineCard: React.FC<DisciplineCardProps> = ({
|
|||||||
<div className="mt-4 flex justify-end">
|
<div className="mt-4 flex justify-end">
|
||||||
<button
|
<button
|
||||||
onClick={() => onToggle(id, isPaused)}
|
onClick={() => onToggle(id, isPaused)}
|
||||||
disabled={effectiveIsLocked}
|
disabled={effectiveIsLocked || atConcurrentLimit}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'rounded px-3 py-1 text-sm font-medium',
|
'rounded px-3 py-1 text-sm font-medium',
|
||||||
effectiveIsLocked
|
effectiveIsLocked || atConcurrentLimit
|
||||||
? 'bg-gray-600 text-gray-400 cursor-not-allowed'
|
? 'bg-gray-600 text-gray-400 cursor-not-allowed'
|
||||||
: isPaused
|
: isPaused
|
||||||
? 'bg-yellow-600 text-white hover:bg-yellow-500'
|
? 'bg-yellow-600 text-white hover:bg-yellow-500'
|
||||||
: 'bg-blue-600 text-white hover:bg-blue-500',
|
: 'bg-blue-600 text-white hover:bg-blue-500',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{effectiveIsLocked ? 'Locked' : isPaused ? 'Start Practicing' : 'Stop Practicing'}
|
{effectiveIsLocked
|
||||||
|
? 'Locked'
|
||||||
|
: atConcurrentLimit
|
||||||
|
? `At Limit (${activeIds.length}/${concurrentLimit})`
|
||||||
|
: isPaused
|
||||||
|
? 'Start Practicing'
|
||||||
|
: 'Stop Practicing'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ const ATTUNEMENT_TABS: AttunementTab[] = [
|
|||||||
interface CardWrapperProps {
|
interface CardWrapperProps {
|
||||||
disc: DisciplineDefinition;
|
disc: DisciplineDefinition;
|
||||||
disciplines: Record<string, { xp: number; paused: boolean }>;
|
disciplines: Record<string, { xp: number; paused: boolean }>;
|
||||||
|
activeIds: string[];
|
||||||
concurrentLimit: number;
|
concurrentLimit: number;
|
||||||
elements: ReturnType<typeof useManaStore.getState>['elements'];
|
elements: ReturnType<typeof useManaStore.getState>['elements'];
|
||||||
signedPacts: ReturnType<typeof usePrestigeStore.getState>['signedPacts'];
|
signedPacts: ReturnType<typeof usePrestigeStore.getState>['signedPacts'];
|
||||||
@@ -48,15 +49,16 @@ interface CardWrapperProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CardWrapper: React.FC<CardWrapperProps> = ({
|
const CardWrapper: React.FC<CardWrapperProps> = ({
|
||||||
disc, disciplines, concurrentLimit, elements, onToggle,
|
disc, disciplines, activeIds, concurrentLimit, elements, signedPacts, onToggle,
|
||||||
}) => {
|
}) => {
|
||||||
const discState = disciplines[disc.id] ?? { xp: 0, paused: true };
|
const discState = disciplines[disc.id] ?? { xp: 0, paused: true };
|
||||||
const prereqCheck = checkDisciplinePrerequisites(disc, disciplines, ALL_DISCIPLINES, elements);
|
const prereqCheck = checkDisciplinePrerequisites(disc, disciplines, ALL_DISCIPLINES, elements, signedPacts);
|
||||||
return (
|
return (
|
||||||
<DisciplineCard
|
<DisciplineCard
|
||||||
definition={disc}
|
definition={disc}
|
||||||
xp={discState.xp}
|
xp={discState.xp}
|
||||||
paused={discState.paused}
|
paused={discState.paused}
|
||||||
|
activeIds={activeIds}
|
||||||
concurrentLimit={concurrentLimit}
|
concurrentLimit={concurrentLimit}
|
||||||
isLocked={!prereqCheck.canProceed}
|
isLocked={!prereqCheck.canProceed}
|
||||||
missingPrereqs={prereqCheck.missingPrereqs}
|
missingPrereqs={prereqCheck.missingPrereqs}
|
||||||
@@ -129,6 +131,7 @@ export const DisciplinesTab: React.FC = () => {
|
|||||||
key={disc.id}
|
key={disc.id}
|
||||||
disc={disc}
|
disc={disc}
|
||||||
disciplines={disciplines}
|
disciplines={disciplines}
|
||||||
|
activeIds={activeIds}
|
||||||
concurrentLimit={concurrentLimit}
|
concurrentLimit={concurrentLimit}
|
||||||
elements={elements}
|
elements={elements}
|
||||||
signedPacts={signedPacts}
|
signedPacts={signedPacts}
|
||||||
|
|||||||
@@ -113,8 +113,7 @@ export function SpireCombatPage() {
|
|||||||
setRoomsCleared(0);
|
setRoomsCleared(0);
|
||||||
const newRoom = generateSpireFloorState(currentFloor, 0, totalRooms);
|
const newRoom = generateSpireFloorState(currentFloor, 0, totalRooms);
|
||||||
setCurrentRoom(newRoom);
|
setCurrentRoom(newRoom);
|
||||||
setAction('climb');
|
}, [currentFloor, totalRooms, setCurrentRoom]);
|
||||||
}, [currentFloor, totalRooms, setCurrentRoom, setAction]);
|
|
||||||
|
|
||||||
const _handleRoomCleared = () => {
|
const _handleRoomCleared = () => {
|
||||||
const nextRoomIndex = roomsCleared + 1;
|
const nextRoomIndex = roomsCleared + 1;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { DebugName } from '@/components/game/debug/debug-context';
|
|||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { FlaskConical } from 'lucide-react';
|
import { FlaskConical } from 'lucide-react';
|
||||||
import { ELEMENTS } from '@/lib/game/constants';
|
import { ELEMENTS } from '@/lib/game/constants';
|
||||||
import { usePrestigeStore, useManaStore } from '@/lib/game/stores';
|
import { usePrestigeStore, useManaStore, fmtDec } from '@/lib/game/stores';
|
||||||
import type { ElementState } from '@/lib/game/types';
|
import type { ElementState } from '@/lib/game/types';
|
||||||
|
|
||||||
interface ElementStatsSectionProps {
|
interface ElementStatsSectionProps {
|
||||||
@@ -54,7 +54,7 @@ export function ElementStatsSection({ elemMax }: ElementStatsSectionProps) {
|
|||||||
return (
|
return (
|
||||||
<div key={id} className="p-2 rounded transition-colors" style={{ border: `1px solid ${def?.color}30`, background: 'var(--bg-sunken)/50', textAlign: 'center' }}>
|
<div key={id} className="p-2 rounded transition-colors" style={{ border: `1px solid ${def?.color}30`, background: 'var(--bg-sunken)/50', textAlign: 'center' }}>
|
||||||
<div className="text-lg" style={{ color: def?.color }}>{def?.sym}</div>
|
<div className="text-lg" style={{ color: def?.color }}>{def?.sym}</div>
|
||||||
<div className="text-xs" style={{ color: 'var(--text-muted)' }}>{state.current}/{state.max}</div>
|
<div className="text-xs" style={{ color: 'var(--text-muted)' }}>{fmtDec(state.current, 2)}/{fmtDec(state.max, 0)}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export function ManaStatsSection({ stats, elemMax }: ManaStatsSectionProps) {
|
|||||||
<span style={{ color: 'var(--text-secondary)' }}>2/hr</span>
|
<span style={{ color: 'var(--text-secondary)' }}>2/hr</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between text-sm font-semibold border-t border-[var(--border-subtle)] pt-2">
|
<div className="flex justify-between text-sm font-semibold border-t border-[var(--border-subtle)] pt-2">
|
||||||
<span style={{ color: 'var(--text-secondary)' }}>Base Regen:</span>
|
<span style={{ color: 'var(--text-secondary)' }}>Computed Base Regen:</span>
|
||||||
<span style={{ color: 'var(--mana-water)' }}>{fmtDec(baseRegen, 2)}/hr</span>
|
<span style={{ color: 'var(--mana-water)' }}>{fmtDec(baseRegen, 2)}/hr</span>
|
||||||
</div>
|
</div>
|
||||||
{upgradeEffects.regenBonus > 0 && (
|
{upgradeEffects.regenBonus > 0 && (
|
||||||
|
|||||||
@@ -3,6 +3,38 @@
|
|||||||
|
|
||||||
import * as CraftingUtils from '../crafting-utils';
|
import * as CraftingUtils from '../crafting-utils';
|
||||||
import type { EquipmentInstance } from '../types';
|
import type { EquipmentInstance } from '../types';
|
||||||
|
import type { CraftingState } from './craftingStore.types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the full default state for the crafting store.
|
||||||
|
* Used by both initial store creation and resetCrafting().
|
||||||
|
*/
|
||||||
|
export function createDefaultCraftingState(): CraftingState {
|
||||||
|
const initial = createInitialEquipmentInstances();
|
||||||
|
return {
|
||||||
|
designProgress: null,
|
||||||
|
designProgress2: null,
|
||||||
|
preparationProgress: null,
|
||||||
|
applicationProgress: null,
|
||||||
|
equipmentCraftingProgress: null,
|
||||||
|
enchantmentDesigns: [],
|
||||||
|
unlockedEffects: [],
|
||||||
|
equippedInstances: initial.equippedInstances,
|
||||||
|
equipmentInstances: initial.instances,
|
||||||
|
lootInventory: {
|
||||||
|
materials: {},
|
||||||
|
blueprints: [],
|
||||||
|
},
|
||||||
|
enchantmentSelection: {
|
||||||
|
selectedEquipmentType: null,
|
||||||
|
selectedEffects: [],
|
||||||
|
designName: '',
|
||||||
|
selectedDesign: null,
|
||||||
|
selectedEquipmentInstance: null,
|
||||||
|
},
|
||||||
|
lastError: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function createInitialEquipmentInstances() {
|
export function createInitialEquipmentInstances() {
|
||||||
const staffId = CraftingUtils.generateInstanceId();
|
const staffId = CraftingUtils.generateInstanceId();
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import * as CraftingEquipment from '../crafting-equipment';
|
|||||||
import { equipItem as equipItemAction, unequipItem as unequipItemAction } from '../crafting-actions/equipment-actions';
|
import { equipItem as equipItemAction, unequipItem as unequipItemAction } from '../crafting-actions/equipment-actions';
|
||||||
import { ErrorCode } from '../utils/result';
|
import { ErrorCode } from '../utils/result';
|
||||||
import { createSafeStorage } from '../utils/safe-persist';
|
import { createSafeStorage } from '../utils/safe-persist';
|
||||||
import { createInitialEquipmentInstances } from './crafting-initial-state';
|
import { createDefaultCraftingState } from './crafting-initial-state';
|
||||||
import {
|
import {
|
||||||
getFabricatorRecipe,
|
getFabricatorRecipe,
|
||||||
deductFabricatorMana,
|
deductFabricatorMana,
|
||||||
@@ -28,30 +28,9 @@ import { processEquipmentCraftingTick } from './crafting-equipment-tick';
|
|||||||
export const useCraftingStore = create<CraftingStore>()(
|
export const useCraftingStore = create<CraftingStore>()(
|
||||||
persist(
|
persist(
|
||||||
(set, get) => {
|
(set, get) => {
|
||||||
const initial = createInitialEquipmentInstances();
|
const defaultState = createDefaultCraftingState();
|
||||||
return {
|
return {
|
||||||
// Initial state
|
...defaultState,
|
||||||
designProgress: null,
|
|
||||||
designProgress2: null,
|
|
||||||
preparationProgress: null,
|
|
||||||
applicationProgress: null,
|
|
||||||
equipmentCraftingProgress: null,
|
|
||||||
enchantmentDesigns: [],
|
|
||||||
unlockedEffects: [],
|
|
||||||
equippedInstances: initial.equippedInstances,
|
|
||||||
equipmentInstances: initial.instances,
|
|
||||||
lootInventory: {
|
|
||||||
materials: {},
|
|
||||||
blueprints: [],
|
|
||||||
},
|
|
||||||
enchantmentSelection: {
|
|
||||||
selectedEquipmentType: null,
|
|
||||||
selectedEffects: [],
|
|
||||||
designName: '',
|
|
||||||
selectedDesign: null,
|
|
||||||
selectedEquipmentInstance: null,
|
|
||||||
},
|
|
||||||
lastError: null,
|
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
setDesignProgress: (progress) => set({ designProgress: progress }),
|
setDesignProgress: (progress) => set({ designProgress: progress }),
|
||||||
@@ -369,6 +348,10 @@ export const useCraftingStore = create<CraftingStore>()(
|
|||||||
const state = get();
|
const state = get();
|
||||||
return processEquipmentCraftingTick(state, set as unknown as (partial: Partial<CraftingState>) => void);
|
return processEquipmentCraftingTick(state, set as unknown as (partial: Partial<CraftingState>) => void);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
resetCrafting: () => {
|
||||||
|
set(createDefaultCraftingState());
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ export interface CraftingActions {
|
|||||||
clearLastError: () => void;
|
clearLastError: () => void;
|
||||||
unlockEffects: (effectIds: string[]) => void;
|
unlockEffects: (effectIds: string[]) => void;
|
||||||
processEquipmentCraftingTick: () => { completed: boolean; logMessage?: string };
|
processEquipmentCraftingTick: () => { completed: boolean; logMessage?: string };
|
||||||
|
resetCrafting: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CraftingStore = CraftingState & CraftingActions;
|
export type CraftingStore = CraftingState & CraftingActions;
|
||||||
|
|||||||
@@ -95,14 +95,8 @@ export const useDisciplineStore = create<DisciplineStore>()(
|
|||||||
if (nonPaused >= s.concurrentLimit) return s;
|
if (nonPaused >= s.concurrentLimit) return s;
|
||||||
if (!canProceedDiscipline(def, existing, gameState)) return s;
|
if (!canProceedDiscipline(def, existing, gameState)) return s;
|
||||||
|
|
||||||
// Invoker disciplines require at least one signed guardian pact
|
|
||||||
if (def.attunement === 'invoker') {
|
|
||||||
const signedPacts = gameState?.signedPacts || [];
|
|
||||||
if (signedPacts.length === 0) return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check discipline prerequisites (requires field → discipline XP or mana type unlock)
|
// Check discipline prerequisites (requires field → discipline XP or mana type unlock)
|
||||||
const prereqCheck = checkDisciplinePrerequisites(def, s.disciplines, ALL_DISCIPLINES, gameState?.elements);
|
const prereqCheck = checkDisciplinePrerequisites(def, s.disciplines, ALL_DISCIPLINES, gameState?.elements, gameState?.signedPacts);
|
||||||
if (!prereqCheck.canProceed) return s;
|
if (!prereqCheck.canProceed) return s;
|
||||||
|
|
||||||
// For conversion disciplines: gate on having all source mana types unlocked
|
// For conversion disciplines: gate on having all source mana types unlocked
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import { useUIStore } from './uiStore';
|
|||||||
import { usePrestigeStore } from './prestigeStore';
|
import { usePrestigeStore } from './prestigeStore';
|
||||||
import { useManaStore } from './manaStore';
|
import { useManaStore } from './manaStore';
|
||||||
import { useCombatStore } from './combatStore';
|
import { useCombatStore } from './combatStore';
|
||||||
|
import { useDisciplineStore } from './discipline-slice';
|
||||||
|
import { useAttunementStore } from './attunementStore';
|
||||||
|
import { useCraftingStore } from './craftingStore';
|
||||||
import { computeDisciplineEffects } from '../effects/discipline-effects';
|
import { computeDisciplineEffects } from '../effects/discipline-effects';
|
||||||
|
|
||||||
// Exact localStorage keys matching each store's persist config `name`
|
// Exact localStorage keys matching each store's persist config `name`
|
||||||
@@ -32,6 +35,9 @@ export const createResetGame = (set: (state: Partial<GameCoordinatorState>) => v
|
|||||||
usePrestigeStore.getState().resetPrestige();
|
usePrestigeStore.getState().resetPrestige();
|
||||||
useManaStore.getState().resetMana({});
|
useManaStore.getState().resetMana({});
|
||||||
useCombatStore.getState().resetCombat(startFloor);
|
useCombatStore.getState().resetCombat(startFloor);
|
||||||
|
useDisciplineStore.getState().resetDisciplines();
|
||||||
|
useAttunementStore.getState().resetAttunements();
|
||||||
|
useCraftingStore.getState().resetCrafting();
|
||||||
|
|
||||||
set({
|
set({
|
||||||
...initialState,
|
...initialState,
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ export function checkDisciplinePrerequisites(
|
|||||||
allDisciplines: Record<string, DisciplineState>,
|
allDisciplines: Record<string, DisciplineState>,
|
||||||
allDefinitions: DisciplineDefinition[],
|
allDefinitions: DisciplineDefinition[],
|
||||||
elements?: Record<string, { unlocked: boolean }>,
|
elements?: Record<string, { unlocked: boolean }>,
|
||||||
|
signedPacts?: number[],
|
||||||
): { canProceed: boolean; missingPrereqs: string[] } {
|
): { canProceed: boolean; missingPrereqs: string[] } {
|
||||||
if (!discipline.requires || discipline.requires.length === 0) {
|
if (!discipline.requires || discipline.requires.length === 0) {
|
||||||
return { canProceed: true, missingPrereqs: [] };
|
return { canProceed: true, missingPrereqs: [] };
|
||||||
@@ -127,7 +128,9 @@ export function checkDisciplinePrerequisites(
|
|||||||
for (const reqId of discipline.requires) {
|
for (const reqId of discipline.requires) {
|
||||||
// Special case: 'signed_pact' requires at least one guardian pact
|
// Special case: 'signed_pact' requires at least one guardian pact
|
||||||
if (reqId === 'signed_pact') {
|
if (reqId === 'signed_pact') {
|
||||||
|
if (!signedPacts || signedPacts.length === 0) {
|
||||||
missingPrereqs.push('Signed guardian pact');
|
missingPrereqs.push('Signed guardian pact');
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user