Compare commits

..

15 Commits

Author SHA1 Message Date
Stefan Zermatten
dce2c92516 Added attack roll bonus and dc to spell list. Use them in spells with #spellList.dcResult and #spellList.attackRollBonusResult 2021-02-23 15:21:20 +02:00
Stefan Zermatten
0fe2780983 Added property viewer for Toggle properties 2021-02-23 15:07:07 +02:00
Stefan Zermatten
e126cdd3cb Added property viewer for slot filler 2021-02-23 14:59:53 +02:00
Stefan Zermatten
d69ada0db4 Slot quantity is now a computed value, added property viewer for slots 2021-02-23 14:53:47 +02:00
Stefan Zermatten
858915b25b Added viewer for Saving Throw properties 2021-02-23 14:38:20 +02:00
Stefan Zermatten
d10a7eca14 Added viewer for Roll properties 2021-02-23 14:29:48 +02:00
Stefan Zermatten
671d17018c Added a viewer for Constant properties 2021-02-23 14:23:00 +02:00
Stefan Zermatten
f2883d320f Improved Attribute damage viewer 2021-02-23 13:59:26 +02:00
Stefan Zermatten
aad0c7249e Removed stray log to console 2021-02-23 12:47:34 +02:00
Stefan Zermatten
612fcca68c Only split properties accross targets if there are targets 2021-02-22 14:30:50 +02:00
Stefan Zermatten
12939c46de made saves walk children when not targeted at self 2021-02-22 14:28:38 +02:00
Stefan Zermatten
3801b17fde Attacks can now critical hit. criticalHitTarget overrides the roll required 2021-02-22 14:07:12 +02:00
Stefan Zermatten
88133a2fa3 Saving throws now work in actions 2021-02-22 12:38:21 +02:00
Stefan Zermatten
d00eedac19 Rolls now work in actions 2021-02-22 11:55:08 +02:00
Stefan Zermatten
6571fb860a Toggles now work in actions to make choices based on action context 2021-02-22 11:36:30 +02:00
33 changed files with 681 additions and 83 deletions

View File

