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

Help with MagicalSurges API

1632847976

Edited 1632848360
Joel
Pro
I've recently added the MagicalSurges.js to our game. It has you identify a character in the game (in this case a wild magic sorcerer) and every time they cast a leveled spell, the API rolls a d20 to see if a Wild Magic Surge occurs (on a roll of 1). It outputs the d20 roll in chat. I know very little about coding, but I got the impression that if the d20 roll results in a 1, the API would automatically get a magical effect from the MagicalSurges rollable table and output the results in chat. (See where it checks for the rollable table and stores it in an array for later use, and also the makeSurge function later on?) What is happening instead is that when the d20 results in a 1, nothing more is output to chat. For those who know about coding, if it is not currently written to get a magical effect from the rollable table and output it to chat, would there be a simple way to add that functionality?
/* MagicalSurges Version 0.0.4 Github <a href="https://github.com/nmrcarroll/roll20-api-scripts/tree/master/MagicalSurges" rel="nofollow">https://github.com/nmrcarroll/roll20-api-scripts/tree/master/MagicalSurges</a> */ var MagicalSurges = MagicalSurges || (function () { let version = '0.0.5', surgeTable, arrayTable, checkInstall = function () { // initalize the state to store our sorcerers in if not already done. if (!state.MagicalSurges) { state.MagicalSurges = { sorc: [], }; } // Attempt to locate the rollable table surgeTable = findObjs({ type: 'rollabletable', name: 'MagicalSurges', })[0]; // If rollable table does not exist, make one. if (!surgeTable) { surgeTable = createObj('rollabletable', { name: 'MagicalSurges', }); // Create a dummy item in the rollable table. createObj('tableitem', { name: "DELETE ME WHEN YOU'VE ADDED YOUR OWN SURGES", rollabletableid: surgeTable.id, }); } // store all the items in the rollable table for later use. arrayTable = findObjs({ type: 'tableitem', rollabletableid: surgeTable.id, }); }, // called when changes are made to the rollable table, reloads the list we have stored. loadTable = function (obj) { if (obj.get('_rollabletableid') == surgeTable.id) { arrayTable = findObjs({ type: 'tableitem', rollabletableid: surgeTable.id, }); } }, // adds player id to the state addPlayer = function (ids) { state.MagicalSurges.sorc = [...new Set([...state.MagicalSurges.sorc, ...ids])]; }, // remove player id from the state removePlayer = function (ids) { state.MagicalSurges.sorc = state.MagicalSurges.sorc.filter(id =&gt; !ids.includes(id)); }, idNameConvert = function (charNames, args) { // Remove all spaces from string. const keyFormat = n =&gt; (n || '').toLowerCase().replace(/\s+/g, ''); const allCharIds = []; const charId2Name = {}; // Creates a dictionary of all characters and their ID's const charKey2Id = findObjs({ type: 'character', }).reduce((m, c) =&gt; { const name = c.get('name'); m[keyFormat(name)] = c.id; allCharIds.push(c.id); charId2Name[c.id] = name; return m; }, {}); // find id matching character name entered or id entered const ids = charNames.reduce((m, n) =&gt; { const kn = keyFormat(n); if (charKey2Id.hasOwnProperty(kn)) { m.push(charKey2Id[kn]); } return m; }, args.slice(2).filter(id =&gt; allCharIds.includes(id))); return [ids, charId2Name]; }, // Checks if the message is a spell from one of our monitored characters checkSpell = function (msg) { const character_id = (findObjs({ type: 'character', name: (msg.content.match(/charname=([^\n{}]*[^"\n{}])/) || [])[1], })[0] || { id: 'API', }).id; if (state.MagicalSurges.sorc.includes(character_id)) { let spell_level = msg.content.match(/spelllevel=([^\n{}]*[^"\n{}])/); // Make sure we're not pulling from the first regex if (spell_level != null) { spell_level = RegExp.$1; } const cantrip = msg.content.includes('cantrip}}'); const whisper = msg.target; // If a spell was rolled, automatically roll a d20 to see if a surge happens if (!cantrip &amp;&amp; (spell_level &gt; 0 || msg.rolltemplate == 'spell')) { const roll = randomInteger(20); if (!cantrip &amp;&amp; (spell_level &gt; 0 || msg.rolltemplate == 'spell')) { let chatMesg = ''; chatMesg = `&amp;{template:simple} {{rname=Wild}} {{r1=${roll}}} {{normal=1}}`; if (whisper == undefined) { sendChat(msg.who, chatMesg); } else { sendChat(msg.who, `/w gm ${chatMesg}`); sendChat(msg.who, `/w ${msg.who} ${chatMesg}`); } } } } }, makeSurge = function () { const roll = randomInteger(arrayTable.length); const effect = arrayTable[roll - 1].get('name'); let chatMesg = ''; chatMesg = `&amp;{template:atk} {{rname=WildRoll}} {{rnamec=rnamec}} {{r1=${roll}}} {{normal=1}} {{desc=${effect}}}`; return chatMesg; }, handleInput = function (msg) { const spellRollTemplates = ['spell', 'atk', 'dmg', 'atkdmg']; if (msg.type !== 'api') { if (msg &amp;&amp; msg.rolltemplate &amp;&amp; spellRollTemplates.includes(msg.rolltemplate)) { const roll = checkSpell(msg); const whisper = msg.target; if (roll == 1) { const mSurge = makeSurge(); if (whisper == undefined) { sendChat(msg.who, mSurge); } else { sendChat(msg.who, `/w gm ${mSurge}`); sendChat(msg.who, `/w ${msg.who} ${mSurge}`); } return roll; } } } const charNames = msg.content.split(/\s+--/); const args = msg.content.split(/\s+/); // Check for specific script commands and respond switch (args[0]) { case '!MagicalSurge': if (args.length === 1) { sendChat('MagicalSurge', `/direct ${makeSurge()}`); break; } switch (args[1]) { case 'gm': sendChat('MagicalSurge', `/w gm ${makeSurge()}`); break; case 'add': case 'remove': if (playerIsGM(msg.playerid) &amp;&amp; (args.length &gt; 2 || charNames.length)) { const idReturn = idNameConvert(charNames, args); const ids = idReturn[0]; const charId2Name = idReturn[1]; if (ids.length) { if (args[1] === 'add') { sendChat('MagicalSurge', `/w gm Adding Characters: ${ids.map(id =&gt; charId2Name[id]).join(', ')}`); addPlayer(ids); sendChat('MagicalSurge', `/w gm Current Sorcerers: ${state.MagicalSurges.sorc.map(id =&gt; charId2Name[id]).join(', ')}`); } else { sendChat('MagicalSurge', `/w gm Removing Characters: ${ids.map(id =&gt; charId2Name[id]).join(', ')}`); removePlayer(ids); sendChat('MagicalSurge', `/w gm Current Sorcerers: ${state.MagicalSurges.sorc.map(id =&gt; charId2Name[id]).join(', ')}`); } } else { sendChat('MagicalSurge', '/w gm No valid characters found. Please be sure to use the character_id or character name.'); } } } } }, registerEventHandlers = function () { on('chat:message', handleInput); on('add:tableitem', loadTable); on('change:tableitem', loadTable); on('destroy:tableitem', loadTable); }; return { CheckInstall: checkInstall, RegisterEventHandlers: registerEventHandlers, }; }()); on('ready', () =&gt; { MagicalSurges.CheckInstall(); MagicalSurges.RegisterEventHandlers(); });
1632877549
Oosh
Sheet Author
API Scripter
The surge tables themselves are Wizards' IP and can't be included in a script without permission. By default, the script looks for a rollable table called 'MagicalSurges'. If you create/populate that table, the script should work, though I didn't read through it all.
In order for this script to fully work beneficially it needs to be able to check if Tides of Chaos is expended, otherwise its not really doing anything for us.&nbsp; The d20 roll is far more rare then Tides of Chaos surges which should happen immediately after a leveled spell when Tides is expended.&nbsp; So does this script check if something like "class_resource" = 0?&nbsp; If not, then this script is not actually going to help save any time.&nbsp; Its going to fail to launch when the d20 is not a 1, when it should surge, and then the Dm needs to go in and just make a simple rollable table macro to manually fire in those instances.&nbsp; Personally I'd rather see a surge button attached to the bottom of sorcerers spells, you could even just insert it into the spell save DC entry in all of their spellcards as an API chat button.&nbsp; I personally attach it as a chaser after the spellcard is pressed.&nbsp;&nbsp; All you really need is to make a macro that fires a WMS rollable table.&nbsp; So really the best API to install is Recursive Tables and maybe Table export to yoink someone elses table for WMS though table imports are hard to find cause sharing tables from books is a huge no no here.&nbsp; Table export is basically only around to help us back up our own work and import non raw tables.&nbsp;&nbsp;
Create a macro that spits out a wms roll on a rollable table, then insert that api chat button into a global attack modifier on the sorcerers table and then always have that global attack checked, then when they fire ANY attack roll spell or save spell, it fires.&nbsp; Then you only have to worry about spells that do spellcard outputs, which you can then paste the few there are, with the api chat button in the spells description.&nbsp;&nbsp;
Oosh said: The surge tables themselves are Wizards' IP and can't be included in a script without permission. By default, the script looks for a rollable table called 'MagicalSurges'. If you create/populate that table, the script should work, though I didn't read through it all. Yes, it did create the MagicalSurges rollable table when I installed it, and I immediately populated it with all 50 effects. The table works if you directly call for a roll from it, and it works if you enter the API command manually (I think it's "!MagicalSurge" and "!MagicalSurge gm")... those work fine. The only thing not working is for the API to automatically do that when the d20 result is a 1.
Joel said: Oosh said: The surge tables themselves are Wizards' IP and can't be included in a script without permission. By default, the script looks for a rollable table called 'MagicalSurges'. If you create/populate that table, the script should work, though I didn't read through it all. Yes, it did create the MagicalSurges rollable table when I installed it, and I immediately populated it with all 50 effects. The table works if you directly call for a roll from it, and it works if you enter the API command manually (I think it's "!MagicalSurge" and "!MagicalSurge gm")... those work fine. The only thing not working is for the API to automatically do that when the d20 result is a 1. Create a macro on the characters sheet that is just !magicalsurge.&nbsp; Then make an api chat button that fires that macro, cut and paste it into the bottom of every spelltemplates description and then add it to every single leveled spell attack roll template on the front page.&nbsp; That is the best you are going to get because no api can automatically fire efficiently every time, you are always going to have to manually fire tides of chaos surges and the automatic ones that do fire are likely going to fire on false positives when the player clicks a spell by accident or is trying to show spell details.&nbsp;&nbsp;
1632924543
timmaugh
Forum Champion
API Scripter
I think I see the problem. This line checks to see if roll was 1: if (roll == 1) { So then back up and see where roll is being assigned its value: const roll = checkSpell(msg); Great. It's getting it from the checkSpell() function. Back up a little more and&nbsp; you'll see that function: // Checks if the message is a spell from one of our monitored characters checkSpell = function (msg) { const character_id = (findObjs({ type: 'character', name: (msg.content.match(/charname=([^\n{}]*[^"\n{}])/) || [])[1], })[0] || { id: 'API', }).id; if (state.MagicalSurges.sorc.includes(character_id)) { let spell_level = msg.content.match(/spelllevel=([^\n{}]*[^"\n{}])/); // Make sure we're not pulling from the first regex if (spell_level != null) { spell_level = RegExp.$1; } const cantrip = msg.content.includes('cantrip}}'); const whisper = msg.target; // If a spell was rolled, automatically roll a d20 to see if a surge happens if (!cantrip &amp;&amp; (spell_level &gt; 0 || msg.rolltemplate == 'spell')) { const roll = randomInteger(20); if (!cantrip &amp;&amp; (spell_level &gt; 0 || msg.rolltemplate == 'spell')) { let chatMesg = ''; chatMesg = `&amp;{template:simple} {{rname=Wild}} {{r1=${roll}}} {{normal=1}}`; if (whisper == undefined) { sendChat(msg.who, chatMesg); } else { sendChat(msg.who, `/w gm ${chatMesg}`); sendChat(msg.who, `/w ${msg.who} ${chatMesg}`); } } } } }, That does declare a roll (although as a constant, which I don't think is right), but that roll isn't the same as the roll from the handleInput() function scope. They are different. The way the handleInput() roll (the one involved in the if statement, above) gets filled is that the checkSpell() function returns something. But you can see... checkSpell() doesn't return anything. There's also strangeness of a nested if statement that has the same condition as its parent if statement... it will always run. There's no point to the nested check. Try this rewrite of the above function. You'll have to paste it into your existing script: // Checks if the message is a spell from one of our monitored characters checkSpell = function (msg) { const character_id = (findObjs({ type: 'character', name: (msg.content.match(/charname=([^\n{}]*[^"\n{}])/) || [])[1], })[0] || { id: 'API', }).id; if (state.MagicalSurges.sorc.includes(character_id)) { let spell_level = msg.content.match(/spelllevel=([^\n{}]*[^"\n{}])/); // Make sure we're not pulling from the first regex if (spell_level != null) { spell_level = RegExp.$1; } const cantrip = msg.content.includes('cantrip}}'); const whisper = msg.target; // If a spell was rolled, automatically roll a d20 to see if a surge happens if (!cantrip &amp;&amp; (spell_level &gt; 0 || msg.rolltemplate == 'spell')) { const roll = randomInteger(20); let chatMesg = ''; chatMesg = `&amp;{template:simple} {{rname=Wild}} {{r1=${roll}}} {{normal=1}}`; if (whisper == undefined) { sendChat(msg.who, chatMesg); } else { sendChat(msg.who, `/w gm ${chatMesg}`); sendChat(msg.who, `/w ${msg.who} ${chatMesg}`); } return roll; } } },
All I need is for the current API (the code is posted above) to automatically draw a random effect from the MagicalSurges table when the WMS d20 results in a 1, and post that random effect in chat. Everything else the API is doing is exactly what we need/want. If someone sees where there is a bug in the code, or can suggest a few lines to add so that it will do that, I'd be grateful to hear about it.
timmaugh said: I think I see the problem. Thank you so much, I will try that as soon as I can and will let you know. I really appreciate it!
1632925175

Edited 1632925190
Joel
Pro
timmaugh said: I think I see the problem. PERFECT!!!!!&nbsp; Thank you so much, that seems to have fixed it! Now when the d20 result is a 1, it pulls a random effect from the table and displays it in the chat. Exactly what I needed. Appreciate you doing that man.
1632925981
timmaugh
Forum Champion
API Scripter
No problem. Glad to help!