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

interaction rolltemplate/API

1586040935
Marco M.
KS Backer
Sheet Author
API Scripter
Compendium Curator
As far as I know, the roll button in the character sheet cannot trigger/modify event in the character sheet. I want to get the result of a roll in the sheet and use it to modify some attributes in my sheet and do something like this: roll=1d4  -> attr_wp=attr_wp-roll I managed to find a work around by having a roll button AND a second box for projection in the sheet where you subtract the value given by the roll. It's functional and anyone can use it, but it requires a 2 step process 1) use the roll button and read the result 2) type the result in the appropriate box Now, is it possible to store the result of a roll button as a variable and use it to modify the sheet? Can I do it directly from the sheet or I need to parse into the the chat?
1586047993
Marco M.
KS Backer
Sheet Author
API Scripter
Compendium Curator
Solved. I modified a script by Scott C. on('chat:message',(msg)=>{ log(msg); let msgCopy = _.clone(msg), rollResult,attributeToSet,characterID; const patternMatch = /{{characterid=(.+?)}} {{attribute=(.+?)}} {{roll=\$\[\[(\d+)\]\]}}/,//this will match the rolltemplate text of {{characterid=-ty8U95t}} {{attribute=repeating_sectionName_-J4tlkjasdf90TH-78sR_receptacle}} {{roll=[[xdy]]}} rollTemplateRegex = /bond/;//What roll templates we want to act on if(msg.rolltemplate && rollTemplateRegex.test(msg.rolltemplate)){ msg.content.replace(patternMatch,(match,cID,attributeToChange,rollIndex)=>{ rollResult = msg.inlinerolls[rollIndex].results.total; characterID = cID; attributeToSet = attributeToChange; let attributeObj = findObjs({ _type:'attribute', _characterid:characterID, name:attributeToSet }); log(`character ID: ${characterID}`); log(`toSet: ${attributeToSet}`); log(`result: ${rollResult}`); log(attributeObj); if(attributeObj){ attributeObj = attributeObj[0]; let res= attributeObj.get("current")-rollResult; attributeObj.set({current:res}); } }); } }); Now the question is different. The API modifies the attribute projection. However, the projection on("change:projection") is supposed to trigger other change in the character sheet, but when the attribute is changed through the API the on change doesn't trigger. Is there a common problem? Is there a workaround?
1586062283
GiGs
Pro
Sheet Author
API Scripter
Changes made by API scripts dont trigger sheet workers, by design. But there's a setWithWorker API command you can use instead of set  , which does trigger sheet workers.
1586076900
Marco M.
KS Backer
Sheet Author
API Scripter
Compendium Curator
ooh thanks that will help a lot :)
1586077798
GiGs
Pro
Sheet Author
API Scripter
You're welcome. I just noticed this line: let res= attributeObj.get("current")-rollResult; You have to be careful with things like this, because in roll20, all attribute values are stored as strings. So this might not work as expected. Especially if the attribute at any point contains values not interpretable as a number. It's a good idea to do some error checking there and coerce it into a number.
1586079039
Marco M.
KS Backer
Sheet Author
API Scripter
Compendium Curator
I had parseInt to be sure let finalres= parseInt(attributeObj.get("current"),10)-rollResult; log(`result: ${finalres}`); attributeObj.setWithWorker({current:finalres}); However, now when I roll it either set the current to 0 or it says it is undefined and doesn't do anything. Weird thing is the log for result is correct. And it was working fine when I was using set instead of setWithWorker
1586079387
GiGs
Pro
Sheet Author
API Scripter
The log shows it whether it's string or number, so its not the best way to check if issues are happening because its a string. Personally I'd break that line up like so const current = parseInt(attributeObj.get("current")); // insert whatever checking is necessary here, and potentially terminate the function if invalid const finalres = current - rollResult; That way you can error check on current value and check it's correct before the arithmetic happens, and check it's value and data type is what it should be. Also you dont need the ,10 in parseInt, btw.
1586082596
Marco M.
KS Backer
Sheet Author
API Scripter
Compendium Curator
Ok now for some reason the code completely stopped to find the object with findObjs. To test I used an attribute I know to exist attr_version: let attributeObj = findObjs({_type: 'attribute',_characterid: characterID,name: 'version'},{ caseInsensitive: true })[0]; log(`character ID: ${characterID}`); log(`toSet: ${attributeToSet}`); log(`result: ${rollResult}`); log(attributeObj); The regular expression part seems to work fine, but the log still cannot find anything: {"content":" {{characterid=-M48fgB3sG9ELrSPwLwp}} {{attribute=bond_projection_2}} {{roll=$[[0]]}} {{name=Projection on Name}} {{maxwp=$[[1]]}} {{max_score=$[[2]]}}","inlinerolls":[{"expression":"1d4","results":{"resultType":"sum","rolls":[{"dice":1,"results":[{"v":1}],"rollid":"-M48s39ySBAlF01_3Uy_","sides":4,"type":"R"}],"total":1,"type":"V"},"rollid":"-M48s39ySBAlF01_3Uy_","signature":"2e6ff72125ad9fdfff5ce5e028ec3b2817d3f16f563aa9013f5bf94c019943e4b3412b0761d2171d436a44d9e3a69033cc9909a67151109a7d8650469314b0b5","tdseed":2660323289118051000},{"expression":"10","results":{"resultType":"M","rolls":[{"expr":10,"type":"M"}],"total":10,"type":"V"},"signature":false},{"expression":"10","results":{"resultType":"M","rolls":[{"expr":10,"type":"M"}],"total":10,"type":"V"},"signature":false}],"playerid":"-LQrzlnhmmVT5GFwzfLO","rolltemplate":"bond","type":"general","who":"Handler (GM)"} "character ID: -M48fgB3sG9ELrSPwLwp" "toSet: bond_projection_2" "result: 1" undefined I add the caseInsensitive part following the suggestion by The Aaron, but it doesn't solve the problem
1586087051
GiGs
Pro
Sheet Author
API Scripter
if version is just set with a default value, and hasnt been explicitly modified by code or manually, the attribute might not technically exist. If you just want to get the value in an API script, and don't need to manipulate it, you might be better off using getAttrByName  - it works for attributes that only exist in default values.
1586092485
Marco M.
KS Backer
Sheet Author
API Scripter
Compendium Curator
Ok you were right about initialization, so now I modified the code as such log(finalres); let object = findObjs({characterid:characterID,name:attributeToSet},{ caseInsensitive: true })[0]; if (object === undefined){ object=createObj("attribute", { name: attributeToSet, characterid: characterID }); } log(object); object.setWithWorker({current: finalres}); //object.set({current: finalres}); log(object); Now, what the sheetworker is supposed to updated the value in projection to the rate value, and IF the projection value is lower than the score value, it also modifies wp by the same amount on("change:bond_projection_1 change:bond_projection_2 change:bond_projection_3 change:bond_projection_4 change:bond_projection_5 change:bond_projection_6 change:bond_projection_7 change:bond_projection_8 change:bond_projection_9 change:bond_projection_10 ", function(v){ let proj=v.sourceAttribute; let tmp=proj.split("_"); let num=tmp[2]; let bond="bond_score_"; bond=bond.concat(num); getAttrs([bond,proj,"wp"], function(values){ let bdiff=0; let update={}; bdiff=Math.round(values[bond])-Math.round(values[proj]); if (bdiff>0){ update[bond]=values[bond]-bdiff; setAttrs(update); setAttrs({ wp: (values.wp-bdiff) }); } else{ update[bond]=values[proj]; setAttrs(update); } }); }); on("ready change:bond_score_1 change:bond_score_2 change:bond_score_3 change:bond_score_4 change:bond_score_5 change:bond_score_6 change:bond_score_7 change:bond_score_8 change:bond_score_9 change:bond_score_10 ", function(v){ let bond=v.sourceAttribute; let value_n=parseInt(v.newValue,10) || 0; if (value_n<0){ value_n = 0; setAttrs({bond: 0}); } let tmp=bond.split("_"); let num=tmp[2]; let proj="bond_projection_"; proj=proj.concat(num); let bproj={}; bproj[proj]=value_n; getAttrs([proj], function(v){ if (v[proj] !== value_n){ setAttrs(bproj); } }) ; }); The worksheet works as a charm when I modify the projection box manually. However, when I set it up using the API script, the code set wp, bond, and projection to 0. The weird thing is that if I use set instead of setWithWorker, the output in projection looks ok. Also, looking at the log with setWithWorker, the value inside the attribute looks ok: {"content":" {{characterid=-M48fgB3sG9ELrSPwLwp}} {{attribute=bond_projection_1}} {{roll=$[[0]]}} {{name=Projection on Name}} {{maxwp=$[[1]]}} {{max_score=$[[2]]}}","inlinerolls":[{"expression":"1d4","results":{"resultType":"sum","rolls":[{"dice":1,"results":[{"v":3}],"rollid":"-M49Pqg_lqDwU4ZzcpCg","sides":4,"type":"R"}],"total":3,"type":"V"},"rollid":"-M49Pqg_lqDwU4ZzcpCg","signature":"535e1467938a9344dc6de0a46958b84ff2028dd58e592b985738598f0d74c5d10c4733385630c32121d01224b6beaf431609162addbed4806500601fa791f263","tdseed":101287523266040340},{"expression":"0","results":{"resultType":"M","rolls":[{"expr":0,"type":"M"}],"total":0,"type":"V"},"signature":false},{"expression":"10","results":{"resultType":"M","rolls":[{"expr":10,"type":"M"}],"total":10,"type":"V"},"signature":false}],"playerid":"-LQrzlnhmmVT5GFwzfLO","rolltemplate":"bond","type":"general","who":"Handler (GM)"} "character ID: -M48fgB3sG9ELrSPwLwp" "toSet: bond_projection_1" "result: 3" 10 // original value 7 // original value - result //value before updating {"name":"bond_projection_1","current":10,"max":"","_id":"-M49LPYr9ezfc4-_rrnS","_type":"attribute","_characterid":"-M48fgB3sG9ELrSPwLwp"} //value after updating {"name":"bond_projection_1","current":7,"max":"","_id":"-M49LPYr9ezfc4-_rrnS","_type":"attribute","_characterid":"-M48fgB3sG9ELrSPwLwp"} I have no clue where the bug is or if it's a problem with the setWithWorker function
1586095267
GiGs
Pro
Sheet Author
API Scripter
Oh yes, there's a bug with the event.newValue  and event.previousValue when being set through the API. You have to use getAttrs to get the value. sourceAttribute still works so you can feed that into the getAttrs. This bug has been around a LONG time, it's irritating they havent fixed it yet. Another thing: you have two setAttrs statements in your code. It's best to try to limit your workers to do getAttrs and setAttrs each only once. Sometimes it takes a bit of careful thinking for the getAttrs, but the setAttrs part is easy. Just declare an attribute to hold the value: const updates = {}; then wherever you have setAttrs, replace with  /* setAttrs({bond: 0}); */ updates.bond = 0; Then at the end of your worker, if(updates) setAttrs(updates); There are two reasons to do this: First, efficiency. getAttrs and setAttrs are asynchronous functions, and need to contact roll20's backend database whenever these functions are called. That is slow, so you want to minimise it as much as possible. Secondly, because they are asynchronous, they dont necessarily complete in the order listed in your function. This can sometimes cause errors, with attributes having the wrong values if they depend on each other. And if the function crashes prematurely, one or more may complete while others dont, exacerbating this. 
1586116460
Marco M.
KS Backer
Sheet Author
API Scripter
Compendium Curator
Mmmh... I understand what you said about the asynchronous value to set attribute (I just did it the other way to see 8f it worked and if I could reproduce unexpected results). But I'm not sure about the first part, about how the workaround works (or better where I'm supposed to use setAttr. Sorry to bother and thanks for the help :)
1586126321
Marco M.
KS Backer
Sheet Author
API Scripter
Compendium Curator
Ok sorry, I was tired... Thank you so much it fixed the issue :) If I change the code to this, everything works fine on("ready change:bond_score_1 change:bond_score_2 change:bond_score_3 change:bond_score_4 change:bond_score_5 change:bond_score_6 change:bond_score_7 change:bond_score_8 change:bond_score_9 change:bond_score_10 ", function(v){ let bond=v.sourceAttribute; let value_n=[]; getAttrs([bond],function(value1){ if (value1[bond]<0){ value_n = 0; setAttrs({bond: 0}); }else{ value_n=parseInt(value1[bond])||0; } let tmp=bond.split("_"); let num=tmp[2]; let proj="bond_projection_"; proj=proj.concat(num); let bproj={}; bproj[proj]=value_n; getAttrs([proj], function(value2){ if (value2[proj] !== value_n){ setAttrs(bproj); } }) ; }); });