``` ~/go/bin/sourcemapper -output ddb -jsurl https://media.dndbeyond.com/character-app/static/js/main.90aa78c5.js ```
254 lines
7.6 KiB
TypeScript
254 lines
7.6 KiB
TypeScript
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<ConfirmModalProps, "onConfirm"> {}
|
|
|
|
export const HpManageModal: FC<HpManageModalProps> = ({
|
|
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 (
|
|
<ConfirmModal
|
|
onClose={handleClose}
|
|
heading="Manage Hit Points"
|
|
onConfirm={onConfirm}
|
|
confirmButtonText="Apply"
|
|
useMobileFullScreen
|
|
{...props}
|
|
>
|
|
{/* Total Max Hit Points */}
|
|
<div className={styles.hpManageModal}>
|
|
<p className={styles.total}>
|
|
<span className={styles.totalLabel}>Maximum Hit Points</span>
|
|
<span className={styles.totalValue}>{totalHp}</span>
|
|
</p>
|
|
|
|
{/* Hit Point Controls */}
|
|
<div className={styles.controls}>
|
|
{preferences.hitPointType ===
|
|
Constants.PreferenceHitPointTypeEnum.FIXED ? (
|
|
<p className={styles.baseHp}>
|
|
<span className={clsx([styles.baseHpLabel])}>Fixed HP</span>
|
|
<span className={styles.baseHpValue}>{baseHp}</span>
|
|
</p>
|
|
) : (
|
|
<FormInputField
|
|
label="Rolled HP"
|
|
initialValue={baseHp}
|
|
type="number"
|
|
onBlur={(value) => setBaseHp(value as number)}
|
|
transformValueOnBlur={transformBaseHp}
|
|
/>
|
|
)}
|
|
<FormInputField
|
|
label="HP Modifier"
|
|
initialValue={bonusHp}
|
|
type="number"
|
|
placeholder={"--"}
|
|
onBlur={(value) => setBonusHp(value as number)}
|
|
transformValueOnBlur={transformBonusHp}
|
|
/>
|
|
<FormInputField
|
|
inputAttributes={
|
|
{
|
|
min: RuleDataUtils.getMinimumHpTotal(ruleData),
|
|
max: HP_OVERRIDE_MAX_VALUE,
|
|
} as HTMLAttributes<HTMLInputElement>
|
|
}
|
|
label="Override HP"
|
|
initialValue={overrideHp}
|
|
type="number"
|
|
placeholder={"--"}
|
|
onBlur={handleOverrideHpUpdate}
|
|
transformValueOnBlur={transformOverrideHp}
|
|
/>
|
|
</div>
|
|
|
|
{/* Hit Point Bonus Sources */}
|
|
{hpInfo.hitPointSources.length > 0 && (
|
|
<div className={styles.hpSources}>
|
|
<h3>Hit Point Bonuses</h3>
|
|
{hpInfo.hitPointSources.map((hitPointSource, idx) => (
|
|
<p key={`${idx}:${hitPointSource.source}`}>
|
|
{FormatUtils.renderSignedNumber(hitPointSource.amount)} from{" "}
|
|
{hitPointSource.source}
|
|
</p>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* Hit Dice & Potential Values */}
|
|
<div className={styles.info}>
|
|
<div className={styles.infoGroup}>
|
|
<h3>Hit Dice</h3>
|
|
{hpInfo.classesHitDice.map((classHitDice) => (
|
|
<div key={ClassUtils.getId(classHitDice.charClass)}>
|
|
<span className={styles.infoLabel}>
|
|
{ClassUtils.getName(classHitDice.charClass)}:
|
|
</span>
|
|
<span>{DiceUtils.renderDie(classHitDice.dice)}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<div className={styles.infoGroup}>
|
|
<h3>Potential Values</h3>
|
|
<div>
|
|
<span className={styles.infoLabel}>Total Fixed Value HP:</span>
|
|
<span>{hpInfo.totalFixedValueHp}</span>
|
|
</div>
|
|
<div>
|
|
<span className={styles.infoLabel}>Total Average HP:</span>
|
|
<span>{hpInfo.totalAverageHp}</span>
|
|
</div>
|
|
<div>
|
|
<span className={styles.infoLabel}>Total Possible HP:</span>
|
|
<span>{hpInfo.possibleMaxHitPoints}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* HP Help */}
|
|
<div className={styles.help}>
|
|
<h3>Max Hit Points</h3>
|
|
<p>
|
|
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
|
|
</p>
|
|
|
|
<h3>Bonus Hit Points</h3>
|
|
<p>
|
|
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.
|
|
</p>
|
|
|
|
<h3>Override Hit Points</h3>
|
|
<p>
|
|
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.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</ConfirmModal>
|
|
);
|
|
};
|