From 439eadf079185f6e70ba64751ce8b917e29c2cec Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Thu, 11 Feb 2021 15:48:23 +0200 Subject: [PATCH] Condensed logs to a single card per action --- .../api/creature/actions/applyAction.js | 12 ++- .../api/creature/actions/applyAdjustment.js | 30 +++---- .../api/creature/actions/applyAttack.js | 16 ++-- .../api/creature/actions/applyDamage.js | 38 ++++----- .../api/creature/actions/applyProperties.js | 37 ++++---- .../api/creature/actions/castSpellWithSlot.js | 1 + app/imports/api/creature/actions/doAction.js | 49 ++++++++--- .../afterComputation/evaluateString.js | 1 - app/imports/api/creature/log/CreatureLogs.js | 84 +++++++++++-------- .../api/creature/log/LogContentSchema.js | 54 ++++++++++++ .../subSchemas/RollDetailsSchema.js | 19 +++++ app/imports/parser/parseTree/RollNode.js | 2 - .../character/CharacterSheetRightDrawer.vue | 2 +- .../character => log}/CharacterLog.vue | 14 ++-- app/imports/ui/log/LogEntry.vue | 54 ++++++++++++ .../ui/properties/forms/ConstantForm.vue | 3 +- 16 files changed, 281 insertions(+), 135 deletions(-) create mode 100644 app/imports/api/creature/log/LogContentSchema.js create mode 100644 app/imports/api/properties/subSchemas/RollDetailsSchema.js rename app/imports/ui/{creature/character => log}/CharacterLog.vue (91%) create mode 100644 app/imports/ui/log/LogEntry.vue diff --git a/app/imports/api/creature/actions/applyAction.js b/app/imports/api/creature/actions/applyAction.js index 5685edf9..189bd6a2 100644 --- a/app/imports/api/creature/actions/applyAction.js +++ b/app/imports/api/creature/actions/applyAction.js @@ -1,11 +1,9 @@ import spendResources from '/imports/api/creature/actions/spendResources.js' -import {insertCreatureLog} from '/imports/api/creature/log/CreatureLogs.js'; -export default function applyAction({prop, creature}){ +export default function applyAction({prop, log}){ spendResources(prop); - insertCreatureLog.call({ - log: { - text: prop.name, - creatureId: creature._id}, - }); + // If this is not the top level action, we can add its name to the log + if (log.content.length){ + log.content.push({name: prop.name}); + } } diff --git a/app/imports/api/creature/actions/applyAdjustment.js b/app/imports/api/creature/actions/applyAdjustment.js index bdd8d21c..ddc792ef 100644 --- a/app/imports/api/creature/actions/applyAdjustment.js +++ b/app/imports/api/creature/actions/applyAdjustment.js @@ -1,12 +1,12 @@ import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js'; -import {insertCreatureLog} from '/imports/api/creature/log/CreatureLogs.js'; import damagePropertiesByName from '/imports/api/creature/creatureProperties/methods/damagePropertiesByName.js'; export default function applyAdjustment({ prop, creature, targets, - actionContext + actionContext, + log }){ let damageTargets = prop.target === 'self' ? [creature] : targets; let scope = { @@ -16,16 +16,10 @@ export default function applyAdjustment({ try { var {result, errors} = evaluateString(prop.amount, scope, 'reduce'); if (typeof result !== 'number') { - return insertCreatureLog.call({ log: { - text: errors.join(', ') || 'Something went wrong', - creatureId: creature._id, - }}); + log.content.push({error: errors.join(', ') || 'Something went wrong'}); } } catch (e){ - return insertCreatureLog.call({ log: { - text: e.toString(), - creatureId: creature._id, - }}); + log.content.push({error: e.toString()}); } if (damageTargets) { damageTargets.forEach(target => { @@ -38,19 +32,15 @@ export default function applyAdjustment({ operation: prop.operation || 'increment', value: result }); - insertCreatureLog.call({ - log: { - text: `${prop.stat} ${prop.operation === 'set' ? 'set to' : ''} ${-result}`, - creatureId: target._id, - } + log.content.push({ + resultPrefix: `${prop.stat} ${prop.operation === 'set' ? 'set to' : ''}`, + result: `${-result}`, }); }); } else { - insertCreatureLog.call({ - log: { - text: `${prop.stat} ${prop.operation === 'set' ? 'set to' : ''} ${-result}`, - creatureId: creature._id, - } + log.content.push({ + resultPrefix: `${prop.stat} ${prop.operation === 'set' ? 'set to' : ''}`, + result: `${-result}`, }); } } diff --git a/app/imports/api/creature/actions/applyAttack.js b/app/imports/api/creature/actions/applyAttack.js index 3da3d099..99e62ab5 100644 --- a/app/imports/api/creature/actions/applyAttack.js +++ b/app/imports/api/creature/actions/applyAttack.js @@ -1,18 +1,14 @@ import roll from '/imports/parser/roll.js'; -import {insertCreatureLog} from '/imports/api/creature/log/CreatureLogs.js'; export default function applyAttack({ prop, - //children, - creature, - //targets, - //actionContext + log, }){ let result = roll(1, 20)[0] + prop.rollBonusResult; - insertCreatureLog.call({ - log: { - text: `${prop.name} attack. ${result} to hit`, - creatureId: creature._id, - } + log.content.push({ + // If this is not the first item in the log content, give it a name + name: log.content.length ? prop.name + ' attack' : undefined, + result, + details: 'to hit', }); } diff --git a/app/imports/api/creature/actions/applyDamage.js b/app/imports/api/creature/actions/applyDamage.js index 5c3fc3a1..35b2aabe 100644 --- a/app/imports/api/creature/actions/applyDamage.js +++ b/app/imports/api/creature/actions/applyDamage.js @@ -1,12 +1,13 @@ import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js'; -import {insertCreatureLog} from '/imports/api/creature/log/CreatureLogs.js'; import dealDamage from '/imports/api/creature/creatureProperties/methods/dealDamage.js'; +import {insertCreatureLog} from '/imports/api/creature/log/CreatureLogs.js'; export default function applyDamage({ prop, creature, targets, - actionContext + actionContext, + log, }){ let damageTargets = prop.target === 'self' ? [creature] : targets; let scope = { @@ -16,18 +17,16 @@ export default function applyDamage({ try { var {result, errors} = evaluateString(prop.amount, scope, 'reduce'); if (typeof result !== 'number') { - return insertCreatureLog.call({ log: { - text: errors.join(', '), - creatureId: creature._id, - }}); + log.content.push({ + error: errors.join(', '), + }); } } catch (e){ - return insertCreatureLog.call({ log: { - text: e.toString(), - creatureId: creature._id, - }}); + log.content.push({ + error: e.toString(), + }); } - if (damageTargets) { + if (damageTargets && damageTargets.length) { damageTargets.forEach(target => { if (prop.target === 'each'){ result = evaluateString(prop.amount, scope, 'reduce'); @@ -44,20 +43,17 @@ export default function applyDamage({ } }); if (target._id !== creature._id){ - insertCreatureLog.call({ - log: { - text: `Dealt ${damageDealt} ${prop.damageType}${prop.damageType !== 'healing'? ' damage': ''}`, - creatureId: creature._id, - } + log.content.push({ + result: damageDealt, + details: `${prop.damageType}${prop.damageType !== 'healing'? ' damage': ''}` + + `${target.name && ' to '}${target.name}`, }); } }); } else { - insertCreatureLog.call({ - log: { - text: `${result} ${prop.damageType}${prop.damageType !== 'healing'? ' damage': ''}`, - creatureId: creature._id, - } + log.content.push({ + result, + details: `${prop.damageType}${prop.damageType !== 'healing'? ' damage': ''}`, }); } } diff --git a/app/imports/api/creature/actions/applyProperties.js b/app/imports/api/creature/actions/applyProperties.js index fc66f053..ece1aee3 100644 --- a/app/imports/api/creature/actions/applyProperties.js +++ b/app/imports/api/creature/actions/applyProperties.js @@ -6,46 +6,49 @@ import applyBuff from '/imports/api/creature/actions/applyBuff.js'; function applyProperty(options){ let prop = options.prop; - if ( - prop.disabled === true || // ignore disabled props - prop.equipped === false || // ignore unequipped items - prop.toggleResult === false || // ignore untoggled toggles - prop.applied === true // ignore buffs that are already applied - ){ + if (prop.type === 'buff'){ + // ignore only applied buffs + if (prop.applied === true){ + return false; + } + // Ignore inactive props of other types + } else if (prop.inactive === true){ return false; } switch (prop.type){ case 'action': case 'spell': applyAction(options); - return true; + break; case 'attack': applyAction(options); applyAttack(options); - return true; + break; case 'damage': applyDamage(options); - return true; + break; case 'adjustment': applyAdjustment(options); - return true; + break; case 'buff': applyBuff(options); - return false; + break; case 'roll': // applyRoll(options); - return true; + break; case 'savingThrow': // applySavingThrow(options); - return false; + break; } + return true; } export default function applyProperties({ forest, creature, targets, - actionContext + actionContext, + log, }){ forest.forEach(child => { let walkChildren = applyProperty({ @@ -53,14 +56,16 @@ export default function applyProperties({ children: child.children, creature, targets, - actionContext + actionContext, + log, }); if (walkChildren){ applyProperties({ forest: child.children, creature, targets, - actionContext + actionContext, + log, }); } }); diff --git a/app/imports/api/creature/actions/castSpellWithSlot.js b/app/imports/api/creature/actions/castSpellWithSlot.js index d0da6a42..041008a4 100644 --- a/app/imports/api/creature/actions/castSpellWithSlot.js +++ b/app/imports/api/creature/actions/castSpellWithSlot.js @@ -66,6 +66,7 @@ const castSpellWithSlot = new ValidatedMethod({ context: {slotLevel}, creature, target, + method: this, }); // Note this only recomputes the top-level creature, not the nearest one recomputeCreatureByDoc(creature); diff --git a/app/imports/api/creature/actions/doAction.js b/app/imports/api/creature/actions/doAction.js index 8c1e3a7c..80be4a13 100644 --- a/app/imports/api/creature/actions/doAction.js +++ b/app/imports/api/creature/actions/doAction.js @@ -3,6 +3,7 @@ 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.js'; +import CreatureLogs, { CreatureLogSchema, insertCreatureLogWork } from '/imports/api/creature/log/CreatureLogs.js'; import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js'; import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js'; import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js'; @@ -13,10 +14,15 @@ const doAction = new ValidatedMethod({ name: 'creatureProperties.doAction', validate: new SimpleSchema({ actionId: SimpleSchema.RegEx.Id, - targetId: { + targetIds: { + type: Array, + defaultValue: [], + maxCount: 10, + optional: true, + }, + 'targetIds.$': { type: String, regEx: SimpleSchema.RegEx.Id, - optional: true, }, }).validator(), mixins: [RateLimiterMixin], @@ -24,26 +30,41 @@ const doAction = new ValidatedMethod({ numRequests: 10, timeInterval: 5000, }, - run({actionId, targetId}) { + run({actionId, targetIds = []}) { let action = CreatureProperties.findOne(actionId); // Check permissions let creature = getRootCreatureAncestor(action); assertEditPermission(creature, this.userId); - let target = undefined; - if (targetId) { - target = Creatures.findOne(targetId); + let targets = []; + targetIds.forEach(targetId => { + let target = Creatures.findOne(targetId); assertEditPermission(target, this.userId); - } - doActionWork({action, creature, target}); - // Note this only recomputes the top-level creature, not the nearest one + targets.push(target); + }); + doActionWork({action, creature, targets, method: this}); + + // recompute creatures recomputeCreatureByDoc(creature); - if (target){ + targets.forEach(target => { recomputeCreatureByDoc(target); - } + }); }, }); -export function doActionWork({action, creature, target, context = {}}){ +export function doActionWork({ + action, + creature, + targets, + context = {}, + method +}){ + // Create the log + let log = CreatureLogSchema.clean({ + name: action.name, + creatureId: creature._id, + creatureName: creature.name, + }); + let decendantForest = nodesToTree({ collection: CreatureProperties, ancestorId: action._id, @@ -56,8 +77,10 @@ export function doActionWork({action, creature, target, context = {}}){ forest: startingForest, actionContext: context, creature, - target, + targets, + log, }); + insertCreatureLogWork({log, creature, method}); } export default doAction; diff --git a/app/imports/api/creature/computation/afterComputation/evaluateString.js b/app/imports/api/creature/computation/afterComputation/evaluateString.js index e012ff58..5bd780a5 100644 --- a/app/imports/api/creature/computation/afterComputation/evaluateString.js +++ b/app/imports/api/creature/computation/afterComputation/evaluateString.js @@ -23,7 +23,6 @@ export default function evaluateString(string, scope, fn = 'compile'){ errors.push('...'); return {result: string, errors}; } - console.log(node); let context = new CompilationContext(); let result = node[fn](scope, context); if (result instanceof ConstantNode){ diff --git a/app/imports/api/creature/log/CreatureLogs.js b/app/imports/api/creature/log/CreatureLogs.js index c617052e..d6f7bc76 100644 --- a/app/imports/api/creature/log/CreatureLogs.js +++ b/app/imports/api/creature/log/CreatureLogs.js @@ -1,5 +1,6 @@ import SimpleSchema from 'simpl-schema'; import Creatures from '/imports/api/creature/Creatures.js'; +import LogContentSchema from '/imports/api/creature/log/LogContentSchema.js'; import { ValidatedMethod } from 'meteor/mdg:validated-method'; import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; import {assertEditPermission} from '/imports/api/creature/creaturePermissions.js'; @@ -13,13 +14,16 @@ if (Meteor.isServer){ let CreatureLogs = new Mongo.Collection('creatureLogs'); let CreatureLogSchema = new SimpleSchema({ - text: { + name: { type: String, + optional: true, }, - type: { - type: String, - allowedValues: ['roll', 'change', 'damage', 'info'], - defaultValue: 'info', + content: { + type: Array, + defaultValue: [], + }, + 'content.$': { + type: LogContentSchema, }, // The real-world date that it occured, usually sorted by date date: { @@ -37,6 +41,10 @@ let CreatureLogSchema = new SimpleSchema({ regEx: SimpleSchema.RegEx.Id, index: 1, }, + creatureName: { + type: String, + optional: true, + }, }); CreatureLogs.attachSchema(CreatureLogSchema); @@ -73,7 +81,7 @@ const insertCreatureLog = new ValidatedMethod({ timeInterval: 5000, }, validate: new SimpleSchema({ - log: CreatureLogSchema.omit('type', 'date'), + log: CreatureLogSchema.omit('date'), }).validator(), run({log}){ const creatureId = log.creatureId; @@ -87,21 +95,27 @@ const insertCreatureLog = new ValidatedMethod({ }}); assertEditPermission(creature, this.userId); // Build the new log - if (typeof log === 'string'){ - log = {text: log}; - } - log.date = new Date(); - // Insert it - let id = CreatureLogs.insert(log); - if (Meteor.isServer){ - this.unblock(); - removeOldLogs(creatureId); - logWebhook({log, creature}); - } + let id = insertCreatureLogWork({log, creature, method: this}) return id; }, }); +export function insertCreatureLogWork({log, creature, method}){ + // Build the new log + if (typeof log === 'string'){ + log = {text: log}; + } + log.date = new Date(); + // Insert it + let id = CreatureLogs.insert(log); + if (Meteor.isServer){ + method.unblock(); + removeOldLogs(creature._id); + logWebhook({log, creature}); + } + return id; +} + function equalIgnoringWhitespace(a, b){ if (typeof a !== 'string' || typeof b !== 'string') return a === b; @@ -136,38 +150,42 @@ const logRoll = new ValidatedMethod({ }}); assertEditPermission(creature, this.userId); let parsedResult = parse(roll); - let logText; + let logContent; if (parsedResult === null) { - logText = 'Unexpected end of input'; + logContent = [{error: 'Unexpected end of input'}]; } else try { - logText = []; + logContent = []; let rollContext = new CompilationContext(); let compiled = parsedResult.compile(creature.variables, rollContext); let compiledString = compiled.toString(); - if (!equalIgnoringWhitespace(compiledString, roll)) logText.push(roll); - logText.push(compiledString); + if (!equalIgnoringWhitespace(compiledString, roll)) logContent.push({ + result: roll + }); + logContent.push({ + details: compiledString + }); let rolled = compiled.roll(creature.variables, rollContext); let rolledString = rolled.toString(); - if (rolledString !== compiledString) logText.push(rolled.toString()); + if (rolledString !== compiledString) logContent.push({ + details: rolled.toString() + }); let result = rolled.reduce(creature.variables, rollContext); let resultString = result.toString(); - if (resultString !== rolledString) logText.push(resultString); - logText = logText.join('\n\n'); + if (resultString !== rolledString) logContent.push({ + result: resultString + }); } catch (e){ - logText = 'Calculation error'; + logContent = [{error: 'Calculation error'}]; } const log = { - text: logText, + content: logContent, creatureId, date: new Date(), }; - let id = CreatureLogs.insert(log); - if (Meteor.isServer){ - this.unblock(); - removeOldLogs(creatureId); - logWebhook({log, creature}); - } + + let id = insertCreatureLogWork({log, creature, method: this}); + return id; }, }); diff --git a/app/imports/api/creature/log/LogContentSchema.js b/app/imports/api/creature/log/LogContentSchema.js new file mode 100644 index 00000000..824d9133 --- /dev/null +++ b/app/imports/api/creature/log/LogContentSchema.js @@ -0,0 +1,54 @@ +import SimpleSchema from 'simpl-schema'; +import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js'; +import RollDetailsSchema from '/imports/api/properties/subSchemas/RollDetailsSchema.js'; + +let LogContentSchema = new SimpleSchema({ + name: { + type: String, + optional: true, + }, + error: { + type: String, + optional: true, + }, + resultPrefix: { + type: String, + optional: true, + }, + result: { + type: String, + optional: true, + }, + expandedResult: { + type: String, + optional: true, + }, + details: { + type: String, + optional: true, + }, + context: { + type: Object, + optional: true, + }, + 'context.errors':{ + type: Array, + defaultValue: [], + }, + 'context.errors.$': { + type: ErrorSchema, + }, + 'context.rolls': { + type: Array, + defaultValue: [], + }, + 'context.rolls.$': { + type: RollDetailsSchema, + }, + 'context.doubleRolls': { + type: Boolean, + optional: true, + }, +}); + +export default LogContentSchema; diff --git a/app/imports/api/properties/subSchemas/RollDetailsSchema.js b/app/imports/api/properties/subSchemas/RollDetailsSchema.js new file mode 100644 index 00000000..4cf28ed4 --- /dev/null +++ b/app/imports/api/properties/subSchemas/RollDetailsSchema.js @@ -0,0 +1,19 @@ +import SimpleSchema from 'simpl-schema'; + +const RollDetailsSchema = new SimpleSchema({ + number: { + type: Number, + }, + diceSize: { + type: Number, + }, + values: { + type: Array, + defaultValue: [], + }, + 'values.$': { + type: Number, + }, +}); + +export default RollDetailsSchema; diff --git a/app/imports/parser/parseTree/RollNode.js b/app/imports/parser/parseTree/RollNode.js index be84e690..d61495df 100644 --- a/app/imports/parser/parseTree/RollNode.js +++ b/app/imports/parser/parseTree/RollNode.js @@ -30,14 +30,12 @@ export default class RollNode extends ParseNode { return new ErrorNode({ node: this, error: 'Number of dice is not an integer', - previousNodes: [this, left, right], }); } if (!right.isInteger){ return new ErrorNode({ node: this, error: 'Dice size is not an integer', - previousNodes: [this, left, right], }); } let number = left.value; diff --git a/app/imports/ui/creature/character/CharacterSheetRightDrawer.vue b/app/imports/ui/creature/character/CharacterSheetRightDrawer.vue index 0e6338e7..f3af8445 100644 --- a/app/imports/ui/creature/character/CharacterSheetRightDrawer.vue +++ b/app/imports/ui/creature/character/CharacterSheetRightDrawer.vue @@ -10,7 +10,7 @@ + + diff --git a/app/imports/ui/properties/forms/ConstantForm.vue b/app/imports/ui/properties/forms/ConstantForm.vue index ef4fd46b..e6978aeb 100644 --- a/app/imports/ui/properties/forms/ConstantForm.vue +++ b/app/imports/ui/properties/forms/ConstantForm.vue @@ -39,8 +39,7 @@ export default { computed: { // We can't rely on autoValue running in every form, so recalculate errors clientErrors(){ - let validationContext = ConstantSchema.newContext(); - let cleanModel = validationContext.clean(this.model); + let cleanModel = ConstantSchema.clean(this.model); return cleanModel.errors; } }