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

Sharing is caring. Also, a bit of help.

I would like to share some of the work I have been doing on my sheet. Here is my armor and weapons section of my Pathfinder sheet. It is a slot based system, but I noticed I left out the belt (everyone needs that girdle of strength!) but that is trivial atm. Pic: There are some nice features here. While you can not see it, each of the AC Bonus fields are part of the autocalc for the Total AC on the statistics page of my sheet. The same for the Penalty (for skill checks) and arcane spell Failure. Max Dex and the Magical Properties are just fluff text for your reference. Ok, now for the part that is where I need help. I have some adhoc buttons there for updating my @{"STAT"_misc_mod} to act as dirty sheet JS emulation. The scripts that power (2 for each row) work just fine. My question is just about security. If you notice at the top I have a message saying to only push the update button once. That is because, atm, I have no way of checking if the button has already been pushed and that the stat bonus field as already been added to @{"STAT"_misc_mod}. So right now, every time the update button is pushed, it fires the script and keeps adding the value. So my question is: how do I handle checking if a value has been previously added? Here is the contents of the "Boots" add button. (note: each update button is independent of each and powered by its own script) on('chat:message', function(msg) { if (msg.type != 'api') return; var parts = msg.content.split(' '); var command = parts.shift().substring(1); if (command == 'updatesheet') { var selectedId = parts[0]; var selectedToken = getObj('graphic', selectedId); var who = getObj('character', selectedToken.get('represents')); var bootStat = parseInt(getAttrByName(who.id, "boots_mod")); var statbonus = parseInt(getAttrByName(who.id, "boots_stat_bonus")); if(bootStat == "STR") { var statMisc = parseInt(getAttrByName(who.id, "STR_mod_temp")); var search = "STR_mod_temp"; } if(bootStat == "DEX") { var statMisc = parseInt(getAttrByName(who.id, "DEX_mod_temp")); var search = "DEX_mod_temp"; } if(bootStat == "CON") { var statMisc = parseInt(getAttrByName(who.id, "CON_mod_temp")); var search = "CON_mod_temp"; } if(bootStat == "INT") { var statMisc = parseInt(getAttrByName(who.id, "INT_mod_temp")); var search = "INT_mod_temp"; } if(bootStat == "WIS") { var statMisc = parseInt(getAttrByName(who.id, "WIS_mod_temp")); var search = "WIS_mod_temp"; } if(bootStat == "CHA") { var statMisc = parseInt(getAttrByName(who.id, "CHA_mod_temp")); var search = "CHA_mod_temp"; } var statTemp; var objMSTR = findObjs({_type: "attribute", name: search, _characterid: who.id}) if (objMSTR.length > 0) { statTemp = objMSTR[0]; } statTemp.set("current", STRmisc + statbonus); } }); Would the best way be to introduce an embodying if () statement that checks to see if var statbonus > 0. If it is, return, else, continue with script?
1403148967

