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

5e OGL by Roll20 - API - Spellcard vs. Spell Attack

In the 5e OGL by Roll20 character sheet, there is the option to flip a spell from SPELLCARD to ATTACK.  When you flip it to ATTACK, it automatically creates an appropriate attack on the "CORE" tab of the character sheet. When you attempt do the same type of switch via the API to the underlying attribute, it creates a blank entry in the Attack & Spellcasting section.  Here is a short script that recreates the problem: on('chat:message', function(msg) { if (msg.type === 'api' && msg.content.search(/^!test\b/) !== -1) { let character = createObj("character", {name: "Test Spell Import"}); let charid = character.id; let level = "2" let uuid = generateRowID(); setAttribute(charid, "repeating_spell-" + level + "_" + uuid + "_spellname", "Acid Arrow"); setAttribute(charid, "repeating_spell-" + level + "_" + uuid + "_spellschool", "evocation"); setAttribute(charid, "repeating_spell-" + level + "_" + uuid + "_spellcastingtime", "1 Action"); setAttribute(charid, "repeating_spell-" + level + "_" + uuid + "_spellrange", "90 feet"); setAttribute(charid, "repeating_spell-" + level + "_" + uuid + "_spellname", "Acid Arrow"); setAttribute(charid, "repeating_spell-" + level + "_" + uuid + "_spellcomp_v", "{{v=1}}"); setAttribute(charid, "repeating_spell-" + level + "_" + uuid + "_spellcomp_s", "{{s=1}}"); setAttribute(charid, "repeating_spell-" + level + "_" + uuid + "_spellcomp_m", "{{m=1}}"); setAttribute(charid, "repeating_spell-" + level + "_" + uuid + "_spellcomp_materials", "Powdered rhubarb leaf and an adder's stomach"); setAttribute(charid, "repeating_spell-" + level + "_" + uuid + "_spellduration", "Instantaneous"); setAttribute(charid, "repeating_spell-" + level + "_" + uuid + "_spelldescription", "A shimmering green arrow streaks toward a target within range and bursts in a spray of acid. Make a ranged spell attack against the target. On a hit, the target takes 4d4 acid damage immediately and 2d4 acid damage at the end of its next turn. On a miss, the arrow splashes the target with acid for half as much of the initial damage and no damage at the end of its next turn."); setAttribute(charid, "repeating_spell-" + level + "_" + uuid + "_spellathigherlevels", "When you cast this spell using a spell slot of 3rd level or higher, the damage (both initial and later) increases by 1d4 for each slot level above 2nd."); setAttribute(charid, "repeating_spell-" + level + "_" + uuid + "_spellattack", "Ranged"); setAttribute(charid, "repeating_spell-" + level + "_" + uuid + "_spelldamage", "4d4"); setAttribute(charid, "repeating_spell-" + level + "_" + uuid + "_spelldamagetype", "Acid"); setAttribute(charid, "repeating_spell-" + level + "_" + uuid + "_spelldamage2", "2d4"); setAttribute(charid, "repeating_spell-" + level + "_" + uuid + "_spelldamagetype2", "Acid"); setAttribute(charid, "repeating_spell-" + level + "_" + uuid + "_spellhldie", "1"); setAttribute(charid, "repeating_spell-" + level + "_" + uuid + "_spellhldietype","d4"); //// Problem seems to be with the following call triggering the attack creation //// setAttribute(charid, "repeating_spell-" + level + "_" + uuid + "_spelloutput", "ATTACK"); } }), setAttribute = function(charid, attr, val) { let attribute = createObj("attribute", {name: attr, characterid: charid}); attribute.setWithWorker("current", val); }, generateUUID = (function() { "use strict"; var a = 0, b = []; return function() { var c = (new Date()).getTime() + 0, d = c === a; a = c; for (var e = new Array(8), f = 7; 0 <= f; f--) { e[f] = "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".charAt(c % 64); c = Math.floor(c / 64); } c = e.join(""); if (d) { for (f = 11; 0 <= f && 63 === b[f]; f--) { b[f] = 0; } b[f]++; } else { for (f = 0; 12 > f; f++) { b[f] = Math.floor(64 * Math.random()); } } for (f = 0; 12 > f; f++){ c += "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".charAt(b[f]); } return c; }; }()), generateRowID = function () { "use strict"; return generateUUID().replace(/_/g, "Z"); }; NOTE:   I did try to update all of the spells and later flip the "repeating_spell-" + level + "_" + uuid + "_spelloutput" to ATTACK, but it makes no difference in the result.  It seems that anytime you flip this via the API, a blank Attack entry is created under Attacks & Spellcasting: The browsers console log doesn't suggest that there are any errors... really not sure what to do to troubleshoot this.  Any advice?
Also worth noting that if I manually switch it back to SPELLCARD and then back to ATTACK the attack is created properly, though the original "empty" entry remains until it's manually removed.
1508782507
The Aaron
Pro
API Scripter
You'll probably have to look at the source of the Sheet's Sheet Workers and see what actions it is taking on the change to that checkbox.  There is probably an assumption that causes it to fail to do the update, or a guard that excludes doing it as a result of the API changing it.
The Aaron said: You'll probably have to look at the source of the Sheet's Sheet Workers and see what actions it is taking on the change to that checkbox.  There is probably an assumption that causes it to fail to do the update, or a guard that excludes doing it as a result of the API changing it. I was afraid you'd say that... it's an area I haven't even begun to look at.
1508783132
The Aaron
Pro
API Scripter
I think you'll have to do some printf() debugging . Grab the source to the sheet and drop a bunch of console.log statements into the method on line 5577:     on("change:repeating_spell-cantrip:spelloutput change:repeating_spell-1:spelloutput change:repeating_spell-2:spelloutput change:repeating_spell-3:spelloutput change:repeating_spell-4:spelloutput change:repeating_spell-5:spelloutput change:repeating_spell-6:spelloutput change:repeating_spell-7:spelloutput change:repeating_spell-8:spelloutput change:repeating_spell-9:spelloutput", function(eventinfo) {         if(eventinfo.sourceType && eventinfo.sourceType === "sheetworker") { /* DEBUG */ console.log('Early out for "sheetworker"');             return;         }         var repeating_source = eventinfo.sourceAttribute.substring(0, eventinfo.sourceAttribute.lastIndexOf('_'));         getAttrs([repeating_source + "_spellattackid", repeating_source + "_spelllevel", repeating_source + "_spelloutput", repeating_source + "_spellattackid", repeating_source + "_spellathigherlevels","character_id"], function(v) {  /* DEBUG */ console.log('Got additional fields:'); /* DEBUG */ console.log(v);             var update = {};             var spelloutput = v[repeating_source + "_spelloutput"];             var spellid = repeating_source.slice(-20);             var attackid = v[repeating_source + "_spellattackid"];             var lvl = v[repeating_source + "_spelllevel"];             if(spelloutput && spelloutput === "ATTACK") { /* DEBUG */ console.log('making spell attack: '+lvl+', '+spellid+', '+v['character_id']);                 create_attack_from_spell(lvl, spellid, v["character_id"]);             }             else if(spelloutput && spelloutput === "SPELLCARD" && attackid && attackid != "") {                 remove_attack(attackid);                 var spelllevel = "@{spelllevel}";                 if(v[repeating_source + "_spellathigherlevels"]) {                     var lvl = parseInt(v[repeating_source + "_spelllevel"],10);                     spelllevel = "?{Cast at what level?";                     for(i = 0; i < 10-lvl; i++) {                         spelllevel = spelllevel + "|Level " + (parseInt(i, 10) + parseInt(lvl, 10)) + "," + (parseInt(i, 10) + parseInt(lvl, 10));                     }                     spelllevel = spelllevel + "}";                 }                 update[repeating_source + "_rollcontent"] = "@{wtype}&{template:spell} {{level=@{spellschool} " + spelllevel + "}} {{name=@{spellname}}} {{castingtime=@{spellcastingtime}}} {{range=@{spellrange}}} {{target=@{spelltarget}}} @{spellcomp_v} @{spellcomp_s} @{spellcomp_m} {{material=@{spellcomp_materials}}} {{duration=@{spellduration}}} {{description=@{spelldescription}}} {{athigherlevels=@{spellathigherlevels}}} @{spellritual} {{innate=@{innate}}} @{spellconcentration} @{charname_output}";             }             setAttrs(update, {silent: true});         });     }); That should be enough to figure out what's going wrong...
1508783437
The Aaron
Pro
API Scripter
After that last /* DEBUG */ line, it calls create_attack_from_spell(), which does this:     var create_attack_from_spell = function(lvl, spellid, character_id) {         var update = {};         var newrowid = generateRowID();         update["repeating_spell-" + lvl + "_" + spellid + "_spellattackid"] = newrowid;         update["repeating_spell-" + lvl + "_" + spellid + "_rollcontent"] = "%{" + character_id + "|repeating_attack_" + newrowid + "_attack}";         setAttrs(update, {}, update_attack_from_spell(lvl, spellid, newrowid, true));     } culminating in a call to update_attack_from_spell(), which looks to be where all the attack setup happens.  I'm going to speculate that the extra parameters on setAttrs() are not getting used in the API implementation of setAttrs().
So I added these in, but it doesn't even look like the sheet workers are triggering.  I know the debug is in place, because I do see the "Got Additional Fields:" message in the console when I trigger it manually from the sheet. When I run the API command I get nothing: app.js?1508175236:44 Finished after going 2 levels deep. app.js?1508175236:44 Begin processing op! app.js?1508175236:44 Inline rolls complete! app.js?1508175236:34 Do refresh link cache! app.js?1508175236:46 Refresh Journal List! app.js?1508175236:46 Search took 12ms
1508788343
The Aaron
Pro
API Scripter
The log events when you trigger from the API would show up in the API's console log, not the browser's javascript log.  Check there?
Also want to point out that to see the DEBUG line you will need to do three things in this order. I just had to learn this the other day so I want to save you some time. Save the changes to the character sheet in the game settings. Refresh the game session Restart the API.
Kyle G. said: Also want to point out that to see the DEBUG line you will need to do three things in this order. I just had to learn this the other day so I want to save you some time. Save the changes to the character sheet in the game settings. Refresh the game session Restart the API. Thanks Kyle, that did it, plus what Aaron mentioned about them showing up in the API console.  Here is what I get: "Trigger change even on spelloutput" "Got additional fields:" "[object Object]" "making spell attack: undefined, -Kx9ns2z8PT6dEQQhxKU, -Kx9ns2l_mcY1e2w_YCT" "DOING UPDATE_ATTACKS: -Kx9ns42C7OGKOuEPYx7" "UPDATING ATTACK: -Kx9ns42C7OGKOuEPYx7" So based on that, sure seems as though the trigger happens, but the additional fields are not sent over, hence the reason everything is blank.  Does this seem like a defect in the API implementation of the sheetworkers?  Is there anything I can do on my side for this one?
1508789259

Edited 1508789311
Kevin said: Thanks Kyle, that did it, plus what Aaron mentioned about them showing up in the API console.  Here is what I get: "Trigger change even on spelloutput" "Got additional fields:" "[object Object]" "making spell attack: undefined , -Kx9ns2z8PT6dEQQhxKU, -Kx9ns2l_mcY1e2w_YCT" "DOING UPDATE_ATTACKS: -Kx9ns42C7OGKOuEPYx7" "UPDATING ATTACK: -Kx9ns42C7OGKOuEPYx7" Your issue is because you aren't saving the spell level attribute (suffix _spelllevel).
1508789621
The Aaron
Pro
API Scripter
Great!
That is indeed it Kyle G.!  Thank you Kyle G. and Thank you The Aaron.  I would have never gotten there... I was just walking down the attributes on the sheet and there isn't actually a place for the _spelllevel. Really appreciate the help... now onto the next hurdle :)
Here's my list of spell attributes in case you find any others are missing from your list. _spellname _spelllevel _spellprepared _spellschool _spellritual _spellcastingtime _spellrange _spelltarget _spellcomp _spellcomp_v _spellcomp_s _spellcomp_m _spellcomp_materials _spellconcentration _spellduration _spelloutput _spellattack _spelldamage _spelldamagetype _spelldamage2 _spelldamagetype2 _spellhealing _spelldmgmod _spellsave _spellsavesuccess _spellhldie _spellhldietype _spellhlbonus _includedesc _spelldescription _spellathigherlevels _spellattackid _spell_damage_progression _spellrollcontent _options-flag