Finished roll check and roll attack buttons from stats page

This commit is contained in:
Stefan Zermatten
2022-02-26 17:35:26 +02:00
parent fea29e60b7
commit 27665e0bdc
14 changed files with 319 additions and 53 deletions

View File

@@ -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;
@@ -59,9 +60,9 @@ function applyAttackWithoutTarget({attack, scope, log}){
criticalMiss,
} = rollAttack(attack, scope);
let name = criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical Miss!' : 'To Hit';
if (attack.advantage === 1 || scope['$attackAdvantage']){
if (scope['$attackAdvantage'] === 1){
name += ' (Advantage)';
} else if(attack.advantage === -1 || scope['$attackDisadvantage']){
} else if(scope['$attackAdvantage'] === -1){
name += ' (Disadvantage)';
}
log.content.push({
@@ -93,9 +94,9 @@ function applyAttackToTarget({attack, target, scope, log}){
let name = criticalHit ? 'Critical Hit!' :
criticalMiss ? 'Critical Miss!' :
result > armor ? 'Hit!' : 'Miss!';
if (attack.advantage === 1 || scope['$attackAdvantage']){
if (scope['$attackAdvantage'] === 1){
name += ' (Advantage)';
} else if(attack.advantage === -1 || scope['$attackDisadvantage']){
} else if(scope['$attackAdvantage'] === -1){
name += ' (Disadvantage)';
}
@@ -121,28 +122,29 @@ function applyAttackToTarget({attack, target, scope, log}){
}
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}~~ ] + ${attack.value} = `;
resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText} = `;
} else {
value = b;
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] + ${attack.value} = `;
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}~~ ] + ${attack.value} = `;
resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText} = `;
} else {
value = b;
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] + ${attack.value} = `;
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText} = `;
}
} else {
value = rollDice(1, 20)[0];
resultPrefix = `1d20 [${value}] + ${attack.value} = `
resultPrefix = `1d20 [${value}] ${rollModifierText} = `
}
scope['$attackRoll'] = {value};
const result = value + attack.value;

View File

@@ -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,

View File

@@ -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,29 +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){
const [a, b] = rollDice(2, 20);
if (a >= b) {
value = a;
resultPrefix = `Advantage: 1d20 [ ${a}, ~~${b}~~ ] + ${save.value} = `;
resultPrefix = `Advantage: 1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText} = `;
} else {
value = b;
resultPrefix = `Advantage: 1d20 [ ~~${a}~~, ${b} ] + ${save.value} = `;
resultPrefix = `Advantage: 1d20 [ ~~${a}~~, ${b} ] ${rollModifierText} = `;
}
} else if (save.advantage === -1){
const [a, b] = rollDice(2, 20);
if (a <= b) {
value = a;
resultPrefix = `Advantage: 1d20 [ ${a}, ~~${b}~~ ] + ${save.value} = `;
resultPrefix = `Disadvantage: 1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText} = `;
} else {
value = b;
resultPrefix = `Advantage: 1d20 [ ~~${a}~~, ${b} ] + ${save.value} = `;
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;

View File

@@ -27,6 +27,7 @@ const doAction = new ValidatedMethod({
scope: {
type: Object,
blackbox: true,
optional: true,
},
}).validator(),
mixins: [RateLimiterMixin],

View File

@@ -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}**`,
});
}

View File

@@ -35,7 +35,7 @@
origin="center center"
>
<vertical-hex
v-if="dataAdvantage !== undefined"
v-if="dataAdvantage"
style="position:absolute; transition: margin-left 0.3s ease;"
:style="{marginLeft: dataAdvantage == 1 ? '24px' : '-24px'}"
disable-hover

View File

