More action test cases

This commit is contained in:
Thaum Rystra
2024-03-18 20:40:20 +02:00
parent 3c78f5b2f5
commit 293deaa592
4 changed files with 120 additions and 11 deletions

View File

@@ -10,11 +10,13 @@ import {
} from '/imports/api/engine/action/functions/actionEngineTest.testFn';
import { LogContent, Mutation, Update } from '/imports/api/engine/action/tasks/TaskResult';
import Alea from 'alea';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
const [
creatureId, targetCreatureId, targetCreature2Id, emptyActionId, selfActionId, attackActionId,
usesActionId, attackMissId, attackNoTargetId, usesResourcesActionId, ammoId, resourceAttId, consumeAmmoId,
consumeResourceId, noUsesActionId, insufficientResourcesActionId
usesActionId, attackMissId, attackNoTargetId, usesResourcesActionId, ammoId, resourceAttId,
consumeAmmoId, consumeResourceId, noUsesActionId, insufficientResourcesActionId,
attributeResetByEventId, eventActionId, advantageAttackId, advantageEffectId
] = randomIds;
const actionTestCreature = {
@@ -44,6 +46,20 @@ const actionTestCreature = {
type: 'action',
attackRoll: { calculation: '-5' },
},
// Attack that has Advantage
{
_id: advantageAttackId,
type: 'action',
attackRoll: { calculation: '0' },
tags: ['hasAdvantage'],
},
{
_id: advantageEffectId,
type: 'effect',
operation: 'advantage',
targetByTags: true,
targetTags: ['hasAdvantage'],
},
// Attack that has no target
{
_id: attackNoTargetId,
@@ -121,7 +137,24 @@ const actionTestCreature = {
quantity: { calculation: '9001' },
}]
}
}
},
// Events and resetting attributes
{
_id: attributeResetByEventId,
type: 'attribute',
name: 'Attribute Reset By testEvent Event',
attributeType: 'stat',
baseValue: { calculation: '27' },
damage: 13,
variableName: 'resetByEventAtt',
reset: 'testEvent'
},
{
_id: eventActionId,
type: 'action',
actionType: 'event',
variableName: 'testEvent',
},
],
}
@@ -280,6 +313,26 @@ describe('Apply Action Properties', function () {
assert.deepEqual(allMutations(action), expectedMutations);
});
it('should make attack rolls that roll with advantage', async function () {
const prop = await CreatureProperties.findOneAsync(advantageAttackId);
assert.equal(prop.attackRoll.advantage, 1, 'The attack roll should have advantage');
const action = await runActionById(advantageAttackId, [targetCreatureId]);
const expectedMutations: Mutation[] = [
{
contents: [{ name: 'Action' }],
targetIds: [targetCreatureId],
}, {
contents: [{
inline: true,
name: 'Hit! (Advantage)',
value: '1d20 [ ~~10~~, 11 ] + 0\n**11**',
}],
targetIds: [targetCreatureId],
}
];
assert.deepEqual(allMutations(action), expectedMutations);
});
it('actions should consume resources', async function () {
const action = await runActionById(usesResourcesActionId, []);
const expectedMutations: Mutation[] = [
@@ -335,4 +388,37 @@ describe('Apply Action Properties', function () {
assert.deepEqual(allMutations(action), expectedMutations);
});
it('should reset attributes when events happen', async function () {
const action = await runActionById(eventActionId, []);
const expectedMutations: Mutation[] = [
{
contents: [{
name: 'Action'
}],
targetIds: [],
},
{
contents: [
{
inline: true,
name: 'Attribute damaged',
value: '+13 Attribute Reset By testEvent Event',
},
],
targetIds: [creatureId],
updates: [
{
inc: {
damage: -13,
value: 13,
},
propId: attributeResetByEventId,
type: 'attribute',
},
],
}
];
assert.deepEqual(allMutations(action), expectedMutations);
});
});

View File

@@ -10,7 +10,7 @@ import { applyAfterChildrenTriggers, applyAfterTriggers, applyChildren } from '/
import recalculateCalculation from '/imports/api/engine/action/functions/recalculateCalculation';
import { getEffectiveActionScope } from '/imports/api/engine/action/functions/getEffectiveActionScope';
import numberToSignedString from '/imports/api/utility/numberToSignedString';
import { getNumberFromScope } from '/imports/api/creature/creatures/CreatureVariables';
import { getConstantValueFromScope, getNumberFromScope } from '/imports/api/creature/creatures/CreatureVariables';
import InputProvider from '/imports/api/engine/action/functions/InputProvider';
import { CalculatedField } from '/imports/api/properties/subSchemas/computedField';
@@ -100,6 +100,7 @@ async function applyAttackToTarget(
result,
criticalHit,
criticalMiss,
advantage
} = await rollAttack(attack, scope, taskResult.pushScope, userInput);
const targetScope = getVariables(targetId);
@@ -109,9 +110,9 @@ async function applyAttackToTarget(
let name = criticalHit ? 'Critical Hit!' :
criticalMiss ? 'Critical Miss!' :
result > targetArmor ? 'Hit!' : 'Miss!';
if (scope['~attackAdvantage']?.value === 1) {
if (advantage === 1) {
name += ' (Advantage)';
} else if (scope['~attackAdvantage']?.value === -1) {
} else if (advantage === -1) {
name += ' (Disadvantage)';
}
@@ -187,10 +188,16 @@ async function applyAttackWithoutTarget(action, prop, attack, taskResult: TaskRe
});
}
async function rollAttack(attack, scope, resultPushScope, userInput: InputProvider) {
async function rollAttack(attack, scope: any, resultPushScope, userInput: InputProvider) {
const advantage: 0 | 1 | -1 = await userInput.advantage(
(!!attack.advantage && !attack.disadvantage) ? 1 :
(!attack.advantage && !!attack.disadvantage) ? -1 :
0
);
const rollModifierText = numberToSignedString(attack.value, true);
let value, resultPrefix;
if (scope['~attackAdvantage']?.value === 1) {
if (advantage === 1) {
const [[a, b]] = await userInput.rollDice([{ number: 2, diceSize: 20 }]);
if (a >= b) {
value = a;
@@ -199,7 +206,7 @@ async function rollAttack(attack, scope, resultPushScope, userInput: InputProvid
value = b;
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText}`;
}
} else if (scope['~attackAdvantage']?.value === -1) {
} else if (advantage === -1) {
const [[a, b]] = await userInput.rollDice([{ number: 2, diceSize: 20 }]);
if (a <= b) {
value = a;
@@ -216,7 +223,7 @@ async function rollAttack(attack, scope, resultPushScope, userInput: InputProvid
const result = value + attack.value;
resultPushScope['~attackRoll'] = { value: result };
const { criticalHit, criticalMiss } = applyCrits(value, scope, resultPushScope);
return { resultPrefix, result, value, criticalHit, criticalMiss };
return { resultPrefix, result, value, criticalHit, criticalMiss, advantage };
}
function applyCrits(value, scope, resultPushScope) {

View File

@@ -1,4 +1,9 @@
type InputProvider = {
/**
* Roll dice
* @param dice How many dice
* @param diceSize How many faces per die
*/
rollDice(
dice: { number: number, diceSize: number }[]
): Promise<number[][]>;
@@ -12,6 +17,10 @@ type InputProvider = {
choices: ({ _id: string } & Record<string, any>)[],
quantity?: [min: number, max: number],
): Promise<string[]>;
/**
* Get advantage, natural, or disadvantage for a d20 roll
*/
advantage(suggestedAdvantage: 0 | 1 | -1): Promise<0 | 1 | -1>;
}
export default InputProvider;

View File

@@ -10,10 +10,11 @@ const inputProviderForTests: InputProvider = {
const result: number[][] = [];
for (const diceRoll of dice) {
const averageRoll = Math.round(diceRoll.diceSize / 2);
// Return an array full of averagely rolled dice
// Return an array full of averagely rolled dice, increasing by 1 for every dice
result.push(
new Array(diceRoll.number)
.fill(averageRoll)
.map((value, index) => (value + index - 1) % diceRoll.diceSize + 1)
)
}
return result;
@@ -28,6 +29,12 @@ const inputProviderForTests: InputProvider = {
chosen.push(choices[i]._id);
}
return chosen;
},
/**
* For testing, always return the suggested advantage, as if the user never chose differently
*/
async advantage(suggestedAdvantage) {
return suggestedAdvantage;
}
}