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:
parent
ab31450dd8
commit
9d7c570553
20 changed files with 3615 additions and 466 deletions
35
lang/de.json
35
lang/de.json
|
@ -118,20 +118,31 @@
|
||||||
"DS4.SortBySpellType": "Nach Zauberspruchtyp sortieren",
|
"DS4.SortBySpellType": "Nach Zauberspruchtyp sortieren",
|
||||||
"DS4.SpellTypeSpellcasting": "Zaubern",
|
"DS4.SpellTypeSpellcasting": "Zaubern",
|
||||||
"DS4.SpellTypeTargetedSpellcasting": "Zielzaubern",
|
"DS4.SpellTypeTargetedSpellcasting": "Zielzaubern",
|
||||||
"DS4.SpellCategory": "Kategorie",
|
"DS4.SpellGroups": "Zaubergruppen",
|
||||||
"DS4.SpellCategoryDescription": "Eine Kategorie, der der Zauberspruch zugehörig ist.",
|
"DS4.SpellGroupsDescription": "Zaubergruppen, denen der Zauberspruch zugehörig ist.",
|
||||||
"DS4.SpellCategoryHealing": "Heilung",
|
"DS4.SpellGroupLightning": "Blitz",
|
||||||
"DS4.SpellCategoryFire": "Feuer",
|
"DS4.SpellGroupEarth": "Erde, Fels, Stein",
|
||||||
"DS4.SpellCategoryIce": "Eis",
|
"DS4.SpellGroupWater": "Wasser",
|
||||||
"DS4.SpellCategoryLight": "Licht",
|
"DS4.SpellGroupIce": "Eis, Frost",
|
||||||
"DS4.SpellCategoryDarkness": "Schatten",
|
"DS4.SpellGroupFire": "Feuer",
|
||||||
"DS4.SpellCategoryMindAffecting": "Geistesbeeinflussend",
|
"DS4.SpellGroupHealing": "Heilung",
|
||||||
"DS4.SpellCategoryElectricity": "Elektrizität",
|
"DS4.SpellGroupLight": "Licht",
|
||||||
"DS4.SpellCategoryNone": "Keine",
|
"DS4.SpellGroupAir": "Luft",
|
||||||
"DS4.SpellCategoryUnset": "Nicht gesetzt",
|
"DS4.SpellGroupTransport": "Transport",
|
||||||
|
"DS4.SpellGroupDamage": "Schaden",
|
||||||
|
"DS4.SpellGroupShadow": "Schatten",
|
||||||
|
"DS4.SpellGroupProtection": "Schutz",
|
||||||
|
"DS4.SpellGroupMindAffecting": "Geistesbeeinflussend",
|
||||||
|
"DS4.SpellGroupDemonology": "Dämonologie",
|
||||||
|
"DS4.SpellGroupNecromancy": "Nekromantie",
|
||||||
|
"DS4.SpellGroupTransmutation": "Verwandlung",
|
||||||
|
"DS4.SpellGroupArea": "Fläche",
|
||||||
"DS4.SpellModifier": "Zauberbonus",
|
"DS4.SpellModifier": "Zauberbonus",
|
||||||
|
"DS4.SpellModifierNumerical": "Zauberbonus (numerisch)",
|
||||||
|
"DS4.SpellModifierComplex": "Zauberbonus (komplex)",
|
||||||
"DS4.SpellModifierAbbr": "ZB",
|
"DS4.SpellModifierAbbr": "ZB",
|
||||||
"DS4.SpellModifierDescription": "Der Zauberbonus auf die Probe.",
|
"DS4.SpellModifierNumericalDescription": "Der numerische Zauberbonus auf die Probe.",
|
||||||
|
"DS4.SpellModifierComplexDescription": "Ein komplexer Zauberbonus auf die Probe (zum Beispiel abhängig von Werten des Ziels). Wenn diese Art von Zauberbonus angegeben ist, wird der numerische ignoriert.",
|
||||||
"DS4.SortBySpellModifier": "Nach Zauberbonus sortieren",
|
"DS4.SortBySpellModifier": "Nach Zauberbonus sortieren",
|
||||||
"DS4.SpellDistance": "Distanz",
|
"DS4.SpellDistance": "Distanz",
|
||||||
"DS4.SpellDistanceDescription": "Die maximale Entfernung zum Ziel. „Selbst“ bedeutet, dass nur der Zauberwirker selbst das Ziel des Zaubers sein kann.",
|
"DS4.SpellDistanceDescription": "Die maximale Entfernung zum Ziel. „Selbst“ bedeutet, dass nur der Zauberwirker selbst das Ziel des Zaubers sein kann.",
|
||||||
|
|
35
lang/en.json
35
lang/en.json
|
@ -118,20 +118,31 @@
|
||||||
"DS4.SortBySpellType": "Sort by Spell Type",
|
"DS4.SortBySpellType": "Sort by Spell Type",
|
||||||
"DS4.SpellTypeSpellcasting": "Spellcasting",
|
"DS4.SpellTypeSpellcasting": "Spellcasting",
|
||||||
"DS4.SpellTypeTargetedSpellcasting": "Targeted Spellcasting",
|
"DS4.SpellTypeTargetedSpellcasting": "Targeted Spellcasting",
|
||||||
"DS4.SpellCategory": "Category",
|
"DS4.SpellGroups": "Spell Groups",
|
||||||
"DS4.SpellCategoryDescription": "A category which the spell belongs to.",
|
"DS4.SpellGroupsDescription": "Spell groups which the spell belongs to.",
|
||||||
"DS4.SpellCategoryHealing": "Healing",
|
"DS4.SpellGroupLightning": "Lightning",
|
||||||
"DS4.SpellCategoryFire": "Fire",
|
"DS4.SpellGroupEarth": "Earth, Rock, Stone",
|
||||||
"DS4.SpellCategoryIce": "Ice",
|
"DS4.SpellGroupWater": "Water",
|
||||||
"DS4.SpellCategoryLight": "Light",
|
"DS4.SpellGroupIce": "Ice, Frost",
|
||||||
"DS4.SpellCategoryDarkness": "Darkness",
|
"DS4.SpellGroupFire": "Fire",
|
||||||
"DS4.SpellCategoryMindAffecting": "Mind Affecting",
|
"DS4.SpellGroupHealing": "Healing",
|
||||||
"DS4.SpellCategoryElectricity": "Electricity",
|
"DS4.SpellGroupLight": "Light",
|
||||||
"DS4.SpellCategoryNone": "None",
|
"DS4.SpellGroupAir": "Air",
|
||||||
"DS4.SpellCategoryUnset": "Unset",
|
"DS4.SpellGroupTransport": "Transport",
|
||||||
|
"DS4.SpellGroupDamage": "Damage",
|
||||||
|
"DS4.SpellGroupShadow": "Shadow",
|
||||||
|
"DS4.SpellGroupProtection": "Protection",
|
||||||
|
"DS4.SpellGroupMindAffecting": "Mind Affecting",
|
||||||
|
"DS4.SpellGroupDemonology": "Demonologie",
|
||||||
|
"DS4.SpellGroupNecromancy": "Necromancy",
|
||||||
|
"DS4.SpellGroupTransmutation": "Transmutation",
|
||||||
|
"DS4.SpellGroupArea": "Area",
|
||||||
"DS4.SpellModifier": "Spell Modifier",
|
"DS4.SpellModifier": "Spell Modifier",
|
||||||
|
"DS4.SpellModifierNumerical": "Spell Modifier (numerical)",
|
||||||
|
"DS4.SpellModifierComplex": "Spell Modifier (complex)",
|
||||||
"DS4.SpellModifierAbbr": "SM",
|
"DS4.SpellModifierAbbr": "SM",
|
||||||
"DS4.SpellModifierDescription": "The spell modifier for the corresponding check.",
|
"DS4.SpellModifierNumericalDescription": "The numerical spell modifier for the corresponding check.",
|
||||||
|
"DS4.SpellModifierComplexDescription": "A complex spell modifier for the corresponding check (for example, dependent on the target’s values). If given, the numerical spell bonus is ignored.",
|
||||||
"DS4.SortBySpellModifier": "Sort by Spell Modifier",
|
"DS4.SortBySpellModifier": "Sort by Spell Modifier",
|
||||||
"DS4.SpellDistance": "Distance",
|
"DS4.SpellDistance": "Distance",
|
||||||
"DS4.SpellDistanceDescription": "The maximum distance to the target, “Self” meaning that only the caster can be the target of this spell.",
|
"DS4.SpellDistanceDescription": "The maximum distance to the target, “Self” meaning that only the caster can be the target of this spell.",
|
||||||
|
|
|
@ -6098,7 +6098,7 @@
|
||||||
"type": "loot",
|
"type": "loot",
|
||||||
"img": "icons/consumables/potions/bottle-conical-corked-labeled-shell-cyan.webp",
|
"img": "icons/consumables/potions/bottle-conical-corked-labeled-shell-cyan.webp",
|
||||||
"data": {
|
"data": {
|
||||||
"description": null,
|
"description": "<p>Weihwasser verursacht gegen Dämonen und Untote nicht abwehrbaren Schaden. Jede Einheit Weihwasser hat einen anderen Angriffswert, der mit W20 ermittelt wird. Dieser Wert wird erst ausgewürfelt, wenn das Weihwasser den Dämonen bzw. Untoten trifft, es sei denn, es wird vorher in Bezug auf seinen Schadenswert von einem Zauberwirker mit GEI+AU, gefolgt von GEI+VE, erfolgreich analysiert.</p>\n<p>Eine Weihwassereinheit kann man auf eine Waffe/ein Geschoss auftragen (benötigt 1 Aktion) und dann einen normalen Angriff mit Schlagen bzw. Schießen würfeln. Ist dieser erfolgreich, wird bei Dämonen und Untoten neben dem normalen Schaden auch noch ein Angriff für das Weihwasser gewürfelt, der nicht abwehrbaren Schaden verursacht. Nach dem ersten Treffer ist die Einheit Weihwasser aufgebraucht.</p>\n<p>Alternativ kann man Weihwassereinheiten in zerbrechliche Phiolen (WB +0; 2 GM) füllen und diese im Nah- oder Fernkampf gegen Dämonen und Untote einsetzen, wobei die zerbrechlichen Gefäße zerspringen. In solchen Fällen verursacht nur das Weihwasser Schaden, nicht die Schießen-Probe.</p>\n<p>Weihwasser kann außerdem dazu benutzt werden, in schützenden Linien oder Kreisen (1 m pro Einheit) auf den Boden geschüttet zu werden, um für eine gewisse Zeit Dämonen bzw. Untote aufzuhalten, die das Weihwasser nicht passieren können.</p>",
|
||||||
"quantity": 1,
|
"quantity": 1,
|
||||||
"price": 0.1,
|
"price": 0.1,
|
||||||
"availability": "hamlet",
|
"availability": "hamlet",
|
||||||
|
|
3644
packs/spells.json
3644
packs/spells.json
File diff suppressed because one or more lines are too long
27
scss/components/shared/_checkbox_grid.scss
Normal file
27
scss/components/shared/_checkbox_grid.scss
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2022 Johannes Loher
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
.ds4-checkbox-grid {
|
||||||
|
$gap: 3px;
|
||||||
|
gap: $gap;
|
||||||
|
display: grid;
|
||||||
|
font-size: var(--font-size-12);
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
gap: $gap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
> * {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__checkbox[type="checkbox"] {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
27
scss/components/shared/_form_group.scss
Normal file
27
scss/components/shared/_form_group.scss
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2022 Johannes Loher
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
.ds4-form-group {
|
||||||
|
clear: both;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: 3px 0;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&--start {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
flex: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
flex: 2;
|
||||||
|
line-height: var(--form-field-height);
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,8 +14,10 @@
|
||||||
// shared
|
// shared
|
||||||
@use "components/shared/add_button";
|
@use "components/shared/add_button";
|
||||||
@use "components/shared/control_button_group";
|
@use "components/shared/control_button_group";
|
||||||
|
@use "components/shared/checkbox_grid";
|
||||||
@use "components/shared/editor";
|
@use "components/shared/editor";
|
||||||
@use "components/shared/embedded_document_list";
|
@use "components/shared/embedded_document_list";
|
||||||
|
@use "components/shared/form_group";
|
||||||
@use "components/shared/rollable_image";
|
@use "components/shared/rollable_image";
|
||||||
@use "components/shared/sheet_body";
|
@use "components/shared/sheet_body";
|
||||||
@use "components/shared/sheet_form";
|
@use "components/shared/sheet_form";
|
||||||
|
|
|
@ -12,8 +12,29 @@ const defaultData: DS4SpellDataSourceData = {
|
||||||
description: "",
|
description: "",
|
||||||
equipped: false,
|
equipped: false,
|
||||||
spellType: "spellcasting",
|
spellType: "spellcasting",
|
||||||
bonus: "",
|
spellModifier: {
|
||||||
spellCategory: "unset",
|
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: {
|
maxDistance: {
|
||||||
value: "",
|
value: "",
|
||||||
unit: "meter",
|
unit: "meter",
|
||||||
|
|
|
@ -13,8 +13,7 @@ export function disableOverriddenFields(
|
||||||
const titleAddition = `(${getGame().i18n.localize("DS4.TooltipNotEditableDueToEffects")})`;
|
const titleAddition = `(${getGame().i18n.localize("DS4.TooltipNotEditableDueToEffects")})`;
|
||||||
|
|
||||||
for (const key of Object.keys(foundry.utils.flattenObject(overrides))) {
|
for (const key of Object.keys(foundry.utils.flattenObject(overrides))) {
|
||||||
const sel = selector(key);
|
const elements = form?.querySelectorAll(selector(key));
|
||||||
const elements = form?.querySelectorAll(sel);
|
|
||||||
elements?.forEach((element) => {
|
elements?.forEach((element) => {
|
||||||
if (inputs.includes(element.tagName)) {
|
if (inputs.includes(element.tagName)) {
|
||||||
element.setAttribute("disabled", "");
|
element.setAttribute("disabled", "");
|
||||||
|
|
|
@ -94,16 +94,24 @@ const i18nKeys = {
|
||||||
targetedSpellcasting: "DS4.SpellTypeTargetedSpellcasting",
|
targetedSpellcasting: "DS4.SpellTypeTargetedSpellcasting",
|
||||||
},
|
},
|
||||||
|
|
||||||
spellCategories: {
|
spellGroups: {
|
||||||
healing: "DS4.SpellCategoryHealing",
|
lightning: "DS4.SpellGroupLightning",
|
||||||
fire: "DS4.SpellCategoryFire",
|
earth: "DS4.SpellGroupEarth",
|
||||||
ice: "DS4.SpellCategoryIce",
|
water: "DS4.SpellGroupWater",
|
||||||
light: "DS4.SpellCategoryLight",
|
ice: "DS4.SpellGroupIce",
|
||||||
darkness: "DS4.SpellCategoryDarkness",
|
fire: "DS4.SpellGroupFire",
|
||||||
mindAffecting: "DS4.SpellCategoryMindAffecting",
|
healing: "DS4.SpellGroupHealing",
|
||||||
electricity: "DS4.SpellCategoryElectricity",
|
light: "DS4.SpellGroupLight",
|
||||||
none: "DS4.SpellCategoryNone",
|
air: "DS4.SpellGroupAir",
|
||||||
unset: "DS4.SpellCategoryUnset",
|
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: {
|
cooldownDurations: {
|
||||||
|
|
|
@ -23,7 +23,7 @@ function localizeAndSortConfigObjects() {
|
||||||
"combatValues",
|
"combatValues",
|
||||||
"cooldownDurations",
|
"cooldownDurations",
|
||||||
"creatureSizeCategories",
|
"creatureSizeCategories",
|
||||||
"spellCategories",
|
"spellGroups",
|
||||||
"traits",
|
"traits",
|
||||||
"checkModifiers",
|
"checkModifiers",
|
||||||
];
|
];
|
||||||
|
|
|
@ -12,8 +12,11 @@ export interface DS4SpellDataSource {
|
||||||
|
|
||||||
export interface DS4SpellDataSourceData extends DS4ItemDataSourceDataBase, DS4ItemDataSourceDataEquipable {
|
export interface DS4SpellDataSourceData extends DS4ItemDataSourceDataBase, DS4ItemDataSourceDataEquipable {
|
||||||
spellType: keyof typeof DS4.i18n.spellTypes;
|
spellType: keyof typeof DS4.i18n.spellTypes;
|
||||||
bonus: string;
|
spellModifier: {
|
||||||
spellCategory: keyof typeof DS4.i18n.spellCategories;
|
numerical: number;
|
||||||
|
complex: string;
|
||||||
|
};
|
||||||
|
spellGroups: Record<keyof typeof DS4.i18n.spellGroups, boolean>;
|
||||||
maxDistance: UnitData<DistanceUnit>;
|
maxDistance: UnitData<DistanceUnit>;
|
||||||
effectRadius: UnitData<DistanceUnit>;
|
effectRadius: UnitData<DistanceUnit>;
|
||||||
duration: UnitData<TemporalUnit>;
|
duration: UnitData<TemporalUnit>;
|
||||||
|
|
|
@ -32,17 +32,19 @@ export class DS4Spell extends DS4Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ownerDataData = this.actor.data.data;
|
const ownerDataData = this.actor.data.data;
|
||||||
const spellModifier = Number.isNumeric(this.data.data.bonus) ? parseInt(this.data.data.bonus) : undefined;
|
const hasComplexModifier = this.data.data.spellModifier.complex !== "";
|
||||||
if (spellModifier === undefined) {
|
if (hasComplexModifier === undefined) {
|
||||||
notifications.info(
|
notifications.info(
|
||||||
game.i18n.format("DS4.InfoManuallyEnterSpellModifier", {
|
game.i18n.format("DS4.InfoManuallyEnterSpellModifier", {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
spellModifier: this.data.data.bonus,
|
spellModifier: this.data.data.spellModifier.complex,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const spellType = this.data.data.spellType;
|
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 });
|
const speaker = ChatMessage.getSpeaker({ actor: this.actor, ...options.speaker });
|
||||||
await createCheckRoll(checkTargetNumber, {
|
await createCheckRoll(checkTargetNumber, {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { migration as migration002 } from "./migrations/002";
|
||||||
import { migration as migration003 } from "./migrations/003";
|
import { migration as migration003 } from "./migrations/003";
|
||||||
import { migration as migration004 } from "./migrations/004";
|
import { migration as migration004 } from "./migrations/004";
|
||||||
import { migration as migration005 } from "./migrations/005";
|
import { migration as migration005 } from "./migrations/005";
|
||||||
|
import { migration as migration006 } from "./migrations/006";
|
||||||
import notifications from "./ui/notifications";
|
import notifications from "./ui/notifications";
|
||||||
|
|
||||||
async function migrate(): Promise<void> {
|
async function migrate(): Promise<void> {
|
||||||
|
@ -135,7 +136,7 @@ interface Migration {
|
||||||
migrateCompendium: (pack: CompendiumCollection<CompendiumCollection.Metadata>) => Promise<void>;
|
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 {
|
function isFirstWorldStart(migrationVersion: number): boolean {
|
||||||
return migrationVersion < 0;
|
return migrationVersion < 0;
|
||||||
|
|
117
src/migrations/006.ts
Normal file
117
src/migrations/006.ts
Normal 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,
|
||||||
|
};
|
|
@ -72,7 +72,7 @@ type CompendiumMigrator = (compendium: CompendiumCollection<CompendiumCollection
|
||||||
export async function migrateCompendiums(migrateCompendium: CompendiumMigrator): Promise<void> {
|
export async function migrateCompendiums(migrateCompendium: CompendiumMigrator): Promise<void> {
|
||||||
for (const compendium of getGame().packs ?? []) {
|
for (const compendium of getGame().packs ?? []) {
|
||||||
if (compendium.metadata.package !== "world") continue;
|
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);
|
await migrateCompendium(compendium);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,8 +144,8 @@ export function getCompendiumMigrator(
|
||||||
{ migrateToTemplateEarly = true } = {},
|
{ migrateToTemplateEarly = true } = {},
|
||||||
) {
|
) {
|
||||||
return async (compendium: CompendiumCollection<CompendiumCollection.Metadata>): Promise<void> => {
|
return async (compendium: CompendiumCollection<CompendiumCollection.Metadata>): Promise<void> => {
|
||||||
const entityName = compendium.metadata.entity;
|
const type = compendium.metadata.type;
|
||||||
if (!["Actor", "Item", "Scene"].includes(entityName)) return;
|
if (!["Actor", "Item", "Scene"].includes(type)) return;
|
||||||
const wasLocked = compendium.locked;
|
const wasLocked = compendium.locked;
|
||||||
await compendium.configure({ locked: false });
|
await compendium.configure({ locked: false });
|
||||||
if (migrateToTemplateEarly) {
|
if (migrateToTemplateEarly) {
|
||||||
|
|
|
@ -173,8 +173,29 @@
|
||||||
"spell": {
|
"spell": {
|
||||||
"templates": ["base", "equipable"],
|
"templates": ["base", "equipable"],
|
||||||
"spellType": "spellcasting",
|
"spellType": "spellcasting",
|
||||||
"bonus": "",
|
"spellModifier": {
|
||||||
"spellCategory": "unset",
|
"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": {
|
"maxDistance": {
|
||||||
"value": "",
|
"value": "",
|
||||||
"unit": "meter"
|
"unit": "meter"
|
||||||
|
|
|
@ -53,9 +53,10 @@ titleKey=titleKey}}
|
||||||
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="data.spellType"
|
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="data.spellType"
|
||||||
title="{{localize 'DS4.SortBySpellType'}}">{{localize 'DS4.SpellTypeAbbr'}}</div>
|
title="{{localize 'DS4.SortBySpellType'}}">{{localize 'DS4.SpellTypeAbbr'}}</div>
|
||||||
|
|
||||||
{{!-- spell bonus --}}
|
{{!-- spell modifier --}}
|
||||||
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="data.bonus"
|
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="data.spellModifier.complex"
|
||||||
title="{{localize 'DS4.SortBySpellModifier'}}">{{localize 'DS4.SpellModifierAbbr'}}</div>
|
data-data-path2="data.spellModifier.numerical" title="{{localize 'DS4.SortBySpellModifier'}}">{{localize
|
||||||
|
'DS4.SpellModifierAbbr'}}</div>
|
||||||
|
|
||||||
{{!-- max. distance --}}
|
{{!-- max. distance --}}
|
||||||
<div title="{{localize 'DS4.SpellDistance'}}"><i class="fas fa-ruler"></i></div>
|
<div title="{{localize 'DS4.SpellDistance'}}"><i class="fas fa-ruler"></i></div>
|
||||||
|
@ -75,9 +76,9 @@ titleKey=titleKey}}
|
||||||
src="{{lookup @root/config.icons.spellTypes itemData.data.spellType}}"
|
src="{{lookup @root/config.icons.spellTypes itemData.data.spellType}}"
|
||||||
title="{{lookup @root/config.i18n.spellTypes itemData.data.spellType}}" />
|
title="{{lookup @root/config.i18n.spellTypes itemData.data.spellType}}" />
|
||||||
|
|
||||||
{{!-- spell bonus --}}
|
{{!-- spell modifier --}}
|
||||||
<input class="ds4-embedded-document-list__editable change-item" type="text" data-dtype="String"
|
<div title="{{localize 'DS4.SpellModifier'}}">{{#if (eq itemData.data.spellModifier.complex
|
||||||
data-property="data.bonus" value="{{itemData.data.bonus}}" title="{{localize 'DS4.SpellModifier'}}" />
|
'')}}{{itemData.data.spellModifier.numerical}}{{else}}{{itemData.data.spellModifier.complex}}{{/if}}</div>
|
||||||
|
|
||||||
{{!-- max. distance --}}
|
{{!-- max. distance --}}
|
||||||
{{> distanceUnit titleKey='DS4.SpellDistance' unitDatum=itemData.data.maxDistance
|
{{> distanceUnit titleKey='DS4.SpellDistance' unitDatum=itemData.data.maxDistance
|
||||||
|
|
|
@ -7,11 +7,21 @@ SPDX-License-Identifier: MIT
|
||||||
<div class="ds4-item-properties ds4-item-properties--spell">
|
<div class="ds4-item-properties ds4-item-properties--spell">
|
||||||
<h4 class="ds4-item-properties__title">{{localize 'DS4.ItemPropertiesSpell'}}</h4>
|
<h4 class="ds4-item-properties__title">{{localize 'DS4.ItemPropertiesSpell'}}</h4>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="data.bonus-{{data._id}}" title="{{localize 'DS4.SpellModifierDescription'}}">{{localize
|
<label for="data.spellModifier.numerical-{{data._id}}"
|
||||||
"DS4.SpellModifier"}}</label>
|
title="{{localize 'DS4.SpellModifierNumericalDescription'}}">{{localize
|
||||||
|
"DS4.SpellModifierNumerical"}}</label>
|
||||||
<div class="form-fields">
|
<div class="form-fields">
|
||||||
<input id="data.bonus-{{data._id}}" data-dtype="String" type="text" name="data.bonus" placeholder="0"
|
<input id="data.spellModifier.numerical-{{data._id}}" data-dtype="Number" type="number"
|
||||||
value="{{data.data.bonus}}" />
|
name="data.spellModifier.numerical" placeholder="0" value="{{data.data.spellModifier.numerical}}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="data.spellModifier.complex-{{data._id}}"
|
||||||
|
title="{{localize 'DS4.SpellModifierComplexDescription'}}">{{localize
|
||||||
|
"DS4.SpellModifierComplex"}}</label>
|
||||||
|
<div class="form-fields">
|
||||||
|
<input id="data.spellModifier.complex-{{data._id}}" data-dtype="String" type="text"
|
||||||
|
name="data.spellModifier.complex" value="{{data.data.spellModifier.complex}}" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -27,19 +37,6 @@ SPDX-License-Identifier: MIT
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
|
||||||
<label for="data.spellCategory-{{data._id}}" title="{{localize 'DS4.SpellCategoryDescription'}}">{{localize
|
|
||||||
"DS4.SpellCategory"}}</label>
|
|
||||||
<div class="form-fields">
|
|
||||||
<select id="data.spellCategory-{{data._id}}" name="data.spellCategory" data-dtype="String">
|
|
||||||
{{#select data.data.spellCategory}}
|
|
||||||
{{#each config.i18n.spellCategories as |value key|}}
|
|
||||||
<option value="{{key}}">{{value}}</option>
|
|
||||||
{{/each}}
|
|
||||||
{{/select}}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group slim">
|
<div class="form-group slim">
|
||||||
<label title="{{localize 'DS4.SpellDistanceDescription'}}">{{localize "DS4.SpellDistance"}}</label>
|
<label title="{{localize 'DS4.SpellDistanceDescription'}}">{{localize "DS4.SpellDistance"}}</label>
|
||||||
<div class="form-fields">
|
<div class="form-fields">
|
||||||
|
@ -94,18 +91,33 @@ SPDX-License-Identifier: MIT
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="ds4-form-group ds4-form-group--start">
|
||||||
|
<label class="ds4-form-group__label" title="{{localize 'DS4.SpellGroupsDescription'}}">{{localize
|
||||||
|
"DS4.SpellGroups"}}</label>
|
||||||
|
<div class="ds4-checkbox-grid">
|
||||||
|
{{#each config.i18n.spellGroups as |value key|}}
|
||||||
|
<div class="ds4-checkbox-grid__item">
|
||||||
|
<input class="ds4-checkbox-grid__checkbox" id="data.spellGroups.{{key}}-{{../data._id}}"
|
||||||
|
name="data.spellGroups.{{key}}" data-dtype="Boolean" type="checkbox" {{checked (lookup
|
||||||
|
../data.data.spellGroups key)}} />
|
||||||
|
<label for="data.spellGroups.{{key}}-{{../data._id}}">{{value}}</label>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group slim">
|
<div class="form-group slim">
|
||||||
<label title="{{localize 'DS4.SpellMinimumLevelDescription'}}">{{localize "DS4.SpellMinimumLevel"}}</label>
|
<label title="{{localize 'DS4.SpellMinimumLevelDescription'}}">{{localize "DS4.SpellMinimumLevel"}}</label>
|
||||||
<div class="form-fields">
|
<div class="form-fields">
|
||||||
<label for="data.minimumLevels.healer-{{data._id}}">{{localize "DS4.SpellCasterClassHealer"}}</label>
|
<label for="data.minimumLevels.healer-{{data._id}}">{{localize "DS4.SpellCasterClassHealer"}}</label>
|
||||||
<input id="data.minimumLevels.healer-{{data._id}}" data-dtype="Number" type="number" min="0" step="1"
|
<input id="data.minimumLevels.healer-{{data._id}}" data-dtype="Number" type="number" min="0" step="1"
|
||||||
name="data.minimumLevels.healer" placeholder="–" value="{{data.data.minimumLevels.healer}}" />
|
name="data.minimumLevels.healer" placeholder="–" value="{{data.data.minimumLevels.healer}}" />
|
||||||
<label for="data.minimumLevels.sorcerer-{{data._id}}">{{localize "DS4.SpellCasterClassSorcerer"}}</label>
|
|
||||||
<input id="data.minimumLevels.sorcerer-{{data._id}}" data-dtype="Number" type="number" min="0" step="1"
|
|
||||||
name="data.minimumLevels.sorcerer" placeholder="–" value="{{data.data.minimumLevels.sorcerer}}" />
|
|
||||||
<label for="data.minimumLevels.wizard-{{data._id}}">{{localize "DS4.SpellCasterClassWizard"}}</label>
|
<label for="data.minimumLevels.wizard-{{data._id}}">{{localize "DS4.SpellCasterClassWizard"}}</label>
|
||||||
<input id="data.minimumLevels.wizard-{{data._id}}" data-dtype="Number" type="number" min="0" step="1"
|
<input id="data.minimumLevels.wizard-{{data._id}}" data-dtype="Number" type="number" min="0" step="1"
|
||||||
name="data.minimumLevels.wizard" placeholder="–" value="{{data.data.minimumLevels.wizard}}" />
|
name="data.minimumLevels.wizard" placeholder="–" value="{{data.data.minimumLevels.wizard}}" />
|
||||||
|
<label for="data.minimumLevels.sorcerer-{{data._id}}">{{localize
|
||||||
|
"DS4.SpellCasterClassSorcerer"}}</label>
|
||||||
|
<input id="data.minimumLevels.sorcerer-{{data._id}}" data-dtype="Number" type="number" min="0" step="1"
|
||||||
|
name="data.minimumLevels.sorcerer" placeholder="–" value="{{data.data.minimumLevels.sorcerer}}" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
|
@ -57,7 +57,7 @@ export async function convertJSONToPack(contents) {
|
||||||
/**
|
/**
|
||||||
* Converts a pack file (NeDB) to a JSON string.
|
* Converts a pack file (NeDB) to a JSON string.
|
||||||
* @param {string} filename The name of the pack file
|
* @param {string} filename The name of the pack file
|
||||||
* @returns {Promise<Array<unknown>>} A promise that resolves to an array of the documents in the pack file
|
* @returns {Promise<string>} A promise that resolves to an array of the documents in the pack file
|
||||||
*/
|
*/
|
||||||
function convertPackFileToJSON(filename) {
|
function convertPackFileToJSON(filename) {
|
||||||
const db = new Datastore({ filename, autoload: true });
|
const db = new Datastore({ filename, autoload: true });
|
||||||
|
|
Loading…
Reference in a new issue