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.
This commit is contained in:
Johannes Loher 2022-11-04 21:08:23 +01:00
parent ab31450dd8
commit 9d7c570553
20 changed files with 3615 additions and 466 deletions

View file

@ -13,8 +13,7 @@ export function disableOverriddenFields(
const titleAddition = `(${getGame().i18n.localize("DS4.TooltipNotEditableDueToEffects")})`;
for (const key of Object.keys(foundry.utils.flattenObject(overrides))) {
const sel = selector(key);
const elements = form?.querySelectorAll(sel);
const elements = form?.querySelectorAll(selector(key));
elements?.forEach((element) => {
if (inputs.includes(element.tagName)) {
element.setAttribute("disabled", "");

View file

@ -94,16 +94,24 @@ const i18nKeys = {
targetedSpellcasting: "DS4.SpellTypeTargetedSpellcasting",
},
spellCategories: {
healing: "DS4.SpellCategoryHealing",
fire: "DS4.SpellCategoryFire",
ice: "DS4.SpellCategoryIce",
light: "DS4.SpellCategoryLight",
darkness: "DS4.SpellCategoryDarkness",
mindAffecting: "DS4.SpellCategoryMindAffecting",
electricity: "DS4.SpellCategoryElectricity",
none: "DS4.SpellCategoryNone",
unset: "DS4.SpellCategoryUnset",
spellGroups: {
lightning: "DS4.SpellGroupLightning",
earth: "DS4.SpellGroupEarth",
water: "DS4.SpellGroupWater",
ice: "DS4.SpellGroupIce",
fire: "DS4.SpellGroupFire",
healing: "DS4.SpellGroupHealing",
light: "DS4.SpellGroupLight",
air: "DS4.SpellGroupAir",
transport: "DS4.SpellGroupTransport",
damage: "DS4.SpellGroupDamage",
shadow: "DS4.SpellGroupShadow",
protection: "DS4.SpellGroupProtection",
mindAffecting: "DS4.SpellGroupMindAffecting",
demonology: "DS4.SpellGroupDemonology",
necromancy: "DS4.SpellGroupNecromancy",
transmutation: "DS4.SpellGroupTransmutation",
area: "DS4.SpellGroupArea",
},
cooldownDurations: {

View file

@ -23,7 +23,7 @@ function localizeAndSortConfigObjects() {
"combatValues",
"cooldownDurations",
"creatureSizeCategories",
"spellCategories",
"spellGroups",
"traits",
"checkModifiers",
];

View file

@ -12,8 +12,11 @@ export interface DS4SpellDataSource {
export interface DS4SpellDataSourceData extends DS4ItemDataSourceDataBase, DS4ItemDataSourceDataEquipable {
spellType: keyof typeof DS4.i18n.spellTypes;
bonus: string;
spellCategory: keyof typeof DS4.i18n.spellCategories;
spellModifier: {
numerical: number;
complex: string;
};
spellGroups: Record<keyof typeof DS4.i18n.spellGroups, boolean>;
maxDistance: UnitData<DistanceUnit>;
effectRadius: UnitData<DistanceUnit>;
duration: UnitData<TemporalUnit>;

View file

@ -32,17 +32,19 @@ export class DS4Spell extends DS4Item {
}
const ownerDataData = this.actor.data.data;
const spellModifier = Number.isNumeric(this.data.data.bonus) ? parseInt(this.data.data.bonus) : undefined;
if (spellModifier === undefined) {
const hasComplexModifier = this.data.data.spellModifier.complex !== "";
if (hasComplexModifier === undefined) {
notifications.info(
game.i18n.format("DS4.InfoManuallyEnterSpellModifier", {
name: this.name,
spellModifier: this.data.data.bonus,
spellModifier: this.data.data.spellModifier.complex,
}),
);
}
const spellType = this.data.data.spellType;
const checkTargetNumber = ownerDataData.combatValues[spellType].total + (spellModifier ?? 0);
const checkTargetNumber =
ownerDataData.combatValues[spellType].total +
(hasComplexModifier ? 0 : this.data.data.spellModifier.numerical);
const speaker = ChatMessage.getSpeaker({ actor: this.actor, ...options.speaker });
await createCheckRoll(checkTargetNumber, {

View file

@ -9,6 +9,7 @@ import { migration as migration002 } from "./migrations/002";
import { migration as migration003 } from "./migrations/003";
import { migration as migration004 } from "./migrations/004";
import { migration as migration005 } from "./migrations/005";
import { migration as migration006 } from "./migrations/006";
import notifications from "./ui/notifications";
async function migrate(): Promise<void> {
@ -135,7 +136,7 @@ interface Migration {
migrateCompendium: (pack: CompendiumCollection<CompendiumCollection.Metadata>) => Promise<void>;
}
const migrations: Migration[] = [migration001, migration002, migration003, migration004, migration005];
const migrations: Migration[] = [migration001, migration002, migration003, migration004, migration005, migration006];
function isFirstWorldStart(migrationVersion: number): boolean {
return migrationVersion < 0;

117
src/migrations/006.ts Normal file
View file

@ -0,0 +1,117 @@
// SPDX-FileCopyrightText: 2022 Johannes Loher
//
// SPDX-License-Identifier: MIT
import {
getActorUpdateDataGetter,
getCompendiumMigrator,
getSceneUpdateDataGetter,
migrateActors,
migrateCompendiums,
migrateItems,
migrateScenes,
} from "./migrationHelpers";
import type { DS4SpellDataSourceData } from "../item/spell/spell-data-source";
async function migrate(): Promise<void> {
await migrateItems(getItemUpdateData);
await migrateActors(getActorUpdateData);
await migrateScenes(getSceneUpdateData);
await migrateCompendiums(migrateCompendium);
}
function getItemUpdateData(itemData: Partial<foundry.data.ItemData["_source"]>) {
if (itemData.type !== "spell") return;
// @ts-expect-error spellCategory is removed with this migration
const spellCategory: string | undefined = itemData.data?.spellCategory;
const spellGroups = migrateSpellCategory(spellCategory);
// @ts-expect-error bonus is removed with this migration
const bonus: string | undefined = itemData.data?.bonus;
const spellModifier = migrateBonus(bonus);
const updateData: Record<string, unknown> = {
data: {
spellGroups,
"-=spellCategory": null,
spellModifier,
"-=bonus": null,
},
};
return updateData;
}
function migrateSpellCategory(spellCategory: string | undefined): DS4SpellDataSourceData["spellGroups"] {
const 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,
};
switch (spellCategory) {
case "healing": {
spellGroups.healing = true;
break;
}
case "fire": {
spellGroups.fire = true;
break;
}
case "ice": {
spellGroups.ice = true;
break;
}
case "light": {
spellGroups.light = true;
break;
}
case "darkness": {
spellGroups.shadow = true;
break;
}
case "mindAffecting": {
spellGroups.mindAffecting = true;
break;
}
case "electricity": {
spellGroups.lightning = true;
break;
}
}
return spellGroups;
}
function migrateBonus(bonus: string | undefined): DS4SpellDataSourceData["spellModifier"] {
const spellModifier = { numerical: 0, complex: "" };
if (bonus) {
if (Number.isNumeric(bonus)) {
spellModifier.numerical = +bonus;
} else {
spellModifier.complex = bonus;
}
}
return spellModifier;
}
const getActorUpdateData = getActorUpdateDataGetter(getItemUpdateData);
const getSceneUpdateData = getSceneUpdateDataGetter(getActorUpdateData);
const migrateCompendium = getCompendiumMigrator({ getItemUpdateData, getActorUpdateData, getSceneUpdateData });
export const migration = {
migrate,
migrateCompendium,
};

View file

@ -72,7 +72,7 @@ type CompendiumMigrator = (compendium: CompendiumCollection<CompendiumCollection
export async function migrateCompendiums(migrateCompendium: CompendiumMigrator): Promise<void> {
for (const compendium of getGame().packs ?? []) {
if (compendium.metadata.package !== "world") continue;
if (!["Actor", "Item", "Scene"].includes(compendium.metadata.entity)) continue;
if (!["Actor", "Item", "Scene"].includes(compendium.metadata.type)) continue;
await migrateCompendium(compendium);
}
}
@ -144,8 +144,8 @@ export function getCompendiumMigrator(
{ migrateToTemplateEarly = true } = {},
) {
return async (compendium: CompendiumCollection<CompendiumCollection.Metadata>): Promise<void> => {
const entityName = compendium.metadata.entity;
if (!["Actor", "Item", "Scene"].includes(entityName)) return;
const type = compendium.metadata.type;
if (!["Actor", "Item", "Scene"].includes(type)) return;
const wasLocked = compendium.locked;
await compendium.configure({ locked: false });
if (migrateToTemplateEarly) {