fix: make ActiveEffects work properly

This commit is contained in:
Johannes Loher 2023-06-25 14:33:18 +02:00
parent 486aaa1aaf
commit e181426882
Signed by: saluu
GPG key ID: 7CB0A9FB553DA045
11 changed files with 112 additions and 98 deletions

View file

@ -173,11 +173,11 @@
"DS4.SpellCasterClassWizard": "Zauberer", "DS4.SpellCasterClassWizard": "Zauberer",
"DS4.SpellPrice": "Preis (Gold)", "DS4.SpellPrice": "Preis (Gold)",
"DS4.SpellPriceDescription": "Der Kaufpreis des Zauberspruchs.", "DS4.SpellPriceDescription": "Der Kaufpreis des Zauberspruchs.",
"DS4.EffectEnabled": "Aktiv", "DS4.EffectEnabled": "Eingeschaltet",
"DS4.EffectEnabledAbbr": "A", "DS4.EffectEnabledAbbr": "E",
"DS4.EffectEffectivelyEnabled": "Effektiv Aktiv (unter Betrachtung, ob ein eventuelles Quellen-Item ausgerüstet ist usw.)", "DS4.EffectActive": "Aktiv (unter Betrachtung, ob ein eventuelles Quellen-Item ausgerüstet ist usw.)",
"DS4.EffectEffectivelyEnabledAbbr": "E", "DS4.EffectActiveAbbr": "A",
"DS4.EffectLabel": "Bezeichnung", "DS4.EffectName": "Name",
"DS4.EffectSourceName": "Quelle", "DS4.EffectSourceName": "Quelle",
"DS4.EffectFactor": "Faktor (wie oft der Effekt angewendet wird)", "DS4.EffectFactor": "Faktor (wie oft der Effekt angewendet wird)",
"DS4.EffectFactorAbbr": "F", "DS4.EffectFactorAbbr": "F",
@ -382,7 +382,7 @@
"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.NewEffectName": "Neuer Effekt",
"DS4.ActiveEffectApplyToItems": "Auf Items Anwenden", "DS4.ActiveEffectApplyToItems": "Auf Items Anwenden",
"DS4.ActiveEffectItemName": "Itemname", "DS4.ActiveEffectItemName": "Itemname",

View file

@ -175,9 +175,9 @@
"DS4.SpellPriceDescription": "The price to purchase the spell.", "DS4.SpellPriceDescription": "The price to purchase the spell.",
"DS4.EffectEnabled": "Enabled", "DS4.EffectEnabled": "Enabled",
"DS4.EffectEnabledAbbr": "E", "DS4.EffectEnabledAbbr": "E",
"DS4.EffectEffectivelyEnabled": "Effectively Enabled (taking into account whether a potential source item is equipped etc.)", "DS4.EffectActive": "Active (taking into account whether a potential source item is equipped etc.)",
"DS4.EffectEffectivelyEnabledAbbr": "EE", "DS4.EffectActiveAbbr": "A",
"DS4.EffectLabel": "Label", "DS4.EffectName": "Name",
"DS4.EffectSourceName": "Source", "DS4.EffectSourceName": "Source",
"DS4.EffectFactor": "Factor (the number of times the effect is being applied)", "DS4.EffectFactor": "Factor (the number of times the effect is being applied)",
"DS4.EffectFactorAbbr": "F", "DS4.EffectFactorAbbr": "F",
@ -382,7 +382,7 @@
"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.NewEffectName": "New Effect",
"DS4.ActiveEffectApplyToItems": "Apply to Items", "DS4.ActiveEffectApplyToItems": "Apply to Items",
"DS4.ActiveEffectItemName": "Item Name", "DS4.ActiveEffectItemName": "Item Name",

View file

