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

    Common Scripts — run_javascript Reference

    All snippets are async function bodies. Replace ACTOR_ID, etc. with real values.


    game.bms.ai provides query shortcuts that don't require knowing field paths.

    // Full actor summary (vitals, resources, attributes, resistances, status)
    return game.bms.ai.actor("ACTOR_ID or Name");

    // All actors, brief
    return game.bms.ai.actors();

    // Actor's items grouped by type
    return game.bms.ai.items("ACTOR_ID or Name");

    // Combat tracker state (non-empty downs only)
    return game.bms.ai.combat();

    // Actor's active effects
    return game.bms.ai.effects("ACTOR_ID or Name");

    const actor = game.actors.getName("CHARACTER NAME");
    return actor ? { id: actor.id, name: actor.name, type: actor.type } : "Not found";
    return game.actors.contents.map(a => ({
    id: a.id,
    name: a.name,
    type: a.type,
    hp: `${a.system.health.value}/${a.system.health.max}`,
    shields: `${a.system.shields.value}/${a.system.shields.max}`,
    }));
    const actor = game.actors.get("ACTOR_ID");
    const result = {};
    for (const [arctype, attrs] of Object.entries(actor.system.attributes)) {
    result[arctype] = {};
    for (const [attr, data] of Object.entries(attrs)) {
    result[arctype][attr] = { value: data.value, bonus: data.bonus, total: data.total };
    }
    }
    return result;
    const actor = game.actors.get("ACTOR_ID");
    return Object.fromEntries(
    Object.entries(actor.system.resistances).filter(([k, v]) => v !== 1)
    );

    const actor = game.actors.get("ACTOR_ID");
    // layer: "tempHp" | "shields" | "health" | "sanity"
    // type: "slash" | "fire" | "cold" | "blunt" | "pierce" | etc.
    const result = await actor.applyDamage(AMOUNT, "TYPE", "tempHp");
    return result; // { original, actual }
    const actor = game.actors.get("ACTOR_ID");
    return actor.system.calculateDamage(AMOUNT, "TYPE", "tempHp");
    // returns { original, actual, vitalDamage: { tempHp, shields, health, sanity } }
    const actor = game.actors.get("ACTOR_ID");
    return await actor.applyDamage(AMOUNT, "TYPE", "health"); // skips tempHp and shields
    const actor = game.actors.get("ACTOR_ID");
    return await actor.applyDamage(AMOUNT, "psychic", "sanity");
    const actor = game.actors.get("ACTOR_ID");
    // targetKey: "health" | "shields" | "tempHp" | "sanity" | "resources.mana"
    await actor.applyRestore("health", {
    useSurge: true, // add floor(max/4) to restoration
    consumeSurge: true, // spend 1 surge
    allowOverflow: false,
    bonusValue: 0 // add flat bonus on top of surge value
    });
    const actor = game.actors.get("ACTOR_ID");
    await actor.update({ "system.health.value": NEW_VALUE });
    const actor = game.actors.get("ACTOR_ID");
    await actor.update({ "system.resources.surges.value": NEW_VALUE });

    The world keeps a slug-keyed registry of every custom resource ever defined on a character so action cost pickers (including those on item-owned actions) can offer the full set. Use the helpers below instead of poking the setting directly.

    const helpers = await import("/systems/body-mind-and-soul/module/helpers/custom-resources.mjs");

    // Read the whole registry: { [slug]: { label, defaults: {...} } }
    helpers.getResourceRegistry();

    // Slug from a label (camelCase, whitespace-only split). "Mana Pool" → "manaPool".
    helpers.resourceSlug("Mana Pool");

    // Idempotent upsert (first-write wins on label/defaults). Safe to call repeatedly.
    await helpers.registerResource({
    slug: "mana", label: "Mana", defaults: { max: 10, recoverOnExtended: 10, recoveryType: "set" },
    });

    // Picker-shaped list (registry ∪ actor-local; actor labels win for shared slugs).
    helpers.mergeWithActorResources(actor); // actor may be null for item-owned actions

    // GM-only: import every actor's custom resources into the registry. Idempotent.
    await helpers.backfillFromActors();

    // Admin: rename a label, update defaults, or remove an entry from the registry.
    // Removal does NOT cascade to actor-local copies.
    await helpers.renameResource("mana", "Manapool");
    await helpers.updateResourceDefaults("mana", { max: 12 });
    await helpers.unregisterResource("mana");

    // Advisory usage count for "what would break if I prune this slug?"
    helpers.countResourceUsage("mana");

    The world setting body-mind-and-soul.customResources is auto-populated by:

    • ResourceConfig._onAdd when a player adds a resource on a character sheet
    • The createActor / updateActor hooks on the lead GM
    • The backfillFromActors pass on every ready

    Manage the registry through Game Settings → Configure Settings → Manage Custom Resources (GM-only).

    // applyRest honors dialog-shaped selections — good when you want to simulate a
    // specific player's short/extended choices.
    await game.bms.applyRest(actor, "extended", {
    vitalChoices: { health: true, shields: true, sanity: true },
    surgeSpend: { health: 0, shields: 0, sanity: 0 }, // short rest only
    runeDistribution: {}, // itemId -> count
    resetDecks: true,
    });

    // fullRestoreNpc is the "All Rest" auto-path for GM-controlled NPCs with
    // compelCheckMode smart/dumb: everything to max, regardless of restType.
    await game.bms.fullRestoreNpc(actor, "extended");
    // Dispatches a rest dialog to every in-scope actor's delegate user, with
    // mode locked to "short" or "extended". GM-owned NPCs with compelCheckMode
    // smart/dumb auto-full-restore; direct NPCs get a GM-local dialog.
    // Scope: selected tokens on canvas, or all tokens on the active scene.
    await game.bms.restAll("short"); // or "extended"

    const c = game.combat;
    if (!c) return "No active combat";
    return {
    round: c.round,
    tick: c.system.bms.tickNumber,
    nextTickIn: c.system.bms.nextTickIn,
    downsTilRound: c.system.bms.downsTilRound,
    combatants: c.combatants.contents.map(cb => ({ id: cb.id, name: cb.name, defeated: cb.defeated })),
    downs: c.system.bms.downs
    .filter(d => !d.isEmpty)
    .map(d => ({
    down: d.downNumber,
    actions: d.actions.map(a => ({
    id: a.id,
    action: a.action,
    combatant: c.combatants.get(a.combatantId)?.name ?? a.combatantId,
    type: a.type,
    rt: a.resolutionTime,
    itemId: a.actionItemId,
    })),
    effects: d.effects.map(e => ({
    id: e.id,
    name: e.name,
    combatant: e.combatantId ? c.combatants.get(e.combatantId)?.name : null,
    rt: e.resolutionTime,
    })),
    }))
    };
    const combat = game.combat;
    // Find the actor's combatant
    const combatant = combat.combatants.find(c => c.actor?.name === "CHARACTER NAME");
    const actor = combatant?.actor;
    const actionItem = actor?.items.getName("ACTION ITEM NAME");
    if (!actionItem) return "Action item not found";
    await combat.queueActionFromSheet(actor, actionItem.id);
    return `Queued ${actionItem.name} for ${actor.name}`;
    await game.combat.advanceTracker();
    return "Advanced one down";
    await game.combat.advanceToNextResolution();
    return "Advanced to next resolution";
    const combat = game.combat;
    const downIndex = 4; // duration in downs
    await combat.addEffectToDown(downIndex, {
    name: "Burning",
    description: "On fire",
    icon: "icons/svg/fire.svg",
    combatantId: null, // or a combatant ID
    activeEffectId: null, // or a linked ActiveEffect ID
    resolutionTime: downIndex,
    originalResolutionTime: downIndex,
    });
    const combat = game.combat;
    let removed = false;
    for (const down of combat.system.bms.downs) {
    for (const effect of down.effects) {
    if (effect.name === "EFFECT NAME") {
    await combat.removeEffectFromDown(down.downNumber, effect.id);
    removed = true;
    break;
    }
    }
    if (removed) break;
    }
    return removed ? "Removed" : "Not found";

    const actor = game.actors.get("ACTOR_ID");
    const ae = actor.effects.getName("EFFECT NAME");
    if (!ae) return "Effect not found";
    await ae.update({ disabled: !ae.disabled });
    return `Effect ${ae.name} is now ${ae.disabled ? "disabled" : "enabled"}`;
    const page = await fromUuid("JournalEntry.xxx.JournalEntryPage.yyy");
    if (!page || page.type !== "bmsEffect") return "Not a bmsEffect page";
    const actor = game.actors.get("ACTOR_ID");
    const effectData = page.system.toActiveEffectData(page.name);
    effectData.origin = page.uuid; // enables auto-sync from master
    await actor.createEmbeddedDocuments("ActiveEffect", [effectData]);
    return `Applied ${page.name} to ${actor.name}`;
    const actor = game.actors.get("ACTOR_ID");
    return actor.effects.contents.map(e => ({
    id: e.id,
    name: e.name,
    disabled: e.disabled,
    icon: e.img,
    }));

    const actor = game.actors.get("ACTOR_ID");
    // types: "action", "feature", "item", "rune"
    return actor.items.filter(i => i.type === "action").map(i => ({ id: i.id, name: i.name }));
    const actor = game.actors.get("ACTOR_ID");
    const item = actor.items.getName("ITEM NAME");
    if (!item?.system.usable) return "Not a usable item";
    await item.use();

    const roll = await new Roll("2d6 + 4").evaluate();
    return { formula: roll.formula, total: roll.total, terms: roll.terms.map(t => t.total ?? t.number) };

    Post a chat message

    await ChatMessage.create({
    content: "<p>Your message here</p>",
    speaker: { alias: "GM" }
    });
    return {
    world: game.world.title,
    system: game.system.id,
    scene: canvas.scene?.name ?? "none",
    combat: game.combat ? `Round ${game.combat.round}` : "none",
    players: game.users.filter(u => u.active).map(u => u.name),
    };