Merge branch 'item-effects' into 'main'
Add effects for owned items See merge request dungeonslayers/ds4!203
This commit is contained in:
commit
4ac29f8f95
14 changed files with 433 additions and 59 deletions
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -12,5 +12,6 @@
|
||||||
"importSorter.importStringConfiguration.maximumNumberOfImportExpressionsPerLine.count": 120,
|
"importSorter.importStringConfiguration.maximumNumberOfImportExpressionsPerLine.count": 120,
|
||||||
"importSorter.importStringConfiguration.tabSize": 4,
|
"importSorter.importStringConfiguration.tabSize": 4,
|
||||||
"importSorter.importStringConfiguration.quoteMark": "double",
|
"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.NewLanguageName": "Neue Sprache",
|
||||||
"DS4.NewAlphabetName": "Neue Schriftzeichen",
|
"DS4.NewAlphabetName": "Neue Schriftzeichen",
|
||||||
"DS4.NewSpecialCreatureAbilityName": "Neue Besondere Kreaturenfähigkeit",
|
"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.NewLanguageName": "New Language",
|
||||||
"DS4.NewAlphabetName": "New Alphabet",
|
"DS4.NewAlphabetName": "New Alphabet",
|
||||||
"DS4.NewSpecialCreatureAbilityName": "New Special Creature Ability",
|
"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
|
// global
|
||||||
@use "global/accessibility";
|
@use "global/accessibility";
|
||||||
@use "global/fonts";
|
@use "global/fonts";
|
||||||
|
@use "global/utils";
|
||||||
|
|
||||||
// shared
|
// shared
|
||||||
@use "components/shared/add_button";
|
@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,27 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
import { DS4Actor } from "./actor/actor";
|
import { mathEvaluator } from "../expression-evaluation/evaluator";
|
||||||
import { mathEvaluator } from "./expression-evaluation/evaluator";
|
import { getGame } from "../helpers";
|
||||||
import { getGame } from "./helpers";
|
|
||||||
|
|
||||||
import type { DS4Item } from "./item/item";
|
import type { DS4Actor } from "../actor/actor";
|
||||||
|
import type { DS4Item } from "../item/item";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface DocumentClassConfig {
|
interface DocumentClassConfig {
|
||||||
ActiveEffect: typeof DS4ActiveEffect;
|
ActiveEffect: typeof DS4ActiveEffect;
|
||||||
}
|
}
|
||||||
|
interface FlagConfig {
|
||||||
|
ActiveEffect: {
|
||||||
|
ds4?: {
|
||||||
|
itemEffectConfig?: {
|
||||||
|
applyToItems?: boolean;
|
||||||
|
itemName?: string;
|
||||||
|
condition?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type PromisedType<T> = T extends Promise<infer U> ? U : T;
|
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.
|
* The item which this effect originates from if it has been transferred from an item to an actor.
|
||||||
*/
|
*/
|
||||||
get originatingItem(): DS4Item | undefined {
|
get originatingItem(): DS4Item | undefined {
|
||||||
if (!(this.parent instanceof DS4Actor)) {
|
if (!(this.parent instanceof Actor)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const itemIdRegex = /Item\.([a-zA-Z0-9]+)/;
|
const itemIdRegex = /Item\.([a-zA-Z0-9]+)/;
|
||||||
|
@ -60,15 +71,17 @@ export class DS4ActiveEffect extends ActiveEffect {
|
||||||
return this.originatingItem?.activeEffectFactor ?? 1;
|
return this.originatingItem?.activeEffectFactor ?? 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
override apply(actor: DS4Actor, change: foundry.data.ActiveEffectData["changes"][number]): unknown {
|
override apply(document: DS4Actor | DS4Item, change: EffectChangeData): unknown {
|
||||||
change.value = Roll.replaceFormulaData(change.value, actor.data);
|
change.value = Roll.replaceFormulaData(change.value, document.data);
|
||||||
try {
|
try {
|
||||||
change.value = DS4ActiveEffect.safeEval(change.value).toString();
|
change.value = DS4ActiveEffect.safeEval(change.value).toString();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.warn(e);
|
logger.warn(e);
|
||||||
// this is a valid case, e.g., if the effect change simply is a string
|
// 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}`;
|
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 };
|
|
@ -5,7 +5,8 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// 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 { DS4 } from "../config";
|
||||||
import { getCanvas, getGame } from "../helpers";
|
import { getCanvas, getGame } from "../helpers";
|
||||||
import { getDS4Settings } from "../settings";
|
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(".rollable-check").on("click", this.onRollCheck.bind(this));
|
||||||
|
|
||||||
html.find(".sort-items").on("click", this.onSortItems.bind(this));
|
html.find(".sort-items").on("click", this.onSortItems.bind(this));
|
||||||
|
|
||||||
|
disableOverriddenFields.call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,8 +3,11 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import { DS4ActiveEffect } from "../active-effect/active-effect";
|
||||||
import { DS4 } from "../config";
|
import { DS4 } from "../config";
|
||||||
|
import { mathEvaluator } from "../expression-evaluation/evaluator";
|
||||||
import { getGame } from "../helpers";
|
import { getGame } from "../helpers";
|
||||||
|
import logger from "../logger";
|
||||||
import { createCheckRoll } from "../rolls/check-factory";
|
import { createCheckRoll } from "../rolls/check-factory";
|
||||||
import { isAttribute, isTrait } from "./actor-data-source-base";
|
import { isAttribute, isTrait } from "./actor-data-source-base";
|
||||||
|
|
||||||
|
@ -25,7 +28,10 @@ declare global {
|
||||||
* The Actor class for DS4
|
* The Actor class for DS4
|
||||||
*/
|
*/
|
||||||
export class DS4Actor extends Actor {
|
export class DS4Actor extends Actor {
|
||||||
|
initialized: boolean | undefined;
|
||||||
|
|
||||||
override prepareData(): void {
|
override prepareData(): void {
|
||||||
|
this.initialized = true;
|
||||||
this.data.reset();
|
this.data.reset();
|
||||||
this.prepareBaseData();
|
this.prepareBaseData();
|
||||||
this.prepareEmbeddedDocuments();
|
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
|
* 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.
|
* {@link ActiveEffect}s and {@link Actor#prepareEmbeddedDocuments} calls this.
|
||||||
|
@ -65,7 +106,9 @@ export class DS4Actor extends Actor {
|
||||||
applyActiveEffectsToBaseData(): void {
|
applyActiveEffectsToBaseData(): void {
|
||||||
// reset overrides because our variant of applying active effects does not set them, it only adds overrides
|
// reset overrides because our variant of applying active effects does not set them, it only adds overrides
|
||||||
this.overrides = {};
|
this.overrides = {};
|
||||||
this.applyActiveEffectsFiltered(
|
DS4ActiveEffect.applyEffetcs(
|
||||||
|
this,
|
||||||
|
this.actorEffects,
|
||||||
(change) =>
|
(change) =>
|
||||||
!this.derivedDataProperties.includes(change.key) &&
|
!this.derivedDataProperties.includes(change.key) &&
|
||||||
!this.finalDerivedDataProperties.includes(change.key),
|
!this.finalDerivedDataProperties.includes(change.key),
|
||||||
|
@ -73,43 +116,9 @@ export class DS4Actor extends Actor {
|
||||||
}
|
}
|
||||||
|
|
||||||
applyActiveEffectsToDerivedData(): void {
|
applyActiveEffectsToDerivedData(): void {
|
||||||
this.applyActiveEffectsFiltered((change) => this.derivedDataProperties.includes(change.key));
|
DS4ActiveEffect.applyEffetcs(this, this.actorEffects, (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 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
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
|
// 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 { DS4CharacterActorSheet } from "../actor/character/character-sheet";
|
||||||
import { DS4CreatureActorSheet } from "../actor/creature/creature-sheet";
|
import { DS4CreatureActorSheet } from "../actor/creature/creature-sheet";
|
||||||
import { DS4ActorProxy } from "../actor/proxy";
|
import { DS4ActorProxy } from "../actor/proxy";
|
||||||
|
@ -65,11 +66,16 @@ async function init() {
|
||||||
|
|
||||||
registerSystemSettings();
|
registerSystemSettings();
|
||||||
|
|
||||||
Actors.unregisterSheet("core", ActorSheet);
|
DocumentSheetConfig.unregisterSheet(Actor, "core", ActorSheet);
|
||||||
Actors.registerSheet("ds4", DS4CharacterActorSheet, { types: ["character"], makeDefault: true });
|
DocumentSheetConfig.registerSheet(Actor, "ds4", DS4CharacterActorSheet, {
|
||||||
Actors.registerSheet("ds4", DS4CreatureActorSheet, { types: ["creature"], makeDefault: true });
|
types: ["character"],
|
||||||
Items.unregisterSheet("core", ItemSheet);
|
makeDefault: true,
|
||||||
Items.registerSheet("ds4", DS4ItemSheet, { 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();
|
preloadFonts();
|
||||||
await registerHandlebarsPartials();
|
await registerHandlebarsPartials();
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// 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 { DS4 } from "../config";
|
||||||
import { getGame } from "../helpers";
|
import { getGame } from "../helpers";
|
||||||
import notifications from "../ui/notifications";
|
import notifications from "../ui/notifications";
|
||||||
|
@ -41,6 +42,16 @@ export class DS4ItemSheet extends ItemSheet<ItemSheet.Options, DS4ItemSheetData>
|
||||||
return data;
|
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(
|
override setPosition(
|
||||||
options: Partial<Application.Position> = {},
|
options: Partial<Application.Position> = {},
|
||||||
): (Application.Position & { height: number }) | void {
|
): (Application.Position & { height: number }) | void {
|
||||||
|
@ -60,6 +71,8 @@ export class DS4ItemSheet extends ItemSheet<ItemSheet.Options, DS4ItemSheetData>
|
||||||
if (!this.options.editable) return;
|
if (!this.options.editable) return;
|
||||||
|
|
||||||
html.find(".control-effect").on("click", this.onControlEffect.bind(this));
|
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.
|
* Creates a new embedded effect.
|
||||||
*
|
|
||||||
* @param event - The originating click event
|
|
||||||
*/
|
*/
|
||||||
protected onCreateEffect(): void {
|
protected onCreateEffect(): void {
|
||||||
DS4ActiveEffect.createDefault(this.item);
|
DS4ActiveEffect.createDefault(this.item);
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import { DS4ActiveEffect } from "../active-effect/active-effect";
|
||||||
import { getGame } from "../helpers";
|
import { getGame } from "../helpers";
|
||||||
|
|
||||||
import type { ItemType } from "./item-data-source";
|
import type { ItemType } from "./item-data-source";
|
||||||
|
@ -24,6 +25,26 @@ declare global {
|
||||||
* The Item class for DS4
|
* The Item class for DS4
|
||||||
*/
|
*/
|
||||||
export class DS4Item extends Item {
|
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 {
|
override prepareDerivedData(): void {
|
||||||
this.data.data.rollable = false;
|
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