Add Pathfinder 2e game system mode
All checks were successful
CI / check (push) Successful in 2m21s
CI / build-image (push) Successful in 24s

Implements PF2e as an alternative game system alongside D&D 5e/5.5e.
Settings modal "Game System" selector switches conditions, bestiary,
stat block layout, and initiative calculation between systems.

- Valued conditions with increment/decrement UX (Clumsy 2, Frightened 3)
- 2,502 PF2e creatures from bundled search index (77 sources)
- PF2e stat block: level, traits, Perception, Fort/Ref/Will, ability mods
- Perception-based initiative rolling
- System-scoped source cache (D&D and PF2e sources don't collide)
- Backwards-compatible condition rehydration (ConditionId[] → ConditionEntry[])
- Difficulty indicator hidden in PF2e mode (excluded from MVP)

Closes dostulata/initiative#19

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-04-07 01:26:22 +02:00
parent 8f6eebc43b
commit e62c49434c
67 changed files with 27758 additions and 527 deletions

View File

@@ -1,21 +1,48 @@
export type ConditionId =
| "blinded"
| "charmed"
| "clumsy"
| "concealed"
| "confused"
| "controlled"
| "dazzled"
| "deafened"
| "doomed"
| "drained"
| "dying"
| "enfeebled"
| "exhaustion"
| "fascinated"
| "fatigued"
| "fleeing"
| "frightened"
| "grabbed"
| "grappled"
| "hidden"
| "immobilized"
| "incapacitated"
| "invisible"
| "off-guard"
| "paralyzed"
| "petrified"
| "poisoned"
| "prone"
| "quickened"
| "restrained"
| "sapped"
| "sickened"
| "slowed"
| "slowed-pf2e"
| "stunned"
| "unconscious";
| "stupefied"
| "unconscious"
| "undetected"
| "wounded";
export interface ConditionEntry {
readonly id: ConditionId;
readonly value?: number;
}
import type { RulesEdition } from "./rules-edition.js";
@@ -24,20 +51,24 @@ export interface ConditionDefinition {
readonly label: string;
readonly description: string;
readonly description5e: string;
readonly descriptionPf2e?: string;
readonly iconName: string;
readonly color: string;
/** When set, the condition only appears in this edition's picker. */
readonly edition?: RulesEdition;
/** When set, the condition only appears in these systems' pickers. */
readonly systems?: readonly RulesEdition[];
readonly valued?: boolean;
}
export function getConditionDescription(
def: ConditionDefinition,
edition: RulesEdition,
): string {
if (edition === "pf2e" && def.descriptionPf2e) return def.descriptionPf2e;
return edition === "5e" ? def.description5e : def.description;
}
export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
// ── Shared conditions (D&D + PF2e) ──
{
id: "blinded",
label: "Blinded",
@@ -45,6 +76,8 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
"Can't see. Auto-fail sight checks. Attacks have Disadvantage; attacks against have Advantage.",
description5e:
"Can't see. Auto-fail sight checks. Attacks have Disadvantage; attacks against have Advantage.",
descriptionPf2e:
"Can't see. All terrain is difficult terrain. 4 status penalty to Perception checks involving sight. Immune to visual effects. Auto-fail checks requiring sight. Off-guard.",
iconName: "EyeOff",
color: "neutral",
},
@@ -57,12 +90,15 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
"Can't attack or target the charmer with harmful abilities. Charmer has Advantage on social checks.",
iconName: "Heart",
color: "pink",
systems: ["5e", "5.5e"],
},
{
id: "deafened",
label: "Deafened",
description: "Can't hear. Auto-fail hearing checks.",
description5e: "Can't hear. Auto-fail hearing checks.",
descriptionPf2e:
"Can't hear. 2 status penalty to Perception checks and Initiative. Auto-fail hearing checks. Immune to auditory effects.",
iconName: "EarOff",
color: "neutral",
},
@@ -75,6 +111,7 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
"L1: Disadvantage on ability checks\nL2: Speed halved\nL3: Disadvantage on attacks and saves\nL4: HP max halved\nL5: Speed 0\nL6: Death\nLong rest removes 1 level.",
iconName: "BatteryLow",
color: "amber",
systems: ["5e", "5.5e"],
},
{
id: "frightened",
@@ -83,8 +120,11 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
"Disadvantage on ability checks and attacks while source of fear is in line of sight. Can't willingly move closer to the source.",
description5e:
"Disadvantage on ability checks and attacks while source of fear is in line of sight. Can't willingly move closer to the source.",
descriptionPf2e:
"X status penalty to all checks and DCs (X = value). Can't willingly approach the source.",
iconName: "Siren",
color: "orange",
valued: true,
},
{
id: "grappled",
@@ -95,6 +135,7 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
"Speed 0. Ends if grappler is Incapacitated or moved out of reach.",
iconName: "Hand",
color: "neutral",
systems: ["5e", "5.5e"],
},
{
id: "incapacitated",
@@ -104,6 +145,7 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
description5e: "Can't take Actions or Reactions.",
iconName: "Ban",
color: "gray",
systems: ["5e", "5.5e"],
},
{
id: "invisible",
@@ -112,6 +154,8 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
"Can't be seen. Advantage on Initiative. Not affected by effects requiring sight (unless caster sees you). Attacks have Advantage; attacks against have Disadvantage.",
description5e:
"Impossible to see without magic or special sense. Heavily Obscured. Attacks have Advantage; attacks against have Disadvantage.",
descriptionPf2e:
"Can't be seen except by special senses. Undetected to everyone. Can't be targeted except by effects that don't require sight.",
iconName: "Ghost",
color: "violet",
},
@@ -122,6 +166,7 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
"Incapacitated. Can't move or speak. Auto-fail Str/Dex saves. Attacks against have Advantage. Hits within 5 ft. are critical.",
description5e:
"Incapacitated. Can't move or speak. Auto-fail Str/Dex saves. Attacks against have Advantage. Hits within 5 ft. are critical.",
descriptionPf2e: "Can't act. Off-guard. 4 status penalty to AC.",
iconName: "ZapOff",
color: "yellow",
},
@@ -132,6 +177,8 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
"Turned to stone. Weight \u00D710. Incapacitated. Can't move or speak. Attacks against have Advantage. Auto-fail Str/Dex saves. Resistant to all damage. Immune to poison and disease.",
description5e:
"Turned to stone. Weight \u00D710. Incapacitated. Can't move or speak. Attacks against have Advantage. Auto-fail Str/Dex saves. Resistant to all damage. Immune to poison and disease.",
descriptionPf2e:
"Can't act. Can't sense anything. AC = 9. Hardness 8. Immune to most effects.",
iconName: "Gem",
color: "slate",
},
@@ -142,6 +189,7 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
description5e: "Disadvantage on attack rolls and ability checks.",
iconName: "Droplet",
color: "green",
systems: ["5e", "5.5e"],
},
{
id: "prone",
@@ -150,6 +198,8 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
"Can only crawl (costs extra movement). Disadvantage on attacks. Attacks within 5 ft. have Advantage; ranged attacks have Disadvantage. Standing up costs half movement.",
description5e:
"Can only crawl (costs extra movement). Disadvantage on attacks. Attacks within 5 ft. have Advantage; ranged attacks have Disadvantage. Standing up costs half movement.",
descriptionPf2e:
"Off-guard. 2 circumstance penalty to attack rolls. Only movement is Crawl and Stand. +1 circumstance bonus to AC vs. ranged attacks, 2 vs. melee.",
iconName: "ArrowDown",
color: "neutral",
},
@@ -160,6 +210,8 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
"Speed is 0. Attacks have Disadvantage. Attacks against have Advantage. Disadvantage on Dex saves.",
description5e:
"Speed is 0. Attacks have Disadvantage. Attacks against have Advantage. Disadvantage on Dex saves.",
descriptionPf2e:
"Off-guard. Immobilized. Can't use any actions with the attack trait except to attempt to Escape.",
iconName: "Link",
color: "neutral",
},
@@ -171,7 +223,7 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
description5e: "",
iconName: "ShieldMinus",
color: "amber",
edition: "5.5e",
systems: ["5.5e"],
},
{
id: "slowed",
@@ -181,7 +233,7 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
description5e: "",
iconName: "Snail",
color: "sky",
edition: "5.5e",
systems: ["5.5e"],
},
{
id: "stunned",
@@ -190,8 +242,11 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
"Incapacitated (can't act or speak). Auto-fail Str/Dex saves. Attacks against have Advantage.",
description5e:
"Incapacitated. Can't move. Can speak only falteringly. Auto-fail Str/Dex saves. Attacks against have Advantage.",
descriptionPf2e:
"Can't act. X value to actions per turn while the value counts down.",
iconName: "Sparkles",
color: "yellow",
valued: true,
},
{
id: "unconscious",
@@ -200,9 +255,261 @@ export const CONDITION_DEFINITIONS: readonly ConditionDefinition[] = [
"Incapacitated. Speed 0. Can't move or speak. Unaware of surroundings. Drops held items, falls Prone. Auto-fail Str/Dex saves. Attacks against have Advantage. Hits within 5 ft. are critical.",
description5e:
"Incapacitated. Speed 0. Can't move or speak. Unaware of surroundings. Drops held items, falls Prone. Auto-fail Str/Dex saves. Attacks against have Advantage. Hits within 5 ft. are critical.",
descriptionPf2e:
"Can't act. Off-guard. 4 status penalty to AC. 3 to Perception. Fall prone, drop items.",
iconName: "Moon",
color: "indigo",
},
// ── PF2e-only conditions ──
{
id: "clumsy",
label: "Clumsy",
description: "",
description5e: "",
descriptionPf2e:
"X status penalty to Dex-based checks and DCs, including AC, Reflex saves, and ranged attack rolls.",
iconName: "Footprints",
color: "amber",
systems: ["pf2e"],
valued: true,
},
{
id: "concealed",
label: "Concealed",
description: "",
description5e: "",
descriptionPf2e:
"DC 5 flat check for targeted attacks. Doesn't change which creatures can see you.",
iconName: "CloudFog",
color: "slate",
systems: ["pf2e"],
},
{
id: "confused",
label: "Confused",
description: "",
description5e: "",
descriptionPf2e:
"Off-guard. Can't Delay, Ready, or use reactions. GM determines targets randomly. Flat check DC 11 to act normally each turn.",
iconName: "CircleHelp",
color: "pink",
systems: ["pf2e"],
},
{
id: "controlled",
label: "Controlled",
description: "",
description5e: "",
descriptionPf2e:
"Another creature determines your actions. You gain no actions of your own.",
iconName: "Drama",
color: "pink",
systems: ["pf2e"],
},
{
id: "dazzled",
label: "Dazzled",
description: "",
description5e: "",
descriptionPf2e:
"All creatures and objects are concealed to you. DC 5 flat check for targeted attacks requiring sight.",
iconName: "Sun",
color: "yellow",
systems: ["pf2e"],
},
{
id: "doomed",
label: "Doomed",
description: "",
description5e: "",
descriptionPf2e:
"Die at dying X (where X = 4 doomed value instead of dying 4). Decreases by 1 on full night's rest.",
iconName: "Skull",
color: "red",
systems: ["pf2e"],
valued: true,
},
{
id: "drained",
label: "Drained",
description: "",
description5e: "",
descriptionPf2e:
"X status penalty to Con-based checks and DCs. Lose X × Hit Die in max HP. Decreases by 1 on full night's rest.",
iconName: "Droplets",
color: "red",
systems: ["pf2e"],
valued: true,
},
{
id: "dying",
label: "Dying",
description: "",
description5e: "",
descriptionPf2e:
"Unconscious. Make recovery checks at start of turn. At dying 4 (or 4 doomed), you die.",
iconName: "HeartPulse",
color: "red",
systems: ["pf2e"],
valued: true,
},
{
id: "enfeebled",
label: "Enfeebled",
description: "",
description5e: "",
descriptionPf2e:
"X status penalty to Str-based rolls, including melee attack and damage rolls.",
iconName: "TrendingDown",
color: "amber",
systems: ["pf2e"],
valued: true,
},
{
id: "fascinated",
label: "Fascinated",
description: "",
description5e: "",
descriptionPf2e:
"2 status penalty to all checks. Can't use hostile actions. Ends if hostile action is used against you.",
iconName: "Eye",
color: "violet",
systems: ["pf2e"],
},
{
id: "fatigued",
label: "Fatigued",
description: "",
description5e: "",
descriptionPf2e:
"1 status penalty to AC and saves. Can't use exploration activities while traveling. Recover after a full night's rest.",
iconName: "BatteryLow",
color: "amber",
systems: ["pf2e"],
},
{
id: "fleeing",
label: "Fleeing",
description: "",
description5e: "",
descriptionPf2e:
"Must spend actions to move away from the source. Can't Delay or Ready.",
iconName: "PersonStanding",
color: "orange",
systems: ["pf2e"],
},
{
id: "grabbed",
label: "Grabbed",
description: "",
description5e: "",
descriptionPf2e:
"Immobilized. Off-guard. Can't use actions with the move trait unless to Break Grapple.",
iconName: "Hand",
color: "neutral",
systems: ["pf2e"],
},
{
id: "hidden",
label: "Hidden",
description: "",
description5e: "",
descriptionPf2e:
"Known location but can't be seen. DC 11 flat check to target. Can use Seek to find.",
iconName: "EyeOff",
color: "slate",
systems: ["pf2e"],
},
{
id: "immobilized",
label: "Immobilized",
description: "",
description5e: "",
descriptionPf2e:
"Can't use any action with the move trait to change position.",
iconName: "Anchor",
color: "neutral",
systems: ["pf2e"],
},
{
id: "off-guard",
label: "Off-Guard",
description: "",
description5e: "",
descriptionPf2e: "2 circumstance penalty to AC. (Formerly flat-footed.)",
iconName: "ShieldOff",
color: "amber",
systems: ["pf2e"],
},
{
id: "quickened",
label: "Quickened",
description: "",
description5e: "",
descriptionPf2e:
"Gain 1 extra action at the start of your turn each round (limited uses specified by the effect).",
iconName: "Zap",
color: "green",
systems: ["pf2e"],
},
{
id: "sickened",
label: "Sickened",
description: "",
description5e: "",
descriptionPf2e:
"X status penalty to all checks and DCs. Can't willingly ingest anything. Reduce by retching (Fortitude save).",
iconName: "Droplet",
color: "green",
systems: ["pf2e"],
valued: true,
},
{
id: "slowed-pf2e",
label: "Slowed",
description: "",
description5e: "",
descriptionPf2e: "Lose X actions at the start of your turn each round.",
iconName: "Snail",
color: "sky",
systems: ["pf2e"],
valued: true,
},
{
id: "stupefied",
label: "Stupefied",
description: "",
description5e: "",
descriptionPf2e:
"X status penalty to Int/Wis/Cha-based checks and DCs, including spell attack rolls and spell DCs. DC 5 + X flat check to cast spells or lose the spell.",
iconName: "BrainCog",
color: "violet",
systems: ["pf2e"],
valued: true,
},
{
id: "undetected",
label: "Undetected",
description: "",
description5e: "",
descriptionPf2e:
"Location unknown. Must pick a square to target; DC 11 flat check. Attacker is off-guard against your attacks.",
iconName: "Ghost",
color: "violet",
systems: ["pf2e"],
},
{
id: "wounded",
label: "Wounded",
description: "",
description5e: "",
descriptionPf2e:
"Next time you gain dying, add wounded value to dying value. Wounded 1 when you recover from dying; increases if already wounded.",
iconName: "HeartCrack",
color: "red",
systems: ["pf2e"],
valued: true,
},
] as const;
export const VALID_CONDITION_IDS: ReadonlySet<string> = new Set(
@@ -213,6 +520,6 @@ export function getConditionsForEdition(
edition: RulesEdition,
): readonly ConditionDefinition[] {
return CONDITION_DEFINITIONS.filter(
(d) => d.edition === undefined || d.edition === edition,
(d) => d.systems === undefined || d.systems.includes(edition),
);
}