import clsx from "clsx"; import { FC, HTMLAttributes, useMemo, useState } from "react"; import { useDispatch } from "react-redux"; import { characterActions, ClassUtils, Constants, DiceUtils, FormatUtils, HelperUtils, RuleDataUtils, } from "@dndbeyond/character-rules-engine"; import { ConfirmModal, ConfirmModalProps } from "~/components/ConfirmModal"; import { useCharacterEngine } from "~/hooks/useCharacterEngine"; import { HP_BASE_MAX_VALUE, HP_BONUS_VALUE, HP_OVERRIDE_MAX_VALUE, } from "~/subApps/sheet/constants"; import { FormInputField } from "~/tools/js/Shared/components/common/FormInputField"; import styles from "./styles.module.css"; export interface HpManageModalProps extends Omit {} export const HpManageModal: FC = ({ onClose, ...props }) => { const dispatch = useDispatch(); const { hpInfo, preferences, ruleData } = useCharacterEngine(); const [baseHp, setBaseHp] = useState(hpInfo.baseHp); const [bonusHp, setBonusHp] = useState(hpInfo.bonusHp); const [overrideHp, setOverrideHp] = useState(hpInfo.overrideHp); const reset = () => { setBaseHp(hpInfo.baseHp); setBonusHp(hpInfo.bonusHp); setOverrideHp(hpInfo.overrideHp); }; const handleClose = () => { onClose(); reset(); }; const onConfirm = () => { if (baseHp !== hpInfo.baseHp) { dispatch(characterActions.baseHitPointsSet(baseHp)); } if (bonusHp !== hpInfo.bonusHp) { dispatch(characterActions.bonusHitPointsSet(bonusHp)); } if (overrideHp !== hpInfo.overrideHp) { dispatch(characterActions.overrideHitPointsSet(overrideHp)); } onClose(); reset(); }; const transformBaseHp = (value: string): number => { let parsedNumber = HelperUtils.parseInputInt( value, RuleDataUtils.getMinimumHpTotal(ruleData) ); return HelperUtils.clampInt( parsedNumber, RuleDataUtils.getMinimumHpTotal(ruleData), HP_BASE_MAX_VALUE ); }; const transformBonusHp = (value: string): number | null => { let parsedNumber = HelperUtils.parseInputInt(value); if (parsedNumber === null) { return parsedNumber; } return HelperUtils.clampInt( parsedNumber, HP_BONUS_VALUE.MIN, HP_BONUS_VALUE.MAX ); }; const transformOverrideHp = (value: string): number | null => { let parsedNumber = HelperUtils.parseInputInt(value, null); if (parsedNumber === null) { return parsedNumber; } return HelperUtils.clampInt( parsedNumber, RuleDataUtils.getMinimumHpTotal(ruleData) ); }; const handleOverrideHpUpdate = (value: number | null): void => { const newOverride = value === null ? null : HelperUtils.clampInt( value, RuleDataUtils.getMinimumHpTotal(ruleData), HP_OVERRIDE_MAX_VALUE ); setOverrideHp(newOverride); }; const totalHp = useMemo(() => { let totalHp: number = baseHp + hpInfo.totalHitPointSources + (bonusHp === null ? 0 : bonusHp); if (overrideHp !== null) { totalHp = overrideHp; } return Math.max(RuleDataUtils.getMinimumHpTotal(ruleData), totalHp); }, [baseHp, bonusHp, overrideHp, hpInfo, ruleData]); return ( {/* Total Max Hit Points */}

Maximum Hit Points {totalHp}

{/* Hit Point Controls */}
{preferences.hitPointType === Constants.PreferenceHitPointTypeEnum.FIXED ? (

Fixed HP {baseHp}

) : ( setBaseHp(value as number)} transformValueOnBlur={transformBaseHp} /> )} setBonusHp(value as number)} transformValueOnBlur={transformBonusHp} /> } label="Override HP" initialValue={overrideHp} type="number" placeholder={"--"} onBlur={handleOverrideHpUpdate} transformValueOnBlur={transformOverrideHp} />
{/* Hit Point Bonus Sources */} {hpInfo.hitPointSources.length > 0 && (

Hit Point Bonuses

{hpInfo.hitPointSources.map((hitPointSource, idx) => (

{FormatUtils.renderSignedNumber(hitPointSource.amount)} from{" "} {hitPointSource.source}

))}
)} {/* Hit Dice & Potential Values */}

Hit Dice

{hpInfo.classesHitDice.map((classHitDice) => (
{ClassUtils.getName(classHitDice.charClass)}: {DiceUtils.renderDie(classHitDice.dice)}
))}

Potential Values

Total Fixed Value HP: {hpInfo.totalFixedValueHp}
Total Average HP: {hpInfo.totalAverageHp}
Total Possible HP: {hpInfo.possibleMaxHitPoints}
{/* HP Help */}

Max Hit Points

Your hit point maximum is determined by the number you roll for your hit dice each level or a fixed value determined by your hit dice and your Constitution modifier

Bonus Hit Points

Use this field to record any miscellaneous bonus hit points you want to add to your normal hit point maximum. These hit points are different from temporary hit points, which you can add on your character sheet during play.

Override Hit Points

Use this field to override your typical hit point maximum. The number you enter here will display as your hit point maximum on your character sheet.

); };