diff --git a/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js b/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js index c729bf81..08eef012 100644 --- a/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js +++ b/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js @@ -14,6 +14,7 @@ const linkDependenciesByType = { effect: linkEffects, proficiency: linkProficiencies, roll: linkRoll, + pointBuy: linkPointBuy, propertySlot: linkSlot, skill: linkSkill, spell: linkAction, @@ -242,6 +243,23 @@ function linkDamageMultiplier(dependencyGraph, prop) { }); } +function linkPointBuy(dependencyGraph, prop){ + dependOnCalc({ dependencyGraph, prop, key: 'min' }); + dependOnCalc({ dependencyGraph, prop, key: 'max' }); + dependOnCalc({ dependencyGraph, prop, key: 'cost' }); + prop.values?.forEach(row => { + row.type = 'pointBuyRow'; + row.tableName = prop.name; + row.tableId = prop._id; + dependencyGraph.addNode(row._id, row); + linkVariableName(dependencyGraph, row); + dependOnCalc({ dependencyGraph, row, key: 'min' }); + dependOnCalc({ dependencyGraph, row, key: 'max' }); + dependOnCalc({ dependencyGraph, row, key: 'cost' }); + }); + if (prop.inactive) return; +} + function linkProficiencies(dependencyGraph, prop){ // The stats depend on the proficiency if (prop.inactive) return; diff --git a/app/imports/api/engine/computation/computeComputation/computeByType.js b/app/imports/api/engine/computation/computeComputation/computeByType.js index b6ccff38..7597b976 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType.js @@ -2,6 +2,7 @@ import _variable from './computeByType/computeVariable.js'; import action from './computeByType/computeAction.js'; import attribute from './computeByType/computeAttribute.js'; import skill from './computeByType/computeSkill.js'; +import pointBuy from './computeByType/computePointBuy.js'; import propertySlot from './computeByType/computeSlot.js'; import container from './computeByType/computeContainer.js'; import _calculation from './computeByType/computeCalculation.js'; @@ -13,6 +14,7 @@ export default Object.freeze({ attribute, container, skill, + pointBuy, propertySlot, spell: action, }); diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computePointBuy.js b/app/imports/api/engine/computation/computeComputation/computeByType/computePointBuy.js new file mode 100644 index 00000000..a1d4cbbd --- /dev/null +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computePointBuy.js @@ -0,0 +1,50 @@ +import { has } from 'lodash'; +import evaluateCalculation from '../../utility/evaluateCalculation.js'; + +export default function computePointBuy(computation, node) { + const prop = node.data; + const tableMin = prop.min?.value || null; + const tableMax = prop.max?.value || null; + prop.spent = 0; + prop.values?.forEach(row => { + // Clean up added properties + delete row.tableId; + delete row.tableName; + delete row.type; + + row.spent = 0; + if (row.value === undefined) return; + const min = has(row, 'min.value') ? row.min.value : tableMin; + const max = has(row, 'max.value') ? row.max.value : tableMax; + const costFunction = EJSON.clone(row.cost || prop.cost); + if (costFunction) costFunction.parseLevel = 'reduce'; + + // Check min and max + if (min !== null && row.value < min) { + row.errors = row.errors || []; + row.errors.push('Value smaller than min value'); + } + if (max !== null && row.value > max) { + row.errors = row.errors || []; + row.errors.push('Value larger than max value'); + } + // Evaluate the cost function + if (!costFunction) return; + evaluateCalculation(costFunction, { ...computation.scope, value: row.value }); + // Write calculation errors + costFunction.errors?.forEach(error => { + if (error?.message) { + row.errors = row.errors || []; + row.errors.push(error.message); + } + }); + if (Number.isFinite(costFunction.value)) { + row.spent = costFunction.value; + prop.spent += costFunction.value; + } + }); + if (prop.spent > prop.total?.value) { + prop.errors = prop.errors || []; + prop.errors.push('Spent more than total points available') + } +} diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateDefinition.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateDefinition.js index 66fbe79a..d665b284 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateDefinition.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateDefinition.js @@ -8,7 +8,13 @@ export default function aggregateDefinition({node, linkedNode, link}){ // get current defining prop const definingProp = node.data.definingProp; // Find the last defining prop - if (!definingProp || prop.order > definingProp.order){ + if ( + !definingProp || + prop.type !== 'pointBuyRow' && ( + definingProp.type === 'pointBuyRow' || + prop.order > definingProp.order + ) + ) { // override the current defining prop overrideProp(definingProp, node); // set this prop as the new defining prop @@ -18,9 +24,32 @@ export default function aggregateDefinition({node, linkedNode, link}){ } // Aggregate the base value due to the defining properties - const propBaseValue = prop.baseValue?.value; + let propBaseValue = prop.baseValue?.value; + // Point buy rows use prop.value instead of prop.baseValue + if (prop.type === 'pointBuyRow') { + propBaseValue = prop.value; + } if (propBaseValue === undefined) return; + // Store a summary of the definition as a base value effect + node.data.effects = node.data.effects || []; + if (prop.type === 'pointBuyRow') { + node.data.effects.push({ + _id: prop.tableId, + name: prop.tableName, + operation: 'base', + amount: { value: propBaseValue }, + type: 'pointBuy', + }); + } else { + node.data.effects.push({ + _id: prop._id, + name: prop.name, + operation: 'base', + amount: { value: propBaseValue }, + type: prop.type, + }); + } if (node.data.baseValue === undefined || propBaseValue > node.data.baseValue){ node.data.baseValue = propBaseValue; } diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateEffect.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateEffect.js index 8cf47d36..e4a58317 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateEffect.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateEffect.js @@ -24,6 +24,7 @@ export default function aggregateEffect({node, linkedNode, link}){ name: linkedNode.data.name, operation: linkedNode.data.operation, amount: linkedNode.data.amount && {value: linkedNode.data.amount.value}, + type: linkedNode.data.type, // ancestors: linkedNode.data.ancestors, }); diff --git a/app/imports/api/properties/Attributes.js b/app/imports/api/properties/Attributes.js index 38721928..51174362 100644 --- a/app/imports/api/properties/Attributes.js +++ b/app/imports/api/properties/Attributes.js @@ -176,6 +176,7 @@ let ComputedOnlyAttributeSchema = createPropertySchema({ effects: { type: Array, optional: true, + removeBeforeCompute: true, }, 'effects.$': { type: Object, diff --git a/app/imports/api/properties/PointBuys.js b/app/imports/api/properties/PointBuys.js index 3be53ae2..24950add 100644 --- a/app/imports/api/properties/PointBuys.js +++ b/app/imports/api/properties/PointBuys.js @@ -13,13 +13,6 @@ let PointBuySchema = createPropertySchema({ optional: true, max: STORAGE_LIMITS.name, }, - variableName: { - type: String, - optional: true, - regEx: VARIABLE_NAME_REGEX, - min: 2, - max: STORAGE_LIMITS.variableName, - }, ignored: { type: Boolean, optional: true, @@ -82,6 +75,7 @@ let PointBuySchema = createPropertySchema({ cost: { type: 'fieldToCompute', optional: true, + parseLevel: 'compile', }, }); @@ -97,6 +91,7 @@ const ComputedOnlyPointBuySchema = createPropertySchema({ cost: { type: 'computedOnlyField', optional: true, + parseLevel: 'compile', }, 'values': { type: Array, @@ -117,6 +112,20 @@ const ComputedOnlyPointBuySchema = createPropertySchema({ 'values.$.cost': { type: 'computedOnlyField', optional: true, + parseLevel: 'compile', + }, + 'values.$.spent': { + type: Number, + optional: true, + removeBeforeCompute: true, + }, + 'values.$.errors': { + type: Array, + optional: true, + removeBeforeCompute: true, + }, + 'values.$.errors.$': { + type: String, }, total: { type: 'computedOnlyField', @@ -127,11 +136,14 @@ const ComputedOnlyPointBuySchema = createPropertySchema({ optional: true, removeBeforeCompute: true, }, - error: { - type: String, + errors: { + type: Array, optional: true, removeBeforeCompute: true, }, + 'errors.$': { + type: String, + }, }); const ComputedPointBuySchema = new SimpleSchema() diff --git a/app/imports/ui/creature/character/CharacterCreationDialog.vue b/app/imports/ui/creature/character/CharacterCreationDialog.vue index 568ae394..17fca049 100644 --- a/app/imports/ui/creature/character/CharacterCreationDialog.vue +++ b/app/imports/ui/creature/character/CharacterCreationDialog.vue @@ -98,7 +98,7 @@ Next {{ displayedText }} -
+