Edited 1403151471
Ah, if I use statbonus > 0, the script will never fire. I thought that the sheet doesn't update the attributes panel until you deselect the field or hit enter. Is this something I should use state for, or should I just make a attr_"slot"_flag for each each item slot that the update button sets to 1 when clicked and reset sets to 0 when clicked? From what I understand about state, is I would make a on ready script fill it with some flag variables and then have the update and reset functions set the state... my limited knowledge with state is confusing me. Guess I am off to the bat cave for the old college try! Edit: I noticed I posted the wrong script, that was a previous build that wasn't working.
1403151434
Lithl
Pro
Sheet Author
API Scripter
Where is STRmisc defined?
That is an error. Like I said, that code I put up by mistake. Let me link the real one. on('chat:message', function(msg) { if (msg.type != 'api') return; var parts = msg.content.split(' '); var command = parts.shift().substring(1); if (command == 'updatesheet') { var selectedId = parts[0]; var selectedToken = getObj('graphic', selectedId); var who = getObj('character', selectedToken.get('represents')); var headStat = getAttrByName(who.id, "head_mod"); var statbonus = parseInt(getAttrByName(who.id, "head_stat_bonus")); log(headStat); if (statbonus > 0) { return; } else { if(headStat == "STR") { var statMisc = parseInt(getAttrByName(who.id, "STR_mod_misc")); } if(headStat == "DEX") { var statMisc = parseInt(getAttrByName(who.id, "DEX_mod_misc")); } if(headStat == "CON") { var statMisc = parseInt(getAttrByName(who.id, "CON_mod_misc")); } if(headStat == "INT") { var statMisc = parseInt(getAttrByName(who.id, "INT_mod_misc")); } if(headStat == "WIS") { var statMisc = parseInt(getAttrByName(who.id, "WIS_mod_misc")); } if(headStat == "CHA") { var statMisc = parseInt(getAttrByName(who.id, "CHA_mod_misc")); } log(statMisc); var statAdj; var objMSTR = findObjs({_type: "attribute", name: statMisc, _characterid: who.id}) if (objMSTR.length > 0) { statAdj = objMSTR[0]; } statAdj.set("current", statMisc + statbonus); } } }); Anyway, it isn't the code that isn't functioning. Well... this one isn't because of the if (statbonus > 0). That is my question, I need a way to set a flag. This is really two scripts, 1 for update (the one above) and another for reset. When update is called and done adding the value of statbonus to the statMisc, it needs to set a flag some where so if the flag is true, return. Then the reset script sets statbonus field back to 0 (default value) and set the flag to false so update can function again, if and when the player has stat enhancing equipment on.
1403161224
Lithl
Pro
Sheet Author
API Scripter
I recommend, instead of adding a value to *_mod_misc, that you recalculate it fully from whatever other attributes contribute to it. That is, instead of (current): updateEquipment(): equipStat_mod_misc := equipStat_mod_misc + equipStat_bonus You would do (proposed): updateEquipment(): equipStat_mod_misc := update(equipStat_mod_misc) updateFoo(): fooStat_mod_misc := update(fooStat_mod_misc) updateBar(): barStat_mod_misc := update(barStat_mod_misc) update(stat_mod_misc): stat_mod_misc := equipStat_bonus + fooStat_bonus + barStat_bonus
1403164686

