Files
initiative/packages/domain/src/conditions.ts
Lukas 553e09f280 Enforce maximum values for PF2e numbered conditions
Cap dying (4), doomed (3), wounded (3), and slowed (3) at their
rule-defined maximums. The domain clamps values in setConditionValue
and the condition picker disables the [+] button at the cap.

Closes #31

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 00:04:47 +02:00

535 lines
15 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.
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"
| "stupefied"
| "unconscious"
| "undetected"
| "wounded";
export interface ConditionEntry {
readonly id: ConditionId;
readonly value?: number;
}
import type { RulesEdition } from "./rules-edition.js";
export interface ConditionDefinition {
readonly id: ConditionId;
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 these systems' pickers. */
readonly systems?: readonly RulesEdition[];
readonly valued?: boolean;
/** Rule-defined maximum value for PF2e valued conditions. */
readonly maxValue?: number;
}
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",
description:
"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. Auto-fail checks requiring sight. Immune to visual effects. Overrides dazzled.",
iconName: "EyeOff",
color: "neutral",
},
{
id: "charmed",
label: "Charmed",
description:
"Can't attack or target the charmer with harmful abilities. Charmer has Advantage on social checks.",
description5e:
"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. Auto-critically-fail hearing checks. 2 status penalty to Perception. Auditory actions require DC 5 flat check. Immune to auditory effects.",
iconName: "EarOff",
color: "neutral",
},
{
id: "exhaustion",
label: "Exhaustion",
description:
"D20 Tests reduced by 2 \u00D7 exhaustion level.\nSpeed reduced by 5 ft. \u00D7 level.\nLong rest removes 1 level.\nDeath at 6 levels.",
description5e:
"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",
label: "Frightened",
description:
"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",
label: "Grappled",
description:
"Speed 0. Disadvantage on attacks against targets other than the grappler. Grappler can drag you (extra movement cost). Ends if grappler is Incapacitated or you leave their reach.",
description5e:
"Speed 0. Ends if grappler is Incapacitated or moved out of reach.",
iconName: "Hand",
color: "neutral",
systems: ["5e", "5.5e"],
},
{
id: "incapacitated",
label: "Incapacitated",
description:
"Can't take Actions, Bonus Actions, or Reactions. Can't speak. Concentration is broken. Disadvantage on Initiative.",
description5e: "Can't take Actions or Reactions.",
iconName: "Ban",
color: "gray",
systems: ["5e", "5.5e"],
},
{
id: "invisible",
label: "Invisible",
description:
"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",
},
{
id: "paralyzed",
label: "Paralyzed",
description:
"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. Can only Recall Knowledge or use mental actions.",
iconName: "ZapOff",
color: "yellow",
},
{
id: "petrified",
label: "Petrified",
description:
"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",
},
{
id: "poisoned",
label: "Poisoned",
description: "Disadvantage on attack rolls and ability checks.",
description5e: "Disadvantage on attack rolls and ability checks.",
iconName: "Droplet",
color: "green",
systems: ["5e", "5.5e"],
},
{
id: "prone",
label: "Prone",
description:
"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",
},
{
id: "restrained",
label: "Restrained",
description:
"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",
},
{
id: "sapped",
label: "Sapped",
description:
"Disadvantage on next attack roll before the start of your next turn. (Weapon Mastery: Sap)",
description5e: "",
iconName: "ShieldMinus",
color: "amber",
systems: ["5.5e"],
},
{
id: "slowed",
label: "Slowed",
description:
"Speed reduced by 10 ft. until the start of your next turn. (Weapon Mastery: Slow)",
description5e: "",
iconName: "Snail",
color: "sky",
systems: ["5.5e"],
},
{
id: "stunned",
label: "Stunned",
description:
"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. Lose X total actions across turns, then the condition ends. Overrides slowed.",
iconName: "Sparkles",
color: "yellow",
valued: true,
},
{
id: "unconscious",
label: "Unconscious",
description:
"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. Blinded. 4 status penalty to AC, Perception, and Reflex saves. 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. Must Strike or cast offensive cantrips at random targets. DC 11 flat check when damaged to end.",
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,
maxValue: 3,
},
{
id: "drained",
label: "Drained",
description: "",
description5e: "",
descriptionPf2e:
"X status penalty to Con-based checks and DCs. Lose X × level 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,
maxValue: 4,
},
{
id: "enfeebled",
label: "Enfeebled",
description: "",
description5e: "",
descriptionPf2e:
"X status penalty to Str-based rolls and DCs, including melee attack and damage rolls and Athletics checks.",
iconName: "TrendingDown",
color: "amber",
systems: ["pf2e"],
valued: true,
},
{
id: "fascinated",
label: "Fascinated",
description: "",
description5e: "",
descriptionPf2e:
"2 status penalty to Perception and skill checks. Can't use concentrate actions unless related to the fascination. Ends if hostile action is used against you or allies.",
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:
"Off-guard. Immobilized. Manipulate actions require DC 5 flat check or are wasted.",
iconName: "Hand",
color: "neutral",
systems: ["pf2e"],
},
{
id: "hidden",
label: "Hidden",
description: "",
description5e: "",
descriptionPf2e:
"Known location but can't be seen. Off-guard to that creature. DC 11 flat check to target or miss.",
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,
maxValue: 3,
},
{
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,
maxValue: 3,
},
] as const;
export const VALID_CONDITION_IDS: ReadonlySet<string> = new Set(
CONDITION_DEFINITIONS.map((d) => d.id),
);
export function getConditionsForEdition(
edition: RulesEdition,
): readonly ConditionDefinition[] {
return CONDITION_DEFINITIONS.filter(
(d) => d.systems === undefined || d.systems.includes(edition),
)
.slice()
.sort((a, b) => a.label.localeCompare(b.label));
}