ds4/spec/item/spell/calculate-spell-price.spec.ts
Johannes Loher 9d7c570553 feat: replace spell category by spell groups
This also allows to assign a spell to multiple spell groups, which is the case for many spells in
the SRD.

Additionally, this makes many small improvements and fixes to the provided spell compendium.
2022-11-04 21:14:32 +01:00

235 lines
8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-FileCopyrightText: 2022 Johannes Loher
//
// SPDX-License-Identifier: MIT
import { describe, expect, it } from "vitest";
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",
spellModifier: {
numerical: 0,
complex: "",
},
spellGroups: {
lightning: false,
earth: false,
water: false,
ice: false,
fire: false,
healing: false,
light: false,
air: false,
transport: false,
damage: false,
shadow: false,
protection: false,
mindAffecting: false,
demonology: false,
necromancy: false,
transmutation: false,
area: false,
},
maxDistance: {
value: "",
unit: "meter",
},
effectRadius: {
value: "",
unit: "meter",
},
duration: {
value: "",
unit: "custom",
},
cooldownDuration: "0r",
minimumLevels: {
healer: null,
wizard: null,
sorcerer: null,
},
};
type TestCase = {
minimumLevel: number | null;
expected: number | null;
};
type CombinedTestCase = {
minimumLevels: DS4SpellDataSourceData["minimumLevels"];
expected: number | null;
description: string;
};
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;
const minimumLevels = {
healer: healerTestCase.minimumLevel,
sorcerer: sorcererTestCase.minimumLevel,
wizard: wizardTestCase.minimumLevel,
};
const description = JSON.stringify(minimumLevels);
combinedTestCases.push({
minimumLevels,
expected,
description,
});
}
}
}
// 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)) {
const minimumLevels = {
...defaultData.minimumLevels,
[spellCasterClass]: testCase.minimumLevel,
};
const description = JSON.stringify(minimumLevels);
combinedTestCases.push({
minimumLevels,
expected: testCase.expected,
description,
});
}
}
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 $description`,
({ minimumLevels, expected }) => {
// given
const data: DS4SpellDataSourceData = {
...dataWithCooldownDuration,
minimumLevels,
};
// when
const spellPrice = calculateSpellPrice(data);
// then
expect(spellPrice).toBe(expected !== null ? expected * factor : expected);
},
);
},
);
});