// ─── Non-Combat Room Actions ─────────────────────────────────────────────────── // Handles timed progression for Library, Recovery, Treasure, and Puzzle rooms. // Extracted from combat-descent-actions.ts to stay under the 400-line limit. import type { CombatState, CombatStore } from './combat-state.types'; import { useDisciplineStore } from './discipline-slice'; import { useManaStore } from './manaStore'; import { useAttunementStore } from './attunementStore'; import { PUZZLE_ROOMS } from '../constants'; type GetFn = () => CombatStore; type SetFn = (state: Partial) => void; type AdvanceFn = (get: GetFn, set: SetFn) => void; // ─── Room Entry Handlers ────────────────────────────────────────────────────── export function onEnterLibraryRoom(get: GetFn, set: SetFn): void { const s = get(); set({ currentRoom: { ...s.currentRoom, libraryProgress: 0, libraryRequired: 1, libraryStayed: false, }, }); get().addActivityLog('floor_transition', `Entered library room on Floor ${s.currentFloor}`); } export function onEnterRecoveryRoom(get: GetFn, set: SetFn): void { const s = get(); set({ currentRoom: { ...s.currentRoom, recoveryProgress: 0, recoveryRequired: 1, recoveryStayed: false, }, }); get().addActivityLog('floor_transition', `Entered recovery room on Floor ${s.currentFloor}`); } export function onEnterTreasureRoom(get: GetFn, set: SetFn): void { const s = get(); set({ currentRoom: { ...s.currentRoom, treasureProgress: 0, treasureRequired: 1, treasureLootClaimed: [], }, }); get().addActivityLog('floor_transition', `Entered treasure room on Floor ${s.currentFloor}`); } export function onEnterPuzzleRoom(get: GetFn, set: SetFn): void { const s = get(); const puzzleId = s.currentRoom.puzzleId || 'enchanter_trial'; const puzzle = PUZZLE_ROOMS[puzzleId]; const attunements = puzzle?.attunements ?? ['enchanter']; let baseHours: number; if (s.currentFloor <= 20) { baseHours = 4; } else if (s.currentFloor <= 50) { baseHours = 8; } else if (s.currentFloor <= 100) { baseHours = 16; } else { baseHours = 24; } const attunementStore = useAttunementStore.getState(); const maxReduction = 0.9; const perAttunementShare = maxReduction / attunements.length; let totalReduction = 0; for (const attId of attunements) { const attState = attunementStore.attunements[attId]; if (attState?.active) { const MAX_LEVEL = 10; const levelFraction = Math.min((attState.level || 1) / MAX_LEVEL, 1); totalReduction += perAttunementShare * levelFraction; } } totalReduction = Math.min(totalReduction, maxReduction); const requiredHours = baseHours * (1 - totalReduction); set({ currentRoom: { ...s.currentRoom, puzzleProgress: 0, puzzleRequired: Math.max(0.5, requiredHours), puzzleId, puzzleAttunements: attunements, }, }); get().addActivityLog('floor_transition', `Entered puzzle room on Floor ${s.currentFloor} (${puzzle?.name || puzzleId})`); } // ─── Tick Handlers ──────────────────────────────────────────────────────────── export function tickNonCombatRoom(get: GetFn, set: SetFn, hours: number, advance: AdvanceFn): void { const s = get(); const rt = s.currentRoom.roomType as string; if (rt === 'library') { tickLibraryRoom(get, set, hours, advance); } else if (rt === 'recovery') { tickRecoveryRoom(get, set, hours, advance); } else if (rt === 'treasure') { tickTreasureRoom(get, set, hours, advance); } else if (rt === 'puzzle') { tickPuzzleRoom(get, set, hours, advance); } } function tickLibraryRoom(get: GetFn, set: SetFn, hours: number, advance: AdvanceFn): void { const s = get(); const room = s.currentRoom; const progress = (room.libraryProgress || 0) + hours; const required = room.libraryRequired || 1; const BASE_LIBRARY_XP_PER_HOUR = 50; const xpGrant = Math.floor(BASE_LIBRARY_XP_PER_HOUR * 25 * hours); if (xpGrant > 0) { const disciplineStore = useDisciplineStore.getState(); const allDisciplines = disciplineStore.disciplines; const entries = Object.entries(allDisciplines); if (entries.length > 0) { const [targetId, targetDs] = entries[Math.floor(Math.random() * entries.length)]; useDisciplineStore.setState((prev) => ({ disciplines: { ...prev.disciplines, [targetId]: { ...targetDs, xp: (targetDs?.xp || 0) + xpGrant }, }, totalXP: prev.totalXP + xpGrant, })); get().addActivityLog('special_effect', `${targetDs?.id || targetId} gained ${xpGrant} XP from studying ancient tomes`); } } if (progress >= required) { set({ currentRoom: { ...room, libraryProgress: progress } }); advance(get, set); } else { set({ currentRoom: { ...room, libraryProgress: progress } }); } } function tickRecoveryRoom(get: GetFn, set: SetFn, hours: number, advance: AdvanceFn): void { const s = get(); const room = s.currentRoom; const progress = (room.recoveryProgress || 0) + hours; const required = room.recoveryRequired || 1; if (progress >= required) { set({ currentRoom: { ...room, recoveryProgress: progress } }); advance(get, set); } else { set({ currentRoom: { ...room, recoveryProgress: progress } }); } } function tickTreasureRoom(get: GetFn, set: SetFn, hours: number, advance: AdvanceFn): void { const s = get(); const room = s.currentRoom; const progress = (room.treasureProgress || 0) + hours; const required = room.treasureRequired || 1; const loot = room.treasureLoot || []; const claimed = room.treasureLootClaimed || []; const thresholds = [0.10, 0.50, 0.95, 1.0]; const newlyClaimed: number[] = []; const claimedSet = new Set(claimed); for (const threshold of thresholds) { if (progress / required >= threshold) { const targetCount = Math.min(Math.ceil(loot.length * threshold), loot.length); for (let i = 0; i < targetCount; i++) { if (!claimedSet.has(i)) { claimedSet.add(i); newlyClaimed.push(i); } } } } for (const idx of newlyClaimed) { const drop = loot[idx]; if (!drop) continue; if (drop.type === 'material' || drop.type === 'gold') { const amount = drop.amount ? Math.floor(Math.random() * (drop.amount.max - drop.amount.min + 1) + drop.amount.min) : 1; useManaStore.getState().addRawMana(amount, 999999); get().addActivityLog('special_effect', `Found ${amount}x ${drop.name}`); } else if (drop.type === 'essence') { const elementMap: Record = { fireEssenceDrop: 'fire', waterEssenceDrop: 'water', airEssenceDrop: 'air', earthEssenceDrop: 'earth', lightEssenceDrop: 'light', darkEssenceDrop: 'dark', deathEssenceDrop: 'death', }; const elem = elementMap[drop.id]; if (elem) { const elemState = useManaStore.getState().elements[elem]; if (elemState) { const amount = drop.amount ? Math.floor(Math.random() * (drop.amount.max - drop.amount.min + 1) + drop.amount.min) : 1; useManaStore.getState().addElementMana(elem, amount, elemState.max); get().addActivityLog('special_effect', `Found ${amount}x ${drop.name}`); } } } else if (drop.type === 'blueprint') { get().addActivityLog('special_effect', `Found blueprint: ${drop.name}`); } } if (progress >= required) { set({ currentRoom: { ...room, treasureProgress: progress, treasureLootClaimed: Array.from(claimedSet) } }); advance(get, set); } else { set({ currentRoom: { ...room, treasureProgress: progress, treasureLootClaimed: Array.from(claimedSet) } }); } } function tickPuzzleRoom(get: GetFn, set: SetFn, hours: number, advance: AdvanceFn): void { const s = get(); const room = s.currentRoom; const progress = (room.puzzleProgress || 0) + hours; const required = room.puzzleRequired || 1; if (progress >= required) { set({ currentRoom: { ...room, puzzleProgress: progress } }); get().addActivityLog('puzzle_solved', `Puzzle solved on Floor ${s.currentFloor}!`); advance(get, set); } else { set({ currentRoom: { ...room, puzzleProgress: progress } }); } } // ─── Player Actions ─────────────────────────────────────────────────────────── export function skipNonCombatRoom(get: GetFn, set: SetFn, advance: AdvanceFn): void { const s = get(); const rt = s.currentRoom.roomType as string; if (rt === 'library' || rt === 'recovery' || rt === 'treasure') { get().addActivityLog('floor_transition', `Skipped ${rt} room on Floor ${s.currentFloor}`); advance(get, set); } } export function stayLongerInRoom(get: GetFn, set: SetFn): void { const s = get(); const room = s.currentRoom; const rt = room.roomType as string; if (rt === 'library' && !room.libraryStayed) { set({ currentRoom: { ...room, libraryRequired: (room.libraryRequired || 1) + 1, libraryStayed: true, }, }); get().addActivityLog('special_effect', 'Decided to study for 1 more hour in the library'); } else if (rt === 'recovery' && !room.recoveryStayed) { set({ currentRoom: { ...room, recoveryRequired: (room.recoveryRequired || 1) + 1, recoveryStayed: true, }, }); get().addActivityLog('special_effect', 'Decided to rest for 1 more hour in the recovery room'); } }