Grabbed dndbeyond's source code
``` ~/go/bin/sourcemapper -output ddb -jsurl https://media.dndbeyond.com/character-app/static/js/main.90aa78c5.js ```
This commit is contained in:
@@ -0,0 +1,965 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user