Back to list
ImproperSubset

fvtt-active-effects

by ImproperSubset

HouseHudson agentic AI plugins: governance skills for AI code assistants and Foundry VTT development patterns

0🍴 0📅 Jan 15, 2026

SKILL.md


name: fvtt-active-effects description: This skill should be used when creating or applying Active Effects, working with effect changes and modes (ADD, MULTIPLY, OVERRIDE, CUSTOM), implementing duration tracking for combat, or handling effect transfer from items to actors.

Foundry VTT Active Effects

Domain: Foundry VTT Module/System Development Status: Production-Ready Last Updated: 2026-01-05

Overview

Active Effects automatically modify Actor data through a changes array. They're the primary mechanism for buffs, debuffs, equipment bonuses, and temporary conditions.

When to Use This Skill

  • Creating equipment bonuses that modify actor stats
  • Implementing buff/debuff spells with duration
  • Setting up status conditions (prone, stunned, etc.)
  • Using CUSTOM mode for complex calculations
  • Managing effect transfer from items to actors

ActiveEffect Structure

Core Properties

{
  name: "Shield of Faith",
  img: "icons/magic/defensive/shield.png",
  disabled: false,
  transfer: true,           // Transfer from item to actor
  origin: "Item.abc123",    // Source reference
  statuses: ["blessed"],    // Status identifiers
  changes: [
    {
      key: "system.attributes.ac.bonus",
      mode: 2,              // ADD
      value: "2"
    }
  ],
  duration: {
    seconds: 600,           // 10 minutes
    rounds: null,
    turns: null,
    combat: null,
    startTime: null,
    startRound: null,
    startTurn: null
  }
}

Effect Modes

CONST.ACTIVE_EFFECT_MODES

ModeValueBehavior
CUSTOM0System-specific logic via hook
ADD1Sum numbers, concat strings
MULTIPLY2Multiply numeric values
OVERRIDE3Replace value entirely
UPGRADE4Use if value > current
DOWNGRADE5Use if value < current

Mode Examples

// ADD - most common
{ key: "system.attributes.ac.bonus", mode: 2, value: "2" }

// MULTIPLY - percentage bonuses
{ key: "system.attributes.movement.walk", mode: 2, value: "1.5" }

// OVERRIDE - fixed values
{ key: "system.attributes.ac.calc", mode: 3, value: "natural" }

// UPGRADE - only if better
{ key: "system.abilities.str.value", mode: 4, value: "19" }

// DOWNGRADE - only if worse
{ key: "system.abilities.dex.value", mode: 5, value: "4" }

Effect Application Timing

prepareData Order

// Actor.prepareData() execution:
1. this.data.reset()              // Reset to source
2. this.prepareBaseData()         // Basic setup
3. this.prepareEmbeddedDocuments() // Effects apply HERE
4. this.prepareDerivedData()      // Calculate derived values

Critical: Effects apply in step 3, so they cannot modify values created in step 4.

Solution: Initialize in prepareBaseData

prepareBaseData() {
  // Initialize values that effects will target
  this.system.attributes.ac.bonus = 0;
  this.system.modifiers.all = [];
}

prepareDerivedData() {
  // Use modified values here
  const ac = this.system.attributes.ac.base +
             this.system.attributes.ac.bonus;
}

CUSTOM Mode

For complex calculations, use mode 0 with the applyActiveEffect hook:

Hooks.on("applyActiveEffect", (actor, change, current, delta, changes) => {
  // Only handle our custom keys
  if (!change.key.startsWith("system.custom.")) return;

  switch (change.key) {
    case "system.custom.damageReduction":
      // Stack damage reduction multiplicatively
      const existing = current || 1;
      changes[change.key] = existing * (1 - delta);
      break;

    case "system.custom.attackBonus":
      // Add with diminishing returns
      const bonus = current || 0;
      changes[change.key] = bonus + Math.floor(delta / (1 + bonus / 10));
      break;
  }
});

Hook Parameters

  • actor: The Actor receiving the effect
  • change: The EffectChangeData object
  • current: Current value at the key
  • delta: Parsed change value (already type-converted)
  • changes: Accumulator - modify this to apply changes

Duration Tracking

Combat Duration

const combatEffect = {
  name: "Haste",
  changes: [...],
  duration: {
    rounds: 10,
    combat: game.combat?.id,
    startRound: game.combat?.round,
    startTurn: game.combat?.turn
  }
};

await actor.createEmbeddedDocuments("ActiveEffect", [combatEffect]);

World Time Duration

const timedEffect = {
  name: "Poison",
  changes: [...],
  duration: {
    seconds: 3600,  // 1 hour
    startTime: game.time.worldTime
  }
};

