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

[Request] Heal selected tokens by +30 but not over max.

My campaign includes a spell called Healing Wind which restores 30 pts (but not more than Max) on bar1 for "Allied" tokens within 10m of the caster, costing him 5 pts from bar2. What would the script look like if I wanted to have the caster type "/hwind Token1, Token2, ... TokenN"?
1380070186

Edited 1380071957
Riley D.
Roll20 Team
There's other posts that detail how to handle chat commands themselves (e.g. how to make sure someone meant to trigger your script), but the meat of it would be as follows. Note that I typed this out in about 10 minutes and didn't have time to actually run it so there may be omissions/errors, but I liberally commented it so hopefully you can take it from here :-) var arguments; // the token names, e.g. "ray, frank, george", expects the first token to be the caster var args = arguments.split(","); _.each(args, function(tokenname, i) { //if spaces between commas, remove the space. if(tokenname.substring(0,1) === " ") tokenname = tokenname.substring(1, tokenname.length); //find the token object var token = findObjects({_type: "token", name: tokenname}); if(token.length == 0) { //didn't find this token return true; } token = token[0]; //If it's the first token, it's the caster. if(i == 0) { var curval = parseInt(token.get("bar2_value"), 10); //Remove 5 pts from bar2 token.set({bar2_value: curval - 5}); return true; } //If it's not hte first token, it's a target. Get the current health (assumed to be bar1) var curval = parseInt(token.get("bar1_value"), 10); //Add 30 curval += 30; //if it's more than the max of bar_1, set it to the max. if(curval > parseInt(token.get("bar1_max"), 10)) { curval = parseInt(token.get("bar1_max"), 10); } //Save the final value. token.set({bar1_value: curval}); });
1380074686

