Merge branch '069-effects-tab' into 'master'

Add effects tab to actor sheets

Closes #69

See merge request dungeonslayers/ds4!128
This commit is contained in:
Johannes Loher 2021-07-23 15:51:23 +00:00
commit f6ee996cb2
32 changed files with 587 additions and 264 deletions

View file

@ -1,10 +1,11 @@
{
"DS4.UserInteractionAddItem": "Neu",
"DS4.UserInteractionEditItem": "Bearbeiten",
"DS4.UserInteractionDeleteItem": "Löschen",
"DS4.UserInteractionAddEffect": "Neuer Effekt",
"DS4.UserInteractionEditEffect": "Effekt bearbeiten",
"DS4.UserInteractionDeleteEffect": "Effekt löschen",
"DS4.UserInteractionAdd": "Neu",
"DS4.UserInteractionAddItemTitle": "Item Erstellen",
"DS4.UserInteractionEditItemTitle": "Item Bearbeiten",
"DS4.UserInteractionDeleteItemTitle": "Item Löschen",
"DS4.UserInteractionAddEffectTitle": "Effekt Erstellen",
"DS4.UserInteractionEditEffectTitle": "Effekt Bearbeiten",
"DS4.UserInteractionDeleteEffectTitle": "Effekt Löschen",
"DS4.DocumentImageAltText": "Bild von {name}",
"DS4.RollableImageRollableTitle": "Für {name} würfeln",
"DS4.DiceOverlayImageAltText": "Bild eines W20",
@ -18,7 +19,6 @@
"DS4.HeadingAbilities": "Fähigkeiten",
"DS4.HeadingSpells": "Zaubersprüche",
"DS4.HeadingDescription": "Beschreibung",
"DS4.HeadingSpecialCreatureAbilities": "Besondere Fähigkeiten",
"DS4.AttackType": "Angriffsart",
"DS4.AttackTypeAbbr": "AA",
"DS4.DialogAttackTypeSelection": "Welche Angriffsart?",
@ -121,6 +121,10 @@
"DS4.SpellMinimumLevelsSorcerer": "Zugangsstufe für Schwarzmagier",
"DS4.SpellMinimumLevelsSorcererAbbr": "Zugangsstufe Sch",
"DS4.SpellPrice": "Preis (Gold)",
"DS4.EffectEnabled": "Aktiv",
"DS4.EffectEnabledAbbr": "A",
"DS4.EffectLabel": "Bezeichnung",
"DS4.EffectSourceName": "Quelle",
"DS4.ActorName": "Name",
"DS4.ActorImageAltText": "Bild des Aktors",
"DS4.ActorTypeCharacter": "Charakter",
@ -207,7 +211,7 @@
"DS4.CreatureBaseInfoSizeCategory": "Größenkategorie",
"DS4.CreatureBaseInfoExperiencePoints": "Erfahrungspunkte",
"DS4.CreatureBaseInfoDescription": "Beschreibung",
"DS4.WarningManageActiveEffectOnOwnedItem": "Das Verwalten von aktiven Effekten innerhalb eines besessen Items wird derzeit nicht unterstützt und wird in einem nachfolgenden Update hinzugefügt.",
"DS4.WarningManageActiveEffectOnOwnedItem": "Das Verwalten von aktiven Effekten innerhalb eines besessen Items wird derzeit nicht unterstützt und wird in einem nachfolgenden Update hinzugefügt. Falls Sie eigentlich den auf den Aktor übertragenen Effekt verwalten wollen, können Sie dies im 'Effekte'-Tab des Aktorbogens tun.",
"DS4.WarningActorCannotOwnItem": "Der Aktor '{actorName}' vom Typ '{actorType}' kann das Item '{itemName}' vom Typ '{itemType}' nicht besitzen.",
"DS4.ErrorDiceCoupFumbleOverlap": "Es gibt eine Überlappung zwischen Patzern und Immersiegen.",
"DS4.ErrorSlayingDiceRecursionLimitExceeded": "Die maximale Rekursionstiefe für slayende Würfelwürfe wurde überschritten.",
@ -253,6 +257,7 @@
"DS4.ErrorActorDoesNotHaveItem": "Der Aktor '{actor}' hat kein Item mit der ID '{id}'.",
"DS4.ErrorUnexpectedError": "Es gab einen unerwarteten Fehler im Dungeonslayers 4 System. Für mehr Details schauen Sie bitte in die Konsole (F12).",
"DS4.ErrorItemDoesNotHaveEffect": "Das Item '{item}' hat keinen Effekt mit der ID '{id}'.",
"DS4.ErrorActorDoesNotHaveEffect": "Der Aktor '{actor}' hat keinen Effekt mit der ID '{id}'.",
"DS4.DialogRollOptionsCheckTargetNumberLabel": "Probenwert",
"DS4.DialogRollOptionsGMModifierLabel": "SL-Modifikator",
"DS4.DialogRollOptionsMaximumCoupResultLabel": "Immersieg bis",
@ -295,5 +300,17 @@
"DS4.ChecksWorkMechanism": "Mechanismus Öffnen",
"DS4.ActorCheckFlavor": "{actor} würfelt eine {check} Probe.",
"DS4.ActorGenericCheckFlavor": "{actor} würfelt eine Probe gegen {attribute} + {trait}.",
"DS4.CheckTooltip": "{check} Probe würfeln"
"DS4.CheckTooltip": "{check} Probe würfeln",
"DS4.NewWeaponName": "Neue Waffe",
"DS4.NewArmorName": "Neue Panzerung",
"DS4.NewShieldName": "Neuer Schild",
"DS4.NewSpellName": "Neuer Zauberspruch",
"DS4.NewEquipmentName": "Neue Ausrüstung",
"DS4.NewLootName": "Neue Beute",
"DS4.NewTalentName": "Neues Talent",
"DS4.NewRacialAbilityName": "Neue Volksfähigkeit",
"DS4.NewLanguageName": "Neue Sprache",
"DS4.NewAlphabetName": "Neue Schriftzeichen",
"DS4.NewSpecialCreatureAbilityName": "Neue Besondere Kreaturenfähigkeit",
"DS4.NewEffectLabel": "Neuer Effekt"
}

View file