@@ -16,7 +16,10 @@
<path
d="M 249.801 51.001 L 71.808 153.637 L 71.477 359.513 L 250.131 462.148 L 428.125 359.513 L 428.455 153.637 L 249.801 51.001 Z"
fill-opacity="1"
:fill="hover ? 'rgb(80, 80, 80)' : 'rgb(40, 40, 40)'"
:fill="theme.isDark ?
hover ? 'rgb(80, 80, 80)' : 'rgb(40, 40, 40)':
hover ? 'rgb(180, 180, 180)' : 'rgb(220, 220, 220)'
"
/>
</svg>
<div
@@ -34,6 +37,13 @@
<script lang="js">
export default {
inject: {
theme: {
default: {
isDark: false,
},
},
},
props: {
height: {
type: Number,

View File

@@ -399,6 +399,9 @@
required: true,
},
},
data(){return {
doCheckLoading: false,
}},
meteor: {
creature(){
return Creatures.findOne(this.creatureId);

View File

@@ -106,6 +106,7 @@ import ItemConsumedView from '/imports/ui/properties/components/actions/ItemCons
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: {
@@ -170,13 +171,13 @@ export default {
doAction.call({
actionId: this.model._id,
scope: {
$attackAdvantage: advantage === 1 || undefined,
$attackDisadvantage: advantage === -1 || undefined,
$attackAdvantage: advantage,
}
}, error => {
this.doActionLoading = false;
if (error){
console.error(error);
snackbar({text: error.reason});
}
});
},

View File

@@ -1,32 +1,46 @@
<template lang="html">
<v-list-item
class="ability-list-tile"
class="ability-list-tile pl-0"
v-on="hasClickListener ? {click} : {}"
>
<v-list-item-action
class="mr-4"
class="ma-0"
style="min-width: 40px;"
>
<div class="text-h4 mod">
<template v-if="swapScoresAndMods">
<span :class="{'primary--text': model.total !== model.value}">
{{ model.value }}
</span>
</template>
<template v-else>
{{ numberToSignedString(model.modifier) }}
</template>
</div>
<div class="text-h6 value">
<template v-if="swapScoresAndMods">
{{ numberToSignedString(model.modifier) }}
</template>
<template v-else>
<span :class="{'primary--text': model.total !== model.value}">
{{ model.value }}
</span>
</template>
</div>
<roll-popup
button-class="mr-4 py-2"
text
height="82"
:roll-text="numberToSignedString(model.modifier)"
:name="model.name"
:advantage="model.advantage"
:loading="checkLoading"
:disabled="!context.editPermission"
@roll="check"
>
<div>
<div class="text-h4 mod">
<template v-if="swapScoresAndMods">
<span :class="{'primary--text': model.total !== model.value}">
{{ model.value }}
</span>
</template>
<template v-else>
{{ numberToSignedString(model.modifier) }}
</template>
</div>
<div class="text-h6 value">
<template v-if="swapScoresAndMods">
{{ numberToSignedString(model.modifier) }}
</template>
<template v-else>
<span :class="{'primary--text': model.total !== model.value}">
{{ model.value }}
</span>
</template>
</div>
</div>
</roll-popup>
</v-list-item-action>
<v-list-item-content>
@@ -39,10 +53,25 @@
<script lang="js">
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
import RollPopup from '/imports/ui/components/RollPopup.vue';
import doCheck from '/imports/api/engine/actions/doCheck.js';
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
export default {
components: {
RollPopup,
},
inject: {
context: {
default: {},
},
},
props: {
model: {type: Object, required: true},
},
data(){return {
checkLoading: false,
}},
computed: {
hasClickListener(){
return this.$listeners && this.$listeners.click
@@ -53,6 +82,21 @@ export default {
click(e){
this.$emit('click', e);
},
check({advantage}){
this.checkLoading = true;
doCheck.call({
propId: this.model._id,
scope: {
$checkAdvantage: advantage,
},
}, error => {
this.checkLoading = false;
if (error){
console.error(error);
snackbar({text: error.reason});
}
});
},
},
meteor: {
swapScoresAndMods(){
@@ -86,5 +130,6 @@ export default {
.mod, .value {
text-align: center;
width: 100%;
min-width: 42px;
}
</style>

View File

@@ -4,7 +4,27 @@
@click="click"
>
<div class="layout align-center">
<v-card-title class="value text-h4 flex-shrink-0">
<roll-popup
v-if="model.attributeType === 'modifier' || model.type === 'skill'"
button-class="px-0"
text
height="70"
min-width="72"
:roll-text="computedValue && computedValue.toString()"
:name="model.name"
:advantage="model.advantage"
:loading="checkLoading"
:disabled="!context.editPermission"
@roll="check"
>
<v-card-title class="value text-h4 flex-shrink-0">
{{ computedValue }}
</v-card-title>
</roll-popup>
<v-card-title
v-else
class="value text-h4 flex-shrink-0"
>
{{ computedValue }}
</v-card-title>
<v-card-title class="name text-subtitle-1 text-truncate d-block pl-0">
@@ -16,13 +36,28 @@
<script lang="js">
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
import RollPopup from '/imports/ui/components/RollPopup.vue';
import doCheck from '/imports/api/engine/actions/doCheck.js';
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
export default {
components: {
RollPopup,
},
inject: {
context: {
default: {},
},
},
props: {
model: {
type: Object,
required: true,
},
},
data(){return {
checkLoading: false,
}},
computed: {
hasClickListener(){
return this.$listeners && !!this.$listeners.click
@@ -40,13 +75,28 @@
click(e){
this.$emit('click', e);
},
check({advantage}){
this.checkLoading = true;
doCheck.call({
propId: this.model._id,
scope: {
$checkAdvantage: advantage,
},
}, error => {
this.checkLoading = false;
if (error){
console.error(error);
snackbar({text: error.reason});
}
});
},
},
}
</script>
<style lang="css" scoped>
.value {
min-width: 64px;
min-width: 72px;
justify-content: center;
}
</style>

View File

@@ -1,6 +1,6 @@
<template lang="html">
<v-list-item
class="skill-list-tile"
class="skill-list-tile pl-0"
style="min-height: 36px;"
v-on="hasClickListener ? {click} : {}"
>
@@ -9,15 +9,18 @@
<roll-popup
v-if="!hideModifier"
class="prof-mod mr-1"
button-class="px-2"
button-class="pl-3 pr-2"
text
:roll-text="displayedModifier"
:name="model.name"
:advantage="model.advantage"
:loading="checkLoading"
:disabled="!context.editPermission"
@roll="check"
>
<proficiency-icon
:value="model.proficiency"
class="prof-icon mr-2"
class="prof-icon"
/>
<div class="prof-mod">
{{ displayedModifier }}
@@ -35,6 +38,11 @@
mdi-chevron-double-down
</v-icon>
</roll-popup>
<proficiency-icon
v-else
:value="model.proficiency"
class="prof-icon ml-3 mr-2"
/>
<div>
{{ model.name }}
<template v-if="model.conditionalBenefits && model.conditionalBenefits.length">
@@ -53,11 +61,18 @@
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
import ProficiencyIcon from '/imports/ui/properties/shared/ProficiencyIcon.vue';
import RollPopup from '/imports/ui/components/RollPopup.vue';
import doCheck from '/imports/api/engine/actions/doCheck.js';
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
export default {
components: {
ProficiencyIcon,
RollPopup,
},
inject: {
context: {
default: {},
},
},
props: {
model: {
@@ -66,6 +81,9 @@ export default {
},
hideModifier: Boolean,
},
data(){return {
checkLoading: false,
}},
computed: {
displayedModifier(){
let mod = this.model.value;
@@ -86,6 +104,21 @@ export default {
click(e){
this.$emit('click', e);
},
check({advantage}){
this.checkLoading = true;
doCheck.call({
propId: this.model._id,
scope: {
$checkAdvantage: advantage,
},
}, error => {
this.checkLoading = false;
if (error){
console.error(error);
snackbar({text: error.reason});
}
});
},
}
}
</script>
@@ -97,4 +130,7 @@ export default {
.prof-mod {
min-width: 32px;
}
.v-icon.theme--light {
color: rgba(0, 0, 0, 0.54) !important;
}
</style>

View File

@@ -1,10 +1,10 @@
export default function numberToSignedString(number){
export default function numberToSignedString(number, spaced){
if (typeof number !== 'number') return number;
if (number === 0){
return '+0';
return spaced ? '+ 0' : '+0';
} else if (number > 0){
return `+${number}`;
return spaced ? `+ ${number}` : `+${number}`;
} else {
return `${number}`;
return spaced ? `- ${Math.abs(number) || number}` : `${number}`;
}
}

View File

@@ -9,6 +9,7 @@ import '/imports/server/publications/index.js';
import '/imports/server/cron/deleteSoftRemovedDocuments.js';
import '/imports/api/parenting/organizeMethods.js';
import '/imports/api/users/patreon/updatePatreonOnLogin.js';
import '/imports/api/engine/actions/doCheck.js';
import '/imports/migrations/server/index.js';
import '/imports/migrations/methods/index.js'
import '/imports/constants/MAINTENANCE_MODE.js';