Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

[Script][DmgTypeFX][5e]Triggers FX based on damage type on selected token when taking damage

February 23 (1 month ago)




DmgTypeFX v1.0 by Surok

Description:
DmgTypeFX monitors chat for damage type keywords (e.g., acid, fire, etc.) and triggers the corresponding FX when a token’s HP (bar1) decreases. The FX that are triggered are based on the most recent chat message—so whatever damage type keywords appear in the latest non‑API chat message determine the FX. Additionally, the API provides a configuration menu (whispered to the GM) with buttons for each damage type. Clicking a button brings up preset drop‑down menus (for Shape and Color) to update that damage type's FX mapping.

How to Use:

  1. Installation:
    • Copy and paste the code below into your Roll20 API Scripts.
  2. FX Triggering:
    • The API continuously scans chat for damage type keywords. When a token’s HP (bar1) decreases (or via token‑mod commands), the FX corresponding to the keywords in the most recent chat message will trigger on that token.
  3. Configuration Menu:
    • In chat, type:
      !DmgTypeFX-menu
      
      This will whisper a configuration menu to the GM with one button per damage type and a “Set Trigger Bar” option.
    • Clicking a damage type button launches drop‑down menus to select a Shape and Color (from preset options), which updates that damage type’s FX mapping.
    • Use the “Set Trigger Bar” button to change which token bar (bar1, bar2, or bar3) is monitored for HP changes.
  4. Help:
    • In chat, type:
      !DmgTypeFX-help
      
      This displays detailed instructions (whispered to the GM) along with a “Return” button to go back to the main menu.

Notes:

  • Only manual changes on bar1 trigger FX; for bar2 or bar3, use token‑mod commands on selected tokens.
  • The API always uses the most recent chat output to determine which damage type (and thus which FX) to trigger.

Below is the complete code for DmgTypeFX v1.0:

/*
    DmgTypeFX
    Version: 1.0
    Author: Surok (https://app.roll20.net/users/335573/surok)
    Description: Monitors chat for damage type keywords and triggers the corresponding FX when a token's HP (bar1) decreases.
                 Also provides a configuration menu with individual buttons for each damage type that prompt for FX settings via drop‑down menus.
*/

