New source found from dndbeyond.com
This commit is contained in:
@@ -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
|
||||||
)}`
|
)}`
|
||||||
: "",
|
: "",
|
||||||
|
|||||||
+1
-1
@@ -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}
|
||||||
|
|||||||
+7
-1
@@ -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}`}
|
||||||
|
|||||||
Reference in New Issue
Block a user