From 50cb6185ce52e6304b32869434ac12a24560c8b0 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Fri, 17 Mar 2023 17:45:05 +0200 Subject: [PATCH] Added proficiency target by tag to backend --- .../buildComputation/linkTypeDependencies.js | 32 +++++++++-- .../computeByType/computeCalculation.js | 54 +++++++++++++++++-- app/imports/api/properties/Proficiencies.js | 50 +++++++++++++++++ 3 files changed, 127 insertions(+), 9 deletions(-) diff --git a/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js b/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js index 7ed69d19..80d72970 100644 --- a/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js +++ b/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js @@ -268,13 +268,35 @@ function linkPointBuy(dependencyGraph, prop) { if (prop.inactive) return; } -function linkProficiencies(dependencyGraph, prop) { +function linkProficiencies(dependencyGraph, prop, computation) { // The stats depend on the proficiency if (prop.inactive) return; - prop.stats.forEach(statName => { - if (!statName) return; - dependencyGraph.addLink(statName, prop._id, prop.type); - }); + if (prop.targetByTags) { + getEffectTagTargets(prop, computation).forEach(targetId => { + const targetProp = computation.propsById[targetId]; + if ( + (targetProp.type === 'attribute' || targetProp.type === 'skill') + && targetProp.variableName + && !prop.targetField + ) { + // If the field wasn't specified and we're targeting an attribute or + // skill, just treat it like a normal proficiency on its variable name + dependencyGraph.addLink(targetProp.variableName, prop._id, 'proficiency'); + } else { + // Otherwise target a field on that property + const key = prop.targetField || getDefaultCalculationField(targetProp); + const calcObj = get(targetProp, key); + if (calcObj && calcObj.calculation) { + dependencyGraph.addLink(`${targetProp._id}.${key}`, prop._id, 'proficiency'); + } + } + }); + } else { + prop.stats.forEach(statName => { + if (!statName) return; + dependencyGraph.addLink(statName, prop._id, 'proficiency'); + }); + } } function linkSavingThrow(dependencyGraph, prop) { diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeCalculation.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeCalculation.js index 054fc7f9..3bd59ab4 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeCalculation.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeCalculation.js @@ -1,12 +1,13 @@ import evaluateCalculation from '../../utility/evaluateCalculation.js'; -export default function computeCalculation(computation, node){ +export default function computeCalculation(computation, node) { const calcObj = node.data; evaluateCalculation(calcObj, computation.scope); aggregateCalculationEffects(node, computation); + aggregateCalculationProficiencies(node, computation); } -export function aggregateCalculationEffects(node, computation){ +function aggregateCalculationEffects(node, computation) { const calcObj = node.data; delete calcObj.effects; computation.dependencyGraph.forEachLinkedNode( @@ -34,15 +35,60 @@ export function aggregateCalculationEffects(node, computation){ }, true // enumerate only outbound links ); - if (calcObj.effects && typeof calcObj.value === 'number'){ + if (calcObj.effects && typeof calcObj.value === 'number') { calcObj.baseValue = calcObj.value; calcObj.effects.forEach(effect => { if ( effect.operation === 'add' && effect.amount && typeof effect.amount.value === 'number' - ){ + ) { calcObj.value += effect.amount.value } }); } } + +function aggregateCalculationProficiencies(node, computation) { + const calcObj = node.data; + delete calcObj.proficiencies; + delete calcObj.proficiency; + computation.dependencyGraph.forEachLinkedNode( + node.id, + (linkedNode, link) => { + // Only proficiency links + if (link.data !== 'proficiency') return; + // That have data + if (!linkedNode.data) return; + // Ignore inactive props + if (linkedNode.data.inactive) return; + + // Collate effects + calcObj.proficiencies = calcObj.proficiencies || []; + calcObj.proficiencies.push({ + _id: linkedNode.data._id, + name: linkedNode.data.name, + value: linkedNode.data.value, + }); + }, + true // enumerate only outbound links + ); + if (calcObj.proficiencies) { + calcObj.proficiency = 0; + calcObj.proficiencies.forEach(prof => { + if (prof.value > calcObj.proficiency) { + calcObj.proficiency = prof.value; + } + }); + // Get the character's proficiency bonus to apply + let profBonus = computation.scope['proficiencyBonus']?.value || 0; + + // Multiply the proficiency bonus by the actual proficiency + if (calcObj.proficiency === 0.49) { + // Round down proficiency bonus in the special case + calcObj.proficiencyBonus = Math.floor(profBonus * 0.5); + } else { + calcObj.proficiencyBonus = Math.ceil(profBonus * calcObj.proficiency); + } + calcObj.value += calcObj.proficiencyBonus; + } +} diff --git a/app/imports/api/properties/Proficiencies.js b/app/imports/api/properties/Proficiencies.js index e79276ff..8c33fefb 100644 --- a/app/imports/api/properties/Proficiencies.js +++ b/app/imports/api/properties/Proficiencies.js @@ -24,6 +24,56 @@ let ProficiencySchema = new SimpleSchema({ allowedValues: [0.49, 0.5, 1, 2], defaultValue: 1, }, + // True when targeting by tags instead of stats + targetByTags: { + type: Boolean, + optional: true, + }, + // If targeting by tags, the field which will be targeted + targetField: { + type: String, + optional: true, + max: STORAGE_LIMITS.variableName, + }, + // Which tags the effect is applied to + targetTags: { + type: Array, + optional: true, + maxCount: STORAGE_LIMITS.tagCount, + }, + 'targetTags.$': { + type: String, + max: STORAGE_LIMITS.tagLength, + }, + extraTags: { + type: Array, + optional: true, + maxCount: STORAGE_LIMITS.extraTagsCount, + }, + 'extraTags.$': { + type: Object, + }, + 'extraTags.$._id': { + type: String, + regEx: SimpleSchema.RegEx.Id, + autoValue() { + if (!this.isSet) return Random.id(); + } + }, + 'extraTags.$.operation': { + type: String, + allowedValues: ['OR', 'NOT'], + defaultValue: 'OR', + }, + 'extraTags.$.tags': { + type: Array, + defaultValue: [], + maxCount: STORAGE_LIMITS.tagCount, + }, + 'extraTags.$.tags.$': { + type: String, + max: STORAGE_LIMITS.tagLength, + }, }); const ComputedOnlyProficiencySchema = new SimpleSchema({});