2025-05-28 15:36:51 -07:00

966 lines
29 KiB
TypeScript

import React, { useContext } from "react";
import { DispatchProp } from "react-redux";
import {
ComponentConstants,
DamageTypeIcon,
} from "@dndbeyond/character-components/es";
import {
AbilityLookup,
BaseInventoryContract,
Constants,
DiceUtils,
EntityUtils,
FormatUtils,
ItemUtils,
LimitedUseUtils,
ModifierUtils,
Spell,
SpellCasterInfo,
SpellSlotContract,
SpellUtils,
characterActions,
ApiRequestHelpers,
RuleData,
RuleDataUtils,
CharacterTheme,
InventoryManager,
ItemManager,
} from "@dndbeyond/character-rules-engine/es";
import { toastMessageActions } from "../../actions/toastMessage";
import { InventoryManagerContext } from "../../managers/InventoryManagerContext";
import { ThemeButton } from "../common/Button";
function canCastAtLevel(level, available) {
return available.indexOf(level) > -1;
}
function isCastAvailableAtLevel(level, available) {
return available.indexOf(level) > -1;
}
// TODO dispatch needs to be factored out
interface Props extends DispatchProp {
characterLevel: number;
spell: Spell;
spellSlots: Array<SpellSlotContract>;
pactMagicSlots: Array<SpellSlotContract>;
castableSpellLevels: Array<number>;
castablePactMagicLevels: Array<number>;
availableSpellLevels: Array<number>;
availablePactMagicLevels: Array<number>;
initialCastLevel: number | null;
spellCasterInfo: SpellCasterInfo;
ruleData: RuleData;
abilityLookup: AbilityLookup;
isReadonly: boolean;
proficiencyBonus: number;
theme?: CharacterTheme;
inventoryManager: InventoryManager;
}
interface State {
castLevel: number;
castableLevelsIdx: number;
castableLevels: Array<number>;
minLevel: number;
maxLevel: number;
}
export class SpellCaster extends React.PureComponent<Props, State> {
static defaultProps = {
initialCastLevel: null,
};
constructor(props: Props) {
super(props);
this.state = this.getStartingCastState(props);
}
componentDidUpdate(
prevProps: Readonly<Props>,
prevState: Readonly<State>,
snapshot?: any
): void {
const { spell } = this.props;
if (
SpellUtils.getUniqueKey(spell) !==
SpellUtils.getUniqueKey(prevProps.spell)
) {
this.setState({
...this.getStartingCastState(this.props),
});
}
}
getStartingCastState = (props: Props): State => {
const { spell, initialCastLevel, spellCasterInfo, ruleData } = props;
let { startLevel, endLevel } = SpellUtils.getCastLevelRange(
spell,
spellCasterInfo,
ruleData
);
const castableLevels = SpellUtils.getCastableLevels(
spell,
spellCasterInfo,
ruleData,
initialCastLevel
);
const castLevel = SpellUtils.getMinCastLevel(
spell,
spellCasterInfo,
ruleData,
initialCastLevel
);
const castableLevelsIdx = castableLevels.findIndex(
(level) => level === castLevel
);
return {
castLevel,
castableLevelsIdx,
castableLevels,
minLevel: startLevel,
maxLevel: endLevel,
};
};
getSpellLevelUses = (
spellSlots: Array<SpellSlotContract>,
level: number
): number => {
const spellSlot = spellSlots.find((slot) => slot.level === level);
if (spellSlot) {
return spellSlot.used;
}
return 0;
};
handleIncreaseCastLevel = (): void => {
this.setState((prevState, props) => ({
castableLevelsIdx: prevState.castableLevelsIdx + 1,
castLevel: prevState.castableLevels[prevState.castableLevelsIdx + 1],
}));
};
handleDecreaseCastLevel = (): void => {
this.setState((prevState, props) => ({
castableLevelsIdx: prevState.castableLevelsIdx - 1,
castLevel: prevState.castableLevels[prevState.castableLevelsIdx - 1],
}));
};
handleCastSpellSlot = (): void => {
this.handleSpellUse(true, false);
};
handleCastPactMagicSlot = (): void => {
this.handleSpellUse(false, true);
};
// TODO dispatch needs to be factored out
handleSpellUse = (useSpellSlot: boolean, usePactMagicSlot: boolean): void => {
const { castLevel, minLevel } = this.state;
const {
dispatch,
spell,
spellSlots,
pactMagicSlots,
isReadonly,
inventoryManager,
} = this.props;
if (isReadonly) {
return;
}
const dataOrigin = SpellUtils.getDataOrigin(spell);
const dataOriginType = SpellUtils.getDataOriginType(spell);
let limitedUse = SpellUtils.getLimitedUse(spell);
let mappingId: number | null = null;
let mappingTypeId: number | null = null;
if (limitedUse) {
let numberUsed = LimitedUseUtils.getNumberUsed(limitedUse);
let uses = SpellUtils.getConsumedLimitedUse(spell, castLevel - minLevel);
switch (dataOriginType) {
case Constants.DataOriginTypeEnum.ITEM:
//TODO itemManager: make a way to get the item with just he mapping id and lookups?
mappingId = ItemUtils.getMappingId(
dataOrigin.primary as BaseInventoryContract
);
mappingTypeId = ItemUtils.getMappingEntityTypeId(
dataOrigin.primary as BaseInventoryContract
);
const itemData = dataOrigin.primary as BaseInventoryContract;
const item = ItemManager.getItem(ItemUtils.getMappingId(itemData));
item.handleItemLimitedUseSet(numberUsed + uses);
dispatch(
toastMessageActions.toastSuccess(
"Item Spell Cast",
`Cast ${SpellUtils.getName(
spell
)} from ${EntityUtils.getDataOriginName(dataOrigin)}`
)
);
break;
default:
mappingId = SpellUtils.getMappingId(spell);
mappingTypeId = SpellUtils.getMappingEntityTypeId(spell);
if (mappingId !== null && mappingTypeId !== null) {
dispatch(
characterActions.spellUseSet(
mappingId,
mappingTypeId,
numberUsed + uses,
dataOriginType
)
);
dispatch(
toastMessageActions.toastSuccess(
"Limited Use Spell Cast",
`Cast ${SpellUtils.getName(spell)} with limited use`
)
);
}
}
}
if (useSpellSlot) {
let castLevelKey =
ApiRequestHelpers.getSpellLevelSpellSlotRequestsDataKey(castLevel);
if (castLevelKey !== null) {
dispatch(
characterActions.spellLevelSpellSlotsSet({
[castLevelKey]: this.getSpellLevelUses(spellSlots, castLevel) + 1,
})
);
dispatch(
toastMessageActions.toastSuccess(
"Spell Cast",
`Cast ${SpellUtils.getName(spell)} in spell slot level ${castLevel}`
)
);
}
}
if (usePactMagicSlot) {
let castLevelKey =
ApiRequestHelpers.getSpellLevelPactMagicRequestsDataKey(castLevel);
if (castLevelKey !== null) {
dispatch(
characterActions.spellLevelPactMagicSlotsSet({
[castLevelKey]:
this.getSpellLevelUses(pactMagicSlots, castLevel) + 1,
})
);
dispatch(
toastMessageActions.toastSuccess(
"Spell Cast",
`Cast ${SpellUtils.getName(spell)} in a pact magic slot`
)
);
}
}
};
handleCast = (): void => {
const { castLevel } = this.state;
const { spell, spellCasterInfo } = this.props;
let usesSpellSlot = SpellUtils.getUsesSpellSlot(spell);
let limitedUse = SpellUtils.getLimitedUse(spell);
const isSpellSlotAvailable = canCastAtLevel(
castLevel,
spellCasterInfo.castableSpellLevels
);
const isPactSlotAvailable = canCastAtLevel(
castLevel,
spellCasterInfo.castablePactMagicLevels
);
if (usesSpellSlot) {
if (limitedUse) {
if (SpellUtils.isValidToUsePactSlots(spell)) {
if (isPactSlotAvailable) {
this.handleSpellUse(false, true);
}
} else {
if (isSpellSlotAvailable) {
this.handleSpellUse(true, false);
}
}
} else {
if (isSpellSlotAvailable) {
this.handleSpellUse(true, false);
} else if (isPactSlotAvailable) {
this.handleSpellUse(false, true);
}
}
} else {
this.handleSpellUse(false, false);
}
};
renderLimitedUseDetails = (): React.ReactNode => {
const { spell, ruleData, abilityLookup, proficiencyBonus } = this.props;
const limitedUse = SpellUtils.getLimitedUse(spell);
if (!limitedUse) {
return null;
}
const maxUses = LimitedUseUtils.deriveMaxUses(
limitedUse,
abilityLookup,
ruleData,
proficiencyBonus
);
const resetType = LimitedUseUtils.getResetType(limitedUse);
if (resetType === null) {
return null;
}
let limitedUseType: string = "Use";
if (
SpellUtils.getDataOriginType(spell) === Constants.DataOriginTypeEnum.ITEM
) {
limitedUseType = "Charge";
}
let resetDisplay: string = "";
switch (resetType) {
case Constants.LimitedUseResetTypeEnum.SHORT_REST:
case Constants.LimitedUseResetTypeEnum.LONG_REST:
resetDisplay = `${
maxUses === 1 ? "Once" : maxUses
} per ${RuleDataUtils.getLimitedUseResetTypeName(resetType, ruleData)}`;
break;
default:
resetDisplay = `${maxUses} ${limitedUseType}${
maxUses !== 1 ? "s" : ""
}, Reset: ${RuleDataUtils.getLimitedUseResetTypeName(
resetType,
ruleData
)}`;
break;
}
return <div className="ct-spell-caster__limited">{resetDisplay}</div>;
};
renderCasting = (): React.ReactNode => {
const { castLevel, minLevel, maxLevel, castableLevelsIdx, castableLevels } =
this.state;
const {
spell,
castableSpellLevels,
castablePactMagicLevels,
spellSlots,
pactMagicSlots,
availableSpellLevels,
availablePactMagicLevels,
ruleData,
abilityLookup,
spellCasterInfo,
isReadonly,
proficiencyBonus,
} = this.props;
const limitedUse = SpellUtils.getLimitedUse(spell);
const isAtWill = SpellUtils.validateIsAtWill(spell, castLevel);
const castAsRitual = SpellUtils.isCastAsRitual(spell);
let castLevelNode: React.ReactNode;
let castActionsNode: React.ReactNode;
if (castAsRitual) {
castActionsNode = "As Ritual";
} else if (isAtWill) {
castActionsNode = "At Will";
} else {
if (limitedUse) {
const maxUses = LimitedUseUtils.deriveMaxUses(
limitedUse,
abilityLookup,
ruleData,
proficiencyBonus
);
const numberUsed = LimitedUseUtils.getNumberUsed(limitedUse);
const consumedAmount = SpellUtils.getConsumedLimitedUse(
spell,
castLevel - minLevel
);
const numberRemaining: number = maxUses - numberUsed;
let limitedUseButtonText: React.ReactNode;
let showRemainingCount: boolean = true;
const dataOriginType = SpellUtils.getDataOriginType(spell);
if (maxUses === 1) {
if (dataOriginType === Constants.DataOriginTypeEnum.ITEM) {
limitedUseButtonText = "1 Charge";
} else {
limitedUseButtonText = "Use";
showRemainingCount = false;
}
} else {
limitedUseButtonText = `${consumedAmount} ${
dataOriginType === Constants.DataOriginTypeEnum.ITEM
? "Charge"
: "Use"
}${consumedAmount !== 1 ? "s" : ""}`;
}
const isLimitedUseAvailable =
SpellUtils.validateIsLimitedUseAvailableAtScaledAmount(
spell,
castLevel,
castLevel - minLevel,
abilityLookup,
ruleData,
spellCasterInfo,
proficiencyBonus
);
const usesSpellSlots = SpellUtils.getUsesSpellSlot(spell);
let spellSlotsAvailable: number | null = null;
let spellSlotName: string = "";
if (usesSpellSlots) {
if (SpellUtils.isValidToUsePactSlots(spell)) {
spellSlotsAvailable = SpellUtils.getCastLevelAvailableCount(
castLevel,
pactMagicSlots
);
spellSlotName = "Pact Slot";
} else {
spellSlotsAvailable = SpellUtils.getCastLevelAvailableCount(
castLevel,
spellSlots
);
spellSlotName = "Spell Slot";
}
}
castActionsNode = (
<React.Fragment>
<div className="ct-spell-caster__casting-action">
<ThemeButton
disabled={!isLimitedUseAvailable}
isInteractive={!isReadonly}
onClick={this.handleCast}
className="ct-spell-caster__casting-action-button--limited"
>
{usesSpellSlots ? `${spellSlotName}, ` : ""}
{limitedUseButtonText}
{usesSpellSlots && (
<span className="ct-spell-caster__casting-action-count ct-spell-caster__casting-action-count--spellcasting">
{spellSlotsAvailable}
</span>
)}
{showRemainingCount && (
<span className="ct-spell-caster__casting-action-count ct-spell-caster__casting-action-count--limited-use">
{numberRemaining}
</span>
)}
</ThemeButton>
</div>
</React.Fragment>
);
} else {
const canSpellCastAtLevel = canCastAtLevel(
castLevel,
castableSpellLevels
);
const canPactMagicCastAtLevel = canCastAtLevel(
castLevel,
castablePactMagicLevels
);
const isSpellCastAvailableAtLevel = isCastAvailableAtLevel(
castLevel,
availableSpellLevels
);
const isPactMagicCastAvailableAtLevel = isCastAvailableAtLevel(
castLevel,
availablePactMagicLevels
);
const spellCastLevelAvailableCount =
SpellUtils.getCastLevelAvailableCount(castLevel, spellSlots);
const pactMagicCastLevelAvailableCount =
SpellUtils.getCastLevelAvailableCount(castLevel, pactMagicSlots);
castActionsNode = (
<React.Fragment>
{isSpellCastAvailableAtLevel && (
<div className="ct-spell-caster__casting-action">
<ThemeButton
disabled={!canSpellCastAtLevel}
isInteractive={!isReadonly}
onClick={this.handleCastSpellSlot}
>
Spell Slot
<span className="ct-spell-caster__casting-action-count ct-spell-caster__casting-action-count--spellcasting">
{spellCastLevelAvailableCount}
</span>
</ThemeButton>
</div>
)}
{isPactMagicCastAvailableAtLevel && (
<div className="ct-spell-caster__casting-action">
<ThemeButton
disabled={!canPactMagicCastAtLevel}
onClick={this.handleCastPactMagicSlot}
>
Pact Slot
<span className="ct-spell-caster__casting-action-count ct-spell-caster__casting-action-count--spellcasting">
{pactMagicCastLevelAvailableCount}
</span>
</ThemeButton>
</div>
)}
</React.Fragment>
);
}
}
if (!SpellUtils.isCantrip(spell)) {
castLevelNode = (
<div className="ct-spell-caster__casting-level">
<span className="ct-spell-caster__casting-level-label">Level</span>
{minLevel !== maxLevel && (
<span className="ct-spell-caster__casting-level-action">
<ThemeButton
disabled={castableLevelsIdx === 0}
onClick={this.handleDecreaseCastLevel}
className="ct-button--decrease"
/>
</span>
)}
<span
className={`ct-spell-caster__casting-level-current ct-spell-caster__casting-level-current--${
minLevel === maxLevel ? "nocontrols" : "controls"
}`}
>
{FormatUtils.renderSpellLevelAbbreviation(castLevel)}
</span>
{minLevel !== maxLevel && (
<span className="ct-spell-caster__casting-level-action">
<ThemeButton
disabled={castableLevelsIdx + 1 === castableLevels.length}
onClick={this.handleIncreaseCastLevel}
className="ct-button--increase"
/>
</span>
)}
</div>
);
}
//`
return (
<div className="ct-spell-caster__casting">
<div className="ct-spell-caster__casting-label">Cast</div>
<div className="ct-spell-caster__casting-actions">
{castActionsNode}
</div>
{castLevelNode}
</div>
);
};
renderAtHigherLevels = (): React.ReactNode => {
const { castLevel } = this.state;
const { spell, characterLevel } = this.props;
const atHigherLevels = SpellUtils.getAtHigherLevels(spell);
if (!atHigherLevels) {
return null;
}
const scaleType = SpellUtils.getScaleType(spell);
const {
additionalAttacks,
additionalTargets,
areaOfEffect,
duration,
creatures,
special,
range,
} = atHigherLevels;
const level = SpellUtils.getLevel(spell);
let additionalAttacksDisplay: React.ReactNode;
if (additionalAttacks !== null) {
const additionalAttackInfo = SpellUtils.getSpellScaledAtHigher(
spell,
scaleType,
additionalAttacks,
characterLevel,
castLevel
);
if (additionalAttackInfo) {
const additionalAttackCount =
level === 0
? additionalAttackInfo.totalCount + 1
: additionalAttackInfo.totalCount;
const additionalAttackLabel = level === 0 ? "Total" : "Additional";
additionalAttacksDisplay = (
<div className="ct-spell-caster__higher ct-spell-caster__higher--attacks">
{additionalAttackLabel} {additionalAttackInfo.description}:{" "}
{additionalAttackCount}
</div>
);
}
}
let additionalTargetsDisplay: React.ReactNode;
if (additionalTargets !== null) {
const additionalTargetInfo = SpellUtils.getSpellScaledAtHigher(
spell,
scaleType,
additionalTargets,
characterLevel,
castLevel
);
if (additionalTargetInfo) {
additionalTargetsDisplay = (
<div className="ct-spell-caster__higher ct-spell-caster__higher--targets">
<div className="ct-spell-caster__higher-targets-info">
{additionalTargetInfo.targets} Additional Target
{additionalTargetInfo.targets === 1 ? "" : "s"}
</div>
<div className="ct-spell-caster__higher-targets-description">
{additionalTargetInfo.description}
</div>
</div>
);
}
}
let aoeDisplay: React.ReactNode;
if (areaOfEffect !== null) {
const aoeInfo = SpellUtils.getSpellScaledAtHigher(
spell,
scaleType,
areaOfEffect,
characterLevel,
castLevel
);
if (aoeInfo && aoeInfo.extendedAoe !== null) {
aoeDisplay = (
<div className="ct-spell-caster__higher ct-spell-caster__higher--aoe">
Extended Area: {FormatUtils.renderDistance(aoeInfo.extendedAoe)}{" "}
{aoeInfo.description}
</div>
);
}
}
let rangeDisplay: React.ReactNode;
if (range !== null) {
const rangeInfo = SpellUtils.getSpellScaledAtHigher(
spell,
scaleType,
range,
characterLevel,
castLevel
);
if (rangeInfo && rangeInfo.range) {
rangeDisplay = (
<div className="ct-spell-caster__higher ct-spell-caster__higher--range">
Extended Range: {FormatUtils.renderDistance(rangeInfo.range)}{" "}
{rangeInfo.description}
</div>
);
}
}
let durationDisplay: React.ReactNode;
if (duration !== null) {
const durationInfo = SpellUtils.getSpellScaledAtHigher(
spell,
scaleType,
duration,
characterLevel,
castLevel
);
if (durationInfo) {
durationDisplay = (
<div className="ct-spell-caster__higher ct-spell-caster__higher--duration">
Extended Duration: {durationInfo.description}
</div>
);
}
}
let creaturesDisplay: React.ReactNode;
if (creatures !== null) {
const creatureInfo = SpellUtils.getSpellScaledAtHigher(
spell,
scaleType,
creatures,
characterLevel,
castLevel
);
if (creatureInfo) {
creaturesDisplay = (
<div className="ct-spell-caster__higher ct-spell-caster__higher--creatures">
Creatures: {creatureInfo.description}
</div>
);
}
}
let specialDisplay: React.ReactNode;
if (special !== null) {
const specialInfo = SpellUtils.getSpellScaledAtHigher(
spell,
scaleType,
special,
characterLevel,
castLevel
);
if (specialInfo) {
specialDisplay = (
<div className="ct-spell-caster__higher ct-spell-caster__higher--special">
Special: {specialInfo.description}
</div>
);
}
}
if (
!additionalAttacksDisplay &&
!additionalTargetsDisplay &&
!aoeDisplay &&
!durationDisplay &&
!creaturesDisplay &&
!specialDisplay &&
!rangeDisplay
) {
return null;
}
return (
<div className="ct-spell-caster__higher-items">
{additionalAttacksDisplay}
{additionalTargetsDisplay}
{rangeDisplay}
{aoeDisplay}
{durationDisplay}
{creaturesDisplay}
{specialDisplay}
</div>
);
};
render() {
const { castLevel } = this.state;
const { characterLevel, spell, theme } = this.props;
const modifiers = SpellUtils.getModifiers(spell);
const damageModifiers = modifiers.filter((modifier) =>
ModifierUtils.isSpellDamageModifier(modifier)
);
let damageNode: React.ReactNode;
if (damageModifiers.length) {
damageNode = (
<div className="ct-spell-caster__modifiers ct-spell-caster__modifiers--damages">
{damageModifiers.map((modifier) => {
const atHigherLevels = ModifierUtils.getAtHigherLevels(modifier);
const id = ModifierUtils.getId(modifier);
const restriction = ModifierUtils.getRestriction(modifier);
const friendlySubtypeName =
ModifierUtils.getFriendlySubtypeName(modifier);
const atHigherDamage = SpellUtils.getSpellScaledAtHigher(
spell,
atHigherLevels ? SpellUtils.getScaleType(spell) : null,
atHigherLevels && atHigherLevels.points
? atHigherLevels.points
: [],
characterLevel,
castLevel
);
const scaledDamageDie = SpellUtils.getSpellFinalScaledDie(
spell,
modifier,
atHigherDamage
);
return (
<div
className="ct-spell-caster__modifier ct-spell-caster__modifier--damage"
key={id ? id : ""}
>
{scaledDamageDie && (
<span className="ct-spell-caster__modifier-amount">
{DiceUtils.renderDice(scaledDamageDie)}
</span>
)}
{friendlySubtypeName !== null && (
<span className="ct-spell-caster__modifier-effect">
<DamageTypeIcon
theme={theme}
type={
FormatUtils.slugify(
friendlySubtypeName
) as ComponentConstants.DamageTypePropType
}
/>{" "}
Damage
</span>
)}
{!!restriction && (
<span className="ct-spell-caster__modifier-restriction">
({restriction})
</span>
)}
</div>
);
})}
</div>
);
}
const hitPointHealingModifiers = modifiers.filter((modifier) =>
ModifierUtils.isSpellHealingHitPointsModifier(modifier)
);
const tempHitPointHealingModifiers = modifiers.filter((modifier) =>
ModifierUtils.isSpellHealingTempHitPointsModifier(modifier)
);
let healingNode: React.ReactNode;
if (
hitPointHealingModifiers.length ||
tempHitPointHealingModifiers.length
) {
healingNode = (
<div className="ct-spell-caster__modifiers ct-spell-caster__modifiers--healing">
{hitPointHealingModifiers.map((modifier) => {
const atHigherLevels = ModifierUtils.getAtHigherLevels(modifier);
const id = ModifierUtils.getId(modifier);
const restriction = ModifierUtils.getRestriction(modifier);
const atHigherHealing = SpellUtils.getSpellScaledAtHigher(
spell,
atHigherLevels ? SpellUtils.getScaleType(spell) : null,
atHigherLevels && atHigherLevels.points
? atHigherLevels.points
: [],
characterLevel,
castLevel
);
const scaledHealingDie = SpellUtils.getSpellFinalScaledDie(
spell,
modifier,
atHigherHealing,
castLevel
);
const isHealingDieAdditional =
SpellUtils.hack__isHealingDieAdditionalBonusFixedValue(
damageModifiers,
spell,
scaledHealingDie
);
return (
<div
className="ct-spell-caster__modifier ct-spell-caster__modifier--hp"
key={id ? id : ""}
>
<span className="ct-spell-caster__modifier-amount">
Regain {isHealingDieAdditional && "Additional"}{" "}
{scaledHealingDie && DiceUtils.renderDice(scaledHealingDie)}{" "}
Hit Points
</span>
{!!restriction && (
<span className="ct-spell-caster__modifier-restriction">
({restriction})
</span>
)}
</div>
);
})}
{tempHitPointHealingModifiers.map((modifier) => {
const atHigherLevels = ModifierUtils.getAtHigherLevels(modifier);
const id = ModifierUtils.getId(modifier);
const restriction = ModifierUtils.getRestriction(modifier);
const atHigherHealing = SpellUtils.getSpellScaledAtHigher(
spell,
atHigherLevels ? SpellUtils.getScaleType(spell) : null,
atHigherLevels && atHigherLevels.points
? atHigherLevels.points
: [],
characterLevel,
castLevel
);
const scaledHealingDie = SpellUtils.getSpellFinalScaledDie(
spell,
modifier,
atHigherHealing,
castLevel
);
return (
<div
className="ct-spell-caster__modifier ct-spell-caster__modifier--temp"
key={id ? id : ""}
>
<span className="ct-spell-caster__modifier-amount">
Regain{" "}
{scaledHealingDie && DiceUtils.renderDice(scaledHealingDie)}{" "}
Temp Hit Points
</span>
{!!restriction && (
<span className="ct-spell-caster__modifier-restriction">
({restriction})
</span>
)}
</div>
);
})}
</div>
);
}
const castingNode = this.renderCasting();
const limitedUseNode = this.renderLimitedUseDetails();
const atHigherLevelsNode = this.renderAtHigherLevels();
if (
!castingNode &&
!limitedUseNode &&
!damageNode &&
!healingNode &&
!atHigherLevelsNode
) {
return null;
}
return (
<div className="ct-spell-caster">
{castingNode}
{limitedUseNode}
{damageNode}
{healingNode}
{atHigherLevelsNode}
</div>
);
}
}
const SpellCasterContainer = (props) => {
const { inventoryManager } = useContext(InventoryManagerContext);
return <SpellCaster inventoryManager={inventoryManager} {...props} />;
};
export default SpellCasterContainer;