More testing of action system, now with test coverage

This commit is contained in:
Thaum Rystra
2024-02-27 07:30:40 +02:00
parent 5141704e23
commit b13ca8c64b
20 changed files with 236 additions and 249 deletions

View File

@@ -1,5 +1,6 @@
import { assert } from 'chai';
import {
allLogContent,
allMutations,
allUpdates,
createTestCreature,
@@ -7,12 +8,13 @@ import {
removeAllCreaturesAndProps,
runActionById
} from '/imports/api/engine/action/functions/actionEngineTest.testFn';
import { Mutation, Update } from '/imports/api/engine/action/tasks/TaskResult';
import { LogContent, Mutation, Update } from '/imports/api/engine/action/tasks/TaskResult';
import Alea from 'alea';
const [
creatureId, targetCreatureId, targetCreature2Id,
emptyActionId, attackActionId, usesActionId, attackMissId,
creatureId, targetCreatureId, targetCreature2Id, emptyActionId, selfActionId, attackActionId,
usesActionId, attackMissId, attackNoTargetId, usesResourcesActionId, ammoId, resourceAttId, consumeAmmoId,
consumeResourceId, noUsesActionId, insufficientResourcesActionId
] = randomIds;
const actionTestCreature = {
@@ -22,6 +24,13 @@ const actionTestCreature = {
{
_id: emptyActionId,
type: 'action',
summary: { text: 'Summary text 1 + 1 = {1 + 1}' }
},
// Attack that targets self
{
_id: selfActionId,
type: 'action',
target: 'self',
},
// Attack that hits
{
@@ -35,6 +44,12 @@ const actionTestCreature = {
type: 'action',
attackRoll: { calculation: '-5' },
},
// Attack that has no target
{
_id: attackNoTargetId,
type: 'action',
attackRoll: { calculation: '1' },
},
// Disable crits
{
type: 'attribute',
@@ -56,6 +71,57 @@ const actionTestCreature = {
usesUsed: 1,
reset: 'longRest',
},
// Not enough uses
{
_id: noUsesActionId,
type: 'action',
uses: { calculation: '5' },
usesUsed: 5,
reset: 'longRest',
},
// Uses Resources
{
_id: ammoId,
type: 'item',
quantity: 12,
tags: ['ammo']
},
{
_id: resourceAttId,
type: 'attribute',
name: 'Resource Name',
attributeType: 'stat',
baseValue: { calculation: '7' },
variableName: 'resourceVar',
},
{
_id: usesResourcesActionId,
type: 'action',
resources: {
itemsConsumed: [{
_id: consumeAmmoId,
tag: 'ammo',
quantity: { calculation: '3' },
itemId: ammoId,
}],
attributesConsumed: [{
_id: consumeResourceId,
variableName: 'resourceVar',
quantity: { calculation: '2' },
}]
}
},
{
_id: insufficientResourcesActionId,
type: 'action',
resources: {
attributesConsumed: [{
_id: consumeResourceId,
variableName: 'resourceVar',
quantity: { calculation: '9001' },
}]
}
}
],
}
@@ -106,11 +172,43 @@ describe('Apply Action Properties', function () {
const action = await runActionById(emptyActionId);
assert.exists(action);
assert.deepEqual(allMutations(action), [{
contents: [{ name: 'Action' }],
contents: [{
name: 'Action',
value: 'Summary text 1 + 1 = 2',
}],
targetIds: [],
}]);
});
it('should target self when set', async function () {
const action = await runActionById(selfActionId);
assert.exists(action);
assert.deepEqual(allMutations(action), [{
contents: [{
name: 'Action',
}],
targetIds: [creatureId],
}]);
});
it('should make attack rolls against no targets', async function () {
const action = await runActionById(attackNoTargetId, []);
const expectedMutations: Mutation[] = [
{
contents: [{ name: 'Action' }],
targetIds: [],
}, {
contents: [{
name: 'To Hit',
value: '1d20 [10] + 1\n**11**',
inline: true,
}],
targetIds: [],
}
];
assert.deepEqual(allMutations(action), expectedMutations);
})
it('should make attack rolls against multiple creatures', async function () {
const action = await runActionById(attackActionId, [
targetCreatureId,
@@ -151,6 +249,19 @@ describe('Apply Action Properties', function () {
assert.deepEqual(allUpdates(action), expectedUpdates);
});
it('should fail to make attacks that have no uses left', async function () {
const action = await runActionById(noUsesActionId, [targetCreatureId]);
const expectedContent: LogContent[] = [
{
name: 'Action'
}, {
name: 'Error',
value: 'Action does not have enough uses left'
}
]
assert.deepEqual(allLogContent(action), expectedContent);
});
it('should make attack rolls that miss', async function () {
const action = await runActionById(attackMissId, [targetCreatureId]);
const expectedMutations: Mutation[] = [
@@ -169,4 +280,59 @@ describe('Apply Action Properties', function () {
assert.deepEqual(allMutations(action), expectedMutations);
});
it('actions should consume resources', async function () {
const action = await runActionById(usesResourcesActionId, []);
const expectedMutations: Mutation[] = [
{
contents: [{ name: 'Action' }],
targetIds: []
},
{
contents: [{
inline: true,
name: 'Attribute damaged',
value: '2 Resource Name',
}],
targetIds: [creatureId],
updates: [{
inc: {
damage: 2,
value: -2
},
propId: resourceAttId,
type: 'attribute'
}],
},
{
targetIds: [],
updates: [
{
inc: {
quantity: -3
},
propId: ammoId,
type: 'item',
}
]
}
];
assert.deepEqual(allMutations(action), expectedMutations);
});
it('should handle insufficient resources', async function () {
const action = await runActionById(insufficientResourcesActionId, []);
const expectedMutations: Mutation[] = [
{
contents: [{
name: 'Action'
}, {
name: 'Error',
value: 'This creature doesn\'t have sufficient resources to perform this action',
}],
targetIds: [],
},
];
assert.deepEqual(allMutations(action), expectedMutations);
});
});

View File

@@ -21,33 +21,36 @@ export default async function applyActionProperty(
const targetIds = prop.target === 'self' ? [action.creatureId] : task.targetIds;
//Log the name and summary, check that the property has enough resources to fire
const content: LogContent = { name: getPropertyTitle(prop) };
if (prop.summary?.text) {
await recalculateInlineCalculations(prop.summary, action, 'reduce', userInput);
content.value = prop.summary.value;
}
if (prop.silent) content.silenced = true;
result.appendLog(content, targetIds);
result.appendLog({
name: getPropertyTitle(prop),
...prop.summary && { value: prop.summary.value },
...prop.silent && { silenced: true },
}, targetIds);
// Check Uses
if (prop.usesLeft <= 0) {
if (!prop.silent) result.appendLog({
result.appendLog({
name: 'Error',
value: `${prop.name || 'action'} does not have enough uses left`,
value: `${getPropertyTitle(prop)} does not have enough uses left`,
...prop.silent && { silenced: true },
}, targetIds);
return;
}
// Check Resources
if (prop.insufficientResources) {
if (!prop.silent) result.appendLog({
result.appendLog({
name: 'Error',
value: 'This creature doesn\'t have sufficient resources to perform this action',
...prop.silent && { silenced: true },
}, targetIds);
return;
}
spendResources(action, prop, targetIds, result, userInput);
await spendResources(action, prop, targetIds, result, userInput);
const attack: CalculatedField = prop.attackRoll || prop.attackRollBonus;

View File

@@ -5,10 +5,12 @@ import { applyTriggers } from '/imports/api/engine/action/functions/applyTaskGro
import { getEffectiveActionScope } from '/imports/api/engine/action/functions/getEffectiveActionScope';
import getPropertyTitle from '/imports/api/utility/getPropertyTitle';
import { getSingleProperty } from '/imports/api/engine/loadCreatures';
import numberToSignedString from '/imports/api/utility/numberToSignedString';
export default async function applyDamagePropTask(
task: DamagePropTask, action: EngineAction, result: TaskResult, userInput
): Promise<number> {
const prop = task.prop;
if (task.targetIds.length > 1) {
@@ -22,7 +24,7 @@ export default async function applyDamagePropTask(
// Set the scope properties
result.pushScope = {};
if (prop.operation === 'increment') {
if (operation === 'increment') {
if (value >= 0) {
result.pushScope['~damage'] = { value };
} else {
@@ -67,7 +69,7 @@ export default async function applyDamagePropTask(
value: `${statName}${operation === 'set' ? ' set to' : ''}` +
` ${value}`,
inline: true,
silenced: prop.silent,
...prop.silent && { silenced: true },
}, task.targetIds);
}
@@ -98,7 +100,7 @@ export default async function applyDamagePropTask(
name: title,
value: `${getPropertyTitle(targetProp)} set to ${value}`,
inline: true,
silenced: prop.silent,
...prop.silent && { silenced: true },
}]
});
} else if (operation === 'increment') {
@@ -120,10 +122,10 @@ export default async function applyDamagePropTask(
type: targetProp.type,
}],
contents: [{
name: 'Attribute damage',
value: `${getPropertyTitle(targetProp)} ${value}`,
name: 'Attribute damaged',
value: `${numberToSignedString(-value)} ${getPropertyTitle(targetProp)}`,
inline: true,
silenced: prop.silent,
...prop.silent && { silenced: true },
}]
});
}

View File

@@ -43,11 +43,13 @@ export default async function applyItemAsAmmoTask(task: ItemAsAmmoTask, action:
type: 'item',
}],
// Log the item name as a heading if it has child properties to apply
contents: itemChildren.length ? [{
name: getPropertyTitle(item) || 'Ammo',
inline: false,
silenced: prop.silent,
}] : undefined,
...itemChildren.length && {
contents: [{
name: getPropertyTitle(item) || 'Ammo',
inline: false,
silenced: prop.silent,
}]
},
});
await applyTriggers(action, item, [action.creatureId], 'ammo.after', userInput);

View File

@@ -53,7 +53,7 @@ export default async function applyTask(
// Create a result an push it to the action results, pass it to the apply function to modify
const result = new TaskResult(task.prop._id, task.targetIds);
result.scope[`#${prop.type}`] = prop;
result.scope[`#${prop.type}`] = { _propId: prop._id };
action.results.push(result);
// Apply the property