Start of action system re-write
This commit is contained in:
@@ -5,7 +5,6 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||
import { computeCreatureDependencyGroup } from '/imports/api/engine/computeCreature.js';
|
||||
|
||||
const damagePropertiesByName = new ValidatedMethod({
|
||||
name: 'CreatureProperties.damagePropertiesByName',
|
||||
@@ -29,14 +28,13 @@ const damagePropertiesByName = new ValidatedMethod({
|
||||
// Check permissions
|
||||
let creature = Creatures.findOne(creatureId, {
|
||||
fields: {
|
||||
damageMultipliers: 1,
|
||||
variables: 1,
|
||||
owner: 1,
|
||||
readers: 1,
|
||||
writers: 1,
|
||||
},
|
||||
});
|
||||
assertEditPermission(creature, this.userId);
|
||||
let lastProperty;
|
||||
CreatureProperties.find({
|
||||
'ancestors.id': creatureId,
|
||||
variableName,
|
||||
@@ -48,9 +46,7 @@ const damagePropertiesByName = new ValidatedMethod({
|
||||
if (!schema.allowsKey('damage')) return;
|
||||
// Damage the property
|
||||
damagePropertyWork({property, operation, value});
|
||||
lastProperty = property;
|
||||
});
|
||||
if (lastProperty) computeCreatureDependencyGroup(lastProperty);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -45,21 +45,17 @@ const damageProperty = new ValidatedMethod({
|
||||
});
|
||||
|
||||
export function damagePropertyWork({property, operation, value}){
|
||||
let damage, newValue;
|
||||
if (operation === 'set'){
|
||||
let currentValue = property.value;
|
||||
const currentValue = property.value;
|
||||
// Set represents what we want the value to be after damage
|
||||
// So we need the actual damage to get to that value
|
||||
let damage = currentValue - value;
|
||||
damage = currentValue - value;
|
||||
// Damage can't exceed total value
|
||||
if (damage > currentValue) damage = currentValue;
|
||||
// Damage must be positive
|
||||
if (damage < 0) damage = 0;
|
||||
CreatureProperties.update(property._id, {
|
||||
$set: {damage}
|
||||
}, {
|
||||
selector: property
|
||||
});
|
||||
return currentValue - damage;
|
||||
newValue = property.total - damage;
|
||||
} else if (operation === 'increment'){
|
||||
let currentValue = property.value - (property.damage || 0);
|
||||
let currentDamage = property.damage;
|
||||
@@ -68,13 +64,16 @@ export function damagePropertyWork({property, operation, value}){
|
||||
if (increment > currentValue) increment = currentValue;
|
||||
// Can't decrease damage below zero
|
||||
if (-increment > currentDamage) increment = -currentDamage;
|
||||
CreatureProperties.update(property._id, {
|
||||
$inc: {damage: increment}
|
||||
}, {
|
||||
selector: property
|
||||
});
|
||||
return increment;
|
||||
damage = currentDamage + increment;
|
||||
newValue = property.total - damage;
|
||||
}
|
||||
|
||||
// Write the results
|
||||
CreatureProperties.update(property._id, {
|
||||
$set: {damage, value: newValue}
|
||||
}, {
|
||||
selector: property
|
||||
});
|
||||
}
|
||||
|
||||
export default damageProperty;
|
||||
|
||||
@@ -125,7 +125,7 @@ export function insertCreatureLogWork({log, creature, method}){
|
||||
// Insert it
|
||||
let id = CreatureLogs.insert(log);
|
||||
if (Meteor.isServer){
|
||||
method.unblock();
|
||||
method?.unblock();
|
||||
removeOldLogs(creature._id);
|
||||
logWebhook({log, creature});
|
||||
}
|
||||
|
||||
15
app/imports/api/engine/actions/applyProperty.js
Normal file
15
app/imports/api/engine/actions/applyProperty.js
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
const applyPropertyByType = {
|
||||
action,
|
||||
branch,
|
||||
buff,
|
||||
damage,
|
||||
roll,
|
||||
savingThrow,
|
||||
spell,
|
||||
toggle,
|
||||
};
|
||||
|
||||
export default function applyProperty(node, ...args){
|
||||
return applyPropertyByType[node.node.type]?.(node, ...args);
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
import recalculateInlineCalculations from './shared/recalculateInlineCalculations.js';
|
||||
import recalculateCalculation from './shared/recalculateCalculation.js';
|
||||
import rollDice from '/imports/parser/rollDice.js';
|
||||
import applyProperty from '../applyProperty.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import { adjustQuantityWork } from '/imports/api/creature/creatureProperties/methods/adjustQuantity.js';
|
||||
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||
|
||||
export default function applyAction(node, {creature, targets, scope, log}){
|
||||
const prop = node.node;
|
||||
if (prop.target === 'self') targets = [creature];
|
||||
const failed = spendResources({prop, log, scope});
|
||||
if (failed) return;
|
||||
|
||||
let content = { name: prop.name };
|
||||
if (prop.summary?.text){
|
||||
recalculateInlineCalculations(prop.summary, scope, log);
|
||||
content.value = prop.summary.value;
|
||||
}
|
||||
log.content.push(content);
|
||||
if (prop.attackRoll && prop.attackRoll.calculation){
|
||||
if (targets.length){
|
||||
targets.forEach(target => {
|
||||
applyAttackToTarget({prop, target, scope, log});
|
||||
// Apply the children, but only to the current target
|
||||
applyChildren(node, {targets: [target], scope, log});
|
||||
});
|
||||
} else {
|
||||
applyAttackWithoutTarget({prop, scope, log});
|
||||
applyChildren(node, {creature, targets, scope, log});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applyAttackWithoutTarget({prop, scope, log}){
|
||||
delete scope['$attackHit'];
|
||||
delete scope['$attackMiss'];
|
||||
|
||||
recalculateCalculation(prop.rollBonus, scope, log);
|
||||
|
||||
let value = rollDice(1, 20)[0];
|
||||
scope['$attackRoll'] = {value};
|
||||
let criticalHitTarget = scope.criticalHitTarget?.value || 20;
|
||||
let criticalHit = value >= criticalHitTarget;
|
||||
if (criticalHit) scope['$criticalHit'] = {value: true};
|
||||
let result = value + prop.rollBonus.value;
|
||||
scope['$toHit'] = {value: result};
|
||||
log.content.push({
|
||||
name: criticalHit ? 'Critical Hit!' : 'To Hit',
|
||||
value: `1d20 {${value}} + ${prop.rollBonus.value} = ` + result,
|
||||
});
|
||||
}
|
||||
|
||||
function applyAttackToTarget({prop, target, scope, log}){
|
||||
delete scope['$attackHit'];
|
||||
delete scope['$attackMiss'];
|
||||
|
||||
recalculateCalculation(prop.rollBonus, scope, log);
|
||||
|
||||
const value = rollDice(1, 20)[0];
|
||||
scope['$attackRoll'] = {value};
|
||||
const criticalHitTarget = scope.criticalHitTarget?.value || 20;
|
||||
const criticalHit = value >= criticalHitTarget;
|
||||
const criticalMiss = value === 1;
|
||||
if (criticalHit) scope['$criticalHit'] = {value: true};
|
||||
if (criticalMiss) scope['$criticalMiss'] = {value: true};
|
||||
const result = value + prop.rollBonus.value;
|
||||
scope['$toHit'] = {value: result};
|
||||
if (target.variables.armor){
|
||||
const armor = target.variables.armor.value;
|
||||
const name = criticalHit ? 'Critical Hit!' :
|
||||
criticalMiss ? 'Critical miss!' :
|
||||
result > armor ? 'Hit!' :
|
||||
'Miss!'
|
||||
log.content.push({
|
||||
name,
|
||||
value: `1d20 {${value}} + ${prop.rollBonus.value} = ` + result,
|
||||
});
|
||||
if ((result > armor) || (criticalHit)){
|
||||
scope['$attackHit'] = true;
|
||||
} else {
|
||||
scope['$attackMiss'] = true;
|
||||
}
|
||||
} else {
|
||||
log.content.push({
|
||||
name: 'Error',
|
||||
value:'Target has no `armor`',
|
||||
});
|
||||
log.content.push({
|
||||
name: criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical miss!' : 'To Hit',
|
||||
value: `1d20 {${value}} + ${prop.rollBonus.value} = ` + result,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function applyChildren(node, args){
|
||||
node.children.forEach(child => applyProperty(child, args));
|
||||
}
|
||||
|
||||
function spendResources({prop, log, scope}){
|
||||
// Check Uses
|
||||
if (prop.usesUsed >= prop.uses?.value){
|
||||
log.content.push({
|
||||
name: 'Error',
|
||||
value: `${prop.name || 'action'} does not have enough uses left`,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
// Resources
|
||||
if (prop.insufficientResources){
|
||||
log.content.push({
|
||||
name: 'Error',
|
||||
value: 'This creature doesn\'t have sufficient resources to perform this action',
|
||||
});
|
||||
return true;
|
||||
}
|
||||
// Items
|
||||
let itemQuantityAdjustments = [];
|
||||
let spendLog = [];
|
||||
let gainLog = [];
|
||||
try {
|
||||
prop.resources.itemsConsumed.forEach(itemConsumed => {
|
||||
if (!itemConsumed.itemId){
|
||||
throw 'No ammo was selected for this prop';
|
||||
}
|
||||
let item = CreatureProperties.findOne(itemConsumed.itemId);
|
||||
if (!item || item.ancestors[0].id !== prop.ancestors[0].id){
|
||||
throw 'The prop\'s ammo was not found on the creature';
|
||||
}
|
||||
if (!item.equipped){
|
||||
throw 'The selected ammo is not equipped';
|
||||
}
|
||||
if (!itemConsumed.quantity) return;
|
||||
itemQuantityAdjustments.push({
|
||||
property: item,
|
||||
operation: 'increment',
|
||||
value: itemConsumed.quantity,
|
||||
});
|
||||
let logName = item.name;
|
||||
if (itemConsumed.quantity > 1 || itemConsumed.quantity < -1){
|
||||
logName = item.plural || logName;
|
||||
}
|
||||
if (itemConsumed.quantity > 0){
|
||||
spendLog.push(logName + ': ' + itemConsumed.quantity);
|
||||
} else if (itemConsumed.quantity < 0){
|
||||
gainLog.push(logName + ': ' + -itemConsumed.quantity);
|
||||
}
|
||||
});
|
||||
} catch (e){
|
||||
log.content.push({
|
||||
name: 'Error',
|
||||
value: e,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
// No more errors should be thrown after this line
|
||||
// Now that we have confirmed that there are no errors, do actual work
|
||||
//Items
|
||||
itemQuantityAdjustments.forEach(adjustQuantityWork);
|
||||
|
||||
// Use uses
|
||||
if (prop.usesResult){
|
||||
CreatureProperties.update(prop._id, {
|
||||
$inc: {usesUsed: 1}
|
||||
}, {
|
||||
selector: prop
|
||||
});
|
||||
log.content.push({
|
||||
name: 'Uses left',
|
||||
value: prop.usesResult - (prop.usesUsed || 0) - 1,
|
||||
});
|
||||
}
|
||||
|
||||
// Damage stats
|
||||
prop.resources.attributesConsumed.forEach(attConsumed => {
|
||||
if (!attConsumed.quantity) return;
|
||||
let stat = scope[attConsumed.variableName];
|
||||
if (!stat) return;
|
||||
damagePropertyWork({
|
||||
property: stat,
|
||||
operation: 'increment',
|
||||
value: attConsumed.quantity,
|
||||
});
|
||||
if (attConsumed.quantity > 0){
|
||||
spendLog.push(stat.name + ': ' + attConsumed.quantity);
|
||||
} else if (attConsumed.quantity < 0){
|
||||
gainLog.push(stat.name + ': ' + -attConsumed.quantity);
|
||||
}
|
||||
});
|
||||
|
||||
// Log all the spending
|
||||
if (gainLog.length) log.content.push({
|
||||
name: 'Gained',
|
||||
value: gainLog.join('\n'),
|
||||
});
|
||||
if (spendLog.length) log.content.push({
|
||||
name: 'Spent',
|
||||
value: spendLog.join('\n'),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import applyProperty from '../applyProperty.js';
|
||||
import recalculateCalculation from './shared/recalculateCalculation.js';
|
||||
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||
|
||||
export default function applyAdjustment(node, {
|
||||
creature, targets, scope, log
|
||||
}){
|
||||
const prop = node.node;
|
||||
const damageTargets = prop.target === 'self' ? [creature] : targets;
|
||||
|
||||
if (!prop.amount) {
|
||||
return applyChildren(node, {creature, targets, scope, log});
|
||||
}
|
||||
|
||||
// Evaluate the amount
|
||||
recalculateCalculation(prop.amount, scope, log);
|
||||
prop.amount.errors?.forEach(error => {
|
||||
if (error.type !== 'info'){
|
||||
log.content.push({name: 'Error', value: error.message});
|
||||
}
|
||||
});
|
||||
const value = prop.amount.value;
|
||||
if (!isFinite(value)) {
|
||||
return applyChildren(node, {creature, targets, scope, log});
|
||||
}
|
||||
|
||||
if (damageTargets?.length) {
|
||||
damageTargets.forEach(target => {
|
||||
let stat = target.variables[prop.stat];
|
||||
if (!stat) {
|
||||
log({
|
||||
name: 'Error',
|
||||
value: `Could not apply attribute damage, creature does not have \`${prop.stat}\` set`
|
||||
});
|
||||
return;
|
||||
}
|
||||
damagePropertyWork({
|
||||
property: stat,
|
||||
operation: prop.operation,
|
||||
value,
|
||||
});
|
||||
log.content.push({
|
||||
name: 'Attribute damage',
|
||||
value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` +
|
||||
` ${value}`,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
log.content.push({
|
||||
name: 'Attribute damage',
|
||||
value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` +
|
||||
` ${value}`,
|
||||
});
|
||||
}
|
||||
|
||||
return applyChildren(node, {creature, targets, scope, log});
|
||||
}
|
||||
|
||||
function applyChildren(node, args){
|
||||
node.children.forEach(child => applyProperty(child, args));
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import applyProperty from '../applyProperty.js';
|
||||
import recalculateCalculation from './shared/recalculateCalculation.js';
|
||||
|
||||
export default function applyBranch(node, {
|
||||
creature, targets, scope, log
|
||||
}){
|
||||
const applyChildren = function(){
|
||||
node.children.forEach(child => applyProperty(child, {
|
||||
creature, targets, scope, log
|
||||
}));
|
||||
};
|
||||
const prop = node.node;
|
||||
switch(prop.branchType){
|
||||
case 'if':
|
||||
recalculateCalculation(prop.condition, scope, log);
|
||||
if (prop.condition?.value) applyChildren();
|
||||
break;
|
||||
case 'hit':
|
||||
if (scope['$attackHit']?.value) applyChildren();
|
||||
break;
|
||||
case 'miss':
|
||||
if (scope['$attackMiss']?.value) applyChildren();
|
||||
break;
|
||||
case 'failedSave':
|
||||
if (scope['$saveFailed']?.value) applyChildren();
|
||||
break;
|
||||
case 'successfulSave':
|
||||
if (scope['$saveSucceeded']?.value) applyChildren();
|
||||
break;
|
||||
case 'eachTarget':
|
||||
if (targets.length){
|
||||
targets.forEach(target => {
|
||||
node.children.forEach(child => applyProperty(child, {
|
||||
creature, targets: [target], scope, log
|
||||
}));
|
||||
});
|
||||
} else {
|
||||
applyChildren();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import {
|
||||
setLineageOfDocs,
|
||||
renewDocIds
|
||||
} from '/imports/api/parenting/parenting.js';
|
||||
import {setDocToLastOrder} from '/imports/api/parenting/order.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import computedSchemas from '/imports/api/properties/computedPropertySchemasIndex.js';
|
||||
import applyFnToKey from '/imports/api/engine/computation/utility/applyFnToKey.js';
|
||||
import { get } from 'lodash';
|
||||
import resolve, { map } from '/imports/parser/resolve.js';
|
||||
import logErrors from './shared/logErrors.js';
|
||||
|
||||
export default function applyBuff(node, {creature, targets, scope, log}){
|
||||
const prop = node.node;
|
||||
let buffTargets = prop.target === 'self' ? [creature] : targets;
|
||||
|
||||
// Then copy the decendants of the buff to the targets
|
||||
prop.applied = true;
|
||||
let propList = [prop];
|
||||
function addChildrenToPropList(children){
|
||||
children.forEach(child => {
|
||||
propList.push(child.node);
|
||||
addChildrenToPropList(child.children);
|
||||
});
|
||||
}
|
||||
addChildrenToPropList(node.children);
|
||||
crystalizeVariables({propList, scope, log});
|
||||
|
||||
let oldParent = {
|
||||
id: prop.parent.id,
|
||||
collection: prop.parent.collection,
|
||||
};
|
||||
buffTargets.forEach(target => {
|
||||
copyNodeListToTarget(propList, target, oldParent);
|
||||
});
|
||||
|
||||
// Don't apply the children of the buff, they get copied to the target instead
|
||||
}
|
||||
|
||||
function copyNodeListToTarget(propList, target, oldParent){
|
||||
let ancestry = [{collection: 'creatures', id: target._id}];
|
||||
setLineageOfDocs({
|
||||
docArray: propList,
|
||||
newAncestry: ancestry,
|
||||
oldParent,
|
||||
});
|
||||
renewDocIds({
|
||||
docArray: propList,
|
||||
});
|
||||
setDocToLastOrder({
|
||||
collection: CreatureProperties,
|
||||
doc: propList[0],
|
||||
});
|
||||
CreatureProperties.batchInsert(propList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all variables with their resolved values
|
||||
* except variables of the form `$target.thing.total` become `thing.total`
|
||||
*/
|
||||
function crystalizeVariables({propList, scope, log}){
|
||||
propList.forEach(prop => {
|
||||
computedSchemas[prop.type].computedFields().forEach( calcKey => {
|
||||
applyFnToKey(prop, calcKey, (prop, key) => {
|
||||
const calcObj = get(prop, key);
|
||||
if (!calcObj?.parseNode) return;
|
||||
map(calcObj.parseNode, node => {
|
||||
// Skip nodes that aren't symbols or accessors
|
||||
if (
|
||||
node.parseType !== 'accessor' && node.parseType !== 'symbol'
|
||||
) return node;
|
||||
// Handle variables
|
||||
if (node.name === '$target'){
|
||||
// strip $target
|
||||
if (node.parseType === 'accessor'){
|
||||
node.name = node.path.shift();
|
||||
} else {
|
||||
// Can't strip symbols
|
||||
log.content.push({
|
||||
name: 'Error',
|
||||
value: 'Variable `$target` should not be used without a property: $target.property'
|
||||
});
|
||||
}
|
||||
return node;
|
||||
} else {
|
||||
// Resolve all other variables
|
||||
const {result, context} = resolve('reduce', node, scope);
|
||||
logErrors(context.errors, log);
|
||||
return result;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export default function logErrors(errors, log){
|
||||
errors?.forEach(error => {
|
||||
if (error.type !== 'info'){
|
||||
log.content.push({name: 'Error', value: error.message});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import evaluateCalculation from '../utility/evaluateCalculation.js';
|
||||
import logErrors from './logErrors.js';
|
||||
|
||||
export default function recalculateCalculation(calc, scope, log){
|
||||
if (!calc.parseNode) return;
|
||||
calc._parseLevel = 'reduce';
|
||||
evaluateCalculation(calc, scope);
|
||||
logErrors(calc.errors, log);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import embedInlineCalculations from '/imports/api/engine/computation/utility/embedInlineCalculations.js';
|
||||
import recalculateCalculation from './recalculateCalculation.js'
|
||||
|
||||
export default function recalculateInlineCalculations(inlineCalcObj, scope, log){
|
||||
// Skip if there are no calculations
|
||||
if (!inlineCalcObj?.calculations?.length) return;
|
||||
// Recalculate each calculation with the current scope
|
||||
inlineCalcObj.inlineCalculations.forEach(calc => {
|
||||
recalculateCalculation(calc, scope, log);
|
||||
});
|
||||
// Embed the new calculated values
|
||||
embedInlineCalculations(inlineCalcObj);
|
||||
}
|
||||
103
app/imports/api/engine/actions/doAction.js
Normal file
103
app/imports/api/engine/actions/doAction.js
Normal file
@@ -0,0 +1,103 @@
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import { CreatureLogSchema, insertCreatureLogWork } from '/imports/api/creature/log/CreatureLogs.js';
|
||||
import { nodeArrayToTree } from '/imports/api/parenting/nodesToTree.js';
|
||||
import applyProperty from './applyProperty.js';
|
||||
import computeCreature from '/imports/api/engine/computeCreature.js';
|
||||
|
||||
export default function doAction({actionId, targetIds, method}){
|
||||
// get the docs
|
||||
const {
|
||||
creature, targets, properties, ancestors
|
||||
} = fetchActionDocs(actionId, targetIds);
|
||||
const ancestorScope = getAncestorScope(ancestors);
|
||||
const propertyForest = nodeArrayToTree(properties);
|
||||
if (propertyForest.length !== 1){
|
||||
throw new Meteor.Error(`The action has ${propertyForest.length} top level properties, expected 1`);
|
||||
}
|
||||
|
||||
// Create the log
|
||||
let log = CreatureLogSchema.clean({
|
||||
creatureId: creature._id,
|
||||
creatureName: creature.name,
|
||||
});
|
||||
|
||||
// Apply the top level property, it is responsible for applying its children
|
||||
// recursively
|
||||
const scope = {
|
||||
...creature.variables,
|
||||
...ancestorScope,
|
||||
}
|
||||
applyProperty(propertyForest[0], {
|
||||
creature,
|
||||
targets,
|
||||
scope,
|
||||
log,
|
||||
});
|
||||
|
||||
// Insert the log
|
||||
insertCreatureLogWork({log, creature, method});
|
||||
|
||||
// Recompute the creature and targets
|
||||
Meteor.defer(() => computeCreature(creature._id));
|
||||
targetIds.forEach(targetId => {
|
||||
Meteor.defer(() => computeCreature(targetId));
|
||||
});
|
||||
}
|
||||
|
||||
function fetchActionDocs(actionId, targetIds){
|
||||
// Fetch the action with ancestors only
|
||||
const action = CreatureProperties.findOne({
|
||||
_id: actionId,
|
||||
removed: {$ne: true},
|
||||
}, {
|
||||
fields: {ancestors: 1}
|
||||
});
|
||||
if (!action) throw new Meteor.Error('The specified action was not found');
|
||||
|
||||
// Fetch all the action's ancestor creatureProperties
|
||||
const ancestorIds = [];
|
||||
action.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},
|
||||
});
|
||||
|
||||
// Fetch the action's top level ancestor creature
|
||||
const creature = Creatures.findOne(action.ancestors[0].id, {
|
||||
fields: {variables: 1},
|
||||
});
|
||||
if (!creature) throw new Meteor.Error('The creature for this action was not found');
|
||||
|
||||
// Fetch all the target creatures
|
||||
const targets = Creatures.find({
|
||||
_id: targetIds,
|
||||
}, {
|
||||
fields: {variables: 1},
|
||||
}).fetch();
|
||||
|
||||
// Get cursor of the properties
|
||||
const properties = CreatureProperties.find({
|
||||
$or: [{_id: actionId}, {'ancestors.id': actionId}],
|
||||
removed: {$ne: true},
|
||||
}, {
|
||||
sort: {order: 1},
|
||||
});
|
||||
|
||||
return {action, creature, targets, properties, ancestors}
|
||||
}
|
||||
|
||||
// Assumes ancestors are in tree order already
|
||||
function getAncestorScope(ancestors){
|
||||
let scope = {};
|
||||
ancestors.forEach(prop => {
|
||||
scope[`#${prop.type}`] = prop;
|
||||
});
|
||||
return scope;
|
||||
}
|
||||
@@ -27,7 +27,6 @@ function computeResources(computation, node){
|
||||
const att = computation.scope[attConsumed.variableName];
|
||||
if (!att._id) return;
|
||||
attConsumed.available = att.value;
|
||||
attConsumed.statId = att._id;
|
||||
attConsumed.statName = att.name;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import resolve, { toString } from '/imports/parser/resolve.js';
|
||||
import INLINE_CALCULATION_REGEX from '/imports/constants/INLINE_CALCULTION_REGEX.js';
|
||||
import embedInlineCalculations from '../utility/embedInlineCalculations.js';
|
||||
import evaluateCalculation from '../utility/evaluateCalculation.js';
|
||||
|
||||
export default function computeCalculations(computation, node){
|
||||
if (!node.data) return;
|
||||
@@ -11,32 +11,3 @@ export default function computeCalculations(computation, node){
|
||||
embedInlineCalculations(inlineCalcObj);
|
||||
});
|
||||
}
|
||||
|
||||
function evaluateCalculation(calculation, scope){
|
||||
const parseNode = calculation.parseNode;
|
||||
const fn = calculation._parseLevel;
|
||||
const calculationScope = {...calculation._localScope, ...scope};
|
||||
const {result: resultNode, context} = resolve(fn, parseNode, calculationScope);
|
||||
calculation.errors = context.errors;
|
||||
if (resultNode?.parseType === 'constant'){
|
||||
calculation.value = resultNode.value;
|
||||
} else if (resultNode?.parseType === 'error'){
|
||||
calculation.value = null;
|
||||
} else {
|
||||
calculation.value = toString(resultNode);
|
||||
}
|
||||
// remove the working fields
|
||||
delete calculation._parseLevel;
|
||||
delete calculation._localScope;
|
||||
}
|
||||
|
||||
function embedInlineCalculations(inlineCalcObj){
|
||||
const string = inlineCalcObj.text;
|
||||
const calculations = inlineCalcObj.inlineCalculations;
|
||||
if (!string || !calculations) return;
|
||||
let index = 0;
|
||||
inlineCalcObj.value = string.replace(INLINE_CALCULATION_REGEX, substring => {
|
||||
let calc = calculations[index++];
|
||||
return (calc && 'value' in calc) ? calc.value : substring;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import INLINE_CALCULATION_REGEX from '/imports/constants/INLINE_CALCULTION_REGEX.js';
|
||||
|
||||
export default function embedInlineCalculations(inlineCalcObj){
|
||||
const string = inlineCalcObj.text;
|
||||
const calculations = inlineCalcObj.inlineCalculations;
|
||||
if (!string || !calculations) return;
|
||||
let index = 0;
|
||||
inlineCalcObj.value = string.replace(INLINE_CALCULATION_REGEX, substring => {
|
||||
let calc = calculations[index++];
|
||||
return (calc && 'value' in calc) ? calc.value : substring;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import resolve, { toString } from '/imports/parser/resolve.js';
|
||||
|
||||
export default function evaluateCalculation(calculation, scope){
|
||||
const parseNode = calculation.parseNode;
|
||||
const fn = calculation._parseLevel;
|
||||
const calculationScope = {...calculation._localScope, ...scope};
|
||||
const {result: resultNode, context} = resolve(fn, parseNode, calculationScope);
|
||||
calculation.errors = context.errors;
|
||||
if (resultNode?.parseType === 'constant'){
|
||||
calculation.value = resultNode.value;
|
||||
} else if (resultNode?.parseType === 'error'){
|
||||
calculation.value = null;
|
||||
} else {
|
||||
calculation.value = toString(resultNode);
|
||||
}
|
||||
// remove the working fields
|
||||
delete calculation._parseLevel;
|
||||
delete calculation._localScope;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
|
||||
export default function writeScope(creatureId, scope){
|
||||
Creatures.update(creatureId, {$set: {variables: scope}});
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
import buildCreatureComputation from './computation/buildCreatureComputation.js';
|
||||
import computeCreatureComputation from './computation/computeCreatureComputation.js';
|
||||
import writeAlteredProperties from './computation/writeComputation/writeAlteredProperties.js';
|
||||
import writeScope from './computation/writeComputation/writeScope.js';
|
||||
|
||||
export default function computeCreature(creatureId){
|
||||
const computation = buildCreatureComputation(creatureId);
|
||||
computeCreatureComputation(computation);
|
||||
writeAlteredProperties(computation);
|
||||
writeScope(creatureId, computation.scope);
|
||||
}
|
||||
|
||||
// For now just recompute the whole creature, TODO only recompute a single
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import spendResources from '/imports/api/creature/actions/spendResources.js'
|
||||
|
||||
export default function applyAction({prop, log}){
|
||||
let content = { name: prop.name };
|
||||
/*
|
||||
if (prop.summary){
|
||||
content.value = embedInlineCalculations(
|
||||
prop.summary, prop.summaryCalculations
|
||||
);
|
||||
}*/
|
||||
log.content.push(content);
|
||||
spendResources({prop, log});
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import roll from '/imports/parser/roll.js';
|
||||
import rollDice from '/imports/parser/rollDice.js';
|
||||
|
||||
export default function applyAttack({
|
||||
prop,
|
||||
@@ -6,7 +6,7 @@ export default function applyAttack({
|
||||
actionContext,
|
||||
creature,
|
||||
}){
|
||||
let value = roll(1, 20)[0];
|
||||
let value = rollDice(1, 20)[0];
|
||||
actionContext.attackRoll = {value};
|
||||
let criticalHitTarget = creature.variables.criticalHitTarget &&
|
||||
creature.variables.criticalHitTarget.value || 20;
|
||||
|
||||
@@ -7,8 +7,8 @@ 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){
|
||||
let prop = options.prop;
|
||||
function applyProperty(args){
|
||||
let prop = args.prop;
|
||||
if (prop.type === 'buff'){
|
||||
// ignore only applied buffs, don't apply them again
|
||||
if (prop.applied === true){
|
||||
@@ -26,28 +26,27 @@ function applyProperty(options){
|
||||
switch (prop.type){
|
||||
case 'action':
|
||||
case 'spell':
|
||||
applyAction(options);
|
||||
break;
|
||||
case 'attack':
|
||||
applyAction(options);
|
||||
applyAttack(options);
|
||||
if (prop.attackRoll && prop.attackRoll.calculation){
|
||||
applyAttack(args)
|
||||
}
|
||||
applyAction(args);
|
||||
break;
|
||||
case 'damage':
|
||||
applyDamage(options);
|
||||
applyDamage(args);
|
||||
break;
|
||||
case 'adjustment':
|
||||
applyAdjustment(options);
|
||||
applyAdjustment(args);
|
||||
break;
|
||||
case 'buff':
|
||||
applyBuff(options);
|
||||
applyBuff(args);
|
||||
return false;
|
||||
case 'toggle':
|
||||
return applyToggle(options);
|
||||
return applyToggle(args);
|
||||
case 'roll':
|
||||
applyRoll(options);
|
||||
applyRoll(args);
|
||||
break;
|
||||
case 'savingThrow':
|
||||
return applySave(options);
|
||||
return applySave(args);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -204,12 +204,6 @@ const ComputedOnlyActionSchema = createPropertySchema({
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
'resources.attributesConsumed.$.statId': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
'resources.attributesConsumed.$.statName': {
|
||||
type: String,
|
||||
optional: true,
|
||||
|
||||
@@ -14,11 +14,10 @@ const AdjustmentSchema = createPropertySchema({
|
||||
// Who this adjustment applies to
|
||||
target: {
|
||||
type: String,
|
||||
defaultValue: 'every',
|
||||
defaultValue: 'target',
|
||||
allowedValues: [
|
||||
'self', // the character who took the Adjustment
|
||||
'each', // rolled once for `each` target
|
||||
'every', // rolled once and applied to `every` target
|
||||
'self',
|
||||
'target',
|
||||
],
|
||||
},
|
||||
// The stat this rolls applies to
|
||||
|
||||
49
app/imports/api/properties/Branch.js
Normal file
49
app/imports/api/properties/Branch.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
||||
|
||||
let BranchSchema = createPropertySchema({
|
||||
branchType: {
|
||||
type: String,
|
||||
allowedValues: [
|
||||
// Uses the condition field to determine whether to apply children
|
||||
'if',
|
||||
// Attack
|
||||
'hit',
|
||||
'miss',
|
||||
// Save
|
||||
'failedSave',
|
||||
'successfulSave',
|
||||
// Iterate through targets
|
||||
'eachTarget',
|
||||
// if it has option children, asks to select one
|
||||
// Otherwise presents its own text with yes/no
|
||||
//'choice',
|
||||
//'option',
|
||||
],
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
condition: {
|
||||
type: 'fieldToCompute',
|
||||
optional: true,
|
||||
parseLevel: 'compile',
|
||||
},
|
||||
});
|
||||
|
||||
let ComputedOnlyBranchSchema = createPropertySchema({
|
||||
condition: {
|
||||
type: 'computedOnlyField',
|
||||
optional: true,
|
||||
parseLevel: 'compile',
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedBranchSchema = new SimpleSchema()
|
||||
.extend(BranchSchema)
|
||||
.extend(ComputedOnlyBranchSchema);
|
||||
|
||||
export { BranchSchema, ComputedBranchSchema, ComputedOnlyBranchSchema }
|
||||
@@ -25,9 +25,8 @@ let BuffSchema = createPropertySchema({
|
||||
target: {
|
||||
type: String,
|
||||
allowedValues: [
|
||||
'self', // the character who took the buff
|
||||
'each', // rolled once for `each` target
|
||||
'every', // rolled once and applied to `every` target
|
||||
'self',
|
||||
'target',
|
||||
],
|
||||
defaultValue: 'every',
|
||||
},
|
||||
|
||||
@@ -16,9 +16,8 @@ const DamageSchema = createPropertySchema({
|
||||
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
|
||||
'self',
|
||||
'target',
|
||||
],
|
||||
},
|
||||
damageType: {
|
||||
|
||||
@@ -20,9 +20,8 @@ let SavingThrowSchema = createPropertySchema({
|
||||
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
|
||||
'self',
|
||||
'target',
|
||||
],
|
||||
},
|
||||
// The variable name of save to roll
|
||||
|
||||
@@ -71,6 +71,7 @@ const transformsByPropType = {
|
||||
'action': actionTransforms,
|
||||
'adjustment': [
|
||||
...getComputedPropertyTransforms('amount'),
|
||||
{from: 'target', to: 'target', up: simplifyTarget},
|
||||
],
|
||||
'attack': [
|
||||
...actionTransforms,
|
||||
@@ -89,6 +90,7 @@ const transformsByPropType = {
|
||||
...getComputedPropertyTransforms('duration'),
|
||||
...getInlineComputationTransforms('description'),
|
||||
{from: 'value', to: 'total', up: nanToNull},
|
||||
{from: 'target', to: 'target', up: simplifyTarget},
|
||||
],
|
||||
'classLevel': [
|
||||
...getInlineComputationTransforms('description'),
|
||||
@@ -98,6 +100,7 @@ const transformsByPropType = {
|
||||
],
|
||||
'damage': [
|
||||
...getComputedPropertyTransforms('amount'),
|
||||
{from: 'target', to: 'target', up: simplifyTarget},
|
||||
],
|
||||
'effect': [
|
||||
{from: 'calculation', to: 'amount.calculation'},
|
||||
@@ -128,6 +131,7 @@ const transformsByPropType = {
|
||||
],
|
||||
'savingThrow': [
|
||||
...getComputedPropertyTransforms('dc'),
|
||||
{from: 'target', to: 'target', up: simplifyTarget},
|
||||
],
|
||||
'skill': [
|
||||
...getComputedPropertyTransforms('baseValue'),
|
||||
@@ -193,6 +197,14 @@ function stripZero(val){
|
||||
}
|
||||
}
|
||||
|
||||
function simplifyTarget(val){
|
||||
if (val === 'self'){
|
||||
return val;
|
||||
} else {
|
||||
return 'target';
|
||||
}
|
||||
}
|
||||
|
||||
function trimErrors(arr){
|
||||
if(!arr) return arr;
|
||||
arr.forEach(e => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import resolve, { toString, traverse } from '../resolve.js';
|
||||
import resolve, { toString, traverse, map } from '../resolve.js';
|
||||
import constant from './constant.js';
|
||||
|
||||
const array = {
|
||||
@@ -41,6 +41,13 @@ const array = {
|
||||
fn(node);
|
||||
node.values.forEach(value => traverse(value, fn));
|
||||
},
|
||||
map(node, fn){
|
||||
const resultingNode = fn(node);
|
||||
if (resultingNode === node){
|
||||
node.values = node.values.map(value => map(value, fn));
|
||||
}
|
||||
return resultingNode;
|
||||
},
|
||||
}
|
||||
|
||||
export default array;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import error from './error.js';
|
||||
import constant from './constant.js';
|
||||
import functions from '/imports/parser/functions.js';
|
||||
import resolve, { toString, traverse } from '../resolve.js';
|
||||
import resolve, { toString, traverse, map } from '../resolve.js';
|
||||
|
||||
const call = {
|
||||
create({functionName, args}) {
|
||||
@@ -104,6 +104,13 @@ const call = {
|
||||
fn(node);
|
||||
node.args.forEach(arg => traverse(arg, fn));
|
||||
},
|
||||
map(node, fn){
|
||||
const resultingNode = fn(node);
|
||||
if (resultingNode === node){
|
||||
node.args = node.args.map(arg => map(arg, fn));
|
||||
}
|
||||
return resultingNode;
|
||||
},
|
||||
checkArugments({node, fn, argumentsExpected, resolvedArgs, context}){
|
||||
// Check that the number of arguments matches the number expected
|
||||
if (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import resolve, { traverse, toString } from '../resolve';
|
||||
import resolve, { traverse, toString, map } from '../resolve';
|
||||
|
||||
const ifNode = {
|
||||
create({condition, consequent, alternative}){
|
||||
@@ -39,6 +39,15 @@ const ifNode = {
|
||||
traverse(node.consequent, fn);
|
||||
traverse(node.alternative, fn);
|
||||
},
|
||||
map(node, fn){
|
||||
const resultingNode = fn(node);
|
||||
if (resultingNode === node){
|
||||
node.condition = map(node.condition, fn);
|
||||
node.consequent = map(node.consequent, fn);
|
||||
node.alternative = map(node.alternative, fn);
|
||||
}
|
||||
return resultingNode;
|
||||
},
|
||||
}
|
||||
|
||||
export default ifNode;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import resolve, { traverse, toString } from '../resolve';
|
||||
import resolve, { traverse, toString, map } from '../resolve';
|
||||
import error from './error';
|
||||
|
||||
const indexNode = {
|
||||
@@ -68,6 +68,14 @@ const indexNode = {
|
||||
traverse(node.array, fn);
|
||||
traverse(node.index, fn);
|
||||
},
|
||||
map(node, fn){
|
||||
const resultingNode = fn(node);
|
||||
if (resultingNode === node){
|
||||
node.array = map(node.array, fn);
|
||||
node.index = map(node.index, fn);
|
||||
}
|
||||
return resultingNode;
|
||||
},
|
||||
}
|
||||
|
||||
export default indexNode;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import resolve, { toString, traverse } from '../resolve.js';
|
||||
import resolve, { toString, traverse, map } from '../resolve.js';
|
||||
import constant from './constant.js';
|
||||
|
||||
const not = {
|
||||
@@ -31,7 +31,14 @@ const not = {
|
||||
traverse(node, fn){
|
||||
fn(node);
|
||||
traverse(node.right, fn);
|
||||
}
|
||||
},
|
||||
map(node, fn){
|
||||
const resultingNode = fn(node);
|
||||
if (resultingNode === node){
|
||||
node.right = map(node.right, fn);
|
||||
}
|
||||
return resultingNode;
|
||||
},
|
||||
}
|
||||
|
||||
export default not;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import resolve, { toString, traverse } from '../resolve.js';
|
||||
import resolve, { toString, traverse, map } from '../resolve.js';
|
||||
import constant from './constant.js';
|
||||
|
||||
const operator = {
|
||||
@@ -71,6 +71,14 @@ const operator = {
|
||||
traverse(node.left, fn);
|
||||
traverse(node.right, fn);
|
||||
},
|
||||
map(node, fn){
|
||||
const resultingNode = fn(node);
|
||||
if (resultingNode === node){
|
||||
node.left = map(node.left, fn);
|
||||
node.right = map(node.right, fn);
|
||||
}
|
||||
return resultingNode;
|
||||
},
|
||||
}
|
||||
|
||||
export default operator;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import resolve, { toString, traverse } from '../resolve.js';
|
||||
import resolve, { toString, traverse, map } from '../resolve.js';
|
||||
|
||||
const parenthesis = {
|
||||
create({content}) {
|
||||
@@ -28,7 +28,14 @@ const parenthesis = {
|
||||
traverse(node, fn){
|
||||
fn(node);
|
||||
traverse(node.content, fn);
|
||||
}
|
||||
},
|
||||
map(node, fn){
|
||||
const resultingNode = fn(node);
|
||||
if (resultingNode === node){
|
||||
node.content = map(node.content, fn);
|
||||
}
|
||||
return resultingNode;
|
||||
},
|
||||
}
|
||||
|
||||
export default parenthesis;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import resolve, { toString, traverse } from '../resolve.js';
|
||||
import resolve, { toString, traverse, map } from '../resolve.js';
|
||||
import error from './error.js';
|
||||
import rollArray from './rollArray.js';
|
||||
import roll from '/imports/parser/roll.js';
|
||||
import rollDice from '/imports/parser/rollDice.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
const rollNode = {
|
||||
@@ -39,7 +39,7 @@ const rollNode = {
|
||||
return errorResult('Dice size is not an integer', node, context);
|
||||
}
|
||||
let number = left.value;
|
||||
if (context.doubleRolls){
|
||||
if (context.options.doubleRolls){
|
||||
number *= 2;
|
||||
}
|
||||
if (number > STORAGE_LIMITS.diceRollValuesCount){
|
||||
@@ -47,7 +47,7 @@ const rollNode = {
|
||||
return errorResult(message, node, context);
|
||||
}
|
||||
let diceSize = right.value;
|
||||
let values = roll(number, diceSize);
|
||||
let values = rollDice(number, diceSize);
|
||||
if (context){
|
||||
context.storeRoll({number, diceSize, values});
|
||||
}
|
||||
@@ -69,6 +69,14 @@ const rollNode = {
|
||||
traverse(node.left, fn);
|
||||
traverse(node.right, fn);
|
||||
},
|
||||
map(node, fn){
|
||||
const resultingNode = fn(node);
|
||||
if (resultingNode === node){
|
||||
node.left = map(node.left, fn);
|
||||
node.right = map(node.right, fn);
|
||||
}
|
||||
return resultingNode;
|
||||
},
|
||||
}
|
||||
|
||||
function errorResult(message, node, context){
|
||||
|
||||
@@ -27,9 +27,6 @@ const rollArray = {
|
||||
context,
|
||||
};
|
||||
},
|
||||
traverse(node, fn){
|
||||
return fn(node);
|
||||
}
|
||||
}
|
||||
|
||||
export default rollArray;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import resolve, { toString, traverse } from '../resolve.js';
|
||||
import resolve, { toString, traverse, map } from '../resolve.js';
|
||||
import constant from './constant.js';
|
||||
|
||||
const unaryOperator = {
|
||||
@@ -41,6 +41,13 @@ const unaryOperator = {
|
||||
fn(node);
|
||||
traverse(node.right, fn);
|
||||
},
|
||||
map(node, fn){
|
||||
const resultingNode = fn(node);
|
||||
if (resultingNode === node){
|
||||
node.right = map(node.right, fn);
|
||||
}
|
||||
return resultingNode;
|
||||
},
|
||||
};
|
||||
|
||||
export default unaryOperator;
|
||||
|
||||
@@ -43,11 +43,24 @@ export function traverse(node, fn){
|
||||
return fn(node);
|
||||
}
|
||||
|
||||
export function map(node, fn){
|
||||
if (!node) return;
|
||||
let type = nodeTypeIndex[node.parseType];
|
||||
if (!type){
|
||||
console.error(node);
|
||||
throw new Meteor.Error('Not valid parse node');
|
||||
}
|
||||
if (type.map){
|
||||
return type.map(node, fn);
|
||||
}
|
||||
return fn(node);
|
||||
}
|
||||
|
||||
export class Context {
|
||||
constructor({errors = [], rolls = [], doubleRolls} = {}){
|
||||
constructor({errors = [], rolls = [], options = {}} = {}){
|
||||
this.errors = errors;
|
||||
this.rolls = rolls;
|
||||
this.doubleRolls = doubleRolls;
|
||||
this.options = options;
|
||||
}
|
||||
error(e){
|
||||
if (!e) return;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export default function roll(number, diceSize){
|
||||
export default function rollDice(number, diceSize){
|
||||
let values = [];
|
||||
let randomSrc = DDP.randomStream('diceRoller');
|
||||
for (let i = 0; i < number; i++){
|
||||
@@ -28,6 +28,7 @@
|
||||
</div>
|
||||
<smart-select
|
||||
label="Type"
|
||||
clearable
|
||||
:items="skillTypes"
|
||||
:value="model.skillType"
|
||||
:error-messages="errors.skillType"
|
||||
|
||||
Reference in New Issue
Block a user