diff --git a/.vscode/settings.json b/.vscode/settings.json index f8012687..133f4a55 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,5 +12,6 @@ "importSorter.importStringConfiguration.maximumNumberOfImportExpressionsPerLine.count": 120, "importSorter.importStringConfiguration.tabSize": 4, "importSorter.importStringConfiguration.quoteMark": "double", - "importSorter.importStringConfiguration.trailingComma": "multiLine" + "importSorter.importStringConfiguration.trailingComma": "multiLine", + "vitest.commandLine": "yarn run vitest" } diff --git a/lang/de.json b/lang/de.json index b4beb543..d8ec0911 100644 --- a/lang/de.json +++ b/lang/de.json @@ -366,5 +366,10 @@ "DS4.NewLanguageName": "Neue Sprache", "DS4.NewAlphabetName": "Neue Schriftzeichen", "DS4.NewSpecialCreatureAbilityName": "Neue Besondere Kreaturenfähigkeit", - "DS4.NewEffectLabel": "Neuer Effekt" + "DS4.NewEffectLabel": "Neuer Effekt", + + "DS4.ActiveEffectApplyToItems": "Auf Items Andwenden", + "DS4.ActiveEffectItemName": "Itemname", + "DS4.ActiveEffectItemCondition": "Bedingung", + "DS4.TooltipDisabledDueToEffects": "inaktiv, weil von Aktiven Effekten beeinflusst" } diff --git a/lang/en.json b/lang/en.json index 485e9eed..64d9b686 100644 --- a/lang/en.json +++ b/lang/en.json @@ -366,5 +366,10 @@ "DS4.NewLanguageName": "New Language", "DS4.NewAlphabetName": "New Alphabet", "DS4.NewSpecialCreatureAbilityName": "New Special Creature Ability", - "DS4.NewEffectLabel": "New Effect" + "DS4.NewEffectLabel": "New Effect", + + "DS4.ActiveEffectApplyToItems": "Apply to Items", + "DS4.ActiveEffectItemName": "Item Name", + "DS4.ActiveEffectItemCondition": "Condition", + "DS4.TooltipDisabledDueToEffects": "disabled, because affected by Active Effects" } diff --git a/scss/ds4.scss b/scss/ds4.scss index acedd839..84bf078c 100644 --- a/scss/ds4.scss +++ b/scss/ds4.scss @@ -9,6 +9,7 @@ // global @use "global/accessibility"; @use "global/fonts"; +@use "global/utils"; // shared @use "components/shared/add_button"; diff --git a/scss/global/_utils.scss b/scss/global/_utils.scss new file mode 100644 index 00000000..b5877b1f --- /dev/null +++ b/scss/global/_utils.scss @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: 2022 Johannes Loher + * + * SPDX-License-Identifier: MIT + */ + +.ds4-code-input { + font-family: var(--font-mono); +} + +// This is needed for higher specifity +form .ds4-code-input { + font-family: var(--font-mono); +} diff --git a/src/active-effect/active-effect-config.ts b/src/active-effect/active-effect-config.ts new file mode 100644 index 00000000..f26494e8 --- /dev/null +++ b/src/active-effect/active-effect-config.ts @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2022 Johannes Loher +// +// SPDX-License-Identifier: MIT + +export class DS4ActiveEffectConfig extends ActiveEffectConfig { + static override get defaultOptions(): DocumentSheetOptions { + return foundry.utils.mergeObject(super.defaultOptions, { + template: "systems/ds4/templates/sheets/active-effect/active-effect-config.hbs", + }); + } + + override activateListeners(html: JQuery<HTMLElement>): void { + super.activateListeners(html); + const checkbox = html[0]?.querySelector<HTMLInputElement>( + 'input[name="flags.ds4.itemEffectConfig.applyToItems"]', + ); + checkbox?.addEventListener("change", () => this.toggleItemEffectConfig(checkbox.checked)); + } + + private toggleItemEffectConfig(active: boolean) { + const elements = this.element[0]?.querySelectorAll(".ds4-item-effect-config"); + elements?.forEach((element) => { + if (active) { + element.classList.remove("ds4-hidden"); + } else { + element.classList.add("ds4-hidden"); + } + }); + this.setPosition({ height: "auto" }); + } +} diff --git a/src/active-effect.ts b/src/active-effect/active-effect.ts similarity index 52% rename from src/active-effect.ts rename to src/active-effect/active-effect.ts index 0c587f96..68049d7a 100644 --- a/src/active-effect.ts +++ b/src/active-effect/active-effect.ts @@ -2,16 +2,27 @@ // // SPDX-License-Identifier: MIT -import { DS4Actor } from "./actor/actor"; -import { mathEvaluator } from "./expression-evaluation/evaluator"; -import { getGame } from "./helpers"; +import { mathEvaluator } from "../expression-evaluation/evaluator"; +import { getGame } from "../helpers"; -import type { DS4Item } from "./item/item"; +import type { DS4Actor } from "../actor/actor"; +import type { DS4Item } from "../item/item"; declare global { interface DocumentClassConfig { ActiveEffect: typeof DS4ActiveEffect; } + interface FlagConfig { + ActiveEffect: { + ds4?: { + itemEffectConfig?: { + applyToItems?: boolean; + itemName?: string; + condition?: string; + }; + }; + }; + } } type PromisedType<T> = T extends Promise<infer U> ? U : T; @@ -42,7 +53,7 @@ export class DS4ActiveEffect extends ActiveEffect { * The item which this effect originates from if it has been transferred from an item to an actor. */ get originatingItem(): DS4Item | undefined { - if (!(this.parent instanceof DS4Actor)) { + if (!(this.parent instanceof Actor)) { return; } const itemIdRegex = /Item\.([a-zA-Z0-9]+)/; @@ -60,15 +71,17 @@ export class DS4ActiveEffect extends ActiveEffect { return this.originatingItem?.activeEffectFactor ?? 1; } - override apply(actor: DS4Actor, change: foundry.data.ActiveEffectData["changes"][number]): unknown { - change.value = Roll.replaceFormulaData(change.value, actor.data); + override apply(document: DS4Actor | DS4Item, change: EffectChangeData): unknown { + change.value = Roll.replaceFormulaData(change.value, document.data); try { change.value = DS4ActiveEffect.safeEval(change.value).toString(); } catch (e) { logger.warn(e); // this is a valid case, e.g., if the effect change simply is a string } - return super.apply(actor, change); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error In the types and foundry's documentation, only actors are allowed, but the implementation actually works for all kinds of documents + return super.apply(document, change); } /** @@ -114,4 +127,55 @@ export class DS4ActiveEffect extends ActiveEffect { } return result as number | `${number | boolean}`; } + + /** + * Apply the given effects to the gicen Actor or item. + * @param document The Actor or Item to which to apply the effects + * @param effetcs The effects to apply + * @param predicate Apply only changes that fullfill this predicate + */ + static applyEffetcs( + document: DS4Actor | DS4Item, + effetcs: DS4ActiveEffect[], + predicate: (change: EffectChangeData) => boolean = () => true, + ): void { + const overrides: Record<string, unknown> = {}; + + // Organize non-disabled and -surpressed effects by their application priority + const changesWithEffect = effetcs.flatMap((e) => e.getFactoredChangesWithEffect(predicate)); + changesWithEffect.sort((a, b) => (a.change.priority ?? 0) - (b.change.priority ?? 0)); + + // Apply all changes + for (const changeWithEffect of changesWithEffect) { + const result = changeWithEffect.effect.apply(document, changeWithEffect.change); + if (result !== null) overrides[changeWithEffect.change.key] = result; + } + + // Expand the set of final overrides + document.overrides = foundry.utils.expandObject({ + ...foundry.utils.flattenObject(document.overrides), + ...overrides, + }); + } + + /** + * Get the array of changes for this effect, considering the {@link DS4ActiveEffect#factor}. + * @param predicate An optional predicate to filter which changes should be considered + * @returns The array of changes from this effect, considering the factor. + */ + protected getFactoredChangesWithEffect( + predicate: (change: EffectChangeData) => boolean = () => true, + ): EffectChangeDataWithEffect[] { + if (this.data.disabled || this.isSurpressed) { + return []; + } + + return this.data.changes.filter(predicate).flatMap((change) => { + change.priority = change.priority ?? change.mode * 10; + return Array<EffectChangeDataWithEffect>(this.factor).fill({ effect: this, change }); + }); + } } + +type EffectChangeData = foundry.data.ActiveEffectData["changes"][number]; +type EffectChangeDataWithEffect = { effect: DS4ActiveEffect; change: EffectChangeData }; diff --git a/src/actor/actor-sheet.ts b/src/actor/actor-sheet.ts index 192b802c..50224e95 100644 --- a/src/actor/actor-sheet.ts +++ b/src/actor/actor-sheet.ts @@ -5,7 +5,8 @@ // // SPDX-License-Identifier: MIT -import { DS4ActiveEffect } from "../active-effect"; +import { DS4ActiveEffect } from "../active-effect/active-effect"; +import { disableOverriddenFields } from "../apps/sheet-helpers"; import { DS4 } from "../config"; import { getCanvas, getGame } from "../helpers"; import { getDS4Settings } from "../settings"; @@ -108,6 +109,8 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Options, DS4ActorSheetD html.find(".rollable-check").on("click", this.onRollCheck.bind(this)); html.find(".sort-items").on("click", this.onSortItems.bind(this)); + + disableOverriddenFields.call(this); } /** diff --git a/src/actor/actor.ts b/src/actor/actor.ts index 183e8018..7c87d4d9 100644 --- a/src/actor/actor.ts +++ b/src/actor/actor.ts @@ -3,8 +3,11 @@ // // SPDX-License-Identifier: MIT +import { DS4ActiveEffect } from "../active-effect/active-effect"; import { DS4 } from "../config"; +import { mathEvaluator } from "../expression-evaluation/evaluator"; import { getGame } from "../helpers"; +import logger from "../logger"; import { createCheckRoll } from "../rolls/check-factory"; import { isAttribute, isTrait } from "./actor-data-source-base"; @@ -25,7 +28,10 @@ declare global { * The Actor class for DS4 */ export class DS4Actor extends Actor { + initialized: boolean | undefined; + override prepareData(): void { + this.initialized = true; this.data.reset(); this.prepareBaseData(); this.prepareEmbeddedDocuments(); @@ -54,6 +60,41 @@ export class DS4Actor extends Actor { ); } + private get actorEffects() { + return this.effects.filter((effect) => !effect.data.flags.ds4?.itemEffectConfig?.applyToItems); + } + + /** + * Get the effects of this actor that should be applied to the given item. + * @param item The item for which to get effects + * @returns The array of effects that are candidates to be applied to the item + */ + itemEffects(item: DS4Item) { + return this.effects.filter((effect) => { + const { applyToItems, itemName, condition } = effect.data.flags.ds4?.itemEffectConfig ?? {}; + + if (!applyToItems || (itemName !== undefined && itemName !== "" && itemName !== item.name)) { + return false; + } + + if (condition !== undefined && condition !== "") { + try { + const replacedCondition = Roll.replaceFormulaData(condition, { + item: item.data, + actor: this.data, + effect: effect.data, + }); + return Boolean(mathEvaluator.evaluate(replacedCondition)); + } catch (error) { + logger.warn(error); + return false; + } + } + + return true; + }); + } + /** * We override this with an empty implementation because we have our own custom way of applying * {@link ActiveEffect}s and {@link Actor#prepareEmbeddedDocuments} calls this. @@ -65,7 +106,9 @@ export class DS4Actor extends Actor { applyActiveEffectsToBaseData(): void { // reset overrides because our variant of applying active effects does not set them, it only adds overrides this.overrides = {}; - this.applyActiveEffectsFiltered( + DS4ActiveEffect.applyEffetcs( + this, + this.actorEffects, (change) => !this.derivedDataProperties.includes(change.key) && !this.finalDerivedDataProperties.includes(change.key), @@ -73,43 +116,9 @@ export class DS4Actor extends Actor { } applyActiveEffectsToDerivedData(): void { - this.applyActiveEffectsFiltered((change) => this.derivedDataProperties.includes(change.key)); - } - - /** - * Apply ActiveEffectChanges to the Actor data which are caused by ActiveEffects and satisfy the given predicate. - * - * @param predicate - The predicate that ActiveEffectChanges need to satisfy in order to be applied - */ - applyActiveEffectsFiltered(predicate: (change: foundry.data.ActiveEffectData["changes"][number]) => boolean): void { - const overrides: Record<string, unknown> = {}; - - // Organize non-disabled and -surpressed effects by their application priority - const changes: (foundry.data.ActiveEffectData["changes"][number] & { effect: ActiveEffect })[] = - this.effects.reduce( - (changes: (foundry.data.ActiveEffectData["changes"][number] & { effect: ActiveEffect })[], e) => { - if (e.data.disabled || e.isSurpressed) return changes; - - const newChanges = e.data.changes.filter(predicate).flatMap((c) => { - const changeSource = c.toObject(); - changeSource.priority = changeSource.priority ?? changeSource.mode * 10; - return Array(e.factor).fill({ ...changeSource, effect: e }); - }); - - return changes.concat(newChanges); - }, - [], - ); - changes.sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0)); - - // Apply all changes - for (const change of changes) { - const result = change.effect.apply(this, change); - if (result !== null) overrides[change.key] = result; - } - - // Expand the set of final overrides - this.overrides = foundry.utils.expandObject({ ...foundry.utils.flattenObject(this.overrides), ...overrides }); + DS4ActiveEffect.applyEffetcs(this, this.actorEffects, (change) => + this.derivedDataProperties.includes(change.key), + ); } /** diff --git a/src/apps/sheet-helpers.ts b/src/apps/sheet-helpers.ts new file mode 100644 index 00000000..1844c254 --- /dev/null +++ b/src/apps/sheet-helpers.ts @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Johannes Loher +// +// SPDX-License-Identifier: MIT + +import { getGame } from "../helpers"; + +export function disableOverriddenFields< + Options extends FormApplicationOptions, + Data extends object, + ConcreteObject extends { overrides: Record<string, unknown> }, +>(this: FormApplication<Options, Data, ConcreteObject>): void { + const inputs = ["INPUT", "SELECT", "TEXTAREA", "BUTTON"]; + const titleAddition = `(${getGame().i18n.localize("DS4.TooltipDisabledDueToEffects")})`; + for (const key of Object.keys(foundry.utils.flattenObject(this.object.overrides))) { + const elements = this.form?.querySelectorAll(`[name="${key}"]`); + elements?.forEach((element) => { + if (inputs.includes(element.tagName)) { + element.setAttribute("disabled", ""); + const title = element.getAttribute("title"); + const newTitle = title === null ? titleAddition : `${title} ${titleAddition}`; + element.setAttribute("title", newTitle); + } + }); + } +} diff --git a/src/hooks/init.ts b/src/hooks/init.ts index ee2f8870..35f22540 100644 --- a/src/hooks/init.ts +++ b/src/hooks/init.ts @@ -4,7 +4,8 @@ // // SPDX-License-Identifier: MIT -import { DS4ActiveEffect } from "../active-effect"; +import { DS4ActiveEffect } from "../active-effect/active-effect"; +import { DS4ActiveEffectConfig } from "../active-effect/active-effect-config"; import { DS4CharacterActorSheet } from "../actor/character/character-sheet"; import { DS4CreatureActorSheet } from "../actor/creature/creature-sheet"; import { DS4ActorProxy } from "../actor/proxy"; @@ -65,11 +66,16 @@ async function init() { registerSystemSettings(); - Actors.unregisterSheet("core", ActorSheet); - Actors.registerSheet("ds4", DS4CharacterActorSheet, { types: ["character"], makeDefault: true }); - Actors.registerSheet("ds4", DS4CreatureActorSheet, { types: ["creature"], makeDefault: true }); - Items.unregisterSheet("core", ItemSheet); - Items.registerSheet("ds4", DS4ItemSheet, { makeDefault: true }); + DocumentSheetConfig.unregisterSheet(Actor, "core", ActorSheet); + DocumentSheetConfig.registerSheet(Actor, "ds4", DS4CharacterActorSheet, { + types: ["character"], + makeDefault: true, + }); + DocumentSheetConfig.registerSheet(Actor, "ds4", DS4CreatureActorSheet, { types: ["creature"], makeDefault: true }); + DocumentSheetConfig.unregisterSheet(Item, "core", ItemSheet); + DocumentSheetConfig.registerSheet(Item, "ds4", DS4ItemSheet, { makeDefault: true }); + DocumentSheetConfig.unregisterSheet(ActiveEffect, "core", ActiveEffectConfig); + DocumentSheetConfig.registerSheet(ActiveEffect, "ds4", DS4ActiveEffectConfig, { makeDefault: true }); preloadFonts(); await registerHandlebarsPartials(); diff --git a/src/item/item-sheet.ts b/src/item/item-sheet.ts index ce6a06de..3553ec23 100644 --- a/src/item/item-sheet.ts +++ b/src/item/item-sheet.ts @@ -4,7 +4,8 @@ // // SPDX-License-Identifier: MIT -import { DS4ActiveEffect } from "../active-effect"; +import { DS4ActiveEffect } from "../active-effect/active-effect"; +import { disableOverriddenFields } from "../apps/sheet-helpers"; import { DS4 } from "../config"; import { getGame } from "../helpers"; import notifications from "../ui/notifications"; @@ -41,6 +42,16 @@ export class DS4ItemSheet extends ItemSheet<ItemSheet.Options, DS4ItemSheetData> return data; } + override _getSubmitData(updateData = {}) { + const data = super._getSubmitData(updateData); + // Prevent submitting overridden values + const overrides = foundry.utils.flattenObject(this.item.overrides); + for (const k of Object.keys(overrides)) { + delete data[k]; + } + return data; + } + override setPosition( options: Partial<Application.Position> = {}, ): (Application.Position & { height: number }) | void { @@ -60,6 +71,8 @@ export class DS4ItemSheet extends ItemSheet<ItemSheet.Options, DS4ItemSheetData> if (!this.options.editable) return; html.find(".control-effect").on("click", this.onControlEffect.bind(this)); + + disableOverriddenFields.call(this); } /** @@ -86,8 +99,6 @@ export class DS4ItemSheet extends ItemSheet<ItemSheet.Options, DS4ItemSheetData> /** * Creates a new embedded effect. - * - * @param event - The originating click event */ protected onCreateEffect(): void { DS4ActiveEffect.createDefault(this.item); diff --git a/src/item/item.ts b/src/item/item.ts index 1da26310..9b3e8db4 100644 --- a/src/item/item.ts +++ b/src/item/item.ts @@ -3,6 +3,7 @@ // // SPDX-License-Identifier: MIT +import { DS4ActiveEffect } from "../active-effect/active-effect"; import { getGame } from "../helpers"; import type { ItemType } from "./item-data-source"; @@ -24,6 +25,26 @@ declare global { * The Item class for DS4 */ export class DS4Item extends Item { + /** An object that tracks the changes to the data model which were applied by active effects */ + overrides: Record<string, unknown> = {}; + + override prepareData() { + this.data.reset(); + this.prepareBaseData(); + this.prepareEmbeddedDocuments(); + this.prepareDerivedData(); + this.applyActiveEffects(); + } + + applyActiveEffects(): void { + if (!this.actor?.initialized) { + return; + } + + this.overrides = {}; + DS4ActiveEffect.applyEffetcs(this, this.actor.itemEffects(this)); + } + override prepareDerivedData(): void { this.data.data.rollable = false; } diff --git a/templates/sheets/active-effect/active-effect-config.hbs b/templates/sheets/active-effect/active-effect-config.hbs new file mode 100644 index 00000000..8c961fa7 --- /dev/null +++ b/templates/sheets/active-effect/active-effect-config.hbs @@ -0,0 +1,178 @@ +{{!-- +SPDX-FileCopyrightText: 2022 Johannes Loher + +SPDX-License-Identifier: MIT +--}} + +<form autocomplete="off"> + + <!-- Effect Header --> + <header class="sheet-header"> + <img class="effect-icon" src="{{ data.icon }}" data-edit="icon"> + <h1 class="effect-title">{{ data.label }}</h1> + </header> + + <!-- Effect Configuration Tabs --> + <nav class="sheet-tabs tabs"> + <a class="item" data-tab="details"><i class="fas fa-book"></i> {{localize "EFFECT.TabDetails"}}</a> + <a class="item" data-tab="duration"><i class="fas fa-clock"></i> {{localize "EFFECT.TabDuration"}}</a> + <a class="item" data-tab="effects"><i class="fas fa-cogs"></i> {{localize "EFFECT.TabEffects"}}</a> + </nav> + + <!-- Details Tab --> + <section class="tab" data-tab="details"> + + <div class="form-group"> + <label>{{ localize "EFFECT.Label" }}</label> + <div class="form-fields"> + <input type="text" name="label" value="{{ data.label }}" /> + </div> + </div> + + <div class="form-group"> + <label>{{ localize "EFFECT.Icon" }}</label> + <div class="form-fields"> + {{filePicker target="icon" type="image"}} + <input class="image" type="text" name="icon" placeholder="path/image.png" value="{{data.icon}}" /> + </div> + </div> + + <div class="form-group"> + <label>{{ localize "EFFECT.IconTint" }}</label> + <div class="form-fields"> + <input class="color" type="text" name="tint" value="{{data.tint}}" /> + <input type="color" value="{{data.tint}}" data-edit="tint" /> + </div> + </div> + + <div class="form-group"> + <label>{{ localize "EFFECT.Disabled" }}</label> + <div class="form-fields"> + <input type="checkbox" name="disabled" {{ checked data.disabled }} /> + </div> + </div> + + {{#if isActorEffect}} + <div class="form-group"> + <label>{{ localize "EFFECT.Origin" }}</label> + <div class="form-fields"> + <input type="text" name="origin" value="{{ data.origin }}" disabled /> + </div> + </div> + {{/if}} + + {{#if isItemEffect}} + <div class="form-group"> + <label>{{ localize "EFFECT.Transfer" }}</label> + <div class="form-fields"> + <input type="checkbox" name="transfer" {{checked data.transfer}} /> + </div> + </div> + {{/if}} + + <div class="form-group"> + <label>{{ localize "DS4.ActiveEffectApplyToItems" }}</label> + <div class="form-fields"> + <input type="checkbox" name="flags.ds4.itemEffectConfig.applyToItems" {{checked + data.flags.ds4.itemEffectConfig.applyToItems}} /> + </div> + + </div> + + <div + class="form-group ds4-item-effect-config{{#unless data.flags.ds4.itemEffectConfig.applyToItems}} ds4-hidden{{/unless}}"> + <label>{{ localize "DS4.ActiveEffectItemName" }}</label> + <div class="form-fields"> + <input type="text" name="flags.ds4.itemEffectConfig.itemName" + value="{{ data.flags.ds4.itemEffectConfig.itemName }}" /> + </div> + </div> + + <div + class="form-group ds4-item-effect-config{{#unless data.flags.ds4.itemEffectConfig.applyToItems}} ds4-hidden{{/unless}}"> + <label>{{ localize "DS4.ActiveEffectItemCondition" }}</label> + <div class="form-fields"> + <input class="ds4-code-input" type="text" name="flags.ds4.itemEffectConfig.condition" + value="{{ data.flags.ds4.itemEffectConfig.condition }}" /> + </div> + </div> + </section> + + <!-- Duration Tab --> + <section class="tab" data-tab="duration"> + <div class="form-group"> + <label>{{ localize "EFFECT.DurationSecs" }}</label> + <div class="form-fields"> + <input type="number" name="duration.seconds" value="{{ data.duration.seconds }}" /> + </div> + </div> + <div class="form-group"> + <label>{{ localize "EFFECT.StartTime" }}</label> + <div class="form-fields"> + <input type="number" name="duration.startTime" value="{{ data.duration.startTime }}" /> + </div> + </div> + <hr /> + <div class="form-group"> + <label>{{ localize "EFFECT.DurationTurns" }}</label> + <div class="form-fields"> + <label>{{ localize "COMBAT.Rounds" }}</label> + <input type="number" name="duration.rounds" value="{{ data.duration.rounds }}" /> + <label>{{ localize "COMBAT.Turns" }}</label> + <input type="number" name="duration.turns" value="{{ data.duration.turns }}" /> + </div> + </div> + <div class="form-group"> + <label>{{ localize "EFFECT.Combat" }}</label> + <div class="form-fields"> + <input type="text" name="duration.combat" value="{{ data.duration.combat }}" disabled /> + </div> + </div> + + <div class="form-group"> + <label>{{ localize "EFFECT.StartTurns" }}</label> + <div class="form-fields"> + <label>{{ localize "COMBAT.Round" }}</label> + <input type="number" name="duration.startRound" value="{{ data.duration.startRound }}" /> + <label>{{ localize "COMBAT.Turn" }}</label> + <input type="number" name="duration.startTurn" value="{{ data.duration.startTurn }}" /> + </div> + </div> + </section> + + <!-- Effects Tab --> + <section class="tab" data-tab="effects"> + <header class="effect-change effects-header flexrow"> + <div class="key">{{ localize "EFFECT.ChangeKey" }}</div> + <div class="mode">{{ localize "EFFECT.ChangeMode" }}</div> + <div class="value">{{ localize "EFFECT.ChangeValue" }}</div> + <div class="effect-controls"> + <a class="effect-control" data-action="add"><i class="far fa-plus-square"></i></a> + </div> + </header> + <ol class="changes-list"> + {{#each data.changes as |change i|}} + <li class="effect-change flexrow" data-index="{{i}}"> + <div class="key"> + <input type="text" name="changes.{{i}}.key" value="{{change.key}}" /> + </div> + <div class="mode"> + <select name="changes.{{i}}.mode" data-dtype="Number"> + {{selectOptions ../modes selected=change.mode}} + </select> + </div> + <div class="value"> + <input type="text" name="changes.{{i}}.value" value="{{change.value}}" /> + </div> + <div class="effect-controls"> + <a class="effect-control" data-action="delete"><i class="fas fa-trash"></i></a> + </div> + </li> + {{/each}} + </ol> + </section> + + <footer class="sheet-footer"> + <button type="submit"><i class="fas fa-save"></i> {{localize submitText}}</button> + </footer> +</form>