@ -1,10 +1,11 @@
{
"DS4.UserInteractionAddItem": "Add item",
"DS4.UserInteractionEditItem": "Edit item",
"DS4.UserInteractionDeleteItem": "Delete item",
"DS4.UserInteractionAddEffect": "Add Effect",
"DS4.UserInteractionEditEffect": "Edit Effect",
"DS4.UserInteractionDeleteEffect": "Delete Effect",
"DS4.UserInteractionAdd": "Add",
"DS4.UserInteractionAddItemTitle": "Create Item",
"DS4.UserInteractionEditItemTitle": "Edit Item",
"DS4.UserInteractionDeleteItemTitle": "Delete Item",
"DS4.UserInteractionAddEffectTitle": "Create Effect",
"DS4.UserInteractionEditEffectTitle": "Edit Effect",
"DS4.UserInteractionDeleteEffectTitle": "Delete Effect",
"DS4.DocumentImageAltText": "Image of {name}",
"DS4.RollableImageRollableTitle": "Roll for {name}",
"DS4.DiceOverlayImageAltText": "Image of a d20",
@ -18,7 +19,6 @@
"DS4.HeadingAbilities": "Abilities",
"DS4.HeadingSpells": "Spells",
"DS4.HeadingDescription": "Description",
"DS4.HeadingSpecialCreatureAbilities": "Special Abilities",
"DS4.AttackType": "Attack Type",
"DS4.AttackTypeAbbr": "AT",
"DS4.DialogAttackTypeSelection": "Which Attack Type?",
@ -121,6 +121,10 @@
"DS4.SpellMinimumLevelsSorcerer": "Minimum level for Sorcerers",
"DS4.SpellMinimumLevelsSorcererAbbr": "Min lvl SRC",
"DS4.SpellPrice": "Price (Gold)",
"DS4.EffectEnabled": "Enabled",
"DS4.EffectEnabledAbbr": "E",
"DS4.EffectLabel": "Label",
"DS4.EffectSourceName": "Source",
"DS4.ActorName": "Name",
"DS4.ActorImageAltText": "Image of the Actor",
"DS4.ActorTypeCharacter": "Character",
@ -207,7 +211,7 @@
"DS4.CreatureBaseInfoSizeCategory": "Size Category",
"DS4.CreatureBaseInfoExperiencePoints": "Experience Points",
"DS4.CreatureBaseInfoDescription": "Description",
"DS4.WarningManageActiveEffectOnOwnedItem": "Managing Active Effects within an Owned Item is not currently supported and will be added in a subsequent update.",
"DS4.WarningManageActiveEffectOnOwnedItem": "Managing Active Effects within an Owned Item is not currently supported and will be added in a subsequent update. If you actually want to manage the effect that has been transferred to the actor, you can do so in the 'Effects' tab in the actor sheet.",
"DS4.WarningActorCannotOwnItem": "The actor '{actorName}' of type '{actorType}' cannot own the item '{itemName}' of type '{itemType}'.",
"DS4.ErrorDiceCoupFumbleOverlap": "There is an overlap between Fumbles and Coups.",
"DS4.ErrorSlayingDiceRecursionLimitExceeded": "Maximum recursion depth for slaying dice roll exceeded.",
@ -253,6 +257,7 @@
"DS4.ErrorActorDoesNotHaveItem": "The actor '{actor}' does not have any item with the id '{id}'.",
"DS4.ErrorUnexpectedError": "There was an unexpected error in the Dungeonslayers 4 system. For more details, please take a look at the console (F12).",
"DS4.ErrorItemDoesNotHaveEffect": "The item '{item}' does not have any effect with the id '{id}'.",
"DS4.ErrorActorDoesNotHaveEffect": "The actor '{actor}' does not have any effect with the id '{id}'.",
"DS4.DialogRollOptionsCheckTargetNumberLabel": "Check Target Number",
"DS4.DialogRollOptionsGMModifierLabel": "Game Master Modifier",
"DS4.DialogRollOptionsMaximumCoupResultLabel": "Coup to",
@ -295,5 +300,17 @@
"DS4.ChecksWorkMechanism": "Work Mechanism",
"DS4.ActorCheckFlavor": "{actor} rolls a {check} check.",
"DS4.ActorGenericCheckFlavor": "{actor} rolls a check against {attribute} + {trait}.",
"DS4.CheckTooltip": "Roll a {check} check"
"DS4.CheckTooltip": "Roll a {check} check",
"DS4.NewWeaponName": "New Weapon",
"DS4.NewArmorName": "New Armor",
"DS4.NewShieldName": "New Shield",
"DS4.NewSpellName": "New Spell",
"DS4.NewEquipmentName": "New Equipment",
"DS4.NewLootName": "New Loot",
"DS4.NewTalentName": "New Talent",
"DS4.NewRacialAbilityName": "New Racial Ability",
"DS4.NewLanguageName": "New Language",
"DS4.NewAlphabetName": "New Alphabet",
"DS4.NewSpecialCreatureAbilityName": "New Special Creature Ability",
"DS4.NewEffectLabel": "New Effect"
}

View file

