Attacks can now be rolled with advantage from the stats tab
TODO the action viewer as well still
This commit is contained in:
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -104,7 +104,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
roll(){
|
||||
this.$emit('roll', {advantage: this.advantage});
|
||||
this.$emit('roll', {advantage: this.dataAdvantage});
|
||||
this.open = false;
|
||||
},
|
||||
close(){
|
||||
|
||||
@@ -5,7 +5,30 @@
|
||||
>
|
||||
<div class="layout align-center px-3">
|
||||
<div class="avatar">
|
||||
<roll-popup
|
||||
v-if="rollBonus"
|
||||
icon
|
||||
outlined
|
||||
style="font-size: 16px; letter-spacing: normal;"
|
||||
class="mr-2"
|
||||
:color="model.color || 'primary'"
|
||||
:loading="doActionLoading"
|
||||
:disabled="model.insufficientResources || !context.editPermission"
|
||||
:roll-text="rollBonus"
|
||||
:name="model.name"
|
||||
:advantage="model.attackRoll && model.attackRoll.advantage"
|
||||
@roll="doAction"
|
||||
>
|
||||
<template v-if="rollBonus && !rollBonusTooLong">
|
||||
{{ rollBonus }}
|
||||
</template>
|
||||
<property-icon
|
||||
v-else
|
||||
:model="model"
|
||||
/>
|
||||
</roll-popup>
|
||||
<v-btn
|
||||
v-else
|
||||
icon
|
||||
outlined
|
||||
style="font-size: 16px; letter-spacing: normal;"
|
||||
@@ -15,11 +38,7 @@
|
||||
:disabled="model.insufficientResources || !context.editPermission"
|
||||
@click.stop="doAction"
|
||||
>
|
||||
<template v-if="rollBonus && !rollBonusTooLong">
|
||||
{{ rollBonus }}
|
||||
</template>
|
||||
<property-icon
|
||||
v-else
|
||||
:model="model"
|
||||
/>
|
||||
</v-btn>
|
||||
@@ -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 {
|
||||
|
||||
<style lang="css" scoped>
|
||||
.action-card {
|
||||
transition: box-shadow .4s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
transition: box-shadow .4s cubic-bezier(0.25, 0.8, 0.25, 1),
|
||||
transform 0.075s ease;
|
||||
}
|
||||
.action-card.active {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
.action-title {
|
||||
font-size: 16px;
|
||||
|
||||
Reference in New Issue
Block a user