Fix condition mechanics: per-statistic PF2e penalties, 5e exhaustion, slowed, frightened decay
- PF2e clumsy/enfeebled/drained/stupefied now penalise their correct, independent statistic family (Dex/Str/Con/mental) instead of collapsing into one max'd '−N status'. Frightened/sickened are 'all checks'. Distinct badges per family. - Slowed is action loss only (no roll penalty); fatigued shows flat −1 AC/saves — neither is value-scaled anymore. - 5e Exhaustion now derives its cumulative level effects (ability-check disadvantage L1, speed halved L2, attack + all-save disadvantage L3, speed 0 L5, dead L6). - 5e Prone badge is range-qualified (adv. melee / disadv. ranged). - New tickConditionsEndOfTurn: PF2e Frightened decays by 1 at end of turn (wired into nextTurn via the combat tracker), removed at 0. MechanicalState.statusPenalty (single number) → statusPenalties (per-family map). Tests updated + added. Deferred: Drained HP/max-HP reduction (needs creature level threaded into the damage flow). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,13 @@ import type { AbilityKey, AdvState } from './types';
|
||||
* hardcoded `if`s in combat code. `deriveState` (creatureState.ts) folds these
|
||||
* into a single query the tracker and dice roller ask.
|
||||
*/
|
||||
/**
|
||||
* Which statistic family a PF2e value-scaled status penalty applies to. Different
|
||||
* families are independent (they target different rolls) and must never be compared
|
||||
* against each other — clumsy hits Dex-based rolls, enfeebled hits Str-based rolls, etc.
|
||||
*/
|
||||
export type PenaltyTarget = 'all' | 'str' | 'dex' | 'con' | 'mental';
|
||||
|
||||
export interface ConditionEffect {
|
||||
/** sets effective speed to 0 */
|
||||
speedZero?: boolean;
|
||||
@@ -19,8 +26,11 @@ export interface ConditionEffect {
|
||||
saves?: Partial<Record<AbilityKey, 'disadvantage' | 'auto-fail'>>;
|
||||
/** cannot take actions or reactions */
|
||||
incapacitated?: boolean;
|
||||
/** pf2e: a numeric status penalty scaling with the condition's value */
|
||||
statusPenalty?: boolean;
|
||||
/**
|
||||
* pf2e: a value-scaled status penalty applied to this statistic family. The
|
||||
* penalty magnitude is the condition's value; family decides which rolls it hits.
|
||||
*/
|
||||
statusPenalty?: PenaltyTarget;
|
||||
}
|
||||
|
||||
const ALL_PHYS: Partial<Record<AbilityKey, 'auto-fail'>> = { str: 'auto-fail', dex: 'auto-fail' };
|
||||
@@ -48,22 +58,24 @@ export const CONDITION_EFFECTS_5E: Record<string, ConditionEffect> = {
|
||||
*/
|
||||
export const CONDITION_EFFECTS_PF2E: Record<string, ConditionEffect> = {
|
||||
blinded: { attacksAgainst: 'advantage' },
|
||||
clumsy: { statusPenalty: true },
|
||||
drained: { statusPenalty: true },
|
||||
// Each valued condition penalises a DIFFERENT statistic family (never compared):
|
||||
clumsy: { statusPenalty: 'dex' }, // AC, Reflex, Dex attacks/skills
|
||||
drained: { statusPenalty: 'con' }, // Fortitude (also reduces HP — applied separately)
|
||||
enfeebled: { statusPenalty: 'str' }, // Str attacks/damage, Athletics
|
||||
stupefied: { statusPenalty: 'mental' }, // spell attacks/DCs, mental checks
|
||||
frightened: { statusPenalty: 'all' }, // every check and DC; decays 1/turn
|
||||
sickened: { statusPenalty: 'all' }, // every check and DC
|
||||
dazzled: { attacksAgainst: 'advantage' },
|
||||
enfeebled: { statusPenalty: true },
|
||||
fatigued: { statusPenalty: true },
|
||||
frightened: { statusPenalty: true },
|
||||
// fatigued (flat -1 AC/saves) and slowed (action loss) are NOT value-scaled — see deriveState.
|
||||
fatigued: {},
|
||||
slowed: {},
|
||||
grabbed: { speedZero: true, attacksAgainst: 'advantage' },
|
||||
immobilized: { speedZero: true },
|
||||
paralyzed: { incapacitated: true, speedZero: true, attacksAgainst: 'advantage' },
|
||||
petrified: { incapacitated: true, speedZero: true },
|
||||
prone: { attacksAgainst: 'advantage' },
|
||||
restrained: { speedZero: true, incapacitated: false, attacksAgainst: 'advantage' },
|
||||
sickened: { statusPenalty: true },
|
||||
slowed: { statusPenalty: true },
|
||||
stunned: { incapacitated: true },
|
||||
stupefied: { statusPenalty: true },
|
||||
unconscious: { incapacitated: true, speedZero: true, attacksAgainst: 'advantage' },
|
||||
'off-guard': { attacksAgainst: 'advantage' },
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user