2025-05-28 15:36:51 -07:00

2213 lines
102 KiB
JavaScript

import { all, call, put, select } from 'redux-saga/effects';
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 { CharacterDerivers } from '../../engine/Character';
import { ChoiceAccessors } from '../../engine/Choice';
import { ClassAccessors, ClassUtils } from '../../engine/Class';
import { ClassFeatureAccessors, ClassFeatureValidators } from '../../engine/ClassFeature';
import { ContainerAccessors, ContainerTypeEnum, ContainerUtils, ContainerValidators, } from '../../engine/Container';
import { AbilityScoreStatTypeEnum, AbilityScoreTypeEnum, BuilderChoiceSubtypeEnum, BuilderChoiceTypeEnum, EntityTypeEnum, PreferenceProgressionTypeEnum, StartingEquipmentTypeEnum, } from '../../engine/Core';
import { CreatureAccessors } from '../../engine/Creature';
import { DefinitionHacks, DefinitionTypeEnum, DefinitionUtils } from '../../engine/Definition';
import { hack__generateDefinitionKey } from '../../engine/Definition/hacks';
import { FeatAccessors } from '../../engine/Feat';
import { FeatureFlagEnum } from '../../engine/FeatureFlagInfo';
import { HelperUtils } from '../../engine/Helper';
import { InfusionAccessors } from '../../engine/Infusion';
import { InfusionChoiceAccessors, InfusionChoiceValidators } from '../../engine/InfusionChoice';
import { ItemAccessors, ItemDerivers, ItemUtils } from '../../engine/Item';
import { KnownInfusionAccessors } from '../../engine/KnownInfusion';
import { ModifierAccessors } from '../../engine/Modifier';
import { OptionalClassFeatureAccessors, OptionalClassFeatureSimulators, OptionalClassFeatureUtils, } from '../../engine/OptionalClassFeature';
import { OptionalOriginAccessors, OptionalOriginSimulators, OptionalOriginUtils } from '../../engine/OptionalOrigin';
import { RaceAccessors, RaceUtils } from '../../engine/Race';
import { RacialTraitAccessors } from '../../engine/RacialTrait';
import { RuleDataAccessors, RuleDataTypeEnum } from '../../engine/RuleData';
import { SpellAccessors, SpellDerivers } from '../../engine/Spell';
import { AdjustmentTypeEnum, ValueHacks } from '../../engine/Value';
import { VehicleAccessors } from '../../engine/Vehicle';
import * as NotificationUtils from '../../notification/utils';
import { CharacterLoadingStatusEnum } from '../../reducers/constants';
import * as SagaHelpers from '../../sagas/SagaHelpers';
import { characterSelectors, serviceDataSelectors } from '../../selectors';
import * as rulesEngineSelectors from '../../selectors/composite/engine';
import { TypeScriptUtils } from '../../utils';
import { callCommitAction } from '../../utils/ReduxActionUtils';
import { serviceDataSagaHandlers } from '../serviceData';
import { hack__handleLoadDefinitions, hack__simulateOwnedClassFeatureDefinitionData, hack__simulateOwnedDefinitionData, hack__simulateOwnedRacialTraitDefinitionData, } from './hacks';
//TODO - could make an autoUpdate func that runs all update handlers with optional data - we do these combos in a few places so could be useful
//TODO TCoE - pull out api handlers into own file
/**
*
* @param definitionType
* @param definitionIds
*/
export function* handleLoadDefinitions(definitionType, definitionIds) {
try {
// make the requests from the definition ids
const serviceResponse = yield call(ApiUtils.makeGetIdsDefinitionTypeRequest(definitionType), {
ids: definitionIds,
});
let responseData = ApiAdapterUtils.getResponseData(serviceResponse);
if (responseData !== null) {
// compile all the definition and accessType responses
yield put(serviceDataActions.definitionPoolAdd(responseData.definitionData, responseData.accessTypes));
}
}
catch (error) { }
}
/**
*
*/
export function* handleLoadAlwaysKnownSpells() {
const background = yield select(rulesEngineSelectors.getBackgroundInfo);
let backgroundId;
if (background !== null && !background.hasCustomBackground) {
backgroundId = BackgroundAccessors.getId(background);
}
const classes = yield select(rulesEngineSelectors.getClasses);
const alwaysPreparedClasses = classes.filter((charClass) => ClassAccessors.getKnowsAllSpells(charClass));
const requests = alwaysPreparedClasses.map((charClass) => () => ApiRequests.getCharacterGameDataAlwaysKnownSpells({
params: {
classId: ClassAccessors.getActiveId(charClass),
classLevel: ClassAccessors.getLevel(charClass),
backgroundId,
},
}));
const classAlwaysKnownSpellResponses = yield all(requests.map(call));
for (let i = 0; i < classAlwaysKnownSpellResponses.length; i++) {
const response = classAlwaysKnownSpellResponses[i];
const data = ApiAdapterUtils.getResponseData(response);
const charClass = alwaysPreparedClasses[i];
if (data !== null && charClass) {
yield put(callCommitAction(serviceDataActions.classAlwaysKnownSpellsSet, data, ClassAccessors.getActiveId(charClass)));
}
}
}
/**
*
*/
export function* handleLoadCharacterLazyData() {
yield call(handleLoadAlwaysKnownSpells);
}
/**
*
* @param action
*/
export function* handleCharacterLoad(action) {
yield put(characterEnvActions.dataSet({
loadingStatus: CharacterLoadingStatusEnum.LOADING,
}));
const flags = Object.values(FeatureFlagEnum);
const [characterResponse, configDataResponse, featureFlagInfoResponse, vehicleConfigDataResponse] = yield all([
call(ApiUtils.makeGetCharacterRequest(action.payload.characterId)),
call(ApiRequests.getCharacterRuleData, {
params: action.payload.requestParams,
}),
call(ApiRequests.getFeatureFlag, { flags }),
call(ApiRequests.getGameDataRuleDataVehicle, {
removeDefaultParams: true,
params: action.payload.requestParams,
}),
]);
// get the response data
const characterJson = ApiAdapterUtils.getResponseData(characterResponse);
if (characterJson !== null) {
yield put(characterActions.characterSet(characterJson));
}
// get the rule data response data put it into the state
const configDataJson = ApiAdapterUtils.getResponseData(configDataResponse);
if (configDataJson !== null) {
yield put(ruleDataActions.dataSet(configDataJson));
}
// get the feature flag info response data put it into the state
const featureFlagInfoJson = ApiAdapterUtils.getResponseData(featureFlagInfoResponse);
if (featureFlagInfoJson !== null) {
yield put(featureFlagInfoActions.dataSet(featureFlagInfoJson));
}
yield put(serviceDataActions.partyInventoryRequest());
// get the vehicle rule data response data put it into the state
const vehicleConfigDataSourceData = ApiAdapterUtils.getResponseData(vehicleConfigDataResponse);
if (vehicleConfigDataSourceData !== null) {
yield put(callCommitAction(serviceDataActions.ruleDataPoolKeySet, RuleDataTypeEnum.VEHICLE, vehicleConfigDataSourceData));
}
const [vehicleMappingsResponse, componentMappingsResponse, infusionsKnownResponse, infusionResponse] = yield all([
call(ApiRequests.getCharacterVehicles),
call(ApiRequests.getCharacterVehicleComponents),
call(ApiRequests.getCharacterKnownInfusions),
call(ApiRequests.getCharacterInfusionItems),
]);
const vehicleMappings = ApiAdapterUtils.getResponseData(vehicleMappingsResponse);
const componentMappings = ApiAdapterUtils.getResponseData(componentMappingsResponse);
const infusionMappings = ApiAdapterUtils.getResponseData(infusionResponse);
const knownInfusionMappings = ApiAdapterUtils.getResponseData(infusionsKnownResponse);
// get all infusion definitions
let infusionDefinitionIds = new Set();
if (infusionMappings !== null && infusionMappings.length) {
for (let i = 0; i < infusionMappings.length; i++) {
const mapping = infusionMappings[i];
yield put(callCommitAction(serviceDataActions.infusionMappingAdd, mapping));
const definitionKey = InfusionAccessors.getDefinitionKey(mapping);
if (definitionKey !== null) {
infusionDefinitionIds.add(DefinitionUtils.getDefinitionKeyId(definitionKey));
}
}
}
if (knownInfusionMappings !== null && knownInfusionMappings.length) {
for (let i = 0; i < knownInfusionMappings.length; i++) {
const mapping = knownInfusionMappings[i];
yield put(callCommitAction(serviceDataActions.knownInfusionMappingAdd, mapping));
const definitionKey = KnownInfusionAccessors.getDefinitionKey(mapping);
if (definitionKey !== null) {
infusionDefinitionIds.add(DefinitionUtils.getDefinitionKeyId(definitionKey));
}
}
}
if (infusionDefinitionIds.size > 0) {
yield call(handleLoadDefinitions, DefinitionTypeEnum.INFUSION, Array.from(infusionDefinitionIds));
}
// get vehicle definitions
const definitionIds = new Set();
if (vehicleMappings !== null && vehicleMappings.length) {
for (let i = 0; i < vehicleMappings.length; i++) {
const mapping = vehicleMappings[i];
yield put(callCommitAction(serviceDataActions.vehicleMappingAdd, mapping));
const definitionKey = VehicleAccessors.getDefinitionKey(mapping);
if (definitionKey !== null) {
definitionIds.add(DefinitionUtils.getDefinitionKeyId(definitionKey));
}
}
yield call(handleLoadDefinitions, DefinitionTypeEnum.VEHICLE, Array.from(definitionIds));
}
if (componentMappings !== null && componentMappings.length) {
for (let i = 0; i < componentMappings.length; i++) {
yield put(callCommitAction(serviceDataActions.vehicleComponentMappingAdd, componentMappings[i]));
}
}
//simulate game-data responses for granted class features and racial traits and add to definitionPool
yield call(hack__simulateOwnedDefinitionData);
//get optional class feature definitions
const optionalClassFeatureMappings = yield select(characterSelectors.getOptionalClassFeatures);
const optionalClassFeatureDefinitionIds = new Set();
optionalClassFeatureMappings === null || optionalClassFeatureMappings === void 0 ? void 0 : optionalClassFeatureMappings.forEach((mapping) => {
const definitionKey = OptionalClassFeatureAccessors.getDefinitionKey(mapping);
if (definitionKey !== null) {
const id = DefinitionHacks.hack__getDefinitionKeyId(definitionKey);
if (id !== null) {
optionalClassFeatureDefinitionIds.add(id);
}
}
});
if (optionalClassFeatureDefinitionIds.size) {
yield call(hack__handleLoadDefinitions, DefinitionTypeEnum.CLASS_FEATURE, Array.from(optionalClassFeatureDefinitionIds));
}
//get optional origin definitions
const optionalOriginsMappings = yield select(characterSelectors.getOptionalOrigins);
const optionalOriginDefinitionIds = new Set();
optionalOriginsMappings === null || optionalOriginsMappings === void 0 ? void 0 : optionalOriginsMappings.forEach((mapping) => {
const definitionKey = OptionalOriginAccessors.getDefinitionKey(mapping);
if (definitionKey !== null) {
const id = DefinitionHacks.hack__getDefinitionKeyId(definitionKey);
if (id !== null) {
optionalOriginDefinitionIds.add(id);
}
}
});
if (optionalOriginDefinitionIds.size) {
yield call(hack__handleLoadDefinitions, DefinitionTypeEnum.RACIAL_TRAIT, Array.from(optionalOriginDefinitionIds));
}
yield call(autoUpdateClassAlwaysPreparedSpells);
if (action.payload.config.includeAlwaysKnownSpells) {
yield call(handleLoadAlwaysKnownSpells);
}
yield call(handleLoadCharacterLazyData);
// Get all available campaign settings
const isCampaignSettingsActive = featureFlagInfoJson === null || featureFlagInfoJson === void 0 ? void 0 : featureFlagInfoJson[FeatureFlagEnum.RELEASE_GATE_CAMPAIGN_SETTINGS];
if (isCampaignSettingsActive) {
const allCampaignSettingsResponse = yield call(ApiRequests.getAllCampaignSettings);
const allCampaignSettings = ApiAdapterUtils.getResponseData(allCampaignSettingsResponse);
yield put(callCommitAction(serviceDataActions.campaignSettingsSet, allCampaignSettings));
}
yield put(characterEnvActions.dataSet({
loadingStatus: CharacterLoadingStatusEnum.LOADED,
}));
}
/**
*
*/
export function* autoUpdateClassAlwaysPreparedSpells(classIdLimiters = []) {
const classes = yield select(rulesEngineSelectors.getClasses);
const alwaysPreparedClasses = classes
.filter((charClass) => classIdLimiters.length ? classIdLimiters.includes(ClassAccessors.getActiveId(charClass)) : true)
.filter((charClass) => ClassAccessors.getKnowsAllSpells(charClass));
const requests = alwaysPreparedClasses.map((charClass) => () => ApiRequests.getCharacterGameDataAlwaysPreparedSpells({
params: {
classId: ClassAccessors.getActiveId(charClass),
classLevel: ClassAccessors.getLevel(charClass),
},
}));
const classAlwaysPreparedSpellResponses = yield all(requests.map(call));
for (let i = 0; i < classAlwaysPreparedSpellResponses.length; i++) {
const response = classAlwaysPreparedSpellResponses[i];
const data = ApiAdapterUtils.getResponseData(response);
const charClass = alwaysPreparedClasses[i];
if (data !== null && charClass) {
yield put(callCommitAction(serviceDataActions.classAlwaysPreparedSpellsSet, data, ClassAccessors.getActiveId(charClass)));
}
}
}
/**
*
* @param spellListIds
*/
export function* apiRemoveSpellsBySpellListIds(spellListIds) {
var _a;
if (!spellListIds.length) {
return;
}
yield call(SagaHelpers.getApiRequestData, ApiRequests.deleteCharacterSpellRemoveBySpellListIds, {
spellListIds,
});
const spellListIdLookup = spellListIds.reduce((acc, id) => {
acc[id] = true;
return acc;
}, {});
//TODO use generateClassSpellListSpellsLookup
const classes = yield select(rulesEngineSelectors.getClasses);
for (let i = 0; i < classes.length; i++) {
const charClass = classes[i];
const classMappingId = ClassAccessors.getMappingId(charClass);
const spells = ClassAccessors.getSpells(charClass);
for (let j = 0; j < spells.length; j++) {
const spell = spells[j];
if (HelperUtils.lookupDataOrFallback(spellListIdLookup, (_a = SpellAccessors.getSpellListId(spell)) !== null && _a !== void 0 ? _a : -1, false)) {
yield put(callCommitAction(characterActions.spellRemove, spell, classMappingId));
}
}
}
}
/**
*
* @param action
*/
export function* handleConditionAdd(action) {
const hitPointInfo = yield select(rulesEngineSelectors.getHitPointInfo);
const totalHp = hitPointInfo.totalHp;
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.putCharacterCondition, Object.assign(Object.assign({}, action.payload), { totalHp }));
yield call(handleDataUpdates, data);
}
/**
*
* @param action
*/
export function* handleConditionSet(action) {
const hitPointInfo = yield select(rulesEngineSelectors.getHitPointInfo);
const totalHp = hitPointInfo.totalHp;
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.putCharacterCondition, Object.assign(Object.assign({}, action.payload), { totalHp }));
yield call(handleDataUpdates, data);
}
/**
*
* @param action
*/
export function* handleConditionRemove(action) {
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.deleteCharacterCondition, action.payload);
yield call(handleDataUpdates, data);
}
/**
*
* @param action
*/
export function* handleRestoreLife(action) {
const { restoreType } = action.payload;
const hitPointInfo = yield select(rulesEngineSelectors.getHitPointInfo);
const totalHp = hitPointInfo.totalHp;
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.putCharacterLifeRestore, { totalHp, restoreType });
yield call(handleDataUpdates, data);
}
/**
*
* @param action
*/
export function* handleAdhocFeatCreate(action) {
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.postCharacterFeatAdHoc, action.payload);
yield call(handleDataUpdates, data);
}
/**
*
* @param action
*/
export function* handleAdhocFeatRemove(action) {
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.deleteCharacterFeatAdHoc, action.payload);
yield call(handleDataUpdates, data);
}
/**
*
* @param action
*/
export function* handleSetEntityFeat(action) {
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.postEntityFeat, action.payload);
yield call(handleDataUpdates, data);
}
/**
*
* @param action
*/
export function* handleSendSocialImageData(action) {
const socialImageData = yield select(rulesEngineSelectors.getSocialImageData);
const isSheetReady = yield select(rulesEngineSelectors.isCharacterSheetReady);
if (isSheetReady) {
yield call(SagaHelpers.getApiRequestData, ApiRequests.putCharacterDecorationSocialImage, socialImageData);
}
}
/**
*
* @param action
*/
export function* handleAbilityScoreSet(action) {
let { statId, type, value } = action.payload;
let requestParams = {
statId,
type,
value,
};
yield call(SagaHelpers.getApiRequestData, ApiRequests.putCharacterAbilityScore, requestParams);
switch (action.payload.type) {
case AbilityScoreStatTypeEnum.OVERRIDE:
yield put(callCommitAction(characterActions.abilityScoreOverrideSet, statId, value));
break;
case AbilityScoreStatTypeEnum.BONUS:
yield put(callCommitAction(characterActions.abilityScoreBonusSet, statId, value));
break;
case AbilityScoreStatTypeEnum.BASE:
yield put(callCommitAction(characterActions.abilityScoreBaseSet, statId, value));
break;
default:
// not implemented
}
}
/**
*
* @param action
*/
export function* handlePreferenceChoose(action) {
const { key, value } = action.payload;
switch (key) {
case 'enableOptionalClassFeatures': {
const classes = yield select(rulesEngineSelectors.getClasses);
if (typeof value === 'boolean') {
const spellListIdsToRemove = ClassUtils.getUpdateEnableOptionalClassFeaturesSpellListIdsToRemove(classes, value);
yield call(apiRemoveSpellsBySpellListIds, spellListIdsToRemove);
}
break;
}
case 'enableOptionalOrigins': {
const race = yield select(rulesEngineSelectors.getRace);
if (race && typeof value === 'boolean') {
const spellListIdsToRemove = RaceUtils.getUpdateEnableOptionalOriginsSpellListIdsToRemove(race, value);
yield call(apiRemoveSpellsBySpellListIds, spellListIdsToRemove);
}
break;
}
default:
//not implemented
}
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.putCharacterPreferences, {
[key]: value,
});
yield put(callCommitAction(characterActions.preferenceSet, key, value));
yield call(handleDataUpdates, data);
}
/**
*
* @param action
*/
export function* handlePortraitUpload(action) {
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.postCharacterDecorationPortraitCustom, action.payload);
const { avatarId, avatarUrl } = data;
yield put(callCommitAction(characterActions.portraitSet, avatarId, avatarUrl));
}
/**
*
* @param action
*/
export function* handleRandomNameRequest(action) {
const race = yield select(rulesEngineSelectors.getRace);
let reqParams = {};
if (race !== null) {
reqParams = {
raceDefinitionKey: RaceAccessors.getDefinitionKey(race),
};
}
const name = yield call(SagaHelpers.getApiRequestData, ApiRequests.getCharacterNameRandom, {
params: reqParams,
});
yield put(characterActions.nameSet(name));
}
/**
*
* @param action
*/
export function* handleRaceChoose(action) {
const { race } = action.payload;
const existingRace = yield select(rulesEngineSelectors.getRace);
if (existingRace !== null) {
yield call(apiRemoveSpellsBySpellListIds, RaceAccessors.getSpellListIds(existingRace));
const existingOptionalOrigins = yield select(rulesEngineSelectors.getOptionalOrigins);
const optionalOriginsIdsToRemove = existingOptionalOrigins.map(OptionalOriginAccessors.getRacialTraitId);
yield call(apiRemoveOptionalOriginsCollection, {
racialTraitIds: optionalOriginsIdsToRemove,
});
}
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.putCharacterRace, {
entityRaceId: RaceAccessors.getEntityRaceId(race),
entityRaceTypeId: RaceAccessors.getEntityRaceTypeId(race),
});
yield call(hack__simulateOwnedRacialTraitDefinitionData, race);
yield call(handleDataUpdates, data);
yield call(autoUpdateChoices);
}
/**
*
* @param apiPayload
*/
export function* apiRemoveOptionalOriginsCollection(apiPayload) {
if (!apiPayload.racialTraitIds.length) {
return;
}
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.deleteCharacterOptionalFeatureOriginCollection, apiPayload);
for (let i = 0; i < apiPayload.racialTraitIds.length; i++) {
yield put(callCommitAction(characterActions.optionalOriginRemove, apiPayload.racialTraitIds[i]));
}
return data;
}
/**
*
* @param action
*/
export function* handleClassAddRequest(action) {
const { charClass, level } = action.payload;
const { id } = charClass;
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.postCharacterClass, {
classId: id,
level,
});
const preferences = yield select(rulesEngineSelectors.getCharacterPreferences);
const currentLevel = yield select(rulesEngineSelectors.getCurrentLevel);
const totalClassLevel = yield select(rulesEngineSelectors.getTotalClassLevel);
if (preferences.progressionType === PreferenceProgressionTypeEnum.XP && totalClassLevel > currentLevel) {
const ruleData = yield select(rulesEngineSelectors.getRuleData);
yield put(characterActions.xpSet(CharacterDerivers.deriveCurrentLevelXp(totalClassLevel, ruleData)));
}
yield call(handleDataUpdates, data);
yield call(autoUpdateChoices);
yield call(hack__simulateOwnedClassFeatureDefinitionData);
}
/**
*
* @param action
*/
export function* handleClassRemoveRequest(action) {
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.deleteCharacterClass, action.payload);
const baseClassLookup = yield select(rulesEngineSelectors.getBaseClassLookup);
const charClassToRemove = HelperUtils.lookupDataOrFallback(baseClassLookup, action.payload.characterClassId);
const optionalClassFeatures = yield select(rulesEngineSelectors.getOptionalClassFeatures);
const optionalFeatureDefinitionKeys = new Set();
optionalClassFeatures.forEach((feature) => {
if (charClassToRemove === null) {
return;
}
const definitionKey = OptionalClassFeatureAccessors.getDefinitionKey(feature);
if (definitionKey === null) {
return;
}
const classFeature = OptionalClassFeatureAccessors.getClassFeature(feature);
if (classFeature === null) {
return;
}
if (ClassFeatureValidators.isValidClassClassFeature(charClassToRemove, classFeature)) {
optionalFeatureDefinitionKeys.add(definitionKey);
}
});
const idsToRemove = Array.from(optionalFeatureDefinitionKeys)
.map(DefinitionHacks.hack__getDefinitionKeyId)
.filter(TypeScriptUtils.isNotNullOrUndefined);
yield call(apiRemoveOptionalClassFeatureCollection, {
classFeatureIds: idsToRemove,
});
if (action.payload.newCharacterXp !== null) {
yield put(characterActions.xpSet(action.payload.newCharacterXp));
}
yield call(handleDataUpdates, data);
yield call(autoUpdateInfusions);
yield call(autoUpdateChoices);
}
/**
*
* @param apiPayload
*/
export function* apiRemoveOptionalClassFeatureCollection(apiPayload) {
if (!apiPayload.classFeatureIds.length) {
return;
}
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.deleteCharacterOptionalFeatureClassFeatureCollection, apiPayload);
for (let i = 0; i < apiPayload.classFeatureIds.length; i++) {
yield put(callCommitAction(characterActions.optionalClassFeatureRemove, apiPayload.classFeatureIds[i]));
}
return data;
}
/**
*
* @param action
*/
export function* handleOptionalClassFeatureCreate(action) {
const data = yield call(apiOptionalClassFeatureCreate, action.payload);
//may need autoUpdateInfusions at some point
yield call(handleDataUpdates, data);
yield call(autoUpdateChoices);
}
/**
*
* @param apiPayload
*/
export function* apiOptionalClassFeatureCreate(apiPayload) {
const optionalClassFeatureResponseData = yield call(SagaHelpers.getApiRequestData, ApiRequests.postCharacterOptionalFeatureClassFeature, apiPayload);
const mappingContract = OptionalClassFeatureSimulators.simulateOptionalClassFeatureContract(apiPayload.classFeatureId, apiPayload.affectedClassFeatureId);
yield put(callCommitAction(characterActions.optionalClassFeatureAdd, mappingContract));
return optionalClassFeatureResponseData;
}
/**
*
* @param action
*/
export function* handleOptionalClassFeatureSetRequest(action) {
const optionalClassFeatures = yield select(rulesEngineSelectors.getOptionalClassFeatures);
const optionalFeature = optionalClassFeatures.find((feature) => OptionalClassFeatureAccessors.getClassFeatureId(feature) === action.payload.classFeatureId);
if (!optionalFeature) {
return;
}
const spellListIds = OptionalClassFeatureUtils.getUpdateMappingSpellListIdsToRemove(optionalFeature, action.payload);
yield call(apiRemoveSpellsBySpellListIds, spellListIds);
const data = yield call(apiOptionalClassFeatureSet, action.payload);
//may need autoUpdateInfusions at some point
yield call(handleDataUpdates, data);
yield call(autoUpdateChoices);
}
/**
*
* @param apiPayload
*/
export function* apiOptionalClassFeatureSet(apiPayload) {
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.putCharacterOptionalFeatureClassFeature, apiPayload);
const newMappingContract = OptionalClassFeatureSimulators.simulateOptionalClassFeatureContract(apiPayload.classFeatureId, apiPayload.affectedClassFeatureId);
yield put(callCommitAction(characterActions.optionalClassFeatureSet, Object.assign({}, newMappingContract)));
return data;
}
/**
*
* @param action
*/
export function* handleOptionalClassFeatureDestroy(action) {
const optionalClassFeatures = yield select(rulesEngineSelectors.getOptionalClassFeatures);
const optionalFeature = optionalClassFeatures.find((feature) => OptionalClassFeatureAccessors.getClassFeatureId(feature) === action.payload.classFeatureId);
if (!optionalFeature) {
return;
}
const spellListIds = OptionalClassFeatureUtils.getRemoveMappingSpellListIds(optionalFeature);
yield call(apiRemoveSpellsBySpellListIds, spellListIds);
const data = yield call(apiOptionalClassFeatureDestroy, action.payload);
//may need autoUpdateInfusions at some point
yield call(handleDataUpdates, data);
yield call(autoUpdateChoices);
}
/**
*
* @param apiPayload
*/
export function* apiOptionalClassFeatureDestroy(apiPayload) {
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.deleteCharacterOptionalFeatureClassFeature, apiPayload);
yield put(callCommitAction(characterActions.optionalClassFeatureRemove, apiPayload.classFeatureId));
return data;
}
/**
*
* @param action
*/
export function* handleOptionalOriginCreate(action) {
const data = yield call(apiOptionalOriginCreate, action.payload);
yield call(handleDataUpdates, data);
yield call(autoUpdateChoices);
}
/**
*
* @param apiPayload
*/
export function* apiOptionalOriginCreate(apiPayload) {
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.postCharacterOptionalFeatureOrigin, apiPayload);
const mappingContract = OptionalOriginSimulators.simulateOptionalOriginContract(apiPayload.racialTraitId, apiPayload.affectedRacialTraitId);
yield put(callCommitAction(characterActions.optionalOriginAdd, mappingContract));
return data;
}
/**
*
* @param action
*/
export function* handleOptionalOriginSetRequest(action) {
const optionalOrigins = yield select(rulesEngineSelectors.getOptionalOrigins);
const optionalOrigin = optionalOrigins.find((origin) => OptionalOriginAccessors.getRacialTraitId(origin) === action.payload.racialTraitId);
if (!optionalOrigin) {
return;
}
const spellListIds = OptionalOriginUtils.getUpdateMappingSpellListIdsToRemove(optionalOrigin, action.payload);
yield call(apiRemoveSpellsBySpellListIds, spellListIds);
const data = yield call(apiOptionalOriginSet, action.payload);
yield call(handleDataUpdates, data);
yield call(autoUpdateChoices);
}
/**
*
* @param apiPayload
*/
export function* apiOptionalOriginSet(apiPayload) {
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.putCharacterOptionalFeatureOrigin, apiPayload);
const newMappingContract = OptionalOriginSimulators.simulateOptionalOriginContract(apiPayload.racialTraitId, apiPayload.affectedRacialTraitId);
yield put(callCommitAction(characterActions.optionalOriginSet, Object.assign({}, newMappingContract)));
return data;
}
/**
*
* @param action
*/
export function* handleOptionalOriginDestroy(action) {
const optionalOrigins = yield select(rulesEngineSelectors.getOptionalOrigins);
const optionalOrigin = optionalOrigins.find((origin) => OptionalOriginAccessors.getRacialTraitId(origin) === action.payload.racialTraitId);
if (!optionalOrigin) {
return;
}
const spellListIds = OptionalOriginUtils.getRemoveMappingSpellListIds(optionalOrigin);
yield call(apiRemoveSpellsBySpellListIds, spellListIds);
const data = yield call(apiOptionalOriginDestroy, action.payload);
yield call(handleDataUpdates, data);
yield call(autoUpdateChoices);
}
/**
*
* @param action
*/
export function* handleCampaignSettingSetRequest(action) {
const CampaignSetting = yield select(rulesEngineSelectors.getCampaignSetting);
if (!CampaignSetting) {
return;
}
const data = yield call(apiCampaignSettingSet, action.payload);
yield call(handleDataUpdates, data);
yield call(autoUpdateChoices);
}
/**
*
* @param apiPayload
*/
export function* apiCampaignSettingSet(apiPayload) {
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.putCharacterCampaignSetting, apiPayload);
yield put(callCommitAction(characterActions.campaignSettingSet, Object.assign({}, apiPayload)));
return data;
}
/**
*
* @param apiPayload
*/
export function* apiOptionalOriginDestroy(apiPayload) {
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.deleteCharacterOptionalFeatureOrigin, apiPayload);
yield put(callCommitAction(characterActions.optionalOriginRemove, apiPayload.racialTraitId));
return data;
}
/**
*
* @param action
*/
export function* handleCustomItemCreate(action) {
//TODO v5.1: remove when mobile moves up - keep the if logic - remove everything in the else
const containerLookup = yield select(rulesEngineSelectors.getContainerLookup);
const { includeCustomItems } = ConfigUtils.getCurrentRulesEngineConfig();
const { quantity, description, containerDefinitionKey, cost, name, notes, weight, partyId } = action.payload;
const isSharedContainer = containerDefinitionKey
? ContainerValidators.validateIsShared(containerDefinitionKey, containerLookup)
: false;
if (includeCustomItems) {
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.postCharacterCustomItem, {
quantity,
description,
cost,
name,
notes,
weight,
containerEntityId: containerDefinitionKey
? DefinitionHacks.hack__getDefinitionKeyId(containerDefinitionKey)
: null,
containerEntityTypeId: containerDefinitionKey
? DefinitionHacks.hack__getDefinitionKeyType(containerDefinitionKey)
: null,
partyId: isSharedContainer ? partyId : null,
});
//put data for item/notes and customItem
if (data.addItems) {
if (notes) {
yield put(callCommitAction(characterActions.valueSet, AdjustmentTypeEnum.NOTES, notes, null, ValueHacks.hack__toString(ItemAccessors.getMappingId(data.addItems[0])), ValueHacks.hack__toString(ItemAccessors.getMappingEntityTypeId(data.addItems[0])), null, null, partyId));
}
const customItem = ItemDerivers.deriveOriginalCustomContract(data.addItems[0], notes);
yield put(callCommitAction(characterActions.customItemAdd, customItem));
yield call(handleCommitAddItems, data.addItems);
}
if (action.meta.accept) {
action.meta.accept();
}
yield call(handleDataUpdates, data);
}
else {
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.postCharacterCustomItemV5, action.payload);
yield put(callCommitAction(characterActions.customItemAdd, data));
}
}
/**
*
* @param action
*/
export function* handleCustomItemDestroy(action) {
//TODO v5.1: remove when mobile moves up
//no longer need useCustomItems flag
//keep the if logic - remove everything in the else
const { includeCustomItems } = ConfigUtils.getCurrentRulesEngineConfig();
const { id, partyId, mappingId } = action.payload;
if (includeCustomItems) {
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.deleteCharacterCustomItem, action.payload);
//if partyId is passed in, the item is shared
if (!partyId) {
// remove Item and CustomItem from character
yield put(callCommitAction(characterActions.itemRemove, mappingId));
yield put(callCommitAction(characterActions.customItemRemove, id));
}
else {
// remove Item from party
yield put(callCommitAction(serviceDataActions.partyItemRemove, mappingId));
}
}
else {
//Calls the V5 version and commits to character custom items
yield put(characterActions.customItemRemove(id));
}
if (action.meta.accept) {
action.meta.accept();
}
}
export function* handleCustomItemSet(action) {
//TODO v5.1: remove when mobile moves up
//no longer need useCustomItems flag
//keep the if logic - remove everything in the else
const { includeCustomItems } = ConfigUtils.getCurrentRulesEngineConfig();
const { id, properties, mappingId, partyId } = action.payload;
if (includeCustomItems) {
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.putCharacterCustomItem, Object.assign({ id,
mappingId, partyId: partyId !== null && partyId !== void 0 ? partyId : null }, properties));
//if partyId is passed in, the item is shared
if (!partyId) {
// update Item and CustomItem from character
yield put(callCommitAction(characterActions.customItemSet, id, properties));
}
if (data.addItems) {
if (typeof properties.notes !== undefined) {
yield put(callCommitAction(characterActions.valueSet, AdjustmentTypeEnum.NOTES, properties.notes, null, ValueHacks.hack__toString(ItemAccessors.getMappingId(data.addItems[0])), ValueHacks.hack__toString(ItemAccessors.getMappingEntityTypeId(data.addItems[0])), null, null, partyId));
}
yield call(handleCommitAddItems, data.addItems);
}
}
else {
//Calls the V5 version and commits to character custom items
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.putCharacterCustomItemV5, Object.assign({ id }, properties));
yield put(callCommitAction(characterActions.customItemSet, id, properties));
}
if (action.meta.accept) {
action.meta.accept();
}
}
/**
*
* @param action
*/
export function* handleItemCustomizationsDelete(action) {
const { mappingId, mappingEntityTypeId, partyId } = action.payload;
const id = ValueHacks.hack__toString(mappingId);
const entityTypeId = ValueHacks.hack__toString(mappingEntityTypeId);
yield put(characterActions.entityValuesRemove(id, entityTypeId, null, null, partyId));
if (action.meta.accept) {
action.meta.accept();
}
}
export function* handleContainerDestroy(item, removeContents) {
const itemMappingId = ItemAccessors.getMappingId(item);
const containerLookup = yield select(rulesEngineSelectors.getContainerLookup);
const inventoryLookup = yield select(rulesEngineSelectors.getInventoryLookup);
const partyInventoryLookup = yield select(rulesEngineSelectors.getPartyInventoryLookup);
const container = HelperUtils.lookupDataOrFallback(containerLookup, DefinitionHacks.hack__generateDefinitionKey(ContainerTypeEnum.ITEM, itemMappingId));
if (container) {
if (removeContents) {
const itemMappingIdsToRemove = ContainerAccessors.getItemMappingIds(container);
for (let i = 0; i < itemMappingIdsToRemove.length; i++) {
const foundItem = HelperUtils.lookupDataOrFallback(inventoryLookup, itemMappingIdsToRemove[i]);
const foundPartyItem = HelperUtils.lookupDataOrFallback(partyInventoryLookup, itemMappingIdsToRemove[i]);
if (foundItem || foundPartyItem) {
let infusion;
if (foundItem) {
infusion = ItemAccessors.getInfusion(foundItem);
}
else if (foundPartyItem) {
infusion = ItemAccessors.getInfusion(foundPartyItem);
}
if (infusion) {
const infusionId = InfusionAccessors.getId(infusion);
if (infusionId !== null) {
yield put(callCommitAction(serviceDataActions.infusionMappingRemove, infusionId, itemMappingIdsToRemove[i]));
}
}
}
if (foundItem) {
yield put(callCommitAction(characterActions.itemRemove, itemMappingIdsToRemove[i], true));
}
else if (foundPartyItem) {
yield put(callCommitAction(serviceDataActions.partyItemRemove, itemMappingIdsToRemove[i], true));
}
}
}
else {
const itemMappingIdsToMove = ContainerAccessors.getItemMappingIds(container);
const isShared = ContainerAccessors.isShared(container);
const characterId = yield select(rulesEngineSelectors.getId);
for (let i = 0; i < itemMappingIdsToMove.length; i++) {
if (isShared) {
const campaign = yield select(rulesEngineSelectors.getCampaign);
if (campaign) {
yield put(callCommitAction(serviceDataActions.partyItemMoveSet, itemMappingIdsToMove[i], CampaignAccessors.getId(campaign), ContainerTypeEnum.CAMPAIGN));
}
}
else {
yield put(callCommitAction(characterActions.itemMoveSet, itemMappingIdsToMove[i], characterId, ContainerTypeEnum.CHARACTER));
}
}
const infusion = ItemAccessors.getInfusion(item);
if (infusion) {
yield put(callCommitAction(serviceDataActions.infusionMappingRemove, InfusionAccessors.getId(infusion), itemMappingId));
}
}
if (ContainerAccessors.isShared(container)) {
yield put(serviceDataActions.partyItemRemove(itemMappingId, removeContents));
}
else {
yield put(characterActions.itemRemove(itemMappingId, removeContents));
}
}
}
/**
*
* @param action
*/
export function* handleItemDestroy(action) {
const { id, removeContainerContents } = action.payload;
// handle the case where the item is the characters item
const inventoryLookup = yield select(rulesEngineSelectors.getInventoryLookup);
let item = HelperUtils.lookupDataOrFallback(inventoryLookup, id);
if (item) {
//if item isContainer, handle removing container and contents
if (ItemAccessors.isContainer(item)) {
yield call(handleContainerDestroy, item, removeContainerContents);
}
else {
//item is not container, just remove it (removeContainerContents should be false for now)
yield put(characterActions.itemRemove(id, false));
}
if (action.meta.accept) {
action.meta.accept();
}
return;
}
//handle the case where the item is the party's item
const partyInventoryLookup = yield select(rulesEngineSelectors.getPartyInventoryLookup);
item = HelperUtils.lookupDataOrFallback(partyInventoryLookup, id);
if (item) {
//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);
}
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();
}
return;
}
}
/**
*
* @param action
*/
export function* handleItemCreate(action) {
var _a, _b;
const { item, quantity, containerDefinitionKey } = action.payload;
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.postCharacterInventoryItem, {
equipment: [
{
containerEntityId: containerDefinitionKey
? DefinitionHacks.hack__getDefinitionKeyId(containerDefinitionKey)
: undefined,
containerEntityTypeId: containerDefinitionKey
? DefinitionHacks.hack__getDefinitionKeyType(containerDefinitionKey)
: undefined,
entityId: ItemAccessors.getDefinitionId(item),
entityTypeId: ItemAccessors.getDefinitionEntityTypeId(item),
quantity,
},
],
}, { onError: (_a = action === null || action === void 0 ? void 0 : action.meta) === null || _a === void 0 ? void 0 : _a.reject, onSuccess: (_b = action === null || action === void 0 ? void 0 : action.meta) === null || _b === void 0 ? void 0 : _b.accept });
if (data.addItems) {
yield call(handleCommitAddItems, data.addItems);
}
if (action.meta.accept) {
action.meta.accept();
}
yield call(handleDataUpdates, data);
}
function* handleCommitAddItems(addItems) {
const containers = yield select(rulesEngineSelectors.getInventoryContainers);
for (let i = 0; i < addItems.length; i++) {
const itemContainer = ContainerUtils.getItemParentContainer(containers, ItemAccessors.getContainerDefinitionKey(addItems[i]));
if (itemContainer && ContainerAccessors.isShared(itemContainer)) {
yield put(callCommitAction(serviceDataActions.partyItemAdd, addItems[i]));
}
else {
yield put(callCommitAction(characterActions.itemAdd, addItems[i]));
}
}
}
/**
*
* @param action
*/
export function* handleItemMove(action) {
var _a;
const containerLookup = yield select(rulesEngineSelectors.getContainerLookup);
const inventoryLookup = yield select(rulesEngineSelectors.getInventoryLookup);
const partyInventoryLookup = yield select(rulesEngineSelectors.getPartyInventoryLookup);
const item = (_a = HelperUtils.lookupDataOrFallback(inventoryLookup, action.payload.id)) !== null && _a !== void 0 ? _a : HelperUtils.lookupDataOrFallback(partyInventoryLookup, action.payload.id);
if (!item) {
return;
}
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);
yield call(SagaHelpers.sendApiRequest, ApiRequests.putCharacterInventoryItemMove, action.payload);
if (isCurrentContainerShared) {
//currently in party
if (isDestinationContainerShared) {
//still in party
yield put(callCommitAction(serviceDataActions.partyItemMoveSet, action.payload.id, action.payload.containerEntityId, action.payload.containerEntityTypeId));
}
else {
if (isItemContainer) {
const inventoryItems = yield select(rulesEngineSelectors.getPartyInventory);
const itemContainer = HelperUtils.lookupDataOrFallback(containerLookup, DefinitionHacks.hack__generateDefinitionKey(EntityTypeEnum.ITEM, ItemAccessors.getMappingId(item)));
// If item is container we need to move contents to inventory
if (itemContainer) {
const inventoryInContainer = ContainerUtils.getInventoryItems(itemContainer, inventoryItems);
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])));
}
}
}
//moving to character
yield put(callCommitAction(characterActions.itemAdd, Object.assign(Object.assign({}, item), { containerEntityId: action.payload.containerEntityId, containerEntityTypeId: action.payload.containerEntityTypeId, containerDefinitionKey: DefinitionHacks.hack__generateDefinitionKey(action.payload.containerEntityTypeId, action.payload.containerEntityId) })));
yield put(callCommitAction(serviceDataActions.partyItemRemove, ItemAccessors.getMappingId(item)));
}
}
else {
//currently on character
if (isDestinationContainerShared) {
if (isItemContainer) {
const inventoryItems = yield select(rulesEngineSelectors.getInventory);
const itemContainer = HelperUtils.lookupDataOrFallback(containerLookup, DefinitionHacks.hack__generateDefinitionKey(EntityTypeEnum.ITEM, ItemAccessors.getMappingId(item)));
// If item is container we need to move contents to party inventory
if (itemContainer) {
const inventoryInContainer = ContainerUtils.getInventoryItems(itemContainer, inventoryItems);
for (let i = 0; i < inventoryInContainer.length; i++) {
yield put(callCommitAction(serviceDataActions.partyItemAdd, Object.assign(Object.assign({}, inventoryInContainer[i]), { containerEntityId: ContainerAccessors.getMappingId(itemContainer), containerEntityTypeId: ContainerAccessors.getContainerType(itemContainer), containerDefinitionKey: ContainerAccessors.getDefinitionKey(itemContainer) })));
yield put(callCommitAction(characterActions.itemRemove, ItemAccessors.getMappingId(inventoryInContainer[i])));
}
}
}
//moving to party
yield put(callCommitAction(serviceDataActions.partyItemAdd, Object.assign(Object.assign({}, item), { containerEntityId: action.payload.containerEntityId, containerEntityTypeId: action.payload.containerEntityTypeId, containerDefinitionKey: DefinitionHacks.hack__generateDefinitionKey(action.payload.containerEntityTypeId, action.payload.containerEntityId) })));
yield put(callCommitAction(characterActions.itemRemove, ItemAccessors.getMappingId(item)));
}
else {
//still on character
yield put(callCommitAction(characterActions.itemMoveSet, action.payload.id, action.payload.containerEntityId, action.payload.containerEntityTypeId));
}
}
if (action.meta.accept) {
action.meta.accept();
}
}
/**
*
* @param action
*/
export function* handleItemEquippedSet(action) {
var _a;
// If we are unequipping and item is attuned, unattune it as well
const containerLookup = yield select(rulesEngineSelectors.getContainerLookup);
const inventoryLookup = yield select(rulesEngineSelectors.getInventoryLookup);
const partyInventoryLookup = yield select(rulesEngineSelectors.getPartyInventoryLookup);
const item = (_a = HelperUtils.lookupDataOrFallback(inventoryLookup, action.payload.id)) !== null && _a !== void 0 ? _a : HelperUtils.lookupDataOrFallback(partyInventoryLookup, action.payload.id);
if (!action.payload.value) {
if (item !== null && ItemAccessors.isAttuned(item)) {
yield put(characterActions.itemAttuneSet(action.payload.id, false));
}
}
const equipParams = {
id: action.payload.id,
value: action.payload.value,
};
yield call(SagaHelpers.sendApiRequest, ApiRequests.putCharacterInventoryItemEquipped, equipParams);
if (item) {
if (ItemUtils.isShared(item, containerLookup)) {
yield put(callCommitAction(serviceDataActions.partyItemEquippedSet, ...Object.values(action.payload)));
}
else {
yield put(callCommitAction(characterActions.itemEquippedSet, ...Object.values(action.payload)));
}
}
if (action.meta.accept) {
action.meta.accept();
}
}
export function* handleItemAttuneSet(action) {
var _a;
const containerLookup = yield select(rulesEngineSelectors.getContainerLookup);
const inventoryLookup = yield select(rulesEngineSelectors.getInventoryLookup);
const partyInventoryLookup = yield select(rulesEngineSelectors.getPartyInventoryLookup);
const item = (_a = HelperUtils.lookupDataOrFallback(inventoryLookup, action.payload.id)) !== null && _a !== void 0 ? _a : HelperUtils.lookupDataOrFallback(partyInventoryLookup, action.payload.id);
const attuneParams = Object.assign({}, action.payload);
yield call(SagaHelpers.sendApiRequest, ApiRequests.putCharacterInventoryItemAttuned, attuneParams);
if (item) {
if (ItemUtils.isShared(item, containerLookup)) {
yield put(callCommitAction(serviceDataActions.partyItemAttuneSet, ...Object.values(action.payload)));
}
else {
yield put(callCommitAction(characterActions.itemAttuneSet, ...Object.values(action.payload)));
}
}
if (action.meta.accept) {
action.meta.accept();
}
}
export function* handleItemQuantitySet(action) {
var _a;
const containerLookup = yield select(rulesEngineSelectors.getContainerLookup);
const inventoryLookup = yield select(rulesEngineSelectors.getInventoryLookup);
const partyInventoryLookup = yield select(rulesEngineSelectors.getPartyInventoryLookup);
const item = (_a = HelperUtils.lookupDataOrFallback(inventoryLookup, action.payload.id)) !== null && _a !== void 0 ? _a : HelperUtils.lookupDataOrFallback(partyInventoryLookup, action.payload.id);
const quantityParams = Object.assign({}, action.payload);
yield call(SagaHelpers.sendApiRequest, ApiRequests.putCharacterInventoryItemQuantity, quantityParams);
if (item) {
if (ItemUtils.isShared(item, containerLookup)) {
yield put(callCommitAction(serviceDataActions.partyItemQuantitySet, ...Object.values(action.payload)));
}
else {
yield put(callCommitAction(characterActions.itemQuantitySet, ...Object.values(action.payload)));
}
}
if (action.meta.accept) {
action.meta.accept();
}
}
export function* handleItemChargesSet(action) {
var _a;
const containerLookup = yield select(rulesEngineSelectors.getContainerLookup);
const inventoryLookup = yield select(rulesEngineSelectors.getInventoryLookup);
const partyInventoryLookup = yield select(rulesEngineSelectors.getPartyInventoryLookup);
const item = (_a = HelperUtils.lookupDataOrFallback(inventoryLookup, action.payload.id)) !== null && _a !== void 0 ? _a : HelperUtils.lookupDataOrFallback(partyInventoryLookup, action.payload.id);
const chargeParams = Object.assign(Object.assign({}, action.payload), { charges: action.payload.uses });
yield call(SagaHelpers.sendApiRequest, ApiRequests.putCharacterInventoryItemCharge, chargeParams);
if (item) {
if (ItemUtils.isShared(item, containerLookup)) {
yield put(callCommitAction(serviceDataActions.partyItemChargesSet, ...Object.values(action.payload)));
}
else {
yield put(callCommitAction(characterActions.itemChargesSet, ...Object.values(action.payload)));
}
}
if (action.meta.accept) {
action.meta.accept();
}
}
/**
*
* @param action
*/
export function* handleStartingEquipmentAdd(action) {
const { items, gold, custom } = action.payload;
if (items && !items.length && !gold && custom && !custom.length) {
yield put(characterActions.startingEquipmentTypeSet(StartingEquipmentTypeEnum.GUIDED));
if (action.meta.reject) {
action.meta.reject();
}
return;
}
let itemsResponse = null;
if (items && items.length) {
let params = {
equipment: items,
};
itemsResponse = yield call(SagaHelpers.sendApiRequest, ApiRequests.postCharacterInventoryItem, params);
}
let goldResponse = null;
if (gold) {
const currentCurrencies = yield select(characterSelectors.getCurrencies);
const currentGold = currentCurrencies === null ? 0 : currentCurrencies.gp;
let goldParams = {
amount: currentGold + gold,
};
goldResponse = yield call(SagaHelpers.sendApiRequest, ApiRequests.putCharacterInventoryCurrencyGold, goldParams);
yield put(callCommitAction(characterActions.currencyGoldSet, goldParams.amount));
}
let customResponse = null;
if (custom && custom.length) {
const currentNotes = yield select(characterSelectors.getNotes);
const personalPossessions = `${currentNotes && currentNotes.personalPossessions ? currentNotes.personalPossessions + '\n' : ''}${custom.join('\n')}`;
const noteParams = {
personalPossessions,
};
customResponse = yield call(SagaHelpers.sendApiRequest, ApiRequests.putCharacterDescriptionNotes, noteParams);
yield put(callCommitAction(characterActions.noteSet, 'personalPossessions', personalPossessions));
}
if ((itemsResponse && itemsResponse.data.success) ||
(goldResponse && goldResponse.data.success) ||
(customResponse && customResponse.data.success)) {
if (itemsResponse && itemsResponse.data.success) {
const data = ApiAdapterUtils.getResponseData(itemsResponse);
if (data && data.addItems) {
for (let i = 0; i < data.addItems.length; i++) {
yield put(callCommitAction(characterActions.itemAdd, data.addItems[i]));
}
}
}
yield put(characterActions.startingEquipmentTypeSet(StartingEquipmentTypeEnum.GUIDED));
if (action.meta.accept) {
action.meta.accept();
}
}
else {
//TODO do something with error
}
}
/**
*
* @param action
*/
export function* handleStartingGoldAdd(action) {
const { gold } = action.payload;
let goldResponse = null;
if (gold) {
const currentCurrencies = yield select(characterSelectors.getCurrencies);
const currentGold = currentCurrencies === null ? 0 : currentCurrencies.gp;
let goldParams = {
amount: currentGold + gold,
};
goldResponse = yield call(SagaHelpers.sendApiRequest, ApiRequests.putCharacterInventoryCurrencyGold, goldParams);
yield put(callCommitAction(characterActions.currencyGoldSet, goldParams.amount));
}
if (goldResponse && goldResponse.data.success) {
yield put(characterActions.startingEquipmentTypeSet(StartingEquipmentTypeEnum.GUIDED));
if (action.meta.accept) {
action.meta.accept();
}
}
else {
//TODO do something with error
}
}
/**
*
* @param action
*/
export function* handleCurrencyTransactionSet(action) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
let data = yield call(SagaHelpers.getApiRequestData, ApiRequests.putCharacterInventoryCurrencyTransaction, action.payload, {
onSuccess: action.meta.accept,
onError: action.meta.reject,
});
// if the request fails because it is offline(mobile-only) the we still want to process the change.
if (data.isOfflineResponse && action.payload.destinationEntityTypeId && action.payload.destinationEntityId) {
const containerEntityTypeId = action.payload.destinationEntityTypeId;
const containerEntityId = action.payload.destinationEntityId;
const containerDefinitionKey = hack__generateDefinitionKey(containerEntityTypeId, containerEntityId);
const containerLookup = yield select(rulesEngineSelectors.getContainerLookup);
const container = HelperUtils.lookupDataOrFallback(containerLookup, containerDefinitionKey);
if (container) {
const coins = container
? ContainerAccessors.getCoin(container)
: null;
if (coins) {
data.cp = (coins.cp || 0) + ((_b = (_a = action.payload) === null || _a === void 0 ? void 0 : _a.cp) !== null && _b !== void 0 ? _b : 0);
data.ep = (coins.ep || 0) + ((_d = (_c = action.payload) === null || _c === void 0 ? void 0 : _c.ep) !== null && _d !== void 0 ? _d : 0);
data.gp = (coins.gp || 0) + ((_f = (_e = action.payload) === null || _e === void 0 ? void 0 : _e.gp) !== null && _f !== void 0 ? _f : 0);
data.pp = (coins.pp || 0) + ((_h = (_g = action.payload) === null || _g === void 0 ? void 0 : _g.pp) !== null && _h !== void 0 ? _h : 0);
data.sp = (coins.sp || 0) + ((_k = (_j = action.payload) === null || _j === void 0 ? void 0 : _j.sp) !== null && _k !== void 0 ? _k : 0);
}
}
}
if (data) {
if (action.payload.destinationEntityTypeId === ContainerTypeEnum.CHARACTER) {
yield put(callCommitAction(characterActions.currenciesSet, data));
}
else if (action.payload.destinationEntityTypeId === ContainerTypeEnum.CAMPAIGN) {
yield put(callCommitAction(serviceDataActions.partyCurrenciesSet, data));
}
else if (action.payload.destinationEntityId && action.payload.destinationEntityTypeId) {
const containerLookup = yield select(rulesEngineSelectors.getContainerLookup);
const isShared = ContainerValidators.validateIsShared(hack__generateDefinitionKey(action.payload.destinationEntityTypeId, action.payload.destinationEntityId), containerLookup);
if (isShared) {
yield put(callCommitAction(serviceDataActions.partyItemCurrencySet, data, action.payload.destinationEntityId));
}
else {
yield put(callCommitAction(characterActions.itemCurrencySet, data, action.payload.destinationEntityId));
}
}
if (action.meta.accept) {
action.meta.accept();
}
}
}
/**
*
* @param action
*/
export function* handleRacialTraitChoiceSetRequest(action) {
const { racialTraitId, choiceId } = action.payload;
yield call(apiRacialTraitChoiceSet, action);
const race = yield select(rulesEngineSelectors.getRace);
if (race) {
const racialTrait = RaceAccessors.getRacialTraits(race).find((racialTrait) => RacialTraitAccessors.getId(racialTrait) === racialTraitId);
if (racialTrait) {
const racialTraitChoice = RacialTraitAccessors.getChoices(racialTrait).find((choice) => ChoiceAccessors.getId(choice) === choiceId);
if (racialTraitChoice) {
yield call(autoUpdateChoices, racialTraitChoice);
}
}
}
}
/**
*
* @param action
*/
function* apiRacialTraitChoiceSet(action) {
const { racialTraitId, choiceType, choiceId, optionValue } = action.payload;
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.putCharacterRaceRacialTraitChoice, {
racialTraitId,
type: choiceType,
choiceKey: choiceId,
choiceValue: optionValue,
});
yield call(handleDataUpdates, data);
}
/**
*
* @param action
*/
export function* handleClassFeatureChoiceSetRequest(action) {
const { classId, classFeatureId, choiceId, choiceType, optionValue } = action.payload;
let oldClasses = yield select(rulesEngineSelectors.getClasses);
let oldCharClass = oldClasses.find((charClass) => ClassAccessors.getActiveId(charClass) === classId);
if (choiceType === BuilderChoiceTypeEnum.SUB_CLASS_OPTION && oldCharClass) {
const subclass = ClassAccessors.getSubclass(oldCharClass);
if (subclass !== null) {
const spellListIds = ClassAccessors.getActiveClassFeatures(oldCharClass)
.filter((classFeature) => ClassFeatureAccessors.getClassId(classFeature) === subclass.id)
.map(ClassFeatureAccessors.getSpellListIds)
.reduce((acc, ids) => acc.concat(ids), []);
yield call(apiRemoveSpellsBySpellListIds, spellListIds);
}
}
yield call(apiClassFeatureChoiceSet, action);
const classes = yield select(rulesEngineSelectors.getClasses);
const charClass = classes.find((charClass) => !!oldCharClass && ClassAccessors.getId(charClass) === ClassAccessors.getId(oldCharClass));
if (charClass) {
const classFeature = ClassAccessors.getClassFeatures(charClass).find((classFeature) => ClassFeatureAccessors.getId(classFeature) === classFeatureId);
if (classFeature) {
const classFeatureChoice = ClassFeatureAccessors.getChoices(classFeature).find((choice) => ChoiceAccessors.getId(choice) === choiceId);
yield call(autoUpdateChoices, classFeatureChoice);
}
if (optionValue !== null && choiceType === BuilderChoiceTypeEnum.SUB_CLASS_OPTION) {
// Need to get the "new" activeId since the subclass ID has changed
yield call(autoUpdateClassAlwaysPreparedSpells, [ClassAccessors.getActiveId(charClass)]);
yield call(hack__simulateOwnedClassFeatureDefinitionData);
}
}
}
/**
*
* @param action
*/
function* apiClassFeatureChoiceSet(action) {
const { classId, classFeatureId, choiceType, choiceId, optionValue, parentChoiceId } = action.payload;
const classMappingIdLookup = yield select(rulesEngineSelectors.getClassMappingIdLookupByActiveId);
const classMappingId = HelperUtils.lookupDataOrFallback(classMappingIdLookup, classId);
if (classMappingId !== null) {
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.putCharacterClassFeatureChoice, {
classId,
classMappingId,
classFeatureId,
type: choiceType,
choiceKey: choiceId,
choiceValue: optionValue,
parentChoiceId,
});
yield call(handleDataUpdates, data);
}
}
/**
*
* @param action
*/
export function* handleFeatChoiceSetRequest(action) {
const { featId, choiceId } = action.payload;
yield call(apiFeatChoiceSet, action);
const feats = yield select(rulesEngineSelectors.getFeats);
const feat = feats.find((feat) => FeatAccessors.getId(feat) === featId);
if (feat) {
const featChoice = FeatAccessors.getChoices(feat).find((choice) => ChoiceAccessors.getId(choice) === choiceId);
yield call(autoUpdateChoices, featChoice);
}
}
/**
*
* @param action
*/
function* apiFeatChoiceSet(action) {
const { featId, choiceType, choiceId, optionValue } = action.payload;
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.putCharacterFeatChoice, {
id: featId,
type: choiceType,
choiceKey: choiceId,
choiceValue: optionValue,
});
yield call(handleDataUpdates, data);
}
/**
*
* @param action
*/
function* apiClassLevelSet(action) {
const { classId, level, newCharacterXp } = action.payload;
const classMappingIdLookup = yield select(rulesEngineSelectors.getClassMappingIdLookupByActiveId);
const classMappingId = HelperUtils.lookupDataOrFallback(classMappingIdLookup, classId);
if (classMappingId !== null) {
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.putCharacterClassLevel, {
classId,
classMappingId,
level,
});
if (newCharacterXp !== null) {
yield put(characterActions.xpSet(newCharacterXp));
}
yield call(handleDataUpdates, data);
}
}
/**
*
* @param action
*/
export function* handleClassLevelSetRequest(action) {
const { classId, level } = action.payload;
let oldClasses = yield select(rulesEngineSelectors.getClasses);
let oldCharClass = oldClasses.find((charClass) => ClassAccessors.getActiveId(charClass) === classId);
if (oldCharClass) {
const oldLevel = ClassAccessors.getLevel(oldCharClass);
// if leveling down
if (level < oldLevel) {
const oldClassId = ClassAccessors.getId(oldCharClass);
const removedClassFeatures = ClassAccessors.getActiveClassFeatures(oldCharClass).filter((classFeature) => ClassFeatureAccessors.getRequiredLevel(classFeature) > level);
const isRemovingSubclass = removedClassFeatures.some((feature) => ClassFeatureAccessors.getChoices(feature).some((choice) => ChoiceAccessors.getType(choice) === BuilderChoiceTypeEnum.SUB_CLASS_OPTION));
const removedSpellListIds = ClassAccessors.getActiveClassFeatures(oldCharClass)
.filter((classFeature) => ClassFeatureAccessors.getRequiredLevel(classFeature) > level ||
(isRemovingSubclass && ClassFeatureAccessors.getClassId(classFeature) !== oldClassId))
.map(ClassFeatureAccessors.getSpellListIds)
.reduce((acc, spellListIds) => acc.concat(spellListIds), []);
yield call(apiRemoveSpellsBySpellListIds, removedSpellListIds);
}
}
yield call(apiClassLevelSet, action);
yield call(autoUpdateInfusions);
yield call(autoUpdateChoices);
yield call(autoUpdateClassAlwaysPreparedSpells, [action.payload.classId]);
}
/**
*
* @param action
*/
export function* handleXpSetRequest(action) {
const { currentXp } = action.payload;
yield call(SagaHelpers.sendApiRequest, ApiRequests.putCharacterProgression, { currentXp });
yield put(characterActions.xpSet(currentXp));
}
/**
*
* @param action
*/
export function* handleSpellCreate(action) {
const { characterClassId, spell } = action.payload;
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.postCharacterSpell, {
characterClassId,
spellId: SpellAccessors.getId(spell),
id: SpellAccessors.getMappingId(spell),
entityTypeId: SpellAccessors.getMappingEntityTypeId(spell),
});
yield put(callCommitAction(characterActions.spellAdd, data, characterClassId));
if (action.meta.accept) {
action.meta.accept();
}
}
/**
*
* @param action
*/
export function* handleSpellRemove(action) {
const { spell } = action.payload;
let mappingId = SpellAccessors.getMappingId(spell);
let mappingEntityTypeId = SpellAccessors.getMappingEntityTypeId(spell);
let [contextId, contextTypeId] = SpellDerivers.deriveExpandedContextIds(spell);
yield put(callCommitAction(characterActions.entityValuesRemove, mappingId, mappingEntityTypeId, contextId, contextTypeId));
if (action.meta.accept) {
action.meta.accept();
}
}
/**
*
* @param action
*/
export function* handleSpellPrepareSet(action) {
// If this is preparing a spell, we need to find out if this is a known spellcaster who may not be
// mapped to the spell directly yet. We can use the service data we got for class always known spells
// to simulate adding the mapping to the state without waiting for the server.
const spellClasses = yield select(rulesEngineSelectors.getSpellClasses);
let mappedSpell;
for (let i = 0; i < spellClasses.length; i++) {
const charClass = spellClasses[i];
const characterClassId = ClassAccessors.getMappingId(charClass);
if (action.payload.characterClassId !== characterClassId) {
continue;
}
const classSpellMappings = yield select(characterSelectors.getClassSpells);
const classSpellMapping = classSpellMappings.find((classSpellMapping) => classSpellMapping.characterClassId === characterClassId);
if (!classSpellMapping || !classSpellMapping.spells) {
continue;
}
mappedSpell = classSpellMapping.spells.find((spell) => {
return (SpellAccessors.getMappingId(spell) === action.payload.id &&
SpellAccessors.getMappingEntityTypeId(spell) === action.payload.entityTypeId &&
SpellAccessors.getId(spell) === action.payload.spellId);
});
if (!ClassAccessors.getKnowsAllSpells(charClass)) {
continue;
}
if (!mappedSpell) {
const classAlwaysKnownSpells = yield select(serviceDataSelectors.getClassAlwaysKnownSpells);
const alwaysKnownSpells = HelperUtils.lookupDataOrFallback(classAlwaysKnownSpells, ClassAccessors.getActiveId(charClass));
if (alwaysKnownSpells) {
const spellContract = alwaysKnownSpells.find((spell) => {
return (SpellAccessors.getMappingId(spell) === action.payload.id &&
SpellAccessors.getMappingEntityTypeId(spell) === action.payload.entityTypeId &&
SpellAccessors.getId(spell) === action.payload.spellId);
});
if (spellContract) {
yield put(callCommitAction(characterActions.spellAdd, spellContract, ClassAccessors.getMappingId(charClass)));
yield put(callCommitAction(characterActions.spellPreparedSet, spellContract, characterClassId, action.payload.prepared));
}
}
}
}
if (mappedSpell) {
yield put(callCommitAction(characterActions.spellPreparedSet, mappedSpell, action.payload.characterClassId, action.payload.prepared));
}
yield call(SagaHelpers.getApiRequestData, ApiRequests.putCharacterSpellPrepared, action.payload);
if (action.meta.accept) {
action.meta.accept();
}
}
/**
*
* @param action
*/
export function* handleSpellCustomizationsDelete(action) {
const { mappingId, mappingEntityTypeId, contextId, contextTypeId } = action.payload;
yield put(characterActions.entityValuesRemove(ValueHacks.hack__toString(mappingId), ValueHacks.hack__toString(mappingEntityTypeId), ValueHacks.hack__toString(contextId), ValueHacks.hack__toString(contextTypeId)));
}
/**
* @param action
* @param data
*/
function* updateBackgroundResult(action, data) {
yield call(handleDataUpdates, data);
yield call(autoUpdateChoices);
}
/**
*
* @param action
*/
export function* handleBackgroundSetRequest(action) {
const existingBackground = yield select(rulesEngineSelectors.getBackgroundInfo);
if (existingBackground !== null) {
yield call(apiRemoveSpellsBySpellListIds, BackgroundAccessors.getSpellListIds(existingBackground));
}
yield put(callCommitAction(characterActions.backgroundHasCustomSet, false));
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.putCharacterBackground, action.payload);
yield call(updateBackgroundResult, action, data);
}
/**
*
* @param action
*/
export function* handleBackgroundChoiceSetRequest(action) {
yield call(apiBackgroundChoiceSet, action);
}
/**
*
* @param action
*/
function* apiBackgroundChoiceSet(action) {
const { choiceType, choiceId, optionValue } = action.payload;
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.putCharacterBackgroundChoice, {
type: choiceType,
choiceKey: choiceId,
choiceValue: optionValue,
});
yield call(handleDataUpdates, data);
}
/**
*
* @param action
*/
export function* handleBackgroundHasCustomSetRequest(action) {
// if you are switching to a custom background, attempt to remove any spells if the background has spellListIds
if (action.payload.hasCustomBackground) {
const existingBackground = yield select(rulesEngineSelectors.getBackgroundInfo);
if (existingBackground !== null) {
yield call(apiRemoveSpellsBySpellListIds, BackgroundAccessors.getSpellListIds(existingBackground));
}
}
yield put(callCommitAction(characterActions.backgroundHasCustomSet, action.payload.hasCustomBackground));
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.putCharacterConfigurationHasCustomBackground, action.payload);
yield call(updateBackgroundResult, action, data);
}
/**
*
* @param action
*/
export function* handleBackgroundCustomSetRequest(action) {
const { properties } = action.payload;
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.putCharacterCustomBackground, {
name: properties.name,
description: properties.description,
backgroundFeatureId: properties.featureId,
backgroundCharacteristicsId: properties.characteristicsId,
backgroundType: properties.modifierType,
});
yield call(updateBackgroundResult, action, data);
}
/**
*
*/
export function* autoUpdateInfusions() {
const choices = yield select(rulesEngineSelectors.getInfusionChoices);
// handle choices that exist are could be invalid
for (let i = 0; i < choices.length; i++) {
const choice = choices[i];
let isAvailable = InfusionChoiceValidators.validateIsAvailable(choice);
if (!isAvailable) {
const knownInfusion = InfusionChoiceAccessors.getKnownInfusion(choice);
const choiceKey = InfusionChoiceAccessors.getKey(choice);
if (knownInfusion && choiceKey !== null) {
yield call(serviceDataSagaHandlers.handleKnownInfusionDestroy, serviceDataActions.knownInfusionMappingDestroy(choiceKey));
}
}
}
const infusionChoiceLookup = yield select(rulesEngineSelectors.getInfusionChoiceLookup);
const knownInfusions = yield select(rulesEngineSelectors.getKnownInfusions);
// handle known infusions that exist that no longer have a choice (ex: class remove)
if (knownInfusions.length) {
for (let i = 0; i < knownInfusions.length; i++) {
const knownInfusion = knownInfusions[i];
const choiceKey = KnownInfusionAccessors.getChoiceKey(knownInfusion);
if (choiceKey !== null && !HelperUtils.lookupDataOrFallback(infusionChoiceLookup, choiceKey)) {
yield call(serviceDataSagaHandlers.handleKnownInfusionDestroy, serviceDataActions.knownInfusionMappingDestroy(choiceKey));
}
}
}
}
/**
*
* @param lastChoice
*/
function* autoUpdateChoices(lastChoice = null) {
yield call(autoUpdateProficiencyChoices, lastChoice);
yield call(autoUpdateClassFeatureChoices);
yield call(autoUpdateRacialTraitChoices);
yield call(autoUpdateBackgroundChoices);
yield call(autoUpdateExpertiseChoices);
}
/**
*
* @param choice
* @param lastChoice
*/
function isOtherProficiencyChoice(choice, lastChoice) {
return (ChoiceAccessors.getType(choice) === BuilderChoiceTypeEnum.MODIFIER_SUB_CHOICE &&
(ChoiceAccessors.getSubType(choice) === BuilderChoiceSubtypeEnum.PROFICIENCY ||
ChoiceAccessors.getSubType(choice) === BuilderChoiceSubtypeEnum.EXPERTISE_NO_REQUIREMENT) &&
ChoiceAccessors.getOptionValue(choice) !== null &&
(!lastChoice || (!!lastChoice && ChoiceAccessors.getId(lastChoice) !== ChoiceAccessors.getId(choice))));
}
/**
*
* @param lastChoice
*/
function* autoUpdateProficiencyChoices(lastChoice) {
let builderChoiceInfo = yield select(rulesEngineSelectors.getChoiceInfo);
let background = yield select(rulesEngineSelectors.getBackgroundInfo);
if (background) {
let choices = BackgroundAccessors.getChoices(background);
for (let choice of choices) {
if (isOtherProficiencyChoice(choice, lastChoice)) {
let selectedChoiceOption = ChoiceAccessors.getOptions(choice).find((option) => option.id === ChoiceAccessors.getOptionValue(choice));
let existingProficiencyModifiers = [];
// TODO fix once https://intellij-support.jetbrains.com/hc/en-us/community/posts/360003813400-Typescript-expression-can-t-follow-value-has-been-checked-for-undefined-outside-of-it
existingProficiencyModifiers = builderChoiceInfo.proficiencyModifiers.filter((modifier) => selectedChoiceOption &&
ModifierAccessors.getFriendlySubtypeName(modifier) === selectedChoiceOption.label);
if (existingProficiencyModifiers.length > 1) {
let choiceId = ChoiceAccessors.getId(choice);
if (choiceId !== null) {
yield call(apiBackgroundChoiceSet, {
type: 'SIMULATED_CHOICE',
payload: {
choiceType: ChoiceAccessors.getType(choice),
choiceId: choiceId,
optionValue: null,
},
});
if (selectedChoiceOption) {
NotificationUtils.dispatchMessage('Background Proficiency Removed', `The ${selectedChoiceOption.label} skill was removed from your background. Choose a new background skill in the Background section.`);
}
}
}
builderChoiceInfo = yield select(rulesEngineSelectors.getChoiceInfo);
}
}
}
let classes = yield select(rulesEngineSelectors.getClasses);
for (let charClass of classes) {
let classFeatures = ClassAccessors.getClassFeatures(charClass);
for (let classFeature of classFeatures) {
let choices = ClassFeatureAccessors.getChoices(classFeature);
for (let choice of choices) {
if (isOtherProficiencyChoice(choice, lastChoice)) {
let selectedFeatureChoiceOption = ChoiceAccessors.getOptions(choice).find((option) => option.id === ChoiceAccessors.getOptionValue(choice));
// TODO fix once https://intellij-support.jetbrains.com/hc/en-us/community/posts/360003813400-Typescript-expression-can-t-follow-value-has-been-checked-for-undefined-outside-of-it
let existingProficiencyModifiers = builderChoiceInfo.proficiencyModifiers.filter((modifier) => selectedFeatureChoiceOption &&
ModifierAccessors.getFriendlySubtypeName(modifier) === selectedFeatureChoiceOption.label);
if (existingProficiencyModifiers.length > 1) {
let classId = charClass.subclassDefinition === null
? ClassAccessors.getId(charClass)
: charClass.subclassDefinition.id;
yield call(apiClassFeatureChoiceSet, {
type: 'SIMULATED_CHOICE',
payload: {
classId,
classFeatureId: ClassFeatureAccessors.getId(classFeature),
choiceType: ChoiceAccessors.getType(choice),
choiceId: ChoiceAccessors.getId(choice),
optionValue: null,
parentChoiceId: ChoiceAccessors.getParentChoiceId(choice),
},
});
if (selectedFeatureChoiceOption) {
NotificationUtils.dispatchMessage('Class Feature Proficiency Removed', `The ${selectedFeatureChoiceOption.label} proficiency was removed from the "${ClassFeatureAccessors.getName(classFeature)}" class feature.
Choose a new proficiency skill in the Class section.`);
}
}
builderChoiceInfo = yield select(rulesEngineSelectors.getChoiceInfo);
}
}
}
}
let race = yield select(rulesEngineSelectors.getRace);
if (race) {
let racialTraits = RaceAccessors.getRacialTraits(race);
for (let racialTrait of racialTraits) {
let choices = RacialTraitAccessors.getChoices(racialTrait);
for (let choice of choices) {
if (isOtherProficiencyChoice(choice, lastChoice)) {
let selectedTraitChoiceOption = ChoiceAccessors.getOptions(choice).find((option) => option.id === ChoiceAccessors.getOptionValue(choice));
// TODO fix once https://intellij-support.jetbrains.com/hc/en-us/community/posts/360003813400-Typescript-expression-can-t-follow-value-has-been-checked-for-undefined-outside-of-it
let existingProficiencyModifiers = builderChoiceInfo.proficiencyModifiers.filter((modifier) => selectedTraitChoiceOption &&
ModifierAccessors.getFriendlySubtypeName(modifier) === selectedTraitChoiceOption.label);
if (existingProficiencyModifiers.length > 1) {
yield call(apiRacialTraitChoiceSet, {
type: 'SIMULATED_CHOICE',
payload: {
racialTraitId: RacialTraitAccessors.getId(racialTrait),
choiceType: ChoiceAccessors.getType(choice),
choiceId: ChoiceAccessors.getId(choice),
optionValue: null,
},
});
if (selectedTraitChoiceOption) {
NotificationUtils.dispatchMessage('Racial Trait Proficiency Removed', `The ${selectedTraitChoiceOption.label} proficiency was removed from the "${RacialTraitAccessors.getName(racialTrait)}" racial trait.
Choose a new proficiency skill in the Race section.`);
}
}
builderChoiceInfo = yield select(rulesEngineSelectors.getChoiceInfo);
}
}
}
}
}
/**
*
*/
function* autoUpdateExpertiseChoices() {
let builderChoiceInfo = yield select(rulesEngineSelectors.getChoiceInfo);
let classes = yield select(rulesEngineSelectors.getClasses);
for (let charClass of classes) {
let classFeatures = ClassAccessors.getClassFeatures(charClass);
for (let classFeature of classFeatures) {
let choices = ClassFeatureAccessors.getChoices(classFeature);
for (let choice of choices) {
if (ChoiceAccessors.getType(choice) === BuilderChoiceTypeEnum.MODIFIER_SUB_CHOICE &&
ChoiceAccessors.getSubType(choice) === BuilderChoiceSubtypeEnum.EXPERTISE &&
ChoiceAccessors.getOptionValue(choice)) {
let selectedFeatureChoiceOption = ChoiceAccessors.getOptions(choice).find((option) => option.id === ChoiceAccessors.getOptionValue(choice));
// TODO fix once https://intellij-support.jetbrains.com/hc/en-us/community/posts/360003813400-Typescript-expression-can-t-follow-value-has-been-checked-for-undefined-outside-of-it
let existingProficiencyModifier = builderChoiceInfo.proficiencyModifiers.find((modifier) => !!selectedFeatureChoiceOption &&
ModifierAccessors.getFriendlySubtypeName(modifier) === selectedFeatureChoiceOption.label);
if (!existingProficiencyModifier) {
let classId = charClass.subclassDefinition === null
? ClassAccessors.getId(charClass)
: charClass.subclassDefinition.id;
yield call(apiClassFeatureChoiceSet, {
type: 'SIMULATED_CHOICE',
payload: {
classId,
classFeatureId: ClassFeatureAccessors.getId(classFeature),
choiceType: ChoiceAccessors.getType(choice),
choiceId: ChoiceAccessors.getId(choice),
optionValue: null,
parentChoiceId: ChoiceAccessors.getParentChoiceId(choice),
},
});
if (selectedFeatureChoiceOption) {
NotificationUtils.dispatchMessage('Class Feature Expertise Removed', `The ${selectedFeatureChoiceOption.label} expertise was removed from the "${ClassFeatureAccessors.getName(classFeature)}" class feature.
Choose a new expertise skill in the Class section.`);
}
}
}
}
}
}
let race = yield select(rulesEngineSelectors.getRace);
if (race) {
let racialTraits = RaceAccessors.getRacialTraits(race);
for (let racialTrait of racialTraits) {
let choices = RacialTraitAccessors.getChoices(racialTrait);
for (let choice of choices) {
if (ChoiceAccessors.getType(choice) === BuilderChoiceTypeEnum.MODIFIER_SUB_CHOICE &&
ChoiceAccessors.getSubType(choice) === BuilderChoiceSubtypeEnum.EXPERTISE &&
ChoiceAccessors.getOptionValue(choice)) {
let selectedTraitChoiceOption = ChoiceAccessors.getOptions(choice).find((option) => option.id === ChoiceAccessors.getOptionValue(choice));
// TODO fix once https://intellij-support.jetbrains.com/hc/en-us/community/posts/360003813400-Typescript-expression-can-t-follow-value-has-been-checked-for-undefined-outside-of-it
let existingProficiencyModifier = builderChoiceInfo.proficiencyModifiers.find((modifier) => !!selectedTraitChoiceOption &&
ModifierAccessors.getFriendlySubtypeName(modifier) === selectedTraitChoiceOption.label);
if (!existingProficiencyModifier) {
yield call(apiRacialTraitChoiceSet, {
type: 'SIMULATED_CHOICE',
payload: {
racialTraitId: RacialTraitAccessors.getId(racialTrait),
choiceType: ChoiceAccessors.getType(choice),
choiceId: ChoiceAccessors.getId(choice),
optionValue: null,
},
});
if (selectedTraitChoiceOption) {
NotificationUtils.dispatchMessage('Racial Trait Expertise Removed', `The ${selectedTraitChoiceOption.label} expertise was removed from the "${RacialTraitAccessors.getName(racialTrait)}" racial trait.
Choose a new expertise skill in the Race section.`);
}
}
}
}
}
}
}
/**
*
*/
function* autoUpdateBackgroundChoices() {
let background = yield select(rulesEngineSelectors.getBackgroundInfo);
let choicesChanged = 0;
if (background) {
let choices = BackgroundAccessors.getChoices(background);
for (let choice of choices) {
let choiceInfo = yield select(rulesEngineSelectors.getChoiceInfo);
let defaultSubtypes = ChoiceAccessors.getDefaultSubtypes(choice);
if (defaultSubtypes.length === 1 &&
!choiceInfo.proficiencyModifiers.find((modifier) => ModifierAccessors.getFriendlySubtypeName(modifier) === defaultSubtypes[0])) {
const selectOption = ChoiceAccessors.getOptions(choice).find((option) => option.label === defaultSubtypes[0]);
if (selectOption) {
let choiceId = ChoiceAccessors.getId(choice);
if (choiceId !== null) {
yield call(apiBackgroundChoiceSet, {
type: 'SIMULATED_CHOICE',
payload: {
choiceType: ChoiceAccessors.getType(choice),
choiceId: choiceId,
optionValue: selectOption.id,
},
});
choicesChanged += 1;
}
}
}
}
}
return choicesChanged;
}
/**
*
*/
function* autoUpdateClassFeatureChoices() {
let classes = yield select(rulesEngineSelectors.getClasses);
let choicesChanged = 0;
for (let charClass of classes) {
let subclass = ClassAccessors.getSubclass(charClass);
let classId = subclass === null ? ClassAccessors.getId(charClass) : subclass.id;
let classFeatures = ClassAccessors.getClassFeatures(charClass);
for (let classFeature of classFeatures) {
let choices = ClassFeatureAccessors.getChoices(classFeature);
for (let choice of choices) {
let choiceInfo = yield select(rulesEngineSelectors.getChoiceInfo);
let defaultSubtypes = ChoiceAccessors.getDefaultSubtypes(choice);
if (defaultSubtypes.length === 1 &&
!choiceInfo.proficiencyModifiers.find((modifier) => ModifierAccessors.getFriendlySubtypeName(modifier) === defaultSubtypes[0])) {
const selectOption = ChoiceAccessors.getOptions(choice).find((option) => option.label === defaultSubtypes[0]);
if (selectOption) {
yield call(apiClassFeatureChoiceSet, {
type: 'SIMULATED_CHOICE',
payload: {
classId,
classFeatureId: ClassFeatureAccessors.getId(classFeature),
choiceType: ChoiceAccessors.getType(choice),
choiceId: ChoiceAccessors.getId(choice),
optionValue: selectOption.id,
parentChoiceId: ChoiceAccessors.getParentChoiceId(choice),
},
});
choicesChanged += 1;
}
}
}
}
}
return choicesChanged;
}
/**
*
*/
function* autoUpdateRacialTraitChoices() {
let race = yield select(rulesEngineSelectors.getRace);
let choicesChanged = 0;
if (race) {
let racialTraits = RaceAccessors.getRacialTraits(race);
for (let racialTrait of racialTraits) {
let choices = RacialTraitAccessors.getChoices(racialTrait);
for (let choice of choices) {
let choiceInfo = yield select(rulesEngineSelectors.getChoiceInfo);
let defaultSubtypes = ChoiceAccessors.getDefaultSubtypes(choice);
if (defaultSubtypes.length === 1 &&
!choiceInfo.proficiencyModifiers.find((modifier) => ModifierAccessors.getFriendlySubtypeName(modifier) === defaultSubtypes[0])) {
const selectOption = ChoiceAccessors.getOptions(choice).find((option) => option.label === defaultSubtypes[0]);
if (selectOption) {
yield call(apiRacialTraitChoiceSet, {
type: 'SIMULATED_CHOICE',
payload: {
racialTraitId: RacialTraitAccessors.getId(racialTrait),
choiceType: ChoiceAccessors.getType(choice),
choiceId: ChoiceAccessors.getId(choice),
optionValue: selectOption.id,
},
});
choicesChanged += 1;
}
}
}
}
}
return choicesChanged;
}
/**
*
* @param action
*/
export function* handleCustomProficiencyCreate(action) {
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.postCharacterCustomProficiency, action.payload);
yield put(callCommitAction(characterActions.customProficiencyAdd, data));
}
/**
*
* @param action
*/
export function* handleCustomActionCreate(action) {
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.postCharacterCustomAction, action.payload);
yield put(callCommitAction(characterActions.customActionAdd, data));
}
/**
*
* @param action
*/
export function* handleActionCustomizationsDelete(action) {
const { mappingId, mappingEntityTypeId } = action.payload;
yield put(characterActions.entityValuesRemove(mappingId, mappingEntityTypeId));
}
/**
*
* @param apiPayload
*/
export function* apiCreatureCreate(apiPayload) {
const creatureResponseData = yield call(SagaHelpers.getApiRequestData, ApiRequests.postCharacterCreature, apiPayload);
for (let i = 0; i < creatureResponseData.length; i++) {
let creatureData = creatureResponseData[i];
yield put(callCommitAction(characterActions.creatureAdd, creatureData));
}
return creatureResponseData;
}
/**
*
* @param action
*/
export function* handleCreatureCreate(action) {
yield call(apiCreatureCreate, action.payload);
if (action.meta.accept) {
action.meta.accept();
}
}
/**
*
* @param action
*/
export function* handleCreatureCustomizationsDelete(action) {
const { mappingId, mappingEntityTypeId } = action.payload;
const creatureLookup = yield select(rulesEngineSelectors.getCreatureLookup);
const creature = HelperUtils.lookupDataOrFallback(creatureLookup, mappingId);
if (creature !== null) {
const creatureProperties = {
description: null,
groupId: CreatureAccessors.getGroupId(creature),
name: null,
};
yield put(characterActions.creatureDataSet(mappingId, creatureProperties));
}
yield put(characterActions.entityValuesRemove(ValueHacks.hack__toString(mappingId), ValueHacks.hack__toString(mappingEntityTypeId)));
}
/**
*
* @param apiPayload
*/
export function* apiItemsCreate(apiPayload) {
const itemResponseData = yield call(SagaHelpers.getApiRequestData, ApiRequests.postCharacterInventoryItem, apiPayload);
const addedItems = itemResponseData.addItems === null ? [] : itemResponseData.addItems;
for (let i = 0; i < addedItems.length; i++) {
yield put(callCommitAction(characterActions.itemAdd, addedItems[i]));
}
yield call(handleDataUpdates, itemResponseData);
return addedItems;
}
/**
*
* @param action
*/
export function* handleShortRest(action) {
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.postCharacterRestShort, action.payload);
yield call(handleDataUpdates, data);
}
/**
*
* @param action
*/
export function* handleLongRest(action) {
const data = yield call(SagaHelpers.getApiRequestData, ApiRequests.postCharacterRestLong, action.payload);
yield call(handleDataUpdates, data);
}
/**
*
* @param result
*/
function* handleDataUpdates(result) {
let resultKeys = Object.keys(result);
let componentUpdates = {};
for (let i = 0; i < resultKeys.length; i++) {
switch (resultKeys[i]) {
case 'actions':
componentUpdates.actions = result.actions;
break;
case 'activeSourceCategories':
yield put(callCommitAction(characterActions.activeSourceCategoriesSet, result.activeSourceCategories));
break;
case 'background':
yield put(callCommitAction(characterActions.backgroundSet, result.background));
break;
case 'bonusHitPoints':
yield put(callCommitAction(characterActions.bonusHitPointsSet, result.bonusHitPoints));
break;
case 'classes':
yield put(callCommitAction(characterActions.classesSet, result.classes));
break;
case 'choices':
componentUpdates.choices = result.choices;
break;
case 'feats':
yield put(callCommitAction(characterActions.featsSet, result.feats));
break;
case 'baseHitPoints':
yield put(callCommitAction(characterActions.baseHitPointsSet, result.baseHitPoints));
break;
case 'modifiers':
componentUpdates.modifiers = result.modifiers;
break;
case 'options':
componentUpdates.options = result.options;
break;
case 'overrideHitPoints':
yield put(callCommitAction(characterActions.overrideHitPointsSet, result.overrideHitPoints));
break;
case 'pactMagic':
yield put(callCommitAction(characterActions.pactMagicSet, result.pactMagic));
break;
case 'race':
yield put(callCommitAction(characterActions.raceSet, result.race));
break;
case 'spellSlots':
yield put(callCommitAction(characterActions.spellSlotsSet, result.spellSlots));
break;
case 'spells':
componentUpdates.spells = result.spells;
break;
case 'temporaryHitPoints':
//TODO get separate updates for these
if (result.hasOwnProperty('removedHitPoints')) {
yield put(callCommitAction(characterActions.hitPointsSet, result.removedHitPoints, result.temporaryHitPoints));
}
break;
case 'removedHitPoints':
if (result.hasOwnProperty('temporaryHitPoints')) {
yield put(callCommitAction(characterActions.hitPointsSet, result.removedHitPoints, result.temporaryHitPoints));
}
break;
case 'conditions':
yield put(callCommitAction(characterActions.conditionsSet, result.conditions));
break;
case 'deathSaves':
yield put(callCommitAction(characterActions.deathSavesSet, result.deathSaves.failCount, result.deathSaves.successCount));
break;
}
}
let classSpells = yield select(characterSelectors.getClassSpells);
let updateClassSpells = false;
if (result.hasOwnProperty('removeClassSpells')) {
classSpells = classSpells.filter((classSpell) => !result.removeClassSpells.includes(classSpell.characterClassId));
updateClassSpells = true;
}
if (result.hasOwnProperty('updateClassSpells')) {
result.updateClassSpells.forEach((updateClassSpell) => {
let idx = classSpells.findIndex((classSpell) => classSpell.characterClassId === updateClassSpell.characterClassId);
classSpells[idx] = updateClassSpell;
});
updateClassSpells = true;
}
if (result.hasOwnProperty('addClassSpells')) {
classSpells = [...classSpells, ...result.addClassSpells];
updateClassSpells = true;
}
if (result.hasOwnProperty('classSpells')) {
classSpells = result.classSpells;
updateClassSpells = true;
}
if (updateClassSpells) {
componentUpdates['classSpells'] = classSpells;
}
if (Object.keys(componentUpdates).length) {
yield put(callCommitAction(characterActions.characterComponentsSet, componentUpdates));
}
}
export function* handleShowHelpTextSetRequest(action) {
yield call(SagaHelpers.sendApiRequest, ApiRequests.putCharacterHelpText, action.payload);
yield put(characterActions.showHelpTextSet(action.payload.showHelpText));
}
export function* handleStartingEquipmentTypeSet(action) {
yield call(SagaHelpers.sendApiRequest, ApiRequests.putCharacterInventoryStartingType, action.payload);
yield put(characterActions.startingEquipmentTypeSetCommit(action.payload));
}
export function* handleSetAbilityScoreTypeRequest(action) {
const { abilityScoreType } = action.payload;
let requestParams = {
abilityScoreType,
};
yield call(SagaHelpers.sendApiRequest, ApiRequests.putCharacterAbilityScoreType, requestParams);
yield put(characterActions.abilityScoreTypeSetCommit(abilityScoreType));
let newScoreValue;
switch (action.payload.abilityScoreType) {
case AbilityScoreTypeEnum.STANDARD_ARRAY:
case AbilityScoreTypeEnum.MANUAL:
newScoreValue = null;
break;
case AbilityScoreTypeEnum.POINT_BUY:
default:
newScoreValue = 8;
break;
}
const ruleData = yield select(rulesEngineSelectors.getRuleData);
const stats = RuleDataAccessors.getStats(ruleData);
for (let i = 0; i < stats.length; i++) {
let statData = stats[i];
yield put(callCommitAction(characterActions.abilityScoreBaseSet, statData.id, newScoreValue));
}
}
export function* handlePremadeInfoGet(action) {
const premadeCharacterResponse = yield call(SagaHelpers.sendApiRequest, ApiRequests.getPremadeInfo, { characterId: action.payload.characterId });
let responseData = ApiAdapterUtils.getResponseData(premadeCharacterResponse);
if (responseData) {
yield put(characterActions.premadeInfoSetCommit(responseData));
}
}