Edited 1380075059
Lithl
Pro
Sheet Author
API Scripter
Riley, unless something changed recently, "token" is a subtype of the "graphic" _type. And "findObjects" isn't an available function (use findObjs). Ryan, API commands must begin with an exclamation mark (eg, !hwind). Additionally, if we assume that the GM hasn't assigned a controller to the enemy tokens, then we can perform the entire script without listing all of the targets. var healing_wind = healing_wind || {}; on('chat:message', function(msg) { if(msg.type != 'api') return; // We're only deal with API commands var parts = msg.content.toLowerCase().split(' '); var command = parts.shift().substring(1); var who = getObj('player', msg.playerid).get('displayname'); if(command != 'hwind') return; // We're only dealing with the !hwind command var page = getObj('page', Campaign().get('playerpageid')); var scale = page.get('scale_number'); var dist = Math.round(10 / scale * 70); // 10m / meters per square * 70 pixels per square; var caster; var allTokens = findObjs({ _type: 'graphic', _pageid: page.id, subtype: 'token' }); if(msg.selected && msg.selected.length == 1 && msg.selected[0]._type == 'graphic') // Caster token is selected { caster = getObj('graphic', msg.selected[0]._id); if(caster.get('subtype') != 'token') // Selected obj isn't a token { sendChat('SYSTEM', '/w '+who+' Selected object isn't a token.'); return; } } else if(msg.who != who) // Player is posting command as their character; find corresponding token to cast from { var character = findObjs({ _type: 'character', name: who })[0]; caster = findObjs({ _type: 'graphic', represents: character.id })[0]; if(!caster) // No token exists for the character the player is speaking as { sendChat('SYSTEM', '/w '+who+' No token exists for '+msg.who+'.'); return; } } else // Try to find a character controlled by the player, cast from it { var character = findObjs({ _type: 'character', controlledby: msg.playerid })[0]; // Assume: One character if(!character) // Player is not controlling any character { sendChat('SYSTEM', '/w '+who+' You do not control any characters.'); return; } caster = findObjs({ _type: 'graphic', _pageid: page.id, represents: character.id })[0]; if(!caster) { sendChat('SYSTEM', '/w '+who+' No token exists for '+character.get('name')+'.'); return; } } if(+caster.get('bar2_value') < 5) // Caster doesn't have enough power to cast { sendChat('SYSTEM', '/w '+who+' You do not have enough power to cast that spell.'); return; } var targetList = healing_wind.findToks(allTokens, caster, dist); // List of tokens hit by the spell _.each(targetList, function(tok) { var curHP = +tok.get('bar1_value'); var maxHP = +tok.get('bar1_max'); curHP = Math.min(curHP + 30, maxHP); tok.set('bar1_value', ''+curHP); }); var curPW = +caster.get('bar2_value'); caster.set('bar2_value', ''+(curPW - 5)); }); healing_wind.findToks = function(allTokens, caster, dist) { targetList = []; _.each(allTokens, function(tok) { if(tok.id == caster.id) return; // Caster is not healed if(tok.get('controlledby') == '') return; // Assumption: Allied tokens are controlled, enemies aren't var distX = Math.abs(tok.get('left') - caster.get('left')); var distY = Math.abs(tok.get('top') - caster.get('top')); if(distX > dist || distY > dist) return; // Assuming range for spell is square; use pythag. if circular // Gets a little complicated if diagonals = 1.5 squares targetList.push(tok); }); return targetList; }; This script is untested, but assuming there's no bugs in it (and the assumptions in the comments are correct), it's complete. To use this script, simply type "!hwind". If you have a single token selected, that token will be used as the caster. If you have no token selected but you're speaking as a character (and there is a token on the current page representing that character), that character's token will be used as the caster. If you're speaking as yourself, it will try to pick the first character you control, and find a token representing that character, which will become the casting token. All controlled tokens within range (10m -- however many squares that is based on the page settings and assuming the distance measurement is in meters) will be healed, and the caster will lose 5 pts from bar2 (if the caster doesn't have 5 pts, the spell fizzles). The distance calculation is assuming a square area (eg, D&D 4e). If a circular area is required, you need to modify the findToks function to use the Pythagorean Theorem. If diagonal distance is 1.5 squares (eg, D&D 3.5), the distance calculation is a bit more complicated, and I'd have to think about it instead of writing off the top of my head.
Indeed, obviously I meant _subtype and findObjs. As I said, it was typed out on my phone in 10 minutes from memory. Glad that you were able to put together something more comprehensive.
Riley and Brian, this helps a lot. My diagonals are 1.5 but if I can't work out the formula that's ok. I'm the GM, Kharak has a little accident in the Arcane Science lab and it mutates the shape of his AoE. :) It'll be installed and going through it's first use tomorrow (Fri. Sept. 27, 2013). /excited ... er I should say !excited shouldn't I? ;)
Using Brian's example I'd like to note there's an unescaped apostrophe in a single-quote string @ line 20. That being said I beta'd the script by selecting a controlled token as the caster, had three other controlled tokens as well as an uncontrolled one inside AoE and a controlled token outside AoE. EP cost for the caster, and the fizzle incase of OOM work perfectly. HP +30, stopping at Max, is also flawless. The problem is that if there is a token that has its controller "Determined by Character settings" aka: token represents someone from the character journal, the script bypasses that token. Is it possible to give, let's say, the blue status marker to Allied Tokens then qualify on that rather than controlled vs. uncontrolled? (Marked tokens too far away still being skipped of course.)
1380325293

Edited 1380325410
Lithl
Pro
Sheet Author
API Scripter
Sorry, wasn't thinking about that case while writing the script for some reason. Try this: healing_wind.findToks = function(allTokens, caster, dist) { targetList = []; _.each(allTokens, function(tok) { if(tok.id == caster.id) return; if(tok.get('represents') != '') { var character = getObj('character', tok.get('represents')); if(character.get('controlledby') == '') return; // Ignore tokens representing uncontrolled characters } else if(tok.get('controlledby') == '') return; // Ignore uncontrolled tokens if they don't rep. a character var distX = Math.abs(tok.get('left') - caster.get('left')); var distY = Math.abs(tok.get('top') - caster.get('top')); if(distX > dist || distY > dist) return; targetList.push(tok); }); return targetList; } Edit: You could use markers to decide allied vs. non (use tok.get('status_blue') or similar)
1380326432

Edited 1380330239
Epic Shiny! Hero Pts for Brian. Seriously, you have shaved about 90 seconds off the spells use here. TYVM. Only thing remaining is to try and circularize the AoE based on 1.5 movement... Am I thinking along the right line with this addition: var distX.... var distY.... var Hypot = Math.sqrt( Math.pow(distX, 2) + Math.pow(distY, 2) ); if (Hypot > dist) return; targetList.....