294 lines
8.6 KiB
TypeScript
294 lines
8.6 KiB
TypeScript
import * as React from "react";
|
|
import { Dispatch, SetStateAction } from "react";
|
|
|
|
import { Tooltip } from "@dndbeyond/character-common-components/es";
|
|
import {
|
|
ConditionUtils,
|
|
DiceUtils,
|
|
HigherLevelContract,
|
|
HigherLevelEntryContract,
|
|
ModifierUtils,
|
|
RuleData,
|
|
RuleDataUtils,
|
|
Spell,
|
|
SpellConditionContract,
|
|
SpellModifierContract,
|
|
SpellUtils,
|
|
CharacterTheme,
|
|
} from "@dndbeyond/character-rules-engine/es";
|
|
import { GameLogContext } from "@dndbeyond/game-log-components";
|
|
import {
|
|
RollKinds,
|
|
RollTypes,
|
|
} from "@dndbeyond/pocket-dimension-dice/constants";
|
|
import { RollContext } from "@dndbeyond/pocket-dimension-dice/types";
|
|
|
|
import Damage from "../Damage";
|
|
import { DigitalDiceWrapper } from "../Dice";
|
|
import { HealingIcon } from "../Icons";
|
|
import { DiceComponentUtils } from "../utils";
|
|
|
|
interface Props {
|
|
spell: Spell;
|
|
castLevel: number;
|
|
characterLevel: number;
|
|
ruleData: RuleData;
|
|
diceEnabled: boolean;
|
|
theme: CharacterTheme;
|
|
isCriticalHit?: boolean;
|
|
rollContext: RollContext;
|
|
rollCallback: () => void;
|
|
}
|
|
class SpellDamageEffect extends React.PureComponent<Props> {
|
|
static defaultProps = {
|
|
diceEnabled: false,
|
|
};
|
|
|
|
getPoints = (
|
|
atHigherLevels: HigherLevelContract | null
|
|
): Array<HigherLevelEntryContract> => {
|
|
let points: Array<HigherLevelEntryContract> = [];
|
|
if (atHigherLevels && atHigherLevels.points !== null) {
|
|
points = atHigherLevels.points;
|
|
}
|
|
return points;
|
|
};
|
|
|
|
render() {
|
|
const {
|
|
spell,
|
|
castLevel,
|
|
characterLevel,
|
|
ruleData,
|
|
diceEnabled,
|
|
theme,
|
|
isCriticalHit,
|
|
rollContext,
|
|
rollCallback,
|
|
} = this.props;
|
|
|
|
let isScaled: boolean = false;
|
|
const modifiers = SpellUtils.getModifiers(spell);
|
|
const tags = SpellUtils.getTags(spell);
|
|
const conditions = SpellUtils.getConditions(spell);
|
|
|
|
let displayNode: React.ReactNode;
|
|
let damageModifiers = modifiers.filter((modifier) =>
|
|
ModifierUtils.isSpellDamageModifier(modifier)
|
|
);
|
|
if (damageModifiers.length) {
|
|
let modifier: SpellModifierContract = damageModifiers[0];
|
|
const friendlySubtypeName =
|
|
ModifierUtils.getFriendlySubtypeName(modifier);
|
|
const atHigherLevels = ModifierUtils.getAtHigherLevels(modifier);
|
|
let scaleType = SpellUtils.getScaleType(spell);
|
|
let points = this.getPoints(atHigherLevels);
|
|
let atHigherDamage = SpellUtils.getSpellScaledAtHigher(
|
|
spell,
|
|
scaleType,
|
|
points,
|
|
characterLevel,
|
|
castLevel
|
|
);
|
|
let scaledDamageDie = SpellUtils.getSpellFinalScaledDie(
|
|
spell,
|
|
modifier,
|
|
atHigherDamage
|
|
);
|
|
isScaled =
|
|
isScaled ||
|
|
SpellUtils.isSpellScaledByCastLevel(
|
|
spell,
|
|
scaleType,
|
|
points,
|
|
castLevel
|
|
);
|
|
|
|
displayNode = (
|
|
<span className="ddbc-spell-damage-effect__damages">
|
|
<DigitalDiceWrapper
|
|
diceNotation={DiceUtils.renderDice(scaledDamageDie)}
|
|
rollType={RollTypes.Damage}
|
|
action={SpellUtils.getName(spell)}
|
|
rollKind={isCriticalHit ? RollKinds.CriticalHit : undefined}
|
|
isDiceEnabled={diceEnabled}
|
|
entityId={String(rollContext.entityId)}
|
|
entityType={String(rollContext.entityType)}
|
|
name={String(rollContext.name)}
|
|
rollCallback={rollCallback}
|
|
>
|
|
<Damage
|
|
damage={DiceComponentUtils.getDamageDiceNotation(
|
|
scaledDamageDie,
|
|
isCriticalHit
|
|
)}
|
|
type={friendlySubtypeName || ""}
|
|
showInfoTooltip={false}
|
|
theme={theme}
|
|
/>
|
|
</DigitalDiceWrapper>
|
|
</span>
|
|
);
|
|
}
|
|
|
|
let hitPointHealingModifiers = modifiers.filter((modifier) =>
|
|
ModifierUtils.isSpellHealingHitPointsModifier(modifier)
|
|
);
|
|
if (!displayNode && hitPointHealingModifiers.length) {
|
|
let modifier: SpellModifierContract = hitPointHealingModifiers[0];
|
|
const atHigherLevels = ModifierUtils.getAtHigherLevels(modifier);
|
|
let scaleType = SpellUtils.getScaleType(spell);
|
|
let points = this.getPoints(atHigherLevels);
|
|
let atHigherHealing = SpellUtils.getSpellScaledAtHigher(
|
|
spell,
|
|
scaleType,
|
|
points,
|
|
characterLevel,
|
|
castLevel
|
|
);
|
|
let scaledHealingDie = SpellUtils.getSpellFinalScaledDie(
|
|
spell,
|
|
modifier,
|
|
atHigherHealing,
|
|
castLevel
|
|
);
|
|
isScaled =
|
|
isScaled ||
|
|
SpellUtils.isSpellScaledByCastLevel(
|
|
spell,
|
|
scaleType,
|
|
points,
|
|
castLevel
|
|
);
|
|
|
|
displayNode = (
|
|
<span className="ddbc-spell-damage-effect__healing ddbc-spell-damage-effect__healing--hp">
|
|
<DigitalDiceWrapper
|
|
isContextMenuEnabled={false}
|
|
diceNotation={DiceUtils.renderDice(scaledHealingDie)}
|
|
rollType={RollTypes.Heal}
|
|
action={SpellUtils.getName(spell)}
|
|
isDiceEnabled={diceEnabled}
|
|
entityId={String(rollContext.entityId)}
|
|
entityType={String(rollContext.entityType)}
|
|
name={String(rollContext.name)}
|
|
>
|
|
{DiceUtils.renderDice(scaledHealingDie)}{" "}
|
|
<HealingIcon theme={theme} isHp={true} />
|
|
</DigitalDiceWrapper>
|
|
</span>
|
|
);
|
|
}
|
|
|
|
const tempHitPointHealingModifiers = modifiers.filter((modifier) =>
|
|
ModifierUtils.isSpellHealingTempHitPointsModifier(modifier)
|
|
);
|
|
if (!displayNode && tempHitPointHealingModifiers.length) {
|
|
let modifier = tempHitPointHealingModifiers[0];
|
|
const atHigherLevels = ModifierUtils.getAtHigherLevels(modifier);
|
|
let scaleType = SpellUtils.getScaleType(spell);
|
|
let points = this.getPoints(atHigherLevels);
|
|
let atHigherHealing = SpellUtils.getSpellScaledAtHigher(
|
|
spell,
|
|
scaleType,
|
|
points,
|
|
characterLevel,
|
|
castLevel
|
|
);
|
|
let scaledHealingDie = SpellUtils.getSpellFinalScaledDie(
|
|
spell,
|
|
modifier,
|
|
atHigherHealing,
|
|
castLevel
|
|
);
|
|
isScaled =
|
|
isScaled ||
|
|
SpellUtils.isSpellScaledByCastLevel(
|
|
spell,
|
|
scaleType,
|
|
points,
|
|
castLevel
|
|
);
|
|
|
|
displayNode = (
|
|
<span className="ddbc-spell-damage-effect__healing ddbc-spell-damage-effect__healing--temp">
|
|
<DigitalDiceWrapper
|
|
isContextMenuEnabled={false}
|
|
diceNotation={DiceUtils.renderDice(scaledHealingDie)}
|
|
rollType={RollTypes.Heal}
|
|
action={SpellUtils.getName(spell)}
|
|
isDiceEnabled={diceEnabled}
|
|
entityId={String(rollContext.entityId)}
|
|
entityType={String(rollContext.entityType)}
|
|
name={String(rollContext.name)}
|
|
>
|
|
{DiceUtils.renderDice(scaledHealingDie)}{" "}
|
|
<HealingIcon theme={theme} isTemp={true} />
|
|
</DigitalDiceWrapper>
|
|
</span>
|
|
);
|
|
}
|
|
|
|
if (!displayNode && conditions.length) {
|
|
let spellCondition: SpellConditionContract = conditions[0];
|
|
let condition = RuleDataUtils.getCondition(
|
|
spellCondition.conditionId,
|
|
ruleData
|
|
);
|
|
let name: string =
|
|
condition === null ? "" : ConditionUtils.getName(condition);
|
|
displayNode = (
|
|
<span
|
|
className={`ddbc-spell-damage-effect__conditions ${
|
|
theme.isDarkMode
|
|
? "ddbc-spell-damage-effect__conditions--dark-mode"
|
|
: ""
|
|
}`}
|
|
>
|
|
<Tooltip
|
|
isDarkMode={theme.isDarkMode}
|
|
title={`${name}${
|
|
spellCondition.exception ? `, ${spellCondition.exception}` : ""
|
|
}`}
|
|
>
|
|
{name}
|
|
</Tooltip>
|
|
</span>
|
|
);
|
|
}
|
|
|
|
if (!displayNode && tags.length) {
|
|
displayNode = (
|
|
<span
|
|
className={`ddbc-spell-damage-effect__tags ${
|
|
theme.isDarkMode ? "ddbc-spell-damage-effect__tags--dark-mode" : ""
|
|
}`}
|
|
>
|
|
{tags[0]}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
let hasMore: boolean = damageModifiers.length > 1;
|
|
hasMore = hasMore || hitPointHealingModifiers.length > 1;
|
|
hasMore = hasMore || tempHitPointHealingModifiers.length > 1;
|
|
hasMore = hasMore || conditions.length > 1;
|
|
hasMore = hasMore || tags.length > 1;
|
|
|
|
return (
|
|
<div
|
|
className={`ddbc-spell-damage-effect ${
|
|
isScaled ? "ddbc-spell-damage-effect--scaled" : ""
|
|
} ${theme.isDarkMode ? "ddbc-spell-damage-effect--dark-mode" : ""}`}
|
|
>
|
|
{displayNode}
|
|
{hasMore ? "*" : ""}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
SpellDamageEffect.contextType = GameLogContext;
|
|
|
|
export default SpellDamageEffect;
|