990 lines
36 KiB
JavaScript
990 lines
36 KiB
JavaScript
import { flatten, round, uniqBy } from 'lodash';
|
|
import { TypeScriptUtils } from '../../utils';
|
|
import { AbilityAccessors } from '../Ability';
|
|
import { ActionAccessors, ActionGenerators } from '../Action';
|
|
import { CharacterValidators } from '../Character';
|
|
import { AbilityStatEnum, AttackTypeRangeEnum, PreferenceEncumbranceTypeEnum, StealthCheckTypeEnum } from '../Core';
|
|
import { DataOriginTypeEnum } from '../DataOrigin';
|
|
import { DefinitionHacks } from '../Definition';
|
|
import { HelperUtils } from '../Helper';
|
|
import { InfusionAccessors, InfusionTypeEnum } from '../Infusion';
|
|
import { LimitedUseAccessors } from '../LimitedUse';
|
|
import { ModifierAccessors, ModifierDerivers, ModifierValidators } from '../Modifier';
|
|
import { isValidWeaponMasteryModifier } from '../Modifier/validators';
|
|
import { RuleDataAccessors, RuleDataUtils } from '../RuleData';
|
|
import { AdjustmentTypeEnum, ValueAccessors, ValueHacks, ValueUtils, ValueValidators, } from '../Value';
|
|
import { getArmorDefinitionAc, getArmorTypeId, getAttackType, getBaseItemId, getBaseTypeId, getBundleSize, getCategoryId, getDefinitionAvatarUrl, getDefinitionCanAttune, getDefinitionCanEquip, getDefinitionCapacityWeight, getDefinitionCost, getDefinitionDamageType, getDefinitionId, getDefinitionMagic, getDefinitionName, getDefinitionProperties, getDefinitionStealthCheck, getDescription, getGearTypeId, getItemRequiredStr, getItemWeaponBehaviors, getLimitedUse, getMappingEntityTypeId, getMappingId, getModifiers, getQuantity, getSources, getWeight, getWeightDefinition, isEquipped, isMagic, isMonkWeapon, isStackable, } from './accessors';
|
|
import { ArmorTypeEnum, FUTURE_ITEM_DEFINITION_TYPE, ITEM_CUSTOMIZATION_ADJUSTMENT_TYPES, ItemBaseTypeEnum, ItemBaseTypeIdEnum, ItemTypeEnum, WeaponPropertyEnum, } from './constants';
|
|
import { hack__deriveStealthMediumArmorMasterShouldReturnStealthDefinition } from './hacks';
|
|
import { hasWeaponProperty, isArmorContract, isBaseArmor, isBaseGear, isBaseWeapon, hasItemWeaponBehaviors, isWeaponContract, isAmmunition, isGearContract, } from './utils';
|
|
import { isCustomItem, validateIsWeaponLike } from './validators';
|
|
/**
|
|
*
|
|
* @param item
|
|
*/
|
|
export function deriveArmorClass(item) {
|
|
if (!isArmorContract(item)) {
|
|
return null;
|
|
}
|
|
const definitionArmorClass = getArmorDefinitionAc(item);
|
|
return deriveArmorModifierAcTotal(item) + (definitionArmorClass === null ? 0 : definitionArmorClass);
|
|
}
|
|
/**
|
|
*
|
|
* @param weapon
|
|
* @param valueLookup
|
|
*/
|
|
export function deriveIsOffhand(weapon, valueLookup) {
|
|
return !!ValueUtils.getKeyValue(valueLookup, AdjustmentTypeEnum.IS_OFFHAND, ValueHacks.hack__toString(getMappingId(weapon)), ValueHacks.hack__toString(getMappingEntityTypeId(weapon)));
|
|
}
|
|
/**
|
|
*
|
|
* @param weapon
|
|
* @param valueLookup
|
|
*/
|
|
export function deriveIsSilvered(weapon, valueLookup) {
|
|
return !!ValueUtils.getKeyValue(valueLookup, AdjustmentTypeEnum.IS_SILVER, ValueHacks.hack__toString(getMappingId(weapon)), ValueHacks.hack__toString(getMappingEntityTypeId(weapon)));
|
|
}
|
|
/**
|
|
*
|
|
* @param weapon
|
|
* @param valueLookup
|
|
*/
|
|
export function deriveIsAdamantine(weapon, valueLookup) {
|
|
return !!ValueUtils.getKeyValue(valueLookup, AdjustmentTypeEnum.IS_ADAMANTINE, ValueHacks.hack__toString(getMappingId(weapon)), ValueHacks.hack__toString(getMappingEntityTypeId(weapon)));
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
* @param infusion
|
|
*/
|
|
export function deriveCanAttune(item, infusion) {
|
|
if (infusion && InfusionAccessors.getType(infusion) !== InfusionTypeEnum.REPLICATE) {
|
|
return InfusionAccessors.requiresAttunement(infusion);
|
|
}
|
|
return getDefinitionCanAttune(item);
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
* @param infusion
|
|
*/
|
|
export function deriveCanEquip(item, infusion) {
|
|
if (infusion !== null) {
|
|
return true;
|
|
}
|
|
return getDefinitionCanEquip(item);
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
* @param ruleData
|
|
*/
|
|
export function deriveCategoryInfo(item, ruleData) {
|
|
if (!isWeaponContract(item)) {
|
|
return null;
|
|
}
|
|
return RuleDataUtils.getWeaponCategoryInfo(getCategoryId(item), ruleData);
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
* @param valueLookup
|
|
*/
|
|
export function deriveIsDisplayAsAttack(item, valueLookup) {
|
|
const override = ValueUtils.getKeyValue(valueLookup, AdjustmentTypeEnum.DISPLAY_AS_ATTACK, ValueHacks.hack__toString(getMappingId(item)), ValueHacks.hack__toString(getMappingEntityTypeId(item)));
|
|
const defaultIsDisplayAsAttack = deriveDefaultIsDisplayAsAttack(item);
|
|
return (defaultIsDisplayAsAttack && (override === null || override)) || !!override;
|
|
}
|
|
/**
|
|
*
|
|
* @param baseTypeId
|
|
*/
|
|
export function deriveBaseTypeNameFromId(baseTypeId) {
|
|
switch (baseTypeId) {
|
|
case ItemBaseTypeIdEnum.WEAPON:
|
|
return 'Weapon';
|
|
case ItemBaseTypeIdEnum.ARMOR:
|
|
return 'Armor';
|
|
case ItemBaseTypeIdEnum.GEAR:
|
|
return 'Gear';
|
|
default:
|
|
// not implemented
|
|
}
|
|
return '';
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
* @param valueLookup
|
|
*/
|
|
export function deriveCost(item, valueLookup) {
|
|
const override = ValueUtils.getKeyValue(valueLookup, AdjustmentTypeEnum.COST_OVERRIDE, ValueHacks.hack__toString(getMappingId(item)), ValueHacks.hack__toString(getMappingEntityTypeId(item)));
|
|
const quantity = getQuantity(item);
|
|
const bundleSize = getBundleSize(item);
|
|
let costPerItem = getDefinitionCost(item);
|
|
if (costPerItem === null) {
|
|
costPerItem = 0;
|
|
}
|
|
if (override !== null) {
|
|
costPerItem = override;
|
|
}
|
|
if (isStackable(item)) {
|
|
costPerItem /= bundleSize === 0 ? 1 : bundleSize;
|
|
}
|
|
return round(costPerItem * quantity, 2);
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
* @param globalModifiers
|
|
*/
|
|
export function deriveStealthCheck(item, globalModifiers) {
|
|
if (!isArmorContract(item)) {
|
|
return StealthCheckTypeEnum.NONE;
|
|
}
|
|
const itemModifiers = getModifiers(item);
|
|
const definitionCheck = getDefinitionStealthCheck(item);
|
|
if (definitionCheck === StealthCheckTypeEnum.DISADVANTAGE) {
|
|
const filteredGlobalModifiers = globalModifiers.filter((modifier) => ModifierValidators.isStealthDisadvantageRemoveModifier(modifier));
|
|
if (itemModifiers.find((modifier) => ModifierValidators.isStealthDisadvantageRemoveModifier(modifier)) ||
|
|
filteredGlobalModifiers.length) {
|
|
if (hack__deriveStealthMediumArmorMasterShouldReturnStealthDefinition(item, filteredGlobalModifiers)) {
|
|
return definitionCheck;
|
|
}
|
|
return StealthCheckTypeEnum.NONE;
|
|
}
|
|
}
|
|
return definitionCheck;
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
* @param ruleData
|
|
*/
|
|
export function deriveAvatarUrl(item, ruleData) {
|
|
let previewImageUrl = getDefinitionAvatarUrl(item);
|
|
switch (getBaseTypeId(item)) {
|
|
case ItemBaseTypeIdEnum.WEAPON:
|
|
if (!previewImageUrl) {
|
|
previewImageUrl = ruleData.defaultWeaponImageUrl;
|
|
}
|
|
break;
|
|
case ItemBaseTypeIdEnum.ARMOR:
|
|
if (!previewImageUrl) {
|
|
previewImageUrl = ruleData.defaultArmorImageUrl;
|
|
}
|
|
break;
|
|
case ItemBaseTypeIdEnum.GEAR:
|
|
if (!previewImageUrl) {
|
|
previewImageUrl = ruleData.defaultGearImageUrl;
|
|
}
|
|
break;
|
|
}
|
|
if (!previewImageUrl && isCustomItem(item)) {
|
|
previewImageUrl = ruleData.defaultGearImageUrl;
|
|
}
|
|
if (previewImageUrl) {
|
|
return previewImageUrl;
|
|
}
|
|
return '';
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
*/
|
|
export function deriveBaseType(item) {
|
|
if (isArmorContract(item)) {
|
|
return ItemBaseTypeEnum.ARMOR;
|
|
}
|
|
else if (isWeaponContract(item)) {
|
|
return ItemBaseTypeEnum.WEAPON;
|
|
}
|
|
return ItemBaseTypeEnum.GEAR;
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
* @param valueLookup
|
|
*/
|
|
export function deriveWeight(item, valueLookup) {
|
|
const quantity = getQuantity(item);
|
|
const bundleSize = getBundleSize(item);
|
|
const isItemStackable = isStackable(item);
|
|
const weight = getWeightDefinition(item);
|
|
const weightOverride = ValueUtils.getKeyValue(valueLookup, AdjustmentTypeEnum.WEIGHT_OVERRIDE, ValueHacks.hack__toString(getMappingId(item)), ValueHacks.hack__toString(getMappingEntityTypeId(item)));
|
|
let baseWeight = weight;
|
|
if (weightOverride !== null) {
|
|
baseWeight = weightOverride;
|
|
}
|
|
if (!baseWeight) {
|
|
return 0;
|
|
}
|
|
if (isItemStackable) {
|
|
if (quantity) {
|
|
return round(baseWeight * (quantity / (bundleSize === 0 ? 1 : bundleSize)), 2);
|
|
}
|
|
return 0;
|
|
}
|
|
return baseWeight;
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
* @param valueLookup
|
|
*/
|
|
export function deriveCapacityWeight(item, valueLookup) {
|
|
const capacityWeight = getDefinitionCapacityWeight(item);
|
|
const capacityOverride = ValueUtils.getKeyValue(valueLookup, AdjustmentTypeEnum.CAPACITY_WEIGHT_OVERRIDE, ValueHacks.hack__toString(getMappingId(item)), ValueHacks.hack__toString(getMappingEntityTypeId(item)));
|
|
return capacityOverride !== null && capacityOverride !== void 0 ? capacityOverride : capacityWeight;
|
|
}
|
|
/**
|
|
*
|
|
* @param items
|
|
* @param initialValue
|
|
*/
|
|
export function deriveItemsWeightTotal(items, initialValue = 0) {
|
|
return items.reduce((acc, item) => (acc += getWeight(item)), initialValue);
|
|
}
|
|
/**
|
|
*
|
|
* @param armor
|
|
* @param modifiers
|
|
* @param abilityLookup
|
|
*/
|
|
export function deriveArmorDexBonusMax(armor, modifiers, abilityLookup) {
|
|
const armorTypeId = getArmorTypeId(armor);
|
|
switch (armorTypeId) {
|
|
case ArmorTypeEnum.MEDIUM_ARMOR:
|
|
const dexAbility = abilityLookup[AbilityStatEnum.DEXTERITY];
|
|
const dexAbilityScore = AbilityAccessors.getScore(dexAbility);
|
|
if (dexAbility && dexAbilityScore !== null && dexAbilityScore >= 16) {
|
|
const maxModifiers = modifiers.filter((modifier) => ModifierValidators.isSetAcMaxDexModifierModifier(modifier));
|
|
const maxArmoredModifiers = modifiers.filter((modifier) => ModifierValidators.isSetAcMaxDexArmoredModifier(modifier));
|
|
const maxModifierPossibilities = [];
|
|
if (maxModifiers.length) {
|
|
maxModifierPossibilities.push(ModifierDerivers.deriveHighestModifierValue(maxModifiers));
|
|
}
|
|
if (maxArmoredModifiers.length) {
|
|
maxModifierPossibilities.push(ModifierDerivers.deriveHighestModifierValue(maxArmoredModifiers));
|
|
}
|
|
if (maxModifierPossibilities.length) {
|
|
return Math.max(...maxModifierPossibilities);
|
|
}
|
|
}
|
|
return 2;
|
|
case ArmorTypeEnum.HEAVY_ARMOR:
|
|
return 0;
|
|
default:
|
|
// not implemented
|
|
}
|
|
return null;
|
|
}
|
|
/**
|
|
*
|
|
* @param modifiers
|
|
*/
|
|
export function deriveUnarmoredDexBonusMax(modifiers) {
|
|
const maxDexModifiers = modifiers.filter((modifier) => ModifierValidators.isSetAcMaxDexModifierModifier(modifier));
|
|
const maxDexUnarmoredModifiers = modifiers.filter((modifier) => ModifierValidators.isSetAcMaxDexUnarmoredModifier(modifier));
|
|
const maxDexValuePossibilities = [];
|
|
if (maxDexModifiers.length) {
|
|
maxDexValuePossibilities.push(ModifierDerivers.deriveHighestModifierValue(maxDexModifiers));
|
|
}
|
|
if (maxDexUnarmoredModifiers.length) {
|
|
maxDexValuePossibilities.push(ModifierDerivers.deriveHighestModifierValue(maxDexUnarmoredModifiers));
|
|
}
|
|
if (maxDexValuePossibilities.length) {
|
|
return Math.max(...maxDexValuePossibilities);
|
|
}
|
|
return null;
|
|
}
|
|
/**
|
|
*
|
|
* @param localModifiers
|
|
* @param globalModifiers
|
|
* @param abilityLookup
|
|
*/
|
|
export function deriveUnarmoredDexBonus(localModifiers, globalModifiers, abilityLookup) {
|
|
let bonus = 0;
|
|
if (Object.keys(abilityLookup).length) {
|
|
const dexMod = abilityLookup[AbilityStatEnum.DEXTERITY];
|
|
bonus = AbilityAccessors.getModifier(dexMod) || 0;
|
|
}
|
|
const ignoreUnarmoredDexBonusModifiers = localModifiers.filter((modifier) => ModifierValidators.isIgnoreUnarmoredDexAcBonusModifier(modifier));
|
|
if (ignoreUnarmoredDexBonusModifiers.length) {
|
|
return 0;
|
|
}
|
|
const max = deriveUnarmoredDexBonusMax(globalModifiers);
|
|
return Math.min(bonus, max === null ? Number.MAX_VALUE : max);
|
|
}
|
|
/**
|
|
*
|
|
* @param armor
|
|
* @param dexModifier
|
|
* @param modifiers
|
|
* @param abilityLookup
|
|
*/
|
|
export function deriveArmorDexBonus(armor, dexModifier, modifiers, abilityLookup) {
|
|
switch (getArmorTypeId(armor)) {
|
|
case ArmorTypeEnum.HEAVY_ARMOR:
|
|
case ArmorTypeEnum.SHIELD:
|
|
return 0;
|
|
}
|
|
const armorDexBonusMax = deriveArmorDexBonusMax(armor, modifiers, abilityLookup);
|
|
return armorDexBonusMax === null ? dexModifier : Math.min(armorDexBonusMax, dexModifier);
|
|
}
|
|
/**
|
|
*
|
|
* @param armor
|
|
* @param dexModifier
|
|
* @param modifiers
|
|
* @param abilityLookup
|
|
* @param ruleData
|
|
*/
|
|
export function deriveArmorAc(armor, dexModifier, modifiers, abilityLookup, ruleData) {
|
|
let armorAc = ruleData.noArmorAcAmount;
|
|
if (armor) {
|
|
const definitionArmorClass = getArmorDefinitionAc(armor);
|
|
if (definitionArmorClass !== null) {
|
|
armorAc = definitionArmorClass;
|
|
}
|
|
}
|
|
return armorAc + deriveArmorDexBonus(armor, dexModifier, modifiers, abilityLookup);
|
|
}
|
|
/**
|
|
*
|
|
* @param armorItems
|
|
* @param dexModifier
|
|
* @param modifiers
|
|
* @param abilityLookup
|
|
* @param ruleData
|
|
*/
|
|
export function deriveHighestArmorAcItem(armorItems, dexModifier, modifiers, abilityLookup, ruleData) {
|
|
const armorAc = armorItems.map((item) => {
|
|
const armorAc = deriveArmorAc(item, dexModifier, modifiers, abilityLookup, ruleData);
|
|
const modifierAc = deriveArmorModifierAcTotal(item);
|
|
const definitionArmorClass = getArmorDefinitionAc(item);
|
|
return {
|
|
item,
|
|
armorClassWithDex: armorAc + modifierAc,
|
|
armorClass: (definitionArmorClass === null ? 0 : definitionArmorClass) + modifierAc,
|
|
};
|
|
});
|
|
return HelperUtils.getLast(armorAc, ['armorClassWithDex']);
|
|
}
|
|
/**
|
|
*
|
|
* @param equippedItems
|
|
* @param strScore
|
|
* @param preferences
|
|
* @param modifiers
|
|
*/
|
|
export function deriveArmorSpeedAdjustmentAmount(equippedItems, strScore, preferences, modifiers) {
|
|
if (preferences.encumbranceType === PreferenceEncumbranceTypeEnum.VARIANT ||
|
|
modifiers.some(ModifierValidators.isIgnoreHeavyArmorSpeedReductionModifier)) {
|
|
return 0;
|
|
}
|
|
const armorItems = equippedItems.filter(isArmorContract);
|
|
const encumberingArmorItem = armorItems.find((item) => {
|
|
const reqStr = getItemRequiredStr(item);
|
|
return reqStr && reqStr > strScore;
|
|
});
|
|
if (encumberingArmorItem) {
|
|
return -10;
|
|
}
|
|
return 0;
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
* @param globalModifiers
|
|
* @param isPactWeapon
|
|
* @param valueLookupByType
|
|
* @param ruleData
|
|
*/
|
|
export function deriveIsProficient(item, globalModifiers, isPactWeapon, valueLookupByType, ruleData) {
|
|
if (isBaseWeapon(item)) {
|
|
if (globalModifiers.filter((modifier) => ModifierValidators.isValidWeaponProficiencyModifier(modifier, item))
|
|
.length) {
|
|
return true;
|
|
}
|
|
if (valueLookupByType.hasOwnProperty(AdjustmentTypeEnum.WEAPON_PROFICIENCY_LEVEL)) {
|
|
const charValues = valueLookupByType[AdjustmentTypeEnum.WEAPON_PROFICIENCY_LEVEL];
|
|
if (charValues.find((charValue) => ValueAccessors.getValueIntId(charValue) === getBaseItemId(item))) {
|
|
return true;
|
|
}
|
|
}
|
|
return isPactWeapon;
|
|
}
|
|
if (isBaseArmor(item)) {
|
|
if (globalModifiers.filter((modifier) => ModifierValidators.isValidArmorProficiencyModifier(modifier, item))
|
|
.length) {
|
|
return true;
|
|
}
|
|
if (valueLookupByType.hasOwnProperty(AdjustmentTypeEnum.ARMOR_PROFICIENCY_LEVEL)) {
|
|
const charValues = valueLookupByType[AdjustmentTypeEnum.ARMOR_PROFICIENCY_LEVEL];
|
|
if (charValues.find((charValue) => ValueAccessors.getValueIntId(charValue) === getBaseItemId(item))) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
if (!isBaseGear(item)) {
|
|
if (getModifiers(item).filter((modifier) => ModifierValidators.isProficiencySelfModifier(modifier)).length) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
* @param modifiers
|
|
*/
|
|
export function deriveIsMonkWeapon(item, modifiers) {
|
|
if (!isWeaponContract(item)) {
|
|
return false;
|
|
}
|
|
const monkWeaponModifiers = modifiers.filter((modifier) => ModifierValidators.isMonkWeaponModifier(modifier));
|
|
return (isMonkWeapon(item) ||
|
|
!!monkWeaponModifiers.find((modifier) => ModifierValidators.isValidWeaponSubTypeModifier(modifier, item)));
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
* @param valueLookup
|
|
*/
|
|
export function deriveIsCustomized(item, valueLookup) {
|
|
return isCustomItem(item)
|
|
? false
|
|
: ValueValidators.validateHasCustomization(ITEM_CUSTOMIZATION_ADJUSTMENT_TYPES, valueLookup, ValueHacks.hack__toString(getMappingId(item)), ValueHacks.hack__toString(getMappingEntityTypeId(item)));
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
*/
|
|
export function deriveWeaponAdditionalDamage(item) {
|
|
let additionalDamages = [];
|
|
if (!isWeaponContract(item)) {
|
|
return additionalDamages;
|
|
}
|
|
const itemModifiers = getModifiers(item);
|
|
const additionalDamageModifiers = itemModifiers.filter((modifier) => ModifierValidators.isWeaponAdditionalDamageModifier(modifier));
|
|
if (additionalDamageModifiers.length) {
|
|
additionalDamages = additionalDamageModifiers.map((modifier) => {
|
|
const damageValue = ModifierAccessors.getValue(modifier);
|
|
return {
|
|
damageType: ModifierAccessors.getFriendlySubtypeName(modifier),
|
|
damage: damageValue ? damageValue : ModifierAccessors.getDice(modifier),
|
|
info: ModifierAccessors.getRestriction(modifier),
|
|
id: ModifierAccessors.getId(modifier),
|
|
};
|
|
});
|
|
}
|
|
return additionalDamages;
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
* @param modifiers
|
|
* @param ruleData
|
|
*/
|
|
export function deriveWeaponReach(item, modifiers, ruleData) {
|
|
let reach = null;
|
|
if (!isBaseWeapon(item)) {
|
|
return reach;
|
|
}
|
|
if (getAttackType(item) === AttackTypeRangeEnum.MELEE) {
|
|
const baseReach = ruleData.baseWeaponReach;
|
|
const bonusReachModifiers = modifiers.filter((modifier) => ModifierValidators.isBonusMeleeReachModifier(modifier));
|
|
const bonusReachModifierTotal = ModifierDerivers.sumModifiers(bonusReachModifiers);
|
|
let reachPropertyTotal = 0;
|
|
if (hasWeaponProperty(item, WeaponPropertyEnum.REACH)) {
|
|
reachPropertyTotal = ruleData.weaponPropertyReachDistance;
|
|
}
|
|
reach = baseReach + reachPropertyTotal + bonusReachModifierTotal;
|
|
}
|
|
return reach;
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
* @param modifiers
|
|
* @param martialArtsLevel
|
|
*/
|
|
export function deriveAvailableWeaponAbilities(item, modifiers, martialArtsLevelScale, isDedicatedWeapon, isPactWeapon, isHexWeapon, appliedWeaponReplacementStats) {
|
|
const availableAbilities = [];
|
|
const attackType = getAttackType(item);
|
|
if (attackType === AttackTypeRangeEnum.MELEE || hasWeaponProperty(item, WeaponPropertyEnum.FINESSE)) {
|
|
availableAbilities.push(AbilityStatEnum.STRENGTH);
|
|
}
|
|
if (attackType === AttackTypeRangeEnum.RANGED ||
|
|
(martialArtsLevelScale !== null && (deriveIsMonkWeapon(item, modifiers) || isDedicatedWeapon)) ||
|
|
hasWeaponProperty(item, WeaponPropertyEnum.FINESSE)) {
|
|
availableAbilities.push(AbilityStatEnum.DEXTERITY);
|
|
}
|
|
if (isMagic(item)) {
|
|
const magicItemBonusModifiers = modifiers.filter((modifier) => ModifierValidators.isBonusMagicItemAttackWithStatModifier(modifier));
|
|
magicItemBonusModifiers.forEach((modifier) => {
|
|
const entityId = ModifierAccessors.getEntityId(modifier);
|
|
if (entityId !== null && !availableAbilities.includes(entityId)) {
|
|
availableAbilities.push(entityId);
|
|
}
|
|
});
|
|
}
|
|
//handle PactWeapon modifiers
|
|
if (isPactWeapon) {
|
|
//Get the enable Pact modifier
|
|
const enablePactModifier = modifiers.find((modifier) => ModifierValidators.isEnablePactWeaponModifier(modifier));
|
|
//Get the origin of the enable Pact Modifier
|
|
const pactDataOrigin = enablePactModifier && ModifierAccessors.getDataOrigin(enablePactModifier);
|
|
if (pactDataOrigin) {
|
|
const replacementAbilities = deriveReplacementWeaponAbilities(modifiers, pactDataOrigin);
|
|
replacementAbilities.forEach((ability) => {
|
|
if (!availableAbilities.includes(ability)) {
|
|
availableAbilities.push(ability);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
//handle HexWeapon modifiers
|
|
if (isHexWeapon) {
|
|
//Get the enable Hex modifier
|
|
const enableHexModifier = modifiers.find((modifier) => ModifierValidators.isEnableHexWeaponModifier(modifier));
|
|
//Get the origin of the enable Hex Modifier
|
|
const hexDataOrigin = enableHexModifier && ModifierAccessors.getDataOrigin(enableHexModifier);
|
|
if (hexDataOrigin) {
|
|
const replacementAbilities = deriveReplacementWeaponAbilities(modifiers, hexDataOrigin);
|
|
replacementAbilities.forEach((ability) => {
|
|
if (!availableAbilities.includes(ability)) {
|
|
availableAbilities.push(ability);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
//handle Weapon Replacement Stats
|
|
if (appliedWeaponReplacementStats.length > 0) {
|
|
const enableAbilityStatReplacementModifiers = modifiers.filter((modifier) => ModifierValidators.isReplaceWeaponAbilityModifier(modifier));
|
|
enableAbilityStatReplacementModifiers.forEach((modifier) => {
|
|
//get the origin of the modifier
|
|
const dataOrigin = ModifierAccessors.getDataOrigin(modifier);
|
|
if (dataOrigin) {
|
|
const replacementAbilities = deriveReplacementWeaponAbilities(modifiers, dataOrigin);
|
|
replacementAbilities.forEach((ability) => {
|
|
if (!availableAbilities.includes(ability)) {
|
|
availableAbilities.push(ability);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
return availableAbilities;
|
|
}
|
|
export function deriveReplacementWeaponAbilities(modifiers, enableModifierDataOrigin) {
|
|
let replacementAbilities = [];
|
|
const replaceAbilityModifiers = modifiers.filter((modifier) => ModifierValidators.isReplaceWeaponAbilityModifier(modifier));
|
|
if (replaceAbilityModifiers.length > 0) {
|
|
replaceAbilityModifiers.forEach((replaceAbilityModifier) => {
|
|
var _a, _b;
|
|
//get the id of the Ability Stat on the replace weapon ability modifier
|
|
const statId = ModifierAccessors.getEntityId(replaceAbilityModifier);
|
|
//get the data origin of the replace weapon ability modifier
|
|
const dataOrigin = ModifierAccessors.getDataOrigin(replaceAbilityModifier);
|
|
if (statId &&
|
|
dataOrigin.type === enableModifierDataOrigin.type &&
|
|
((_a = dataOrigin.primary.definition) === null || _a === void 0 ? void 0 : _a.id) === ((_b = enableModifierDataOrigin.primary.definition) === null || _b === void 0 ? void 0 : _b.id) &&
|
|
!replacementAbilities.includes(statId)) {
|
|
replacementAbilities.push(statId);
|
|
}
|
|
});
|
|
}
|
|
return replacementAbilities;
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
* @param modifiers
|
|
*/
|
|
export function deriveCanWeaponOffhand(item, modifiers) {
|
|
if (!isBaseWeapon(item)) {
|
|
return false;
|
|
}
|
|
const ignoreOffhandLightRestrictionsModifiers = modifiers.filter((modifier) => ModifierValidators.isIgnoreOffhandLightRestrictionsModifier(modifier));
|
|
let canOffhand = false;
|
|
if (hasWeaponProperty(item, WeaponPropertyEnum.LIGHT) || ignoreOffhandLightRestrictionsModifiers.length > 0) {
|
|
canOffhand = true;
|
|
}
|
|
return canOffhand;
|
|
}
|
|
/**
|
|
*
|
|
* @param weapon
|
|
*/
|
|
export function deriveCanPactWeapon(weapon) {
|
|
return getAttackType(weapon) === AttackTypeRangeEnum.MELEE;
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
* @param proficiency
|
|
*/
|
|
export function deriveCanHexWeapon(item, proficiency) {
|
|
if (!isBaseWeapon(item) && !validateIsWeaponLike(item)) {
|
|
return false;
|
|
}
|
|
return proficiency && !hasWeaponProperty(item, WeaponPropertyEnum.TWO_HANDED);
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
* @param proficiency
|
|
*/
|
|
export function deriveCanBeDedicatedWeapon(item, proficiency) {
|
|
if (!isBaseWeapon(item) && !validateIsWeaponLike(item)) {
|
|
return false;
|
|
}
|
|
return (proficiency &&
|
|
!hasWeaponProperty(item, WeaponPropertyEnum.HEAVY) &&
|
|
!hasWeaponProperty(item, WeaponPropertyEnum.SPECIAL));
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
* @param modifiers
|
|
*/
|
|
export function deriveMasteryName(item, modifiers) {
|
|
if (getBaseTypeId(item) !== ItemBaseTypeIdEnum.WEAPON) {
|
|
return null;
|
|
}
|
|
//get modifiers that are weaponmastery
|
|
const masteryModifiers = modifiers.filter((modifier) => isValidWeaponMasteryModifier(modifier));
|
|
//get entity id from modifier and match to item baseWeaponid
|
|
const masteryModifier = masteryModifiers.find((modifier) => ModifierAccessors.getEntityId(modifier) === getBaseItemId(item));
|
|
return masteryModifier ? ModifierAccessors.getFriendlySubtypeName(masteryModifier) : null;
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
* @param valueLookup
|
|
*/
|
|
export function deriveName(item, valueLookup) {
|
|
const override = ValueUtils.getKeyValue(valueLookup, AdjustmentTypeEnum.NAME_OVERRIDE, ValueHacks.hack__toString(getMappingId(item)), ValueHacks.hack__toString(getMappingEntityTypeId(item)));
|
|
if (override !== null && override !== '') {
|
|
return override;
|
|
}
|
|
const definitionName = getDefinitionName(item);
|
|
if (definitionName === null) {
|
|
return '';
|
|
}
|
|
return definitionName;
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
* @param valueLookup
|
|
*/
|
|
export function deriveNotes(item, valueLookup) {
|
|
const notes = ValueUtils.getKeyValue(valueLookup, AdjustmentTypeEnum.NOTES, ValueHacks.hack__toString(getMappingId(item)), ValueHacks.hack__toString(getMappingEntityTypeId(item)));
|
|
if (notes !== null && notes !== '') {
|
|
return notes;
|
|
}
|
|
return null;
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
* @param modifiers
|
|
* @param ruleData
|
|
*/
|
|
export function deriveProperties(item, modifiers, ruleData) {
|
|
let properties = [];
|
|
if (isArmorContract(item)) {
|
|
return properties;
|
|
}
|
|
if (isWeaponContract(item)) {
|
|
properties = getDefinitionProperties(item);
|
|
}
|
|
else if (hasItemWeaponBehaviors(item)) {
|
|
const behavior = getItemWeaponBehaviors(item);
|
|
const firstBehavior = behavior[0];
|
|
properties = firstBehavior.properties === null ? [] : firstBehavior.properties;
|
|
}
|
|
const weaponPropertyModifiers = modifiers.filter((modifier) => ModifierValidators.isWeaponPropertyModifier(modifier));
|
|
const weaponPropertyModifierInfos = weaponPropertyModifiers
|
|
.map((modifier) => {
|
|
const entityId = ModifierAccessors.getEntityId(modifier);
|
|
if (entityId === null) {
|
|
return null;
|
|
}
|
|
const configWeaponProperty = RuleDataUtils.getWeaponPropertyInfo(entityId, ruleData);
|
|
if (configWeaponProperty === null) {
|
|
return null;
|
|
}
|
|
return {
|
|
id: configWeaponProperty.id,
|
|
description: configWeaponProperty.description,
|
|
name: configWeaponProperty.name,
|
|
notes: '',
|
|
};
|
|
})
|
|
.filter(TypeScriptUtils.isNotNullOrUndefined);
|
|
properties = uniqBy([...properties, ...weaponPropertyModifierInfos], (property) => property.name);
|
|
const ignoreWeaponPropertyModifiers = modifiers.filter((modifier) => ModifierValidators.isIgnoreWeaponPropertyModifier(modifier));
|
|
if (ignoreWeaponPropertyModifiers.length) {
|
|
const ignoreWeaponPropertyModifiersNames = ignoreWeaponPropertyModifiers
|
|
.map((modifier) => ModifierAccessors.getFriendlySubtypeName(modifier))
|
|
.filter(TypeScriptUtils.isNotNullOrUndefined);
|
|
properties = properties.filter((property) => property.name !== null && !ignoreWeaponPropertyModifiersNames.includes(property.name));
|
|
}
|
|
return properties;
|
|
}
|
|
/**
|
|
*
|
|
* @param armorItems
|
|
*/
|
|
export function deriveValidGlobalModifiersFromArmor(armorItems) {
|
|
if (!armorItems.length) {
|
|
return [];
|
|
}
|
|
// get all armor modifiers that aren't AC bonuses
|
|
return flatten(armorItems.map((item) => getModifiers(item).filter((modifier) => !ModifierValidators.isBonusArmorModifier(modifier) &&
|
|
!ModifierValidators.isProficiencySelfModifier(modifier) &&
|
|
isEquipped(item) &&
|
|
ModifierValidators.isValidAttunedItemModifier(modifier, item))));
|
|
}
|
|
/**
|
|
*
|
|
* @param weaponItems
|
|
*/
|
|
export function deriveValidGlobalModifiersFromWeapons(weaponItems) {
|
|
if (!weaponItems.length) {
|
|
return [];
|
|
}
|
|
// get all weapon modifiers that aren't bonus magic
|
|
return flatten(weaponItems.map((item) => getModifiers(item).filter((modifier) => !ModifierValidators.isBonusMagicModifier(modifier) &&
|
|
!ModifierValidators.isProficiencySelfModifier(modifier) &&
|
|
!ModifierValidators.isReplaceDamageTypeModifier(modifier) &&
|
|
!ModifierValidators.isWeaponPropertyModifier(modifier) &&
|
|
!ModifierValidators.isWeaponAdditionalDamageModifier(modifier) &&
|
|
isEquipped(item) &&
|
|
ModifierValidators.isValidAttunedItemModifier(modifier, item))));
|
|
}
|
|
/**
|
|
*
|
|
* @param gearItems
|
|
*/
|
|
export function deriveValidGlobalModifiersFromGear(gearItems) {
|
|
if (!gearItems.length) {
|
|
return [];
|
|
}
|
|
return flatten(gearItems.map((item) => getModifiers(item).filter((modifier) => isEquipped(item) &&
|
|
(!hasItemWeaponBehaviors(item) ||
|
|
(hasItemWeaponBehaviors(item) && !ModifierValidators.isBonusMagicModifier(modifier))) &&
|
|
!ModifierValidators.isProficiencySelfModifier(modifier) &&
|
|
ModifierValidators.isValidAttunedItemModifier(modifier, item))));
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
*/
|
|
export function deriveValidArmorStatModifiers(item) {
|
|
return getModifiers(item).filter((modifier) => ModifierValidators.isBonusArmorModifier(modifier) &&
|
|
ModifierValidators.isValidAttunedItemModifier(modifier, item));
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
*/
|
|
export function deriveArmorModifierAcTotal(item) {
|
|
const validModifiers = deriveValidArmorStatModifiers(item);
|
|
return ModifierDerivers.sumModifiers(validModifiers);
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
* @param modifiers
|
|
*/
|
|
export function deriveDamageType(item, modifiers) {
|
|
let damageType = getDefinitionDamageType(item);
|
|
const replaceDamageTypeModifiers = modifiers.filter((modifier) => ModifierValidators.isReplaceDamageTypeModifier(modifier));
|
|
if (replaceDamageTypeModifiers.length) {
|
|
damageType = ModifierAccessors.getFriendlySubtypeName(replaceDamageTypeModifiers[0]);
|
|
}
|
|
return damageType;
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
*/
|
|
export function deriveDefinitionKey(item) {
|
|
return DefinitionHacks.hack__generateDefinitionKey(FUTURE_ITEM_DEFINITION_TYPE, getDefinitionId(item));
|
|
}
|
|
/**
|
|
*
|
|
* @param infusion
|
|
* @param valueLookup
|
|
* @param item
|
|
*/
|
|
export function deriveInfusionActions(infusion, valueLookup, item) {
|
|
if (infusion === null) {
|
|
return [];
|
|
}
|
|
const itemLimitedUse = getLimitedUse(item);
|
|
const baseActions = ActionGenerators.generateDataOriginBaseActions(InfusionAccessors.getDefinitionActions(infusion), valueLookup, DataOriginTypeEnum.ITEM, item);
|
|
return baseActions.map((action) => {
|
|
const actionLimitedUse = ActionAccessors.getLimitedUse(action);
|
|
// TODO I don't like this but im not sure how to fix it. and moving on because of time
|
|
// possibly calculate in ActionDerivers.deriveLimitedUse
|
|
if (actionLimitedUse !== null) {
|
|
return Object.assign(Object.assign({}, action), { limitedUse: Object.assign(Object.assign({}, actionLimitedUse), { numberUsed: itemLimitedUse === null ? 0 : LimitedUseAccessors.getNumberUsed(itemLimitedUse) }) });
|
|
}
|
|
return action;
|
|
});
|
|
}
|
|
/**
|
|
*
|
|
* @param infusionActions
|
|
* @param itemLimitedUse
|
|
*/
|
|
export function deriveLimitedUse(infusionActions, itemLimitedUse) {
|
|
// if we have no infusion actions, we will just keep what we have
|
|
if (infusionActions.length === 0) {
|
|
return itemLimitedUse;
|
|
}
|
|
// This is for when we updated charges used to null, but still have limited use object
|
|
if (itemLimitedUse !== null && LimitedUseAccessors.getNumberUsed(itemLimitedUse) === null) {
|
|
return null;
|
|
}
|
|
const limitedUseInfusionAction = infusionActions.find((action) => ActionAccessors.getLimitedUse(action) !== null);
|
|
if (!limitedUseInfusionAction) {
|
|
return itemLimitedUse;
|
|
}
|
|
// We made assumption that all of the limited use must be the same for all actions on items
|
|
const actionLimitedUse = ActionAccessors.getLimitedUse(limitedUseInfusionAction);
|
|
if (actionLimitedUse) {
|
|
return {
|
|
resetType: actionLimitedUse.resetType === null ? null : actionLimitedUse.resetType.toString(),
|
|
resetTypeDescription: null,
|
|
maxUses: actionLimitedUse.maxUses,
|
|
numberUsed: itemLimitedUse === null ? 0 : itemLimitedUse.numberUsed,
|
|
};
|
|
}
|
|
return itemLimitedUse;
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
* @param infusion
|
|
*/
|
|
export function deriveIsMagic(item, infusion) {
|
|
if (infusion) {
|
|
return true;
|
|
}
|
|
return getDefinitionMagic(item);
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
*/
|
|
export function deriveDefaultIsDisplayAsAttack(item) {
|
|
if (!isEquipped(item)) {
|
|
return false;
|
|
}
|
|
return validateIsWeaponLike(item);
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
* @param ruleData
|
|
*/
|
|
export function deriveType(item, ruleData) {
|
|
if (isArmorContract(item)) {
|
|
const armorTypeId = getArmorTypeId(item);
|
|
if (armorTypeId !== null) {
|
|
const armorType = HelperUtils.lookupDataOrFallback(RuleDataAccessors.getArmorTypeLookup(ruleData), armorTypeId);
|
|
if (armorType !== null) {
|
|
return armorType.name;
|
|
}
|
|
}
|
|
}
|
|
else if (isWeaponContract(item)) {
|
|
if (isAmmunition(item)) {
|
|
return 'Ammunition';
|
|
}
|
|
const weapon = RuleDataUtils.getWeaponByEntityId(getBaseItemId(item), getBaseTypeId(item), ruleData);
|
|
if (weapon !== null) {
|
|
return weapon.name;
|
|
}
|
|
}
|
|
else if (isGearContract(item)) {
|
|
if (!getDefinitionMagic(item)) {
|
|
return 'Gear';
|
|
}
|
|
const gearTypeId = getGearTypeId(item);
|
|
switch (gearTypeId) {
|
|
case ItemTypeEnum.ARMOR:
|
|
return 'Armor';
|
|
case ItemTypeEnum.ARTIFACT:
|
|
return 'Artifact';
|
|
case ItemTypeEnum.POTION:
|
|
return 'Potion';
|
|
case ItemTypeEnum.RING:
|
|
return 'Ring';
|
|
case ItemTypeEnum.ROD:
|
|
return 'Rod';
|
|
case ItemTypeEnum.SCROLL:
|
|
return 'Scroll';
|
|
case ItemTypeEnum.STAFF:
|
|
return 'Staff';
|
|
case ItemTypeEnum.WAND:
|
|
return 'Wand';
|
|
case ItemTypeEnum.WEAPON:
|
|
return 'Weapon';
|
|
case ItemTypeEnum.WONDROUS_ITEM:
|
|
return 'Wondrous item';
|
|
default:
|
|
// not implemented
|
|
}
|
|
}
|
|
else if (isCustomItem(item)) {
|
|
return 'Custom';
|
|
}
|
|
return null;
|
|
}
|
|
/**
|
|
*
|
|
* @param item
|
|
* @param notes
|
|
*/
|
|
export function deriveOriginalCustomContract(item, notes) {
|
|
if (isCustomItem(item)) {
|
|
return {
|
|
id: getDefinitionId(item),
|
|
name: getDefinitionName(item),
|
|
weight: getWeightDefinition(item),
|
|
cost: getDefinitionCost(item),
|
|
quantity: getQuantity(item),
|
|
description: getDescription(item),
|
|
notes,
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
export function derivePrimarySourceCategoryId(item, ruleData) {
|
|
var _a;
|
|
const primarySources = getSources(item).filter(CharacterValidators.isPrimarySource);
|
|
if (!primarySources.length) {
|
|
return 0;
|
|
}
|
|
const source = HelperUtils.lookupDataOrFallback(RuleDataAccessors.getSourceDataLookup(ruleData), primarySources[0].sourceId);
|
|
return ((_a = source === null || source === void 0 ? void 0 : source.sourceCategory) === null || _a === void 0 ? void 0 : _a.id) || 0;
|
|
}
|
|
export function deriveSecondarySourceCategoryIds(item, ruleData) {
|
|
const secondarySources = getSources(item).filter(CharacterValidators.isSecondarySource);
|
|
return secondarySources.map((source) => {
|
|
var _a;
|
|
const sourceData = HelperUtils.lookupDataOrFallback(RuleDataAccessors.getSourceDataLookup(ruleData), source.sourceId);
|
|
return ((_a = sourceData === null || sourceData === void 0 ? void 0 : sourceData.sourceCategory) === null || _a === void 0 ? void 0 : _a.id) || 0;
|
|
});
|
|
}
|