// ─── Guardian Encounters ─────────────────────────────────────────────────────── // Procedural guardian generation and unified lookup. // // Guardian progression: // Floors 10-80: Base + utility elements (static, in guardian-data.ts) // Floors 90-110: Compound elements (static, in guardian-data.ts) // Floors 120-140: Exotic elements (static, in guardian-data.ts) // Floor 150+: Procedural combo guardians (this file) // // All lookups go through getGuardianForFloor() which merges static + procedural. import type { GuardianDef } from '../types'; import { BASE_GUARDIANS } from './guardian-data'; // ─── Name Generation ────────────────────────────────────────────────────────── const GUARDIAN_PREFIXES: Record = { fire: ['Ignis', 'Pyra', 'Sol', 'Vulcan', 'Ember'], water: ['Aqua', 'Marina', 'Thal', 'Pelag', 'Coral'], air: ['Ventus', 'Zephyr', 'Aero', 'Nimbus', 'Gale'], earth: ['Terra', 'Petra', 'Mont', 'Gaia', 'Ore'], light: ['Lux', 'Solaris', 'Radi', 'Lumin', 'Aur'], dark: ['Umbra', 'Noct', 'Teneb', 'Ereb', 'Nyx'], death: ['Mors', 'Necro', 'Than', 'Mort', 'Skull'], transference: ['Link', 'Arcana', 'Vinc', 'Bind', 'Chain'], metal: ['Ferr', 'Chroma', 'Steel', 'Arg', 'Ore'], sand: ['Arena', 'Dune', 'Siroc', 'Erg', 'Sah'], lightning: ['Volt', 'Fulg', 'Electr', 'Spark', 'Storm'], crystal: ['Prism', 'Gemma', 'Crystal', 'Shard', 'Facet'], stellar: ['Astro', 'Stella', 'Nova', 'Cosmo', 'Lumin'], void: ['Void', 'Abyss', 'Null', 'Nihil', 'Obliv'], }; const GUARDIAN_TITLES: string[] = [ 'Warden', 'Keeper', 'Lord', 'Titan', 'Sovereign', 'Guardian', 'Sentinel', 'Champion', 'Overlord', 'Archon', ]; export function generateGuardianName(element: string, floor: number = 0): string { const prefixes = GUARDIAN_PREFIXES[element] || ['Unknown']; const prefix = prefixes[floor % prefixes.length]; const title = GUARDIAN_TITLES[Math.floor(floor / 10) % GUARDIAN_TITLES.length]; return `${prefix} the ${title}`; } export function generateComboGuardianName(elements: string[], floor: number = 0): string { const parts = elements.map((el, i) => { const prefixes = GUARDIAN_PREFIXES[el] || ['Unknown']; return prefixes[(floor + i) % prefixes.length]; }); const title = GUARDIAN_TITLES[Math.floor(floor / 10) % GUARDIAN_TITLES.length]; return `${parts.join('-')} the ${title}`; } // ─── Guardian HP Scaling ────────────────────────────────────────────────────── export function getGuardianHP(floor: number): number { const base = 5000; const exponent = 1.1 + (floor / 200); return Math.floor(base * Math.pow(floor / 10, exponent)); } // ─── Combination Guardians (Floor 150+) ─────────────────────────────────────── // Procedural guardians that get stronger over time. Each combo pairs two // base/utility elements and scales stats based on floor number. const COMBO_PAIRS: [string, string][] = [ ['fire', 'water'], // Steam ['fire', 'air'], // Smoke ['water', 'earth'], // Mud ['light', 'dark'], // Twilight ['death', 'light'], // Undeath ['fire', 'death'], // Hellfire ['water', 'dark'], // Abyssal ['air', 'light'], // Radiant wind ['earth', 'death'], // Fossil ]; export function getComboGuardian(floor: number): GuardianDef { const comboIndex = Math.floor((floor - 150) / 10) % COMBO_PAIRS.length; const [el1, el2] = COMBO_PAIRS[comboIndex]; const hp = getGuardianHP(floor); const armor = Math.min(0.5, 0.25 + (floor - 150) * 0.002); return { name: '', element: `${el1}+${el2}`, hp, pact: 6.0 + (floor - 150) * 0.05, color: '#E8D5F5', armor, boons: [ { type: 'elementalDamage', value: 10, desc: `+10% ${el1} damage` }, { type: 'elementalDamage', value: 10, desc: `+10% ${el2} damage` }, ], pactCost: Math.floor(hp * 0.5), pactTime: 20 + Math.floor((floor - 150) / 10), uniquePerk: `Dual-aspect: ${el1} and ${el2} spells gain +20% effectiveness`, power: Math.floor(hp * 0.5), effects: [ { type: `${el1}_boost`, value: 0.2 }, { type: `${el2}_boost`, value: 0.2 }, ], signingCost: { mana: Math.floor(hp * 0.5), time: 20 + Math.floor((floor - 150) / 10) }, unlocksMana: [el1, el2], damageMultiplier: 3.0 + (floor - 150) * 0.02, insightMultiplier: 2.5 + (floor - 150) * 0.01, }; } // ─── Procedural Guardian Lookup (Floor 150+) ────────────────────────────────── export function getExtendedGuardian(floor: number): GuardianDef | null { if (floor >= 150 && floor % 10 === 0) { const g = getComboGuardian(floor); if (!g.name) { const elements = g.element.split('+'); g.name = generateComboGuardianName(elements, floor); } return g; } return null; } // ─── Unified Guardian System ───────────────────────────────────────────────── // Merges static guardians (floors 10–140) with procedural combo guardians (150+). /** Get the guardian for any floor. Returns null if no guardian at that floor. */ export function getGuardianForFloor(floor: number): GuardianDef | null { if (BASE_GUARDIANS[floor]) { const g = { ...BASE_GUARDIANS[floor] }; if (!g.name) g.name = generateGuardianName(g.element, floor); return g; } return getExtendedGuardian(floor); } /** All guardian floors — merged from static + extended. */ export function getAllGuardianFloors(): number[] { const staticFloors = Object.keys(BASE_GUARDIANS).map(Number); const comboFloors = Array.from({ length: 10 }, (_, i) => 150 + i * 10); const all = new Set([...staticFloors, ...comboFloors]); return Array.from(all).sort((a, b) => a - b); } /** All guardian floors including procedural — kept for backwards compatibility. */ export const ALL_GUARDIAN_FLOORS: number[] = [ ...Object.keys(BASE_GUARDIANS).map(Number), ...Array.from({ length: 10 }, (_, i) => 150 + i * 10), ].sort((a, b) => a - b); /** Check if a floor is a guardian floor (every 10th floor). */ export function isGuardianFloor(floor: number): boolean { return floor % 10 === 0; }