Merge branch 'version-2-dev' into version-2
This commit is contained in:
@@ -1,53 +0,0 @@
|
|||||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
|
||||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
|
||||||
import SimpleSchema from 'simpl-schema';
|
|
||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
|
||||||
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';
|
|
||||||
|
|
||||||
const damagePropertiesByName = new ValidatedMethod({
|
|
||||||
name: 'CreatureProperties.damagePropertiesByName',
|
|
||||||
validate: new SimpleSchema({
|
|
||||||
creatureId: SimpleSchema.RegEx.Id,
|
|
||||||
variableName: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
operation: {
|
|
||||||
type: String,
|
|
||||||
allowedValues: ['set', 'increment']
|
|
||||||
},
|
|
||||||
value: Number,
|
|
||||||
}).validator(),
|
|
||||||
mixins: [RateLimiterMixin],
|
|
||||||
rateLimit: {
|
|
||||||
numRequests: 20,
|
|
||||||
timeInterval: 5000,
|
|
||||||
},
|
|
||||||
run({creatureId, variableName, operation, value}) {
|
|
||||||
// Check permissions
|
|
||||||
let creature = Creatures.findOne(creatureId, {
|
|
||||||
fields: {
|
|
||||||
variables: 1,
|
|
||||||
owner: 1,
|
|
||||||
readers: 1,
|
|
||||||
writers: 1,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
assertEditPermission(creature, this.userId);
|
|
||||||
CreatureProperties.find({
|
|
||||||
'ancestors.id': creatureId,
|
|
||||||
variableName,
|
|
||||||
removed: {$ne: false},
|
|
||||||
inactive: {$ne: true},
|
|
||||||
}).forEach(property => {
|
|
||||||
// Check if property can take damage
|
|
||||||
let schema = CreatureProperties.simpleSchema(property);
|
|
||||||
if (!schema.allowsKey('damage')) return;
|
|
||||||
// Damage the property
|
|
||||||
damagePropertyWork({property, operation, value});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default damagePropertiesByName;
|
|
||||||
@@ -2,8 +2,9 @@ import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
|||||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
|
||||||
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||||
|
import { applyTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
|
import ActionContext from '/imports/api/engine/actions/ActionContext.js';
|
||||||
|
|
||||||
const damageProperty = new ValidatedMethod({
|
const damageProperty = new ValidatedMethod({
|
||||||
name: 'creatureProperties.damage',
|
name: 'creatureProperties.damage',
|
||||||
@@ -20,58 +21,105 @@ const damageProperty = new ValidatedMethod({
|
|||||||
numRequests: 20,
|
numRequests: 20,
|
||||||
timeInterval: 5000,
|
timeInterval: 5000,
|
||||||
},
|
},
|
||||||
run({_id, operation, value}) {
|
run({ _id, operation, value }) {
|
||||||
// Check permissions
|
|
||||||
let property = CreatureProperties.findOne(_id);
|
// Get action context
|
||||||
if (!property) throw new Meteor.Error(
|
const prop = CreatureProperties.findOne(_id);
|
||||||
|
if (!prop) throw new Meteor.Error(
|
||||||
'Damage property failed', 'Property doesn\'t exist'
|
'Damage property failed', 'Property doesn\'t exist'
|
||||||
);
|
);
|
||||||
let rootCreature = getRootCreatureAncestor(property);
|
const creatureId = prop.ancestors[0].id;
|
||||||
assertEditPermission(rootCreature, this.userId);
|
const actionContext = new ActionContext(creatureId, [creatureId], this);
|
||||||
|
|
||||||
|
// Check permissions
|
||||||
|
assertEditPermission(actionContext.creature, this.userId);
|
||||||
|
|
||||||
// Check if property can take damage
|
// Check if property can take damage
|
||||||
let schema = CreatureProperties.simpleSchema(property);
|
let schema = CreatureProperties.simpleSchema(prop);
|
||||||
if (!schema.allowsKey('damage')){
|
if (!schema.allowsKey('damage')){
|
||||||
throw new Meteor.Error(
|
throw new Meteor.Error(
|
||||||
'Damage property failed',
|
'Damage property failed',
|
||||||
`Property of type "${property.type}" can't be damaged`
|
`Property of type "${prop.type}" can't be damaged`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let result = damagePropertyWork({ property, operation, value });
|
|
||||||
|
const result = damagePropertyWork({ prop, operation, value, actionContext });
|
||||||
|
|
||||||
|
// Insert the log
|
||||||
|
actionContext.writeLog();
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export function damagePropertyWork({property, operation, value}){
|
export function damagePropertyWork({ prop, operation, value, actionContext }) {
|
||||||
let damage, newValue;
|
|
||||||
|
// Save the value to the scope before applying the before triggers
|
||||||
|
if (operation === 'increment') {
|
||||||
|
if (value >= 0) {
|
||||||
|
actionContext.scope['$damage'] = value;
|
||||||
|
} else {
|
||||||
|
actionContext.scope['$healing'] = -value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
actionContext.scope['$set'] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyTriggers(actionContext.triggers?.damageProperty?.before, prop, actionContext);
|
||||||
|
|
||||||
|
// fetch the value from the scope after the before triggers, in case they changed them
|
||||||
|
if (operation === 'increment') {
|
||||||
|
if (value >= 0) {
|
||||||
|
value = actionContext.scope['$damage'];
|
||||||
|
} else {
|
||||||
|
value = -actionContext.scope['$healing'];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = actionContext.scope['$set'];
|
||||||
|
}
|
||||||
|
|
||||||
|
let damage, newValue, increment;
|
||||||
if (operation === 'set'){
|
if (operation === 'set'){
|
||||||
const total = property.total || 0;
|
const total = prop.total || 0;
|
||||||
// Set represents what we want the value to be after damage
|
// Set represents what we want the value to be after damage
|
||||||
// So we need the actual damage to get to that value
|
// So we need the actual damage to get to that value
|
||||||
damage = total - value;
|
damage = total - value;
|
||||||
// Damage can't exceed total value
|
// Damage can't exceed total value
|
||||||
if (damage > total) damage = total;
|
if (damage > total && !prop.ignoreLowerLimit) damage = total;
|
||||||
// Damage must be positive
|
// Damage must be positive
|
||||||
if (damage < 0) damage = 0;
|
if (damage < 0 && !prop.ignoreUpperLimit) damage = 0;
|
||||||
newValue = property.total - damage;
|
newValue = prop.total - damage;
|
||||||
|
// Write the results
|
||||||
|
CreatureProperties.update(prop._id, {
|
||||||
|
$set: { damage, value: newValue, dirty: true }
|
||||||
|
}, {
|
||||||
|
selector: prop
|
||||||
|
});
|
||||||
} else if (operation === 'increment'){
|
} else if (operation === 'increment'){
|
||||||
let currentValue = property.value || 0;
|
let currentValue = prop.value || 0;
|
||||||
let currentDamage = property.damage || 0;
|
let currentDamage = prop.damage || 0;
|
||||||
let increment = value;
|
increment = value;
|
||||||
// Can't increase damage above the remaining value
|
// Can't increase damage above the remaining value
|
||||||
if (increment > currentValue) increment = currentValue;
|
if (increment > currentValue && !prop.ignoreLowerLimit) increment = currentValue;
|
||||||
// Can't decrease damage below zero
|
// Can't decrease damage below zero
|
||||||
if (-increment > currentDamage) increment = -currentDamage;
|
if (-increment > currentDamage && !prop.ignoreUpperLimit) increment = -currentDamage;
|
||||||
damage = currentDamage + increment;
|
damage = currentDamage + increment;
|
||||||
newValue = property.total - damage;
|
newValue = prop.total - damage;
|
||||||
|
// Write the results
|
||||||
|
CreatureProperties.update(prop._id, {
|
||||||
|
$inc: { damage: increment, value: -increment },
|
||||||
|
$set: { dirty: true },
|
||||||
|
}, {
|
||||||
|
selector: prop
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the results
|
applyTriggers(actionContext.triggers?.damageProperty?.after, prop, actionContext);
|
||||||
CreatureProperties.update(property._id, {
|
|
||||||
$set: {damage, value: newValue, dirty: true}
|
if (operation === 'set') {
|
||||||
}, {
|
return damage;
|
||||||
selector: property
|
} else if (operation === 'increment') {
|
||||||
});
|
return increment;
|
||||||
return damage;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default damageProperty;
|
export default damageProperty;
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
|
||||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
|
||||||
import SimpleSchema from 'simpl-schema';
|
|
||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
|
||||||
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';
|
|
||||||
|
|
||||||
const dealDamage = new ValidatedMethod({
|
|
||||||
name: 'creatureProperties.dealDamage',
|
|
||||||
validate: new SimpleSchema({
|
|
||||||
creatureId: SimpleSchema.RegEx.Id,
|
|
||||||
damageType: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
amount: Number,
|
|
||||||
}).validator(),
|
|
||||||
mixins: [RateLimiterMixin],
|
|
||||||
rateLimit: {
|
|
||||||
numRequests: 20,
|
|
||||||
timeInterval: 5000,
|
|
||||||
},
|
|
||||||
run({creatureId, damageType, amount}) {
|
|
||||||
// permissions
|
|
||||||
let creature = Creatures.findOne(creatureId, {
|
|
||||||
fields: {
|
|
||||||
owner: 1,
|
|
||||||
readers: 1,
|
|
||||||
writers: 1,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
assertEditPermission(creature, this.userId);
|
|
||||||
|
|
||||||
const totalDamage = dealDamageWork({creature, damageType, amount})
|
|
||||||
return totalDamage;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export function dealDamageWork({creature, damageType, amount}){
|
|
||||||
// Get all the health bars and do damage to them
|
|
||||||
let healthBars = CreatureProperties.find({
|
|
||||||
'ancestors.id': creature._id,
|
|
||||||
type: 'attribute',
|
|
||||||
attributeType:'healthBar',
|
|
||||||
removed: {$ne: true},
|
|
||||||
inactive: {$ne: true},
|
|
||||||
}, {
|
|
||||||
sort: {order: -1},
|
|
||||||
});
|
|
||||||
//let multiplier = creature.damageMultipliers[damageType];
|
|
||||||
//if (multiplier === undefined) multiplier = 1;
|
|
||||||
//let totalDamage = Math.floor(amount * multiplier);
|
|
||||||
const totalDamage = amount;
|
|
||||||
let damageLeft = totalDamage;
|
|
||||||
if (damageType === 'healing') damageLeft = -totalDamage;
|
|
||||||
let propertyIds = [];
|
|
||||||
healthBars.forEach(healthBar => {
|
|
||||||
if (damageLeft === 0) return;
|
|
||||||
let damageAdded = damagePropertyWork({
|
|
||||||
property: healthBar,
|
|
||||||
operation: 'increment',
|
|
||||||
value: damageLeft,
|
|
||||||
});
|
|
||||||
damageLeft -= damageAdded;
|
|
||||||
propertyIds.push(healthBar._id);
|
|
||||||
});
|
|
||||||
return totalDamage;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default dealDamage;
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
import '/imports/api/creature/creatureProperties/methods/adjustQuantity.js';
|
import '/imports/api/creature/creatureProperties/methods/adjustQuantity.js';
|
||||||
import '/imports/api/creature/creatureProperties/methods/damagePropertiesByName.js';
|
|
||||||
import '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
import '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||||
import '/imports/api/creature/creatureProperties/methods/dealDamage.js';
|
|
||||||
import '/imports/api/creature/creatureProperties/methods/duplicateProperty.js';
|
import '/imports/api/creature/creatureProperties/methods/duplicateProperty.js';
|
||||||
import '/imports/api/creature/creatureProperties/methods/equipItem.js';
|
import '/imports/api/creature/creatureProperties/methods/equipItem.js';
|
||||||
import '/imports/api/creature/creatureProperties/methods/insertProperty.js';
|
import '/imports/api/creature/creatureProperties/methods/insertProperty.js';
|
||||||
|
|||||||
@@ -3,12 +3,9 @@ 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 { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||||
import { groupBy, remove, union } from 'lodash';
|
import { union } from 'lodash';
|
||||||
import {
|
import ActionContext from '/imports/api/engine/actions/ActionContext.js';
|
||||||
getCreature, getVariables, getPropertiesOfType
|
import { applyTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
} from '/imports/api/engine/loadCreatures.js';
|
|
||||||
import { CreatureLogSchema, insertCreatureLogWork } from '/imports/api/creature/log/CreatureLogs.js';
|
|
||||||
import { applyTrigger } from '/imports/api/engine/actions/applyTriggers.js';
|
|
||||||
|
|
||||||
const restCreature = new ValidatedMethod({
|
const restCreature = new ValidatedMethod({
|
||||||
name: 'creature.methods.rest',
|
name: 'creature.methods.rest',
|
||||||
@@ -27,59 +24,37 @@ const restCreature = new ValidatedMethod({
|
|||||||
numRequests: 5,
|
numRequests: 5,
|
||||||
timeInterval: 5000,
|
timeInterval: 5000,
|
||||||
},
|
},
|
||||||
run({creatureId, restType}) {
|
run({ creatureId, restType }) {
|
||||||
|
// Get action context
|
||||||
|
const actionContext = new ActionContext(creatureId, [creatureId], this);
|
||||||
// Check permissions
|
// Check permissions
|
||||||
let creature = getCreature(creatureId);
|
assertEditPermission(actionContext.creature, this.userId);
|
||||||
assertEditPermission(creature, this.userId);
|
|
||||||
|
|
||||||
// Add the variables to the creature document
|
// Join, sort, and apply before triggers
|
||||||
const variables = getVariables(creatureId);
|
const beforeTriggers = union(
|
||||||
delete variables._id;
|
actionContext.triggers.anyRest?.before, actionContext.triggers[restType]?.before
|
||||||
delete variables._creatureId;
|
).sort((a, b) => a.order - b.order);
|
||||||
creature.variables = variables;
|
applyTriggers(beforeTriggers, null, actionContext);
|
||||||
const scope = creature.variables;
|
|
||||||
|
|
||||||
// Get the triggers
|
// Rest
|
||||||
let triggers = getPropertiesOfType(creatureId, 'trigger');
|
actionContext.addLog({
|
||||||
remove(triggers, trigger =>
|
name: restType === 'shortRest' ? 'Short rest' : 'Long rest',
|
||||||
trigger.event !== 'anyRest' &&
|
|
||||||
trigger.event !== 'longRest' &&
|
|
||||||
trigger.event !== 'shortRest'
|
|
||||||
);
|
|
||||||
triggers = groupBy(triggers, 'event');
|
|
||||||
for (let type in triggers) {
|
|
||||||
triggers[type] = groupBy(triggers[type], 'timing')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the log
|
|
||||||
const log = CreatureLogSchema.clean({
|
|
||||||
creatureId: creature._id,
|
|
||||||
creatureName: creature.name,
|
|
||||||
});
|
});
|
||||||
|
doRestWork(restType, actionContext);
|
||||||
|
|
||||||
const targets = [creature];
|
// Join, sort, and apply after triggers
|
||||||
|
const afterTriggers = union(
|
||||||
|
actionContext.triggers.anyRest?.after, actionContext.triggers[restType]?.after
|
||||||
|
).sort((a, b) => a.order - b.order);
|
||||||
|
applyTriggers(afterTriggers, null, actionContext);
|
||||||
|
|
||||||
applyTriggers(triggers, restType, 'before', { creature, targets, scope, log });
|
// Insert log
|
||||||
doRestWork(creature, restType);
|
actionContext.writeLog();
|
||||||
applyTriggers(triggers, restType, 'after', { creature, targets, scope, log });
|
|
||||||
|
|
||||||
insertCreatureLogWork({log, creature, method: this});
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function applyTriggers(triggers, restType, timing, opts) {
|
function doRestWork(restType, actionContext) {
|
||||||
// Get matching triggers
|
const creatureId = actionContext.creature._id;
|
||||||
let selectedTriggers = triggers[restType]?.[timing] || [];
|
|
||||||
// Get any rest triggers as well
|
|
||||||
selectedTriggers = union(selectedTriggers, triggers['anyRest']?.[timing]);
|
|
||||||
selectedTriggers.sort((a, b) => a.order - b.order);
|
|
||||||
// Apply the triggers
|
|
||||||
selectedTriggers.forEach(trigger => {
|
|
||||||
applyTrigger(trigger, opts)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function doRestWork(creature, restType) {
|
|
||||||
// Long rests reset short rest properties as well
|
// Long rests reset short rest properties as well
|
||||||
let resetFilter;
|
let resetFilter;
|
||||||
if (restType === 'shortRest'){
|
if (restType === 'shortRest'){
|
||||||
@@ -89,7 +64,7 @@ function doRestWork(creature, restType) {
|
|||||||
}
|
}
|
||||||
// Only apply to active properties
|
// Only apply to active properties
|
||||||
let filter = {
|
let filter = {
|
||||||
'ancestors.id': creature._id,
|
'ancestors.id': creatureId,
|
||||||
reset: resetFilter,
|
reset: resetFilter,
|
||||||
removed: { $ne: true },
|
removed: { $ne: true },
|
||||||
inactive: { $ne: true },
|
inactive: { $ne: true },
|
||||||
@@ -123,7 +98,7 @@ function doRestWork(creature, restType) {
|
|||||||
// Reset half hit dice on a long rest, starting with the highest dice
|
// Reset half hit dice on a long rest, starting with the highest dice
|
||||||
if (restType === 'longRest'){
|
if (restType === 'longRest'){
|
||||||
let hitDice = CreatureProperties.find({
|
let hitDice = CreatureProperties.find({
|
||||||
'ancestors.id': creature._id,
|
'ancestors.id': creatureId,
|
||||||
type: 'attribute',
|
type: 'attribute',
|
||||||
attributeType: 'hitDice',
|
attributeType: 'hitDice',
|
||||||
removed: {$ne: true},
|
removed: {$ne: true},
|
||||||
@@ -132,7 +107,7 @@ function doRestWork(creature, restType) {
|
|||||||
fields: {
|
fields: {
|
||||||
hitDiceSize: 1,
|
hitDiceSize: 1,
|
||||||
damage: 1,
|
damage: 1,
|
||||||
value: 1,
|
total: 1,
|
||||||
}
|
}
|
||||||
}).fetch();
|
}).fetch();
|
||||||
// Use a collator to do sorting in natural order
|
// Use a collator to do sorting in natural order
|
||||||
@@ -143,8 +118,8 @@ function doRestWork(creature, restType) {
|
|||||||
let compare = (a, b) => collator.compare(b.hitDiceSize, a.hitDiceSize)
|
let compare = (a, b) => collator.compare(b.hitDiceSize, a.hitDiceSize)
|
||||||
hitDice.sort(compare);
|
hitDice.sort(compare);
|
||||||
// Get the total number of hit dice that can be recovered this rest
|
// Get the total number of hit dice that can be recovered this rest
|
||||||
let totalHd = hitDice.reduce((sum, hd) => sum + (hd.value || 0), 0);
|
let totalHd = hitDice.reduce((sum, hd) => sum + (hd.total || 0), 0);
|
||||||
let resetMultiplier = creature.settings.hitDiceResetMultiplier || 0.5;
|
let resetMultiplier = actionContext.creature.settings.hitDiceResetMultiplier || 0.5;
|
||||||
let recoverableHd = Math.max(Math.floor(totalHd*resetMultiplier), 1);
|
let recoverableHd = Math.max(Math.floor(totalHd*resetMultiplier), 1);
|
||||||
// recover each hit dice in turn until the recoverable amount is used up
|
// recover each hit dice in turn until the recoverable amount is used up
|
||||||
let amountToRecover, resultingDamage;
|
let amountToRecover, resultingDamage;
|
||||||
|
|||||||
78
app/imports/api/engine/actions/ActionContext.js
Normal file
78
app/imports/api/engine/actions/ActionContext.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { CreatureLogSchema, insertCreatureLogWork } from '/imports/api/creature/log/CreatureLogs.js';
|
||||||
|
import {
|
||||||
|
getCreature, getVariables, getPropertiesOfType
|
||||||
|
} from '/imports/api/engine/loadCreatures.js';
|
||||||
|
import { groupBy, remove } from 'lodash';
|
||||||
|
|
||||||
|
export default class ActionContext{
|
||||||
|
constructor(creatureId, targetIds = [], method) {
|
||||||
|
// Get the creature
|
||||||
|
this.creature = getCreature(creatureId)
|
||||||
|
|
||||||
|
if (!this.creature) {
|
||||||
|
throw new Meteor.Error('No Creature', `No creature could be found with id: ${creatureId}`)
|
||||||
|
}
|
||||||
|
// Create a log
|
||||||
|
this.log = CreatureLogSchema.clean({
|
||||||
|
creatureId: creatureId,
|
||||||
|
creatureName: this.creature.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get the variables of the acting creature
|
||||||
|
this.creature.variables = getVariables(creatureId);
|
||||||
|
delete this.creature.variables._id;
|
||||||
|
delete this.creature.variables._creatureId;
|
||||||
|
// Alias as scope
|
||||||
|
this.scope = this.creature.variables;
|
||||||
|
|
||||||
|
// Get the targets and their variables
|
||||||
|
this.targets = [];
|
||||||
|
targetIds.forEach(targetId => {
|
||||||
|
let target;
|
||||||
|
if (targetId === creatureId) {
|
||||||
|
target = this.creature;
|
||||||
|
} else {
|
||||||
|
target = getCreature(targetId);
|
||||||
|
target.variables = getVariables(targetId);
|
||||||
|
delete target.variables._id;
|
||||||
|
delete target.variables._creatureId;
|
||||||
|
}
|
||||||
|
this.targets.push(target);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store a reference to the method for inserting the log
|
||||||
|
this.method = method;
|
||||||
|
|
||||||
|
// Get triggers
|
||||||
|
this.triggers = getPropertiesOfType(creatureId, 'trigger');
|
||||||
|
// Remove deleted or inactive triggers
|
||||||
|
remove(this.triggers, trigger => trigger.removed || trigger.inactive);
|
||||||
|
// Sort triggers by order
|
||||||
|
this.triggers.sort((a, b) => a.order - b.order);
|
||||||
|
// Group the triggers into triggers.<event>.<timing> or
|
||||||
|
// triggers.doActionProperty.<propertyType>.<timing>
|
||||||
|
this.triggers = groupBy(this.triggers, 'event');
|
||||||
|
for (let event in this.triggers) {
|
||||||
|
if (event === 'doActionProperty') {
|
||||||
|
this.triggers[event] = groupBy(this.triggers[event], 'actionPropertyType');
|
||||||
|
for (let propertyType in this.triggers[event]) {
|
||||||
|
this.triggers[event][propertyType] = groupBy(this.triggers[event][propertyType], 'timing');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.triggers[event] = groupBy(this.triggers[event], 'timing');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addLog(content) {
|
||||||
|
if (content.name || content.value){
|
||||||
|
this.log.content.push(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeLog() {
|
||||||
|
insertCreatureLogWork({
|
||||||
|
log: this.log,
|
||||||
|
creature: this.creature,
|
||||||
|
method: this.method,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,6 @@ import note from './applyPropertyByType/applyNote.js';
|
|||||||
import roll from './applyPropertyByType/applyRoll.js';
|
import roll from './applyPropertyByType/applyRoll.js';
|
||||||
import savingThrow from './applyPropertyByType/applySavingThrow.js';
|
import savingThrow from './applyPropertyByType/applySavingThrow.js';
|
||||||
import toggle from './applyPropertyByType/applyToggle.js';
|
import toggle from './applyPropertyByType/applyToggle.js';
|
||||||
import applyTriggers from '/imports/api/engine/actions/applyTriggers.js';
|
|
||||||
|
|
||||||
const applyPropertyByType = {
|
const applyPropertyByType = {
|
||||||
action,
|
action,
|
||||||
@@ -22,9 +21,7 @@ const applyPropertyByType = {
|
|||||||
toggle,
|
toggle,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function applyProperty(node, opts, ...rest) {
|
export default function applyProperty(node, actionContext, ...rest) {
|
||||||
applyTriggers(node, opts, 'before');
|
actionContext.scope[`#${node.node.type}`] = node.node;
|
||||||
opts.scope[`#${node.node.type}`] = node.node;
|
applyPropertyByType[node.node.type]?.(node, actionContext, ...rest);
|
||||||
applyPropertyByType[node.node.type]?.(node, opts, ...rest);
|
|
||||||
applyTriggers(node, opts, 'after');
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,23 +6,24 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur
|
|||||||
import { adjustQuantityWork } from '/imports/api/creature/creatureProperties/methods/adjustQuantity.js';
|
import { adjustQuantityWork } from '/imports/api/creature/creatureProperties/methods/adjustQuantity.js';
|
||||||
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||||
|
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
|
|
||||||
export default function applyAction(node, {creature, targets, scope, log}){
|
export default function applyAction(node, actionContext) {
|
||||||
|
applyNodeTriggers(node, 'before', actionContext);
|
||||||
const prop = node.node;
|
const prop = node.node;
|
||||||
if (prop.target === 'self') targets = [creature];
|
let targets = actionContext.targets;
|
||||||
|
if (prop.target === 'self') targets = [actionContext.creature];
|
||||||
|
|
||||||
// Log the name and summary
|
// Log the name and summary
|
||||||
let content = { name: prop.name };
|
let content = { name: prop.name };
|
||||||
if (prop.summary?.text){
|
if (prop.summary?.text){
|
||||||
recalculateInlineCalculations(prop.summary, scope, log);
|
recalculateInlineCalculations(prop.summary, actionContext);
|
||||||
content.value = prop.summary.value;
|
content.value = prop.summary.value;
|
||||||
}
|
}
|
||||||
if (content.name || content.value){
|
actionContext.addLog(content);
|
||||||
log.content.push(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spend the resources
|
// Spend the resources
|
||||||
const failed = spendResources({prop, log, scope});
|
const failed = spendResources(prop, actionContext);
|
||||||
if (failed) return;
|
if (failed) return;
|
||||||
|
|
||||||
const attack = prop.attackRoll || prop.attackRollBonus;
|
const attack = prop.attackRoll || prop.attackRollBonus;
|
||||||
@@ -31,28 +32,29 @@ export default function applyAction(node, {creature, targets, scope, log}){
|
|||||||
if (attack && attack.calculation){
|
if (attack && attack.calculation){
|
||||||
if (targets.length){
|
if (targets.length){
|
||||||
targets.forEach(target => {
|
targets.forEach(target => {
|
||||||
applyAttackToTarget({attack, target, scope, log});
|
applyAttackToTarget({attack, target, actionContext});
|
||||||
// Apply the children, but only to the current target
|
// Apply the children, but only to the current target
|
||||||
applyChildren(node, {creature, targets: [target], scope, log});
|
actionContext.targets = [target];
|
||||||
|
applyChildren(node, actionContext);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
applyAttackWithoutTarget({attack, scope, log});
|
applyAttackWithoutTarget({attack, actionContext});
|
||||||
applyChildren(node, {creature, targets, scope, log});
|
applyChildren(node, actionContext);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
applyChildren(node, {creature, targets, scope, log});
|
applyChildren(node, actionContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyAttackWithoutTarget({attack, scope, log}){
|
function applyAttackWithoutTarget({attack, actionContext}){
|
||||||
delete scope['$attackHit'];
|
delete actionContext.scope['$attackHit'];
|
||||||
delete scope['$attackMiss'];
|
delete actionContext.scope['$attackMiss'];
|
||||||
delete scope['$criticalHit'];
|
delete actionContext.scope['$criticalHit'];
|
||||||
delete scope['$criticalMiss'];
|
delete actionContext.scope['$criticalMiss'];
|
||||||
delete scope['$attackRoll'];
|
delete actionContext.scope['$attackRoll'];
|
||||||
|
|
||||||
recalculateCalculation(attack, scope, log);
|
|
||||||
|
|
||||||
|
recalculateCalculation(attack, actionContext);
|
||||||
|
const scope = actionContext.scope;
|
||||||
let {
|
let {
|
||||||
resultPrefix,
|
resultPrefix,
|
||||||
result,
|
result,
|
||||||
@@ -72,14 +74,15 @@ function applyAttackWithoutTarget({attack, scope, log}){
|
|||||||
scope['$attackMiss'] = {value: true};
|
scope['$attackMiss'] = {value: true};
|
||||||
}
|
}
|
||||||
|
|
||||||
log.content.push({
|
actionContext.addLog({
|
||||||
name,
|
name,
|
||||||
value: `${resultPrefix}\n**${result}**`,
|
value: `${resultPrefix}\n**${result}**`,
|
||||||
inline: true,
|
inline: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyAttackToTarget({attack, target, scope, log}){
|
function applyAttackToTarget({attack, target, actionContext}){
|
||||||
|
const scope = actionContext.scope;
|
||||||
delete scope['$attackHit'];
|
delete scope['$attackHit'];
|
||||||
delete scope['$attackMiss'];
|
delete scope['$attackMiss'];
|
||||||
delete scope['$criticalHit'];
|
delete scope['$criticalHit'];
|
||||||
@@ -87,7 +90,7 @@ function applyAttackToTarget({attack, target, scope, log}){
|
|||||||
delete scope['$attackDiceRoll'];
|
delete scope['$attackDiceRoll'];
|
||||||
delete scope['$attackRoll'];
|
delete scope['$attackRoll'];
|
||||||
|
|
||||||
recalculateCalculation(attack, scope, log);
|
recalculateCalculation(attack, actionContext);
|
||||||
|
|
||||||
let {
|
let {
|
||||||
resultPrefix,
|
resultPrefix,
|
||||||
@@ -108,7 +111,7 @@ function applyAttackToTarget({attack, target, scope, log}){
|
|||||||
name += ' (Disadvantage)';
|
name += ' (Disadvantage)';
|
||||||
}
|
}
|
||||||
|
|
||||||
log.content.push({
|
actionContext.addLog({
|
||||||
name,
|
name,
|
||||||
value: `${resultPrefix}\n**${result}**`,
|
value: `${resultPrefix}\n**${result}**`,
|
||||||
inline: true,
|
inline: true,
|
||||||
@@ -119,11 +122,11 @@ function applyAttackToTarget({attack, target, scope, log}){
|
|||||||
scope['$attackHit'] = {value: true};
|
scope['$attackHit'] = {value: true};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.content.push({
|
actionContext.addLog({
|
||||||
name: 'Error',
|
name: 'Error',
|
||||||
value:'Target has no `armor`',
|
value:'Target has no `armor`',
|
||||||
});
|
});
|
||||||
log.content.push({
|
actionContext.addLog({
|
||||||
name: criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical Miss!' : 'To Hit',
|
name: criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical Miss!' : 'To Hit',
|
||||||
value: `${resultPrefix}\n**${result}**`,
|
value: `${resultPrefix}\n**${result}**`,
|
||||||
inline: true,
|
inline: true,
|
||||||
@@ -177,14 +180,15 @@ function applyCrits(value, scope){
|
|||||||
return {criticalHit, criticalMiss};
|
return {criticalHit, criticalMiss};
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyChildren(node, args){
|
function applyChildren(node, actionContext) {
|
||||||
node.children.forEach(child => applyProperty(child, args));
|
applyNodeTriggers(node, 'after', actionContext);
|
||||||
|
node.children.forEach(child => applyProperty(child, actionContext));
|
||||||
}
|
}
|
||||||
|
|
||||||
function spendResources({prop, log, scope}){
|
function spendResources(prop, actionContext){
|
||||||
// Check Uses
|
// Check Uses
|
||||||
if (prop.usesLeft <= 0){
|
if (prop.usesLeft <= 0){
|
||||||
log.content.push({
|
actionContext.addLog({
|
||||||
name: 'Error',
|
name: 'Error',
|
||||||
value: `${prop.name || 'action'} does not have enough uses left`,
|
value: `${prop.name || 'action'} does not have enough uses left`,
|
||||||
});
|
});
|
||||||
@@ -192,7 +196,7 @@ function spendResources({prop, log, scope}){
|
|||||||
}
|
}
|
||||||
// Resources
|
// Resources
|
||||||
if (prop.insufficientResources){
|
if (prop.insufficientResources){
|
||||||
log.content.push({
|
actionContext.addLog({
|
||||||
name: 'Error',
|
name: 'Error',
|
||||||
value: 'This creature doesn\'t have sufficient resources to perform this action',
|
value: 'This creature doesn\'t have sufficient resources to perform this action',
|
||||||
});
|
});
|
||||||
@@ -204,7 +208,7 @@ function spendResources({prop, log, scope}){
|
|||||||
let gainLog = [];
|
let gainLog = [];
|
||||||
try {
|
try {
|
||||||
prop.resources.itemsConsumed.forEach(itemConsumed => {
|
prop.resources.itemsConsumed.forEach(itemConsumed => {
|
||||||
recalculateCalculation(itemConsumed.quantity, scope, log);
|
recalculateCalculation(itemConsumed.quantity, actionContext);
|
||||||
if (!itemConsumed.itemId){
|
if (!itemConsumed.itemId){
|
||||||
throw 'No ammo was selected for this prop';
|
throw 'No ammo was selected for this prop';
|
||||||
}
|
}
|
||||||
@@ -235,7 +239,7 @@ function spendResources({prop, log, scope}){
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (e){
|
} catch (e){
|
||||||
log.content.push({
|
actionContext.addLog({
|
||||||
name: 'Error',
|
name: 'Error',
|
||||||
value: e,
|
value: e,
|
||||||
});
|
});
|
||||||
@@ -253,7 +257,7 @@ function spendResources({prop, log, scope}){
|
|||||||
}, {
|
}, {
|
||||||
selector: prop
|
selector: prop
|
||||||
});
|
});
|
||||||
log.content.push({
|
actionContext.addLog({
|
||||||
name: 'Uses left',
|
name: 'Uses left',
|
||||||
value: prop.usesLeft - 1,
|
value: prop.usesLeft - 1,
|
||||||
inline: true,
|
inline: true,
|
||||||
@@ -262,18 +266,19 @@ function spendResources({prop, log, scope}){
|
|||||||
|
|
||||||
// Damage stats
|
// Damage stats
|
||||||
prop.resources.attributesConsumed.forEach(attConsumed => {
|
prop.resources.attributesConsumed.forEach(attConsumed => {
|
||||||
recalculateCalculation(attConsumed.quantity, scope, log);
|
recalculateCalculation(attConsumed.quantity, actionContext);
|
||||||
|
|
||||||
if (!attConsumed.quantity?.value) return;
|
if (!attConsumed.quantity?.value) return;
|
||||||
let stat = scope[attConsumed.variableName];
|
let stat = actionContext.scope[attConsumed.variableName];
|
||||||
if (!stat){
|
if (!stat){
|
||||||
spendLog.push(stat.name + ': ' + ' not found');
|
spendLog.push(stat.name + ': ' + ' not found');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
damagePropertyWork({
|
damagePropertyWork({
|
||||||
property: stat,
|
prop: stat,
|
||||||
operation: 'increment',
|
operation: 'increment',
|
||||||
value: attConsumed.quantity.value,
|
value: attConsumed.quantity.value,
|
||||||
|
actionContext,
|
||||||
});
|
});
|
||||||
if (attConsumed.quantity.value > 0){
|
if (attConsumed.quantity.value > 0){
|
||||||
spendLog.push(stat.name + ': ' + attConsumed.quantity.value);
|
spendLog.push(stat.name + ': ' + attConsumed.quantity.value);
|
||||||
@@ -283,12 +288,12 @@ function spendResources({prop, log, scope}){
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Log all the spending
|
// Log all the spending
|
||||||
if (gainLog.length) log.content.push({
|
if (gainLog.length) actionContext.addLog({
|
||||||
name: 'Gained',
|
name: 'Gained',
|
||||||
value: gainLog.join('\n'),
|
value: gainLog.join('\n'),
|
||||||
inline: true,
|
inline: true,
|
||||||
});
|
});
|
||||||
if (spendLog.length) log.content.push({
|
if (spendLog.length) actionContext.addLog({
|
||||||
name: 'Spent',
|
name: 'Spent',
|
||||||
value: spendLog.join('\n'),
|
value: spendLog.join('\n'),
|
||||||
inline: true,
|
inline: true,
|
||||||
|
|||||||
@@ -1,41 +1,42 @@
|
|||||||
import applyProperty from '../applyProperty.js';
|
import applyProperty from '../applyProperty.js';
|
||||||
import recalculateCalculation from './shared/recalculateCalculation.js';
|
import recalculateCalculation from './shared/recalculateCalculation.js';
|
||||||
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||||
|
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
|
|
||||||
export default function applyAdjustment(node, {
|
export default function applyAdjustment(node, actionContext){
|
||||||
creature, targets, scope, log
|
applyNodeTriggers(node, 'before', actionContext);
|
||||||
}){
|
|
||||||
const prop = node.node;
|
const prop = node.node;
|
||||||
const damageTargets = prop.target === 'self' ? [creature] : targets;
|
const damageTargets = prop.target === 'self' ? [actionContext.creature] : actionContext.targets;
|
||||||
|
|
||||||
if (!prop.amount) {
|
if (!prop.amount) {
|
||||||
return applyChildren(node, {creature, targets, scope, log});
|
return applyChildren(node, actionContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate the amount
|
// Evaluate the amount
|
||||||
recalculateCalculation(prop.amount, scope, log);
|
recalculateCalculation(prop.amount, actionContext);
|
||||||
|
|
||||||
const value = +prop.amount.value;
|
const value = +prop.amount.value;
|
||||||
if (!isFinite(value)) {
|
if (!isFinite(value)) {
|
||||||
return applyChildren(node, {creature, targets, scope, log});
|
return applyChildren(node, actionContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (damageTargets?.length) {
|
if (damageTargets?.length) {
|
||||||
damageTargets.forEach(target => {
|
damageTargets.forEach(target => {
|
||||||
let stat = target.variables[prop.stat];
|
let stat = target.variables[prop.stat];
|
||||||
if (!stat?.type) {
|
if (!stat?.type) {
|
||||||
log.content.push({
|
actionContext.addLog({
|
||||||
name: 'Error',
|
name: 'Error',
|
||||||
value: `Could not apply attribute damage, creature does not have \`${prop.stat}\` set`
|
value: `Could not apply attribute damage, creature does not have \`${prop.stat}\` set`
|
||||||
});
|
});
|
||||||
return applyChildren(node, {creature, targets, scope, log});
|
return applyChildren(node, actionContext);
|
||||||
}
|
}
|
||||||
damagePropertyWork({
|
damagePropertyWork({
|
||||||
property: stat,
|
prop: stat,
|
||||||
operation: prop.operation,
|
operation: prop.operation,
|
||||||
value: value,
|
value,
|
||||||
|
actionContext,
|
||||||
});
|
});
|
||||||
log.content.push({
|
actionContext.addLog({
|
||||||
name: 'Attribute damage',
|
name: 'Attribute damage',
|
||||||
value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` +
|
value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` +
|
||||||
` ${value}`,
|
` ${value}`,
|
||||||
@@ -43,7 +44,7 @@ export default function applyAdjustment(node, {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log.content.push({
|
actionContext.addLog({
|
||||||
name: 'Attribute damage',
|
name: 'Attribute damage',
|
||||||
value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` +
|
value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` +
|
||||||
` ${value}`,
|
` ${value}`,
|
||||||
@@ -51,9 +52,10 @@ export default function applyAdjustment(node, {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return applyChildren(node, {creature, targets, scope, log});
|
return applyChildren(node, actionContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyChildren(node, args){
|
function applyChildren(node, actionContext){
|
||||||
node.children.forEach(child => applyProperty(child, args));
|
applyNodeTriggers(node, 'after', actionContext);
|
||||||
|
node.children.forEach(child => applyProperty(child, actionContext));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,27 @@
|
|||||||
import applyProperty from '../applyProperty.js';
|
import applyProperty from '../applyProperty.js';
|
||||||
import recalculateCalculation from './shared/recalculateCalculation.js';
|
import recalculateCalculation from './shared/recalculateCalculation.js';
|
||||||
import rollDice from '/imports/parser/rollDice.js';
|
import rollDice from '/imports/parser/rollDice.js';
|
||||||
|
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
|
|
||||||
export default function applyBranch(node, {
|
export default function applyBranch(node, actionContext){
|
||||||
creature, targets, scope, log
|
applyNodeTriggers(node, 'before', actionContext);
|
||||||
}){
|
|
||||||
const applyChildren = function(){
|
const applyChildren = function(){
|
||||||
node.children.forEach(child => applyProperty(child, {
|
applyNodeTriggers(node, 'after', actionContext);
|
||||||
creature, targets, scope, log
|
node.children.forEach(child => applyProperty(child, actionContext));
|
||||||
}));
|
|
||||||
};
|
};
|
||||||
|
const scope = actionContext.scope;
|
||||||
|
const targets = actionContext.targets;
|
||||||
const prop = node.node;
|
const prop = node.node;
|
||||||
switch(prop.branchType){
|
switch(prop.branchType){
|
||||||
case 'if':
|
case 'if':
|
||||||
recalculateCalculation(prop.condition, scope, log);
|
recalculateCalculation(prop.condition, actionContext);
|
||||||
if (prop.condition?.value) applyChildren();
|
if (prop.condition?.value) applyChildren();
|
||||||
break;
|
break;
|
||||||
case 'index':
|
case 'index':
|
||||||
if (node.children.length){
|
if (node.children.length){
|
||||||
recalculateCalculation(prop.condition, scope, log);
|
recalculateCalculation(prop.condition, actionContext);
|
||||||
if (!isFinite(prop.condition?.value)) {
|
if (!isFinite(prop.condition?.value)) {
|
||||||
log.content.push({
|
actionContext.addLog({
|
||||||
name: 'Branch Error',
|
name: 'Branch Error',
|
||||||
value: 'Index did not resolve into a valid number'
|
value: 'Index did not resolve into a valid number'
|
||||||
});
|
});
|
||||||
@@ -29,49 +30,47 @@ export default function applyBranch(node, {
|
|||||||
let index = Math.floor(prop.condition?.value);
|
let index = Math.floor(prop.condition?.value);
|
||||||
if (index < 1) index = 1;
|
if (index < 1) index = 1;
|
||||||
if (index > node.children.length) index = node.children.length;
|
if (index > node.children.length) index = node.children.length;
|
||||||
applyProperty(node.children[index - 1], {
|
applyNodeTriggers(node, 'after', actionContext);
|
||||||
creature, targets, scope, log
|
applyProperty(node.children[index - 1], actionContext);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'hit':
|
case 'hit':
|
||||||
if (scope['$attackHit']?.value){
|
if (scope['$attackHit']?.value){
|
||||||
if (!targets.length) log.content.push({value: '**On hit**'});
|
if (!targets.length) actionContext.addLog({value: '**On hit**'});
|
||||||
applyChildren();
|
applyChildren();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'miss':
|
case 'miss':
|
||||||
if (scope['$attackMiss']?.value){
|
if (scope['$attackMiss']?.value){
|
||||||
if (!targets.length) log.content.push({value: '**On miss**'});
|
if (!targets.length) actionContext.addLog({value: '**On miss**'});
|
||||||
applyChildren();
|
applyChildren();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'failedSave':
|
case 'failedSave':
|
||||||
if (scope['$saveFailed']?.value){
|
if (scope['$saveFailed']?.value){
|
||||||
if (!targets.length) log.content.push({value: '**On failed save**'});
|
if (!targets.length) actionContext.addLog({value: '**On failed save**'});
|
||||||
applyChildren();
|
applyChildren();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'successfulSave':
|
case 'successfulSave':
|
||||||
if (scope['$saveSucceeded']?.value){
|
if (scope['$saveSucceeded']?.value){
|
||||||
if (!targets.length) log.content.push({value: '**On save**',});
|
if (!targets.length) actionContext.addLog({value: '**On save**',});
|
||||||
applyChildren();
|
applyChildren();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'random':
|
case 'random':
|
||||||
if (node.children.length){
|
if (node.children.length){
|
||||||
let index = rollDice(1, node.children.length)[0] - 1;
|
let index = rollDice(1, node.children.length)[0] - 1;
|
||||||
applyProperty(node.children[index], {
|
applyNodeTriggers(node, 'after', actionContext);
|
||||||
creature, targets, scope, log
|
applyProperty(node.children[index], actionContext);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'eachTarget':
|
case 'eachTarget':
|
||||||
if (targets.length){
|
if (targets.length) {
|
||||||
targets.forEach(target => {
|
targets.forEach(target => {
|
||||||
node.children.forEach(child => applyProperty(child, {
|
applyNodeTriggers(node, 'after', actionContext);
|
||||||
creature, targets: [target], scope, log
|
actionContext.targets = [target]
|
||||||
}));
|
node.children.forEach(child => applyProperty(child, actionContext));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
applyChildren();
|
applyChildren();
|
||||||
|
|||||||
@@ -12,10 +12,12 @@ import symbol from '/imports/parser/parseTree/symbol.js';
|
|||||||
import logErrors from './shared/logErrors.js';
|
import logErrors from './shared/logErrors.js';
|
||||||
import { insertCreatureLog } from '/imports/api/creature/log/CreatureLogs.js';
|
import { insertCreatureLog } from '/imports/api/creature/log/CreatureLogs.js';
|
||||||
import cyrb53 from '/imports/api/engine/computation/utility/cyrb53.js';
|
import cyrb53 from '/imports/api/engine/computation/utility/cyrb53.js';
|
||||||
|
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
|
|
||||||
export default function applyBuff(node, {creature, targets, scope, log}){
|
export default function applyBuff(node, actionContext){
|
||||||
|
applyNodeTriggers(node, 'before', actionContext);
|
||||||
const prop = node.node;
|
const prop = node.node;
|
||||||
let buffTargets = prop.target === 'self' ? [creature] : targets;
|
let buffTargets = prop.target === 'self' ? [actionContext.creature] : actionContext.targets;
|
||||||
|
|
||||||
// Then copy the decendants of the buff to the targets
|
// Then copy the decendants of the buff to the targets
|
||||||
let propList = [prop];
|
let propList = [prop];
|
||||||
@@ -26,7 +28,7 @@ export default function applyBuff(node, {creature, targets, scope, log}){
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
addChildrenToPropList(node.children);
|
addChildrenToPropList(node.children);
|
||||||
crystalizeVariables({propList, scope, log});
|
crystalizeVariables({propList, actionContext});
|
||||||
|
|
||||||
let oldParent = {
|
let oldParent = {
|
||||||
id: prop.parent.id,
|
id: prop.parent.id,
|
||||||
@@ -38,9 +40,9 @@ export default function applyBuff(node, {creature, targets, scope, log}){
|
|||||||
|
|
||||||
//Log the buff
|
//Log the buff
|
||||||
if (prop.name || prop.description?.value){
|
if (prop.name || prop.description?.value){
|
||||||
if (target._id === creature._id){
|
if (target._id === actionContext.creature._id){
|
||||||
// Targeting self
|
// Targeting self
|
||||||
log.content.push({
|
actionContext.addLog({
|
||||||
name: prop.name,
|
name: prop.name,
|
||||||
value: prop.description?.value,
|
value: prop.description?.value,
|
||||||
});
|
});
|
||||||
@@ -58,6 +60,7 @@ export default function applyBuff(node, {creature, targets, scope, log}){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
applyNodeTriggers(node, 'after', actionContext);
|
||||||
|
|
||||||
// Don't apply the children of the buff, they get copied to the target instead
|
// Don't apply the children of the buff, they get copied to the target instead
|
||||||
}
|
}
|
||||||
@@ -83,7 +86,7 @@ function copyNodeListToTarget(propList, target, oldParent){
|
|||||||
* Replaces all variables with their resolved values
|
* Replaces all variables with their resolved values
|
||||||
* except variables of the form `$target.thing.total` become `thing.total`
|
* except variables of the form `$target.thing.total` become `thing.total`
|
||||||
*/
|
*/
|
||||||
function crystalizeVariables({propList, scope, log}){
|
function crystalizeVariables({propList, actionContext}){
|
||||||
propList.forEach(prop => {
|
propList.forEach(prop => {
|
||||||
computedSchemas[prop.type].computedFields().forEach( calcKey => {
|
computedSchemas[prop.type].computedFields().forEach( calcKey => {
|
||||||
applyFnToKey(prop, calcKey, (prop, key) => {
|
applyFnToKey(prop, calcKey, (prop, key) => {
|
||||||
@@ -104,7 +107,7 @@ function crystalizeVariables({propList, scope, log}){
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Can't strip symbols
|
// Can't strip symbols
|
||||||
log.content.push({
|
actionContext.addLog({
|
||||||
name: 'Error',
|
name: 'Error',
|
||||||
value: 'Variable `$target` should not be used without a property: $target.property',
|
value: 'Variable `$target` should not be used without a property: $target.property',
|
||||||
});
|
});
|
||||||
@@ -112,8 +115,8 @@ function crystalizeVariables({propList, scope, log}){
|
|||||||
return node;
|
return node;
|
||||||
} else {
|
} else {
|
||||||
// Resolve all other variables
|
// Resolve all other variables
|
||||||
const {result, context} = resolve('reduce', node, scope);
|
const {result, context} = resolve('reduce', node, actionContext.scope);
|
||||||
logErrors(context.errors, log);
|
logErrors(context.errors, actionContext);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,27 +1,30 @@
|
|||||||
import { some, intersection, difference } from 'lodash';
|
import { some, intersection, difference, remove } from 'lodash';
|
||||||
import applyProperty from '../applyProperty.js';
|
import applyProperty from '../applyProperty.js';
|
||||||
import { dealDamageWork } 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 resolve, { Context, toString } from '/imports/parser/resolve.js';
|
import resolve, { Context, toString } from '/imports/parser/resolve.js';
|
||||||
import logErrors from './shared/logErrors.js';
|
import logErrors from './shared/logErrors.js';
|
||||||
import applyEffectsToCalculationParseNode from '/imports/api/engine/actions/applyPropertyByType/shared/applyEffectsToCalculationParseNode.js';
|
import applyEffectsToCalculationParseNode from '/imports/api/engine/actions/applyPropertyByType/shared/applyEffectsToCalculationParseNode.js';
|
||||||
|
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||||
|
import {
|
||||||
|
getPropertiesOfType
|
||||||
|
} from '/imports/api/engine/loadCreatures.js';
|
||||||
|
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
|
|
||||||
export default function applyDamage(node, {
|
export default function applyDamage(node, actionContext){
|
||||||
creature, targets, scope, log
|
applyNodeTriggers(node, 'before', actionContext);
|
||||||
}){
|
|
||||||
const applyChildren = function(){
|
const applyChildren = function(){
|
||||||
node.children.forEach(child => applyProperty(child, {
|
applyNodeTriggers(node, 'after', actionContext);
|
||||||
creature, targets, scope, log
|
node.children.forEach(child => applyProperty(child, actionContext));
|
||||||
}));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const prop = node.node;
|
const prop = node.node;
|
||||||
|
const scope = actionContext.scope;
|
||||||
|
|
||||||
// Skip if there is no parse node to work with
|
// Skip if there is no parse node to work with
|
||||||
if (!prop.amount?.parseNode) return;
|
if (!prop.amount?.parseNode) return;
|
||||||
|
|
||||||
// Choose target
|
// Choose target
|
||||||
let damageTargets = prop.target === 'self' ? [creature] : targets;
|
let damageTargets = prop.target === 'self' ? [actionContext.creature] : actionContext.targets;
|
||||||
// Determine if the hit is critical
|
// Determine if the hit is critical
|
||||||
let criticalHit = scope['$criticalHit']?.value &&
|
let criticalHit = scope['$criticalHit']?.value &&
|
||||||
prop.damageType !== 'healing' // Can't critically heal
|
prop.damageType !== 'healing' // Can't critically heal
|
||||||
@@ -36,19 +39,19 @@ export default function applyDamage(node, {
|
|||||||
const logName = prop.damageType === 'healing' ? 'Healing' : 'Damage';
|
const logName = prop.damageType === 'healing' ? 'Healing' : 'Damage';
|
||||||
|
|
||||||
// roll the dice only and store that string
|
// roll the dice only and store that string
|
||||||
applyEffectsToCalculationParseNode(prop.amount, log);
|
applyEffectsToCalculationParseNode(prop.amount, actionContext.log);
|
||||||
const {result: rolled} = resolve('roll', prop.amount.parseNode, scope, context);
|
const {result: rolled} = resolve('roll', prop.amount.parseNode, scope, context);
|
||||||
if (rolled.parseType !== 'constant'){
|
if (rolled.parseType !== 'constant'){
|
||||||
logValue.push(toString(rolled));
|
logValue.push(toString(rolled));
|
||||||
}
|
}
|
||||||
logErrors(context.errors, log);
|
logErrors(context.errors, actionContext);
|
||||||
|
|
||||||
// Reset the errors so we don't log the same errors twice
|
// Reset the errors so we don't log the same errors twice
|
||||||
context.errors = [];
|
context.errors = [];
|
||||||
|
|
||||||
// Resolve the roll to a final value
|
// Resolve the roll to a final value
|
||||||
const {result: reduced} = resolve('reduce', rolled, scope, context);
|
const {result: reduced} = resolve('reduce', rolled, scope, context);
|
||||||
logErrors(context.errors, log);
|
logErrors(context.errors, actionContext);
|
||||||
|
|
||||||
// Store the result
|
// Store the result
|
||||||
if (reduced.parseType === 'constant'){
|
if (reduced.parseType === 'constant'){
|
||||||
@@ -94,15 +97,17 @@ export default function applyDamage(node, {
|
|||||||
logValue
|
logValue
|
||||||
});
|
});
|
||||||
|
|
||||||
|
actionContext.target = [target];
|
||||||
// Deal the damage to the target
|
// Deal the damage to the target
|
||||||
let damageDealt = dealDamageWork({
|
let damageDealt = dealDamage({
|
||||||
creature: target,
|
target,
|
||||||
damageType: prop.damageType,
|
damageType: prop.damageType,
|
||||||
amount: damage,
|
amount: damage,
|
||||||
|
actionContext
|
||||||
});
|
});
|
||||||
|
|
||||||
// Log the damage done
|
// Log the damage done
|
||||||
if (target._id === creature._id){
|
if (target._id === actionContext.creature._id){
|
||||||
// Target is same as self, log damage as such
|
// Target is same as self, log damage as such
|
||||||
logValue.push(`**${damageDealt}** ${suffix} to self`);
|
logValue.push(`**${damageDealt}** ${suffix} to self`);
|
||||||
} else {
|
} else {
|
||||||
@@ -123,7 +128,7 @@ export default function applyDamage(node, {
|
|||||||
// There are no targets, just log the result
|
// There are no targets, just log the result
|
||||||
logValue.push(`**${damage}** ${suffix}`);
|
logValue.push(`**${damage}** ${suffix}`);
|
||||||
}
|
}
|
||||||
log.content.push({
|
actionContext.addLog({
|
||||||
name: logName,
|
name: logName,
|
||||||
value: logValue.join('\n'),
|
value: logValue.join('\n'),
|
||||||
inline: true,
|
inline: true,
|
||||||
@@ -178,3 +183,49 @@ function multiplierAppliesTo(damageProp){
|
|||||||
return hasRequiredTags && hasNoExcludedTags;
|
return hasRequiredTags && hasNoExcludedTags;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function dealDamage({target, damageType, amount, actionContext}){
|
||||||
|
// Get all the health bars and do damage to them
|
||||||
|
let healthBars = getPropertiesOfType(target._id, 'attribute');
|
||||||
|
|
||||||
|
// Keep only the healthbars that can take damage/healing
|
||||||
|
remove(healthBars, (bar) =>
|
||||||
|
bar.attributeType !== 'healthBar' ||
|
||||||
|
bar.inactive ||
|
||||||
|
bar.removed ||
|
||||||
|
bar.overridden ||
|
||||||
|
(amount >= 0 && bar.healthBarNoDamage) ||
|
||||||
|
(amount < 0 && bar.healthBarNoHealing)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sort healthbars by damage/healing order or tree order as a fallback
|
||||||
|
healthBars.sort((a, b) => {
|
||||||
|
let diff;
|
||||||
|
if (amount >= 0) {
|
||||||
|
diff = a.healthBarDamageOrder - b.healthBarDamageOrder;
|
||||||
|
} else {
|
||||||
|
diff = a.healthBarHealingOrder - b.healthBarHealingOrder;
|
||||||
|
}
|
||||||
|
if (Number.isFinite(diff)) {
|
||||||
|
return diff;
|
||||||
|
} else {
|
||||||
|
return a.order - b.order;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Deal the damage to each healthbar in order until all damage is done
|
||||||
|
const totalDamage = amount;
|
||||||
|
let damageLeft = totalDamage;
|
||||||
|
if (damageType === 'healing') damageLeft = -totalDamage;
|
||||||
|
healthBars.forEach(healthBar => {
|
||||||
|
if (damageLeft === 0) return;
|
||||||
|
let damageAdded = damagePropertyWork({
|
||||||
|
prop: healthBar,
|
||||||
|
operation: 'increment',
|
||||||
|
value: damageLeft,
|
||||||
|
actionContext
|
||||||
|
});
|
||||||
|
damageLeft -= damageAdded;
|
||||||
|
});
|
||||||
|
return totalDamage;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,25 +1,27 @@
|
|||||||
import recalculateInlineCalculations from './shared/recalculateInlineCalculations.js';
|
import recalculateInlineCalculations from './shared/recalculateInlineCalculations.js';
|
||||||
import applyProperty from '../applyProperty.js';
|
import applyProperty from '../applyProperty.js';
|
||||||
|
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
|
|
||||||
export default function applyNote(node, {creature, targets, scope, log}){
|
export default function applyNote(node, actionContext){
|
||||||
|
applyNodeTriggers(node, 'before', actionContext);
|
||||||
const prop = node.node;
|
const prop = node.node;
|
||||||
|
|
||||||
// Log Name, summary
|
// Log Name, summary
|
||||||
let content = { name: prop.name };
|
let content = { name: prop.name };
|
||||||
if (prop.summary?.text){
|
if (prop.summary?.text){
|
||||||
recalculateInlineCalculations(prop.summary, scope, log);
|
recalculateInlineCalculations(prop.summary, actionContext);
|
||||||
content.value = prop.summary.value;
|
content.value = prop.summary.value;
|
||||||
}
|
}
|
||||||
if (content.name || content.value){
|
if (content.name || content.value){
|
||||||
log.content.push(content);
|
actionContext.addLog(content);
|
||||||
}
|
}
|
||||||
// Log description
|
// Log description
|
||||||
if (prop.description?.text){
|
if (prop.description?.text){
|
||||||
recalculateInlineCalculations(prop.description, scope, log);
|
recalculateInlineCalculations(prop.description, actionContext);
|
||||||
log.content.push({value: prop.description.value});
|
actionContext.addLog({value: prop.description.value});
|
||||||
}
|
}
|
||||||
|
// Apply triggers
|
||||||
|
applyNodeTriggers(node, 'after', actionContext);
|
||||||
// Apply children
|
// Apply children
|
||||||
node.children.forEach(child => applyProperty(child, {
|
node.children.forEach(child => applyProperty(child, actionContext));
|
||||||
creature, targets, scope, log
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,33 +2,34 @@ import applyProperty from '../applyProperty.js';
|
|||||||
import logErrors from './shared/logErrors.js';
|
import logErrors from './shared/logErrors.js';
|
||||||
import applyEffectsToCalculationParseNode from '/imports/api/engine/actions/applyPropertyByType/shared/applyEffectsToCalculationParseNode.js';
|
import applyEffectsToCalculationParseNode from '/imports/api/engine/actions/applyPropertyByType/shared/applyEffectsToCalculationParseNode.js';
|
||||||
import resolve, { toString } from '/imports/parser/resolve.js';
|
import resolve, { toString } from '/imports/parser/resolve.js';
|
||||||
|
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
|
|
||||||
export default function applyRoll(node, {creature, targets, scope, log}){
|
export default function applyRoll(node, actionContext){
|
||||||
|
applyNodeTriggers(node, 'before', actionContext);
|
||||||
const prop = node.node;
|
const prop = node.node;
|
||||||
|
|
||||||
const applyChildren = function(){
|
const applyChildren = function(){
|
||||||
node.children.forEach(child => applyProperty(child, {
|
applyNodeTriggers(node, 'after', actionContext);
|
||||||
creature, targets, scope, log
|
node.children.forEach(child => applyProperty(child, actionContext));
|
||||||
}));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (prop.roll?.calculation){
|
if (prop.roll?.calculation){
|
||||||
const logValue = [];
|
const logValue = [];
|
||||||
|
|
||||||
// roll the dice only and store that string
|
// roll the dice only and store that string
|
||||||
applyEffectsToCalculationParseNode(prop.roll, log);
|
applyEffectsToCalculationParseNode(prop.roll, actionContext);
|
||||||
const {result: rolled, context} = resolve('roll', prop.roll.parseNode, scope);
|
const {result: rolled, context} = resolve('roll', prop.roll.parseNode, actionContext.scope);
|
||||||
if (rolled.parseType !== 'constant'){
|
if (rolled.parseType !== 'constant'){
|
||||||
logValue.push(toString(rolled));
|
logValue.push(toString(rolled));
|
||||||
}
|
}
|
||||||
logErrors(context.errors, log);
|
logErrors(context.errors, actionContext);
|
||||||
|
|
||||||
// Reset the errors so we don't log the same errors twice
|
// Reset the errors so we don't log the same errors twice
|
||||||
context.errors = [];
|
context.errors = [];
|
||||||
|
|
||||||
// Resolve the roll to a final value
|
// Resolve the roll to a final value
|
||||||
const {result: reduced} = resolve('reduce', rolled, scope, context);
|
const {result: reduced} = resolve('reduce', rolled, actionContext.scope, context);
|
||||||
logErrors(context.errors, log);
|
logErrors(context.errors, actionContext);
|
||||||
|
|
||||||
// Store the result
|
// Store the result
|
||||||
if (reduced.parseType === 'constant'){
|
if (reduced.parseType === 'constant'){
|
||||||
@@ -45,11 +46,11 @@ export default function applyRoll(node, {creature, targets, scope, log}){
|
|||||||
}
|
}
|
||||||
const value = reduced.value;
|
const value = reduced.value;
|
||||||
|
|
||||||
scope[prop.variableName] = value;
|
actionContext.scope[prop.variableName] = value;
|
||||||
logValue.push(`**${value}**`);
|
logValue.push(`**${value}**`);
|
||||||
|
|
||||||
if (!prop.silent){
|
if (!prop.silent){
|
||||||
log.content.push({
|
actionContext.addLog({
|
||||||
name: prop.name,
|
name: prop.name,
|
||||||
value: logValue.join('\n'),
|
value: logValue.join('\n'),
|
||||||
inline: true,
|
inline: true,
|
||||||
|
|||||||
@@ -2,38 +2,38 @@ import rollDice from '/imports/parser/rollDice.js';
|
|||||||
import recalculateCalculation from './shared/recalculateCalculation.js';
|
import recalculateCalculation from './shared/recalculateCalculation.js';
|
||||||
import applyProperty from '../applyProperty.js';
|
import applyProperty from '../applyProperty.js';
|
||||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||||
|
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
|
|
||||||
export default function applySavingThrow(node, {creature, targets, scope, log}){
|
export default function applySavingThrow(node, actionContext){
|
||||||
|
applyNodeTriggers(node, 'before', actionContext);
|
||||||
const prop = node.node;
|
const prop = node.node;
|
||||||
|
|
||||||
let saveTargets = prop.target === 'self' ? [creature] : targets;
|
let saveTargets = prop.target === 'self' ? [actionContext.creature] : actionContext.targets;
|
||||||
|
|
||||||
recalculateCalculation(prop.dc, scope, log);
|
recalculateCalculation(prop.dc, actionContext);
|
||||||
|
|
||||||
const dc = (prop.dc?.value);
|
const dc = (prop.dc?.value);
|
||||||
if (!isFinite(dc)){
|
if (!isFinite(dc)){
|
||||||
log.content.push({
|
actionContext.addLog({
|
||||||
name: 'Error',
|
name: 'Error',
|
||||||
value: 'Saving throw requires a DC',
|
value: 'Saving throw requires a DC',
|
||||||
});
|
});
|
||||||
return node.children.forEach(child => applyProperty(child, {
|
return node.children.forEach(child => applyProperty(child, actionContext));
|
||||||
creature, targets, scope, log
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
log.content.push({
|
actionContext.addLog({
|
||||||
name: prop.name,
|
name: prop.name,
|
||||||
value: `DC **${dc}**`,
|
value: `DC **${dc}**`,
|
||||||
inline: true,
|
inline: true,
|
||||||
});
|
});
|
||||||
|
const scope = actionContext.scope;
|
||||||
|
|
||||||
// If there are no save targets, apply all children as if the save both
|
// If there are no save targets, apply all children as if the save both
|
||||||
// succeeeded and failed
|
// succeeeded and failed
|
||||||
if (!saveTargets?.length){
|
if (!saveTargets?.length){
|
||||||
scope['$saveFailed'] = {value: true};
|
scope['$saveFailed'] = {value: true};
|
||||||
scope['$saveSucceeded'] = {value: true};
|
scope['$saveSucceeded'] = { value: true };
|
||||||
return node.children.forEach(child => applyProperty(child, {
|
applyNodeTriggers(node, 'after', actionContext);
|
||||||
creature, targets, scope, log
|
return node.children.forEach(child => applyProperty(child, actionContext));
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Each target makes the saving throw
|
// Each target makes the saving throw
|
||||||
@@ -43,16 +43,16 @@ export default function applySavingThrow(node, {creature, targets, scope, log}){
|
|||||||
delete scope['$saveDiceRoll'];
|
delete scope['$saveDiceRoll'];
|
||||||
delete scope['$saveRoll'];
|
delete scope['$saveRoll'];
|
||||||
|
|
||||||
const applyChildren = function(){
|
const applyChildren = function () {
|
||||||
node.children.forEach(child => applyProperty(child, {
|
applyNodeTriggers(node, 'after', actionContext);
|
||||||
creature, targets: [target], scope, log
|
actionContext.targets = [target]
|
||||||
}));
|
node.children.forEach(child => applyProperty(child, actionContext));
|
||||||
};
|
};
|
||||||
|
|
||||||
const save = target.variables[prop.stat];
|
const save = target.variables[prop.stat];
|
||||||
|
|
||||||
if (!save){
|
if (!save){
|
||||||
log.content.push({
|
actionContext.addLog({
|
||||||
name: 'Saving throw error',
|
name: 'Saving throw error',
|
||||||
value: 'No saving throw found: ' + prop.stat,
|
value: 'No saving throw found: ' + prop.stat,
|
||||||
});
|
});
|
||||||
@@ -94,7 +94,7 @@ export default function applySavingThrow(node, {creature, targets, scope, log}){
|
|||||||
} else {
|
} else {
|
||||||
scope['$saveFailed'] = {value: true};
|
scope['$saveFailed'] = {value: true};
|
||||||
}
|
}
|
||||||
log.content.push({
|
actionContext.addLog({
|
||||||
name: saveSuccess ? 'Successful save' : 'Failed save',
|
name: saveSuccess ? 'Successful save' : 'Failed save',
|
||||||
value: resultPrefix + '\n**' + result + '**',
|
value: resultPrefix + '\n**' + result + '**',
|
||||||
inline: true,
|
inline: true,
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import applyProperty from '../applyProperty.js';
|
import applyProperty from '../applyProperty.js';
|
||||||
import recalculateCalculation from './shared/recalculateCalculation.js';
|
import recalculateCalculation from './shared/recalculateCalculation.js';
|
||||||
|
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
|
|
||||||
export default function applyToggle(node, {
|
export default function applyToggle(node, actionContext){
|
||||||
creature, targets, scope, log
|
applyNodeTriggers(node, 'before', actionContext);
|
||||||
}){
|
|
||||||
const prop = node.node;
|
const prop = node.node;
|
||||||
recalculateCalculation(prop.condition, scope, log);
|
recalculateCalculation(prop.condition, actionContext);
|
||||||
if (prop.condition?.value) {
|
if (prop.condition?.value) {
|
||||||
return node.children.forEach(child => applyProperty(child, {
|
applyNodeTriggers(node, 'after', actionContext);
|
||||||
creature, targets, scope, log
|
return node.children.forEach(child => applyProperty(child, actionContext));
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import operator from '/imports/parser/parseTree/operator.js';
|
|||||||
import { parse } from '/imports/parser/parser.js';
|
import { parse } from '/imports/parser/parser.js';
|
||||||
import logErrors from './logErrors.js';
|
import logErrors from './logErrors.js';
|
||||||
|
|
||||||
export default function applyEffectsToCalculationParseNode(calcObj, log){
|
export default function applyEffectsToCalculationParseNode(calcObj, actionContext){
|
||||||
if (!calcObj.effects) return;
|
if (!calcObj.effects) return;
|
||||||
calcObj.effects.forEach(effect => {
|
calcObj.effects.forEach(effect => {
|
||||||
if (effect.operation !== 'add') return;
|
if (effect.operation !== 'add') return;
|
||||||
@@ -18,7 +18,7 @@ export default function applyEffectsToCalculationParseNode(calcObj, log){
|
|||||||
fn: 'add'
|
fn: 'add'
|
||||||
});
|
});
|
||||||
} catch (e){
|
} catch (e){
|
||||||
logErrors([e], log)
|
logErrors([e], actionContext)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default function logErrors(errors, log){
|
export default function logErrors(errors, actionContext){
|
||||||
errors?.forEach(error => {
|
errors?.forEach(error => {
|
||||||
if (error.type !== 'info'){
|
if (error.type !== 'info'){
|
||||||
log.content.push({name: 'Error', value: error.message});
|
actionContext.addLog({name: 'Error', value: error.message});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import evaluateCalculation from '/imports/api/engine/computation/utility/evaluat
|
|||||||
import applyEffectsToCalculationParseNode from '/imports/api/engine/actions/applyPropertyByType/shared/applyEffectsToCalculationParseNode.js';
|
import applyEffectsToCalculationParseNode from '/imports/api/engine/actions/applyPropertyByType/shared/applyEffectsToCalculationParseNode.js';
|
||||||
import logErrors from './logErrors.js';
|
import logErrors from './logErrors.js';
|
||||||
|
|
||||||
export default function recalculateCalculation(calc, scope, log, context){
|
export default function recalculateCalculation(calc, actionContext, context){
|
||||||
if (!calc?.parseNode) return;
|
if (!calc?.parseNode) return;
|
||||||
calc._parseLevel = 'reduce';
|
calc._parseLevel = 'reduce';
|
||||||
applyEffectsToCalculationParseNode(calc, log);
|
applyEffectsToCalculationParseNode(calc, actionContext.log);
|
||||||
evaluateCalculation(calc, scope, context);
|
evaluateCalculation(calc, actionContext.scope, context);
|
||||||
logErrors(calc.errors, log);
|
logErrors(calc.errors, actionContext.log);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import embedInlineCalculations from '/imports/api/engine/computation/utility/embedInlineCalculations.js';
|
import embedInlineCalculations from '/imports/api/engine/computation/utility/embedInlineCalculations.js';
|
||||||
import recalculateCalculation from './recalculateCalculation.js'
|
import recalculateCalculation from './recalculateCalculation.js'
|
||||||
|
|
||||||
export default function recalculateInlineCalculations(inlineCalcObj, scope, log){
|
export default function recalculateInlineCalculations(inlineCalcObj, actionContext){
|
||||||
// Skip if there are no calculations
|
// Skip if there are no calculations
|
||||||
if (!inlineCalcObj?.inlineCalculations?.length) return;
|
if (!inlineCalcObj?.inlineCalculations?.length) return;
|
||||||
// Recalculate each calculation with the current scope
|
// Recalculate each calculation with the current scope
|
||||||
inlineCalcObj.inlineCalculations.forEach(calc => {
|
inlineCalcObj.inlineCalculations.forEach(calc => {
|
||||||
recalculateCalculation(calc, scope, log);
|
recalculateCalculation(calc, actionContext);
|
||||||
});
|
});
|
||||||
// Embed the new calculated values
|
// Embed the new calculated values
|
||||||
embedInlineCalculations(inlineCalcObj);
|
embedInlineCalculations(inlineCalcObj);
|
||||||
|
|||||||
@@ -6,18 +6,78 @@ import applyProperty from '/imports/api/engine/actions/applyProperty.js';
|
|||||||
import { difference, intersection } from 'lodash';
|
import { difference, intersection } from 'lodash';
|
||||||
import getEffectivePropTags from '/imports/api/engine/computation/utility/getEffectivePropTags.js';
|
import getEffectivePropTags from '/imports/api/engine/computation/utility/getEffectivePropTags.js';
|
||||||
|
|
||||||
export default function applyTriggers(node, { creature, targets, scope, log }, timing) {
|
export function applyNodeTriggers(node, timing, actionContext) {
|
||||||
const prop = node.node;
|
const prop = node.node;
|
||||||
const type = prop.type;
|
const type = prop.type;
|
||||||
if (creature.triggers?.[type]?.[timing]) {
|
const triggers = actionContext.triggers?.doActionProperty?.[type]?.[timing];
|
||||||
creature.triggers[type][timing].forEach(trigger => {
|
if (triggers) {
|
||||||
if (triggerMatchTags(trigger, prop)) {
|
triggers.forEach(trigger => {
|
||||||
applyTrigger(trigger, { creature, targets, scope, log });
|
applyTrigger(trigger, prop, actionContext);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function applyTriggers(triggers = [], prop, actionContext) {
|
||||||
|
// Apply the triggers
|
||||||
|
triggers.forEach(trigger => {
|
||||||
|
applyTrigger(trigger, prop, actionContext)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyTrigger(trigger, prop, actionContext) {
|
||||||
|
// If there is a prop we are applying the trigger from,
|
||||||
|
// don't fire if the tags don't match
|
||||||
|
if (prop && !triggerMatchTags(trigger, prop)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent trigger from firing if it's inactive
|
||||||
|
if (trigger.inactive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent triggers from firing if their condition is false
|
||||||
|
if (trigger.condition?.parseNode) {
|
||||||
|
recalculateCalculation(trigger.condition, actionContext);
|
||||||
|
if (!trigger.condition.value) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent triggers from firing themselves in a loop
|
||||||
|
if (trigger.firing) {
|
||||||
|
/*
|
||||||
|
log.content.push({
|
||||||
|
name: trigger.name || 'Trigger',
|
||||||
|
value: 'Trigger can\'t fire itself',
|
||||||
|
inline: true,
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
trigger.firing = true;
|
||||||
|
|
||||||
|
// Fire the trigger
|
||||||
|
const content = {
|
||||||
|
name: trigger.name || 'Trigger',
|
||||||
|
value: trigger.description,
|
||||||
|
inline: false,
|
||||||
|
}
|
||||||
|
if (trigger.description?.text){
|
||||||
|
recalculateInlineCalculations(trigger.description, actionContext);
|
||||||
|
content.value = trigger.description.value;
|
||||||
|
}
|
||||||
|
actionContext.addLog(content);
|
||||||
|
|
||||||
|
// Get all the trigger's properties and apply them
|
||||||
|
const properties = getPropertyDecendants(actionContext.creature._id, trigger._id);
|
||||||
|
properties.sort((a, b) => a.order - b.order);
|
||||||
|
const propertyForest = nodeArrayToTree(properties);
|
||||||
|
propertyForest.forEach(node => {
|
||||||
|
applyProperty(node, actionContext);
|
||||||
|
});
|
||||||
|
|
||||||
|
trigger.firing = false;
|
||||||
|
}
|
||||||
|
|
||||||
function triggerMatchTags(trigger, prop) {
|
function triggerMatchTags(trigger, prop) {
|
||||||
let matched = false;
|
let matched = false;
|
||||||
const propTags = getEffectivePropTags(prop);
|
const propTags = getEffectivePropTags(prop);
|
||||||
@@ -49,51 +109,3 @@ function triggerMatchTags(trigger, prop) {
|
|||||||
});
|
});
|
||||||
return matched;
|
return matched;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyTrigger(trigger, { creature, targets, scope, log }) {
|
|
||||||
// Prevent triggers from firing if their condition is false
|
|
||||||
if (trigger.condition?.parseNode) {
|
|
||||||
recalculateCalculation(trigger.condition, scope, log);
|
|
||||||
if (!trigger.condition.value) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent triggers from firing themselves in a loop
|
|
||||||
if (trigger.firing) {
|
|
||||||
/*
|
|
||||||
log.content.push({
|
|
||||||
name: trigger.name || 'Trigger',
|
|
||||||
value: 'Trigger can\'t fire itself',
|
|
||||||
inline: true,
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
trigger.firing = true;
|
|
||||||
|
|
||||||
// Fire the trigger
|
|
||||||
const content = {
|
|
||||||
name: trigger.name || 'Trigger',
|
|
||||||
value: trigger.summary,
|
|
||||||
inline: false,
|
|
||||||
}
|
|
||||||
if (trigger.summary?.text){
|
|
||||||
recalculateInlineCalculations(trigger.summary, scope, log);
|
|
||||||
content.value = trigger.summary.value;
|
|
||||||
}
|
|
||||||
log.content.push(content);
|
|
||||||
|
|
||||||
// Get all the trigger's properties and apply them
|
|
||||||
const properties = getPropertyDecendants(creature._id, trigger._id);
|
|
||||||
properties.sort((a, b) => a.order - b.order);
|
|
||||||
const propertyForest = nodeArrayToTree(properties);
|
|
||||||
propertyForest.forEach(node => {
|
|
||||||
applyProperty(node, {
|
|
||||||
creature,
|
|
||||||
targets,
|
|
||||||
scope,
|
|
||||||
log,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
trigger.firing = false;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||||
import { CreatureLogSchema, insertCreatureLogWork } from '/imports/api/creature/log/CreatureLogs.js';
|
|
||||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.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 {
|
import {
|
||||||
getCreature, getVariables, getProperyAncestors, getPropertyDecendants, getPropertiesOfType
|
getProperyAncestors, getPropertyDecendants
|
||||||
} from '/imports/api/engine/loadCreatures.js';
|
} from '/imports/api/engine/loadCreatures.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 applyProperty from './applyProperty.js';
|
import applyProperty from './applyProperty.js';
|
||||||
import { groupBy, remove } from 'lodash';
|
import ActionContext from '/imports/api/engine/actions/ActionContext.js';
|
||||||
|
|
||||||
const doAction = new ValidatedMethod({
|
const doAction = new ValidatedMethod({
|
||||||
name: 'creatureProperties.doAction',
|
name: 'creatureProperties.doAction',
|
||||||
@@ -37,32 +36,16 @@ const doAction = new ValidatedMethod({
|
|||||||
numRequests: 10,
|
numRequests: 10,
|
||||||
timeInterval: 5000,
|
timeInterval: 5000,
|
||||||
},
|
},
|
||||||
run({actionId, targetIds = [], scope}) {
|
run({ actionId, targetIds = [], scope }) {
|
||||||
|
// Get action context
|
||||||
let action = CreatureProperties.findOne(actionId);
|
let action = CreatureProperties.findOne(actionId);
|
||||||
// Check permissions
|
|
||||||
const creatureId = action.ancestors[0].id;
|
const creatureId = action.ancestors[0].id;
|
||||||
let creature = getCreature(action.ancestors[0].id);
|
const actionContext = new ActionContext(creatureId, targetIds, this);
|
||||||
assertEditPermission(creature, this.userId);
|
|
||||||
|
// Check permissions
|
||||||
// Add the variables to the creature document
|
assertEditPermission(actionContext.creature, this.userId);
|
||||||
const variables = getVariables(creatureId);
|
actionContext.targets.forEach(target => {
|
||||||
delete variables._id;
|
|
||||||
delete variables._creatureId;
|
|
||||||
creature.variables = variables;
|
|
||||||
|
|
||||||
// Get all the targets and make sure we can edit them
|
|
||||||
let targets = [];
|
|
||||||
targetIds.forEach(targetId => {
|
|
||||||
let target = getCreature(targetId);
|
|
||||||
assertEditPermission(target, this.userId);
|
assertEditPermission(target, this.userId);
|
||||||
|
|
||||||
// add the variables to the target documents
|
|
||||||
const variables = getVariables(creatureId);
|
|
||||||
delete variables._id;
|
|
||||||
delete variables._creatureId;
|
|
||||||
target.variables = variables;
|
|
||||||
|
|
||||||
targets.push(target);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const ancestors = getProperyAncestors(creatureId, action._id);
|
const ancestors = getProperyAncestors(creatureId, action._id);
|
||||||
@@ -73,11 +56,11 @@ const doAction = new ValidatedMethod({
|
|||||||
properties.sort((a, b) => a.order - b.order);
|
properties.sort((a, b) => a.order - b.order);
|
||||||
|
|
||||||
// Do the action
|
// Do the action
|
||||||
doActionWork({creature, targets, properties, ancestors, method: this, methodScope: scope});
|
doActionWork({properties, ancestors, actionContext, methodScope: scope});
|
||||||
|
|
||||||
// Recompute all involved creatures
|
// Recompute all involved creatures
|
||||||
Creatures.update({
|
Creatures.update({
|
||||||
_id: { $in: [creature._id, ...targetIds] }
|
_id: { $in: [creatureId, ...targetIds] }
|
||||||
}, {
|
}, {
|
||||||
$set: {dirty: true},
|
$set: {dirty: true},
|
||||||
});
|
});
|
||||||
@@ -87,7 +70,7 @@ const doAction = new ValidatedMethod({
|
|||||||
export default doAction;
|
export default doAction;
|
||||||
|
|
||||||
export function doActionWork({
|
export function doActionWork({
|
||||||
creature, targets, properties, ancestors, method, methodScope = {}, log
|
properties, ancestors, actionContext, methodScope = {},
|
||||||
}){
|
}){
|
||||||
// get the docs
|
// get the docs
|
||||||
const ancestorScope = getAncestorScope(ancestors);
|
const ancestorScope = getAncestorScope(ancestors);
|
||||||
@@ -96,36 +79,15 @@ export function doActionWork({
|
|||||||
throw new Meteor.Error(`The action has ${propertyForest.length} top level properties, expected 1`);
|
throw new Meteor.Error(`The action has ${propertyForest.length} top level properties, expected 1`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the triggers
|
// Include the ancestry and method scope in the context scope
|
||||||
const triggers = getPropertiesOfType(creature._id, 'trigger');
|
Object.assign(actionContext.scope, ancestorScope, methodScope);
|
||||||
remove(triggers, trigger => trigger.event !== 'doActionProperty');
|
|
||||||
creature.triggers = groupBy(triggers, 'actionPropertyType');
|
|
||||||
for (let type in creature.triggers) {
|
|
||||||
creature.triggers[type] = groupBy(creature.triggers[type], 'timing')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the log
|
|
||||||
if (!log) log = CreatureLogSchema.clean({
|
|
||||||
creatureId: creature._id,
|
|
||||||
creatureName: creature.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Apply the top level property, it is responsible for applying its children
|
// Apply the top level property, it is responsible for applying its children
|
||||||
// recursively
|
// recursively
|
||||||
const scope = {
|
applyProperty(propertyForest[0], actionContext);
|
||||||
...creature.variables,
|
|
||||||
...ancestorScope,
|
|
||||||
...methodScope
|
|
||||||
}
|
|
||||||
applyProperty(propertyForest[0], {
|
|
||||||
creature,
|
|
||||||
targets,
|
|
||||||
scope,
|
|
||||||
log,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Insert the log
|
// Insert the log
|
||||||
insertCreatureLogWork({log, creature, method});
|
actionContext.writeLog();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assumes ancestors are in tree order already
|
// Assumes ancestors are in tree order already
|
||||||
|
|||||||
@@ -1,11 +1,53 @@
|
|||||||
import '/imports/api/simpleSchemaConfig.js';
|
import '/imports/api/simpleSchemaConfig.js';
|
||||||
//import testTypes from './testTypes/index.js';
|
//import testTypes from './testTypes/index.js';
|
||||||
import { doActionWork } from './doAction.js';
|
import { doActionWork } from './doAction.js';
|
||||||
import createAction from './tests/createAction.testFn.js';
|
import { CreatureLogSchema } from '/imports/api/creature/log/CreatureLogs.js';
|
||||||
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
|
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||||
|
|
||||||
|
function cleanProp(prop){
|
||||||
|
let schema = CreatureProperties.simpleSchema(prop);
|
||||||
|
return schema.clean(prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanCreature(creature){
|
||||||
|
let schema = Creatures.simpleSchema(creature);
|
||||||
|
return schema.clean(creature);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fake ActionContext to test actions with
|
||||||
|
const creatureId = 'actionTestCreatureId';
|
||||||
|
const creatureName = 'Action Test Creature';
|
||||||
|
const testActionContext = {
|
||||||
|
creature: cleanCreature({
|
||||||
|
_id: creatureId,
|
||||||
|
}),
|
||||||
|
log: CreatureLogSchema.clean({
|
||||||
|
creatureId: creatureId,
|
||||||
|
creatureName: creatureName,
|
||||||
|
}),
|
||||||
|
scope: {},
|
||||||
|
addLog(content) {
|
||||||
|
if (content.name || content.value){
|
||||||
|
this.log.content.push(content);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
writeLog: () => { },
|
||||||
|
}
|
||||||
|
|
||||||
|
const action = cleanProp({
|
||||||
|
type: 'action',
|
||||||
|
});
|
||||||
|
const actionAncestors = [];
|
||||||
|
|
||||||
describe('Do Action', function(){
|
describe('Do Action', function(){
|
||||||
it('Does an empty action', function(){
|
it('Does an empty action', function(){
|
||||||
doActionWork(createAction({properties: [{type: 'action'}]}));
|
doActionWork({
|
||||||
|
properties: [action],
|
||||||
|
ancestors: actionAncestors,
|
||||||
|
actionContext: testActionContext,
|
||||||
|
methodScope: {},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
//testTypes.forEach(test => it(test.text, test.fn));
|
//testTypes.forEach(test => it(test.text, test.fn));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
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 {
|
||||||
|
getProperyAncestors, getPropertyDecendants
|
||||||
|
} from '/imports/api/engine/loadCreatures.js';
|
||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||||
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||||
import { doActionWork } from '/imports/api/engine/actions/doAction.js';
|
import { doActionWork } from '/imports/api/engine/actions/doAction.js';
|
||||||
import { CreatureLogSchema } from '/imports/api/creature/log/CreatureLogs.js';
|
import { CreatureLogSchema } from '/imports/api/creature/log/CreatureLogs.js';
|
||||||
|
import ActionContext from '/imports/api/engine/actions/ActionContext.js';
|
||||||
|
|
||||||
const doAction = new ValidatedMethod({
|
const doAction = new ValidatedMethod({
|
||||||
name: 'creatureProperties.doCastSpell',
|
name: 'creatureProperties.doCastSpell',
|
||||||
@@ -39,47 +42,31 @@ const doAction = new ValidatedMethod({
|
|||||||
numRequests: 10,
|
numRequests: 10,
|
||||||
timeInterval: 5000,
|
timeInterval: 5000,
|
||||||
},
|
},
|
||||||
run({spellId, slotId, targetIds = [], scope = {}}) {
|
run({ spellId, slotId, targetIds = [], scope = {} }) {
|
||||||
|
// Get action context
|
||||||
let spell = CreatureProperties.findOne(spellId);
|
let spell = CreatureProperties.findOne(spellId);
|
||||||
|
const creatureId = spell.ancestors[0].id;
|
||||||
|
const actionContext = new ActionContext(creatureId, targetIds, this);
|
||||||
|
|
||||||
// Check permissions
|
// Check permissions
|
||||||
let creature = getRootCreatureAncestor(spell);
|
assertEditPermission(actionContext.creature, this.userId);
|
||||||
|
actionContext.targets.forEach(target => {
|
||||||
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);
|
assertEditPermission(target, this.userId);
|
||||||
targets.push(target);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch all the action's ancestor creatureProperties
|
const ancestors = getProperyAncestors(creatureId, spell._id);
|
||||||
const ancestorIds = [];
|
ancestors.sort((a, b) => a.order - b.order);
|
||||||
spell.ancestors.forEach(ref => {
|
|
||||||
if (ref.collection === 'creatureProperties') {
|
|
||||||
ancestorIds.push(ref.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get cursor of ancestors
|
const properties = getPropertyDecendants(creatureId, spell._id);
|
||||||
const ancestors = CreatureProperties.find({
|
properties.push(spell);
|
||||||
_id: {$in: ancestorIds},
|
properties.sort((a, b) => a.order - b.order);
|
||||||
}, {
|
|
||||||
sort: {order: 1},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get cursor of the properties
|
|
||||||
const properties = CreatureProperties.find({
|
|
||||||
$or: [{_id: spell._id}, {'ancestors.id': spell._id}],
|
|
||||||
removed: {$ne: true},
|
|
||||||
}, {
|
|
||||||
sort: {order: 1},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Spend the appropriate slot
|
// Spend the appropriate slot
|
||||||
let slotLevel = spell.level || 0;
|
let slotLevel = spell.level || 0;
|
||||||
let slot;
|
let slot;
|
||||||
|
|
||||||
|
actionContext.scope['slotLevel'] = slotLevel;
|
||||||
|
|
||||||
if (slotId && !spell.castWithoutSpellSlots){
|
if (slotId && !spell.castWithoutSpellSlots){
|
||||||
slot = CreatureProperties.findOne(slotId);
|
slot = CreatureProperties.findOne(slotId);
|
||||||
if (!slot){
|
if (!slot){
|
||||||
@@ -104,34 +91,32 @@ const doAction = new ValidatedMethod({
|
|||||||
}
|
}
|
||||||
slotLevel = slot.spellSlotLevel.value;
|
slotLevel = slot.spellSlotLevel.value;
|
||||||
damagePropertyWork({
|
damagePropertyWork({
|
||||||
property: slot,
|
prop: slot,
|
||||||
operation: 'increment',
|
operation: 'increment',
|
||||||
value: 1,
|
value: 1,
|
||||||
|
actionContext,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
scope['slotLevel'] = slotLevel;
|
|
||||||
|
|
||||||
// Post the slot level spent to the log
|
// Post the slot level spent to the log
|
||||||
const log = CreatureLogSchema.clean({
|
|
||||||
creatureId: creature._id,
|
|
||||||
creatureName: creature.name,
|
|
||||||
});
|
|
||||||
if (slot?.spellSlotLevel?.value){
|
if (slot?.spellSlotLevel?.value){
|
||||||
log.content.push({
|
actionContext.addLog({
|
||||||
name: `Casting using a level ${slotLevel} spell slot`
|
name: `Casting using a level ${slotLevel} spell slot`
|
||||||
});
|
});
|
||||||
} else if (slotLevel) {
|
} else if (slotLevel) {
|
||||||
log.content.push({
|
actionContext.addLog({
|
||||||
name: `Casting at level ${slotLevel}`
|
name: `Casting at level ${slotLevel}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do the action
|
// Do the action
|
||||||
doActionWork({ creature, targets, properties, ancestors, method: this, methodScope: scope, log });
|
doActionWork({
|
||||||
|
properties, ancestors, actionContext, methodScope: scope,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Force the characters involved to recalculate
|
||||||
Creatures.update({
|
Creatures.update({
|
||||||
_id: { $in: [creature._id, ...targetIds] }
|
_id: { $in: [creatureId, ...targetIds] }
|
||||||
}, {
|
}, {
|
||||||
$set: { dirty: true },
|
$set: { dirty: true },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.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 { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||||
import rollDice from '/imports/parser/rollDice.js';
|
import rollDice from '/imports/parser/rollDice.js';
|
||||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||||
|
import { applyTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
|
import ActionContext from '/imports/api/engine/actions/ActionContext.js';
|
||||||
|
|
||||||
const doCheck = new ValidatedMethod({
|
const doCheck = new ValidatedMethod({
|
||||||
name: 'creatureProperties.doCheck',
|
name: 'creatureProperties.doCheck',
|
||||||
@@ -24,34 +24,32 @@ const doCheck = new ValidatedMethod({
|
|||||||
},
|
},
|
||||||
run({propId, scope}) {
|
run({propId, scope}) {
|
||||||
const prop = CreatureProperties.findOne(propId);
|
const prop = CreatureProperties.findOne(propId);
|
||||||
const creature = getRootCreatureAncestor(prop);
|
const creatureId = prop.ancestors[0].id;
|
||||||
|
const actionContext = new ActionContext(creatureId, [creatureId], this);
|
||||||
|
Object.assign(actionContext.scope, scope);
|
||||||
|
|
||||||
// Check permissions
|
// Check permissions
|
||||||
assertEditPermission(creature, this.userId);
|
assertEditPermission(actionContext.creature, this.userId);
|
||||||
|
|
||||||
// Do the check
|
// Do the check
|
||||||
doCheckWork({creature, prop, method: this, methodScope: scope});
|
doCheckWork({prop, actionContext});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default doCheck;
|
export default doCheck;
|
||||||
|
|
||||||
export function doCheckWork({
|
export function doCheckWork({prop, actionContext}){
|
||||||
creature, prop, method, methodScope = {}
|
|
||||||
}){
|
|
||||||
// Create the log
|
|
||||||
let log = CreatureLogSchema.clean({
|
|
||||||
creatureId: creature._id,
|
|
||||||
creatureName: creature.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
rollCheck({prop, log, methodScope});
|
applyTriggers(actionContext.triggers.check?.before, prop, actionContext);
|
||||||
|
rollCheck(prop, actionContext);
|
||||||
|
applyTriggers(actionContext.triggers.check?.after, prop, actionContext);
|
||||||
|
|
||||||
// Insert the log
|
// Insert the log
|
||||||
insertCreatureLogWork({log, creature, method});
|
actionContext.writeLog();
|
||||||
}
|
}
|
||||||
|
|
||||||
function rollCheck({prop, log, methodScope}){
|
function rollCheck(prop, actionContext) {
|
||||||
|
const scope = actionContext.scope;
|
||||||
// get the modifier for the roll
|
// get the modifier for the roll
|
||||||
let rollModifier;
|
let rollModifier;
|
||||||
let logName = `${prop.name} check`;
|
let logName = `${prop.name} check`;
|
||||||
@@ -77,7 +75,7 @@ function rollCheck({prop, log, methodScope}){
|
|||||||
const rollModifierText = numberToSignedString(rollModifier, true);
|
const rollModifierText = numberToSignedString(rollModifier, true);
|
||||||
|
|
||||||
let value, values, resultPrefix;
|
let value, values, resultPrefix;
|
||||||
if (methodScope['$checkAdvantage'] === 1){
|
if (scope['$checkAdvantage'] === 1){
|
||||||
logName += ' (Advantage)';
|
logName += ' (Advantage)';
|
||||||
const [a, b] = rollDice(2, 20);
|
const [a, b] = rollDice(2, 20);
|
||||||
if (a >= b) {
|
if (a >= b) {
|
||||||
@@ -87,7 +85,7 @@ function rollCheck({prop, log, methodScope}){
|
|||||||
value = b;
|
value = b;
|
||||||
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText} = `;
|
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText} = `;
|
||||||
}
|
}
|
||||||
} else if (methodScope['$checkAdvantage'] === -1){
|
} else if (scope['$checkAdvantage'] === -1){
|
||||||
logName += ' (Disadvantage)';
|
logName += ' (Disadvantage)';
|
||||||
const [a, b] = rollDice(2, 20);
|
const [a, b] = rollDice(2, 20);
|
||||||
if (a <= b) {
|
if (a <= b) {
|
||||||
@@ -103,7 +101,7 @@ function rollCheck({prop, log, methodScope}){
|
|||||||
resultPrefix = `1d20 [ ${value} ] ${rollModifierText} = `
|
resultPrefix = `1d20 [ ${value} ] ${rollModifierText} = `
|
||||||
}
|
}
|
||||||
const result = (value + rollModifier) || 0;
|
const result = (value + rollModifier) || 0;
|
||||||
log.content.push({
|
actionContext.addLog({
|
||||||
name: logName,
|
name: logName,
|
||||||
value: `${resultPrefix} **${result}**`,
|
value: `${resultPrefix} **${result}**`,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
|
||||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
|
||||||
|
|
||||||
export default function createAction({
|
|
||||||
creature = {_id: 'creatureId'},
|
|
||||||
targets = [],
|
|
||||||
properties = [],
|
|
||||||
ancestors = [],
|
|
||||||
method
|
|
||||||
} = {}){
|
|
||||||
properties = properties.map(cleanProp);
|
|
||||||
ancestors = ancestors.map(cleanProp);
|
|
||||||
creature = cleanCreature(creature);
|
|
||||||
ancestors = ancestors.map(cleanCreature);
|
|
||||||
return {creature, targets, properties, ancestors, method};
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanProp(prop){
|
|
||||||
let schema = CreatureProperties.simpleSchema(prop);
|
|
||||||
return schema.clean(prop);
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanCreature(creature){
|
|
||||||
let schema = Creatures.simpleSchema(creature);
|
|
||||||
return schema.clean(creature);
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import applyAction from './applyAction.testFn.js';
|
|
||||||
|
|
||||||
export default [{
|
|
||||||
text: 'Applies actions',
|
|
||||||
fn: applyAction,
|
|
||||||
},];
|
|
||||||
@@ -178,8 +178,8 @@ function getTargetListFromTags(tags, computation){
|
|||||||
const targetTagIdLists = [];
|
const targetTagIdLists = [];
|
||||||
if (!tags) return [];
|
if (!tags) return [];
|
||||||
tags.forEach(tag => {
|
tags.forEach(tag => {
|
||||||
const idList = computation.propsWithTag[tag];
|
const idList = computation.propsWithTag[tag] || [];
|
||||||
if (idList) targetTagIdLists.push(idList);
|
targetTagIdLists.push(idList);
|
||||||
});
|
});
|
||||||
const targets = intersection(...targetTagIdLists);
|
const targets = intersection(...targetTagIdLists);
|
||||||
return targets;
|
return targets;
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ function aggregateAbilityEffects({computation, skillNode, abilityNode}){
|
|||||||
// to a skill from its ability
|
// to a skill from its ability
|
||||||
if (link.data === 'effect'){
|
if (link.data === 'effect'){
|
||||||
if (![
|
if (![
|
||||||
'advantage', 'disadvantage', 'passiveAdd', 'fail'
|
'advantage', 'disadvantage', 'passiveAdd', 'fail', 'conditional'
|
||||||
].includes(linkedNode.data.operation)){
|
].includes(linkedNode.data.operation)){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,23 @@ let AttributeSchema = createPropertySchema({
|
|||||||
type: String,
|
type: String,
|
||||||
regEx: /^#([a-f0-9]{3}){1,2}\b$/i,
|
regEx: /^#([a-f0-9]{3}){1,2}\b$/i,
|
||||||
optional: true,
|
optional: true,
|
||||||
|
},
|
||||||
|
// Control how the health bar takes damage or healing
|
||||||
|
healthBarNoDamage: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
healthBarNoHealing: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
healthBarDamageOrder: {
|
||||||
|
type: SimpleSchema.Integer,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
healthBarHealingOrder: {
|
||||||
|
type: SimpleSchema.Integer,
|
||||||
|
optional: true,
|
||||||
},
|
},
|
||||||
// The starting value, before effects
|
// The starting value, before effects
|
||||||
baseValue: {
|
baseValue: {
|
||||||
@@ -79,6 +96,16 @@ let AttributeSchema = createPropertySchema({
|
|||||||
decimal: {
|
decimal: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
optional: true,
|
optional: true,
|
||||||
|
},
|
||||||
|
// Can the total after damage be negative
|
||||||
|
ignoreLowerLimit: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
// Can the damage value be negative
|
||||||
|
ignoreUpperLimit: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
},
|
},
|
||||||
// Automatically zero the adjustment on these conditions
|
// Automatically zero the adjustment on these conditions
|
||||||
reset: {
|
reset: {
|
||||||
|
|||||||
@@ -5,8 +5,11 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
|||||||
const eventOptions = {
|
const eventOptions = {
|
||||||
doActionProperty: 'Do action',
|
doActionProperty: 'Do action',
|
||||||
// receiveActionProperty: 'Receiving action property',
|
// receiveActionProperty: 'Receiving action property',
|
||||||
|
check: 'Roll check',
|
||||||
// flipToggle: 'Toggle changed',
|
// flipToggle: 'Toggle changed',
|
||||||
// adjustProperty: 'Attribute adjusted',
|
// itemEquipped: 'Item equipped'
|
||||||
|
// itemUnequipped: 'Item unequipped'
|
||||||
|
damageProperty: 'Attribute damaged or healed',
|
||||||
anyRest: 'Short or long rest',
|
anyRest: 'Short or long rest',
|
||||||
longRest: 'Long rest',
|
longRest: 'Long rest',
|
||||||
shortRest: 'Short rest',
|
shortRest: 'Short rest',
|
||||||
@@ -26,6 +29,7 @@ const actionPropertyTypeOptions = {
|
|||||||
note: 'Note',
|
note: 'Note',
|
||||||
roll: 'Roll',
|
roll: 'Roll',
|
||||||
savingThrow: 'Saving throw',
|
savingThrow: 'Saving throw',
|
||||||
|
spell: 'Spell',
|
||||||
toggle: 'Toggle',
|
toggle: 'Toggle',
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,10 +44,6 @@ let TriggerSchema = createPropertySchema({
|
|||||||
optional: true,
|
optional: true,
|
||||||
max: STORAGE_LIMITS.name,
|
max: STORAGE_LIMITS.name,
|
||||||
},
|
},
|
||||||
summary: {
|
|
||||||
type: 'inlineCalculationFieldToCompute',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
description: {
|
description: {
|
||||||
type: 'inlineCalculationFieldToCompute',
|
type: 'inlineCalculationFieldToCompute',
|
||||||
optional: true,
|
optional: true,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ function computedOnlyField(field){
|
|||||||
[`${field}.effects`]: {
|
[`${field}.effects`]: {
|
||||||
type: Array,
|
type: Array,
|
||||||
optional: true,
|
optional: true,
|
||||||
|
removeBeforeCompute: true,
|
||||||
},
|
},
|
||||||
[`${field}.effects.$`]: {
|
[`${field}.effects.$`]: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -110,8 +110,8 @@ export function getUserTier(user){
|
|||||||
export function assertUserHasPaidBenefits(user){
|
export function assertUserHasPaidBenefits(user){
|
||||||
let tier = getUserTier(user);
|
let tier = getUserTier(user);
|
||||||
if (!tier.paidBenefits){
|
if (!tier.paidBenefits){
|
||||||
throw new Meteor.Error('Creatures.methods.insert.denied',
|
throw new Meteor.Error('no paid benefits',
|
||||||
`The ${tier.name} tier does not allow you to insert a creature`);
|
`The ${tier.name} tier does not have the required benefits`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ const expectedMigratedAttribute = {
|
|||||||
damage: 3,
|
damage: 3,
|
||||||
value: 17,
|
value: 17,
|
||||||
constitutionMod: 2,
|
constitutionMod: 2,
|
||||||
|
dirty: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
const exampleAttack = {
|
const exampleAttack = {
|
||||||
@@ -221,6 +222,7 @@ describe('migrateProperty', function() {
|
|||||||
prop: newAction,
|
prop: newAction,
|
||||||
reversed: true,
|
reversed: true,
|
||||||
});
|
});
|
||||||
|
delete reversedAction.dirty;
|
||||||
assert.deepEqual(action, exampleAction, 'action should not be bashed');
|
assert.deepEqual(action, exampleAction, 'action should not be bashed');
|
||||||
assert.deepEqual(exampleAction, reversedAction, 'operation should be reversible');
|
assert.deepEqual(exampleAction, reversedAction, 'operation should be reversible');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,10 +24,3 @@
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css">
|
|
||||||
.markdown img {
|
|
||||||
max-width: 100%;
|
|
||||||
margin: 8px 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -13,6 +13,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="js">
|
<script lang="js">
|
||||||
|
import PROPERTIES from '/imports/constants/PROPERTIES.js';
|
||||||
|
const filterOptions = [];
|
||||||
|
for (let key in PROPERTIES) {
|
||||||
|
if (key === 'reference') continue;
|
||||||
|
filterOptions.push({
|
||||||
|
text: PROPERTIES[key].name,
|
||||||
|
value: key,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
@@ -22,27 +32,7 @@ export default {
|
|||||||
},
|
},
|
||||||
data(){return {
|
data(){return {
|
||||||
filterTerms: [],
|
filterTerms: [],
|
||||||
filterOptions: [
|
filterOptions,
|
||||||
{text: 'Actions', value: 'action'},
|
|
||||||
{text: 'Attacks', value: 'attack'},
|
|
||||||
{text: 'Attributes', value: 'attribute'},
|
|
||||||
{text: 'Buffs', value: 'buff'},
|
|
||||||
{text: 'Class Levels', value: 'classLevel'},
|
|
||||||
{text: 'Damage Multipliers', value: 'damageMultiplier'},
|
|
||||||
{text: 'Effects', value: 'effect'},
|
|
||||||
{text: 'Experiences', value: 'experience'},
|
|
||||||
{text: 'Features', value: 'feature'},
|
|
||||||
{text: 'Folders', value: 'folder'},
|
|
||||||
{text: 'Notes', value: 'note'},
|
|
||||||
{text: 'Proficiencies', value: 'proficiency'},
|
|
||||||
{text: 'Rolls', value: 'roll'},
|
|
||||||
{text: 'Saving Throws', value: 'savingThrow'},
|
|
||||||
{text: 'Skills', value: 'skill'},
|
|
||||||
{text: 'Spell Lists', value: 'spellList'},
|
|
||||||
{text: 'Spells', value: 'spell'},
|
|
||||||
{text: 'Containers', value: 'container'},
|
|
||||||
{text: 'Items', value: 'item'},
|
|
||||||
],
|
|
||||||
}},
|
}},
|
||||||
computed: {
|
computed: {
|
||||||
filter(){
|
filter(){
|
||||||
|
|||||||
@@ -201,6 +201,7 @@ export default {
|
|||||||
},
|
},
|
||||||
librariesSelectedByCollections() {
|
librariesSelectedByCollections() {
|
||||||
let ids = [];
|
let ids = [];
|
||||||
|
if (!this.model.allowedLibraryCollections) return ids;
|
||||||
LibraryCollections.find({
|
LibraryCollections.find({
|
||||||
_id: { $in: this.model.allowedLibraryCollections }
|
_id: { $in: this.model.allowedLibraryCollections }
|
||||||
}).forEach(collection => {
|
}).forEach(collection => {
|
||||||
|
|||||||
@@ -64,6 +64,7 @@
|
|||||||
},
|
},
|
||||||
noLinks: Boolean,
|
noLinks: Boolean,
|
||||||
noIcons: Boolean,
|
noIcons: Boolean,
|
||||||
|
editing: Boolean,
|
||||||
},
|
},
|
||||||
computed:{
|
computed:{
|
||||||
props(){
|
props(){
|
||||||
@@ -95,7 +96,10 @@
|
|||||||
store.commit('pushDialogStack', {
|
store.commit('pushDialogStack', {
|
||||||
component: 'creature-property-dialog',
|
component: 'creature-property-dialog',
|
||||||
elementId: `breadcrumb-${id}`,
|
elementId: `breadcrumb-${id}`,
|
||||||
data: {_id: id},
|
data: {
|
||||||
|
_id: id,
|
||||||
|
startInEditTab: this.editing,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -120,7 +124,10 @@
|
|||||||
store.commit('pushDialogStack', {
|
store.commit('pushDialogStack', {
|
||||||
component: 'creature-root-dialog',
|
component: 'creature-root-dialog',
|
||||||
elementId: 'breadcrumb-root',
|
elementId: 'breadcrumb-root',
|
||||||
data: {_id: this.model.ancestors[0].id},
|
data: {
|
||||||
|
_id: this.model.ancestors[0].id,
|
||||||
|
startInEditTab: this.editing,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,20 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="model">
|
<template v-if="model">
|
||||||
|
<div
|
||||||
|
class="layout mb-4"
|
||||||
|
>
|
||||||
|
<template v-if="!embedded">
|
||||||
|
<breadcrumbs
|
||||||
|
:model="model"
|
||||||
|
:editing="editing"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<v-spacer />
|
||||||
|
<v-chip disabled>
|
||||||
|
{{ typeName }}
|
||||||
|
</v-chip>
|
||||||
|
</div>
|
||||||
<v-fade-transition
|
<v-fade-transition
|
||||||
mode="out-in"
|
mode="out-in"
|
||||||
>
|
>
|
||||||
@@ -51,17 +65,6 @@
|
|||||||
</component>
|
</component>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div
|
|
||||||
class="layout mb-4"
|
|
||||||
>
|
|
||||||
<template v-if="!embedded">
|
|
||||||
<breadcrumbs :model="model" />
|
|
||||||
</template>
|
|
||||||
<v-spacer />
|
|
||||||
<v-chip disabled>
|
|
||||||
{{ typeName }}
|
|
||||||
</v-chip>
|
|
||||||
</div>
|
|
||||||
<component
|
<component
|
||||||
:is="model.type + 'Viewer'"
|
:is="model.type + 'Viewer'"
|
||||||
:key="_id"
|
:key="_id"
|
||||||
@@ -200,7 +203,7 @@ export default {
|
|||||||
watch: {
|
watch: {
|
||||||
_id: {
|
_id: {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
handler(newId){
|
handler(newId) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.currentId = newId;
|
this.currentId = newId;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,8 +17,11 @@
|
|||||||
<v-card-title>
|
<v-card-title>
|
||||||
{{ model.name }}
|
{{ model.name }}
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-card-text v-if="model.description">
|
<v-card-text>
|
||||||
{{ model.description.value }}
|
<property-description
|
||||||
|
text
|
||||||
|
:model="model.description"
|
||||||
|
/>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
@@ -36,10 +39,12 @@
|
|||||||
|
|
||||||
<script lang="js">
|
<script lang="js">
|
||||||
import CardHighlight from '/imports/ui/components/CardHighlight.vue';
|
import CardHighlight from '/imports/ui/components/CardHighlight.vue';
|
||||||
|
import PropertyDescription from '/imports/ui/properties/viewers/shared/PropertyDescription.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
CardHighlight,
|
CardHighlight,
|
||||||
|
PropertyDescription,
|
||||||
},
|
},
|
||||||
inject: {
|
inject: {
|
||||||
theme: {
|
theme: {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
style="height: 100%; transform-origin: left; transition: all 0.5s ease;"
|
style="height: 100%; transform-origin: left; transition: all 0.5s ease;"
|
||||||
:style="{
|
:style="{
|
||||||
backgroundColor: barColor,
|
backgroundColor: barColor,
|
||||||
transform: `scaleX(${value / maxValue})`,
|
transform: `scaleX(${fillFraction})`,
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
@@ -92,87 +92,93 @@ import IncrementMenu from '/imports/ui/components/IncrementMenu.vue';
|
|||||||
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
|
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
|
||||||
import chroma from 'chroma-js';
|
import chroma from 'chroma-js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
IncrementMenu
|
IncrementMenu
|
||||||
},
|
},
|
||||||
inject: {
|
inject: {
|
||||||
theme: {
|
theme: {
|
||||||
default: {
|
default: {
|
||||||
isDark: false,
|
isDark: false,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
props: {
|
},
|
||||||
value: Number,
|
props: {
|
||||||
maxValue: Number,
|
value: Number,
|
||||||
name: String,
|
maxValue: Number,
|
||||||
color: {
|
name: String,
|
||||||
type: String,
|
color: {
|
||||||
default() {
|
type: String,
|
||||||
return this.$vuetify.theme.currentTheme.primary
|
default() {
|
||||||
},
|
return this.$vuetify.theme.currentTheme.primary
|
||||||
},
|
},
|
||||||
midColor: {
|
},
|
||||||
type: String,
|
midColor: {
|
||||||
default: undefined,
|
type: String,
|
||||||
},
|
default: undefined,
|
||||||
lowColor: {
|
},
|
||||||
type: String,
|
lowColor: {
|
||||||
default: undefined,
|
type: String,
|
||||||
},
|
default: undefined,
|
||||||
_id: String,
|
},
|
||||||
},
|
_id: String,
|
||||||
data() {
|
},
|
||||||
return {
|
data() {
|
||||||
editing: false,
|
return {
|
||||||
hover: false,
|
editing: false,
|
||||||
};
|
hover: false,
|
||||||
},
|
};
|
||||||
computed: {
|
},
|
||||||
barColor() {
|
computed: {
|
||||||
const fraction = this.value / this.maxValue;
|
fillFraction() {
|
||||||
if (!Number.isFinite(fraction)) return this.color;
|
let fraction = this.value / this.maxValue;
|
||||||
if (fraction > 0.5){
|
if (fraction < 0) fraction = 0;
|
||||||
return this.color;
|
if (fraction > 1) fraction = 1;
|
||||||
} else if (this.midColor && this.lowColor) {
|
return fraction;
|
||||||
return chroma.mix(this.lowColor, this.midColor, fraction * 2).hex();
|
},
|
||||||
} else if (this.midColor){
|
barColor() {
|
||||||
return this.midColor;
|
const fraction = this.value / this.maxValue;
|
||||||
}
|
if (!Number.isFinite(fraction)) return this.color;
|
||||||
|
if (fraction > 0.5){
|
||||||
return this.color;
|
return this.color;
|
||||||
},
|
} else if (this.midColor && this.lowColor) {
|
||||||
barBackgroundColor(){
|
return chroma.mix(this.lowColor, this.midColor, fraction * 2).hex();
|
||||||
return chroma(this.barColor)
|
} else if (this.midColor){
|
||||||
.darken(1.5)
|
return this.midColor;
|
||||||
.desaturate(1.5)
|
|
||||||
.hex();
|
|
||||||
},
|
|
||||||
isTextLight(){
|
|
||||||
return isDarkColor(this.barBackgroundColor);
|
|
||||||
/* Change color at the halfway mark
|
|
||||||
const fraction = this.value / this.maxValue;
|
|
||||||
if (fraction >= 0.5){
|
|
||||||
return isDarkColor(this.barColor);
|
|
||||||
} else {
|
|
||||||
return isDarkColor(this.barBackgroundColor);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
return this.color;
|
||||||
},
|
},
|
||||||
methods: {
|
barBackgroundColor(){
|
||||||
edit() {
|
return chroma(this.barColor)
|
||||||
this.editing = true;
|
.darken(1.5)
|
||||||
},
|
.desaturate(1.5)
|
||||||
cancelEdit() {
|
.hex();
|
||||||
this.editing = false;
|
},
|
||||||
},
|
isTextLight(){
|
||||||
changeIncrementMenu(e){
|
return isDarkColor(this.barBackgroundColor);
|
||||||
this.$emit('change', e);
|
/* Change color at the halfway mark
|
||||||
this.editing = false;
|
const fraction = this.value / this.maxValue;
|
||||||
|
if (fraction >= 0.5){
|
||||||
|
return isDarkColor(this.barColor);
|
||||||
|
} else {
|
||||||
|
return isDarkColor(this.barBackgroundColor);
|
||||||
}
|
}
|
||||||
},
|
*/
|
||||||
};
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
edit() {
|
||||||
|
this.editing = true;
|
||||||
|
},
|
||||||
|
cancelEdit() {
|
||||||
|
this.editing = false;
|
||||||
|
},
|
||||||
|
changeIncrementMenu(e){
|
||||||
|
this.$emit('change', e);
|
||||||
|
this.editing = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<v-btn
|
<v-btn
|
||||||
icon
|
icon
|
||||||
small
|
small
|
||||||
:disabled="model.value >= model.total || context.editPermission === false"
|
:disabled="(model.value >= model.total && !model.ignoreUpperLimit) || context.editPermission === false"
|
||||||
@click="increment(1)"
|
@click="increment(1)"
|
||||||
>
|
>
|
||||||
<v-icon>mdi-chevron-up</v-icon>
|
<v-icon>mdi-chevron-up</v-icon>
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
<v-btn
|
<v-btn
|
||||||
icon
|
icon
|
||||||
small
|
small
|
||||||
:disabled="model.value <= 0 || context.editPermission === false"
|
:disabled="(model.value <= 0 && !model.ignoreLowerLimit) || context.editPermission === false"
|
||||||
@click="increment(-1)"
|
@click="increment(-1)"
|
||||||
>
|
>
|
||||||
<v-icon>mdi-chevron-down</v-icon>
|
<v-icon>mdi-chevron-down</v-icon>
|
||||||
@@ -28,7 +28,10 @@
|
|||||||
<div class="text-h4">
|
<div class="text-h4">
|
||||||
{{ model.value }}
|
{{ model.value }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-h6 ml-2 max-value">
|
<div
|
||||||
|
v-if="model.total !== 0"
|
||||||
|
class="text-h6 ml-2 max-value"
|
||||||
|
>
|
||||||
/{{ model.total }}
|
/{{ model.total }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -78,7 +78,6 @@
|
|||||||
<form-section
|
<form-section
|
||||||
v-if="model.attributeType === 'healthBar'"
|
v-if="model.attributeType === 'healthBar'"
|
||||||
name="Health Bar Settings"
|
name="Health Bar Settings"
|
||||||
standalone
|
|
||||||
>
|
>
|
||||||
<color-picker
|
<color-picker
|
||||||
:value="model.healthBarColorMid"
|
:value="model.healthBarColorMid"
|
||||||
@@ -90,6 +89,47 @@
|
|||||||
label="Empty color"
|
label="Empty color"
|
||||||
@input="value => $emit('change', {path: ['healthBarColorLow'], value})"
|
@input="value => $emit('change', {path: ['healthBarColorLow'], value})"
|
||||||
/>
|
/>
|
||||||
|
<v-layout
|
||||||
|
wrap
|
||||||
|
class="mt-4"
|
||||||
|
>
|
||||||
|
<text-field
|
||||||
|
label="Damage order"
|
||||||
|
type="number"
|
||||||
|
style="max-width: 300px;"
|
||||||
|
hint="Lower ordered health bars will take damage before higher ordered ones"
|
||||||
|
class="mr-4"
|
||||||
|
:disabled="model.healthBarNoDamage"
|
||||||
|
:value="model.healthBarDamageOrder"
|
||||||
|
:error-messages="errors.healthBarDamageOrder"
|
||||||
|
@change="change('healthBarDamageOrder', ...arguments)"
|
||||||
|
/>
|
||||||
|
<smart-switch
|
||||||
|
label="Ignore damage"
|
||||||
|
:value="model.healthBarNoDamage"
|
||||||
|
:error-messages="errors.healthBarNoDamage"
|
||||||
|
@change="change('healthBarNoDamage', ...arguments)"
|
||||||
|
/>
|
||||||
|
</v-layout>
|
||||||
|
<v-layout wrap>
|
||||||
|
<text-field
|
||||||
|
label="Healing order"
|
||||||
|
type="number"
|
||||||
|
style="max-width: 300px;"
|
||||||
|
hint="Lower ordered health bars will take healing before higher ordered ones"
|
||||||
|
class="mr-4"
|
||||||
|
:disabled="model.healthBarNoHealing"
|
||||||
|
:value="model.healthBarHealingOrder"
|
||||||
|
:error-messages="errors.healthBarHealingOrder"
|
||||||
|
@change="change('healthBarHealingOrder', ...arguments)"
|
||||||
|
/>
|
||||||
|
<smart-switch
|
||||||
|
label="Ignore healing"
|
||||||
|
:value="model.healthBarNoHealing"
|
||||||
|
:error-messages="errors.healthBarNoHealing"
|
||||||
|
@change="change('healthBarNoHealing', ...arguments)"
|
||||||
|
/>
|
||||||
|
</v-layout>
|
||||||
</form-section>
|
</form-section>
|
||||||
</v-expand-transition>
|
</v-expand-transition>
|
||||||
|
|
||||||
@@ -121,6 +161,18 @@
|
|||||||
:error-messages="errors.decimal"
|
:error-messages="errors.decimal"
|
||||||
@change="change('decimal', ...arguments)"
|
@change="change('decimal', ...arguments)"
|
||||||
/>
|
/>
|
||||||
|
<smart-switch
|
||||||
|
label="Can be damaged into negative values"
|
||||||
|
:value="model.ignoreLowerLimit"
|
||||||
|
:error-messages="errors.ignoreLowerLimit"
|
||||||
|
@change="change('ignoreLowerLimit', ...arguments)"
|
||||||
|
/>
|
||||||
|
<smart-switch
|
||||||
|
label="Can be incremented above total"
|
||||||
|
:value="model.ignoreUpperLimit"
|
||||||
|
:error-messages="errors.ignoreUpperLimit"
|
||||||
|
@change="change('ignoreUpperLimit', ...arguments)"
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
class="layout justify-center"
|
class="layout justify-center"
|
||||||
style="align-self: stretch;"
|
style="align-self: stretch;"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<div class="slot-form">
|
<div class="trigger-form">
|
||||||
<text-field
|
<text-field
|
||||||
ref="focusFirst"
|
ref="focusFirst"
|
||||||
label="Name"
|
label="Name"
|
||||||
@@ -8,64 +8,6 @@
|
|||||||
@change="change('name', ...arguments)"
|
@change="change('name', ...arguments)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<v-layout align-center>
|
|
||||||
<v-btn
|
|
||||||
icon
|
|
||||||
style="margin-top: -30px;"
|
|
||||||
class="mr-2"
|
|
||||||
:loading="addExtraTagsLoading"
|
|
||||||
:disabled="extraTagsFull"
|
|
||||||
@click="addExtraTags"
|
|
||||||
>
|
|
||||||
<v-icon>
|
|
||||||
mdi-plus
|
|
||||||
</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<smart-combobox
|
|
||||||
label="Tags Required"
|
|
||||||
hint="The slot must be filled with a property which has all the listed tags"
|
|
||||||
multiple
|
|
||||||
chips
|
|
||||||
deletable-chips
|
|
||||||
:value="model.targetTags"
|
|
||||||
:error-messages="errors.targetTags"
|
|
||||||
@change="change('targetTags', ...arguments)"
|
|
||||||
/>
|
|
||||||
</v-layout>
|
|
||||||
<v-slide-x-transition group>
|
|
||||||
<div
|
|
||||||
v-for="(extras, i) in model.extraTags"
|
|
||||||
:key="extras._id"
|
|
||||||
class="extra-tags layout align-center justify-space-between"
|
|
||||||
>
|
|
||||||
<smart-select
|
|
||||||
label="Operation"
|
|
||||||
style="width: 90px; flex-grow: 0;"
|
|
||||||
:items="extraTagOperations"
|
|
||||||
:value="extras.operation"
|
|
||||||
:error-messages="errors.extraTags && errors.extraTags[i]"
|
|
||||||
@change="change(['extraTags', i, 'operation'], ...arguments)"
|
|
||||||
/>
|
|
||||||
<smart-combobox
|
|
||||||
label="Tags"
|
|
||||||
:hint="extras.operation === 'OR' ? 'The slot can be filled with a property that has all of these tags instead' : 'The slot cannot be filled with a property that has any of these tags'"
|
|
||||||
class="mx-2"
|
|
||||||
multiple
|
|
||||||
chips
|
|
||||||
deletable-chips
|
|
||||||
:value="extras.tags"
|
|
||||||
@change="change(['extraTags', i, 'tags'], ...arguments)"
|
|
||||||
/>
|
|
||||||
<v-btn
|
|
||||||
icon
|
|
||||||
style="margin-top: -30px;"
|
|
||||||
@click="$emit('pull', {path: ['extraTags', i]})"
|
|
||||||
>
|
|
||||||
<v-icon>mdi-delete</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</div>
|
|
||||||
</v-slide-x-transition>
|
|
||||||
|
|
||||||
<smart-select
|
<smart-select
|
||||||
label="Timing"
|
label="Timing"
|
||||||
style="flex-basis: 300px;"
|
style="flex-basis: 300px;"
|
||||||
@@ -95,6 +37,70 @@
|
|||||||
@change="change('actionPropertyType', ...arguments)"
|
@change="change('actionPropertyType', ...arguments)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<v-layout
|
||||||
|
v-show="showTags"
|
||||||
|
align-center
|
||||||
|
>
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
style="margin-top: -30px;"
|
||||||
|
class="mr-2"
|
||||||
|
:loading="addExtraTagsLoading"
|
||||||
|
:disabled="extraTagsFull"
|
||||||
|
@click="addExtraTags"
|
||||||
|
>
|
||||||
|
<v-icon>
|
||||||
|
mdi-plus
|
||||||
|
</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<smart-combobox
|
||||||
|
label="Tags Required"
|
||||||
|
hint="The trigger will be fired by a property which has all the listed tags"
|
||||||
|
multiple
|
||||||
|
chips
|
||||||
|
deletable-chips
|
||||||
|
:value="model.targetTags"
|
||||||
|
:error-messages="errors.targetTags"
|
||||||
|
@change="change('targetTags', ...arguments)"
|
||||||
|
/>
|
||||||
|
</v-layout>
|
||||||
|
<v-slide-x-transition
|
||||||
|
v-show="showTags"
|
||||||
|
group
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(extras, i) in model.extraTags"
|
||||||
|
:key="extras._id"
|
||||||
|
class="extra-tags layout align-center justify-space-between"
|
||||||
|
>
|
||||||
|
<smart-select
|
||||||
|
label="Operation"
|
||||||
|
style="width: 90px; flex-grow: 0;"
|
||||||
|
:items="extraTagOperations"
|
||||||
|
:value="extras.operation"
|
||||||
|
:error-messages="errors.extraTags && errors.extraTags[i]"
|
||||||
|
@change="change(['extraTags', i, 'operation'], ...arguments)"
|
||||||
|
/>
|
||||||
|
<smart-combobox
|
||||||
|
label="Tags"
|
||||||
|
:hint="extras.operation === 'OR' ? 'The trigger can be fired by a property that has all of these tags instead' : 'The trigger won\'t be fired by a property that has any of these tags'"
|
||||||
|
class="mx-2"
|
||||||
|
multiple
|
||||||
|
chips
|
||||||
|
deletable-chips
|
||||||
|
:value="extras.tags"
|
||||||
|
@change="change(['extraTags', i, 'tags'], ...arguments)"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
style="margin-top: -30px;"
|
||||||
|
@click="$emit('pull', {path: ['extraTags', i]})"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-delete</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</v-slide-x-transition>
|
||||||
|
|
||||||
<computed-field
|
<computed-field
|
||||||
label="Condition"
|
label="Condition"
|
||||||
hint="A caclulation to determine if this trigger should fire"
|
hint="A caclulation to determine if this trigger should fire"
|
||||||
@@ -105,15 +111,6 @@
|
|||||||
$emit('change', {path: ['condition', ...path], value, ack})"
|
$emit('change', {path: ['condition', ...path], value, ack})"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<inline-computation-field
|
|
||||||
label="Summary"
|
|
||||||
hint="This will appear in the feature card in the character sheet"
|
|
||||||
:model="model.summary"
|
|
||||||
:error-messages="errors.summary"
|
|
||||||
@change="({path, value, ack}) =>
|
|
||||||
$emit('change', {path: ['summary', ...path], value, ack})"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<inline-computation-field
|
<inline-computation-field
|
||||||
label="Description"
|
label="Description"
|
||||||
hint="The rest of the description that doesn't fit in the summary goes here"
|
hint="The rest of the description that doesn't fit in the summary goes here"
|
||||||
@@ -183,6 +180,11 @@ export default {
|
|||||||
if (!this.model.extraTags) return false;
|
if (!this.model.extraTags) return false;
|
||||||
let maxCount = TriggerSchema.get('extraTags', 'maxCount');
|
let maxCount = TriggerSchema.get('extraTags', 'maxCount');
|
||||||
return this.model.extraTags.length >= maxCount;
|
return this.model.extraTags.length >= maxCount;
|
||||||
|
},
|
||||||
|
showTags() {
|
||||||
|
return this.model.event !== 'shortRest' &&
|
||||||
|
this.model.event !== 'longRest' &&
|
||||||
|
this.model.event !== 'anyRest';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<div class="trigger-viewer">
|
<div class="trigger-viewer">
|
||||||
<v-row dense>
|
<v-row dense>
|
||||||
|
<property-field
|
||||||
|
name="Timing"
|
||||||
|
:value="timingText"
|
||||||
|
/>
|
||||||
<property-field
|
<property-field
|
||||||
name="Event"
|
name="Event"
|
||||||
:value="eventText"
|
:value="eventText"
|
||||||
/>
|
/>
|
||||||
|
<property-field
|
||||||
|
name="Event Type"
|
||||||
|
:value="actionPropertyText"
|
||||||
|
/>
|
||||||
<property-field
|
<property-field
|
||||||
v-if="(model.targetTags && model.targetTags.length) || (model.extraTags && model.extraTags.length)"
|
v-if="(model.targetTags && model.targetTags.length) || (model.extraTags && model.extraTags.length)"
|
||||||
name="Tags Required"
|
name="Tags Required"
|
||||||
@@ -23,10 +31,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</property-field>
|
</property-field>
|
||||||
<property-description
|
|
||||||
name="Summary"
|
|
||||||
:model="model.summary"
|
|
||||||
/>
|
|
||||||
<property-description
|
<property-description
|
||||||
name="Description"
|
name="Description"
|
||||||
:model="model.description"
|
:model="model.description"
|
||||||
@@ -36,39 +40,34 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="js">
|
<script lang="js">
|
||||||
import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js'
|
import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js'
|
||||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||||
import FillSlotButton from '/imports/ui/creature/buildTree/FillSlotButton.vue';
|
import { timingOptions, eventOptions, actionPropertyTypeOptions } from '/imports/api/properties/Triggers.js';
|
||||||
|
|
||||||
const eventText = {
|
export default {
|
||||||
doActionProperty: 'Do action property',
|
mixins: [propertyViewerMixin],
|
||||||
receiveActionProperty: 'Receiving action property',
|
inject: {
|
||||||
flipToggle: 'Toggle changed',
|
context: {
|
||||||
adjustProperty: 'Attribute adjusted',
|
default: {},
|
||||||
anyRest: 'Short or long rest',
|
},
|
||||||
longRest: 'Long rest',
|
},
|
||||||
shortRest: 'Short rest',
|
computed: {
|
||||||
|
slotTypeName(){
|
||||||
|
if (!this.model.slotType) return;
|
||||||
|
return getPropertyName(this.model.slotType);
|
||||||
|
},
|
||||||
|
timingText(){
|
||||||
|
if (!this.model.timing) return;
|
||||||
|
return timingOptions[this.model.timing];
|
||||||
|
},
|
||||||
|
actionPropertyText(){
|
||||||
|
if (!this.model.actionPropertyType) return;
|
||||||
|
return actionPropertyTypeOptions[this.model.actionPropertyType];
|
||||||
|
},
|
||||||
|
eventText(){
|
||||||
|
if (!this.model.event) return;
|
||||||
|
return eventOptions[this.model.event];
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
FillSlotButton,
|
|
||||||
},
|
|
||||||
mixins: [propertyViewerMixin],
|
|
||||||
inject: {
|
|
||||||
context: {
|
|
||||||
default: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
slotTypeName(){
|
|
||||||
if (!this.model.slotType) return;
|
|
||||||
return getPropertyName(this.model.slotType);
|
|
||||||
},
|
|
||||||
eventText(){
|
|
||||||
if (!this.model.event) return;
|
|
||||||
return eventText[this.model.event]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="calculation && calculation.effects"
|
v-if="calculation && calculation.effects"
|
||||||
class="flex-grow-1"
|
class="flex-grow-1"
|
||||||
|
style="max-width: 100%;"
|
||||||
>
|
>
|
||||||
<inline-effect
|
<inline-effect
|
||||||
v-if="typeof calculation.value === 'number'"
|
v-if="typeof calculation.value === 'number'"
|
||||||
|
|||||||
94
app/imports/ui/styles/markdown.css
Normal file
94
app/imports/ui/styles/markdown.css
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
.markdown {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h1,
|
||||||
|
.markdown h2,
|
||||||
|
.markdown h3,
|
||||||
|
.markdown h4,
|
||||||
|
.markdown h5,
|
||||||
|
.markdown h6 {
|
||||||
|
line-height: initial;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h1:not(:first-child),
|
||||||
|
.markdown h2:not(:first-child),
|
||||||
|
.markdown h3:not(:first-child),
|
||||||
|
.markdown h4:not(:first-child),
|
||||||
|
.markdown h5:not(:first-child),
|
||||||
|
.markdown h6:not(:first-child) {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:not(:first-child) .markdown h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
letter-spacing: -.0083333333em;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h2 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h3 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
letter-spacing: .0073529412em;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h4 {
|
||||||
|
font-size: 1rem;
|
||||||
|
letter-spacing: .0125em;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h5 {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
letter-spacing: .0125em;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h6 {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
letter-spacing: .0125em;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown pre code {
|
||||||
|
display: block;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme--dark.v-application .markdown hr {
|
||||||
|
border-color: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown img {
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown table {
|
||||||
|
min-width: 100%;
|
||||||
|
border-spacing: 0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown th {
|
||||||
|
font-weight: initial;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown td {
|
||||||
|
padding: 2px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown tbody>tr:nth-child(odd) {
|
||||||
|
background-color: rgba(0, 0, 0, 0.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme--dark.v-application .markdown tbody>tr:nth-child(odd) {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1)
|
||||||
|
}
|
||||||
@@ -6,5 +6,6 @@ import './fitAvatars.css';
|
|||||||
import './inheritBackgrounds.css';
|
import './inheritBackgrounds.css';
|
||||||
import './largeFormatInputs.css';
|
import './largeFormatInputs.css';
|
||||||
import './lineClamp.css';
|
import './lineClamp.css';
|
||||||
|
import './markdown.css';
|
||||||
import './speedDial.css';
|
import './speedDial.css';
|
||||||
import './toolbarFlex.css';
|
import './toolbarFlex.css';
|
||||||
|
|||||||
Reference in New Issue
Block a user