65 lines
2.5 KiB
TypeScript
65 lines
2.5 KiB
TypeScript
// ponytail: 5e DMG p.82 XP thresholds per character. Single source of truth
|
|
// for the encounter builder's budget math. The "Easy/Medium/Hard/Deadly"
|
|
// thresholds are *per character*; the table is indexed by character level.
|
|
|
|
export type Difficulty = "Easy" | "Medium" | "Hard" | "Deadly";
|
|
|
|
const THRESHOLDS: Record<number, Record<Difficulty, number>> = {
|
|
1: { Easy: 25, Medium: 50, Hard: 75, Deadly: 100 },
|
|
2: { Easy: 50, Medium: 100, Hard: 150, Deadly: 200 },
|
|
3: { Easy: 75, Medium: 150, Hard: 225, Deadly: 400 },
|
|
4: { Easy: 125, Medium: 250, Hard: 375, Deadly: 500 },
|
|
5: { Easy: 250, Medium: 500, Hard: 750, Deadly: 1100 },
|
|
6: { Easy: 300, Medium: 600, Hard: 900, Deadly: 1400 },
|
|
7: { Easy: 350, Medium: 750, Hard: 1100, Deadly: 1700 },
|
|
8: { Easy: 450, Medium: 900, Hard: 1400, Deadly: 2100 },
|
|
9: { Easy: 550, Medium: 1100, Hard: 1600, Deadly: 2400 },
|
|
10: { Easy: 600, Medium: 1200, Hard: 1900, Deadly: 2800 },
|
|
11: { Easy: 800, Medium: 1600, Hard: 2400, Deadly: 3600 },
|
|
12: { Easy: 1000, Medium: 2000, Hard: 3000, Deadly: 4500 },
|
|
13: { Easy: 1100, Medium: 2200, Hard: 3400, Deadly: 5100 },
|
|
14: { Easy: 1250, Medium: 2500, Hard: 3800, Deadly: 5700 },
|
|
15: { Easy: 1400, Medium: 2800, Hard: 4300, Deadly: 6400 },
|
|
16: { Easy: 1600, Medium: 3200, Hard: 4800, Deadly: 7200 },
|
|
17: { Easy: 2000, Medium: 3900, Hard: 5900, Deadly: 8800 },
|
|
18: { Easy: 2100, Medium: 4200, Hard: 6300, Deadly: 9500 },
|
|
19: { Easy: 2400, Medium: 4900, Hard: 7300, Deadly: 10900 },
|
|
20: { Easy: 2800, Medium: 5700, Hard: 8500, Deadly: 12700 },
|
|
};
|
|
|
|
export interface Budget {
|
|
easy: number;
|
|
medium: number;
|
|
hard: number;
|
|
deadly: number;
|
|
}
|
|
|
|
/** Compute per-difficulty XP budgets for a party. Clamps level to 1..20. */
|
|
export function encounterBudget(partyLevel: number, partySize: number): Budget {
|
|
const lvl = Math.max(1, Math.min(20, Math.round(partyLevel)));
|
|
const size = Math.max(1, Math.round(partySize));
|
|
const t = THRESHOLDS[lvl];
|
|
return {
|
|
easy: t.Easy * size,
|
|
medium: t.Medium * size,
|
|
hard: t.Hard * size,
|
|
deadly: t.Deadly * size,
|
|
};
|
|
}
|
|
|
|
/** Map an XP total to a difficulty bucket. */
|
|
export function difficultyForXp(xp: number, budget: Budget): Difficulty {
|
|
if (xp >= budget.deadly) return "Deadly";
|
|
if (xp >= budget.hard) return "Hard";
|
|
if (xp >= budget.medium) return "Medium";
|
|
if (xp >= budget.easy) return "Easy";
|
|
return "Easy";
|
|
}
|
|
|
|
export const DIFFICULTY_COLOR: Record<Difficulty, string> = {
|
|
Easy: "var(--color-success)",
|
|
Medium: "var(--color-info)",
|
|
Hard: "var(--color-gold-bright)",
|
|
Deadly: "var(--color-danger)",
|
|
};
|