import { visuallyHidden } from "@mui/utils"; import { orderBy } from "lodash"; import { Tooltip } from "@dndbeyond/character-common-components/es"; import { BoxBackground, FancyBoxSvg230x200, FancyBoxSvg281x200, SavingThrowsSummary, } from "@dndbeyond/character-components/es"; import { AbilityManager, CharacterTheme, CharacterUtils, Constants, DeathSaveInfo, DiceAdjustment, RuleData, SituationalSavingThrowInfoLookup, } from "@dndbeyond/character-rules-engine/es"; import { IRollContext } from "@dndbeyond/dice"; import DiceAdjustmentSummary from "../../../Shared/components/DiceAdjustmentSummary"; import { StyleSizeTypeEnum } from "../../../Shared/reducers/appEnv"; import { AppEnvDimensionsState } from "../../../Shared/stores/typings"; interface Props { abilities: Array; ruleData: RuleData; savingThrowDiceAdjustments: Array; situationalBonusSavingThrowsLookup: SituationalSavingThrowInfoLookup; deathSaveInfo: DeathSaveInfo; maxSummariesShown?: number; onAbilityClick?: (ability: AbilityManager) => void; onInfoClick?: () => void; onClick?: () => void; dimensions: AppEnvDimensionsState; theme: CharacterTheme; diceEnabled?: boolean; rollContext: IRollContext; } const sortDiceAdjustments = ( diceAdjustments: Array ): Array => { let sortOrderLookup: Record = { [Constants.DiceAdjustmentTypeEnum.ADVANTAGE]: 1, [Constants.DiceAdjustmentTypeEnum.DISADVANTAGE]: 2, [Constants.DiceAdjustmentTypeEnum.BONUS]: 3, }; return orderBy(diceAdjustments, [ (diceAdjustment) => sortOrderLookup[diceAdjustment.type], ]); }; export default function SavingThrowsBox({ abilities, ruleData, savingThrowDiceAdjustments, situationalBonusSavingThrowsLookup, deathSaveInfo, maxSummariesShown = 3, onAbilityClick, onInfoClick, onClick, dimensions, theme, diceEnabled = false, rollContext, }: Props) { const filterDiceAdjustments = (): Array => { const advantageSavingThrowAdjustments: Array = []; const disadvantageSavingThrowAdjustments: Array = []; const bonusSavingThrowAdjustments: Array = []; savingThrowDiceAdjustments.forEach((adjustment) => { if (adjustment.type === Constants.DiceAdjustmentTypeEnum.ADVANTAGE) { advantageSavingThrowAdjustments.push(adjustment); } if (adjustment.type === Constants.DiceAdjustmentTypeEnum.DISADVANTAGE) { disadvantageSavingThrowAdjustments.push(adjustment); } if (adjustment.type === Constants.DiceAdjustmentTypeEnum.BONUS) { bonusSavingThrowAdjustments.push(adjustment); } }); // TODO: this logic "seems" more related to game logic and less like view logic it should be moved. //get highest dice adjustment count from all saving throws let highestSavingThrowAdjustmentCount: number = Math.max( advantageSavingThrowAdjustments.length, disadvantageSavingThrowAdjustments.length, bonusSavingThrowAdjustments.length ); //get highest dice adjustment count from all dice adjustments let highestDiceAdjustmentCount: number = Math.max( highestSavingThrowAdjustmentCount, deathSaveInfo.advantageAdjustments.length, deathSaveInfo.disadvantageAdjustments.length ); let diceAdjustments: Array = []; if (highestDiceAdjustmentCount) { let saveAdjustmentMaxLoopCount: number = Math.min( maxSummariesShown, highestSavingThrowAdjustmentCount ); //Does a round robin of all saving throw adjustments for (let i = 0; i < saveAdjustmentMaxLoopCount; i++) { if (diceAdjustments.length >= maxSummariesShown) { continue; } if (advantageSavingThrowAdjustments.length > i) { diceAdjustments.push(advantageSavingThrowAdjustments[i]); } if ( diceAdjustments.length < maxSummariesShown && disadvantageSavingThrowAdjustments.length > i ) { diceAdjustments.push(disadvantageSavingThrowAdjustments[i]); } if ( diceAdjustments.length < maxSummariesShown && bonusSavingThrowAdjustments.length > i ) { diceAdjustments.push(bonusSavingThrowAdjustments[i]); } } //Does a round robin of any death save adjustments if there is room in the diceAdjustments array let deathSaveMaxLoopCount: number = Math.max( 0, maxSummariesShown - diceAdjustments.length ); for (let i = 0; i < deathSaveMaxLoopCount; i++) { if (diceAdjustments.length >= maxSummariesShown) { continue; } if (deathSaveInfo.advantageAdjustments.length > i) { diceAdjustments.push(deathSaveInfo.advantageAdjustments[i]); } if ( diceAdjustments.length < maxSummariesShown && deathSaveInfo.disadvantageAdjustments.length > i ) { diceAdjustments.push(deathSaveInfo.disadvantageAdjustments[i]); } } if (diceAdjustments.length > 1) { return sortDiceAdjustments(diceAdjustments); } } return diceAdjustments; }; const handleAbilityClick = (ability: AbilityManager): void => { if (onAbilityClick) { onAbilityClick(ability); } }; const handleClick = (evt: React.MouseEvent): void => { if (onClick) { evt.stopPropagation(); evt.nativeEvent.stopImmediatePropagation(); onClick(); } }; const handleInfoClick = (evt: React.MouseEvent): void => { if (onInfoClick) { evt.stopPropagation(); evt.nativeEvent.stopImmediatePropagation(); onInfoClick(); } }; const renderSummary = ( diceAdjustment: DiceAdjustment, includeTooltip: boolean = true ): React.ReactNode => { const tooltip = CharacterUtils.generateSavingThrowAdjustmentSummary( diceAdjustment, ruleData ); // White space is very intentional return ( ); //` }; const renderSingleSummary = ( diceAdjustment: DiceAdjustment ): React.ReactNode => { return (
{renderSummary(diceAdjustment, false)}
); }; const renderMultiSummary = ( diceAdjustments: Array ): React.ReactNode => { return (
{diceAdjustments.map((diceAdjustment) => renderSummary(diceAdjustment))}
); }; const renderEmpty = (): React.ReactNode => { return (
Saving Throw Modifiers
); }; const renderSaveInfo = (): React.ReactNode => { const diceAdjustments = filterDiceAdjustments(); if (diceAdjustments.length === 0) { return renderEmpty(); } if (diceAdjustments.length === 1) { return renderSingleSummary(diceAdjustments[0]); } return renderMultiSummary(diceAdjustments); }; let BoxBackgroundComponent: React.ComponentType = FancyBoxSvg230x200; let useSmallRowStyle: boolean = true; if ( dimensions.styleSizeType > StyleSizeTypeEnum.DESKTOP || dimensions.styleSizeType <= StyleSizeTypeEnum.TABLET ) { BoxBackgroundComponent = FancyBoxSvg281x200; useSmallRowStyle = false; } return (

Saving Throws

{renderSaveInfo()}
); }