Files
ttrpg_manager/src/lib/rules/dnd5e/progression.ts
T
NilsBriggen f1e15d4011 5e builder: add the full set of official PHB/XGE/TCE subclasses
Fills DND5E_SUBCLASSES with the official archetypes missing from classes.json across all
12 classes (Battle Master, Hexblade, Assassin, the 8 wizard schools, all cleric domains,
etc.) — ~90 entries, merged + deduped by name ahead of the on-disk SRD+homebrew list.
Selectable options only; feature mechanics remain a separate task.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 22:38:41 +02:00

234 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<number, number[]> = {
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<number, number[]> = {
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<number, [number, number]> = {
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<string, { name: string; desc: string }[]> = {
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 deaths 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 creatures 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 transmuters 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.' },
],
};