New source found from dndbeyond.com

This commit is contained in:
2026-07-08 01:00:09 -07:00
parent 9a983a6d7b
commit dcefa0d2f2
2865 changed files with 222467 additions and 49053 deletions
@@ -1,11 +1,12 @@
import { all, call, put, select } from 'redux-saga/effects';
import { ItemPlanAccessors } from "../../engine/ItemPlan";
import { characterActions, characterEnvActions, featureFlagInfoActions, ruleDataActions, serviceDataActions, } from '../../actions';
import { ApiRequests } from '../../api';
import * as ApiUtils from '../../api/utils';
import * as ApiAdapterUtils from '../../apiAdapter/utils';
import { ConfigUtils } from '../../config';
import { BackgroundAccessors } from '../../engine/Background';
import { CampaignAccessors, PartyInventorySharingStateEnum } from '../../engine/Campaign';
import { CampaignAccessors } from '../../engine/Campaign';
import { CharacterDerivers } from '../../engine/Character';
import { ChoiceAccessors } from '../../engine/Choice';
import { ClassAccessors, ClassUtils } from '../../engine/Class';
@@ -426,6 +427,16 @@ export function* handlePreferenceChoose(action) {
[key]: value,
});
yield put(callCommitAction(characterActions.preferenceSet, key, value));
// These cases need to happen after the preference is set in state
switch (key) {
case 'longRestType': {
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.getCharacterRestLong);
yield put(serviceDataActions.longRestTextSet(data));
break;
}
default:
//not implemented
}
yield call(handleDataUpdates, data);
}
/**
@@ -557,6 +568,7 @@ export function* handleClassRemoveRequest(action) {
yield call(handleDataUpdates, data);
yield call(autoUpdateInfusions);
yield call(autoUpdateChoices);
yield call(autoUpdateReplicatedItems);
}
/**
*
@@ -858,7 +870,7 @@ export function* handleItemCustomizationsDelete(action) {
action.meta.accept();
}
}
export function* handleContainerDestroy(item, removeContents) {
export function* handleContainerDestroy(item, removeContents, onAccept) {
const itemMappingId = ItemAccessors.getMappingId(item);
const containerLookup = yield select(rulesEngineSelectors.getContainerLookup);
const inventoryLookup = yield select(rulesEngineSelectors.getInventoryLookup);
@@ -920,6 +932,9 @@ export function* handleContainerDestroy(item, removeContents) {
yield put(characterActions.itemRemove(itemMappingId, removeContents));
}
}
if (onAccept) {
onAccept();
}
}
/**
*
@@ -933,7 +948,7 @@ export function* handleItemDestroy(action) {
if (item) {
//if item isContainer, handle removing container and contents
if (ItemAccessors.isContainer(item)) {
yield call(handleContainerDestroy, item, removeContainerContents);
yield call(handleContainerDestroy, item, removeContainerContents, action.meta.accept);
}
else {
//item is not container, just remove it (removeContainerContents should be false for now)
@@ -951,20 +966,11 @@ export function* handleItemDestroy(action) {
//if item isContainer, handle removing container and contents
// TODO IMS: check that this works with shared containers
if (ItemAccessors.isContainer(item)) {
yield call(handleContainerDestroy, item, removeContainerContents);
yield call(handleContainerDestroy, item, removeContainerContents, action.meta.accept);
}
else {
//item is not container, just remove it (removeContainerContents should be false for now)
yield put(serviceDataActions.partyItemRemove(id, false));
const partyInfo = yield select(serviceDataSelectors.getPartyInfo);
if (partyInfo &&
CampaignAccessors.getSharingState(partyInfo) === PartyInventorySharingStateEnum.DELETE_ONLY) {
const partyInventory = CampaignAccessors.getPartyBaseInventoryContracts(partyInfo);
// If all that is left if what we just deleted
if (partyInventory.length === 1 && ItemAccessors.getId(partyInventory[0]) === id) {
yield put(serviceDataActions.partyCampaignInfoSet(Object.assign(Object.assign({}, partyInfo), { sharingState: PartyInventorySharingStateEnum.OFF })));
}
}
}
if (action.meta.accept) {
action.meta.accept();
@@ -1027,6 +1033,7 @@ export function* handleItemMove(action) {
if (!item) {
return;
}
const partyInfo = yield select(serviceDataSelectors.getPartyInfo);
const isCurrentContainerShared = ItemUtils.isShared(item, containerLookup);
const isDestinationContainerShared = ContainerValidators.validateIsShared(DefinitionHacks.hack__generateDefinitionKey(action.payload.containerEntityTypeId, action.payload.containerEntityId), containerLookup);
const isItemContainer = ItemAccessors.isContainer(item);
@@ -1041,12 +1048,30 @@ export function* handleItemMove(action) {
if (isItemContainer) {
const inventoryItems = yield select(rulesEngineSelectors.getPartyInventory);
const itemContainer = HelperUtils.lookupDataOrFallback(containerLookup, DefinitionHacks.hack__generateDefinitionKey(EntityTypeEnum.ITEM, ItemAccessors.getMappingId(item)));
const currentCharacterId = yield select(rulesEngineSelectors.getId);
// If item is container we need to move contents to inventory
if (itemContainer) {
const inventoryInContainer = ContainerUtils.getInventoryItems(itemContainer, inventoryItems);
//loop through items in the container and move contents. If there is an infusion, only move if infusion belongs to the character otherwise leave in party inventory
for (let i = 0; i < inventoryInContainer.length; i++) {
yield put(callCommitAction(characterActions.itemAdd, Object.assign(Object.assign({}, inventoryInContainer[i]), { containerEntityId: ContainerAccessors.getMappingId(itemContainer), containerEntityTypeId: ContainerAccessors.getContainerType(itemContainer), containerDefinitionKey: ContainerAccessors.getDefinitionKey(itemContainer) })));
yield put(callCommitAction(serviceDataActions.partyItemRemove, ItemAccessors.getMappingId(inventoryInContainer[i])));
const infusion = ItemAccessors.getInfusion(inventoryInContainer[i]);
let canMoveInfusion = false;
if (infusion) {
const infusionCharacterId = InfusionAccessors.getCharacterId(infusion);
canMoveInfusion = infusionCharacterId === currentCharacterId;
}
const itemPartyRestrictions = ItemAccessors.getPartyInventoryRestrictions(inventoryInContainer[i]);
const canMoveRestrictedItem = itemPartyRestrictions.length > 0
? itemPartyRestrictions.some((restriction) => restriction.characterId === currentCharacterId)
: true;
if (partyInfo && (!canMoveRestrictedItem || (infusion && !canMoveInfusion))) {
//if we can't move the infusion or replicated item, we need to remove the item from the container so it doesn't get "lost" when we move the container - leave the item in the party equipment container
yield put(callCommitAction(serviceDataActions.partyItemMoveSet, ItemAccessors.getMappingId(inventoryInContainer[i]), CampaignAccessors.getId(partyInfo), ContainerTypeEnum.CAMPAIGN));
}
else {
yield put(callCommitAction(characterActions.itemAdd, Object.assign(Object.assign({}, inventoryInContainer[i]), { containerEntityId: ContainerAccessors.getMappingId(itemContainer), containerEntityTypeId: ContainerAccessors.getContainerType(itemContainer), containerDefinitionKey: ContainerAccessors.getDefinitionKey(itemContainer) })));
yield put(callCommitAction(serviceDataActions.partyItemRemove, ItemAccessors.getMappingId(inventoryInContainer[i])));
}
}
}
}
@@ -1381,6 +1406,19 @@ export function* handleClassFeatureChoiceSetRequest(action) {
}
//remove any spells that are associated with the spell lists from the subclass or feat choices
yield call(apiRemoveSpellsBySpellListIds, spellListIdsToRemove);
//remove any item mappings from feature options that come from item plans (magic item mappings)
if (choiceType === BuilderChoiceTypeEnum.FEATURE_OPTION && oldCharClass) {
const itemPlans = yield select(rulesEngineSelectors.getAvailableItemPlans);
const chosenItemPlan = itemPlans.find((plan) => {
const planChoice = ItemPlanAccessors.getChoice(plan);
return ChoiceAccessors.getId(planChoice) === choiceId;
});
const item = chosenItemPlan ? ItemPlanAccessors.getItem(chosenItemPlan) : null;
if (item) {
// TODO: PARTY - can we send the party inventory update request here instead of from the component?
yield put(characterActions.itemDestroy(ItemAccessors.getMappingId(item), false, action.meta.accept));
}
}
yield call(apiClassFeatureChoiceSet, action);
const classes = yield select(rulesEngineSelectors.getClasses);
const charClass = classes.find((charClass) => !!oldCharClass && ClassAccessors.getId(charClass) === ClassAccessors.getId(oldCharClass));
@@ -1502,6 +1540,7 @@ export function* handleClassLevelSetRequest(action) {
yield call(autoUpdateInfusions);
yield call(autoUpdateChoices);
yield call(autoUpdateClassAlwaysPreparedSpells, [action.payload.classId]);
yield call(autoUpdateReplicatedItems);
}
/**
*
@@ -1731,6 +1770,27 @@ function* autoUpdateChoices(lastChoice = null) {
yield call(autoUpdateBackgroundChoices);
yield call(autoUpdateExpertiseChoices);
}
function* autoUpdateReplicatedItems() {
const itemOriginLookup = yield select(rulesEngineSelectors.getAllInventoryLookupByOrigin);
const itemPlans = yield select(rulesEngineSelectors.getAvailableItemPlans);
const itemMappingIdsToRemove = [];
Object.keys(itemOriginLookup).forEach((originKey) => {
if (!itemPlans.some((itemPlan) => ItemPlanAccessors.getOriginDefinitionKey(itemPlan) === originKey)) {
const item = itemOriginLookup[originKey];
if (item) {
// If the item is not part of any item plan, we can remove it
itemMappingIdsToRemove.push(ItemAccessors.getMappingId(item));
}
}
});
while (itemMappingIdsToRemove.length > 0) {
const itemMappingId = itemMappingIdsToRemove.pop();
if (!itemMappingId) {
continue;
}
yield put(characterActions.itemRemove(itemMappingId));
}
}
/**
*
* @param choice
@@ -2261,3 +2321,40 @@ export function* handlePremadeInfoGet(action) {
yield put(characterActions.premadeInfoSetCommit(responseData));
}
}
export function* handleItemPlanInventoryMappingCreate(action) {
var _a;
const itemDefinitionKey = action.payload.itemDefinitionKey;
const containerDefinitionKey = action.payload.containerDefinitionKey;
const itemPlanOriginDefinitionKey = (_a = action.payload.itemPlanOriginDefinitionKey) !== null && _a !== void 0 ? _a : null;
const quantity = action.payload.quantity;
const itemId = Number(DefinitionUtils.getDefinitionKeyId(itemDefinitionKey));
const itemEntityTypeId = Number(DefinitionUtils.getDefinitionKeyType(itemDefinitionKey));
const containerEntityId = containerDefinitionKey
? Number(DefinitionUtils.getDefinitionKeyId(containerDefinitionKey))
: null;
const containerEntityTypeId = containerDefinitionKey
? Number(DefinitionUtils.getDefinitionKeyType(containerDefinitionKey))
: null;
const originEntityId = itemPlanOriginDefinitionKey
? Number(DefinitionUtils.getDefinitionKeyId(itemPlanOriginDefinitionKey))
: null;
const originEntityTypeId = itemPlanOriginDefinitionKey
? Number(DefinitionUtils.getDefinitionKeyType(itemPlanOriginDefinitionKey))
: null;
if (itemId !== null && itemEntityTypeId !== null) {
let params = {
equipment: [
{
containerEntityId,
containerEntityTypeId,
entityId: itemId,
entityTypeId: itemEntityTypeId,
quantity,
originEntityId,
originEntityTypeId,
},
],
};
yield call(apiItemsCreate, params);
}
}
@@ -57,6 +57,8 @@ const SYNC_ACTION_LOOKUP = {
[types.CUSTOM_ITEM_CREATE]: true,
[types.ITEM_DESTROY]: true,
[types.CUSTOM_ITEM_DESTROY]: true,
//ITEM PLAN
[types.ITEM_PLAN_INVENTORY_MAPPING_CREATE]: true,
//CURRENCY
[types.CURRENCY_TRANSACTION_SET]: true,
//OPTIONAL_FEATURES
@@ -454,6 +456,8 @@ export function* executeHandler(action) {
[types.CUSTOM_ITEM_DESTROY]: sagaHandlers.handleCustomItemDestroy,
[types.ITEM_CHARGES_SET]: sagaHandlers.handleItemChargesSet,
[types.ITEM_MOVE_SET]: sagaHandlers.handleItemMove,
//ITEM PLAN
[types.ITEM_PLAN_INVENTORY_MAPPING_CREATE]: sagaHandlers.handleItemPlanInventoryMappingCreate,
//CURRENCY
[types.CURRENCY_TRANSACTION_SET]: sagaHandlers.handleCurrencyTransactionSet,
//OPTIONAL_FEATURES
@@ -2,11 +2,10 @@ import { call, put, select } from 'redux-saga/effects';
import { characterActions, serviceDataActions } from '../../actions';
import { ApiRequests } from '../../api';
import * as apiShared from '../../api/requests';
import { CampaignAccessors, PartyInventorySharingStateEnum } from '../../engine/Campaign';
import { CampaignAccessors } from '../../engine/Campaign';
import { CreatureAccessors } from '../../engine/Creature';
import { DefinitionAccessors, DefinitionTypeEnum, DefinitionUtils } from '../../engine/Definition';
import { DefinitionPoolUtils } from '../../engine/DefinitionPool';
import { FeatureFlagEnum, FeatureFlagInfoUtils } from '../../engine/FeatureFlagInfo';
import { HelperUtils } from '../../engine/Helper';
import { InfusionAccessors, InfusionTypeEnum } from '../../engine/Infusion';
import { InfusionChoiceAccessors } from '../../engine/InfusionChoice';
@@ -17,7 +16,7 @@ import { VehicleAccessors, VehicleSimulators } from '../../engine/Vehicle';
import { VehicleComponentAccessors } from '../../engine/VehicleComponent';
import { initialCoinState } from '../../reducers/character';
import * as SagaHelpers from '../../sagas/SagaHelpers';
import { characterSelectors, featureFlagInfoSelectors, rulesEngineSelectors, serviceDataSelectors, } from '../../selectors';
import { characterSelectors, rulesEngineSelectors, serviceDataSelectors, } from '../../selectors';
import { TypeScriptUtils } from '../../utils';
import { callCommitAction } from '../../utils/ReduxActionUtils';
import { apiCreatureCreate, apiItemsCreate, handleLoadDefinitions } from '../character/handlers';
@@ -95,6 +94,8 @@ export function* handleInfusionCreate(action) {
entityId: itemId,
entityTypeId: itemEntityTypeId,
quantity: 1,
originEntityId: null,
originEntityTypeId: null,
},
],
});
@@ -165,14 +166,15 @@ export function* handleInfusionDestroy(action) {
//if infused item is a REPLICATE type remove the item from inventory
//otherwise leave the item but un-attune and reset charges
if (infusionType === InfusionTypeEnum.REPLICATE && inventoryMappingId !== null) {
yield put(characterActions.itemRemove(inventoryMappingId, true));
yield put(characterActions.itemDestroy(inventoryMappingId, true, action.meta.accept, action.meta.reject));
// Mapping was removed on the backend due to being an item
// Only call commit action to mirror backend
yield put(callCommitAction(serviceDataActions.infusionMappingRemove, action.payload.infusionId, action.payload.inventoryMappingId));
}
else {
const inventoryLookup = yield select(rulesEngineSelectors.getInventoryLookup);
const foundItem = HelperUtils.lookupDataOrFallback(inventoryLookup, action.payload.inventoryMappingId);
const partyInventoryLookup = yield select(rulesEngineSelectors.getPartyInventoryLookup);
const foundItem = HelperUtils.lookupDataOrFallback(Object.assign(Object.assign({}, inventoryLookup), partyInventoryLookup), action.payload.inventoryMappingId);
if (foundItem) {
if (ItemAccessors.isAttuned(foundItem)) {
yield put(characterActions.itemAttuneSet(inventoryMappingId, false));
@@ -182,6 +184,9 @@ export function* handleInfusionDestroy(action) {
// TODO IMS Infusions come back here and switch this out
yield put(characterActions.itemChargesSet(ItemAccessors.getMappingId(foundItem), 0));
}
if (action.meta.accept) {
action.meta.accept();
}
}
yield put(serviceDataActions.infusionMappingRemove(action.payload.infusionId, action.payload.inventoryMappingId));
}
@@ -244,31 +249,27 @@ export function* handleVehicleRemove(action) {
}
export function* handlePartyInventoryRequest(action) {
var _a, _b, _c, _d, _e, _f;
const featureFlagInfo = yield select(featureFlagInfoSelectors.getFeatureFlagInfo);
const partyFlag = FeatureFlagInfoUtils.getFeatureFlagInfoValue(FeatureFlagEnum.RELEASE_GATE_IMS, featureFlagInfo);
// Party Inventory
if (partyFlag) {
const campaignInfo = yield select(characterSelectors.getCampaign);
if (campaignInfo) {
const data = yield call(SagaHelpers.getApiRequestData, apiShared.getPartyInventory, { campaignId: CampaignAccessors.getId(campaignInfo) });
// This sets the data in the serviceData.partyInfo state
yield put(serviceDataActions.partyCampaignInfoSet(Object.assign(Object.assign({}, campaignInfo), { sharingState: (_a = data === null || data === void 0 ? void 0 : data.sharingState) !== null && _a !== void 0 ? _a : PartyInventorySharingStateEnum.OFF, partyInventory: (_b = data === null || data === void 0 ? void 0 : data.partyItems) !== null && _b !== void 0 ? _b : [], spells: (_c = data === null || data === void 0 ? void 0 : data.spells) !== null && _c !== void 0 ? _c : null, modifiers: (_d = data === null || data === void 0 ? void 0 : data.modifiers) !== null && _d !== void 0 ? _d : null, coin: (_e = data === null || data === void 0 ? void 0 : data.currency) !== null && _e !== void 0 ? _e : initialCoinState })));
yield call(handleUpdatePartyInventoryValues, data);
// Party infusions set into serviceData
if ((_f = data.partyInfusions) === null || _f === void 0 ? void 0 : _f.length) {
let infusionDefinitionIds = new Set();
for (let i = 0; i < data.partyInfusions.length; i++) {
const mapping = data.partyInfusions[i];
yield put(callCommitAction(serviceDataActions.infusionMappingAdd, mapping));
const definitionKey = InfusionAccessors.getDefinitionKey(mapping);
if (definitionKey !== null) {
infusionDefinitionIds.add(DefinitionUtils.getDefinitionKeyId(definitionKey));
}
}
if (infusionDefinitionIds.size > 0) {
yield call(handleLoadDefinitions, DefinitionTypeEnum.INFUSION, Array.from(infusionDefinitionIds));
const campaignInfo = yield select(characterSelectors.getCampaign);
if (campaignInfo) {
const data = yield call(SagaHelpers.getApiRequestData, apiShared.getPartyInventory, { campaignId: CampaignAccessors.getId(campaignInfo) });
// This sets the data in the serviceData.partyInfo state
yield put(serviceDataActions.partyCampaignInfoSet(Object.assign(Object.assign({}, campaignInfo), { partyInventory: (_a = data === null || data === void 0 ? void 0 : data.partyItems) !== null && _a !== void 0 ? _a : [], spells: (_b = data === null || data === void 0 ? void 0 : data.spells) !== null && _b !== void 0 ? _b : null, modifiers: (_c = data === null || data === void 0 ? void 0 : data.modifiers) !== null && _c !== void 0 ? _c : null, coin: (_d = data === null || data === void 0 ? void 0 : data.currency) !== null && _d !== void 0 ? _d : initialCoinState, partyRestrictions: (_e = data === null || data === void 0 ? void 0 : data.partyRestrictions) !== null && _e !== void 0 ? _e : [] })));
yield call(handleUpdatePartyInventoryValues, data);
// Party infusions set into serviceData
if ((_f = data.partyInfusions) === null || _f === void 0 ? void 0 : _f.length) {
let infusionDefinitionIds = new Set();
for (let i = 0; i < data.partyInfusions.length; i++) {
const mapping = data.partyInfusions[i];
yield put(callCommitAction(serviceDataActions.infusionMappingAdd, mapping));
const definitionKey = InfusionAccessors.getDefinitionKey(mapping);
if (definitionKey !== null) {
infusionDefinitionIds.add(DefinitionUtils.getDefinitionKeyId(definitionKey));
}
}
if (infusionDefinitionIds.size > 0) {
yield call(handleLoadDefinitions, DefinitionTypeEnum.INFUSION, Array.from(infusionDefinitionIds));
}
}
}
}