@@ -4,12 +4,19 @@ export default function applyAttack({
prop, prop,
log, log,
actionContext, actionContext,
creature,
}){ }){
let value = roll(1, 20)[0]; let value = roll(1, 20)[0];
actionContext.attackRoll = {value}; actionContext.attackRoll = {value};
let criticalHitTarget = creature.variables.criticalHitTarget &&
creature.variables.criticalHitTarget.currentValue || 20;
let criticalHit = value >= criticalHitTarget;
if (criticalHit) actionContext.criticalHit = {value: true};
let result = value + prop.rollBonusResult; let result = value + prop.rollBonusResult;
actionContext.toHit = {value: result};
log.content.push({ log.content.push({
name: 'To Hit', name: criticalHit ? 'Critical Hit!' : 'To Hit',
resultPrefix: `1d20 [${value}] + ${prop.rollBonusResult} = `, resultPrefix: `1d20 [${value}] + ${prop.rollBonusResult} = `,
result, result,
}); });

View File

@@ -1,6 +1,7 @@
import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js'; import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
import dealDamage from '/imports/api/creature/creatureProperties/methods/dealDamage.js'; import dealDamage from '/imports/api/creature/creatureProperties/methods/dealDamage.js';
import {insertCreatureLog} from '/imports/api/creature/log/CreatureLogs.js'; import {insertCreatureLog} from '/imports/api/creature/log/CreatureLogs.js';
import { CompilationContext } from '/imports/parser/parser.js';
export default function applyDamage({ export default function applyDamage({
prop, prop,
@@ -14,8 +15,19 @@ export default function applyDamage({
...creature.variables, ...creature.variables,
...actionContext, ...actionContext,
}; };
if (targets.length === 1){
scope.target = targets[0].variables;
}
let criticalHit = !!(
actionContext.criticalHit &&
actionContext.criticalHit.value &&
prop.damageType !== 'healing' // Can't critically heal
);
let context = new CompilationContext({
doubleRolls: criticalHit,
});
try { try {
var {result, errors} = evaluateString(prop.amount, scope, 'reduce'); var {result, errors} = evaluateString(prop.amount, scope, 'reduce', context);
if (typeof result !== 'number') { if (typeof result !== 'number') {
log.content.push({ log.content.push({
error: errors.join(', '), error: errors.join(', '),
@@ -26,11 +38,13 @@ export default function applyDamage({
error: e.toString(), error: e.toString(),
}); });
} }
let suffix = (criticalHit ? ' critical ' : '') +
prop.damageType +
(prop.damageType !== 'healing' ? ' damage': '');
if (damageTargets && damageTargets.length) { if (damageTargets && damageTargets.length) {
damageTargets.forEach(target => { damageTargets.forEach(target => {
let name = prop.damageType === 'healing' ? 'Healing' : 'Damage'; let name = prop.damageType === 'healing' ? 'Healing' : 'Damage';
let suffix = prop.damageType +
prop.damageType !== 'healing' ? ' damage': '';
if (prop.target === 'each'){ if (prop.target === 'each'){
result = evaluateString(prop.amount, scope, 'reduce'); result = evaluateString(prop.amount, scope, 'reduce');
} }
@@ -69,7 +83,7 @@ export default function applyDamage({
log.content.push({ log.content.push({
name: prop.damageType === 'healing' ? 'Healing' : 'Damage', name: prop.damageType === 'healing' ? 'Healing' : 'Damage',
result, result,
details: `${prop.damageType}${prop.damageType !== 'healing'? ' damage': ''}`, details: suffix,
}); });
} }
} }

View File

@@ -1,8 +1,11 @@
import applyAction from '/imports/api/creature/actions/applyAction.js'; import applyAction from '/imports/api/creature/actions/applyAction.js';
import applyAdjustment from '/imports/api/creature/actions/applyAdjustment.js'; import applyAdjustment from '/imports/api/creature/actions/applyAdjustment.js';
import applyAttack from '/imports/api/creature/actions/applyAttack.js'; import applyAttack from '/imports/api/creature/actions/applyAttack.js';
import applyDamage from '/imports/api/creature/actions/applyDamage.js';
import applyBuff from '/imports/api/creature/actions/applyBuff.js'; import applyBuff from '/imports/api/creature/actions/applyBuff.js';
import applyDamage from '/imports/api/creature/actions/applyDamage.js';
import applyRoll from '/imports/api/creature/actions/applyRoll.js';
import applyToggle from '/imports/api/creature/actions/applyToggle.js';
import applySave from '/imports/api/creature/actions/applySave.js';
function applyProperty(options){ function applyProperty(options){
let prop = options.prop; let prop = options.prop;
@@ -11,8 +14,13 @@ function applyProperty(options){
if (prop.applied === true){ if (prop.applied === true){
return false; return false;
} }
// Only ignore toggles if they wont be computed
} else if (prop.type === 'toggle') {
if (prop.disabled) return false;
if (prop.enabled) return true;
if (!prop.condition) return false;
// Ignore inactive props of other types // Ignore inactive props of other types
} else if (prop.inactive === true){ } else if (prop.deactivatedBySelf === true){
return false; return false;
} }
switch (prop.type){ switch (prop.type){
@@ -33,40 +41,40 @@ function applyProperty(options){
case 'buff': case 'buff':
applyBuff(options); applyBuff(options);
break; break;
case 'toggle':
return applyToggle(options);
case 'roll': case 'roll':
// applyRoll(options); applyRoll(options);
break; break;
case 'savingThrow': case 'savingThrow':
// applySavingThrow(options); return applySave(options);
break;
} }
return true; return true;
} }
export default function applyProperties({ function applyPropertyAndWalkChildren({prop, child, targets, ...options}){
forest, let shouldKeepWalking = applyProperty({ prop, targets, ...options });
creature, if (shouldKeepWalking){
targets, applyProperties({ forest: child.children, targets, ...options,});
actionContext, }
log, }
}){
export default function applyProperties({ forest, targets, ...options}){
forest.forEach(child => { forest.forEach(child => {
let walkChildren = applyProperty({ let prop = child.node;
prop: child.node, if (shouldSplit(prop) && targets.length){
children: child.children, targets.forEach(target => {
creature, let targets = [target]
targets, applyPropertyAndWalkChildren({ targets, prop, child, ...options});
actionContext,
log,
});
if (walkChildren){
applyProperties({
forest: child.children,
creature,
targets,
actionContext,
log,
}); });
} else {
applyPropertyAndWalkChildren({prop, child, targets, ...options});
} }
}); });
} }
function shouldSplit(prop){
if (prop.target === 'each'){
return true;
}
}

View File

@@ -0,0 +1,32 @@
import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
export default function applyRoll({
prop,
creature,
actionContext,
log,
}){
let scope = {
...creature.variables,
...actionContext,
};
try {
var {result, errors} = evaluateString(prop.roll, scope, 'reduce');
actionContext[prop.variableName] = result;
log.content.push({
name: prop.name,
resultPrefix: prop.variableName + ' = ' + prop.roll + ' = ',
result,
});
if (errors.length) {
log.content.push({
error: errors.join(', '),
});
}
} catch (e){
log.content.push({
error: e.toString(),
});
}
}

View File

@@ -0,0 +1,79 @@
import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
import CreaturesProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import roll from '/imports/parser/roll.js';
export default function applySave({
prop,
creature,
actionContext,
log,
}){
let scope = {
...creature.variables,
...actionContext,
};
try {
// Calculate the DC
var {result, errors} = evaluateString(prop.dc, scope, 'reduce');
let dc = result;
log.content.push({
name: prop.name,
resultPrefix: ' DC ',
result,
});
if (errors.length) {
log.content.push({
error: errors.join(', '),
});
return false;
}
if (prop.target === 'self'){
let save = CreaturesProperties.findOne({
'ancestors.id': creature._id,
type: 'skill',
skillType: 'save',
variableName: prop.stat,
removed: {$ne: true},
inactive: {$ne: true},
});
if (!save){
log.content.push({
error: 'No saving throw found: ' + prop.stat,
});
return;
}
let value, values, resultPrefix;
if (save.advantage === 1){
values = roll(2, 20).sort().reverse();
value = values[0];
resultPrefix = `Advantage: 1d20 [${values[0]},~~${values[1]}~~] + ${save.value} = `
} else if (save.advantage === -1){
values = roll(2, 20).sort();
value = values[0];
resultPrefix = `Disadvantage: 1d20 [${values[0]},~~${values[1]}~~] + ${save.value} = `
} else {
values = roll(1, 20);
value = values[0];
resultPrefix = `1d20 [${value}] + ${save.value} = `
}
actionContext.savingThrowRoll = {value};
let result = value + save.value;
actionContext.savingThrow = {value: result};
let saveSuccess = result >= dc;
log.content.push({
name: 'Save',
resultPrefix,
result,
details: saveSuccess ? 'Passed' : 'Failed'
});
return !saveSuccess;
} else {
// TODO
return true;
}
} catch (e){
log.content.push({
error: e.toString(),
});
}
}

View File

@@ -0,0 +1,35 @@
import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
export default function applyToggle({
prop,
creature,
actionContext,
log,
}){
let scope = {
...creature.variables,
...actionContext,
};
if (Number.isFinite(+prop.condition)){
return !!+prop.condition;
}
try {
var {result, errors} = evaluateString(prop.condition, scope, 'reduce');
if (typeof result !== 'number' && typeof result !== 'boolean') {
log.content.push({
error: errors.join(', '),
});
return false;
}
log.content.push({
name: prop.name,
resultPrefix: prop.condition + ' = ',
result,
});
return !!result;
} catch (e){
log.content.push({
error: e.toString(),
});
}
}

View File

@@ -1,7 +1,7 @@
import { parse, CompilationContext } from '/imports/parser/parser.js'; import { parse, CompilationContext } from '/imports/parser/parser.js';
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js'; import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
export default function evaluateString(string, scope, fn = 'compile'){ export default function evaluateString(string, scope, fn = 'compile', context){
let errors = []; let errors = [];
if (!string){ if (!string){
errors.push('No string provided'); errors.push('No string provided');
@@ -18,7 +18,9 @@ export default function evaluateString(string, scope, fn = 'compile'){
errors.push(e); errors.push(e);
return {result: string, errors}; return {result: string, errors};
} }
let context = new CompilationContext(); if (!context){
context = new CompilationContext({});
}
let result = node[fn](scope, context); let result = node[fn](scope, context);
if (result instanceof ConstantNode){ if (result instanceof ConstantNode){
return {result: result.value, errors: context.errors} return {result: result.value, errors: context.errors}

View File

@@ -14,16 +14,22 @@ export default function computeEndStepProperty(prop, memo){
break; break;
case 'attack': case 'attack':
computeAction(prop, memo); computeAction(prop, memo);
computeAttack(prop, memo); computePropertyField(prop, memo, 'rollBonus');
break; break;
case 'savingThrow': case 'savingThrow':
computeSavingThrow(prop, memo); computePropertyField(prop, memo, 'dc');
break; break;
case 'spellList': case 'spellList':
computeSpellList(prop, memo); computePropertyField(prop, memo, 'maxPrepared');
computePropertyField(prop, memo, 'attackRollBonus');
computePropertyField(prop, memo, 'dc');
break; break;
case 'propertySlot': case 'propertySlot':
computeSlot(prop, memo); computePropertyField(prop, memo, 'quantityExpected');
computePropertyField(prop, memo, 'slotCondition');
break;
case 'roll':
computePropertyField(prop, memo, 'roll', 'compile');
break; break;
} }
} }
@@ -111,19 +117,3 @@ function computePropertyField(prop, memo, fieldName, fn){
delete prop[`${fieldName}Errors`]; delete prop[`${fieldName}Errors`];
} }
} }
function computeAttack(prop, memo){
computePropertyField(prop, memo, 'rollBonus');
}
function computeSavingThrow(prop, memo){
computePropertyField(prop, memo, 'dc');
}
function computeSpellList(prop, memo){
computePropertyField(prop, memo, 'maxPrepared');
}
function computeSlot(prop, memo){
computePropertyField(prop, memo, 'slotCondition');
}

View File

@@ -21,6 +21,9 @@ export default function evaluateCalculation({
context, context,
dependencies, dependencies,
}; };
if (typeof string !== 'string'){
string = string.toString();
}
// Parse the string // Parse the string
let calc; let calc;
try { try {

View File

@@ -43,6 +43,13 @@ let CreaturePropertySchema = new SimpleSchema({
optional: true, optional: true,
index: 1, index: 1,
}, },
// Denormalised flag if this property was made inactive because of its own
// state
deactivatedBySelf: {
type: Boolean,
optional: true,
index: 1,
},
// Denormalised list of all properties or creatures this property depends on // Denormalised list of all properties or creatures this property depends on
dependencies: { dependencies: {
type: Array, type: Array,

View File

@@ -20,9 +20,16 @@ export default function recomputeInactiveProperties(ancestorId){
CreatureProperties.update({ CreatureProperties.update({
'ancestors.id': ancestorId, 'ancestors.id': ancestorId,
'_id': {$in: disabledIds}, '_id': {$in: disabledIds},
$or: [{inactive: {$ne: true}}, {deactivatedByAncestor: true}], $or: [
{inactive: {$ne: true}},
{deactivatedBySelf: {$ne: true}},
{deactivatedByAncestor: true},
],
}, { }, {
$set: {inactive: true}, $set: {
inactive: true,
deactivatedBySelf: true,
},
$unset: {deactivatedByAncestor: 1}, $unset: {deactivatedByAncestor: 1},
}, { }, {
multi: true, multi: true,
@@ -31,7 +38,10 @@ export default function recomputeInactiveProperties(ancestorId){
// Decendants of inactive properties // Decendants of inactive properties
CreatureProperties.update({ CreatureProperties.update({
'ancestors.id': {$eq: ancestorId, $in: disabledIds}, 'ancestors.id': {$eq: ancestorId, $in: disabledIds},
$or: [{inactive: {$ne: true}}, {deactivatedByAncestor: {$ne: true}}], $or: [
{inactive: {$ne: true}},
{deactivatedByAncestor: {$ne: true}},
],
}, { }, {
$set: { $set: {
inactive: true, inactive: true,
@@ -46,7 +56,10 @@ export default function recomputeInactiveProperties(ancestorId){
CreatureProperties.update({ CreatureProperties.update({
'ancestors.id': {$eq: ancestorId, $nin: disabledIds}, 'ancestors.id': {$eq: ancestorId, $nin: disabledIds},
'_id': {$nin: disabledIds}, '_id': {$nin: disabledIds},
$or: [{inactive: true}, {deactivatedByAncestor: true}], $or: [
{inactive: true},
{deactivatedByAncestor: true},
],
}, { }, {
$unset: { $unset: {
inactive: 1, inactive: 1,

View File

@@ -23,10 +23,14 @@ export default function recomputeSlotFullness(ancestorId){
} }
}); });
let spaceLeft; let spaceLeft;
if (slot.quantityExpected === 0){ let expected = slot.quantityExpectedResult;
if (typeof expected !== 'number'){
expected = 1;
}
if (expected === 0){
spaceLeft = null; spaceLeft = null;
} else { } else {
spaceLeft = slot.quantityExpected - totalFilled; spaceLeft = expected - totalFilled;
} }
if (slot.totalFilled !== totalFilled || slot.spaceLeft !== spaceLeft){ if (slot.totalFilled !== totalFilled || slot.spaceLeft !== spaceLeft){
CreatureProperties.update(slot._id, { CreatureProperties.update(slot._id, {

View File

@@ -1,5 +1,6 @@
import SimpleSchema from 'simpl-schema'; import SimpleSchema from 'simpl-schema';
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js'; import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js';
/** /**
* Rolls are children to actions or other rolls, they are triggered with 0 or * Rolls are children to actions or other rolls, they are triggered with 0 or
@@ -20,6 +21,17 @@ import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
* child rolls are applied * child rolls are applied
*/ */
let RollSchema = new SimpleSchema({ let RollSchema = new SimpleSchema({
name: {
type: String,
defaultValue: 'New Roll',
},
// The technical, lowercase, single-word name used in formulae
variableName: {
type: String,
regEx: VARIABLE_NAME_REGEX,
min: 2,
defaultValue: 'newRoll',
},
// The roll, can be simplified, but only computed in context // The roll, can be simplified, but only computed in context
roll: { roll: {
type: String, type: String,

View File

@@ -8,11 +8,22 @@ let SavingThrowSchema = new SimpleSchema ({
type: String, type: String,
optional: true, optional: true,
}, },
// The computed DC
dc: { dc: {
type: String, type: String,
optional: true, optional: true,
}, },
// The variable name of ability the save to roll // Who this saving throw applies to
target: {
type: String,
defaultValue: 'every',
allowedValues: [
'self', // the character who took the action
'each', // rolled once for `each` target
'every', // rolled once and applied to `every` target
],
},
// The variable name of save to roll
stat: { stat: {
type: String, type: String,
optional: true, optional: true,

View File

@@ -22,9 +22,9 @@ let SlotSchema = new SimpleSchema({
type: String, type: String,
}, },
quantityExpected: { quantityExpected: {
type: SimpleSchema.Integer, type: String,
defaultValue: 1, optional: true,
min: 0, defaultValue: '1',
}, },
ignored: { ignored: {
type: Boolean, type: Boolean,

View File

@@ -23,6 +23,16 @@ let SpellListSchema = new SimpleSchema({
type: String, type: String,
optional: true, optional: true,
}, },
// Calculation of The attack roll bonus used by spell attacks in this list
attackRollBonus: {
type: String,
optional: true,
},
// Calculation of the save dc used by spells in this list
dc: {
type: String,
optional: true,
},
}); });
const ComputedOnlySpellListSchema = new SimpleSchema({ const ComputedOnlySpellListSchema = new SimpleSchema({
@@ -33,6 +43,7 @@ const ComputedOnlySpellListSchema = new SimpleSchema({
}, },
'descriptionCalculations.$': InlineComputationSchema, 'descriptionCalculations.$': InlineComputationSchema,
// maxPrepared
maxPreparedResult: { maxPreparedResult: {
type: Number, type: Number,
optional: true, optional: true,
@@ -44,6 +55,32 @@ const ComputedOnlySpellListSchema = new SimpleSchema({
'maxPreparedErrors.$':{ 'maxPreparedErrors.$':{
type: ErrorSchema, type: ErrorSchema,
}, },
// attackRollBonus
attackRollBonusResult: {
type: Number,
optional: true,
},
attackRollBonusErrors: {
type: Array,
optional: true,
},
'attackRollBonusErrors.$':{
type: ErrorSchema,
},
// dc
dcResult: {
type: Number,
optional: true,
},
dcErrors: {
type: Array,
optional: true,
},
'dcErrors.$':{
type: ErrorSchema,
},
}); });
const ComputedSpellListSchema = new SimpleSchema() const ComputedSpellListSchema = new SimpleSchema()

View File

@@ -10,7 +10,6 @@ export default function sendWebhook({webhookURL, data = {}}){
const hook = new Discord.WebhookClient(id, token); const hook = new Discord.WebhookClient(id, token);
// Send a message using the webhook // Send a message using the webhook
console.log(JSON.stringify(data, null, 2));
hook.send(data); hook.send(data);
} }

View File

@@ -8,8 +8,8 @@
<h3 class="layout row align-center"> <h3 class="layout row align-center">
{{ slot.name }} {{ slot.name }}
<v-spacer /> <v-spacer />
<span v-if="slot.quantityExpected > 1"> <span v-if="slot.quantityExpectedResult > 1">
{{ slot.totalFilled }} / {{ slot.quantityExpected }} {{ slot.totalFilled }} / {{ slot.quantityExpectedResult }}
</span> </span>
</h3> </h3>
<v-list v-if="slot.children.length"> <v-list v-if="slot.children.length">
@@ -38,7 +38,7 @@
</v-list-tile> </v-list-tile>
</v-list> </v-list>
<v-btn <v-btn
v-if="!slot.quantityExpected || slot.spaceLeft" v-if="!slot.quantityExpectedResult || slot.spaceLeft"
icon icon
:data-id="`slot-add-button-${slot._id}`" :data-id="`slot-add-button-${slot._id}`"
class="slot-add-button" class="slot-add-button"

View File

@@ -1,5 +1,21 @@
<template lang="html"> <template lang="html">
<div class="roll-form"> <div class="roll-form">
<div class="layout row wrap">
<text-field
label="Name"
:value="model.name"
:error-messages="errors.name"
@change="change('name', ...arguments)"
/>
<text-field
label="Variable name"
:value="model.variableName"
style="flex-basis: 300px;"
hint="Use this name in action formulae to refer to the result of this roll"
:error-messages="errors.variableName"
@change="change('variableName', ...arguments)"
/>
</div>
<text-field <text-field
ref="focusFirst" ref="focusFirst"
label="Roll" label="Roll"

View File

@@ -8,6 +8,7 @@
@change="change('name', ...arguments)" @change="change('name', ...arguments)"
/> />
<text-field <text-field
ref="focusFirst"
label="DC" label="DC"
:value="model.dc" :value="model.dc"
:error-messages="errors.dc" :error-messages="errors.dc"
@@ -21,6 +22,15 @@
:error-messages="errors.stat" :error-messages="errors.stat"
@change="change('stat', ...arguments)" @change="change('stat', ...arguments)"
/> />
<smart-select
label="Target"
:hint="targetOptionHint"
:items="targetOptions"
:value="model.target"
:error-messages="errors.target"
:menu-props="{auto: true, lazy: true}"
@change="change('target', ...arguments)"
/>
<smart-combobox <smart-combobox
label="Tags" label="Tags"
class="mr-2" class="mr-2"
@@ -40,5 +50,34 @@ import propertyFormMixin from '/imports/ui/properties/forms/shared/propertyFormM
export default { export default {
mixins: [saveListMixin, propertyFormMixin], mixins: [saveListMixin, propertyFormMixin],
computed: {
targetOptions(){
return [
{
text: 'Self',
value: 'self',
}, {
text: 'Roll once for each target',
value: 'each',
}, {
text: 'Roll once and apply to every target',
value: 'every',
},
];
},
targetOptionHint(){
let hints = {
self: 'The damage will be applied to the character\'s own attribute when taking the action',
target: 'The damage will be applied to the target of the action',
each: 'The damage will be rolled separately for each of the targets of the action',
every: 'The damage will be rolled once and applied to each of the targets of the action',
};
if (this.parentTarget === 'singleTarget'){
hints.each = hints.target;
hints.every = hints.target;
}
return hints[this.model.target];
}
},
}; };
</script> </script>

View File

@@ -28,8 +28,6 @@
/> />
<text-field <text-field
label="Quantity" label="Quantity"
type="number"
min="0"
hint="How many matching properties must be used to fill this slot, 0 is unlimited" hint="How many matching properties must be used to fill this slot, 0 is unlimited"
:value="model.quantityExpected" :value="model.quantityExpected"
:error-messages="errors.quantityExpected" :error-messages="errors.quantityExpected"

View File

@@ -25,7 +25,7 @@
@change="change('description', ...arguments)" @change="change('description', ...arguments)"
/> />
<calculation-error-list :calculations="model.descriptionCalculations" /> <calculation-error-list :calculations="model.descriptionCalculations" />
<text-field <text-field
label="Maximum prepared spells" label="Maximum prepared spells"
:value="model.maxPrepared" :value="model.maxPrepared"
@@ -35,6 +35,24 @@
/> />
<calculation-error-list :errors="model.maxPreparedErrors" /> <calculation-error-list :errors="model.maxPreparedErrors" />
<text-field
label="Spell save DC"
:value="model.dc"
hint="The spell save DC of spells in this list"
:error-messages="errors.dc"
@change="change('dc', ...arguments)"
/>
<calculation-error-list :errors="model.dcErrors" />
<text-field
label="Attack roll bonus"
:value="model.attackRollBonus"
hint="The attack roll bonus of spell attacks made by spells in this list"
:error-messages="errors.attackRollBonus"
@change="change('attackRollBonus', ...arguments)"
/>
<calculation-error-list :errors="model.attackRollBonusErrors" />
<smart-combobox <smart-combobox
label="Tags" label="Tags"
multiple multiple

View File

@@ -10,7 +10,11 @@
<div <div
class="text-no-wrap text-truncate" class="text-no-wrap text-truncate"
> >
{{ model.amountResult }} {{ model.stat }} damage <span v-if="model.amountResult < 0">+</span>
{{ absoluteAmount }} {{ model.stat }}
<span v-if="typeof absoluteAmount === 'string' || absoluteAmount >= 0">
damage
</span>
</div> </div>
</div> </div>
</template> </template>
@@ -20,5 +24,14 @@ import treeNodeViewMixin from '/imports/ui/properties/treeNodeViews/treeNodeView
export default { export default {
mixins: [treeNodeViewMixin], mixins: [treeNodeViewMixin],
computed: {
absoluteAmount(){
if (typeof this.model.amountResult === 'number'){
return Math.abs(this.model.amountResult);
} else {
return this.model.amountResult;
}
},
}
} }
</script> </script>

View File

@@ -1,14 +1,81 @@
<template lang="html"> <template lang="html">
<div class="adjustment-viewer layout row align-center"> <v-list-tile class="effect-viewer">
{{ model.amountResult }} {{ model.stat }} damage <v-list-tile-avatar>
</div> <v-tooltip bottom>
<template
v-if="effectIcon !== 'remove'"
#activator="{ on }"
>
<v-icon
class="mx-2"
style="cursor: default;"
v-on="on"
>
{{ effectIcon }}
</v-icon>
</template>
<span>{{ tooltip }}</span>
</v-tooltip>
</v-list-tile-avatar>
<v-list-tile-action class="headline">
{{ displayedValue }}
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>
<code>{{ model.stat }}</code>
<template v-if="effectIcon === 'remove'">
damage
</template>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</template> </template>
<script> <script>
import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js'; import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js';
import getEffectIcon from '/imports/ui/utility/getEffectIcon.js';
export default { export default {
mixins: [propertyViewerMixin], mixins: [propertyViewerMixin],
computed: {
effectIcon(){
let effectOp = this.model.operation === 'increment' ? 'add' : 'base';
let value = this.value;
if (typeof value === 'string'){
value = 1;
}
return getEffectIcon(effectOp, -value);
},
value(){
return 'amountResult' in this.model ?
this.model.amountResult :
this.model.amount;
},
displayedValue(){
if (
typeof this.value === 'number' &&
this.model.operation !== 'set'
){
return Math.abs(this.value);
} else {
return this.value;
}
},
tooltip(){
if (this.model.operation === 'increment'){
if (
typeof this.value === 'string' ||
this.value >= 0
){
return 'Minus';
} else {
return 'Add'
}
} else {
return 'Set'
}
}
},
} }
</script> </script>

View File

@@ -0,0 +1,23 @@
<template lang="html">
<div class="buff-viewer">
<property-name :value="model.name" />
<property-variable-name :value="model.variableName" />
<property-field
name="Calculation"
:value="model.calculation"
/>
<calculation-error-list :errors="model.errors" />
</div>
</template>
<script>
import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js'
import CalculationErrorList from '/imports/ui/properties/forms/shared/CalculationErrorList.vue';
export default {
components: {
CalculationErrorList,
},
mixins: [propertyViewerMixin],
}
</script>

View File

@@ -0,0 +1,23 @@
<template lang="html">
<div class="buff-viewer">
<property-name :value="model.name" />
<property-variable-name :value="model.variableName" />
<property-field
name="Roll"
:value="'rollResult' in model ? model.rollResult : model.roll"
/>
<calculation-error-list :errors="model.rollErrors" />
</div>
</template>
<script>
import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js'
import CalculationErrorList from '/imports/ui/properties/forms/shared/CalculationErrorList.vue';
export default {
components: {
CalculationErrorList,
},
mixins: [propertyViewerMixin],
}
</script>

View File

@@ -0,0 +1,21 @@
<template lang="html">
<div class="buff-viewer">
<property-name :value="model.name" />
<property-field
name="Save"
:value="model.stat"
/>
<property-field
name="DC"
:value="'dcResult' in model ? model.dcResult : model.dc"
/>
</div>
</template>
<script>
import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js'
export default {
mixins: [propertyViewerMixin],
}
</script>

View File

@@ -0,0 +1,37 @@
<template lang="html">
<div class="slot-filler-viewer">
<property-name :value="model.name" />
<v-img
v-if="model.picture"
:src="model.picture"
:height="200"
contain
class="slot-card-image"
/>
<property-field
name="Type"
:value="model.slotFillerType"
/>
<property-field
name="Quantity"
:value="model.slotQuantityFilled"
/>
<property-field
name="Condition"
:value="model.slotFillerCondition"
/>
<property-description
:string="model.description"
:calculations="model.descriptionCalculations"
:inactive="model.inactive"
/>
</div>
</template>
<script>
import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js';
export default {
mixins: [propertyViewerMixin],
}
</script>

View File

@@ -0,0 +1,41 @@
<template lang="html">
<div class="buff-viewer">
<property-name :value="model.name" />
<property-field
name="Type"
:value="model.slotType"
/>
<property-field
name="Quantity"
:value="'quantityExpectedResult' in model ? model.quantityExpectedResult : model.quantityExpected"
/>
<property-field
name="Condition"
:value="model.slotCondition"
/>
<property-field
v-if="'slotConditionResult' in model"
name="Condition result"
:value="model.slotConditionResult"
/>
<template v-if="model.tags.length">
<div class="caption">
Tags
</div>
<property-tags :tags="model.tags" />
</template>
<property-description
:string="model.description"
:calculations="model.descriptionCalculations"
:inactive="model.inactive"
/>
</div>
</template>
<script>
import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js'
export default {
mixins: [propertyViewerMixin],
}
</script>

View File

@@ -4,7 +4,15 @@
<property-variable-name :value="model.variableName" /> <property-variable-name :value="model.variableName" />
<property-field <property-field
name="Maximum prepared spells" name="Maximum prepared spells"
:value="model.maxPreparedResult" :value="'maxPreparedResult' in model ? model.maxPreparedResult : model.maxPrepared"
/>
<property-field
name="Spell Save DC"
:value="'dcResult' in model ? model.dcResult : model.dcResult"
/>
<property-field
name="Attack roll bonus"
:value="'attackRollBonusResult' in model ? model.attackRollBonusResult : model.attackRollBonus"
/> />
<property-description <property-description
:string="model.description" :string="model.description"

View File

@@ -0,0 +1,31 @@
<template lang="html">
<div class="toggle-viewer">
<property-name :value="model.name" />
<property-field
v-if="model.disabled || model.enabled"
name="Status"
:value="model.enabled ? 'Enabled' : 'Disabled'"
/>
<template
v-else-if="model.condition"
>
<property-field
name="Condition"
:value="model.condition"
/>
<property-field
v-if="'toggleResult' in model"
name="Result"
:value="model.toggleResult"
/>
</template>
</div>
</template>
<script>
import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js'
export default {
mixins: [propertyViewerMixin],
}
</script>

View File

@@ -1,11 +1,11 @@
<template lang="html"> <template lang="html">
<div v-if="value !== undefined || $slots.default"> <div v-if="value !== undefined || $slots.default">
<div class="caption"> <div class="caption">
{{name}} {{ name }}
</div> </div>
<p class="ml-2 subheading"> <p class="ml-2 subheading">
<slot> <slot>
{{value}} {{ value }}
</slot> </slot>
</p> </p>
</div> </div>
@@ -15,7 +15,7 @@
export default { export default {
props: { props: {
name: String, name: String,
value: [String, Number], value: [String, Number, Boolean],
} }
} }
</script> </script>

View File

@@ -5,6 +5,7 @@ import AttributeViewer from '/imports/ui/properties/viewers/AttributeViewer.vue'
import BuffViewer from '/imports/ui/properties/viewers/BuffViewer.vue'; import BuffViewer from '/imports/ui/properties/viewers/BuffViewer.vue';
import ContainerViewer from '/imports/ui/properties/viewers/ContainerViewer.vue'; import ContainerViewer from '/imports/ui/properties/viewers/ContainerViewer.vue';
import ClassLevelViewer from '/imports/ui/properties/viewers/ClassLevelViewer.vue'; import ClassLevelViewer from '/imports/ui/properties/viewers/ClassLevelViewer.vue';
import ConstantViewer from '/imports/ui/properties/viewers/ConstantViewer.vue';
import DamageViewer from '/imports/ui/properties/viewers/DamageViewer.vue'; import DamageViewer from '/imports/ui/properties/viewers/DamageViewer.vue';
import DamageMultiplierViewer from '/imports/ui/properties/viewers/DamageMultiplierViewer.vue'; import DamageMultiplierViewer from '/imports/ui/properties/viewers/DamageMultiplierViewer.vue';
import EffectViewer from '/imports/ui/properties/viewers/EffectViewer.vue'; import EffectViewer from '/imports/ui/properties/viewers/EffectViewer.vue';
@@ -13,10 +14,14 @@ import FolderViewer from '/imports/ui/properties/viewers/FolderViewer.vue';
import ItemViewer from '/imports/ui/properties/viewers/ItemViewer.vue'; import ItemViewer from '/imports/ui/properties/viewers/ItemViewer.vue';
import NoteViewer from '/imports/ui/properties/viewers/NoteViewer.vue'; import NoteViewer from '/imports/ui/properties/viewers/NoteViewer.vue';
import ProficiencyViewer from '/imports/ui/properties/viewers/ProficiencyViewer.vue'; import ProficiencyViewer from '/imports/ui/properties/viewers/ProficiencyViewer.vue';
//import RollViewer from '/imports/ui/properties/viewers/RollViewer.vue'; import RollViewer from '/imports/ui/properties/viewers/RollViewer.vue';
import SkillViewer from '/imports/ui/properties/viewers/SkillViewer.vue'; import SkillViewer from '/imports/ui/properties/viewers/SkillViewer.vue';
import SavingThrowViewer from '/imports/ui/properties/viewers/SavingThrowViewer.vue';
import SlotViewer from '/imports/ui/properties/viewers/SlotViewer.vue';
import SlotFillerViewer from '/imports/ui/properties/viewers/SlotFillerViewer.vue';
import SpellListViewer from '/imports/ui/properties/viewers/SpellListViewer.vue'; import SpellListViewer from '/imports/ui/properties/viewers/SpellListViewer.vue';
import SpellViewer from '/imports/ui/properties/viewers/SpellViewer.vue'; import SpellViewer from '/imports/ui/properties/viewers/SpellViewer.vue';
import ToggleViewer from '/imports/ui/properties/viewers/ToggleViewer.vue';
export default { export default {
action: ActionViewer, action: ActionViewer,
@@ -26,6 +31,7 @@ export default {
buff: BuffViewer, buff: BuffViewer,
container: ContainerViewer, container: ContainerViewer,
classLevel: ClassLevelViewer, classLevel: ClassLevelViewer,
constant: ConstantViewer,
damage: DamageViewer, damage: DamageViewer,
damageMultiplier: DamageMultiplierViewer, damageMultiplier: DamageMultiplierViewer,
effect: EffectViewer, effect: EffectViewer,
@@ -34,8 +40,12 @@ export default {
item: ItemViewer, item: ItemViewer,
note: NoteViewer, note: NoteViewer,
proficiency: ProficiencyViewer, proficiency: ProficiencyViewer,
// roll: RollViewer, propertySlot: SlotViewer,
roll: RollViewer,
savingThrow: SavingThrowViewer,
slotFiller: SlotFillerViewer,
skill: SkillViewer, skill: SkillViewer,
spellList: SpellListViewer, spellList: SpellListViewer,
spell: SpellViewer, spell: SpellViewer,
toggle: ToggleViewer,
}; };