Condensed logs to a single card per action

This commit is contained in:
Stefan Zermatten
2021-02-11 15:48:23 +02:00
parent d7083cf242
commit 439eadf079
16 changed files with 281 additions and 135 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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);

View File

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

View File

@@ -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){

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,7 +10,7 @@
</template>
<script>
import LogTab from '/imports/ui/creature/character/CharacterLog.vue';
import LogTab from '/imports/ui/log/CharacterLog.vue';
export default {
components: {
LogTab,

View File

@@ -7,15 +7,11 @@
class="log flex layout column reverse align-end pa-3"
style="overflow: auto;"
>
<v-card
<log-entry
v-for="log in logs"
:key="log._id"
class="ma-2"
>
<v-card-text class="pa-2">
<markdown-text :markdown="log.text" />
</v-card-text>
</v-card>
:model="log"
/>
</div>
<v-card>
<v-text-field
@@ -39,11 +35,11 @@ import CreatureLogs, { logRoll } from '/imports/api/creature/log/CreatureLogs.js
import Creatures from '/imports/api/creature/Creatures.js';
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
import { parse } from '/imports/parser/parser.js';
import MarkdownText from '/imports/ui/components/MarkdownText.vue';
import LogEntry from '/imports/ui/log/LogEntry.vue';
export default {
components: {
MarkdownText,
LogEntry,
},
props: {
creatureId: {

View File

@@ -0,0 +1,54 @@
<template lang="html">
<v-card
class="ma-2"
>
<v-card-title
v-if="model.name"
class="pa-2"
>
<h3>
{{ model.name }}
</h3>
</v-card-title>
<v-card-text
v-if="model.text || (model.content && model.content.length)"
class="pa-2"
>
{{ model.text }}
<div
v-for="(content, index) in model.content"
:key="index"
class="content-line"
>
{{ content.name }}
<span
v-if="content.error"
class="error"
>{{ content.error }}</span>
{{ content.resultPrefix }}
<span
v-if="content.result"
class="subheading font-weight-bold mx-1"
>{{ content.result }}</span>
{{ content.details }}
</div>
</v-card-text>
</v-card>
</template>
<script>
export default {
props: {
model: {
type: Object,
required: true,
},
},
}
</script>
<style lang="css" scoped>
.content-line {
min-height: 24px;
}
</style>

View File

@@ -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;
}
}