New source found from dndbeyond.com

This commit is contained in:
2025-07-02 01:00:11 -07:00
parent 2c657770de
commit b7f7f4a655
24 changed files with 291 additions and 43 deletions
@@ -20,6 +20,7 @@ import {
DefinitionPool,
DefinitionUtils,
EntitledEntity,
EntityRestrictionData,
FeatDefinitionContract,
FeatLookup,
InfusionChoice,
@@ -156,6 +157,7 @@ interface Props {
optionalClassFeatureLookup: OptionalClassFeatureLookup;
theme: CharacterTheme;
inventoryManager: InventoryManager;
entityRestrictionData: EntityRestrictionData;
}
interface State {
showClassFeatures: boolean;
@@ -407,6 +409,7 @@ export default class ClassManager extends React.PureComponent<Props, State> {
optionalClassFeatureLookup,
theme,
activeSourceCategories,
entityRestrictionData,
} = this.props;
return (
@@ -441,6 +444,7 @@ export default class ClassManager extends React.PureComponent<Props, State> {
onChangeReplacementPromise={onChangeReplacementPromise}
onRemoveSelectionPromise={onRemoveSelectionPromise}
ruleData={ruleData}
entityRestrictionData={entityRestrictionData}
/>
),
id: "optional-features",
@@ -49,6 +49,7 @@ import {
InventoryLookup,
ItemUtils,
InventoryManager,
EntityRestrictionData,
} from "@dndbeyond/character-rules-engine/es";
import { HpSummary } from "~/subApps/builder/components/HpSummary";
@@ -125,6 +126,7 @@ interface Props extends DispatchProp {
characterId: number;
activeSourceCategories: Array<number>;
createModal: (modalData: ModalData) => void;
entityRestrictionData: EntityRestrictionData;
}
class ClassesManage extends React.PureComponent<Props> {
@@ -931,6 +933,7 @@ class ClassesManage extends React.PureComponent<Props> {
theme,
activeSourceCategories,
inventoryManager,
entityRestrictionData,
} = this.props;
const levelsRemaining: number = Math.max(
@@ -1010,6 +1013,7 @@ class ClassesManage extends React.PureComponent<Props> {
classSpellListSpellsLookup={classSpellListSpellsLookup}
activeSourceCategories={activeSourceCategories}
inventoryManager={inventoryManager}
entityRestrictionData={entityRestrictionData}
/>
))}
{levelsRemaining !== 0 && (
@@ -1086,6 +1090,8 @@ export default ConnectedBuilderPage(
characterId: rulesEngineSelectors.getId(state),
activeSourceCategories:
rulesEngineSelectors.getActiveSourceCategories(state),
entityRestrictionData:
rulesEngineSelectors.getEntityRestrictionData(state),
};
}
);
@@ -1,10 +1,7 @@
import axios, { Canceler } from "axios";
import React from "react";
import {
MarketplaceCta,
LoadingPlaceholder,
} from "@dndbeyond/character-components/es";
import { LoadingPlaceholder } from "@dndbeyond/character-components/es";
import {
ApiAdapterUtils,
CharClass,
@@ -20,6 +17,7 @@ import {
ClassUtils,
HelperUtils,
RuleData,
EntityRestrictionData,
} from "@dndbeyond/character-rules-engine/es";
import { Link } from "~/components/Link";
@@ -58,6 +56,7 @@ interface OptionalFeatureManagerProps {
reject: () => void
) => void;
ruleData: RuleData;
entityRestrictionData: EntityRestrictionData;
}
interface OptionalFeatureManagerState {
loadingStatus: DataLoadingStatusEnum;
@@ -180,6 +179,7 @@ export class OptionalFeatureManager extends React.PureComponent<
onChangeReplacementPromise,
onRemoveSelectionPromise,
onSelection,
entityRestrictionData,
} = this.props;
let contentNode: React.ReactNode;
@@ -234,13 +234,41 @@ export class OptionalFeatureManager extends React.PureComponent<
const consolidatedOptionalOrigins = [
...availableOptionalFeatures,
...unEntitledOptionalClassFeatures,
].filter(
(feature) =>
!ClassFeatureUtils.getHideInContext(
feature,
Constants.AppContextTypeEnum.BUILDER
)
);
]
.filter(
(feature) =>
!ClassFeatureUtils.getHideInContext(
feature,
Constants.AppContextTypeEnum.BUILDER
)
)
.filter((feature) => {
//check if the feature is already selectec
const optionalFeatureMapping = HelperUtils.lookupDataOrFallback(
optionalClassFeatureLookup,
ClassFeatureUtils.getDefinitionKey(feature)
);
//always return the feature if it is already selected
if (optionalFeatureMapping) {
return true;
}
const sourceIds = ClassFeatureUtils.getSources(feature).map(
(source) => source.sourceId
);
// if there are no sources, we assume it is homebrew
if (sourceIds.length === 0) {
//return true if the user has homebrew content enabled
return entityRestrictionData.preferences.useHomebrewContent;
}
// filter out features that do not have any sources from the active sources lookup
return sourceIds.some((sourceId) =>
entityRestrictionData.activeSourceLookup.hasOwnProperty(sourceId)
);
});
if (!consolidatedOptionalOrigins.length) {
contentNode = this.renderOptionalFeatureCta();
@@ -294,7 +322,7 @@ export class OptionalFeatureManager extends React.PureComponent<
affectedFeatureDefinitionKey={
affectedFeatureDefinitionKey
}
isSelected={!!optionalFeatureMapping ?? false}
isSelected={!!optionalFeatureMapping}
onSelection={onSelection}
onChangeReplacementPromise={onChangeReplacementPromise}
onRemoveSelectionPromise={onRemoveSelectionPromise}
@@ -338,7 +366,7 @@ export class OptionalFeatureManager extends React.PureComponent<
affectedFeatureDefinitionKey={
affectedFeatureDefinitionKey
}
isSelected={!!optionalFeatureMapping ?? false}
isSelected={!!optionalFeatureMapping}
onSelection={onSelection}
onChangeReplacementPromise={onChangeReplacementPromise}
onRemoveSelectionPromise={onRemoveSelectionPromise}
@@ -17,6 +17,7 @@ import {
Constants,
Container,
ContainerUtils,
CoreUtils,
DataOrigin,
DiceUtils,
EntityValueLookup,
@@ -135,6 +136,19 @@ export class ItemDetail extends React.PureComponent<Props> {
}
};
handleReplaceAbilityStatChange = (
enabled: boolean,
statId: Constants.AbilityStatEnum
) => {
const { onCustomDataUpdate } = this.props;
const adjustmentType = CoreUtils.getAdjustmentTypeByStatId(statId);
if (onCustomDataUpdate && adjustmentType) {
onCustomDataUpdate(adjustmentType, enabled);
}
};
handleHexWeaponChange = (enabled) => {
const { onCustomDataUpdate } = this.props;
@@ -319,7 +333,7 @@ export class ItemDetail extends React.PureComponent<Props> {
};
renderClassCustomize = () => {
const { item } = this.props;
const { item, ruleData } = this.props;
const isHexWeapon = ItemUtils.isHexWeapon(item);
const canHexWeapon = ItemUtils.canHexWeapon(item);
@@ -327,8 +341,16 @@ export class ItemDetail extends React.PureComponent<Props> {
const canPactWeapon = ItemUtils.canPactWeapon(item);
const canBeDedicatedWeapon = ItemUtils.canBeDedicatedWeapon(item);
const isDedicatedWeapon = ItemUtils.isDedicatedWeapon(item);
const replacementWeaponStats = ItemUtils.getReplacementWeaponStats(item);
const appliedWeaponReplacementStats =
ItemUtils.getAppliedWeaponReplacementStats(item);
if (!canHexWeapon && !canPactWeapon && !canBeDedicatedWeapon) {
if (
!canHexWeapon &&
!canPactWeapon &&
!canBeDedicatedWeapon &&
replacementWeaponStats.length === 0
) {
return null;
}
@@ -361,6 +383,21 @@ export class ItemDetail extends React.PureComponent<Props> {
/>
</div>
)}
{replacementWeaponStats.map((statId) => (
<div className="ct-item-detail__class-customize-item" key={statId}>
<Checkbox
enabled={appliedWeaponReplacementStats.includes(statId)}
onChange={(enabled) =>
this.handleReplaceAbilityStatChange(enabled, statId)
}
label={`Use ${RuleDataUtils.getStatNameById(
statId,
ruleData,
true
)}`}
/>
</div>
))}
</div>
);
};
@@ -1,4 +1,5 @@
import * as React from "react";
import {
AbilityLookup,
Attack,
@@ -21,15 +22,17 @@ import {
} from "@dndbeyond/dice";
import StarIcon from "@dndbeyond/fontawesome-cache/svgs/solid/star.svg";
import { GameLogContext } from "@dndbeyond/game-log-components";
import { ItemName } from "~/components/ItemName";
import { NumberDisplay } from "~/components/NumberDisplay";
import Tooltip from "~/tools/js/commonComponents/Tooltip";
import Damage from "../../Damage";
import { DigitalDiceWrapper } from "../../Dice";
import { AttackTypeIcon } from "../../Icons";
import NoteComponents from "../../NoteComponents";
import { DiceComponentUtils } from "../../utils";
import CombatAttack from "../CombatAttack";
import { DigitalDiceWrapper } from "../../Dice";
import Damage from "../../Damage";
interface Props {
attack: Attack;
@@ -130,6 +133,7 @@ class CombatItemAttack extends React.PureComponent<Props, State> {
theme,
rollContext,
className,
ruleData,
} = this.props;
const [{ messageTargetOptions, defaultMessageTargetOption, userId }] =
@@ -156,6 +160,8 @@ class CombatItemAttack extends React.PureComponent<Props, State> {
const isPactWeapon = ItemUtils.isPactWeapon(item);
const isDedicatedWeapon = ItemUtils.isDedicatedWeapon(item);
const isLegacy = ItemUtils.isLegacy(item);
const appliedWeaponReplacementStats =
ItemUtils.getAppliedWeaponReplacementStats(item);
let combinedMetaItems: Array<string> = [];
@@ -165,7 +171,12 @@ class CombatItemAttack extends React.PureComponent<Props, State> {
if (type === Constants.WeaponTypeEnum.AMMUNITION) {
combinedMetaItems.push("Ammunition");
} else {
if (isHexWeapon || isPactWeapon || isDedicatedWeapon) {
if (
isHexWeapon ||
isPactWeapon ||
isDedicatedWeapon ||
appliedWeaponReplacementStats.length > 0
) {
if (isHexWeapon) {
combinedMetaItems.push("Hex Weapon");
}
@@ -175,6 +186,13 @@ class CombatItemAttack extends React.PureComponent<Props, State> {
if (isDedicatedWeapon) {
combinedMetaItems.push("Dedicated Weapon");
}
if (appliedWeaponReplacementStats.length > 0) {
appliedWeaponReplacementStats.forEach((statId) => {
combinedMetaItems.push(
`Using ${RuleDataUtils.getStatNameById(statId, ruleData, true)}`
);
});
}
} else {
combinedMetaItems.push(`${attackTypeName} Weapon`);
}