fix(actor): prevent double ActiveEffect application in prepareData cycle

- Move wound data initialization to prepareBaseData() (before effects are applied)
- Initialize combatStatus in prepareBaseData() to prevent undefined errors
- Add protection against recursive effect application in prepareEmbeddedDocuments()
- This prevents the 'ActiveEffect application phase has already completed' error

The error occurred because modify data in prepareDerivedData() (like combatStatus)
could trigger observers that try to re-apply effects during the same cycle.

By initializing all required data in prepareBaseData() and protecting
prepareEmbeddedDocuments() from recursive calls, we ensure effects are
applied exactly once per preparation cycle.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
2026-06-04 23:14:38 +02:00
parent b85efd663b
commit 81adfb7ffd
+53 -18
View File
@@ -6,19 +6,37 @@
export class VermineActor extends Actor { export class VermineActor extends Actor {
/** @override */ /** @override */
prepareData() { prepareBaseData() {
// Prepare data for the actor. Calling the super version of this executes // Data modifications in this step occur before processing embedded
// the following, in order: data reset (to clear active effects), // documents or derived data.
// prepareBaseData(), prepareEmbeddedDocuments() (including active effects),
// prepareDerivedData().
// Initialize wound data to prevent undefined errors with active effects // Initialize wound data to prevent undefined errors with active effects
if (!this.system.minorWound) this.system.minorWound = { value: 0, min: 0, max: 5, threshold: 1 }; if (!this.system.minorWound) this.system.minorWound = { value: 0, min: 0, max: 5, threshold: 1 };
if (!this.system.majorWound) this.system.majorWound = { value: 0, min: 0, max: 4, threshold: 4 }; if (!this.system.majorWound) this.system.majorWound = { value: 0, min: 0, max: 4, threshold: 4 };
if (!this.system.deadlyWound) this.system.deadlyWound = { value: 0, min: 0, max: 2, threshold: 8 }; if (!this.system.deadlyWound) this.system.deadlyWound = { value: 0, min: 0, max: 2, threshold: 8 };
super.prepareData(); // Initialize combatStatus to prevent errors
if (!this.system.combatStatus) {
this.system.combatStatus = { difficulty: "9", label: "Passif" };
}
if (this.type == 'character') {
}
}
/** @override */
prepareEmbeddedDocuments() {
// Prevent recursive effect application
// Foundry V11+ can sometimes call this multiple times in a preparation cycle
if (this._preparingEffects) return;
this._preparingEffects = true;
try {
super.prepareEmbeddedDocuments();
} finally {
delete this._preparingEffects;
}
} }
/** @override */ /** @override */
@@ -86,20 +104,37 @@ export class VermineActor extends Actor {
} }
prepareCombatStatus() { prepareCombatStatus() {
// Ensure combatStatus exists (defined in base template) // Ensure combatStatus exists (defined in base template)
if (!this.system.combatStatus) return; if (!this.system.combatStatus) {
this.system.combatStatus = { difficulty: "9", label: "Passif" };
return;
}
// Ensure difficulty exists
if (!this.system.combatStatus.difficulty) {
this.system.combatStatus.difficulty = "9";
}
//combat initiative reaction difficulty //combat initiative reaction difficulty
switch (parseInt(this.system.combatStatus.difficulty)) { const difficulty = parseInt(this.system.combatStatus.difficulty) || 9;
case 5: this.system.combatStatus.label = "Offensif";
break; // Only update if values are different to avoid triggering unnecessary updates
case 7: this.system.combatStatus.label = "Actif"; const currentLabel = this.system.combatStatus.label;
break; let newLabel = "Passif";
case 9: this.system.combatStatus.label = "Passif";
break; switch (difficulty) {
default: case 5: newLabel = "Offensif"; break;
this.system.combatStatus.label = "Passif"; case 7: newLabel = "Actif"; break;
this.system.combatStatus.difficulty = "9"; case 9: newLabel = "Passif"; break;
break; }
// Only update if label changed
if (currentLabel !== newLabel) {
this.system.combatStatus.label = newLabel;
}
// Only update difficulty if it was undefined or invalid
if (!this.system.combatStatus.difficulty || isNaN(parseInt(this.system.combatStatus.difficulty))) {
this.system.combatStatus.difficulty = "9";
} }
} }