feat: update for v10

This commit is contained in:
Johannes Loher 2022-11-21 03:00:46 +01:00
parent 6277e27056
commit f25b46a226
63 changed files with 41349 additions and 24332 deletions

4
.gitignore vendored
View file

@ -34,5 +34,5 @@ junit.xml
.pnp.*
# foundry
client
common
/client
/common

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -4,175 +4,263 @@
"name": "Gormanische Schrift",
"type": "alphabet",
"img": "icons/svg/book.svg",
"data": {
"description": ""
},
"effects": [],
"folder": null,
"sort": 0,
"permission": {
"flags": {},
"system": {
"description": ""
},
"ownership": {
"default": 0
},
"flags": {}
"_stats": {
"systemId": "ds4",
"systemVersion": "1.18.1",
"coreVersion": "10.290",
"createdTime": 1668995342357,
"modifiedTime": 1668995456385,
"lastModifiedBy": "DS4BuildSystem00"
}
},
{
"_id": "GQNpFENXcjJGeYr2",
"name": "Freiwort",
"type": "language",
"img": "systems/ds4/assets/icons/game-icons/lorc/conversation.svg",
"data": {
"description": "<p>Freiwort ist die Gemeinsprache der Freien Lande.</p>"
},
"effects": [],
"folder": null,
"sort": 0,
"permission": {
"flags": {},
"system": {
"description": "<p>Freiwort ist die Gemeinsprache der Freien Lande.</p>"
},
"ownership": {
"default": 0
},
"flags": {}
"_stats": {
"systemId": "ds4",
"systemVersion": "1.18.1",
"coreVersion": "10.290",
"createdTime": 1668995342358,
"modifiedTime": 1668995456386,
"lastModifiedBy": "DS4BuildSystem00"
}
},
{
"_id": "O1U9jd0yJoydHwT8",
"name": "Zasarische Schrift",
"type": "alphabet",
"img": "icons/svg/book.svg",
"data": {
"description": ""
},
"effects": [],
"folder": null,
"sort": 0,
"permission": {
"flags": {},
"system": {
"description": ""
},
"ownership": {
"default": 0
},
"flags": {}
"_stats": {
"systemId": "ds4",
"systemVersion": "1.18.1",
"coreVersion": "10.290",
"createdTime": 1668995342359,
"modifiedTime": 1668995456388,
"lastModifiedBy": "DS4BuildSystem00"
}
},
{
"_id": "PzkVTViII6ungWyp",
"name": "Zwergisch",
"type": "language",
"img": "systems/ds4/assets/icons/game-icons/lorc/conversation.svg",
"data": {
"description": ""
},
"effects": [],
"folder": null,
"sort": 0,
"permission": {
"flags": {},
"system": {
"description": ""
},
"ownership": {
"default": 0
},
"flags": {}
"_stats": {
"systemId": "ds4",
"systemVersion": "1.18.1",
"coreVersion": "10.290",
"createdTime": 1668995342360,
"modifiedTime": 1668995456389,
"lastModifiedBy": "DS4BuildSystem00"
}
},
{
"_id": "josgKzD9UmDOvTup",
"name": "Ornamentschrift",
"type": "alphabet",
"img": "icons/svg/book.svg",
"data": {
"description": ""
},
"effects": [],
"folder": null,
"sort": 0,
"permission": {
"flags": {},
"system": {
"description": ""
},
"ownership": {
"default": 0
},
"flags": {}
"_stats": {
"systemId": "ds4",
"systemVersion": "1.18.1",
"coreVersion": "10.290",
"createdTime": 1668995342361,
"modifiedTime": 1668995456390,
"lastModifiedBy": "DS4BuildSystem00"
}
},
{
"_id": "k8FSxBda9CoLOJqZ",
"name": "Kaitanische Schrift",
"type": "alphabet",
"img": "icons/svg/book.svg",
"data": {
"description": ""
},
"effects": [],
"folder": null,
"sort": 0,
"permission": {
"flags": {},
"system": {
"description": ""
},
"ownership": {
"default": 0
},
"flags": {}
"_stats": {
"systemId": "ds4",
"systemVersion": "1.18.1",
"coreVersion": "10.290",
"createdTime": 1668995342362,
"modifiedTime": 1668995456391,
"lastModifiedBy": "DS4BuildSystem00"
}
},
{
"_id": "n2Nbg0ttFzcMxp7T",
"name": "Keilschrift",
"type": "alphabet",
"img": "icons/svg/book.svg",
"data": {
"description": ""
},
"effects": [],
"folder": null,
"sort": 0,
"permission": {
"flags": {},
"system": {
"description": ""
},
"ownership": {
"default": 0
},
"flags": {}
"_stats": {
"systemId": "ds4",
"systemVersion": "1.18.1",
"coreVersion": "10.290",
"createdTime": 1668995342364,
"modifiedTime": 1668995456392,
"lastModifiedBy": "DS4BuildSystem00"
}
},
{
"_id": "n6KU1XIzbPWups0D",
"name": "Kaitanisch",
"type": "language",
"img": "systems/ds4/assets/icons/game-icons/lorc/conversation.svg",
"data": {
"description": ""
},
"effects": [],
"folder": null,
"sort": 0,
"permission": {
"flags": {},
"system": {
"description": ""
},
"ownership": {
"default": 0
},
"flags": {}
"_stats": {
"systemId": "ds4",
"systemVersion": "1.18.1",
"coreVersion": "10.290",
"createdTime": 1668995342365,
"modifiedTime": 1668995456393,
"lastModifiedBy": "DS4BuildSystem00"
}
},
{
"_id": "usEWD48iYnMHO3Ty",
"name": "Zasarisch",
"type": "language",
"img": "systems/ds4/assets/icons/game-icons/lorc/conversation.svg",
"data": {
"description": ""
},
"effects": [],
"folder": null,
"sort": 0,
"permission": {
"flags": {},
"system": {
"description": ""
},
"ownership": {
"default": 0
},
"flags": {}
"_stats": {
"systemId": "ds4",
"systemVersion": "1.18.1",
"coreVersion": "10.290",
"createdTime": 1668995342366,
"modifiedTime": 1668995456394,
"lastModifiedBy": "DS4BuildSystem00"
}
},
{
"_id": "xpvHuSywc8lJa2UN",
"name": "Elfisch",
"type": "language",
"img": "systems/ds4/assets/icons/game-icons/lorc/conversation.svg",
"data": {
"description": ""
},
"effects": [],
"folder": null,
"sort": 0,
"permission": {
"flags": {},
"system": {
"description": ""
},
"ownership": {
"default": 0
},
"flags": {}
"_stats": {
"systemId": "ds4",
"systemVersion": "1.18.1",
"coreVersion": "10.290",
"createdTime": 1668995342367,
"modifiedTime": 1668995456395,
"lastModifiedBy": "DS4BuildSystem00"
}
},
{
"_id": "ylqXcZHRbIBeV20Z",
"name": "Ahnenrunen",
"type": "alphabet",
"img": "icons/svg/book.svg",
"data": {
"description": ""
},
"effects": [],
"folder": null,
"sort": 0,
"permission": {
"flags": {},
"system": {
"description": ""
},
"ownership": {
"default": 0
},
"flags": {}
"_stats": {
"systemId": "ds4",
"systemVersion": "1.18.1",
"coreVersion": "10.290",
"createdTime": 1668995342375,
"modifiedTime": 1668995456396,
"lastModifiedBy": "DS4BuildSystem00"
}
}
]

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -37,14 +37,14 @@ export class DS4ActorSheet extends ActorSheet {
get template() {
const basePath = "systems/ds4/templates/sheets/actor";
if (!getGame().user?.isGM && this.actor.limited) return `${basePath}/limited-sheet.hbs`;
return `${basePath}/${this.actor.data.type}-sheet.hbs`;
return `${basePath}/${this.actor.type}-sheet.hbs`;
}
/** @override */
async getData(options) {
async getData(options = {}) {
const itemsByType = Object.fromEntries(
Object.entries(this.actor.itemTypes).map(([itemType, items]) => {
return [itemType, items.map((item) => item.data).sort((a, b) => (a.sort || 0) - (b.sort || 0))];
return [itemType, [...items].sort((a, b) => (a.sort || 0) - (b.sort || 0))];
}),
);
@ -53,35 +53,39 @@ export class DS4ActorSheet extends ActorSheet {
...effect.toObject(),
sourceName: await effect.getCurrentSourceName(),
factor: effect.factor,
isEffectivelyEnabled: !effect.data.disabled && !effect.isSurpressed,
isEffectivelyEnabled: !effect.disabled && !effect.isSurpressed,
};
});
const enrichedEffects = await Promise.all(enrichedEffectPromises);
const data = {
const context = {
...this.addTooltipsToData(await super.getData(options)),
config: DS4,
itemsByType,
enrichedEffects,
settings: getDS4Settings(),
};
return data;
return context;
}
/**
* Adds tooltips to the attributes, traits, and combatValues of the actor data of the given {@link ActorSheet.Data}.
* @param {object} data
* Adds tooltips to the attributes, traits, and combatValues of the given context object.
* @param {object} context
* @protected
*/
addTooltipsToData(data) {
const valueGroups = [data.data.data.attributes, data.data.data.traits, data.data.data.combatValues];
addTooltipsToData(context) {
const valueGroups = [
context.data.system.attributes,
context.data.system.traits,
context.data.system.combatValues,
];
valueGroups.forEach((valueGroup) => {
Object.values(valueGroup).forEach((attribute) => {
attribute.tooltip = this.getTooltipForValue(attribute);
});
});
return data;
return context;
}
/**
@ -154,13 +158,9 @@ export class DS4ActorSheet extends ActorSheet {
* @protected
*/
onCreateItem(event) {
const { type, ...data } = foundry.utils.deepClone(event.currentTarget.dataset);
const { type } = foundry.utils.deepClone(event.currentTarget.dataset);
const name = getGame().i18n.localize(`DS4.New${type.capitalize()}Name`);
const itemData = {
name: name,
type: type,
data: data,
};
const itemData = { name, type };
Item.create(itemData, { parent: this.actor, pack: this.actor.pack ?? undefined });
}
@ -394,24 +394,25 @@ export class DS4ActorSheet extends ActorSheet {
const dataPath = target.dataset["dataPath"];
enforce(dataPath !== undefined, `Could not find property 'dataPath' in the dataset of ${target}`);
const dataPath2 = target.dataset["dataPath2"];
/** @type {import("../../documents/item/item").DS4Item[]}*/
const items = this.actor.items.filter((item) => item.type === type);
items.sort((a, b) => a.data.sort - b.data.sort);
items.sort((a, b) => a.sort - b.sort);
/**
* @param {boolean} invert Whether or not to inverse the sort order
* @returns {(a: import("../../documents/item/item").DS4Item, b: import("../../documents/item/item").DS4Item) => number} A function for sorting items
*/
const sortFunction = (invert) => (a, b) => {
const propertyA = getProperty(a.data, dataPath);
const propertyB = getProperty(b.data, dataPath);
const propertyA = getProperty(a, dataPath);
const propertyB = getProperty(b, dataPath);
const comparison =
typeof propertyA === "string" || typeof propertyB === "string"
? compareAsStrings(propertyA, propertyB, invert)
: compareAsNumbers(propertyA, propertyB, invert);
if (comparison === 0 && dataPath2 !== undefined) {
const propertyA = getProperty(a.data, dataPath);
const propertyB = getProperty(b.data, dataPath);
const propertyA = getProperty(a, dataPath);
const propertyB = getProperty(b, dataPath);
return typeof propertyA === "string" || typeof propertyB === "string"
? compareAsStrings(propertyA, propertyB, invert)
: compareAsNumbers(propertyA, propertyB, invert);
@ -441,14 +442,14 @@ export class DS4ActorSheet extends ActorSheet {
* @override
*/
async _onDropItem(event, data) {
const item = await Item.fromDropData(data);
if (item && !this.actor.canOwnItemType(item.data.type)) {
const item = await Item.implementation.fromDropData(data);
if (item && !this.actor.canOwnItemType(item.type)) {
notifications.warn(
getGame().i18n.format("DS4.WarningActorCannotOwnItem", {
actorName: this.actor.name,
actorType: this.actor.data.type,
actorType: this.actor.type,
itemName: item.name,
itemType: item.data.type,
itemType: item.type,
}),
);
return false;

View file

@ -13,4 +13,13 @@ export class DS4CharacterActorSheet extends DS4ActorSheet {
classes: ["sheet", "ds4-actor-sheet", "ds4-character-sheet"],
});
}
/** @override */
async getData(options = {}) {
const context = await super.getData(options);
context.data.system.profile.biography = await TextEditor.enrichHTML(context.data.system.profile.biography, {
async: true,
});
return context;
}
}

View file

@ -13,4 +13,14 @@ export class DS4CreatureActorSheet extends DS4ActorSheet {
classes: ["sheet", "ds4-actor-sheet", "ds4-creature-sheet"],
});
}
/** @override */
async getData(options = {}) {
const context = await super.getData(options);
context.data.system.baseInfo.description = await TextEditor.enrichHTML(
context.data.system.baseInfo.description,
{ async: true },
);
return context;
}
}

View file

@ -6,7 +6,6 @@
import { DS4 } from "../config";
import { DS4ActiveEffect } from "../documents/active-effect";
import { isDS4ItemDataTypePhysical } from "../documents/item/item-data-source-base";
import { notifications } from "../ui/notifications";
import { enforce, getGame } from "../utils/utils";
import { disableOverriddenFields } from "./sheet-helpers";
@ -29,19 +28,22 @@ export class DS4ItemSheet extends ItemSheet {
/** @override */
get template() {
const basePath = "systems/ds4/templates/sheets/item";
return `${basePath}/${this.item.data.type}-sheet.hbs`;
return `${basePath}/${this.item.type}-sheet.hbs`;
}
/** @override */
async getData() {
const data = {
...(await super.getData()),
async getData(options = {}) {
const superContext = await super.getData(options);
superContext.data.system.description = await TextEditor.enrichHTML(superContext.data.system.description, {
async: true,
});
const context = {
...superContext,
config: DS4,
isOwned: this.item.isOwned,
actor: this.item.actor,
isPhysical: isDS4ItemDataTypePhysical(this.item.data.data),
};
return data;
return context;
}
/** @override */

View file

@ -56,7 +56,7 @@ export class DS4ActiveEffect extends ActiveEffect {
return;
}
const itemIdRegex = /Item\.([a-zA-Z0-9]+)/;
const itemId = this.data.origin?.match(itemIdRegex)?.[1];
const itemId = this.origin?.match(itemIdRegex)?.[1];
if (!itemId) {
return;
}
@ -73,7 +73,7 @@ export class DS4ActiveEffect extends ActiveEffect {
/** @override */
apply(document, change) {
change.value = Roll.replaceFormulaData(change.value, document.data);
change.value = Roll.replaceFormulaData(change.value, document);
try {
change.value = DS4ActiveEffect.safeEval(change.value).toString();
} catch (e) {
@ -101,16 +101,17 @@ export class DS4ActiveEffect extends ActiveEffect {
*/
async getSource() {
if (this.source === undefined) {
this.source = this.data.origin !== undefined ? await fromUuid(this.data.origin) : null;
this.source = this.origin != null ? await fromUuid(this.origin) : null;
}
return this.source;
}
/**
* Create a new {@link DS4ActiveEffect} using default data.
* Create a new {@link DS4ActiveEffect} using default values.
*
* @param {import("./item/item").DS4Item | import("./actor/actor").DS4Actor} parent The parent of the effect.
* @returns {Promise<DS4ActiveEffect | undefined>}A promise that resolved to the created effect or udifined of the creation was prevented.
* @returns {Promise<DS4ActiveEffect | undefined>} A promise that resolved to the created effect or udifined of the
* creation was prevented.
*/
static async createDefault(parent) {
const createData = {
@ -169,23 +170,27 @@ export class DS4ActiveEffect extends ActiveEffect {
* @protected
*/
getFactoredChangesWithEffect(predicate = () => true) {
if (this.data.disabled || this.isSurpressed) {
if (this.disabled || this.isSurpressed) {
return [];
}
return this.data.changes.filter(predicate).flatMap((change) => {
return this.changes.filter(predicate).flatMap((change) => {
change.priority = change.priority ?? change.mode * 10;
return Array(this.factor).fill({ effect: this, change });
});
}
}
/**
* @typedef {foundry.data.ActiveEffectData["changes"][number]} EffectChangeData
*/
/**
* @typedef {object} EffectChangeDataWithEffect
* @property {DS4ActiveEffect} effect
* @property {EffectChangeData} change
*/
/**
* @typedef {object} EffectChangeData
* @property {string} key The attribute path in the Actor or Item data which the change modifies
* @property {string} value The value of the change effect
* @property {number} mode The modification mode with which the change is applied
* @property {number} priority The priority level with which this change is applied
*/

View file

@ -17,7 +17,6 @@ import { isAttribute, isTrait } from "./actor-data-source-base";
export class DS4Actor extends Actor {
/** @override */
prepareData() {
this.data.reset();
this.prepareBaseData();
this.prepareEmbeddedDocuments();
this.prepareIntermediateData();
@ -29,18 +28,16 @@ export class DS4Actor extends Actor {
/** @override */
prepareBaseData() {
const data = this.data;
data.data.rolling = {
this.system.rolling = {
minimumFumbleResult: 20,
maximumCoupResult: 1,
};
const attributes = data.data.attributes;
Object.values(attributes).forEach((attribute) => (attribute.total = attribute.base + attribute.mod));
Object.values(this.system.attributes).forEach(
(attribute) => (attribute.total = attribute.base + attribute.mod),
);
const traits = data.data.traits;
Object.values(traits).forEach((trait) => (trait.total = trait.base + trait.mod));
Object.values(this.system.traits).forEach((trait) => (trait.total = trait.base + trait.mod));
}
/** @override */
@ -54,26 +51,26 @@ export class DS4Actor extends Actor {
* applied to the Actor.
*/
prepareIntermediateData() {
this.data.data.armorValueSpellMalus = this.armorValueSpellMalusOfEquippedItems;
this.system.armorValueSpellMalus = this.armorValueSpellMalusOfEquippedItems;
}
/**
* The effects that should be applioed to this actor.
* @type {this["effects"]}
* @type {import("../active-effect").DS4ActiveEffect[]}
* @protected
*/
get actorEffects() {
return this.effects.filter((effect) => !effect.data.flags.ds4?.itemEffectConfig?.applyToItems);
return this.effects.filter((effect) => !effect.flags.ds4?.itemEffectConfig?.applyToItems);
}
/**
* Get the effects of this actor that should be applied to the given item.
* @param {import("../item/item").DS4Item} item The item for which to get effects
* @returns The array of effects that are candidates to be applied to the item
* @returns {import("../active-effect").DS4ActiveEffect[]} The array of effects that are candidates to be applied to the item
*/
itemEffects(item) {
return this.effects.filter((effect) => {
const { applyToItems, itemName, condition } = effect.data.flags.ds4?.itemEffectConfig ?? {};
const { applyToItems, itemName, condition } = effect.flags.ds4?.itemEffectConfig ?? {};
if (!applyToItems || (itemName !== undefined && itemName !== "" && itemName !== item.name)) {
return false;
@ -82,9 +79,9 @@ export class DS4Actor extends Actor {
if (condition !== undefined && condition !== "") {
try {
const replacedCondition = DS4Actor.replaceFormulaData(condition, {
item: item.data,
actor: this.data,
effect: effect.data,
item,
actor: this,
effect,
});
return replacedCondition !== undefined ? Boolean(mathEvaluator.evaluate(replacedCondition)) : false;
} catch (error) {
@ -189,7 +186,7 @@ export class DS4Actor extends Actor {
* @override
*/
prepareDerivedData() {
this.data.data.armorValueSpellMalus = Math.max(this.data.data.armorValueSpellMalus, 0);
this.system.armorValueSpellMalus = Math.max(this.system.armorValueSpellMalus, 0);
this.prepareCombatValues();
this.prepareChecks();
}
@ -200,11 +197,11 @@ export class DS4Actor extends Actor {
*/
get derivedDataProperties() {
const combatValueProperties = Object.keys(DS4.i18n.combatValues).map(
(combatValue) => `data.combatValues.${combatValue}.total`,
(combatValue) => `system.combatValues.${combatValue}.total`,
);
const checkProperties = Object.keys(DS4.i18n.checks)
.filter((check) => check !== "defend")
.map((check) => `data.checks.${check}`);
.map((check) => `system.checks.${check}`);
return combatValueProperties.concat(checkProperties);
}
@ -212,18 +209,17 @@ export class DS4Actor extends Actor {
* Apply final transformations to the Actor data after all effects have been applied.
*/
prepareFinalDerivedData() {
Object.values(this.data.data.attributes).forEach((attribute) => (attribute.total = Math.ceil(attribute.total)));
Object.values(this.data.data.traits).forEach((trait) => (trait.total = Math.ceil(trait.total)));
Object.entries(this.data.data.combatValues)
Object.values(this.system.attributes).forEach((attribute) => (attribute.total = Math.ceil(attribute.total)));
Object.values(this.system.traits).forEach((trait) => (trait.total = Math.ceil(trait.total)));
Object.entries(this.system.combatValues)
.filter(([key]) => key !== "movement")
.map(([, value]) => value)
.forEach((combatValue) => (combatValue.total = Math.ceil(combatValue.total)));
Object.keys(this.data.data.checks).forEach((key) => {
this.data.data.checks[key] = Math.ceil(this.data.data.checks[key]);
.forEach(([, combatValue]) => (combatValue.total = Math.ceil(combatValue.total)));
Object.keys(this.system.checks).forEach((key) => {
this.system.checks[key] = Math.ceil(this.system.checks[key]);
});
this.data.data.combatValues.hitPoints.max = this.data.data.combatValues.hitPoints.total;
this.data.data.checks.defend = this.data.data.combatValues.defense.total;
this.system.combatValues.hitPoints.max = this.system.combatValues.hitPoints.total;
this.system.checks.defend = this.system.combatValues.defense.total;
}
/**
@ -232,7 +228,7 @@ export class DS4Actor extends Actor {
* @type {string[]}
*/
get finalDerivedDataProperties() {
return ["data.combatValues.hitPoints.max", "data.checks.defend"];
return ["system.combatValues.hitPoints.max", "system.checks.defend"];
}
/**
@ -257,21 +253,21 @@ export class DS4Actor extends Actor {
* @protected
*/
prepareCombatValues() {
const data = this.data.data;
const system = this.system;
data.combatValues.hitPoints.base = data.attributes.body.total + data.traits.constitution.total + 10;
data.combatValues.defense.base =
data.attributes.body.total + data.traits.constitution.total + this.armorValueOfEquippedItems;
data.combatValues.initiative.base = data.attributes.mobility.total + data.traits.agility.total;
data.combatValues.movement.base = data.attributes.mobility.total / 2 + 1;
data.combatValues.meleeAttack.base = data.attributes.body.total + data.traits.strength.total;
data.combatValues.rangedAttack.base = data.attributes.mobility.total + data.traits.dexterity.total;
data.combatValues.spellcasting.base =
data.attributes.mind.total + data.traits.aura.total - data.armorValueSpellMalus;
data.combatValues.targetedSpellcasting.base =
data.attributes.mind.total + data.traits.dexterity.total - data.armorValueSpellMalus;
system.combatValues.hitPoints.base = system.attributes.body.total + system.traits.constitution.total + 10;
system.combatValues.defense.base =
system.attributes.body.total + system.traits.constitution.total + this.armorValueOfEquippedItems;
system.combatValues.initiative.base = system.attributes.mobility.total + system.traits.agility.total;
system.combatValues.movement.base = system.attributes.mobility.total / 2 + 1;
system.combatValues.meleeAttack.base = system.attributes.body.total + system.traits.strength.total;
system.combatValues.rangedAttack.base = system.attributes.mobility.total + system.traits.dexterity.total;
system.combatValues.spellcasting.base =
system.attributes.mind.total + system.traits.aura.total - system.armorValueSpellMalus;
system.combatValues.targetedSpellcasting.base =
system.attributes.mind.total + system.traits.dexterity.total - system.armorValueSpellMalus;
Object.values(data.combatValues).forEach(
Object.values(system.combatValues).forEach(
(combatValue) => (combatValue.total = combatValue.base + combatValue.mod),
);
}
@ -282,7 +278,7 @@ export class DS4Actor extends Actor {
* @protected
*/
get armorValueOfEquippedItems() {
return this.equippedItemsWithArmor.map((item) => item.data.data.armorValue).reduce((a, b) => a + b, 0);
return this.equippedItemsWithArmor.map((item) => item.system.armorValue).reduce((a, b) => a + b, 0);
}
/**
@ -292,12 +288,8 @@ export class DS4Actor extends Actor {
*/
get armorValueSpellMalusOfEquippedItems() {
return this.equippedItemsWithArmor
.filter(
(item) =>
!(item.data.type === "armor" && ["cloth", "natural"].includes(item.data.data.armorMaterialType)),
)
.map((item) => item.data.data.armorValue)
.reduce((a, b) => a + b, 0);
.filter((item) => !(item.type === "armor" && ["cloth", "natural"].includes(item.system.armorMaterialType)))
.reduce((sum, item) => sum + item.system.armorValue, 0);
}
/**
@ -306,9 +298,7 @@ export class DS4Actor extends Actor {
* @protected
*/
get equippedItemsWithArmor() {
return this.items
.filter((item) => item.data.type === "armor" || item.data.type === "shield")
.filter((item) => item.data.data.equipped);
return this.items.filter((item) => (item.type === "armor" || item.type === "shield") && item.system.equipped);
}
/**
@ -316,57 +306,63 @@ export class DS4Actor extends Actor {
* @protected
*/
prepareChecks() {
const data = this.data.data;
data.checks = {
appraise: data.attributes.mind.total + data.traits.intellect.total,
changeSpell: data.attributes.mind.total + data.traits.intellect.total,
climb: data.attributes.mobility.total + data.traits.strength.total,
communicate: data.attributes.mind.total + data.traits.dexterity.total + this.itemTypes.language.length,
decipherScript: data.attributes.mind.total + data.traits.intellect.total,
const system = this.system;
system.checks = {
appraise: system.attributes.mind.total + system.traits.intellect.total,
changeSpell: system.attributes.mind.total + system.traits.intellect.total,
climb: system.attributes.mobility.total + system.traits.strength.total,
communicate: system.attributes.mind.total + system.traits.dexterity.total + this.itemTypes.language.length,
decipherScript: system.attributes.mind.total + system.traits.intellect.total,
defend: 0, // assigned in prepareFinalDerivedData as it must always match data.combatValues.defense.total and is not changeable by effects
defyPoison: data.attributes.body.total + data.traits.constitution.total,
disableTraps: data.attributes.mind.total + data.traits.dexterity.total,
featOfStrength: data.attributes.body.total + data.traits.strength.total,
flirt: data.attributes.mind.total + data.traits.aura.total,
haggle: data.attributes.mind.total + Math.max(data.traits.intellect.total, data.traits.intellect.total),
hide: data.attributes.mobility.total + data.traits.agility.total,
identifyMagic: data.attributes.mind.total + data.traits.intellect.total,
jump: data.attributes.mobility.total + data.traits.agility.total,
knowledge: data.attributes.mind.total + data.traits.intellect.total,
openLock: data.attributes.mind.total + data.traits.dexterity.total,
perception: Math.max(data.attributes.mind.total + data.traits.intellect.total, 8),
pickPocket: data.attributes.mobility.total + data.traits.dexterity.total,
readTracks: data.attributes.mind.total + data.traits.intellect.total,
resistDisease: data.attributes.body.total + data.traits.constitution.total,
ride: data.attributes.mobility.total + Math.max(data.traits.agility.total, data.traits.aura.total),
search: Math.max(data.attributes.mind.total + data.traits.intellect.total, 8),
senseMagic: data.attributes.mind.total + data.traits.aura.total,
sneak: data.attributes.mobility.total + data.traits.agility.total,
startFire: data.attributes.mind.total + data.traits.dexterity.total,
swim: data.attributes.mobility.total + data.traits.strength.total,
wakeUp: data.attributes.mind.total + data.traits.intellect.total,
defyPoison: system.attributes.body.total + system.traits.constitution.total,
disableTraps: system.attributes.mind.total + system.traits.dexterity.total,
featOfStrength: system.attributes.body.total + system.traits.strength.total,
flirt: system.attributes.mind.total + system.traits.aura.total,
haggle:
system.attributes.mind.total + Math.max(system.traits.intellect.total, system.traits.intellect.total),
hide: system.attributes.mobility.total + system.traits.agility.total,
identifyMagic: system.attributes.mind.total + system.traits.intellect.total,
jump: system.attributes.mobility.total + system.traits.agility.total,
knowledge: system.attributes.mind.total + system.traits.intellect.total,
openLock: system.attributes.mind.total + system.traits.dexterity.total,
perception: Math.max(system.attributes.mind.total + system.traits.intellect.total, 8),
pickPocket: system.attributes.mobility.total + system.traits.dexterity.total,
readTracks: system.attributes.mind.total + system.traits.intellect.total,
resistDisease: system.attributes.body.total + system.traits.constitution.total,
ride: system.attributes.mobility.total + Math.max(system.traits.agility.total, system.traits.aura.total),
search: Math.max(system.attributes.mind.total + system.traits.intellect.total, 8),
senseMagic: system.attributes.mind.total + system.traits.aura.total,
sneak: system.attributes.mobility.total + system.traits.agility.total,
startFire: system.attributes.mind.total + system.traits.dexterity.total,
swim: system.attributes.mobility.total + system.traits.strength.total,
wakeUp: system.attributes.mind.total + system.traits.intellect.total,
workMechanism:
data.attributes.mind.total + Math.max(data.traits.dexterity.total, data.traits.intellect.total),
system.attributes.mind.total + Math.max(system.traits.dexterity.total, system.traits.intellect.total),
};
}
/**
* Handle how changes to a Token attribute bar are applied to the Actor.
* This only differs from the base implementation by also allowing negative values.
* @param {string} attribute The attribute path
* @param {number} value The target attribute value
* @param {boolean} [isDelta=false] Whether the number represents a relative change (true) or an absolute change (false)
* @param {boolean} [isBar=true] Whether the new value is part of an attribute bar, or just a direct value
* @returns {Promise<DS4Actor>} The updated Actor document
* @override
*/
async modifyTokenAttribute(attribute, value, isDelta = false, isBar = true) {
const current = foundry.utils.getProperty(this.data.data, attribute);
const current = foundry.utils.getProperty(this.system, attribute);
// Determine the updates to make to the actor data
/** @type {Record<string, number>} */
let updates;
if (isBar) {
if (isDelta) value = Math.min(Number(current.value) + value, current.max);
updates = { [`data.${attribute}.value`]: value };
updates = { [`system.${attribute}.value`]: value };
} else {
if (isDelta) value = Number(current) + value;
updates = { [`data.${attribute}`]: value };
updates = { [`system.${attribute}`]: value };
}
// Call a hook to handle token resource bar updates
@ -382,10 +378,10 @@ export class DS4Actor extends Actor {
*/
async rollCheck(check, options = {}) {
const speaker = ChatMessage.getSpeaker({ actor: this, ...options.speaker });
await createCheckRoll(this.data.data.checks[check], {
await createCheckRoll(this.system.checks[check], {
rollMode: getGame().settings.get("core", "rollMode"),
maximumCoupResult: this.data.data.rolling.maximumCoupResult,
minimumFumbleResult: this.data.data.rolling.minimumFumbleResult,
maximumCoupResult: this.system.rolling.maximumCoupResult,
minimumFumbleResult: this.system.rolling.minimumFumbleResult,
flavor: "DS4.ActorCheckFlavor",
flavorData: { actor: speaker.alias ?? this.name, check: DS4.i18nKeys.checks[check] },
speaker,
@ -404,12 +400,12 @@ export class DS4Actor extends Actor {
return;
}
const { attribute, trait } = attributeAndTrait;
const checkTargetNumber = this.data.data.attributes[attribute].total + this.data.data.traits[trait].total;
const checkTargetNumber = this.system.attributes[attribute].total + this.system.traits[trait].total;
const speaker = ChatMessage.getSpeaker({ actor: this, ...options.speaker });
await createCheckRoll(checkTargetNumber, {
rollMode: getGame().settings.get("core", "rollMode"),
maximumCoupResult: this.data.data.rolling.maximumCoupResult,
minimumFumbleResult: this.data.data.rolling.minimumFumbleResult,
maximumCoupResult: this.system.rolling.maximumCoupResult,
minimumFumbleResult: this.system.rolling.minimumFumbleResult,
flavor: "DS4.ActorGenericCheckFlavor",
flavorData: {
actor: speaker.alias ?? this.name,
@ -438,7 +434,7 @@ export class DS4Actor extends Actor {
options: Object.fromEntries(
Object.entries(DS4.i18n.attributes).map(([attribute, translation]) => [
attribute,
`${translation} (${this.data.data.attributes[attribute].total})`,
`${translation} (${this.system.attributes[attribute].total})`,
]),
),
},
@ -448,7 +444,7 @@ export class DS4Actor extends Actor {
options: Object.fromEntries(
Object.entries(DS4.i18n.traits).map(([trait, translation]) => [
trait,
`${translation} (${this.data.data.traits[trait].total})`,
`${translation} (${this.system.traits[trait].total})`,
]),
),
},

View file

@ -8,12 +8,12 @@ export class DS4Character extends DS4Actor {
/** @override */
prepareFinalDerivedData() {
super.prepareFinalDerivedData();
this.data.data.slayerPoints.max = 3;
this.system.slayerPoints.max = 3;
}
/** @override */
get finalDerivedDataProperties() {
return [...super.finalDerivedDataProperties, "data.slayerPoints.max"];
return [...super.finalDerivedDataProperties, "system.slayerPoints.max"];
}
/** @override */

View file

@ -17,15 +17,15 @@ import { getGame } from "../utils/utils";
export class DS4ChatMessage extends ChatMessage {
prepareData() {
super.prepareData();
if (this.data.flavor) {
if (this.flavor) {
const game = getGame();
const flavorData = Object.fromEntries(
Object.entries(this.data.flags.ds4?.flavorData ?? {}).map(([key, value]) => [
Object.entries(this.flags.ds4?.flavorData ?? {}).map(([key, value]) => [
key,
typeof value === "string" ? game.i18n.localize(value) : value,
]),
);
this.data.flavor = game.i18n.format(this.data.flavor, flavorData);
this.flavor = game.i18n.format(this.flavor, flavorData);
}
}
}

View file

@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: 2022 Johannes Loher
//
// SPDX-License-Identifier: MIT
/**
* @typedef {object} RollOptions
* @property {Speaker} speaker
*/
/**
* @typedef {object} Speaker
* @property {TokenDocument} [token]
* @property {string} [alias]
*/
export {}

View file

@ -17,10 +17,6 @@ export interface DS4ItemDataSourceDataPhysical {
storageLocation: string;
}
export function isDS4ItemDataTypePhysical(input: object): boolean {
return "quantity" in input && "price" in input && "availability" in input && "storageLocation" in input;
}
export interface DS4ItemDataSourceDataEquipable {
equipped: boolean;
}

View file

@ -17,7 +17,7 @@ export class DS4Item extends Item {
/** @override */
prepareDerivedData() {
this.data.data.rollable = false;
this.system.rollable = false;
}
/**
@ -25,7 +25,7 @@ export class DS4Item extends Item {
* @returns {boolean} Whether the item is a non-equpped equibale or not
*/
isNonEquippedEuipable() {
return "equipped" in this.data.data && !this.data.data.equipped;
return "equipped" in this.system && !this.system.equipped;
}
/**
@ -52,6 +52,6 @@ export class DS4Item extends Item {
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async roll(options = {}) {
throw new Error(getGame().i18n.format("DS4.ErrorRollingForItemTypeNotPossible", { type: this.data.type }));
throw new Error(getGame().i18n.format("DS4.ErrorRollingForItemTypeNotPossible", { type: this.type }));
}
}

View file

@ -11,10 +11,10 @@ import { calculateSpellPrice } from "./calculate-spell-price";
export class DS4Spell extends DS4Item {
/** @override */
prepareDerivedData() {
this.data.data.rollable = this.data.data.equipped;
this.data.data.price = calculateSpellPrice(this.data.data);
if (this.data.data.allowsDefense) {
this.data.data.opponentDefense = 0;
this.system.rollable = this.system.equipped;
this.system.price = calculateSpellPrice(this.system);
if (this.system.allowsDefense) {
this.system.opponentDefense = 0;
}
}
@ -22,12 +22,12 @@ export class DS4Spell extends DS4Item {
async roll(options = {}) {
const game = getGame();
if (!this.data.data.equipped) {
if (!this.system.equipped) {
return notifications.warn(
game.i18n.format("DS4.WarningItemMustBeEquippedToBeRolled", {
name: this.name,
id: this.id,
type: this.data.type,
type: this.type,
}),
);
}
@ -36,21 +36,21 @@ export class DS4Spell extends DS4Item {
throw new Error(game.i18n.format("DS4.ErrorCannotRollUnownedItem", { name: this.name, id: this.id }));
}
const ownerDataData = this.actor.data.data;
const hasComplexModifier = this.data.data.spellModifier.complex !== "";
const ownerSystemData = this.actor.system;
const hasComplexModifier = this.system.spellModifier.complex !== "";
if (hasComplexModifier === undefined) {
notifications.info(
game.i18n.format("DS4.InfoManuallyEnterSpellModifier", {
name: this.name,
spellModifier: this.data.data.spellModifier.complex,
spellModifier: this.system.spellModifier.complex,
}),
);
}
const spellType = this.data.data.spellType;
const opponentDefense = this.data.data.opponentDefense;
const spellType = this.system.spellType;
const opponentDefense = this.system.opponentDefense;
const checkTargetNumber =
ownerDataData.combatValues[spellType].total +
(hasComplexModifier ? 0 : this.data.data.spellModifier.numerical);
ownerSystemData.combatValues[spellType].total +
(hasComplexModifier ? 0 : this.system.spellModifier.numerical);
const speaker = ChatMessage.getSpeaker({ actor: this.actor, ...options.speaker });
const flavor =
opponentDefense !== undefined && opponentDefense !== 0
@ -67,8 +67,8 @@ export class DS4Spell extends DS4Item {
await createCheckRoll(checkTargetNumber, {
rollMode: game.settings.get("core", "rollMode"),
maximumCoupResult: ownerDataData.rolling.maximumCoupResult,
minimumFumbleResult: ownerDataData.rolling.minimumFumbleResult,
maximumCoupResult: ownerSystemData.rolling.maximumCoupResult,
minimumFumbleResult: ownerSystemData.rolling.minimumFumbleResult,
flavor: flavor,
flavorData: flavorData,
speaker,

View file

@ -8,12 +8,11 @@ export class DS4Talent extends DS4Item {
/** @override */
prepareDerivedData() {
super.prepareDerivedData();
const data = this.data.data;
data.rank.total = data.rank.base + data.rank.mod;
this.system.rank.total = this.system.rank.base + this.system.rank.mod;
}
/** @override */
get activeEffectFactor() {
return this.data.data.rank.total;
return this.system.rank.total;
}
}

View file

@ -11,26 +11,26 @@ import { DS4Item } from "../item";
export class DS4Weapon extends DS4Item {
/** @override */
prepareDerivedData() {
const data = this.data.data;
data.rollable = data.equipped;
data.opponentDefenseForAttackType = {};
if (data.attackType === "melee" || data.attackType === "meleeRanged") {
data.opponentDefenseForAttackType.melee = data.opponentDefense;
const system = this.system;
system.rollable = system.equipped;
system.opponentDefenseForAttackType = {};
if (system.attackType === "melee" || system.attackType === "meleeRanged") {
system.opponentDefenseForAttackType.melee = system.opponentDefense;
}
if (data.attackType === "ranged" || data.attackType === "meleeRanged") {
data.opponentDefenseForAttackType.ranged = data.opponentDefense;
if (system.attackType === "ranged" || system.attackType === "meleeRanged") {
system.opponentDefenseForAttackType.ranged = system.opponentDefense;
}
}
/** @override */
async roll(options = {}) {
const game = getGame();
if (!this.data.data.equipped) {
if (!this.system.equipped) {
return notifications.warn(
game.i18n.format("DS4.WarningItemMustBeEquippedToBeRolled", {
name: this.name,
id: this.id,
type: this.data.type,
type: this.type,
}),
);
}
@ -39,12 +39,12 @@ export class DS4Weapon extends DS4Item {
throw new Error(game.i18n.format("DS4.ErrorCannotRollUnownedItem", { name: this.name, id: this.id }));
}
const ownerDataData = this.actor.data.data;
const weaponBonus = this.data.data.weaponBonus;
const ownerSystemData = this.actor.system;
const weaponBonus = this.system.weaponBonus;
const attackType = await this.getPerformedAttackType();
const opponentDefense = this.data.data.opponentDefenseForAttackType[attackType];
const opponentDefense = this.system.opponentDefenseForAttackType[attackType];
const combatValue = `${attackType}Attack`;
const checkTargetNumber = ownerDataData.combatValues[combatValue].total + weaponBonus;
const checkTargetNumber = ownerSystemData.combatValues[combatValue].total + weaponBonus;
const speaker = ChatMessage.getSpeaker({ actor: this.actor, ...options.speaker });
const flavor =
opponentDefense !== undefined && opponentDefense !== 0
@ -61,8 +61,8 @@ export class DS4Weapon extends DS4Item {
await createCheckRoll(checkTargetNumber, {
rollMode: getGame().settings.get("core", "rollMode"),
maximumCoupResult: ownerDataData.rolling.maximumCoupResult,
minimumFumbleResult: ownerDataData.rolling.minimumFumbleResult,
maximumCoupResult: ownerSystemData.rolling.maximumCoupResult,
minimumFumbleResult: ownerSystemData.rolling.minimumFumbleResult,
speaker,
flavor,
flavorData,
@ -77,8 +77,8 @@ export class DS4Weapon extends DS4Item {
* @protected
*/
async getPerformedAttackType() {
if (this.data.data.attackType !== "meleeRanged") {
return this.data.data.attackType;
if (this.system.attackType !== "meleeRanged") {
return this.system.attackType;
}
const { melee, ranged } = { ...DS4.i18n.attackTypes };

View file

@ -12,7 +12,7 @@ function getFallbackData() {
if (!fallbackData) {
fallbackData = {};
for (const type of getGame().system.template.Actor?.types ?? []) {
foundry.utils.mergeObject(fallbackData, new DS4ActorProxy({ type, name: "temporary" }).data.data);
foundry.utils.mergeObject(fallbackData, new DS4ActorProxy({ type, name: "temporary" }).system);
}
}
return fallbackData;

View file

@ -2,12 +2,8 @@
//
// SPDX-License-Identifier: MIT
import { isCheck } from "../documents/actor/actor-data-properties-base";
import { DS4Item } from "../documents/item/item";
import { createRollCheckMacro } from "../macros/roll-check";
import { createRollItemMacro } from "../macros/roll-item";
import { notifications } from "../ui/notifications";
import { getGame } from "../utils/utils";
export function registerForHotbarDropHook() {
Hooks.on("hotbarDrop", onHotbarDrop);
@ -23,32 +19,17 @@ export function registerForHotbarDropHook() {
* @param {Hotbar} hotbar The hotbar on which something wqas
* @param {DropData} data The drop data associated to the drop event
* @param {string} slot The slot on the hotbar that somethingwas dropped on
* @returns {Promise<void>}
* @returns {void | false}
*/
async function onHotbarDrop(hotbar, data, slot) {
function onHotbarDrop(hotbar, data, slot) {
switch (data.type) {
case "Item": {
if (!("data" in data)) {
return notifications.warn(getGame().i18n.localize("DS4.WarningMacrosCanOnlyBeCreatedForOwnedItems"));
}
const itemData = data.data;
if (!DS4Item.rollableItemTypes.includes(itemData.type)) {
return notifications.warn(
getGame().i18n.format("DS4.WarningItemIsNotRollable", {
name: itemData.name,
id: itemData._id,
type: itemData.type,
}),
);
}
return createRollItemMacro(itemData, slot);
createRollItemMacro(data, slot);
return false;
}
case "Check": {
if (!("data" in data) || typeof data.data !== "string" || !isCheck(data.data)) {
return notifications.warn(getGame().i18n.localize("DS4.WarningInvalidCheckDropped"));
}
return createRollCheckMacro(data.data, slot);
createRollCheckMacro(data, slot);
return false;
}
}
}

View file

@ -3,6 +3,7 @@
// SPDX-License-Identifier: MIT
import { DS4 } from "../config";
import { isCheck } from "../documents/actor/actor-data-properties-base";
import { notifications } from "../ui/notifications";
import { getGame } from "../utils/utils";
import { getActiveActorAndToken } from "./helpers";
@ -10,12 +11,15 @@ import { getActiveActorAndToken } from "./helpers";
/**
* Creates a macro from a check drop.
* Get an existing roll check macro if one exists, otherwise create a new one.
* @param {import("../documents/actor/actor-data-properties-base").Check} check The name of the check to perform
* @param {string} slot The hotbar slot to use
* @returns {Promise<void>} A promise that resoolves when the macro has been created.
* @param {object} data The check drop data
* @param {string} slot The hotbar slot to use
* @returns {Promise<void>} A promise that resolves when the macro has been created.
*/
export async function createRollCheckMacro(check, slot) {
const macro = await getOrCreateRollCheckMacro(check);
export async function createRollCheckMacro(data, slot) {
if (!("data" in data) || typeof data.data !== "string" || !isCheck(data.data)) {
return notifications.warn(getGame().i18n.localize("DS4.WarningInvalidCheckDropped"));
}
const macro = await getOrCreateRollCheckMacro(data.data);
await getGame().user?.assignHotbarMacro(macro ?? null, slot);
}
@ -26,9 +30,7 @@ export async function createRollCheckMacro(check, slot) {
async function getOrCreateRollCheckMacro(check) {
const command = `game.ds4.macros.rollCheck("${check}");`;
const existingMacro = getGame().macros?.find(
(m) => m.name === DS4.i18n.checks[check] && m.data.command === command,
);
const existingMacro = getGame().macros?.find((m) => m.name === DS4.i18n.checks[check] && m.command === command);
if (existingMacro) {
return existingMacro;
}

View file

@ -2,18 +2,33 @@
//
// SPDX-License-Identifier: MIT
import { DS4Item } from "../documents/item/item";
import { notifications } from "../ui/notifications";
import { getGame } from "../utils/utils";
import { getActiveActorAndToken } from "./helpers";
/**
* Creates a macro from an item drop.
* Create a macro from an item drop.
* Get an existing roll item macro if one exists, otherwise create a new one.
* @param {object} itemData The item data
* @param {string} slot The hotbar slot to use
* @param {object} data The item drop data
* @param {string} slot The hotbar slot to use
* @returns {Promise<void>} A promise that resolves once the macro has been created.
*/
export async function createRollItemMacro(itemData, slot) {
export async function createRollItemMacro(data, slot) {
const item = await Item.implementation.fromDropData(data);
if (!item.parent) {
return notifications.warn(getGame().i18n.localize("DS4.WarningMacrosCanOnlyBeCreatedForOwnedItems"));
}
if (!DS4Item.rollableItemTypes.includes(item.type)) {
return notifications.warn(
getGame().i18n.format("DS4.WarningItemIsNotRollable", {
name: item.name,
id: item.id,
type: item.type,
}),
);
}
const macro = await getOrCreateRollItemMacro(itemData);
await getGame().user?.assignHotbarMacro(macro ?? null, slot);
}

View file

@ -20,7 +20,7 @@ async function migrate() {
/** @type {import("./migrationHelpers").ActorUpdateDataGetter} */
function getActorUpdateData() {
const updateData = {
data: {
system: {
combatValues: [
"hitPoints",
"defense",

View file

@ -24,7 +24,7 @@ async function migrate() {
function getItemUpdateData(itemData) {
if (!["loot"].includes(itemData.type ?? "")) return undefined;
return {
data: {
system: {
"-=equipped": null,
},
};

View file

@ -23,10 +23,10 @@ async function migrate() {
/** @type {import("./migrationHelpers").ItemUpdateDataGetter} */
function getItemUpdateData(itemData) {
if (itemData.type !== "spell") return;
const cooldownDurationUnit = itemData.data?.cooldownDuration.unit;
const cooldownDurationUnit = itemData.system?.cooldownDuration.unit;
const updateData = {
data: {
system: {
"-=scrollPrice": null,
minimumLevels: { healer: null, wizard: null, sorcerer: null },
cooldownDuration: {

View file

@ -32,12 +32,12 @@ async function migrate() {
/** @type {import("./migrationHelpers").ItemUpdateDataGetter} */
function getItemUpdateData(itemData) {
if (itemData.type !== "spell") return;
const cooldownDurationUnit = itemData.data?.cooldownDuration.unit;
const cooldownDurationValue = itemData.data?.cooldownDuration.value;
const cooldownDurationUnit = itemData.system?.cooldownDuration.unit;
const cooldownDurationValue = itemData.system?.cooldownDuration.value;
const cooldownDuration = migrateCooldownDuration(cooldownDurationValue, cooldownDurationUnit);
const updateData = {
data: {
system: {
cooldownDuration,
},
};

View file

@ -23,15 +23,15 @@ async function migrate() {
/** @type {import("./migrationHelpers").ItemUpdateDataGetter} */
function getItemUpdateData(itemData) {
if (itemData.type !== "spell") return;
const spellCategory = itemData.data?.spellCategory;
const spellCategory = itemData.system?.spellCategory;
const spellGroups = migrateSpellCategory(spellCategory);
// @ts-expect-error bonus is removed with this migration
const bonus = itemData.data?.bonus;
const bonus = itemData.system?.bonus;
const spellModifier = migrateBonus(bonus);
const updateData = {
data: {
system: {
spellGroups,
"-=spellCategory": null,
spellModifier,

View file

@ -25,7 +25,7 @@ function getItemUpdateData(itemData) {
if (itemData.type !== "spell") return;
return {
data: {
system: {
allowsDefense: false,
},
};

61
src/migration/008.js Normal file
View file

@ -0,0 +1,61 @@
// SPDX-FileCopyrightText: 2022 Johannes Loher
//
// SPDX-License-Identifier: MIT
import {
getActorUpdateDataGetter,
getCompendiumMigrator,
getItemUpdateDataGetter,
getSceneUpdateDataGetter,
migrateActors,
migrateCompendiums,
migrateItems,
migrateScenes,
} from "./migrationHelpers";
/** @type {import("./migration").Migration["migrate"]} */
async function migrate() {
await migrateItems(getItemUpdateData);
await migrateActors(getActorUpdateData);
await migrateScenes(getSceneUpdateData);
await migrateCompendiums(migrateCompendium);
}
/** @type {import("./migrationHelpers").EffectUpdateDataGetter} */
function getEffectUpdateData(effectData) {
const data = foundry.utils.deepClone(effectData);
let hasUpdates = false;
if ("changes" in data) {
for (const change of data.changes) {
const newValue = change.value.replaceAll(/@data\./g, "@system.");
if (newValue !== change.value) {
hasUpdates = true;
change.value = newValue;
}
}
}
/** @type {string | undefined} */
const condition = data.flags?.ds4?.itemEffectConfig?.condition;
if (condition !== undefined) {
const newCondition = condition.replaceAll(/(@actor|@item|@effect)\.data/g, "$1.system");
if (newCondition !== condition) {
hasUpdates = true;
data.flags.ds4.itemEffectConfig.condition = newCondition;
}
}
if (hasUpdates) {
return data;
}
}
const getItemUpdateData = getItemUpdateDataGetter(getEffectUpdateData);
const getActorUpdateData = getActorUpdateDataGetter(getItemUpdateData, getEffectUpdateData);
const getSceneUpdateData = getSceneUpdateDataGetter(getActorUpdateData);
const migrateCompendium = getCompendiumMigrator({ getItemUpdateData, getActorUpdateData, getSceneUpdateData });
/** @type {import("./migration").Migration} */
export const migration = {
migrate,
migrateCompendium,
};

View file

@ -12,6 +12,7 @@ import { migration as migration004 } from "./004";
import { migration as migration005 } from "./005";
import { migration as migration006 } from "./006";
import { migration as migration007 } from "./007";
import { migration as migration008 } from "./008";
/**
* Perform migrations.
@ -166,7 +167,16 @@ function getTargetMigrationVersion() {
/**
* @type {Migration[]}
*/
const migrations = [migration001, migration002, migration003, migration004, migration005, migration006, migration007];
const migrations = [
migration001,
migration002,
migration003,
migration004,
migration005,
migration006,
migration007,
migration008,
];
/**
* DOes the migration version indicate the world is being started for the first time?

View file

@ -7,7 +7,9 @@ import { DS4Item } from "../documents/item/item";
import { logger } from "../utils/logger";
import { getGame } from "../utils/utils";
/** @typedef {(itemData: Partial<foundry.data.ItemData["_source"]>) => DeepPartial<foundry.data.ItemData["_source"]> | Record<string, unknown> | undefined} ItemUpdateDataGetter */
/** @typedef {(effectData: object) => Record<string, unknown> | undefined} EffectUpdateDataGetter */
/** @typedef {(itemData: object) => Record<string, unknown> | undefined} ItemUpdateDataGetter */
/**
* Migrate world items.
@ -28,7 +30,7 @@ export async function migrateItems(getItemUpdateData) {
}
}
/** @typedef {(actorData: Partial<foundry.data.ActorData["_source"]>) => DeepPartial<foundry.data.ActorData["_source"]> | undefined} ActorUpdateDataGetter */
/** @typedef {(actorData: object>) => Record<string, unknown> | undefined} ActorUpdateDataGetter */
/**
* Migrate world actors.
@ -52,7 +54,7 @@ export async function migrateActors(getActorUpdateData) {
}
}
/** @typedef {(aceneData: foundry.data.SceneData) => DeepPartial<foundry.data.SceneData["_source"]> | undefined} SceneUpdateDataGetter */
/** @typedef {(scene: Scene) => Record<string, unknown> | undefined} SceneUpdateDataGetter */
/**
* Migrate world scenes.
@ -62,10 +64,12 @@ export async function migrateActors(getActorUpdateData) {
export async function migrateScenes(getSceneUpdateData) {
for (const scene of getGame().scenes ?? []) {
try {
const updateData = getSceneUpdateData(scene.data);
const updateData = getSceneUpdateData(scene);
if (updateData) {
logger.info(`Migrating Scene document ${scene.name} (${scene.id})`);
await scene.update(updateData);
// We need to clear the old syntehtic actors from the cache
scene.tokens.forEach((t) => (t._actor = null));
}
} catch (err) {
logger.error(
@ -76,7 +80,7 @@ export async function migrateScenes(getSceneUpdateData) {
}
}
/** @typedef {(pack: CompendiumCollection) => Promise<void>} CompendiumMigrator*/
/** @typedef {(pack: CompendiumCollection) => Promise<void>} CompendiumMigrator */
/**
* Migrate world compendium packs.
@ -92,34 +96,70 @@ export async function migrateCompendiums(migrateCompendium) {
}
/**
* Get a function to create actor update data that adjusts the owned items of the actor according to the given function.
* @param {ItemUpdateDataGetter} getItemUpdateData The function to generate item update data
* Get a function to create item update data based on the given function to update embedded documents.
* @param {EffectUpdateDataGetter} [getEffectUpdateData] A function to generate effect update data
* @returns {ItemUpdateDataGetter} A function to get item update data
*/
export function getItemUpdateDataGetter(getEffectUpdateData) {
return (itemData) => {
let hasEffectUpdates = false;
const effects = itemData.effects?.map((effectData) => {
const update = getEffectUpdateData(effectData);
if (update) {
hasEffectUpdates = true;
return foundry.utils.mergeObject(effectData, update, { inplace: false, performDeletions: true });
} else {
return effectData;
}
});
return hasEffectUpdates ? { effects } : undefined;
};
}
/**
* Get a function to create actor update data based on the given function to update embedded documents.
* @param {ItemUpdateDataGetter} [getItemUpdateData] A function to generate item update data
* @param {EffectUpdateDataGetter} [getEffectUpdateData] A function to generate effect update data
* @returns {ActorUpdateDataGetter} A function to get actor update data
*/
export function getActorUpdateDataGetter(getItemUpdateData) {
export function getActorUpdateDataGetter(getItemUpdateData, getEffectUpdateData) {
return (actorData) => {
let hasItemUpdates = false;
const items = actorData.items?.map((itemData) => {
const update = getItemUpdateData(itemData);
const update = getItemUpdateData?.(itemData);
if (update) {
hasItemUpdates = true;
return { ...itemData, ...update };
return foundry.utils.mergeObject(itemData, update, { inplace: false, performDeletions: true });
} else {
return itemData;
}
});
return hasItemUpdates ? { items } : undefined;
let hasEffectUpdates = false;
const effects = actorData.effects?.map((effectData) => {
const update = getEffectUpdateData?.(effectData);
if (update) {
hasEffectUpdates = true;
return foundry.utils.mergeObject(effectData, update, { inplace: false, performDeletions: true });
} else {
return effectData;
}
});
const result = {
items: hasItemUpdates ? items : undefined,
effects: hasEffectUpdates ? effects : undefined,
};
return hasItemUpdates | hasEffectUpdates ? result : undefined;
};
}
/**
* Get a function to create scene update data that adjusts the actors of the tokens of the scene according to the given function.
* @param {ActorUpdateDataGetter} getItemUpdateData The function to generate actor update data
* @param {ActorUpdateDataGetter} [getItemUpdateData] The function to generate actor update data
* @returns {SceneUpdateDataGetter} A function to get scene update data
*/
export function getSceneUpdateDataGetter(getActorUpdateData) {
return (sceneData) => {
const tokens = sceneData.tokens.map((token) => {
return (scene) => {
const tokens = scene.tokens.map((token) => {
const t = token.toObject();
if (!t.actorId || t.actorLink) {
t.actorData = {};
@ -129,18 +169,18 @@ export function getSceneUpdateDataGetter(getActorUpdateData) {
} else if (!t.actorLink) {
const actorData = foundry.utils.deepClone(t.actorData);
actorData.type = token.actor?.type;
const update = getActorUpdateData(actorData);
const update = getActorUpdateData?.(actorData);
if (update !== undefined) {
["items", "effects"].forEach((embeddedName) => {
const embeddedUpdates = update[embeddedName];
if (embeddedUpdates === undefined || !embeddedUpdates.length) return;
if (!embeddedUpdates?.length) return;
const updates = new Map(embeddedUpdates.flatMap((u) => (u && u._id ? [[u._id, u]] : [])));
const originals = t.actorData[embeddedName];
if (!originals) return;
originals.forEach((original) => {
if (!original._id) return;
const update = updates.get(original._id);
if (update) foundry.utils.mergeObject(original, update);
if (update) foundry.utils.mergeObject(original, update, { performDeletions: true });
});
delete update[embeddedName];
});
@ -191,7 +231,7 @@ export function getCompendiumMigrator(
const updateData = getActorUpdateData(doc.toObject());
updateData && (await doc.update(updateData));
} else if (doc instanceof Scene && getSceneUpdateData) {
const updateData = getSceneUpdateData(doc.data);
const updateData = getSceneUpdateData(doc);
updateData && (await doc.update(updateData));
}
} catch (err) {

View file

@ -1,5 +1,5 @@
{
"name": "ds4",
"id": "ds4",
"title": "Dungeonslayers 4",
"description": "An implementation of the <a href='https://www.dungeonslayers.net'>Dungeonslayers</a> 4 game system for Foundry Virtual Tabletop.",
"authors": [
@ -34,8 +34,10 @@
"bugs": "https://git.f3l.de/dungeonslayers/ds4/-/issues",
"changelog": "https://git.f3l.de/dungeonslayers/ds4/-/releases/v1.18.2",
"version": "1.18.2",
"minimumCoreVersion": "9.238",
"compatibleCoreVersion": "9",
"compatibility": {
"minimum": "10.290",
"verified": "10"
},
"esmodules": ["ds4.js"],
"styles": ["css/ds4.css"],
"languages": [

View file

@ -8,40 +8,41 @@ SPDX-License-Identifier: MIT
<div class="ds4-actor-progression">
<div class="ds4-actor-progression__entry">
<h2 class="ds4-actor-progression__label"><label for="data.combatValues.hitPoints.value-{{data._id}}"
<h2 class="ds4-actor-progression__label"><label for="system.combatValues.hitPoints.value-{{data._id}}"
title="{{localize 'DS4.CombatValuesHitPointsCurrent'}}">{{localize
"DS4.CombatValuesHitPointsCurrentAbbr"}}</label>
</h2>
<input class="ds4-actor-progression__input" type="number" name="data.combatValues.hitPoints.value"
id="data.combatValues.hitPoints.value-{{data._id}}" value="{{data.data.combatValues.hitPoints.value}}"
<input class="ds4-actor-progression__input" type="number" name="system.combatValues.hitPoints.value"
id="system.combatValues.hitPoints.value-{{data._id}}" value="{{data.system.combatValues.hitPoints.value}}"
data-dtype="Number" />
</div>
{{#if (eq data.type "character")}}
{{#if settings.showSlayerPoints}}
<div class="ds4-actor-progression__entry">
<h2 class="ds4-actor-progression__label"><label for="data.slayersPoints.value-{{data._id}}"
<h2 class="ds4-actor-progression__label"><label for="system.slayersPoints.value-{{data._id}}"
title="{{localize 'DS4.CharacterSlayerPoints'}}">{{localize "DS4.CharacterSlayerPointsAbbr"}}</label>
</h2>
<input class="ds4-actor-progression__input ds4-actor-progression__input--slayer-points" type="number"
max="{{data.data.slayerPoints.max}}" min="0" step="1" name="data.slayerPoints.value"
id="data.slayersPoints.value-{{data._id}}" value="{{data.data.slayerPoints.value}}" data-dtype="Number" />
max="{{data.system.slayerPoints.max}}" min="0" step="1" name="system.slayerPoints.value"
id="system.slayersPoints.value-{{data._id}}" value="{{data.system.slayerPoints.value}}"
data-dtype="Number" />
</div>
{{/if}}
<div class="ds4-actor-progression__entry">
<h2 class="ds4-actor-progression__label"><label for="data.progression.level-{{data._id}}"
<h2 class="ds4-actor-progression__label"><label for="system.progression.level-{{data._id}}"
title="{{localize 'DS4.CharacterProgressionLevel'}}">{{localize
"DS4.CharacterProgressionLevelAbbr"}}</label>
</h2>
<input class="ds4-actor-progression__input" type="number" min="0" name="data.progression.level"
id="data.progression.level-{{data._id}}" value="{{data.data.progression.level}}" data-dtype="Number" />
<input class="ds4-actor-progression__input" type="number" min="0" name="system.progression.level"
id="system.progression.level-{{data._id}}" value="{{data.system.progression.level}}" data-dtype="Number" />
</div>
<div class="ds4-actor-progression__entry">
<h2 class="ds4-actor-progression__label"><label for="data.progression.experiencePoints-{{data._id}}"
<h2 class="ds4-actor-progression__label"><label for="system.progression.experiencePoints-{{data._id}}"
title="{{localize 'DS4.CharacterProgressionExperiencePoints'}}">{{localize
"DS4.CharacterProgressionExperiencePointsAbbr"}}</label>
</h2>
<input class="ds4-actor-progression__input" type="number" min="0" name="data.progression.experiencePoints"
id="data.progression.experiencePoints-{{data._id}}" value="{{data.data.progression.experiencePoints}}"
<input class="ds4-actor-progression__input" type="number" min="0" name="system.progression.experiencePoints"
id="system.progression.experiencePoints-{{data._id}}" value="{{data.system.progression.experiencePoints}}"
data-dtype="Number" />
</div>
{{/if}}

View file

@ -5,6 +5,6 @@ SPDX-License-Identifier: MIT
--}}
<div class="ds4-biography">
{{editor content=data.data.profile.biography target="data.profile.biography" button=true owner=owner
editable=editable}}
{{editor data.system.profile.biography target="system.profile.biography" button=true owner=owner
editable=editable engine="prosemirror"}}
</div>

View file

@ -9,55 +9,56 @@ SPDX-License-Identifier: MIT
<div class="ds4-actor-properties">
<div class="ds4-actor-properties__property">
<label class="ds4-actor-properties__property-label"
for="data.baseInfo.race-{{data._id}}">{{config.i18n.characterBaseInfo.race}}</label>
<input type="text" name="data.baseInfo.race" id="data.baseInfo.race-{{data._id}}"
value="{{data.data.baseInfo.race}}" data-dtype="String" />
for="system.baseInfo.race-{{data._id}}">{{config.i18n.characterBaseInfo.race}}</label>
<input type="text" name="system.baseInfo.race" id="system.baseInfo.race-{{data._id}}"
value="{{data.system.baseInfo.race}}" data-dtype="String" />
</div>
<div class="ds4-actor-properties__property">
<label class="ds4-actor-properties__property-label"
for="data.baseInfo.culture-{{data._id}}">{{config.i18n.characterBaseInfo.culture}}</label>
<input id="data.baseInfo.culture-{{data._id}}" type="text" name="data.baseInfo.culture"
value="{{data.data.baseInfo.culture}}" data-dtype="String" />
for="system.baseInfo.culture-{{data._id}}">{{config.i18n.characterBaseInfo.culture}}</label>
<input id="system.baseInfo.culture-{{data._id}}" type="text" name="system.baseInfo.culture"
value="{{data.system.baseInfo.culture}}" data-dtype="String" />
</div>
<div class="ds4-actor-properties__property">
<label class="ds4-actor-properties__property-label"
for="data.progression.progressPoints.used-{{data._id}}">{{config.i18n.characterProgression.progressPoints}}</label>
for="system.progression.progressPoints.used-{{data._id}}">{{config.i18n.characterProgression.progressPoints}}</label>
<div class="ds4-actor-properties__property-multi-input">
<input id="data.progression.progressPoints.used-{{data._id}}" type="number"
name="data.progression.progressPoints.used" value="{{data.data.progression.progressPoints.used}}"
<input id="system.progression.progressPoints.used-{{data._id}}" type="number"
name="system.progression.progressPoints.used" value="{{data.system.progression.progressPoints.used}}"
data-dtype="Number" />
<span class="input-divider"> / </span>
<label class="ds4-hidden" for="data.progression.progressPoints.total-{{data._id}}">Total
<label class="ds4-hidden" for="system.progression.progressPoints.total-{{data._id}}">Total
Progression Points</label>
<input type="number" id="data.progression.progressPoints.total-{{data._id}}"
name="data.progression.progressPoints.total" value="{{data.data.progression.progressPoints.total}}"
<input type="number" id="system.progression.progressPoints.total-{{data._id}}"
name="system.progression.progressPoints.total" value="{{data.system.progression.progressPoints.total}}"
data-dtype="Number" />
</div>
</div>
<div class="ds4-actor-properties__property">
<label class="ds4-actor-properties__property-label"
for="data.progression.talentPoints.used-{{data._id}}">{{config.i18n.characterProgression.talentPoints}}</label>
for="system.progression.talentPoints.used-{{data._id}}">{{config.i18n.characterProgression.talentPoints}}</label>
<div class="ds4-actor-properties__property-multi-input">
<input type="number" name="data.progression.talentPoints.used"
id="data.progression.talentPoints.used-{{data._id}}" value="{{data.data.progression.talentPoints.used}}"
data-dtype="Number" />
<input type="number" name="system.progression.talentPoints.used"
id="system.progression.talentPoints.used-{{data._id}}"
value="{{data.system.progression.talentPoints.used}}" data-dtype="Number" />
<span class="input-divider"> / </span>
<label for="data.progression.talentPoints.total-{{data._id}}" class="ds4-hidden">Total Talent Points</label>
<input type="number" name="data.progression.talentPoints.total"
id="data.progression.talentPoints.total-{{data._id}}"
value="{{data.data.progression.talentPoints.total}}" data-dtype="Number" />
<label for="system.progression.talentPoints.total-{{data._id}}" class="ds4-hidden">Total Talent
Points</label>
<input type="number" name="system.progression.talentPoints.total"
id="system.progression.talentPoints.total-{{data._id}}"
value="{{data.system.progression.talentPoints.total}}" data-dtype="Number" />
</div>
</div>
<div class="ds4-actor-properties__property">
<label class="ds4-actor-properties__property-label"
for="data.baseInfo.class-{{data._id}}">{{config.i18n.characterBaseInfo.class}}</label>
<input type="text" id="data.baseInfo.class-{{data._id}}" name="data.baseInfo.class"
value="{{data.data.baseInfo.class}}" data-dtype="String" />
for="system.baseInfo.class-{{data._id}}">{{config.i18n.characterBaseInfo.class}}</label>
<input type="text" id="system.baseInfo.class-{{data._id}}" name="system.baseInfo.class"
value="{{data.system.baseInfo.class}}" data-dtype="String" />
</div>
<div class="ds4-actor-properties__property">
<label class="ds4-actor-properties__property-label"
for="data.baseInfo.heroClass-{{data._id}}">{{config.i18n.characterBaseInfo.heroClass}}</label>
<input type="text" id="data.baseInfo.heroClass-{{data._id}}" name="data.baseInfo.heroClass"
value="{{data.data.baseInfo.heroClass}}" data-dtype="String" />
for="system.baseInfo.heroClass-{{data._id}}">{{config.i18n.characterBaseInfo.heroClass}}</label>
<input type="text" id="system.baseInfo.heroClass-{{data._id}}" name="system.baseInfo.heroClass"
value="{{data.system.baseInfo.heroClass}}" data-dtype="String" />
</div>
</div>

View file

@ -7,6 +7,6 @@ SPDX-License-Identifier: MIT
<div class="ds4-checks">
{{#each config.i18n.checks as |check-label check-key|}}
{{> systems/ds4/templates/sheets/actor/components/check.hbs check-key=check-key check-target-number=(lookup
../data.data.checks check-key) check-label=check-label}}
../data.system.checks check-key) check-label=check-label}}
{{/each}}
</div>

View file

@ -25,8 +25,8 @@ SPDX-License-Identifier: MIT
title="{{combat-value-title}} {{localize 'DS4.TooltipBaseValue'}}">{{combat-value-data.base}}</span>
<span>+</span>
<input class="ds4-combat-value__formula-modifier" type="number"
id="data.combatValues.{{combat-value-key}}.mod-{{actor-id}}"
name="data.combatValues.{{combat-value-key}}.mod" value='{{combat-value-data.mod}}' data-dtype="Number"
id="system.combatValues.{{combat-value-key}}.mod-{{actor-id}}"
name="system.combatValues.{{combat-value-key}}.mod" value='{{combat-value-data.mod}}' data-dtype="Number"
title="{{combat-value-title}} {{localize 'DS4.TooltipModifier'}}" />
</div>
</div>

View file

@ -8,7 +8,7 @@ SPDX-License-Identifier: MIT
<div class="ds4-combat-values">
{{#each config.i18n.combatValues as |combat-value-title combat-value-key|}}
{{> systems/ds4/templates/sheets/actor/components/combat-value.hbs combat-value-key=combat-value-key
combat-value-data=(lookup ../data.data.combatValues combat-value-key) combat-value-label=(lookup
combat-value-data=(lookup ../data.system.combatValues combat-value-key) combat-value-label=(lookup
../config.i18n.combatValuesSheet combat-value-key) combat-value-title=combat-value-title
actor-id=../data._id}}
{{/each}}

View file

@ -15,17 +15,17 @@ SPDX-License-Identifier: MIT
--}}
<div class="ds4-core-value ds4-core-value--{{core-value-variant}}">
<label for="data.{{core-value-variant}}s.{{core-value-key}}.base-{{actor-id}}"
<label for="system.{{core-value-variant}}s.{{core-value-key}}.base-{{actor-id}}"
class="ds4-core-value__label">{{core-value-label}}</label>
<div class="ds4-core-value__value">
<input class="ds4-core-value__value-input" type="number"
name="data.{{core-value-variant}}s.{{core-value-key}}.base"
id="data.{{core-value-variant}}s.{{core-value-key}}.base-{{actor-id}}" value='{{core-value-data.base}}'
name="system.{{core-value-variant}}s.{{core-value-key}}.base"
id="system.{{core-value-variant}}s.{{core-value-key}}.base-{{actor-id}}" value='{{core-value-data.base}}'
data-dtype="Number" title="{{core-value-label}} {{localize 'DS4.TooltipBaseValue'}}" />
<span>+</span>
<input class="ds4-core-value__value-input" type="number"
name="data.{{core-value-variant}}s.{{core-value-key}}.mod"
id="data.{{core-value-variant}}s.{{core-value-key}}.mod-{{actor-id}}" value='{{core-value-data.mod}}'
name="system.{{core-value-variant}}s.{{core-value-key}}.mod"
id="system.{{core-value-variant}}s.{{core-value-key}}.mod-{{actor-id}}" value='{{core-value-data.mod}}'
data-dtype="Number" title="{{core-value-label}} {{localize 'DS4.TooltipModifier'}}" />
<span class="ds4-core-value__value-arrow">➞</span>
<span class="ds4-core-value__value-total"

View file

@ -8,12 +8,12 @@ SPDX-License-Identifier: MIT
<div class="ds4-core-values">
{{#each config.i18n.attributes as |attribute-label attribute-key|}}
{{> systems/ds4/templates/sheets/actor/components/core-value.hbs core-value-label=attribute-label
core-value-key=attribute-key core-value-data=(lookup ../data.data.attributes
core-value-key=attribute-key core-value-data=(lookup ../data.system.attributes
attribute-key) core-value-variant="attribute" actor-id=../data._id}}
{{/each}}
{{#each config.i18n.traits as |trait-label trait-key|}}
{{> systems/ds4/templates/sheets/actor/components/core-value.hbs core-value-label=trait-label
core-value-key=trait-key
core-value-data=(lookup ../data.data.traits trait-key) core-value-variant="trait" actor-id=../data._id}}
core-value-data=(lookup ../data.system.traits trait-key) core-value-variant="trait" actor-id=../data._id}}
{{/each}}
</div>

View file

@ -9,10 +9,10 @@ SPDX-License-Identifier: MIT
<div class="ds4-actor-properties">
<div class="ds4-actor-properties__property">
<label class="ds4-actor-properties__property-label"
for="data.baseInfo.creatureType-{{data._id}}">{{config.i18n.creatureBaseInfo.creatureType}}</label>
<select class="ds4-actor-properties__property-select" id="data.baseInfo.creatureType-{{data._id}}"
name="data.baseInfo.creatureType" data-dtype="String">
{{#select data.data.baseInfo.creatureType}}
for="system.baseInfo.creatureType-{{data._id}}">{{config.i18n.creatureBaseInfo.creatureType}}</label>
<select class="ds4-actor-properties__property-select" id="system.baseInfo.creatureType-{{data._id}}"
name="system.baseInfo.creatureType" data-dtype="String">
{{#select data.system.baseInfo.creatureType}}
{{#each config.i18n.creatureTypes as |value key|}}
<option value="{{key}}">{{value}}</option>
{{/each}}
@ -21,22 +21,22 @@ SPDX-License-Identifier: MIT
</div>
<div class="ds4-actor-properties__property">
<label class="ds4-actor-properties__property-label"
for="data.baseInfo.loot-{{data._id}}">{{config.i18n.creatureBaseInfo.loot}}</label>
<input type="text" id="data.baseInfo.loot-{{data._id}}" name="data.baseInfo.loot"
value="{{data.data.baseInfo.loot}}" data-dtype="String" />
for="system.baseInfo.loot-{{data._id}}">{{config.i18n.creatureBaseInfo.loot}}</label>
<input type="text" id="system.baseInfo.loot-{{data._id}}" name="system.baseInfo.loot"
value="{{data.system.baseInfo.loot}}" data-dtype="String" />
</div>
<div class="ds4-actor-properties__property">
<label class="ds4-actor-properties__property-label"
for="data.baseInfo.foeFactor-{{data._id}}">{{config.i18n.creatureBaseInfo.foeFactor}}</label>
<input type="text" id="data.baseInfo.foeFactor-{{data._id}}" name="data.baseInfo.foeFactor"
value="{{data.data.baseInfo.foeFactor}}" data-dtype="Number" />
for="system.baseInfo.foeFactor-{{data._id}}">{{config.i18n.creatureBaseInfo.foeFactor}}</label>
<input type="text" id="system.baseInfo.foeFactor-{{data._id}}" name="system.baseInfo.foeFactor"
value="{{data.system.baseInfo.foeFactor}}" data-dtype="Number" />
</div>
<div class="ds4-actor-properties__property">
<label class="ds4-actor-properties__property-label"
for="data.baseInfo.sizeCategory-{{data._id}}">{{config.i18n.creatureBaseInfo.sizeCategory}}</label>
<select class="ds4-actor-properties__property-select" id="data.baseInfo.sizeCategory-{{data._id}}"
name="data.baseInfo.sizeCategory" data-dtype="String">
{{#select data.data.baseInfo.sizeCategory}}
for="system.baseInfo.sizeCategory-{{data._id}}">{{config.i18n.creatureBaseInfo.sizeCategory}}</label>
<select class="ds4-actor-properties__property-select" id="system.baseInfo.sizeCategory-{{data._id}}"
name="system.baseInfo.sizeCategory" data-dtype="String">
{{#select data.system.baseInfo.sizeCategory}}
{{#each config.i18n.creatureSizeCategories as |value key|}}
<option value="{{key}}">{{value}}</option>
{{/each}}
@ -45,8 +45,8 @@ SPDX-License-Identifier: MIT
</div>
<div class="ds4-actor-properties__property">
<label class="ds4-actor-properties__property-label"
for="data.baseInfo.experiencePoints-{{data._id}}">{{config.i18n.creatureBaseInfo.experiencePoints}}</label>
<input type="text" id="data.baseInfo.experiencePoints-{{data._id}}" name="data.baseInfo.experiencePoints"
value="{{data.data.baseInfo.experiencePoints}}" data-dtype="Number" />
for="system.baseInfo.experiencePoints-{{data._id}}">{{config.i18n.creatureBaseInfo.experiencePoints}}</label>
<input type="text" id="system.baseInfo.experiencePoints-{{data._id}}" name="system.baseInfo.experiencePoints"
value="{{data.system.baseInfo.experiencePoints}}" data-dtype="Number" />
</div>
</div>

View file

@ -8,9 +8,9 @@ SPDX-License-Identifier: MIT
<h4 class="ds4-currency-title">{{localize 'DS4.CharacterCurrency'}}</h4>
<div class="ds4-currency">
{{#each data.data.currency as |value key|}}
<label for="data.currency.{{key}}" class="flex05">{{lookup ../config.i18n.characterCurrency key}}</label>
{{#each data.system.currency as |value key|}}
<label for="system.currency.{{key}}" class="flex05">{{lookup ../config.i18n.characterCurrency key}}</label>
<input class="ds4-currency__value ds4-currency__value--{{key}}" type="number" min="0" step="1"
name="data.currency.{{key}}" id="data.currency.{{key}}" value="{{value}}" data-dtype="Number" />
name="system.currency.{{key}}" id="system.currency.{{key}}" value="{{value}}" data-dtype="Number" />
{{/each}}
</div>

View file

@ -5,6 +5,6 @@ SPDX-License-Identifier: MIT
--}}
<div class="ds4-description">
{{editor content=data.data.baseInfo.description target="data.baseInfo.description" button=true owner=owner
editable=editable}}
{{editor data.system.baseInfo.description target="system.baseInfo.description" button=true owner=owner
editable=editable engine="prosemirror"}}
</div>

View file

@ -19,24 +19,24 @@ SPDX-License-Identifier: MIT
{{!-- equipped --}}
{{#if isEquipable}}
<input class="ds4-embedded-document-list__editable ds4-embedded-document-list__editable--checkbox change-item" type="checkbox" {{checked
itemData.data.equipped}} data-dtype="Boolean" data-property="data.equipped"
itemData.system.equipped}} data-dtype="Boolean" data-property="system.equipped"
title="{{localize 'DS4.ItemEquipped'}}">
{{/if}}
{{!-- image --}}
{{> systems/ds4/templates/sheets/shared/components/rollable-image.hbs rollable=(and itemData.data.rollable @root/editable)
{{> systems/ds4/templates/sheets/shared/components/rollable-image.hbs rollable=(and itemData.system.rollable @root/editable)
src=itemData.img alt=(localize "DS4.DocumentImageAltText" name=itemData.name) title=itemData.name
rollableTitle=(localize "DS4.RollableImageRollableTitle" name=itemData.name) rollableClass="rollable-item"}}
{{!-- amount --}}
{{#if hasQuantity}}
<input class="ds4-embedded-document-list__editable change-item" type="number" min="0" step="1" value="{{itemData.data.quantity}}"
data-dtype="Number" data-property="data.quantity" title="{{localize 'DS4.Quantity'}}" />
<input class="ds4-embedded-document-list__editable change-item" type="number" min="0" step="1" value="{{itemData.system.quantity}}"
data-dtype="Number" data-property="system.quantity" title="{{localize 'DS4.Quantity'}}" />
{{/if}}
{{!-- name --}}
<input class="ds4-embedded-document-list__editable change-item" type="text" value="{{itemData.name}}" data-dtype="String"
data-property="name" title="{{htmlToPlainText itemData.data.description}}" />
data-property="name" title="{{htmlToPlainText itemData.system.description}}" />
{{!-- item type specifics --}}
{{#if @partial-block }}
@ -45,8 +45,8 @@ SPDX-License-Identifier: MIT
{{!-- description --}}
{{#unless hideDescription}}
<div class="ds4-embedded-document-list__description" title="{{htmlToPlainText itemData.data.description}}">
{{{itemData.data.description}}}</div>
<div class="ds4-embedded-document-list__description" title="{{htmlToPlainText itemData.system.description}}">
{{{itemData.system.description}}}</div>
{{/unless}}
{{!-- control button group --}}

View file

@ -18,7 +18,7 @@ SPDX-License-Identifier: MIT
<li class="ds4-embedded-document-list__row ds4-embedded-document-list__row--header" data-type={{type}}>
{{!-- equipped --}}
{{#if isEquipable}}
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="data.equipped"
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="system.equipped"
title="{{localize 'DS4.SortByItemEquipped'}}">
{{localize 'DS4.ItemEquippedAbbr'}}</div>
{{/if}}
@ -28,7 +28,7 @@ SPDX-License-Identifier: MIT
{{!-- amount --}}
{{#if hasQuantity}}
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="data.quantity"
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="system.quantity"
title="{{localize 'DS4.SortByQuantity'}}">#</div>
{{/if}}
@ -44,7 +44,7 @@ SPDX-License-Identifier: MIT
{{!-- description --}}
{{#unless hideDescription}}
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="data.description"
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="system.description"
title="{{localize 'DS4.SortByDescription'}}">{{localize
'DS4.Description'}}</div>
{{/unless}}

View file

@ -13,19 +13,19 @@ SPDX-License-Identifier: MIT
{{#> systems/ds4/templates/sheets/actor/components/item-list-header.hbs isEquipable=true hasQuantity=true
type='weapon'}}
{{!-- attack type --}}
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="data.attackType"
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="system.attackType"
title="{{localize 'DS4.SortByAttackType'}}">
{{localize
'DS4.AttackTypeAbbr'}}</div>
{{!-- weapon bonus --}}
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="data.weaponBonus"
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="system.weaponBonus"
title="{{localize 'DS4.SortByWeaponBonus'}}">
{{localize 'DS4.WeaponBonusAbbr'}}
</div>
{{!-- opponent defense --}}
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="data.opponentDefense"
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="system.opponentDefense"
title="{{localize 'DS4.SortByOpponentDefense'}}">
{{localize 'DS4.OpponentDefenseAbbr'}}
</div>
@ -36,24 +36,24 @@ SPDX-License-Identifier: MIT
hasQuantity=true}}
{{!-- attack type --}}
<img class="ds4-embedded-document-list__image"
src="{{lookup @root/config.icons.attackTypes itemData.data.attackType}}"
title="{{lookup @root/config.i18n.attackTypes itemData.data.attackType}}" />
src="{{lookup @root/config.icons.attackTypes itemData.system.attackType}}"
title="{{lookup @root/config.i18n.attackTypes itemData.system.attackType}}" />
{{!-- weapon bonus --}}
<div>{{ itemData.data.weaponBonus}}</div>
<div>{{ itemData.system.weaponBonus}}</div>
{{!-- opponent defense --}}
<div>
{{#if itemData.data.opponentDefenseForAttackType.melee includeZero=true}}
{{#if itemData.data.opponentDefenseForAttackType.ranged includeZero=true}}
{{#if itemData.system.opponentDefenseForAttackType.melee includeZero=true}}
{{#if itemData.system.opponentDefenseForAttackType.ranged includeZero=true}}
<span
title="{{localize 'DS4.OpponentDefenseMelee'}}">{{itemData.data.opponentDefenseForAttackType.melee}}</span>/<span
title="{{localize 'DS4.OpponentDefenseRanged'}}">{{itemData.data.opponentDefenseForAttackType.ranged}}</span>
title="{{localize 'DS4.OpponentDefenseMelee'}}">{{itemData.system.opponentDefenseForAttackType.melee}}</span>/<span
title="{{localize 'DS4.OpponentDefenseRanged'}}">{{itemData.system.opponentDefenseForAttackType.ranged}}</span>
{{else}}
{{itemData.data.opponentDefenseForAttackType.melee}}
{{itemData.system.opponentDefenseForAttackType.melee}}
{{/if}}
{{else}}
{{itemData.data.opponentDefenseForAttackType.ranged}}
{{itemData.system.opponentDefenseForAttackType.ranged}}
{{/if}}
</div>
{{/systems/ds4/templates/sheets/actor/components/item-list-entry.hbs}}
@ -70,15 +70,15 @@ documentType='item' type='weapon'}}
{{#> systems/ds4/templates/sheets/actor/components/item-list-header.hbs isEquipable=true hasQuantity=true
type="armor"}}
{{!-- armor material type --}}
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="data.armorMaterialType"
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="system.armorMaterialType"
title="{{localize 'DS4.SortByArmorMaterialType'}}">{{localize 'DS4.ArmorMaterialTypeAbbr'}}</div>
{{!-- armor type --}}
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="data.armorType"
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="system.armorType"
title="{{localize 'DS4.SortByArmorType'}}">{{localize 'DS4.ArmorTypeAbbr'}}</div>
{{!-- armor value --}}
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="data.armorValue"
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="system.armorValue"
title="{{localize 'DS4.SortByArmorValue'}}">
{{localize 'DS4.ArmorValueAbbr'}}
</div>
@ -88,17 +88,17 @@ documentType='item' type='weapon'}}
{{#> systems/ds4/templates/sheets/actor/components/item-list-entry.hbs itemData=itemData isEquipable=true
hasQuantity=true}}
{{!-- armor material type --}}
<div title="{{lookup @root/config.i18n.armorMaterialTypes itemData.data.armorMaterialType}}">
{{lookup @root/config.i18n.armorMaterialTypesAbbr itemData.data.armorMaterialType}}
<div title="{{lookup @root/config.i18n.armorMaterialTypes itemData.system.armorMaterialType}}">
{{lookup @root/config.i18n.armorMaterialTypesAbbr itemData.system.armorMaterialType}}
</div>
{{!-- armor type --}}
<div title="{{lookup @root/config.i18n.armorTypes itemData.data.armorType}}">
{{lookup @root/config.i18n.armorTypesAbbr itemData.data.armorType}}
<div title="{{lookup @root/config.i18n.armorTypes itemData.system.armorType}}">
{{lookup @root/config.i18n.armorTypesAbbr itemData.system.armorType}}
</div>
{{!-- armor value --}}
<div>{{ itemData.data.armorValue}}</div>
<div>{{ itemData.system.armorValue}}</div>
{{/systems/ds4/templates/sheets/actor/components/item-list-entry.hbs}}
{{/each}}
</ol>
@ -113,7 +113,7 @@ documentType='item' type='armor'}}
{{#> systems/ds4/templates/sheets/actor/components/item-list-header.hbs isEquipable=true hasQuantity=true
type='shield'}}
{{!-- armor value --}}
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="data.armorValue"
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="system.armorValue"
title="{{localize 'DS4.SortByArmorValue'}}">
{{localize 'DS4.ArmorValueAbbr'}}
</div>
@ -122,7 +122,7 @@ documentType='item' type='armor'}}
{{#> systems/ds4/templates/sheets/actor/components/item-list-entry.hbs itemData=itemData isEquipable=true
hasQuantity=true}}
{{!-- armor value --}}
<div>{{itemData.data.armorValue}}</div>
<div>{{itemData.system.armorValue}}</div>
{{/systems/ds4/templates/sheets/actor/components/item-list-entry.hbs}}
{{/each}}
</ol>
@ -137,7 +137,7 @@ documentType='item' type='shield'}}
{{#> systems/ds4/templates/sheets/actor/components/item-list-header.hbs isEquipable=true hasQuantity=true
type='equipment'}}
{{!-- storage location --}}
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="data.storageLocation"
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="system.storageLocation"
title="{{localize 'DS4.SortByStorageLocation'}}">{{localize 'DS4.StorageLocation'}}</div>
{{/systems/ds4/templates/sheets/actor/components/item-list-header.hbs}}
{{#each itemsByType.equipment as |itemData id|}}
@ -145,7 +145,7 @@ documentType='item' type='shield'}}
hasQuantity=true}}
{{!-- storage location --}}
<input class="ds4-embedded-document-list__editable change-item" type="text"
value="{{itemData.data.storageLocation}}" data-dtype="String" data-property="data.storageLocation"
value="{{itemData.system.storageLocation}}" data-dtype="String" data-property="system.storageLocation"
title="{{localize 'DS4.StorageLocation'}}">
{{/systems/ds4/templates/sheets/actor/components/item-list-entry.hbs}}
{{/each}}
@ -160,14 +160,14 @@ documentType='item' type='equipment'}}
<ol class="ds4-embedded-document-list ds4-embedded-document-list--loot item-list">
{{#> systems/ds4/templates/sheets/actor/components/item-list-header.hbs hasQuantity=true type='loot'}}
{{!-- storage location --}}
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="data.storageLocation"
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="system.storageLocation"
title="{{localize 'DS4.SortByStorageLocation'}}">{{localize 'DS4.StorageLocation'}}</div>
{{/systems/ds4/templates/sheets/actor/components/item-list-header.hbs}}
{{#each itemsByType.loot as |itemData id|}}
{{#> systems/ds4/templates/sheets/actor/components/item-list-entry.hbs itemData=itemData hasQuantity=true}}
{{!-- storage location --}}
<input class="ds4-embedded-document-list__editable change-item" type="text"
value="{{itemData.data.storageLocation}}" data-dtype="String" data-property="data.storageLocation"
value="{{itemData.system.storageLocation}}" data-dtype="String" data-property="system.storageLocation"
title="{{localize 'DS4.StorageLocation'}}">
{{/systems/ds4/templates/sheets/actor/components/item-list-entry.hbs}}
{{/each}}

View file

@ -6,24 +6,24 @@ SPDX-License-Identifier: MIT
--}}
<div class="ds4-profile">
{{#each data.data.profile as |profile-data-value profile-data-key|}}
{{#each data.system.profile as |profile-data-value profile-data-key|}}
{{#if (and (ne profile-data-key 'biography') (ne profile-data-key 'specialCharacteristics'))}}
<div class="ds4-profile__entry">
<label class="ds4-profile__entry-label" for="data.profile.{{profile-data-key}}">
<label class="ds4-profile__entry-label" for="system.profile.{{profile-data-key}}">
{{lookup ../config.i18n.characterProfile profile-data-key}}
</label>
<input class="ds4-profile__entry-input" type="text" name="data.profile.{{profile-data-key}}"
<input class="ds4-profile__entry-input" type="text" name="system.profile.{{profile-data-key}}"
value="{{profile-data-value}}"
data-dtype="{{lookup ../config.i18n.characterProfileDTypes profile-data-key}}" />
</div>
{{/if}}
{{/each}}
<div class="ds4-profile__entry">
<label class="ds4-profile__entry-label" for="data.profile.specialCharacteristics">
<label class="ds4-profile__entry-label" for="system.profile.specialCharacteristics">
{{lookup config.i18n.characterProfile 'specialCharacteristics'}}
</label>
<textarea class="ds4-profile__entry-input ds4-profile__entry-input--multiline"
name="data.profile.specialCharacteristics" data-dtype="String"
rows="4">{{data.data.profile.specialCharacteristics}}</textarea>
name="system.profile.specialCharacteristics" data-dtype="String"
rows="4">{{data.system.profile.specialCharacteristics}}</textarea>
</div>
</div>

View file

@ -12,13 +12,13 @@ SPDX-License-Identifier: MIT
<ol class="ds4-embedded-document-list ds4-embedded-document-list--talent item-list">
{{#> systems/ds4/templates/sheets/actor/components/item-list-header.hbs type='talent'}}
{{!-- rank --}}
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="data.rank.total"
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="system.rank.total"
title="{{localize 'DS4.SortByTalentRank'}}">{{localize 'DS4.TalentRank'}}</div>
{{/systems/ds4/templates/sheets/actor/components/item-list-header.hbs}}
{{#each itemsByType.talent as |itemData id|}}
{{#> systems/ds4/templates/sheets/actor/components/item-list-entry.hbs itemData=itemData}}
{{!-- rank --}}
<div>{{toRomanNumerals itemData.data.rank.total}}</div>
<div>{{toRomanNumerals itemData.system.rank.total}}</div>
{{/systems/ds4/templates/sheets/actor/components/item-list-entry.hbs}}
{{/each}}
</ol>

View file

@ -50,12 +50,12 @@ titleKey=titleKey}}
{{#> systems/ds4/templates/sheets/actor/components/item-list-header.hbs isEquipable=true hideDescription=true
type='spell'}}
{{!-- spell type --}}
<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="system.spellType"
title="{{localize 'DS4.SortBySpellType'}}">{{localize 'DS4.SpellTypeAbbr'}}</div>
{{!-- spell modifier --}}
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="data.spellModifier.complex"
data-data-path2="data.spellModifier.numerical" title="{{localize 'DS4.SortBySpellModifier'}}">{{localize
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="system.spellModifier.complex"
data-data-path2="system.spellModifier.numerical" title="{{localize 'DS4.SortBySpellModifier'}}">{{localize
'DS4.SpellModifierAbbr'}}</div>
{{!-- max. distance --}}
@ -73,23 +73,23 @@ titleKey=titleKey}}
hideDescription=true}}
{{!-- spell type --}}
<img class="ds4-embedded-document-list__image"
src="{{lookup @root/config.icons.spellTypes itemData.data.spellType}}"
title="{{lookup @root/config.i18n.spellTypes itemData.data.spellType}}" />
src="{{lookup @root/config.icons.spellTypes itemData.system.spellType}}"
title="{{lookup @root/config.i18n.spellTypes itemData.system.spellType}}" />
{{!-- spell modifier --}}
<div title="{{localize 'DS4.SpellModifier'}}">{{#if (eq itemData.data.spellModifier.complex
'')}}{{itemData.data.spellModifier.numerical}}{{else}}{{itemData.data.spellModifier.complex}}{{/if}}</div>
<div title="{{localize 'DS4.SpellModifier'}}">{{#if (eq itemData.system.spellModifier.complex
'')}}{{itemData.system.spellModifier.numerical}}{{else}}{{itemData.system.spellModifier.complex}}{{/if}}</div>
{{!-- max. distance --}}
{{> distanceUnit titleKey='DS4.SpellDistance' unitDatum=itemData.data.maxDistance
{{> distanceUnit titleKey='DS4.SpellDistance' unitDatum=itemData.system.maxDistance
config=@root/config}}
{{!-- duration --}}
{{> temporalUnit titleKey='DS4.SpellDuration' unitDatum=itemData.data.duration config=@root/config}}
{{> temporalUnit titleKey='DS4.SpellDuration' unitDatum=itemData.system.duration config=@root/config}}
{{!-- cooldown duration --}}
<div title="{{localize 'DS4.CooldownDuration'}}">{{lookup @root/config.i18n.cooldownDurations
itemData.data.cooldownDuration}}</div>
itemData.system.cooldownDuration}}</div>
{{/systems/ds4/templates/sheets/actor/components/item-list-entry.hbs}}
{{/each}}

View file

@ -7,10 +7,10 @@ SPDX-License-Identifier: MIT
<div class="ds4-item-properties ds4-item-properties--armor">
<h4 class="ds4-item-properties__title">{{localize 'DS4.ItemPropertiesArmor'}}</h4>
<div class="form-group">
<label for="data.armorType-{{data._id}}">{{localize "DS4.ArmorType"}}</label>
<label for="system.armorType-{{data._id}}">{{localize "DS4.ArmorType"}}</label>
<div class="form-fields">
<select id="data.armorType-{{data._id}}" name="data.armorType" data-dtype="String">
{{#select data.data.armorType}}
<select id="system.armorType-{{data._id}}" name="system.armorType" data-dtype="String">
{{#select data.system.armorType}}
{{#each config.i18n.armorTypes as |value key|}}
<option value="{{key}}">{{value}}</option>
{{/each}}
@ -19,10 +19,10 @@ SPDX-License-Identifier: MIT
</div>
</div>
<div class="form-group">
<label for="data.armorMaterialType-{{data._id}}">{{localize "DS4.ArmorMaterialType"}}</label>
<label for="system.armorMaterialType-{{data._id}}">{{localize "DS4.ArmorMaterialType"}}</label>
<div class="form-fields">
<select id="data.armorMaterialType-{{data._id}}" name="data.armorMaterialType" data-dtype="String">
{{#select data.data.armorMaterialType}}
<select id="system.armorMaterialType-{{data._id}}" name="system.armorMaterialType" data-dtype="String">
{{#select data.system.armorMaterialType}}
{{#each config.i18n.armorMaterialTypes as |value key|}}
<option value="{{key}}">{{value}}</option>
{{/each}}

View file

@ -7,8 +7,8 @@ SPDX-License-Identifier: MIT
<div class="ds4-item-properties ds4-item-properties--equipable">
<h4 class="ds4-item-properties__title">{{localize 'DS4.ItemPropertiesEquipable'}}</h4>
<div class="form-group">
<label for="data.equipped-{{data._id}}">{{localize "DS4.ItemEquipped"}}</label>
<input id="data.equipped-{{data._id}}" data-dtype="Boolean" type="checkbox" name="data.equipped" {{checked
data.data.equipped}} />
<label for="system.equipped-{{data._id}}">{{localize "DS4.ItemEquipped"}}</label>
<input id="system.equipped-{{data._id}}" data-dtype="Boolean" type="checkbox" name="system.equipped" {{checked
data.system.equipped}} />
</div>
</div>

View file

@ -7,15 +7,15 @@ SPDX-License-Identifier: MIT
<div class="ds4-item-properties ds4-item-properties--physical">
<h4 class="ds4-item-properties__title">{{localize 'DS4.ItemPropertiesPhysical'}}</h4>
<div class="form-group">
<label for="data.price-{{data._id}}">{{localize "DS4.PriceGold"}}</label>
<input id="data.price-{{data._id}}" data-dtype="Number" type="number" min="0" max="99999" step="0.01"
placeholder="0" name="data.price" value="{{data.data.price}}" />
<label for="system.price-{{data._id}}">{{localize "DS4.PriceGold"}}</label>
<input id="system.price-{{data._id}}" data-dtype="Number" type="number" min="0" max="99999" step="0.01"
placeholder="0" name="system.price" value="{{data.system.price}}" />
</div>
<div class="form-group">
<label for="data.availability-{{data._id}}">{{localize "DS4.ItemAvailability"}}</label>
<label for="system.availability-{{data._id}}">{{localize "DS4.ItemAvailability"}}</label>
<div class="form-fields">
<select id="data.availability-{{data._id}}" name="data.availability" data-dtype="String">
{{#select data.data.availability}}
<select id="system.availability-{{data._id}}" name="system.availability" data-dtype="String">
{{#select data.system.availability}}
{{#each config.i18n.itemAvailabilities as |value key|}}
<option value="{{key}}">{{value}}</option>
{{/each}}
@ -25,14 +25,14 @@ SPDX-License-Identifier: MIT
</div>
{{#if isOwned}}
<div class="form-group">
<label for="data.quantity-{{data._id}}">{{localize "DS4.Quantity"}}</label>
<input id="data.quantity-{{data._id}}" data-dtype="Number" type="number" name="data.quantity" placeholder="0"
value="{{data.data.quantity}}" />
<label for="system.quantity-{{data._id}}">{{localize "DS4.Quantity"}}</label>
<input id="system.quantity-{{data._id}}" data-dtype="Number" type="number" name="system.quantity" placeholder="0"
value="{{data.system.quantity}}" />
</div>
<div class="form-group">
<label for="data.storageLocation-{{data._id}}">{{localize "DS4.StorageLocation"}}</label>
<input id="data.storageLocation-{{data._id}}" data-dtype="String" type="text" name="data.storageLocation"
value="{{data.data.storageLocation}}" />
<label for="system.storageLocation-{{data._id}}">{{localize "DS4.StorageLocation"}}</label>
<input id="system.storageLocation-{{data._id}}" data-dtype="String" type="text" name="system.storageLocation"
value="{{data.system.storageLocation}}" />
</div>
{{/if}}
</div>

View file

@ -7,8 +7,8 @@ SPDX-License-Identifier: MIT
<div class="ds4-item-properties ds4-item-properties--protective">
<h4 class="ds4-item-properties__title">{{localize 'DS4.ItemPropertiesProtective'}}</h4>
<div class="form-group">
<label for="data.armorValue-{{data._id}}">{{localize "DS4.ArmorValue"}}</label>
<input id="data.armorValue-{{data._id}}" data-dtype="Number" type="number" min="0" step="1" placeholder="0"
name="data.armorValue" value="{{data.data.armorValue}}" />
<label for="system.armorValue-{{data._id}}">{{localize "DS4.ArmorValue"}}</label>
<input id="system.armorValue-{{data._id}}" data-dtype="Number" type="number" min="0" step="1" placeholder="0"
name="system.armorValue" value="{{data.system.armorValue}}" />
</div>
</div>

View file

@ -7,9 +7,9 @@ SPDX-License-Identifier: MIT
<div class="ds4-item-properties ds4-item-properties--special-creature-ability">
<h4 class="ds4-item-properties__title">{{localize 'DS4.ItemPropertiesSpecialCreatureAbility'}}</h4>
<div class="form-group">
<label for="data.experiencePoints-{{data._id}}">{{localize
<label for="system.experiencePoints-{{data._id}}">{{localize
"DS4.SpecialCreatureAbilityExperiencePoints"}}</label>
<input id="data.experiencePoints-{{data._id}}" data-dtype="Number" type="number" min="0" step="1"
placeholder="0" name="data.experiencePoints" value="{{data.data.experiencePoints}}" />
<input id="system.experiencePoints-{{data._id}}" data-dtype="Number" type="number" min="0" step="1"
placeholder="0" name="system.experiencePoints" value="{{data.system.experiencePoints}}" />
</div>
</div>

View file

@ -7,29 +7,29 @@ SPDX-License-Identifier: MIT
<div class="ds4-item-properties ds4-item-properties--spell">
<h4 class="ds4-item-properties__title">{{localize 'DS4.ItemPropertiesSpell'}}</h4>
<div class="form-group">
<label for="data.spellModifier.numerical-{{data._id}}"
<label for="system.spellModifier.numerical-{{data._id}}"
title="{{localize 'DS4.SpellModifierNumericalDescription'}}">{{localize
"DS4.SpellModifierNumerical"}}</label>
<div class="form-fields">
<input id="data.spellModifier.numerical-{{data._id}}" data-dtype="Number" type="number"
name="data.spellModifier.numerical" placeholder="0" value="{{data.data.spellModifier.numerical}}" />
<input id="system.spellModifier.numerical-{{data._id}}" data-dtype="Number" type="number"
name="system.spellModifier.numerical" placeholder="0" value="{{data.system.spellModifier.numerical}}" />
</div>
</div>
<div class="form-group">
<label for="data.spellModifier.complex-{{data._id}}"
<label for="system.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}}" />
<input id="system.spellModifier.complex-{{data._id}}" data-dtype="String" type="text"
name="system.spellModifier.complex" value="{{data.system.spellModifier.complex}}" />
</div>
</div>
<div class="form-group">
<label for="data.spellType-{{data._id}}" title="{{localize 'DS4.SpellTypeDescription'}}">{{localize
<label for="system.spellType-{{data._id}}" title="{{localize 'DS4.SpellTypeDescription'}}">{{localize
"DS4.SpellType"}}</label>
<div class="form-fields">
<select id="data.spellType-{{data._id}}" name="data.spellType" data-dtype="String">
{{#select data.data.spellType}}
<select id="system.spellType-{{data._id}}" name="system.spellType" data-dtype="String">
{{#select data.system.spellType}}
{{#each config.i18n.spellTypes as |value key|}}
<option value="{{key}}">{{value}}</option>
{{/each}}
@ -40,10 +40,10 @@ SPDX-License-Identifier: MIT
<div class="form-group slim">
<label title="{{localize 'DS4.SpellDistanceDescription'}}">{{localize "DS4.SpellDistance"}}</label>
<div class="form-fields">
<input data-dtype="String" type="text" name="data.maxDistance.value"
value="{{data.data.maxDistance.value}}" />
<select name="data.maxDistance.unit" data-dtype="String">
{{#select data.data.maxDistance.unit}}
<input data-dtype="String" type="text" name="system.maxDistance.value"
value="{{data.system.maxDistance.value}}" />
<select name="system.maxDistance.unit" data-dtype="String">
{{#select data.system.maxDistance.unit}}
{{#each config.i18n.distanceUnits as |value key|}}
<option value="{{key}}">{{value}}</option>
{{/each}}
@ -54,10 +54,10 @@ SPDX-License-Identifier: MIT
<div class="form-group slim">
<label title="{{localize 'DS4.SpellEffectRadiusDescription'}}">{{localize "DS4.SpellEffectRadius"}}</label>
<div class="form-fields">
<input data-dtype="String" type="text" name="data.effectRadius.value"
value="{{data.data.effectRadius.value}}" />
<select name="data.effectRadius.unit" data-dtype="String">
{{#select data.data.effectRadius.unit}}
<input data-dtype="String" type="text" name="system.effectRadius.value"
value="{{data.system.effectRadius.value}}" />
<select name="system.effectRadius.unit" data-dtype="String">
{{#select data.system.effectRadius.unit}}
{{#each config.i18n.distanceUnits as |value key|}}
<option value="{{key}}">{{value}}</option>
{{/each}}
@ -68,9 +68,9 @@ SPDX-License-Identifier: MIT
<div class="form-group slim">
<label title="{{localize 'DS4.SpellDurationDescription'}}">{{localize "DS4.SpellDuration"}}</label>
<div class="form-fields">
<input data-dtype="String" type="text" name="data.duration.value" value="{{data.data.duration.value}}" />
<select name="data.duration.unit" data-dtype="String">
{{#select data.data.duration.unit}}
<input data-dtype="String" type="text" name="system.duration.value" value="{{data.system.duration.value}}" />
<select name="system.duration.unit" data-dtype="String">
{{#select data.system.duration.unit}}
{{#each config.i18n.temporalUnits as |value key|}}
<option value="{{key}}">{{value}}</option>
{{/each}}
@ -79,11 +79,11 @@ SPDX-License-Identifier: MIT
</div>
</div>
<div class="form-group">
<label for="data.cooldownDuration-{{data._id}}"
<label for="system.cooldownDuration-{{data._id}}"
title="{{localize 'DS4.CooldownDurationDescription'}}">{{localize "DS4.CooldownDuration"}}</label>
<div class="form-fields">
<select id="data.cooldownDuration-{{data._id}}" name="data.cooldownDuration" data-dtype="String">
{{#select data.data.cooldownDuration}}
<select id="system.cooldownDuration-{{data._id}}" name="system.cooldownDuration" data-dtype="String">
{{#select data.system.cooldownDuration}}
{{#each config.i18n.cooldownDurations as |value key|}}
<option value="{{key}}">{{value}}</option>
{{/each}}
@ -97,42 +97,42 @@ SPDX-License-Identifier: MIT
<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>
<input class="ds4-checkbox-grid__checkbox" id="system.spellGroups.{{key}}-{{../data._id}}"
name="system.spellGroups.{{key}}" data-dtype="Boolean" type="checkbox" {{checked (lookup
../data.system.spellGroups key)}} />
<label for="system.spellGroups.{{key}}-{{../data._id}}">{{value}}</label>
</div>
{{/each}}
</div>
</div>
<div class="form-group">
<label for="data.allowsDefense-{{data._id}}" title="{{localize 'DS4.SpellAllowsDefenseDescription'}}">{{localize
<label for="system.allowsDefense-{{data._id}}" title="{{localize 'DS4.SpellAllowsDefenseDescription'}}">{{localize
"DS4.SpellAllowsDefense"}}</label>
<div class="form-fields">
<input id="data.allowsDefense-{{data._id}}" data-dtype="Boolean" type="checkbox" name="data.allowsDefense"
{{checked data.data.allowsDefense}} />
<input id="system.allowsDefense-{{data._id}}" data-dtype="Boolean" type="checkbox" name="system.allowsDefense"
{{checked data.system.allowsDefense}} />
</div>
</div>
<div class="form-group slim">
<label title="{{localize 'DS4.SpellMinimumLevelDescription'}}">{{localize "DS4.SpellMinimumLevel"}}</label>
<div class="form-fields">
<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"
name="data.minimumLevels.healer" placeholder="" value="{{data.data.minimumLevels.healer}}" />
<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"
name="data.minimumLevels.wizard" placeholder="" value="{{data.data.minimumLevels.wizard}}" />
<label for="data.minimumLevels.sorcerer-{{data._id}}">{{localize
<label for="system.minimumLevels.healer-{{data._id}}">{{localize "DS4.SpellCasterClassHealer"}}</label>
<input id="system.minimumLevels.healer-{{data._id}}" data-dtype="Number" type="number" min="0" step="1"
name="system.minimumLevels.healer" placeholder="" value="{{data.system.minimumLevels.healer}}" />
<label for="system.minimumLevels.wizard-{{data._id}}">{{localize "DS4.SpellCasterClassWizard"}}</label>
<input id="system.minimumLevels.wizard-{{data._id}}" data-dtype="Number" type="number" min="0" step="1"
name="system.minimumLevels.wizard" placeholder="" value="{{data.system.minimumLevels.wizard}}" />
<label for="system.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}}" />
<input id="system.minimumLevels.sorcerer-{{data._id}}" data-dtype="Number" type="number" min="0" step="1"
name="system.minimumLevels.sorcerer" placeholder="" value="{{data.system.minimumLevels.sorcerer}}" />
</div>
</div>
<div class="form-group">
<label for="data.price-{{data._id}}" title="{{localize 'DS4.SpellPriceDescription'}}">{{localize
<label for="system.price-{{data._id}}" title="{{localize 'DS4.SpellPriceDescription'}}">{{localize
"DS4.SpellPrice"}}</label>
<div class="form-fields">
<span id="data.price-{{data._id}}">{{data.data.price}}</span>
<span id="system.price-{{data._id}}">{{data.system.price}}</span>
</div>
</div>
</div>

View file

@ -9,18 +9,18 @@ SPDX-License-Identifier: MIT
<div class="form-group slim">
<label>{{localize "DS4.TalentRank"}}</label>
<div class="form-fields">
<label for="data.rank.base-{{data._id}}">{{localize "DS4.TalentRankBase"}}</label>
<input id="data.rank.base-{{data._id}}" data-dtype="Number" type="number" min="0"
max="{{data.data.rank.max}}" step="1" name="data.rank.base" value="{{data.data.rank.base}}" />
<label for="data.rank.max-{{data._id}}">{{localize "DS4.TalentRankMax"}}</label>
<input id="data.rank.max-{{data._id}}" data-dtype="Number" type="number" min="0" step="1"
name="data.rank.max" value="{{data.data.rank.max}}" />
<label for="system.rank.base-{{data._id}}">{{localize "DS4.TalentRankBase"}}</label>
<input id="system.rank.base-{{data._id}}" data-dtype="Number" type="number" min="0"
max="{{data.system.rank.max}}" step="1" name="system.rank.base" value="{{data.system.rank.base}}" />
<label for="system.rank.max-{{data._id}}">{{localize "DS4.TalentRankMax"}}</label>
<input id="system.rank.max-{{data._id}}" data-dtype="Number" type="number" min="0" step="1"
name="system.rank.max" value="{{data.system.rank.max}}" />
<br />
<label for="data.rank.mod-{{data._id}}">{{localize "DS4.TalentRankMod"}}</label>
<input id="data.rank.mod-{{data._id}}" data-dtype="Number" type="number" min="0" step="1"
name="data.rank.mod" value="{{data.data.rank.mod}}" />
<label for="data.rank.total-{{data._id}}">{{localize "DS4.TalentRankTotal"}}</label>
<span id="data.rank.total-{{data._id}}">{{data.data.rank.total}}</span>
<label for="system.rank.mod-{{data._id}}">{{localize "DS4.TalentRankMod"}}</label>
<input id="system.rank.mod-{{data._id}}" data-dtype="Number" type="number" min="0" step="1"
name="system.rank.mod" value="{{data.system.rank.mod}}" />
<label for="system.rank.total-{{data._id}}">{{localize "DS4.TalentRankTotal"}}</label>
<span id="system.rank.total-{{data._id}}">{{data.system.rank.total}}</span>
</div>
</div>
</div>

View file

@ -7,10 +7,10 @@ SPDX-License-Identifier: MIT
<div class="ds4-item-properties ds4-item-properties--weapon">
<h4 class="ds4-item-properties__title">{{localize 'DS4.ItemPropertiesWeapon'}}</h4>
<div class="form-group">
<label for="data.attackType-{{data._id}}">{{localize "DS4.AttackType"}}</label>
<label for="system.attackType-{{data._id}}">{{localize "DS4.AttackType"}}</label>
<div class="form-fields">
<select id="data.attackType-{{data._id}}" name="data.attackType" data-dtype="String">
{{#select data.data.attackType}}
<select id="system.attackType-{{data._id}}" name="system.attackType" data-dtype="String">
{{#select data.system.attackType}}
{{#each config.i18n.attackTypes as |value key|}}
<option value="{{key}}">{{value}}</option>
{{/each}}
@ -19,13 +19,13 @@ SPDX-License-Identifier: MIT
</div>
</div>
<div class="form-group">
<label for="data.weaponBonus-{{data._id}}">{{localize "DS4.WeaponBonus"}}</label>
<input id="data.weaponBonus-{{data._id}}" data-dtype="Number" type="number" name="data.weaponBonus"
placeholder="0" value="{{data.data.weaponBonus}}" />
<label for="system.weaponBonus-{{data._id}}">{{localize "DS4.WeaponBonus"}}</label>
<input id="system.weaponBonus-{{data._id}}" data-dtype="Number" type="number" name="system.weaponBonus"
placeholder="0" value="{{data.system.weaponBonus}}" />
</div>
<div class="form-group">
<label for="data.opponentDefense-{{data._id}}">{{localize "DS4.OpponentDefense"}}</label>
<input id="data.opponentDefense-{{data._id}}" data-dtype="Number" type="number" name="data.opponentDefense"
placeholder="0" value="{{data.data.opponentDefense}}" />
<label for="system.opponentDefense-{{data._id}}">{{localize "DS4.OpponentDefense"}}</label>
<input id="system.opponentDefense-{{data._id}}" data-dtype="Number" type="number" name="system.opponentDefense"
placeholder="0" value="{{data.system.opponentDefense}}" />
</div>
</div>

View file

@ -5,6 +5,6 @@ SPDX-License-Identifier: MIT
--}}
<div class="ds4-sheet-tab tab description" data-group="primary" data-tab="description">
{{editor content=data.data.description target="data.description" button=true owner=owner
editable=editable}}
{{editor data.system.description target="system.description" button=true owner=owner
editable=editable engine="prosemirror"}}
</div>

View file

@ -18,6 +18,7 @@ function cleanPackEntry(entry, cleanSourceId = true) {
}
});
if (entry.permission) entry.permission = { default: 0 };
if (entry._stats?.lastModifiedBy) entry._stats.lastModifiedBy = "DS4BuildSystem00";
const embeddedDocumentCollections = [
"drawings",