@ -3,6 +3,7 @@
// SPDX-License-Identifier: MIT
import { DS4Actor } from "./actor/actor";
import { getGame } from "./helpers";
declare global {
interface DocumentClassConfig {
@ -10,7 +11,18 @@ declare global {
}
}
type PromisedType<T> = T extends Promise<infer U> ? U : T;
export class DS4ActiveEffect extends ActiveEffect {
/**
* A fallback icon that can be used if no icon is defined for the effect.
*/
static FALLBACK_ICON = "icons/svg/aura.svg";
/**
* A cached reference to the source document to avoid recurring database lookups
*/
protected source: PromisedType<ReturnType<typeof fromUuid>> | undefined = undefined;
/** @override */
apply(actor: DS4Actor, change: foundry.data.ActiveEffectData["changes"][number]): unknown {
change.value = Roll.replaceFormulaData(change.value, actor.data);
@ -21,4 +33,25 @@ export class DS4ActiveEffect extends ActiveEffect {
}
return super.apply(actor, change);
}
/**
* Gets the current source name based on the cached source object.
*/
async getCurrentSourceName(): Promise<string> {
const game = getGame();
const origin = await this.getSource();
if (origin === null) return game.i18n.localize("None");
return origin.name ?? game.i18n.localize("Unknown");
}
/**
* Gets the source document for this effect. Uses the cached {@link DS4ActiveEffect#origin} if it has already been
* set.
*/
protected async getSource(): ReturnType<typeof fromUuid> {
if (this.source === undefined) {
this.source = this.data.origin !== undefined ? await fromUuid(this.data.origin) : null;
}
return this.source;
}
}

View file

@ -5,16 +5,17 @@
//
// SPDX-License-Identifier: MIT
import { DS4ActiveEffect } from "../../active-effect";
import { ModifiableDataBaseTotal } from "../../common/common-data";
import { DS4 } from "../../config";
import { getCanvas, getGame } from "../../helpers";
import { DS4Item } from "../../item/item";
import { DS4Settings, getDS4Settings } from "../../settings";
import notifications from "../../ui/notifications";
import { enforce } from "../../utils";
import { isCheck } from "../actor-data-properties";
/**
* The base Sheet class for all DS4 Actors
* The base sheet class for all {@link DS4Actor}s.
*/
export class DS4ActorSheet extends ActorSheet<ActorSheet.Options, DS4ActorSheetData> {
/** @override */
@ -22,15 +23,7 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Options, DS4ActorSheetD
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["ds4", "sheet", "actor"],
height: 620,
scrollY: [
".values",
".inventory",
".spells",
".abilities",
".profile",
".biography",
".special-creature-abilities",
],
scrollY: [".values", ".inventory", ".spells", ".abilities", ".effects", ".biography", ".description"],
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "values" }],
dragDrop: [
{ dragSelector: ".item-list .item", dropSelector: null },
@ -46,29 +39,36 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Options, DS4ActorSheetD
return `${basePath}/${this.actor.data.type}-sheet.hbs`;
}
/**
* This method returns the data for the template of the actor sheet.
* It explicitly adds the items of the object sorted by type in the
* object itemsByType.
* @returns The data fed to the template of the actor sheet
*/
/** @override */
async getData(): Promise<DS4ActorSheetData> {
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))];
}),
);
const enrichedEffectPromises = this.actor.effects.map(async (effect) => {
return {
...effect.toObject(),
icon: effect.data.icon ?? DS4ActiveEffect.FALLBACK_ICON,
sourceName: await effect.getCurrentSourceName(),
};
});
const enrichedEffects = await Promise.all(enrichedEffectPromises);
const data = {
...this.addTooltipsToData(await super.getData()),
// Add the localization config to the data:
config: DS4,
// Add the items explicitly sorted by type to the data:
itemsByType,
enrichedEffects,
settings: getDS4Settings(),
};
return data;
}
/**
* Adds tooltips to the attributes, traits, and combatValues of the actor data of the given {@link ActorSheet.Data}.
*/
protected addTooltipsToData(data: ActorSheet.Data): ActorSheet.Data {
const valueGroups = [data.data.data.attributes, data.data.data.traits, data.data.data.combatValues];
@ -80,6 +80,9 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Options, DS4ActorSheetD
return data;
}
/**
* Generates a tooltip for a given attribute, trait, or combatValue.
*/
protected getTooltipForValue(value: ModifiableDataBaseTotal<number>): string {
return `${value.base} (${getGame().i18n.localize("DS4.TooltipBaseValue")}) + ${
value.mod
@ -92,144 +95,213 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Options, DS4ActorSheetD
activateListeners(html: JQuery): void {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
// Add Inventory Item
html.find(".item-create").on("click", this.onItemCreate.bind(this));
html.find(".control-item").on("click", this.onControlItem.bind(this));
html.find(".change-item").on("change", this.onChangeItem.bind(this));
// Update Inventory Item
html.find(".item-edit").on("click", (ev) => {
const li = $(ev.currentTarget).parents(".item");
const id = li.data("itemId");
const item = this.actor.items.get(id);
if (!item) {
throw new Error(getGame().i18n.format("DS4.ErrorActorDoesNotHaveItem", { id, actor: this.actor.name }));
}
if (!item.sheet) {
throw new Error(getGame().i18n.localize("DS4.ErrorUnexpectedError"));
}
item.sheet.render(true);
});
// Delete Inventory Item
html.find(".item-delete").on("click", (ev) => {
const li = $(ev.currentTarget).parents(".item");
this.actor.deleteEmbeddedDocuments("Item", [li.data("itemId")]);
li.slideUp(200, () => this.render(false));
});
html.find(".item-change").on("change", this.onItemChange.bind(this));
html.find(".control-effect").on("click", this.onControlEffect.bind(this));
html.find(".change-effect").on("change", this.onChangeEffect.bind(this));
html.find(".rollable-item").on("click", this.onRollItem.bind(this));
html.find(".rollable-check").on("click", this.onRollCheck.bind(this));
}
/**
* Handle creating a new embedded Item for the actor using initial data defined in the HTML dataset
* Handles a click on an element of this sheet to control an embedded item of the actor corresponding to this sheet.
*
* @param event - The originating click event
*/
protected onItemCreate(event: JQuery.ClickEvent): void {
protected onControlItem(event: JQuery.ClickEvent): void {
event.preventDefault();
const header = event.currentTarget;
const { type, ...data } = foundry.utils.deepClone(header.dataset);
const name = `New ${type.capitalize()}`;
const a = event.currentTarget;
switch (a.dataset["action"]) {
case "create":
return this.onCreateItem(event);
case "edit":
return this.onEditItem(event);
case "delete":
return this.onDeleteItem(event);
}
}
/**
* Creates a new embedded item using the initial data defined in the HTML dataset of the clicked element.
*
* @param event - The originating click event
*/
protected onCreateItem(event: JQuery.ClickEvent): void {
const { type, ...data } = foundry.utils.deepClone(event.currentTarget.dataset);
const name = getGame().i18n.localize(`DS4.New${type.capitalize()}Name`);
const itemData = {
name: name,
type: type,
data: data,
};
DS4Item.create(itemData, { parent: this.actor });
Item.create(itemData, { parent: this.actor });
}
/**
* Handle changes to properties of an Owned Item from within character sheet.
* Can currently properly bind: see getValue().
* Assumes the item property is given as the value of the HTML element property 'data-property'.
* @param ev - The originating change event
* Opens the sheet of the embedded item corresponding to the clicked element.
*
* @param event - The originating click event
*/
protected onItemChange(ev: JQuery.ChangeEvent): void {
ev.preventDefault();
const el: HTMLFormElement = $(ev.currentTarget).get(0);
const id = $(ev.currentTarget).parents(".item").data("itemId");
protected onEditItem(event: JQuery.ClickEvent): void {
const id = $(event.currentTarget).parents(".item").data("id");
const item = this.actor.items.get(id);
if (!item) {
throw new Error(getGame().i18n.format("DS4.ErrorActorDoesNotHaveItem", { id, actor: this.actor.name }));
}
const itemObject = item.toObject();
const property: string | undefined = $(ev.currentTarget).data("property");
// Early return:
// Disabled => do nothing
if (el.disabled || el.getAttribute("disabled")) return;
// name not given => raise
if (property === undefined) {
throw TypeError("HTML element does not provide 'data-property' attribute");
}
// Set new value
const newValue = this.getValue(el);
foundry.utils.setProperty(itemObject, property, newValue);
item.update(itemObject);
enforce(item, getGame().i18n.format("DS4.ErrorActorDoesNotHaveItem", { id, actor: this.actor.name }));
enforce(item.sheet);
item.sheet.render(true);
}
/**
* Collect the value of a form element depending on the element's type
* The value is parsed to:
* - Checkbox: boolean
* - Text input: string
* - Number: number
* @param el - The input element to collect the value of
* Deletes the embedded item corresponding to the clicked element.
*
* @param event - The originating click event
*/
private getValue(el: HTMLFormElement): boolean | string | number {
// One needs to differentiate between e.g. checkboxes (value="on") and select boxes etc.
// Checkbox:
if (el.type === "checkbox") {
const value: boolean = el.checked;
return value;
protected onDeleteItem(event: JQuery.ClickEvent): void {
const li = $(event.currentTarget).parents(".item");
this.actor.deleteEmbeddedDocuments("Item", [li.data("id")]);
li.slideUp(200, () => this.render(false));
}
/**
* Applies a change to a property of an embedded item depending on the `data-property` attribute of the
* {@link HTMLInputElement} that has been changed and its new value.
*
* @param event - The originating change event
*/
protected onChangeItem(event: JQuery.ChangeEvent): void {
return this.onChangeEmbeddedDocument(event, ".item", "Item");
}
/**
* Handles a click on an element of this sheet to control an embedded effect of the actor corresponding to this
* sheet.
*
* @param event - The originating click event
*/
protected onControlEffect(event: JQuery.ClickEvent): void {
event.preventDefault();
const a = event.currentTarget;
switch (a.dataset["action"]) {
case "create":
return this.onCreateEffect();
case "edit":
return this.onEditEffect(event);
case "delete":
return this.onDeleteEffect(event);
}
}
// Text input:
else if (el.type === "text") {
const value: string = el.value;
return value;
}
/**
* Creates a new embedded effect.
*
* @param event - The originating click event
*/
protected onCreateEffect(): void {
const effectData = {
label: getGame().i18n.localize(`DS4.NewEffectLabel`),
icon: "icons/svg/aura.svg",
origin: this.actor.uuid,
};
ActiveEffect.create(effectData, { parent: this.actor });
}
// Numbers:
else if (el.type === "number") {
const value = Number(el.value.trim());
return value;
}
/**
* Opens the sheet of the embedded effect corresponding to the clicked element.
*
* @param event - The originating click event
*/
protected onEditEffect(event: JQuery.ClickEvent): void {
const id = $(event.currentTarget).parents(".effect").data("id");
const effect = this.actor.effects.get(id);
enforce(effect, getGame().i18n.format("DS4.ErrorActorDoesNotHaveEffect", { id, actor: this.actor.name }));
effect.sheet.render(true);
}
// // Ranges:
// else if (el.type === "range") {
// const value: string = el.value.trim();
// return value;
// }
/**
* Deletes the embedded item corresponding to the clicked element.
*
* @param event - The originating click event
*/
protected onDeleteEffect(event: JQuery.ClickEvent): void {
const li = $(event.currentTarget).parents(".effect");
const id = li.data("id");
this.actor.deleteEmbeddedDocuments("ActiveEffect", [id]);
li.slideUp(200, () => this.render(false));
}
// // Radio Checkboxes (untested, cf. FormDataExtended.process)
// else if (el.type === "radio") {
// const chosen: HTMLFormElement = el.find((r: HTMLFormElement) => r["checked"]);
// const value: string = chosen ? chosen.value : null;
// return value;
// }
/**
* Applies a change to a property of an embedded effect depending on the `data-property` attribute of the
* {@link HTMLInputElement} that has been changed and its new value.
*
* @param event - The originating change event
*/
protected onChangeEffect(event: JQuery.ChangeEvent): void {
return this.onChangeEmbeddedDocument(event, ".effect", "ActiveEffect");
}
// // Multi-Select (untested, cf. FormDataExtended.process)
// else if (el.type === "select-multiple") {
// const value: Array<string> = [];
// el.options.array.forEach((opt: HTMLOptionElement) => {
// if (opt.selected) value.push(opt.value);
// });
// return value;
/**
* Applies a change to a property of an embedded document of the actor belonging to this sheet. The change depends
* on the `data-property` attribute of the {@link HTMLInputElement} that has been changed and its new value.
*
* @param event - The originating change event
* @param documentSelector - The selector for the closest parent of the changed {@link HTMLInputElement}, which
* contains the `data-id` attribute providing the `id` of the embedded document to be
* changed.
* @param documentName - The name of the embedded document to be changed.
*/
protected onChangeEmbeddedDocument(
event: JQuery.ChangeEvent,
documentSelector: string,
documentName: "Item" | "ActiveEffect",
): void {
event.preventDefault();
const element = $(event.currentTarget).get(0);
enforce(element instanceof HTMLInputElement);
if (element.disabled) return;
// unsupported:
else {
throw new TypeError("Binding of item property to this type of HTML element not supported; given: " + el);
const effectElement = element.closest(documentSelector);
enforce(effectElement instanceof HTMLElement);
const id = effectElement.dataset["id"];
const property = element.dataset["property"];
const inverted = Boolean(element.dataset["inverted"]);
enforce(property !== undefined, TypeError("HTML element does not provide 'data-property' attribute"));
const newValue = this.parseValue(element, inverted);
this.actor.updateEmbeddedDocuments(documentName, [{ _id: id, [property]: newValue }]);
}
/**
* Parses the value of the given {@link HTMLInputElement} depending on the element's type
* The value is parsed to:
* - checkbox: `boolean`
* - text input: `string`
* - number: `number`
*
* @param element - The input element to parse the value from
* @param inverted - Whether or not the value should be inverted
*/
protected parseValue(element: HTMLInputElement, inverted = false): boolean | string | number {
switch (element.type) {
case "checkbox": {
const value: boolean = element.checked;
return inverted ? !value : value;
}
case "text": {
const value: string = element.value;
return value;
}
case "number": {
const value = Number(element.value.trim());
return value;
}
default: {
throw new TypeError(
"Binding of item property to this type of HTML element not supported; given: " + element,
);
}
}
}
@ -239,11 +311,9 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Options, DS4ActorSheetD
*/
protected onRollItem(event: JQuery.ClickEvent): void {
event.preventDefault();
const id = $(event.currentTarget).parents(".item").data("itemId");
const id = $(event.currentTarget).parents(".item").data("id");
const item = this.actor.items.get(id);
if (!item) {
throw new Error(getGame().i18n.format("DS4.ErrorActorDoesNotHaveItem", { id, actor: this.actor.name }));
}
enforce(item, getGame().i18n.format("DS4.ErrorActorDoesNotHaveItem", { id, actor: this.actor.name }));
item.roll().catch((e) => notifications.error(e, { log: true }));
}
@ -265,7 +335,7 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Options, DS4ActorSheetD
const check = target.dataset.check;
if (!check) return super._onDragStart(event);
if (!isCheck(check)) throw new Error(getGame().i18n.format("DS4.ErrorCannotDragMissingCheck", { check }));
enforce(isCheck(check), getGame().i18n.format("DS4.ErrorCannotDragMissingCheck", { check }));
const dragData = {
actorId: this.actor.id,
@ -280,7 +350,7 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Options, DS4ActorSheetD
/** @override */
protected async _onDropItem(event: DragEvent, data: ActorSheet.DropData.Item): Promise<unknown> {
const item = await DS4Item.fromDropData(data);
const item = await Item.fromDropData(data);
if (item && !this.actor.canOwnItemType(item.data.type)) {
notifications.warn(
getGame().i18n.format("DS4.WarningActorCannotOwnItem", {
@ -299,5 +369,13 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Options, DS4ActorSheetD
interface DS4ActorSheetData extends ActorSheet.Data<ActorSheet.Options> {
config: typeof DS4;
itemsByType: Record<string, foundry.data.ItemData[]>;
enrichedEffects: EnrichedActiveEffectDataSource[];
settings: DS4Settings;
}
type ActiveEffectDataSource = foundry.data.ActiveEffectData["_source"];
interface EnrichedActiveEffectDataSource extends ActiveEffectDataSource {
icon: string;
sourceName: string;
}

View file

@ -8,30 +8,33 @@ export default async function registerHandlebarsPartials(): Promise<void> {
const templatePaths = [
"systems/ds4/templates/sheets/actor/components/actor-header.hbs",
"systems/ds4/templates/sheets/actor/components/actor-progression.hbs",
"systems/ds4/templates/sheets/actor/components/add-button.hbs",
"systems/ds4/templates/sheets/actor/components/biography.hbs",
"systems/ds4/templates/sheets/actor/components/character-properties.hbs",
"systems/ds4/templates/sheets/actor/components/check.hbs",
"systems/ds4/templates/sheets/actor/components/checks.hbs",
"systems/ds4/templates/sheets/actor/components/combat-value.hbs",
"systems/ds4/templates/sheets/actor/components/combat-values.hbs",
"systems/ds4/templates/sheets/actor/components/control-button-group.hbs",
"systems/ds4/templates/sheets/actor/components/core-value.hbs",
"systems/ds4/templates/sheets/actor/components/core-values.hbs",
"systems/ds4/templates/sheets/actor/components/creature-properties.hbs",
"systems/ds4/templates/sheets/actor/components/currency.hbs",
"systems/ds4/templates/sheets/actor/components/effect-list-entry.hbs",
"systems/ds4/templates/sheets/actor/components/effect-list-header.hbs",
"systems/ds4/templates/sheets/actor/components/item-list-entry.hbs",
"systems/ds4/templates/sheets/actor/components/item-list-header.hbs",
"systems/ds4/templates/sheets/actor/components/items-overview.hbs",
"systems/ds4/templates/sheets/actor/components/overview-add-button.hbs",
"systems/ds4/templates/sheets/actor/components/overview-control-buttons.hbs",
"systems/ds4/templates/sheets/actor/components/profile.hbs",
"systems/ds4/templates/sheets/actor/components/rollable-image.hbs",
"systems/ds4/templates/sheets/actor/components/talent-rank-equation.hbs",
"systems/ds4/templates/sheets/actor/tabs/abilities.hbs",
"systems/ds4/templates/sheets/actor/tabs/biography.hbs",
"systems/ds4/templates/sheets/actor/tabs/character-abilities.hbs",
"systems/ds4/templates/sheets/actor/tabs/character-inventory.hbs",
"systems/ds4/templates/sheets/actor/tabs/creature-abilities.hbs",
"systems/ds4/templates/sheets/actor/tabs/creature-inventory.hbs",
"systems/ds4/templates/sheets/actor/tabs/description.hbs",
"systems/ds4/templates/sheets/actor/tabs/special-creature-abilities.hbs",
"systems/ds4/templates/sheets/actor/tabs/effects.hbs",
"systems/ds4/templates/sheets/actor/tabs/spells.hbs",
"systems/ds4/templates/sheets/actor/tabs/values.hbs",
"systems/ds4/templates/sheets/item/components/body.hbs",

View file

@ -15,3 +15,7 @@ export function getGame(): Game {
}
return game;
}
export function getGameSafe(): Game | undefined {
return game instanceof Game ? game : undefined;
}

View file

@ -99,8 +99,9 @@ export class DS4ItemSheet extends ItemSheet<ItemSheet.Options, DS4ItemSheetData>
*/
protected async createActiveEffect(): Promise<ActiveEffect | undefined> {
const createData = {
label: "New Effect",
label: getGame().i18n.localize(`DS4.NewEffectLabel`),
icon: "icons/svg/aura.svg",
origin: this.item.uuid,
};
return ActiveEffect.create(createData, { parent: this.item });

View file

@ -38,7 +38,7 @@ async function getOrCreateRollItemMacro(itemData: foundry.data.ItemData["_source
}
/**
* Executes the roll item macro for the given itemId.
* Executes the roll item macro for the item associated to the given `itemId`.
*/
export async function rollItem(itemId: string): Promise<void> {
const actor = getActiveActor();

View file

@ -5,10 +5,12 @@
import { getGame } from "./helpers";
export function registerSystemSettings(): void {
const game = getGame();
/**
* Track the migrations version of the latest migration that has been applied
*/
getGame().settings.register("ds4", "systemMigrationVersion", {
game.settings.register("ds4", "systemMigrationVersion", {
name: "System Migration Version",
scope: "world",
config: false,
@ -16,7 +18,7 @@ export function registerSystemSettings(): void {
default: -1,
});
getGame().settings.register("ds4", "useSlayingDiceForAutomatedChecks", {
game.settings.register("ds4", "useSlayingDiceForAutomatedChecks", {
name: "DS4.SettingUseSlayingDiceForAutomatedChecksName",
hint: "DS4.SettingUseSlayingDiceForAutomatedChecksHint",
scope: "world",
@ -25,7 +27,7 @@ export function registerSystemSettings(): void {
default: false,
});
getGame().settings.register("ds4", "showSlayerPoints", {
game.settings.register("ds4", "showSlayerPoints", {
name: "DS4.SettingShowSlayerPointsName",
hint: "DS4.SettingShowSlayerPointsHint",
scope: "world",
@ -42,9 +44,10 @@ export interface DS4Settings {
}
export function getDS4Settings(): DS4Settings {
const game = getGame();
return {
systemMigrationVersion: getGame().settings.get("ds4", "systemMigrationVersion"),
useSlayingDiceForAutomatedChecks: getGame().settings.get("ds4", "useSlayingDiceForAutomatedChecks"),
showSlayerPoints: getGame().settings.get("ds4", "showSlayerPoints"),
systemMigrationVersion: game.settings.get("ds4", "systemMigrationVersion"),
useSlayingDiceForAutomatedChecks: game.settings.get("ds4", "useSlayingDiceForAutomatedChecks"),
showSlayerPoints: game.settings.get("ds4", "showSlayerPoints"),
};
}

24
src/module/utils.ts Normal file
View file

@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: 2021 Johannes Loher
//
// SPDX-License-Identifier: MIT
import { getGameSafe } from "./helpers";
/**
* Tests if the given `value` is truthy.
*
* If it is not truthy, an {@link Error} is thrown, which depends on the given `message` parameter:
* - If `message` is a string`, it is used to construct a new {@link Error} which then is thrown.
* - If `message` is an instance of {@link Error}, it is thrown.
* - If `message` is `undefined`, an {@link Error} with a default message is thrown.
*/
export function enforce(value: unknown, message?: string | Error): asserts value {
if (!value) {
if (!message) {
message =
getGameSafe()?.i18n.localize("DS4.ErrorUnexpectedError") ??
"There was an unexpected error in the Dungeonslayers 4 system. For more details, please take a look at the console (F12).";
}
throw message instanceof Error ? message : new Error(message);
}
}

View file

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: 2021 Johannes Loher
*
* SPDX-License-Identifier: MIT
*/
.ds4-control-button-group {
display: flex;
text-align: center;
width: 100%;
padding: 0 calc(1em / 3);
&__button {
flex: 1;
}
}

View file

@ -8,7 +8,7 @@
@use "../utils/mixins";
@use "../utils/variables";
.ds4-item-list {
.ds4-embedded-document-list {
@include mixins.mark-invalid-or-disabled-input;
$row-height: 1.75em;
@ -23,24 +23,51 @@
&--weapon {
grid-template-columns: $row-height $row-height 3ch 3fr $row-height 1fr 3ch 5fr 5ch;
:nth-child(9n + 1),
:nth-child(9n + 5),
:nth-child(9n + 6),
:nth-child(9n + 7) {
justify-self: center;
}
}
&--armor {
grid-template-columns: $row-height $row-height 3ch 3fr 1fr 1fr 3ch 5fr 5ch;
:nth-child(9n + 1),
:nth-child(9n + 7) {
justify-self: center;
}
}
&--shield {
grid-template-columns: $row-height $row-height 3ch 1fr 3ch 3fr 5ch;
:nth-child(7n + 1),
:nth-child(7n + 5) {
justify-self: center;
}
}
&--equipment {
grid-template-columns: $row-height $row-height 3ch 1fr 10ch 3fr 5ch;
:nth-child(7n + 1) {
justify-self: center;
}
}
&--loot {
grid-template-columns: $row-height 3ch 1fr 10ch 3fr 5ch;
}
&--spell {
grid-template-columns: $row-height $row-height 2fr $row-height 1fr 1fr 1fr 1fr 5ch;
:nth-child(9n + 1),
:nth-child(9n + 4),
:nth-child(9n + 6),
:nth-child(9n + 7),
:nth-child(9n + 8) {
justify-self: center;
}
}
&--talent {
grid-template-columns: $row-height 1fr 21ch 3fr 5ch;
:nth-child(9n + 3) {
justify-self: center;
}
}
&--racial-ability,
&--language,
@ -49,6 +76,13 @@
grid-template-columns: $row-height 1fr 3fr 5ch;
}
&--effect {
grid-template-columns: $row-height $row-height 3fr 2fr 5ch;
:nth-child(5n + 1) {
justify-self: center;
}
}
&__row {
display: contents; // TODO: Once chromium supports `grid-template-columns: subgrid` (https://bugs.chromium.org/p/chromium/issues/detail?id=618969), switch to `display: grid; grid: 1/-1; grid-template-columns: subgrid`
@ -96,17 +130,9 @@
text-overflow: ellipsis;
}
}
&__control-buttons {
display: grid;
grid-template-columns: 1fr 1fr;
text-align: center;
width: 100%;
padding: 0 calc(1em / 3);
}
}
.ds4-item-list-title {
.ds4-embedded-document-list-title {
border-bottom: variables.$border-groove;
font-weight: bold;
margin-bottom: 0;

View file

@ -29,12 +29,13 @@
@include meta.load-css("components/checks");
@include meta.load-css("components/combat_value");
@include meta.load-css("components/combat_values");
@include meta.load-css("components/control_button_group");
@include meta.load-css("components/core_value");
@include meta.load-css("components/core_values");
@include meta.load-css("components/currency");
@include meta.load-css("components/description");
@include meta.load-css("components/embedded_document_list");
@include meta.load-css("components/forms");
@include meta.load-css("components/item_list");
@include meta.load-css("components/profile");
@include meta.load-css("components/rollable_image");
@include meta.load-css("components/sheet_tab_nav");

View file

@ -5,7 +5,6 @@
"version": "1.1.3",
"minimumCoreVersion": "0.8.8",
"compatibleCoreVersion": "0.8.8",
"templateVersion": 6,
"author": "Johannes Loher, Gesina Schwalbe, Oliver Rümpelein, Siegfried Krug, Max Tharr, Sascha Martens",
"authors": [
{

View file

@ -18,6 +18,7 @@ SPDX-License-Identifier: MIT
<a class="ds4-sheet-tab-nav__item item" data-tab="inventory">{{localize 'DS4.HeadingInventory'}}</a>
<a class="ds4-sheet-tab-nav__item item" data-tab="spells">{{localize 'DS4.HeadingSpells'}}</a>
<a class="ds4-sheet-tab-nav__item item" data-tab="abilities">{{localize 'DS4.HeadingAbilities'}}</a>
<a class="ds4-sheet-tab-nav__item item" data-tab="effects">{{localize 'DS4.HeadingEffects'}}</a>
<a class="ds4-sheet-tab-nav__item item" data-tab="biography">{{localize 'DS4.HeadingBiography'}}</a>
</nav>
@ -35,7 +36,10 @@ SPDX-License-Identifier: MIT
{{> systems/ds4/templates/sheets/actor/tabs/spells.hbs}}
{{!-- Abilities Tab --}}
{{> systems/ds4/templates/sheets/actor/tabs/abilities.hbs}}
{{> systems/ds4/templates/sheets/actor/tabs/character-abilities.hbs}}
{{!-- Effects Tab --}}
{{> systems/ds4/templates/sheets/actor/tabs/effects.hbs}}
{{!-- Biography Tab --}}
{{> systems/ds4/templates/sheets/actor/tabs/biography.hbs}}

View file

@ -0,0 +1,20 @@
{{!--
SPDX-FileCopyrightText: 2021 Johannes Loher
SPDX-FileCopyrightText: 2021 Gesina Schwalbe
SPDX-License-Identifier: MIT
--}}
{{!
!-- Render an "add" button.
!-- @param documentType: The type of document this button controls, item or effect
!-- @param title: The title to use for the link element (will be localized)
!-- @param type: An optional property to use as data-type attribute
}}
<div>
<a class="control-{{documentType}}" title="{{localize title}}" data-action="create" {{#if type}}data-type="{{type}}"
{{/if}}>
<i class="fas fa-plus"></i>
{{localize "DS4.UserInteractionAdd"}}
</a>
</div>

View file

@ -0,0 +1,20 @@
{{!--
SPDX-FileCopyrightText: 2021 Johannes Loher
SPDX-FileCopyrightText: 2021 Gesina Schwalbe
SPDX-License-Identifier: MIT
--}}
{{!--
!-- Render a group of an "edit" and a "delete" button.
!-- The current item is defined by the data-id attribute of the parent li element.
!-- @param documentType: The type of document that is controlled by this button group, item or effect
!-- @param editTitle: The title to use for the edit link element (will be localized)
!-- @param deleteTitle: The title to use for the delete link element (will be localized)
--}}
<div class="ds4-control-button-group">
<a class="ds4-control-button-group__button control-{{documentType}}" data-action="edit"
title="{{localize editTitle}}"><i class="fas fa-edit"></i></a>
<a class="ds4-control-button-group__button control-{{documentType}}" data-action="delete"
title="{{localize deleteTitle}}"><i class="fas fa-trash"></i></a>
</div>

View file

@ -10,7 +10,7 @@ SPDX-License-Identifier: MIT
<div class="ds4-currency">
{{#each data.data.currency as |value key|}}
<label for="data.currency.{{key}}" class="flex05">{{lookup ../config.i18n.characterCurrency key}}</label>
<input class="ds4-currency__value ds4-currency__value--{{key}} item-change" type="number" min="0" step="1"
<input class="ds4-currency__value ds4-currency__value--{{key}} change-item" type="number" min="0" step="1"
name="data.currency.{{key}}" id="data.currency.{{key}}" value="{{value}}" data-dtype="Number" />
{{/each}}
</div>

View file

@ -0,0 +1,31 @@
{{!--
SPDX-FileCopyrightText: 2021 Johannes Loher
SPDX-FileCopyrightText: 2021 Gesina Schwalbe
SPDX-License-Identifier: MIT
--}}
{{!--
!-- Render an effect list entry row.
!-- @param effectData: The data of the item.
--}}
<li class="ds4-embedded-document-list__row effect" data-id="{{effectData._id}}">
{{!-- enabled --}}
<input class="ds4-embedded-document-list__editable ds4-embedded-document-list__editable--checkbox change-effect" type="checkbox" {{checked
(ne effectData.disabled true)}} data-dtype="Boolean" data-property="disabled" data-inverted="true"
title="{{localize 'DS4.EffectEnabled'}}">
{{!-- icon --}}
{{> systems/ds4/templates/sheets/actor/components/rollable-image.hbs rollable=false src=effectData.icon
alt=(localize "DS4.EffectIconAltText" label=effectData.label) title=effectData.label}}
{{!-- label --}}
<div title="{{effectData.label}}">{{effectData.label}}</div>
{{!-- source name --}}
<div>{{effectData.sourceName}}</div>
{{!-- control button group --}}
{{> systems/ds4/templates/sheets/actor/components/control-button-group.hbs documentType="effect"
editTitle="DS4.UserInteractionEditEffectTitle" deleteTitle="DS4.UserInteractionDeleteEffectTitle"}}
</li>

View file

@ -0,0 +1,26 @@
{{!--
SPDX-FileCopyrightText: 2021 Johannes Loher
SPDX-FileCopyrightText: 2021 Gesina Schwalbe
SPDX-License-Identifier: MIT
--}}
{{!--
!-- Render an effect list header row.
--}}
<li class="ds4-embedded-document-list__row ds4-embedded-document-list__row--header">
{{!-- enabled --}}
<div title="{{localize 'DS4.EffectEnabled'}}">{{localize 'DS4.EffectEnabledAbbr'}}</div>
{{!-- icon --}}
<div></div>
{{!-- label --}}
<div>{{localize 'DS4.EffectLabel'}}</div>
{{!-- source name --}}
<div>{{localize 'DS4.EffectSourceName'}}</div>
{{!-- control buttons placeholder --}}
<div></div>
</li>

View file

@ -15,10 +15,10 @@ SPDX-License-Identifier: MIT
!-- @param hideDescription: A flag to disable the description column.
!-- @param @partial-block: Custom column headers can be passed using the partial block.
--}}
<li class="ds4-item-list__row item" data-item-id="{{itemData._id}}">
<li class="ds4-embedded-document-list__row item" data-id="{{itemData._id}}">
{{!-- equipped --}}
{{#if isEquipable}}
<input class="ds4-item-list__editable ds4-item-list__editable--checkbox item-change" type="checkbox" {{checked
<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"
title="{{localize 'DS4.ItemEquipped'}}">
{{/if}}
@ -30,12 +30,12 @@ SPDX-License-Identifier: MIT
{{!-- amount --}}
{{#if hasQuantity}}
<input class="ds4-item-list__editable item-change" type="number" min="0" step="1" value="{{itemData.data.quantity}}"
<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'}}" />
{{/if}}
{{!-- name --}}
<input class="ds4-item-list__editable item-change" type="text" value="{{itemData.name}}" data-dtype="String"
<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}}" />
{{!-- item type specifics --}}
@ -45,11 +45,11 @@ SPDX-License-Identifier: MIT
{{!-- description --}}
{{#unless hideDescription}}
<div class="ds4-item-list__description" title="{{htmlToPlainText itemData.data.description}}">
<div class="ds4-embedded-document-list__description" title="{{htmlToPlainText itemData.data.description}}">
{{{itemData.data.description}}}</div>
{{/unless}}
{{!-- control buttons --}}
{{> systems/ds4/templates/sheets/actor/components/overview-control-buttons.hbs
class="ds4-item-list__control-buttons" }}
{{!-- control button group --}}
{{> systems/ds4/templates/sheets/actor/components/control-button-group.hbs documentType="item"
editTitle="DS4.UserInteractionEditItemTitle" deleteTitle="DS4.UserInteractionDeleteItemTitle"}}
</li>

View file

@ -14,7 +14,7 @@ SPDX-License-Identifier: MIT
!-- @param hideDescription: A flag to disable the description column.
!-- @param @partial-block: Custom column headers can be passed using the partial block.
--}}
<li class="ds4-item-list__row ds4-item-list__row--header">
<li class="ds4-embedded-document-list__row ds4-embedded-document-list__row--header">
{{!-- equipped --}}
{{#if isEquipable}}
<div title="{{localize 'DS4.ItemEquipped'}}">{{localize 'DS4.ItemEquippedAbbr'}}</div>

View file

@ -7,12 +7,12 @@ SPDX-License-Identifier: MIT
--}}
{{!-- WEAPONS --}}
<h4 class="ds4-item-list-title">{{localize 'DS4.ItemTypeWeaponPlural'}}</h4>
<h4 class="ds4-embedded-document-list-title">{{localize 'DS4.ItemTypeWeaponPlural'}}</h4>
{{#unless (isEmpty itemsByType.weapon)}}
<ol class="ds4-item-list ds4-item-list--weapon item-list">
<ol class="ds4-embedded-document-list ds4-embedded-document-list--weapon item-list">
{{#> systems/ds4/templates/sheets/actor/components/item-list-header.hbs isEquipable=true hasQuantity=true}}
{{!-- attack type --}}
<div class="ds4-item-list__image" title="{{localize 'DS4.AttackType'}}">{{localize 'DS4.AttackTypeAbbr'}}</div>
<div class="ds4-embedded-document-list__image" title="{{localize 'DS4.AttackType'}}">{{localize 'DS4.AttackTypeAbbr'}}</div>
{{!-- weapon bonus --}}
<div title="{{localize 'DS4.WeaponBonus'}}">
@ -29,7 +29,7 @@ SPDX-License-Identifier: MIT
{{#> systems/ds4/templates/sheets/actor/components/item-list-entry.hbs itemData=itemData isEquipable=true
hasQuantity=true}}
{{!-- attack type --}}
<img class="ds4-item-list__image" src="{{lookup ../../config.icons.attackTypes itemData.data.attackType}}"
<img class="ds4-embedded-document-list__image" src="{{lookup ../../config.icons.attackTypes itemData.data.attackType}}"
title="{{lookup ../../config.i18n.attackTypes itemData.data.attackType}}" />
{{!-- weapon bonus --}}
@ -41,12 +41,13 @@ SPDX-License-Identifier: MIT
{{/each}}
</ol>
{{/unless}}
{{> systems/ds4/templates/sheets/actor/components/overview-add-button.hbs dataType='weapon'}}
{{> systems/ds4/templates/sheets/actor/components/add-button.hbs title='DS4.UserInteractionAddItemTitle'
documentType='item' type='weapon'}}
{{!-- ARMOR --}}
<h4 class="ds4-item-list-title">{{localize 'DS4.ItemTypeArmorPlural'}}</h4>
<h4 class="ds4-embedded-document-list-title">{{localize 'DS4.ItemTypeArmorPlural'}}</h4>
{{#unless (isEmpty itemsByType.armor)}}
<ol class="ds4-item-list ds4-item-list--armor item-list">
<ol class="ds4-embedded-document-list ds4-embedded-document-list--armor item-list">
{{#> systems/ds4/templates/sheets/actor/components/item-list-header.hbs isEquipable=true hasQuantity=true}}
{{!-- armor material type --}}
<div title="{{localize 'DS4.ArmorMaterialType'}}">{{localize 'DS4.ArmorMaterialTypeAbbr'}}</div>
@ -79,12 +80,13 @@ SPDX-License-Identifier: MIT
{{/each}}
</ol>
{{/unless}}
{{> systems/ds4/templates/sheets/actor/components/overview-add-button.hbs dataType='armor'}}
{{> systems/ds4/templates/sheets/actor/components/add-button.hbs title='DS4.UserInteractionAddItemTitle'
documentType='item' type='armor'}}
{{!-- SHIELD --}}
<h4 class="ds4-item-list-title">{{localize 'DS4.ItemTypeShieldPlural'}}</h4>
<h4 class="ds4-embedded-document-list-title">{{localize 'DS4.ItemTypeShieldPlural'}}</h4>
{{#unless (isEmpty itemsByType.shield)}}
<ol class="ds4-item-list ds4-item-list--shield item-list">
<ol class="ds4-embedded-document-list ds4-embedded-document-list--shield item-list">
{{#> systems/ds4/templates/sheets/actor/components/item-list-header.hbs isEquipable=true hasQuantity=true}}
{{!-- armor value --}}
<div title="{{localize 'DS4.ArmorValue'}}">
@ -100,12 +102,13 @@ SPDX-License-Identifier: MIT
{{/each}}
</ol>
{{/unless}}
{{> systems/ds4/templates/sheets/actor/components/overview-add-button.hbs dataType='shield'}}
{{> systems/ds4/templates/sheets/actor/components/add-button.hbs title='DS4.UserInteractionAddItemTitle'
documentType='item' type='shield'}}
{{!-- EQUIPMENT --}}
<h4 class="ds4-item-list-title">{{localize 'DS4.ItemTypeEquipmentPlural'}}</h4>
<h4 class="ds4-embedded-document-list-title">{{localize 'DS4.ItemTypeEquipmentPlural'}}</h4>
{{#unless (isEmpty itemsByType.equipment)}}
<ol class="ds4-item-list ds4-item-list--equipment item-list">
<ol class="ds4-embedded-document-list ds4-embedded-document-list--equipment item-list">
{{#> systems/ds4/templates/sheets/actor/components/item-list-header.hbs isEquipable=true hasQuantity=true}}
{{!-- storage location --}}
<div>{{localize 'DS4.StorageLocation'}}</div>
@ -114,18 +117,19 @@ SPDX-License-Identifier: MIT
{{#> systems/ds4/templates/sheets/actor/components/item-list-entry.hbs itemData=itemData isEquipable=true
hasQuantity=true}}
{{!-- storage location --}}
<input class="ds4-item-list__editable item-change" type="text" value="{{itemData.data.storageLocation}}"
<input class="ds4-embedded-document-list__editable change-item" type="text" value="{{itemData.data.storageLocation}}"
data-dtype="String" data-property="data.storageLocation" title="{{localize 'DS4.StorageLocation'}}">
{{/systems/ds4/templates/sheets/actor/components/item-list-entry.hbs}}
{{/each}}
</ol>
{{/unless}}
{{> systems/ds4/templates/sheets/actor/components/overview-add-button.hbs dataType='equipment'}}
{{> systems/ds4/templates/sheets/actor/components/add-button.hbs title='DS4.UserInteractionAddItemTitle'
documentType='item' type='equipment'}}
{{!-- LOOT --}}
<h4 class="ds4-item-list-title">{{localize 'DS4.ItemTypeLootPlural'}}</h4>
<h4 class="ds4-embedded-document-list-title">{{localize 'DS4.ItemTypeLootPlural'}}</h4>
{{#unless (isEmpty itemsByType.loot)}}
<ol class="ds4-item-list ds4-item-list--loot item-list">
<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}}
{{!-- storage location --}}
<div>{{localize 'DS4.StorageLocation'}}</div>
@ -133,10 +137,11 @@ SPDX-License-Identifier: MIT
{{#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-item-list__editable item-change" type="text" value="{{itemData.data.storageLocation}}"
<input class="ds4-embedded-document-list__editable change-item" type="text" value="{{itemData.data.storageLocation}}"
data-dtype="String" data-property="data.storageLocation" title="{{localize 'DS4.StorageLocation'}}">
{{/systems/ds4/templates/sheets/actor/components/item-list-entry.hbs}}
{{/each}}
</ol>
{{/unless}}
{{> systems/ds4/templates/sheets/actor/components/overview-add-button.hbs dataType='loot'}}
{{> systems/ds4/templates/sheets/actor/components/add-button.hbs title='DS4.UserInteractionAddItemTitle'
documentType='item' type='loot'}}

View file

@ -1,17 +0,0 @@
{{!--
SPDX-FileCopyrightText: 2021 Johannes Loher
SPDX-FileCopyrightText: 2021 Gesina Schwalbe
SPDX-License-Identifier: MIT
--}}
{{!
!-- Render an "add" button for adding an item of given data type.
!-- @param dataType: hand over the dataType to the partial as hash parameter
}}
<div class="item-controls">
<a class="item-control item-create" title="Create item" data-type="{{dataType}}">
<i class="fas fa-plus"></i>
{{localize "DS4.UserInteractionAddItem"}}
</a>
</div>

View file

@ -1,17 +0,0 @@
{{!--
SPDX-FileCopyrightText: 2021 Johannes Loher
SPDX-FileCopyrightText: 2021 Gesina Schwalbe
SPDX-License-Identifier: MIT
--}}
{{!--
!-- Render a group of an "edit" and a "delete" button for the current item.
!-- The current item is defined by the data-item-id HTML property of the parent li element.
!-- @param class: Additional CSS class(es) for the controls
--}}
<div class="item-controls {{class}}">
<a class="item-control item-edit" title="{{localize 'DS4.UserInteractionEditItem'}}"><i class="fas fa-edit"></i></a>
<a class="item-control item-delete" title="{{localize 'DS4.UserInteractionDeleteItem'}}"><i
class="fas fa-trash"></i></a>
</div>

View file

@ -18,7 +18,7 @@ disable the input element
!-- @param localizeString: The string to use as key for the localized tooltip
--}}
{{#*inline "talentRankValue"}}
<input class="ds4-talent-rank-equation__value item-change" data-dtype="Number" type="number" min="0" step="1" {{#if (eq
<input class="ds4-talent-rank-equation__value change-item" data-dtype="Number" type="number" min="0" step="1" {{#if (eq
property 'base' ) }}max="{{talentRank.max}}" {{/if}} {{disabled}} data-property="data.rank.{{property}}"
value="{{lookup talentRank property}}" title="{{localize localizeString}}" />
{{/inline}}

View file

@ -17,9 +17,9 @@ SPDX-License-Identifier: MIT
<nav class="ds4-sheet-tab-nav sheet-tabs tabs" data-group="primary">
<a class="ds4-sheet-tab-nav__item item" data-tab="values">{{localize 'DS4.HeadingValues'}}</a>
<a class="ds4-sheet-tab-nav__item item" data-tab="inventory">{{localize 'DS4.HeadingInventory'}}</a>
<a class="ds4-sheet-tab-nav__item item" data-tab="special-creature-abilities">{{localize
'DS4.HeadingSpecialCreatureAbilities'}}</a>
<a class="ds4-sheet-tab-nav__item item" data-tab="spells">{{localize 'DS4.HeadingSpells'}}</a>
<a class="ds4-sheet-tab-nav__item item" data-tab="abilities">{{localize 'DS4.HeadingAbilities'}}</a>
<a class="ds4-sheet-tab-nav__item item" data-tab="effects">{{localize 'DS4.HeadingEffects'}}</a>
<a class="ds4-sheet-tab-nav__item item" data-tab="description">{{localize 'DS4.HeadingDescription'}}</a>
</nav>
@ -31,12 +31,15 @@ SPDX-License-Identifier: MIT
{{!-- Inventory Tab --}}
{{> systems/ds4/templates/sheets/actor/tabs/creature-inventory.hbs}}
{{!-- Special Creature Abilities Tab --}}
{{> systems/ds4/templates/sheets/actor/tabs/special-creature-abilities.hbs}}
{{!-- Spells Tab --}}
{{> systems/ds4/templates/sheets/actor/tabs/spells.hbs}}
{{!-- Abilities Tab --}}
{{> systems/ds4/templates/sheets/actor/tabs/creature-abilities.hbs}}
{{!-- Effects Tab --}}
{{> systems/ds4/templates/sheets/actor/tabs/effects.hbs}}
{{!-- Description Tab --}}
{{> systems/ds4/templates/sheets/actor/tabs/description.hbs}}
</section>

View file

@ -7,9 +7,9 @@ SPDX-License-Identifier: MIT
<div class="tab abilities" data-group="primary" data-tab="abilities">
{{!-- TALENT --}}
<h4 class="ds4-item-list-title">{{localize 'DS4.ItemTypeTalentPlural'}}</h4>
<h4 class="ds4-embedded-document-list-title">{{localize 'DS4.ItemTypeTalentPlural'}}</h4>
{{#unless (isEmpty itemsByType.talent)}}
<ol class="ds4-item-list ds4-item-list--talent item-list">
<ol class="ds4-embedded-document-list ds4-embedded-document-list--talent item-list">
{{#> systems/ds4/templates/sheets/actor/components/item-list-header.hbs}}
{{!-- rank --}}
<div>{{localize 'DS4.TalentRank'}}</div>
@ -22,41 +22,45 @@ SPDX-License-Identifier: MIT
{{/each}}
</ol>
{{/unless}}
{{> systems/ds4/templates/sheets/actor/components/overview-add-button.hbs dataType='talent'}}
{{> systems/ds4/templates/sheets/actor/components/add-button.hbs title='DS4.UserInteractionAddItemTitle'
documentType='item' type='talent'}}
{{!-- RACIAL ABILITY --}}
<h4 class="ds4-item-list-title">{{localize 'DS4.ItemTypeRacialAbilityPlural'}}</h4>
<h4 class="ds4-embedded-document-list-title">{{localize 'DS4.ItemTypeRacialAbilityPlural'}}</h4>
{{#unless (isEmpty itemsByType.racialAbility)}}
<ol class="ds4-item-list ds4-item-list--racial-ability item-list">
<ol class="ds4-embedded-document-list ds4-embedded-document-list--racial-ability item-list">
{{> systems/ds4/templates/sheets/actor/components/item-list-header.hbs}}
{{#each itemsByType.racialAbility as |itemData id|}}
{{> systems/ds4/templates/sheets/actor/components/item-list-entry.hbs itemData=itemData}}
{{/each}}
</ol>
{{/unless}}
{{> systems/ds4/templates/sheets/actor/components/overview-add-button.hbs dataType='racialAbility'}}
{{> systems/ds4/templates/sheets/actor/components/add-button.hbs title='DS4.UserInteractionAddItemTitle'
documentType='item' type='racialAbility'}}
{{!-- LANGUAGE --}}
<h4 class="ds4-item-list-title">{{localize 'DS4.ItemTypeLanguagePlural'}}</h4>
<h4 class="ds4-embedded-document-list-title">{{localize 'DS4.ItemTypeLanguagePlural'}}</h4>
{{#unless (isEmpty itemsByType.language)}}
<ol class="ds4-item-list ds4-item-list--language item-list">
<ol class="ds4-embedded-document-list ds4-embedded-document-list--language item-list">
{{> systems/ds4/templates/sheets/actor/components/item-list-header.hbs}}
{{#each itemsByType.language as |itemData id|}}
{{> systems/ds4/templates/sheets/actor/components/item-list-entry.hbs itemData=itemData}}
{{/each}}
</ol>
{{/unless}}
{{> systems/ds4/templates/sheets/actor/components/overview-add-button.hbs dataType='language'}}
{{> systems/ds4/templates/sheets/actor/components/add-button.hbs title='DS4.UserInteractionAddItemTitle'
documentType='item' type='language'}}
{{!-- ALPHABET --}}
<h4 class="ds4-item-list-title">{{localize 'DS4.ItemTypeAlphabetPlural'}}</h4>
<h4 class="ds4-embedded-document-list-title">{{localize 'DS4.ItemTypeAlphabetPlural'}}</h4>
{{#unless (isEmpty itemsByType.alphabet)}}
<ol class="ds4-item-list ds4-item-list--alphabet item-list">
<ol class="ds4-embedded-document-list ds4-embedded-document-list--alphabet item-list">
{{> systems/ds4/templates/sheets/actor/components/item-list-header.hbs}}
{{#each itemsByType.alphabet as |itemData id|}}
{{> systems/ds4/templates/sheets/actor/components/item-list-entry.hbs itemData=itemData}}
{{/each}}
</ol>
{{/unless}}
{{> systems/ds4/templates/sheets/actor/components/overview-add-button.hbs dataType='alphabet'}}
{{> systems/ds4/templates/sheets/actor/components/add-button.hbs title='DS4.UserInteractionAddItemTitle'
documentType='item' type='alphabet'}}
</div>

View file

@ -5,14 +5,15 @@ SPDX-FileCopyrightText: 2021 Gesina Schwalbe
SPDX-License-Identifier: MIT
--}}
<div class="tab special-creature-abilities" data-group="primary" data-tab="special-creature-abilities">
<div class="tab abilities" data-group="primary" data-tab="abilities">
{{#unless (isEmpty itemsByType.specialCreatureAbility)}}
<ol class="ds4-item-list ds4-item-list--special-creature-ability item-list">
<ol class="ds4-embedded-document-list ds4-embedded-document-list--special-creature-ability item-list">
{{> systems/ds4/templates/sheets/actor/components/item-list-header.hbs}}
{{#each itemsByType.specialCreatureAbility as |itemData id|}}
{{> systems/ds4/templates/sheets/actor/components/item-list-entry.hbs itemData=itemData}}
{{/each}}
</ol>
{{/unless}}
{{> systems/ds4/templates/sheets/actor/components/overview-add-button.hbs dataType='specialCreatureAbility'}}
{{> systems/ds4/templates/sheets/actor/components/add-button.hbs title='DS4.UserInteractionAddItemTitle'
documentType='item' type='specialCreatureAbility'}}
</div>

View file

@ -0,0 +1,19 @@
{{!--
SPDX-FileCopyrightText: 2021 Johannes Loher
SPDX-FileCopyrightText: 2021 Gesina Schwalbe
SPDX-License-Identifier: MIT
--}}
<div class="tab effects" data-group="primary" data-tab="effects">
{{#unless (isEmpty data.effects)}}
<ol class="ds4-embedded-document-list ds4-embedded-document-list--effect">
{{> systems/ds4/templates/sheets/actor/components/effect-list-header.hbs}}
{{#each enrichedEffects as |effectData id| }}
{{> systems/ds4/templates/sheets/actor/components/effect-list-entry.hbs effectData=effectData}}
{{/each}}
</ol>
{{/unless}}
{{> systems/ds4/templates/sheets/actor/components/add-button.hbs title='DS4.UserInteractionAddEffectTitle'
documentType='effect'}}
</div>

View file

@ -51,7 +51,7 @@ titleKey=titleKey}}
<div class="tab spells" data-group="primary" data-tab="spells">
{{#unless (isEmpty itemsByType.spell)}}
<ol class="ds4-item-list ds4-item-list--spell item-list">
<ol class="ds4-embedded-document-list ds4-embedded-document-list--spell item-list">
{{#> systems/ds4/templates/sheets/actor/components/item-list-header.hbs isEquipable=true hideDescription=true}}
{{!-- spell type --}}
<div title="{{localize 'DS4.SpellType'}}">{{localize 'DS4.SpellTypeAbbr'}}</div>
@ -72,11 +72,11 @@ titleKey=titleKey}}
{{#> systems/ds4/templates/sheets/actor/components/item-list-entry.hbs itemData=itemData isEquipable=true
hideDescription=true}}
{{!-- spell type --}}
<img class="ds4-item-list__image" src="{{lookup ../../config.icons.spellTypes itemData.data.spellType}}"
<img class="ds4-embedded-document-list__image" src="{{lookup ../../config.icons.spellTypes itemData.data.spellType}}"
title="{{lookup ../../config.i18n.spellTypes itemData.data.spellType}}" />
{{!-- spell bonus --}}
<input class="ds4-item-list__editable item-change" type="text" data-dtype="String" data-property="data.bonus"
<input class="ds4-embedded-document-list__editable change-item" type="text" data-dtype="String" data-property="data.bonus"
value="{{itemData.data.bonus}}" title="{{localize 'DS4.SpellBonus'}}" />
{{!-- max. distance --}}
@ -93,5 +93,6 @@ titleKey=titleKey}}
{{/each}}
</ol>
{{/unless}}
{{> systems/ds4/templates/sheets/actor/components/overview-add-button.hbs dataType='spell' }}
{{> systems/ds4/templates/sheets/actor/components/add-button.hbs title='DS4.UserInteractionAddItemTitle'
documentType='item' type='spell'}}
</div>

View file

@ -12,17 +12,18 @@ SPDX-License-Identifier: MIT
<div class="effect-image"></div>
<div class="effect-name">Name</div>
<div class="effect-controls">
<a class="effect-control" data-action="create" title="{{localize 'DS4.UserInteractionAddEffect'}}">
<i class="fas fa-plus"></i> {{localize 'DS4.UserInteractionAddEffect'}}</a>
<a class="effect-control" data-action="create" title="{{localize 'DS4.UserInteractionAddEffectTitle'}}">
<i class="fas fa-plus"></i> {{localize 'DS4.UserInteractionAdd'}}</a>
</div>
</li>
{{#each item.effects as |effect id|}}
<li class="effect flexrow" data-effect-id="{{effect.id}}">
<h4 class="effect-name">{{effect.data.label}}</h4>
<div class="effect-controls">
<a class="effect-control" data-action="edit" title="{{localize 'DS4.UserInteractionEditEffect'}}">
<a class="effect-control" data-action="edit" title="{{localize 'DS4.UserInteractionEditEffectTitle'}}">
<i class="fas fa-edit"></i></a>
<a class="effect-control" data-action="delete" title="{{localize 'DS4.UserInteractionDeleteEffect'}}">
<a class="effect-control" data-action="delete"
title="{{localize 'DS4.UserInteractionDeleteEffectTitle'}}">
<i class="fas fa-trash"></i></a>
</div>
</li>