@ -51,9 +51,9 @@ export class DS4ActorSheet extends ActorSheet {
const enrichedEffectPromises = this.actor.effects.map(async (effect) => { const enrichedEffectPromises = this.actor.effects.map(async (effect) => {
return { return {
...effect.toObject(), ...effect.toObject(),
sourceName: await effect.getCurrentSourceName(), sourceName: effect.sourceName,
factor: effect.factor, factor: effect.factor,
isEffectivelyEnabled: !effect.disabled && !effect.isSurpressed, active: effect.active,
}; };
}); });
const enrichedEffects = await Promise.all(enrichedEffectPromises); const enrichedEffects = await Promise.all(enrichedEffectPromises);

View file

@ -35,11 +35,8 @@ export class DS4ActiveEffect extends ActiveEffect {
*/ */
source = undefined; source = undefined;
/** /** @override */
* Whether or not this effect is currently surpressed. get isSuppressed() {
* @type {boolean}
*/
get isSurpressed() {
const originatingItem = this.originatingItem; const originatingItem = this.originatingItem;
if (!originatingItem) { if (!originatingItem) {
return false; return false;
@ -82,30 +79,6 @@ export class DS4ActiveEffect extends ActiveEffect {
return super.apply(document, change); return super.apply(document, change);
} }
/**
* Gets the current source name based on the cached source object.
* @returns {Promise<string>} The current source name
*/
async getCurrentSourceName() {
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#source} if it has already been
* set.
* @protected
* @returns {Promise<foundry.abstract.Document | null>}
*/
async getSource() {
if (this.source === undefined) {
this.source = this.origin != null ? await fromUuid(this.origin) : null;
}
return this.source;
}
/** /**
* Create a new {@link DS4ActiveEffect} using default values. * Create a new {@link DS4ActiveEffect} using default values.
* *
@ -115,7 +88,7 @@ export class DS4ActiveEffect extends ActiveEffect {
*/ */
static async createDefault(parent) { static async createDefault(parent) {
const createData = { const createData = {
label: getGame().i18n.localize(`DS4.NewEffectLabel`), name: getGame().i18n.localize(`DS4.NewEffectName`),
icon: this.FALLBACK_ICON, icon: this.FALLBACK_ICON,
}; };
@ -141,13 +114,25 @@ export class DS4ActiveEffect extends ActiveEffect {
* @param {import("./item/item").DS4Item | import("./actor/actor").DS4Actor} document The Actor or Item to which to apply the effects * @param {import("./item/item").DS4Item | import("./actor/actor").DS4Actor} document The Actor or Item to which to apply the effects
* @param {DS4ActiveEffect[]} effetcs The effects to apply * @param {DS4ActiveEffect[]} effetcs The effects to apply
* @param {(change: EffectChangeData) => boolean} [predicate=() => true] Apply only changes that fullfill this predicate * @param {(change: EffectChangeData) => boolean} [predicate=() => true] Apply only changes that fullfill this predicate
* @returns {Set<string>} The statuses that are applied by this effect
*/ */
static applyEffetcs(document, effetcs, predicate = () => true) { static applyEffetcs(document, effetcs, predicate = () => true) {
/** @type {Record<string, unknown>} */ /** @type {Record<string, unknown>} */
const overrides = {}; const overrides = {};
// Organize non-disabled and -surpressed effects by their application priority /** @type {Set<string>} */
const changesWithEffect = effetcs.flatMap((e) => e.getFactoredChangesWithEffect(predicate)); const statuses = new Set();
// Organize active effect changes by their application priority
const changesWithEffect = effetcs.flatMap((e) => {
if (!e.active) {
return [];
}
for (const statusId of e.statuses) {
statuses.add(statusId);
}
return e.getFactoredChangesWithEffect(predicate);
});
changesWithEffect.sort((a, b) => (a.change.priority ?? 0) - (b.change.priority ?? 0)); changesWithEffect.sort((a, b) => (a.change.priority ?? 0) - (b.change.priority ?? 0));
// Apply all changes // Apply all changes
@ -162,6 +147,8 @@ export class DS4ActiveEffect extends ActiveEffect {
...foundry.utils.flattenObject(document.overrides), ...foundry.utils.flattenObject(document.overrides),
...overrides, ...overrides,
}); });
return statuses;
} }
/** /**
@ -171,13 +158,10 @@ export class DS4ActiveEffect extends ActiveEffect {
* @protected * @protected
*/ */
getFactoredChangesWithEffect(predicate = () => true) { getFactoredChangesWithEffect(predicate = () => true) {
if (this.disabled || this.isSurpressed) {
return [];
}
return this.changes.filter(predicate).flatMap((change) => { return this.changes.filter(predicate).flatMap((change) => {
change.priority = change.priority ?? change.mode * 10; const c = foundry.utils.deepClone(change);
return Array(this.factor).fill({ effect: this, change }); c.priority = c.priority ?? c.mode * 10;
return Array(this.factor).fill({ effect: this, change: c });
}); });
} }
} }

View file

@ -15,6 +15,9 @@ import { isAttribute, isTrait } from "./actor-data-source-base";
* The Actor class for DS4 * The Actor class for DS4
*/ */
export class DS4Actor extends Actor { export class DS4Actor extends Actor {
/** @type {Set<string>} */
newStatuses = new Set();
/** @override */ /** @override */
prepareData() { prepareData() {
this.prepareBaseData(); this.prepareBaseData();
@ -23,11 +26,14 @@ export class DS4Actor extends Actor {
this.applyActiveEffectsToBaseData(); this.applyActiveEffectsToBaseData();
this.prepareDerivedData(); this.prepareDerivedData();
this.applyActiveEffectsToDerivedData(); this.applyActiveEffectsToDerivedData();
this.handleStatusChanges();
this.prepareFinalDerivedData(); this.prepareFinalDerivedData();
} }
/** @override */ /** @override */
prepareBaseData() { prepareBaseData() {
this.newStatuses = new Set();
this.system.rolling = { this.system.rolling = {
minimumFumbleResult: 20, minimumFumbleResult: 20,
maximumCoupResult: 1, maximumCoupResult: 1,
@ -60,7 +66,13 @@ export class DS4Actor extends Actor {
* @protected * @protected
*/ */
get actorEffects() { get actorEffects() {
return this.effects.filter((effect) => !effect.flags.ds4?.itemEffectConfig?.applyToItems); const effects = [];
for (const effect of this.allApplicableEffects()) {
if (!effect.flags.ds4?.itemEffectConfig?.applyToItems) {
effects.push(effect);
}
}
return effects;
} }
/** /**
@ -69,7 +81,8 @@ export class DS4Actor extends Actor {
* @returns {import("../active-effect").DS4ActiveEffect[]} The array of effects that are candidates to be applied to the item * @returns {import("../active-effect").DS4ActiveEffect[]} The array of effects that are candidates to be applied to the item
*/ */
itemEffects(item) { itemEffects(item) {
return this.effects.filter((effect) => { /** @type {(effect: DS4ActiveEffect) => boolean} */
const shouldEffectBeAppliedToItem = (effect, item) => {
const { applyToItems, itemName, condition } = effect.flags.ds4?.itemEffectConfig ?? {}; const { applyToItems, itemName, condition } = effect.flags.ds4?.itemEffectConfig ?? {};
if (!applyToItems || (itemName !== undefined && itemName !== "" && itemName !== item.name)) { if (!applyToItems || (itemName !== undefined && itemName !== "" && itemName !== item.name)) {
@ -78,11 +91,7 @@ export class DS4Actor extends Actor {
if (condition !== undefined && condition !== "") { if (condition !== undefined && condition !== "") {
try { try {
const replacedCondition = DS4Actor.replaceFormulaData(condition, { const replacedCondition = DS4Actor.replaceFormulaData(condition, { item, actor: this, effect });
item,
actor: this,
effect,
});
return replacedCondition !== undefined ? Boolean(mathEvaluator.evaluate(replacedCondition)) : false; return replacedCondition !== undefined ? Boolean(mathEvaluator.evaluate(replacedCondition)) : false;
} catch (error) { } catch (error) {
logger.warn(error); logger.warn(error);
@ -91,7 +100,15 @@ export class DS4Actor extends Actor {
} }
return true; return true;
}); };
const effects = [];
for (const effect of this.allApplicableEffects()) {
if (shouldEffectBeAppliedToItem(effect, item)) {
effects.push(effect);
}
}
return effects;
} }
/** /**
@ -153,7 +170,8 @@ export class DS4Actor extends Actor {
*/ */
applyActiveEffectsToItem(item) { applyActiveEffectsToItem(item) {
item.overrides = {}; item.overrides = {};
DS4ActiveEffect.applyEffetcs(item, this.itemEffects(item)); item.reset();
DS4ActiveEffect.applyEffetcs(item, this.itemEffects(item)).forEach(this.newStatuses.add.bind(this.newStatuses));
} }
/** /**
@ -168,7 +186,7 @@ export class DS4Actor extends Actor {
(change) => (change) =>
!this.derivedDataProperties.includes(change.key) && !this.derivedDataProperties.includes(change.key) &&
!this.finalDerivedDataProperties.includes(change.key), !this.finalDerivedDataProperties.includes(change.key),
); ).forEach(this.newStatuses.add.bind(this.newStatuses));
} }
/** /**
@ -178,7 +196,7 @@ export class DS4Actor extends Actor {
applyActiveEffectsToDerivedData() { applyActiveEffectsToDerivedData() {
DS4ActiveEffect.applyEffetcs(this, this.actorEffects, (change) => DS4ActiveEffect.applyEffetcs(this, this.actorEffects, (change) =>
this.derivedDataProperties.includes(change.key), this.derivedDataProperties.includes(change.key),
); ).forEach(this.newStatuses.add.bind(this.newStatuses));
} }
/** /**
@ -205,6 +223,30 @@ export class DS4Actor extends Actor {
return combatValueProperties.concat(checkProperties); return combatValueProperties.concat(checkProperties);
} }
handleStatusChanges() {
this.statuses ??= new Set();
// Identify which special statuses had been active
const specialStatuses = new Map();
for (const statusId of Object.values(CONFIG.specialStatusEffects)) {
specialStatuses.set(statusId, this.statuses.has(statusId));
}
this.statuses.clear();
// set new statuses
this.newStatuses.forEach(this.statuses.add.bind(this.statuses));
// Apply special statuses that changed to active tokens
const tokens = this.getActiveTokens();
for (const [statusId, wasActive] of specialStatuses) {
const isActive = this.statuses.has(statusId);
if (isActive === wasActive) continue;
for (const token of tokens) {
token._onApplyStatusEffect(statusId, isActive);
}
}
}
/** /**
* Apply final transformations to the Actor data after all effects have been applied. * Apply final transformations to the Actor data after all effects have been applied.
*/ */

View file

@ -38,12 +38,8 @@
"minimum": "10.290", "minimum": "10.290",
"verified": "10" "verified": "10"
}, },
"esmodules": [ "esmodules": ["ds4.js"],
"ds4.js" "styles": ["css/ds4.css"],
],
"styles": [
"css/ds4.css"
],
"languages": [ "languages": [
{ {
"lang": "en", "lang": "en",

View file

@ -9,7 +9,9 @@ SPDX-License-Identifier: MIT
<!-- Effect Header --> <!-- Effect Header -->
<header class="sheet-header"> <header class="sheet-header">
<img class="effect-icon" src="{{ data.icon }}" data-edit="icon"> <img class="effect-icon" src="{{ data.icon }}" data-edit="icon">
<h1 class="effect-title">{{ data.label }}</h1> <h1 class="effect-title">
<input name="name" type="text" value="{{data.name}}" placeholder="{{ localize 'Name' }}" />
</h1>
</header> </header>
<!-- Effect Configuration Tabs --> <!-- Effect Configuration Tabs -->
@ -21,30 +23,19 @@ SPDX-License-Identifier: MIT
<!-- Details Tab --> <!-- Details Tab -->
<section class="tab" data-tab="details"> <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"> <div class="form-group">
<label>{{ localize "EFFECT.IconTint" }}</label> <label>{{ localize "EFFECT.IconTint" }}</label>
<div class="form-fields"> <div class="form-fields">
<input class="color" type="text" name="tint" value="{{data.tint}}" /> {{colorPicker name="tint" value=data.tint}}
<input type="color" value="{{data.tint}}" data-edit="tint" />
</div> </div>
</div> </div>
<div class="form-group stacked">
<label>{{ localize "EFFECT.Description" }}</label>
{{editor descriptionHTML target="description" button=false editable=editable engine="prosemirror"
collaborate=false}}
</div>
<div class="form-group"> <div class="form-group">
<label>{{ localize "EFFECT.Disabled" }}</label> <label>{{ localize "EFFECT.Disabled" }}</label>
<div class="form-fields"> <div class="form-fields">
@ -63,10 +54,11 @@ SPDX-License-Identifier: MIT
{{#if isItemEffect}} {{#if isItemEffect}}
<div class="form-group"> <div class="form-group">
<label>{{ localize "EFFECT.Transfer" }}</label> <label>{{ labels.transfer.name }}</label>
<div class="form-fields"> <div class="form-fields">
<input type="checkbox" name="transfer" {{checked data.transfer}} /> <input type="checkbox" name="transfer" {{checked data.transfer}} />
</div> </div>
<p class="hint">{{ labels.transfer.hint }}</p>
</div> </div>
{{/if}} {{/if}}

View file

@ -15,15 +15,15 @@ SPDX-License-Identifier: MIT
type="checkbox" {{checked (ne effectData.disabled true)}} data-dtype="Boolean" data-property="disabled" type="checkbox" {{checked (ne effectData.disabled true)}} data-dtype="Boolean" data-property="disabled"
data-inverted="true" title="{{localize 'DS4.EffectEnabled'}}"> data-inverted="true" title="{{localize 'DS4.EffectEnabled'}}">
{{!-- effectively enabled --}} {{!-- active --}}
{{#if effectData.isEffectivelyEnabled}}<i class="fas fa-check"></i>{{else}}<i class="fas fa-ban"></i>{{/if}} {{#if effectData.active}}<i class="fas fa-check"></i>{{else}}<i class="fas fa-ban"></i>{{/if}}
{{!-- icon --}} {{!-- icon --}}
{{> systems/ds4/templates/sheets/shared/components/rollable-image.hbs rollable=false src=effectData.icon {{> systems/ds4/templates/sheets/shared/components/rollable-image.hbs rollable=false src=effectData.icon
alt=(localize "DS4.DocumentImageAltText" name=effectData.label) title=effectData.label}} alt=(localize "DS4.DocumentImageAltText" name=effectData.label) title=effectData.label}}
{{!-- label --}} {{!-- name --}}
<div title="{{effectData.label}}">{{effectData.label}}</div> <div title="{{effectData.name}}">{{effectData.name}}</div>
{{!-- source name --}} {{!-- source name --}}
<div title="{{effectData.sourceName}}">{{effectData.sourceName}}</div> <div title="{{effectData.sourceName}}">{{effectData.sourceName}}</div>

View file

@ -12,14 +12,14 @@ SPDX-License-Identifier: MIT
{{!-- enabled --}} {{!-- enabled --}}
<div title="{{localize 'DS4.EffectEnabled'}}">{{localize 'DS4.EffectEnabledAbbr'}}</div> <div title="{{localize 'DS4.EffectEnabled'}}">{{localize 'DS4.EffectEnabledAbbr'}}</div>
{{!-- effectively enabled --}} {{!-- active --}}
<div title="{{localize 'DS4.EffectEffectivelyEnabled'}}">{{localize 'DS4.EffectEffectivelyEnabledAbbr'}}</div> <div title="{{localize 'DS4.EffectActive'}}">{{localize 'DS4.EffectActiveAbbr'}}</div>
{{!-- icon --}} {{!-- icon --}}
<div></div> <div></div>
{{!-- label --}} {{!-- name --}}
<div>{{localize 'DS4.EffectLabel'}}</div> <div>{{localize 'DS4.EffectName'}}</div>
{{!-- source name --}} {{!-- source name --}}
<div>{{localize 'DS4.EffectSourceName'}}</div> <div>{{localize 'DS4.EffectSourceName'}}</div>

View file

@ -13,8 +13,8 @@ SPDX-License-Identifier: MIT
{{> systems/ds4/templates/sheets/shared/components/rollable-image.hbs rollable=false src=effectData.icon {{> systems/ds4/templates/sheets/shared/components/rollable-image.hbs rollable=false src=effectData.icon
alt=(localize "DS4.DocumentImageAltText" name=effectData.label) title=effectData.label}} alt=(localize "DS4.DocumentImageAltText" name=effectData.label) title=effectData.label}}
{{!-- label --}} {{!-- name --}}
<div title="{{effectData.label}}">{{effectData.label}}</div> <div title="{{effectData.name}}">{{effectData.name}}</div>
{{!-- control button group --}} {{!-- control button group --}}
{{> systems/ds4/templates/sheets/shared/components/control-button-group.hbs documentType="effect" {{> systems/ds4/templates/sheets/shared/components/control-button-group.hbs documentType="effect"

View file

@ -12,7 +12,7 @@ SPDX-License-Identifier: MIT
<div></div> <div></div>
{{!-- label --}} {{!-- label --}}
<div>{{localize 'DS4.EffectLabel'}}</div> <div>{{localize 'DS4.EffectName'}}</div>
{{!-- control buttons placeholder --}} {{!-- control buttons placeholder --}}
<div></div> <div></div>