Grabbed dndbeyond's source code

```
~/go/bin/sourcemapper -output ddb -jsurl https://media.dndbeyond.com/character-app/static/js/main.90aa78c5.js
```
This commit is contained in:
2025-05-28 11:50:03 -07:00
commit 8df9031d27
3592 changed files with 319051 additions and 0 deletions
@@ -0,0 +1,18 @@
export var ArmorClassTypeEnum;
(function (ArmorClassTypeEnum) {
ArmorClassTypeEnum["ARMOR"] = "Armor";
ArmorClassTypeEnum["SHIELD"] = "Shield";
ArmorClassTypeEnum["DEX_BONUS"] = "Dexterity Bonus";
ArmorClassTypeEnum["MAGIC_BONUS"] = "Magic Bonus";
ArmorClassTypeEnum["MISC_BONUS"] = "Misc Bonus";
ArmorClassTypeEnum["UNARMORED_BONUS"] = "Unarmored Bonus";
ArmorClassTypeEnum["ARMORED_BONUS"] = "Armored Bonus";
ArmorClassTypeEnum["OVERRIDE_BASE_ARMOR"] = "Base Armor + DEX (Override)";
})(ArmorClassTypeEnum || (ArmorClassTypeEnum = {}));
export var ArmorClassExtraTypeEnum;
(function (ArmorClassExtraTypeEnum) {
ArmorClassExtraTypeEnum["MODIFIER"] = "MODIFIER";
ArmorClassExtraTypeEnum["ITEM"] = "ITEM";
ArmorClassExtraTypeEnum["STRING"] = "STRING";
ArmorClassExtraTypeEnum["NUMBER"] = "NUMBER";
})(ArmorClassExtraTypeEnum || (ArmorClassExtraTypeEnum = {}));
@@ -0,0 +1,7 @@
export var AttackSourceTypeEnum;
(function (AttackSourceTypeEnum) {
AttackSourceTypeEnum["ACTION"] = "ACTION";
AttackSourceTypeEnum["ITEM"] = "ITEM";
AttackSourceTypeEnum["SPELL"] = "SPELL";
AttackSourceTypeEnum["CUSTOM"] = "CUSTOM";
})(AttackSourceTypeEnum || (AttackSourceTypeEnum = {}));
@@ -0,0 +1,6 @@
export var DeathCauseEnum;
(function (DeathCauseEnum) {
DeathCauseEnum["NONE"] = "NONE";
DeathCauseEnum["DEATHSAVES"] = "DEATHSAVES";
DeathCauseEnum["CONDITION"] = "CONDITION";
})(DeathCauseEnum || (DeathCauseEnum = {}));
@@ -0,0 +1,14 @@
export var ProtectionSupplierTypeEnum;
(function (ProtectionSupplierTypeEnum) {
ProtectionSupplierTypeEnum["RACIAL_TRAIT"] = "RACIAL_TRAIT";
ProtectionSupplierTypeEnum["CLASS_FEATURE"] = "CLASS_FEATURE";
ProtectionSupplierTypeEnum["FEAT"] = "FEAT";
ProtectionSupplierTypeEnum["ITEM"] = "ITEM";
})(ProtectionSupplierTypeEnum || (ProtectionSupplierTypeEnum = {}));
export var ProtectionAvailabilityStatusEnum;
(function (ProtectionAvailabilityStatusEnum) {
ProtectionAvailabilityStatusEnum["AVAILABLE"] = "AVAILABLE";
ProtectionAvailabilityStatusEnum["NO_LIMITED_USE_REMAINING"] = "NO_LIMITED_USE_REMAINING";
ProtectionAvailabilityStatusEnum["ITEM_NOT_EQUIPPED"] = "ITEM_NOT_EQUIPPED";
ProtectionAvailabilityStatusEnum["ITEM_NOT_ATTUNED"] = "ITEM_NOT_ATTUNED";
})(ProtectionAvailabilityStatusEnum || (ProtectionAvailabilityStatusEnum = {}));
@@ -0,0 +1,42 @@
import { ActionAccessors } from '../../Action';
import { ActivationAccessors } from '../../Activation';
import { ActivatableTypeEnum } from '../../Core';
import { OptionAccessors } from '../../Option';
/**
*
* @param options
* @param parentEntity
*/
export function deriveActivatableOptions(options, parentEntity) {
const activatables = [];
options.forEach((option) => {
const actions = OptionAccessors.getActions(option);
if (actions.length) {
activatables.push(...deriveActivatableActions(actions, parentEntity));
}
});
return activatables;
}
/**
*
* @param actions
* @param parentEntity
*/
export function deriveActivatableActions(actions, parentEntity) {
const activatables = [];
actions.forEach((action) => {
const activation = ActionAccessors.getActivation(action);
if (activation !== null && ActivationAccessors.getType(activation) !== null) {
activatables.push({
type: ActivatableTypeEnum.ACTION,
sortText: ActionAccessors.getName(action),
activation,
key: ActionAccessors.getUniqueKey(action),
limitedUse: ActionAccessors.getLimitedUse(action),
entity: action,
parentEntity,
});
}
});
return activatables;
}
@@ -0,0 +1,13 @@
/**
*
* @param armorClassSources
* @param initialValue
*/
export function deriveArmorClassSupplierTotal(armorClassSources, initialValue = 0) {
return armorClassSources.reduce((acc, armorClassSource) => {
if (armorClassSource.amount) {
acc += armorClassSource.amount;
}
return acc;
}, initialValue);
}
@@ -0,0 +1,45 @@
import { AbilityAccessors } from '../../Ability';
import { ModifierDerivers, ModifierValidators } from '../../Modifier';
/**
*
* @param proficiencyBonus
* @param abilityModifier
*/
export function deriveAttackSaveValue(proficiencyBonus, abilityModifier) {
return 8 + proficiencyBonus + abilityModifier;
}
/**
*
* @param proficiencyBonus
* @param abilityModifier
*/
export function deriveAttackModifier(proficiencyBonus, abilityModifier) {
return proficiencyBonus + abilityModifier;
}
/**
*
* @param availableAbilities
* @param modifiers
* @param proficiencyBonus
* @param abilityLookup
*/
export function deriveAttackAbilityPossibilities(availableAbilities, modifiers, proficiencyBonus, abilityLookup) {
return availableAbilities.map((abilityId) => {
const ability = abilityLookup[abilityId];
// get bonus stat attack modifiers
const bonusStatToHitModifiers = modifiers.filter((modifier) => ModifierValidators.isValidBonusStatAttackModifier(modifier, abilityId));
const bonusStatToHitModifierTotal = ModifierDerivers.sumModifiers(bonusStatToHitModifiers, abilityLookup);
// get damage stat attack modifiers
const damageStatAttackModifiers = modifiers.filter((modifier) => ModifierValidators.isValidDamageStatAttackModifier(modifier, abilityId));
const damageStatAttackModifierTotal = ModifierDerivers.sumModifiers(damageStatAttackModifiers, abilityLookup);
const abilityModifier = ability ? AbilityAccessors.getModifier(ability) : null;
const modifier = abilityModifier ? abilityModifier : 0;
const toHit = modifier + proficiencyBonus + bonusStatToHitModifierTotal;
return {
abilityId,
toHit,
modifier,
damageBonus: damageStatAttackModifierTotal + modifier,
};
});
}
@@ -0,0 +1,20 @@
import { DiceAdjustmentTypeEnum } from '../../Dice';
import { ModifierDerivers, ModifierValidators } from '../../Modifier';
/**
*
* @param modifiers
*/
export function deriveAdvantageDeathSavesAdjustments(modifiers) {
return modifiers
.filter((modifier) => ModifierValidators.isAdvantageDeathSavesModifier(modifier))
.map((modifier) => ModifierDerivers.deriveDiceAdjustment(modifier, DiceAdjustmentTypeEnum.ADVANTAGE));
}
/**
*
* @param modifiers
*/
export function deriveDisadvantageDeathSavesAdjustments(modifiers) {
return modifiers
.filter((modifier) => ModifierValidators.isDisadvantageDeathSavesModifier(modifier))
.map((modifier) => ModifierDerivers.deriveDiceAdjustment(modifier, DiceAdjustmentTypeEnum.DISADVANTAGE));
}
@@ -0,0 +1,15 @@
import { DataOriginGenerators, DataOriginTypeEnum } from '../../DataOrigin';
/**
*
* @param adjustment
*/
export function deriveCustomDefenseAdjustmentDataOrigin(adjustment) {
return DataOriginGenerators.generateDataOrigin(DataOriginTypeEnum.CUSTOM, adjustment.source ? adjustment.source : 'Custom');
}
/**
*
* @param adjustment
*/
export function deriveCustomConditionDefenseAdjustmentDataOrigin(adjustment) {
return DataOriginGenerators.generateDataOrigin(DataOriginTypeEnum.CUSTOM, adjustment.source ? adjustment.source : 'Custom');
}
@@ -0,0 +1,43 @@
import { ItemAccessors, ItemValidators } from '../../Item';
import { LimitedUseDerivers } from '../../LimitedUse';
import { ModifierAccessors } from '../../Modifier';
import { ProtectionAvailabilityStatusEnum } from '../constants';
/**
*
* @param limitedUse
* @param abilityLookup
* @param ruleData
* @param proficiencyBonus
*/
export function deriveProtectionSupplierAvailabilityStatus(limitedUse, abilityLookup, ruleData, proficiencyBonus) {
let availabilityStatus = ProtectionAvailabilityStatusEnum.AVAILABLE;
if (!LimitedUseDerivers.deriveHasUsesAvailable(limitedUse, abilityLookup, ruleData, proficiencyBonus)) {
availabilityStatus = ProtectionAvailabilityStatusEnum.NO_LIMITED_USE_REMAINING;
}
return availabilityStatus;
}
/**
*
* @param item
* @param modifier
* @param limitedUse
* @param abilityLookup
* @param ruleData
* @param proficiencyBonus
* @param characterId
*/
export function deriveItemProtectionSupplierAvailabilityStatus(item, modifier, limitedUse, abilityLookup, ruleData, proficiencyBonus, characterId) {
if (ItemAccessors.canEquip(item)) {
const isEquippedToCurrentCharacter = ItemValidators.isEquippedToCurrentCharacter(item, characterId);
if (!isEquippedToCurrentCharacter) {
return ProtectionAvailabilityStatusEnum.ITEM_NOT_EQUIPPED;
}
if (ModifierAccessors.requiresAttunement(modifier) && !ItemAccessors.isAttuned(item)) {
return ProtectionAvailabilityStatusEnum.ITEM_NOT_ATTUNED;
}
}
if (!LimitedUseDerivers.deriveHasUsesAvailable(limitedUse, abilityLookup, ruleData, proficiencyBonus)) {
return ProtectionAvailabilityStatusEnum.NO_LIMITED_USE_REMAINING;
}
return ProtectionAvailabilityStatusEnum.AVAILABLE;
}
@@ -0,0 +1,43 @@
import { ProficiencyLevelEnum, ProficiencyRoundingEnum } from '../../Core';
import { ModifierDerivers, ModifierValidators } from '../../Modifier';
import { RuleDataUtils } from '../../RuleData';
/**
*
* @param level
* @param modifiers
* @param ruleData
*/
export function deriveProficiencyBonus(level, modifiers, ruleData) {
const levelBonus = RuleDataUtils.getLevelProficiencyBonus(level, ruleData);
const bonusProficiencyBonusModifiers = modifiers.filter((modifier) => ModifierValidators.isBonusProficiencyBonusModifier(modifier));
const modifierBonusTotal = ModifierDerivers.sumModifiers(bonusProficiencyBonusModifiers);
return levelBonus + modifierBonusTotal;
}
/**
*
* @param proficiencyLevel
* @param proficiencyBonus
* @param proficiencyRounding
*/
export function deriveProficiencyBonusAmount(proficiencyLevel, proficiencyBonus, proficiencyRounding = ProficiencyRoundingEnum.DOWN) {
let proficiencyBonusAmount = 0;
switch (proficiencyLevel) {
case ProficiencyLevelEnum.EXPERT:
proficiencyBonusAmount = proficiencyBonus * 2;
break;
case ProficiencyLevelEnum.FULL:
proficiencyBonusAmount = proficiencyBonus;
break;
case ProficiencyLevelEnum.HALF:
if (proficiencyRounding === ProficiencyRoundingEnum.UP) {
proficiencyBonusAmount = Math.ceil(proficiencyBonus / 2);
}
else {
proficiencyBonusAmount = Math.floor(proficiencyBonus / 2);
}
break;
default:
// not implemented
}
return proficiencyBonusAmount;
}
@@ -0,0 +1,48 @@
import { RuleDataAccessors } from '../../RuleData';
/**
*
* @param xp
* @param ruleData
*/
export function deriveXpLevel(xp, ruleData) {
let level = 0;
if (xp === 0) {
return 1;
}
if (xp >= deriveMaxXp(ruleData)) {
return RuleDataAccessors.getMaxCharacterLevel(ruleData);
}
const levelExperiencePoints = RuleDataAccessors.getLevelExperiencePoints(ruleData);
levelExperiencePoints.forEach((levelXpAmount, levelIdx) => {
if (level === 0 && xp < levelXpAmount) {
level = levelIdx;
}
});
return level;
}
/**
*
* @param level
* @param ruleData
*/
export function deriveCurrentLevelXp(level, ruleData) {
const levelExperiencePoints = RuleDataAccessors.getLevelExperiencePoints(ruleData);
return levelExperiencePoints[Math.max(0, level - 1)];
}
/**
*
* @param level
* @param ruleData
*/
export function deriveNextLevelXp(level, ruleData) {
const levelExperiencePoints = RuleDataAccessors.getLevelExperiencePoints(ruleData);
return levelExperiencePoints[Math.min(levelExperiencePoints.length - 1, level)];
}
/**
*
* @param ruleData
*/
export function deriveMaxXp(ruleData) {
const levelExperiencePoints = RuleDataAccessors.getLevelExperiencePoints(ruleData);
return levelExperiencePoints[levelExperiencePoints.length - 1];
}
@@ -0,0 +1,22 @@
import { SaveTypeEnum } from '../../Core';
/**
*
* @param type
*/
export function deriveSaveTypeName(type) {
let name = '';
switch (type) {
case SaveTypeEnum.ADVANTAGE:
name = 'Advantage';
break;
case SaveTypeEnum.DISADVANTAGE:
name = 'Disadvantage';
break;
case SaveTypeEnum.BONUS:
name = 'Bonus';
break;
default:
//not implemented
}
return name;
}
@@ -0,0 +1,59 @@
import { AbilitySkillEnum } from '../../Core';
import { ModifierDerivers, ModifierValidators } from '../../Modifier';
import { SkillAccessors } from '../../Skill';
/**
*
* @param skillModifier
* @param bonuses
*/
export function derivePassiveSkill(skillModifier, bonuses = 0) {
return 10 + skillModifier + bonuses;
}
/**
*
* @param skills
* @param modifiers
* @param abilityLookup
*/
export function derivePassivePerception(skills, modifiers, abilityLookup) {
const skill = skills.find((skill) => SkillAccessors.getId(skill) === AbilitySkillEnum.PERCEPTION);
const skillModifier = skill ? SkillAccessors.getModifier(skill) : null;
if (!skill || skillModifier === null) {
return 0;
}
const bonusModifiers = modifiers.filter((modifier) => ModifierValidators.isBonusPassivePerceptionModifier(modifier));
const bonusModifierTotal = ModifierDerivers.sumModifiers(bonusModifiers, abilityLookup);
return derivePassiveSkill(skillModifier, bonusModifierTotal);
}
/**
*
* @param skills
* @param modifiers
* @param abilityLookup
*/
export function derivePassiveInvestigation(skills, modifiers, abilityLookup) {
const skill = skills.find((skill) => SkillAccessors.getId(skill) === AbilitySkillEnum.INVESTIGATION);
const skillModifier = skill ? SkillAccessors.getModifier(skill) : null;
if (!skill || skillModifier === null) {
return 0;
}
const bonusModifiers = modifiers.filter((modifier) => ModifierValidators.isBonusPassiveInvestigationModifier(modifier));
const bonusModifierTotal = ModifierDerivers.sumModifiers(bonusModifiers, abilityLookup);
return derivePassiveSkill(skillModifier, bonusModifierTotal);
}
/**
*
* @param skills
* @param modifiers
* @param abilityLookup
*/
export function derivePassiveInsight(skills, modifiers, abilityLookup) {
const skill = skills.find((skill) => SkillAccessors.getId(skill) === AbilitySkillEnum.INSIGHT);
const skillModifier = skill ? SkillAccessors.getModifier(skill) : null;
if (!skill || skillModifier === null) {
return 0;
}
const bonusModifiers = modifiers.filter((modifier) => ModifierValidators.isBonusPassiveInsightModifier(modifier));
const bonusModifierTotal = ModifierDerivers.sumModifiers(bonusModifiers, abilityLookup);
return derivePassiveSkill(skillModifier, bonusModifierTotal);
}
@@ -0,0 +1,28 @@
import { PreferenceEncumbranceTypeEnum, WeightSpeedTypeEnum } from '../../Core';
import { getCarryCapacity, getEncumberedWeight, getHeavilyEncumberedWeight } from '../utils';
/**
*
* @param currentWeight
* @param strScore
* @param size
* @param preferences
* @param modifiers
*/
export function deriveCurrentWeightSpeedType(currentWeight, strScore, size, preferences, modifiers) {
if (preferences !== null) {
if (preferences.encumbranceType === PreferenceEncumbranceTypeEnum.VARIANT) {
if (currentWeight > getHeavilyEncumberedWeight(strScore, size, modifiers)) {
return WeightSpeedTypeEnum.HEAVILY_ENCUMBERED;
}
else if (currentWeight > getEncumberedWeight(strScore, size, modifiers)) {
return WeightSpeedTypeEnum.ENCUMBERED;
}
}
else if (preferences.encumbranceType === PreferenceEncumbranceTypeEnum.ENCUMBRANCE) {
if (currentWeight > getCarryCapacity(strScore, size, modifiers)) {
return WeightSpeedTypeEnum.OVER_CARRYING_CAPACITY;
}
}
}
return WeightSpeedTypeEnum.NORMAL;
}
@@ -0,0 +1,122 @@
import { orderBy } from 'lodash';
import { ActionAccessors } from '../../Action';
import { ActivationAccessors } from '../../Activation';
import { ClassAccessors } from '../../Class';
import { ClassFeatureAccessors } from '../../ClassFeature';
import { ActivatableTypeEnum } from '../../Core';
import { FeatAccessors } from '../../Feat';
import { ItemAccessors, ItemValidators } from '../../Item';
import { RaceAccessors } from '../../Race';
import { RacialTraitAccessors } from '../../RacialTrait';
import { SpellAccessors } from '../../Spell';
import { deriveActivatableActions, deriveActivatableOptions } from '../derivers';
/**
*
* @param characterSpells
* @param classSpells
* @param classes
* @param race
* @param feats
* @param innateActions
* @param customActions
* @param equippedItems
*/
export function generateActivatables(characterSpells, classSpells, classes, race, feats, innateActions, // TODO might need to change to InnateAction
customActions, // TODO might need to change to CustomAction
equippedItems) {
const activatables = [];
characterSpells.forEach((spell) => {
const activation = SpellAccessors.getActivation(spell);
if (activation !== null && ActivationAccessors.getType(activation) !== null) {
activatables.push({
type: ActivatableTypeEnum.CHARACTER_SPELL,
sortText: SpellAccessors.getName(spell),
key: SpellAccessors.getUniqueKey(spell),
activation,
limitedUse: SpellAccessors.getLimitedUse(spell),
entity: spell,
parentEntity: null,
});
}
});
classSpells.forEach((spell) => {
const activation = SpellAccessors.getActivation(spell);
if (activation !== null && ActivationAccessors.getType(activation) !== null) {
activatables.push({
type: ActivatableTypeEnum.CLASS_SPELL,
sortText: SpellAccessors.getName(spell),
key: SpellAccessors.getUniqueKey(spell),
activation,
limitedUse: null,
entity: spell,
parentEntity: null,
});
}
});
classes.forEach((charClass) => {
ClassAccessors.getUniqueClassFeatures(charClass).forEach((feature) => {
const actions = ClassFeatureAccessors.getActions(feature);
const options = ClassFeatureAccessors.getOptions(feature);
if (actions.length) {
activatables.push(...deriveActivatableActions(actions, charClass));
}
if (options.length) {
activatables.push(...deriveActivatableOptions(options, charClass));
}
});
});
if (race) {
RaceAccessors.getRacialTraits(race).forEach((racialTrait) => {
const actions = RacialTraitAccessors.getActions(racialTrait);
const options = RacialTraitAccessors.getOptions(racialTrait);
if (actions.length) {
activatables.push(...deriveActivatableActions(actions, race));
}
if (options.length) {
activatables.push(...deriveActivatableOptions(options, race));
}
});
}
feats.forEach((feat) => {
const actions = FeatAccessors.getActions(feat);
const options = FeatAccessors.getOptions(feat);
if (actions.length) {
activatables.push(...deriveActivatableActions(actions, race));
}
if (options.length) {
activatables.push(...deriveActivatableOptions(options, race));
}
});
innateActions.forEach((action) => {
const activation = ActionAccessors.getActivation(action);
if (activation !== null) {
activatables.push({
type: ActivatableTypeEnum.ACTION,
sortText: ActionAccessors.getName(action),
activation,
key: ActionAccessors.getUniqueKey(action),
limitedUse: null,
entity: action,
parentEntity: null,
});
}
});
customActions.forEach((action) => {
const activation = ActionAccessors.getActivation(action);
if (activation !== null && ActivationAccessors.getType(activation) !== null) {
activatables.push({
type: ActivatableTypeEnum.ACTION,
sortText: ActionAccessors.getName(action),
activation,
key: ActionAccessors.getUniqueKey(action),
limitedUse: null,
entity: action,
parentEntity: null,
});
}
});
equippedItems.filter(ItemValidators.validateCanContributeActions).forEach((item) => {
activatables.push(...deriveActivatableActions(ItemAccessors.getInfusionActions(item), item));
});
return orderBy(activatables, ['sortText']);
}
@@ -0,0 +1,294 @@
import { forOwn, groupBy } from 'lodash';
import { AbilityAccessors } from '../../Ability';
import { AbilityStatEnum } from '../../Core';
import { DataOriginTypeEnum } from '../../DataOrigin';
import { HelperUtils } from '../../Helper';
import { ArmorTypeEnum, ItemAccessors, ItemDerivers, ItemHacks, ItemValidators, } from '../../Item';
import { ModifierAccessors, ModifierDerivers, ModifierGenerators, ModifierHacks, ModifierValidators, } from '../../Modifier';
import { AdjustmentTypeEnum, ValueAccessors, ValueUtils } from '../../Value';
import { ArmorClassExtraTypeEnum, ArmorClassTypeEnum } from '../constants';
import { deriveArmorClassSupplierTotal } from '../derivers';
/**
*
* @param type
* @param amount
* @param extra
* @param extraType
*/
export function generateArmorClassSupplier(type, amount, extra = null, extraType = null) {
return {
type,
amount,
extra,
extraType,
};
}
/**
*
* @param armorItem
* @param localModifiers
* @param globalModifiers
* @param abilityLookup
* @param valueLookup
* @param modifierData
*/
export function generateArmoredBaseArmorSuppliers(armorItem, localModifiers, globalModifiers, abilityLookup, valueLookup, modifierData) {
const dexModifier = AbilityAccessors.getModifier(abilityLookup[AbilityStatEnum.DEXTERITY]);
const baseArmorOverrideValue = ValueUtils.getKeyValue(valueLookup, AdjustmentTypeEnum.OVERRIDE_BASE_ARMOR);
const suppliers = [];
if (baseArmorOverrideValue === null) {
suppliers.push(generateArmorClassSupplier(ArmorClassTypeEnum.ARMOR, armorItem.armorClass, armorItem.item, ArmorClassExtraTypeEnum.ITEM));
suppliers.push(generateArmorClassSupplier(ArmorClassTypeEnum.DEX_BONUS, ItemDerivers.deriveArmorDexBonus(armorItem.item, dexModifier ? dexModifier : 0, globalModifiers, abilityLookup), ItemDerivers.deriveArmorDexBonusMax(armorItem.item, globalModifiers, abilityLookup), ArmorClassExtraTypeEnum.NUMBER));
}
const setArmoredModifiers = localModifiers.filter((modifier) => ModifierValidators.isSetArmoredArmorClassModifier(modifier));
const setArmoredHighestModifier = ModifierDerivers.deriveHighestValueModifier(setArmoredModifiers, modifierData);
if (setArmoredHighestModifier) {
suppliers.push(generateArmorClassSupplier(ArmorClassTypeEnum.ARMORED_BONUS, ModifierDerivers.deriveValue(setArmoredHighestModifier, modifierData), setArmoredHighestModifier, ArmorClassExtraTypeEnum.MODIFIER));
}
const bonusArmoredModifiers = globalModifiers.filter((modifier) => ModifierValidators.isBonusArmoredArmorClassModifier(modifier));
bonusArmoredModifiers.forEach((modifier) => {
suppliers.push(generateArmorClassSupplier(ArmorClassTypeEnum.ARMORED_BONUS, ModifierDerivers.deriveValue(modifier, modifierData), modifier, ArmorClassExtraTypeEnum.MODIFIER));
});
return suppliers;
}
/**
*
* @param localModifiers
* @param globalModifiers
* @param abilityLookup
* @param valueLookup
* @param ruleData
* @param modifierData
*/
export function generateUnarmoredBaseArmorSuppliers(localModifiers, globalModifiers, abilityLookup, valueLookup, ruleData, modifierData) {
const baseArmorOverrideValue = ValueUtils.getKeyValue(valueLookup, AdjustmentTypeEnum.OVERRIDE_BASE_ARMOR);
const suppliers = [];
if (baseArmorOverrideValue === null) {
suppliers.push(generateArmorClassSupplier(ArmorClassTypeEnum.ARMOR, ruleData.noArmorAcAmount, 'None', ArmorClassExtraTypeEnum.STRING));
const unarmoredDexModifier = ItemDerivers.deriveUnarmoredDexBonus(localModifiers, globalModifiers, abilityLookup);
if (unarmoredDexModifier !== 0) {
suppliers.push(generateArmorClassSupplier(ArmorClassTypeEnum.DEX_BONUS, unarmoredDexModifier, ItemDerivers.deriveUnarmoredDexBonusMax(globalModifiers), ArmorClassExtraTypeEnum.NUMBER));
}
}
const setUnarmoredModifiers = localModifiers.filter((modifier) => ModifierValidators.isSetUnarmoredArmorClassModifier(modifier));
// TODO: Fix Dirty Hack for Integrated Protection
const hasLightArmorProficiency = globalModifiers.filter((modifier) => ModifierHacks.hack__isProficiencyLightArmor(modifier)).length > 0;
if (setUnarmoredModifiers.length) {
// let setUnarmoredHighestModifier = ModifierUtils.getHighestValueModifier(setUnarmoredModifiers, modifierData);
const setUnarmoredHighestModifier = HelperUtils.getLast(setUnarmoredModifiers, (modifier) => ItemHacks.hack__getSetUnarmoredModifierValue(modifier, modifierData, hasLightArmorProficiency));
if (setUnarmoredHighestModifier) {
suppliers.push(generateArmorClassSupplier(ArmorClassTypeEnum.UNARMORED_BONUS, ItemHacks.hack__getSetUnarmoredModifierValue(setUnarmoredHighestModifier, modifierData, hasLightArmorProficiency), setUnarmoredHighestModifier, ArmorClassExtraTypeEnum.MODIFIER));
}
}
const bonusUnarmoredModifiers = globalModifiers.filter((modifier) => ModifierValidators.isBonusUnarmoredArmorClassModifier(modifier));
bonusUnarmoredModifiers.forEach((modifier) => {
suppliers.push(generateArmorClassSupplier(ArmorClassTypeEnum.UNARMORED_BONUS, ModifierDerivers.deriveValue(modifier, modifierData), modifier, ArmorClassExtraTypeEnum.MODIFIER));
});
return suppliers;
}
/**
*
* @param armorItem
* @param modifiers
* @param abilityLookup
* @param valueLookup
* @param ruleData
* @param modifierData
*/
export function generateBaseArmorSuppliers(armorItem, modifiers, abilityLookup, valueLookup, ruleData, modifierData) {
const branchPossibilities = [];
const branchingModifiers = modifiers.filter((modifier) => ModifierValidators.isArmorBranchingModifier(modifier));
const groupedBranchModifiers = groupBy(branchingModifiers, (modifier) => ModifierGenerators.generateDataOriginKey(modifier));
forOwn(groupedBranchModifiers, (branchGroupModifiers) => {
const ignoreUnarmoredWhileArmoredModifiers = branchGroupModifiers.filter((modifier) => ModifierValidators.isIgnoreUnarmoredWhileArmoredModifier(modifier));
const ignoreUnarmoredWhileArmored = !!ignoreUnarmoredWhileArmoredModifiers.length;
if (armorItem) {
branchPossibilities.push(generateBaseArmorBranchPossibility(armorItem, branchGroupModifiers, modifiers, abilityLookup, valueLookup, ruleData, modifierData));
if (!ignoreUnarmoredWhileArmored) {
branchPossibilities.push(generateBaseArmorBranchPossibility(null, branchGroupModifiers, modifiers, abilityLookup, valueLookup, ruleData, modifierData));
}
}
else {
branchPossibilities.push(generateBaseArmorBranchPossibility(null, branchGroupModifiers, modifiers, abilityLookup, valueLookup, ruleData, modifierData));
}
});
// innate armor calc
branchPossibilities.push(generateBaseArmorBranchPossibility(armorItem, [], modifiers, abilityLookup, valueLookup, ruleData, modifierData));
const bestBaseArmorBranchPossibility = HelperUtils.getLast(branchPossibilities, (possibility) => possibility.total);
if (bestBaseArmorBranchPossibility === null) {
return [];
}
return bestBaseArmorBranchPossibility.suppliers;
}
/**
*
* @param mainhandWeapons
* @param offhandWeapons
* @param modifiers
* @param modifierData
*/
export function generateDualWieldArmorClassSuppliers(mainhandWeapons, offhandWeapons, modifiers, modifierData) {
const armorClassSuppliers = [];
const bonusDualWieldAcModifiers = modifiers.filter((modifier) => ModifierValidators.isBonusDualWieldAcModifier(modifier));
if (bonusDualWieldAcModifiers.length && mainhandWeapons.length && offhandWeapons.length) {
bonusDualWieldAcModifiers.forEach((modifier) => {
armorClassSuppliers.push(generateArmorClassSupplier(ArmorClassTypeEnum.MISC_BONUS, ModifierDerivers.deriveValue(modifier, modifierData), modifier, ArmorClassExtraTypeEnum.MODIFIER));
});
}
return armorClassSuppliers;
}
/**
*
* @param armorItem
* @param shieldItem
* @param modifiers
* @param abilityLookup
* @param equippedItems
* @param valueLookup
* @param ruleData
* @param modifierData
*/
export function generateArmorClassSuppliers(armorItem, shieldItem, modifiers, abilityLookup, equippedItems, valueLookup, ruleData, modifierData) {
const armorClassSuppliers = [];
// base armor AC sources based on potential branching paths
const baseArmorSuppliers = generateBaseArmorSuppliers(armorItem, modifiers, abilityLookup, valueLookup, ruleData, modifierData);
armorClassSuppliers.push(...baseArmorSuppliers);
// TODO part of dirty hack
const isWearingHeavyArmor = armorItem
? ItemAccessors.getArmorTypeId(armorItem.item) === ArmorTypeEnum.HEAVY_ARMOR
: false;
// general AC bonuses (for both unarmored and armored)
const armorModifiers = modifiers.filter((modifier) => ModifierValidators.isBonusArmorModifier(modifier));
armorModifiers.forEach((modifier) => {
const dataOrigin = ModifierAccessors.getDataOrigin(modifier);
const dataOriginType = ModifierAccessors.getDataOriginType(modifier);
let type = ArmorClassTypeEnum.MISC_BONUS;
if (dataOriginType === DataOriginTypeEnum.ITEM) {
// TODO This may cause issues with magic infusions, but should be minor and can be fixed when we do overhaul of accessing DataOrigin entities
type = ItemAccessors.getDefinitionMagic(dataOrigin.primary)
? ArmorClassTypeEnum.MAGIC_BONUS
: ArmorClassTypeEnum.MISC_BONUS;
}
// TODO part of dirty hack
const value = ItemHacks.hack__deriveBonusArmorModifierValue(modifier, modifierData, isWearingHeavyArmor);
armorClassSuppliers.push(generateArmorClassSupplier(type, value,
// ModifierDerivers.deriveValue(modifier, modifierData),
modifier, ArmorClassExtraTypeEnum.MODIFIER));
});
// misc overall bonuses
const shieldArmorClass = shieldItem ? shieldItem.armorClass : 0;
const mainhandWeapons = equippedItems.filter(ItemValidators.isEquippedMainhandWeapon);
const offhandWeapons = equippedItems.filter(ItemValidators.isEquippedOffhandWeapon);
const dualWieldArmorClassSuppliers = generateDualWieldArmorClassSuppliers(mainhandWeapons, offhandWeapons, modifiers, modifierData);
const dualWieldArmorClassBonus = deriveArmorClassSupplierTotal(dualWieldArmorClassSuppliers);
if (dualWieldArmorClassBonus > 0 && dualWieldArmorClassBonus > shieldArmorClass) {
armorClassSuppliers.push(...dualWieldArmorClassSuppliers);
}
else {
// best shield AC
if (shieldItem) {
armorClassSuppliers.push(generateArmorClassSupplier(ArmorClassTypeEnum.SHIELD, shieldItem.armorClass, shieldItem.item, ArmorClassExtraTypeEnum.ITEM));
}
}
// overall magic bonus adjustment value
const magicBonusAdjustment = ValueUtils.getData(valueLookup, AdjustmentTypeEnum.MAGIC_BONUS_AC);
if (magicBonusAdjustment !== null) {
armorClassSuppliers.push(generateArmorClassSupplier(ArmorClassTypeEnum.MAGIC_BONUS, ValueAccessors.getValue(magicBonusAdjustment), ValueAccessors.getNotes(magicBonusAdjustment), ArmorClassExtraTypeEnum.STRING));
}
// overall misc bonus adjustment value
const miscBonusAdjustment = ValueUtils.getData(valueLookup, AdjustmentTypeEnum.MISC_BONUS_AC);
if (miscBonusAdjustment !== null) {
armorClassSuppliers.push(generateArmorClassSupplier(ArmorClassTypeEnum.MISC_BONUS, ValueAccessors.getValue(miscBonusAdjustment), ValueAccessors.getNotes(miscBonusAdjustment), ArmorClassExtraTypeEnum.STRING));
}
return armorClassSuppliers;
}
/**
*
* @param armorItem
* @param localModifiers
* @param globalModifiers
* @param abilityLookup
* @param valueLookup
* @param ruleData
* @param modifierData
*/
export function generateBaseArmorBranchPossibility(armorItem, localModifiers, globalModifiers, abilityLookup, valueLookup, ruleData, modifierData) {
let suppliers = [];
const baseArmorOverride = ValueUtils.getData(valueLookup, AdjustmentTypeEnum.OVERRIDE_BASE_ARMOR);
if (baseArmorOverride !== null) {
suppliers.push(generateArmorClassSupplier(ArmorClassTypeEnum.OVERRIDE_BASE_ARMOR, ValueAccessors.getValue(baseArmorOverride), ValueAccessors.getNotes(baseArmorOverride), ArmorClassExtraTypeEnum.STRING));
}
// basearmor + set highest + status bonuses
// - set highest is all of the "set > (un)armored armor class" modifiers
// - status bonuses are bonuses based on whether you are armored or unarmored
if (armorItem) {
suppliers.push(...generateArmoredBaseArmorSuppliers(armorItem, localModifiers, globalModifiers, abilityLookup, valueLookup, modifierData));
}
else {
suppliers.push(...generateUnarmoredBaseArmorSuppliers(localModifiers, globalModifiers, abilityLookup, valueLookup, ruleData, modifierData));
}
const minimumBaseArmorModifiers = localModifiers.filter((modifier) => ModifierValidators.isSetMinimumBaseArmorModifier(modifier));
if (minimumBaseArmorModifiers.length) {
const minBaseArmorModifier = minimumBaseArmorModifiers[0];
const setMinBaseArmorValue = ModifierDerivers.deriveValue(minBaseArmorModifier, modifierData);
if (setMinBaseArmorValue > deriveArmorClassSupplierTotal(suppliers)) {
suppliers = [
generateArmorClassSupplier(ArmorClassTypeEnum.ARMOR, setMinBaseArmorValue, minBaseArmorModifier, ArmorClassExtraTypeEnum.MODIFIER),
];
}
}
return {
total: deriveArmorClassSupplierTotal(suppliers),
suppliers,
};
}
/**
*
* @param equippedItems
* @param dexModifier
* @param modifiers
* @param abilityLookup
* @param ruleData
*/
export function generateHighestAcEquippedArmor(equippedItems, dexModifier, modifiers, abilityLookup, ruleData) {
const equippedArmor = equippedItems.filter(ItemValidators.isEquippedNonShieldArmor);
return ItemDerivers.deriveHighestArmorAcItem(equippedArmor, dexModifier, modifiers, abilityLookup, ruleData);
}
/**
*
* @param equippedItems
* @param dexModifier
* @param modifiers
* @param abilityLookup
* @param ruleData
*/
export function generateHighestAcEquippedShield(equippedItems, dexModifier, modifiers, abilityLookup, ruleData) {
const equippedShields = equippedItems.filter(ItemValidators.isEquippedShield);
return ItemDerivers.deriveHighestArmorAcItem(equippedShields, dexModifier, modifiers, abilityLookup, ruleData);
}
/**
*
* @param valueLookup
*/
export function generateArmorClassAdjustments(valueLookup) {
return {
overrideAc: ValueUtils.getData(valueLookup, AdjustmentTypeEnum.OVERRIDE_AC),
overrideBaseArmor: ValueUtils.getData(valueLookup, AdjustmentTypeEnum.OVERRIDE_BASE_ARMOR),
miscBonus: ValueUtils.getData(valueLookup, AdjustmentTypeEnum.MISC_BONUS_AC),
magicBonus: ValueUtils.getData(valueLookup, AdjustmentTypeEnum.MAGIC_BONUS_AC),
};
}
/**
*
* @param armorClassSuppliers
* @param armorClassAdjustments
*/
export function generateArmorClassTotal(armorClassSuppliers, armorClassAdjustments) {
if (armorClassAdjustments.overrideAc !== null) {
const overrideAcValue = ValueUtils.getTypedCharacterValueValue(armorClassAdjustments.overrideAc);
if (overrideAcValue !== null) {
return overrideAcValue;
}
}
return deriveArmorClassSupplierTotal(armorClassSuppliers);
}
@@ -0,0 +1,89 @@
import { orderBy } from 'lodash';
import { ActionAccessors } from '../../Action';
import { ActivationAccessors, ActivationTypeEnum } from '../../Activation';
import { ItemAccessors } from '../../Item';
import { ModifierAccessors, ModifierValidators } from '../../Modifier';
import { SpellAccessors } from '../../Spell';
import { AttackSourceTypeEnum } from '../constants';
/**
*
* @param actions
* @param itemAttacks
* @param spellAttackList
* @param customAttacks
*/
export function generateAttacks(actions, itemAttacks, spellAttackList, customAttacks) {
const attacks = [];
itemAttacks.forEach((attack) => {
let activation = ActivationTypeEnum.ACTION;
if (ItemAccessors.isOffhand(attack)) {
activation = ActivationTypeEnum.BONUS_ACTION;
}
attacks.push({
type: AttackSourceTypeEnum.ITEM,
key: `${AttackSourceTypeEnum.ITEM}-${attack.id}`,
activation,
data: attack,
});
});
spellAttackList.forEach((attack) => {
attacks.push({
type: AttackSourceTypeEnum.SPELL,
key: `${AttackSourceTypeEnum.SPELL}-${SpellAccessors.getUniqueKey(attack)}`,
activation: SpellAccessors.getActivationType(attack),
data: attack,
});
});
actions.forEach((action, idx) => {
if (!ActionAccessors.displayAsAttack(action)) {
return;
}
attacks.push({
type: AttackSourceTypeEnum.ACTION,
key: `${AttackSourceTypeEnum.ACTION}-${idx}-${ActionAccessors.getUniqueKey(action)}`,
activation: ActivationAccessors.getType(ActionAccessors.getActivation(action)),
data: action,
});
});
customAttacks.forEach((attack) => {
if (!ActionAccessors.displayAsAttack(attack)) {
return;
}
attacks.push({
type: AttackSourceTypeEnum.CUSTOM,
key: `${AttackSourceTypeEnum.CUSTOM}-${ActionAccessors.getUniqueKey(attack)}`,
activation: ActivationAccessors.getType(ActionAccessors.getActivation(attack)),
data: attack,
});
});
return attacks;
}
/**
*
* @param modifiers
*/
export function generateAttacksPerActionInfo(modifiers) {
const extraAttackModifiers = modifiers.filter((modifier) => ModifierValidators.isSetExtraAttacksModifier(modifier));
const sortedExtraAttackModifiers = orderBy(extraAttackModifiers, [
(modifier) => ModifierAccessors.getValue(modifier),
(modifier) => {
const restriction = ModifierAccessors.getRestriction(modifier);
return restriction !== null && restriction.length;
},
], ['asc', 'desc']);
const attacksPerActionBestModifier = sortedExtraAttackModifiers.pop();
const defaultValue = 0;
const defaultRestriction = '';
let attackInfoValue = defaultValue;
let attackInfoRestriction = defaultRestriction;
if (attacksPerActionBestModifier) {
const bestModiferValue = ModifierAccessors.getValue(attacksPerActionBestModifier);
attackInfoValue = bestModiferValue ? bestModiferValue : defaultValue;
const bestModifierRestriction = ModifierAccessors.getRestriction(attacksPerActionBestModifier);
attackInfoRestriction = bestModifierRestriction ? bestModifierRestriction : defaultRestriction;
}
return {
value: 1 + attackInfoValue,
restriction: attackInfoRestriction,
};
}
@@ -0,0 +1,69 @@
import { HelperUtils } from '../../Helper';
import { RaceAccessors } from '../../Race';
import { RuleDataAccessors } from '../../RuleData';
/**
*
* @param alignmentId
* @param ruleData
*/
export function generateAlignment(alignmentId, ruleData) {
if (alignmentId === null) {
return null;
}
return HelperUtils.lookupDataOrFallback(RuleDataAccessors.getAlignmentLookup(ruleData), alignmentId);
}
/**
*
* @param lifeStyleId
* @param ruleData
*/
export function generateLifestyle(lifeStyleId, ruleData) {
if (lifeStyleId === null) {
return null;
}
return HelperUtils.lookupDataOrFallback(RuleDataAccessors.getLifestyleLookup(ruleData), lifeStyleId);
}
/**
*
* @param race
*/
export function generateSize(race) {
if (race === null) {
return null;
}
return RaceAccessors.getSizeInfo(race);
}
/**
*
* @param traitsContract
*/
export function generateCharacterTraits(traitsContract) {
if (traitsContract !== null) {
return traitsContract;
}
return {
appearance: null,
bonds: null,
flaws: null,
ideals: null,
personalityTraits: null,
};
}
/**
*
* @param notesContract
*/
export function generateCharacterNotes(notesContract) {
if (notesContract !== null) {
return notesContract;
}
return {
allies: null,
backstory: null,
enemies: null,
organizations: null,
otherHoldings: null,
otherNotes: null,
personalPossessions: null,
};
}
@@ -0,0 +1,74 @@
import { MovementTypeEnum, PreferenceAbilityScoreDisplayTypeEnum, PreferenceEncumbranceTypeEnum, PreferenceHitPointTypeEnum, PreferencePrivacyTypeEnum, PreferenceProgressionTypeEnum, PreferenceSharingTypeEnum, SenseTypeEnum, } from '../../Core';
/**
*
* @param race
* @param classes
* @param stats
* @param configuration
*/
export function generateIsCharacterSheetReady(race, classes, stats, configuration) {
if (!race) {
return false;
}
if (!classes.length) {
return false;
}
let hasAllStats = true;
if (configuration.abilityScoreType === null) {
hasAllStats = false;
}
stats.forEach((stat) => {
if (stat.value === null) {
hasAllStats = false;
}
});
if (!hasAllStats) {
return false;
}
return true;
}
/**
*
* @param preferencesContract
*/
export function generateCharacterPreferences(preferencesContract, featureFlagInfo) {
if (preferencesContract !== null) {
return preferencesContract;
}
return {
abilityScoreDisplayType: PreferenceAbilityScoreDisplayTypeEnum.MODIFIERS_TOP,
enableOptionalClassFeatures: false,
enableOptionalOrigins: false,
encumbranceType: PreferenceEncumbranceTypeEnum.ENCUMBRANCE,
enforceFeatRules: true,
enforceMulticlassRules: true,
hitPointType: PreferenceHitPointTypeEnum.FIXED,
ignoreCoinWeight: true,
primaryMovement: MovementTypeEnum.WALK,
primarySense: SenseTypeEnum.PASSIVE_PERCEPTION,
privacyType: PreferencePrivacyTypeEnum.CAMPAIGN_ONLY,
progressionType: PreferenceProgressionTypeEnum.MILESTONE,
sharingType: PreferenceSharingTypeEnum.LIMITED,
showCompanions: false,
showScaledSpells: true,
showUnarmedStrike: true,
showWildShape: false,
enableDarkMode: false,
useHomebrewContent: true,
enableContainerCurrency: false,
};
}
/**
*
* @param configurationContract
*/
export function generateCharacterConfiguration(configurationContract) {
if (configurationContract !== null) {
return configurationContract;
}
return {
abilityScoreType: null,
showHelpText: false,
startingEquipmentType: null,
};
}
@@ -0,0 +1,41 @@
import { ModifierValidators } from '../../Modifier';
import { RuleDataAccessors } from '../../RuleData';
import { DeathCauseEnum } from '../constants';
import { deriveAdvantageDeathSavesAdjustments, deriveDisadvantageDeathSavesAdjustments } from '../derivers';
/**
*
* @param deathSaves
* @param modifiers
* @param ruleData
*/
export function generateDeathCause(deathSaves, modifiers, ruleData) {
const maxDeathsavesFail = RuleDataAccessors.getMaxDeathsavesFail(ruleData);
if (deathSaves !== null && deathSaves.failCount !== null && deathSaves.failCount >= maxDeathsavesFail) {
return DeathCauseEnum.DEATHSAVES;
}
if (modifiers.find((modifier) => ModifierValidators.isSetDeathModifier(modifier))) {
return DeathCauseEnum.CONDITION;
}
return DeathCauseEnum.NONE;
}
/**
*
* @param deathCause
*/
export function generateIsDead(deathCause) {
return deathCause !== DeathCauseEnum.NONE;
}
/**
*
* @param deathSaveContract
* @param modifiers
*/
export function generateDeathSaveInfo(deathSaveContract, modifiers) {
return {
failCount: deathSaveContract !== null && deathSaveContract.failCount !== null ? deathSaveContract.failCount : 0,
successCount: deathSaveContract !== null && deathSaveContract.successCount !== null ? deathSaveContract.successCount : 0,
isStabilized: deathSaveContract === null ? false : deathSaveContract.isStabilized,
advantageAdjustments: deriveAdvantageDeathSavesAdjustments(modifiers),
disadvantageAdjustments: deriveDisadvantageDeathSavesAdjustments(modifiers),
};
}
@@ -0,0 +1,221 @@
import { TypeScriptUtils } from '../../../utils';
import { ConditionAccessors } from '../../Condition';
import { DamageAdjustmentTypeEnum, DefenseAdjustmentTypeEnum } from '../../Core';
import { FormatUtils } from '../../Format';
import { HelperUtils } from '../../Helper';
import { ModifierAccessors } from '../../Modifier';
import { RuleDataAccessors, RuleDataUtils } from '../../RuleData';
import { deriveCustomConditionDefenseAdjustmentDataOrigin, deriveCustomDefenseAdjustmentDataOrigin } from '../derivers';
import { isImmunityDamageAdjustment, isResistanceDamageAdjustment, isVulnerabilityDamageAdjustment, } from '../validators';
/**
*
* @param customDamageAdjustments
* @param modifiers
* @param ruleData
*/
export function generateActiveResistances(customDamageAdjustments, modifiers, ruleData) {
const damageAdjustments = [];
customDamageAdjustments.forEach((adjustment) => {
damageAdjustments.push({
name: adjustment.name === null ? '' : adjustment.name,
dataOrigin: deriveCustomDefenseAdjustmentDataOrigin(adjustment),
isCustom: true,
type: DefenseAdjustmentTypeEnum.DAMAGE_ADJUSTMENT,
slug: FormatUtils.slugify(adjustment.name),
});
});
modifiers.forEach((modifier) => {
const damageAdjustment = RuleDataUtils.getDamageAdjustmentBySlug(ModifierAccessors.getSubType(modifier), DamageAdjustmentTypeEnum.RESISTANCE, ruleData);
let name = '';
if (damageAdjustment) {
name = damageAdjustment.name === null ? '' : damageAdjustment.name;
}
else {
const subtypeName = ModifierAccessors.getFriendlySubtypeName(modifier);
name = subtypeName === null ? '' : subtypeName;
}
damageAdjustments.push({
name,
dataOrigin: ModifierAccessors.getDataOrigin(modifier),
isCustom: false,
type: DefenseAdjustmentTypeEnum.DAMAGE_ADJUSTMENT,
slug: FormatUtils.slugify(name),
});
});
return damageAdjustments;
}
/**
*
* @param customDamageAdjustments
* @param customConditionAdjustments
* @param modifiers
* @param ruleData
*/
export function generateActiveImmunities(customDamageAdjustments, customConditionAdjustments, modifiers, ruleData) {
const defenseAdjustments = [];
customDamageAdjustments.forEach((adjustment) => {
defenseAdjustments.push({
name: adjustment.name === null ? '' : adjustment.name,
dataOrigin: deriveCustomDefenseAdjustmentDataOrigin(adjustment),
isCustom: true,
type: DefenseAdjustmentTypeEnum.DAMAGE_ADJUSTMENT,
slug: FormatUtils.slugify(adjustment.name),
});
});
customConditionAdjustments.forEach((adjustment) => {
defenseAdjustments.push({
name: ConditionAccessors.getName(adjustment),
dataOrigin: deriveCustomConditionDefenseAdjustmentDataOrigin(adjustment),
isCustom: true,
type: DefenseAdjustmentTypeEnum.CONDITION_ADJUSTMENT,
slug: FormatUtils.slugify(ConditionAccessors.getName(adjustment)),
});
});
modifiers.forEach((modifier) => {
const damageAdjustment = RuleDataUtils.getDamageAdjustmentBySlug(ModifierAccessors.getSubType(modifier), DamageAdjustmentTypeEnum.IMMUNITY, ruleData);
if (damageAdjustment) {
defenseAdjustments.push({
name: damageAdjustment.name === null ? '' : damageAdjustment.name,
dataOrigin: ModifierAccessors.getDataOrigin(modifier),
isCustom: false,
type: DefenseAdjustmentTypeEnum.DAMAGE_ADJUSTMENT,
slug: FormatUtils.slugify(damageAdjustment.name),
});
return;
}
const condition = RuleDataUtils.getConditionBySlug(ModifierAccessors.getSubType(modifier), ruleData);
if (condition) {
defenseAdjustments.push({
name: ConditionAccessors.getName(condition),
dataOrigin: ModifierAccessors.getDataOrigin(modifier),
isCustom: false,
type: DefenseAdjustmentTypeEnum.CONDITION_ADJUSTMENT,
slug: ConditionAccessors.getSlug(condition),
});
return;
}
const subtypeName = ModifierAccessors.getFriendlySubtypeName(modifier);
defenseAdjustments.push({
name: subtypeName === null ? '' : subtypeName,
dataOrigin: ModifierAccessors.getDataOrigin(modifier),
isCustom: false,
type: DefenseAdjustmentTypeEnum.DAMAGE_ADJUSTMENT,
slug: FormatUtils.slugify(ModifierAccessors.getFriendlySubtypeName(modifier)),
});
});
return defenseAdjustments;
}
/**
*
* @param customDamageAdjustments
* @param modifiers
* @param ruleData
*/
export function generateActiveVulnerabilities(customDamageAdjustments, modifiers, ruleData) {
const damageAdjustments = [];
customDamageAdjustments.forEach((adjustment) => {
damageAdjustments.push({
name: adjustment.name === null ? '' : adjustment.name,
dataOrigin: deriveCustomDefenseAdjustmentDataOrigin(adjustment),
isCustom: true,
type: DefenseAdjustmentTypeEnum.DAMAGE_ADJUSTMENT,
slug: FormatUtils.slugify(adjustment.name),
});
});
modifiers.forEach((modifier) => {
let name;
const damageAdjustment = RuleDataUtils.getDamageAdjustmentBySlug(ModifierAccessors.getSubType(modifier), DamageAdjustmentTypeEnum.VULNERABILITY, ruleData);
if (damageAdjustment) {
name = damageAdjustment.name;
}
else {
name = ModifierAccessors.getFriendlySubtypeName(modifier);
}
damageAdjustments.push({
name,
dataOrigin: ModifierAccessors.getDataOrigin(modifier),
isCustom: false,
type: DefenseAdjustmentTypeEnum.DAMAGE_ADJUSTMENT,
slug: FormatUtils.slugify(name),
});
});
return damageAdjustments;
}
/**
*
* @param ruleData
*/
export function generateResistanceData(ruleData) {
return RuleDataAccessors.getDamageAdjustments(ruleData).filter(isResistanceDamageAdjustment);
}
/**
*
* @param ruleData
*/
export function generateVulnerabilityData(ruleData) {
return RuleDataAccessors.getDamageAdjustments(ruleData).filter(isVulnerabilityDamageAdjustment);
}
/**
*
* @param ruleData
*/
export function generateImmunityData(ruleData) {
return RuleDataAccessors.getDamageAdjustments(ruleData).filter(isImmunityDamageAdjustment);
}
/**
*
* @param customDamageAdjustments
*/
export function generateCustomResistanceDamageAdjustments(customDamageAdjustments) {
return customDamageAdjustments.filter(isResistanceDamageAdjustment);
}
/**
*
* @param customDamageAdjustments
*/
export function generateCustomImmunityDamageAdjustments(customDamageAdjustments) {
return customDamageAdjustments.filter(isImmunityDamageAdjustment);
}
/**
*
* @param customDamageAdjustments
*/
export function generateCustomVulnerabilityDamageAdjustments(customDamageAdjustments) {
return customDamageAdjustments.filter(isVulnerabilityDamageAdjustment);
}
/**
*
* @param customDefenseAdjustments
* @param ruleData
*/
export function generateCustomDamageAdjustments(customDefenseAdjustments, ruleData) {
const damageAdjustmentLookup = RuleDataAccessors.getDamageAdjustmentsLookup(ruleData);
return customDefenseAdjustments
.filter((adjustment) => adjustment.type === DefenseAdjustmentTypeEnum.DAMAGE_ADJUSTMENT)
.map((adjustment) => {
const damageAdjustmentData = HelperUtils.lookupDataOrFallback(damageAdjustmentLookup, adjustment.adjustmentId);
if (damageAdjustmentData !== null) {
return Object.assign(Object.assign({}, damageAdjustmentData), { source: adjustment.source === null ? '' : adjustment.source });
}
return null;
})
.filter(TypeScriptUtils.isNotNullOrUndefined);
}
/**
*
* @param customDefenseAdjustments
* @param ruleData
*/
export function generateCustomConditionAdjustments(customDefenseAdjustments, ruleData) {
const conditionDataLookup = RuleDataAccessors.getConditionLookup(ruleData);
return customDefenseAdjustments
.filter((adjustment) => adjustment.type === DefenseAdjustmentTypeEnum.CONDITION_ADJUSTMENT)
.map((adjustment) => {
const conditionData = HelperUtils.lookupDataOrFallback(conditionDataLookup, adjustment.adjustmentId);
if (conditionData !== null) {
return Object.assign(Object.assign({}, conditionData), { source: adjustment.source === null ? '' : adjustment.source });
}
return null;
})
.filter(TypeScriptUtils.isNotNullOrUndefined);
}
@@ -0,0 +1,314 @@
import { AbilityAccessors } from '../../Ability';
import { ActionAccessors } from '../../Action';
import { ClassAccessors } from '../../Class';
import { ClassFeatureAccessors } from '../../ClassFeature';
import { AbilityStatEnum, PreferenceHitPointTypeEnum } from '../../Core';
import { DataOriginTypeEnum } from '../../DataOrigin';
import { DiceUtils } from '../../Dice';
import { EntityUtils } from '../../Entity';
import { FeatAccessors } from '../../Feat';
import { HelperUtils } from '../../Helper';
import { ItemAccessors } from '../../Item';
import { ModifierAccessors, ModifierDerivers, ModifierValidators } from '../../Modifier';
import { RaceAccessors } from '../../Race';
import { RacialTraitAccessors } from '../../RacialTrait';
import { RuleDataAccessors } from '../../RuleData';
import { ProtectionSupplierTypeEnum } from '../constants';
import { deriveItemProtectionSupplierAvailabilityStatus, deriveProtectionSupplierAvailabilityStatus, } from '../derivers';
/**
*
* @param hitPointParts
* @param currentLevel
* @param classes
* @param abilityLookup
* @param raceModifiers
* @param classesModifiers
* @param miscModifiers
* @param globalModifiers
* @param preferences
* @param ruleData
*/
export function generateHitPointInfo(hitPointParts, currentLevel, classes, abilityLookup, raceModifiers, classesModifiers, miscModifiers, globalModifiers, preferences, ruleData) {
return getHitPointInfo(hitPointParts.baseHp, hitPointParts.overrideHp, hitPointParts.bonusHp, hitPointParts.tempHp, hitPointParts.removedHp, currentLevel, classes, abilityLookup, raceModifiers, classesModifiers, miscModifiers, globalModifiers, preferences, ruleData);
}
/**
* //TODO better naming that fits generator convention
* @param baseHp
* @param overrideHp
* @param bonusHp
* @param tempHp
* @param removedHp
* @param currentLevel
* @param classes
* @param abilityLookup
* @param raceModifiers
* @param classesModifiers
* @param miscModifiers
* @param globalModifiers
* @param preferences
* @param ruleData
*/
export function getHitPointInfo(baseHp, overrideHp, bonusHp, tempHp, removedHp, currentLevel, classes, abilityLookup, raceModifiers, classesModifiers, miscModifiers, globalModifiers, preferences, ruleData) {
const classesHitDice = classes.map((charClass) => ({
dice: {
diceCount: ClassAccessors.getLevel(charClass),
diceValue: ClassAccessors.getHitDiceType(charClass),
diceMultiplier: null,
diceString: null,
fixedValue: null,
},
charClass,
}));
const hitPointSources = [];
let conStat;
let conStatModifier;
if (Object.keys(abilityLookup).length) {
conStat = abilityLookup[AbilityStatEnum.CONSTITUTION] || 0;
conStatModifier = AbilityAccessors.getModifier(conStat);
}
if (conStatModifier) {
const source = AbilityAccessors.getLabel(conStat);
hitPointSources.push({
amount: currentLevel * conStatModifier,
source: source === null ? 'UNK' : source,
});
}
const racialHpPerLevelModifiers = raceModifiers.filter((modifier) => ModifierValidators.isBonusHitPointsPerLevelModifier(modifier));
const classesHpPerLevelModifiers = classesModifiers.filter((modifier) => ModifierValidators.isBonusHitPointsPerLevelModifier(modifier));
const miscHpPerLevelModifiers = miscModifiers.filter((modifier) => ModifierValidators.isBonusHitPointsPerLevelModifier(modifier));
if (racialHpPerLevelModifiers.length) {
racialHpPerLevelModifiers.reduce((acc, modifier) => {
const value = ModifierAccessors.getValue(modifier);
if (value !== null) {
acc.push({
amount: currentLevel * value,
source: EntityUtils.getDataOriginName(ModifierAccessors.getDataOrigin(modifier)),
});
}
return acc;
}, hitPointSources);
}
if (classesHpPerLevelModifiers.length) {
classesHpPerLevelModifiers.reduce((acc, modifier) => {
const value = ModifierAccessors.getValue(modifier);
const dataOrigin = ModifierAccessors.getDataOrigin(modifier);
const dataOriginType = ModifierAccessors.getDataOriginType(modifier);
if (dataOriginType === DataOriginTypeEnum.CLASS_FEATURE && value !== null) {
acc.push({
amount: ClassAccessors.getLevel(dataOrigin.parent) * value,
source: EntityUtils.getDataOriginName(ModifierAccessors.getDataOrigin(modifier)),
});
}
return acc;
}, hitPointSources);
}
if (miscHpPerLevelModifiers.length) {
miscHpPerLevelModifiers.reduce((acc, modifier) => {
const value = ModifierAccessors.getValue(modifier);
if (value !== null) {
acc.push({
amount: currentLevel * value,
source: EntityUtils.getDataOriginName(ModifierAccessors.getDataOrigin(modifier)),
});
}
return acc;
}, hitPointSources);
}
const globalBonusHpModifiers = globalModifiers.filter((modifier) => ModifierValidators.isBonusHitPointsModifier(modifier));
if (globalBonusHpModifiers.length) {
globalBonusHpModifiers.reduce((acc, modifier) => {
const statId = ModifierAccessors.getStatId(modifier);
if (statId !== null) {
const stat = HelperUtils.lookupDataOrFallback(abilityLookup, statId);
if (stat !== null) {
const statModifier = AbilityAccessors.getModifier(stat);
acc.push({
amount: statModifier === null ? 0 : statModifier,
source: EntityUtils.getDataOriginName(ModifierAccessors.getDataOrigin(modifier)),
});
}
}
else {
const hpIncrease = ModifierAccessors.getValue(modifier);
if (!!hpIncrease) {
acc.push({
amount: hpIncrease,
source: EntityUtils.getDataOriginName(ModifierAccessors.getDataOrigin(modifier)),
});
}
}
return acc;
}, hitPointSources);
}
const totalHitPointSources = hitPointSources.reduce((acc, hitPointSource) => (acc += hitPointSource.amount), 0);
const totalAvgClassHitDie = classes.reduce((acc, charClass) => {
const hitDiceType = ClassAccessors.getHitDiceType(charClass);
const level = ClassAccessors.getLevel(charClass);
if (ClassAccessors.isStartingClass(charClass)) {
acc += hitDiceType;
acc += Math.floor((level - 1) * DiceUtils.getAverageDieValue(hitDiceType));
}
else {
acc += Math.floor(level * DiceUtils.getAverageDieValue(hitDiceType));
}
return acc;
}, 0);
const totalFixedClassHitDie = classes.reduce((acc, charClass) => {
const hitDiceType = ClassAccessors.getHitDiceType(charClass);
const level = ClassAccessors.getLevel(charClass);
if (ClassAccessors.isStartingClass(charClass)) {
acc += hitDiceType;
acc += (level - 1) * DiceUtils.getFixedDieValue(hitDiceType);
}
else {
acc += level * DiceUtils.getFixedDieValue(hitDiceType);
}
return acc;
}, 0);
const totalClassHitDieMax = classes.reduce((acc, charClass) => (acc += ClassAccessors.getLevel(charClass) * ClassAccessors.getHitDiceType(charClass)), 0);
const totalFixedValueHp = totalFixedClassHitDie + totalHitPointSources;
const totalAverageHp = totalAvgClassHitDie + totalHitPointSources;
const possibleMaxHitPoints = totalClassHitDieMax + totalHitPointSources;
let finalBaseHp = baseHp;
if (preferences && preferences.hitPointType === PreferenceHitPointTypeEnum.FIXED) {
finalBaseHp = totalFixedClassHitDie;
}
const baseTotalHp = finalBaseHp + totalHitPointSources;
let totalHp = baseTotalHp + (bonusHp ? bonusHp : 0);
if (overrideHp !== null) {
totalHp = overrideHp;
}
const finalTotalHp = Math.max(totalHp, RuleDataAccessors.getMinimumHpTotal(ruleData));
const finalRemovedHp = Math.min(finalTotalHp, removedHp);
return {
baseHp: finalBaseHp,
baseTotalHp,
bonusHp,
classesHitDice,
hitPointSources,
overrideHp,
possibleMaxHitPoints,
remainingHp: finalTotalHp - finalRemovedHp,
removedHp: finalRemovedHp,
tempHp,
totalAverageHp,
totalFixedValueHp,
totalHitPointSources,
totalHp: finalTotalHp,
};
}
/**
*
* @param classes
* @param race
* @param feats
* @param items
* @param modifierData
* @param abilityLookup
* @param ruleData
* @param proficiencyBonus
* @param characterId
*/
export function generateProtectionSuppliers(classes, race, feats, items, modifierData, abilityLookup, ruleData, proficiencyBonus, characterId) {
let protectionSuppliers = [];
if (race !== null) {
RaceAccessors.getRacialTraits(race).forEach((racialTrait) => {
const modifiers = RacialTraitAccessors.getModifiers(racialTrait);
const foundProtectionModifier = modifiers.find(ModifierValidators.isProtectionZeroHpModifier);
if (!foundProtectionModifier) {
return;
}
const actions = RacialTraitAccessors.getActions(racialTrait);
if (actions.length !== 1) {
return;
}
const action = actions[0];
const limitedUse = ActionAccessors.getLimitedUse(action);
if (!limitedUse) {
return;
}
protectionSuppliers.push({
type: ProtectionSupplierTypeEnum.RACIAL_TRAIT,
data: racialTrait,
modifier: foundProtectionModifier,
setHpValue: ModifierDerivers.deriveValue(foundProtectionModifier, modifierData),
key: [ProtectionSupplierTypeEnum.RACIAL_TRAIT, RacialTraitAccessors.getUniqueKey(racialTrait)].join('-'),
availabilityStatus: deriveProtectionSupplierAvailabilityStatus(limitedUse, abilityLookup, ruleData, proficiencyBonus),
action,
});
});
}
classes.forEach((charClass) => {
ClassAccessors.getClassFeatures(charClass).forEach((classFeature) => {
const modifiers = ClassFeatureAccessors.getModifiers(classFeature);
const foundProtectionModifier = modifiers.find(ModifierValidators.isProtectionZeroHpModifier);
if (!foundProtectionModifier) {
return;
}
const actions = ClassFeatureAccessors.getActions(classFeature);
if (actions.length !== 1) {
return;
}
const action = actions[0];
const limitedUse = ActionAccessors.getLimitedUse(action);
if (!limitedUse) {
return;
}
protectionSuppliers.push({
type: ProtectionSupplierTypeEnum.CLASS_FEATURE,
data: classFeature,
modifier: foundProtectionModifier,
setHpValue: ModifierDerivers.deriveValue(foundProtectionModifier, modifierData),
key: [ProtectionSupplierTypeEnum.CLASS_FEATURE, ClassFeatureAccessors.getUniqueKey(classFeature)].join('-'),
availabilityStatus: deriveProtectionSupplierAvailabilityStatus(limitedUse, abilityLookup, ruleData, proficiencyBonus),
action,
});
});
});
feats.forEach((feat) => {
const modifiers = FeatAccessors.getModifiers(feat);
const foundProtectionModifier = modifiers.find(ModifierValidators.isProtectionZeroHpModifier);
if (!foundProtectionModifier) {
return;
}
const actions = FeatAccessors.getActions(feat);
if (actions.length !== 1) {
return;
}
const action = actions[0];
const limitedUse = ActionAccessors.getLimitedUse(action);
if (!limitedUse) {
return;
}
protectionSuppliers.push({
type: ProtectionSupplierTypeEnum.FEAT,
data: feat,
modifier: foundProtectionModifier,
setHpValue: ModifierDerivers.deriveValue(foundProtectionModifier, modifierData),
key: [ProtectionSupplierTypeEnum.FEAT, FeatAccessors.getUniqueKey(feat)].join('-'),
availabilityStatus: deriveProtectionSupplierAvailabilityStatus(limitedUse, abilityLookup, ruleData, proficiencyBonus),
action,
});
});
items.forEach((item) => {
const modifiers = ItemAccessors.getModifiers(item);
const foundProtectionModifier = modifiers.find(ModifierValidators.isProtectionZeroHpModifier);
if (!foundProtectionModifier) {
return;
}
const limitedUse = ItemAccessors.getLimitedUse(item);
if (limitedUse === null) {
return;
}
protectionSuppliers.push({
type: ProtectionSupplierTypeEnum.ITEM,
data: item,
modifier: foundProtectionModifier,
setHpValue: ModifierDerivers.deriveValue(foundProtectionModifier, modifierData),
key: [ProtectionSupplierTypeEnum.ITEM, ItemAccessors.getUniqueKey(item)].join('-'),
availabilityStatus: deriveItemProtectionSupplierAvailabilityStatus(item, foundProtectionModifier, limitedUse, abilityLookup, ruleData, proficiencyBonus, characterId),
action: null,
});
});
return protectionSuppliers;
}
@@ -0,0 +1,58 @@
import { AbilityAccessors } from '../../Ability';
import { AbilityStatEnum } from '../../Core';
import { ModifierBonusTypeEnum, ModifierDerivers, ModifierUtils, ModifierValidators } from '../../Modifier';
/**
*
* @param modifiers
* @param abilityLookup
* @param profBonus
*/
export function getInitiative(modifiers, abilityLookup, profBonus) {
const bonusInitiativeModifiers = modifiers.filter((modifier) => ModifierValidators.isBonusInitiativeModifier(modifier));
const bonusInitiativeModifierTotal = abilityLookup
? ModifierDerivers.sumModifiers(bonusInitiativeModifiers, abilityLookup)
: 0;
const includeProficiencyBonus = ModifierUtils.countModifierBonusTypes(bonusInitiativeModifiers, ModifierBonusTypeEnum.PROFICIENCY_BONUS) > 0;
let halfProficiency = false;
if (modifiers.filter((modifier) => ModifierValidators.isHalfProficiencyInitiativeModifier(modifier)).length) {
halfProficiency = true;
}
let halfProficiencyRoundUp = false;
if (modifiers.filter((modifier) => ModifierValidators.isHalfProficiencyRoundUpInitiativeModifier(modifier)).length) {
halfProficiencyRoundUp = true;
}
let modifierProficiencyBonus = 0;
if (includeProficiencyBonus) {
modifierProficiencyBonus = profBonus;
}
else if (halfProficiencyRoundUp) {
modifierProficiencyBonus = Math.ceil(profBonus / 2);
}
else if (halfProficiency) {
modifierProficiencyBonus = Math.floor(profBonus / 2);
}
let dexAbility;
let dexAbilityModifier;
if (Object.keys(abilityLookup).length) {
dexAbility = abilityLookup[AbilityStatEnum.DEXTERITY];
dexAbilityModifier = AbilityAccessors.getModifier(dexAbility) || 0;
}
return dexAbilityModifier + bonusInitiativeModifierTotal + modifierProficiencyBonus;
}
/**
*
* @param processedInitiative
* @param initiativeScore
*
* @returns number
*/
export function getStaticInitiative(processedInitiative, initiativeScore) {
return processedInitiative + initiativeScore.amount;
}
/**
* //TODO use array.some prototype
* @param modifiers
*/
export function generateHasInitiativeAdvantage(modifiers) {
return modifiers.filter((modifier) => ModifierValidators.isAdvantageInitiativeModifier(modifier)).length > 0;
}
@@ -0,0 +1,12 @@
import { TypeScriptUtils } from '../../../utils';
import { ModifierAccessors, ModifierValidators } from '../../Modifier';
/**
*
* @param modifiers
*/
export function generateLanguages(modifiers) {
return modifiers
.filter((modifier) => ModifierValidators.isLanguageModifier(modifier))
.map((modifier) => ModifierAccessors.getFriendlySubtypeName(modifier))
.filter(TypeScriptUtils.isNotNullOrUndefined);
}
@@ -0,0 +1,169 @@
import { orderBy } from 'lodash';
import { EntityUtils } from '../../Entity';
import { HelperUtils } from '../../Helper';
import { ModifierAccessors, ModifierValidators } from '../../Modifier';
import { RuleDataAccessors, RuleDataUtils } from '../../RuleData';
import { AdjustmentTypeEnum, ProficiencyAdjustmentTypeEnum, ValueAccessors, ValueUtils, } from '../../Value';
import { deriveProficiencyBonus } from '../derivers';
import { isCustomSkillProficiency } from '../validators';
/**
*
* @param typeValueLookup
* @param modifiers
*/
export function generateProficiencyLookup(typeValueLookup, modifiers) {
let proficiencyLookup = {};
ProficiencyAdjustmentTypeEnum.forEach((typeId) => {
const values = ValueUtils.getTypeData(typeValueLookup, typeId);
values.forEach((value) => {
proficiencyLookup = Object.assign(Object.assign({}, proficiencyLookup), { [ValueAccessors.getEntityKey(value)]: 1 });
});
});
modifiers
.filter((modifier) => ModifierValidators.isProficiencyModifier(modifier) && ModifierAccessors.getEntityId(modifier))
.forEach((modifier) => {
proficiencyLookup = Object.assign(Object.assign({}, proficiencyLookup), { [ModifierAccessors.getEntityKey(modifier)]: 1 });
});
return proficiencyLookup;
}
/**
*
* @param currentLevel
* @param modifiers
* @param ruleData
*/
export function generateProficiencyBonus(currentLevel, modifiers, ruleData) {
return deriveProficiencyBonus(currentLevel, modifiers, ruleData);
}
/**
*
* @param customProficiencies
* @param valueLookupByType
* @param modifiers
* @param ruleData
*/
export function generateProficiencyGroups(customProficiencies, valueLookupByType, modifiers, ruleData) {
const decoratedProficiencyGroups = RuleDataAccessors.getProficiencyGroups(ruleData).map((group) => (Object.assign(Object.assign({}, group), { modifiers: [], custom: [], values: [] })));
const proficiencyGroupLookup = {};
const customProficiencyGroupLookup = {};
const customAdjustmentGroupLookup = {};
decoratedProficiencyGroups.forEach((group, groupIdx) => {
if (group.entityTypeIds !== null) {
group.entityTypeIds.forEach((entityTypeId) => {
proficiencyGroupLookup[entityTypeId] = groupIdx;
});
}
if (group.customAdjustments !== null) {
group.customAdjustments.forEach((adjustmentTypeId) => {
customAdjustmentGroupLookup[adjustmentTypeId] = groupIdx;
});
}
customProficiencyGroupLookup[group.customProficiencyGroup] = groupIdx;
});
modifiers.forEach((modifier) => {
const entityTypeId = ModifierAccessors.getEntityTypeId(modifier);
const isMasteryModifier = ModifierValidators.isValidWeaponMasteryModifier(modifier);
if (entityTypeId !== null && !isMasteryModifier) {
const groupLookupIdx = proficiencyGroupLookup[entityTypeId];
if (typeof groupLookupIdx !== 'undefined') {
decoratedProficiencyGroups[groupLookupIdx].modifiers.push(modifier);
}
}
});
customProficiencies.forEach((proficiency) => {
const groupLookupIdx = customProficiencyGroupLookup[proficiency.type];
if (typeof groupLookupIdx !== 'undefined') {
decoratedProficiencyGroups[groupLookupIdx].custom.push(proficiency);
}
});
Object.keys(customAdjustmentGroupLookup).forEach((adjustmentTypeId) => {
if (valueLookupByType.hasOwnProperty(adjustmentTypeId)) {
const groupLookupIdx = customAdjustmentGroupLookup[adjustmentTypeId];
decoratedProficiencyGroups[groupLookupIdx].values.push(...valueLookupByType[adjustmentTypeId]);
}
});
const proficiencyGroups = decoratedProficiencyGroups.map((group) => {
if (group.label === 'Weapons') {
RuleDataAccessors.getWeaponCategories(ruleData).forEach((category) => {
if (group.modifiers.find((modifier) => ModifierValidators.isValidProficiencyWeaponCategoryModifier(modifier, category))) {
group.modifiers = group.modifiers.filter((modifier) => {
const weapon = RuleDataUtils.getWeaponByEntityId(ModifierAccessors.getEntityId(modifier), ModifierAccessors.getEntityTypeId(modifier), ruleData);
return !weapon || (weapon && weapon.categoryId !== category.id);
});
}
});
}
const uniqueProficiencyGroups = {};
group.modifiers.forEach((modifier) => {
const friendlySubTypeName = ModifierAccessors.getFriendlySubtypeName(modifier);
if (friendlySubTypeName !== null) {
if (!uniqueProficiencyGroups.hasOwnProperty(friendlySubTypeName)) {
uniqueProficiencyGroups[friendlySubTypeName] = {
label: friendlySubTypeName,
sources: [],
restriction: ModifierAccessors.getRestriction(modifier),
};
}
uniqueProficiencyGroups[friendlySubTypeName].sources.push(EntityUtils.getDataOriginName(ModifierAccessors.getDataOrigin(modifier), undefined, true));
}
});
group.custom.forEach((proficiency) => {
const proficiencyName = proficiency.name;
if (proficiencyName !== null) {
if (!uniqueProficiencyGroups.hasOwnProperty(proficiencyName)) {
uniqueProficiencyGroups[proficiencyName] = {
label: proficiencyName,
sources: [],
};
}
if (proficiency.notes !== null) {
uniqueProficiencyGroups[proficiencyName].sources.push(proficiency.notes);
}
}
});
const armorNameLookup = RuleDataUtils.getArmorNameLookup(ruleData);
const weaponNameLookup = RuleDataUtils.getWeaponNameLookup(ruleData);
const toolNameLookup = RuleDataUtils.getToolNameLookup(ruleData);
group.values.forEach((charValue) => {
let name = '';
const valueId = ValueAccessors.getValueIntId(charValue);
const notes = ValueAccessors.getNotes(charValue);
if (valueId !== null) {
switch (ValueAccessors.getTypeId(charValue)) {
case AdjustmentTypeEnum.ARMOR_PROFICIENCY_LEVEL:
name = armorNameLookup[valueId];
break;
case AdjustmentTypeEnum.WEAPON_PROFICIENCY_LEVEL:
name = weaponNameLookup[valueId];
break;
case AdjustmentTypeEnum.TOOL_PROFICIENCY_LEVEL:
name = toolNameLookup[valueId];
break;
case AdjustmentTypeEnum.LANGUAGE_PROFICIENCY_LEVEL:
name = RuleDataUtils.getLanguageName(valueId, ruleData);
break;
default:
// not implemented
}
}
if (!uniqueProficiencyGroups.hasOwnProperty(name)) {
uniqueProficiencyGroups[name] = {
label: name,
sources: [],
};
}
uniqueProficiencyGroups[name].sources.push(notes ? notes : 'Custom');
});
return Object.assign(Object.assign({}, group), { modifierGroups: orderBy(HelperUtils.convertObjectToArray(uniqueProficiencyGroups), [
(modifierGroup) => modifierGroup.label,
]) });
});
return proficiencyGroups;
}
/**
*
* @param proficiencies
*/
export function generateCustomSkillProficiencies(proficiencies) {
return proficiencies.filter(isCustomSkillProficiency);
}
@@ -0,0 +1,50 @@
import { ClassAccessors } from '../../Class';
import { ClassFeatureAccessors, ClassFeatureUtils } from '../../ClassFeature';
import { PreferenceProgressionTypeEnum } from '../../Core';
import { ItemUtils } from '../../Item';
import { deriveCurrentLevelXp, deriveNextLevelXp, deriveXpLevel } from '../derivers';
/**
*
* @param xp
* @param preferences
* @param totalClassLevel
* @param ruleData
*/
export function generateCurrentLevel(xp, preferences, totalClassLevel, ruleData) {
if (preferences !== null && preferences.progressionType === PreferenceProgressionTypeEnum.XP) {
return deriveXpLevel(xp, ruleData);
}
return totalClassLevel;
}
/**
*
* @param classLevel
* @param currentLevel
* @param currentLevelXp
* @param ruleData
*/
export function generateExperienceInfo(classLevel, currentLevel, currentLevelXp, ruleData) {
return {
classLevel,
currentLevel,
currentLevelXp,
currentLevelStartingXp: deriveCurrentLevelXp(currentLevel, ruleData),
nextLevelXp: deriveNextLevelXp(currentLevel, ruleData),
};
}
/**
*
* @param classes
* @param equippedItems
*/
export function generateMartialArtsLevelScale(classes, equippedItems) {
if (equippedItems.filter(ItemUtils.isArmorContract).length) {
return null;
}
const martialArtsClass = classes.find((charClass) => ClassAccessors.getClassFeatures(charClass).filter((feature) => ClassFeatureUtils.doesEnableMartialArts(feature)).length > 0);
if (martialArtsClass) {
const classFeature = ClassAccessors.getClassFeatures(martialArtsClass).find((feature) => ClassFeatureUtils.doesEnableMartialArts(feature));
return classFeature ? ClassFeatureAccessors.getLevelScale(classFeature) : null;
}
return null;
}
@@ -0,0 +1,102 @@
import { groupBy } from 'lodash';
import { AbilityStatEnum, SaveTypeEnum, SituationalBonusSavingThrowTypeEnum } from '../../Core';
import { DiceAdjustmentRollTypeEnum, DiceAdjustmentTypeEnum, DiceDerivers } from '../../Dice';
import { FormatUtils } from '../../Format';
import { ModifierAccessors, ModifierDerivers, ModifierValidators } from '../../Modifier';
import { RuleDataAccessors, RuleDataUtils } from '../../RuleData';
import { deriveSaveTypeName } from '../derivers';
/**
*
* @param highestShield
* @param modifiers
* @param ruleData
*/
export function generateSituationalBonusSavingThrows(highestShield, modifiers, ruleData) {
const saves = [];
RuleDataAccessors.getStats(ruleData).forEach((statData) => {
if (statData.id === AbilityStatEnum.DEXTERITY) {
const bonusShieldAcModifiers = modifiers.filter((modifier) => ModifierValidators.isBonusShieldAcOnDexSavesModifier(modifier));
if (highestShield) {
bonusShieldAcModifiers.forEach((modifier) => {
saves.push({
key: ModifierAccessors.getEntityKey(modifier),
statId: statData.id,
value: highestShield.armorClass,
extra: modifier,
type: SituationalBonusSavingThrowTypeEnum.MODIFIER,
});
});
}
}
});
return saves;
}
/**
*
* @param situationalBonusSavingThrows
*/
export function generateSituationalBonusSavingThrowsLookup(situationalBonusSavingThrows) {
return groupBy(situationalBonusSavingThrows, 'statId');
}
/**
*
* @param type
* @param modifier
*/
export function generateSavingThrowModifierSummary(type, modifier) {
let tooltipParts = [];
if (type === SaveTypeEnum.BONUS) {
let valueTotal = ModifierAccessors.getValueTotal(modifier);
if (valueTotal) {
tooltipParts.push(FormatUtils.renderSignedNumber(valueTotal));
}
}
tooltipParts.push(deriveSaveTypeName(type));
let abilityName = ModifierDerivers.deriveSavingThrowModifierAbilityName(modifier);
if (abilityName) {
tooltipParts.push(`on ${abilityName}`);
}
else {
if (!modifier.restriction) {
tooltipParts.push('on saves');
}
}
let restrictionText = ModifierAccessors.getRestriction(modifier);
if (restrictionText) {
tooltipParts.push(restrictionText);
}
return tooltipParts.join(' ');
}
/**
*
* @param adjustment
* @param ruleData
*/
export function generateSavingThrowAdjustmentSummary(adjustment, ruleData) {
let tooltipParts = [];
if (adjustment.type === DiceAdjustmentTypeEnum.BONUS) {
if (adjustment.amount) {
tooltipParts.push(FormatUtils.renderSignedNumber(adjustment.amount));
}
}
tooltipParts.push(DiceDerivers.deriveDiceAdjustmentTypeName(adjustment.type));
let abilityName = null;
if (adjustment.statId) {
abilityName = RuleDataUtils.getStatNameById(adjustment.statId, ruleData);
}
if (abilityName) {
tooltipParts.push(`on ${abilityName}`);
}
else if (adjustment.rollType === DiceAdjustmentRollTypeEnum.DEATH_SAVE) {
tooltipParts.push('on death saves');
}
if (adjustment.restriction) {
tooltipParts.push(adjustment.restriction);
}
else {
if (adjustment.rollType === DiceAdjustmentRollTypeEnum.SAVE) {
tooltipParts.push('on saves');
}
}
return tooltipParts.join(' ');
}
@@ -0,0 +1,124 @@
import { keyBy } from 'lodash';
import { SenseTypeEnum } from '../../Core';
import { ModifierDerivers, ModifierValidators } from '../../Modifier';
import { RuleDataUtils } from '../../RuleData';
import { derivePassiveInsight, derivePassiveInvestigation, derivePassivePerception } from '../derivers';
/**
*
* @param senseKey
* @param modifiers
* @param abilityLookup
* @param customSenseLookup
*/
export function getSenseDistanceByKeyBonusTakeHighest(senseKey, modifiers, abilityLookup, customSenseLookup) {
const customSenseData = customSenseLookup[senseKey];
if (customSenseData) {
const customDistance = customSenseData.distance;
if (customDistance !== null) {
return customDistance;
}
}
const typeModifierKey = RuleDataUtils.getSenseTypeModifierKey(senseKey);
const setSenseModifiers = modifiers.filter((modifier) => ModifierValidators.isValidSetSenseModifier(modifier, typeModifierKey));
if (setSenseModifiers.length) {
return ModifierDerivers.deriveHighestModifierValue(setSenseModifiers, abilityLookup);
}
const baseSenseModifiers = modifiers.filter((modifier) => ModifierValidators.isValidSetBaseSenseModifier(modifier, typeModifierKey));
const highestBaseSenseModifierValue = ModifierDerivers.deriveHighestModifierValue(baseSenseModifiers, abilityLookup);
const bonusSenseModifiers = modifiers.filter((modifier) => ModifierValidators.isValidSenseModifier(modifier, typeModifierKey) ||
ModifierValidators.isValidBonusSenseModifier(modifier, typeModifierKey));
const highestBonusSenseModifierValue = ModifierDerivers.deriveHighestModifierValue(bonusSenseModifiers, abilityLookup);
return highestBaseSenseModifierValue + highestBonusSenseModifierValue;
}
/**
* @deprecated use getSenseDistanceByKeyBonusTakeHighest
*/
export const getSenseDistanceByKey = getSenseDistanceByKeyBonusTakeHighest;
/**
*
* @param senseKey
* @param modifiers
* @param abilityLookup
* @param customSenseLookup
*/
export function getSenseDistanceByKeyBonusTakeAll(senseKey, modifiers, abilityLookup, customSenseLookup) {
const customSenseData = customSenseLookup[senseKey];
if (customSenseData) {
const customDistance = customSenseData.distance;
if (customDistance !== null) {
return customDistance;
}
}
const typeModifierKey = RuleDataUtils.getSenseTypeModifierKey(senseKey);
const setSenseModifiers = modifiers.filter((modifier) => ModifierValidators.isValidSetSenseModifier(modifier, typeModifierKey));
if (setSenseModifiers.length) {
return ModifierDerivers.deriveHighestModifierValue(setSenseModifiers, abilityLookup);
}
const baseSenseModifiers = modifiers.filter((modifier) => ModifierValidators.isValidSetBaseSenseModifier(modifier, typeModifierKey));
const highestBaseSenseModifierValue = ModifierDerivers.deriveHighestModifierValue(baseSenseModifiers, abilityLookup);
const bonusSenseModifiers = modifiers.filter((modifier) => ModifierValidators.isValidSenseModifier(modifier, typeModifierKey) ||
ModifierValidators.isValidBonusSenseModifier(modifier, typeModifierKey));
const totalBonusSenseModifierValue = bonusSenseModifiers.reduce((total, modifier) => total + ModifierDerivers.getModifierValue(modifier, abilityLookup), 0);
return highestBaseSenseModifierValue + totalBonusSenseModifierValue;
}
/**
*
* @param modifiers
* @param abilityLookup
* @param customSenseLookup
*/
export function generateSenseInfo(modifiers, abilityLookup, customSenseLookup) {
return {
[SenseTypeEnum.BLINDSIGHT]: getSenseDistanceByKeyBonusTakeHighest(SenseTypeEnum.BLINDSIGHT, modifiers, abilityLookup, customSenseLookup),
[SenseTypeEnum.DARKVISION]: getSenseDistanceByKeyBonusTakeAll(SenseTypeEnum.DARKVISION, modifiers, abilityLookup, customSenseLookup),
[SenseTypeEnum.PASSIVE_PERCEPTION]: getSenseDistanceByKeyBonusTakeHighest(SenseTypeEnum.PASSIVE_PERCEPTION, modifiers, abilityLookup, customSenseLookup),
[SenseTypeEnum.TREMORSENSE]: getSenseDistanceByKeyBonusTakeHighest(SenseTypeEnum.TREMORSENSE, modifiers, abilityLookup, customSenseLookup),
[SenseTypeEnum.TRUESIGHT]: getSenseDistanceByKeyBonusTakeHighest(SenseTypeEnum.TRUESIGHT, modifiers, abilityLookup, customSenseLookup),
};
}
/**
*
* @param skills
* @param modifiers
* @param abilityLookup
* @param overrideValue
*/
export function generatePassivePerception(skills, modifiers, abilityLookup, overrideValue) {
if (overrideValue !== null) {
return overrideValue;
}
return derivePassivePerception(skills, modifiers, abilityLookup);
}
/**
*
* @param skills
* @param modifiers
* @param abilityLookup
* @param overrideValue
*/
export function generatePassiveInvestigation(skills, modifiers, abilityLookup, overrideValue) {
if (overrideValue !== null) {
return overrideValue;
}
return derivePassiveInvestigation(skills, modifiers, abilityLookup);
}
/**
*
* @param skills
* @param modifiers
* @param abilityLookup
* @param overrideValue
*/
export function generatePassiveInsight(skills, modifiers, abilityLookup, overrideValue) {
if (overrideValue !== null) {
return overrideValue;
}
return derivePassiveInsight(skills, modifiers, abilityLookup);
}
/**
*
* @param senses
*/
export function generateCustomSenseLookup(senses) {
return keyBy(senses, 'senseId');
}
@@ -0,0 +1,29 @@
import { CampaignSettingAccessors } from '../../CampaignSetting';
import { FeatureFlagEnum, FeatureFlagInfoUtils } from '../../FeatureFlagInfo';
import { RuleDataAccessors } from '../../RuleData';
/**
*
* @param activeSourceCategories
* @param campaignSetting
* @param ruleData
*/
export function generateActiveSources(activeSourceCategories, customCampaignSetting, campaignSetting, ruleData, featureFlagInfo) {
// console.log('customCampaignSetting', customCampaignSetting);
const isCampaignSettingsActive = FeatureFlagInfoUtils.getFeatureFlagInfoValue(FeatureFlagEnum.RELEASE_GATE_CAMPAIGN_SETTINGS, featureFlagInfo);
const sourceData = RuleDataAccessors.getSourceData(ruleData);
let campaignSettingSources = [];
if (customCampaignSetting) {
if (customCampaignSetting.campaignSettingId) {
campaignSettingSources = CampaignSettingAccessors.getAvailableSourceIds(campaignSetting[0]);
}
else {
campaignSettingSources = CampaignSettingAccessors.getEnabledSourceIds(customCampaignSetting);
}
}
// Return the Campaign Setting sources if the feature is enabled and they have been set
if (isCampaignSettingsActive && (campaignSettingSources === null || campaignSettingSources === void 0 ? void 0 : campaignSettingSources.length)) {
return sourceData.filter((source) => campaignSettingSources === null || campaignSettingSources === void 0 ? void 0 : campaignSettingSources.includes(source.id));
}
// Otherwise return the sources mapped to the specified Source Category
return sourceData.filter((source) => activeSourceCategories.includes(source.sourceCategoryId));
}
@@ -0,0 +1,138 @@
import { keyBy } from 'lodash';
import { SpeedMovementKeyEnum, WeightSpeedTypeEnum } from '../../Core';
import { ModifierDerivers, ModifierValidators } from '../../Modifier';
import { RaceAccessors } from '../../Race';
import { RuleDataUtils } from '../../RuleData';
/**
*
* @param speedMovementKey
* @param baseWeightSpeedLookup
* @param weightSpeedType
* @param mirrorSpeed
* @param armorSpeedAdjustment
* @param modifiers
* @param customSpeedLookup
* @param equippedArmor
*/
export function generateSpeedInfoByKey(speedMovementKey, baseWeightSpeedLookup, weightSpeedType, mirrorSpeed, armorSpeedAdjustment, modifiers, customSpeedLookup, equippedArmor) {
if (baseWeightSpeedLookup === null) {
return null;
}
const speedReductionRemoveModifiers = modifiers.filter((modifier) => ModifierValidators.isSpeedReductionRemoveModifier(modifier));
const hasSpeedReductionRemove = speedReductionRemoveModifiers.length > 0;
// set up initial speed
let finalSpeed = baseWeightSpeedLookup[speedMovementKey];
// calc based on standard modifiers
const setModifiers = modifiers.filter((modifier) => ModifierValidators.isValidSetSpeedModifier(modifier, speedMovementKey));
if (setModifiers.length) {
finalSpeed = ModifierDerivers.deriveHighestModifierValue(setModifiers);
}
else {
const innateSetModifiers = modifiers.filter((modifier) => ModifierValidators.isValidSetInnateSpeedModifier(modifier, speedMovementKey));
let baseMovementSpeed = baseWeightSpeedLookup[speedMovementKey];
if (baseMovementSpeed === null) {
baseMovementSpeed = 0;
}
const baseSpeed = Math.max(baseMovementSpeed, ModifierDerivers.deriveHighestModifierValue(innateSetModifiers));
if (baseSpeed === 0) {
finalSpeed = baseSpeed;
}
else {
const speedAdjustmentModifiers = modifiers.filter((modifier) => ModifierValidators.isValidBonusSpeedModifier(modifier, speedMovementKey));
const speedAdjustmentModifierTotal = ModifierDerivers.sumModifiers(speedAdjustmentModifiers);
let unarmoredBonus = 0;
if (!equippedArmor) {
const unarmoredBonusModifiers = modifiers.filter((modifier) => ModifierValidators.isBonusUnarmoredMovementModifier(modifier));
unarmoredBonus = ModifierDerivers.sumModifiers(unarmoredBonusModifiers);
}
finalSpeed = baseSpeed + speedAdjustmentModifierTotal + armorSpeedAdjustment + unarmoredBonus;
}
}
// check if there are any speed reductions
if (!hasSpeedReductionRemove && finalSpeed !== 0) {
let speed = finalSpeed;
if (weightSpeedType === WeightSpeedTypeEnum.ENCUMBERED) {
speed -= 10;
}
else if (weightSpeedType === WeightSpeedTypeEnum.HEAVILY_ENCUMBERED) {
speed -= 20;
}
finalSpeed = Math.max(0, speed);
}
// check for custom speed overrides
const movementId = RuleDataUtils.getMovementTypeBySpeedMovementKey(speedMovementKey);
if (movementId !== null) {
const speed = customSpeedLookup[movementId];
if (speed && speed.distance !== null) {
finalSpeed = speed.distance;
}
}
// Have to do another pass to check if any of the speed set modifiers are null, if so use the mirror speed (most likely walking)
if (mirrorSpeed !== null) {
const speedSetModifiers = modifiers.filter((modifier) => ModifierValidators.isValidSetInnateSpeedModifier(modifier, speedMovementKey));
const nullSpeedSetModifiers = speedSetModifiers.filter((modifier) => ModifierDerivers.deriveFixedModifierValue(modifier, null) === null);
if (nullSpeedSetModifiers.length) {
finalSpeed = mirrorSpeed;
}
}
return finalSpeed;
}
/**
*
* @param weightSpeeds
* @param weightSpeedType
* @param armorSpeedAdjustment
* @param modifiers
* @param customSpeedLookup
* @param equippedArmor
*/
export function generateSpeedInfo(weightSpeeds, weightSpeedType, armorSpeedAdjustment, modifiers, customSpeedLookup, equippedArmor) {
const baseWeightSpeedLookup = weightSpeeds ? weightSpeeds[WeightSpeedTypeEnum.NORMAL] : null;
const walkingSpeed = generateSpeedInfoByKey(SpeedMovementKeyEnum.WALK, baseWeightSpeedLookup, weightSpeedType, null, armorSpeedAdjustment, modifiers, customSpeedLookup, equippedArmor);
return {
[SpeedMovementKeyEnum.WALK]: walkingSpeed,
[SpeedMovementKeyEnum.FLY]: generateSpeedInfoByKey(SpeedMovementKeyEnum.FLY, baseWeightSpeedLookup, weightSpeedType, walkingSpeed, armorSpeedAdjustment, modifiers, customSpeedLookup, equippedArmor),
[SpeedMovementKeyEnum.CLIMB]: generateSpeedInfoByKey(SpeedMovementKeyEnum.CLIMB, baseWeightSpeedLookup, weightSpeedType, walkingSpeed, armorSpeedAdjustment, modifiers, customSpeedLookup, equippedArmor),
[SpeedMovementKeyEnum.BURROW]: generateSpeedInfoByKey(SpeedMovementKeyEnum.BURROW, baseWeightSpeedLookup, weightSpeedType, walkingSpeed, armorSpeedAdjustment, modifiers, customSpeedLookup, equippedArmor),
[SpeedMovementKeyEnum.SWIM]: generateSpeedInfoByKey(SpeedMovementKeyEnum.SWIM, baseWeightSpeedLookup, weightSpeedType, walkingSpeed, armorSpeedAdjustment, modifiers, customSpeedLookup, equippedArmor),
};
}
/**
*
* @param speedInfo
* @param customSpeedLookup
* @param ruleData
*/
export function generateSpeeds(speedInfo, customSpeedLookup, ruleData) {
return Object.keys(speedInfo).map((movementKey, idx) => {
const movementAmount = speedInfo[movementKey];
let source = '';
const movementType = RuleDataUtils.getMovementTypeBySpeedMovementKey(movementKey);
if (movementType && customSpeedLookup[movementType]) {
source = customSpeedLookup[movementType].source;
}
return {
distance: movementAmount,
type: movementType,
source,
name: RuleDataUtils.getMovementDescription(movementType, ruleData),
};
});
}
/**
*
* @param race
*/
export function generateWeightSpeeds(race) {
if (race === null) {
return null;
}
return RaceAccessors.getWeightSpeeds(race);
}
/**
*
* @param speeds
*/
export function generateCustomSpeedLookup(speeds) {
return keyBy(speeds, 'movementId');
}
@@ -0,0 +1,118 @@
import { AbilityAccessors } from '../../Ability';
import { ClassAccessors } from '../../Class';
import { ModifierDerivers, ModifierValidators } from '../../Modifier';
/**
*
* @param modifiers
* @param xpInfo
* @param proficiencyBonus
* @param abilityLookup
* @param spellListDataOriginLookup
*/
export function generateOverallSpellInfo(modifiers, xpInfo, proficiencyBonus, abilityLookup, spellListDataOriginLookup) {
const bonusCantripDamageModifiers = modifiers.filter((modifier) => ModifierValidators.isBonusCantripDamage(modifier));
const bonusCantripDamageModifierTotal = ModifierDerivers.sumModifiers(bonusCantripDamageModifiers, abilityLookup);
return {
bonusCantripDamage: bonusCantripDamageModifierTotal,
characterLevel: xpInfo.currentLevel,
proficiencyBonus,
abilityLookup,
modifiers,
spellListDataOriginLookup,
};
}
/**
*
* @param classes
* @param characterLevel
* @param spellSlots
* @param pactMagicSlots
* @param castableSpellLevels
* @param castablePactMagicLevels
* @param availableSpellLevels
* @param availablePactMagicLevels
* @param maxLevel
* @param abilityLookup
* @param classSpellInfoLookup
*/
export function generateSpellCasterInfo(classes, characterLevel, spellSlots, pactMagicSlots, castableSpellLevels, castablePactMagicLevels, availableSpellLevels, availablePactMagicLevels, maxLevel, abilityLookup, classSpellInfoLookup) {
var _a, _b, _c, _d;
const castingInfo = {
modifiers: [],
spellAttacks: [],
saveDcs: [],
};
for (let i = 0; i < classes.length; i++) {
const charClass = classes[i];
const spellRules = ClassAccessors.getSpellRules(charClass);
if (spellRules === null) {
continue;
}
let modifier = 0;
if (spellRules.spellcastingAbilityStatId) {
modifier = AbilityAccessors.getModifier(abilityLookup[spellRules.spellcastingAbilityStatId]);
}
const classSpellInfo = classSpellInfoLookup[ClassAccessors.getId(charClass)];
const spellAttack = classSpellInfo.toHit;
const saveDc = classSpellInfo.spellSaveDc;
if (ClassAccessors.isSpellcastingActive(charClass) || ClassAccessors.isPactMagicActive(charClass)) {
const existingModifierIndex = castingInfo.modifiers.findIndex((existingModifier) => existingModifier.value === modifier);
if (existingModifierIndex > -1) {
castingInfo.modifiers[existingModifierIndex].sources.push(charClass);
}
else {
castingInfo.modifiers.push({
value: modifier,
sources: [charClass],
});
}
const existingSpellAttackIndex = castingInfo.spellAttacks.findIndex((existingSpellAttack) => existingSpellAttack.value === spellAttack);
if (existingSpellAttackIndex > -1) {
castingInfo.spellAttacks[existingSpellAttackIndex].sources.push(charClass);
}
else {
castingInfo.spellAttacks.push({
value: spellAttack,
sources: [charClass],
});
}
const existingSaveDcIndex = castingInfo.saveDcs.findIndex((existingSaveDc) => existingSaveDc.value === saveDc);
if (existingSaveDcIndex > -1) {
castingInfo.saveDcs[existingSaveDcIndex].sources.push(charClass);
}
else {
castingInfo.saveDcs.push({
value: saveDc,
sources: [charClass],
});
}
}
}
const firstAvailableSpellSlotLevel = (_a = availableSpellLevels.sort()[0]) !== null && _a !== void 0 ? _a : null;
const firstAvailablePactMagicSlotLevel = (_b = availablePactMagicLevels.sort()[0]) !== null && _b !== void 0 ? _b : null;
const lastAvailableSpellSlotLevel = (_c = availableSpellLevels.sort().reverse()[0]) !== null && _c !== void 0 ? _c : null;
const lastAvailablePactMagicSlotLevel = (_d = availablePactMagicLevels.sort().reverse()[0]) !== null && _d !== void 0 ? _d : null;
return {
characterLevel,
spellSlots,
pactMagicSlots,
castableSpellLevels,
castablePactMagicLevels,
availableSpellLevels,
availablePactMagicLevels,
firstAvailableSpellSlotLevel,
firstAvailablePactMagicSlotLevel,
lastAvailableSpellSlotLevel,
lastAvailablePactMagicSlotLevel,
maxLevel,
castingInfo,
};
}
/**
*
* @param classSpellLists
* @param characterSpells
*/
export function generateHasSpells(classSpellLists, characterSpells) {
return !!(classSpellLists.length || characterSpells.length);
}
@@ -0,0 +1,106 @@
import { ContainerAccessors, ContainerTypeEnum } from '../../Container';
import { ItemDerivers, ItemValidators } from '../../Item';
import { RuleDataAccessors } from '../../RuleData';
/**
*
* @param currencies
* @param preferences
* @param ruleData
*/
export function generateCoinWeight(currencies, preferences, ruleData) {
let weight = 0;
if (currencies === null) {
return weight;
}
if (preferences !== null && !preferences.ignoreCoinWeight) {
RuleDataAccessors.getCurrencyData(ruleData).forEach((currencyInfo) => {
if (currencyInfo.name !== null) {
const heldCurrencyAmount = currencies[currencyInfo.name.toLowerCase()];
if (heldCurrencyAmount) {
weight += currencyInfo.weight * heldCurrencyAmount;
}
}
});
}
return weight;
}
/**
*
* @param allItems
* @param customItems
* @returns {number}
*/
export function generateItemWeight(allItems, customItems) {
return ItemDerivers.deriveItemsWeightTotal(allItems) + ItemDerivers.deriveItemsWeightTotal(customItems);
}
/**
*
* @param characterContainers
* @param partyContainers
* @param characterId
* @returns {number}
*/
export function generateContainerItemWeight(characterContainers, partyContainers, characterId) {
return [...characterContainers, ...partyContainers].reduce((acc, container) => {
const type = ContainerAccessors.getContainerType(container);
const isEquipped = ContainerAccessors.isEquipped(container);
if (type === ContainerTypeEnum.ITEM && isEquipped) {
// A container item
const containerItem = ContainerAccessors.getItem(container);
// Ignore containers equipped to someone else in the party inventory
if (containerItem && ItemValidators.isEquippedToCurrentCharacter(containerItem, characterId)) {
const appliedWeight = ContainerAccessors.getWeightInfo(container).itemApplied;
acc += appliedWeight;
}
}
if (type === ContainerTypeEnum.CHARACTER || type === ContainerTypeEnum.CAMPAIGN) {
// Based on the generateContainerWeightInfo generator,
// when looking at the top level party inventory,
// applied weight only includes items that are equipped to you.
const appliedWeight = ContainerAccessors.getWeightInfo(container).itemApplied;
acc += appliedWeight;
}
return acc;
}, 0);
}
/**
*
* @param characterContainers
* @param partyContainers
* @param characterId
* @param preferences
* @returns {number}
*/
export function generateCointainerCoinWeight(characterContainers, partyContainers, characterId, preferences) {
// 0 when ignoring coin weight
if (!preferences || preferences.ignoreCoinWeight) {
return 0;
}
// Whether cointainers are enabled or not, the process is the same.
// All the coins will be in the top level character container if cointainers are off.
return [...characterContainers, ...partyContainers].reduce((acc, container) => {
const type = ContainerAccessors.getContainerType(container);
const isEquipped = ContainerAccessors.isEquipped(container);
if (type === ContainerTypeEnum.ITEM && isEquipped) {
const containerItem = ContainerAccessors.getItem(container);
if (containerItem && ItemValidators.isEquippedToCurrentCharacter(containerItem, characterId)) {
const appliedWeight = ContainerAccessors.getWeightInfo(container).coinApplied;
acc += appliedWeight;
}
}
if (type === ContainerTypeEnum.CHARACTER || type === ContainerTypeEnum.CAMPAIGN) {
const appliedWeight = ContainerAccessors.getWeightInfo(container).coinApplied;
acc += appliedWeight;
}
return acc;
}, 0);
}
/**
* //TODO could include suppliers data
* @param itemWeight
* @param coinWeight
*/
export function generateTotalWeight(itemWeight, coinWeight) {
// When coin weight is ignored, coinWeight will be 0.
return itemWeight + coinWeight;
}
@@ -0,0 +1,26 @@
/**
*
* @param baseHp
* @param overrideHp
* @param bonusHp
* @param tempHp
* @param removedHp
* https://github.com/reduxjs/reselect/issues/378
*/
export function hack__generateHitPointParts(baseHp, overrideHp, bonusHp, tempHp, removedHp) {
return {
baseHp,
overrideHp,
bonusHp,
tempHp,
removedHp,
};
}
export function hack__generateSpecialWeaponPropertiesEnabled(hexWeaponEnabled, pactWeaponEnabled, improvedPactWeaponEnabled, dedicatedWeaponEnabled) {
return {
hexWeaponEnabled,
pactWeaponEnabled,
improvedPactWeaponEnabled,
dedicatedWeaponEnabled,
};
}
@@ -0,0 +1,11 @@
import * as CharacterConstants from './constants';
import * as CharacterDerivers from './derivers';
import * as CharacterGenerators from './generators';
import * as CharacterHacks from './hacks';
import * as CharacterTypings from './typings';
import * as CharacterUtils from './utils';
import * as CharacterValidators from './validators';
export * from './typings';
export * from './constants';
export { CharacterDerivers, CharacterValidators, CharacterGenerators, CharacterUtils, CharacterHacks };
export default Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, CharacterConstants), CharacterDerivers), CharacterValidators), CharacterGenerators), CharacterTypings), CharacterUtils), CharacterHacks);
@@ -0,0 +1,51 @@
/**
*
* @param preKey
*/
export function getPreferenceKey(preKey) {
switch (preKey) {
case 'abilityScoreDisplayType':
return preKey;
case 'enableDarkMode':
return preKey;
case 'enableContainerCurrency':
return preKey;
case 'enableOptionalClassFeatures':
return preKey;
case 'enableOptionalOrigins':
return preKey;
case 'encumbranceType':
return preKey;
case 'enforceFeatRules':
return preKey;
case 'enforceMulticlassRules':
return preKey;
case 'hitPointType':
return preKey;
case 'ignoreCoinWeight':
return preKey;
case 'primaryMovement':
return preKey;
case 'primarySense':
return preKey;
case 'privacyType':
return preKey;
case 'progressionType':
return preKey;
case 'sharingType':
return preKey;
case 'showCompanions':
return preKey;
case 'showScaledSpells':
return preKey;
case 'showUnarmedStrike':
return preKey;
case 'showWildShape':
return preKey;
case 'useHomebrewContent':
return preKey;
default:
//not implemented
}
return null;
}
@@ -0,0 +1,24 @@
import { groupBy } from 'lodash';
import { EntityUtils } from '../../Entity';
/**
*
* @param defenseAdjustments
*/
export function groupDefenseAdjustments(defenseAdjustments) {
const groupedDefenseAdjustments = [];
const keyedGroupDefenseAdjustments = groupBy(defenseAdjustments, 'name');
Object.keys(keyedGroupDefenseAdjustments).forEach((groupKey) => {
const damageAdjustments = keyedGroupDefenseAdjustments[groupKey];
groupedDefenseAdjustments.push({
name: groupKey,
sources: damageAdjustments
.map((adjustment) => {
return EntityUtils.getDataOriginName(adjustment.dataOrigin, undefined, true);
})
.sort(),
hasCustom: !!damageAdjustments.find((adjustment) => adjustment.isCustom),
damageAdjustments,
});
});
return groupedDefenseAdjustments;
}
@@ -0,0 +1,31 @@
/**
*
* @param hitPointInfo
* @param hpDiff
*/
export function calculateHitPoints(hitPointInfo, hpDiff) {
let newTemp;
let newHp;
const hpTemp = hitPointInfo.tempHp ? hitPointInfo.tempHp : 0;
if (hpDiff < 0) {
newTemp = Math.max(hpTemp + hpDiff, 0);
if (Math.abs(hpDiff) > hpTemp) {
newHp = hitPointInfo.remainingHp + (hpTemp + hpDiff);
}
else {
newHp = hitPointInfo.remainingHp;
}
}
else {
newTemp = hitPointInfo.tempHp;
newHp = Math.min(hitPointInfo.remainingHp + hpDiff, hitPointInfo.totalHp);
}
newHp = Math.max(0, newHp);
const newRemovedHp = hitPointInfo.totalHp - newHp;
return {
newTemp,
newHp,
newRemovedHp,
startHp: hitPointInfo.remainingHp,
};
}
@@ -0,0 +1,118 @@
import { CreatureSizeEnum } from '../../Core';
import { ModifierAccessors, ModifierDerivers, ModifierSubTypeEnum, ModifierValidators } from '../../Modifier';
/**
*
* @param sizeId
* @param modifiers
*/
export function getEncumbranceSize(sizeId, modifiers) {
let encumbranceSize = sizeId;
let isSmallerOverride = false;
let isLargerOverride = false;
const carryingCapacitySizeModifiers = modifiers.filter((modifier) => ModifierValidators.isCarryingCapacitySizeModifier(modifier));
carryingCapacitySizeModifiers.forEach((modifier) => {
switch (ModifierAccessors.getSubType(modifier)) {
case ModifierSubTypeEnum.TINY:
isSmallerOverride = true;
break;
case ModifierSubTypeEnum.LARGE:
case ModifierSubTypeEnum.HUGE:
case ModifierSubTypeEnum.GARGANTUAN:
isLargerOverride = true;
break;
default:
// not implemented
}
});
if (isSmallerOverride) {
encumbranceSize = CreatureSizeEnum.TINY;
}
if (isLargerOverride) {
encumbranceSize = CreatureSizeEnum.LARGE;
}
return encumbranceSize;
}
/**
*
* @param value
* @param encumbranceSizeId
*/
export function getEncumbranceSizeScaledValue(value, encumbranceSizeId) {
switch (encumbranceSizeId) {
case CreatureSizeEnum.TINY:
return value / 2;
case CreatureSizeEnum.LARGE:
case CreatureSizeEnum.HUGE:
case CreatureSizeEnum.GARGANTUAN:
return value * 2;
default:
// not implemented
}
return value;
}
/**
*
* @param value
* @param modifiers
*/
export function getCarryCapacityModifierScaledValue(value, modifiers) {
// get weight multiplied by any multipliers, positive is times, negative is division (ex: 2 is times two, -3 is divide by 3)
const carryingCapacityMultiplierModifiers = modifiers.filter((modifier) => ModifierValidators.isCarringCapacityMultiplierModifier(modifier));
const highestCarryingCapacityMultiplier = ModifierDerivers.deriveHighestModifierValue(carryingCapacityMultiplierModifiers);
if (highestCarryingCapacityMultiplier) {
if (highestCarryingCapacityMultiplier < 0) {
return (value /= Math.abs(highestCarryingCapacityMultiplier));
}
return (value *= highestCarryingCapacityMultiplier);
}
return value;
}
/**
*
* @param strengthScale
* @param strScore
* @param size
* @param modifiers
*/
export function getStrengthScaledCarryCapacity(strengthScale, strScore, size, modifiers) {
const sizeId = size ? size.id : null;
const baseWeight = strScore * strengthScale;
const encumbranceSize = getEncumbranceSize(sizeId, modifiers);
let weight = getEncumbranceSizeScaledValue(baseWeight, encumbranceSize);
weight = getCarryCapacityModifierScaledValue(weight, modifiers);
return weight;
}
/**
*
* @param strScore
* @param size
* @param modifiers
*/
export function getCarryCapacity(strScore, size, modifiers) {
return getStrengthScaledCarryCapacity(15, strScore, size, modifiers);
}
/**
*
* @param carryCapacity
*/
export function getPushDragLiftWeight(carryCapacity) {
return carryCapacity * 2;
}
/**
*
* @param strScore
* @param size
* @param modifiers
*/
export function getEncumberedWeight(strScore, size, modifiers) {
return getStrengthScaledCarryCapacity(5, strScore, size, modifiers);
}
/**
*
* @param strScore
* @param size
* @param modifiers
*/
export function getHeavilyEncumberedWeight(strScore, size, modifiers) {
return getStrengthScaledCarryCapacity(10, strScore, size, modifiers);
}
@@ -0,0 +1,22 @@
import { DamageAdjustmentTypeEnum } from '../../Core';
/**
*
* @param damageAdjustment
*/
export function isResistanceDamageAdjustment(damageAdjustment) {
return damageAdjustment.type === DamageAdjustmentTypeEnum.RESISTANCE;
}
/**
*
* @param damageAdjustment
*/
export function isVulnerabilityDamageAdjustment(damageAdjustment) {
return damageAdjustment.type === DamageAdjustmentTypeEnum.VULNERABILITY;
}
/**
*
* @param damageAdjustment
*/
export function isImmunityDamageAdjustment(damageAdjustment) {
return damageAdjustment.type === DamageAdjustmentTypeEnum.IMMUNITY;
}
@@ -0,0 +1,8 @@
import { CustomProficiencyTypeEnum } from '../../Core';
/**
*
* @param proficiency
*/
export function isCustomSkillProficiency(proficiency) {
return proficiency.type === CustomProficiencyTypeEnum.SKILL;
}
@@ -0,0 +1,22 @@
import { SourceTypeEnum } from '../../Core';
/**
*
* @param source
*/
export function isPrimarySource(source) {
return source.sourceType === SourceTypeEnum.PRIMARY;
}
/**
*
* @param source
*/
export function isSecondarySource(source) {
return source.sourceType === SourceTypeEnum.SECONDARY;
}
/**
*
* @param source
*/
export function isHiddenSource(source) {
return source.sourceType === SourceTypeEnum.HIDDEN;
}