// ─── Spire Utility Functions ─────────────────────────────────────────────────── // Spire-specific utility functions for room generation, enemy stat scaling, etc. import type { RoomType, FloorState, EnemyState } from '../types'; import { PUZZLE_ROOMS } from '../constants'; import { getFloorMaxHP, getFloorElement } from './floor-utils'; import { getEnemyName } from './enemy-utils'; import { getGuardianForFloor, isGuardianFloor } from '../data/guardian-encounters'; // ─── Spire Room Configuration ───────────────────────────────────────────────── export const SPIRE_CONFIG = { minRoomsPerFloor: 5, maxRoomsPerFloor: 15, guardianRooms: 1, puzzleRoomChance: 0.12, rareRoomChance: 0.05, recoveryRoomChance: 0.4, libraryRoomChance: 0.3, treasureRoomChance: 0.3, }; // ─── Room Count ─────────────────────────────────────────────────────────────── export function getRoomsForFloor(floor: number): number { if (isGuardianFloor(floor)) return SPIRE_CONFIG.guardianRooms; const base = SPIRE_CONFIG.minRoomsPerFloor; const range = SPIRE_CONFIG.maxRoomsPerFloor - SPIRE_CONFIG.minRoomsPerFloor; // Slight increase in rooms at higher floors const floorBonus = Math.min(range, Math.floor(floor / 20)); const randomVariation = Math.floor(Math.random() * 3); return base + floorBonus + randomVariation; } // ─── Spire Room Types ───────────────────────────────────────────────────────── export type SpireRoomType = RoomType | 'recovery' | 'library' | 'treasure'; // ─── Room Generation ────────────────────────────────────────────────────────── export function generateSpireRoomType(floor: number, roomIndex: number, totalRooms: number): SpireRoomType { // Last room on guardian floors is always guardian if (isGuardianFloor(floor) && roomIndex === totalRooms - 1) { return 'guardian'; } // First room on a floor is never a special room (always combat) if (roomIndex === 0) { return generateCombatRoomType(floor); } // Rare rooms (mid-floor) if (roomIndex === Math.floor(totalRooms / 2) && Math.random() < SPIRE_CONFIG.rareRoomChance) { return generateRareRoomType(); } // Puzzle rooms if (floor % 7 === 0 && Math.random() < SPIRE_CONFIG.puzzleRoomChance) { return 'puzzle'; } return generateCombatRoomType(floor); } function generateCombatRoomType(_floor: number): RoomType { const roll = Math.random(); if (roll < 0.12) return 'swarm'; if (roll < 0.22) return 'speed'; return 'combat'; } function generateRareRoomType(): SpireRoomType { const roll = Math.random(); if (roll < SPIRE_CONFIG.recoveryRoomChance) return 'recovery'; if (roll < SPIRE_CONFIG.recoveryRoomChance + SPIRE_CONFIG.libraryRoomChance) return 'library'; return 'treasure'; } // ─── Floor State Generation ─────────────────────────────────────────────────── export function generateSpireFloorState(floor: number, roomIndex: number, totalRooms: number): FloorState { const roomType = generateSpireRoomType(floor, roomIndex, totalRooms); const element = getFloorElement(floor); const baseHP = getFloorMaxHP(floor); switch (roomType) { case 'guardian': { const guardian = getGuardianForFloor(floor); if (guardian) { return { roomType: 'guardian', enemies: [{ id: 'guardian', name: guardian.name, hp: guardian.hp, maxHP: guardian.hp, armor: guardian.armor || 0, dodgeChance: 0, barrier: 0, element: guardian.element, }], }; } // Fallback if no guardian defined for this floor return generateCombatRoom(floor, element, baseHP); } case 'swarm': return generateSwarmRoom(floor, element, baseHP); case 'speed': return generateSpeedRoom(floor, element, baseHP); case 'puzzle': { const puzzleKeys = Object.keys(PUZZLE_ROOMS); const selectedPuzzle = puzzleKeys[Math.floor(Math.random() * puzzleKeys.length)]; const puzzle = PUZZLE_ROOMS[selectedPuzzle]; return { roomType: 'puzzle', enemies: [], puzzleProgress: 0, puzzleRequired: 1, puzzleId: selectedPuzzle, puzzleAttunements: puzzle.attunements, }; } case 'recovery': return { roomType: 'recovery', enemies: [], recoveryProgress: 0, recoveryRequired: 1, }; case 'library': return { roomType: 'library', enemies: [], libraryProgress: 0, libraryRequired: 1, }; case 'treasure': return { roomType: 'treasure', enemies: [], }; default: return generateCombatRoom(floor, element, baseHP); } } function generateCombatRoom(floor: number, element: string, baseHP: number): FloorState { const armor = getSpireEnemyArmor(floor); const barrier = getSpireEnemyBarrier(floor, element); const enemyName = getEnemyName(element, floor); return { roomType: 'combat', enemies: [{ id: 'enemy', name: enemyName, hp: baseHP, maxHP: baseHP, armor, dodgeChance: 0, barrier, element, }], }; } function generateSwarmRoom(floor: number, element: string, baseHP: number): FloorState { const numEnemies = 3 + Math.floor(Math.random() * 5); // 3-7 enemies const enemies: EnemyState[] = []; for (let i = 0; i < numEnemies; i++) { enemies.push({ id: `swarm_${i}`, name: `${getEnemyName(element, floor)} ${i + 1}`, hp: Math.floor(baseHP * 0.35), maxHP: Math.floor(baseHP * 0.35), armor: Math.floor(floor / 15) * 0.02, dodgeChance: 0, barrier: 0, element, }); } return { roomType: 'swarm', enemies }; } function generateSpeedRoom(floor: number, element: string, baseHP: number): FloorState { const dodgeChance = Math.min(0.55, 0.20 + floor * 0.005); const armor = getSpireEnemyArmor(floor); return { roomType: 'speed', enemies: [{ id: 'agile_enemy', name: `Agile ${getEnemyName(element, floor)}`, hp: baseHP, maxHP: baseHP, armor, dodgeChance, barrier: getSpireEnemyBarrier(floor, element), element, }], }; } // ─── Enemy Stat Scaling ─────────────────────────────────────────────────────── export function getSpireEnemyArmor(floor: number): number { if (floor < 10) return 0; const baseChance = Math.min(0.5, (floor - 10) * 0.01); if (Math.random() > baseChance) return 0; const minArmor = 0.05; const maxArmor = 0.30; const progress = Math.min(1, (floor - 10) / 90); return minArmor + (maxArmor - minArmor) * progress * Math.random(); } export function getSpireEnemyBarrier(floor: number, element: string): number { if (floor < 15) return 0; const barrierElements = ['light', 'water', 'earth']; const baseChance = barrierElements.includes(element) ? 0.12 : 0.06; const floorBonus = Math.min(0.2, (floor - 15) * 0.003); if (Math.random() > Math.min(0.35, baseChance + floorBonus)) return 0; const progress = Math.min(1, (floor - 15) / 85); return 0.1 + progress * 0.2; } // ─── Insight Calculation ────────────────────────────────────────────────────── export function calcInsight(floor: number, isGuardian: boolean): number { const base = Math.floor(Math.pow(floor, 1.2)); return isGuardian ? Math.floor(base * 2.5) : base; } // ─── Room Type Display ──────────────────────────────────────────────────────── export function getSpireRoomTypeDisplay(roomType: SpireRoomType): { label: string; icon: string; color: string } { const displays: Record = { combat: { label: 'Combat', icon: '⚔️', color: '#EF4444' }, swarm: { label: 'Swarm', icon: '🐝', color: '#F59E0B' }, speed: { label: 'Speed', icon: '💨', color: '#3B82F6' }, guardian: { label: 'Guardian', icon: '🛡️', color: '#EF4444' }, puzzle: { label: 'Puzzle', icon: '🧩', color: '#8B5CF6' }, recovery: { label: 'Recovery', icon: '💚', color: '#10B981' }, library: { label: 'Ancient Library', icon: '📚', color: '#6366F1' }, treasure: { label: 'Treasure', icon: '💎', color: '#F59E0B' }, }; return displays[roomType] || { label: 'Unknown', icon: '❓', color: '#6B7280' }; }