diff --git a/app/.meteor/packages b/app/.meteor/packages
index 232dc1ac..f87dac0b 100644
--- a/app/.meteor/packages
+++ b/app/.meteor/packages
@@ -11,7 +11,7 @@ accounts-google@1.4.0
email@2.2.0
meteor-base@1.5.1
mobile-experience@1.1.0
-mongo@1.14.0
+mongo@1.14.6
session@1.2.0
tracker@1.2.0
logging@1.3.1
diff --git a/app/.meteor/release b/app/.meteor/release
index a19cd698..b1b0cceb 100644
--- a/app/.meteor/release
+++ b/app/.meteor/release
@@ -1 +1 @@
-METEOR@2.6
+METEOR@2.6.1
diff --git a/app/.meteor/versions b/app/.meteor/versions
index 1a35a30c..b8171bc3 100644
--- a/app/.meteor/versions
+++ b/app/.meteor/versions
@@ -12,7 +12,7 @@ aldeed:collection2@3.5.0
aldeed:schema-index@3.0.0
allow-deny@1.1.1
autoupdate@1.8.0
-babel-compiler@7.8.0
+babel-compiler@7.8.1
babel-runtime@1.5.0
base64@1.0.12
binary-heap@1.0.11
@@ -68,7 +68,7 @@ mobile-status-bar@1.1.0
modern-browsers@0.1.7
modules@0.18.0
modules-runtime@0.12.0
-mongo@1.14.4
+mongo@1.14.6
mongo-decimal@0.1.2
mongo-dev-server@1.1.0
mongo-id@1.0.8
@@ -121,6 +121,6 @@ tracker@1.2.0
typescript@4.4.1
underscore@1.0.10
url@1.3.2
-webapp@1.13.0
+webapp@1.13.1
webapp-hashing@1.1.0
zer0th:meteor-vuetify-loader@0.1.41
diff --git a/app/imports/api/creature/creatures/Creatures.js b/app/imports/api/creature/creatures/Creatures.js
index 375d29b9..e75eb3c6 100644
--- a/app/imports/api/creature/creatures/Creatures.js
+++ b/app/imports/api/creature/creatures/Creatures.js
@@ -120,6 +120,20 @@ let CreatureSchema = new SimpleSchema({
blackbox: true,
defaultValue: {}
},
+ computeErrors: {
+ type: Array,
+ optional: true,
+ },
+ 'computeErrors.$': {
+ type: Object,
+ },
+ 'computeErrors.$.type': {
+ type: String,
+ },
+ 'computeErrors.$.details' : {
+ type: Object,
+ blackbox: true,
+ },
// Tabletop
tabletop: {
diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js
index 2f5a5be0..d8daf1d1 100644
--- a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js
+++ b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js
@@ -5,6 +5,7 @@ import applyProperty from '../applyProperty.js';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.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/ui/utility/numberToSignedString.js';
export default function applyAction(node, {creature, targets, scope, log}){
const prop = node.node;
@@ -24,16 +25,18 @@ export default function applyAction(node, {creature, targets, scope, log}){
const failed = spendResources({prop, log, scope});
if (failed) return;
+ const attack = prop.attackRoll || prop.attackRollBonus;
+
// Attack if there is an attack roll
- if (prop.attackRoll && prop.attackRoll.calculation){
+ if (attack && attack.calculation){
if (targets.length){
targets.forEach(target => {
- applyAttackToTarget({prop, target, scope, log});
+ applyAttackToTarget({attack, target, scope, log});
// Apply the children, but only to the current target
applyChildren(node, {targets: [target], scope, log});
});
} else {
- applyAttackWithoutTarget({prop, scope, log});
+ applyAttackWithoutTarget({attack, scope, log});
applyChildren(node, {creature, targets, scope, log});
}
} else {
@@ -41,44 +44,34 @@ export default function applyAction(node, {creature, targets, scope, log}){
}
}
-function applyAttackWithoutTarget({prop, scope, log}){
+function applyAttackWithoutTarget({attack, scope, log}){
delete scope['$attackHit'];
delete scope['$attackMiss'];
delete scope['$criticalHit'];
delete scope['$criticalMiss'];
delete scope['$attackRoll'];
- recalculateCalculation(prop.attackRoll, scope, log);
+ recalculateCalculation(attack, scope, log);
- let value = rollDice(1, 20)[0];
- scope['$attackRoll'] = {value};
- let criticalHitTarget = scope.criticalHitTarget?.value || 20;
- let criticalHit = value >= criticalHitTarget;
- if (criticalHit){
- scope['$criticalHit'] = {value: true};
- scope['$attackHit'] = {value: true};
- } else {
- let criticalMiss = value === 1;
- if (criticalMiss){
- scope['$criticalMiss'] = 1;
- log.content.push({
- name: 'Critical Miss!',
- });
- scope['$attackMiss'] = {value: true};
- } else {
- // Untargeted attacks hit by default
- scope['$attackHit'] = {value: true}
- }
+ let {
+ resultPrefix,
+ result,
+ criticalHit,
+ criticalMiss,
+ } = rollAttack(attack, scope);
+ let name = criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical Miss!' : 'To Hit';
+ if (scope['$attackAdvantage'] === 1){
+ name += ' (Advantage)';
+ } else if(scope['$attackAdvantage'] === -1){
+ name += ' (Disadvantage)';
}
- let result = value + prop.attackRoll.value;
- scope['$attackRoll'] = {value: result};
log.content.push({
- name: criticalHit ? 'Critical Hit!' : 'To Hit',
- value: `1d20 [${value}] + ${prop.attackRoll.value} = ` + result,
+ name,
+ value: `${resultPrefix} **${result}**`,
});
}
-function applyAttackToTarget({prop, target, scope, log}){
+function applyAttackToTarget({attack, target, scope, log}){
delete scope['$attackHit'];
delete scope['$attackMiss'];
delete scope['$criticalHit'];
@@ -86,26 +79,30 @@ function applyAttackToTarget({prop, target, scope, log}){
delete scope['$attackDiceRoll'];
delete scope['$attackRoll'];
- recalculateCalculation(prop.attackRoll, scope, log);
+ recalculateCalculation(attack, scope, log);
+
+ let {
+ resultPrefix,
+ result,
+ criticalHit,
+ criticalMiss,
+ } = rollAttack(attack, scope);
- const value = rollDice(1, 20)[0];
- scope['$attackDiceRoll'] = {value};
- const criticalHitTarget = scope.criticalHitTarget?.value || 20;
- const criticalHit = value >= criticalHitTarget;
- const criticalMiss = value === 1;
- if (criticalHit) scope['$criticalHit'] = {value: true};
- if (criticalMiss) scope['$criticalMiss'] = {value: true};
- const result = value + prop.attackRoll.value;
- scope['$attackRoll'] = {value: result};
if (target.variables.armor){
const armor = target.variables.armor.value;
- const name = criticalHit ? 'Critical Hit!' :
- criticalMiss ? 'Critical miss!' :
- result > armor ? 'Hit!' :
- 'Miss!'
+
+ let name = criticalHit ? 'Critical Hit!' :
+ criticalMiss ? 'Critical Miss!' :
+ result > armor ? 'Hit!' : 'Miss!';
+ if (scope['$attackAdvantage'] === 1){
+ name += ' (Advantage)';
+ } else if(scope['$attackAdvantage'] === -1){
+ name += ' (Disadvantage)';
+ }
+
log.content.push({
name,
- value: `1d20 {${value}} + ${prop.attackRoll.value} = ` + result,
+ value: `${resultPrefix} **${result}**`,
});
if ((result > armor) || (criticalHit)){
scope['$attackHit'] = true;
@@ -118,12 +115,63 @@ function applyAttackToTarget({prop, target, scope, log}){
value:'Target has no `armor`',
});
log.content.push({
- name: criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical miss!' : 'To Hit',
- value: `1d20 {${value}} + ${prop.attackRoll.value} = ` + result,
+ name: criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical Miss!' : 'To Hit',
+ value: `${resultPrefix} **${result}**`,
});
}
}
+function rollAttack(attack, scope){
+ const rollModifierText = numberToSignedString(attack.value, true);
+ let value, resultPrefix;
+ if (attack.advantage === 1 || scope['$attackAdvantage']){
+ const [a, b] = rollDice(2, 20);
+ if (a >= b) {
+ value = a;
+ resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText} = `;
+ } else {
+ value = b;
+ resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText} = `;
+ }
+ } else if (attack.advantage === -1 || scope['$attackDisadvantage']){
+ const [a, b] = rollDice(2, 20);
+ if (a <= b) {
+ value = a;
+ resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText} = `;
+ } else {
+ value = b;
+ resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText} = `;
+ }
+ } else {
+ value = rollDice(1, 20)[0];
+ resultPrefix = `1d20 [${value}] ${rollModifierText} = `
+ }
+ scope['$attackRoll'] = {value};
+ const result = value + attack.value;
+ const {criticalHit, criticalMiss} = applyCrits(value, scope);
+ return {resultPrefix, result, value, criticalHit, criticalMiss};
+}
+
+function applyCrits(value, scope){
+ let criticalHitTarget = scope.criticalHitTarget?.value || 20;
+ let criticalHit = value >= criticalHitTarget;
+ let criticalMiss;
+ if (criticalHit){
+ scope['$criticalHit'] = {value: true};
+ scope['$attackHit'] = {value: true};
+ } else {
+ criticalMiss = value === 1;
+ if (criticalMiss){
+ scope['$criticalMiss'] = 1;
+ scope['$attackMiss'] = {value: true};
+ } else {
+ // Untargeted attacks hit by default
+ scope['$attackHit'] = {value: true}
+ }
+ }
+ return {criticalHit, criticalMiss};
+}
+
function applyChildren(node, args){
node.children.forEach(child => applyProperty(child, args));
}
diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js b/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js
index 2368b0cb..6c10a5ba 100644
--- a/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js
+++ b/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js
@@ -88,16 +88,16 @@ export default function applyDamage(node, {
// Log the damage done
if (target._id === creature._id){
// Target is same as self, log damage as such
- logValue.push(damageDealt + suffix + ' to self');
+ logValue.push(`**${damageDealt}** ${suffix} to self`);
} else {
- logValue.push('Dealt ' + damageDealt + suffix + ` ${target.name && ' to '}${target.name}`);
+ logValue.push(`Dealt **${damageDealt}** ${suffix} ${target.name && ' to '}${target.name}`);
// Log the damage received on that creature's log as well
insertCreatureLog.call({
log: {
creatureId: target._id,
content: [{
name,
- value: 'Recieved ' + damageDealt + suffix,
+ value: `Recieved **${damageDealt}** ${suffix}`,
}],
}
});
@@ -105,7 +105,7 @@ export default function applyDamage(node, {
});
} else {
// There are no targets, just log the result
- logValue.push(damage + suffix);
+ logValue.push(`**${damage}** ${suffix}`);
}
log.content.push({
name: logName,
diff --git a/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js b/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js
index b6855e19..b55525ed 100644
--- a/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js
+++ b/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js
@@ -1,6 +1,7 @@
import rollDice from '/imports/parser/rollDice.js';
import recalculateCalculation from './shared/recalculateCalculation.js';
import applyProperty from '../applyProperty.js';
+import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
export default function applySavingThrow(node, {creature, targets, scope, log}){
const prop = node.node;
@@ -46,19 +47,31 @@ export default function applySavingThrow(node, {creature, targets, scope, log}){
return applyChildren();
}
+ const rollModifierText = numberToSignedString(save.value, true);
+
let value, values, resultPrefix;
if (save.advantage === 1){
- values = rollDice(2, 20).sort().reverse();
- value = values[0];
- resultPrefix = `Advantage: 1d20 [${values[0]},~~${values[1]}~~] + ${save.value} = `
+ const [a, b] = rollDice(2, 20);
+ if (a >= b) {
+ value = a;
+ resultPrefix = `Advantage: 1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText} = `;
+ } else {
+ value = b;
+ resultPrefix = `Advantage: 1d20 [ ~~${a}~~, ${b} ] ${rollModifierText} = `;
+ }
} else if (save.advantage === -1){
- values = rollDice(2, 20).sort();
- value = values[0];
- resultPrefix = `Disadvantage: 1d20 [${values[0]},~~${values[1]}~~] + ${save.value} = `
+ const [a, b] = rollDice(2, 20);
+ if (a <= b) {
+ value = a;
+ resultPrefix = `Disadvantage: 1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText} = `;
+ } else {
+ value = b;
+ resultPrefix = `Disadvantage: 1d20 [ ~~${a}~~, ${b} ] ${rollModifierText} = `;
+ }
} else {
values = rollDice(1, 20);
value = values[0];
- resultPrefix = `1d20 [${value}] + ${save.value} = `
+ resultPrefix = `1d20 [ ${value} ] ${rollModifierText} = `
}
scope['$saveDiceRoll'] = {value};
const result = value + save.value || 0;
diff --git a/app/imports/api/engine/actions/doAction.js b/app/imports/api/engine/actions/doAction.js
index 6a275b34..83658f62 100644
--- a/app/imports/api/engine/actions/doAction.js
+++ b/app/imports/api/engine/actions/doAction.js
@@ -24,13 +24,18 @@ const doAction = new ValidatedMethod({
type: String,
regEx: SimpleSchema.RegEx.Id,
},
+ scope: {
+ type: Object,
+ blackbox: true,
+ optional: true,
+ },
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 10,
timeInterval: 5000,
},
- run({actionId, targetIds = []}) {
+ run({actionId, targetIds = [], scope}) {
let action = CreatureProperties.findOne(actionId);
// Check permissions
let creature = getRootCreatureAncestor(action);
@@ -69,7 +74,7 @@ const doAction = new ValidatedMethod({
});
// Do the action
- doActionWork({creature, targets, properties, ancestors, method: this});
+ doActionWork({creature, targets, properties, ancestors, method: this, methodScope: scope});
// Recompute all involved creatures
computeCreature(creature._id);
@@ -82,7 +87,7 @@ const doAction = new ValidatedMethod({
export default doAction;
export function doActionWork({
- creature, targets, properties, ancestors, method
+ creature, targets, properties, ancestors, method, methodScope = {}
}){
// get the docs
const ancestorScope = getAncestorScope(ancestors);
@@ -102,6 +107,7 @@ export function doActionWork({
const scope = {
...creature.variables,
...ancestorScope,
+ ...methodScope
}
applyProperty(propertyForest[0], {
creature,
diff --git a/app/imports/api/engine/actions/doCheck.js b/app/imports/api/engine/actions/doCheck.js
new file mode 100644
index 00000000..ce66db02
--- /dev/null
+++ b/app/imports/api/engine/actions/doCheck.js
@@ -0,0 +1,114 @@
+import SimpleSchema from 'simpl-schema';
+import { ValidatedMethod } from 'meteor/mdg:validated-method';
+import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
+import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
+import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
+import { CreatureLogSchema, insertCreatureLogWork } from '/imports/api/creature/log/CreatureLogs.js';
+import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
+import computeCreature from '/imports/api/engine/computeCreature.js';
+import rollDice from '/imports/parser/rollDice.js';
+import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
+
+const doCheck = new ValidatedMethod({
+ name: 'creatureProperties.doCheck',
+ validate: new SimpleSchema({
+ propId: SimpleSchema.RegEx.Id,
+ scope: {
+ type: Object,
+ blackbox: true,
+ },
+ }).validator(),
+ mixins: [RateLimiterMixin],
+ rateLimit: {
+ numRequests: 10,
+ timeInterval: 5000,
+ },
+ run({propId, scope}) {
+ const prop = CreatureProperties.findOne(propId);
+ const creature = getRootCreatureAncestor(prop);
+
+ // Check permissions
+ assertEditPermission(creature, this.userId);
+
+ // Do the check
+ doCheckWork({creature, prop, method: this, methodScope: scope});
+
+ // Recompute all involved creatures
+ computeCreature(creature._id);
+ },
+});
+
+export default doCheck;
+
+export function doCheckWork({
+ creature, prop, method, methodScope = {}
+}){
+ // Create the log
+ let log = CreatureLogSchema.clean({
+ creatureId: creature._id,
+ creatureName: creature.name,
+ });
+
+ rollCheck({prop, log, methodScope});
+
+ // Insert the log
+ insertCreatureLogWork({log, creature, method});
+}
+
+function rollCheck({prop, log, methodScope}){
+ // get the modifier for the roll
+ let rollModifier;
+ let logName = `${prop.name} check`;
+ if (prop.type === 'skill'){
+ rollModifier = prop.value;
+ if (prop.skillType === 'save'){
+ if (prop.name.match(/save/i)){
+ logName = prop.name;
+ } else {
+ logName = prop.name ? `${prop.name} save` : 'Saving Throw';
+ }
+ }
+ } else if (prop.type === 'attribute'){
+ if (prop.attributeType === 'ability'){
+ rollModifier = prop.modifier;
+ } else {
+ rollModifier = prop.value;
+ }
+ } else {
+ throw (`${prop.type} not supported for checks`);
+ }
+
+ const rollModifierText = numberToSignedString(rollModifier, true);
+
+ let value, values, resultPrefix;
+ if (methodScope['$checkAdvantage'] === 1){
+ logName += ' (Advantage)';
+ const [a, b] = rollDice(2, 20);
+ if (a >= b) {
+ value = a;
+ resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText} = `;
+ } else {
+ value = b;
+ resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText} = `;
+ }
+ } else if (methodScope['$checkAdvantage'] === -1){
+ logName += ' (Disadvantage)';
+ const [a, b] = rollDice(2, 20);
+ if (a <= b) {
+ value = a;
+ resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText} = `;
+ } else {
+ value = b;
+ resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText} = `;
+ }
+ } else {
+ values = rollDice(1, 20);
+ value = values[0];
+ resultPrefix = `1d20 [ ${value} ] ${rollModifierText} = `
+ }
+ const result = (value + rollModifier) || 0;
+ log.content.push({
+ name: logName,
+ value: `${resultPrefix} **${result}**`,
+ });
+}
diff --git a/app/imports/api/engine/computation/CreatureComputation.js b/app/imports/api/engine/computation/CreatureComputation.js
index a806ddd6..d6c45ebe 100644
--- a/app/imports/api/engine/computation/CreatureComputation.js
+++ b/app/imports/api/engine/computation/CreatureComputation.js
@@ -10,6 +10,7 @@ export default class CreatureComputation {
this.scope = {};
this.props = properties;
this.dependencyGraph = createGraph();
+ this.errors = [];
// Store properties for easy access later
properties.forEach(prop => {
diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateClassLevel.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateClassLevel.js
index 22197dc2..24ece344 100644
--- a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateClassLevel.js
+++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateClassLevel.js
@@ -1,6 +1,6 @@
export default function aggregateClassLevel({node, linkedNode, link}){
+ if (node.data.inactive) return;
if (link.data === 'classLevel'){
- if (node.data.inactive) return;
if (!node.data.classLevelAggregator) node.data.classLevelAggregator = {
levelsFilled: [true], // Level 0 is always filled
level: 0,
@@ -11,6 +11,6 @@ export default function aggregateClassLevel({node, linkedNode, link}){
aggregator.levelsFilled[linkedProp.level] = true;
} else if (link.data === 'level'){
node.data.baseValue = (node.data.baseValue || 0) +
- linkedNode.data.classLevelAggregator.level;
+ (linkedNode.data.classLevelAggregator?.level || 0);
}
}
diff --git a/app/imports/api/engine/computation/computeCreatureComputation.js b/app/imports/api/engine/computation/computeCreatureComputation.js
index 5eba016d..be866e1b 100644
--- a/app/imports/api/engine/computation/computeCreatureComputation.js
+++ b/app/imports/api/engine/computation/computeCreatureComputation.js
@@ -1,6 +1,7 @@
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 path from 'ngraph.path';
export default function computeCreatureComputation(computation){
const stack = [];
@@ -12,6 +13,12 @@ export default function computeCreatureComputation(computation){
node._visitedChildren = false;
stack.push(node)
});
+
+ // The graph nodes in the stack are ordered, by reversing the order we
+ // compute higher nodes in the tree first, which for dep loops is more likely
+ // to be a good guess of where to start thant the inverse
+ stack.reverse();
+
// Depth first traversal of nodes
while (stack.length){
let top = stack[stack.length - 1];
@@ -27,7 +34,7 @@ export default function computeCreatureComputation(computation){
} else {
top._visitedChildren = true;
// Push dependencies to graph to be computed first
- pushDependenciesToStack(top.id, graph, stack);
+ pushDependenciesToStack(top.id, graph, stack, computation);
}
}
@@ -42,8 +49,20 @@ function compute(computation, node){
computeByType[node.data?.type || '_variable']?.(computation, node);
}
-function pushDependenciesToStack(nodeId, graph, stack){
+function pushDependenciesToStack(nodeId, graph, stack, computation){
graph.forEachLinkedNode(nodeId, linkedNode => {
+ if (linkedNode._visitedChildren && !linkedNode._visited){
+ const pather = path.nba(graph, {
+ oriented: true
+ });
+ const loop = pather.find(nodeId, nodeId);
+ computation.errors.push({
+ type: 'dependencyLoop',
+ details: {
+ nodes: loop.map(node => node.id)
+ },
+ });
+ }
stack.push(linkedNode);
}, true);
}
diff --git a/app/imports/api/engine/computation/writeComputation/writeErrors.js b/app/imports/api/engine/computation/writeComputation/writeErrors.js
new file mode 100644
index 00000000..66002515
--- /dev/null
+++ b/app/imports/api/engine/computation/writeComputation/writeErrors.js
@@ -0,0 +1,9 @@
+import Creatures from '/imports/api/creature/creatures/Creatures.js';
+
+export default function(creatureId, errors = []){
+ if (errors.length){
+ Creatures.update(creatureId, {$set: {computeErrors: errors}});
+ } else {
+ Creatures.update(creatureId, {$unset: {computeErrors: 1}});
+ }
+}
diff --git a/app/imports/api/engine/computeCreature.js b/app/imports/api/engine/computeCreature.js
index 58fe1ed6..fc142270 100644
--- a/app/imports/api/engine/computeCreature.js
+++ b/app/imports/api/engine/computeCreature.js
@@ -2,12 +2,14 @@ import buildCreatureComputation from './computation/buildCreatureComputation.js'
import computeCreatureComputation from './computation/computeCreatureComputation.js';
import writeAlteredProperties from './computation/writeComputation/writeAlteredProperties.js';
import writeScope from './computation/writeComputation/writeScope.js';
+import writeErrors from './computation/writeComputation/writeErrors.js';
export default function computeCreature(creatureId){
const computation = buildCreatureComputation(creatureId);
computeCreatureComputation(computation);
writeAlteredProperties(computation);
writeScope(creatureId, computation.scope);
+ writeErrors(creatureId, computation.errors);
}
// For now just recompute the whole creature, TODO only recompute a single
diff --git a/app/imports/api/properties/subSchemas/inlineCalculationField.js b/app/imports/api/properties/subSchemas/inlineCalculationField.js
index 7be26ed7..22e37654 100644
--- a/app/imports/api/properties/subSchemas/inlineCalculationField.js
+++ b/app/imports/api/properties/subSchemas/inlineCalculationField.js
@@ -47,7 +47,7 @@ function computedOnlyInlineCalculationField(field){
},
[`${field}.inlineCalculations.$`]: {
type: Object,
- parseLevel: 'compile',
+ parseLevel: 'reduce',
computedField: true,
},
// The part between bracers {}
diff --git a/app/imports/migrations/server/dbv1/dbv1.js b/app/imports/migrations/server/dbv1/dbv1.js
index 9e140ef3..d299fc58 100644
--- a/app/imports/migrations/server/dbv1/dbv1.js
+++ b/app/imports/migrations/server/dbv1/dbv1.js
@@ -217,7 +217,9 @@ function getInlineComputationTransforms(key){
function calculationUp(val){
if (typeof val !== 'string') return val;
- return val.replace('.value', '.total').replace('.currentValue', '.value');
+ return val.replace(/#(\w+).(\w+)Result/g, '#$1.$2')
+ .replace('.value', '.total')
+ .replace('.currentValue', '.value');
}
function calculationDown(val){
diff --git a/app/imports/parser/parseTree/rollArray.js b/app/imports/parser/parseTree/rollArray.js
index 49474acb..d8b0c016 100644
--- a/app/imports/parser/parseTree/rollArray.js
+++ b/app/imports/parser/parseTree/rollArray.js
@@ -16,7 +16,7 @@ const rollArray = {
};
},
toString(node){
- return `${node.diceNum || ''}d${node.diceSize} [${node.values.join(', ')}]`;
+ return `${node.diceNum || ''}d${node.diceSize} [ ${node.values.join(', ')} ]`;
},
reduce(node, scope, context){
const total = node.values.reduce((a, b) => a + b, 0);
diff --git a/app/imports/ui/components/HorizontalHex.vue b/app/imports/ui/components/HorizontalHex.vue
new file mode 100644
index 00000000..b0b6ee8c
--- /dev/null
+++ b/app/imports/ui/components/HorizontalHex.vue
@@ -0,0 +1,46 @@
+
+
+ {{model.total}}
+
+