From 59c69a46a8f80e3c9889ed857a62e4c28cf4a68c Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Fri, 25 Feb 2022 13:44:09 +0200 Subject: [PATCH] Attacks can now be rolled with advantage from the stats tab TODO the action viewer as well still --- .../applyPropertyByType/applyAction.js | 138 ++++++++++++------ .../applyPropertyByType/applySavingThrow.js | 24 ++- app/imports/api/engine/actions/doAction.js | 11 +- app/imports/parser/parseTree/rollArray.js | 2 +- app/imports/ui/components/RollPopup.vue | 2 +- .../components/actions/ActionCard.vue | 49 +++++-- 6 files changed, 159 insertions(+), 67 deletions(-) diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js index 2f5a5be0..bd437edb 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js @@ -24,16 +24,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 +43,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 (attack.advantage === 1 || scope['$attackAdvantage']){ + name += ' (Advantage)'; + } else if(attack.advantage === -1 || scope['$attackDisadvantage']){ + 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 +78,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 (attack.advantage === 1 || scope['$attackAdvantage']){ + name += ' (Advantage)'; + } else if(attack.advantage === -1 || scope['$attackDisadvantage']){ + 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 +114,62 @@ 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){ + let value, resultPrefix; + if (attack.advantage === 1 || scope['$attackAdvantage']){ + const [a, b] = rollDice(2, 20); + if (a >= b) { + value = a; + resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] + ${attack.value} = `; + } else { + value = b; + resultPrefix = `1d20 [ ~~${a}~~, ${b} ] + ${attack.value} = `; + } + } else if (attack.advantage === -1 || scope['$attackDisadvantage']){ + const [a, b] = rollDice(2, 20); + if (a <= b) { + value = a; + resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] + ${attack.value} = `; + } else { + value = b; + resultPrefix = `1d20 [ ~~${a}~~, ${b} ] + ${attack.value} = `; + } + } else { + value = rollDice(1, 20)[0]; + resultPrefix = `1d20 [${value}] + ${attack.value} = ` + } + 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/applySavingThrow.js b/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js index b6855e19..a9a7b179 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js @@ -48,17 +48,27 @@ export default function applySavingThrow(node, {creature, targets, scope, log}){ 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}~~ ] + ${save.value} = `; + } else { + value = b; + resultPrefix = `Advantage: 1d20 [ ~~${a}~~, ${b} ] + ${save.value} = `; + } } 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 = `Advantage: 1d20 [ ${a}, ~~${b}~~ ] + ${save.value} = `; + } else { + value = b; + resultPrefix = `Advantage: 1d20 [ ~~${a}~~, ${b} ] + ${save.value} = `; + } } else { values = rollDice(1, 20); value = values[0]; - resultPrefix = `1d20 [${value}] + ${save.value} = ` + resultPrefix = `1d20 [ ${value} ] + ${save.value} = ` } 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..35cf7d4f 100644 --- a/app/imports/api/engine/actions/doAction.js +++ b/app/imports/api/engine/actions/doAction.js @@ -24,13 +24,17 @@ const doAction = new ValidatedMethod({ type: String, regEx: SimpleSchema.RegEx.Id, }, + scope: { + type: Object, + blackbox: 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 +73,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 +86,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 +106,7 @@ export function doActionWork({ const scope = { ...creature.variables, ...ancestorScope, + ...methodScope } applyProperty(propertyForest[0], { creature, 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/RollPopup.vue b/app/imports/ui/components/RollPopup.vue index f8a39f5a..0afd1d85 100644 --- a/app/imports/ui/components/RollPopup.vue +++ b/app/imports/ui/components/RollPopup.vue @@ -104,7 +104,7 @@ export default { }, methods: { roll(){ - this.$emit('roll', {advantage: this.advantage}); + this.$emit('roll', {advantage: this.dataAdvantage}); this.open = false; }, close(){ diff --git a/app/imports/ui/properties/components/actions/ActionCard.vue b/app/imports/ui/properties/components/actions/ActionCard.vue index 6e0eb0ac..fadbfa20 100644 --- a/app/imports/ui/properties/components/actions/ActionCard.vue +++ b/app/imports/ui/properties/components/actions/ActionCard.vue @@ -5,7 +5,30 @@ >
+ + + + - @@ -85,6 +104,7 @@ 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'; export default { @@ -93,6 +113,7 @@ export default { ItemConsumedView, MarkdownText, PropertyIcon, + RollPopup, }, inject: { context: { @@ -131,7 +152,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,10 +164,16 @@ 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 === 1 || undefined, + $attackDisadvantage: advantage === -1 || undefined, + } + }, error => { this.doActionLoading = false; if (error){ console.error(error); @@ -157,7 +184,7 @@ export default { this.activated = true; setTimeout(() => { this.activated = undefined; - }, 300); + }, 150); } } } @@ -165,7 +192,11 @@ export default {