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
@@ -0,0 +1 @@
export const ITEM_PLAN_INVENTORY_MAPPING_CREATE = 'character.ITEM_PLAN_INVENTORY_MAPPING_CREATE';
@@ -7,7 +7,7 @@ import * as actionTypes from '../actionTypes';
* @param choiceId
* @param optionValue
*/
export function classFeatureChoiceSetRequest(classId, classFeatureId, choiceType, choiceId, optionValue, parentChoiceId) {
export function classFeatureChoiceSetRequest(classId, classFeatureId, choiceType, choiceId, optionValue, parentChoiceId, accept, reject) {
return {
type: actionTypes.CLASS_FEATURE_CHOICE_SET_REQUEST,
payload: {
@@ -18,6 +18,9 @@ export function classFeatureChoiceSetRequest(classId, classFeatureId, choiceType
optionValue,
parentChoiceId,
},
meta: {},
meta: {
accept,
reject,
},
};
}
@@ -158,7 +158,7 @@ export function itemAdd(item) {
* @param id
* @param removeContainerContents
*/
export function itemRemove(id, removeContainerContents) {
export function itemRemove(id, removeContainerContents = false) {
return {
type: actionTypes.ITEM_REMOVE,
payload: {
@@ -0,0 +1,24 @@
import * as actionTypes from '../actionTypes';
/**
*
* @param item
* @param quantity
* @param accept
* @param reject
* @param containerDefinitionKey
*/
export function itemPlanInventoryMappingCreate(itemDefinitionKey, quantity, itemPlanOriginDefinitionKey, containerDefinitionKey, accept, reject) {
return {
type: actionTypes.ITEM_PLAN_INVENTORY_MAPPING_CREATE,
payload: {
itemDefinitionKey,
quantity,
itemPlanOriginDefinitionKey,
containerDefinitionKey,
},
meta: {
accept,
reject,
},
};
}
@@ -0,0 +1 @@
export const LONG_REST_TEXT_SET = 'serviceData.LONG_REST_TEXT_SET';
@@ -30,14 +30,17 @@ export function infusionMappingAdd(infusionMapping) {
* @param infusionId
* @param inventoryMappingId
*/
export function infusionMappingDestroy(infusionId, inventoryMappingId) {
export function infusionMappingDestroy(infusionId, inventoryMappingId, accept, reject) {
return {
type: actionTypes.INFUSION_MAPPING_DESTROY,
payload: {
infusionId,
inventoryMappingId,
},
meta: {},
meta: {
accept,
reject,
},
};
}
/**
@@ -0,0 +1,8 @@
import * as actionTypes from '../actionTypes';
export function longRestTextSet(longRestText) {
return {
type: actionTypes.LONG_REST_TEXT_SET,
payload: longRestText,
meta: {},
};
}
@@ -6,15 +6,3 @@ import { ApiTypeEnum } from '../../../constants';
export const postCharacterBuilderStandardBuild = ApiAdapterUtils.makePost(ApiTypeEnum.CHARACTER_SERVICE, 'builder/standard-build', {
removeDefaultParams: true,
});
/**
*
*/
export const postCharacterBuilderRandomBuild = ApiAdapterUtils.makePost(ApiTypeEnum.CHARACTER_SERVICE, 'builder/random-build', {
removeDefaultParams: true,
});
/**
*
*/
export const postCharacterBuilderQuickBuild = ApiAdapterUtils.makePost(ApiTypeEnum.CHARACTER_SERVICE, 'builder/quick-build', {
removeDefaultParams: true,
});
@@ -24,8 +24,6 @@ export const getCharacterGameDataBackdrops = ApiAdapterUtils.makeGet(ApiTypeEnum
* This is a temporary character service endpoint and will eventually be moved to the game data service
*/
export const getCharacterGameDataFeats = ApiAdapterUtils.makeGet(ApiTypeEnum.HACK__CHARACTER_SERVICE_GAME_DATA, 'game-data/feats');
// /features/v1/features/collection?category=blessing
export const getCharacterGameDataFeatures = ApiAdapterUtils.makeGet(ApiTypeEnum.GAME_DATA_SERVICE, 'features/v1/features/collection');
/**
* This is a temporary character service endpoint and will eventually be moved to the game data service
*/
@@ -1,6 +0,0 @@
import { ApiTypeEnum } from "../../constants";
import * as ApiAdapterUtils from '../../../apiAdapter/utils';
export const getCharacterFeatures = ApiAdapterUtils.makeInterpolatedGet(ApiTypeEnum.CHARACTER_SERVICE, 'features/{characterId}', { removeDefaultParams: true });
export const postCharacterFeature = ApiAdapterUtils.makePost(ApiTypeEnum.CHARACTER_SERVICE, 'feature');
export const deleteCharacterFeature = ApiAdapterUtils.makeDelete(ApiTypeEnum.CHARACTER_SERVICE, 'feature');
export const postFeaturesAndSubfeatures = ApiAdapterUtils.makePost(ApiTypeEnum.GAME_DATA_SERVICE, 'features/v1/features/collection');
@@ -2,16 +2,16 @@ import axios from 'axios';
import { LoggerUtils } from '../logger';
import { ApiException } from './errors';
export function handleApiException(error) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
// only do something if not canceled, cancellation should be fine
if (axios.isCancel(error)) {
throw error;
}
else {
throw new ApiException((_a = error.config.url) !== null && _a !== void 0 ? _a : null, (_c = (_b = error.response) === null || _b === void 0 ? void 0 : _b.status) !== null && _c !== void 0 ? _c : null, (_d = error.config.method) !== null && _d !== void 0 ? _d : null, {
params: (_e = error.config.params) !== null && _e !== void 0 ? _e : null,
data: (_f = error.config.data) !== null && _f !== void 0 ? _f : null,
serverErrorData: (_j = (_h = (_g = error.response) === null || _g === void 0 ? void 0 : _g.data) === null || _h === void 0 ? void 0 : _h.data) !== null && _j !== void 0 ? _j : [],
throw new ApiException((_b = (_a = error.config) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : null, (_d = (_c = error.response) === null || _c === void 0 ? void 0 : _c.status) !== null && _d !== void 0 ? _d : null, (_f = (_e = error.config) === null || _e === void 0 ? void 0 : _e.method) !== null && _f !== void 0 ? _f : null, {
params: (_h = (_g = error.config) === null || _g === void 0 ? void 0 : _g.params) !== null && _h !== void 0 ? _h : null,
data: (_k = (_j = error.config) === null || _j === void 0 ? void 0 : _j.data) !== null && _k !== void 0 ? _k : null,
serverErrorData: (_m = (_l = error.response) === null || _l === void 0 ? void 0 : _l.data) !== null && _m !== void 0 ? _m : [],
});
}
}
@@ -1,4 +1,3 @@
import { PartyInventorySharingStateEnum } from './constants';
/**
*
* @param campaign
@@ -75,10 +74,10 @@ export function getPartyBaseInventoryContracts(partyInfo) {
/**
*
* @param partyInfo
* @return {Array<PartyInventoryRestrictionContract>}
*/
export function getSharingState(partyInfo) {
var _a;
return (_a = partyInfo.sharingState) !== null && _a !== void 0 ? _a : PartyInventorySharingStateEnum.OFF;
export function getPartyInventoryRestrictions(partyInfo) {
return partyInfo.partyRestrictions;
}
/**
*
@@ -1,6 +1,5 @@
export var PartyInventorySharingStateEnum;
(function (PartyInventorySharingStateEnum) {
PartyInventorySharingStateEnum[PartyInventorySharingStateEnum["OFF"] = 0] = "OFF";
PartyInventorySharingStateEnum[PartyInventorySharingStateEnum["ON"] = 1] = "ON";
PartyInventorySharingStateEnum[PartyInventorySharingStateEnum["DELETE_ONLY"] = 2] = "DELETE_ONLY";
})(PartyInventorySharingStateEnum || (PartyInventorySharingStateEnum = {}));
export var PartyRestrictionTypeEnum;
(function (PartyRestrictionTypeEnum) {
PartyRestrictionTypeEnum[PartyRestrictionTypeEnum["INFUSION"] = 0] = "INFUSION";
PartyRestrictionTypeEnum[PartyRestrictionTypeEnum["REPLICATED"] = 1] = "REPLICATED";
})(PartyRestrictionTypeEnum || (PartyRestrictionTypeEnum = {}));
@@ -1,3 +1,4 @@
import { CampaignAccessors } from '../Campaign';
import { ItemGenerators } from '../Item';
import { getPartyBaseInventoryContracts } from './accessors';
/**
@@ -11,6 +12,6 @@ import { getPartyBaseInventoryContracts } from './accessors';
*/
export function generateBasePartyInventory(partyInfo, modifierLookup, spellLookup, valueLookup, inventoryInfusionLookup, ruleData) {
return partyInfo
? ItemGenerators.generateBaseItems(getPartyBaseInventoryContracts(partyInfo), modifierLookup, spellLookup, valueLookup, inventoryInfusionLookup, ruleData)
? ItemGenerators.generateBaseItems(getPartyBaseInventoryContracts(partyInfo), modifierLookup, spellLookup, valueLookup, inventoryInfusionLookup, ruleData, CampaignAccessors.getPartyInventoryRestrictions(partyInfo))
: [];
}
@@ -1,23 +1,4 @@
import { getCharacters } from './accessors';
import { PartyInventorySharingStateEnum } from './constants';
/**
*
* @param sharingState
* @returns {Boolean}
* @description Returns true as long as the sharing state is not OFF. WARNING: DELETE_ONLY overlaps with the inactive sharing state
*/
export function isSharingStateActive(sharingState) {
return sharingState !== PartyInventorySharingStateEnum.OFF;
}
/**
*
* @param sharingState
* @returns {Boolean}
* @description Returns true as long as the sharing state is not ON. WARNING: DELETE_ONLY overlaps with the active sharing state
*/
export function isSharingStateInactive(sharingState) {
return sharingState !== PartyInventorySharingStateEnum.ON;
}
/**
*
* @param campaign
@@ -1,4 +1,4 @@
import { MovementTypeEnum, PreferenceAbilityScoreDisplayTypeEnum, PreferenceEncumbranceTypeEnum, PreferenceHitPointTypeEnum, PreferencePrivacyTypeEnum, PreferenceProgressionTypeEnum, PreferenceSharingTypeEnum, SenseTypeEnum, } from '../../Core';
import { MovementTypeEnum, PreferenceAbilityScoreDisplayTypeEnum, PreferenceEncumbranceTypeEnum, PreferenceHitPointTypeEnum, PreferenceLongRestTypeEnum, PreferencePrivacyTypeEnum, PreferenceProgressionTypeEnum, PreferenceSharingTypeEnum, SenseTypeEnum, } from '../../Core';
/**
*
* @param race
@@ -56,6 +56,7 @@ export function generateCharacterPreferences(preferencesContract, featureFlagInf
enableDarkMode: false,
useHomebrewContent: true,
enableContainerCurrency: false,
longRestType: PreferenceLongRestTypeEnum.HALF,
};
}
/**
@@ -24,6 +24,8 @@ export function getPreferenceKey(preKey) {
return preKey;
case 'ignoreCoinWeight':
return preKey;
case 'longRestType':
return preKey;
case 'primaryMovement':
return preKey;
case 'primarySense':
@@ -121,3 +121,6 @@ export function getTagConstraints(choice) {
var _a;
return (_a = choice.tagConstraints) !== null && _a !== void 0 ? _a : [];
}
export function getItemDefinitionKey(choice) {
return choice.itemDefinitionKey;
}
@@ -1,6 +1,5 @@
import { orderBy } from 'lodash';
import { TypeScriptUtils } from "../../utils";
import { ChoiceAccessors } from '.';
import { ClassAccessors } from '../Class';
import { AbilityStatEnum, BuilderChoiceSubtypeEnum, BuilderChoiceTypeEnum, } from '../Core';
import { DataOriginTypeEnum } from '../DataOrigin';
@@ -12,7 +11,7 @@ import { PrerequisiteValidators } from '../Prerequisite';
import { RuleDataAccessors } from '../RuleData';
import { SourceUtils } from '../Source';
import { SpellUtils } from '../Spell';
import { getDefaultSubtypes, getOptions, getOptionValue, isOptional } from './accessors';
import { getDefaultSubtypes, getItemDefinitionKey, getOptions, getOptionValue, getTagConstraints, getType, isOptional, } from './accessors';
export function isOnlyDefaultSelected(choice) {
const defaultSubtypes = getDefaultSubtypes(choice);
if (defaultSubtypes.length !== 1) {
@@ -71,15 +70,17 @@ export function getSortedChoiceOptionsForFeatsInfo(featId, choice, ruleData, ent
//used in FeatureChoice for all choices
// extracted from FeatureChoice.tsx
// TODO needs to be broken down into smaller utils and simplified
export function getSortedChoiceOptionsForFeaturesInfo(choice, fetchedFeats, featLookup, charClass, fetchedSubclasses, characterPreferences, prerequisiteData, ruleData, entityRestrictionData) {
var _a, _b;
export function getSortedChoiceOptionsForFeaturesInfo(choice, fetchedFeats, featLookup, charClass, fetchedSubclasses, itemShoppe, characterPreferences, prerequisiteData, ruleData, entityRestrictionData) {
var _a, _b, _c, _d;
let description;
let introDescription;
let availableOptions;
let featSubChoices;
const optionValue = ChoiceAccessors.getOptionValue(choice);
const options = ChoiceAccessors.getOptions(choice);
const type = ChoiceAccessors.getType(choice);
const tagConstraints = ChoiceAccessors.getTagConstraints(choice);
const optionValue = getOptionValue(choice);
const options = getOptions(choice);
const type = getType(choice);
const tagConstraints = getTagConstraints(choice);
const itemDefinitionKey = getItemDefinitionKey(choice);
switch (type) {
case BuilderChoiceTypeEnum.FEAT_CHOICE_OPTION:
const availableFeats = getAvailableFeatChoices(optionValue, fetchedFeats, featLookup);
@@ -153,12 +154,12 @@ export function getSortedChoiceOptionsForFeaturesInfo(choice, fetchedFeats, feat
.filter(TypeScriptUtils.isNotNullOrUndefined)
: [];
sources.forEach((source) => {
var _a, _b;
var _a;
if ((_a = source.sourceCategory) === null || _a === void 0 ? void 0 : _a.isToggleable) {
description += source.sourceCategory.description ? source.sourceCategory.description : '';
}
description += (_b = chosenSubclass.description) !== null && _b !== void 0 ? _b : '';
});
description += (_b = chosenSubclass.description) !== null && _b !== void 0 ? _b : '';
}
break;
case BuilderChoiceTypeEnum.ENTITY_SPELL_OPTION:
@@ -168,17 +169,39 @@ export function getSortedChoiceOptionsForFeaturesInfo(choice, fetchedFeats, feat
// If there is a chosen spell, set detailChoiceDesc to its description.
const chosenSpell = spellOptions.find((spell) => spell.id === optionValue);
if (chosenSpell) {
description = (_b = chosenSpell.description) !== null && _b !== void 0 ? _b : '';
description = (_c = chosenSpell.description) !== null && _c !== void 0 ? _c : '';
}
break;
default:
availableOptions = options.map((option) => (Object.assign(Object.assign({}, option), { value: option.id })));
// If every option in the array has a null sourceId, ignore sorting by sourceId.
// If any option has a non-null sourceId, sort by sourceId.
const shouldSortBySourceId = options.some((option) => option.hasOwnProperty('sourceId') && option.sourceId !== null);
if (shouldSortBySourceId) {
const optionsToGroup = SourceUtils.getSimpleSourcedDefinitionContracts(options);
availableOptions = SourceUtils.getGroupedOptionsBySourceCategory(optionsToGroup, ruleData, optionValue, entityRestrictionData);
const chosenOption = optionsToGroup.find((option) => option.id === optionValue);
if (chosenOption) {
description = (_d = chosenOption.description) !== null && _d !== void 0 ? _d : '';
}
}
else {
availableOptions = options.map((option) => (Object.assign(Object.assign({}, option), { value: option.id })));
}
}
if (itemDefinitionKey !== null && optionValue !== null) {
const item = itemShoppe === null || itemShoppe === void 0 ? void 0 : itemShoppe.getInventoryItems().items.find((item) => item.getDefinitionKey() === itemDefinitionKey);
//If there is a chosen item mapping and it exists in the item shoppe, set the choice description to the item's description. and return the intro text to display as well.
if (item) {
description = item.getDescription() || '';
introDescription = item.getIntroText();
}
}
return {
choice,
options: availableOptions,
featSubChoices,
description,
introDescription,
};
}
// filter Choice options based on modifiers and default subtypes that identify when a user has made a choice from another source. We filter those options out or we append the source data origin name next to the option label
@@ -388,6 +388,9 @@ export function getEnablesPactWeapon(charClass) {
export function getEnablesDedicatedWeapon(charClass) {
return charClass.enablesDedicatedWeapon;
}
export function getEnablesReplicateMagicItem(charClass) {
return charClass.enablesReplicateMagicItem;
}
/**
*
* @param charClass
@@ -7,7 +7,7 @@ import { HelperUtils } from '../Helper';
import { ModifierAccessors, ModifierValidators } from '../Modifier';
import { OptionalClassFeatureAccessors } from '../OptionalClassFeature';
import { DefaultSpellCastingLearningStyle, SpellDerivers } from '../Spell';
import { getActiveId, getDefinitionClassFeatures, getDefinitionSpellcastingStatId, getId, getLevel, getMappingId, getSubclass, getClassFeatures, getDefinitionSpellCastingLearningStyle, } from './accessors';
import { getActiveId, getDefinitionClassFeatures, getDefinitionSpellcastingStatId, getId, getLevel, getMappingId, getSubclass, getClassFeatures, getDefinitionSpellCastingLearningStyle, getEnablesReplicateMagicItem, } from './accessors';
/**
*
* @param charClass
@@ -183,10 +183,11 @@ export function deriveReplacementWeaponAbilityStats(classModifiers) {
*
* @param classFeatures
*/
export function deriveDedicatedHexAndPactWeaponEnabled(classFeatures, classModifiers) {
export function deriveFeaturesEnabled(classFeatures, classModifiers) {
let enablesHexWeapon = false;
let enablesPactWeapon = false;
let enablesDedicatedWeapon = false;
let enablesReplicateMagicItem = false;
classModifiers.forEach((modifier) => {
if (!enablesPactWeapon && ModifierValidators.isEnablePactWeaponModifier(modifier)) {
enablesPactWeapon = true;
@@ -196,6 +197,10 @@ export function deriveDedicatedHexAndPactWeaponEnabled(classFeatures, classModif
enablesHexWeapon = true;
return;
}
if (!enablesReplicateMagicItem && ModifierValidators.isEnableReplicateMagicItemModifier(modifier)) {
enablesReplicateMagicItem = true;
return;
}
});
//TODO remove this once we have dedicated weapon driven by modifiers
classFeatures.forEach((feature) => {
@@ -207,6 +212,7 @@ export function deriveDedicatedHexAndPactWeaponEnabled(classFeatures, classModif
enablesHexWeapon,
enablesPactWeapon,
enablesDedicatedWeapon,
enablesReplicateMagicItem,
};
}
/**
@@ -310,3 +316,20 @@ export function deriveMaxInfusions(charClass) {
}
return null;
}
//Derives the maximum number of replicated magic items based on if the class enables the replicate magic item feature and if any of its class features have item mappings.
// It sums up the level scale fixed values and returns the total as the maximum number of replicated magic items.
export function deriveMaxReplicatedMagicItems(charClass) {
const enablesReplicateMagicItem = getEnablesReplicateMagicItem(charClass);
const features = getClassFeatures(charClass);
const replicateFeatures = features.filter((classFeature) => ClassFeatureAccessors.getHasItemMappings(classFeature));
if (enablesReplicateMagicItem) {
let maxCount = 0;
replicateFeatures.forEach((replicateFeature) => {
var _a, _b;
const levelScale = (_b = (_a = ClassFeatureAccessors.getLevelScale(replicateFeature)) === null || _a === void 0 ? void 0 : _a.fixedValue) !== null && _b !== void 0 ? _b : 0;
maxCount += levelScale;
});
return maxCount;
}
return null;
}
@@ -9,7 +9,7 @@ import { ModifierDerivers, ModifierValidators } from '../Modifier';
import { OptionAccessors } from '../Option';
import { SpellAccessors, SpellDerivers, SpellGenerators, } from '../Spell';
import { getActiveId, getBaseClassSpells, getClassFeatures, getDefinitionClassFeatures, getEnablesDedicatedWeapon, getEnablesHexWeapon, getEnablesPactWeapon, getId, getLevel, getMappingId, getName, getReplacementWeaponAbilityStats, getSlug, getSpellPrepareType, getSpellRules, getSpells, getSubclass, isPactMagicActive, isSpellcastingActive, isStartingClass, } from './accessors';
import { deriveActiveId, deriveClassFeatureGroups, deriveConsolidatedClassFeatures, deriveDedicatedHexAndPactWeaponEnabled, deriveIsPactMagicActive, deriveIsSpellcastingActive, deriveReplacementWeaponAbilityStats, deriveSpellCastingLearningStyle, deriveSpellListIds, deriveSpellRules, deriveSpells, deriveUniqueKey, } from './derivers';
import { deriveActiveId, deriveClassFeatureGroups, deriveConsolidatedClassFeatures, deriveFeaturesEnabled, deriveIsPactMagicActive, deriveIsSpellcastingActive, deriveReplacementWeaponAbilityStats, deriveSpellCastingLearningStyle, deriveSpellListIds, deriveSpellRules, deriveSpells, deriveUniqueKey, } from './derivers';
export function generateBaseCharClasses(charClasses, optionalClassFeatures, allClassSpells, allClassAlwaysPreparedSpells, allClassAlwaysKnownSpells, appContext, ruleData, classesLookupData, definitionPool, characterPreferences, characterId, choiceComponents, baseFeats) {
const orderedClasses = orderBy(charClasses, [(charClass) => isStartingClass(charClass), (charClass) => getName(charClass)], ['desc', 'asc']);
const hack__baseCharClasses = orderedClasses.map((charClass) => hack__generateHackBaseCharClass(charClass, optionalClassFeatures, ruleData, classesLookupData, characterPreferences.enableOptionalClassFeatures));
@@ -65,12 +65,13 @@ export function generateBaseCharClass(charClass, optionalClassFeatures, allClass
const { activeFeatures, visibleFeatures, orderedFeatures, uniqueFeatures, higherLevelFeatures } = deriveClassFeatureGroups(charClass, updatedFeatures, appContext);
// do any class features or modifiers enable hex, pact or dedicated weapons
//TODO eventually should just be able to use modifiers here and not have to check class feature names
const { enablesHexWeapon, enablesPactWeapon, enablesDedicatedWeapon } = deriveDedicatedHexAndPactWeaponEnabled(updatedFeatures, classModifiers);
const { enablesHexWeapon, enablesPactWeapon, enablesDedicatedWeapon, enablesReplicateMagicItem } = deriveFeaturesEnabled(updatedFeatures, classModifiers);
return Object.assign(Object.assign({}, charClass), { activeId: deriveActiveId(charClass), spells: SpellGenerators.updateClassSpells(featureSpells, classSpells), modifiers: classModifiers, featureSpells, classFeatures: updatedFeatures, optionalClassFeatures, activeClassFeatures: activeFeatures, visibleClassFeatures: visibleFeatures, orderedClassFeatures: orderedFeatures, uniqueClassFeatures: uniqueFeatures, higherLevelClassFeatures: higherLevelFeatures, feats,
actions,
enablesDedicatedWeapon,
enablesHexWeapon,
enablesPactWeapon, slug: FormatUtils.cobaltSlugify(getName(charClass)), spellListIds: deriveSpellListIds(updatedFeatures, getLevel(charClass)), spellCastingLearningStyle: deriveSpellCastingLearningStyle(charClass), replacementWeaponAbilityStats: deriveReplacementWeaponAbilityStats(classModifiers) });
enablesPactWeapon,
enablesReplicateMagicItem, slug: FormatUtils.cobaltSlugify(getName(charClass)), spellListIds: deriveSpellListIds(updatedFeatures, getLevel(charClass)), spellCastingLearningStyle: deriveSpellCastingLearningStyle(charClass), replacementWeaponAbilityStats: deriveReplacementWeaponAbilityStats(classModifiers) });
}
export function generateClassFeatureLookup(baseCharClasses) {
const lookup = {};
@@ -416,6 +416,9 @@ export function getInfusionRules(feature) {
var _a, _b;
return (_b = (_a = getDefinition(feature)) === null || _a === void 0 ? void 0 : _a.infusionRules) !== null && _b !== void 0 ? _b : [];
}
export function getItemPlans(feature) {
return feature.itemPlans;
}
/**
*
* @param feature
@@ -427,3 +430,7 @@ export function getFeatListContracts(featureContract) {
var _a, _b;
return (_b = (_a = getDefinition(featureContract)) === null || _a === void 0 ? void 0 : _a.grantedFeats) !== null && _b !== void 0 ? _b : [];
}
export function getHasItemMappings(feature) {
var _a, _b;
return (_b = (_a = getDefinition(feature)) === null || _a === void 0 ? void 0 : _a.hasItemMappings) !== null && _b !== void 0 ? _b : false;
}
@@ -7,10 +7,11 @@ import { DefinitionPoolUtils } from '../DefinitionPool';
import { FeatGenerators } from '../Feat';
import { FeatList } from '../FeatList';
import { InfusionChoiceGenerators } from '../InfusionChoice';
import { ItemPlanGenerators } from '../ItemPlan';
import { ModifierGenerators } from '../Modifier';
import { OptionAccessors, OptionGenerators } from '../Option';
import { SpellGenerators } from '../Spell';
import { getCreatureRules, getDefinitionKey, getEntityTypeId, getFeatListContracts, getId, getInfusionRules, getOptions, getUniqueKey, } from './accessors';
import { getCreatureRules, getDefinitionKey, getEntityTypeId, getFeatListContracts, getHasItemMappings, getId, getInfusionRules, getOptions, getUniqueKey, } from './accessors';
export function generateClassFeature(charClass, classFeature, ruleData, classesLookupData, definitionPool, characterId, choiceComponents, baseFeats) {
const id = getId(classFeature);
const entityTypeId = getEntityTypeId(classFeature);
@@ -22,6 +23,9 @@ export function generateClassFeature(charClass, classFeature, ruleData, classesL
const feats = FeatGenerators.generateDataOriginFeats(id, entityTypeId, featLookup, DataOriginTypeEnum.CLASS_FEATURE, classFeature, charClass);
const options = generateClassFeatureOptions(classFeature, charClass, featLookup, actionLookup, spellLookup, optionLookup, modifierLookup, valueLookup, spellListDataOriginLookup, ruleData);
const infusionChoices = InfusionChoiceGenerators.generateInfusionChoices(getInfusionRules(classFeature), DataOriginTypeEnum.CLASS_FEATURE, classFeature, charClass, infusionChoiceInfusionLookup, knownInfusionLookupByChoiceKey, characterId);
const itemPlans = getHasItemMappings(classFeature)
? ItemPlanGenerators.generateItemPlans(choices, options, DataOriginTypeEnum.CLASS_FEATURE, classFeature, charClass)
: [];
const featLists = getFeatListContracts(classFeature).map((contract) => new FeatList(contract, choiceComponents.definitionKeyNameMap, baseFeats));
return Object.assign(Object.assign({}, classFeature), { actions,
choices,
@@ -29,7 +33,8 @@ export function generateClassFeature(charClass, classFeature, ruleData, classesL
modifiers,
spells,
options,
infusionChoices, accessType: DefinitionPoolUtils.getDefinitionAccessType(getDefinitionKey(classFeature), definitionPool), featLists });
infusionChoices,
itemPlans, accessType: DefinitionPoolUtils.getDefinitionAccessType(getDefinitionKey(classFeature), definitionPool), featLists });
}
/**
*
@@ -31,7 +31,7 @@ classes, choiceComponents, baseFeats) {
const featLists = choiceComponents && baseFeats
? getFeatListContracts(classFeatureMapping).map((contract) => new FeatList(contract, choiceComponents.definitionKeyNameMap, baseFeats))
: [];
return Object.assign(Object.assign({}, classFeatureMapping), { actions: [], choices: [], feats: [], modifiers: [], options: [], spells: [], infusionChoices: [], accessType: DefinitionPoolUtils.getDefinitionAccessType(definitionKey, definitionPool), featLists });
return Object.assign(Object.assign({}, classFeatureMapping), { actions: [], choices: [], feats: [], modifiers: [], options: [], spells: [], infusionChoices: [], accessType: DefinitionPoolUtils.getDefinitionAccessType(definitionKey, definitionPool), featLists, itemPlans: [] });
}
/**
*
@@ -1,9 +1,8 @@
import { keyBy, sortBy } from 'lodash';
import { ConfigUtils } from '../../config';
import { CampaignAccessors, CampaignUtils } from '../Campaign';
import { CampaignAccessors } from '../Campaign';
import { generateCoinWeight } from '../Character/generators';
import { DefinitionHacks } from '../Definition';
import { FeatureFlagEnum, FeatureFlagInfoUtils } from '../FeatureFlagInfo';
import { ItemAccessors, ItemDerivers, ItemValidators } from '../Item';
import { getDefinitionKey, getName, isShared } from './accessors';
import { ContainerTypeEnum } from './constants';
@@ -23,18 +22,12 @@ function generateInitialCoins() {
* @param characterId
* @param partyInfo
* @param partyInventory
* @param featureFlagInfo
* @param characterCoin
* @param preferences
* @param ruleData
*/
export function generateContainers(items, customItems, characterId, partyInfo, partyInventory, featureFlagInfo, characterCoin, preferences, ruleData) {
let sharingStateActive = false;
if (partyInfo && CampaignUtils.isSharingStateActive(CampaignAccessors.getSharingState(partyInfo))) {
sharingStateActive = true;
}
const partyInventoryActive = FeatureFlagInfoUtils.getFeatureFlagInfoValue(FeatureFlagEnum.RELEASE_GATE_IMS, featureFlagInfo) &&
sharingStateActive;
export function generateContainers(items, customItems, characterId, partyInfo, partyInventory, characterCoin, preferences, ruleData) {
const partyInventoryActive = partyInfo !== null;
const rulesEngineConfig = ConfigUtils.getCurrentRulesEngineConfig();
// Do we want to display Cointainers? You betcha!
const canCointainer = (preferences === null || preferences === void 0 ? void 0 : preferences.enableContainerCurrency) && (rulesEngineConfig === null || rulesEngineConfig === void 0 ? void 0 : rulesEngineConfig.canUseCurrencyContainers);
@@ -64,8 +57,9 @@ export function generateContainers(items, customItems, characterId, partyInfo, p
});
}
const playerContainers = sortBy(itemContainers, (container) => getName(container));
const sortedPartyContainers = sortBy(partyContainers, (container) => getName(container));
return partyInventoryActive && partyEquipmentContainer
? [equipmentContainer, ...playerContainers, partyEquipmentContainer, ...partyContainers]
? [equipmentContainer, ...playerContainers, partyEquipmentContainer, ...sortedPartyContainers]
: [equipmentContainer, ...playerContainers];
}
/**
@@ -1,7 +1,5 @@
import { ContainerAccessors } from '.';
import { PartyInventorySharingStateEnum } from '../Campaign';
import { ItemAccessors } from '../Item';
import { getDefinitionKey, getItemMappingIds, getName } from './accessors';
import { getDefinitionKey, getItemMappingIds, getName, isShared } from './accessors';
import { ContainerTypeEnum } from './constants';
/**
*
@@ -51,14 +49,14 @@ export function getInventoryItems(container, inventory) {
* @param containers
* @returns {Array<MenuOption>}
*/
export function getGroupedOptions(currentContainerDefinitionKey, containers, label, sharingState) {
export function getGroupedOptions(currentContainerDefinitionKey, containers, label) {
const excludedKeys = [];
if (currentContainerDefinitionKey) {
excludedKeys.push(currentContainerDefinitionKey);
}
const availableContainers = getAvailableContainers(containers, excludedKeys);
const characterContainers = availableContainers.filter((container) => !ContainerAccessors.isShared(container));
const partyContainers = availableContainers.filter(ContainerAccessors.isShared);
const characterContainers = availableContainers.filter((container) => !isShared(container));
const partyContainers = availableContainers.filter(isShared);
const options = [];
if (characterContainers.length) {
options.push({
@@ -69,9 +67,8 @@ export function getGroupedOptions(currentContainerDefinitionKey, containers, lab
})),
});
}
// Only display Party inventory if there are containers and the sharing state is turned to on
// ( e.g. hide these containers as options when OFF and in DELETE_ONLY )
if (partyContainers.length && sharingState === PartyInventorySharingStateEnum.ON) {
// Only display Party inventory if there are containers
if (partyContainers.length) {
options.push({
label: `${!characterContainers.length ? `${label} ` : ''}Party Inventory`,
options: partyContainers.map((container) => ({
@@ -160,6 +160,11 @@ export var PreferenceProgressionTypeEnum;
PreferenceProgressionTypeEnum[PreferenceProgressionTypeEnum["MILESTONE"] = 1] = "MILESTONE";
PreferenceProgressionTypeEnum[PreferenceProgressionTypeEnum["XP"] = 2] = "XP";
})(PreferenceProgressionTypeEnum || (PreferenceProgressionTypeEnum = {}));
export var PreferenceLongRestTypeEnum;
(function (PreferenceLongRestTypeEnum) {
PreferenceLongRestTypeEnum[PreferenceLongRestTypeEnum["HALF"] = 1] = "HALF";
PreferenceLongRestTypeEnum[PreferenceLongRestTypeEnum["FULL"] = 2] = "FULL";
})(PreferenceLongRestTypeEnum || (PreferenceLongRestTypeEnum = {}));
export var SpeedMovementKeyEnum;
(function (SpeedMovementKeyEnum) {
SpeedMovementKeyEnum["WALK"] = "walk";
@@ -1,27 +1,16 @@
export var FeatureFlagEnum;
(function (FeatureFlagEnum) {
// Brian added this in 06/2021 expected to be removed in 10/2021
FeatureFlagEnum["RELEASE_GATE_IMS"] = "release-gate-ims";
// Julie added this on 4/19/22 expected to be removed in ?? TODO
FeatureFlagEnum["RELEASE_GATE_GFS_BLESSINGS_UI"] = "release-gate-gfs-blessings-ui";
// Brian added this on 10/24/22 expected to remove in 11/2022
FeatureFlagEnum["RELEASE_GATE_CAMPAIGN_SETTINGS"] = "release-gate-character-sheet-campaign-settings";
// Tim added this on 02/23/23 expected to remove in ??
FeatureFlagEnum["RELEASE_GATE_CHARACTER_SHEET_TOUR"] = "release-gate-character-sheet-tour";
// Brittany added this on 2/24/2023
FeatureFlagEnum["RELEASE_GATE_PREMADE_CHARACTERS"] = "release-gate-premade-characters";
})(FeatureFlagEnum || (FeatureFlagEnum = {}));
export const FEATURE_FLAG_LIST = [
FeatureFlagEnum.RELEASE_GATE_IMS,
FeatureFlagEnum.RELEASE_GATE_GFS_BLESSINGS_UI,
FeatureFlagEnum.RELEASE_GATE_CAMPAIGN_SETTINGS,
FeatureFlagEnum.RELEASE_GATE_CHARACTER_SHEET_TOUR,
FeatureFlagEnum.RELEASE_GATE_PREMADE_CHARACTERS,
];
export const DEFAULT_FEATURE_FLAG_INFO = {
[FeatureFlagEnum.RELEASE_GATE_IMS]: false,
[FeatureFlagEnum.RELEASE_GATE_GFS_BLESSINGS_UI]: false,
[FeatureFlagEnum.RELEASE_GATE_CAMPAIGN_SETTINGS]: false,
[FeatureFlagEnum.RELEASE_GATE_CHARACTER_SHEET_TOUR]: false,
[FeatureFlagEnum.RELEASE_GATE_PREMADE_CHARACTERS]: false,
};
@@ -51,6 +51,27 @@ export function getDefinitionEntityTypeId(item) {
var _a, _b;
return (_b = (_a = item.definition) === null || _a === void 0 ? void 0 : _a.entityTypeId) !== null && _b !== void 0 ? _b : -1;
}
/**
*
* @param item
*/
export function getOriginEntityId(item) {
return item.originEntityId;
}
/**
*
* @param item
*/
export function getOriginEntityTypeId(item) {
return item.originEntityTypeId;
}
/**
*
* @param item
*/
export function getOriginDefinitionKey(item) {
return item.originDefinitionKey;
}
/**
*
* @param item
@@ -1046,3 +1067,6 @@ export function getReplacementWeaponStats(item) {
export function getAppliedWeaponReplacementStats(item) {
return item.appliedWeaponReplacementStats;
}
export function getPartyInventoryRestrictions(item) {
return item.partyInventoryRestrictions;
}
@@ -987,3 +987,8 @@ export function deriveSecondarySourceCategoryIds(item, ruleData) {
return ((_a = sourceData === null || sourceData === void 0 ? void 0 : sourceData.sourceCategory) === null || _a === void 0 ? void 0 : _a.id) || 0;
});
}
export function derivePartyInventoryRestrictions(item, partyInventoryRestrictions) {
return partyInventoryRestrictions.filter((restriction) => {
return restriction.inventoryMappingId === getMappingId(item) && restriction.itemId === getDefinitionId(item);
});
}
@@ -24,9 +24,9 @@ import { ModifierDerivers, ModifierGenerators, ModifierValidators, } from '../Mo
import { RuleDataAccessors } from '../RuleData';
import { SpellGenerators } from '../Spell';
import { AdjustmentTypeEnum, ValueAccessors, ValueHacks, ValueUtils, } from '../Value';
import { getDefinitionDamage, getEntityTypeId, getFixedDamage, getId, getItemWeaponBehaviors, getLimitedUse, getMappingEntityTypeId, getMappingId, getModifiers, getName, getUniqueKey, isAttuned, isDisplayAsAttack, isEquipped, } from './accessors';
import { getDefinitionDamage, getEntityTypeId, getFixedDamage, getId, getItemWeaponBehaviors, getLimitedUse, getMappingEntityTypeId, getMappingId, getModifiers, getName, getOriginDefinitionKey, getUniqueKey, isAttuned, isDisplayAsAttack, isEquipped, } from './accessors';
import { ItemBaseTypeEnum, ItemRarityNameEnum, WeaponPropertyEnum } from './constants';
import { deriveArmorClass, deriveAvailableWeaponAbilities, deriveAvatarUrl, deriveBaseType, deriveCanAttune, deriveCanBeDedicatedWeapon, deriveCanEquip, deriveCanHexWeapon, deriveCanWeaponOffhand, deriveCapacityWeight, deriveCategoryInfo, deriveCost, deriveDamageType, deriveDefinitionKey, deriveInfusionActions, deriveIsAdamantine, deriveIsCustomized, deriveIsDisplayAsAttack, deriveIsMagic, deriveIsMonkWeapon, deriveIsOffhand, deriveIsProficient, deriveIsSilvered, deriveLimitedUse, deriveMasteryName, deriveName, deriveNotes, deriveOriginalCustomContract, derivePrimarySourceCategoryId, deriveProperties, deriveSecondarySourceCategoryIds, deriveStealthCheck, deriveType, deriveValidGlobalModifiersFromArmor, deriveValidGlobalModifiersFromGear, deriveValidGlobalModifiersFromWeapons, deriveWeaponAdditionalDamage, deriveWeaponReach, deriveWeight, } from './derivers';
import { deriveArmorClass, deriveAvailableWeaponAbilities, deriveAvatarUrl, deriveBaseType, deriveCanAttune, deriveCanBeDedicatedWeapon, deriveCanEquip, deriveCanHexWeapon, deriveCanWeaponOffhand, deriveCapacityWeight, deriveCategoryInfo, deriveCost, deriveDamageType, deriveDefinitionKey, deriveInfusionActions, deriveIsAdamantine, deriveIsCustomized, deriveIsDisplayAsAttack, deriveIsMagic, deriveIsMonkWeapon, deriveIsOffhand, deriveIsProficient, deriveIsSilvered, deriveLimitedUse, deriveMasteryName, deriveName, deriveNotes, deriveOriginalCustomContract, derivePartyInventoryRestrictions, derivePrimarySourceCategoryId, deriveProperties, deriveSecondarySourceCategoryIds, deriveStealthCheck, deriveType, deriveValidGlobalModifiersFromArmor, deriveValidGlobalModifiersFromGear, deriveValidGlobalModifiersFromWeapons, deriveWeaponAdditionalDamage, deriveWeaponReach, deriveWeight, } from './derivers';
import { hasItemWeaponBehaviors, hasWeaponProperty, isAmmunition, isArmorContract, isBaseWeapon, isGearContract, isWeaponContract, } from './utils';
import { isCustomItem, isEquippedToCurrentCharacter, validateIsWeaponLike } from './validators';
/**
@@ -47,7 +47,7 @@ export function generateCustomItems(customItems, ruleData, characterId) {
export function generateCustomItem(customItem, ruleData, characterId) {
var _a, _b;
const { id, quantity, notes, weight } = customItem, customItemData = __rest(customItem, ["id", "quantity", "notes", "weight"]);
const definition = Object.assign(Object.assign({}, customItemData), { armorTypeId: null, armorClass: null, attackType: null, attunementDescription: null, avatarUrl: RuleDataAccessors.getDefaultGearImageUrl(ruleData), baseArmorName: null, baseItemId: null, baseTypeId: -1, bundleSize: 1, canAttune: false, canBeAddedToInventory: false, canEquip: false, capacity: null, capacityWeight: 0, categoryId: null, damage: null, damageType: null, entityTypeId: -1, filterType: null, fixedDamage: null, gearTypeId: null, grantedModifiers: [], groupedId: null, id: -1, isConsumable: false, isContainer: false, isHomebrew: false, isLegacy: false, isMonkWeapon: false, isPack: false, largeAvatarUrl: null, levelInfusionGranted: null, longRange: null, magic: false, properties: [], range: null, rarity: ItemRarityNameEnum.COMMON, snippet: null, sourcePageNumber: null, sources: [], stackable: true, stealthCheck: StealthCheckTypeEnum.NONE, strengthRequirement: null, subType: null, tags: [], type: 'Custom', version: null, weaponBehaviors: [], weight: weight !== null && weight !== void 0 ? weight : 0, weightMultiplier: 1 });
const definition = Object.assign(Object.assign({}, customItemData), { armorTypeId: null, armorClass: null, attackType: null, attunementDescription: null, avatarUrl: RuleDataAccessors.getDefaultGearImageUrl(ruleData), baseArmorName: null, baseItemId: null, baseTypeId: -1, bundleSize: 1, canAttune: false, canBeAddedToInventory: false, canEquip: false, capacity: null, capacityWeight: 0, categoryId: null, damage: null, damageType: null, definitionKey: '-1:-1', entityTypeId: -1, filterType: null, fixedDamage: null, gearTypeId: null, grantedModifiers: [], groupedId: null, id: -1, isConsumable: false, isContainer: false, isHomebrew: false, isLegacy: false, isMonkWeapon: false, isPack: false, largeAvatarUrl: null, levelInfusionGranted: null, longRange: null, magic: false, properties: [], range: null, rarity: ItemRarityNameEnum.COMMON, snippet: null, sourcePageNumber: null, sources: [], stackable: true, stealthCheck: StealthCheckTypeEnum.NONE, strengthRequirement: null, subType: null, tags: [], type: 'Custom', version: null, weaponBehaviors: [], weight: weight !== null && weight !== void 0 ? weight : 0, weightMultiplier: 1 });
const simulatedContract = {
chargesUsed: 0,
containerEntityId: characterId,
@@ -65,9 +65,12 @@ export function generateCustomItem(customItem, ruleData, characterId) {
id,
isAttuned: false,
limitedUse: null,
originEntityId: null,
originEntityTypeId: null,
originDefinitionKey: null,
quantity: quantity !== null && quantity !== void 0 ? quantity : 1,
};
return Object.assign(Object.assign({}, simulatedContract), { avatarUrl: (_a = definition.avatarUrl) !== null && _a !== void 0 ? _a : '', armorClass: null, baseItemType: ItemBaseTypeEnum.GEAR, additionalDamages: [], versatileDamage: null, reach: null, canAttune: false, canBeDedicatedWeapon: false, canEquip: false, canHexWeapon: false, canOffhand: false, canPactWeapon: false, categoryInfo: null, definitionKey: '', hexWeaponEnabled: false, isDedicatedWeapon: false, isHexWeapon: false, isPactWeapon: false, metaItems: [], pactWeaponEnabled: false, toHit: null, damage: null, infusion: null, infusionActions: [], isOffhand: false, isAdamantine: false, isMagic: false, isSilvered: false, damageType: definition.damageType, displayAsAttack: false, isCustom: true, isCustomized: false, isDisplayAsAttack: false, proficiency: false, modifiers: [], originalContract: customItem, spells: [], type: null, name: (_b = definition.name) !== null && _b !== void 0 ? _b : 'Unidentified Item', cost: deriveCost(simulatedContract, {}), weight: deriveWeight(simulatedContract, {}), capacityWeight: deriveCapacityWeight(simulatedContract, {}), notes, properties: [], stealthCheck: StealthCheckTypeEnum.NONE, masteryName: null, primarySourceCategoryId: -1, secondarySourceCategoryIds: [], replacementWeaponStats: [], appliedWeaponReplacementStats: [] });
return Object.assign(Object.assign({}, simulatedContract), { avatarUrl: (_a = definition.avatarUrl) !== null && _a !== void 0 ? _a : '', armorClass: null, baseItemType: ItemBaseTypeEnum.GEAR, additionalDamages: [], versatileDamage: null, reach: null, canAttune: false, canBeDedicatedWeapon: false, canEquip: false, canHexWeapon: false, canOffhand: false, canPactWeapon: false, categoryInfo: null, definitionKey: '', hexWeaponEnabled: false, isDedicatedWeapon: false, isHexWeapon: false, isPactWeapon: false, metaItems: [], pactWeaponEnabled: false, toHit: null, damage: null, infusion: null, infusionActions: [], isOffhand: false, isAdamantine: false, isMagic: false, isSilvered: false, damageType: definition.damageType, displayAsAttack: false, isCustom: true, isCustomized: false, isDisplayAsAttack: false, proficiency: false, modifiers: [], originalContract: customItem, spells: [], type: null, name: (_b = definition.name) !== null && _b !== void 0 ? _b : 'Unidentified Item', cost: deriveCost(simulatedContract, {}), weight: deriveWeight(simulatedContract, {}), capacityWeight: deriveCapacityWeight(simulatedContract, {}), notes, properties: [], stealthCheck: StealthCheckTypeEnum.NONE, masteryName: null, primarySourceCategoryId: -1, secondarySourceCategoryIds: [], replacementWeaponStats: [], appliedWeaponReplacementStats: [], partyInventoryRestrictions: [] });
}
/**
*
@@ -81,7 +84,7 @@ export function generateWeaponBehaviorBaseItem(baseWeapon, parentItem, valueLook
return null;
}
const item = Object.assign(Object.assign({}, parentItem), { definition: Object.assign(Object.assign(Object.assign({}, parentItem.definition), baseWeapon), { fixedDamage: null }) });
return Object.assign(Object.assign({}, item), { avatarUrl: deriveAvatarUrl(item, ruleData), baseItemType: ItemBaseTypeEnum.WEAPON, canAttune: deriveCanAttune(item, null), canEquip: deriveCanEquip(item, null), categoryInfo: deriveCategoryInfo(item, ruleData), cost: deriveCost(item, valueLookup), damageType: deriveDamageType(item, []), definitionKey: deriveDefinitionKey(item), isAdamantine: deriveIsAdamantine(item, valueLookup), isCustom: isCustomItem(item), isCustomized: deriveIsCustomized(item, valueLookup), isDisplayAsAttack: deriveIsDisplayAsAttack(item, valueLookup), isOffhand: deriveIsOffhand(item, valueLookup), isSilvered: deriveIsSilvered(item, valueLookup), isMagic: deriveIsMagic(item, null), infusion: null, infusionActions: [], modifiers: getModifiers(parentItem), name: deriveName(item, valueLookup), notes: deriveNotes(item, valueLookup), originalContract: null, properties: deriveProperties(item, [], ruleData), spells: [], type: deriveType(item, ruleData), weight: deriveWeight(item, valueLookup), capacityWeight: deriveCapacityWeight(item, valueLookup) });
return Object.assign(Object.assign({}, item), { avatarUrl: deriveAvatarUrl(item, ruleData), baseItemType: ItemBaseTypeEnum.WEAPON, canAttune: deriveCanAttune(item, null), canEquip: deriveCanEquip(item, null), categoryInfo: deriveCategoryInfo(item, ruleData), cost: deriveCost(item, valueLookup), damageType: deriveDamageType(item, []), definitionKey: deriveDefinitionKey(item), isAdamantine: deriveIsAdamantine(item, valueLookup), isCustom: isCustomItem(item), isCustomized: deriveIsCustomized(item, valueLookup), isDisplayAsAttack: deriveIsDisplayAsAttack(item, valueLookup), isOffhand: deriveIsOffhand(item, valueLookup), isSilvered: deriveIsSilvered(item, valueLookup), isMagic: deriveIsMagic(item, null), infusion: null, infusionActions: [], modifiers: getModifiers(parentItem), name: deriveName(item, valueLookup), notes: deriveNotes(item, valueLookup), originalContract: null, properties: deriveProperties(item, [], ruleData), spells: [], type: deriveType(item, ruleData), weight: deriveWeight(item, valueLookup), capacityWeight: deriveCapacityWeight(item, valueLookup), partyInventoryRestrictions: [] });
}
/**
*
@@ -294,8 +297,8 @@ export function generateItem(item, abilityLookup, proficiencyBonus, modifiers, e
* @param inventoryInfusionLookup
* @param ruleData
*/
export function generateBaseItems(items, modifierLookup, spellLookup, valueLookup, inventoryInfusionLookup, ruleData) {
return items.map((item) => generateBaseItem(item, modifierLookup, spellLookup, valueLookup, inventoryInfusionLookup, ruleData));
export function generateBaseItems(items, modifierLookup, spellLookup, valueLookup, inventoryInfusionLookup, ruleData, partyInventoryRestrictions = []) {
return items.map((item) => generateBaseItem(item, modifierLookup, spellLookup, valueLookup, inventoryInfusionLookup, ruleData, partyInventoryRestrictions));
}
/**
*
@@ -306,7 +309,7 @@ export function generateBaseItems(items, modifierLookup, spellLookup, valueLooku
* @param inventoryInfusionLookup
* @param ruleData
*/
export function generateBaseItem(item, modifierLookup, spellLookup, valueLookup, inventoryInfusionLookup, ruleData) {
export function generateBaseItem(item, modifierLookup, spellLookup, valueLookup, inventoryInfusionLookup, ruleData, partyInventoryRestrictions = []) {
const id = getId(item);
const entityTypeId = getEntityTypeId(item);
const infusion = HelperUtils.lookupDataOrFallback(inventoryInfusionLookup, getMappingId(item));
@@ -322,7 +325,7 @@ export function generateBaseItem(item, modifierLookup, spellLookup, valueLookup,
const infusionActions = deriveInfusionActions(infusion, valueLookup, item);
const notes = deriveNotes(item, valueLookup);
return Object.assign(Object.assign({}, item), { avatarUrl: deriveAvatarUrl(item, ruleData), baseItemType: deriveBaseType(item), canAttune: deriveCanAttune(item, infusion), canEquip: deriveCanEquip(item, infusion), categoryInfo: deriveCategoryInfo(item, ruleData), cost: deriveCost(item, valueLookup), definitionKey: deriveDefinitionKey(item), damageType: deriveDamageType(item, modifiers), infusion,
infusionActions, isAdamantine: deriveIsAdamantine(item, valueLookup), isCustom: isCustomItem(item), isCustomized: deriveIsCustomized(item, valueLookup), isDisplayAsAttack: deriveIsDisplayAsAttack(item, valueLookup), isMagic: deriveIsMagic(item, infusion), isOffhand: deriveIsOffhand(item, valueLookup), isSilvered: deriveIsSilvered(item, valueLookup), limitedUse: deriveLimitedUse(infusionActions, getLimitedUse(item)), modifiers, name: deriveName(item, valueLookup), notes, originalContract: deriveOriginalCustomContract(item, notes), properties: deriveProperties(item, modifiers, ruleData), spells: baseSpells.map((spell) => updateBaseItemSpells(item, spell)), type: deriveType(item, ruleData), weight: deriveWeight(item, valueLookup), capacityWeight: deriveCapacityWeight(item, valueLookup) });
infusionActions, isAdamantine: deriveIsAdamantine(item, valueLookup), isCustom: isCustomItem(item), isCustomized: deriveIsCustomized(item, valueLookup), isDisplayAsAttack: deriveIsDisplayAsAttack(item, valueLookup), isMagic: deriveIsMagic(item, infusion), isOffhand: deriveIsOffhand(item, valueLookup), isSilvered: deriveIsSilvered(item, valueLookup), limitedUse: deriveLimitedUse(infusionActions, getLimitedUse(item)), modifiers, name: deriveName(item, valueLookup), notes, originalContract: deriveOriginalCustomContract(item, notes), properties: deriveProperties(item, modifiers, ruleData), spells: baseSpells.map((spell) => updateBaseItemSpells(item, spell)), type: deriveType(item, ruleData), weight: deriveWeight(item, valueLookup), capacityWeight: deriveCapacityWeight(item, valueLookup), partyInventoryRestrictions: derivePartyInventoryRestrictions(item, partyInventoryRestrictions) });
}
/**
*
@@ -404,6 +407,10 @@ export function generateHasMaxAttunedItems(attunedItems, attunedItemCountMax) {
export function generateInventoryLookup(items) {
return keyBy(items, (item) => getMappingId(item));
}
export function generateInventoryLookupByOrigin(inventory, partyInventory) {
const combinedItems = [...inventory, ...partyInventory];
return HelperUtils.generateNonNullLookup(combinedItems, (item) => getOriginDefinitionKey(item));
}
/**
*
* @param items
@@ -7,7 +7,7 @@ import { SpellAccessors } from '../Spell';
import { getAdditionalDamages, getAttackType, getLimitedUse, getLongRange, getNotes, getProperties, getRange, getStealthCheck, getStrengthRequirement, getTags, isAdamantine, isOffhand, isSilvered, getArmorClass, getInfusion, isCustom, } from './accessors';
import { WeaponPropertyEnum } from './constants';
import { deriveCategoryInfo } from './derivers';
import { getApplicableWeaponSpellDamageGroups, hasWeaponProperty, isBaseArmor, isBaseGear, isBaseWeapon, isShield, } from './utils';
import { getApplicableWeaponSpellDamageGroups, hasWeaponProperty, isBaseArmor, isBaseGear, isBaseWeapon, isShield, isReplicated, } from './utils';
import { validateIsWeaponLike } from './validators';
/**
*
@@ -190,12 +190,16 @@ function getInfusionNoteComponents(item, infusion) {
* @param abilityLookup
* @param proficiencyBonus
*/
export function getNoteComponents(item, weaponSpellDamageGroups, ruleData, abilityLookup, proficiencyBonus) {
export function getNoteComponents(item, weaponSpellDamageGroups, ruleData, abilityLookup, proficiencyBonus, itemPlan = null) {
const notes = [];
const infusion = getInfusion(item);
if (infusion) {
notes.push(...getInfusionNoteComponents(item, infusion));
}
// If there is an item plan or the item is marked as replicated by PartyResctrictions - add a note
if (itemPlan || isReplicated(item)) {
notes.push(NoteGenerators.createPlainText('Replicated Item'));
}
if (isBaseWeapon(item)) {
notes.push(...getWeaponNoteComponents(item, weaponSpellDamageGroups, ruleData, abilityLookup, proficiencyBonus));
}
@@ -13,9 +13,9 @@ import { deriveArmorClass, deriveAvatarUrl, deriveBaseType, deriveCanAttune, der
*/
export function simulateItem(itemDefinition, globalModifiers, valueLookupByType, ruleData, overrides) {
var _a, _b;
const simulatedItemContract = Object.assign({ chargesUsed: 0, containerEntityId: -1, containerEntityTypeId: -1, containerDefinitionKey: '-1:-1', currency: null, definition: itemDefinition, definitionId: itemDefinition.id, definitionTypeId: -1, displayAsAttack: null, entityTypeId: -1, equipped: false, equippedEntityId: null, equippedEntityTypeId: null, id: -1, isAttuned: false, limitedUse: null, quantity: 1 }, ((_a = overrides === null || overrides === void 0 ? void 0 : overrides.simulatedItemContract) !== null && _a !== void 0 ? _a : {}));
const simulatedItemContract = Object.assign({ chargesUsed: 0, containerEntityId: -1, containerEntityTypeId: -1, containerDefinitionKey: '-1:-1', currency: null, definition: itemDefinition, definitionId: itemDefinition.id, definitionTypeId: -1, displayAsAttack: null, entityTypeId: -1, equipped: false, equippedEntityId: null, equippedEntityTypeId: null, id: -1, isAttuned: false, limitedUse: null, originEntityId: null, originEntityTypeId: null, originDefinitionKey: null, quantity: 1 }, ((_a = overrides === null || overrides === void 0 ? void 0 : overrides.simulatedItemContract) !== null && _a !== void 0 ? _a : {}));
const modifiers = (_b = getDefinitionGrantedModifiers(simulatedItemContract)) !== null && _b !== void 0 ? _b : [];
const simulatedItem = Object.assign(Object.assign({}, simulatedItemContract), { avatarUrl: deriveAvatarUrl(simulatedItemContract, ruleData), baseItemType: deriveBaseType(simulatedItemContract), canAttune: deriveCanAttune(simulatedItemContract, null), canEquip: deriveCanEquip(simulatedItemContract, null), categoryInfo: deriveCategoryInfo(simulatedItemContract, ruleData), cost: deriveCost(simulatedItemContract, {}), damageType: deriveDamageType(simulatedItemContract, []), definitionKey: deriveDefinitionKey(simulatedItemContract), isAdamantine: false, isCustom: false, isCustomized: false, isDisplayAsAttack: deriveIsDisplayAsAttack(simulatedItemContract, {}), isOffhand: false, isSilvered: false, isMagic: deriveIsMagic(simulatedItemContract, null), infusion: null, infusionActions: [], modifiers: modifiers.map((modifier) => ModifierGenerators.generateModifier(modifier, DataOriginTypeEnum.SIMULATED, null)), name: deriveName(simulatedItemContract, {}), notes: null, originalContract: null, properties: deriveProperties(simulatedItemContract, [], ruleData), spells: [], type: deriveType(simulatedItemContract, ruleData), weight: deriveWeight(simulatedItemContract, {}), capacityWeight: deriveCapacityWeight(simulatedItemContract, {}) });
const simulatedItem = Object.assign(Object.assign({}, simulatedItemContract), { avatarUrl: deriveAvatarUrl(simulatedItemContract, ruleData), baseItemType: deriveBaseType(simulatedItemContract), canAttune: deriveCanAttune(simulatedItemContract, null), canEquip: deriveCanEquip(simulatedItemContract, null), categoryInfo: deriveCategoryInfo(simulatedItemContract, ruleData), cost: deriveCost(simulatedItemContract, {}), damageType: deriveDamageType(simulatedItemContract, []), definitionKey: deriveDefinitionKey(simulatedItemContract), isAdamantine: false, isCustom: false, isCustomized: false, isDisplayAsAttack: deriveIsDisplayAsAttack(simulatedItemContract, {}), isOffhand: false, isSilvered: false, isMagic: deriveIsMagic(simulatedItemContract, null), infusion: null, infusionActions: [], modifiers: modifiers.map((modifier) => ModifierGenerators.generateModifier(modifier, DataOriginTypeEnum.SIMULATED, null)), name: deriveName(simulatedItemContract, {}), notes: null, originalContract: null, properties: deriveProperties(simulatedItemContract, [], ruleData), spells: [], type: deriveType(simulatedItemContract, ruleData), weight: deriveWeight(simulatedItemContract, {}), capacityWeight: deriveCapacityWeight(simulatedItemContract, {}), partyInventoryRestrictions: [] });
const additionalDamages = deriveWeaponAdditionalDamage(simulatedItem);
const reach = deriveWeaponReach(simulatedItem, globalModifiers, ruleData);
const proficiency = deriveIsProficient(simulatedItem, globalModifiers, false, valueLookupByType, ruleData);
@@ -1,9 +1,11 @@
import { orderBy } from 'lodash';
import { TypeScriptUtils } from '../../utils';
import { ActionAccessors } from '../Action';
import { PartyRestrictionTypeEnum } from '../Campaign';
import { ContainerValidators } from '../Container';
import { ItemPlanAccessors } from '../ItemPlan';
import { ValueHacks, ValueUtils } from '../Value';
import { canAttune, getArmorTypeId, getAttackType, getBaseItemId, getBaseType, getBaseTypeId, getContainerDefinitionKey, getGearTypeId, getItemWeaponBehaviors, getMappingEntityTypeId, getMappingId, getMasteryName, getName, getPrimarySourceCategoryId, getProperties, getRarity, getSecondarySourceCategoryIds, isAttuned, isContainer, } from './accessors';
import { canAttune, getArmorTypeId, getAttackType, getBaseItemId, getBaseType, getBaseTypeId, getContainerDefinitionKey, getDefinition, getGearTypeId, getItemWeaponBehaviors, getMappingEntityTypeId, getMappingId, getMasteryName, getName, getOriginDefinitionKey, getPartyInventoryRestrictions, getPrimarySourceCategoryId, getProperties, getRarity, getSecondarySourceCategoryIds, isAttuned, isContainer, } from './accessors';
import { ArmorTypeEnum, ItemBaseTypeEnum, ItemBaseTypeIdEnum, ItemRarityNameEnum, ItemTypeEnum, } from './constants';
//TODO probably move alot of these to validators and validate naming :)
export function isShield(armor) {
@@ -177,3 +179,15 @@ export function getMasteryAction(item, actions) {
export function getAllSourceCategoryIds(item) {
return [getPrimarySourceCategoryId(item), ...getSecondarySourceCategoryIds(item)];
}
/// Returns the item plan for the given item if it exists in the provided item plans.
export function getItemPlan(item, itemPlans) {
return (itemPlans.find((plan) => {
var _a;
return ((_a = getDefinition(item)) === null || _a === void 0 ? void 0 : _a.definitionKey) === ItemPlanAccessors.getItemDefinitionKey(plan) &&
getOriginDefinitionKey(item) === ItemPlanAccessors.getOriginDefinitionKey(plan);
}) || null);
}
export function isReplicated(item) {
const partyRestrictions = getPartyInventoryRestrictions(item);
return partyRestrictions.some((restriction) => restriction.type === PartyRestrictionTypeEnum.REPLICATED);
}
@@ -0,0 +1,21 @@
export function getItemDefinitionKey(itemPlan) {
return itemPlan.itemDefinitionKey;
}
export function getDataOrigin(itemPlan) {
return itemPlan.dataOrigin;
}
export function getItemName(itemPlan) {
return itemPlan.itemName;
}
export function getChoice(itemPlan) {
return itemPlan.choice;
}
export function getOriginDefinitionKey(itemPlan) {
return itemPlan.originDefinitionKey;
}
export function getDescription(itemPlan) {
return itemPlan.description;
}
export function getItem(itemPlan) {
return itemPlan.item;
}
@@ -0,0 +1,70 @@
import { sortBy } from 'lodash';
import { ChoiceAccessors } from '../Choice';
import { ClassAccessors, ClassDerivers } from '../Class';
import { ClassFeatureAccessors } from '../ClassFeature';
import { ContainerAccessors } from '../Container';
import { DataOriginGenerators } from '../DataOrigin';
import { DefinitionHacks } from '../Definition';
import { HelperUtils } from '../Helper';
import { ItemAccessors } from '../Item';
import { OptionAccessors } from '../Option';
import { getItemDefinitionKey, getItemName, getOriginDefinitionKey } from './accessors';
export function generateItemPlans(choices, options, dataOriginType, primary, parent) {
return choices.map((choice) => {
return generateItemPlan(choice, options, DataOriginGenerators.generateDataOrigin(dataOriginType, primary, parent));
});
}
export function generateItemPlan(choice, options, dataOrigin) {
const optionValue = ChoiceAccessors.getOptionValue(choice);
const chosenOption = options.find((option) => OptionAccessors.getId(option) === optionValue);
const itemName = chosenOption ? `Plan: ${OptionAccessors.getName(chosenOption)}` : 'Plan: Unknown';
let originDefinitionKey = null;
if (chosenOption && optionValue) {
originDefinitionKey = DefinitionHacks.hack__generateDefinitionKey(OptionAccessors.getDefinitionEntityTypeId(chosenOption), optionValue);
}
return {
originDefinitionKey,
choice,
dataOrigin,
itemName,
itemDefinitionKey: choice.itemDefinitionKey,
};
}
export function generateAggregatedItemPlans(classes, inventoryLookupByOrigin, containerLookup) {
const availablePlans = [];
classes.forEach((charClass) => {
ClassAccessors.getClassFeatures(charClass).forEach((feature) => {
const itemPlans = ClassFeatureAccessors.getItemPlans(feature).filter((plan) => getOriginDefinitionKey(plan) !== null && getItemDefinitionKey(plan) !== null);
availablePlans.push(...itemPlans);
});
});
//add inventory mappings to the item plans if they exist in the lookup
const updatedPlans = availablePlans.map((plan) => {
const planOriginDefinitionKey = getOriginDefinitionKey(plan);
const item = planOriginDefinitionKey
? HelperUtils.lookupDataOrFallback(inventoryLookupByOrigin, planOriginDefinitionKey)
: null;
const container = item
? HelperUtils.lookupDataOrFallback(containerLookup, ItemAccessors.getContainerDefinitionKey(item))
: null;
// if there is a container, use its name, otherwise default to 'In Equipment'
let containerName = 'In Equipment';
if (item && container) {
containerName = !ItemAccessors.isContainer(item)
? `In ${ContainerAccessors.getName(container)}`
: `In ${ContainerAccessors.isShared(container) ? 'Party ' : ''}Inventory`;
}
return Object.assign(Object.assign({}, plan), { item, description: item ? containerName : 'No item replicated.' });
});
return sortBy(updatedPlans, (plan) => getItemName(plan));
}
export function generateMaxReplicatedItemCount(classes) {
let totalMaxReplicatedItemsCount = 0;
classes.forEach((charClass) => {
const classMaxCount = ClassDerivers.deriveMaxReplicatedMagicItems(charClass);
if (classMaxCount) {
totalMaxReplicatedItemsCount += classMaxCount;
}
});
return totalMaxReplicatedItemsCount > 0 ? totalMaxReplicatedItemsCount : null;
}
@@ -0,0 +1,6 @@
import * as ItemPlanAccessors from './accessors';
import * as ItemPlanGenerators from './generators';
import * as ItemPlanTypings from './typings';
export * from './typings';
export { ItemPlanAccessors, ItemPlanGenerators };
export default Object.assign(Object.assign(Object.assign({}, ItemPlanAccessors), ItemPlanGenerators), ItemPlanTypings);
@@ -174,6 +174,8 @@ export var ModifierSubTypeEnum;
ModifierSubTypeEnum["STUDDED_LEATHER"] = "studded-leather";
ModifierSubTypeEnum["PRIDE_SILK_OUTFIT"] = "pride-silk-outfit";
ModifierSubTypeEnum["BARK_ARMOR"] = "bark-armor";
ModifierSubTypeEnum["LEATHER_SHIRT"] = "leather-shirt";
ModifierSubTypeEnum["LEATHER_CORSLET"] = "leather-corslet";
// medium armor
ModifierSubTypeEnum["HIDE"] = "hide";
ModifierSubTypeEnum["CHAIN_SHIRT"] = "chain-shirt";
@@ -182,6 +184,7 @@ export var ModifierSubTypeEnum;
ModifierSubTypeEnum["HALF_PLATE"] = "half-plate";
ModifierSubTypeEnum["SPIKED_ARMOR"] = "spiked-armor";
ModifierSubTypeEnum["SCUMWEED_ARMOR"] = "scumweed-armor";
ModifierSubTypeEnum["SCALE_ARMOUR"] = "scale-armour";
// heavy armor
ModifierSubTypeEnum["RING_MAIL"] = "ring-mail";
ModifierSubTypeEnum["CHAIN_MAIL"] = "chain-mail";
@@ -220,6 +223,8 @@ export var ModifierSubTypeEnum;
// Warlock Feature
ModifierSubTypeEnum["ENABLE_PACT_WEAPON"] = "enable-pact-weapon";
ModifierSubTypeEnum["ENABLE_HEX_WEAPON"] = "enable-hex-weapon";
// Artificer Feature
ModifierSubTypeEnum["ENABLE_REPLICATE_MAGIC_ITEM"] = "enable-replicate-magic-item";
// Enable Feature (used in tandem with REPLACE_WEAPON_ABILITY modifier)
ModifierSubTypeEnum["ENABLE_STRENGTH"] = "enable-strength";
ModifierSubTypeEnum["ENABLE_DEXTERITY"] = "enable-dexterity";
@@ -253,28 +258,31 @@ export const DAMAGE_ADJUSTMENT_LIST = [
ModifierSubTypeEnum.BLUDGEONING_PIERCING_SLASHING_NONMAGIC,
];
export const LIGHT_ARMOR_LIST = [
ModifierSubTypeEnum.PADDED,
ModifierSubTypeEnum.LEATHER,
ModifierSubTypeEnum.STUDDED_LEATHER,
ModifierSubTypeEnum.PRIDE_SILK_OUTFIT,
ModifierSubTypeEnum.BARK_ARMOR,
ModifierSubTypeEnum.LEATHER,
ModifierSubTypeEnum.LEATHER_CORSLET,
ModifierSubTypeEnum.LEATHER_SHIRT,
ModifierSubTypeEnum.PADDED,
ModifierSubTypeEnum.PRIDE_SILK_OUTFIT,
ModifierSubTypeEnum.STUDDED_LEATHER,
];
export const MEDIUM_ARMOR_LIST = [
ModifierSubTypeEnum.HIDE,
ModifierSubTypeEnum.CHAIN_SHIRT,
ModifierSubTypeEnum.SCALE_MAIL,
ModifierSubTypeEnum.BREASTPLATE,
ModifierSubTypeEnum.CHAIN_SHIRT,
ModifierSubTypeEnum.HALF_PLATE,
ModifierSubTypeEnum.SPIKED_ARMOR,
ModifierSubTypeEnum.HIDE,
ModifierSubTypeEnum.SCALE_ARMOUR,
ModifierSubTypeEnum.SCALE_MAIL,
ModifierSubTypeEnum.SCUMWEED_ARMOR,
ModifierSubTypeEnum.SPIKED_ARMOR,
];
export const HEAVY_ARMOR_LIST = [
ModifierSubTypeEnum.RING_MAIL,
ModifierSubTypeEnum.CHAIN_MAIL,
ModifierSubTypeEnum.SPLINT,
ModifierSubTypeEnum.DIVERS_ARMOR,
ModifierSubTypeEnum.PLATE,
ModifierSubTypeEnum.POWERED_ARMOR,
ModifierSubTypeEnum.DIVERS_ARMOR,
ModifierSubTypeEnum.RING_MAIL,
ModifierSubTypeEnum.SPLINT,
];
export const SHIELDS_LIST = [ModifierSubTypeEnum.SHIELD, ModifierSubTypeEnum.POT_LID];
export const ALL_ARMOR_LIST = [
@@ -306,6 +306,10 @@ export function isEnableHexWeaponModifier(modifier) {
return (getType(modifier) === ModifierTypeEnum.ENABLE_FEATURE &&
getSubType(modifier) === ModifierSubTypeEnum.ENABLE_HEX_WEAPON);
}
export function isEnableReplicateMagicItemModifier(modifier) {
return (getType(modifier) === ModifierTypeEnum.ENABLE_FEATURE &&
getSubType(modifier) === ModifierSubTypeEnum.ENABLE_REPLICATE_MAGIC_ITEM);
}
export function isEnablesAbilityStatModifier(modifier) {
if (getType(modifier) !== ModifierTypeEnum.ENABLE_FEATURE) {
return false;
@@ -14,6 +14,7 @@ export var SnippetMathOperatorEnum;
})(SnippetMathOperatorEnum || (SnippetMathOperatorEnum = {}));
export var SnippetTagValueTypeEnum;
(function (SnippetTagValueTypeEnum) {
SnippetTagValueTypeEnum["SPEED"] = "speed";
SnippetTagValueTypeEnum["SAVE_DC"] = "savedc";
SnippetTagValueTypeEnum["SPELL_ATTACK"] = "spellattack";
SnippetTagValueTypeEnum["SCALE_VALUE"] = "scalevalue";
@@ -51,6 +52,14 @@ export var SnippetAbilityKeyEnum;
SnippetAbilityKeyEnum["WISDOM"] = "wis";
SnippetAbilityKeyEnum["CHARISMA"] = "cha";
})(SnippetAbilityKeyEnum || (SnippetAbilityKeyEnum = {}));
export var SnippetSpeedMovementKeyEnum;
(function (SnippetSpeedMovementKeyEnum) {
SnippetSpeedMovementKeyEnum["WALK"] = "walk";
SnippetSpeedMovementKeyEnum["FLY"] = "fly";
SnippetSpeedMovementKeyEnum["BURROW"] = "burrow";
SnippetSpeedMovementKeyEnum["SWIM"] = "swim";
SnippetSpeedMovementKeyEnum["CLIMB"] = "climb";
})(SnippetSpeedMovementKeyEnum || (SnippetSpeedMovementKeyEnum = {}));
export var SnippetContentChunkTypeEnum;
(function (SnippetContentChunkTypeEnum) {
SnippetContentChunkTypeEnum["TEXT"] = "TEXT";
@@ -5,15 +5,17 @@
* @param abilityLookup
* @param xpInfo
* @param hitPointInfo
* @param speedInfo
* @param ruleData
*/
export function generateSnippetData(proficiencyBonus, abilityKeyLookup, abilityLookup, xpInfo, hitPointInfo, ruleData) {
export function generateSnippetData(proficiencyBonus, abilityKeyLookup, abilityLookup, xpInfo, hitPointInfo, speedInfo, ruleData) {
return {
proficiencyBonus,
abilityKeyLookup,
abilityLookup,
xpInfo,
hitPointInfo,
speedInfo,
ruleData,
};
}
@@ -2,6 +2,7 @@ import { isFinite, toNumber } from 'lodash';
import { TypeScriptUtils } from '../../utils';
import { AbilityAccessors } from '../Ability';
import { CharacterDerivers } from '../Character';
import { SpeedMovementKeyEnum } from '../Core';
import { DiceRenderers } from '../Dice';
import { FormatUtils } from '../Format';
import { LimitedUseDerivers } from '../LimitedUse';
@@ -90,6 +91,7 @@ export function isSnippetPostProcessTypeEnum(key) {
*/
export function isSnippetTagValueTypeEnum(key) {
switch (key) {
case SnippetTagValueTypeEnum.SPEED:
case SnippetTagValueTypeEnum.SCALE_VALUE:
case SnippetTagValueTypeEnum.LIMITED_USE:
case SnippetTagValueTypeEnum.CLASS_LEVEL:
@@ -107,6 +109,23 @@ export function isSnippetTagValueTypeEnum(key) {
}
return false;
}
/**
*
* @param type
*/
export function isSnippetSpeedMovementKeyEnum(key) {
switch (key) {
case SpeedMovementKeyEnum.WALK:
case SpeedMovementKeyEnum.FLY:
case SpeedMovementKeyEnum.BURROW:
case SpeedMovementKeyEnum.SWIM:
case SpeedMovementKeyEnum.CLIMB:
return true;
default:
// not implemented
}
return false;
}
/**
*
* @param type
@@ -207,6 +226,7 @@ export function throwInvalidSnippetCharacters(str) {
keywordMatches === null || keywordMatches === void 0 ? void 0 : keywordMatches.forEach((keyword) => {
if (isSnippetTagValueTypeEnum(keyword) ||
isSnippetAbilityKeyEnum(keyword) ||
isSnippetSpeedMovementKeyEnum(keyword) ||
isSnippetValueModifierTypeEnum(keyword) ||
isSnippetPostProcessTypeEnum(keyword)) {
return;
@@ -323,9 +343,21 @@ export function deriveStringTagValue(tagValue, contextData) {
* @param proficiencyBonus
*/
export function deriveNumericTagValue(tagValue, contextData, snippetData, proficiencyBonus) {
var _a;
const { type, params } = tagValue;
let value = 0;
switch (type) {
case SnippetTagValueTypeEnum.SPEED: {
const movementType = (_a = params[0]) !== null && _a !== void 0 ? _a : SpeedMovementKeyEnum.WALK;
if (!isSnippetSpeedMovementKeyEnum(movementType)) {
throw new Error(`Invalid speed type: ${movementType}`);
}
const speed = snippetData.speedInfo[movementType];
if (speed === null) {
throw new Error(`No speed data available for ${movementType}`);
}
return speed;
}
case SnippetTagValueTypeEnum.SAVE_DC:
if (params.length < 1) {
throw new Error('Save DC missing ability key');
@@ -4,7 +4,7 @@ import { AbilityScoreStatTypeEnum } from "../engine/Core";
import { HelperUtils } from "../engine/Helper";
import { RuleDataAccessors } from "../engine/RuleData";
import { rulesEngineSelectors } from "../selectors";
import { FeaturesManager } from './FeaturesManager';
import { BaseManager } from './BaseManager';
export const abilityDefinitionMap = new Map();
const abilityMangerMap = new Map();
export const getAbilityManager = (params) => {
@@ -23,7 +23,7 @@ export const getAbilityManager = (params) => {
abilityMangerMap.set(abilityId, newAbilityManager);
return newAbilityManager;
};
export class AbilityManager extends FeaturesManager {
export class AbilityManager extends BaseManager {
constructor(params) {
super(params);
//Accessors
@@ -87,7 +87,6 @@ export class AbilityManager extends FeaturesManager {
getCompendiumText() {
const abilityRuleData = this.getAbilityRuleData();
// TODO: we should accessor all the things?
// maybe gfs will be the time to do it?
return abilityRuleData.compendiumText;
}
}
@@ -8,15 +8,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
import { orderBy } from 'lodash';
import { AbilityAccessors } from "../engine/Ability";
import { FeatureFlagEnum } from "../engine/FeatureFlagInfo";
import { characterSelectors, featureFlagInfoSelectors, rulesEngineSelectors } from "../selectors";
import { rulesEngineSelectors } from "../selectors";
import { getAbilityManager } from './AbilityManager';
import { FeatureFlagManager } from './FeatureFlagManager';
// import { BlessingManager, getBlessingManager } from './FeatureManager';
import { FeaturesManager } from './FeaturesManager';
import { transformAbilityScores } from './utils/modifierTransformers';
export class AttributesManager extends FeaturesManager {
import { BaseManager } from './BaseManager';
export class AttributesManager extends BaseManager {
constructor(params) {
super(params);
this.getHighestAbilityScore = (abilities) => {
@@ -28,31 +23,9 @@ export class AttributesManager extends FeaturesManager {
getAbilities() {
return __awaiter(this, void 0, void 0, function* () {
const abilities = rulesEngineSelectors.getAbilities(this.state);
const featureFlagsManager = new FeatureFlagManager(featureFlagInfoSelectors.getFeatureFlagInfo(this.state));
const hasAccessToBlessing = featureFlagsManager.getFlag(FeatureFlagEnum.RELEASE_GATE_GFS_BLESSINGS_UI);
if (!hasAccessToBlessing) {
return abilities.map((ability) => {
return getAbilityManager(Object.assign(Object.assign({}, this.params), { ability }));
});
}
else {
// TODO: GFS this is all the wrong place
// it should just be
// const abilities = getCharacter.abilities ...
const validGlobalModifiers = this.getValidGlobalModifiers();
const baseStats = characterSelectors.getStats(this.state);
const ruleData = rulesEngineSelectors.getRuleData(this.state);
const { generatedFeature } = transformAbilityScores(validGlobalModifiers, baseStats, ruleData);
// TODO: GFS THERE IS SOME MORE TO DO HERE
const characterFeatureManagers = yield this.getCharacterFeatures();
// TODO: GFS this should be part of the process and I should just ask for the the object here.
const myAbilityState = this.processCharacter(generatedFeature);
return abilities.map((ability) => {
var _a, _b, _c, _d, _e, _f;
const name = (_b = (_a = AbilityAccessors.getLabel(ability)) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : 'The ability with no name';
return getAbilityManager(Object.assign(Object.assign({}, this.params), { ability: Object.assign(Object.assign({}, ability), { maxStatScore: (_c = myAbilityState.attributes[name].max) !== null && _c !== void 0 ? _c : null, modifier: (_d = myAbilityState.attributes[name].modifier) !== null && _d !== void 0 ? _d : null, score: (_e = myAbilityState.attributes[name].value) !== null && _e !== void 0 ? _e : null, totalScore: (_f = myAbilityState.attributes[name].value) !== null && _f !== void 0 ? _f : null }) }));
});
}
return abilities.map((ability) => {
return getAbilityManager(Object.assign(Object.assign({}, this.params), { ability }));
});
});
}
getAbilitiesWithoutBlessings() {
@@ -1,4 +1,6 @@
import { HelperUtils } from "../engine/Helper";
import { getCurrentRulesEngineConfig } from '../config/utils';
export const subscriptionsMap = new Map();
export class BaseManager {
constructor(params = {}) {
this.randomId = 0; // helpful for debugging
@@ -50,3 +52,26 @@ export class BaseManager {
};
}
}
// manage subscriptions
BaseManager.subscribeToUpdates = ({ onUpdate, shouldInit = true }) => {
// TODO: maybe from lib.
const mapKey = HelperUtils.generateGuid();
// connect to redux for updates
let unsubscribe;
if (getCurrentRulesEngineConfig().store) {
const store = getCurrentRulesEngineConfig().store;
unsubscribe = store === null || store === void 0 ? void 0 : store.subscribe(() => {
onUpdate();
});
}
subscriptionsMap.set(mapKey, onUpdate);
if (shouldInit) {
onUpdate();
}
return () => {
subscriptionsMap.delete(mapKey);
if (unsubscribe) {
unsubscribe();
}
};
};
@@ -15,27 +15,10 @@ import { FeatAccessors, FeatSimulators } from "../engine/Feat";
import { HelperUtils } from "../engine/Helper";
import { featDefinitionMap, getFeatManager } from "./FeatManager";
import { apiCreatorSelectors, rulesEngineSelectors } from "../selectors";
import { FeaturesManager } from './FeaturesManager';
export class CharacterFeaturesManager extends FeaturesManager {
import { BaseManager } from './BaseManager';
export class CharacterFeaturesManager extends BaseManager {
constructor(params) {
super(params);
//TODO GFS THIS!!!!
this.getBlessings = () => __awaiter(this, void 0, void 0, function* () {
// TODO get the blessings only?
/**
* this could be on the character obj under blessings key
*/
const blessingManagers = yield this.getCharacterFeatures();
this.blessingManagers = blessingManagers;
return blessingManagers;
});
this.getBlessing = (id) => { var _a; return (_a = this.blessingManagers.find((blessing) => id === blessing.getId())) !== null && _a !== void 0 ? _a : null; };
// TODO is there value in moving these to hasFeatures and hasBlessings
this.hasBlessings = () => __awaiter(this, void 0, void 0, function* () {
const blessings = yield this.getBlessings();
return blessings.length > 0;
});
this.hasBlessing = (blessing) => !!this.blessingManagers.find((characterBlessing) => characterBlessing.getId() === blessing.getId());
this.handlePreferenceChange = (preferenceKey, value) => {
const typedPrefKey = CharacterUtils.getPreferenceKey(preferenceKey);
if (typedPrefKey !== null) {
@@ -43,7 +26,6 @@ export class CharacterFeaturesManager extends FeaturesManager {
}
};
this.params = params;
this.blessingManagers = [];
}
mapFeatsToManagers(feats) {
return feats.map((feat) => getFeatManager(Object.assign(Object.assign({}, this.params), { feat })));
@@ -127,16 +109,6 @@ export class CharacterFeaturesManager extends FeaturesManager {
return [];
});
}
getBlessingShoppe(additionalConfig) {
return __awaiter(this, void 0, void 0, function* () {
const blessings = yield this.getFeaturesShoppe({
params: {
category: 'blessing',
},
});
return orderBy(blessings, (blessing) => blessing.getName());
});
}
//UTILS
// filter to only allow one of each simulated repeatable feat group
filterUniqueSimulatedRepeatableFeats(feats, parentFeats) {
@@ -1,7 +1,7 @@
import { characterActions, serviceDataActions } from '../actions';
import { ConfigUtils } from '../config';
import { rulesEngineConfig } from '../config/utils';
import { ContainerAccessors } from '../engine/Container';
import { ContainerAccessors, ContainerValidators } from '../engine/Container';
import { CoreUtils, CURRENCY_VALUE } from '../engine/Core';
import { DefinitionHacks } from '../engine/Definition';
import { FormatUtils } from '../engine/Format';
@@ -22,6 +22,7 @@ export class CoinManager extends InventoryManager {
if (!isEmpty && container) {
const destinationEntityTypeId = ContainerAccessors.getContainerType(container);
const destinationEntityId = ContainerAccessors.getMappingId(container);
const isDestinationContainerShared = ContainerValidators.validateIsShared(DefinitionHacks.hack__generateDefinitionKey(destinationEntityTypeId, destinationEntityId), containerLookup);
const currentCoins = this.getContainerCoin(containerDefinitionKey);
if (multiplier === 1) {
const validAddCoin = this.calculateAddCoinPayload(currentCoins, coin.cp || 0, coin.ep || 0, coin.gp || 0, coin.pp || 0, coin.sp || 0);
@@ -39,6 +40,11 @@ export class CoinManager extends InventoryManager {
}
this.dispatch(characterActions.currencyTransactionSet(Object.assign(Object.assign({}, adjustedCurrency), { destinationEntityId: destinationEntityId, destinationEntityTypeId: destinationEntityTypeId }), () => {
typeof onSuccess === 'function' && onSuccess();
if (isDestinationContainerShared) {
const data = {};
const eventType = this.EVENT_TYPES.ITEM_SHARED_FULFILLED;
this.sendMessage({ data, eventType });
}
}, () => {
typeof onError === 'function' && onError();
}));
@@ -238,25 +244,6 @@ export class CoinManager extends InventoryManager {
}
};
// Validators
this.canAddCoin = (coin, containerDefinitionKey) => {
const containerLookup = rulesEngineSelectors.getContainerLookup(this.state);
const container = HelperUtils.lookupDataOrFallback(containerLookup, containerDefinitionKey);
const isShared = container ? ContainerAccessors.isShared(container) : false;
if (isShared && this.isSharingTurnedDeleteOnly()) {
return false;
}
return true;
};
this.canRemoveCoin = (coin, containerDefinitionKey) => {
const containerLookup = rulesEngineSelectors.getContainerLookup(this.state);
const container = HelperUtils.lookupDataOrFallback(containerLookup, containerDefinitionKey);
const isShared = container ? ContainerAccessors.isShared(container) : false;
if (isShared && this.isSharingTurnedDeleteOnly()) {
let hasNegatives = Object.values(coin).some((value) => value < 0);
return !hasNegatives;
}
return true;
};
this.canUseCointainers = () => {
const rulesEngineConfig = ConfigUtils.getCurrentRulesEngineConfig();
const preferences = rulesEngineSelectors.getPreferences(this.state);
@@ -144,6 +144,7 @@ export class ContainerManager extends MessageManager {
this.getCoin = () => ContainerAccessors.getCoin(this.container);
// Validators
this.isCharacterContainer = () => ContainerValidators.isCharacterContainer(this.container);
this.isPartyContainer = () => ContainerValidators.isPartyContainer(this.container);
this.isSharedOtherContainerDefinitionKey = (otherContainerDefinitionKey) => {
const containerLookup = rulesEngineSelectors.getContainerLookup(this.state);
return ContainerValidators.validateIsShared(otherContainerDefinitionKey, containerLookup);
@@ -1,46 +0,0 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { ApiRequests } from "../api";
export class FeatureManager {
constructor(params) {
this.handleAcceptOnSuccess = (onSuccess) => {
typeof onSuccess === 'function' && onSuccess();
};
this.handleRejectOnError = (onError) => {
typeof onError === 'function' && onError();
};
this.handleAdd = (onSuccess, onError) => __awaiter(this, void 0, void 0, function* () {
yield ApiRequests.postCharacterFeature({ featureId: this.getId() });
this.bustCache();
this.handleAcceptOnSuccess(onSuccess);
this.runSubscriptions();
});
this.handleRemove = (onSuccess, onError) => __awaiter(this, void 0, void 0, function* () {
// featureManagerMap.delete(this.getId()); //TODO GFS do we still need to do it this way
yield ApiRequests.deleteCharacterFeature({ featureId: this.getId() });
this.bustCache();
this.handleAcceptOnSuccess(onSuccess);
this.runSubscriptions();
});
//Accessors
this.getId = () => this.feature.featureId;
this.getStatements = () => this.feature.statements;
this.getFeature = () => this.feature;
// Label accessors
// TODO what are the default/standard ones?
//TODO GFS: talk about role.toLowerCase
this.getName = () => { var _a, _b; return (_b = (_a = this.feature.labels.find((label) => label.role.toLowerCase() === 'name')) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : 'FEATURE WITH NO NAME'; }; //TODO GFS ENUMS for all of these
this.getDescription = () => { var _a, _b; return (_b = (_a = this.feature.labels.find((label) => label.role.toLowerCase() === 'description')) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : ''; };
this.params = params;
this.feature = params.feature;
this.runSubscriptions = params.runSubscriptions;
this.bustCache = params.bustCache;
}
}
@@ -1,166 +0,0 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { generateCharacterState } from '@dndbeyond/character-gfs';
import { ApiRequests } from "../api";
import { ApiAdapterUtils } from "../apiAdapter";
import { getCurrentRulesEngineConfig } from "../config/utils";
import { HelperUtils } from "../engine/Helper";
import { apiCreatorSelectors, rulesEngineSelectors } from "../selectors";
import { TypeScriptUtils } from "../utils";
import { FeatureManager } from './FeatureManager';
import { MessageManager } from './MessageManager';
export const featureMap = new Map();
export const subscriptionsMap = new Map();
const featureManagerMap = new Map();
export const getFeatureManager = (params) => {
const { feature } = params;
const featureId = feature.featureId; //TODO GFS Accessors?
if (featureManagerMap.has(featureId)) {
const FeatureManager = featureManagerMap.get(featureId);
if (!FeatureManager) {
throw new Error(`FeatureManager for feature ${featureId} is null`);
}
if (FeatureManager.params.feature === feature) {
return FeatureManager;
}
}
const newFeatureManager = new FeatureManager(params);
featureManagerMap.set(featureId, newFeatureManager);
return newFeatureManager;
};
// we want this cached across all instances of the FeaturesManager
let rootCharacterFeatureManagers = null; // needs
let allCharacterFeatureManagers = null; // needs
let availableFeaturesResponseData = null; // not required to bust
export class FeaturesManager extends MessageManager {
constructor(params = {}) {
super(params);
// these values are used for caching and may need to be busted when the character changes
this.randomId = 0; // helpful for debugging
this.runSubscriptions = () => {
subscriptionsMap.forEach((cb) => cb());
};
this.transformLoadedFeatures = (features) => {
return features.map((feature) => {
featureMap.set(feature.featureId, feature);
return getFeatureManager({ feature, runSubscriptions: this.runSubscriptions, bustCache: this.bustCache });
});
};
this.getAvailableFeatures = (additionalConfig) => __awaiter(this, void 0, void 0, function* () {
var _a;
if (availableFeaturesResponseData) {
return availableFeaturesResponseData;
}
const loadFeatures = apiCreatorSelectors.makeLoadAvailableFeatures(this.state);
const response = yield loadFeatures(additionalConfig);
availableFeaturesResponseData = (_a = ApiAdapterUtils.getResponseData(response)) !== null && _a !== void 0 ? _a : [];
availableFeaturesResponseData.map((feature) => {
featureMap.set(feature.featureId, feature);
});
return availableFeaturesResponseData;
});
this.getFeaturesShoppe = (additionalConfig) => __awaiter(this, void 0, void 0, function* () {
let data = yield this.getAvailableFeatures(additionalConfig);
if (data) {
return this.transformLoadedFeatures(data);
}
return [];
});
this.getAllCharacterFeatures = (rootCharacterFeatureIds) => __awaiter(this, void 0, void 0, function* () {
var _b;
if (allCharacterFeatureManagers) {
return allCharacterFeatureManagers;
}
const allFeaturesAndSubFeatureResponse = yield ApiRequests.postFeaturesAndSubfeatures({
featureIds: rootCharacterFeatureIds,
});
const allFeaturesAndSubFeatureData = (_b = ApiAdapterUtils.getResponseData(allFeaturesAndSubFeatureResponse)) !== null && _b !== void 0 ? _b : [];
allFeaturesAndSubFeatureData.map((feature) => {
featureMap.set(feature.featureId, feature);
});
const allFeaturesAndSubFeatureIds = allFeaturesAndSubFeatureData.map((feature) => feature.featureId);
allCharacterFeatureManagers = allFeaturesAndSubFeatureIds
.map((id) => {
const feature = featureMap.get(id);
return feature
? getFeatureManager({ feature, runSubscriptions: this.runSubscriptions, bustCache: this.bustCache })
: null;
})
.filter(TypeScriptUtils.isNotNullOrUndefined);
return allCharacterFeatureManagers || [];
});
this.bustCache = () => __awaiter(this, void 0, void 0, function* () {
availableFeaturesResponseData = null;
rootCharacterFeatureManagers = null;
allCharacterFeatureManagers = null;
});
this.getCharacterFeatures = () => __awaiter(this, void 0, void 0, function* () {
var _c;
if (rootCharacterFeatureManagers) {
return rootCharacterFeatureManagers;
}
const characterId = rulesEngineSelectors.getId(this.state);
const characterFeatureResponse = yield ApiRequests.getCharacterFeatures({ characterId });
const characterFeatureData = (_c = ApiAdapterUtils.getResponseData(characterFeatureResponse)) !== null && _c !== void 0 ? _c : [];
const characterFeatureIds = characterFeatureData.map((feature) => feature.id);
if (characterFeatureIds.length > 0) {
yield this.getAvailableFeatures({
params: {
category: 'blessing',
},
});
yield this.getAllCharacterFeatures(characterFeatureIds);
}
rootCharacterFeatureManagers = characterFeatureIds
.map((id) => {
const feature = featureMap.get(id);
return feature
? getFeatureManager({ feature, runSubscriptions: this.runSubscriptions, bustCache: this.bustCache })
: null;
})
.filter(TypeScriptUtils.isNotNullOrUndefined);
return rootCharacterFeatureManagers;
});
this.processCharacter = (generatedFeature) => {
featureMap.set(generatedFeature.featureId, generatedFeature);
return generateCharacterState({
rootFeatureIds: [
generatedFeature.featureId,
...((rootCharacterFeatureManagers === null || rootCharacterFeatureManagers === void 0 ? void 0 : rootCharacterFeatureManagers.map((featureManager) => featureManager.getId())) || []),
],
featureMap,
});
};
this.randomId = Math.floor(Math.random() * 1000000);
}
}
// manage subscriptions
FeaturesManager.subscribeToUpdates = ({ onUpdate, shouldInit = true }) => {
// TODO: maybe from lib.
const mapKey = HelperUtils.generateGuid();
// connect to redux for updates
let unsubscribe;
if (getCurrentRulesEngineConfig().store) {
const store = getCurrentRulesEngineConfig().store;
unsubscribe = store === null || store === void 0 ? void 0 : store.subscribe(() => {
onUpdate();
});
}
subscriptionsMap.set(mapKey, onUpdate);
if (shouldInit) {
onUpdate();
}
return () => {
subscriptionsMap.delete(mapKey);
if (unsubscribe) {
unsubscribe();
}
};
};
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
};
import * as ApiAdapterUtils from "../apiAdapter/utils";
import { characterActions } from '../actions';
import { ContainerAccessors, ContainerGenerators, ContainerTypeEnum, ContainerUtils, ContainerValidators, } from '../engine/Container';
import { ContainerGenerators, ContainerTypeEnum, ContainerUtils, ContainerValidators, } from '../engine/Container';
import { EntityTypeEnum, DefaultCharacterName } from '../engine/Core';
import { DefinitionHacks } from '../engine/Definition';
import { HelperUtils } from '../engine/Helper';
@@ -19,6 +19,15 @@ import { ValueHacks } from '../engine/Value';
import { apiCreatorSelectors, characterSelectors, rulesEngineSelectors } from '../selectors';
import { getContainerManager } from './ContainerManager';
import { PartyManager } from './PartyManager';
let inventoryManager = null;
let itemShoppe = null;
export function getInventoryManager(params) {
if (inventoryManager) {
return inventoryManager;
}
inventoryManager = new InventoryManager(params);
return inventoryManager;
}
export class InventoryManager extends PartyManager {
constructor(params) {
super(params);
@@ -89,9 +98,11 @@ export class InventoryManager extends PartyManager {
this.dispatch(characterActions.customItemDestroy(ItemAccessors.getDefinitionId(item), ItemAccessors.getMappingId(item), isShared ? this.getPartyId() : null, this.handleAcceptOnSuccess(isShared, onSuccess)));
}
else {
const itemPlans = rulesEngineSelectors.getAvailableItemPlans(this.state);
const isContainer = ItemAccessors.isContainer(item);
const infusion = ItemAccessors.getInfusion(item);
this.dispatch(characterActions.itemDestroy(ItemAccessors.getMappingId(item), isContainer && infusion ? false : true, this.handleAcceptOnSuccess(isShared, onSuccess)));
const itemPlan = ItemUtils.getItemPlan(item, itemPlans);
this.dispatch(characterActions.itemDestroy(ItemAccessors.getMappingId(item), isContainer && (infusion || itemPlan) ? false : true, this.handleAcceptOnSuccess(isShared, onSuccess)));
}
};
/**
@@ -158,9 +169,19 @@ export class InventoryManager extends PartyManager {
this.dispatch(characterActions.itemCustomizationsDelete(ItemAccessors.getMappingId(item), ItemAccessors.getMappingEntityTypeId(item), isShared ? this.getPartyId() : null, this.handleAcceptOnSuccess(isShared, onSuccess)));
};
// Validators
this.canAddToContainer = (container) => {
const isShared = ContainerAccessors.isShared(container);
return !isShared || this.isSharingTurnedOn();
// check for party inventory restrictions for isClaimable if its a shared item
this.isClaimRestricted = (item) => {
const isShared = this.isShared(item);
if (!isShared) {
return false;
}
const partyInventoryRestrictions = ItemAccessors.getPartyInventoryRestrictions(item);
if ((partyInventoryRestrictions === null || partyInventoryRestrictions === void 0 ? void 0 : partyInventoryRestrictions.length) > 0) {
if (partyInventoryRestrictions.some((restriction) => !restriction.isClaimable && restriction.characterId !== rulesEngineSelectors.getId(this.state))) {
return true;
}
}
return false;
};
/**
* @deprecated
@@ -170,10 +191,7 @@ export class InventoryManager extends PartyManager {
const canEquip = ItemAccessors.canEquip(item);
const isEquipped = ItemAccessors.isEquipped(item);
if (isShared && canEquip) {
if (this.isSharingTurnedOn() || (this.isSharingTurnedDeleteOnly() && isEquipped)) {
return isEquipped ? this.isEquippedToCurrentCharacter(item) : true;
}
return false;
return isEquipped ? this.isEquippedToCurrentCharacter(item) : true;
}
return canEquip;
};
@@ -184,9 +202,6 @@ export class InventoryManager extends PartyManager {
const isAttuned = ItemAccessors.isAttuned(item);
if (!isAttuned) {
if (isShared) {
if (this.isSharingTurnedDeleteOnly()) {
return false;
}
return this.isEquippedToCurrentCharacter(item) && !hasMaxAttunedItems && canAttune;
}
return !hasMaxAttunedItems && canAttune;
@@ -199,30 +214,18 @@ export class InventoryManager extends PartyManager {
}
};
this.canModifyQuantity = (item) => {
const isShared = this.isShared(item);
const isStackable = ItemAccessors.isStackable(item);
if (isShared && isStackable) {
return this.isSharingTurnedOn();
}
return isStackable;
};
this.canCustomizeItem = (item) => {
const isShared = this.isShared(item);
if (isShared) {
return this.isSharingTurnedOn();
}
return true;
};
this.canUseItemCharges = (item) => {
const isShared = this.isShared(item);
if (isShared) {
return this.isSharingTurnedOn();
}
return !!ItemAccessors.getLimitedUse(item);
};
this.canRemoveItem = (item) => {
const isShared = this.isShared(item);
if (isShared) {
if (this.isClaimRestricted(item)) {
return false;
}
const infusion = ItemAccessors.getInfusion(item);
if (infusion) {
const infusionCharacterId = InfusionAccessors.getCharacterId(infusion);
@@ -230,16 +233,22 @@ export class InventoryManager extends PartyManager {
return false;
}
}
return ItemAccessors.isEquipped(item)
? this.isSharingTurnedOnOrDeleteOnly() && this.isEquippedToCurrentCharacter(item)
: this.isSharingTurnedOnOrDeleteOnly();
return ItemAccessors.isEquipped(item) ? this.isEquippedToCurrentCharacter(item) : true;
}
return true;
};
this.canMoveItem = (item) => {
// If you are changing this, you may want to change the canRemoveItem above
const isContainer = ItemAccessors.isContainer(item);
if (isContainer) {
return false;
}
const isShared = this.isShared(item);
if (isShared) {
if (this.isClaimRestricted(item)) {
return false;
}
//Might be able to remove this in favor of the above restriction check
const infusion = ItemAccessors.getInfusion(item);
if (infusion) {
const infusionCharacterId = InfusionAccessors.getCharacterId(infusion);
@@ -247,26 +256,23 @@ export class InventoryManager extends PartyManager {
return false;
}
}
return ItemAccessors.isEquipped(item)
? this.isSharingTurnedOnOrDeleteOnly() && this.isEquippedToCurrentCharacter(item)
: this.isSharingTurnedOnOrDeleteOnly();
}
else if (this.isSharingTurnedDeleteOnly()) {
const localContainers = rulesEngineSelectors.getCharacterInventoryContainers(this.state);
return localContainers.length > 1;
return ItemAccessors.isEquipped(item) ? this.isEquippedToCurrentCharacter(item) : true;
}
const containerLookup = rulesEngineSelectors.getContainerLookup(this.state);
return Object.keys(containerLookup).length > 1;
};
this.canShareItem = (item) => {
//Can a container item be moved into the party inventory?
this.canShareContainer = (item) => {
//check that the item is a container
const isContainer = ItemAccessors.isContainer(item);
if (!isContainer) {
return false;
}
const isShared = this.isShared(item);
return !isShared && this.isSharingTurnedOn();
return !isShared;
};
this.canClaimItem = (item) => {
//Can a container item be "claimed" out of the party inventory?
this.canClaimContainer = (item) => {
const isContainer = ItemAccessors.isContainer(item);
if (!isContainer || (ItemAccessors.isEquipped(item) && !this.isEquippedToCurrentCharacter(item))) {
return false;
@@ -278,6 +284,9 @@ export class InventoryManager extends PartyManager {
const definitionKey = DefinitionHacks.hack__generateDefinitionKey(EntityTypeEnum.ITEM, ItemAccessors.getMappingId(item));
const container = HelperUtils.lookupDataOrFallback(containerLookup, definitionKey);
if (container) {
if (this.isClaimRestricted(item)) {
return false;
}
const infusion = ItemAccessors.getInfusion(item);
if (infusion) {
const infusionCharacterId = InfusionAccessors.getCharacterId(infusion);
@@ -292,7 +301,7 @@ export class InventoryManager extends PartyManager {
return false;
}
}
return this.isSharingTurnedOnOrDeleteOnly();
return true;
}
return false;
};
@@ -370,6 +379,11 @@ export class InventoryManager extends PartyManager {
getInventoryShoppe({ onSuccess, additionalApiConfig, }) {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
// If we already have the item shoppe, return it
if (itemShoppe) {
onSuccess(itemShoppe);
return itemShoppe;
}
const characterId = characterSelectors.getId(this.state);
const loadItems = apiCreatorSelectors.makeLoadAvailableItems(this.state);
//set up pagination params
@@ -404,6 +418,7 @@ export class InventoryManager extends PartyManager {
const ruleData = rulesEngineSelectors.getRuleData(this.state);
const shoppeContainer = ContainerGenerators.generateContainer(-1, ContainerTypeEnum.SHOPPE, 'Item Shoppe', transformedItems, false, null, null, characterId, null, ruleData);
const shoppeManager = getContainerManager(Object.assign(Object.assign({}, this.params), { container: shoppeContainer, overrideItems: transformedItems }));
itemShoppe = shoppeManager;
onSuccess(shoppeManager);
return shoppeManager;
});
@@ -57,7 +57,8 @@ export class ItemManager extends PartyManager {
else {
const isContainer = this.isContainer();
const infusion = this.getInfusion();
this.dispatch(characterActions.itemDestroy(this.getMappingId(), isContainer && infusion ? false : true, this.handleAcceptOnSuccess(isShared, onSuccess)));
const itemPlan = this.getItemPlan();
this.dispatch(characterActions.itemDestroy(this.getMappingId(), isContainer && (infusion || itemPlan) ? false : true, this.handleAcceptOnSuccess(isShared, onSuccess)));
}
};
this.handleMove = ({ containerDefinitionKey }, onSuccess, onError) => {
@@ -124,6 +125,13 @@ export class ItemManager extends PartyManager {
this.getPrimarySourceCategoryId = () => ItemAccessors.getPrimarySourceCategoryId(this.item);
this.getSecondarySourceCategoryIds = () => ItemAccessors.getSecondarySourceCategoryIds(this.item);
this.getAllSourceCategoryIds = () => ItemUtils.getAllSourceCategoryIds(this.item);
this.getDefinitionKey = () => { var _a; return (_a = ItemAccessors.getDefinition(this.item)) === null || _a === void 0 ? void 0 : _a.definitionKey; };
this.getOriginDefinitionKey = () => ItemAccessors.getOriginDefinitionKey(this.item);
this.getDescription = () => ItemAccessors.getDescription(this.item);
this.getRarity = () => ItemAccessors.getRarity(this.item);
this.canAttune = () => ItemAccessors.canAttune(this.item);
this.getAttunementDescription = () => ItemAccessors.getAttunementDescription(this.item);
this.getPartyInventoryRestrictions = () => ItemAccessors.getPartyInventoryRestrictions(this.item);
// Validator
this.isShared = () => this.requiresContainer().isShared();
this.isArmorContract = () => ItemUtils.isArmorContract(this.item);
@@ -134,10 +142,7 @@ export class ItemManager extends PartyManager {
const canEquip = ItemAccessors.canEquip(this.item);
const isEquipped = ItemAccessors.isEquipped(this.item);
if (isShared && canEquip) {
if (this.isSharingTurnedOn() || (this.isSharingTurnedDeleteOnly() && isEquipped)) {
return isEquipped ? this.isEquippedToCurrentCharacter() : true;
}
return false;
return isEquipped ? this.isEquippedToCurrentCharacter() : true;
}
return canEquip;
};
@@ -145,6 +150,7 @@ export class ItemManager extends PartyManager {
const characterId = rulesEngineSelectors.getId(this.state);
return ItemValidators.isEquippedToCurrentCharacter(this.item, characterId);
};
this.isReplicated = () => ItemUtils.isReplicated(this.item);
// Utils
this.getRarityLevel = () => ItemUtils.getRarityLevel(this.item);
this.generateContainerDefinitionKey = () => {
@@ -161,12 +167,61 @@ export class ItemManager extends PartyManager {
}
return DefaultCharacterName;
};
this.getItemPlan = () => {
const itemPlans = rulesEngineSelectors.getAvailableItemPlans(this.state);
return ItemUtils.getItemPlan(this.item, itemPlans);
};
this.getNotes = () => {
const weaponSpellDamageGroups = rulesEngineSelectors.getWeaponSpellDamageGroups(this.state);
const ruleData = rulesEngineSelectors.getRuleData(this.state);
const abilityLookup = rulesEngineSelectors.getAbilityLookup(this.state);
const proficiencyBonus = rulesEngineSelectors.getProficiencyBonus(this.state);
return ItemNotes.getNoteComponents(this.item, weaponSpellDamageGroups, ruleData, abilityLookup, proficiencyBonus);
return ItemNotes.getNoteComponents(this.item, weaponSpellDamageGroups, ruleData, abilityLookup, proficiencyBonus, this.getItemPlan());
};
this.getIntroText = () => {
const strings = [];
const subType = this.getSubType();
const rarity = this.getRarity();
const attunementDescription = this.getAttunementDescription();
let type = this.getType();
if (this.isWeaponContract()) {
if (this.isMagic()) {
type = `Weapon (${type})`;
}
else {
type = 'Weapon';
}
}
else if (this.isArmorContract()) {
if (this.isMagic()) {
type = `Armor (${this.getBaseArmorName()})`;
}
else {
type = 'Armor';
}
}
else if (this.isGearContract()) {
type = subType || type;
}
else if (this.isCustom()) {
type = 'Custom';
}
if (this.isLegacy()) {
strings.push('Legacy • ');
}
if (type) {
strings.push(type);
}
if (!this.isCustom() && rarity) {
strings.push(`, ${rarity}`);
}
if (this.canAttune()) {
strings.push(` (requires attunement${attunementDescription ? ` by a ${attunementDescription}` : ''})`);
}
if (this.isContainer()) {
strings.push(', container');
}
return strings.join('');
};
this.getMetaText = () => {
const appliedWeaponReplacementStats = this.getAppliedWeaponReplacementStats();
@@ -1,6 +1,5 @@
import { serviceDataActions } from '../actions';
import { CampaignAccessors, CampaignUtils } from '../engine/Campaign';
import { PartyInventorySharingStateEnum } from '../engine/Campaign/constants';
import { serviceDataSelectors } from '../selectors';
import { MessageManager } from './MessageManager';
export class PartyManager extends MessageManager {
@@ -28,11 +27,6 @@ export class PartyManager extends MessageManager {
};
};
// Accessors
this.getSharingState = () => {
var _a;
const partyInfo = serviceDataSelectors.getPartyInfo(this.state);
return (_a = (partyInfo && CampaignAccessors.getSharingState(partyInfo))) !== null && _a !== void 0 ? _a : PartyInventorySharingStateEnum.OFF; // if we get null return off
};
this.getPartyId = () => {
const partyInfo = serviceDataSelectors.getPartyInfo(this.state);
return partyInfo ? CampaignAccessors.getId(partyInfo) : null;
@@ -44,11 +38,6 @@ export class PartyManager extends MessageManager {
}
return '';
};
// Validators
this.isSharingTurnedOn = () => this.getSharingState() === PartyInventorySharingStateEnum.ON;
this.isSharingTurnedOff = () => this.getSharingState() === PartyInventorySharingStateEnum.OFF;
this.isSharingTurnedDeleteOnly = () => this.getSharingState() === PartyInventorySharingStateEnum.DELETE_ONLY;
this.isSharingTurnedOnOrDeleteOnly = () => CampaignUtils.isSharingStateActive(this.getSharingState());
this.addSubscriptions({
[this.EVENT_TYPES.ITEM_SHARED_FULFILLED]: this.updateSharedInventory,
});
@@ -1,148 +0,0 @@
import { ExpressionOperators, StatementOperators } from '@dndbeyond/character-gfs';
import { ModifierAccessors, ModifierTypeEnum, ModifierValidators } from "../../../engine/Modifier";
import { RuleDataUtils } from "../../../engine/RuleData";
export function transformAbilityScores(validGlobalModifiers, baseStats, ruleData) {
const abilityScoreModifiers = validGlobalModifiers.filter((modifier) => {
return (ModifierValidators.isValidStatScoreModifier(modifier, 1) ||
ModifierValidators.isValidStatScoreModifier(modifier, 2) ||
ModifierValidators.isValidStatScoreModifier(modifier, 3) ||
ModifierValidators.isValidStatScoreModifier(modifier, 4) ||
ModifierValidators.isValidStatScoreModifier(modifier, 5) ||
ModifierValidators.isValidStatScoreModifier(modifier, 6));
});
const baseAbilityScores = baseStats.flatMap((stat) => {
var _a, _b, _c;
if (stat.id) {
const statName = (_b = (_a = RuleDataUtils.getStatNameById(stat.id, ruleData, true)) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : '';
stat.value = (_c = stat.value) !== null && _c !== void 0 ? _c : 0;
// Math.floor((stat.value - 10) / 2)
return [
{
precedence: 0.5,
target: ['attributes', statName, 'value'],
operator: StatementOperators.SET,
operand: {
operator: ExpressionOperators.STATIC_VALUE,
values: [stat.value],
operands: null,
},
},
{
precedence: 0.5,
target: ['attributes', statName, 'modifier'],
operator: StatementOperators.SET,
operand: {
operator: ExpressionOperators.FLOOR,
values: null,
operands: [
{
operator: ExpressionOperators.MULTIPLY,
values: null,
operands: [
{
operator: ExpressionOperators.STATIC_VALUE,
values: [0.5],
operands: null,
},
{
operator: ExpressionOperators.ADD,
values: null,
operands: [
{
operator: ExpressionOperators.STATIC_VALUE,
values: [-10],
operands: null,
},
{
operator: ExpressionOperators.DYNAMIC_VALUE,
values: ['attributes', statName, 'value'],
operands: null,
},
],
},
],
},
],
},
},
{
precedence: 0.5,
target: ['attributes', statName, 'max'],
operator: StatementOperators.SET,
operand: {
operator: ExpressionOperators.STATIC_VALUE,
values: [20],
operands: null,
},
},
];
}
return [];
});
const gfsAbilityScores = abilityScoreModifiers.map((abilityScoreModifier) => {
var _a, _b, _c, _d;
const modifierType = ModifierAccessors.getType(abilityScoreModifier);
switch (modifierType) {
case ModifierTypeEnum.SET:
return {
precedence: 0.5,
target: [
'attributes',
(_a = ModifierAccessors.getSubType(abilityScoreModifier)) === null || _a === void 0 ? void 0 : _a.replace('-score', ''),
'value',
],
operator: StatementOperators.SET,
operand: {
operator: ExpressionOperators.STATIC_VALUE,
values: [ModifierAccessors.getValue(abilityScoreModifier)],
},
};
case ModifierTypeEnum.BONUS:
default:
return {
precedence: 0.5,
target: [
'attributes',
(_b = ModifierAccessors.getSubType(abilityScoreModifier)) === null || _b === void 0 ? void 0 : _b.replace('-score', ''),
'value',
],
operator: StatementOperators.SET,
operand: {
operator: ExpressionOperators.ADD,
values: null,
operands: [
{
operator: ExpressionOperators.DYNAMIC_VALUE,
operands: null,
values: [
'attributes',
(_c = ModifierAccessors.getSubType(abilityScoreModifier)) === null || _c === void 0 ? void 0 : _c.replace('-score', ''),
'value',
],
},
{
operator: ExpressionOperators.STATIC_VALUE,
values: [(_d = ModifierAccessors.getValue(abilityScoreModifier)) !== null && _d !== void 0 ? _d : 0],
operands: null,
},
],
},
};
}
});
const generatedFeature = {
meta: {
name: 'Generated Ability Scores',
category: 'Generated',
},
statements: [...baseAbilityScores, ...gfsAbilityScores],
featureId: 'generated-ability-scores',
labels: [],
sources: [],
subfeatures: [],
acceptableInputs: {},
primarySource: null,
secondarySources: [],
};
return { baseAbilityScores, gfsAbilityScores, generatedFeature };
}
@@ -269,6 +269,9 @@ const initialItemState = {
id: -1,
isAttuned: null,
limitedUse: null,
originEntityId: null,
originEntityTypeId: null,
originDefinitionKey: null,
quantity: -1,
};
export function itemReducer(state = initialItemState, action) {
@@ -134,7 +134,7 @@ const initialPartyInfoState = {
name: null,
publicNotes: null,
partyInventory: [],
sharingState: 0,
partyRestrictions: [],
modifiers: null,
spells: null,
};
@@ -187,6 +187,7 @@ export const initialState = {
},
partyInfo: null,
campaignSettings: [],
longRestText: null,
};
function serviceData(state = initialState, action) {
var _a;
@@ -309,6 +310,9 @@ function serviceData(state = initialState, action) {
case actionTypes.CAMPAIGN_SETTINGS_SET_COMMIT: {
return Object.assign(Object.assign({}, state), { campaignSettings: action.payload });
}
case actionTypes.LONG_REST_TEXT_SET: {
return Object.assign(Object.assign({}, state), { longRestText: action.payload });
}
default:
// not implemented
}
@@ -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));
}
}
}
}
@@ -158,14 +158,3 @@ export const makeLoadAvailableOptionalClassFeatures = createSelector([rulesEngin
};
};
});
//----------------------------
// BELOW LIES GFS
//----------------------------
// TODO: restrictEntityDefinitionResponseData is filtering by homebrew and sources categories based on preferences
// so maybe that should just be part of the api.
// if you change it during game you would have to make the api call again.
export const makeLoadAvailableFeatures = createSelector([rulesEngineSelectors.getEntityRestrictionData, characterSelectors.getId], (entityRestrictionData, characterId) => (additionalConfig) => {
return ApiRequests.getCharacterGameDataFeatures(additionalConfig).then((response) =>
// restrictEntityDefinitionResponseData(response, entityRestrictionData, characterId)
response);
});
@@ -1,4 +1,5 @@
import { createSelector } from 'reselect';
import { ItemPlanGenerators } from "../../engine/ItemPlan";
import { ApiGenerators } from '../../api';
import { AbilityGenerators } from '../../engine/Ability';
import { ActionGenerators } from '../../engine/Action';
@@ -759,6 +760,11 @@ export const getInventoryLookup = createSelector([getInventory], ItemGenerators.
*
*/
export const getPartyInventoryLookup = createSelector([getPartyInventory], ItemGenerators.generateInventoryLookup);
/**
* returns all Items in all inventories, grouped by origin
* @returns {Record<string, Array<Item>>}
*/
export const getAllInventoryLookupByOrigin = createSelector([getInventory, getPartyInventory], ItemGenerators.generateInventoryLookupByOrigin);
/**
* TODO v5.1: remove customItems from this selector when mobile moves up to 5.1 customItems as Items
* @returns {Array<Container>}
@@ -769,7 +775,6 @@ export const getInventoryContainers = createSelector([
characterSelectors.getId,
serviceDataSelectors.getPartyInfo,
getPartyInventory,
featureFlagInfoSelectors.getFeatureFlagInfo,
characterSelectors.getCurrencies,
characterSelectors.getPreferences,
getRuleData,
@@ -780,6 +785,11 @@ export const getPartyInventoryContainers = createSelector([getInventoryContainer
* @returns {ContainerLookup}
*/
export const getContainerLookup = createSelector([getInventoryContainers], ContainerGenerators.generateContainerLookup);
/**
* @returns {Array<ItemPlan>}
*/
export const getAvailableItemPlans = createSelector([getClasses, getAllInventoryLookupByOrigin, getContainerLookup], ItemPlanGenerators.generateAggregatedItemPlans);
export const getMaxReplicatedItemsCount = createSelector([getClasses], ItemPlanGenerators.generateMaxReplicatedItemCount);
/**
* @returns {Array<Modifier>}
*/
@@ -1384,7 +1394,15 @@ export const getProficiencyGroups = createSelector([
/**
* @returns {SnippetData}
*/
export const getSnippetData = createSelector([getProficiencyBonus, getAbilityKeyLookup, getAbilityLookup, getExperienceInfo, getHitPointInfo, getRuleData], SnippetGenerators.generateSnippetData);
export const getSnippetData = createSelector([
getProficiencyBonus,
getAbilityKeyLookup,
getAbilityLookup,
getExperienceInfo,
getHitPointInfo,
getCurrentCarriedWeightSpeed,
getRuleData,
], SnippetGenerators.generateSnippetData);
/**
* @returns {boolean}
*/
@@ -8,3 +8,4 @@ export const getVehicleComponentMappings = (state) => state.serviceData.vehicleC
export const getVehicleMappings = (state) => state.serviceData.vehicleMappings;
export const getPartyInfo = (state) => state.serviceData.partyInfo;
export const getCampaignSettings = (state) => state.serviceData.campaignSettings;
export const getLongRestText = (state) => state.serviceData.longRestText;