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] Add number to an attribute based on pattern of token movement (?)

Hi there, so this request is a little weird and I'm not super sure it's possible but, here goes: I need a script that will add a +(whatever) bonus to a character attribute based on the movements made by the token currently representing him/her. The idea is that if the token is moved more than 2 spaces in one direction, the bonus is automatically added--I think that part is (fairly) simple. The tricky part (and the bit I'm not sure about) is that I need the bonus to clear when that token is moved in a different direction or ends its turn.  The reason for this script is to help me calculate jump distance--I already have a little macro that runs the equation to calculate it, but with all the other junk I need to keep an eye on while DMing, it would really just be a lot easier if I had number I could divide by that updated by itself without me having to check previous movements and change the number every time a character needs to jump. I realize that sounds a bit lazy, but when there is a lot going on in an encounter, all of the little things together can become a bit overwhelming and really slow things down. Anywho, I realize this might not even be totally do-able, but I'd be really grateful to anyone willing to try and it cobble together for me nonetheless. Thanks! 
1494217287
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Seems doable to me. I just don't have the time to work on it (I've already got a backlog of scripts that I've promised people i would finish/fix)
1494243147
The Aaron
Pro
API Scripter
That does sound like something fun to do...  and useful.  Really just needs to calculate the length of their final move segment.  Realistically, you might want to take page scale into account.  10ft squares means you only need to move "1 square" to get your 10ft.  What are you using for diagonal movement rules?
1494247178
The Aaron
Pro
API Scripter
Give this a shot... on('ready',function(){     "use strict";     const requiredMoveDistance = 10, /* assumes ft...*/           attributeName = 'jumpBonus',           valueSuccess = '2',           valueFailure = '1';     let getPageScale = (function(){         let cache = {};         const pageScale = function( pageid ){             if(!_.has(cache,pageid)){                 let page = getObj('page',pageid);                 if(page){                     cache[pageid]={                         scale: page.get('scale_number'),                         units: page.get('scale_units'),                         diagonal: page.get('diagonaltype')                     };                 } else {                     cache[pageid]={                         scale: 5,                         units: 'ft',                         diagonal: 'pythagorean'                     };                 }             }             return cache[pageid];         };         on('change:page',(p)=>pageScale(p.id));         on('destroy:page',(p)=>delete cache[p.id]);         return pageScale;     }()),     getLastMoveDistance = function(obj){         let x = obj.get('left'),             y = obj.get('top'),             p = _.chain(obj.get('lastmove').split(/,/))                 .map((n)=>parseInt(n,10))                 .last(2)                 .value(),             x1 = p[0]||x,             y1 = p[1]||y,             dist = Math.sqrt( Math.pow(Math.abs(x-x1),2) + Math.pow(Math.abs(y-y1),2)),             scale = getPageScale(obj.get('pageid'))             ;         // this should really take diagonaltype and units into account...         return (dist/70)*scale.scale;     };     on('change:graphic',function(obj,prev){         if(Campaign().get('initiativepage') !== false && obj.get('represents')){             let toRaw=Campaign().get('turnorder'),                 to = JSON.parse(toRaw)||[];             if( to[0] && obj.id === to[0].id){                 let lastMoveDist=getLastMoveDistance(obj),                     attr = findObjs({                         type: 'attribute',                         name: attributeName,                         characterid: obj.get('represents')                     })[0];                 if(!attr){                     attr=createObj('attribute',{                         characterid: obj.get('represents'),                         name: attributeName,                         current: valueFailure                     });                 }                 if(lastMoveDist >= requiredMoveDistance){                     attr.set('current',valueSuccess);                 } else {                     attr.set('current',valueFailure);                 }             }         }     });     on('change:campaign:initiativepage',function(c,prev){             _.each(findObjs({                 type: 'attribute',                 name: attributeName             }),(a)=>a.set('current',valueFailure));     });     on('change:campaign:turnorder',function(c,prev){         let toRaw=c.get('turnorder'),             to = JSON.parse(toRaw)||[],             t = getObj('graphic',to[0] && to[0].id),             cid = t && t.get('represents');             _.each(findObjs({                 type: 'attribute',                 name: attributeName             }),(a)=>{                 if(a.get('characterid') !== cid ){                     a.set('current',valueFailure);                 }             });     }); }); Attribute name is 'jumpBonus' and will have a value of either 1 or 2 depending on the last move distance of the token.  It only updates when the turnorder is up, and only for the token that is top of the initiative order and only if they represent a character.  When turnorder is closed, it resets everyone to 1.  When turnorder changes, it resets everyone to 1.  If the last segment of the last move is 10 or more, it will set the attribute to 2. You can adjust the attribute name, threshold and values at the top of the script.
1494281203

Edited 1494281783
Hey thanks! This is awesome and super helpful. As for what I'm using for diagonal movement, I just go the simpler route of counting them the same as moving in any other direction, so this works perfectly as far as my uses are concerned. Thanks again for writing this up for me, you're a magic man. haha
Actually, I may have spoke to soon about it 'working for me,' because it is isn't actually working. haha It puts the 1 in the 'jumpBonus' attribute but I can't get it to update the number to 2 no matter which direction/how many spaces I've moved it. Also, my current map settings 1sq = 1sq because I leave the calculation of actual feet when its needed to my equation macros, its just easier for me that way. Anyway, let me know if you need any more info to help you pinpoint what might be wrong with it. Thanks :D
1494283905
The Aaron
Pro
API Scripter
Do you have the turn order open and the token you are moving as the first turn?  Also, what are your page settings? Screenshot would be great. 
Yes the turn order is open and the token at the top of the initiative is the one I'm moving around. As for my page settings: There you go. Thanks again.
1494292403
The Aaron
Pro
API Scripter
Ok. I'll modify it to work with units rather than ft, and counting squares rather than distance. Actually, if you change the requiredMoveDistance=10 to a 2, it will likely work. 
Thanks a lot. And after I changed the required distance to 2, it works a bit better now, but the number seems to update to 2 when I move only one square, and it doesn't reset back to 1 when I change direction. 
1494325956
The Aaron
Pro
API Scripter
No worries, I'll duplicate your settings and tune it up for your situation as soon as I get the coffee brewing... stabdvy! 
1494328776
The Aaron
Pro
API Scripter
Ok, this should work for you: on('ready',function(){     "use strict";     const requiredMoveDistance = 2,           attributeName = 'jumpBonus',           valueSuccess = '2',           valueFailure = '1';     let getPageScale = (function(){         let cache = {};         const pageScale = function( pageid ){             if(!_.has(cache,pageid)){                 let page = getObj('page',pageid);                 if(page){                     cache[pageid]={                         scale: page.get('scale_number'),                         units: page.get('scale_units'),                         diagonal: page.get('diagonaltype')                     };                 } else {                     cache[pageid]={                         scale: 5,                         units: 'ft',                         diagonal: 'pythagorean'                     };                 }             }             return cache[pageid];         };         on('change:page',(p)=>(delete cache[p.id],pageScale(p.id)));         on('destroy:page',(p)=>delete cache[p.id]);         return pageScale;     }()),     getLastMoveDistance = function(obj){         let x = obj.get('left'),             y = obj.get('top'),             p = _.chain(obj.get('lastmove').split(/,/))                 .map((n)=>parseInt(n,10))                 .last(2)                 .value(),             x1 = p[0]||x,             y1 = p[1]||y,             scale = getPageScale(obj.get('pageid')),             dist             ;         switch(scale.diagonal){             case 'foure':                 dist = Math.max(Math.abs(x-x1), Math.abs(y-y1));                 dist = (dist/70)*scale.scale;                 break;             case 'manhattan':                 dist = Math.abs(x-x1) + Math.abs(y-y1);                 dist = (dist/70)*scale.scale;                 break;             case 'threefive':                 // best guess... no way to REALLY do this right easily                 let xd = Math.abs(x-x1)/70,                     yd = Math.abs(y-y1)/70;                 dist = Math.max(xd,yd)*scale.scale;                 if(Math.floor(xd) && Math.floor(yd)){                     dist = Math.floor(Math.ceil(dist/5)*1.5)*5;                 }                 break;             default:             case 'pythagorean':                 dist = Math.sqrt( Math.pow(Math.abs(x-x1),2) + Math.pow(Math.abs(y-y1),2));                 dist = (dist/70)*scale.scale;                 break;         }         return dist;     };     on('change:graphic',function(obj,prev){         if(Campaign().get('initiativepage') !== false && obj.get('represents')){             let toRaw=Campaign().get('turnorder'),                 to = JSON.parse(toRaw)||[];             if( to[0] && obj.id === to[0].id){                 let lastMoveDist=getLastMoveDistance(obj),                     attr = findObjs({                         type: 'attribute',                         name: attributeName,                         characterid: obj.get('represents')                     })[0];                 if(!attr){                     attr=createObj('attribute',{                         characterid: obj.get('represents'),                         name: attributeName,                         current: valueFailure                     });                 }                 if(lastMoveDist >= requiredMoveDistance){                     attr.set('current',valueSuccess);                 } else {                     attr.set('current',valueFailure);                 }             }         }     });     on('change:campaign:initiativepage',function(c,prev){             _.each(findObjs({                 type: 'attribute',                 name: attributeName             }),(a)=>a.set('current',valueFailure));     });     on('change:campaign:turnorder',function(c,prev){         let toRaw=c.get('turnorder'),             to = JSON.parse(toRaw)||[],             t = getObj('graphic',to[0] && to[0].id),             cid = t && t.get('represents');             _.each(findObjs({                 type: 'attribute',                 name: attributeName             }),(a)=>{                 if(a.get('characterid') !== cid ){                     a.set('current',valueFailure);                 }             });     }); }); It will now respect the page settings regarding diagonal movement type (though 3.5 setting is a bit weird, but I don't figure you use that so didn't spend too much time on it. =D).
1494363426

Edited 1494363658
PaprikaCC
API Scripter
For the 3.5 movement, switch out lines 64-67 with this: dist = (Math.max(xd, yd) + Math.floor(Math.min(xd, yd) / 2)) * scale.scale;
1494363828
The Aaron
Pro
API Scripter
Nice!
This so great. Thanks for putting this together for me, it works like a charm. You know, it's actually kinda crazy just how helpful something like this. Now that I think about it, there a quite a few rules in D&D that are dependent on whether or not a character has moved 'x' squares before making 'y' action, so this'll be even more useful than what I originally intended it for. haha thanks again, you are still a magic man :P
1494366686
The Aaron
Pro
API Scripter
neat!  You should list those things, might be able to expand this a bit..
1494372797

Edited 1494373347
Well the one I immediately think of off the top my head are the rules for charging in most editions of D&D, which require the character moves a certain distance towards their target to gain the charge bonus (which varies across the different editions)--but is usually something along the lines of a small +1 or +2 bonus to hit, and sometimes a similar bonus to damage.  There are a ton of traps I can think of that become more effective based on how many squares a character moved before stepping on them. For example there's stuff like ice & slippery moss traps that will slide a character a distance equal to or half the number of spaces they had moved in one direction before triggering said traps. There are also items like steel ball bearings that will knock a target prone if they had already moved so many squares before hitting  the space they've been dropped in. These would be neat to see somehow done in the API. I have played campaigns with unique throw distance rules for certain weapons--increasing both their range, bonus to hit and sometimes the damage done based on whether or not a character moved so many squares in one direction prior to the attack. haha there's a lot of stuff like this in D&D, or really just games in general that use Newton's first law as a game mechanic--that idea, just in general would make for a really cool movement script. I would use the f*ck out of something like that. But idk man, there is a lot more D&D related stuff for this sort of thing I can think of, but it sorta just depends on exactly how much you want to 'expand' the idea. lol  If nothing else it would be nice if the script also detected more than just the prior move to calculate the bonus, so that it would still update if a character moved one square, then another in the same direction--but I also realize that would take a bit more work though and don't want to burden you with more to do when I'm already pretty happy with this.