Merge branch 'master' into version-2-tabletop
This commit is contained in:
@@ -8,6 +8,8 @@ import { damagePropertyWork } from '/imports/api/creature/creatureProperties/met
|
||||
import numberToSignedString from '/imports/api/utility/numberToSignedString.js';
|
||||
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||
import { resetProperties } from '/imports/api/creature/creatures/methods/restCreature.js';
|
||||
import { getPropertyDecendants } from '/imports/api/engine/loadCreatures.js';
|
||||
import { nodeArrayToTree } from '/imports/api/parenting/nodesToTree.js';
|
||||
|
||||
export default function applyAction(node, actionContext) {
|
||||
applyNodeTriggers(node, 'before', actionContext);
|
||||
@@ -51,11 +53,11 @@ export default function applyAction(node, actionContext) {
|
||||
}
|
||||
|
||||
function applyAttackWithoutTarget({ attack, actionContext }) {
|
||||
delete actionContext.scope['$attackHit'];
|
||||
delete actionContext.scope['$attackMiss'];
|
||||
delete actionContext.scope['$criticalHit'];
|
||||
delete actionContext.scope['$criticalMiss'];
|
||||
delete actionContext.scope['$attackRoll'];
|
||||
delete actionContext.scope['~attackHit'];
|
||||
delete actionContext.scope['~attackMiss'];
|
||||
delete actionContext.scope['~criticalHit'];
|
||||
delete actionContext.scope['~criticalMiss'];
|
||||
delete actionContext.scope['~attackRoll'];
|
||||
|
||||
recalculateCalculation(attack, actionContext);
|
||||
const scope = actionContext.scope;
|
||||
@@ -66,16 +68,16 @@ function applyAttackWithoutTarget({ attack, actionContext }) {
|
||||
criticalMiss,
|
||||
} = rollAttack(attack, scope);
|
||||
let name = criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical Miss!' : 'To Hit';
|
||||
if (scope['$attackAdvantage'] === 1) {
|
||||
if (scope['~attackAdvantage']?.value === 1) {
|
||||
name += ' (Advantage)';
|
||||
} else if (scope['$attackAdvantage'] === -1) {
|
||||
} else if (scope['~attackAdvantage']?.value === -1) {
|
||||
name += ' (Disadvantage)';
|
||||
}
|
||||
if (!criticalMiss) {
|
||||
scope['$attackHit'] = { value: true }
|
||||
scope['~attackHit'] = { value: true }
|
||||
}
|
||||
if (!criticalHit) {
|
||||
scope['$attackMiss'] = { value: true };
|
||||
scope['~attackMiss'] = { value: true };
|
||||
}
|
||||
|
||||
actionContext.addLog({
|
||||
@@ -87,12 +89,12 @@ function applyAttackWithoutTarget({ attack, actionContext }) {
|
||||
|
||||
function applyAttackToTarget({ attack, target, actionContext }) {
|
||||
const scope = actionContext.scope;
|
||||
delete scope['$attackHit'];
|
||||
delete scope['$attackMiss'];
|
||||
delete scope['$criticalHit'];
|
||||
delete scope['$criticalMiss'];
|
||||
delete scope['$attackDiceRoll'];
|
||||
delete scope['$attackRoll'];
|
||||
delete scope['~attackHit'];
|
||||
delete scope['~attackMiss'];
|
||||
delete scope['~criticalHit'];
|
||||
delete scope['~criticalMiss'];
|
||||
delete scope['~attackDiceRoll'];
|
||||
delete scope['~attackRoll'];
|
||||
|
||||
recalculateCalculation(attack, actionContext);
|
||||
|
||||
@@ -109,9 +111,9 @@ function applyAttackToTarget({ attack, target, actionContext }) {
|
||||
let name = criticalHit ? 'Critical Hit!' :
|
||||
criticalMiss ? 'Critical Miss!' :
|
||||
result > armor ? 'Hit!' : 'Miss!';
|
||||
if (scope['$attackAdvantage'] === 1) {
|
||||
if (scope['~attackAdvantage']?.value === 1) {
|
||||
name += ' (Advantage)';
|
||||
} else if (scope['$attackAdvantage'] === -1) {
|
||||
} else if (scope['~attackAdvantage']?.value === -1) {
|
||||
name += ' (Disadvantage)';
|
||||
}
|
||||
|
||||
@@ -121,9 +123,9 @@ function applyAttackToTarget({ attack, target, actionContext }) {
|
||||
inline: true,
|
||||
});
|
||||
if (criticalMiss || result < armor) {
|
||||
scope['$attackMiss'] = { value: true };
|
||||
scope['~attackMiss'] = { value: true };
|
||||
} else {
|
||||
scope['$attackHit'] = { value: true };
|
||||
scope['~attackHit'] = { value: true };
|
||||
}
|
||||
} else {
|
||||
actionContext.addLog({
|
||||
@@ -141,7 +143,7 @@ function applyAttackToTarget({ attack, target, actionContext }) {
|
||||
function rollAttack(attack, scope) {
|
||||
const rollModifierText = numberToSignedString(attack.value, true);
|
||||
let value, resultPrefix;
|
||||
if (scope['$attackAdvantage'] === 1) {
|
||||
if (scope['~attackAdvantage']?.value === 1) {
|
||||
const [a, b] = rollDice(2, 20);
|
||||
if (a >= b) {
|
||||
value = a;
|
||||
@@ -150,7 +152,7 @@ function rollAttack(attack, scope) {
|
||||
value = b;
|
||||
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText}`;
|
||||
}
|
||||
} else if (scope['$attackAdvantage'] === -1) {
|
||||
} else if (scope['~attackAdvantage']?.value === -1) {
|
||||
const [a, b] = rollDice(2, 20);
|
||||
if (a <= b) {
|
||||
value = a;
|
||||
@@ -163,23 +165,23 @@ function rollAttack(attack, scope) {
|
||||
value = rollDice(1, 20)[0];
|
||||
resultPrefix = `1d20 [${value}] ${rollModifierText}`
|
||||
}
|
||||
scope['$attackDiceRoll'] = { value };
|
||||
scope['~attackDiceRoll'] = { value };
|
||||
const result = value + attack.value;
|
||||
scope['$attackRoll'] = { result };
|
||||
scope['~attackRoll'] = { value: result };
|
||||
const { criticalHit, criticalMiss } = applyCrits(value, scope);
|
||||
return { resultPrefix, result, value, criticalHit, criticalMiss };
|
||||
}
|
||||
|
||||
function applyCrits(value, scope) {
|
||||
let criticalHitTarget = scope.criticalHitTarget?.value || 20;
|
||||
const criticalHitTarget = scope['~criticalHitTarget']?.value || 20;
|
||||
let criticalHit = value >= criticalHitTarget;
|
||||
let criticalMiss;
|
||||
if (criticalHit) {
|
||||
scope['$criticalHit'] = { value: true };
|
||||
scope['~criticalHit'] = { value: true };
|
||||
} else {
|
||||
criticalMiss = value === 1;
|
||||
if (criticalMiss) {
|
||||
scope['$criticalMiss'] = { value: true };
|
||||
scope['~criticalMiss'] = { value: true };
|
||||
}
|
||||
}
|
||||
return { criticalHit, criticalMiss };
|
||||
@@ -211,6 +213,7 @@ function spendResources(prop, actionContext) {
|
||||
let itemQuantityAdjustments = [];
|
||||
let spendLog = [];
|
||||
let gainLog = [];
|
||||
let ammoChildren = [];
|
||||
try {
|
||||
prop.resources.itemsConsumed.forEach(itemConsumed => {
|
||||
recalculateCalculation(itemConsumed.quantity, actionContext);
|
||||
@@ -221,11 +224,8 @@ function spendResources(prop, actionContext) {
|
||||
if (!item || item.ancestors[0].id !== prop.ancestors[0].id) {
|
||||
throw 'The prop\'s ammo was not found on the creature';
|
||||
}
|
||||
if (!item.equipped) {
|
||||
throw 'The selected ammo is not equipped';
|
||||
}
|
||||
if (
|
||||
!itemConsumed.quantity.value ||
|
||||
!itemConsumed?.quantity?.value ||
|
||||
!isFinite(itemConsumed.quantity.value)
|
||||
) return;
|
||||
itemQuantityAdjustments.push({
|
||||
@@ -242,12 +242,14 @@ function spendResources(prop, actionContext) {
|
||||
} else if (itemConsumed.quantity.value < 0) {
|
||||
gainLog.push(logName + ': ' + -itemConsumed.quantity.value);
|
||||
}
|
||||
ammoChildren.push(...getItemChildren(item, actionContext, prop));
|
||||
});
|
||||
} catch (e) {
|
||||
actionContext.addLog({
|
||||
name: 'Error',
|
||||
value: e,
|
||||
value: e.toString(),
|
||||
});
|
||||
console.error(e);
|
||||
return true;
|
||||
}
|
||||
// No more errors should be thrown after this line
|
||||
@@ -303,4 +305,36 @@ function spendResources(prop, actionContext) {
|
||||
value: spendLog.join('\n'),
|
||||
inline: true,
|
||||
});
|
||||
|
||||
// Apply the ammo children
|
||||
ammoChildren.forEach(prop => {
|
||||
applyProperty(prop, actionContext);
|
||||
});
|
||||
}
|
||||
|
||||
function getItemChildren(item, actionContext, prop) {
|
||||
// Skip if the prop or the item are ancestors of one another, otherwise infinite loop
|
||||
if (hasAncestorRelationship(item, prop)) return [];
|
||||
// Get the item children
|
||||
const itemProperties = getPropertyDecendants(actionContext.creature._id, item._id);
|
||||
// Tree them up
|
||||
const propertyForest = nodeArrayToTree(itemProperties);
|
||||
return propertyForest
|
||||
}
|
||||
|
||||
function hasAncestorRelationship(a, b) {
|
||||
let top, bottom;
|
||||
if (a.ancestors.length === b.ancestors.length) {
|
||||
// Can't be ancestors of one another if they have the same number of ancestors
|
||||
return false;
|
||||
} else if (a.ancestors.length > b.ancestors.length) {
|
||||
// longer ancestor list goes on the bottom
|
||||
top = b;
|
||||
bottom = a;
|
||||
} else {
|
||||
top = a;
|
||||
bottom = b;
|
||||
}
|
||||
const expectedAncestorPosition = top.ancestors.length;
|
||||
return bottom.ancestors[expectedAncestorPosition]?.id === top._id;
|
||||
}
|
||||
|
||||
@@ -3,22 +3,22 @@ import recalculateCalculation from './shared/recalculateCalculation.js';
|
||||
import rollDice from '/imports/parser/rollDice.js';
|
||||
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||
|
||||
export default function applyBranch(node, actionContext){
|
||||
export default function applyBranch(node, actionContext) {
|
||||
applyNodeTriggers(node, 'before', actionContext);
|
||||
const applyChildren = function(){
|
||||
const applyChildren = function () {
|
||||
applyNodeTriggers(node, 'after', actionContext);
|
||||
node.children.forEach(child => applyProperty(child, actionContext));
|
||||
};
|
||||
const scope = actionContext.scope;
|
||||
const targets = actionContext.targets;
|
||||
const prop = node.node;
|
||||
switch(prop.branchType){
|
||||
switch (prop.branchType) {
|
||||
case 'if':
|
||||
recalculateCalculation(prop.condition, actionContext);
|
||||
if (prop.condition?.value) applyChildren();
|
||||
break;
|
||||
case 'index':
|
||||
if (node.children.length){
|
||||
if (node.children.length) {
|
||||
recalculateCalculation(prop.condition, actionContext);
|
||||
if (!isFinite(prop.condition?.value)) {
|
||||
actionContext.addLog({
|
||||
@@ -35,31 +35,31 @@ export default function applyBranch(node, actionContext){
|
||||
}
|
||||
break;
|
||||
case 'hit':
|
||||
if (scope['$attackHit']?.value){
|
||||
if (!targets.length && !prop.silent) actionContext.addLog({value: '**On hit**'});
|
||||
if (scope['~attackHit']?.value) {
|
||||
if (!targets.length && !prop.silent) actionContext.addLog({ value: '**On hit**' });
|
||||
applyChildren();
|
||||
}
|
||||
break;
|
||||
case 'miss':
|
||||
if (scope['$attackMiss']?.value){
|
||||
if (!targets.length && !prop.silent) actionContext.addLog({value: '**On miss**'});
|
||||
if (scope['~attackMiss']?.value) {
|
||||
if (!targets.length && !prop.silent) actionContext.addLog({ value: '**On miss**' });
|
||||
applyChildren();
|
||||
}
|
||||
break;
|
||||
case 'failedSave':
|
||||
if (scope['$saveFailed']?.value){
|
||||
if (!targets.length && !prop.silent) actionContext.addLog({value: '**On failed save**'});
|
||||
if (scope['~saveFailed']?.value) {
|
||||
if (!targets.length && !prop.silent) actionContext.addLog({ value: '**On failed save**' });
|
||||
applyChildren();
|
||||
}
|
||||
break;
|
||||
case 'successfulSave':
|
||||
if (scope['$saveSucceeded']?.value){
|
||||
if (!targets.length && !prop.silent) actionContext.addLog({value: '**On save**',});
|
||||
if (scope['~saveSucceeded']?.value) {
|
||||
if (!targets.length && !prop.silent) actionContext.addLog({ value: '**On save**', });
|
||||
applyChildren();
|
||||
}
|
||||
break;
|
||||
case 'random':
|
||||
if (node.children.length){
|
||||
if (node.children.length) {
|
||||
let index = rollDice(1, node.children.length)[0] - 1;
|
||||
applyNodeTriggers(node, 'after', actionContext);
|
||||
applyProperty(node.children[index], actionContext);
|
||||
|
||||
@@ -21,7 +21,10 @@ export default function applyBuff(node, actionContext) {
|
||||
const prop = node.node;
|
||||
let buffTargets = prop.target === 'self' ? [actionContext.creature] : actionContext.targets;
|
||||
|
||||
// Then copy the decendants of the buff to the targets
|
||||
// Mark the buff as dirty for recalculation
|
||||
prop.dirty = true;
|
||||
|
||||
// Then copy the descendants of the buff to the targets
|
||||
let propList = [prop];
|
||||
function addChildrenToPropList(children, { skipCrystalize } = {}) {
|
||||
children.forEach(child => {
|
||||
@@ -97,7 +100,7 @@ function copyNodeListToTarget(propList, target, oldParent) {
|
||||
|
||||
/**
|
||||
* 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, actionContext }) {
|
||||
propList.forEach(prop => {
|
||||
@@ -116,8 +119,8 @@ function crystalizeVariables({ propList, actionContext }) {
|
||||
node.parseType !== 'accessor' && node.parseType !== 'symbol'
|
||||
) return node;
|
||||
// Handle variables
|
||||
if (node.name === '$target') {
|
||||
// strip $target
|
||||
if (node.name === '~target') {
|
||||
// strip ~target
|
||||
if (node.parseType === 'accessor') {
|
||||
node.name = node.path.shift();
|
||||
if (!node.path.length) {
|
||||
@@ -127,7 +130,7 @@ function crystalizeVariables({ propList, actionContext }) {
|
||||
// Can't strip symbols
|
||||
actionContext.addLog({
|
||||
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',
|
||||
});
|
||||
}
|
||||
return node;
|
||||
|
||||
@@ -27,7 +27,7 @@ export default function applyDamage(node, actionContext) {
|
||||
// Choose target
|
||||
let damageTargets = prop.target === 'self' ? [actionContext.creature] : actionContext.targets;
|
||||
// Determine if the hit is critical
|
||||
let criticalHit = scope['$criticalHit']?.value &&
|
||||
let criticalHit = scope['~criticalHit']?.value &&
|
||||
prop.damageType !== 'healing' // Can't critically heal
|
||||
;
|
||||
// Double the damage rolls if the hit is critical
|
||||
@@ -73,12 +73,12 @@ export default function applyDamage(node, actionContext) {
|
||||
damage = Math.floor(damage);
|
||||
|
||||
// Convert extra damage into the stored type
|
||||
if (prop.damageType === 'extra' && scope['$lastDamageType']) {
|
||||
prop.damageType = scope['$lastDamageType'];
|
||||
if (prop.damageType === 'extra' && scope['~lastDamageType']?.value) {
|
||||
prop.damageType = scope['~lastDamageType']?.value;
|
||||
}
|
||||
// Store current damage type
|
||||
if (prop.damageType !== 'healing') {
|
||||
scope['$lastDamageType'] = prop.damageType;
|
||||
scope['~lastDamageType'] = { value: prop.damageType };
|
||||
}
|
||||
|
||||
// Memoise the damage suffix for the log
|
||||
@@ -193,14 +193,18 @@ function dealDamage({ target, damageType, amount, actionContext }) {
|
||||
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)
|
||||
);
|
||||
healthBars = healthBars.filter((bar) => {
|
||||
if (bar.attributeType !== 'healthBar' || bar.inactive || bar.removed || bar.overridden) {
|
||||
return false;
|
||||
}
|
||||
if (damageType === 'healing' && bar.healthBarNoHealing) {
|
||||
return false;
|
||||
}
|
||||
if (damageType !== 'healing' && amount >= 0 && bar.healthBarNoDamage) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// Sort healthbars by damage/healing order or tree order as a fallback
|
||||
healthBars.sort((a, b) => {
|
||||
|
||||
@@ -4,22 +4,22 @@ import applyEffectsToCalculationParseNode from '/imports/api/engine/actions/appl
|
||||
import resolve, { toString } from '/imports/parser/resolve.js';
|
||||
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||
|
||||
export default function applyRoll(node, actionContext){
|
||||
export default function applyRoll(node, actionContext) {
|
||||
applyNodeTriggers(node, 'before', actionContext);
|
||||
const prop = node.node;
|
||||
|
||||
const applyChildren = function(){
|
||||
const applyChildren = function () {
|
||||
applyNodeTriggers(node, 'after', actionContext);
|
||||
node.children.forEach(child => applyProperty(child, actionContext));
|
||||
};
|
||||
|
||||
if (prop.roll?.calculation){
|
||||
if (prop.roll?.calculation) {
|
||||
const logValue = [];
|
||||
|
||||
// roll the dice only and store that string
|
||||
applyEffectsToCalculationParseNode(prop.roll, actionContext);
|
||||
const {result: rolled, context} = resolve('roll', prop.roll.parseNode, actionContext.scope);
|
||||
if (rolled.parseType !== 'constant'){
|
||||
const { result: rolled, context } = resolve('roll', prop.roll.parseNode, actionContext.scope);
|
||||
if (rolled.parseType !== 'constant') {
|
||||
logValue.push(toString(rolled));
|
||||
}
|
||||
logErrors(context.errors, actionContext);
|
||||
@@ -28,28 +28,28 @@ export default function applyRoll(node, actionContext){
|
||||
context.errors = [];
|
||||
|
||||
// Resolve the roll to a final value
|
||||
const {result: reduced} = resolve('reduce', rolled, actionContext.scope, context);
|
||||
const { result: reduced } = resolve('reduce', rolled, actionContext.scope, context);
|
||||
logErrors(context.errors, actionContext);
|
||||
|
||||
// Store the result
|
||||
if (reduced.parseType === 'constant'){
|
||||
if (reduced.parseType === 'constant') {
|
||||
prop.roll.value = reduced.value;
|
||||
} else if (reduced.parseType === 'error'){
|
||||
} else if (reduced.parseType === 'error') {
|
||||
prop.roll.value = null;
|
||||
} else {
|
||||
prop.roll.value = toString(reduced);
|
||||
}
|
||||
|
||||
// If we didn't end up with a constant of finite amount, give up
|
||||
if (reduced?.parseType !== 'constant' || !isFinite(reduced.value)){
|
||||
// If we didn't end up with a constant or a number of finite value, give up
|
||||
if (reduced?.parseType !== 'constant' || (reduced.valueType === 'number' && !isFinite(reduced.value))) {
|
||||
return applyChildren();
|
||||
}
|
||||
const value = reduced.value;
|
||||
|
||||
actionContext.scope[prop.variableName] = value;
|
||||
actionContext.scope[prop.variableName] = { value };
|
||||
logValue.push(`**${value}**`);
|
||||
|
||||
if (!prop.silent){
|
||||
if (!prop.silent) {
|
||||
actionContext.addLog({
|
||||
name: prop.name,
|
||||
value: logValue.join('\n'),
|
||||
|
||||
@@ -8,6 +8,7 @@ import { applyUnresolvedEffects } from '/imports/api/engine/actions/doCheck.js';
|
||||
export default function applySavingThrow(node, actionContext) {
|
||||
applyNodeTriggers(node, 'before', actionContext);
|
||||
const prop = node.node;
|
||||
const originalTargets = actionContext.targets;
|
||||
|
||||
let saveTargets = prop.target === 'self' ? [actionContext.creature] : actionContext.targets;
|
||||
|
||||
@@ -31,22 +32,22 @@ export default function applySavingThrow(node, actionContext) {
|
||||
// If there are no save targets, apply all children as if the save both
|
||||
// succeeeded and failed
|
||||
if (!saveTargets?.length) {
|
||||
scope['$saveFailed'] = { value: true };
|
||||
scope['$saveSucceeded'] = { value: true };
|
||||
scope['~saveFailed'] = { value: true };
|
||||
scope['~saveSucceeded'] = { value: true };
|
||||
applyNodeTriggers(node, 'after', actionContext);
|
||||
return node.children.forEach(child => applyProperty(child, actionContext));
|
||||
}
|
||||
|
||||
// Each target makes the saving throw
|
||||
saveTargets.forEach(target => {
|
||||
delete scope['$saveFailed'];
|
||||
delete scope['$saveSucceeded'];
|
||||
delete scope['$saveDiceRoll'];
|
||||
delete scope['$saveRoll'];
|
||||
delete scope['~saveFailed'];
|
||||
delete scope['~saveSucceeded'];
|
||||
delete scope['~saveDiceRoll'];
|
||||
delete scope['~saveRoll'];
|
||||
|
||||
const applyChildren = function () {
|
||||
applyNodeTriggers(node, 'after', actionContext);
|
||||
actionContext.targets = [target]
|
||||
applyNodeTriggers(node, 'after', actionContext);
|
||||
node.children.forEach(child => applyProperty(child, actionContext));
|
||||
};
|
||||
|
||||
@@ -90,14 +91,14 @@ export default function applySavingThrow(node, actionContext) {
|
||||
value = values[0];
|
||||
resultPrefix = `1d20 [ ${value} ] ${rollModifierText}`
|
||||
}
|
||||
scope['$saveDiceRoll'] = { value };
|
||||
scope['~saveDiceRoll'] = { value };
|
||||
const result = value + rollModifier || 0;
|
||||
scope['$saveRoll'] = { value: result };
|
||||
scope['~saveRoll'] = { value: result };
|
||||
const saveSuccess = result >= dc;
|
||||
if (saveSuccess) {
|
||||
scope['$saveSucceeded'] = { value: true };
|
||||
scope['~saveSucceeded'] = { value: true };
|
||||
} else {
|
||||
scope['$saveFailed'] = { value: true };
|
||||
scope['~saveFailed'] = { value: true };
|
||||
}
|
||||
if (!prop.silent) actionContext.addLog({
|
||||
name: saveSuccess ? 'Successful save' : 'Failed save',
|
||||
@@ -106,4 +107,6 @@ export default function applySavingThrow(node, actionContext) {
|
||||
});
|
||||
return applyChildren();
|
||||
});
|
||||
// reset the targets after the save to each child
|
||||
actionContext.targets = originalTargets;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export function applyTrigger(trigger, prop, actionContext) {
|
||||
if (trigger.inactive) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Prevent triggers from firing if their condition is false
|
||||
if (trigger.condition?.parseNode) {
|
||||
recalculateCalculation(trigger.condition, actionContext);
|
||||
@@ -61,11 +61,11 @@ export function applyTrigger(trigger, prop, actionContext) {
|
||||
value: trigger.description,
|
||||
inline: false,
|
||||
}
|
||||
if (trigger.description?.text){
|
||||
if (trigger.description?.text) {
|
||||
recalculateInlineCalculations(trigger.description, actionContext);
|
||||
content.value = trigger.description.value;
|
||||
}
|
||||
if(!trigger.silent) actionContext.addLog(content);
|
||||
if (!trigger.silent) actionContext.addLog(content);
|
||||
|
||||
// Get all the trigger's properties and apply them
|
||||
const properties = getPropertyDecendants(actionContext.creature._id, trigger._id);
|
||||
@@ -78,7 +78,7 @@ export function applyTrigger(trigger, prop, actionContext) {
|
||||
trigger.firing = false;
|
||||
}
|
||||
|
||||
function triggerMatchTags(trigger, prop) {
|
||||
export function triggerMatchTags(trigger, prop) {
|
||||
let matched = false;
|
||||
const propTags = getEffectivePropTags(prop);
|
||||
// Check the target tags
|
||||
@@ -89,23 +89,26 @@ function triggerMatchTags(trigger, prop) {
|
||||
matched = true;
|
||||
}
|
||||
// Check the extra tags
|
||||
trigger.extraTags?.forEach(extra => {
|
||||
if (extra.operation === 'OR') {
|
||||
if (matched) return;
|
||||
if (
|
||||
!extra.tags.length ||
|
||||
difference(extra.tags, propTags).length === 0
|
||||
) {
|
||||
matched = true;
|
||||
}
|
||||
} else if (extra.operation === 'NOT') {
|
||||
if (
|
||||
extra.tags.length &&
|
||||
intersection(extra.tags, propTags)
|
||||
) {
|
||||
return false;
|
||||
if (trigger.extraTags) {
|
||||
for (const extra of trigger.extraTags) {
|
||||
if (extra.operation === 'OR') {
|
||||
if (matched) break;
|
||||
if (
|
||||
!extra.tags.length ||
|
||||
difference(extra.tags, propTags).length === 0
|
||||
) {
|
||||
matched = true;
|
||||
}
|
||||
} else if (extra.operation === 'NOT') {
|
||||
if (
|
||||
extra.tags.length &&
|
||||
intersection(extra.tags, propTags).length > 0
|
||||
) {
|
||||
matched = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
67
app/imports/api/engine/actions/applyTriggers.testFn.js
Normal file
67
app/imports/api/engine/actions/applyTriggers.testFn.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import { triggerMatchTags } from '/imports/api/engine/actions/applyTriggers.js';
|
||||
import clean from '/imports/api/engine/computation/utility/cleanProp.testFn.js';
|
||||
import { assert } from 'chai';
|
||||
|
||||
export default function () {
|
||||
const prop = clean({
|
||||
id: 'propWithTags',
|
||||
type: 'action',
|
||||
tags: ['yes1', 'notUsed', 'no1', 'yes2', 'no2', 'or1', 'or2'],
|
||||
});
|
||||
const positiveProp = clean({
|
||||
id: 'propWithTags',
|
||||
type: 'action',
|
||||
tags: ['yes1', 'notUsed', 'yes2', 'or1', 'or2'],
|
||||
});
|
||||
assert.isTrue(
|
||||
triggerMatchTags(clean({
|
||||
type: 'trigger',
|
||||
targetTags: ['yes1'],
|
||||
}), prop),
|
||||
'Trigger matches on a single target tag'
|
||||
);
|
||||
assert.isTrue(
|
||||
triggerMatchTags(clean({
|
||||
type: 'trigger',
|
||||
targetTags: ['yes1', 'yes2'],
|
||||
}), prop),
|
||||
'Trigger matches on a multiple target tags'
|
||||
);
|
||||
assert.isFalse(
|
||||
triggerMatchTags(clean({
|
||||
type: 'trigger',
|
||||
targetTags: ['yes1'],
|
||||
extraTags: [{ operation: 'NOT', tags: ['no1'] }]
|
||||
}), prop),
|
||||
'Trigger correctly fails to match when not tags are present'
|
||||
);
|
||||
assert.isFalse(
|
||||
triggerMatchTags(clean({
|
||||
type: 'trigger',
|
||||
extraTags: [{ operation: 'NOT', tags: ['no1'] }]
|
||||
}), prop),
|
||||
'Trigger correctly fails to match when only not tags are present'
|
||||
);
|
||||
assert.isTrue(
|
||||
triggerMatchTags(clean({
|
||||
type: 'trigger',
|
||||
extraTags: [{ operation: 'NOT', tags: ['no1'] }]
|
||||
}), positiveProp),
|
||||
'Trigger matches when only not tags are present'
|
||||
);
|
||||
assert.isTrue(
|
||||
triggerMatchTags(clean({
|
||||
type: 'trigger',
|
||||
extraTags: [{ operation: 'OR', tags: ['or1'] }]
|
||||
}), positiveProp),
|
||||
'Trigger matches when OR tags are present'
|
||||
);
|
||||
assert.isTrue(
|
||||
triggerMatchTags(clean({
|
||||
type: 'trigger',
|
||||
targetTags: ['missing1'],
|
||||
extraTags: [{ operation: 'OR', tags: ['or1'] }]
|
||||
}), positiveProp),
|
||||
'Trigger matches when only OR tags are present'
|
||||
);
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
import '/imports/api/simpleSchemaConfig.js';
|
||||
//import testTypes from './testTypes/index.js';
|
||||
import applyTriggers from '/imports/api/engine/actions/applyTriggers.testFn.js';
|
||||
import { doActionWork } from './doAction.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){
|
||||
function cleanProp(prop) {
|
||||
let schema = CreatureProperties.simpleSchema(prop);
|
||||
return schema.clean(prop);
|
||||
}
|
||||
|
||||
function cleanCreature(creature){
|
||||
function cleanCreature(creature) {
|
||||
let schema = Creatures.simpleSchema(creature);
|
||||
return schema.clean(creature);
|
||||
}
|
||||
@@ -28,7 +29,7 @@ const testActionContext = {
|
||||
}),
|
||||
scope: {},
|
||||
addLog(content) {
|
||||
if (content.name || content.value){
|
||||
if (content.name || content.value) {
|
||||
this.log.content.push(content);
|
||||
}
|
||||
},
|
||||
@@ -40,8 +41,8 @@ const action = cleanProp({
|
||||
});
|
||||
const actionAncestors = [];
|
||||
|
||||
describe('Do Action', function(){
|
||||
it('Does an empty action', function(){
|
||||
describe('Do Action', function () {
|
||||
it('Does an empty action', function () {
|
||||
doActionWork({
|
||||
properties: [action],
|
||||
ancestors: actionAncestors,
|
||||
@@ -51,3 +52,7 @@ describe('Do Action', function(){
|
||||
});
|
||||
//testTypes.forEach(test => it(test.text, test.fn));
|
||||
});
|
||||
|
||||
describe('Action utility functions', function () {
|
||||
it('Triggers match tags', applyTriggers);
|
||||
})
|
||||
|
||||
@@ -117,7 +117,8 @@ const doAction = new ValidatedMethod({
|
||||
}
|
||||
}
|
||||
|
||||
actionContext.scope['slotLevel'] = slotLevel;
|
||||
actionContext.scope['slotLevel'] = { value: slotLevel };
|
||||
actionContext.scope['~slotLevel'] = { value: slotLevel };
|
||||
|
||||
// Do the action
|
||||
doActionWork({
|
||||
|
||||
@@ -81,7 +81,7 @@ function rollCheck(prop, actionContext) {
|
||||
rollModifier += effectBonus;
|
||||
|
||||
let value, values, resultPrefix;
|
||||
if (scope['$checkAdvantage'] === 1) {
|
||||
if (scope['~checkAdvantage']?.value === 1) {
|
||||
logName += ' (Advantage)';
|
||||
const [a, b] = rollDice(2, 20);
|
||||
if (a >= b) {
|
||||
@@ -91,7 +91,7 @@ function rollCheck(prop, actionContext) {
|
||||
value = b;
|
||||
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText} = `;
|
||||
}
|
||||
} else if (scope['$checkAdvantage'] === -1) {
|
||||
} else if (scope['~checkAdvantage']?.value === -1) {
|
||||
logName += ' (Disadvantage)';
|
||||
const [a, b] = rollDice(2, 20);
|
||||
if (a <= b) {
|
||||
@@ -107,9 +107,9 @@ function rollCheck(prop, actionContext) {
|
||||
resultPrefix = `1d20 [ ${value} ] ${rollModifierText} = `
|
||||
}
|
||||
const result = (value + rollModifier) || 0;
|
||||
scope['$checkDiceRoll'] = value;
|
||||
scope['$checkRoll'] = result;
|
||||
scope['$checkModifier'] = rollModifier;
|
||||
scope['~checkDiceRoll'] = { value };
|
||||
scope['~checkRoll'] = { value: result };
|
||||
scope['~checkModifier'] = { value: rollModifier };
|
||||
actionContext.addLog({
|
||||
name: logName,
|
||||
value: `${resultPrefix} **${result}**`,
|
||||
|
||||
Reference in New Issue
Block a user