New source found from dndbeyond.com

This commit is contained in:
2025-07-16 01:00:16 -07:00
parent 151590af7a
commit f9f8dd0e7a
25 changed files with 886 additions and 1299 deletions
@@ -1,11 +1,15 @@
import { orderBy } from 'lodash';
import { TypeScriptUtils } from "../../utils";
import { ChoiceAccessors } from '.';
import { ClassAccessors } from '../Class';
import { AbilityStatEnum, BuilderChoiceSubtypeEnum } from '../Core';
import { AbilityStatEnum, BuilderChoiceSubtypeEnum, BuilderChoiceTypeEnum, } from '../Core';
import { DataOriginTypeEnum } from '../DataOrigin';
import { EntityUtils } from '../Entity';
import { FeatAccessors, FeatUtils } from '../Feat';
import { HelperUtils } from '../Helper';
import { ModifierAccessors } from '../Modifier';
import { PrerequisiteValidators } from '../Prerequisite';
import { RuleDataAccessors } from '../RuleData';
import { SourceUtils } from '../Source';
import { SpellUtils } from '../Spell';
import { getDefaultSubtypes, getOptions, getOptionValue, isOptional } from './accessors';
@@ -33,8 +37,9 @@ export function isTodo(choice) {
return !isOptional(choice) && getOptionValue(choice) === null;
}
// Given a Choice, return options for a select element that are potentially grouped by sourceCategory
//used in DetailChoiceFeat.tsx for feat choices
export function getSortedChoiceOptionsInfo(choice, ruleData, entityRestrictionData) {
//used in FeatPane and FeatDetail for feat choices
// and in getSortedChoiceOptionsForFeaturesInfo for feature choices that are feats and have subchoices
export function getSortedChoiceOptionsForFeatsInfo(featId, choice, ruleData, entityRestrictionData) {
var _a;
const options = getOptions(choice);
let description;
@@ -56,10 +61,126 @@ export function getSortedChoiceOptionsInfo(choice, ruleData, entityRestrictionDa
featChoiceOptions = orderBy(featChoiceOptions, 'label');
}
return {
featId,
choice,
options: featChoiceOptions,
description,
};
}
// Given a Choice, return options for a select element that are potentially grouped by sourceCategory
//used in FeatureChoice for all choices
// extracted from FeatureChoice.tsx
// TODO needs to be broken down into smaller utils and simplified
export function getSortedChoiceOptionsForFeaturesInfo(choice, fetchedFeats, featLookup, charClass, fetchedSubclasses, characterPreferences, prerequisiteData, ruleData, entityRestrictionData) {
var _a, _b;
let description;
let availableOptions;
let featSubChoices;
const optionValue = ChoiceAccessors.getOptionValue(choice);
const options = ChoiceAccessors.getOptions(choice);
const type = ChoiceAccessors.getType(choice);
const tagConstraints = ChoiceAccessors.getTagConstraints(choice);
switch (type) {
case BuilderChoiceTypeEnum.FEAT_CHOICE_OPTION:
const availableFeats = getAvailableFeatChoices(optionValue, fetchedFeats, featLookup);
const repeatableFeatTracker = new Set();
// Add selected feat to repeatable tracker if repeatable
const selectedFeat = optionValue ? featLookup[optionValue] : null;
if (selectedFeat && FeatAccessors.isRepeatable(selectedFeat)) {
const parentId = FeatUtils.getRepeatableGroupId(selectedFeat);
if (parentId) {
repeatableFeatTracker.add(parentId);
}
}
const filteredFeats = availableFeats.filter((feat) => {
const featId = FeatAccessors.getId(feat);
const isRepeatable = FeatAccessors.isRepeatable(feat);
// If the feat is the currently selected, always include it
if (featId === optionValue) {
return true;
}
// Always exclude all previous selected feats
if (featLookup[featId]) {
return false;
}
// If the Feat does not meet the tag constraints, should they exist, exclude it
const tagCategories = FeatAccessors.getCategories(feat);
if (tagConstraints && !FeatUtils.doesSatisfyTagConstraints(tagCategories, tagConstraints)) {
return false;
}
// Handle prerequisites when enforcing feat rules
if (characterPreferences.enforceFeatRules &&
!PrerequisiteValidators.validatePrerequisiteGrouping(FeatAccessors.getPrerequisites(feat), prerequisiteData)) {
return false;
}
// Special handling for repeatable feats, there can be only one
if (isRepeatable) {
const parentId = FeatUtils.getRepeatableGroupId(feat);
// If a feat from this repeatable group exist exclude all others
if (repeatableFeatTracker.has(parentId)) {
return false;
}
repeatableFeatTracker.add(parentId);
}
// If none of the exclusions above are met, include the feat
return true;
});
//Group available feats by source category
availableOptions = SourceUtils.getGroupedOptionsBySourceCategory(filteredFeats
.map((feat) => FeatAccessors.getDefinition(feat))
.filter(TypeScriptUtils.isNotNullOrUndefined), ruleData);
if (selectedFeat && optionValue !== null) {
description = (_a = FeatAccessors.getDescription(selectedFeat)) !== null && _a !== void 0 ? _a : '';
const featChoices = FeatAccessors.getChoices(selectedFeat);
if (featChoices.length > 0) {
featSubChoices = featChoices.map((featChoice) => {
return getSortedChoiceOptionsForFeatsInfo(optionValue, featChoice, ruleData, entityRestrictionData);
});
}
// subchoicesNode = <DetailChoiceFeat featId={optionValue} />;
}
break;
case BuilderChoiceTypeEnum.SUB_CLASS_OPTION:
const subclassData = charClass ? getAvailableSubclassChoices(charClass, fetchedSubclasses) : [];
//Group available subclasses by source category
availableOptions = SourceUtils.getGroupedOptionsBySourceCategory(subclassData, ruleData);
const chosenSubclass = subclassData.find((subclass) => subclass.id === optionValue);
if (chosenSubclass) {
description = '';
const sources = chosenSubclass.sources
? chosenSubclass.sources
.map((sourceMapping) => HelperUtils.lookupDataOrFallback(RuleDataAccessors.getSourceDataLookup(ruleData), sourceMapping.sourceId))
.filter(TypeScriptUtils.isNotNullOrUndefined)
: [];
sources.forEach((source) => {
var _a, _b;
if ((_a = source.sourceCategory) === null || _a === void 0 ? void 0 : _a.isToggleable) {
description += source.sourceCategory.description ? source.sourceCategory.description : '';
}
description += (_b = chosenSubclass.description) !== null && _b !== void 0 ? _b : '';
});
}
break;
case BuilderChoiceTypeEnum.ENTITY_SPELL_OPTION:
// Map over the options to mock parts of a SpellDefinitionContract.
const spellOptions = SourceUtils.getSimpleSourcedDefinitionContracts(options);
availableOptions = SourceUtils.getGroupedOptionsBySourceCategory(spellOptions, ruleData, optionValue, entityRestrictionData);
// If there is a chosen spell, set detailChoiceDesc to its description.
const chosenSpell = spellOptions.find((spell) => spell.id === optionValue);
if (chosenSpell) {
description = (_b = chosenSpell.description) !== null && _b !== void 0 ? _b : '';
}
break;
default:
availableOptions = options.map((option) => (Object.assign(Object.assign({}, option), { value: option.id })));
}
return {
choice,
options: availableOptions,
featSubChoices,
description,
};
}
// filter Choice options based on modifiers and default subtypes that identify when a user has made a choice from another source. We filter those options out or we append the source data origin name next to the option label
export function getRemainingOptions(options, modifiers, defaultSubtypes, showBackgroundProficiencyOptions, optionValue) {
let remainingSubtypes = [];
@@ -209,3 +330,21 @@ export function getSortedRenderOptions(options, classId, subType, entityRestrict
? orderBy(renderOptions, 'sortOrder')
: orderBy(renderOptions, 'label');
}
//Taken from FeatureChoice.tsx
function getAvailableSubclassChoices(charClass, fetchedSubclasses) {
const existingSubclass = ClassAccessors.getSubclass(charClass);
//if there is an existing subclass that isn't already in the fetched subclasses, append it to the list
if (existingSubclass && !fetchedSubclasses.some((subclass) => subclass.id === existingSubclass.id)) {
return [...fetchedSubclasses, existingSubclass];
}
return fetchedSubclasses;
}
//Taken from FeatureChoice.tsx
function getAvailableFeatChoices(existingFeatId, fetchedFeats, featLookup) {
const existingFeat = existingFeatId ? HelperUtils.lookupDataOrFallback(featLookup, existingFeatId) : null;
// If there is an existing feat that isn't already in the fetched feats, append it to the list
if (existingFeat !== null && !fetchedFeats.some((feat) => FeatAccessors.getId(feat) === existingFeatId)) {
return [...fetchedFeats, existingFeat];
}
return fetchedFeats;
}