``` ~/go/bin/sourcemapper -output ddb -jsurl https://media.dndbeyond.com/character-app/static/js/main.90aa78c5.js ```
654 lines
27 KiB
JavaScript
654 lines
27 KiB
JavaScript
import { TypeScriptUtils } from '../../utils';
|
|
import { AbilityAccessors, AbilityUtils } from '../Ability';
|
|
import { CharacterDerivers } from '../Character';
|
|
import { AbilitySkillEnum, AbilityStatEnum, ProficiencyLevelEnum, } from '../Core';
|
|
import { HelperUtils } from '../Helper';
|
|
import { InfusionAccessors } from '../Infusion';
|
|
import { RuleDataAccessors, RuleDataUtils } from '../RuleData';
|
|
import { SkillAccessors } from '../Skill';
|
|
import { AdjustmentTypeEnum, ValueHacks, ValueUtils, ValueValidators } from '../Value';
|
|
import { getAverageHitPoints, getChallengeProficiencyBonus, getChallengeRatingId, getDamageAdjustments, getDefinitionAlignmentId, getDefinitionArmorClass, getDefinitionConditionImmunities, getDefinitionDamageAdjustments, getDefinitionPassivePerception, getDefinitionSavingThrows, getDefinitionSizeId, getDefinitionSkills, getDefinitionStats, getDefinitionSubTypes, getDefinitionTypeId, getEnvironments, getId, getLairChallengeRatingId, getMappingEntityTypeId, getMappingId, getMovements, getRemoveHitPoints, getTemporaryHitPoints, getTypeId, } from './accessors';
|
|
import { CREATURE_CUSTOMIZATION_ADJUSTMENT_TYPES, CreatureGroupFlagEnum } from './constants';
|
|
import { getStatInfo, hasGroupFlag, shouldReplaceWithOwnerStat } from './utils';
|
|
/**
|
|
*
|
|
* @param creature
|
|
*/
|
|
export function derivePreOwnerHitPointInfo(creature) {
|
|
const finalTotalHp = Math.max(getAverageHitPoints(creature), 0);
|
|
const finalBaseHp = finalTotalHp;
|
|
const finalRemovedHp = Math.min(finalTotalHp, getRemoveHitPoints(creature));
|
|
return {
|
|
baseHp: finalBaseHp,
|
|
tempHp: 0,
|
|
removedHp: finalRemovedHp,
|
|
bonusHp: 0,
|
|
overrideHp: 0,
|
|
totalHp: finalTotalHp,
|
|
remainingHp: finalTotalHp - finalRemovedHp,
|
|
};
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param ownerData
|
|
* @param valueLookup
|
|
* @param ruleData
|
|
*/
|
|
export function deriveHitPointInfo(creature, ownerData, valueLookup, ruleData) {
|
|
const flagLevelMultiplierHpPossibilities = [];
|
|
if (hasGroupFlag(creature, CreatureGroupFlagEnum.MAX_HIT_POINTS_LEVEL_MULTIPLIER_OPTION)) {
|
|
const flagInfo = RuleDataUtils.getCreatureGroupFlagInfo(CreatureGroupFlagEnum.MAX_HIT_POINTS_LEVEL_MULTIPLIER_OPTION, ruleData);
|
|
if (flagInfo) {
|
|
const contextClassLevel = flagInfo.valueContextId === null ? 0 : ownerData.classLevelLookup[flagInfo.valueContextId];
|
|
const contextValue = flagInfo.value === null ? 1 : flagInfo.value;
|
|
flagLevelMultiplierHpPossibilities.push((contextClassLevel ? contextClassLevel : 0) * contextValue);
|
|
}
|
|
}
|
|
if (hasGroupFlag(creature, CreatureGroupFlagEnum.ARTIFICER_HP_MULTIPLIER)) {
|
|
const flagInfo = RuleDataUtils.getCreatureGroupFlagInfo(CreatureGroupFlagEnum.ARTIFICER_HP_MULTIPLIER, ruleData);
|
|
if (flagInfo) {
|
|
const contextClassLevel = flagInfo.valueContextId === null ? 0 : ownerData.classLevelLookup[flagInfo.valueContextId];
|
|
const contextValue = flagInfo.value === null ? 1 : flagInfo.value;
|
|
flagLevelMultiplierHpPossibilities.push((contextClassLevel ? contextClassLevel : 0) * contextValue);
|
|
}
|
|
}
|
|
const definitionTotalHp = getAverageHitPoints(creature);
|
|
let finalTotalHp = Math.max(definitionTotalHp, ...flagLevelMultiplierHpPossibilities);
|
|
if (hasGroupFlag(creature, CreatureGroupFlagEnum.MAX_HIT_POINTS_BASE_ARTIFICER_LEVEL)) {
|
|
const flagInfo = RuleDataUtils.getCreatureGroupFlagInfo(CreatureGroupFlagEnum.MAX_HIT_POINTS_BASE_ARTIFICER_LEVEL, ruleData);
|
|
if (flagInfo && flagInfo.valueContextId !== null) {
|
|
finalTotalHp = HelperUtils.lookupDataOrFallback(ownerData.classLevelLookup, flagInfo.valueContextId, finalTotalHp);
|
|
}
|
|
}
|
|
if (hasGroupFlag(creature, CreatureGroupFlagEnum.MAX_HIT_POINTS_ADD_INT_MODIFIER)) {
|
|
const flagInfo = RuleDataUtils.getCreatureGroupFlagInfo(CreatureGroupFlagEnum.MAX_HIT_POINTS_ADD_INT_MODIFIER, ruleData);
|
|
if (flagInfo) {
|
|
const contextStatModifier = flagInfo.valueContextId === null ? null : ownerData.abilityLookup[flagInfo.valueContextId];
|
|
let finalModifier = 0;
|
|
if (contextStatModifier && contextStatModifier.modifier) {
|
|
finalModifier = contextStatModifier.modifier;
|
|
}
|
|
finalTotalHp = HelperUtils.clampInt(finalTotalHp + finalModifier, RuleDataAccessors.getMinimumHpTotal(ruleData));
|
|
}
|
|
}
|
|
if (hasGroupFlag(creature, CreatureGroupFlagEnum.MAX_HIT_POINTS_ADD_MONSTER_CON_MODIFIER)) {
|
|
const flagInfo = RuleDataUtils.getCreatureGroupFlagInfo(CreatureGroupFlagEnum.MAX_HIT_POINTS_ADD_MONSTER_CON_MODIFIER, ruleData);
|
|
if (flagInfo !== null && flagInfo.valueContextId) {
|
|
let contextStatModifier = 0;
|
|
const contextStatInfo = getStatInfo(creature, flagInfo.valueContextId);
|
|
if (contextStatInfo !== null && contextStatInfo.modifier !== null) {
|
|
contextStatModifier = contextStatInfo.modifier;
|
|
}
|
|
finalTotalHp = HelperUtils.clampInt(finalTotalHp + contextStatModifier, ruleData.minimumHpTotal);
|
|
}
|
|
}
|
|
//if there is an override for the creature's hit points, use that instead
|
|
const hpOverride = deriveOverridenValue(creature, AdjustmentTypeEnum.CREATURE_HIT_POINTS, null, valueLookup);
|
|
if (hpOverride) {
|
|
finalTotalHp = hpOverride;
|
|
}
|
|
const tempHp = getTemporaryHitPoints(creature);
|
|
const finalRemovedHp = Math.min(finalTotalHp, getRemoveHitPoints(creature));
|
|
return {
|
|
baseHp: finalTotalHp,
|
|
tempHp,
|
|
removedHp: finalRemovedHp,
|
|
bonusHp: 0,
|
|
overrideHp: 0,
|
|
totalHp: finalTotalHp,
|
|
remainingHp: finalTotalHp - finalRemovedHp,
|
|
};
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param ownerData
|
|
* @param ruleData
|
|
*/
|
|
export function deriveInitialTempHp(creature, ownerData, ruleData) {
|
|
let initialTempHp = 0;
|
|
if (hasGroupFlag(creature, CreatureGroupFlagEnum.TEMP_HIT_POINTS_BASE_DRUID_LEVEL)) {
|
|
const flagInfo = RuleDataUtils.getCreatureGroupFlagInfo(CreatureGroupFlagEnum.TEMP_HIT_POINTS_BASE_DRUID_LEVEL, ruleData);
|
|
if (flagInfo) {
|
|
//find the level of the class or subclass id that matches the valueContextId and use it as the initial temp hp
|
|
const contextClassLevel = flagInfo.valueContextId === null ? 0 : ownerData.classLevelLookup[flagInfo.valueContextId];
|
|
initialTempHp = contextClassLevel;
|
|
}
|
|
}
|
|
if (hasGroupFlag(creature, CreatureGroupFlagEnum.TEMP_HIT_POINTS_BASE_DRUID_LEVEL_MULTIPLIER)) {
|
|
const flagInfo = RuleDataUtils.getCreatureGroupFlagInfo(CreatureGroupFlagEnum.TEMP_HIT_POINTS_BASE_DRUID_LEVEL_MULTIPLIER, ruleData);
|
|
if (flagInfo) {
|
|
//find the level of the class or subclass id that matches the valueContextId and multiply it by the contextValue
|
|
const contextClassLevel = flagInfo.valueContextId === null ? 0 : ownerData.classLevelLookup[flagInfo.valueContextId];
|
|
//only multiply the tempHp if the there is a contextClassLevel
|
|
if (contextClassLevel > 0) {
|
|
const contextValue = flagInfo.value === null ? 1 : flagInfo.value;
|
|
initialTempHp = contextClassLevel * contextValue;
|
|
}
|
|
}
|
|
}
|
|
return initialTempHp;
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
*/
|
|
export function deriveUseOwnerHp(creature) {
|
|
//If this is true, the CreaturePane will use the player HP tracker instead of the Creature HP tracker and MaxHP override should be hidden from creature customization
|
|
return hasGroupFlag(creature, CreatureGroupFlagEnum.USE_OWNER_MAX_HIT_POINTS);
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param skill
|
|
* @param ownerData
|
|
* @param ruleData
|
|
*/
|
|
export function deriveOwnerSkillModifier(creature, skill, ownerData, ruleData) {
|
|
var _a, _b, _c;
|
|
const skillInfo = RuleDataUtils.getSkillInfo(skill.id, ruleData);
|
|
let statModifier = 0;
|
|
let proficiencyBonus = ownerData.proficiencyBonus;
|
|
let otherBonuses = 0;
|
|
if (skillInfo) {
|
|
const statInfo = getStatInfo(creature, skillInfo.stat);
|
|
const ownerSkill = HelperUtils.lookupDataOrFallback(ownerData.skillLookup, skill.id);
|
|
statModifier = (_a = statInfo === null || statInfo === void 0 ? void 0 : statInfo.monster.modifier) !== null && _a !== void 0 ? _a : 0;
|
|
if (ownerSkill !== null) {
|
|
const ownerProficiencyLevel = SkillAccessors.getProficiencyLevel(ownerSkill);
|
|
// Creature gives proficiency bonus if the owner doesn't have any
|
|
let proficiencyLevel = ProficiencyLevelEnum.FULL;
|
|
if (ownerProficiencyLevel !== ProficiencyLevelEnum.NONE) {
|
|
proficiencyLevel = ownerProficiencyLevel;
|
|
}
|
|
proficiencyBonus = CharacterDerivers.deriveProficiencyBonusAmount(proficiencyLevel, ownerData.proficiencyBonus);
|
|
if (shouldReplaceWithOwnerStat(creature, skillInfo.stat)) {
|
|
if (SkillAccessors.getIsOverridden(ownerSkill)) {
|
|
statModifier = (_b = SkillAccessors.getModifier(ownerSkill)) !== null && _b !== void 0 ? _b : 0;
|
|
proficiencyBonus = 0;
|
|
}
|
|
else {
|
|
statModifier = SkillAccessors.getStatModifier(ownerSkill);
|
|
otherBonuses = SkillAccessors.getModifierBonuses(ownerSkill);
|
|
}
|
|
}
|
|
else {
|
|
const statInfo = getStatInfo(creature, skillInfo.stat);
|
|
statModifier = (_c = statInfo === null || statInfo === void 0 ? void 0 : statInfo.monster.modifier) !== null && _c !== void 0 ? _c : 0;
|
|
}
|
|
}
|
|
}
|
|
return statModifier + proficiencyBonus + otherBonuses;
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param stat
|
|
* @param ownerData
|
|
*/
|
|
export function deriveOwnerSaveModifier(creature, stat, ownerData) {
|
|
var _a, _b, _c;
|
|
const statInfo = getStatInfo(creature, stat.id);
|
|
let statModifier = 0;
|
|
let proficiencyBonus = ownerData.proficiencyBonus;
|
|
let otherBonuses = 0;
|
|
const ownerAbility = HelperUtils.lookupDataOrFallback(ownerData.abilityLookup, stat.id);
|
|
if (ownerAbility !== null) {
|
|
const ownerProficiencyLevel = AbilityAccessors.getProficiencyLevel(ownerAbility);
|
|
// Creature gives proficiency bonus if the owner doesn't have any
|
|
let proficiencyLevel = ProficiencyLevelEnum.FULL;
|
|
if (ownerProficiencyLevel !== ProficiencyLevelEnum.NONE) {
|
|
proficiencyLevel = ownerProficiencyLevel;
|
|
}
|
|
proficiencyBonus = CharacterDerivers.deriveProficiencyBonusAmount(proficiencyLevel, ownerData.proficiencyBonus);
|
|
if (shouldReplaceWithOwnerStat(creature, stat.id)) {
|
|
if (AbilityAccessors.getIsSaveOverridden(ownerAbility)) {
|
|
statModifier = (_a = AbilityAccessors.getSave(ownerAbility)) !== null && _a !== void 0 ? _a : 0;
|
|
proficiencyBonus = 0;
|
|
}
|
|
else {
|
|
statModifier = (_b = AbilityAccessors.getModifier(ownerAbility)) !== null && _b !== void 0 ? _b : 0;
|
|
otherBonuses = AbilityAccessors.getSaveBonuses(ownerAbility);
|
|
}
|
|
}
|
|
else {
|
|
statModifier = (_c = statInfo === null || statInfo === void 0 ? void 0 : statInfo.monster.modifier) !== null && _c !== void 0 ? _c : 0;
|
|
}
|
|
}
|
|
return statModifier + proficiencyBonus + otherBonuses;
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param skill
|
|
* @param ruleData
|
|
*/
|
|
export function deriveCreatureSkillModifier(creature, skill, ruleData) {
|
|
var _a, _b;
|
|
const skillInfo = RuleDataUtils.getSkillInfo(skill.skillId, ruleData);
|
|
let statModifier = 0;
|
|
if (skillInfo) {
|
|
const statInfo = getStatInfo(creature, skillInfo.stat);
|
|
statModifier = (_a = statInfo === null || statInfo === void 0 ? void 0 : statInfo.monster.modifier) !== null && _a !== void 0 ? _a : 0;
|
|
}
|
|
const proficiencyBonus = getChallengeProficiencyBonus(creature);
|
|
const otherBonuses = (_b = skill.additionalBonus) !== null && _b !== void 0 ? _b : 0;
|
|
return statModifier + proficiencyBonus + otherBonuses;
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param savingThrow
|
|
*/
|
|
export function deriveCreatureSaveModifier(creature, savingThrow) {
|
|
var _a, _b;
|
|
const statInfo = getStatInfo(creature, savingThrow.statId);
|
|
const statModifier = (_a = statInfo === null || statInfo === void 0 ? void 0 : statInfo.monster.modifier) !== null && _a !== void 0 ? _a : 0;
|
|
const proficiencyBonus = getChallengeProficiencyBonus(creature);
|
|
const otherBonuses = (_b = savingThrow.bonusModifier) !== null && _b !== void 0 ? _b : 0;
|
|
return statModifier + proficiencyBonus + otherBonuses;
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param ownerData
|
|
* @param ruleData
|
|
*/
|
|
export function deriveSkills(creature, ownerData, ruleData) {
|
|
const skills = RuleDataAccessors.getAbilitySkills(ruleData);
|
|
return skills
|
|
.map((skillInfo) => {
|
|
var _a;
|
|
const statInfo = RuleDataUtils.getStatInfo(skillInfo.stat, ruleData);
|
|
const creatureSkill = getDefinitionSkills(creature).find((skill) => skill.skillId === skillInfo.id);
|
|
const isCreatureProficient = !!creatureSkill;
|
|
const potentialModifiers = [];
|
|
if (hasGroupFlag(creature, CreatureGroupFlagEnum.EVALUATE_OWNER_SKILL_PROFICIENCIES)) {
|
|
const ownerSkill = HelperUtils.lookupDataOrFallback(ownerData.skillLookup, skillInfo.id);
|
|
const isOwnerProficient = ownerSkill !== null && SkillAccessors.getProficiencyLevel(ownerSkill) !== ProficiencyLevelEnum.NONE;
|
|
if (isCreatureProficient || isOwnerProficient) {
|
|
potentialModifiers.push(deriveOwnerSkillModifier(creature, skillInfo, ownerData, ruleData));
|
|
}
|
|
// Can potentially use creatures modifier if the owner is "proficient"
|
|
if (creatureSkill && isCreatureProficient && isOwnerProficient) {
|
|
potentialModifiers.push(deriveCreatureSkillModifier(creature, creatureSkill, ruleData));
|
|
}
|
|
}
|
|
else if (creatureSkill && isCreatureProficient) {
|
|
potentialModifiers.push(deriveCreatureSkillModifier(creature, creatureSkill, ruleData));
|
|
}
|
|
// if we didn't generate any potential modifiers, exit early and null will be filtered out
|
|
if (!potentialModifiers.length) {
|
|
return null;
|
|
}
|
|
const modifier = Math.max(...potentialModifiers);
|
|
let bonuses = 0;
|
|
if (hasGroupFlag(creature, CreatureGroupFlagEnum.PROFICIENT_SKILLS_ADD_PROFICIENCY_BONUS)) {
|
|
bonuses += ownerData.proficiencyBonus;
|
|
}
|
|
return {
|
|
id: skillInfo.id,
|
|
modifier: modifier + bonuses,
|
|
statKey: (_a = statInfo === null || statInfo === void 0 ? void 0 : statInfo.key) !== null && _a !== void 0 ? _a : 'UNK',
|
|
};
|
|
})
|
|
.filter(TypeScriptUtils.isNotNullOrUndefined);
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param ownerData
|
|
* @param ruleData
|
|
*/
|
|
export function deriveSavingThrows(creature, ownerData, ruleData) {
|
|
const stats = RuleDataAccessors.getStats(ruleData);
|
|
return stats
|
|
.map((stat) => {
|
|
var _a;
|
|
const creatureSavingThrow = getDefinitionSavingThrows(creature).find((savingThrow) => savingThrow.statId === stat.id);
|
|
const isCreatureProficient = !!creatureSavingThrow;
|
|
const potentialModifiers = [];
|
|
if (hasGroupFlag(creature, CreatureGroupFlagEnum.EVALUATE_OWNER_SAVE_PROFICIENCIES)) {
|
|
const ownerAbility = HelperUtils.lookupDataOrFallback(ownerData.abilityLookup, stat.id);
|
|
const isOwnerProficient = ownerAbility !== null &&
|
|
AbilityAccessors.getProficiencyLevel(ownerAbility) !== ProficiencyLevelEnum.NONE;
|
|
if (isCreatureProficient || isOwnerProficient) {
|
|
potentialModifiers.push(deriveOwnerSaveModifier(creature, stat, ownerData));
|
|
}
|
|
// Can potentially use creatures modifier if the owner is "proficient"
|
|
if (creatureSavingThrow && isCreatureProficient && isOwnerProficient) {
|
|
potentialModifiers.push(deriveCreatureSaveModifier(creature, creatureSavingThrow));
|
|
}
|
|
}
|
|
else if (creatureSavingThrow && isCreatureProficient) {
|
|
potentialModifiers.push(deriveCreatureSaveModifier(creature, creatureSavingThrow));
|
|
}
|
|
// if we didn't generate any potential modifiers, exit early and null will be filtered out
|
|
if (!potentialModifiers.length) {
|
|
return null;
|
|
}
|
|
const modifier = Math.max(...potentialModifiers);
|
|
let bonuses = 0;
|
|
if (hasGroupFlag(creature, CreatureGroupFlagEnum.PROFICIENT_SAVING_THROWS_ADD_PROFICIENCY_BONUS)) {
|
|
bonuses += ownerData.proficiencyBonus;
|
|
}
|
|
return {
|
|
statId: stat.id,
|
|
modifier: modifier + bonuses,
|
|
statKey: (_a = stat.key) !== null && _a !== void 0 ? _a : '',
|
|
};
|
|
})
|
|
.filter(TypeScriptUtils.isNotNullOrUndefined);
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param ownerData
|
|
* @param ruleData
|
|
*/
|
|
export function deriveStats(creature, ownerData, ruleData) {
|
|
return getDefinitionStats(creature).map((stat) => {
|
|
var _a;
|
|
const originalScore = stat.value;
|
|
let score = stat.value;
|
|
if (shouldReplaceWithOwnerStat(creature, stat.statId)) {
|
|
const ownerAbility = HelperUtils.lookupDataOrFallback(ownerData.abilityLookup, stat.statId);
|
|
if (ownerAbility !== null) {
|
|
score = AbilityAccessors.getScore(ownerAbility);
|
|
}
|
|
}
|
|
const statInfo = RuleDataUtils.getStatInfo(stat.statId, ruleData);
|
|
const statKey = (_a = statInfo === null || statInfo === void 0 ? void 0 : statInfo.key) !== null && _a !== void 0 ? _a : null;
|
|
const modifier = score === null ? null : AbilityUtils.getStatScoreModifier(score, ruleData);
|
|
const originalModifier = originalScore === null ? null : AbilityUtils.getStatScoreModifier(originalScore, ruleData);
|
|
return {
|
|
id: stat.statId,
|
|
score,
|
|
modifier,
|
|
statKey,
|
|
monster: {
|
|
score: originalScore,
|
|
modifier: originalModifier,
|
|
},
|
|
};
|
|
});
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param ruleData
|
|
*/
|
|
export function deriveDamageAdjustments(creature, ruleData) {
|
|
return getDefinitionDamageAdjustments(creature)
|
|
.map((damageAdjustmentId) => RuleDataUtils.getDamageAdjustmentInfo(damageAdjustmentId, ruleData))
|
|
.filter(TypeScriptUtils.isNotNullOrUndefined);
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param ruleData
|
|
*/
|
|
export function deriveConditionImmunities(creature, ruleData) {
|
|
return getDefinitionConditionImmunities(creature)
|
|
.map((conditionId) => RuleDataUtils.getConditionInfo(conditionId, ruleData))
|
|
.filter(TypeScriptUtils.isNotNullOrUndefined);
|
|
}
|
|
/**
|
|
*
|
|
* @param damageAdjustmentType
|
|
* @param creature
|
|
*/
|
|
export function deriveDamageAdjustmentType(damageAdjustmentType, creature) {
|
|
return getDamageAdjustments(creature).filter((damageAdjustment) => damageAdjustment.type === damageAdjustmentType);
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param ruleData
|
|
*/
|
|
export function deriveSubTypes(creature, ruleData) {
|
|
const subTypes = getDefinitionSubTypes(creature);
|
|
return subTypes
|
|
.map((subTypeId) => RuleDataUtils.getMonsterSubTypeInfo(subTypeId, ruleData))
|
|
.filter(TypeScriptUtils.isNotNullOrUndefined);
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param ownerData
|
|
* @param skillLookup
|
|
* @param ruleData
|
|
*/
|
|
export function derivePassivePerception(creature, ownerData, skillLookup, ruleData) {
|
|
if (!hasGroupFlag(creature, CreatureGroupFlagEnum.EVALUATE_UPDATED_PASSIVE_PERCEPTION)) {
|
|
return getDefinitionPassivePerception(creature);
|
|
}
|
|
let skillModifier = 0;
|
|
const perceptionInfo = skillLookup[AbilitySkillEnum.PERCEPTION];
|
|
if (perceptionInfo) {
|
|
skillModifier = perceptionInfo.modifier;
|
|
}
|
|
else {
|
|
// fallback to perception's stat modifier if perception isn't called out
|
|
const skillInfo = RuleDataUtils.getSkillInfo(AbilitySkillEnum.PERCEPTION, ruleData);
|
|
if (skillInfo) {
|
|
const statInfo = getStatInfo(creature, skillInfo.stat);
|
|
skillModifier = statInfo && statInfo.modifier !== null ? statInfo.modifier : 0;
|
|
}
|
|
}
|
|
const derivedPassivePerception = CharacterDerivers.derivePassiveSkill(skillModifier);
|
|
const passivePerceptionPossibilities = [derivedPassivePerception];
|
|
if (hasGroupFlag(creature, CreatureGroupFlagEnum.EVALUATE_OWNER_PASSIVE_PERCEPTION)) {
|
|
if (ownerData.passivePerception !== null) {
|
|
passivePerceptionPossibilities.push(ownerData.passivePerception);
|
|
}
|
|
}
|
|
return Math.max(...passivePerceptionPossibilities);
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param ruleData
|
|
*/
|
|
export function deriveEnvironmentTags(creature, ruleData) {
|
|
const envs = getEnvironments(creature);
|
|
return envs
|
|
.map((envId) => {
|
|
const envInfo = RuleDataUtils.getEnvironmentInfo(envId, ruleData);
|
|
if (envInfo) {
|
|
return envInfo.name;
|
|
}
|
|
return null;
|
|
})
|
|
.filter(TypeScriptUtils.isNotNullOrUndefined);
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param ruleData
|
|
*/
|
|
export function deriveSubTypeTags(creature, ruleData) {
|
|
const subTypes = getDefinitionSubTypes(creature);
|
|
return subTypes
|
|
.map((subTypeId) => {
|
|
const info = RuleDataUtils.getMonsterSubTypeInfo(subTypeId, ruleData);
|
|
if (info) {
|
|
return info.name;
|
|
}
|
|
return null;
|
|
})
|
|
.filter(TypeScriptUtils.isNotNullOrUndefined);
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param ruleData
|
|
*/
|
|
export function deriveTypeTag(creature, ruleData) {
|
|
return RuleDataUtils.getMonsterTypeName(getTypeId(creature), ruleData, null);
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
*/
|
|
export function deriveMovementIds(creature) {
|
|
return getMovements(creature).map((movement) => movement.movementId);
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param ruleData
|
|
*/
|
|
export function deriveMovementNames(creature, ruleData) {
|
|
return getMovements(creature).map((movement) => RuleDataUtils.getMovementDescription(movement.movementId, ruleData));
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param ownerData
|
|
* @param valueLookup
|
|
*/
|
|
export function deriveArmorClass(creature, ownerData, valueLookup, ruleData) {
|
|
var _a;
|
|
const armorOverride = deriveOverridenValue(creature, AdjustmentTypeEnum.CREATURE_AC, null, valueLookup);
|
|
if (armorOverride) {
|
|
return armorOverride;
|
|
}
|
|
let bonuses = 0;
|
|
if (hasGroupFlag(creature, CreatureGroupFlagEnum.ARMOR_ADD_PROFICIENCY_BONUS)) {
|
|
bonuses += ownerData.proficiencyBonus;
|
|
}
|
|
let armorClass = getDefinitionArmorClass(creature);
|
|
if (hasGroupFlag(creature, CreatureGroupFlagEnum.ARMOR_ADD_OWNER_WIS_PLUS_FIXED_VALUE)) {
|
|
const flagInfo = RuleDataUtils.getCreatureGroupFlagInfo(CreatureGroupFlagEnum.ARMOR_ADD_OWNER_WIS_PLUS_FIXED_VALUE, ruleData);
|
|
if (flagInfo) {
|
|
const contextClassLevel = flagInfo.valueContextId === null ? 0 : ownerData.classLevelLookup[flagInfo.valueContextId];
|
|
//Check if the owner has a matching class or subclass to the contextValueId
|
|
if (contextClassLevel > 0) {
|
|
const ownerWisdomModifier = (_a = ownerData.abilityLookup[AbilityStatEnum.WISDOM].modifier) !== null && _a !== void 0 ? _a : 0;
|
|
const contextValue = flagInfo.value === null ? 0 : flagInfo.value;
|
|
//AC will should be the highest value between the current AC and the owner's wisdom modifier + the context value
|
|
armorClass = Math.max(armorClass, contextValue + ownerWisdomModifier);
|
|
}
|
|
}
|
|
}
|
|
//Add any final bonuses to the AC;
|
|
return armorClass + bonuses;
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param valueTypeId
|
|
* @param fallback
|
|
* @param valueLookup
|
|
*/
|
|
function deriveOverridenValue(creature, valueTypeId, fallback, valueLookup) {
|
|
if (valueLookup) {
|
|
const override = ValueUtils.getKeyValue(valueLookup, valueTypeId, ValueHacks.hack__toString(getMappingId(creature)), ValueHacks.hack__toString(getMappingEntityTypeId(creature)));
|
|
if (override) {
|
|
return override;
|
|
}
|
|
}
|
|
return fallback;
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param valueLookup
|
|
*/
|
|
export function deriveSizeId(creature, valueLookup) {
|
|
return deriveOverridenValue(creature, AdjustmentTypeEnum.CREATURE_SIZE, getDefinitionSizeId(creature), valueLookup);
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param valueLookup
|
|
*/
|
|
export function deriveTypeId(creature, valueLookup) {
|
|
return deriveOverridenValue(creature, AdjustmentTypeEnum.CREATURE_TYPE_OVERRIDE, getDefinitionTypeId(creature), valueLookup);
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param valueLookup
|
|
*/
|
|
export function deriveAlignmentId(creature, valueLookup) {
|
|
return deriveOverridenValue(creature, AdjustmentTypeEnum.CREATURE_ALIGNMENT, getDefinitionAlignmentId(creature), valueLookup);
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param valueLookup
|
|
*/
|
|
export function deriveNotes(creature, valueLookup) {
|
|
return deriveOverridenValue(creature, AdjustmentTypeEnum.CREATURE_NOTES, null, valueLookup);
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param ruleData
|
|
*/
|
|
export function deriveLevel(creature, ruleData) {
|
|
if (hasGroupFlag(creature, CreatureGroupFlagEnum.USE_CHALLENGE_RATING_AS_LEVEL)) {
|
|
const challengeInfo = RuleDataUtils.getChallengeInfo(getChallengeRatingId(creature), ruleData);
|
|
const level = challengeInfo ? challengeInfo.value : 0;
|
|
const maxCharLevel = RuleDataAccessors.getMaxCharacterLevel(ruleData);
|
|
return HelperUtils.clampInt(level, 1, maxCharLevel);
|
|
}
|
|
return null;
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param ruleData
|
|
*/
|
|
export function deriveChallengeInfo(creature, ruleData) {
|
|
if (hasGroupFlag(creature, CreatureGroupFlagEnum.USE_CHALLENGE_RATING_AS_LEVEL)) {
|
|
return null;
|
|
}
|
|
return RuleDataUtils.getChallengeInfo(getChallengeRatingId(creature), ruleData);
|
|
}
|
|
export function deriveLairChallengeInfo(creature, ruleData) {
|
|
if (hasGroupFlag(creature, CreatureGroupFlagEnum.USE_CHALLENGE_RATING_AS_LEVEL)) {
|
|
return null;
|
|
}
|
|
return RuleDataUtils.getChallengeInfo(getLairChallengeRatingId(creature), ruleData);
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param groupInfo
|
|
* @param infusion
|
|
*/
|
|
export function deriveFlags(creature, groupInfo, infusion) {
|
|
const flags = [];
|
|
if (groupInfo && groupInfo.flags !== null) {
|
|
flags.push(...groupInfo.flags);
|
|
}
|
|
if (infusion) {
|
|
const creatureData = InfusionAccessors.getCreatureData(infusion);
|
|
if (creatureData) {
|
|
const foundCreatureData = creatureData.find((creatureDataEntry) => creatureDataEntry.monsterId === getId(creature));
|
|
if (foundCreatureData && foundCreatureData.flags !== null) {
|
|
flags.push(...foundCreatureData.flags);
|
|
}
|
|
}
|
|
}
|
|
return flags;
|
|
}
|
|
/**
|
|
*
|
|
* @param creature
|
|
* @param valueLookup
|
|
*/
|
|
export function deriveIsCustomized(creature, valueLookup) {
|
|
let isCustomized = ValueValidators.validateHasCustomization(CREATURE_CUSTOMIZATION_ADJUSTMENT_TYPES, valueLookup, ValueHacks.hack__toString(getMappingId(creature)), ValueHacks.hack__toString(getMappingEntityTypeId(creature)));
|
|
if (!isCustomized) {
|
|
if (creature.name !== null && creature.name !== '') {
|
|
isCustomized = true;
|
|
}
|
|
}
|
|
return isCustomized;
|
|
}
|