(function() {
    // === Basic UI Styling for Chat Menus ===
    const containerStyle = "border: 2px solid black; border-radius: 4px; " +
                           "box-shadow: 1px 1px 1px #707070; text-align: center; " +
                           "padding: 3px 0; margin: 0 auto; color: #000; " +
                           "background-image: -webkit-linear-gradient(-45deg, #a7c7dc 0%, #85b2d3 100%);";
    const headerStyle = "text-align: center; margin: 0 0 10px;";
    const smallButtonStyle = "padding: 1px; text-align: center; font-size: 9pt; " +
                             "width: 48px; height: 14px; border: 1px solid black; " +
                             "margin: 1px; border-radius: 4px; box-shadow: 1px 1px 1px #707070; " +
                             "color: white; text-decoration: none; display: inline-block;";
    const menuButtonStyle = "display: block; text-decoration: none; color: white; " +
                            "background-color: #6FAEC7; padding: 5px; border-radius: 4px; " +
                            "box-shadow: 1px 1px 1px #707070; margin-bottom: 5px;";
    // Main menu navigation block with a Help button.
    const mainNavBlock = '<div style="text-align: center; margin-top: 10px;">' +
                         '<a href="!DmgTypeFX-help" style="' + smallButtonStyle + '">Help</a>' +
                         '</div>';
    // Help menu navigation block with a "Return" button.
    const helpNavBlock = '<div style="text-align: center; margin-top: 10px;">' +
                         '<a href="!DmgTypeFX-menu" style="' + smallButtonStyle + '">Return</a>' +
                         '</div>';
    function buildMenu(title, content, nav) {
      return '<div style="' + containerStyle + '">' +
             '<h3 style="' + headerStyle + '">' + title + '</h3>' +
             '<div>' + content + '</div>' +
             nav +
             '</div>';
    }
    
    // === Global Variables & FX Mapping ===
    
    // Stores the last detected damage type words from chat.
    var lastDetectedDamageTypes = [];
    
    // Mapping for 5e damage types to FX effect sequences.
    // Each value is a semicolon-separated list of FX names.
    const damageFxMap = {
        acid:        "bubbling-acid",    // Bubbling Acid
        bludgeoning: "bomb-blood",       // Bomb Blood
        cold:        "burst-frost",      // Burst Frost
        fire:        "explode-fire",     // Explode Fire
        force:       "bomb-magic",       // Bomb Magic
        lightning:   "splatter-magic",   // Splatter Magic
        poison:      "bubbling-slime",   // Bubbling Slime
        thunder:     "nova-smoke",       // Nova Smoke
        radiant:     "glow-holy",        // Glow Holy
        necrotic:    "glow-death",       // Glow Death
        psychic:     "nova-magic",       // Nova Magic
        piercing:    "pooling-blood",    // Pooling Blood
        slashing:    "splatter-blood"    // Splatter Blood
    };
    
    // Global, case-insensitive, regex to detect damage type words.
    // (Global flag 'g' ensures we can collect multiple types in one message.)
    const damageTypeRegex = /\b(acid|bludgeoning|cold|fire|force|lightning|poison|thunder|radiant|necrotic|psychic|piercing|slashing)\b/gi;
    
    /**
     * playEffects(token, fxStr)
     * Splits a semicolon-separated string of FX names and spawns each effect at the token's location.
     *
     * @param {Graphic} token - The Roll20 token object.
     * @param {string} fxStr - A semicolon-separated list of FX names.
     */
    function playEffects(token, fxStr) {
        if (!fxStr) return;
        let fxArray = fxStr.split(";");
        fxArray.forEach(function(fx, index) {
            setTimeout(function() {
                spawnFx(token.get("left"), token.get("top"), fx.trim(), token.get("pageid"));
            }, index * 250); // Adjust delay between FX as needed.
        });
    }
    
    // === Damage FX Triggering ===
    
    // 1. Monitor chat for damage type keywords.
    on("chat:message", function(msg) {
        if (msg.type === "api") return;
        
        let matches = [];
        let regex = new RegExp(damageTypeRegex.source, "gi");
        let m;
        while ((m = regex.exec(msg.content)) !== null) {
            let dt = m[1].toLowerCase();
            if (!matches.includes(dt)) {
                matches.push(dt);
            }
        }
        if (matches.length > 0) {
            lastDetectedDamageTypes = matches;
            log("Detected damage types from chat: " + lastDetectedDamageTypes.join(", "));
        }
    });
    
    // 2. Monitor token HP changes (bar1).
    on("change:graphic", function(token, prev) {
        let prevHP = parseInt(prev.bar1_value, 10) || 0;
        let currentHP = parseInt(token.get("bar1_value"), 10) || 0;
        if (currentHP < prevHP) {
            if (lastDetectedDamageTypes.length > 0) {
                lastDetectedDamageTypes.forEach(function(dt) {
                    if (damageFxMap.hasOwnProperty(dt)) {
                        let fxSequence = damageFxMap[dt];
                        playEffects(token, fxSequence);
                    }
                });
            }
        }
    });
    
    // 3. Monitor token‑mod commands that change HP.
    on("chat:message", function(msg) {
        if (msg.type !== "api" || msg.content.indexOf("!token-mod") !== 0) return;
        if (msg.content.indexOf("--set bar1_value|") === -1) return;
        let regexCmd = /--set\s+bar1_value\|(-?\d+)/i;
        let match = msg.content.match(regexCmd);
        if (!match) return;
        let valueChange = parseInt(match[1], 10);
        if (valueChange < 0) {
            if (msg.selected && msg.selected.length > 0) {
                msg.selected.forEach(function(sel) {
                    let token = getObj("graphic", sel._id);
                    if (token && lastDetectedDamageTypes.length > 0) {
                        lastDetectedDamageTypes.forEach(function(dt) {
                            if (damageFxMap.hasOwnProperty(dt)) {
                                let fxSequence = damageFxMap[dt];
                                playEffects(token, fxSequence);
                            }
                        });
                    }
                });
            }
        }
    });
    
    // === Configuration Menu Commands ===
    
    // (A) Main Configuration Menu: Each damage type button launches drop-down prompts to change its FX.
    on("chat:message", function(msg) {
        if (msg.type !== "api") return;
        let args = msg.content.split(/\s+/);
        if (args[0] === "!DmgTypeFX-menu" && args.length === 1) {
            let list = '<ul style="list-style: none; padding: 0; margin: 0; text-align: center;">';
            for (let dt in damageFxMap) {
                list += '<li><a href="!DmgTypeFX-config ' + dt + ' ' +
                    '?{Choose Shape for ' + dt + '|Breath,breath|Beam,beam|Rocket,rocket|Burn,burn|Glow,glow|Sparkle,sparkle|Shield,shield|Bomb,bomb|Explode,explode|Burst,burst|Bubbling,bubbling|Nova,nova|Splatter,splatter|Pooling,pooling|Missile,missile} ' +
                    '?{Choose Color for ' + dt + '|Fire,fire|Charm,charm|Acid,acid|Death,death|Holy,holy|Blood,blood|Frost,frost|Slime,slime|Smoke,smoke|Water,water|Magic,magic}" style="' + menuButtonStyle + '">' +
                    dt + '</a></li>';
            }
            list += '</ul>';
            let menu = buildMenu("DmgTypeFX Configuration Menu", list, mainNavBlock);
            // Include short instructions above the menu.
            let instructions = "<p style='font-size:10pt; text-align:center;'>This API monitors chat for damage type keywords and triggers FX when tokens lose HP. It uses the last message in chat for the FX type. Use '!DmgTypeFX-menu' to open this configuration menu. Click a damage type button below to choose a Shape and Color from preset drop‑down menus, which updates that damage type's FX.</p>";
            sendChat("DmgTypeFX", "/w gm " + instructions + menu);
        }
    });
    
    // (B) Help Menu: Shows instructions with left-aligned text and a "Return" button.
    on("chat:message", function(msg) {
        if (msg.type !== "api") return;
        let args = msg.content.split(/\s+/);
        if (args[0] === "!DmgTypeFX-help") {
            let helpText = "<p style='font-size:10pt; text-align:left;'>" +
                "Instructions:<br>" +
                "• This API monitors chat for damage type keywords (e.g. fire, acid) and triggers FX when tokens lose HP.<br>" +
                "• It uses the last message in chat for the FX type.<br>" +
                "• Use '!DmgTypeFX-menu' to open the configuration menu. Click a damage type button below to choose a Shape and Color from preset drop‑down menus, which updates that damage type's FX.<br>" +
                "• To trigger FX, simply include a damage type keyword in your chat message and then reduce a token's HP (or use a token‑mod command)." +
                "</p>";
            let menu = buildMenu("DmgTypeFX Help", helpText, helpNavBlock);
            sendChat("DmgTypeFX", "/w gm " + menu);
        }
    });
    
    // (C) Configuration Command: Updates the FX mapping using the drop-down values.
    on("chat:message", function(msg) {
        if (msg.type !== "api") return;
        let args = msg.content.split(/\s+/);
        if (args[0] === "!DmgTypeFX-config") {
            if (args.length < 4) {
                sendChat("DmgTypeFX", "/w gm Usage: !DmgTypeFX-config [DamageType] [Shape] [Color]");
                return;
            }
            let dmgType = args[1].toLowerCase();
            let shape = args[2];
            let color = args[3];
            let newFx = shape.toLowerCase() + "-" + color.toLowerCase();
            damageFxMap[dmgType] = newFx;
            sendChat("DmgTypeFX", "/w gm " + dmgType + " FX updated to: " + newFx);
        }
    });
})();


Nice - no campaign atm to play with it tho but bookmarking this for my next future table.