Body, Mind & Soul — API Reference
    Preparing search index...

    Workflows — Step-by-Step GM Task Guides


    When: A player takes a hit, you're adjudicating damage, or running a script effect.

    1. Find the actor ID if you don't have it:

      search_documents(query: "Character Name", documentTypes: ["Actor"])
      
    2. Apply damage via run_javascript:

      const actor = game.actors.get("ACTOR_ID");
      return await actor.applyDamage(AMOUNT, "TYPE", "tempHp");
      • This respects resistances, cascades through layers (tempHp → shields → health), fires hooks, and posts a chat message automatically.
      • Returns { original, actual } — actual may be lower due to resistances.
    3. If you want to preview first without applying:

      return game.actors.get("ACTOR_ID").system.calculateDamage(AMOUNT, "TYPE", "tempHp");
      

    1. run_javascript:
      const actor = game.actors.get("ACTOR_ID");
      await actor.applyRestore("health", { useSurge: true, consumeSurge: true });
      • Restores floor(health.max / 4) + any bonusValue, deducts one surge.
      • For shields or sanity, change "health" to "shields" or "sanity".

    When: You need to set a specific value without surge logic (e.g., "set HP to 5", "set sanity to 0").

    1. Read the actor first (required for update):

      read_document(documentType: "Actor", documentId: "ACTOR_ID")
      
    2. Update the specific field:

      update_document(
      documentType: "Actor",
      documentId: "ACTOR_ID",
      updates: { "system.health.value": 5 }
      )

    1. run_javascript with the tracker summary from common-scripts.
    2. Or: read_document(documentType: "Combat", documentId: "COMBAT_ID") and look at system.bms.downs.

    To find the active combat ID: run_javascriptreturn game.combat?.id


    When: You need to add an action to the tracker on behalf of a combatant.

    1. Find the actor and item:

      const actor = game.actors.getName("CHARACTER NAME");
      const item = actor.items.getName("ACTION NAME");
      return { actorId: actor.id, itemId: item.id, swings: item.system.swings };
    2. Queue it:

      await game.combat.queueActionFromSheet(
      game.actors.get("ACTOR_ID"),
      "ACTION_ITEM_ID",
      0 // swing index (0 = first swing)
      );

    When: Casting a spell, applying a condition, or tracking something that expires after N downs.

    const combat = game.combat;
    const durationDowns = 4; // 4 downs = 2 ticks = 2 seconds
    await combat.addEffectToDown(durationDowns, {
    name: "Blinded",
    description: "Cannot see",
    icon: "icons/svg/blind.svg",
    combatantId: "COMBATANT_ID", // or null for unlinked
    activeEffectId: null, // or a real ActiveEffect ID to auto-disable on expiry
    resolutionTime: durationDowns,
    originalResolutionTime: durationDowns,
    });

    Duration reference: 2 downs = 1 tick (1 second), 12 downs = 1 round (6 seconds).


    When: A spell or ability grants a status effect.

    Option A — Enable an existing (disabled) effect:

    const actor = game.actors.get("ACTOR_ID");
    const ae = actor.effects.getName("EFFECT NAME");
    await ae.update({ disabled: false });

    Option B — Create a new effect:

    1. read_document the actor first, then:
      update_document(
      documentType: "Actor",
      documentId: "ACTOR_ID",
      operations: [{
      type: "insert",
      collection: "effects",
      data: {
      name: "Burning",
      img: "icons/svg/fire.svg",
      disabled: false,
      changes: [{ key: "system.resistances.fire", mode: 2, value: "0.5" }]
      }
      }]
      )

    1. run_javascript to inspect an existing NPC for reference schema:

      const npc = game.actors.find(a => a.type === "npc");
      return npc ? { id: npc.id, name: npc.name } : "No NPCs found";
    2. If you need to see the full schema:

      read_document(documentType: "Actor", documentId: "EXISTING_NPC_ID")
      
    3. Create:

      • Use create_document with documentType: "Actor" and include type: "npc" in the data.
      • Set vitals, attributes, and resistances in system.*.
      • Add items (actions, features) via embedded operations or separate create_document calls.

    1. Check for lingering tracker effects with linked ActiveEffects:

      const c = game.combat;
      return c.system.bms.downs.flatMap(d => d.effects.filter(e => e.activeEffectId))
      .map(e => ({ name: e.name, actor: e.actor?.name }));
    2. If any linked effects exist, confirm with the GM whether to disable them.

    3. End combat:

      await game.combat.endCombat();
      

      (This will prompt the GM in-client to disable linked effects.)


    When: Creating a consumable or activatable item that fires a BMS trigger when used.

    IMPORTANT RULES — read before attempting:

    • Conditions array should be [] or use alwaysNEVER use isThisItem (it doesn't exist and silently blocks the trigger)
    • chatMessage content uses {actor.name} tokens, NOT @actor
    • amount in outcomes must be a string (e.g. "1" not 1)
    • NEVER use dot-path array indexing like system.triggers.0.hook — always update via the array field
    list_documents(documentType: "Item")
    read_document(documentType: "Item", documentId: "EXISTING_ITEM_ID")

    Include ALL trigger data inline. No follow-up patching needed.

    {
    "documentType": "Item",
    "data": {
    "name": "Surge Restorer",
    "type": "item",
    "img": "icons/svg/item-bag.svg",
    "system": {
    "description": "Restores 1 surge when used.",
    "quantity": 1,
    "usable": true,
    "triggers": [
    {
    "id": "",
    "hook": "bms.itemUsed",
    "enabled": true,
    "conditions": [],
    "outcomes": [
    {
    "id": "",
    "type": "changeResource",
    "config": { "resource": "surges", "operation": "add", "amount": "1" }
    },
    {
    "id": "",
    "type": "chatMessage",
    "config": { "content": "{actor.name} restores 1 surge." }
    }
    ]
    }
    ]
    }
    }
    }
    read_document(documentType: "Item", documentId: "CREATED_ITEM_ID")
    

    Confirm triggers[0].hook is correct and outcomes contains the right entries.

    system.triggers supports partial updates — include only the fields you want to change, identified by id. Nested conditions and outcomes are also merged by id.

    Change just a trigger's hook (all other fields preserved):

    { "updates": { "system.triggers": [{ "id": "triggerId", "hook": "bms.combatDown" }] } }
    

    Update one outcome's config (other outcomes preserved):

    {
    "updates": {
    "system.triggers": [{
    "id": "triggerId",
    "outcomes": [{ "id": "outcomeId", "config": { "content": "New message." } }]
    }]
    }
    }

    Full replacement (omit id from all items — same behaviour as before):

    { "updates": { "system.triggers": [{ "hook": "bms.itemUsed", "enabled": true, "conditions": [], "outcomes": [...] }] } }
    

    If you need to read_document first to get the current trigger IDs, do so — but you only need to send back the fields you're changing.


    1. Search journal entries:

      search_documents(query: "keyword", documentTypes: ["JournalEntry"])
      
    2. Read the result:

      read_document(documentType: "JournalEntry", documentId: "JOURNAL_ID")
      

      Then look through pages for the relevant page content.


    const actor = game.actors.get("ACTOR_ID");
    return {
    id: actor.id,
    name: actor.name,
    type: actor.type,
    items: actor.items.map(i => ({ id: i.id, name: i.name, type: i.type })),
    effects: actor.effects.contents.map(e => ({ id: e.id, name: e.name, disabled: e.disabled })),
    resources: Object.keys(actor.system.resources),
    actionGroups: actor.system.actionGroups?.map(g => g.label) ?? [],
    };