import * as React from "react"; import { AbilityLookup, Attack, Constants, DataOriginRefData, EntityUtils, FormatUtils, Spell, SpellCasterInfo, SpellUtils, RuleData, CharacterTheme, } from "@dndbeyond/character-rules-engine/es"; import { Dice, DiceEvent, IRollContext } from "@dndbeyond/dice"; import { NumberDisplay } from "~/components/NumberDisplay"; import { SpellName } from "~/components/SpellName"; import { SpellSchoolIcon } from "../../Icons"; import NoteComponents from "../../NoteComponents"; import SpellDamageEffect from "../../SpellDamageEffect"; import { SpellSchoolPropType } from "../../componentConstants"; import { DiceComponentUtils } from "../../utils"; import CombatAttack from "../CombatAttack"; interface Props { attack: Attack; spell: Spell; onClick?: (attack: Attack) => void; ruleData: RuleData; abilityLookup: AbilityLookup; spellCasterInfo: SpellCasterInfo; showNotes: boolean; className: string; diceEnabled: boolean; theme: CharacterTheme; rollContext: IRollContext; dataOriginRefData: DataOriginRefData; proficiencyBonus: number; } interface State { isCriticalHit: boolean; } export default class CombatSpellAttack extends React.PureComponent< Props, State > { diceEventHandler: (eventData: any) => void; constructor(props: Props) { super(props); this.state = { isCriticalHit: false, }; } static defaultProps = { showNotes: true, className: "", diceEnabled: false, }; componentDidMount = () => { this.diceEventHandler = DiceComponentUtils.setupResetCritStateOnRoll( SpellUtils.getName(this.props.spell), this ); }; componentWillUnmount = () => { Dice.removeEventListener(DiceEvent.ROLL, this.diceEventHandler); }; handleClick = (): void => { const { onClick, attack } = this.props; if (onClick) { onClick(attack); } }; renderNotes = (): React.ReactNode => { const { spell, abilityLookup, ruleData, showNotes, spellCasterInfo, proficiencyBonus, theme, } = this.props; if (!showNotes) { return null; } const castLevel = SpellUtils.getMinCastLevel( spell, spellCasterInfo, ruleData ); const characterLevel = SpellUtils.getCharacterLevel(spell); let scaledAmount: number = 0; return (
); }; handleRoll = (wasCrit: boolean) => { this.setState({ isCriticalHit: wasCrit }); }; render() { const { attack, spell, ruleData, spellCasterInfo, showNotes, diceEnabled, theme, dataOriginRefData, rollContext, className, } = this.props; const { isCriticalHit } = this.state; const characterLevel = SpellUtils.getCharacterLevel(spell); const attackSaveValue = SpellUtils.getAttackSaveValue(spell); const toHit = SpellUtils.getToHit(spell); const isCustomized = SpellUtils.isCustomized(spell); const range = SpellUtils.getRange(spell); const level = SpellUtils.getLevel(spell); const school = SpellUtils.getSchool(spell); const concentration = SpellUtils.getConcentration(spell); const requiresAttackRoll = SpellUtils.getRequiresAttackRoll(spell); const requiresSavingThrow = SpellUtils.getRequiresSavingThrow(spell); const castLevel = SpellUtils.getMinCastLevel( spell, spellCasterInfo, ruleData ); let metaItems: Array = []; if (SpellUtils.isLegacy(spell)) { metaItems.push("Legacy"); } metaItems.push(FormatUtils.renderSpellLevelName(level)); const spellDataOrigin = SpellUtils.getDataOrigin(spell); if (spellDataOrigin) { const dataOriginName = EntityUtils.getDataOriginName(spellDataOrigin); metaItems.push(dataOriginName); } let expandedDataOrigin = SpellUtils.getExpandedDataOriginRef(spell); if (expandedDataOrigin) { metaItems.push( EntityUtils.getDataOriginRefName(expandedDataOrigin, dataOriginRefData) ); } if (concentration) { metaItems.push("Concentration"); } if (isCustomized) { metaItems.push("Customized"); } let rangeAreaNode: React.ReactNode; if (range !== null) { rangeAreaNode = ( {!!range.origin && range.origin !== Constants.SpellRangeTypeNameEnum.RANGED && ( {range.origin} )} {!!range.rangeValue && ( )} ); } let toHitDisplay: number | null = null; let saveDcValue: number | null = null; let saveDcLabel: React.ReactNode; if (requiresAttackRoll) { toHitDisplay = toHit; } else if (requiresSavingThrow) { saveDcValue = attackSaveValue; saveDcLabel = SpellUtils.getSaveDcAbilityShortName(spell, ruleData); } let attackClassNames: Array = ["ddbc-combat-attack--spell", className]; if (isCriticalHit) { attackClassNames.push("ddbc-combat-attack--crit"); } return ( } name={ } metaItems={metaItems} rangeValue={rangeAreaNode} rangeLabel={""} toHit={toHitDisplay} attackSaveValue={saveDcValue} attackSaveLabel={saveDcLabel} rollContext={rollContext} damage={ } onClick={this.handleClick} notes={this.renderNotes()} showNotes={showNotes} diceEnabled={diceEnabled} onRoll={this.handleRoll} theme={theme} /> ); } }