Register

Version 11 Active Effects Changes

ActiveEffects have undergone a number of changes in Version 11. Several properties have been renamed in order to make them consistent with other Document types. The way that ActiveEffects manage statuses has also been changed, improving their flexibility. Finally, the way that ActiveEffects on Items are transferred to Actors has undergone a large paradigm shift due to the lifting of certain restrictions around grandchild embedded updates. See here for the general details on those changes, and keep reading this article for the specifics on how they related to ActiveEffect transferral.

Property Name Changes

The description field has been added to ActiveEffects. If you add this property during ActiveEffect preparation, or as a getter, it should be removed.

The label field has been renamed to name. This is to make it more consistent with other Documents, that all use name. A deprecation path is in place for existing code that still uses label.

The img getter has been added as an alias of the icon field. Eventually icon will be deprecated in favor of img, however we did not get this change made in time for the V11 release, so icon remains the canonical field name. You may update your code to read the img field instead though, ahead of the eventual change.

ActiveEffect Statuses

The status effects configured in CONFIG.statusEffects have special meaning in a System. Previously, ActiveEffects could apply these statuses to Actors by way of the statusId flag. This only allows for an ActiveEffect to apply a single status condition, however. In Version 11, the statusId flag has now been deprecated, and instead the statuses property has been directly added to the ActiveEffect schema, allowing an ActiveEffect to apply multiple statuses.

Version 10 (Before)

// Create a new effect to add the status.
ActiveEffect.implementation.create({
  "flags.core.statusId": "prone"
}, { parent: actor });

// Or add the status to an existing effect.
effect.setFlag("core", "statusId", "prone");

// Remove the status by deleting the effect.
effect.delete();

// Or remove the status but keep the effect.
effect.unsetFlag("core", "statusId");

Version 11 (After)

// Create a new effect to add statuses.
ActiveEffect.implementation.create({
  statuses: ["prone", "unconscious"]
}, { parent: actor });

// Or add the statuses to an existing effect.
const statuses = effect.statuses;
["prone", "unconscious"].forEach(statuses.add, statuses);
effect.update({ statuses: Array.from(statuses) });

// Remove all statuses by deleting the effect.
effect.delete();

// Or remove a single status.
statuses.delete("unconscious");
effect.update({ statuses: Array.from(statuses) });

To retrieve the combined list of all statuses that are affecting an Actor, across all of their applicable ActiveEffects, you can use the Actor#statuses property.

const effect = await ActiveEffect.implementation.create({
  statuses: ["prone", "unconscious"]
}, { parent: actor });
console.log(actor.statuses); // Set{"prone", "unconscious"};
await effect.update({ disabled: true });
console.log(actor.statuses); // Set{}

ActiveEffect Transferral

In previous versions of Foundry Virtual Tabletop, it was impossible to directly update a grandchild embedded Document. This meant that directly manipulating an ActiveEffect on an owned Item could not be done in the normal way. In order to work around this, ActiveEffects were given the transfer property which controlled whether they would be transferred to the grandparent Actor in the event their parent Item was ever added to an Actor, and would be deleted if that Item were ever deleted from the Actor. Now that this limitation has been removed (see full details here), it is no longer necessary to have ActiveEffects transferred to their grandparent Actors. They can remain on the Item and still apply their effects to the Actor, while still being enabled or disabled, or otherwise edited in the normal way.

Not transferring the ActiveEffect has a number of benefits: Firstly, it is no longer necessary to use the origin property to keep track of the Item the ActiveEffect was transferred from. This property historically had problems where unlinked Tokens or Actors housed in compendia were concerned. This also frees up the origin to be used exclusively for more useful things, such as whether an effect was applied to one Actor by another Actor.

Another benefit is that there needs to be no special logic around deleting an Item, in order to make sure that any transferred ActiveEffects are appropriately cleaned up. Any Item that is deleted will automatically delete all its children ActiveEffects too. And, of course, there also needs to be no special logic for performing the ActiveEffect creation on the Actor when an Item is added to that Actor.

The transfer field remains, however, and its behavior depends on the value of the new CONFIG.ActiveEffect.legacyTransferral flag. When this value is true (the default), ActiveEffects will continue to be transferred to their grandparent Actors, and deleted when their parent Items are removed from the Actor. When this value is false, the transfer property will only control whether the ActiveEffect applies its changes to the Actor or not. Whether this flag is true or false should not affect the behavior of ActiveEffects and how they apply their changes to Actors at all. For this reason, if you are a System developer, it is recommended that you set the legacyTransferral flag to false as soon as you are able to verify that none of your codebase relies on this legacy transferral behavior.

Legacy Transferral

CONFIG.ActiveEffect.legacyTransferral = true;
await ActiveEffect.implementation.create({
  name: 'Name Change',
  transfer: true,
  changes: [{
    key: 'name',
    value: 'Other Name',
    mode: CONST.ACTIVE_EFFECT_MODES.OVERRIDE
  }]
}, { parent: item });
await Item.create(item.toObject(), { parent: actor });

// Verify the Actor's name has been changed.
console.log(actor.name); // "Other Name"

// A new ActiveEffect has been created on the Actor now, with its origin pointing to the Item.
actor.effects.getName('Name Change');

Non-Legacy Transferral

CONFIG.ActiveEffect.legacyTransferral = false;
await ActiveEffect.implementation.create({
  name: 'Name Change',
  transfer: true,
  changes: [{
    key: 'name',
    value: 'Other Name',
    mode: CONST.ACTIVE_EFFECT_MODES.OVERRIDE
  }]
}, { parent: item });
await Item.create(item.toObject(), { parent: actor });

// Verify the Actor's name has been changed.
console.log(actor.name); // "Other Name"

// No ActiveEffect created on the Actor.
actor.effects.getName('Name Change'); // undefined

One potential change you may have to accommodate if your System does not use legacy transferral, is retrieving the collection of ActiveEffects that are applying or are capable of applying to a given Actor. With legacy transferral, this is easy, as you only need to check the Actor#effects collection. With non-legacy transferral, each of the Actor's Items must also be checked. To make this more ergonomic and efficient, several additional methods have been added to the Actor class.

// Retrieve an iterator over all effects that can apply to the actor.
for ( const effect of actor.allApplicableEffects() ) {
  // The effect might exist on the Actor, or it might exist on one of the Actor's Items.
  // If it's the latter, then its transfer value will be true.
}

// Get a list of all the applicable effects.
const effects = Array.from(actor.allApplicableEffects());

// Get a list of all effects that are actually applied to the actor.
const applied = actor.appliedEffects();

// Get a list of all effects that are temporarily applied to the actor.
// Note: This is the same as V10.
const temporary = actor.temporaryEffects();