Edited 1403164771
Currently there is only 1 field that handles *_mod_misc and it is a manual number field since there are a number of things that can add to it from potions to armor to feats. However, equipment is the most static form of adding to the misc modifier value, so I elected this form. Since there isn't and sheet JS, I went with buttons on the sheet for emulation of JS. There are 11 potential places that can add to any of the 6 *_mod_misc fields. Each piece of worn gear (excluding weapons) can add to the over all stat. Currently the sheet calculates the 6 stats as @{"adj_stat"} = @{"base_stat"} + @{*_mod_misc} + @{*_mod_temp}. So you are saying I should make an *_adj_misc attribute that holds the values from all the gear and then factor that into the calculation for the adj_stat? That sounds fine, but still doesn't address the issue that pushing update will just keep adding the value of the slot the button is associate with. In the picture in the OP, the slot for boots is adding +1 to the STR_misc_mod attribute, and if you keep hitting the update button for that slot, you just keep adding +1 with every push. That is what I am trying to avoid. Like I said, I didn't think the values were added to the attribute panel until the focus left that field, so I used that simple if statement, but that was a bust.
1403186661
Lithl
Pro
Sheet Author
API Scripter
I'm saying don't add to the existing attribute at all, simply recalculate it (from the full list of sources for the misc modifier) whenever the user hits Update. Instead of "M += E", use "M = A + B + C + D + E" FWIW, you could also probably use the attribute changed event instead of forcing the user to press a button. Example: on('change:attribute:current', function(obj, prev) { var miscMod; switch (obj.get('name')) { case 'head_stat_bonus': case 'boots_stat_bonus': // etc... miscMod = findObj({ type: 'attribute', characterid: prev._characterid, name: getAttrByName('head_mod', prev._characterid) + '_mod_misc' })[0]; if (!miscMod) { miscMod = createObj({ type: 'attribute', characterid: prev._characterid, name: getAttrByName('head_mod', prev._characterid) + '_mod_misc', current: 0 }); } // Two options. Either re-calculate the modifier: // miscMod.set('current', obj.get('current') + otherMod + otherOtherMod + ...); // OR, subtract the mod before the change, then add the mod after the change: // miscMod.set('current', miscMod.get('current') - prev.current + obj.get('current')); break; default: // some *other* attribute changed break; } });
Finally got my script working. I didn't realize anything declared out side the predefined callbacks were global. I used a few state decs to use as a flag for each update. Still looking into restructuring the math though. Thanks for the help brian. I wish I could understand your script, but switch function and cases are still a bit beyond me. Last time I dealt with them was in 2005 in my c++ class at ITT.
1403241315
Lithl
Pro
Sheet Author
API Scripter
In every language where I've bothered to look, switch compiles into the same thing as a series of if/else blocks. Thus, the following two code segments are identical (or close enough): switch(myvar) { case 0: case 1: doStuffLessThan2(); break; case 2: doStuff2(); break; case 3: doStuff3Plus(); case 4: doStuf3_4(); break; default: doDefaultStuff(); break; } if (myvar == 0 || myvar == 1) { doStuffLessThan2(); } else if (myvar == 2) { doStuff2(); } else if (myvar == 3 || myvar == 4) { if (myvar == 3) { doStuff3Plus(); } doStuff3_4(); } else { doDefaultStuff(); }
What does the | | mean? is that OR logic?
1403279622
Lithl
Pro
Sheet Author
API Scripter
|| is logical OR, yes.
1403298667
Lithl
Pro
Sheet Author
API Scripter
Michael P. said: I think I understand a little bit now... so this script will fire when ever the field changes an attribute. The get.obj('name') is grabbing the attribute name based on what field is being altered? The thing about head_mod is I broke each slot down into its own field, and each slot_mod is actual just a selection field to determine which of the 6 attributes _misc_mod value is going to be altered. In this example, miscMod is only going to be what ever head_mod is set to at the time. I'll go through it a piece at a time to explain: on('change:attribute:current', function(obj, prev) { Whenever the current value of an attribute changes, this event will trigger. Changes to the attribute's max won't trigger the event. obj will be the actual attribute object as it looks after the change. prev will be a regular JavaScript object containing the attribute's properties before the change. For example: prev = { _id: '-123Abc', _type: 'attribute', _characterid: '-456Def', name: 'head_stat_bonus', current: '0', max: '' } var miscMod; switch (obj.get('name')) { case 'head_stat_bonus': case 'boots_stat_bonus': // etc... I've chosen to use a switch to check the attribute name. obj.get('name') could easily be replaced with prev.name , since we know that the only difference between obj and prev is the value of current. We only want to do things when it's the current value for one of the stat_bonus fields that changes, and we want to do the same thing for all of them, so we fallthrough every case. miscMod = findObj({ type: 'attribute', characterid: prev._characterid, name: getAttrByName('head_mod', prev._characterid) + '_mod_misc' })[0]; Find the miscellaneous modifier we want to modify based on the item's stat bonus. I've just realized that I'm exclusively using head_mod here, so the first parameter to getAttrByName should instead be prev.name.substring(0, prev.name.indexOf('_'))+'_mod' . This makes sure we're getting the STR/DEX/etc. from the right item slot. Once we have ''/'STR'/'DEX'/etc. we append '_mod_misc' to get the name of the miscellaneous modifier attribute we want, and we pull that up with findObj. if (!miscMod) { miscMod = createObj({ type: 'attribute', characterid: prev._characterid, name: getAttrByName('head_mod', prev._characterid) + '_mod_misc', current: 0 }); } If we didn't find an attribute, that means it doesn't exist yet. Again as I type this I notice an error (well, two, but one of them is the same error as before, with getAttrByName): *_mod might be an empty string, so we should jump out of the script if that's the case. Before if (!miscMod) we need to add the line if (getAttrByName(prev.name.substring(0, prev.name.indexOf('_'))+'_mod', prev._characterid) == '') return; Once that's all sorted, if there is no existing *_mod_misc, we create one so that we can modify it to our heart's content. // Two options. Either re-calculate the modifier: // miscMod.set('current', obj.get('current') + otherMod + otherOtherMod + ...); // OR, subtract the mod before the change, then add the mod after the change: // miscMod.set('current', miscMod.get('current') - prev.current + obj.get('current')); Here I present two options to you. You can either recalculate the *_mod_misc entirely, with whatever other attributes contribute to it... or you can subtract the previous value of the *_stat_bonus from the *_mod_misc, and then add the current value back in. break; default: // some *other* attribute changed break; } }); The break keyword exist the switch block. The default keyword is where the switch enters if the value doesn't match any of the cases. In this example, hitting the default case means that an attribute other than the *_stat_bonus set was changed, and we don't want to do anything, so we break out of the switch.
I gotcha, and the prev.name.substring(0, prev.name.indexOf('_'))+'_mod' is basically returning all the characters before the first '_' which in each case will be the "slot" name resulting in "head_mod?" Cool, I think I understand now.
1403319706
Lithl
Pro
Sheet Author
API Scripter
Correct.
1403350933

Edited 1403351047
I am having an issue with my sheet it seems. My selection box for the stats doesn't seem to be putting any values into the attribute panel, so the script is failing. I noticed that in my gist up above, I didn't assign any values for the "slot"_mod attribute, but I did add @{head_stat} to the value for head_mod for testing, but it isn't pulling the string value of "STR" or any of the values of the options for some reason. However, it is pulling the values of all the other field from the head slot fields. Weird that it is not pulling the string. I have saved the sheet, refreshed the campaign, deleted all the attributes to repopulate them, but still not getting head_mod to equal the value of head_stat option.
1403356600
Actoba
Pro
Sheet Author
Most (if not all....i only saw the highlighted bits of the Gist above) of your select option lines are missing the equals sign after name and value. For example, this: <option name"attr_head_stat" value"STR">STR</option> Should be <option name="attr_head_stat" value="STR">STR</option>
1403358106

Edited 1403358542
OMG. I guess this is what I get for listening to my heavy metal station on pandora while coding. I start rocking out, and forget what I am doing. So that is why those where highlighted. Pfft. You know what, I need to start proofing my code mode. I am making simple mistakes that I am somehow blind to. I looked at those very values... didn't even notice I left out the = sign. MAN!
1403359113

Edited 1403360698
Ok, so I change all the = signs. They are now populating in the attribute panel. However, the script doesn't seem to fire correctly as when I select the stat from the drop down, then add to the value of stat bonus field, it isn't reflecting on statistic page. The gist of the script. I decided on the subtract then add method. I added some sendchat outputs to help me see what the hell is going on. I get nothing when I change any thing on the sheet that is an attribute value. Edit: I take that back, I just changed my strength from 19 to 20 and got my send chat. Edit 2: Got this message in the console when altering one of the case values. Clearly it is not finding something, but I can't tell if it the value or the string from the stat_mod ReferenceError: findObj is not defined at evalmachine.&lt;anonymous&gt;:51:22 at eval ( Edit 3: The error pops up only when changing the value in one of the case fields, but not when I change the selection, so something is going wrong when pulling the value, but my limited experience isn't helping me debug this. <a href="https://gist.github.com/mpiersant/9a801e11047c87a2642b" rel="nofollow">https://gist.github.com/mpiersant/9a801e11047c87a2642b</a>
1403381388
Lithl
Pro
Sheet Author
API Scripter
findObj is not a function; findObjs is. That's a mistake in the code I posted too, though, so don't feel too bad. =)
1403397544

Edited 1403398177
Hehe, I still should see with my eyes though. The work is progressing. Believe it or not, when you teach me one thing, I am really learning three or four other things from the process. Like with this I learned a lot about streamlining my code. I was looking at 20 scripts on those buttons to do what you did in one. Teach a man to fish... Edit: I am still not getting the value to change, and it is no longer posting my sendchats, but at the same time not throwing out any console errors. Not even logging to the console. Each test, I am deleting the values out of the attribute panel for safety.
1403407420

Edited 1403408603
Ok, after the API downtime, I am discovering this error: "Invalid character_id head_mod for getAttrByName()" Edit , I commented out lines 16 to 32 and then logged this: log(getAttrByName(prev.name.substring(0, prev.name.indexOf('_'))+'_mod', prev._characterid) + '_mod_misc'); The return was: "undefined_mod_misc" Edit 2 : Further debugging yields that the log for log(prev.name.substring(0, prev.name.indexOf('_'))+'_mod'); returns 'head_mod' so it is definitely looking at the right stat. In this case, head_mod should = 'STR' as per the selection. This script is not liking the method of get of the characterid for prev.name.substring(0, prev.name.indexOf('_'))+'_mod' , Edit 3 : log(prev._characterid); returns: "-JPJmOB-dMhTlkLz6psY" which is in fact the characterid in question. The question is now, why does the script not like the call for getting the value of 'head_mod' using the valid character id? Both sides of the return proper values when logged on their own, but fail when called in conjunction with each other.
I think I see the error, getAttrByName parameters should be id, name right? Shouldn't it read getAttrByName(prev._characterid, prev.name.substring(0, prev.name.indexOf('_'))+'_mod') + '_mod_misc');?
1403409694

Edited 1403409929
Success, finally the log reads "STR" when logging log(getAttrByName(prev._characterid, prev.name.substring(0, prev.name.indexOf('_'))+'_mod')); Edit: Short lived success. Updated the findObjs funtion: miscMod = findObjs({ type: 'attribute', characterid: prev._characterid, name: getAttrByName(prev._characterid, prev.name.substring(0, prev.name.indexOf('_'))+'_mod') + '_mod_misc' })[0]; when I log miscMod now, it is still undefined even I was successfully able to pull the string STR_mod_misc. This field is not an autocalc, it is just a number field. What is miscMod supposed to be? The value of the field, or the name of the field?
1403413583
Lithl
Pro
Sheet Author
API Scripter
miscMod should be the attribute object itself. miscMod.get('current') would be the value of the field.
Ok, I see that. miscMod is still undefined, thus any miscMod.get() is going to return the same error. Cannot get method of undefined.
1403428437
Lithl
Pro
Sheet Author
API Scripter
And you can confirm that the name: portion is getting the correct string for the attribute you're looking for?
1403432181

Edited 1403432309
too lazy to cut the screen down... You can see the commented section was logging STR and the un-commented one is logging STR_mod_misc <a href="http://i.imgur.com/dMw2lE0.png" rel="nofollow">http://i.imgur.com/dMw2lE0.png</a>
1403472581
Lithl
Pro
Sheet Author
API Scripter
If STR_mod_misc is the correct attribute name and miscMod is coming up undefined, that means your miscellaneous modifier doesn't exist yet, which is what the if (!miscMod) block is for, creating the nonexistent attribute so that you can fiddle with it. Fix the getAttrByName call in the createObj and uncomment that section; does it work?
Ah yes, when I was resetting on the stats by clicking them after deleting them in the attribute panel, I forgot to click on the misc and temp value field to put their defaults in there.
Working fine now after fixing the booboo in the name setting.
1403481103

Edited 1403481478
The only problem I have now is basically the math method. I went with the subtract method as it seemed the easier of the two, but each click of the spin buttons is effecting the value. Looks like I will have to do the recalculation method instead. Edit: I am going to have to make all the slot_stat_bonus field to a text box because the number box updates with each click of the spin buttons, and adds them as a new digit. I will just have to parseInt the current value when calling it in the math.
1403517466

Edited 1403523357
Okay, after lots of debugging and testing, I finally have it working. Thank you Brian for your help. Here is the working code: on('change:attribute:current', function(obj, prev) { var miscMod; switch (obj.get('name')) { case 'head_stat_bonus': case 'chest_stat_bonus': case 'shield_stat_bonus': case 'belt_stat_bonus': case 'legs_stat_bonus': case 'bracers_stat_bonus': case 'gloves_stat_bonus': case 'boots_stat_bonus': case 'amulet_stat_bonus': case 'lring_stat_bonus': case 'rring_stat_bonus': miscMod = findObjs({ type: 'attribute', characterid: prev._characterid, name: getAttrByName(prev._characterid, prev.name.substring(0, prev.name.indexOf('_'))+'_mod') + '_mod_misc' })[0]; if (getAttrByName(prev._characterid, prev.name.substring(0, prev.name.indexOf('_'))+'_mod') == '') { return; } if (!miscMod) { miscMod = createObj({ type: 'attribute', characterid: prev._characterid, name: getAttrByName(prev._characterid, prev.name.substring(0, prev.name.indexOf('_'))+'_mod') + '_mod_misc', current: 0 }); } if (parseInt(miscMod.get('current')) == 0) { miscMod.set('current', parseInt(miscMod.get('current')) + parseInt(obj.get('current'))); } else { miscMod.set('current', parseInt(miscMod.get('current')) - parseInt(prev.current) + parseInt(obj.get('current'))); } break; default: // some *other* attribute changed break; } });