Adding failing saving throw tests
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
import { assert } from 'chai';
|
||||
import {
|
||||
allMutations,
|
||||
createTestCreature,
|
||||
getRandomIds,
|
||||
removeAllCreaturesAndProps,
|
||||
runActionById
|
||||
} from '/imports/api/engine/action/functions/actionEngineTest.testFn';
|
||||
|
||||
const [
|
||||
creatureId, savingThrowId, targetCreatureId, targetCreature2Id
|
||||
] = getRandomIds(4);
|
||||
|
||||
const actionTestCreature = {
|
||||
_id: creatureId,
|
||||
props: [
|
||||
{
|
||||
_id: savingThrowId,
|
||||
type: 'savingThrow',
|
||||
name: 'Strength Save',
|
||||
dc: { calculation: '10 + 3' },
|
||||
stat: 'strengthSave',
|
||||
children: [{
|
||||
type: 'branch',
|
||||
branchType: 'successfulSave',
|
||||
children: [{
|
||||
type: 'note',
|
||||
summary: { text: 'note to apply on save' },
|
||||
}],
|
||||
}, {
|
||||
type: 'branch',
|
||||
branchType: 'failedSave',
|
||||
children: [{
|
||||
type: 'note',
|
||||
summary: { text: 'note to apply on failed save' },
|
||||
}],
|
||||
}],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const actionTargetCreature = {
|
||||
_id: targetCreatureId,
|
||||
props: [
|
||||
{
|
||||
type: 'skill',
|
||||
variableName: 'strengthSave',
|
||||
baseValue: { calculation: '3' },
|
||||
},
|
||||
],
|
||||
}
|
||||
const actionTargetCreature2 = {
|
||||
_id: targetCreature2Id,
|
||||
props: [
|
||||
{
|
||||
type: 'skill',
|
||||
variableName: 'strengthSave',
|
||||
baseValue: { calculation: '2' },
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
describe.only('Apply saving throw properties', function () {
|
||||
// Increase timeout
|
||||
this.timeout(8000);
|
||||
|
||||
before(async function () {
|
||||
await removeAllCreaturesAndProps();
|
||||
await createTestCreature(actionTestCreature);
|
||||
await createTestCreature(actionTargetCreature);
|
||||
await createTestCreature(actionTargetCreature2);
|
||||
});
|
||||
|
||||
it('Makes multiple creatures make saves', async function () {
|
||||
const action = await runActionById(savingThrowId, [targetCreatureId, targetCreature2Id]);
|
||||
assert.exists(action);
|
||||
assert.deepEqual(allMutations(action), [
|
||||
{
|
||||
'contents': [{
|
||||
'inline': true,
|
||||
'name': 'Strength Save',
|
||||
'value': 'DC **13**',
|
||||
}, {
|
||||
'inline': true,
|
||||
'name': 'Successful save',
|
||||
'value': '1d20 [ 10 ] + 3\n**13**',
|
||||
}],
|
||||
'targetIds': [targetCreatureId],
|
||||
}, {
|
||||
'contents': [{
|
||||
'value': 'note to apply on save',
|
||||
}],
|
||||
'targetIds': [targetCreatureId],
|
||||
}, {
|
||||
'contents': [{
|
||||
'inline': true,
|
||||
'name': 'Strength Save',
|
||||
'value': 'DC **13**',
|
||||
}, {
|
||||
'inline': true,
|
||||
'name': 'Failed save',
|
||||
'value': '1d20 [ 10 ] + 2\n**12**',
|
||||
}],
|
||||
'targetIds': [targetCreature2Id],
|
||||
}, {
|
||||
'contents': [{
|
||||
'value': 'note to apply on failed save',
|
||||
}],
|
||||
'targetIds': [targetCreature2Id],
|
||||
},
|
||||
],
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,8 @@
|
||||
import { getFromScope } from '/imports/api/creature/creatures/CreatureVariables';
|
||||
import { EngineAction } from '/imports/api/engine/action/EngineActions';
|
||||
import InputProvider from '/imports/api/engine/action/functions/userInput/InputProvider';
|
||||
import { applyDefaultAfterPropTasks } from '/imports/api/engine/action/functions/applyTaskGroups';
|
||||
import { applyDefaultAfterPropTasks, applyTaskToEachTarget } from '/imports/api/engine/action/functions/applyTaskGroups';
|
||||
import getPropertyTitle from '/imports/api/utility/getPropertyTitle';
|
||||
import { getEffectiveActionScope } from '/imports/api/engine/action/functions/getEffectiveActionScope';
|
||||
import recalculateCalculation from '/imports/api/engine/action/functions/recalculateCalculation';
|
||||
import { PropTask } from '/imports/api/engine/action/tasks/Task';
|
||||
import TaskResult from '/imports/api/engine/action/tasks/TaskResult';
|
||||
@@ -11,23 +10,23 @@ import { getVariables } from '/imports/api/engine/loadCreatures';
|
||||
import numberToSignedString from '/imports/api/utility/numberToSignedString';
|
||||
import { isFiniteNode } from '/imports/parser/parseTree/constant';
|
||||
|
||||
// TODO saves are not split to targets correctly
|
||||
// This will cause issues with triggers firing on saves on multiple targets
|
||||
// FIXME saves against multiple targets contaminate later targets scope with the ~saveFailed or ~saveSucceeded variables of the previous targets
|
||||
|
||||
export default async function applySavingThrowProperty(
|
||||
task: PropTask, action: EngineAction, result: TaskResult, inputProvider: InputProvider
|
||||
): Promise<void> {
|
||||
|
||||
const prop = task.prop;
|
||||
const originalTargetIds = task.targetIds;
|
||||
|
||||
const saveTargetIds = prop.target === 'self' ? [action.creatureId] : originalTargetIds;
|
||||
const saveTargetIds = prop.target === 'self' ? [action.creatureId] : task.targetIds;
|
||||
|
||||
if (saveTargetIds.length > 1)
|
||||
if (saveTargetIds.length > 1) {
|
||||
return applyTaskToEachTarget(action, task, saveTargetIds, inputProvider);
|
||||
}
|
||||
|
||||
recalculateCalculation(prop.dc, action, 'reduce', inputProvider);
|
||||
recalculateCalculation(prop.dc, action, 'reduce', inputProvider);
|
||||
|
||||
if (!isFiniteNode(prop.dc?.parseNode)) {
|
||||
if (!isFiniteNode(prop.dc?.valueNode)) {
|
||||
result.appendLog({
|
||||
name: 'Error',
|
||||
value: 'Saving throw requires a DC',
|
||||
@@ -36,17 +35,19 @@ export default async function applySavingThrowProperty(
|
||||
}
|
||||
|
||||
const dc = (prop.dc?.value);
|
||||
if (!prop.silent) result.appendLog({
|
||||
result.appendLog({
|
||||
name: getPropertyTitle(prop),
|
||||
value: `DC **${dc}**`,
|
||||
inline: true,
|
||||
...prop.silent && { silenced: prop.silent }
|
||||
}, saveTargetIds);
|
||||
const scope = await getEffectiveActionScope(action);
|
||||
|
||||
const targetId = saveTargetIds[0];
|
||||
|
||||
// If there are no save targets, apply all children as if the save both
|
||||
// succeeded and failed
|
||||
if (!saveTargetIds?.length) {
|
||||
if (!targetId) {
|
||||
console.warn('no target, returning early');
|
||||
result.pushScope = {
|
||||
['~saveFailed']: { value: true },
|
||||
['~saveSucceeded']: { value: true },
|
||||
@@ -55,60 +56,57 @@ export default async function applySavingThrowProperty(
|
||||
}
|
||||
|
||||
// Each target makes the saving throw
|
||||
for (const targetId of saveTargetIds) {
|
||||
const save = getFromScope(prop.stat, getVariables(targetId));
|
||||
|
||||
const save = getFromScope(prop.stat, getVariables(targetId));
|
||||
|
||||
if (!save) {
|
||||
result.appendLog({
|
||||
name: 'Saving throw error',
|
||||
value: 'No saving throw found: ' + prop.stat,
|
||||
}, [targetId]);
|
||||
return applyDefaultAfterPropTasks(action, prop, [targetId], inputProvider);
|
||||
}
|
||||
|
||||
const rollModifierText = numberToSignedString(save.value, true);
|
||||
const rollModifier = save.value;
|
||||
|
||||
let value, resultPrefix;
|
||||
if (save.advantage === 1) {
|
||||
const [[a, b]] = await inputProvider.rollDice([{ number: 2, diceSize: 20 }]);
|
||||
if (a >= b) {
|
||||
value = a;
|
||||
resultPrefix = `Advantage\n1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText}`;
|
||||
} else {
|
||||
value = b;
|
||||
resultPrefix = `Advantage\n1d20 [ ~~${a}~~, ${b} ] ${rollModifierText}`;
|
||||
}
|
||||
} else if (save.advantage === -1) {
|
||||
const [[a, b]] = await inputProvider.rollDice([{ number: 2, diceSize: 20 }]);
|
||||
if (a <= b) {
|
||||
value = a;
|
||||
resultPrefix = `Disadvantage\n1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText}`;
|
||||
} else {
|
||||
value = b;
|
||||
resultPrefix = `Disadvantage\n1d20 [ ~~${a}~~, ${b} ] ${rollModifierText}`;
|
||||
}
|
||||
} else {
|
||||
const [[rolledValue]] = await inputProvider.rollDice([{ number: 1, diceSize: 20 }]);
|
||||
value = rolledValue;
|
||||
resultPrefix = `1d20 [ ${value} ] ${rollModifierText}`
|
||||
}
|
||||
result.pushScope = {};
|
||||
result.pushScope['~saveDiceRoll'] = { value };
|
||||
const resultValue = value + rollModifier || 0;
|
||||
result.pushScope['~saveRoll'] = { value: resultValue };
|
||||
const saveSuccess = resultValue >= dc;
|
||||
if (saveSuccess) {
|
||||
result.pushScope['~saveSucceeded'] = { value: true };
|
||||
} else {
|
||||
result.pushScope['~saveFailed'] = { value: true };
|
||||
}
|
||||
if (!prop.silent) result.appendLog({
|
||||
name: saveSuccess ? 'Successful save' : 'Failed save',
|
||||
value: resultPrefix + '\n**' + resultValue + '**',
|
||||
inline: true,
|
||||
if (!save) {
|
||||
result.appendLog({
|
||||
name: 'Saving throw error',
|
||||
value: 'No saving throw found: ' + prop.stat,
|
||||
}, [targetId]);
|
||||
return applyDefaultAfterPropTasks(action, prop, [targetId], inputProvider);
|
||||
}
|
||||
|
||||
const rollModifierText = numberToSignedString(save.value, true);
|
||||
const rollModifier = save.value;
|
||||
|
||||
let value, resultPrefix;
|
||||
if (save.advantage === 1) {
|
||||
const [[a, b]] = await inputProvider.rollDice([{ number: 2, diceSize: 20 }]);
|
||||
if (a >= b) {
|
||||
value = a;
|
||||
resultPrefix = `Advantage\n1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText}`;
|
||||
} else {
|
||||
value = b;
|
||||
resultPrefix = `Advantage\n1d20 [ ~~${a}~~, ${b} ] ${rollModifierText}`;
|
||||
}
|
||||
} else if (save.advantage === -1) {
|
||||
const [[a, b]] = await inputProvider.rollDice([{ number: 2, diceSize: 20 }]);
|
||||
if (a <= b) {
|
||||
value = a;
|
||||
resultPrefix = `Disadvantage\n1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText}`;
|
||||
} else {
|
||||
value = b;
|
||||
resultPrefix = `Disadvantage\n1d20 [ ~~${a}~~, ${b} ] ${rollModifierText}`;
|
||||
}
|
||||
} else {
|
||||
const [[rolledValue]] = await inputProvider.rollDice([{ number: 1, diceSize: 20 }]);
|
||||
value = rolledValue;
|
||||
resultPrefix = `1d20 [ ${value} ] ${rollModifierText}`
|
||||
}
|
||||
result.pushScope = {};
|
||||
result.pushScope['~saveDiceRoll'] = { value };
|
||||
const resultValue = value + rollModifier || 0;
|
||||
result.pushScope['~saveRoll'] = { value: resultValue };
|
||||
const saveSuccess = resultValue >= dc;
|
||||
if (saveSuccess) {
|
||||
result.pushScope['~saveSucceeded'] = { value: true };
|
||||
} else {
|
||||
result.pushScope['~saveFailed'] = { value: true };
|
||||
}
|
||||
if (!prop.silent) result.appendLog({
|
||||
name: saveSuccess ? 'Successful save' : 'Failed save',
|
||||
value: resultPrefix + '\n**' + resultValue + '**',
|
||||
inline: true,
|
||||
}, [targetId]);
|
||||
return applyDefaultAfterPropTasks(action, prop, [targetId], inputProvider);
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ const ComputedOnlySavingThrowSchema = createPropertySchema({
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedSavingThrowSchema = new SimpleSchema()
|
||||
const ComputedSavingThrowSchema = new SimpleSchema({})
|
||||
.extend(SavingThrowSchema)
|
||||
.extend(ComputedOnlySavingThrowSchema);
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ let ComputedOnlySkillSchema = createPropertySchema({
|
||||
},
|
||||
})
|
||||
|
||||
const ComputedSkillSchema = new SimpleSchema()
|
||||
const ComputedSkillSchema = new SimpleSchema({})
|
||||
.extend(ComputedOnlySkillSchema)
|
||||
.extend(SkillSchema);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user