895807d6c9
Layout: - CombatantRow: action controls (damage/AC/move/remove) grouped into one right-aligned wrapper so they wrap together instead of scattering on narrow widths. - top bar: the campaign switcher now shrinks/truncates instead of overflowing on tablet widths. - character sheet header: stat coins go full-width grid on mobile; the name truncates and scales down on small screens. UX consistency: - player Cast and resource Spend/Regain no longer fail silently — Cast shows the reason, resource −/+ disable at their bounds. - the AI encounter builder now catches load failures and reports them; level-up advisor shows the human error message, not the error-kind enum. - section headers standardized on the .smallcaps design token across 16 files (replacing an ad-hoc uppercase class). - player HP bar uses the shared Meter primitive. pf2e depth: condition-effects table gains drained, dazzled, enfeebled, fatigued. FeatCard: dropped a dead text-xl wrapper left from the emoji purge. Left as documented (functional, off-theme, refactor-risky): the native confirm/prompt in Settings cloud-conflict and the session-password flow. Gate: 293 unit + 35 e2e + 2 realtime; tsc + build clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
74 lines
3.4 KiB
TypeScript
74 lines
3.4 KiB
TypeScript
import type { SystemId } from '@/lib/rules';
|
|
import type { AbilityKey, AdvState } from './types';
|
|
|
|
/**
|
|
* The mechanical effect a condition imposes — a declarative table, never
|
|
* hardcoded `if`s in combat code. `deriveState` (creatureState.ts) folds these
|
|
* into a single query the tracker and dice roller ask.
|
|
*/
|
|
export interface ConditionEffect {
|
|
/** sets effective speed to 0 */
|
|
speedZero?: boolean;
|
|
/** this creature's attack rolls */
|
|
attackRolls?: AdvState;
|
|
/** attack rolls made AGAINST this creature */
|
|
attacksAgainst?: AdvState;
|
|
/** disadvantage on ability checks (5e) */
|
|
abilityChecks?: boolean;
|
|
/** per-ability save penalty (auto-fail dominates disadvantage) */
|
|
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;
|
|
}
|
|
|
|
const ALL_PHYS: Partial<Record<AbilityKey, 'auto-fail'>> = { str: 'auto-fail', dex: 'auto-fail' };
|
|
|
|
/** D&D 5e condition mechanics (keys lowercased to match stored names). */
|
|
export const CONDITION_EFFECTS_5E: Record<string, ConditionEffect> = {
|
|
blinded: { attackRolls: 'disadvantage', attacksAgainst: 'advantage' },
|
|
frightened: { attackRolls: 'disadvantage', abilityChecks: true },
|
|
grappled: { speedZero: true },
|
|
incapacitated: { incapacitated: true },
|
|
invisible: { attackRolls: 'advantage', attacksAgainst: 'disadvantage' },
|
|
paralyzed: { incapacitated: true, speedZero: true, attacksAgainst: 'advantage', saves: ALL_PHYS },
|
|
petrified: { incapacitated: true, speedZero: true, attacksAgainst: 'advantage', saves: ALL_PHYS },
|
|
poisoned: { attackRolls: 'disadvantage', abilityChecks: true },
|
|
prone: { attackRolls: 'disadvantage', attacksAgainst: 'advantage' },
|
|
restrained: { speedZero: true, attackRolls: 'disadvantage', attacksAgainst: 'advantage', saves: { dex: 'disadvantage' } },
|
|
stunned: { incapacitated: true, speedZero: true, attacksAgainst: 'advantage', saves: ALL_PHYS },
|
|
unconscious: { incapacitated: true, speedZero: true, attacksAgainst: 'advantage', saves: ALL_PHYS },
|
|
};
|
|
|
|
/**
|
|
* Pathfinder 2e condition mechanics. PF2e uses flat numeric penalties rather
|
|
* than advantage/disadvantage; valued conditions (frightened N, clumsy N, …)
|
|
* contribute a status penalty aggregated by `deriveState`.
|
|
*/
|
|
export const CONDITION_EFFECTS_PF2E: Record<string, ConditionEffect> = {
|
|
blinded: { attacksAgainst: 'advantage' },
|
|
clumsy: { statusPenalty: true },
|
|
drained: { statusPenalty: true },
|
|
dazzled: { attacksAgainst: 'advantage' },
|
|
enfeebled: { statusPenalty: true },
|
|
fatigued: { statusPenalty: true },
|
|
frightened: { statusPenalty: true },
|
|
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' },
|
|
};
|
|
|
|
export function conditionEffects(system: SystemId): Record<string, ConditionEffect> {
|
|
return system === 'pf2e' ? CONDITION_EFFECTS_PF2E : CONDITION_EFFECTS_5E;
|
|
}
|