New source found from dndbeyond.com

This commit is contained in:
2025-07-23 01:00:16 -07:00
parent f9f8dd0e7a
commit 9a983a6d7b
6 changed files with 59 additions and 64 deletions
@@ -1,3 +1,4 @@
import sortBy from 'lodash/sortBy';
import { AbilityAccessors } from '../Ability'; import { AbilityAccessors } from '../Ability';
import { FormatUtils } from '../Format'; import { FormatUtils } from '../Format';
import { getEntityId, getFriendlySubtypeName, getPrerequisites, getSubType, getType, getValue, getShouldExclude, } from './accessors'; 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); 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}`;
}
@@ -1,4 +1,3 @@
import sortBy from 'lodash/sortBy';
import { characterActions } from "../actions"; import { characterActions } from "../actions";
import { ChoiceUtils } from "../engine/Choice"; import { ChoiceUtils } from "../engine/Choice";
import { DisplayConfigurationTypeEnum } from "../engine/Core"; import { DisplayConfigurationTypeEnum } from "../engine/Core";
@@ -77,48 +76,7 @@ export class FeatManager extends BaseManager {
return PrerequisiteUtils.getPrerequisiteGroupingFailures(this.getPrerequisites(), prerequisiteData); return PrerequisiteUtils.getPrerequisiteGroupingFailures(this.getPrerequisites(), prerequisiteData);
}; };
// Creates a string that summarizes the prerequisite failures for this feat // Creates a string that summarizes the prerequisite failures for this feat
this.getPrerequisiteFailuresText = () => { this.getPrerequisiteFailuresText = () => PrerequisiteUtils.getPrerequisiteFailuresText(this.getPrerequisiteFailures());
//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.getRepeatableGroupId = () => FeatUtils.getRepeatableGroupId(this.feat); this.getRepeatableGroupId = () => FeatUtils.getRepeatableGroupId(this.feat);
this.isRepeatableFeatParent = () => this.isRepeatable() && this.getRepeatableParentId() === null; this.isRepeatableFeatParent = () => this.isRepeatable() && this.getRepeatableParentId() === null;
this.getDefinitionKey = () => { this.getDefinitionKey = () => {
@@ -1,16 +0,0 @@
import { PrerequisiteFailure } from "../types";
// Format a group of PrerequisiteFailures into a human-readable string
const formatReq = (group: Array<PrerequisiteFailure>) =>
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<Array<PrerequisiteFailure>>
) => groups.map((group) => formatReq(group).join(", ")).join(" or ");
@@ -2,6 +2,8 @@ import clsx from "clsx";
import { FC, HTMLAttributes, useEffect, useState } from "react"; import { FC, HTMLAttributes, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { PrerequisiteUtils } from "@dndbeyond/character-rules-engine";
import { Accordion } from "~/components/Accordion"; import { Accordion } from "~/components/Accordion";
import { HtmlContent } from "~/components/HtmlContent"; import { HtmlContent } from "~/components/HtmlContent";
import { Link } from "~/components/Link"; import { Link } from "~/components/Link";
@@ -19,7 +21,6 @@ import { Search } from "../../components/Search";
import { Spinner } from "../../components/Spinner"; import { Spinner } from "../../components/Spinner";
import { RouteKey } from "../../constants"; import { RouteKey } from "../../constants";
import { useClassContext } from "../../contexts/Class"; import { useClassContext } from "../../contexts/Class";
import { getMissingRequirements } from "../../helpers/getMissingRequirements";
import { import {
ClassDefinitionContract, ClassDefinitionContract,
ClassItems, ClassItems,
@@ -137,6 +138,8 @@ export const ClassChoose: FC<ClassChooseProps> = ({
return classes.map( return classes.map(
({ id: classId, prerequisites }): MulticlassAvailability => { ({ id: classId, prerequisites }): MulticlassAvailability => {
let canMulticlass = false; let canMulticlass = false;
// If there are no prerequisites, the class can always be
const missingRequirements = getGroupingFailures( const missingRequirements = getGroupingFailures(
prerequisites, prerequisites,
prerequisiteData prerequisiteData
@@ -242,11 +245,11 @@ export const ClassChoose: FC<ClassChooseProps> = ({
metaItems.push({ metaItems.push({
type: "error", type: "error",
text: !canStartingClassMulticlass text: !canStartingClassMulticlass
? `Starting class does not meet multiclass prerequisites: ${getMissingRequirements( ? `Starting class does not meet multiclass prerequisites - ${PrerequisiteUtils.getPrerequisiteFailuresText(
startingClassRequirements startingClassRequirements
)}` )}`
: !canMulticlass : !canMulticlass
? `Prerequisites not met: ${getMissingRequirements( ? `Prerequisites not met - ${PrerequisiteUtils.getPrerequisiteFailuresText(
missingRequirements missingRequirements
)}` )}`
: "", : "",
@@ -147,7 +147,7 @@ export default class VehicleBlockPrimary extends React.PureComponent<Props> {
return ( return (
<> <>
<div className="ct-vehicle-block__attributes"> <div className="ct-vehicle-block__attributes ct-vehicle-block__attributes--primary">
{primaryProperties !== null && ( {primaryProperties !== null && (
<VehicleBlockPrimaryAttributes <VehicleBlockPrimaryAttributes
armorClassInfo={primaryProperties.armorClassInfo} armorClassInfo={primaryProperties.armorClassInfo}
@@ -17,6 +17,12 @@ export default class VehicleBlockShell extends React.PureComponent<Props> {
case Constants.VehicleConfigurationDisplayTypeEnum.INFERNAL_WAR_MACHINE: case Constants.VehicleConfigurationDisplayTypeEnum.INFERNAL_WAR_MACHINE:
type = "infernal"; type = "infernal";
break; break;
case Constants.VehicleConfigurationDisplayTypeEnum.ELEMENTAL_AIRSHIP:
type = "elemental-airship";
break;
case Constants.VehicleConfigurationDisplayTypeEnum.SPELLJAMMER:
type = "spelljammer";
break;
case Constants.VehicleConfigurationDisplayTypeEnum.SHIP: case Constants.VehicleConfigurationDisplayTypeEnum.SHIP:
default: default:
type = "ship"; type = "ship";
@@ -24,7 +30,7 @@ export default class VehicleBlockShell extends React.PureComponent<Props> {
} }
return ( return (
<div className="ct-vehicle-block"> <div className={`ct-vehicle-block ct-vehicle-block--${type}`}>
<VehicleBlockShellCap displayType={displayType} /> <VehicleBlockShellCap displayType={displayType} />
<div <div
className={`ct-vehicle-block__block ct-vehicle-block__block--${type}`} className={`ct-vehicle-block__block ct-vehicle-block__block--${type}`}