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

sendChat(speakingAs, chatText, callback) seems to be running asynchronously.

I'm trying to use sendChat to parse a dice roll and store it as a damage value, which will then be compared to the character's SDC & HP (Palladium). The RandomInteger(20) function works for the attack roll, but since the dice expression isn't nearly as simple for damage, I have been trying to use sendChat. Unfortunately, the sendChat callback is being executed AFTER the code that appends the damage to the powercard. var PS = getAttrByName(userToken.get("represents"), "PS"); if(strengthDamage == "Y") { strDamage = PS - 15; if(strDamage < 0) {strDamage = 0;} } log(strDamage); var maDamage = getAttrByName(userToken.get("represents"), "DAM") var dmgRollResult; var damage = 0; sendChat("API", "/roll " + atk_damage + " + " + strDamage + " + " + maDamage, function(ops){ log(ops[0]); dmgRollResult = JSON.parse(ops[0].content); log(dmgRollResult.total); damage = dmgRollResult.total; }); var ignoreAR = parseInt(arIgnore); var targetPersonalAR = getAttrByName(targetToken.get("represents"), "PERSONAL_AR"); var targetPersonalSDC = getAttrByName(targetToken.get("represents"), "PERSONAL_SDC"); var targetRoboticAR = getAttrByName(targetToken.get("represents"), "ROBOTIC_AR"); var targetRoboticSDC = getAttrByName(targetToken.get("represents"), "ROBOTIC_SDC"); var targetArmorAR = getAttrByName(targetToken.get("represents"), "ARMOR_AR"); var targetArmorSDC = getAttrByName(targetToken.get("represents"), "ARMOR_SDC"); var targetBarrierAR = getAttrByName(targetToken.get("represents"),"BARRIER_AR"); var targetBarrierSDC = getAttrByName(targetToken.get("represents"),"BARRIER_SDC"); var targetHP = getAttrByName(targetToken.get("represents"), "HP") var bypassBarrier = true; var bypassArmor = true; var bypassRobotic = true; var bypassPersonal = true; var diceRoll = randomInteger(20); var output = "!power --leftsub|" + userToken.get("name") + " --rightsub|" + targetToken.get("name"); var totalRoll = diceRoll + atk_bonus + bonus; if(diceRoll == 1) { output = output + " --Die Roll|Natural 1! Automatic Failure!"; } else if(diceRoll == 20) { output = output + " --Die Roll|Natural 20! Automatic Critical!"; damage = damage * 2; } else { output = output + " --Die Roll|" + diceRoll.toString(); } var totalDamage = damage; output = output + " --Total Damage|" + totalDamage.toString(); log(output); my totalDamage variable is still at 0 when it is logged, and after log(output) fires as the last line, the log(dmgRollResult.total) line fires. I've read Brian's solution but since that was about a year ago I was hoping there was an answer that would be limited to this sendChat command rather than restructuring the entire function.
1410811772
The Aaron
Roll20 Production Team
API Scripter
sendChat() is asynchronous. You can just move the rest of your operations into the callback function: var PS = getAttrByName(userToken.get("represents"), "PS"); if (strengthDamage == "Y") { strDamage = PS - 15; if (strDamage < 0) { strDamage = 0; } } log(strDamage); var maDamage = getAttrByName(userToken.get("represents"), "DAM"); var dmgRollResult; var damage = 0; sendChat("API", "/roll " + atk_damage + " + " + strDamage + " + " + maDamage, function(ops) { log(ops[0]); dmgRollResult = JSON.parse(ops[0].content); log(dmgRollResult.total); damage = dmgRollResult.total; var ignoreAR = parseInt(arIgnore); var targetPersonalAR = getAttrByName(targetToken.get("represents"), "PERSONAL_AR"); var targetPersonalSDC = getAttrByName(targetToken.get("represents"), "PERSONAL_SDC"); var targetRoboticAR = getAttrByName(targetToken.get("represents"), "ROBOTIC_AR"); var targetRoboticSDC = getAttrByName(targetToken.get("represents"), "ROBOTIC_SDC"); var targetArmorAR = getAttrByName(targetToken.get("represents"), "ARMOR_AR"); var targetArmorSDC = getAttrByName(targetToken.get("represents"), "ARMOR_SDC"); var targetBarrierAR = getAttrByName(targetToken.get("represents"), "BARRIER_AR"); var targetBarrierSDC = getAttrByName(targetToken.get("represents"), "BARRIER_SDC"); var targetHP = getAttrByName(targetToken.get("represents"), "HP"); var bypassBarrier = true; var bypassArmor = true; var bypassRobotic = true; var bypassPersonal = true; var diceRoll = randomInteger(20); var output = "!power --leftsub|" + userToken.get("name") + " --rightsub|" + targetToken.get("name"); var totalRoll = diceRoll + atk_bonus + bonus; if (diceRoll == 1) { output = output + " --Die Roll|Natural 1! Automatic Failure!"; } else if (diceRoll == 20) { output = output + " --Die Roll|Natural 20! Automatic Critical!"; damage = damage * 2; } else { output = output + " --Die Roll|" + diceRoll.toString(); } var totalDamage = damage; output = output + " --Total Damage|" + totalDamage.toString(); log(output); });
Thanks! I'll try it. On a side note, it makes me feel all gross dropping "main thread" code into an asynchronous callback function.
1410812971
The Aaron
Roll20 Production Team
API Scripter
Yeah, I had the same feelings initially. One thing you can do is put all your processing in another function, then chain call all your sendChat()s up front, with the last one calling the main function. But honestly, it's not as big a deal as it feels like coming from a traditional procedural language like C++. =D
Just finished implementing all the changes... feel a double dose of dirty... I had 3 sendChat functions working like that, so my main thread code is nested 3 deep. D: At least it works. Now my GM can stop harassing me about automated attack rolls for monsters. Edit: I know C++, but I work as a .Net/Java developer, so I almost never touch it. I'm spoiled by my Visual Studio IDE and IntelliSense.
1410814694
Lithl
Pro
Sheet Author
API Scripter
I suppose you could always implement an asynchronous semaphore. I haven't tried the following, but it ought to work: function Semaphore(lockProcess, lock) { this.lock = lock || 0; this.process = lockProcess; } Semaphore.prototype.V = function() { this.lock++; }; Semaphore.prototype.P = function() { this.lock--; if (this.lock == 0 && this.process) { this.process(); } } function myProcess() { ... } ... var lockSendChat = new Semaphore(myProcess); lockSendChat.V(); lockSendChat.V(); lockSendChat.V(); // Alternative to calling V() three times: var lockSendChat = new Semaphore(myProcess, 3); sendChat(who, message1, function(ops) { ... lockSendChat.P(); }); sendChat(who, message2, function(ops) { ... lockSendChat.P(); }); sendChat(who, message3, function(ops) { ... lockSendChat.P(); }); The V and P functions of a semaphore are standard, but not necessarily intuitive to a native English speaker. They stand for verhogen and prolaag (which in turn is short for probeer te verlagen ). The former means "increase" and the latter means "try to reduce" in Dutch, the native language of Edsger Dijkstra, the computer scientist who invented the semaphore construct.
1410814762
Lithl
Pro
Sheet Author
API Scripter
Charles H. said: I know C++, but I work as a .Net/Java developer, so I almost never touch it. I'm spoiled by my Visual Studio IDE and IntelliSense. Visual Studio can handle C/++, although if I recall correctly the C11 compiler it uses has one or two well-known bugs.
Brian said: Visual Studio can handle C/++, although if I recall correctly the C11 compiler it uses has one or two well-known bugs. It can, but most of my work is writing business process software for small businesses, so the bulk of my work is just C#, SQL, and ASP.NET stuff. Also, to make sure I understand the Semaphore code properly: When declared, the Semaphore contains X number of locks. Until P is called X number of times, the "main thread" code will not fire. Once P is called the Xth time, the Semaphore automatically triggers the "main thread" code.
1410816622

