Completed first pass at action system re-write. Untested
This commit is contained in:
@@ -35,6 +35,9 @@ export default function applyAction(node, {creature, targets, scope, log}){
|
|||||||
function applyAttackWithoutTarget({prop, scope, log}){
|
function applyAttackWithoutTarget({prop, scope, log}){
|
||||||
delete scope['$attackHit'];
|
delete scope['$attackHit'];
|
||||||
delete scope['$attackMiss'];
|
delete scope['$attackMiss'];
|
||||||
|
delete scope['$criticalHit'];
|
||||||
|
delete scope['$criticalMiss'];
|
||||||
|
delete scope['$attackRoll'];
|
||||||
|
|
||||||
recalculateCalculation(prop.rollBonus, scope, log);
|
recalculateCalculation(prop.rollBonus, scope, log);
|
||||||
|
|
||||||
@@ -54,18 +57,22 @@ function applyAttackWithoutTarget({prop, scope, log}){
|
|||||||
function applyAttackToTarget({prop, target, scope, log}){
|
function applyAttackToTarget({prop, target, scope, log}){
|
||||||
delete scope['$attackHit'];
|
delete scope['$attackHit'];
|
||||||
delete scope['$attackMiss'];
|
delete scope['$attackMiss'];
|
||||||
|
delete scope['$criticalHit'];
|
||||||
|
delete scope['$criticalMiss'];
|
||||||
|
delete scope['$attackDiceRoll'];
|
||||||
|
delete scope['$attackRoll'];
|
||||||
|
|
||||||
recalculateCalculation(prop.rollBonus, scope, log);
|
recalculateCalculation(prop.rollBonus, scope, log);
|
||||||
|
|
||||||
const value = rollDice(1, 20)[0];
|
const value = rollDice(1, 20)[0];
|
||||||
scope['$attackRoll'] = {value};
|
scope['$attackDiceRoll'] = {value};
|
||||||
const criticalHitTarget = scope.criticalHitTarget?.value || 20;
|
const criticalHitTarget = scope.criticalHitTarget?.value || 20;
|
||||||
const criticalHit = value >= criticalHitTarget;
|
const criticalHit = value >= criticalHitTarget;
|
||||||
const criticalMiss = value === 1;
|
const criticalMiss = value === 1;
|
||||||
if (criticalHit) scope['$criticalHit'] = {value: true};
|
if (criticalHit) scope['$criticalHit'] = {value: true};
|
||||||
if (criticalMiss) scope['$criticalMiss'] = {value: true};
|
if (criticalMiss) scope['$criticalMiss'] = {value: true};
|
||||||
const result = value + prop.rollBonus.value;
|
const result = value + prop.rollBonus.value;
|
||||||
scope['$toHit'] = {value: result};
|
scope['$attackRoll'] = {value: result};
|
||||||
if (target.variables.armor){
|
if (target.variables.armor){
|
||||||
const armor = target.variables.armor.value;
|
const armor = target.variables.armor.value;
|
||||||
const name = criticalHit ? 'Critical Hit!' :
|
const name = criticalHit ? 'Critical Hit!' :
|
||||||
|
|||||||
@@ -1,51 +1,32 @@
|
|||||||
// import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
|
import applyProperty from '../applyProperty.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';
|
import recalculateCalculation from './shared/recalculateCalculation.js';
|
||||||
|
import { Context } from '/imports/parser/resolve.js';
|
||||||
|
|
||||||
export default function applyDamage({
|
export default function applyDamage(node, {
|
||||||
prop,
|
creature, targets, scope, log
|
||||||
creature,
|
|
||||||
targets,
|
|
||||||
actionContext,
|
|
||||||
log,
|
|
||||||
}){
|
}){
|
||||||
let damageTargets = prop.target === 'self' ? [creature] : targets;
|
const applyChildren = function(){
|
||||||
let scope = {
|
node.children.forEach(child => applyProperty(child, {
|
||||||
...creature.variables,
|
creature, targets, scope, log
|
||||||
...actionContext,
|
}));
|
||||||
};
|
};
|
||||||
// Add the target's variables to the scope
|
|
||||||
if (targets.length === 1){
|
const prop = node.node;
|
||||||
scope.target = targets[0].variables;
|
let damageTargets = prop.target === 'self' ? [creature] : targets;
|
||||||
}
|
|
||||||
// Determine if the hit is critical
|
// Determine if the hit is critical
|
||||||
let criticalHit = !!(
|
let criticalHit = scope['$criticalHit']?.value &&
|
||||||
actionContext.criticalHit &&
|
|
||||||
actionContext.criticalHit.value &&
|
|
||||||
prop.damageType !== 'healing' // Can't critically heal
|
prop.damageType !== 'healing' // Can't critically heal
|
||||||
);
|
;
|
||||||
// Double the damage rolls if the hit is critical
|
// Double the damage rolls if the hit is critical
|
||||||
let context = new CompilationContext({
|
let context = new Context({
|
||||||
doubleRolls: criticalHit,
|
options: {doubleRolls: criticalHit},
|
||||||
});
|
});
|
||||||
|
recalculateCalculation(prop.amount, scope, log, context);
|
||||||
|
|
||||||
// Compute the roll the first time, logging any errors
|
// If we didn't end up with a finite amount, give up
|
||||||
var {result} = evaluateString({
|
if (!isFinite(prop.amount?.value)) return applyChildren();
|
||||||
string: prop.amount,
|
|
||||||
scope,
|
|
||||||
fn: 'reduce',
|
|
||||||
context
|
|
||||||
});
|
|
||||||
|
|
||||||
// If the result is an error bail out now
|
|
||||||
if (result.constructor.name === 'ErrorNode'){
|
|
||||||
log.content.push({
|
|
||||||
name: 'Damage error',
|
|
||||||
value: result.toString(),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Memoise the damage suffix for the log
|
// Memoise the damage suffix for the log
|
||||||
let suffix = (criticalHit ? ' critical ' : ' ') +
|
let suffix = (criticalHit ? ' critical ' : ' ') +
|
||||||
@@ -57,28 +38,11 @@ export default function applyDamage({
|
|||||||
damageTargets.forEach(target => {
|
damageTargets.forEach(target => {
|
||||||
let name = prop.damageType === 'healing' ? 'Healing' : 'Damage';
|
let name = prop.damageType === 'healing' ? 'Healing' : 'Damage';
|
||||||
|
|
||||||
// Reroll the damage if needed
|
|
||||||
if (prop.target === 'each'){
|
|
||||||
({result, context} = evaluateString({
|
|
||||||
string: prop.amount,
|
|
||||||
scope,
|
|
||||||
fn: 'reduce'
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
// If the result is an error or not a number bail out now
|
|
||||||
if (result.constructor.name === 'ErrorNode' || !result.isNumber){
|
|
||||||
log.content.push({
|
|
||||||
name: 'Damage error',
|
|
||||||
value: result.toString(),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deal the damage to the target
|
// Deal the damage to the target
|
||||||
let damageDealt = dealDamage.call({
|
let damageDealt = dealDamage.call({
|
||||||
creatureId: target._id,
|
creatureId: target._id,
|
||||||
damageType: prop.damageType,
|
damageType: prop.damageType,
|
||||||
amount: result.value,
|
amount: prop.amount.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Log the damage done
|
// Log the damage done
|
||||||
@@ -109,7 +73,8 @@ export default function applyDamage({
|
|||||||
// There are no targets, just log the result
|
// There are no targets, just log the result
|
||||||
log.content.push({
|
log.content.push({
|
||||||
name: prop.damageType === 'healing' ? 'Healing' : 'Damage',
|
name: prop.damageType === 'healing' ? 'Healing' : 'Damage',
|
||||||
value: result.toString() + suffix,
|
value: prop.amount.value + suffix,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
return applyChildren();
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import applyProperty from '../applyProperty.js';
|
||||||
|
import recalculateCalculation from './shared/recalculateCalculation.js';
|
||||||
|
|
||||||
|
export default function applyRoll(node, {creature, targets, scope, log}){
|
||||||
|
const prop = node.node;
|
||||||
|
|
||||||
|
if (prop.roll?.calculation){
|
||||||
|
recalculateCalculation(prop.roll, scope, log, context);
|
||||||
|
|
||||||
|
if (isFinite(prop.roll.value)){
|
||||||
|
scope[prop.variableName] = prop.roll.value;
|
||||||
|
}
|
||||||
|
log.content.push({
|
||||||
|
name: prop.name,
|
||||||
|
value: prop.variableName + ' = ' + prop.roll + ' = ' + prop.roll.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return node.children.forEach(child => applyProperty(child, {
|
||||||
|
creature, targets, scope, log
|
||||||
|
}));
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import rollDice from '/imports/parser/rollDice.js';
|
||||||
|
import recalculateCalculation from './shared/recalculateCalculation.js';
|
||||||
|
import applyProperty from '../applyProperty.js';
|
||||||
|
|
||||||
|
export default function applySavingThrow(node, {creature, targets, scope, log}){
|
||||||
|
let saveTargets = prop.target === 'self' ? [creature] : targets;
|
||||||
|
|
||||||
|
const prop = node.node;
|
||||||
|
|
||||||
|
recalculateCalculation(prop.dc, scope, log, context);
|
||||||
|
|
||||||
|
const dc = (prop.dc?.value);
|
||||||
|
if (!isFinite(dc)){
|
||||||
|
log.content.push({
|
||||||
|
name: 'Error',
|
||||||
|
value: 'Saving throw requires a DC',
|
||||||
|
});
|
||||||
|
return node.children.forEach(child => applyProperty(child, {
|
||||||
|
creature, targets, scope, log
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
log.content.push({
|
||||||
|
name: prop.name,
|
||||||
|
value: ' DC ' + dc,
|
||||||
|
});
|
||||||
|
|
||||||
|
saveTargets.forEach(target => {
|
||||||
|
delete scope['$saveFailed'];
|
||||||
|
delete scope['$saveSucceeded'];
|
||||||
|
delete scope['$saveDiceRoll'];
|
||||||
|
delete scope['$saveRoll'];
|
||||||
|
|
||||||
|
const applyChildren = function(){
|
||||||
|
node.children.forEach(child => applyProperty(child, {
|
||||||
|
creature, targets: [target], scope, log
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const save = target.variables[prop.stat];
|
||||||
|
|
||||||
|
if (!save){
|
||||||
|
log.content.push({
|
||||||
|
name: 'Saving throw error',
|
||||||
|
value: 'No saving throw found: ' + prop.stat,
|
||||||
|
});
|
||||||
|
return applyChildren();
|
||||||
|
}
|
||||||
|
|
||||||
|
let value, values, resultPrefix;
|
||||||
|
if (save.advantage === 1){
|
||||||
|
values = rollDice(2, 20).sort().reverse();
|
||||||
|
value = values[0];
|
||||||
|
resultPrefix = `Advantage: 1d20 [${values[0]},~~${values[1]}~~] + ${save.value} = `
|
||||||
|
} else if (save.advantage === -1){
|
||||||
|
values = rollDice(2, 20).sort();
|
||||||
|
value = values[0];
|
||||||
|
resultPrefix = `Disadvantage: 1d20 [${values[0]},~~${values[1]}~~] + ${save.value} = `
|
||||||
|
} else {
|
||||||
|
values = rollDice(1, 20);
|
||||||
|
value = values[0];
|
||||||
|
resultPrefix = `1d20 [${value}] + ${save.value} = `
|
||||||
|
}
|
||||||
|
scope['$saveDiceRoll'] = {value};
|
||||||
|
const result = value + save.value || 0;
|
||||||
|
scope['$saveRoll'] = {value: result};
|
||||||
|
const saveSuccess = result >= dc;
|
||||||
|
if (saveSuccess){
|
||||||
|
scope['$saveSucceeded'] = {value: true};
|
||||||
|
} else {
|
||||||
|
scope['$saveFailed'] = {value: true};
|
||||||
|
}
|
||||||
|
log.content.push({
|
||||||
|
name: 'Save',
|
||||||
|
value: resultPrefix + result + (saveSuccess ? 'Passed' : 'Failed')
|
||||||
|
});
|
||||||
|
return applyChildren();
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import applyProperty from '../applyProperty.js';
|
||||||
|
import recalculateCalculation from './shared/recalculateCalculation.js';
|
||||||
|
|
||||||
|
export default function applyToggle(node, {
|
||||||
|
creature, targets, scope, log
|
||||||
|
}){
|
||||||
|
const prop = node.node;
|
||||||
|
recalculateCalculation(prop.condition, scope, log);
|
||||||
|
if (prop.condition?.value) {
|
||||||
|
return node.children.forEach(child => applyProperty(child, {
|
||||||
|
creature, targets, scope, log
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import evaluateCalculation from '../utility/evaluateCalculation.js';
|
import evaluateCalculation from '../utility/evaluateCalculation.js';
|
||||||
import logErrors from './logErrors.js';
|
import logErrors from './logErrors.js';
|
||||||
|
|
||||||
export default function recalculateCalculation(calc, scope, log){
|
export default function recalculateCalculation(calc, scope, log, context){
|
||||||
if (!calc.parseNode) return;
|
if (!calc.parseNode) return;
|
||||||
calc._parseLevel = 'reduce';
|
calc._parseLevel = 'reduce';
|
||||||
evaluateCalculation(calc, scope);
|
evaluateCalculation(calc, scope, context);
|
||||||
logErrors(calc.errors, log);
|
logErrors(calc.errors, log);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,90 @@
|
|||||||
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
|
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||||
|
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
||||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
import { CreatureLogSchema, insertCreatureLogWork } from '/imports/api/creature/log/CreatureLogs.js';
|
import { CreatureLogSchema, insertCreatureLogWork } from '/imports/api/creature/log/CreatureLogs.js';
|
||||||
|
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||||
import { nodeArrayToTree } from '/imports/api/parenting/nodesToTree.js';
|
import { nodeArrayToTree } from '/imports/api/parenting/nodesToTree.js';
|
||||||
import applyProperty from './applyProperty.js';
|
import applyProperty from './applyProperty.js';
|
||||||
import computeCreature from '/imports/api/engine/computeCreature.js';
|
import computeCreature from '/imports/api/engine/computeCreature.js';
|
||||||
|
|
||||||
export default function doAction({actionId, targetIds, method}){
|
const doAction = new ValidatedMethod({
|
||||||
|
name: 'creatureProperties.doAction',
|
||||||
|
validate: new SimpleSchema({
|
||||||
|
actionId: SimpleSchema.RegEx.Id,
|
||||||
|
targetIds: {
|
||||||
|
type: Array,
|
||||||
|
defaultValue: [],
|
||||||
|
maxCount: 20,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'targetIds.$': {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
},
|
||||||
|
}).validator(),
|
||||||
|
mixins: [RateLimiterMixin],
|
||||||
|
rateLimit: {
|
||||||
|
numRequests: 10,
|
||||||
|
timeInterval: 5000,
|
||||||
|
},
|
||||||
|
run({actionId, targetIds = []}) {
|
||||||
|
let action = CreatureProperties.findOne(actionId);
|
||||||
|
// Check permissions
|
||||||
|
let creature = getRootCreatureAncestor(action);
|
||||||
|
|
||||||
|
assertEditPermission(creature, this.userId);
|
||||||
|
|
||||||
|
// Get all the targets and make sure we can edit them
|
||||||
|
let targets = [];
|
||||||
|
targetIds.forEach(targetId => {
|
||||||
|
let target = Creatures.findOne(targetId);
|
||||||
|
assertEditPermission(target, this.userId);
|
||||||
|
targets.push(target);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch all the action's ancestor creatureProperties
|
||||||
|
const ancestorIds = [];
|
||||||
|
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},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get cursor of the properties
|
||||||
|
const properties = CreatureProperties.find({
|
||||||
|
$or: [{_id: action._id}, {'ancestors.id': action._id}],
|
||||||
|
removed: {$ne: true},
|
||||||
|
}, {
|
||||||
|
sort: {order: 1},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Do the action
|
||||||
|
doActionWork({creature, targets, properties, ancestors, method: this});
|
||||||
|
|
||||||
|
// Recompute all involved creatures
|
||||||
|
Meteor.defer(() => computeCreature(creature._id));
|
||||||
|
targets.forEach(target => {
|
||||||
|
Meteor.defer(() => computeCreature(target._id));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default doAction;
|
||||||
|
|
||||||
|
export function doActionWork({
|
||||||
|
creature, targets, properties, ancestors, method
|
||||||
|
}){
|
||||||
// get the docs
|
// get the docs
|
||||||
const {
|
|
||||||
creature, targets, properties, ancestors
|
|
||||||
} = fetchActionDocs(actionId, targetIds);
|
|
||||||
const ancestorScope = getAncestorScope(ancestors);
|
const ancestorScope = getAncestorScope(ancestors);
|
||||||
const propertyForest = nodeArrayToTree(properties);
|
const propertyForest = nodeArrayToTree(properties);
|
||||||
if (propertyForest.length !== 1){
|
if (propertyForest.length !== 1){
|
||||||
@@ -37,60 +112,6 @@ export default function doAction({actionId, targetIds, method}){
|
|||||||
|
|
||||||
// Insert the log
|
// Insert the log
|
||||||
insertCreatureLogWork({log, creature, method});
|
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
|
// Assumes ancestors are in tree order already
|
||||||
|
|||||||
@@ -3,22 +3,19 @@ import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
|||||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||||
import { CreatureLogSchema, insertCreatureLogWork } from '/imports/api/creature/log/CreatureLogs.js';
|
|
||||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
||||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||||
import computeCreature from '/imports/api/engine/computeCreature.js';
|
import computeCreature from '/imports/api/engine/computeCreature.js';
|
||||||
import nodesToTree from '/imports/api/parenting/nodesToTree.js';
|
import doAction from '../doAction.js';
|
||||||
import applyProperties from '/imports/api/creature/actions/applyProperties.js';
|
|
||||||
import getAncestorContext from '/imports/api/creature/actions/getAncestorContext.js';
|
|
||||||
|
|
||||||
const doAction = new ValidatedMethod({
|
const commitAction = new ValidatedMethod({
|
||||||
name: 'creatureProperties.doAction',
|
name: 'creatureProperties.doAction',
|
||||||
validate: new SimpleSchema({
|
validate: new SimpleSchema({
|
||||||
actionId: SimpleSchema.RegEx.Id,
|
actionId: SimpleSchema.RegEx.Id,
|
||||||
targetIds: {
|
targetIds: {
|
||||||
type: Array,
|
type: Array,
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
maxCount: 10,
|
maxCount: 20,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
'targetIds.$': {
|
'targetIds.$': {
|
||||||
@@ -36,9 +33,6 @@ const doAction = new ValidatedMethod({
|
|||||||
// Check permissions
|
// Check permissions
|
||||||
let creature = getRootCreatureAncestor(action);
|
let creature = getRootCreatureAncestor(action);
|
||||||
|
|
||||||
// Build ancestor context
|
|
||||||
let actionContext = getAncestorContext(action);
|
|
||||||
|
|
||||||
assertEditPermission(creature, this.userId);
|
assertEditPermission(creature, this.userId);
|
||||||
let targets = [];
|
let targets = [];
|
||||||
targetIds.forEach(targetId => {
|
targetIds.forEach(targetId => {
|
||||||
@@ -46,7 +40,7 @@ const doAction = new ValidatedMethod({
|
|||||||
assertEditPermission(target, this.userId);
|
assertEditPermission(target, this.userId);
|
||||||
targets.push(target);
|
targets.push(target);
|
||||||
});
|
});
|
||||||
doActionWork({action, creature, targets, actionContext, method: this});
|
doAction({action, creature, targets, method: this});
|
||||||
|
|
||||||
// recompute creatures
|
// recompute creatures
|
||||||
computeCreature(creature._id);
|
computeCreature(creature._id);
|
||||||
@@ -57,35 +51,4 @@ const doAction = new ValidatedMethod({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export function doActionWork({
|
export default commitAction;
|
||||||
action,
|
|
||||||
creature,
|
|
||||||
targets,
|
|
||||||
actionContext = {},
|
|
||||||
method
|
|
||||||
}){
|
|
||||||
// Create the log
|
|
||||||
let log = CreatureLogSchema.clean({
|
|
||||||
creatureId: creature._id,
|
|
||||||
creatureName: creature.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
let decendantForest = nodesToTree({
|
|
||||||
collection: CreatureProperties,
|
|
||||||
ancestorId: action._id,
|
|
||||||
});
|
|
||||||
let startingForest = [{
|
|
||||||
node: action,
|
|
||||||
children: decendantForest,
|
|
||||||
}];
|
|
||||||
applyProperties({
|
|
||||||
forest: startingForest,
|
|
||||||
actionContext,
|
|
||||||
creature,
|
|
||||||
targets,
|
|
||||||
log,
|
|
||||||
});
|
|
||||||
insertCreatureLogWork({log, creature, method});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default doAction;
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import resolve, { toString } from '/imports/parser/resolve.js';
|
import resolve, { toString } from '/imports/parser/resolve.js';
|
||||||
|
|
||||||
export default function evaluateCalculation(calculation, scope){
|
export default function evaluateCalculation(calculation, scope, givenContext){
|
||||||
const parseNode = calculation.parseNode;
|
const parseNode = calculation.parseNode;
|
||||||
const fn = calculation._parseLevel;
|
const fn = calculation._parseLevel;
|
||||||
const calculationScope = {...calculation._localScope, ...scope};
|
const calculationScope = {...calculation._localScope, ...scope};
|
||||||
const {result: resultNode, context} = resolve(fn, parseNode, calculationScope);
|
const {result: resultNode, context} = resolve(fn, parseNode, calculationScope, givenContext);
|
||||||
calculation.errors = context.errors;
|
calculation.errors = context.errors;
|
||||||
if (resultNode?.parseType === 'constant'){
|
if (resultNode?.parseType === 'constant'){
|
||||||
calculation.value = resultNode.value;
|
calculation.value = resultNode.value;
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
// import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
|
|
||||||
import damagePropertiesByName from '/imports/api/creature/creatureProperties/methods/damagePropertiesByName.js';
|
|
||||||
|
|
||||||
export default function applyAdjustment({
|
|
||||||
prop,
|
|
||||||
creature,
|
|
||||||
targets,
|
|
||||||
actionContext,
|
|
||||||
log
|
|
||||||
}){
|
|
||||||
let damageTargets = prop.target === 'self' ? [creature] : targets;
|
|
||||||
let scope = {
|
|
||||||
...creature.variables,
|
|
||||||
...actionContext,
|
|
||||||
};
|
|
||||||
var {result, context} = evaluateString({
|
|
||||||
string: prop.amount,
|
|
||||||
scope,
|
|
||||||
fn: 'reduce'
|
|
||||||
});
|
|
||||||
context.errors.forEach(e => {
|
|
||||||
log.content.push({
|
|
||||||
name: 'Attribute damage error',
|
|
||||||
value: e.message || e.toString(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
if (damageTargets) {
|
|
||||||
damageTargets.forEach(target => {
|
|
||||||
if (prop.target === 'each'){
|
|
||||||
({result} = evaluateString({
|
|
||||||
string: prop.amount,
|
|
||||||
scope,
|
|
||||||
fn: 'reduce'
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
damagePropertiesByName.call({
|
|
||||||
creatureId: target._id,
|
|
||||||
variableName: prop.stat,
|
|
||||||
operation: prop.operation || 'increment',
|
|
||||||
value: result.value,
|
|
||||||
});
|
|
||||||
log.content.push({
|
|
||||||
name: 'Attribute damage',
|
|
||||||
value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` +
|
|
||||||
` ${result.isNumber ? -result.value : result.toString()}`,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
log.content.push({
|
|
||||||
name: 'Attribute damage',
|
|
||||||
value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` +
|
|
||||||
` ${result.isNumber ? -result.value : result.toString()}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import rollDice from '/imports/parser/rollDice.js';
|
|
||||||
|
|
||||||
export default function applyAttack({
|
|
||||||
prop,
|
|
||||||
log,
|
|
||||||
actionContext,
|
|
||||||
creature,
|
|
||||||
}){
|
|
||||||
let value = rollDice(1, 20)[0];
|
|
||||||
actionContext.attackRoll = {value};
|
|
||||||
let criticalHitTarget = creature.variables.criticalHitTarget &&
|
|
||||||
creature.variables.criticalHitTarget.value || 20;
|
|
||||||
let criticalHit = value >= criticalHitTarget;
|
|
||||||
if (criticalHit) actionContext.criticalHit = {value: true};
|
|
||||||
let result = value + prop.rollBonusResult;
|
|
||||||
actionContext.toHit = {value: result};
|
|
||||||
|
|
||||||
log.content.push({
|
|
||||||
name: criticalHit ? 'Critical Hit!' : 'To Hit',
|
|
||||||
value: `1d20 [${value}] + ${prop.rollBonusResult} = ` + result,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
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';
|
|
||||||
|
|
||||||
export default function applyBuff({
|
|
||||||
prop,
|
|
||||||
children,
|
|
||||||
creature,
|
|
||||||
targets = [],
|
|
||||||
//actionContext,
|
|
||||||
}){
|
|
||||||
let buffTargets = prop.target === 'self' ? [creature] : targets;
|
|
||||||
|
|
||||||
//let scope = {
|
|
||||||
// ...creature.variables,
|
|
||||||
// ...actionContext,
|
|
||||||
//};
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
// If the target is not self, walk through all decendants and replace
|
|
||||||
// variables in calculations with their values from the creature scope
|
|
||||||
// If the target is self, replace all the target.x references with just x
|
|
||||||
|
|
||||||
// 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(children);
|
|
||||||
let oldParent = {
|
|
||||||
id: prop.parent.id,
|
|
||||||
collection: prop.parent.collection,
|
|
||||||
};
|
|
||||||
buffTargets.forEach(target => {
|
|
||||||
copyNodeListToTarget(propList, target, oldParent);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
import applyAction from '/imports/api/creature/actions/applyAction.js';
|
|
||||||
import applyAdjustment from '/imports/api/creature/actions/applyAdjustment.js';
|
|
||||||
import applyAttack from '/imports/api/creature/actions/applyAttack.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(args){
|
|
||||||
let prop = args.prop;
|
|
||||||
if (prop.type === 'buff'){
|
|
||||||
// ignore only applied buffs, don't apply them again
|
|
||||||
if (prop.applied === true){
|
|
||||||
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
|
|
||||||
} else if (prop.deactivatedBySelf === true){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
switch (prop.type){
|
|
||||||
case 'action':
|
|
||||||
case 'spell':
|
|
||||||
if (prop.attackRoll && prop.attackRoll.calculation){
|
|
||||||
applyAttack(args)
|
|
||||||
}
|
|
||||||
applyAction(args);
|
|
||||||
break;
|
|
||||||
case 'damage':
|
|
||||||
applyDamage(args);
|
|
||||||
break;
|
|
||||||
case 'adjustment':
|
|
||||||
applyAdjustment(args);
|
|
||||||
break;
|
|
||||||
case 'buff':
|
|
||||||
applyBuff(args);
|
|
||||||
return false;
|
|
||||||
case 'toggle':
|
|
||||||
return applyToggle(args);
|
|
||||||
case 'roll':
|
|
||||||
applyRoll(args);
|
|
||||||
break;
|
|
||||||
case 'savingThrow':
|
|
||||||
return applySave(args);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyPropertyAndWalkChildren({prop, children, targets, ...options}){
|
|
||||||
let shouldKeepWalking = applyProperty({ prop, children, targets, ...options });
|
|
||||||
if (shouldKeepWalking){
|
|
||||||
applyProperties({ forest: children, targets, ...options,});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function applyProperties({ forest, targets, ...options}){
|
|
||||||
forest.forEach(node => {
|
|
||||||
let prop = node.node;
|
|
||||||
options.actionContext[`#${prop.type}`] = prop;
|
|
||||||
let children = node.children;
|
|
||||||
if (shouldSplit(prop) && targets.length){
|
|
||||||
targets.forEach(target => {
|
|
||||||
let targets = [target]
|
|
||||||
applyPropertyAndWalkChildren({ targets, prop, children, ...options});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
applyPropertyAndWalkChildren({prop, children, targets, ...options});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldSplit(prop){
|
|
||||||
if (prop.target === 'each'){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
// import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
|
|
||||||
|
|
||||||
export default function applyRoll({
|
|
||||||
prop,
|
|
||||||
creature,
|
|
||||||
actionContext,
|
|
||||||
log,
|
|
||||||
}){
|
|
||||||
let scope = {
|
|
||||||
...creature.variables,
|
|
||||||
...actionContext,
|
|
||||||
};
|
|
||||||
var {result} = evaluateString({
|
|
||||||
string: prop.roll,
|
|
||||||
scope,
|
|
||||||
fn: 'reduce'
|
|
||||||
});
|
|
||||||
if (result.isNumber){
|
|
||||||
actionContext[prop.variableName] = result.value;
|
|
||||||
}
|
|
||||||
log.content.push({
|
|
||||||
name: prop.name,
|
|
||||||
value: prop.variableName + ' = ' + prop.roll + ' = ' + result.toString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
// 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} = evaluateString({
|
|
||||||
string: prop.dc,
|
|
||||||
scope,
|
|
||||||
fn: 'reduce'
|
|
||||||
});
|
|
||||||
let dc = result.value;
|
|
||||||
log.content.push({
|
|
||||||
name: prop.name,
|
|
||||||
value: ' DC ' + result.toString(),
|
|
||||||
});
|
|
||||||
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({
|
|
||||||
name: 'Saving throw error',
|
|
||||||
value: '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',
|
|
||||||
value: resultPrefix + result + (saveSuccess ? 'Passed' : 'Failed')
|
|
||||||
});
|
|
||||||
return !saveSuccess;
|
|
||||||
} else {
|
|
||||||
// TODO
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (e){
|
|
||||||
log.content.push({
|
|
||||||
name: 'Save error',
|
|
||||||
value: e.toString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
var {result} = evaluateString({
|
|
||||||
string: prop.condition,
|
|
||||||
scope,
|
|
||||||
fn: 'reduce'
|
|
||||||
});
|
|
||||||
if (result.constructor.name === 'ErrorNode') {
|
|
||||||
log.content.push({
|
|
||||||
name: 'Toggle error',
|
|
||||||
value: result.toString(),
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
log.content.push({
|
|
||||||
name: prop.name || 'Toggle',
|
|
||||||
value: prop.condition + ' = ' + result.toString(),
|
|
||||||
});
|
|
||||||
return !!result.value;
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
|
||||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
|
||||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
|
||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
|
||||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
|
||||||
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
|
||||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
|
||||||
import computeCreature from '/imports/api/engine/computeCreature.js';
|
|
||||||
import { doActionWork } from '/imports/api/creature/actions/doAction.js';
|
|
||||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
|
||||||
import getAncestorContext from '/imports/api/creature/actions/getAncestorContext.js';
|
|
||||||
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory';
|
|
||||||
import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties';
|
|
||||||
|
|
||||||
const castSpellWithSlot = new ValidatedMethod({
|
|
||||||
name: 'creatureProperties.castSpellWithSlot',
|
|
||||||
validate: new SimpleSchema({
|
|
||||||
spellId: SimpleSchema.RegEx.Id,
|
|
||||||
slotId: {
|
|
||||||
type: String,
|
|
||||||
regEx: SimpleSchema.RegEx.Id,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
targetId: {
|
|
||||||
type: String,
|
|
||||||
regEx: SimpleSchema.RegEx.Id,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
}).validator(),
|
|
||||||
mixins: [RateLimiterMixin],
|
|
||||||
rateLimit: {
|
|
||||||
numRequests: 10,
|
|
||||||
timeInterval: 5000,
|
|
||||||
},
|
|
||||||
run({spellId, slotId, targetId}) {
|
|
||||||
let spell = CreatureProperties.findOne(spellId);
|
|
||||||
// Check permissions
|
|
||||||
let creature = getRootCreatureAncestor(spell);
|
|
||||||
assertEditPermission(creature, this.userId);
|
|
||||||
let target = undefined;
|
|
||||||
if (targetId) {
|
|
||||||
target = Creatures.findOne(targetId);
|
|
||||||
assertEditPermission(target, this.userId);
|
|
||||||
}
|
|
||||||
let slotLevel = spell.level || 0;
|
|
||||||
if (slotLevel !== 0){
|
|
||||||
let slot = CreatureProperties.findOne(slotId);
|
|
||||||
if (!slot){
|
|
||||||
throw new Meteor.Error('No slot',
|
|
||||||
'Slot not found to cast spell');
|
|
||||||
}
|
|
||||||
if (!slot.value){
|
|
||||||
throw new Meteor.Error('No slot',
|
|
||||||
'Slot depleted');
|
|
||||||
}
|
|
||||||
if (!(slot.spellSlotLevelValue >= spell.level)){
|
|
||||||
throw new Meteor.Error('Slot too small',
|
|
||||||
'Slot is not large enough to cast spell');
|
|
||||||
}
|
|
||||||
slotLevel = slot.spellSlotLevelValue;
|
|
||||||
damagePropertyWork({
|
|
||||||
property: slot,
|
|
||||||
operation: 'increment',
|
|
||||||
value: 1,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let actionContext = getAncestorContext(spell);
|
|
||||||
|
|
||||||
doActionWork({
|
|
||||||
action: spell,
|
|
||||||
actionContext: {slotLevel, ...actionContext},
|
|
||||||
creature,
|
|
||||||
targets: target ? [target] : [],
|
|
||||||
method: this,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Note these lines only recompute the top-level creature, not the nearest one
|
|
||||||
// The acting creature might have a new item
|
|
||||||
recomputeInventory(creature._id);
|
|
||||||
// The spell might add properties which need to be activated
|
|
||||||
recomputeInactiveProperties(creature._id);
|
|
||||||
recomputeCreatureByDoc(creature);
|
|
||||||
|
|
||||||
if (target){
|
|
||||||
recomputeInventory(target._id);
|
|
||||||
recomputeInactiveProperties(target._id);
|
|
||||||
recomputeCreatureByDoc(target);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default castSpellWithSlot;
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
|
||||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
|
||||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
|
||||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
|
||||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
|
||||||
import roll from '/imports/parser/roll.js';
|
|
||||||
|
|
||||||
const doCheck = new ValidatedMethod({
|
|
||||||
name: 'creature.doCheck',
|
|
||||||
validate: new SimpleSchema({
|
|
||||||
creatureId: {
|
|
||||||
type: String,
|
|
||||||
regEx: SimpleSchema.RegEx.Id,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
attributeName: {
|
|
||||||
type: String,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
}).validator(),
|
|
||||||
mixins: [RateLimiterMixin],
|
|
||||||
rateLimit: {
|
|
||||||
numRequests: 10,
|
|
||||||
timeInterval: 5000,
|
|
||||||
},
|
|
||||||
run({creatureId, attributeName}) {
|
|
||||||
let creature = Creatures.findOne(creatureId);
|
|
||||||
assertEditPermission(creature, this.userId);
|
|
||||||
let bonus = getAttributeValue({creature, attributeName})
|
|
||||||
return doCheckWork({bonus});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function getAttributeValue({creature, attributeName}){
|
|
||||||
let att = creature.variables[attributeName];
|
|
||||||
if (!att) throw new Meteor.Error('No such attribute',
|
|
||||||
`This creature does not have a ${attributeName} property`);
|
|
||||||
let bonus = att.attributeType === 'ability'? att.modifier : att.value;
|
|
||||||
return bonus || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function doCheckWork({bonus, advantage = 0}){
|
|
||||||
let rolls = roll(2,20);
|
|
||||||
let chosenRoll;
|
|
||||||
if (advantage === 1){
|
|
||||||
chosenRoll = Math.max.apply(rolls);
|
|
||||||
} else if (advantage === -1){
|
|
||||||
chosenRoll = Math.min.apply(rolls);
|
|
||||||
} else {
|
|
||||||
chosenRoll = rolls[0];
|
|
||||||
}
|
|
||||||
let result = chosenRoll + bonus;
|
|
||||||
return {rolls, bonus, chosenRoll, result};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default doCheck;
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
|
||||||
|
|
||||||
export default function getAncestorContext(prop){
|
|
||||||
// Build ancestor context
|
|
||||||
const actionContext = {};
|
|
||||||
let ancestorIds = prop.ancestors.map(ref => ref.id);
|
|
||||||
CreatureProperties.find({
|
|
||||||
_id: {$in: ancestorIds}
|
|
||||||
}, {
|
|
||||||
sort: {order: 1},
|
|
||||||
}).forEach(ancestor => {
|
|
||||||
actionContext[`#${ancestor.type}`] = ancestor;
|
|
||||||
});
|
|
||||||
return actionContext;
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
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 spendResources({prop, log}){
|
|
||||||
// Check Uses
|
|
||||||
if (prop.usesUsed >= prop.usesResult){
|
|
||||||
throw new Meteor.Error('Insufficient Uses',
|
|
||||||
'This prop has no uses left');
|
|
||||||
}
|
|
||||||
// Resources
|
|
||||||
if (prop.insufficientResources){
|
|
||||||
throw new Meteor.Error('Insufficient Resources',
|
|
||||||
'This creature doesn\'t have sufficient resources to perform this prop');
|
|
||||||
}
|
|
||||||
// Items
|
|
||||||
let itemQuantityAdjustments = [];
|
|
||||||
let spendLog = [];
|
|
||||||
let gainLog = [];
|
|
||||||
prop.resources.itemsConsumed.forEach(itemConsumed => {
|
|
||||||
if (!itemConsumed.itemId){
|
|
||||||
throw new Meteor.Error('Ammo not selected',
|
|
||||||
'No ammo was selected for this prop');
|
|
||||||
}
|
|
||||||
let item = CreatureProperties.findOne(itemConsumed.itemId);
|
|
||||||
if (!item || item.ancestors[0].id !== prop.ancestors[0].id){
|
|
||||||
throw new Meteor.Error('Ammo not found',
|
|
||||||
'The prop\'s ammo was not found on the creature');
|
|
||||||
}
|
|
||||||
if (!item.equipped){
|
|
||||||
throw new Meteor.Error('Ammo not equipped',
|
|
||||||
'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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// 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 = CreatureProperties.findOne(attConsumed.statId);
|
|
||||||
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'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
|
||||||
import {
|
|
||||||
ItemConsumedSchema,
|
|
||||||
ComputedOnlyItemConsumedSchema,
|
|
||||||
ComputedItemConsumedSchema
|
|
||||||
} from '/imports/api/properties/subSchemas/ItemConsumedSchema.js';
|
|
||||||
import {
|
|
||||||
AttributeConsumedSchema,
|
|
||||||
ComputedOnlyAttributeConsumedSchema,
|
|
||||||
ComputedAttributeConsumedSchema
|
|
||||||
} from '/imports/api/properties/subSchemas/AttributeConsumedSchema.js';
|
|
||||||
|
|
||||||
const ResourcesSchema = new SimpleSchema({
|
|
||||||
itemsConsumed: {
|
|
||||||
type: Array,
|
|
||||||
defaultValue: [],
|
|
||||||
},
|
|
||||||
'itemsConsumed.$': {
|
|
||||||
type: ItemConsumedSchema,
|
|
||||||
},
|
|
||||||
attributesConsumed: {
|
|
||||||
type: Array,
|
|
||||||
defaultValue: [],
|
|
||||||
},
|
|
||||||
'attributesConsumed.$': {
|
|
||||||
type: AttributeConsumedSchema,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const ResourcesComputedOnlySchema = new SimpleSchema({
|
|
||||||
itemsConsumed: {
|
|
||||||
type: Array,
|
|
||||||
defaultValue: [],
|
|
||||||
},
|
|
||||||
'itemsConsumed.$': {
|
|
||||||
type: ComputedOnlyItemConsumedSchema,
|
|
||||||
},
|
|
||||||
attributesConsumed: {
|
|
||||||
type: Array,
|
|
||||||
defaultValue: [],
|
|
||||||
},
|
|
||||||
'attributesConsumed.$': {
|
|
||||||
type: ComputedOnlyAttributeConsumedSchema,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const ResourcesComputedSchema = new SimpleSchema({
|
|
||||||
itemsConsumed: {
|
|
||||||
type: Array,
|
|
||||||
defaultValue: [],
|
|
||||||
},
|
|
||||||
'itemsConsumed.$': {
|
|
||||||
type: ComputedItemConsumedSchema,
|
|
||||||
},
|
|
||||||
attributesConsumed: {
|
|
||||||
type: Array,
|
|
||||||
defaultValue: [],
|
|
||||||
},
|
|
||||||
'attributesConsumed.$': {
|
|
||||||
type: ComputedAttributeConsumedSchema,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export {
|
|
||||||
ResourcesSchema,
|
|
||||||
ResourcesComputedOnlySchema,
|
|
||||||
ResourcesComputedSchema,
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user