ds4/spec/item/spell/calculate-spell-price.spec.ts

211 lines
7.3 KiB
TypeScript
Raw Normal View History

// SPDX-FileCopyrightText: 2022 Johannes Loher
//
// SPDX-License-Identifier: MIT
import { calculateSpellPrice } from "../../../src/item/spell/calculate-spell-price";
import type { CooldownDuration, DS4SpellDataSourceData } from "../../../src/item/spell/spell-data-source";
const defaultData: DS4SpellDataSourceData = {
description: "",
equipped: false,
spellType: "spellcasting",
bonus: "",
spellCategory: "unset",
maxDistance: {
value: "",
unit: "meter",
},
effectRadius: {
value: "",
unit: "meter",
},
duration: {
value: "",
unit: "custom",
},
cooldownDuration: "0r",
manaCost: {
healer: null,
wizard: null,
sorcerer: null,
},
minimumLevels: {
healer: null,
wizard: null,
sorcerer: null,
},
};
type TestCase = {
minimumLevel: number | null;
expected: number | null;
};
type CombinedTestCase = {
minimumLevels: DS4SpellDataSourceData["minimumLevels"];
expected: number | null;
};
const testCases: Record<keyof DS4SpellDataSourceData["minimumLevels"], TestCase[]> = {
healer: [
{ minimumLevel: null, expected: null },
{ minimumLevel: 1, expected: 10 },
{ minimumLevel: 2, expected: 45 },
{ minimumLevel: 3, expected: 80 },
{ minimumLevel: 4, expected: 115 },
{ minimumLevel: 5, expected: 150 },
{ minimumLevel: 6, expected: 185 },
{ minimumLevel: 7, expected: 220 },
{ minimumLevel: 8, expected: 255 },
{ minimumLevel: 9, expected: 290 },
{ minimumLevel: 10, expected: 325 },
{ minimumLevel: 11, expected: 360 },
{ minimumLevel: 12, expected: 395 },
{ minimumLevel: 13, expected: 430 },
{ minimumLevel: 14, expected: 465 },
{ minimumLevel: 15, expected: 500 },
{ minimumLevel: 16, expected: 535 },
{ minimumLevel: 17, expected: 570 },
{ minimumLevel: 18, expected: 605 },
{ minimumLevel: 19, expected: 640 },
{ minimumLevel: 20, expected: 675 },
],
sorcerer: [
{ minimumLevel: null, expected: null },
{ minimumLevel: 1, expected: 10 },
{ minimumLevel: 2, expected: 75 },
{ minimumLevel: 3, expected: 140 },
{ minimumLevel: 4, expected: 205 },
{ minimumLevel: 5, expected: 270 },
{ minimumLevel: 6, expected: 335 },
{ minimumLevel: 7, expected: 400 },
{ minimumLevel: 8, expected: 465 },
{ minimumLevel: 9, expected: 530 },
{ minimumLevel: 10, expected: 595 },
{ minimumLevel: 11, expected: 660 },
{ minimumLevel: 12, expected: 725 },
{ minimumLevel: 13, expected: 790 },
{ minimumLevel: 14, expected: 855 },
{ minimumLevel: 15, expected: 920 },
{ minimumLevel: 16, expected: 985 },
{ minimumLevel: 17, expected: 1050 },
{ minimumLevel: 18, expected: 1115 },
{ minimumLevel: 19, expected: 1180 },
{ minimumLevel: 20, expected: 1245 },
],
wizard: [
{ minimumLevel: null, expected: null },
{ minimumLevel: 1, expected: 10 },
{ minimumLevel: 2, expected: 60 },
{ minimumLevel: 3, expected: 110 },
{ minimumLevel: 4, expected: 160 },
{ minimumLevel: 5, expected: 210 },
{ minimumLevel: 6, expected: 260 },
{ minimumLevel: 7, expected: 310 },
{ minimumLevel: 8, expected: 360 },
{ minimumLevel: 9, expected: 410 },
{ minimumLevel: 10, expected: 460 },
{ minimumLevel: 11, expected: 510 },
{ minimumLevel: 12, expected: 560 },
{ minimumLevel: 13, expected: 610 },
{ minimumLevel: 14, expected: 660 },
{ minimumLevel: 15, expected: 710 },
{ minimumLevel: 16, expected: 760 },
{ minimumLevel: 17, expected: 810 },
{ minimumLevel: 18, expected: 860 },
{ minimumLevel: 19, expected: 910 },
{ minimumLevel: 20, expected: 960 },
],
};
function buildCombinedTestCases(): CombinedTestCase[] {
const combinedTestCases: CombinedTestCase[] = [];
// permutation test cases
const isRelevantPermutationTestCase = (t: TestCase) =>
([null, 1, 10, 20] as (number | null)[]).includes(t.minimumLevel);
for (const healerTestCase of testCases.healer.filter(isRelevantPermutationTestCase)) {
for (const sorcererTestCase of testCases.sorcerer.filter(isRelevantPermutationTestCase)) {
for (const wizardTestCase of testCases.wizard.filter(isRelevantPermutationTestCase)) {
const expected =
healerTestCase.expected !== null ||
sorcererTestCase.expected !== null ||
wizardTestCase.expected !== null
? Math.min(
healerTestCase.expected ?? Infinity,
sorcererTestCase.expected ?? Infinity,
wizardTestCase.expected ?? Infinity,
)
: null;
combinedTestCases.push({
minimumLevels: {
healer: healerTestCase.minimumLevel,
sorcerer: sorcererTestCase.minimumLevel,
wizard: wizardTestCase.minimumLevel,
},
expected,
});
}
}
}
// single test cases
const isRelevantSingleTestCase = (t: TestCase) => t.minimumLevel !== null;
for (const spellCasterClass of ["healer", "sorcerer", "wizard"] as const) {
for (const testCase of testCases[spellCasterClass].filter(isRelevantSingleTestCase)) {
combinedTestCases.push({
minimumLevels: {
...defaultData.minimumLevels,
[spellCasterClass]: testCase.minimumLevel,
},
expected: testCase.expected,
});
}
}
return combinedTestCases;
}
describe("calculateSpellPrice", () => {
const cooldownDurations: { cooldownDuration: CooldownDuration; factor: number }[] = [
{ cooldownDuration: "0r", factor: 1 },
{ cooldownDuration: "1r", factor: 1 },
{ cooldownDuration: "2r", factor: 1 },
{ cooldownDuration: "5r", factor: 1 },
{ cooldownDuration: "10r", factor: 1 },
{ cooldownDuration: "100r", factor: 1 },
{ cooldownDuration: "1d", factor: 2 },
{ cooldownDuration: "d20d", factor: 3 },
];
describe.each(cooldownDurations)(
"with cooldown duration set to $cooldownDuration",
({ cooldownDuration, factor }) => {
const dataWithCooldownDuration = {
...defaultData,
cooldownDuration,
};
it.each(buildCombinedTestCases())(
`returns ${factor} × $expected if the minimum leves are $minimumLevels`,
({ minimumLevels, expected }) => {
// given
const data: DS4SpellDataSourceData = {
...dataWithCooldownDuration,
minimumLevels,
};
// when
const spellPrice = calculateSpellPrice(data);
// then
expect(spellPrice).toBe(expected !== null ? expected * factor : expected);
},
);
},
);
});