diff --git a/app/imports/api/creature/Creatures.js b/app/imports/api/creature/Creatures.js index d65aae6f..999faf32 100644 --- a/app/imports/api/creature/Creatures.js +++ b/app/imports/api/creature/Creatures.js @@ -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, } }); diff --git a/app/imports/api/creature/getActiveProperties.js b/app/imports/api/creature/getActiveProperties.js index 999da873..f1311c71 100644 --- a/app/imports/api/creature/getActiveProperties.js +++ b/app/imports/api/creature/getActiveProperties.js @@ -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; } diff --git a/app/imports/api/creature/restCreature.js b/app/imports/api/creature/restCreature.js new file mode 100644 index 00000000..8808dbc0 --- /dev/null +++ b/app/imports/api/creature/restCreature.js @@ -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; diff --git a/app/imports/api/properties/Attributes.js b/app/imports/api/properties/Attributes.js index becc3769..1b35640a 100644 --- a/app/imports/api/properties/Attributes.js +++ b/app/imports/api/properties/Attributes.js @@ -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: { diff --git a/app/imports/ui/creature/CreatureForm.vue b/app/imports/ui/creature/CreatureForm.vue index ddec2f8d..9d2ee14a 100644 --- a/app/imports/ui/creature/CreatureForm.vue +++ b/app/imports/ui/creature/CreatureForm.vue @@ -47,9 +47,21 @@ +