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 {
/** @override */
prepareData() {
// Prepare data for the actor. Calling the super version of this executes
// the following, in order: data reset (to clear active effects),
// prepareBaseData(), prepareEmbeddedDocuments() (including active effects),
// prepareDerivedData().
prepareBaseData() {
// Data modifications in this step occur before processing embedded
// documents or derived data.
// 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.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 };
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 */
@@ -86,20 +104,37 @@ export class VermineActor extends Actor {
}
prepareCombatStatus() {
// 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
switch (parseInt(this.system.combatStatus.difficulty)) {
case 5: this.system.combatStatus.label = "Offensif";
break;
case 7: this.system.combatStatus.label = "Actif";
break;
case 9: this.system.combatStatus.label = "Passif";
break;
default:
this.system.combatStatus.label = "Passif";
this.system.combatStatus.difficulty = "9";
break;
const difficulty = parseInt(this.system.combatStatus.difficulty) || 9;
// Only update if values are different to avoid triggering unnecessary updates
const currentLabel = this.system.combatStatus.label;
let newLabel = "Passif";
switch (difficulty) {
case 5: newLabel = "Offensif"; break;
case 7: newLabel = "Actif"; break;
case 9: newLabel = "Passif"; 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";
}
}