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 .
×

[Script] RandomTarget - select a group of tokens to randomly choose a target

1783013330
Surok
Pro
API Scripter
I've been using the Fate API for many years now and while it got the job done it never listed which tokens were selected. My own preference is to be as transparent as possible with my players. So decided to build a random target API from the ground up with all the bells and whistles I wanted. Features Smart Selection: Automatically filters your selection to grab valid tokens, safely ignoring the map layer and lighting assets. Chat UI:  It displays the pool of candidates as mini-icons (with hover-to-read names) and highlights the final chosen target. Auto-Ping: Immediately pings the chosen token on the VTT. Clickable Chat Image: The target's image in the chat box acts as a button. Anyone can click it to instantly re-ping the target's current location on the map. How to Use It Add the code below as a new script in your API Sandbox. Create a macro in your game with the command: !randomtarget Select tokens on the board, click your macro, and let the script do the rest! /** * Random Target * * Usage: * 1. Select tokens on the board. * 2. Type !randomtarget in the chat (or use a macro). * 3. Click the target's image in the chat to ping them again! */ on("ready", () => { on("chat:message", (msg) => { // Ensure we are only looking at API commands if (msg.type !== "api") return; // --- NEW FEATURE: Listen for the clickable image ping --- if (msg.content.toLowerCase().startsWith("!pingtoken")) { let args = msg.content.split(" "); if (args.length > 1) { let tokenId = args[1]; let token = getObj("graphic", tokenId); if (token) { let left = token.get("left"); let top = token.get("top"); let pageId = token.get("_pageid"); // Ping the map (visible to everyone) sendPing(left, top, pageId, msg.playerid, false); } } return; } // --- EXISTING FEATURE: The Random Target logic --- if (msg.content.toLowerCase().startsWith("!randomtarget")) { if (!msg.selected || msg.selected.length === 0) { sendChat("Targeting System", `/w "${msg.who}" Please select at least one token on the board first.`); return; } // Filter out maps, lighting, etc. let candidates = msg.selected .filter(obj => obj._type === "graphic") .map(obj => getObj("graphic", obj._id)) .filter(token => { return token !== undefined && token.get("layer") !== "map"; }); if (candidates.length === 0) { sendChat("Targeting System", `/w "${msg.who}" No valid tokens found in your selection.`); return; } const getCleanImgUrl = (url) => { if (!url) return ""; return url.replace(/(max|med)/i, "thumb").split("?")[0]; }; let randomIndex = Math.floor(Math.random() * candidates.length); let chosenToken = candidates[randomIndex]; // Initial automated ping on selection let left = chosenToken.get("left"); let top = chosenToken.get("top"); let pageId = chosenToken.get("_pageid"); sendPing(left, top, pageId, msg.playerid, false); // Condensed Candidate List let candidatesHtml = candidates.map(t => { let name = t.get("name") || "Unnamed"; let img = getCleanImgUrl(t.get("imgsrc")); return `<img src="${img}" style="width: 22px; height: 22px; border: 1px solid #d3d3d3; border-radius: 3px; vertical-align: middle; margin: 2px;" title="${name}" alt="${name}" />`; }).join(""); let chosenName = chosenToken.get("name") || "Unnamed Target"; let chosenImg = getCleanImgUrl(chosenToken.get("imgsrc")); let chosenId = chosenToken.id; // Clean, White, Silver-Bordered UI with clickable image link and updated Headers let chatMessage = ` <div style="border: 1px solid #c0c0c0; border-radius: 4px; padding: 6px; background-color: #ffffff; font-family: Arial, sans-serif; color: #333; box-shadow: 1px 1px 3px rgba(0,0,0,0.1);"> <div style="font-size: 11px; font-weight: bold; border-bottom: 1px solid #e0e0e0; padding-bottom: 4px; margin-bottom: 6px; text-align: center; text-transform: uppercase; letter-spacing: 1px; color: #666;"> Randomly Choosing </div> <div style="text-align: center; margin-bottom: 8px;"> ${candidatesHtml} </div> <div style="text-align: center; padding: 6px; border: 1px solid #d3d3d3; border-radius: 4px; background-color: #fafafa;"> <div style="font-size: 11px; font-weight: bold; text-transform: uppercase; letter-spacing: 1px; color: #666; margin-bottom: 6px;"> Target Selected </div> <a href="!pingtoken ${chosenId}" style="background-color: transparent; border: none; padding: 0; margin: 0; display: inline-block; box-shadow: none; text-decoration: none;" title="Click to Ping Map"> <img src="${chosenImg}" style="width: 60px; height: 60px; border: 1px solid #c0c0c0; border-radius: 4px; object-fit: contain; margin-bottom: 4px; background-color: #fff; cursor: pointer;" /> </a> <div style="font-size: 14px; font-weight: bold; color: #222;">${chosenName}</div> </div> </div> `.replace(/\n\s*/g, ''); sendChat("Targeting", chatMessage); } }); });
1783029280
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Nice! I will give that a try. My own variant is mostly styling, and it plays an ominous sound. This looks like a genuinely useful upgrade .
Great job! Very Useful!
Just added this, thank you very much.  I really like the presentation of this also