diff --git a/ddb_main/packages/rules-engine/es/engine/Prerequisite/utils.js b/ddb_main/packages/rules-engine/es/engine/Prerequisite/utils.js index 26d80e1..5dc4279 100644 --- a/ddb_main/packages/rules-engine/es/engine/Prerequisite/utils.js +++ b/ddb_main/packages/rules-engine/es/engine/Prerequisite/utils.js @@ -1,3 +1,4 @@ +import sortBy from 'lodash/sortBy'; import { AbilityAccessors } from '../Ability'; import { FormatUtils } from '../Format'; import { getEntityId, getFriendlySubtypeName, getPrerequisites, getSubType, getType, getValue, getShouldExclude, } from './accessors'; @@ -207,3 +208,46 @@ export function getPrerequisiteGroupingFailures(prerequisiteGrouping, prerequisi } return groupingFailures.filter((group) => group.length); } +// Creates a string that summarizes the prerequisite failures for feats and multiclassing +export function getPrerequisiteFailuresText(prereqFailures) { + //get all the prerequisite failures, sorted so that if every group failure is a missing prereq, they come first + const failures = sortBy(prereqFailures, (failureGroup) => (failureGroup.every((f) => !f.shouldExclude) ? 0 : 1)); + // Create an intro string for the summary + let intro = ''; + // Check if all failures are either missing or prohibited + const hasOnlyMissingFailures = failures.every((failureGroup) => failureGroup.every((f) => !f.shouldExclude)); + const hasOnlyProhibitedFailures = failures.every((failureGroup) => failureGroup.every((f) => f.shouldExclude)); + // Determine the intro text based on the type of failures + if (hasOnlyMissingFailures) { + intro = 'Missing: '; + } + else if (hasOnlyProhibitedFailures) { + intro = 'Prohibited by: '; + } + else { + intro = 'Requires: '; + } + // Each failure is sorted so that prohibited failures come last, and then combined into a single string + const combinedStrings = failures + .map((failure) => sortBy(failure, (f) => (f.shouldExclude ? 1 : 0)) + .map((failureAnd) => { + if (failureAnd.shouldExclude && !hasOnlyProhibitedFailures && !hasOnlyMissingFailures) { + return `Prohibited by: ${failureAnd.data.requiredDescription}`; + } + return `${failureAnd.data.requiredDescription}`; + }) + .reduce((acc, desc, idx) => { + let connector = ''; + if (idx < failure.length - 2) { + connector = ', '; + } + else if (idx < failure.length - 1) { + connector = ' and '; + } + acc += `${desc}${connector}`; + return acc; + }, '')) + .join(' or '); + // return the intro text followed by the combined strings + return `${intro}${combinedStrings}`; +} diff --git a/ddb_main/packages/rules-engine/es/managers/FeatManager.js b/ddb_main/packages/rules-engine/es/managers/FeatManager.js index 185fe25..98b3ec9 100644 --- a/ddb_main/packages/rules-engine/es/managers/FeatManager.js +++ b/ddb_main/packages/rules-engine/es/managers/FeatManager.js @@ -1,4 +1,3 @@ -import sortBy from 'lodash/sortBy'; import { characterActions } from "../actions"; import { ChoiceUtils } from "../engine/Choice"; import { DisplayConfigurationTypeEnum } from "../engine/Core"; @@ -77,48 +76,7 @@ export class FeatManager extends BaseManager { return PrerequisiteUtils.getPrerequisiteGroupingFailures(this.getPrerequisites(), prerequisiteData); }; // Creates a string that summarizes the prerequisite failures for this feat - this.getPrerequisiteFailuresText = () => { - //get all the prerequisite failures, sorted so that if every group failure is a missing prereq, they come first - const failures = sortBy(this.getPrerequisiteFailures(), (failureGroup) => failureGroup.every((f) => !f.shouldExclude) ? 0 : 1); - // Create an intro string for the summary - let intro = ''; - // Check if all failures are either missing or prohibited - const hasOnlyMissingFailures = failures.every((failureGroup) => failureGroup.every((f) => !f.shouldExclude)); - const hasOnlyProhibitedFailures = failures.every((failureGroup) => failureGroup.every((f) => f.shouldExclude)); - // Determine the intro text based on the type of failures - if (hasOnlyMissingFailures) { - intro = 'Missing: '; - } - else if (hasOnlyProhibitedFailures) { - intro = 'Prohibits: '; - } - else { - intro = 'Requires: '; - } - // Each failure is sorted so that prohibited failures come last, and then combined into a single string - const combinedStrings = failures - .map((failure) => sortBy(failure, (f) => (f.shouldExclude ? 1 : 0)) - .map((failureAnd) => { - if (failureAnd.shouldExclude && !hasOnlyProhibitedFailures && !hasOnlyMissingFailures) { - return `Prohibits ${failureAnd.data.requiredDescription}`; - } - return `${failureAnd.data.requiredDescription}`; - }) - .reduce((acc, desc, idx) => { - let connector = ''; - if (idx < failure.length - 2) { - connector = ', '; - } - else if (idx < failure.length - 1) { - connector = ' and '; - } - acc += `${desc}${connector}`; - return acc; - }, '')) - .join(' or '); - // return the intro text followed by the combined strings - return `${intro}${combinedStrings}`; - }; + this.getPrerequisiteFailuresText = () => PrerequisiteUtils.getPrerequisiteFailuresText(this.getPrerequisiteFailures()); this.getRepeatableGroupId = () => FeatUtils.getRepeatableGroupId(this.feat); this.isRepeatableFeatParent = () => this.isRepeatable() && this.getRepeatableParentId() === null; this.getDefinitionKey = () => { diff --git a/ddb_main/subApps/builder/helpers/getMissingRequirements.ts b/ddb_main/subApps/builder/helpers/getMissingRequirements.ts deleted file mode 100644 index c1c1e31..0000000 --- a/ddb_main/subApps/builder/helpers/getMissingRequirements.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { PrerequisiteFailure } from "../types"; - -// Format a group of PrerequisiteFailures into a human-readable string -const formatReq = (group: Array) => - group.map(({ data }) => { - const key = data.statKey?.toUpperCase() || ""; - const value = data.requiredValue; - const amount = data.currentAmount === null ? "None" : data.currentAmount; - - return `${key} ${value} (${amount})`; - }); - -// Loop through groups of PrerequisiteFailures and format them into a human-readable string -export const getMissingRequirements = ( - groups: Array> -) => groups.map((group) => formatReq(group).join(", ")).join(" or "); diff --git a/ddb_main/subApps/builder/routes/ClassChoose/ClassChoose.tsx b/ddb_main/subApps/builder/routes/ClassChoose/ClassChoose.tsx index c6db395..3b36105 100644 --- a/ddb_main/subApps/builder/routes/ClassChoose/ClassChoose.tsx +++ b/ddb_main/subApps/builder/routes/ClassChoose/ClassChoose.tsx @@ -2,6 +2,8 @@ import clsx from "clsx"; import { FC, HTMLAttributes, useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; +import { PrerequisiteUtils } from "@dndbeyond/character-rules-engine"; + import { Accordion } from "~/components/Accordion"; import { HtmlContent } from "~/components/HtmlContent"; import { Link } from "~/components/Link"; @@ -19,7 +21,6 @@ import { Search } from "../../components/Search"; import { Spinner } from "../../components/Spinner"; import { RouteKey } from "../../constants"; import { useClassContext } from "../../contexts/Class"; -import { getMissingRequirements } from "../../helpers/getMissingRequirements"; import { ClassDefinitionContract, ClassItems, @@ -137,6 +138,8 @@ export const ClassChoose: FC = ({ return classes.map( ({ id: classId, prerequisites }): MulticlassAvailability => { let canMulticlass = false; + + // If there are no prerequisites, the class can always be const missingRequirements = getGroupingFailures( prerequisites, prerequisiteData @@ -242,11 +245,11 @@ export const ClassChoose: FC = ({ metaItems.push({ type: "error", text: !canStartingClassMulticlass - ? `Starting class does not meet multiclass prerequisites: ${getMissingRequirements( + ? `Starting class does not meet multiclass prerequisites - ${PrerequisiteUtils.getPrerequisiteFailuresText( startingClassRequirements )}` : !canMulticlass - ? `Prerequisites not met: ${getMissingRequirements( + ? `Prerequisites not met - ${PrerequisiteUtils.getPrerequisiteFailuresText( missingRequirements )}` : "", diff --git a/ddb_main/tools/js/Shared/components/VehicleBlock/VehicleBlockPrimary/VehicleBlockPrimary.tsx b/ddb_main/tools/js/Shared/components/VehicleBlock/VehicleBlockPrimary/VehicleBlockPrimary.tsx index 501495f..fd7c9bf 100644 --- a/ddb_main/tools/js/Shared/components/VehicleBlock/VehicleBlockPrimary/VehicleBlockPrimary.tsx +++ b/ddb_main/tools/js/Shared/components/VehicleBlock/VehicleBlockPrimary/VehicleBlockPrimary.tsx @@ -147,7 +147,7 @@ export default class VehicleBlockPrimary extends React.PureComponent { return ( <> -
+
{primaryProperties !== null && ( { case Constants.VehicleConfigurationDisplayTypeEnum.INFERNAL_WAR_MACHINE: type = "infernal"; break; + case Constants.VehicleConfigurationDisplayTypeEnum.ELEMENTAL_AIRSHIP: + type = "elemental-airship"; + break; + case Constants.VehicleConfigurationDisplayTypeEnum.SPELLJAMMER: + type = "spelljammer"; + break; case Constants.VehicleConfigurationDisplayTypeEnum.SHIP: default: type = "ship"; @@ -24,7 +30,7 @@ export default class VehicleBlockShell extends React.PureComponent { } return ( -
+