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 @@ + + + + + diff --git a/app/imports/ui/components/RollPopup.vue b/app/imports/ui/components/RollPopup.vue new file mode 100644 index 00000000..0167bc1d --- /dev/null +++ b/app/imports/ui/components/RollPopup.vue @@ -0,0 +1,118 @@ + + + + + diff --git a/app/imports/ui/components/VerticalHex.vue b/app/imports/ui/components/VerticalHex.vue new file mode 100644 index 00000000..2b16f245 --- /dev/null +++ b/app/imports/ui/components/VerticalHex.vue @@ -0,0 +1,68 @@ + + + + + diff --git a/app/imports/ui/creature/character/characterSheetTabs/StatsTab.vue b/app/imports/ui/creature/character/characterSheetTabs/StatsTab.vue index 2c9265a6..8745e03a 100644 --- a/app/imports/ui/creature/character/characterSheetTabs/StatsTab.vue +++ b/app/imports/ui/creature/character/characterSheetTabs/StatsTab.vue @@ -399,6 +399,9 @@ required: true, }, }, + data(){return { + doCheckLoading: false, + }}, meteor: { creature(){ return Creatures.findOne(this.creatureId); diff --git a/app/imports/ui/properties/components/actions/ActionCard.vue b/app/imports/ui/properties/components/actions/ActionCard.vue index 6e0eb0ac..655192a8 100644 --- a/app/imports/ui/properties/components/actions/ActionCard.vue +++ b/app/imports/ui/properties/components/actions/ActionCard.vue @@ -5,7 +5,30 @@ >
+ + + + - @@ -85,7 +104,9 @@ import doAction from '/imports/api/engine/actions/doAction.js'; import AttributeConsumedView from '/imports/ui/properties/components/actions/AttributeConsumedView.vue'; import ItemConsumedView from '/imports/ui/properties/components/actions/ItemConsumedView.vue'; import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue'; +import RollPopup from '/imports/ui/components/RollPopup.vue'; import MarkdownText from '/imports/ui/components/MarkdownText.vue'; +import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js'; export default { components: { @@ -93,6 +114,7 @@ export default { ItemConsumedView, MarkdownText, PropertyIcon, + RollPopup, }, inject: { context: { @@ -131,7 +153,7 @@ export default { 'theme--dark': this.theme.isDark, 'theme--light': !this.theme.isDark, 'muted-text': this.model.insufficientResources, - 'shrink': this.activated, + 'active': this.activated, 'elevation-8': this.hovering, } }, @@ -143,13 +165,19 @@ export default { click(e){ this.$emit('click', e); }, - doAction(){ + doAction({advantage}){ this.doActionLoading = true; this.shwing(); - doAction.call({actionId: this.model._id}, error => { + doAction.call({ + actionId: this.model._id, + scope: { + $attackAdvantage: advantage, + } + }, error => { this.doActionLoading = false; if (error){ console.error(error); + snackbar({text: error.reason}); } }); }, @@ -157,7 +185,7 @@ export default { this.activated = true; setTimeout(() => { this.activated = undefined; - }, 300); + }, 150); } } } @@ -165,7 +193,11 @@ export default { diff --git a/app/imports/ui/properties/components/attributes/AttributeCard.vue b/app/imports/ui/properties/components/attributes/AttributeCard.vue index 597b0521..41a35ed7 100644 --- a/app/imports/ui/properties/components/attributes/AttributeCard.vue +++ b/app/imports/ui/properties/components/attributes/AttributeCard.vue @@ -4,7 +4,27 @@ @click="click" >
- + + + {{ computedValue }} + + + {{ computedValue }} @@ -16,13 +36,28 @@ diff --git a/app/imports/ui/properties/components/attributes/SpellSlotListTile.vue b/app/imports/ui/properties/components/attributes/SpellSlotListTile.vue index 67fcd039..d47b5132 100644 --- a/app/imports/ui/properties/components/attributes/SpellSlotListTile.vue +++ b/app/imports/ui/properties/components/attributes/SpellSlotListTile.vue @@ -4,7 +4,7 @@ v-on="hasClickListener ? {click} : {}" > - +
+ + + {{model.total}} + + {{ model.name }} diff --git a/app/imports/ui/properties/components/skills/SkillListTile.vue b/app/imports/ui/properties/components/skills/SkillListTile.vue index dacfb26b..45f8b138 100644 --- a/app/imports/ui/properties/components/skills/SkillListTile.vue +++ b/app/imports/ui/properties/components/skills/SkillListTile.vue @@ -1,40 +1,57 @@