Edited 1410816640
The Aaron
Roll20 Production Team
API Scripter
If you want to go the deferred route, it's probably easier to use underscore's _.debounce() or _.after() . debounced - will only execute after 300ms, regardless of how many times (other than 0) it is called : function myProcess() { // [...] } var debouncedMyProcess=_.debounce(myProcess,300); sendChat(who, message1, function(ops){ // [...] debouncedMyProcess(); }); sendChat(who, message1, function(ops){ // [...] debouncedMyProcess(); }); sendChat(who, message1, function(ops){ // [...] debouncedMyProcess(); }); after - will only execute the 3rd time it is called : function myProcess() { // [...] } var after3MyProcess=_.after(3,myProcess); sendChat(who, message1, function(ops){ // [...] after3MyProcess(); }); sendChat(who, message1, function(ops){ // [...] after3MyProcess(); }); sendChat(who, message1, function(ops){ // [...] after3MyProcess(); });
1410819419

Edited 1410819474
Lithl
Pro
Sheet Author
API Scripter
Charles H. said: Also, to make sure I understand the Semaphore code properly: When declared, the Semaphore contains X number of locks. Until P is called X number of times, the "main thread" code will not fire. Once P is called the Xth time, the Semaphore automatically triggers the "main thread" code. Correct. Additionally, calling V will add to X. However... Aaron said: after - will only execute the 3rd time it is called : function myProcess() { // [...] } var after3MyProcess=_.after(3,myProcess); sendChat(who, message1, function(ops){ // [...] after3MyProcess(); }); sendChat(who, message1, function(ops){ // [...] after3MyProcess(); }); sendChat(who, message1, function(ops){ // [...] after3MyProcess(); }); It looks like Underscore already has an asynchronous semaphore implemented! Neat!
1410842067
The Aaron
Roll20 Production Team
API Scripter
Well, not quite an asynchronous semaphore, as you have to know the count up front and you can't increment it, only decrement it. But for simple cases it fills the same space... =D