import { type Creature, calculateInitiative, formatInitiativeModifier, } from "@initiative/domain"; interface StatBlockProps { creature: Creature; } function abilityMod(score: number): string { const mod = Math.floor((score - 10) / 2); return mod >= 0 ? `+${mod}` : `${mod}`; } function PropertyLine({ label, value, }: { label: string; value: string | undefined; }) { if (!value) return null; return (
{label} {value}
); } function SectionDivider() { return (
); } export function StatBlock({ creature }: StatBlockProps) { const abilities = [ { label: "STR", score: creature.abilities.str }, { label: "DEX", score: creature.abilities.dex }, { label: "CON", score: creature.abilities.con }, { label: "INT", score: creature.abilities.int }, { label: "WIS", score: creature.abilities.wis }, { label: "CHA", score: creature.abilities.cha }, ]; const initiative = calculateInitiative({ dexScore: creature.abilities.dex, cr: creature.cr, initiativeProficiency: creature.initiativeProficiency, }); return (
{/* Header */}

{creature.name}

{creature.size} {creature.type}, {creature.alignment}

{creature.sourceDisplayName}

{/* Stats bar */}
Armor Class {creature.ac} {creature.acSource && ( {" "} ({creature.acSource}) )} Initiative{" "} {formatInitiativeModifier(initiative.modifier)} ( {initiative.passive})
Hit Points{" "} {creature.hp.average}{" "} ({creature.hp.formula})
Speed {creature.speed}
{/* Ability scores */}
{abilities.map(({ label, score }) => (
{label}
{score}{" "} ({abilityMod(score)})
))}
{/* Properties */}
Challenge {creature.cr}{" "} (Proficiency Bonus +{creature.proficiencyBonus})
{/* Traits */} {creature.traits && creature.traits.length > 0 && ( <>
{creature.traits.map((t) => (
{t.name}. {t.text}
))}
)} {/* Spellcasting */} {creature.spellcasting && creature.spellcasting.length > 0 && ( <> {creature.spellcasting.map((sc) => (
{sc.name}.{" "} {sc.headerText}
{sc.atWill && sc.atWill.length > 0 && (
At Will:{" "} {sc.atWill.join(", ")}
)} {sc.daily?.map((d) => (
{d.uses}/day {d.each ? " each" : ""}: {" "} {d.spells.join(", ")}
))} {sc.restLong?.map((d) => (
{d.uses}/long rest {d.each ? " each" : ""}: {" "} {d.spells.join(", ")}
))}
))} )} {/* Actions */} {creature.actions && creature.actions.length > 0 && ( <>

Actions

{creature.actions.map((a) => (
{a.name}. {a.text}
))}
)} {/* Bonus Actions */} {creature.bonusActions && creature.bonusActions.length > 0 && ( <>

Bonus Actions

{creature.bonusActions.map((a) => (
{a.name}. {a.text}
))}
)} {/* Reactions */} {creature.reactions && creature.reactions.length > 0 && ( <>

Reactions

{creature.reactions.map((a) => (
{a.name}. {a.text}
))}
)} {/* Legendary Actions */} {creature.legendaryActions && ( <>

Legendary Actions

{creature.legendaryActions.preamble}

{creature.legendaryActions.entries.map((a) => (
{a.name}. {a.text}
))}
)}
); }