import type { ClassDef } from '../progression'; /** * Curated 5e SRD class data for the guided builder. The on-disk MPMB files carry * only names, so hit dice / proficiencies / caster type are authored here for the * core classes. Unknown classes fall back to manual entry in the wizard. */ export const DND5E_CLASSES: ClassDef[] = [ { name: 'Barbarian', hitDie: 12, keyAbilities: ['str'], saveProficient: ['str', 'con'], skillCount: 2, skillKeys: ['animal-handling', 'athletics', 'intimidation', 'nature', 'perception', 'survival'], caster: 'none' }, { name: 'Bard', hitDie: 8, keyAbilities: ['cha'], saveProficient: ['dex', 'cha'], skillCount: 3, skillKeys: [], caster: 'full', spellAbility: 'cha' }, { name: 'Cleric', hitDie: 8, keyAbilities: ['wis'], saveProficient: ['wis', 'cha'], skillCount: 2, skillKeys: ['history', 'insight', 'medicine', 'persuasion', 'religion'], caster: 'full', spellAbility: 'wis' }, { name: 'Druid', hitDie: 8, keyAbilities: ['wis'], saveProficient: ['int', 'wis'], skillCount: 2, skillKeys: ['arcana', 'animal-handling', 'insight', 'medicine', 'nature', 'perception', 'religion', 'survival'], caster: 'full', spellAbility: 'wis' }, { name: 'Fighter', hitDie: 10, keyAbilities: ['str', 'dex'], saveProficient: ['str', 'con'], skillCount: 2, skillKeys: ['acrobatics', 'animal-handling', 'athletics', 'history', 'insight', 'intimidation', 'perception', 'survival'], caster: 'none' }, { name: 'Monk', hitDie: 8, keyAbilities: ['dex', 'wis'], saveProficient: ['str', 'dex'], skillCount: 2, skillKeys: ['acrobatics', 'athletics', 'history', 'insight', 'religion', 'stealth'], caster: 'none' }, { name: 'Paladin', hitDie: 10, keyAbilities: ['str', 'cha'], saveProficient: ['wis', 'cha'], skillCount: 2, skillKeys: ['athletics', 'insight', 'intimidation', 'medicine', 'persuasion', 'religion'], caster: 'half', spellAbility: 'cha' }, { name: 'Ranger', hitDie: 10, keyAbilities: ['dex', 'wis'], saveProficient: ['str', 'dex'], skillCount: 3, skillKeys: ['animal-handling', 'athletics', 'insight', 'investigation', 'nature', 'perception', 'stealth', 'survival'], caster: 'half', spellAbility: 'wis' }, { name: 'Rogue', hitDie: 8, keyAbilities: ['dex'], saveProficient: ['dex', 'int'], skillCount: 4, skillKeys: ['acrobatics', 'athletics', 'deception', 'insight', 'intimidation', 'investigation', 'perception', 'performance', 'persuasion', 'sleight-of-hand', 'stealth'], caster: 'none' }, { name: 'Sorcerer', hitDie: 6, keyAbilities: ['cha'], saveProficient: ['con', 'cha'], skillCount: 2, skillKeys: ['arcana', 'deception', 'insight', 'intimidation', 'persuasion', 'religion'], caster: 'full', spellAbility: 'cha' }, { name: 'Warlock', hitDie: 8, keyAbilities: ['cha'], saveProficient: ['wis', 'cha'], skillCount: 2, skillKeys: ['arcana', 'deception', 'history', 'intimidation', 'investigation', 'nature', 'religion'], caster: 'pact', spellAbility: 'cha' }, { name: 'Wizard', hitDie: 6, keyAbilities: ['int'], saveProficient: ['int', 'wis'], skillCount: 2, skillKeys: ['arcana', 'history', 'insight', 'investigation', 'medicine', 'religion'], caster: 'full', spellAbility: 'int' }, ]; // Spell slots [1st..9th] by character level for a full caster (PHB). const FULL: Record = { 1: [2], 2: [3], 3: [4, 2], 4: [4, 3], 5: [4, 3, 2], 6: [4, 3, 3], 7: [4, 3, 3, 1], 8: [4, 3, 3, 2], 9: [4, 3, 3, 3, 1], 10: [4, 3, 3, 3, 2], 11: [4, 3, 3, 3, 2, 1], 12: [4, 3, 3, 3, 2, 1], 13: [4, 3, 3, 3, 2, 1, 1], 14: [4, 3, 3, 3, 2, 1, 1], 15: [4, 3, 3, 3, 2, 1, 1, 1], 16: [4, 3, 3, 3, 2, 1, 1, 1], 17: [4, 3, 3, 3, 2, 1, 1, 1, 1], 18: [4, 3, 3, 3, 3, 1, 1, 1, 1], 19: [4, 3, 3, 3, 3, 2, 1, 1, 1], 20: [4, 3, 3, 3, 3, 2, 2, 1, 1], }; // Half caster (Paladin/Ranger), spells from level 2. const HALF: Record = { 2: [2], 3: [3], 4: [3], 5: [4, 2], 6: [4, 2], 7: [4, 3], 8: [4, 3], 9: [4, 3, 2], 10: [4, 3, 2], 11: [4, 3, 3], 12: [4, 3, 3], 13: [4, 3, 3, 1], 14: [4, 3, 3, 1], 15: [4, 3, 3, 2], 16: [4, 3, 3, 2], 17: [4, 3, 3, 3, 1], 18: [4, 3, 3, 3, 1], 19: [4, 3, 3, 3, 2], 20: [4, 3, 3, 3, 2], }; // Warlock pact magic: [slotCount, slotLevel]. const PACT: Record = { 1: [1, 1], 2: [2, 1], 3: [2, 2], 4: [2, 2], 5: [2, 3], 6: [2, 3], 7: [2, 4], 8: [2, 4], 9: [2, 5], 10: [2, 5], 11: [3, 5], 12: [3, 5], 13: [3, 5], 14: [3, 5], 15: [3, 5], 16: [3, 5], 17: [4, 5], 18: [4, 5], 19: [4, 5], 20: [4, 5], }; function clamp(level: number): number { return Math.max(1, Math.min(20, Math.floor(level) || 1)); } export function dnd5eSlots(caster: ClassDef['caster'], level: number): { level: number; max: number; current: number }[] { const lvl = clamp(level); const table = caster === 'full' ? FULL[lvl] : caster === 'half' ? HALF[lvl] : undefined; if (!table) return []; return table.map((max, i) => ({ level: i + 1, max, current: max })); } /** * PHB multiclass spellcaster level: full-caster levels count fully, half-casters * halve (floored), third-casters third (floored). Warlock pact magic is separate * and excluded. The combined level reads the standard full-caster slot table — * so e.g. Paladin 6 / Sorcerer 1 = level (3 + 1) = 4 → 4/3 first-/second slots. */ export function multiclassCasterLevel(parts: { full?: number; half?: number; third?: number }): number { return (parts.full ?? 0) + Math.floor((parts.half ?? 0) / 2) + Math.floor((parts.third ?? 0) / 3); } export function multiclassSlots(parts: { full?: number; half?: number; third?: number }): { level: number; max: number; current: number }[] { const lvl = multiclassCasterLevel(parts); if (lvl < 1) return []; // a single half-caster level (e.g. Paladin 1) grants no slots const table = FULL[Math.min(20, lvl)]; if (!table) return []; return table.map((max, i) => ({ level: i + 1, max, current: max })); } export function dnd5ePact(level: number): { level: number; max: number; current: number } | undefined { const p = PACT[clamp(level)]; return p ? { level: p[1], max: p[0], current: p[0] } : undefined; } /** Average HP at a level: max die at L1, then per-level average + CON each level. */ export function dnd5eHp(hitDie: number, level: number, conMod: number): number { const lvl = clamp(level); const avg = Math.ceil(hitDie / 2) + 1; return Math.max(1, hitDie + conMod + (lvl - 1) * (avg + conMod)); } /** Levels that grant an Ability Score Improvement (or feat). Includes class extras. */ export function dnd5eAsiLevels(className: string): number[] { const base = [4, 8, 12, 16, 19]; const c = className.toLowerCase(); if (c === 'fighter') return [...base, 6, 14].sort((a, b) => a - b); if (c === 'rogue') return [...base, 10].sort((a, b) => a - b); return base; } /** * Curated official 5e subclasses, merged into the builder's class list on top of * whatever the on-disk classes.json carries (which is SRD + third-party homebrew and * is missing most PHB/XGE archetypes). Keyed by class name; deduped by name at merge * time. Descriptions are short "what it plays like" blurbs — subclass *features* are * not yet auto-applied (a separate task), so these add the missing options to pick. */ export const DND5E_SUBCLASSES: Record = { Barbarian: [ { name: 'Path of the Totem Warrior', desc: 'Adopt a spirit animal (Bear/Eagle/Wolf) for resilience, mobility, or pack tactics.' }, { name: 'Path of the Ancestral Guardian', desc: 'Spectral ancestors shield your allies and punish foes who ignore you.' }, { name: 'Path of the Storm Herald', desc: 'Your rage radiates an aura of desert heat, sea cold, or tundra lightning.' }, { name: 'Path of the Zealot', desc: 'A divine warrior whose rage deals extra radiant/necrotic damage and cheats death.' }, { name: 'Path of the Beast', desc: 'Grow natural weapons — bite, claws, or tail — that change each rage.' }, { name: 'Path of Wild Magic', desc: 'Rage triggers chaotic magical surges and bends luck for your allies.' }, { name: 'Path of the Battlerager', desc: 'A spiked-armor dwarf who rages into the enemy and damages whatever grapples it.' }, ], Bard: [ { name: 'College of Valor', desc: 'A martial skald: armor, extra attack, and Combat Inspiration for allies.' }, { name: 'College of Glamour', desc: 'Fey-charming performer who beguiles foes and rallies allies with temp HP.' }, { name: 'College of Swords', desc: 'A blade-flourishing duelist who weaves Bardic Inspiration into weapon attacks.' }, { name: 'College of Whispers', desc: 'A sinister spy who plants fear and wears the identity of those it kills.' }, { name: 'College of Eloquence', desc: 'A peerless orator — unfailing Persuasion and inspiration that never wastes.' }, { name: 'College of Creation', desc: 'Sings reality into being: animate objects and a Mote of Potential.' }, { name: 'College of Spirits', desc: 'Channels tales of the dead through a Spiritual Focus for random boons.' }, ], Cleric: [ { name: 'Knowledge Domain', desc: 'A scholar-priest with bonus skills, languages, and read-the-past magic.' }, { name: 'Light Domain', desc: 'Radiant blaster with Warding Flare and a fiery turn-undead burst.' }, { name: 'Nature Domain', desc: 'A druidic cleric with heavy armor, a skill, and charm of beasts/plants.' }, { name: 'Tempest Domain', desc: 'Storm-caller: maximize lightning/thunder and smite attackers with thunderbolts.' }, { name: 'Trickery Domain', desc: 'A deceiver who blesses allies with poison and conjures duplicate images.' }, { name: 'War Domain', desc: 'A frontline crusader with bonus weapon attacks and divine guidance to hit.' }, { name: 'Death Domain', desc: 'A necromantic cleric wielding extra necrotic damage and reaper cantrips.' }, { name: 'Arcana Domain', desc: 'A mage-priest who adds wizard spells and disrupts other magic.' }, { name: 'Forge Domain', desc: 'An armored smith who blesses armor/weapons and channels fire.' }, { name: 'Grave Domain', desc: 'A warden of death’s border who heals the dying and curses the marked.' }, { name: 'Order Domain', desc: 'A lawful enforcer granting allies free attacks when you heal or charm.' }, { name: 'Peace Domain', desc: 'A bonded protector who links allies to share defenses and reactions.' }, { name: 'Twilight Domain', desc: 'A guardian of the night granting darkvision and rolling temp-HP aura.' }, ], Druid: [ { name: 'Circle of the Moon', desc: 'A combat shapeshifter who wild-shapes into powerful beasts as a bonus action.' }, { name: 'Circle of Dreams', desc: 'A fey-touched healer with a restful Hearth and Balm of the Summer Court.' }, { name: 'Circle of the Shepherd', desc: 'A summoner who plants totem spirits that buff and heal conjured allies.' }, { name: 'Circle of Spores', desc: 'A necro-druid wreathed in halo of spores that deal damage and raise the dead.' }, { name: 'Circle of Stars', desc: 'An astral druid adopting Archer/Chalice/Dragon Starry Forms.' }, { name: 'Circle of Wildfire', desc: 'Bonded to a wildfire spirit of flame and renewal — burn and heal.' }, ], Fighter: [ { name: 'Battle Master', desc: 'A tactician with superiority dice and maneuvers (trip, disarm, riposte…).' }, { name: 'Eldritch Knight', desc: 'A martial caster blending wizard spells with weapon attacks.' }, { name: 'Arcane Archer', desc: 'An elf marksman with magical Arcane Shots (banishing, seeking, grasping).' }, { name: 'Cavalier', desc: 'A mounted/guardian fighter who marks foes and protects allies.' }, { name: 'Samurai', desc: 'A resolute duelist whose Fighting Spirit grants advantage and temp HP.' }, { name: 'Psi Warrior', desc: 'A psionic soldier using Psionic Energy dice to shove, shield, and strike.' }, { name: 'Rune Knight', desc: 'A giant-runed warrior who grows large and carves magical runes into gear.' }, { name: 'Echo Knight', desc: 'Summons a manifest echo of itself to attack and teleport from.' }, { name: 'Banneret (Purple Dragon Knight)', desc: 'An inspiring leader who rallies and protects the whole party.' }, ], Monk: [ { name: 'Way of Shadow', desc: 'A ninja who melds into darkness and teleports between shadows.' }, { name: 'Way of the Four Elements', desc: 'A martial elementalist who spends ki to cast elemental disciplines.' }, { name: 'Way of the Drunken Master', desc: 'An unpredictable brawler who weaves, redirects misses, and disengages freely.' }, { name: 'Way of the Kensei', desc: 'A weapon-bonded monk who makes chosen weapons into monk weapons.' }, { name: 'Way of the Sun Soul', desc: 'Hurls radiant ki bolts and unleashes a searing burst.' }, { name: 'Way of Mercy', desc: 'A masked medic who heals with one hand and inflicts necrotic harm with the other.' }, { name: 'Way of the Astral Self', desc: 'Manifests spectral arms and body powered by Wisdom.' }, ], Paladin: [ { name: 'Oath of the Ancients', desc: 'A fey-light champion of joy with retributive aura and nature magic.' }, { name: 'Oath of Vengeance', desc: 'A relentless avenger who marks a foe and hunts it down.' }, { name: 'Oath of Conquest', desc: 'A tyrant of fear whose aura freezes the frightened in place.' }, { name: 'Oath of Redemption', desc: 'A pacifist peacemaker who rebukes attackers and shields allies.' }, { name: 'Oath of Glory', desc: 'A heroic athlete who boosts allies’ speed and damage to inspire legend.' }, { name: 'Oath of the Watchers', desc: 'A sentinel against extraplanar threats with bonus initiative and saves.' }, { name: 'Oath of the Crown', desc: 'A sworn protector of civilization who taunts and intercepts blows.' }, { name: 'Oathbreaker', desc: 'A fallen paladin who commands undead and aura-boosts cruelty.' }, ], Ranger: [ { name: 'Beast Master', desc: 'Bonds with an animal companion that fights at your command.' }, { name: 'Gloom Stalker', desc: 'An ambusher of the dark — extra first-round attack, invisible in shadow.' }, { name: 'Horizon Walker', desc: 'A planar sentinel dealing force damage and teleporting between attacks.' }, { name: 'Monster Slayer', desc: 'Studies a foe to exploit its weakness and turn its own attacks back.' }, { name: 'Fey Wanderer', desc: 'A whimsical wanderer adding psychic damage and charm to its hunt.' }, { name: 'Swarmkeeper', desc: 'Accompanied by a magical swarm that moves, damages, and protects.' }, { name: 'Drakewarden', desc: 'Raises a draconic companion that grows into a rideable mount.' }, ], Rogue: [ { name: 'Mastermind', desc: 'A master of intrigue and tactics: extra tool/language proficiencies, Help as a bonus action at range, and uncanny insight into a creature’s drives.' }, { name: 'Assassin', desc: 'A killer who excels at ambush — auto-crits on surprised foes and disguises.' }, { name: 'Arcane Trickster', desc: 'A spell-thief blending illusion/enchantment magic with mage-hand legerdemain.' }, { name: 'Scout', desc: 'A skirmisher and survivalist who darts away when foes close in.' }, { name: 'Swashbuckler', desc: 'A dashing duelist who sneak-attacks one-on-one and disengages with flair.' }, { name: 'Inquisitive', desc: 'A sleuth who reads tells, finds weak points, and sees through deception.' }, { name: 'Phantom', desc: 'A death-touched rogue who steals skills from the slain and deals soul damage.' }, { name: 'Soulknife', desc: 'A psionic assassin who manifests psychic blades and walks unseen.' }, ], Sorcerer: [ { name: 'Wild Magic', desc: 'Chaotic surges and Tides of Chaos — power at the cost of unpredictability.' }, { name: 'Divine Soul', desc: 'Heaven-touched caster with access to cleric spells and favored rerolls.' }, { name: 'Shadow Magic', desc: 'A Shadowfell-born caster with a hound of ill omen and a brush with death.' }, { name: 'Storm Sorcery', desc: 'Rides the wind — fly on a casting, blast lightning/thunder.' }, { name: 'Aberrant Mind', desc: 'A psionic mind with telepathy and subtle, Sorcery-Point-fueled spells.' }, { name: 'Clockwork Soul', desc: 'Channels cosmic order to negate advantage/disadvantage and ward allies.' }, ], Warlock: [ { name: 'The Archfey', desc: 'A fey patron granting fae presence, misty escape, and beguiling defenses.' }, { name: 'The Great Old One', desc: 'An alien patron of telepathy, psychic crits, and mind-enslaving magic.' }, { name: 'The Celestial', desc: 'A radiant patron granting healing light and resurrection of allies.' }, { name: 'The Hexblade', desc: 'A weapon-bonded patron — Charisma attacks, a curse, and armor proficiency.' }, { name: 'The Fathomless', desc: 'A deep-sea patron with a tentacle of the depths and abyssal mobility.' }, { name: 'The Genie', desc: 'A noble genie patron granting a bottle refuge and elemental damage.' }, { name: 'The Undead', desc: 'A deathless patron of dread — frighten foes and ignore being downed.' }, { name: 'The Undying', desc: 'A patron of unlife granting resilience against death and disease.' }, ], Wizard: [ { name: 'School of Abjuration', desc: 'A protective ward absorbs damage; counter and dispel magic better.' }, { name: 'School of Conjuration', desc: 'A summoner who conjures objects and minor creatures and teleports.' }, { name: 'School of Divination', desc: 'A seer who replaces rolls with foreseen Portent dice.' }, { name: 'School of Enchantment', desc: 'A charmer who hypnotizes and redirects attacks onto other foes.' }, { name: 'School of Illusion', desc: 'A trickster whose illusions become partly real and reshape on the fly.' }, { name: 'School of Necromancy', desc: 'A death-mage who commands the undead and drains life to heal.' }, { name: 'School of Transmutation', desc: 'A shapechanger with a transmuter’s stone of versatile boons.' }, { name: 'Bladesinging', desc: 'An elven sword-mage who weaves dazzling defense with melee and spells.' }, { name: 'War Magic', desc: 'A battle-mage balancing arcane deflection with offensive surges.' }, { name: 'Order of Scribes', desc: 'A living spellbook that swaps damage types and pens spells cheaply.' }, { name: 'Chronurgy Magic', desc: 'A time-mage who alters initiative and saving throws with temporal magic.' }, { name: 'Graviturgy Magic', desc: 'A gravity-mage who manipulates weight, speed, and crushing force.' }, ], };