Checking Remaining Duration

// Combat-based
const remaining = effect.duration.rounds -
  (game.combat.round - effect.duration.startRound);

// Time-based
const elapsed = game.time.worldTime - effect.duration.startTime;
const remaining = effect.duration.seconds - elapsed;

Effect Transfer

Legacy Mode (Default)

Effects are copied from Item to Actor when embedded:

// Default: CONFIG.ActiveEffect.legacyTransferral = true
// Effect is duplicated to actor.effects

Effects stay on Item but apply to parent Actor:

// In system init:
CONFIG.ActiveEffect.legacyTransferral = false;

// Benefits:
// - Edit effects without re-adding items
// - Cleaner data model
// - Effects removed when item removed

Transfer Property

{
  name: "Magic Sword Bonus",
  transfer: true,   // Apply to actor (default)
  // transfer: false  // Stay on item only
  changes: [...]
}

Common Patterns

Equipment Bonus

const magicArmorEffect = {
  name: "+2 Armor",
  img: "icons/equipment/chest/plate.png",
  transfer: true,
  changes: [
    {
      key: "system.attributes.ac.bonus",
      mode: 2,  // ADD
      value: "2"
    }
  ]
};

await item.createEmbeddedDocuments("ActiveEffect", [magicArmorEffect]);

Temporary Buff

async function applyBless(actor) {
  await actor.createEmbeddedDocuments("ActiveEffect", [{
    name: "Bless",
    img: "icons/magic/holy/prayer.png",
    statuses: ["blessed"],
    changes: [
      { key: "system.bonuses.attack", mode: 2, value: "1d4" },
      { key: "system.bonuses.save", mode: 2, value: "1d4" }
    ],
    duration: {
      rounds: 10,
      combat: game.combat?.id,
      startRound: game.combat?.round
    }
  }]);
}

Status Condition

const proneEffect = {
  name: "Prone",
  img: "icons/svg/falling.svg",
  statuses: ["prone"],
  changes: [
    { key: "system.attributes.ac.bonus", mode: 2, value: "-4" }
  ],
  flags: {
    core: { statusId: "prone" }
  }
};

Formula-Based Value

{
  key: "system.attributes.ac.bonus",
  mode: 2,
  value: "@abilities.dex.mod"  // Roll data reference
}

Common Pitfalls

1. Targeting Derived Values

// WRONG - calculated in prepareDerivedData, too late
{ key: "system.attributes.ac.value", ... }

// CORRECT - target the bonus that feeds into calculation
{ key: "system.attributes.ac.bonus", ... }

2. Modifying Items Directly

// WRONG - effects cannot modify embedded items
{ key: "items.0.system.damage", ... }

// CORRECT - modify actor data only
{ key: "system.bonuses.weaponDamage", ... }

3. Forgetting to Initialize

// WRONG - bonus undefined, effect fails
prepareDerivedData() {
  this.system.ac.total = this.system.ac.base + this.system.ac.bonus;
}

// CORRECT - initialize in prepareBaseData
prepareBaseData() {
  this.system.ac.bonus = 0;
}

4. String vs Number Values

// Effect values are always strings in the changes array
{ key: "system.hp.bonus", mode: 2, value: "10" }

// ADD mode parses to number automatically
// For CUSTOM mode, parse manually:
const numValue = Number(change.value);

5. Checking Effect Active State

// Always check before processing
for (const effect of actor.effects) {
  if (!effect.active) continue;  // Skip disabled
  // Process effect...
}

// Or use the filtered collection
for (const effect of actor.appliedEffects) {
  // Only active effects
}

6. Duration Display Lag

// UI may not update in real-time
// Close/reopen sheet to see accurate duration
// Or force refresh:
await effect.updateDuration();

Finding Valid Keys

// Discover available data paths:
const paths = Object.keys(
  foundry.utils.flattenObject(game.system.model.Actor.character)
);
console.log(paths);

// Or inspect a specific actor:
console.log(Object.keys(
  foundry.utils.flattenObject(actor.system)
));

Implementation Checklist

  • Initialize effect targets in prepareBaseData()
  • Use correct mode for the modification type
  • Set transfer: true for item effects that apply to actors
  • Include duration for temporary effects
  • Add statuses array for conditions
  • Implement CUSTOM mode hook for complex logic
  • Check effect.active before processing
  • Test with multiple stacking effects
  • Verify effects removed when source removed

References


Last Updated: 2026-01-05 Status: Production-Ready Maintainer: ImproperSubset

Score

Total Score

55/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

+10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

0/5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon