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] Token bleeds on map

1400945796

Edited 1401112865
EDIT: If you're looking for v2, check a few posts down . Read this first though, so you know what I'm talking about there :) So, I finally decided to upgrade to Mentor and check this API thing out. Yikes, this is fun! We've been playing Dragon Age RPG lately, and since I've heard that the battles are exaggerately bloody in the video game, I decided to make a blood splatter script (and attach it to damage dealing macros). It worked out pretty swell, and it was surprisingly quick to hack together. The hardest part was finding good blood splatter images. I figured someone else might find this fun, and maybe even help me improve it someway. So, enjoy: Here it is in action Usage: !splatter @{target|token_id} Script: // See: <a href="https://app.roll20.net/forum/post/733277/api-fire" rel="nofollow">https://app.roll20.net/forum/post/733277/api-fire</a>... (function () { var oldCreateObj = createObj; createObj = function () { var obj = oldCreateObj.apply(this, arguments); if (obj && !obj.fbpath) { obj.fbpath = obj.changed._fbpath.replace(/([^\/]*\/){4}/, "/"); } return obj; }; }()); // TODO: This script pretty much assumes square-ish/round tokens. Width is the desicive factor in many places. // helper functions function toDegrees(angle) { return angle * (180 / Math.PI); } function toRadians(angle) { return angle * (Math.PI / 180); } // Global constants var bloodUrls = [ "any amount of urls to assets UPLOADED to your gallery (see Wiki for imgsrc usage). Recommended is square-ish image that is partly transparent to make several blood splatters on top of each other intensified." ]; // Splat functions // Splatter's location is [top,left], and moved to random direction r pixels function createSplat(pageId, top, left, r) { var randomDirection = randomInteger(360) - 1, createResult = createObj("graphic", { _subtype: "token", _pageid: pageId, imgsrc: bloodUrls[randomInteger(bloodUrls.length) - 1], top: top + Math.sin(toRadians(randomDirection)) * r, left: left + Math.cos(toRadians(randomDirection)) * r, width: 70, height: 70, rotation: randomInteger(360) - 1, layer: "map", flipv: randomInteger(2) === 2 ? true : false, fliph: randomInteger(2) === 2 ? true : false, controlledby: "" }), splatterEnlargeFunction = function (splatterDrawing, targetWidth, increment) { var sx = splatterDrawing.get("width"), sy = splatterDrawing.get("height"); if (sx &lt; targetWidth) { splatterDrawing.set({width: sx + increment, height: sy + increment}); setTimeout(function () {splatterEnlargeFunction(splatterDrawing, targetWidth, increment); }, 60); } }; setTimeout(function () { splatterEnlargeFunction(createResult, 120 + randomInteger(40), 9); }, 100); } // Script to add a splat of blood to a token on("chat:message", function (msg) { var cmdName = "!splatter ", msgTxt = msg.content, targetTokenId, target; if (msg.type === "api" && msgTxt.indexOf(cmdName) !== -1) { targetTokenId = msgTxt.slice(cmdName.length); if (_.isString(targetTokenId) && targetTokenId.length &gt; 0) { target = getObj("graphic", targetTokenId); createSplat(target.get("_pageid"), target.get("top"), target.get("left"), target.get("width") / 2); } } }); /* TODO: Listen to "lastmove" for a bloodtrail for "wounded tokens */ Edit 1 Notice that you must setup the bloodUrls variable yourself. I used the ones from this marketplace item (there's two blood splatters in there, they work pretty well), and because it's not free I didn't retain the urls to the images in my library (I don't even know if someone else's campaign could use resources in my library...).
1400965360
Lithl
Pro
Sheet Author
API Scripter
Neat!
1400971522
Sam M.
Pro
Sheet Author
But why would you hurt the doge?
I cant get it to work
1400999836

Edited 1401112971
Shaun Bradley (Elmo) said: I cant get it to work Did you setup the bloodUrls variable? It should be an array of strings, and there must be at least one. Of course you can have any number of them for added variety. Edit: I updated the OP to reflect this information. It really should've been stated more clearly, instead of in the middle of the script itself :) Later edit: Modifying the script is not necessary anymore. See v2 a few posts down.
Sam said: But why would you hurt the doge? As you can see from the health bars in the gif, no doges were harmed in the making of this script ;) (and for the feels, in the battle in which the merchant (doge's master) met he's maker, he demanded with his dying breath that all healing should target his also near death pet. That dog seems to be destined to do great things)
might I suggest working this script to include variable splatter designs based on customizable commands? for example, !splatter for blood, !crater or similar for a small crater from explosives, !leak for oil spills or such (modern - futuristic settings) I think this is marvellous. keep up the good work
1401112784

Edited 1401390390
Michael H. said: might I suggest working this script to include variable splatter designs based on customizable commands? for example, !splatter for blood, !crater or similar for a small crater from explosives, !leak for oil spills or such (modern - futuristic settings) I think this is marvellous. keep up the good work Oh, that's nice! In fact, let me see your "different kinds of splatters" and raise you a "rollable splatter tables". Gimme a minute.... and here's a thing: Splatter v2 Here's an updated version of splatter. Firstly, find the script at the end of this message and replace any older version with this. Setup Create a rollable table called SplatterTable (case sensitive), and populate it with items. Item name should start with the type of splatter it is (these are case sensitive as well), and the image must be in your uploaded pictures (note: I think that web search results are moved there automatically by Roll20, 'cause I didn't upload those bones in the pic myself) (see the wiki page of API for more information on imgsrc). The weights of items are respected. That's right, my skeletons bleed bones! They're hard core like that. Usage You invoke the scipt like this !splatter @{target|token_id} bloodtype The bloodtype is the desired bloodtype that you've defined while creating the table. If it is omitted , the script tries to find a splatter of type blood . My attack macros! *Shriek* So, I had the !splatter in my damage rolling token actions - which is not good, 'cause not every entity in the world bleeds blood. Sure, I could've made a token actions "Bleed X" for different kinds of bloods, and then try and remember to call it everytime someone was damaged. Instead, since every (combatant) token I use represents a character, here's what I have in my damage rolling macros: !splatter @{target|token_id} @{target|Bloodtype} So, yeah. Characters in my campaign designate their bloodtype now. Even more confusing must be, that almost everyone's bloodtype is blood :D Script // See: <a href="https://app.roll20.net/forum/post/733277/api-fire" rel="nofollow">https://app.roll20.net/forum/post/733277/api-fire</a>... (function () { var oldCreateObj = createObj; createObj = function () { var obj = oldCreateObj.apply(this, arguments); if (obj && !obj.fbpath) { obj.fbpath = obj.changed._fbpath.replace(/([^\/]*\/){4}/, "/"); } return obj; }; }()); // TODO: This script pretty much assumes square-ish/round tokens. Width is the desicive factor in many places. // helper functions function getSplatterImageUrl (splatterType) { var table, urls = [], ret; table = findObjs( {_type:"rollabletable", name: "SplatterTable"}); log("Found these tables, using the first one: "); log(table); if(table.length !== 1) { sendChat("Splatter","No table SplatterTable found."); throw "Splatter, no table"; } table = table[0]; var currentPageGraphics = findObjs({ _type: "tableitem", _rollabletableid: table.get("_id") }); _.each(currentPageGraphics, function(obj) { var i; if(obj.get("name").indexOf(splatterType) === 0) { for(i = 0 ; i &lt; obj.get("weight"); ++i) { urls.push(obj.get("avatar")); } } }); if(urls.length === 0) { sendChat("Splatter","No splatter types of \""+splatterType+"\" found in table SplatterTable.") throw "Splatter, no suitable splatterType"; } ret = urls[randomInteger(urls.length)-1]; log("Found image: "+ret); ret = ret.replace("max.png","thumb.png").replace("med.png","thumb.png"); log("Using image: "+ret); return ret; } // Splat functions function toDegrees(angle) { return angle * (180 / Math.PI); } function toRadians(angle) { return angle * (Math.PI / 180); } // Splatter's location is [top,left], and moved to random direction r pixels function createSplat2(pageId, top, left, r, splatterType) { var randomDirection = randomInteger(360) - 1, createResult = createObj("graphic", { _subtype: "token", _pageid: pageId, imgsrc: getSplatterImageUrl(splatterType), top: top + Math.sin(toRadians(randomDirection)) * r, left: left + Math.cos(toRadians(randomDirection)) * r, width: 70, height: 70, rotation: randomInteger(360) - 1, layer: "map", flipv: randomInteger(2) === 2 ? true : false, fliph: randomInteger(2) === 2 ? true : false, controlledby: "" }), splatterEnlargeFunction = function (splatterDrawing, targetWidth, increment) { var sx = splatterDrawing.get("width"), sy = splatterDrawing.get("height"); log("sx: "+sx+", targetWidth: "+targetWidth); if (sx &lt; targetWidth) { log("Enlarging splatter drawing"); splatterDrawing.set({width: sx + increment, height: sy + increment}); setTimeout(function () {splatterEnlargeFunction(splatterDrawing, targetWidth, increment); }, 60); } }; toFront(createResult); setTimeout(function () { splatterEnlargeFunction(createResult, 2*r, 9); }, 100); } // Script to add a splat of blood to a token on("chat:message", function (msg) { var cmdName = "!splatter", msgTxt = msg.content, targetTokenId, target, params; log("msgTxt: "+msgTxt); if (msg.type === "api" && msgTxt.indexOf(cmdName) !== -1) { params = msgTxt.split(" "); targetTokenId = params[1]; splatterType = params[2]; if(splatterType === undefined || splatterType.length === 0) { splatterType = "blood"; } log("Splatting some \""+splatterType+"\" for token "+targetTokenId); if (_.isString(targetTokenId) && targetTokenId.length &gt; 0) { target = getObj("graphic", targetTokenId); log("target: ");log(target); try { createSplat2(target.get("_pageid"), target.get("top"), target.get("left"), target.get("width") / 2, splatterType); } catch(e) { log(e); } } } }); // TODO: Listen to "lastmove" for a bloodtrail
1401388141
DXWarlock
Sheet Author
API Scripter
This is awesome :) But one problem I seem to have with it, it puts the blood on map layer behind the map itself..so cannot see it :\
William R. said: This is awesome :) But one problem I seem to have with it, it puts the blood on map layer behind the map itself..so cannot see it :\ You are correct, there is a small bug that was fixed with a simple toFront(createResult); I edited the previous post. (Also, no one seemed to mind that the script read table called Splatter2 instead of SplatterTable as documented. Fixed that as well :) )
1401390560
DXWarlock
Sheet Author
API Scripter
Ah yea I noticed that I just edited the table name to match the script :)
1401390953
DXWarlock
Sheet Author
API Scripter
Hmm even with the new one, still ends up behind everything else for me :\
William R. said: Hmm even with the new one, still ends up behind everything else for me :\ Could you check that all of your map elements are on the map layer? If any of them are on the token layer, the splatter can't get in fron of them.
1401395157

Edited 1401395166
DXWarlock
Sheet Author
API Scripter
just got a sinlge map element. was testing it on my landing page. Its a single image on the map layer and player tokens on token layer.
Do you have any other scripts that could interfere with this one? Could you try this in a fresh project? Other than that, I'm stumped at the moment. What could be the culprit?
1401452110

Edited 1401452544
DXWarlock
Sheet Author
API Scripter
I dont have any scripts that mess with token order or front/back. but to test, disabling all my other scripts, and on my testbed barebones campaign (on dev) it does it. see it for a second then "poof" behind the map image. Assuming it something on my end or such, as no one else complains. So dont be banging your head against it too much..Its more than likely not a universal problem.
1401453337

Edited 1401453495
DXWarlock
Sheet Author
API Scripter
To update: your script works fine ..I was being sleep deprived and stupid working on it yesterday I got into one of my "I'm working on my campaign until its ready for saturday" 14 hour benders :P I totally forgot inside my "catch inline rolls" formatter script I have, I tossed in a random "on token change" catch one day and forgot about it being in it..and its saved on both my main and test games (the only one on my test actually)..didn't think to disable it as I was thinking "surely looking for rolls to format text would have NOTHING to do with it." In the end somewhere in this bit of code, my script it doing it. now for me to find out why its fighting with yours. ( if anyone can see an "IF" I can add or edit to this help stop it before I hamfist a way, I offer a firm handshake and a smile as thanks :) ) CONFIG.forEach(function (opts) { //SET HURT----------- if(obj.get("bar" + opts.barId + "_max") == undefined || obj.get("bar" + opts.barId + "_value") == undefined) return; var maxValue = parseInt(obj.get("bar" + opts.barId + "_max")); var curValue = parseInt(obj.get("bar" + opts.barId + "_value")); if(maxValue != NaN && curValue != NaN) { var markerName = "status_" + opts.status; if(curValue &lt;= (maxValue * opts.barRatioMax) && curValue &gt; (maxValue * opts.barRatioMin)) { obj.set(markerName, opts.whenLow); } else { obj.set(markerName, !opts.whenLow); } } });
I have a strange request :) Your script is awesome and clever and helps add to the immersion of my games. Is it possible to make it so it deletes itself after the blood spatter? Or maybe just the method on how to do that. I ask because I want to include other effects then just blood . Like an explosion or a hit etc. I know this isnt its intended purpose but unfortunately I am unable to figure out how to create a delete command after its reached the designated size.
1401476109
Lithl
Pro
Sheet Author
API Scripter
The API cannot delete objects. You can resize them so you don't seem them any more, change the image to something transparent, or move them off the map, but you can't delete them. (At least, not currently.) If you start making lots of tokens and then pushing them under the rug, of course, your campaign might eventually slow down. One option might be to store the ids of the "deleted" graphics in the state object, and when you would create a new graphic use one of those instead, if available.
Ah I see :) Nevermind then. I assume its in the works for a delete function then.
wow, I quite like the idea of using a table to put the images in. now i am totally going to be using this script in every single game i run. its just too awesome not to use. seriously. though an option to put every blood image to pos 0,0 so that a person can bulk select and delete the lot at once would be lovely. or am I just getting greedy here?
This is amazing. :) Thanks a lot. Now to find me a lot of neat blood tokens for the upcoming weekend.
Michael H. said: wow, I quite like the idea of using a table to put the images in. now i am totally going to be using this script in every single game i run. its just too awesome not to use. seriously. though an option to put every blood image to pos 0,0 so that a person can bulk select and delete the lot at once would be lovely. or am I just getting greedy here? That should be totally doable. In fact, I begun work on such a function. I just need to solve how to mark generated graphics as such, so that all of them - and only them - get moved to a designated "clean up area". (A variable in the script containing the ids of generated graphics could work, sure, but would be reseted when the sandbox winds down) But I'll continue work on that after we're done moving house.
1402417613
Lithl
Pro
Sheet Author
API Scripter
Aki J. said: A variable in the script containing the ids of generated graphics could work, sure, but would be reseted when the sandbox winds down That's what state is for. Anything you put into the state object persists between sessions.
Brian said: Aki J. said: A variable in the script containing the ids of generated graphics could work, sure, but would be reseted when the sandbox winds down That's what state is for. Anything you put into the state object persists between sessions. Here's link to the Wiki page where state is covered. Cool. Somehow I've missed that. Thanks! :) (it does get reset when the campaign is restarted , but as long as you don't make changes to your scripts you're golden, if I understood correctly)
Multiple tokens can have the same "name"... Simply have them count down and move token named "XXX" to gmlayer pos x,y after an alotted time. 2 minutes should be good if you don't want a blood trail. Or add an !cleanup script to add tokens named "XXX" not at pos x,y to the cleanup array and move them to pos x,y. If you prefer them to have different names, simply add an iteration to the naming function... "bleed 001, bleed 002".
William R. said: To update: your script works fine ..I was being sleep deprived and stupid working on it yesterday I got into one of my "I'm working on my campaign until its ready for saturday" 14 hour benders :P I totally forgot inside my "catch inline rolls" formatter script I have, I tossed in a random "on token change" catch one day and forgot about it being in it..and its saved on both my main and test games (the only one on my test actually)..didn't think to disable it as I was thinking "surely looking for rolls to format text would have NOTHING to do with it." In the end somewhere in this bit of code, my script it doing it. now for me to find out why its fighting with yours. ( if anyone can see an "IF" I can add or edit to this help stop it before I hamfist a way, I offer a firm handshake and a smile as thanks :) ) CONFIG.forEach(function (opts) { //SET HURT----------- if(obj.get("bar" + opts.barId + "_max") == undefined || obj.get("bar" + opts.barId + "_value") == undefined) return; var maxValue = parseInt(obj.get("bar" + opts.barId + "_max")); var curValue = parseInt(obj.get("bar" + opts.barId + "_value")); if(maxValue != NaN && curValue != NaN) { var markerName = "status_" + opts.status; if(curValue &lt;= (maxValue * opts.barRatioMax) && curValue &gt; (maxValue * opts.barRatioMin)) { obj.set(markerName, opts.whenLow); } else { obj.set(markerName, !opts.whenLow); } } }); Were you ever able to solve this? I'm having the same issue, only it's my status effect script that is interfering with mine. I don't know anything about writing API scripts, so any help would be appreciated, because I'd really love to use this script!
what lines would i have to alter to make it so i could just add different tokens not blood mybe different demons lets say
1407641687
The Aaron
Roll20 Production Team
API Scripter
You don't need to modify the code at all. Just add new rows to SplaterTable with the new type as the name.
once again you come have come through YODA
1407643122
The Aaron
Roll20 Production Team
API Scripter
;)
1407671959
The Aaron
Roll20 Production Team
API Scripter
Shaun: Private messaged you.