Merge branch 'develop' into feature-tabletop
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import action from './applyPropertyByType/applyAction.js';
|
||||
import ammo from './applyPropertyByType/applyItemAsAmmo.js'
|
||||
import adjustment from './applyPropertyByType/applyAdjustment.js';
|
||||
import branch from './applyPropertyByType/applyBranch.js';
|
||||
import buff from './applyPropertyByType/applyBuff.js';
|
||||
@@ -12,6 +13,7 @@ import toggle from './applyPropertyByType/applyToggle.js';
|
||||
|
||||
const applyPropertyByType = {
|
||||
action,
|
||||
ammo,
|
||||
adjustment,
|
||||
branch,
|
||||
buff,
|
||||
|
||||
@@ -4,13 +4,10 @@ import rollDice from '/imports/parser/rollDice.js';
|
||||
import applyProperty from '../applyProperty.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import applyChildren from '/imports/api/engine/actions/applyPropertyByType/shared/applyChildren.js';
|
||||
import { adjustQuantityWork } from '/imports/api/creature/creatureProperties/methods/adjustQuantity.js';
|
||||
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||
import numberToSignedString from '/imports/api/utility/numberToSignedString.js';
|
||||
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||
import { resetProperties } from '/imports/api/creature/creatures/methods/restCreature.js';
|
||||
import { getPropertyDecendants } from '/imports/api/engine/loadCreatures.js';
|
||||
import { nodeArrayToTree } from '/imports/api/parenting/nodesToTree.js';
|
||||
|
||||
export default function applyAction(node, actionContext) {
|
||||
applyNodeTriggers(node, 'before', actionContext);
|
||||
@@ -174,7 +171,11 @@ function rollAttack(attack, scope) {
|
||||
}
|
||||
|
||||
function applyCrits(value, scope) {
|
||||
const criticalHitTarget = scope['~criticalHitTarget']?.value || 20;
|
||||
let scopeCrit = scope['~criticalHitTarget']?.value;
|
||||
if (scopeCrit?.parseType === 'constant') {
|
||||
scopeCrit = scopeCrit.value;
|
||||
}
|
||||
const criticalHitTarget = scopeCrit || 20;
|
||||
let criticalHit = value >= criticalHitTarget;
|
||||
let criticalMiss;
|
||||
if (criticalHit) {
|
||||
@@ -206,10 +207,9 @@ function spendResources(prop, actionContext) {
|
||||
return true;
|
||||
}
|
||||
// Items
|
||||
let itemQuantityAdjustments = [];
|
||||
let spendLog = [];
|
||||
let gainLog = [];
|
||||
let ammoChildren = [];
|
||||
const ammoToApply = [];
|
||||
try {
|
||||
prop.resources.itemsConsumed.forEach(itemConsumed => {
|
||||
recalculateCalculation(itemConsumed.quantity, actionContext);
|
||||
@@ -224,11 +224,6 @@ function spendResources(prop, actionContext) {
|
||||
!itemConsumed?.quantity?.value ||
|
||||
!isFinite(itemConsumed.quantity.value)
|
||||
) return;
|
||||
itemQuantityAdjustments.push({
|
||||
property: item,
|
||||
operation: 'increment',
|
||||
value: itemConsumed.quantity.value,
|
||||
});
|
||||
let logName = item.name;
|
||||
if (itemConsumed.quantity.value > 1 || itemConsumed.quantity.value < -1) {
|
||||
logName = item.plural || logName;
|
||||
@@ -238,7 +233,20 @@ function spendResources(prop, actionContext) {
|
||||
} else if (itemConsumed.quantity.value < 0) {
|
||||
gainLog.push(logName + ': ' + -itemConsumed.quantity.value);
|
||||
}
|
||||
ammoChildren.push(...getItemChildren(item, actionContext, prop));
|
||||
// So long as the item isn't an ancestor of the current prop apply it
|
||||
// If it was an ancestor this would be an infinite loop
|
||||
if (!hasAncestorRelationship(item, prop)) {
|
||||
ammoToApply.push({
|
||||
node: {
|
||||
...item,
|
||||
// Use ammo pseudo-type
|
||||
type: 'ammo',
|
||||
// Store the adjustment to be applied
|
||||
adjustment: itemConsumed.quantity.value,
|
||||
},
|
||||
children: []
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
actionContext.addLog({
|
||||
@@ -249,9 +257,6 @@ function spendResources(prop, actionContext) {
|
||||
return true;
|
||||
}
|
||||
// No more errors should be thrown after this line
|
||||
// Now that we have confirmed that there are no errors, do actual work
|
||||
//Items
|
||||
itemQuantityAdjustments.forEach(adjustQuantityWork);
|
||||
|
||||
// Use uses
|
||||
if (prop.usesLeft) {
|
||||
@@ -291,6 +296,11 @@ function spendResources(prop, actionContext) {
|
||||
}
|
||||
});
|
||||
|
||||
// Apply the ammo children
|
||||
ammoToApply.forEach(node => {
|
||||
applyProperty(node, actionContext);
|
||||
});
|
||||
|
||||
// Log all the spending
|
||||
if (gainLog.length && !prop.silent) actionContext.addLog({
|
||||
name: 'Gained',
|
||||
@@ -302,21 +312,6 @@ function spendResources(prop, actionContext) {
|
||||
value: spendLog.join('\n'),
|
||||
inline: true,
|
||||
});
|
||||
|
||||
// Apply the ammo children
|
||||
ammoChildren.forEach(prop => {
|
||||
applyProperty(prop, actionContext);
|
||||
});
|
||||
}
|
||||
|
||||
function getItemChildren(item, actionContext, prop) {
|
||||
// Skip if the prop or the item are ancestors of one another, otherwise infinite loop
|
||||
if (hasAncestorRelationship(item, prop)) return [];
|
||||
// Get the item children
|
||||
const itemProperties = getPropertyDecendants(actionContext.creature._id, item._id);
|
||||
// Tree them up
|
||||
const propertyForest = nodeArrayToTree(itemProperties);
|
||||
return propertyForest
|
||||
}
|
||||
|
||||
function hasAncestorRelationship(a, b) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from '/imports/api/engine/loadCreatures.js';
|
||||
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||
import getEffectivePropTags from '/imports/api/engine/computation/utility/getEffectivePropTags.js';
|
||||
import applySavingThrow from '/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js';
|
||||
|
||||
export default function applyDamage(node, actionContext) {
|
||||
applyNodeTriggers(node, 'before', actionContext);
|
||||
@@ -36,7 +37,7 @@ export default function applyDamage(node, actionContext) {
|
||||
const logName = prop.damageType === 'healing' ? 'Healing' : 'Damage';
|
||||
|
||||
// roll the dice only and store that string
|
||||
applyEffectsToCalculationParseNode(prop.amount, actionContext.log);
|
||||
applyEffectsToCalculationParseNode(prop.amount, actionContext);
|
||||
const { result: rolled } = resolve('roll', prop.amount.parseNode, scope, context);
|
||||
if (rolled.parseType !== 'constant') {
|
||||
logValue.push(toString(rolled));
|
||||
@@ -67,6 +68,7 @@ export default function applyDamage(node, actionContext) {
|
||||
|
||||
// Round the damage to a whole number
|
||||
damage = Math.floor(damage);
|
||||
scope['~damage'] = damage;
|
||||
|
||||
// Convert extra damage into the stored type
|
||||
if (prop.damageType === 'extra' && scope['~lastDamageType']?.value) {
|
||||
@@ -82,24 +84,74 @@ export default function applyDamage(node, actionContext) {
|
||||
prop.damageType +
|
||||
(prop.damageType !== 'healing' ? ' damage ' : '');
|
||||
|
||||
// If there is a save, calculate the save damage
|
||||
let damageOnSave, saveNode, saveRoll;
|
||||
if (prop.save) {
|
||||
if (prop.save.damageFunction?.calculation) {
|
||||
applyEffectsToCalculationParseNode(prop.save.damageFunction, actionContext);
|
||||
let { result: saveDamageRolled } = resolve('roll', prop.save.damageFunction.parseNode, scope, context);
|
||||
saveRoll = toString(saveDamageRolled);
|
||||
let { result: saveDamageResult } = resolve('reduce', saveDamageRolled, scope, context);
|
||||
// If we didn't end up with a constant of finite amount, give up
|
||||
if (reduced?.parseType !== 'constant' || !isFinite(reduced.value)) {
|
||||
return applyChildren(node, actionContext);
|
||||
}
|
||||
damageOnSave = +saveDamageResult.value;
|
||||
// Round the damage to a whole number
|
||||
damageOnSave = Math.floor(damageOnSave);
|
||||
} else {
|
||||
damageOnSave = Math.floor(damage / 2);
|
||||
}
|
||||
saveNode = {
|
||||
node: {
|
||||
...prop.save,
|
||||
name: prop.save.stat,
|
||||
silent: prop.silent,
|
||||
},
|
||||
children: [],
|
||||
}
|
||||
}
|
||||
|
||||
if (damageTargets && damageTargets.length) {
|
||||
// Iterate through all the targets
|
||||
damageTargets.forEach(target => {
|
||||
actionContext.target = [target];
|
||||
let damageToApply = damage;
|
||||
|
||||
// If there is a saving throw, apply that first
|
||||
if (prop.save) {
|
||||
applySavingThrow(saveNode, actionContext);
|
||||
if (scope['~saveSucceeded']?.value) {
|
||||
// Log the total damage
|
||||
logValue.push(toString(reduced));
|
||||
// Log the save damage
|
||||
const damageText = damageFunctionText(prop.save);
|
||||
if (damageText) {
|
||||
logValue.push(damageText);
|
||||
} else {
|
||||
logValue.push(
|
||||
'**Damage on successful save**',
|
||||
prop.save.damageFunction.calculation,
|
||||
saveRoll
|
||||
);
|
||||
}
|
||||
damageToApply = damageOnSave;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply weaknesses/resistances/immunities
|
||||
damage = applyDamageMultipliers({
|
||||
damageToApply = applyDamageMultipliers({
|
||||
target,
|
||||
damage,
|
||||
damage: damageToApply,
|
||||
damageProp: prop,
|
||||
logValue
|
||||
});
|
||||
|
||||
actionContext.target = [target];
|
||||
// Deal the damage to the target
|
||||
let damageDealt = dealDamage({
|
||||
target,
|
||||
damageType: prop.damageType,
|
||||
amount: damage,
|
||||
amount: damageToApply,
|
||||
actionContext
|
||||
});
|
||||
|
||||
@@ -123,6 +175,10 @@ export default function applyDamage(node, actionContext) {
|
||||
} else {
|
||||
// There are no targets, just log the result
|
||||
logValue.push(`**${damage}** ${suffix}`);
|
||||
if (prop.save) {
|
||||
applySavingThrow(saveNode, actionContext);
|
||||
logValue.push(`**${damageOnSave}** ${suffix} on a successful save`);
|
||||
}
|
||||
}
|
||||
if (!prop.silent) actionContext.addLog({
|
||||
name: logName,
|
||||
@@ -132,6 +188,16 @@ export default function applyDamage(node, actionContext) {
|
||||
return applyChildren(node, actionContext);
|
||||
}
|
||||
|
||||
function damageFunctionText(save, scope, context, actionContext) {
|
||||
if (!save) return [];
|
||||
if (!save.damageFunction) {
|
||||
return '**Half damage on successful save**';
|
||||
}
|
||||
if (save.damageFunction.calculation == '0' || save.damageFunction.value === 0) {
|
||||
return '**No damage on successful save**'
|
||||
}
|
||||
}
|
||||
|
||||
function applyDamageMultipliers({ target, damage, damageProp, logValue }) {
|
||||
const damageType = damageProp?.damageType;
|
||||
if (!damageType) return damage;
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { getPropertyDecendants } from '/imports/api/engine/loadCreatures.js';
|
||||
import applyProperty from '../applyProperty.js';
|
||||
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||
import { nodeArrayToTree } from '/imports/api/parenting/nodesToTree.js';
|
||||
import { adjustQuantityWork } from '/imports/api/creature/creatureProperties/methods/adjustQuantity.js';
|
||||
|
||||
export default function applyItemAsAmmo(node, actionContext) {
|
||||
// The item node should come without children, since it is not part of the original action tree
|
||||
const prop = node.node;
|
||||
// Get all the item's descendant properties
|
||||
const properties = getPropertyDecendants(actionContext.creature._id, prop._id);
|
||||
properties.sort((a, b) => a.order - b.order);
|
||||
const propertyForest = nodeArrayToTree(properties);
|
||||
|
||||
// Apply the item
|
||||
applyNodeTriggers(node, 'before', actionContext);
|
||||
|
||||
// Do the quantity adjustment
|
||||
const itemProp = { ...prop, type: 'item' };
|
||||
delete itemProp.adjustment;
|
||||
adjustQuantityWork({
|
||||
property: itemProp,
|
||||
operation: 'increment',
|
||||
value: prop.adjustment,
|
||||
});
|
||||
|
||||
// Simulate the change to quantity
|
||||
prop.quantity -= prop.adjustment;
|
||||
|
||||
// Log the item name as a heading if it's not silent and has child properties to apply
|
||||
if (!prop.silent && propertyForest.length) {
|
||||
actionContext.addLog({
|
||||
name: prop.name || 'Ammo',
|
||||
inline: false,
|
||||
});
|
||||
}
|
||||
applyNodeTriggers(node, 'after', actionContext);
|
||||
|
||||
// Apply the item's children
|
||||
propertyForest.forEach(node => applyProperty(node, actionContext));
|
||||
applyNodeTriggers(node, 'afterChildren', actionContext);
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import walkDown from '/imports/api/engine/computation/utility/walkdown.js';
|
||||
|
||||
export default function computeInactiveStatus(node){
|
||||
export default function computeInactiveStatus(node) {
|
||||
const prop = node.node;
|
||||
if (!isActive(prop)){
|
||||
if (!isActive(prop)) {
|
||||
// Mark prop inactive due to self
|
||||
prop.inactive = true;
|
||||
prop.deactivatedBySelf = true;
|
||||
}
|
||||
if(!childrenActive(prop)){
|
||||
if (!childrenActive(prop)) {
|
||||
// Mark children as inactive due to ancestor
|
||||
walkDown(node.children, child => {
|
||||
child.node.inactive = true;
|
||||
@@ -16,27 +16,25 @@ export default function computeInactiveStatus(node){
|
||||
}
|
||||
}
|
||||
|
||||
function isActive(prop){
|
||||
function isActive(prop) {
|
||||
if (prop.disabled) return false;
|
||||
switch (prop.type){
|
||||
switch (prop.type) {
|
||||
// Unprepared spells are inactive
|
||||
case 'spell': return !!prop.prepared || !!prop.alwaysPrepared;
|
||||
default: return true;
|
||||
}
|
||||
}
|
||||
|
||||
function childrenActive(prop){
|
||||
function childrenActive(prop) {
|
||||
// Children of disabled properties are always inactive
|
||||
if (prop.disabled) return false;
|
||||
switch (prop.type){
|
||||
switch (prop.type) {
|
||||
// Only equipped items with non-zero quantity have active children
|
||||
case 'item': return !!prop.equipped && prop.quantity !== 0;
|
||||
// The children of actions, spells, and triggers are always inactive
|
||||
case 'action': return false;
|
||||
case 'spell': return false;
|
||||
case 'trigger': return false;
|
||||
// The children of notes are always inactive
|
||||
case 'note': return false;
|
||||
// Other children are active
|
||||
default: return true;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,15 @@ export default function linkCalculationDependencies(dependencyGraph, prop, { pro
|
||||
};
|
||||
// Add this calculation to the dependency graph
|
||||
const calcNodeId = `${prop._id}.${calcObj._key}`;
|
||||
dependencyGraph.addNode(calcNodeId, calcObj);
|
||||
|
||||
// Skip empty calculations that aren't targeted by anything
|
||||
if (
|
||||
!calcObj.calculation
|
||||
&& !calcObj.effects
|
||||
&& !calcObj.proficiencies
|
||||
) return;
|
||||
|
||||
dependencyGraph.addNode(calcNodeId, calcObj);
|
||||
// Traverse the parsed calculation looking for variable names
|
||||
traverse(calcObj.parseNode, node => {
|
||||
// Skip nodes that aren't symbols or accessors
|
||||
|
||||
@@ -98,8 +98,10 @@ function linkAdjustment(dependencyGraph, prop) {
|
||||
|
||||
function linkAttribute(dependencyGraph, prop) {
|
||||
linkVariableName(dependencyGraph, prop);
|
||||
// Depends on spellSlotLevel
|
||||
dependOnCalc({ dependencyGraph, prop, key: 'spellSlotLevel' });
|
||||
// Spell slots depend on spellSlotLevel
|
||||
if (prop.type === 'spellSlot') {
|
||||
dependOnCalc({ dependencyGraph, prop, key: 'spellSlotLevel' });
|
||||
}
|
||||
|
||||
// Depends on base value
|
||||
dependOnCalc({ dependencyGraph, prop, key: 'baseValue' });
|
||||
@@ -159,7 +161,7 @@ function linkEffects(dependencyGraph, prop, computation) {
|
||||
// Otherwise target a field on that property
|
||||
const key = prop.targetField || getDefaultCalculationField(targetProp);
|
||||
const calcObj = get(targetProp, key);
|
||||
if (calcObj && calcObj.calculation) {
|
||||
if (calcObj) {
|
||||
dependencyGraph.addLink(`${targetProp._id}.${key}`, prop._id, 'effect');
|
||||
}
|
||||
}
|
||||
@@ -175,7 +177,7 @@ function linkEffects(dependencyGraph, prop, computation) {
|
||||
// Returns an array of IDs of the properties the effect targets
|
||||
export function getEffectTagTargets(effect, computation) {
|
||||
let targets = getTargetListFromTags(effect.targetTags, computation);
|
||||
let notIds = [];
|
||||
let notIds = [effect._id]; // Can't target itself
|
||||
if (effect.extraTags) {
|
||||
effect.extraTags.forEach(ex => {
|
||||
if (ex.operation === 'OR') {
|
||||
@@ -257,21 +259,23 @@ function linkDamageMultiplier(dependencyGraph, prop) {
|
||||
function linkPointBuy(dependencyGraph, prop) {
|
||||
dependOnCalc({ dependencyGraph, prop, key: 'min' });
|
||||
dependOnCalc({ dependencyGraph, prop, key: 'max' });
|
||||
dependOnCalc({ dependencyGraph, prop, key: 'cost' });
|
||||
dependOnCalc({ dependencyGraph, prop, key: 'total' });
|
||||
prop.values?.forEach(row => {
|
||||
|
||||
prop.values?.forEach((row, index) => {
|
||||
// Get a unique id for the row because it might be shared among duplicated point buy tables
|
||||
// prop._id is forced unique by the database, so it can be used instead
|
||||
const uniqueRowId = prop._id + '_row_' + index;
|
||||
// Wrap the document in a new object so we don't bash it unintentionally
|
||||
const pointBuyRow = {
|
||||
...row,
|
||||
_id: uniqueRowId,
|
||||
type: 'pointBuyRow',
|
||||
tableName: prop.name,
|
||||
tableId: prop._id,
|
||||
}
|
||||
dependencyGraph.addNode(row._id, pointBuyRow);
|
||||
dependencyGraph.addNode(pointBuyRow._id, pointBuyRow);
|
||||
linkVariableName(dependencyGraph, pointBuyRow);
|
||||
dependOnCalc({ dependencyGraph, pointBuyRow, key: 'row.min' });
|
||||
dependOnCalc({ dependencyGraph, pointBuyRow, key: 'row.max' });
|
||||
dependOnCalc({ dependencyGraph, pointBuyRow, key: 'row.cost' });
|
||||
dependencyGraph.addLink(pointBuyRow._id, prop._id, 'pointBuyRow');
|
||||
});
|
||||
if (prop.inactive) return;
|
||||
}
|
||||
@@ -297,7 +301,7 @@ function linkProficiencies(dependencyGraph, prop, computation) {
|
||||
// Otherwise target a field on that property
|
||||
const key = prop.targetField || getDefaultCalculationField(targetProp);
|
||||
const calcObj = get(targetProp, key);
|
||||
if (calcObj && calcObj.calculation) {
|
||||
if (calcObj) {
|
||||
dependencyGraph.addLink(`${targetProp._id}.${key}`, prop._id, 'proficiency');
|
||||
}
|
||||
}
|
||||
@@ -335,7 +339,7 @@ function linkSkill(dependencyGraph, prop, computation) {
|
||||
// other skill isn't supported
|
||||
const key = prop.targetField || getDefaultCalculationField(targetProp);
|
||||
const calcObj = get(targetProp, key);
|
||||
if (calcObj && calcObj.calculation) {
|
||||
if (calcObj) {
|
||||
dependencyGraph.addLink(`${targetProp._id}.${key}`, prop._id, 'proficiency');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import INLINE_CALCULATION_REGEX from '/imports/constants/INLINE_CALCULTION_REGEX.js';
|
||||
import { prettifyParseError, parse } from '/imports/parser/parser.js';
|
||||
import applyFnToKey from '/imports/api/engine/computation/utility/applyFnToKey.js';
|
||||
import { get, unset } from 'lodash';
|
||||
import { get, set, unset } from 'lodash';
|
||||
import errorNode from '/imports/parser/parseTree/error.js';
|
||||
import cyrb53 from '/imports/api/engine/computation/utility/cyrb53.js';
|
||||
|
||||
@@ -63,12 +63,21 @@ function parseAllCalculationFields(prop, schemas) {
|
||||
// For all fields matching they keys
|
||||
// supports `keys.$.with.$.arrays`
|
||||
applyFnToKey(prop, calcKey, (prop, key) => {
|
||||
const calcObj = get(prop, key);
|
||||
let calcObj = get(prop, key);
|
||||
// Create a calculation object if one doesn't exist, it will get deleted again later if
|
||||
// it's not used, but if an effect targets a calculated field, we should have one to target
|
||||
if (
|
||||
!calcObj
|
||||
&& subDocsExist(prop, key)
|
||||
) {
|
||||
calcObj = {};
|
||||
set(prop, key, calcObj);
|
||||
}
|
||||
// Sub document didn't exist, skip this field
|
||||
if (!calcObj) return;
|
||||
// Delete the whole calculation object if the calculation string isn't set
|
||||
// Keep a list of empty calculations for potential deletion if they aren't used
|
||||
if (!calcObj.calculation) {
|
||||
unset(prop, calcKey);
|
||||
return;
|
||||
prop._computationDetails.emptyCalculations.push(calcObj);
|
||||
}
|
||||
// Store a reference to all the calculations
|
||||
prop._computationDetails.calculations.push(calcObj);
|
||||
@@ -84,15 +93,31 @@ function parseAllCalculationFields(prop, schemas) {
|
||||
});
|
||||
}
|
||||
|
||||
function subDocsExist(prop, key) {
|
||||
const path = key.split('.');
|
||||
if (path.length < 2) return !!prop;
|
||||
path.pop();
|
||||
const subPath = path.join('.');
|
||||
return !!get(prop, subPath);
|
||||
}
|
||||
|
||||
export function removeEmptyCalculations(prop) {
|
||||
prop._computationDetails.emptyCalculations.forEach(calcObj => {
|
||||
if (!calcObj.effects?.length) {
|
||||
unset(prop, calcObj._key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function parseCalculation(calcObj) {
|
||||
const calcHash = cyrb53(calcObj.calculation);
|
||||
const calcHash = cyrb53(calcObj.calculation || '0');
|
||||
// If the cached parse calculation is equal to the calculation, skip
|
||||
if (calcHash === calcObj.hash) {
|
||||
return;
|
||||
}
|
||||
calcObj.hash = calcHash;
|
||||
try {
|
||||
calcObj.parseNode = parse(calcObj.calculation);
|
||||
calcObj.parseNode = parse(calcObj.calculation || '0');
|
||||
calcObj.parseError = null;
|
||||
} catch (e) {
|
||||
let error = {
|
||||
|
||||
@@ -75,6 +75,7 @@ export function buildComputationFromProps(properties, creature, variables) {
|
||||
// Add a place to store all the computation details
|
||||
prop._computationDetails = {
|
||||
calculations: [],
|
||||
emptyCalculations: [],
|
||||
inlineCalculations: [],
|
||||
toggleAncestors: [],
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export default function computeAction(computation, node) {
|
||||
const prop = node.data;
|
||||
if (prop.uses) {
|
||||
if (Number.isFinite(prop.uses?.value)) {
|
||||
prop.usesLeft = prop.uses.value - (prop.usesUsed || 0);
|
||||
if (!prop.usesLeft) {
|
||||
prop.insufficientResources = true;
|
||||
|
||||
@@ -3,8 +3,8 @@ 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;
|
||||
const min = has(prop, 'min.value') ? prop.min.value : null;
|
||||
const max = has(prop, 'max.value') ? prop.max.value : null;
|
||||
prop.spent = 0;
|
||||
prop.values?.forEach(row => {
|
||||
// Clean up added properties
|
||||
@@ -14,9 +14,7 @@ export default function computePointBuy(computation, node) {
|
||||
|
||||
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);
|
||||
const costFunction = EJSON.clone(prop.cost);
|
||||
if (costFunction) costFunction.parseLevel = 'reduce';
|
||||
|
||||
// Check min and max
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import computeToggles from '/imports/api/engine/computation/computeComputation/computeToggles.js';
|
||||
import computeByType from '/imports/api/engine/computation/computeComputation/computeByType.js';
|
||||
import embedInlineCalculations from './utility/embedInlineCalculations.js';
|
||||
import { removeEmptyCalculations } from './buildComputation/parseCalculationFields.js';
|
||||
import path from 'ngraph.path';
|
||||
|
||||
export default function computeCreatureComputation(computation){
|
||||
export default function computeCreatureComputation(computation) {
|
||||
const stack = [];
|
||||
// Computation scope of {variableName: prop}
|
||||
const graph = computation.dependencyGraph;
|
||||
@@ -20,16 +21,16 @@ export default function computeCreatureComputation(computation){
|
||||
stack.reverse();
|
||||
|
||||
// Depth first traversal of nodes
|
||||
while (stack.length){
|
||||
while (stack.length) {
|
||||
let top = stack[stack.length - 1];
|
||||
if (top._visited){
|
||||
if (top._visited) {
|
||||
// The object has already been computed, skip
|
||||
stack.pop();
|
||||
} else if (top._visitedChildren){
|
||||
} else if (top._visitedChildren) {
|
||||
// Mark the object as visited and remove from stack
|
||||
top._visited = true;
|
||||
stack.pop();
|
||||
// Compute the top object of the stack
|
||||
// Compute the top object of the stack
|
||||
compute(computation, top);
|
||||
} else {
|
||||
top._visitedChildren = true;
|
||||
@@ -42,14 +43,14 @@ export default function computeCreatureComputation(computation){
|
||||
computation.props.forEach(finalizeProp);
|
||||
}
|
||||
|
||||
function compute(computation, node){
|
||||
function compute(computation, node) {
|
||||
// Determine the prop's active status by its toggles
|
||||
computeToggles(computation, node);
|
||||
// Compute the property by type
|
||||
computeByType[node.data?.type || '_variable']?.(computation, node);
|
||||
}
|
||||
|
||||
function pushDependenciesToStack(nodeId, graph, stack, computation){
|
||||
function pushDependenciesToStack(nodeId, graph, stack, computation) {
|
||||
graph.forEachLinkedNode(nodeId, linkedNode => {
|
||||
if (linkedNode._visitedChildren && !linkedNode._visited) {
|
||||
// This is a dependency loop, find a path from the node to itself
|
||||
@@ -66,7 +67,7 @@ function pushDependenciesToStack(nodeId, graph, stack, computation){
|
||||
loop = [linkedNode, ...newLoop];
|
||||
}
|
||||
}, true);
|
||||
|
||||
|
||||
if (loop.length) {
|
||||
computation.errors.push({
|
||||
type: 'dependencyLoop',
|
||||
@@ -80,11 +81,13 @@ function pushDependenciesToStack(nodeId, graph, stack, computation){
|
||||
}, true);
|
||||
}
|
||||
|
||||
function finalizeProp(prop){
|
||||
function finalizeProp(prop) {
|
||||
// Embed the inline calculations
|
||||
prop._computationDetails?.inlineCalculations?.forEach(inlineCalcObj => {
|
||||
embedInlineCalculations(inlineCalcObj);
|
||||
});
|
||||
// Clean up the calculations that were never used
|
||||
removeEmptyCalculations(prop);
|
||||
// Clean up the computation details
|
||||
delete prop._computationDetails;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user