``` ~/go/bin/sourcemapper -output ddb -jsurl https://media.dndbeyond.com/character-app/static/js/main.90aa78c5.js ```
1321 lines
50 KiB
JavaScript
1321 lines
50 KiB
JavaScript
import { TypeScriptUtils } from '../../utils';
|
|
import { AbilityAccessors, AbilityDerivers } from '../Ability';
|
|
import { CharacterDerivers } from '../Character';
|
|
import { ClassAccessors } from '../Class';
|
|
import { AdditionalTypeEnum, CoreSimulators, MulticlassSpellSlotRoundingEnum } from '../Core';
|
|
import { DataOriginGenerators, DataOriginTypeEnum } from '../DataOrigin';
|
|
import { DiceAccessors, DiceSimulators } from '../Dice';
|
|
import { HelperUtils } from '../Helper';
|
|
import { ItemAccessors } from '../Item';
|
|
import { LimitedUseAccessors } from '../LimitedUse';
|
|
import { ModifierAccessors, ModifierDerivers, ModifierValidators } from '../Modifier';
|
|
import { RuleDataAccessors } from '../RuleData';
|
|
import { AdjustmentTypeEnum, ValueHacks, ValueUtils, ValueValidators } from '../Value';
|
|
import { getCastAtLevel, getCharacterLevel, getDataOrigin, getDataOriginType, getDefinitionAtHigherLevels, getDefinitionModifiers, getDefinitionName, getExpandedDataOriginRef, getLevel, getLimitedUse, getMappingEntityTypeId, getMappingId, getModifiers, getOverrideSaveDc, getRange, getRequiresSavingThrow, getRitual, getRitualCastingType, getScaleType, getSpellCastingAbilityId, getSpellListId, getUsesSpellSlot, isActive, isAlwaysPrepared, isAttack, isCantrip, isRitual, } from './accessors';
|
|
import { DB_STRING_SPELL_ELDRITCH_BLAST, RitualCastingTypeEnum, SPELL_CUSTOMIZATION_ADJUSTMENT_TYPES, SpellPrepareTypeEnum, SpellScaleTypeNameEnum, } from './constants';
|
|
import { getSpellFinalScaledDie } from './utils';
|
|
/**
|
|
* Calculates the Spellcasting Ability Modifier for the specified Proficiency Bonus and Ability Score Modifier,
|
|
* @param profBonus The proficiency bonus
|
|
* @param statModifier The modifier from the Ability Score
|
|
*/
|
|
export function deriveSpellAttackModifier(profBonus, statModifier) {
|
|
return profBonus + statModifier;
|
|
}
|
|
/**
|
|
* Determines if the specified spell is currently prepared
|
|
* @param spell The spell in question
|
|
* @param includeAlwaysPrepared When set to true, "Always Prepared" spells will be return true even if they are not prepared.
|
|
*/
|
|
export function deriveIsPrepared(spell, includeAlwaysPrepared = true) {
|
|
return spell.prepared || (includeAlwaysPrepared && isAlwaysPrepared(spell));
|
|
}
|
|
/**
|
|
* Determines if the specified spell can be cast as a ritual
|
|
* @param spell The spell in question
|
|
*/
|
|
export function deriveCanCastAsRitual(spell) {
|
|
return getRitualCastingType(spell) === RitualCastingTypeEnum.CAN_CAST_AS_RITUAL;
|
|
}
|
|
/**
|
|
* Determines if the spell can only be cast as a ritual
|
|
* @param spell The spell in question
|
|
*/
|
|
export function deriveMustCastAsRitual(spell) {
|
|
return getRitualCastingType(spell) === RitualCastingTypeEnum.MUST_CAST_AS_RITUAL;
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
*/
|
|
export function deriveIsRitual(spell) {
|
|
const dataOrigin = getDataOrigin(spell);
|
|
const dataOriginType = getDataOriginType(spell);
|
|
if (dataOrigin) {
|
|
switch (dataOriginType) {
|
|
case DataOriginTypeEnum.CLASS:
|
|
case DataOriginTypeEnum.CLASS_FEATURE:
|
|
return getRitual(spell) && deriveIsCastedByRitualSpellcaster(spell);
|
|
case DataOriginTypeEnum.RACE:
|
|
case DataOriginTypeEnum.FEAT:
|
|
case DataOriginTypeEnum.ITEM:
|
|
return deriveCanCastAsRitual(spell) || deriveMustCastAsRitual(spell);
|
|
case DataOriginTypeEnum.SIMULATED:
|
|
return getRitual(spell);
|
|
default:
|
|
// not implemented
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
*/
|
|
export function deriveIsCastAsRitual(spell) {
|
|
if (!getDataOrigin(spell) || !isRitual(spell)) {
|
|
return false;
|
|
}
|
|
// if we have an override to only cast, then don't let them cast with spell slot at all
|
|
if (deriveMustCastAsRitual(spell)) {
|
|
return true;
|
|
}
|
|
// otherwise if the spell isn't active, but we got to this point, it could only be cast as ritual
|
|
return !isActive(spell);
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
*/
|
|
export function deriveExpandedContextIds(spell) {
|
|
let contextId = null;
|
|
let contextTypeId = null;
|
|
const expandedDataOriginRef = getExpandedDataOriginRef(spell);
|
|
if (expandedDataOriginRef) {
|
|
const dataOrigin = getDataOrigin(spell);
|
|
const dataOriginType = getDataOriginType(spell);
|
|
switch (dataOriginType) {
|
|
case DataOriginTypeEnum.CLASS:
|
|
const charClass = dataOrigin.primary;
|
|
contextId = ClassAccessors.getMappingId(charClass);
|
|
contextTypeId = ClassAccessors.getMappingEntityTypeId(charClass);
|
|
break;
|
|
default:
|
|
// not implemented
|
|
}
|
|
}
|
|
return [contextId, contextTypeId];
|
|
}
|
|
/**
|
|
* TODO - move to utils
|
|
* @param level
|
|
* @param slotLevels
|
|
*/
|
|
export function getCastLevelAvailableCount(level, slotLevels) {
|
|
const slotLevel = slotLevels.find((slotLevel) => slotLevel.level === level);
|
|
if (!slotLevel) {
|
|
return null;
|
|
}
|
|
return slotLevel.available - slotLevel.used;
|
|
}
|
|
/**
|
|
* TODO - move to utils
|
|
* @param spell
|
|
* @param scaledAmount
|
|
*/
|
|
export function getConsumedLimitedUse(spell, scaledAmount) {
|
|
const limitedUse = getLimitedUse(spell);
|
|
if (limitedUse === null) {
|
|
return 0;
|
|
}
|
|
const minConsumed = LimitedUseAccessors.getMinNumberConsumed(limitedUse);
|
|
if (getDataOriginType(spell) === DataOriginTypeEnum.ITEM) {
|
|
return minConsumed + scaledAmount;
|
|
}
|
|
return minConsumed;
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
*/
|
|
export function deriveStartingCastLevel(spell) {
|
|
if (isCantrip(spell)) {
|
|
return getLevel(spell);
|
|
}
|
|
const castAtLevel = getCastAtLevel(spell);
|
|
let startingCastLevel = getLevel(spell);
|
|
if (castAtLevel !== null) {
|
|
startingCastLevel = castAtLevel;
|
|
}
|
|
return startingCastLevel;
|
|
}
|
|
/**
|
|
* TODO - move to utils
|
|
* @param spell
|
|
* @param spellCasterInfo
|
|
* @param ruleData
|
|
* @param initialCastLevel
|
|
*/
|
|
export function getMinCastLevel(spell, spellCasterInfo, ruleData, initialCastLevel = null) {
|
|
const { startLevel, endLevel } = getCastLevelRange(spell, spellCasterInfo, ruleData);
|
|
let castLevel = deriveStartingCastLevel(spell);
|
|
if (initialCastLevel !== null) {
|
|
castLevel = initialCastLevel;
|
|
}
|
|
return Math.max(castLevel, startLevel);
|
|
}
|
|
/**
|
|
* TODO - move to utils
|
|
* @param spell
|
|
* @param spellCasterInfo
|
|
* @param ruleData
|
|
* @param initialCastLevel
|
|
*/
|
|
export function getCastableLevels(spell, spellCasterInfo, ruleData, initialCastLevel = null) {
|
|
const { startLevel, endLevel } = getCastLevelRange(spell, spellCasterInfo, ruleData);
|
|
const usesSpellSlot = getUsesSpellSlot(spell);
|
|
const castAtLevel = getCastAtLevel(spell);
|
|
const castableLevels = [];
|
|
for (let i = startLevel; i <= endLevel; i++) {
|
|
if (initialCastLevel !== null && initialCastLevel === i) {
|
|
castableLevels.push(i);
|
|
}
|
|
else if (castAtLevel !== null && castAtLevel === i) {
|
|
castableLevels.push(i);
|
|
}
|
|
else if (getDataOriginType(spell) === DataOriginTypeEnum.ITEM) {
|
|
castableLevels.push(i);
|
|
}
|
|
else if (usesSpellSlot) {
|
|
if (spellCasterInfo.availableSpellLevels.includes(i) ||
|
|
spellCasterInfo.availablePactMagicLevels.includes(i)) {
|
|
castableLevels.push(i);
|
|
}
|
|
}
|
|
}
|
|
return castableLevels;
|
|
}
|
|
/**
|
|
* TODO - move to utils
|
|
* @param spell
|
|
* @param spellCasterInfo
|
|
* @param ruleData
|
|
*/
|
|
export function getCastLevelRange(spell, spellCasterInfo, ruleData) {
|
|
let startLevel = getLevel(spell);
|
|
let endLevel = getLevel(spell);
|
|
const castAtLevel = getCastAtLevel(spell);
|
|
const limitedUse = getLimitedUse(spell);
|
|
if (isCantrip(spell)) {
|
|
return {
|
|
startLevel,
|
|
endLevel,
|
|
};
|
|
}
|
|
if (getUsesSpellSlot(spell)) {
|
|
const availableLevels = [
|
|
...spellCasterInfo.availableSpellLevels,
|
|
...spellCasterInfo.availablePactMagicLevels,
|
|
].filter((level) => level >= getLevel(spell));
|
|
startLevel = Math.max(getLevel(spell), Math.min(...availableLevels));
|
|
endLevel = Math.max(getLevel(spell), ...availableLevels);
|
|
if (limitedUse && getDataOriginType(spell) === DataOriginTypeEnum.CLASS_FEATURE) {
|
|
const dataOrigin = getDataOrigin(spell);
|
|
const charClass = dataOrigin.parent;
|
|
if (ClassAccessors.isPactMagicActive(charClass)) {
|
|
startLevel = Math.min(...spellCasterInfo.availablePactMagicLevels);
|
|
endLevel = Math.max(...spellCasterInfo.availablePactMagicLevels);
|
|
}
|
|
else {
|
|
startLevel = Math.max(getLevel(spell), Math.min(...spellCasterInfo.availableSpellLevels));
|
|
endLevel = Math.max(getLevel(spell), ...spellCasterInfo.availableSpellLevels);
|
|
}
|
|
}
|
|
}
|
|
if (getDataOriginType(spell) !== DataOriginTypeEnum.CLASS) {
|
|
if (castAtLevel === null) {
|
|
switch (getDataOriginType(spell)) {
|
|
case DataOriginTypeEnum.ITEM:
|
|
let itemSpellLevelDiff = 0;
|
|
if (limitedUse) {
|
|
const minNumberConsumed = LimitedUseAccessors.getMinNumberConsumed(limitedUse);
|
|
let maxNumberConsumed = LimitedUseAccessors.getMaxNumberConsumed(limitedUse);
|
|
if (maxNumberConsumed === null) {
|
|
maxNumberConsumed = minNumberConsumed;
|
|
}
|
|
itemSpellLevelDiff = maxNumberConsumed - minNumberConsumed;
|
|
}
|
|
endLevel = startLevel + itemSpellLevelDiff;
|
|
break;
|
|
default:
|
|
// not implemented
|
|
}
|
|
}
|
|
else {
|
|
startLevel = castAtLevel;
|
|
endLevel = castAtLevel;
|
|
}
|
|
}
|
|
const maxSpellLevel = RuleDataAccessors.getMaxSpellLevel(ruleData);
|
|
return {
|
|
startLevel: HelperUtils.clampInt(startLevel, 1, maxSpellLevel),
|
|
endLevel: HelperUtils.clampInt(endLevel, 1, maxSpellLevel),
|
|
};
|
|
}
|
|
/**
|
|
* TODO - move to validators
|
|
* @param spell
|
|
* @param scaleType
|
|
* @param higherLevelsItems
|
|
* @param castLevel
|
|
*/
|
|
export function isSpellScaledByCastLevel(spell, scaleType, higherLevelsItems, castLevel) {
|
|
if (scaleType === null) {
|
|
return false;
|
|
}
|
|
if (scaleType === SpellScaleTypeNameEnum.CHARACTER_LEVEL) {
|
|
return false;
|
|
}
|
|
if (getCastAtLevel(spell) === castLevel) {
|
|
return false;
|
|
}
|
|
if (!higherLevelsItems.length) {
|
|
return false;
|
|
}
|
|
return !!spellLevelFilter(higherLevelsItems, castLevel);
|
|
}
|
|
/**
|
|
* TODO - move to utils
|
|
* @param spell
|
|
* @param scaleType
|
|
* @param higherLevelsItems
|
|
* @param characterLevel //deprecated and will be removed, DCP-1412
|
|
* @param castLevel
|
|
*/
|
|
export function getSpellScaledAtHigher(spell, scaleType, higherLevelsItems, characterLevel, castLevel) {
|
|
if (scaleType === null) {
|
|
return null;
|
|
}
|
|
const castAtLevel = getCastAtLevel(spell);
|
|
if (scaleType === SpellScaleTypeNameEnum.CHARACTER_LEVEL) {
|
|
return characterLevelFilter(higherLevelsItems, getCharacterLevel(spell));
|
|
}
|
|
else if (scaleType === SpellScaleTypeNameEnum.SPELL_SCALE || scaleType === SpellScaleTypeNameEnum.SPELL_LEVEL) {
|
|
return spellLevelFilter(higherLevelsItems, castAtLevel === null ? castLevel : castAtLevel);
|
|
}
|
|
return null;
|
|
}
|
|
/**
|
|
* TODO - move to utils
|
|
* @param spell
|
|
* @param characterLevel
|
|
*/
|
|
export function getScaledDamage(spell, characterLevel = null) {
|
|
const damageDice = [];
|
|
const castLevel = deriveStartingCastLevel(spell);
|
|
const damageModifiers = getModifiers(spell).filter((modifier) => ModifierValidators.isSpellDamageModifier(modifier));
|
|
damageModifiers.map((modifier) => {
|
|
const { atHigherLevels } = modifier;
|
|
if (atHigherLevels !== null && atHigherLevels.points !== null) {
|
|
const atHigherDamage = getSpellScaledAtHigher(spell, getScaleType(spell), atHigherLevels.points, characterLevel, castLevel);
|
|
const scaledDamageDie = getSpellFinalScaledDie(spell, modifier, atHigherDamage);
|
|
const type = ModifierAccessors.getFriendlySubtypeName(modifier);
|
|
if (scaledDamageDie !== null && type !== null) {
|
|
damageDice.push({
|
|
dice: scaledDamageDie,
|
|
type,
|
|
restriction: ModifierAccessors.getRestriction(modifier),
|
|
});
|
|
}
|
|
}
|
|
});
|
|
return damageDice;
|
|
}
|
|
/**
|
|
* TODO - move to validators
|
|
* @param spell
|
|
* @param level
|
|
* @param spellCasterInfo
|
|
*/
|
|
export function isSpellAvailableAtLevel(spell, level, spellCasterInfo) {
|
|
if (isCantrip(spell)) {
|
|
return true;
|
|
}
|
|
if (spellCasterInfo.availableSpellLevels.includes(level) ||
|
|
spellCasterInfo.availablePactMagicLevels.includes(level)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* NOTE: leaving for now, this will go away eventually once we generate higher level locally
|
|
* @param higherLevelsItems
|
|
* @param characterLevel
|
|
*/
|
|
function characterLevelFilter(higherLevelsItems, characterLevel) {
|
|
if (characterLevel === null) {
|
|
return null;
|
|
}
|
|
if (!higherLevelsItems.length) {
|
|
return null;
|
|
}
|
|
return HelperUtils.getLast(higherLevelsItems.filter((item) => item.level !== null && item.level <= characterLevel), ['level']);
|
|
}
|
|
/**
|
|
* NOTE: leaving for now, this will go away eventually once we generate higher level locally
|
|
* @param higherLevelsItems
|
|
* @param castLevel
|
|
*/
|
|
function spellLevelFilter(higherLevelsItems, castLevel) {
|
|
if (castLevel === null) {
|
|
return null;
|
|
}
|
|
if (!higherLevelsItems.length) {
|
|
return null;
|
|
}
|
|
return HelperUtils.getLast(higherLevelsItems.filter((item) => item.level === null || item.level <= castLevel), ['level']);
|
|
}
|
|
/**
|
|
*
|
|
* @param prepareType
|
|
* @param level
|
|
* @param modifier
|
|
*/
|
|
export function deriveSpellPrepareMax(prepareType, level, modifier) {
|
|
switch (prepareType) {
|
|
case SpellPrepareTypeEnum.LEVEL:
|
|
return Math.max(modifier + level, 1);
|
|
case SpellPrepareTypeEnum.HALF_LEVEL:
|
|
return Math.max(modifier + Math.floor(level / 2), 1);
|
|
default:
|
|
// not implemented
|
|
}
|
|
return null;
|
|
}
|
|
/**
|
|
*
|
|
* @param charClass
|
|
* @param abilityId
|
|
* @param ruleData
|
|
* @param overrideIsRitualSpellCaster
|
|
*/
|
|
export function deriveClassSpellRules(charClass, abilityId, ruleData, overrideIsRitualSpellCaster = false) {
|
|
var _a;
|
|
const spellcastingAbilityStatId = abilityId;
|
|
let spellcastingAbilityStat = null;
|
|
if (spellcastingAbilityStatId) {
|
|
spellcastingAbilityStat = ruleData.statsLookup[spellcastingAbilityStatId].key;
|
|
}
|
|
const configSpellRules = ClassAccessors.getSpellConfiguration(charClass);
|
|
let filteredLevelCantripsKnownMaxes = [];
|
|
if (configSpellRules && configSpellRules.levelCantripsKnownMaxes !== null) {
|
|
filteredLevelCantripsKnownMaxes = configSpellRules.levelCantripsKnownMaxes.filter((max) => max !== 0);
|
|
}
|
|
const spellPrepareType = ClassAccessors.getSpellPrepareType(charClass);
|
|
const knowsAllSpells = ClassAccessors.getKnowsAllSpells(charClass);
|
|
const hasSpellPrepareMax = spellPrepareType !== null;
|
|
const isKnownSpellcaster = !hasSpellPrepareMax;
|
|
const isSpellbookSpellcaster = hasSpellPrepareMax && !knowsAllSpells;
|
|
const isPreparedSpellcaster = hasSpellPrepareMax && !isSpellbookSpellcaster;
|
|
let isRitualSpellCaster = false;
|
|
if (overrideIsRitualSpellCaster) {
|
|
isRitualSpellCaster = true;
|
|
}
|
|
else {
|
|
isRitualSpellCaster = configSpellRules === null ? false : configSpellRules.isRitualSpellCaster;
|
|
}
|
|
let levelCantripsKnownMaxes = null;
|
|
if (filteredLevelCantripsKnownMaxes.length) {
|
|
levelCantripsKnownMaxes = configSpellRules === null ? null : configSpellRules.levelCantripsKnownMaxes;
|
|
}
|
|
let levelSpellKnownMaxes = [];
|
|
if (configSpellRules !== null && configSpellRules.levelSpellKnownMaxes) {
|
|
levelSpellKnownMaxes = configSpellRules.levelSpellKnownMaxes.filter(TypeScriptUtils.isNotNullOrUndefined);
|
|
}
|
|
let levelPreparedSpellMaxes = [];
|
|
if (configSpellRules !== null && configSpellRules.levelPreparedSpellMaxes) {
|
|
levelPreparedSpellMaxes = configSpellRules.levelPreparedSpellMaxes.filter(TypeScriptUtils.isNotNullOrUndefined);
|
|
}
|
|
let levelSpellSlots = [];
|
|
if (configSpellRules !== null) {
|
|
levelSpellSlots = (_a = configSpellRules.levelSpellSlots) !== null && _a !== void 0 ? _a : [
|
|
[],
|
|
[],
|
|
[],
|
|
[],
|
|
[],
|
|
[],
|
|
[],
|
|
[],
|
|
[],
|
|
[],
|
|
[],
|
|
[],
|
|
[],
|
|
[],
|
|
[],
|
|
[],
|
|
[],
|
|
[],
|
|
[],
|
|
[],
|
|
];
|
|
}
|
|
return {
|
|
spellcastingAbilityStat,
|
|
spellcastingAbilityStatId,
|
|
knowsAllSpells,
|
|
levelCantripsKnownMaxes,
|
|
levelSpellKnownMaxes,
|
|
levelPreparedSpellMaxes,
|
|
levelSpellSlots,
|
|
isRitualSpellCaster,
|
|
isKnownSpellcaster,
|
|
isSpellbookSpellcaster,
|
|
isPreparedSpellcaster,
|
|
};
|
|
}
|
|
/**
|
|
*
|
|
* @param pactMagicClasses
|
|
* @param ruleData
|
|
*/
|
|
export function deriveClassPactMagicSlots(pactMagicClasses, ruleData) {
|
|
let pactMagicSlotsAvailable = [];
|
|
if (pactMagicClasses.length > 1) {
|
|
pactMagicSlotsAvailable = deriveMulticlassSlots(pactMagicClasses, ruleData, RuleDataAccessors.getPactMagicMultiClassSpellSlots(ruleData));
|
|
}
|
|
else if (pactMagicClasses.length === 1) {
|
|
pactMagicSlotsAvailable = deriveClassLevelSpellSlots(pactMagicClasses[0]);
|
|
}
|
|
return pactMagicSlotsAvailable;
|
|
}
|
|
/**
|
|
*
|
|
* @param spellcastingClasses
|
|
* @param ruleData
|
|
*/
|
|
export function deriveClassSpellSlots(spellcastingClasses, ruleData) {
|
|
let spellSlotsAvailable = [];
|
|
if (spellcastingClasses.length > 1) {
|
|
spellSlotsAvailable = deriveMulticlassSlots(spellcastingClasses, ruleData, RuleDataAccessors.getMultiClassSpellSlots(ruleData));
|
|
}
|
|
else if (spellcastingClasses.length === 1) {
|
|
spellSlotsAvailable = deriveClassLevelSpellSlots(spellcastingClasses[0]);
|
|
}
|
|
return spellSlotsAvailable;
|
|
}
|
|
/**
|
|
*
|
|
* @param charClass
|
|
*/
|
|
export function deriveClassLevelSpellSlots(charClass) {
|
|
const classLevel = ClassAccessors.getLevel(charClass);
|
|
const configSpellRules = ClassAccessors.getSpellConfiguration(charClass);
|
|
return configSpellRules && configSpellRules.levelSpellSlots ? configSpellRules.levelSpellSlots[classLevel] : [];
|
|
}
|
|
/**
|
|
*
|
|
* @param charClasses
|
|
* @param ruleData
|
|
* @param spellSlotsData
|
|
*/
|
|
export function deriveMulticlassSlots(charClasses, ruleData, spellSlotsData) {
|
|
if (spellSlotsData === null) {
|
|
return [];
|
|
}
|
|
const combinedLevel = charClasses.reduce((acc, charClass) => {
|
|
const configSpellRules = ClassAccessors.getSpellConfiguration(charClass);
|
|
if (configSpellRules !== null && configSpellRules.multiClassSpellSlotDivisor !== null) {
|
|
const levelContribution = ClassAccessors.getLevel(charClass) / configSpellRules.multiClassSpellSlotDivisor;
|
|
if (configSpellRules.multiClassSpellSlotRounding === MulticlassSpellSlotRoundingEnum.UP) {
|
|
acc += Math.ceil(levelContribution);
|
|
}
|
|
else {
|
|
acc += Math.floor(levelContribution);
|
|
}
|
|
}
|
|
return acc;
|
|
}, 0);
|
|
return spellSlotsData[combinedLevel];
|
|
}
|
|
/**
|
|
*
|
|
* @param knownCantripsMax
|
|
* @param activeCantripsCount
|
|
*/
|
|
export function deriveIsCantripsKnownMaxed(knownCantripsMax, activeCantripsCount) {
|
|
let cantripsKnownMaxed = false;
|
|
if (knownCantripsMax === null) {
|
|
cantripsKnownMaxed = true;
|
|
}
|
|
else {
|
|
cantripsKnownMaxed = activeCantripsCount >= knownCantripsMax;
|
|
}
|
|
return cantripsKnownMaxed;
|
|
}
|
|
/**
|
|
*
|
|
* @param knownSpellsMax
|
|
* @param knownSpellCount
|
|
* @param knowAllClassSpells
|
|
*/
|
|
export function deriveIsSpellsKnownMaxed(knownSpellsMax, knownSpellCount, knowAllClassSpells) {
|
|
let spellsKnownMaxed = false;
|
|
if (knowAllClassSpells) {
|
|
spellsKnownMaxed = true;
|
|
}
|
|
else if (knownSpellsMax === null) {
|
|
spellsKnownMaxed = false;
|
|
}
|
|
else {
|
|
spellsKnownMaxed = knownSpellCount >= knownSpellsMax;
|
|
}
|
|
return spellsKnownMaxed;
|
|
}
|
|
/**
|
|
*
|
|
* @param spellSlots
|
|
* @param pactMagicSlots
|
|
* @param ruleData
|
|
*/
|
|
export function deriveCombinedSpellSlotsInfo(spellSlots, pactMagicSlots, ruleData) {
|
|
const maxSpellLevel = RuleDataAccessors.getMaxSpellLevel(ruleData);
|
|
const combinedSpellSlots = [];
|
|
for (let i = 1; i <= maxSpellLevel; i++) {
|
|
const pactMagicSlotLevel = pactMagicSlots.find((slotLevel) => slotLevel.level === i);
|
|
const spellSlotLevel = spellSlots.find((slotLevel) => slotLevel.level === i);
|
|
const pactMagicAvailable = pactMagicSlotLevel ? pactMagicSlotLevel.available : 0;
|
|
const pactMagicUsed = pactMagicSlotLevel ? pactMagicSlotLevel.used : 0;
|
|
const spellAvailable = spellSlotLevel ? spellSlotLevel.available : 0;
|
|
const spellUsed = spellSlotLevel ? spellSlotLevel.used : 0;
|
|
combinedSpellSlots.push({
|
|
level: i,
|
|
available: pactMagicAvailable + spellAvailable,
|
|
used: pactMagicUsed + spellUsed,
|
|
remaining: pactMagicAvailable + spellAvailable - (pactMagicUsed + spellUsed),
|
|
});
|
|
}
|
|
return combinedSpellSlots;
|
|
}
|
|
/**
|
|
*
|
|
* @param spellcastingClasses
|
|
* @param spellSlotsData
|
|
* @param ruleData
|
|
*/
|
|
export function deriveSpellSlotsInfo(spellcastingClasses, spellSlotsData, ruleData) {
|
|
const spellSlotsAvailable = deriveClassSpellSlots(spellcastingClasses, ruleData);
|
|
return spellSlotsAvailable
|
|
.map((availableCount, idx) => {
|
|
if (availableCount === 0) {
|
|
return null;
|
|
}
|
|
return {
|
|
available: availableCount,
|
|
used: spellSlotsData.length ? spellSlotsData[idx].used : 0,
|
|
level: idx + 1,
|
|
};
|
|
})
|
|
.filter(TypeScriptUtils.isNotNullOrUndefined);
|
|
}
|
|
/**
|
|
*
|
|
* @param pactMagicClasses
|
|
* @param pactMagicSlotsData
|
|
* @param ruleData
|
|
*/
|
|
export function derivePactMagicSlotsInfo(pactMagicClasses, pactMagicSlotsData, ruleData) {
|
|
const pactMagicSlotsAvailable = deriveClassPactMagicSlots(pactMagicClasses, ruleData);
|
|
return pactMagicSlotsAvailable
|
|
.map((availableCount, idx) => {
|
|
if (availableCount === 0) {
|
|
return null;
|
|
}
|
|
return {
|
|
available: availableCount,
|
|
used: pactMagicSlotsData.length ? pactMagicSlotsData[idx].used : 0,
|
|
level: idx + 1,
|
|
};
|
|
})
|
|
.filter(TypeScriptUtils.isNotNullOrUndefined)
|
|
.reduce((acc, slot) => {
|
|
if (slot.available !== 0) {
|
|
acc.push(slot);
|
|
}
|
|
return acc;
|
|
}, []);
|
|
}
|
|
/**
|
|
* TODO refactor this to supply base toHit and bonus toHit so that it doesn't have to know if bonuses are added in already
|
|
* @param spell
|
|
* @param modifiers
|
|
* @param abilityLookup
|
|
* @param profBonus
|
|
* @param fallbackToHit
|
|
* @param valueLookup
|
|
*/
|
|
export function deriveSpellToHit(spell, modifiers, abilityLookup, profBonus, fallbackToHit, valueLookup) {
|
|
let toHit = null;
|
|
if (isAttack(spell)) {
|
|
const toHitOverride = deriveCustomValue(spell, AdjustmentTypeEnum.TO_HIT_OVERRIDE, null, valueLookup);
|
|
let toHitBonus = deriveCustomValue(spell, AdjustmentTypeEnum.TO_HIT_BONUS, 0, valueLookup);
|
|
toHitBonus = toHitBonus === null ? 0 : toHitBonus;
|
|
if (toHitOverride !== null) {
|
|
return toHitOverride;
|
|
}
|
|
let baseToHit = 0;
|
|
const spellCastingAbilityId = getSpellCastingAbilityId(spell);
|
|
if (spellCastingAbilityId === null) {
|
|
baseToHit = fallbackToHit;
|
|
}
|
|
else {
|
|
baseToHit = deriveSpellAttackModifier(profBonus, AbilityDerivers.deriveStatModifier(spellCastingAbilityId, abilityLookup));
|
|
}
|
|
const dataOriginType = getDataOrigin(spell) ? getDataOriginType(spell) : null;
|
|
if (dataOriginType !== DataOriginTypeEnum.CLASS && dataOriginType !== DataOriginTypeEnum.CLASS_FEATURE) {
|
|
// find out about bonus toHit for current spell
|
|
const bonusToHitModifiers = modifiers.filter((modifier) => ModifierValidators.isValidBonusSpellAttackModifier(modifier, spell));
|
|
baseToHit += ModifierDerivers.sumModifiers(bonusToHitModifiers, abilityLookup);
|
|
}
|
|
toHit = baseToHit + toHitBonus;
|
|
}
|
|
return toHit;
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
* @param abilityLookup
|
|
* @param fallbackModifier
|
|
*/
|
|
export function deriveSpellSpellcastingModifier(spell, abilityLookup, fallbackModifier) {
|
|
const spellCastingAbilityId = getSpellCastingAbilityId(spell);
|
|
if (spellCastingAbilityId !== null) {
|
|
const ability = abilityLookup[spellCastingAbilityId];
|
|
const abilityModifier = AbilityAccessors.getModifier(ability);
|
|
if (ability && abilityModifier !== null) {
|
|
return abilityModifier;
|
|
}
|
|
return fallbackModifier;
|
|
}
|
|
return fallbackModifier;
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
*/
|
|
export function deriveIsCastedBySpellbookSpellcaster(spell) {
|
|
const dataOrigin = getDataOrigin(spell);
|
|
return !!(dataOrigin.primary !== null && ClassAccessors.isSpellbookSpellcaster(dataOrigin.primary));
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
*/
|
|
export function deriveIsCastedByRitualSpellcaster(spell) {
|
|
const dataOrigin = getDataOrigin(spell);
|
|
const dataOriginType = getDataOriginType(spell);
|
|
switch (dataOriginType) {
|
|
case DataOriginTypeEnum.CLASS:
|
|
return !!(dataOrigin.primary !== null &&
|
|
ClassAccessors.isRitualSpellCaster(dataOrigin.primary));
|
|
case DataOriginTypeEnum.CLASS_FEATURE:
|
|
return !!(dataOrigin.parent !== null &&
|
|
ClassAccessors.isRitualSpellCaster(dataOrigin.parent));
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
*/
|
|
export function deriveIsActive(spell) {
|
|
switch (getDataOriginType(spell)) {
|
|
case DataOriginTypeEnum.CLASS:
|
|
const dataOrigin = getDataOrigin(spell);
|
|
const spellIsPepared = deriveIsPrepared(spell);
|
|
const charClass = dataOrigin.primary;
|
|
return (isCantrip(spell) ||
|
|
(ClassAccessors.isPreparedSpellcaster(charClass) && spellIsPepared) ||
|
|
ClassAccessors.isKnownSpellcaster(charClass) ||
|
|
(ClassAccessors.isSpellbookSpellcaster(charClass) && spellIsPepared));
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
* @param modifiers
|
|
* @param abilityLookup
|
|
* @param fixedDamageBonus
|
|
* @param valueLookup
|
|
*/
|
|
export function deriveSpellBonusFixedDamage(spell, modifiers, abilityLookup, fixedDamageBonus, valueLookup) {
|
|
let adjustmentDamageBonus = deriveCustomValue(spell, AdjustmentTypeEnum.FIXED_VALUE_BONUS, 0, valueLookup);
|
|
adjustmentDamageBonus = adjustmentDamageBonus === null ? 0 : adjustmentDamageBonus;
|
|
const damageSpellAttackModifiers = modifiers.filter((modifier) => ModifierValidators.isValidDamageSpellAttackModifier(modifier, spell));
|
|
const damageSpellAttackModifierTotal = ModifierDerivers.sumModifiers(damageSpellAttackModifiers, abilityLookup);
|
|
let extraDamageBonus = 0;
|
|
if (getDefinitionName(spell) === DB_STRING_SPELL_ELDRITCH_BLAST) {
|
|
const eldritchBlastBonusDamageModifiers = modifiers.filter((modifier) => ModifierValidators.isEldritchBlastBonusDamageModifier(modifier));
|
|
const edlritchBlastBonusDamageTotal = ModifierDerivers.sumModifiers(eldritchBlastBonusDamageModifiers, abilityLookup);
|
|
extraDamageBonus += edlritchBlastBonusDamageTotal;
|
|
}
|
|
return damageSpellAttackModifierTotal + fixedDamageBonus + adjustmentDamageBonus + extraDamageBonus;
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
* @param modifiers
|
|
* @param proficiencyBonus
|
|
* @param abilityLookup
|
|
* @param fallbackBaseSaveDc
|
|
* @param valueLookup
|
|
*/
|
|
export function deriveSpellAttackSaveValue(spell, modifiers, proficiencyBonus, abilityLookup, fallbackBaseSaveDc, valueLookup) {
|
|
if (!getRequiresSavingThrow(spell)) {
|
|
return null;
|
|
}
|
|
const saveDcOverride = deriveCustomValue(spell, AdjustmentTypeEnum.SAVE_DC_OVERRIDE, null, valueLookup);
|
|
let saveDcBonus = deriveCustomValue(spell, AdjustmentTypeEnum.SAVE_DC_BONUS, 0, valueLookup);
|
|
saveDcBonus = saveDcBonus === null ? 0 : saveDcBonus;
|
|
if (saveDcOverride !== null) {
|
|
return saveDcOverride;
|
|
}
|
|
const overrideSaveDc = getOverrideSaveDc(spell);
|
|
if (overrideSaveDc !== null) {
|
|
return overrideSaveDc;
|
|
}
|
|
let saveDc = 0;
|
|
const spellCastingAbilityId = getSpellCastingAbilityId(spell);
|
|
if (spellCastingAbilityId === null) {
|
|
saveDc = fallbackBaseSaveDc + saveDcBonus;
|
|
}
|
|
else {
|
|
const abilityModifier = AbilityDerivers.deriveStatModifier(spellCastingAbilityId, abilityLookup);
|
|
saveDc = CharacterDerivers.deriveAttackSaveValue(proficiencyBonus, abilityModifier) + saveDcBonus;
|
|
}
|
|
switch (getDataOriginType(spell)) {
|
|
case DataOriginTypeEnum.CLASS:
|
|
case DataOriginTypeEnum.CLASS_FEATURE:
|
|
case DataOriginTypeEnum.ITEM:
|
|
break;
|
|
default:
|
|
const bonusToHitModifiers = modifiers.filter((modifier) => ModifierValidators.isBonusSpellSaveDc(modifier));
|
|
saveDc += ModifierDerivers.sumModifiers(bonusToHitModifiers, abilityLookup);
|
|
}
|
|
return saveDc;
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
* @param modifiers
|
|
* @param abilityLookup
|
|
*/
|
|
export function deriveSpellRange(spell, modifiers, abilityLookup) {
|
|
const range = getRange(spell);
|
|
let extraRangeBonus = 0;
|
|
if (getDefinitionName(spell) === DB_STRING_SPELL_ELDRITCH_BLAST) {
|
|
const eldritchBlastBonusRangeModifiers = modifiers.filter((modifier) => ModifierValidators.isEldritchBlastBonusRangeModifier(modifier));
|
|
const edlritchBlastBonusRangeTotal = ModifierDerivers.sumModifiers(eldritchBlastBonusRangeModifiers, abilityLookup);
|
|
extraRangeBonus += edlritchBlastBonusRangeTotal;
|
|
}
|
|
let spellAttackRangeMultiplierTotal = 1;
|
|
if (isAttack(spell)) {
|
|
const spellAttackRangeMultiplierModifiers = modifiers.filter((modifier) => ModifierValidators.isBonusSpellAttackRangeMultiplierModifier(modifier));
|
|
spellAttackRangeMultiplierTotal = Math.max(1, ModifierDerivers.sumModifiers(spellAttackRangeMultiplierModifiers, abilityLookup, 1));
|
|
}
|
|
return (((range !== null && range.rangeValue ? range.rangeValue : 0) + extraRangeBonus) *
|
|
spellAttackRangeMultiplierTotal);
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
* @param valueLookup
|
|
*/
|
|
export function deriveIsCustomized(spell, valueLookup) {
|
|
const [contextId, contextTypeId] = deriveExpandedContextIds(spell);
|
|
return ValueValidators.validateHasCustomization(SPELL_CUSTOMIZATION_ADJUSTMENT_TYPES, valueLookup, ValueHacks.hack__toString(getMappingId(spell)), ValueHacks.hack__toString(getMappingEntityTypeId(spell)), ValueHacks.hack__toString(contextId), ValueHacks.hack__toString(contextTypeId));
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
* @param spellListDataOriginLookup
|
|
*/
|
|
export function deriveExpandedDataOriginRef(spell, spellListDataOriginLookup) {
|
|
const spellListId = getSpellListId(spell);
|
|
if (spellListId !== null) {
|
|
const dataOriginRef = HelperUtils.lookupDataOrFallback(spellListDataOriginLookup, spellListId);
|
|
if (dataOriginRef === null) {
|
|
return DataOriginGenerators.generateDataOriginRef(DataOriginTypeEnum.UNKNOWN, '');
|
|
}
|
|
return dataOriginRef;
|
|
}
|
|
return null;
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
* @param valueTypeId
|
|
* @param fallback
|
|
* @param valueLookup
|
|
*/
|
|
function deriveCustomValue(spell, valueTypeId, fallback, valueLookup) {
|
|
if (valueLookup) {
|
|
const [contextId, contextTypeId] = deriveExpandedContextIds(spell);
|
|
return ValueUtils.getKeyValue(valueLookup, valueTypeId, ValueHacks.hack__toString(getMappingId(spell)), ValueHacks.hack__toString(getMappingEntityTypeId(spell)), fallback, ValueHacks.hack__toString(contextId), ValueHacks.hack__toString(contextTypeId));
|
|
}
|
|
return fallback;
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
* @param valueLookup
|
|
*/
|
|
export function deriveNotes(spell, valueLookup) {
|
|
return deriveCustomValue(spell, AdjustmentTypeEnum.NOTES, null, valueLookup);
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
* @param valueLookup
|
|
*/
|
|
export function deriveName(spell, valueLookup) {
|
|
const definitionName = getDefinitionName(spell);
|
|
let fallbackName = '';
|
|
if (definitionName !== null) {
|
|
fallbackName = definitionName;
|
|
}
|
|
return deriveCustomValue(spell, AdjustmentTypeEnum.NAME_OVERRIDE, fallbackName, valueLookup);
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
* @param modifiers
|
|
* @param abilityLookup
|
|
*/
|
|
export function deriveRange(spell, modifiers, abilityLookup) {
|
|
let range = null;
|
|
if (spell.range !== null) {
|
|
range = Object.assign(Object.assign({}, spell.range), { rangeValue: deriveSpellRange(spell, modifiers, abilityLookup) });
|
|
}
|
|
return range;
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
* @param classInfo
|
|
*/
|
|
export function deriveCanRemove(spell, classInfo) {
|
|
let canRemove = false;
|
|
if (getLevel(spell) === 0) {
|
|
if (classInfo.knownCantripsMax !== null) {
|
|
canRemove = true;
|
|
}
|
|
}
|
|
else if (!classInfo.knowsAllSpells) {
|
|
canRemove = true;
|
|
}
|
|
return canRemove;
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
* @param classInfo
|
|
*/
|
|
export function deriveCanPrepare(spell, classInfo) {
|
|
let canPrepare = !!classInfo.prepareMax;
|
|
if (getLevel(spell) === 0 || isAlwaysPrepared(spell)) {
|
|
canPrepare = false;
|
|
}
|
|
return canPrepare;
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
* @param valueLookup
|
|
*/
|
|
export function deriveIsDisplayAsAttack(spell, valueLookup) {
|
|
return deriveCustomValue(spell, AdjustmentTypeEnum.DISPLAY_AS_ATTACK, null, valueLookup);
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
* @param characterLevel
|
|
*/
|
|
export function deriveCharacterLevel(spell, characterLevel) {
|
|
var _a;
|
|
if (getScaleType(spell) === SpellScaleTypeNameEnum.CHARACTER_LEVEL) {
|
|
return (_a = getCastAtLevel(spell)) !== null && _a !== void 0 ? _a : characterLevel;
|
|
}
|
|
return characterLevel;
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
*/
|
|
export function deriveUniqueKey(spell) {
|
|
let sourceId = '';
|
|
switch (getDataOriginType(spell)) {
|
|
case DataOriginTypeEnum.ITEM:
|
|
sourceId = ItemAccessors.getUniqueKey(getDataOrigin(spell).primary);
|
|
break;
|
|
case DataOriginTypeEnum.CLASS:
|
|
sourceId = ClassAccessors.getId(getDataOrigin(spell).primary);
|
|
break;
|
|
default:
|
|
// not implemented
|
|
}
|
|
return `${deriveKnownKey(spell)}-${sourceId}`;
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
*/
|
|
export function deriveKnownKey(spell) {
|
|
return `${getMappingId(spell)}-${getMappingEntityTypeId(spell)}`;
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
*/
|
|
export function deriveLeveledKnownKey(spell, castLevel) {
|
|
return `${getMappingId(spell)}-${getMappingEntityTypeId(spell)}-${castLevel}`;
|
|
}
|
|
/**
|
|
*
|
|
* @param scaleType
|
|
* @param higherLevelDefinition
|
|
* @param spell
|
|
* @param ruleData
|
|
*/
|
|
export function deriveAtHigherLevelAdditionalCountEntries(scaleType, higherLevelDefinition, spell, ruleData) {
|
|
var _a, _b, _c;
|
|
const entries = [];
|
|
if (scaleType === SpellScaleTypeNameEnum.SPELL_SCALE) {
|
|
const spellLevel = getLevel(spell);
|
|
const maxSpellLevel = RuleDataAccessors.getMaxSpellLevel(ruleData);
|
|
const levelIncrement = (_a = higherLevelDefinition.level) !== null && _a !== void 0 ? _a : 1;
|
|
const start = spellLevel + levelIncrement;
|
|
let j = 1;
|
|
for (let i = start; i <= maxSpellLevel; i += levelIncrement, j++) {
|
|
entries.push(CoreSimulators.simulateHigherLevelEntryContract({
|
|
level: i,
|
|
description: higherLevelDefinition.details,
|
|
totalCount: j * ((_b = higherLevelDefinition.value) !== null && _b !== void 0 ? _b : 1),
|
|
}));
|
|
}
|
|
}
|
|
else {
|
|
entries.push(CoreSimulators.simulateHigherLevelEntryContract({
|
|
level: higherLevelDefinition.level,
|
|
description: higherLevelDefinition.details,
|
|
totalCount: (_c = higherLevelDefinition.value) !== null && _c !== void 0 ? _c : 1,
|
|
}));
|
|
}
|
|
return entries;
|
|
}
|
|
/**
|
|
*
|
|
* @param scaleType
|
|
* @param higherLevelDefinition
|
|
* @param spell
|
|
* @param ruleData
|
|
*/
|
|
export function deriveAtHigherLevelAdditionalTargetsEntries(scaleType, higherLevelDefinition, spell, ruleData) {
|
|
var _a, _b, _c;
|
|
const entries = [];
|
|
if (scaleType === SpellScaleTypeNameEnum.SPELL_SCALE) {
|
|
const spellLevel = getLevel(spell);
|
|
const maxSpellLevel = RuleDataAccessors.getMaxSpellLevel(ruleData);
|
|
const levelIncrement = (_a = higherLevelDefinition.level) !== null && _a !== void 0 ? _a : 1;
|
|
const start = spellLevel + levelIncrement;
|
|
let j = 1;
|
|
for (let i = start; i <= maxSpellLevel; i += levelIncrement, j++) {
|
|
entries.push(CoreSimulators.simulateHigherLevelEntryContract({
|
|
level: i,
|
|
description: higherLevelDefinition.details,
|
|
targets: j * ((_b = higherLevelDefinition.value) !== null && _b !== void 0 ? _b : 1),
|
|
}));
|
|
}
|
|
}
|
|
else {
|
|
entries.push(CoreSimulators.simulateHigherLevelEntryContract({
|
|
level: higherLevelDefinition.level,
|
|
description: higherLevelDefinition.details,
|
|
targets: (_c = higherLevelDefinition.value) !== null && _c !== void 0 ? _c : 1,
|
|
}));
|
|
}
|
|
return entries;
|
|
}
|
|
/**
|
|
*
|
|
* @param scaleType
|
|
* @param higherLevelDefinition
|
|
* @param spell
|
|
* @param ruleData
|
|
*/
|
|
export function deriveAtHigherLevelExtendedAreaEntries(scaleType, higherLevelDefinition, spell, ruleData) {
|
|
var _a, _b, _c;
|
|
const entries = [];
|
|
if (scaleType === SpellScaleTypeNameEnum.SPELL_SCALE) {
|
|
const spellRange = getRange(spell);
|
|
const spellLevel = getLevel(spell);
|
|
const maxSpellLevel = RuleDataAccessors.getMaxSpellLevel(ruleData);
|
|
const levelIncrement = (_a = higherLevelDefinition.level) !== null && _a !== void 0 ? _a : 1;
|
|
const start = spellLevel + levelIncrement;
|
|
let j = 1;
|
|
for (let i = start; i <= maxSpellLevel; i += levelIncrement, j++) {
|
|
entries.push(CoreSimulators.simulateHigherLevelEntryContract({
|
|
level: i,
|
|
description: higherLevelDefinition.details,
|
|
extendedAoe: j * ((_b = higherLevelDefinition.value) !== null && _b !== void 0 ? _b : 1) + ((_c = spellRange === null || spellRange === void 0 ? void 0 : spellRange.aoeValue) !== null && _c !== void 0 ? _c : 0),
|
|
}));
|
|
}
|
|
}
|
|
else {
|
|
entries.push(CoreSimulators.simulateHigherLevelEntryContract({
|
|
level: higherLevelDefinition.level,
|
|
description: higherLevelDefinition.details,
|
|
extendedAoe: higherLevelDefinition.value,
|
|
}));
|
|
}
|
|
return entries;
|
|
}
|
|
/**
|
|
*
|
|
* @param scaleType
|
|
* @param higherLevelDefinition
|
|
* @param spell
|
|
* @param ruleData
|
|
*/
|
|
export function deriveAtHigherLevelCommonDescriptionEntries(scaleType, higherLevelDefinition, spell, ruleData) {
|
|
const entries = [];
|
|
if (scaleType === SpellScaleTypeNameEnum.SPELL_SCALE) {
|
|
let levelIncrement = higherLevelDefinition.level;
|
|
if (levelIncrement === null) {
|
|
entries.push(CoreSimulators.simulateHigherLevelEntryContract({
|
|
level: higherLevelDefinition.level,
|
|
description: higherLevelDefinition.details,
|
|
}));
|
|
}
|
|
else {
|
|
const spellLevel = getLevel(spell);
|
|
const maxSpellLevel = RuleDataAccessors.getMaxSpellLevel(ruleData);
|
|
const start = spellLevel + levelIncrement;
|
|
for (let i = start; i <= maxSpellLevel; i += levelIncrement) {
|
|
entries.push(CoreSimulators.simulateHigherLevelEntryContract({
|
|
level: i,
|
|
description: higherLevelDefinition.details,
|
|
}));
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
entries.push(CoreSimulators.simulateHigherLevelEntryContract({
|
|
level: higherLevelDefinition.level,
|
|
description: higherLevelDefinition.details,
|
|
}));
|
|
}
|
|
return entries;
|
|
}
|
|
export function deriveAtHigherLevelExtendedRangeEntries(scaleType, higherLevelDefinition, spell, ruleData) {
|
|
var _a, _b, _c;
|
|
const entries = [];
|
|
if (scaleType === SpellScaleTypeNameEnum.SPELL_SCALE) {
|
|
const spellRange = getRange(spell);
|
|
const spellLevel = getLevel(spell);
|
|
const maxSpellLevel = RuleDataAccessors.getMaxSpellLevel(ruleData);
|
|
const levelIncrement = (_a = higherLevelDefinition.level) !== null && _a !== void 0 ? _a : 1;
|
|
const start = spellLevel + levelIncrement;
|
|
let j = 1;
|
|
for (let i = start; i <= maxSpellLevel; i += levelIncrement, j++) {
|
|
entries.push(CoreSimulators.simulateHigherLevelEntryContract({
|
|
level: i,
|
|
description: higherLevelDefinition.details,
|
|
range: j * ((_b = higherLevelDefinition.value) !== null && _b !== void 0 ? _b : 1) + ((_c = spellRange === null || spellRange === void 0 ? void 0 : spellRange.rangeValue) !== null && _c !== void 0 ? _c : 0),
|
|
}));
|
|
}
|
|
}
|
|
else {
|
|
entries.push(CoreSimulators.simulateHigherLevelEntryContract({
|
|
level: higherLevelDefinition.level,
|
|
description: higherLevelDefinition.details,
|
|
range: higherLevelDefinition.value,
|
|
}));
|
|
}
|
|
return entries;
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
* @param ruleData
|
|
*/
|
|
export function deriveAtHigherLevels(spell, ruleData) {
|
|
var _a;
|
|
const higherLevelData = getDefinitionAtHigherLevels(spell);
|
|
// TODO V5: do we need the check this julie/brian for context
|
|
const scaleType = higherLevelData ? getScaleType(spell) : null;
|
|
if (higherLevelData === null || scaleType === null) {
|
|
return null;
|
|
}
|
|
const additionalAttacks = [];
|
|
const additionalTargets = [];
|
|
const areaOfEffect = [];
|
|
const creatures = [];
|
|
const duration = [];
|
|
const special = [];
|
|
const range = [];
|
|
(_a = higherLevelData.higherLevelDefinitions) === null || _a === void 0 ? void 0 : _a.forEach((higherLevelDefinition) => {
|
|
switch (higherLevelDefinition.typeId) {
|
|
case AdditionalTypeEnum.ADDITIONAL_COUNT:
|
|
additionalAttacks.push(...deriveAtHigherLevelAdditionalCountEntries(scaleType, higherLevelDefinition, spell, ruleData));
|
|
break;
|
|
case AdditionalTypeEnum.ADDITIONAL_TARGETS:
|
|
additionalTargets.push(...deriveAtHigherLevelAdditionalTargetsEntries(scaleType, higherLevelDefinition, spell, ruleData));
|
|
break;
|
|
case AdditionalTypeEnum.EXTENDED_AREA:
|
|
areaOfEffect.push(...deriveAtHigherLevelExtendedAreaEntries(scaleType, higherLevelDefinition, spell, ruleData));
|
|
break;
|
|
case AdditionalTypeEnum.EXTENDED_DURATION:
|
|
duration.push(...deriveAtHigherLevelCommonDescriptionEntries(scaleType, higherLevelDefinition, spell, ruleData));
|
|
break;
|
|
case AdditionalTypeEnum.ADDITIONAL_CREATURES:
|
|
creatures.push(...deriveAtHigherLevelCommonDescriptionEntries(scaleType, higherLevelDefinition, spell, ruleData));
|
|
break;
|
|
case AdditionalTypeEnum.EXTENDED_RANGE:
|
|
range.push(...deriveAtHigherLevelExtendedRangeEntries(scaleType, higherLevelDefinition, spell, ruleData));
|
|
break;
|
|
case AdditionalTypeEnum.ADDITIONAL_POINTS:
|
|
case AdditionalTypeEnum.SPECIAL:
|
|
default:
|
|
special.push(...deriveAtHigherLevelCommonDescriptionEntries(scaleType, higherLevelDefinition, spell, ruleData));
|
|
break;
|
|
}
|
|
});
|
|
return {
|
|
additionalAttacks,
|
|
additionalTargets,
|
|
areaOfEffect,
|
|
creatures,
|
|
duration,
|
|
higherLevelDefinitions: higherLevelData.higherLevelDefinitions,
|
|
points: [],
|
|
special,
|
|
range,
|
|
};
|
|
}
|
|
/**
|
|
*
|
|
* @param modifier
|
|
* @param spell
|
|
* @param ruleData
|
|
*/
|
|
export function deriveModifier(modifier, spell, ruleData) {
|
|
var _a;
|
|
const higherLevelData = ModifierAccessors.getAtHigherLevels(modifier);
|
|
// TODO V5: do we need the check this julie/brian for context
|
|
const scaleType = higherLevelData ? getScaleType(spell) : null;
|
|
if (higherLevelData === null || scaleType === null) {
|
|
return modifier;
|
|
}
|
|
const additionalAttacks = [];
|
|
const additionalTargets = [];
|
|
const areaOfEffect = [];
|
|
const creatures = [];
|
|
const duration = [];
|
|
const points = [];
|
|
const special = [];
|
|
const range = [];
|
|
(_a = higherLevelData.higherLevelDefinitions) === null || _a === void 0 ? void 0 : _a.forEach((higherLevelDefinition) => {
|
|
var _a, _b, _c, _d;
|
|
switch (higherLevelDefinition.typeId) {
|
|
case AdditionalTypeEnum.ADDITIONAL_POINTS:
|
|
if (scaleType === SpellScaleTypeNameEnum.SPELL_SCALE) {
|
|
const spellLevel = getLevel(spell);
|
|
const maxSpellLevel = RuleDataAccessors.getMaxSpellLevel(ruleData);
|
|
const defDice = higherLevelDefinition.dice;
|
|
const defDiceValue = defDice === null ? null : DiceAccessors.getValue(defDice);
|
|
const defDiceCount = defDice === null ? null : DiceAccessors.getCount(defDice);
|
|
const modifierDice = ModifierAccessors.getDie(modifier);
|
|
const modifierDiceValue = modifierDice === null ? null : DiceAccessors.getValue(modifierDice);
|
|
const modifierDiceCount = modifierDice === null ? null : DiceAccessors.getCount(modifierDice);
|
|
const modifierDiceFixed = modifierDice === null ? null : DiceAccessors.getFixedValue(modifierDice);
|
|
let levelIncrement = (_a = higherLevelDefinition.level) !== null && _a !== void 0 ? _a : 1;
|
|
let start = spellLevel + levelIncrement;
|
|
let j = 1;
|
|
for (let i = start; i <= maxSpellLevel; i += levelIncrement, j++) {
|
|
// Dice count will be base spell modifier dice count plus the product of the iteration of the spell level bonus and the dice count of the bonus
|
|
let newDiceCount = (modifierDiceCount !== null && modifierDiceCount !== void 0 ? modifierDiceCount : 0) + j * (defDiceCount !== null && defDiceCount !== void 0 ? defDiceCount : 0);
|
|
// Add bonus fixed value to base fixed value
|
|
let newFixedValue = (modifierDiceFixed !== null && modifierDiceFixed !== void 0 ? modifierDiceFixed : 0) + j * ((_b = higherLevelDefinition.value) !== null && _b !== void 0 ? _b : 0);
|
|
points.push(CoreSimulators.simulateHigherLevelEntryContract({
|
|
level: i,
|
|
description: higherLevelDefinition.details,
|
|
die: DiceSimulators.simulateDiceContract({
|
|
diceCount: newDiceCount,
|
|
diceValue: defDiceValue !== null && defDiceValue !== void 0 ? defDiceValue : modifierDiceValue,
|
|
fixedValue: newFixedValue,
|
|
}),
|
|
}));
|
|
}
|
|
}
|
|
else {
|
|
const defDice = higherLevelDefinition.dice;
|
|
points.push(CoreSimulators.simulateHigherLevelEntryContract({
|
|
level: higherLevelDefinition.level,
|
|
description: higherLevelDefinition.details,
|
|
die: DiceSimulators.simulateDiceContract({
|
|
diceCount: defDice === null ? null : DiceAccessors.getCount(defDice),
|
|
diceValue: (_c = (defDice === null ? null : DiceAccessors.getValue(defDice))) !== null && _c !== void 0 ? _c : (_d = ModifierAccessors.getDie(modifier)) === null || _d === void 0 ? void 0 : _d.diceValue,
|
|
fixedValue: defDice === null ? null : DiceAccessors.getFixedValue(defDice),
|
|
}),
|
|
}));
|
|
}
|
|
break;
|
|
case AdditionalTypeEnum.ADDITIONAL_COUNT:
|
|
additionalAttacks.push(...deriveAtHigherLevelAdditionalCountEntries(scaleType, higherLevelDefinition, spell, ruleData));
|
|
break;
|
|
case AdditionalTypeEnum.ADDITIONAL_TARGETS:
|
|
additionalTargets.push(...deriveAtHigherLevelAdditionalTargetsEntries(scaleType, higherLevelDefinition, spell, ruleData));
|
|
break;
|
|
case AdditionalTypeEnum.EXTENDED_AREA:
|
|
areaOfEffect.push(...deriveAtHigherLevelExtendedAreaEntries(scaleType, higherLevelDefinition, spell, ruleData));
|
|
break;
|
|
case AdditionalTypeEnum.EXTENDED_RANGE:
|
|
range.push(...deriveAtHigherLevelExtendedRangeEntries(scaleType, higherLevelDefinition, spell, ruleData));
|
|
break;
|
|
case AdditionalTypeEnum.EXTENDED_DURATION:
|
|
duration.push(...deriveAtHigherLevelCommonDescriptionEntries(scaleType, higherLevelDefinition, spell, ruleData));
|
|
break;
|
|
case AdditionalTypeEnum.ADDITIONAL_CREATURES:
|
|
creatures.push(...deriveAtHigherLevelCommonDescriptionEntries(scaleType, higherLevelDefinition, spell, ruleData));
|
|
break;
|
|
case AdditionalTypeEnum.SPECIAL:
|
|
default:
|
|
special.push(...deriveAtHigherLevelCommonDescriptionEntries(scaleType, higherLevelDefinition, spell, ruleData));
|
|
break;
|
|
}
|
|
});
|
|
return Object.assign(Object.assign({}, modifier), { atHigherLevels: {
|
|
additionalAttacks,
|
|
additionalTargets,
|
|
areaOfEffect,
|
|
creatures,
|
|
duration,
|
|
higherLevelDefinitions: higherLevelData.higherLevelDefinitions,
|
|
points,
|
|
special,
|
|
range,
|
|
} });
|
|
}
|
|
/**
|
|
*
|
|
* @param spell
|
|
* @param ruleData
|
|
*/
|
|
export function deriveModifiers(spell, ruleData) {
|
|
return getDefinitionModifiers(spell).map((modifier) => deriveModifier(modifier, spell, ruleData));
|
|
}
|