diff --git a/app/imports/api/engine/actions/doAction.js b/app/imports/api/engine/actions/doAction.js index 83658f62..e72e43bc 100644 --- a/app/imports/api/engine/actions/doAction.js +++ b/app/imports/api/engine/actions/doAction.js @@ -87,7 +87,7 @@ const doAction = new ValidatedMethod({ export default doAction; export function doActionWork({ - creature, targets, properties, ancestors, method, methodScope = {} + creature, targets, properties, ancestors, method, methodScope = {}, log }){ // get the docs const ancestorScope = getAncestorScope(ancestors); @@ -97,7 +97,7 @@ export function doActionWork({ } // Create the log - let log = CreatureLogSchema.clean({ + if (!log) log = CreatureLogSchema.clean({ creatureId: creature._id, creatureName: creature.name, }); diff --git a/app/imports/api/engine/actions/doCastSpell.js b/app/imports/api/engine/actions/doCastSpell.js new file mode 100644 index 00000000..890c4879 --- /dev/null +++ b/app/imports/api/engine/actions/doCastSpell.js @@ -0,0 +1,142 @@ +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 Creatures from '/imports/api/creature/creatures/Creatures.js'; +import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; +import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js'; +import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js'; +import { doActionWork } from '/imports/api/engine/actions/doAction.js'; +import computeCreature from '/imports/api/engine/computeCreature.js'; +import { CreatureLogSchema } from '/imports/api/creature/log/CreatureLogs.js'; + +const doAction = new ValidatedMethod({ + name: 'creatureProperties.doCastSpell', + validate: new SimpleSchema({ + spellId: SimpleSchema.RegEx.Id, + slotId: { + type: String, + regEx: SimpleSchema.RegEx.Id, + optional: true, + }, + targetIds: { + type: Array, + defaultValue: [], + maxCount: 20, + optional: true, + }, + 'targetIds.$': { + type: String, + regEx: SimpleSchema.RegEx.Id, + }, + scope: { + type: Object, + blackbox: true, + optional: true, + }, + }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 10, + timeInterval: 5000, + }, + run({spellId, slotId, targetIds = [], scope = {}}) { + let spell = CreatureProperties.findOne(spellId); + // Check permissions + let creature = getRootCreatureAncestor(spell); + + assertEditPermission(creature, this.userId); + + // Get all the targets and make sure we can edit them + let targets = []; + targetIds.forEach(targetId => { + let target = Creatures.findOne(targetId); + assertEditPermission(target, this.userId); + targets.push(target); + }); + + // Fetch all the action's ancestor creatureProperties + const ancestorIds = []; + spell.ancestors.forEach(ref => { + if (ref.collection === 'creatureProperties') { + ancestorIds.push(ref.id); + } + }); + + // Get cursor of ancestors + const ancestors = CreatureProperties.find({ + _id: {$in: ancestorIds}, + }, { + sort: {order: 1}, + }); + + // Get cursor of the properties + const properties = CreatureProperties.find({ + $or: [{_id: spell._id}, {'ancestors.id': spell._id}], + removed: {$ne: true}, + }, { + sort: {order: 1}, + }); + + // Spend the appropriate slot + let slotLevel = spell.level || 0; + let slot; + if (slotId && !spell.castWithoutSpellSlots){ + slot = CreatureProperties.findOne(slotId); + if (!slot){ + throw new Meteor.Error('No slot', + 'Slot not found to cast spell'); + } + if (!slot.value){ + throw new Meteor.Error('No slot', + 'Slot depleted'); + } + if (slot.attributeType !== 'spellSlot'){ + throw new Meteor.Error('Not a slot', + 'The given property is not a valid spell slot'); + } + if (!slot.spellSlotLevel?.value){ + throw new Meteor.Error('No slot level', + 'Slot does not have a spell slot level'); + } + if (slot.spellSlotLevel.value < spell.level){ + throw new Meteor.Error('Slot too small', + 'Slot is not large enough to cast spell'); + } + slotLevel = slot.spellSlotLevel.value; + damagePropertyWork({ + property: slot, + operation: 'increment', + value: 1, + }); + } + + scope['slotLevel'] = slotLevel; + + // Post the slot level spent to the log + const log = CreatureLogSchema.clean({ + creatureId: creature._id, + creatureName: creature.name, + }); + if (slot?.spellSlotLevel?.value){ + log.content.push({ + name: `Casting using a level ${slotLevel} spell slot` + }); + } else if (slotLevel) { + log.content.push({ + name: `Casting at level ${slotLevel}` + }); + } + + // Do the action + doActionWork({creature, targets, properties, ancestors, method: this, methodScope: scope, log}); + + // Recompute all involved creatures + computeCreature(creature._id); + targets.forEach(target => { + computeCreature(target._id); + }); + }, +}); + +export default doAction; diff --git a/app/imports/api/engine/actions/index.js b/app/imports/api/engine/actions/index.js new file mode 100644 index 00000000..5cd7a8b4 --- /dev/null +++ b/app/imports/api/engine/actions/index.js @@ -0,0 +1,2 @@ +import './doCastSpell.js'; +import './doCheck.js'; diff --git a/app/imports/api/engine/actions/methods/commitAction.js b/app/imports/api/engine/actions/methods/commitAction.js deleted file mode 100644 index 472ca1d8..00000000 --- a/app/imports/api/engine/actions/methods/commitAction.js +++ /dev/null @@ -1,54 +0,0 @@ -import SimpleSchema from 'simpl-schema'; -import { ValidatedMethod } from 'meteor/mdg:validated-method'; -import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; -import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; -import Creatures from '/imports/api/creature/creatures/Creatures.js'; -import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js'; -import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js'; -import computeCreature from '/imports/api/engine/computeCreature.js'; -import doAction from '../doAction.js'; - -const commitAction = new ValidatedMethod({ - name: 'creatureProperties.doAction', - validate: new SimpleSchema({ - actionId: SimpleSchema.RegEx.Id, - targetIds: { - type: Array, - defaultValue: [], - maxCount: 20, - optional: true, - }, - 'targetIds.$': { - type: String, - regEx: SimpleSchema.RegEx.Id, - }, - }).validator(), - mixins: [RateLimiterMixin], - rateLimit: { - numRequests: 10, - timeInterval: 5000, - }, - run({actionId, targetIds = []}) { - let action = CreatureProperties.findOne(actionId); - // Check permissions - let creature = getRootCreatureAncestor(action); - - assertEditPermission(creature, this.userId); - let targets = []; - targetIds.forEach(targetId => { - let target = Creatures.findOne(targetId); - assertEditPermission(target, this.userId); - targets.push(target); - }); - doAction({action, creature, targets, method: this}); - - // recompute creatures - computeCreature(creature._id); - - targets.forEach(target => { - computeCreature(target._id); - }); - }, -}); - -export default commitAction; diff --git a/app/imports/api/engine/computeCreature.js b/app/imports/api/engine/computeCreature.js index fc142270..066a3cee 100644 --- a/app/imports/api/engine/computeCreature.js +++ b/app/imports/api/engine/computeCreature.js @@ -5,11 +5,21 @@ import writeScope from './computation/writeComputation/writeScope.js'; import writeErrors from './computation/writeComputation/writeErrors.js'; export default function computeCreature(creatureId){ + if (Meteor.isClient) return; const computation = buildCreatureComputation(creatureId); - computeCreatureComputation(computation); - writeAlteredProperties(computation); - writeScope(creatureId, computation.scope); - writeErrors(creatureId, computation.errors); + try { + computeCreatureComputation(computation); + writeAlteredProperties(computation); + writeScope(creatureId, computation.scope); + writeErrors(creatureId, computation.errors); + } catch (e){ + computation.errors.push({ + type: 'crash', + details: e.reason, + }); + } finally { + writeErrors(creatureId, [...computation.errors]); + } } // For now just recompute the whole creature, TODO only recompute a single diff --git a/app/imports/ui/components/ColorPicker.vue b/app/imports/ui/components/ColorPicker.vue index 23b8463b..143f2b66 100644 --- a/app/imports/ui/components/ColorPicker.vue +++ b/app/imports/ui/components/ColorPicker.vue @@ -7,10 +7,15 @@ > @@ -122,6 +127,10 @@ type: String, default: undefined, }, + label: { + type: String, + default: undefined, + } }, data(){ return { colors: [ diff --git a/app/imports/ui/components/global/IconPicker.vue b/app/imports/ui/components/global/IconPicker.vue index 3abec54f..d7f3ed1a 100644 --- a/app/imports/ui/components/global/IconPicker.vue +++ b/app/imports/ui/components/global/IconPicker.vue @@ -8,21 +8,22 @@ >