Added short and long rest buttons, closes #87
This commit is contained in:
@@ -7,6 +7,7 @@ import {assertEditPermission} from '/imports/api/sharing/sharingPermissions.js';
|
||||
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
||||
|
||||
import '/imports/api/creature/removeCreature.js';
|
||||
import '/imports/api/creature/restCreature.js';
|
||||
|
||||
//set up the collection for creatures
|
||||
let Creatures = new Mongo.Collection('creatures');
|
||||
@@ -31,6 +32,13 @@ let CreatureSettingsSchema = new SimpleSchema({
|
||||
hideUnusedStats: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
// How much each hitDice resets on a long rest
|
||||
hitDiceResetMultiplier: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
min: 0,
|
||||
max: 1,
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -6,6 +6,15 @@ export default function getActiveProperties({
|
||||
filter = {},
|
||||
options,
|
||||
includeUntoggled = false
|
||||
}){
|
||||
filter = getActivePropertyFilter({ancestorId, filter, includeUntoggled});
|
||||
return CreatureProperties.find(filter, options).fetch();
|
||||
}
|
||||
|
||||
export function getActivePropertyFilter({
|
||||
ancestorId,
|
||||
filter = {},
|
||||
includeUntoggled = false
|
||||
}){
|
||||
if (!ancestorId){
|
||||
throw 'Ancestor Id is required to get active properties'
|
||||
@@ -14,9 +23,9 @@ export default function getActiveProperties({
|
||||
let disabledAncestorsFilter = {
|
||||
'ancestors.id': ancestorId,
|
||||
$or: [
|
||||
{disabled: true},
|
||||
{equipped: false},
|
||||
{applied: false},
|
||||
{disabled: true}, // Everything can be disabled
|
||||
{equipped: false}, // Items can be equipped
|
||||
{applied: false}, // Buffs can be applied
|
||||
],
|
||||
};
|
||||
if (!includeUntoggled){
|
||||
@@ -48,5 +57,5 @@ export default function getActiveProperties({
|
||||
filter._id = {
|
||||
$nin: disabledAncestorIds,
|
||||
}
|
||||
return CreatureProperties.find(filter, options).fetch();
|
||||
return filter;
|
||||
}
|
||||
|
||||
103
app/imports/api/creature/restCreature.js
Normal file
103
app/imports/api/creature/restCreature.js
Normal file
@@ -0,0 +1,103 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import CreatureProperties from '/imports/api/creature/CreatureProperties.js';
|
||||
import getActiveProperties, { getActivePropertyFilter } from '/imports/api/creature/getActiveProperties.js';
|
||||
import {assertEditPermission} from '/imports/api/creature/creaturePermissions.js';
|
||||
|
||||
const restCreature = new ValidatedMethod({
|
||||
name: 'creature.methods.longRest',
|
||||
validate: new SimpleSchema({
|
||||
creatureId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
restType: {
|
||||
type: String,
|
||||
allowedValues: ['shortRest', 'longRest'],
|
||||
},
|
||||
}).validator(),
|
||||
run({creatureId, restType}) {
|
||||
let creature = Creatures.findOne(creatureId, {
|
||||
fields: {
|
||||
owner: 1,
|
||||
writers: 1,
|
||||
settings: 1,
|
||||
}
|
||||
}) ;
|
||||
// Need edit permissions
|
||||
assertEditPermission(creature, this.userId);
|
||||
|
||||
// Long rests reset short rest properties as well
|
||||
let resetFilter;
|
||||
if (restType === 'shortRest'){
|
||||
resetFilter = 'shortRest'
|
||||
} else {
|
||||
resetFilter = {$in: ['shortRest', 'longRest']}
|
||||
}
|
||||
// Only apply to active properties
|
||||
let filter = getActivePropertyFilter({
|
||||
filter: {reset: resetFilter},
|
||||
ancestorId: creatureId,
|
||||
includeUntoggled: true,
|
||||
});
|
||||
// update all attribute's damage
|
||||
filter.type = 'attribute';
|
||||
CreatureProperties.update(filter, {
|
||||
$set: {damage: 0}
|
||||
}, {
|
||||
selector: {type: 'attribute'},
|
||||
multi: true,
|
||||
});
|
||||
// Update all action-like properties' usesUsed
|
||||
filter.type = {$in: [
|
||||
'action',
|
||||
'attack',
|
||||
'spell'
|
||||
]};
|
||||
CreatureProperties.update(filter, {
|
||||
$set: {usesUsed: 0}
|
||||
}, {
|
||||
selector: {type: 'action'},
|
||||
multi: true,
|
||||
});
|
||||
// Reset half hit dice on a long rest, starting with the highest dice
|
||||
if (restType === 'longRest'){
|
||||
let hitDice = getActiveProperties({
|
||||
ancestorId: creatureId,
|
||||
filter: {type: 'attribute', attributeType: 'hitDice'},
|
||||
options: {fields: {
|
||||
hitDiceSize: 1,
|
||||
damage: 1,
|
||||
value: 1,
|
||||
}},
|
||||
});
|
||||
// Use a collator to do sorting in natural order
|
||||
let collator = new Intl.Collator('en', {
|
||||
numeric: true, sensitivity: 'base'
|
||||
});
|
||||
// Get the hit dice in decending order of hitDiceSize
|
||||
let compare = (a, b) => collator.compare(b.hitDiceSize, a.hitDiceSize)
|
||||
hitDice.sort(compare);
|
||||
// Get the total number of hit dice that can be recovered this rest
|
||||
let totalHd = hitDice.reduce((sum, hd) => sum + (hd.value || 0), 0);
|
||||
let resetMultiplier = creature.settings.hitDiceResetMultiplier || 0.5;
|
||||
let recoverableHd = Math.max(Math.floor(totalHd*resetMultiplier), 1);
|
||||
// recover each hit dice in turn until the recoverable amount is used up
|
||||
let amountToRecover, resultingDamage;
|
||||
hitDice.forEach(hd => {
|
||||
if (!recoverableHd) return;
|
||||
amountToRecover = Math.min(recoverableHd, hd.damage);
|
||||
recoverableHd -= amountToRecover;
|
||||
resultingDamage = hd.damage - amountToRecover;
|
||||
CreatureProperties.update(hd._id, {
|
||||
$set: {damage: resultingDamage}
|
||||
}, {
|
||||
selector: {type: 'attribute'},
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default restCreature;
|
||||
@@ -87,6 +87,12 @@ let ComputedOnlyAttributeSchema = new SimpleSchema({
|
||||
type: SimpleSchema.oneOf(Number, String, Boolean),
|
||||
defaultValue: 0,
|
||||
optional: true,
|
||||
},
|
||||
// The computed value of the attribute minus the damage
|
||||
currentValue: {
|
||||
type: SimpleSchema.oneOf(Number, String, Boolean),
|
||||
defaultValue: 0,
|
||||
optional: true,
|
||||
},
|
||||
// The computed modifier, provided the attribute type is `ability`
|
||||
modifier: {
|
||||
|
||||
@@ -47,9 +47,21 @@
|
||||
<v-switch
|
||||
label="Hide redundant stats"
|
||||
:input-value="model.settings.hideUnusedStats"
|
||||
:error-messages="errors.hideUnusedStats"
|
||||
:disabled="disabled"
|
||||
@change="value => $emit('change', {path: ['settings','hideUnusedStats'], value: !!value || null})"
|
||||
@change="value => $emit('change', {path: ['settings','hideUnusedStats'], value: !!value})"
|
||||
/>
|
||||
<text-field
|
||||
label="Hit Dice reset multiplier"
|
||||
hint="What fraction of your hit dice are reset every long rest"
|
||||
placeholder="0.5"
|
||||
type="number"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.1"
|
||||
:value="model.settings.hitDiceResetMultiplier"
|
||||
:debounce-time="debounceTime"
|
||||
:disabled="disabled"
|
||||
@change="(value, ack) => $emit('change', {path: ['settings','hitDiceResetMultiplier'], value, ack})"
|
||||
/>
|
||||
<!--
|
||||
<v-switch
|
||||
|
||||
51
app/imports/ui/creature/RestButton.vue
Normal file
51
app/imports/ui/creature/RestButton.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template lang="html">
|
||||
<v-btn
|
||||
:loading="loading"
|
||||
:disabled="loading"
|
||||
outline
|
||||
style="width: 160px;"
|
||||
@click="rest"
|
||||
>
|
||||
<v-icon left>
|
||||
{{ type === 'shortRest' ? 'snooze' : 'bedtime' }}
|
||||
</v-icon>
|
||||
{{ type === 'shortRest' ? 'Short Rest' : 'Long Rest' }}
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import restCreature from '/imports/api/creature/restCreature.js';
|
||||
|
||||
export default {
|
||||
props:{
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
creatureId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data(){return {
|
||||
loading: false,
|
||||
}},
|
||||
methods: {
|
||||
rest(){
|
||||
this.loading = true;
|
||||
restCreature.call({
|
||||
creatureId: this.creatureId,
|
||||
restType: this.type,
|
||||
}, error => {
|
||||
this.loading = false;
|
||||
if (error){
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
@@ -1,6 +1,5 @@
|
||||
<template lang="html">
|
||||
<div
|
||||
v-if="creature"
|
||||
class="stats-tab ma-2"
|
||||
>
|
||||
<div class="px-2 pt-2">
|
||||
@@ -8,6 +7,20 @@
|
||||
</div>
|
||||
|
||||
<column-layout>
|
||||
<div class="character-buttons">
|
||||
<v-card>
|
||||
<v-card-text class="layout column align-center">
|
||||
<rest-button
|
||||
:creature-id="creatureId"
|
||||
type="shortRest"
|
||||
/>
|
||||
<rest-button
|
||||
:creature-id="creatureId"
|
||||
type="longRest"
|
||||
/>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
<div class="ability-scores">
|
||||
<v-card>
|
||||
<v-list>
|
||||
@@ -291,6 +304,7 @@
|
||||
import SpellSlotListTile from '/imports/ui/properties/components/attributes/SpellSlotListTile.vue';
|
||||
import ActionListTile from '/imports/ui/properties/components/actions/ActionListTile.vue';
|
||||
import AttackListTile from '/imports/ui/properties/components/actions/AttackListTile.vue';
|
||||
import RestButton from '/imports/ui/creature/RestButton.vue';
|
||||
import getActiveProperties from '/imports/api/creature/getActiveProperties.js';
|
||||
|
||||
const getProperties = function(creature, filter,){
|
||||
@@ -321,6 +335,7 @@
|
||||
|
||||
export default {
|
||||
components: {
|
||||
RestButton,
|
||||
AbilityListTile,
|
||||
AttributeCard,
|
||||
ColumnLayout,
|
||||
|
||||
Reference in New Issue
Block a user