import type { Pf2eCreature } from "@initiative/domain"; import { formatInitiativeModifier } from "@initiative/domain"; import { PropertyLine, SectionDivider, TraitSection, } from "./stat-block-parts.js"; interface Pf2eStatBlockProps { creature: Pf2eCreature; } const ALIGNMENTS = new Set([ "lg", "ng", "cg", "ln", "n", "cn", "le", "ne", "ce", ]); function capitalize(s: string): string { return s.charAt(0).toUpperCase() + s.slice(1); } function displayTraits(traits: readonly string[]): string[] { return traits.filter((t) => !ALIGNMENTS.has(t)).map(capitalize); } function formatMod(mod: number): string { return mod >= 0 ? `+${mod}` : `${mod}`; } export function Pf2eStatBlock({ creature }: Readonly) { const abilityEntries = [ { label: "Str", mod: creature.abilityMods.str }, { label: "Dex", mod: creature.abilityMods.dex }, { label: "Con", mod: creature.abilityMods.con }, { label: "Int", mod: creature.abilityMods.int }, { label: "Wis", mod: creature.abilityMods.wis }, { label: "Cha", mod: creature.abilityMods.cha }, ]; return (
{/* Header */}

{creature.name}

Level {creature.level}
{displayTraits(creature.traits).map((trait) => ( {trait} ))}

{creature.sourceDisplayName}

{/* Perception, Languages, Skills */}
Perception{" "} {formatInitiativeModifier(creature.perception)} {creature.senses ? `; ${creature.senses}` : ""}
{/* Ability Modifiers */}
{abilityEntries.map((a) => (
{a.label}
{formatMod(a.mod)}
))}
{/* Top abilities (before defenses) */} {/* Defenses */}
AC {creature.ac} {creature.acConditional ? ` (${creature.acConditional})` : ""};{" "} Fort{" "} {formatMod(creature.saveFort)},{" "} Ref{" "} {formatMod(creature.saveRef)},{" "} Will{" "} {formatMod(creature.saveWill)}
HP {creature.hp}
{/* Mid abilities (reactions, auras) */} {/* Speed */}
Speed {creature.speed}
{/* Attacks */} {/* Bottom abilities (active abilities) */}
); }