feat: add functionality to apply Active Affects to owned Items
In the Active Effect Config, there are now additional inputs to configure the effect to be applied to items owned by the actor instead of the actor itself. It is possible to select the items to which to apply the effect via matching by name, or via a condition expression, that provides similar capabilities as the evaluation of mathematical expressions in rolls. Data from the Actor, Item, and Active Effect can be accessed similar to how properties are accessed in roll formulas (using the prefixes `@actor`, `@item`, and `@effect`). For example, in order to apply an effect to all ranged weapons, the conditions would be ```js '@item.type' === 'weapon' && '@item.data.attackType' === 'ranged' ```
This commit is contained in:
parent
27b6506847
commit
b1ed05a796
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
// global
|
||||
@use "global/accessibility";
|
||||
@use "global/fonts";
|
||||
@use "global/utils";
|
||||
|
||||
// shared
|
||||
@use "components/shared/add_button";
|
||||
|
|
14
scss/global/_utils.scss
Normal file
14
scss/global/_utils.scss
Normal file
|
@ -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);
|
||||
}
|
31
src/active-effect/active-effect-config.ts
Normal file
31
src/active-effect/active-effect-config.ts
Normal file
|
@ -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" });
|
||||
}
|
||||
}
|
|
@ -2,16 +2,26 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { DS4Actor } from "./actor/actor";
|
||||
import { mathEvaluator } from "./expression-evaluation/evaluator";
|
||||
import { getGame } from "./helpers";
|
||||
|
||||
import type { DS4Item } from "./item/item";
|
||||
import { DS4Actor } from "../actor/actor";
|
||||
import { mathEvaluator } from "../expression-evaluation/evaluator";
|
||||
import { getGame } from "../helpers";
|
||||
|
||||
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;
|
||||
|
@ -60,15 +70,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 +126,25 @@ export class DS4ActiveEffect extends ActiveEffect {
|
|||
}
|
||||
return result as number | `${number | boolean}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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 });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export type EffectChangeData = foundry.data.ActiveEffectData["changes"][number];
|
||||
export type EffectChangeDataWithEffect = { effect: DS4ActiveEffect; change: EffectChangeData };
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
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";
|
||||
|
||||
|
@ -14,7 +16,7 @@ import type { DS4Item } from "../item/item";
|
|||
import type { ItemType } from "../item/item-data-source";
|
||||
import type { DS4ShieldDataProperties } from "../item/shield/shield-data-properties";
|
||||
import type { Check } from "./actor-data-properties-base";
|
||||
|
||||
import type { EffectChangeData } from "../active-effect/active-effect";
|
||||
declare global {
|
||||
interface DocumentClassConfig {
|
||||
Actor: typeof DS4Actor;
|
||||
|
@ -25,7 +27,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 +59,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.
|
||||
|
@ -81,31 +121,17 @@ export class DS4Actor extends Actor {
|
|||
*
|
||||
* @param predicate - The predicate that ActiveEffectChanges need to satisfy in order to be applied
|
||||
*/
|
||||
applyActiveEffectsFiltered(predicate: (change: foundry.data.ActiveEffectData["changes"][number]) => boolean): void {
|
||||
applyActiveEffectsFiltered(predicate: (change: EffectChangeData) => 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));
|
||||
const changesWithEffect = this.actorEffects.flatMap((e) => e.getFactoredChangesWithEffect(predicate));
|
||||
changesWithEffect.sort((a, b) => (a.change.priority ?? 0) - (b.change.priority ?? 0));
|
||||
|
||||
// Apply all changes
|
||||
for (const change of changes) {
|
||||
const result = change.effect.apply(this, change);
|
||||
if (result !== null) overrides[change.key] = result;
|
||||
for (const changeWithEffect of changesWithEffect) {
|
||||
const result = changeWithEffect.effect.apply(this, changeWithEffect.change);
|
||||
if (result !== null) overrides[changeWithEffect.change.key] = result;
|
||||
}
|
||||
|
||||
// Expand the set of final overrides
|
||||
|
|
25
src/apps/sheet-helpers.ts
Normal file
25
src/apps/sheet-helpers.ts
Normal file
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -24,6 +24,39 @@ 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 = {};
|
||||
const overrides: Record<string, unknown> = {};
|
||||
|
||||
// Organize non-disabled and -surpressed effects by their application priority
|
||||
const changesWithEffect = this.actor?.itemEffects(this).flatMap((e) => e.getFactoredChangesWithEffect()) ?? [];
|
||||
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(this, changeWithEffect.change);
|
||||
if (result !== null) overrides[changeWithEffect.change.key] = result;
|
||||
}
|
||||
|
||||
// Expand the set of final overrides
|
||||
this.overrides = foundry.utils.expandObject({ ...foundry.utils.flattenObject(this.overrides), ...overrides });
|
||||
}
|
||||
|
||||
override prepareDerivedData(): void {
|
||||
this.data.data.rollable = false;
|
||||
}
|
||||
|
|
178
templates/sheets/active-effect/active-effect-config.hbs
Normal file
178
templates/sheets/active-effect/active-effect-config.hbs
Normal file
|
@ -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>
|
Loading…
Reference in a new issue