import { DiceUtils, DiceContract, RollValueContract, HelperUtils, } from "@dndbeyond/character-rules-engine/es"; import { RollKinds, RollTypes, } from "@dndbeyond/pocket-dimension-dice/constants"; import { parseDiceNotation } from "@dndbeyond/pocket-dimension-dice/helpers/parseDiceNotation"; import { toDiceNotationString } from "@dndbeyond/pocket-dimension-dice/helpers/toDiceNotationString"; import { RollDicePayload } from "@dndbeyond/pocket-dimension-dice/types"; import { DiceComponentUtils, TypeScriptUtils } from "../index"; import { hack__generateDiceRollResultTermLookup, hack__generateDiceRollResultValueExcludeTypeLookup, } from "./hacks"; /** * Checks for a critical hit */ export function isCriticalRoll(result: RollDicePayload["data"]): boolean { // Does this roll have a to-hit roll? const hitRoll = result.rolls.find((r) => r.rollType == RollTypes.ToHit); if (hitRoll) { const minRollForCrit: number = 20; let numCritRolls: number = hitRoll.result?.values.filter((v) => v >= minRollForCrit).length ?? 0; let critRoll: boolean = false; switch (hitRoll.rollKind) { case RollKinds.Disadvantage: critRoll = numCritRolls >= 2; break; case RollKinds.Advantage: default: critRoll = numCritRolls > 0; break; } return critRoll; } return false; } /** * Returns a dice notation string taking into account the current state * (doubles damage dice when in critical hit state) * * @param isCriticalHit Flags if the dice notation is in a critical hit state * @param damage Normally displayed damage */ export function getDamageDiceNotation( damage: number | DiceContract | null, isCriticalHit?: boolean ): string { if (typeof damage === "number") { return damage.toString(); } if (!isCriticalHit) { return DiceUtils.renderDice(damage); } let dn = parseDiceNotation(DiceUtils.renderDice(damage)); for (let i = 0; i < dn.set.length; i++) { // Double the dice on each term if (dn.set[i]) { dn.set[i]!.count *= 2; } } return toDiceNotationString(dn.set, dn.constant); } /** * * @param dieType */ export function getDieTypeValue(dieType: string): number { return Number(dieType.substr(1)); } /** * Returns Array given the a rollRequestResult with a single RollResult * * @param rollRequestResult */ export function generateRollValueContracts( rollRequestResult: RollDicePayload["data"] ): Array | null { //these hacks make it easier to grab the data from the dice api until the api is updated const hack__diceRollResultTermLookup = hack__generateDiceRollResultTermLookup(rollRequestResult); const hack__diceRollResultValueExcludeTypeLookup = hack__generateDiceRollResultValueExcludeTypeLookup(rollRequestResult); //RollResult | undefined - RollResult is not exported from Dice; // only expect to have one single rollRequest. const rollResult = rollRequestResult.rolls[0].result ?? null; if (!rollResult) { return null; } return rollResult.values .map((value, idx) => { const dieTerm = HelperUtils.lookupDataOrFallback( hack__diceRollResultTermLookup, idx ); const excludeType = HelperUtils.lookupDataOrFallback( hack__diceRollResultValueExcludeTypeLookup, idx ); if (!dieTerm || !excludeType) { return null; } return { value, dieType: DiceComponentUtils.getDieTypeValue(dieTerm.dieType), excludeType, }; }) .filter(TypeScriptUtils.isNotNullOrUndefined); }