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

Need help with custom sort order of tracker

1586716283

Edited 1586718995
Hello lovely people, So I am using an optional rule for 7th Edition Call of Cthulhu that allows the players to roll for their initiative and then they go according to the degree of success they scored. If a tie happens, person with the highest dexterity goes first. Now, I've written the first part of the custom initiative script and it works fine. Rolls for the player and determines degree of success and puts that in the tracker. on("ready",function() {     on("chat:message",function(msg){                  // CLEAR TURN TRACKER         if(msg.type=="api" && (msg.content.indexOf("!CthulhuReset")==0))         {             var turnorder = [];         Campaign().set("turnorder", JSON.stringify(turnorder));         return;         };         // CLEAR TURN TRACKER         if(msg.type=="api" && (msg.content.indexOf("!CthulhuInit")==0 || msg.content.indexOf("!CthulhuInitBonus")==0))         {             var selected = msg.selected;             if (selected===undefined)             {                 sendChat("API","Please select a character.");                 return;             }             var tok = getObj("graphic",selected[0]._id);             var character = getObj("character",tok.get("represents"));                           if (character===undefined)             {                 sendChat("API","Please add a name to this token.");                 return;             }             var RollOne = randomInteger(100);             var RollTwo = randomInteger(100);                          if (msg.content.indexOf("!CthulhuInitBonus")==0)                          {                 var Rollsarr = [RollOne, RollTwo];                 var playerRoll = Math.min.apply(Math, Rollsarr);             }                          else                          {                 playerRoll = RollOne;             }                          // var playerRoll = randomInteger(100);             // var playerRollBonus = randomInteger(100);             var playerDex = getAttrByName(character.id, "dex");             var InitDegree;               if (playerRoll == 1)              {             InitDegree = "Critical";             }             else if (playerRoll <= playerDex / 5)             {             InitDegree = "Extreme";             }             else if (playerRoll <= playerDex / 2)             {             InitDegree = "Hard";             }              else if (playerRoll <= playerDex)             {             InitDegree = "Success";             }              else if (playerRoll == 100)             {             InitDegree = "Critical Fail";             }              else if (playerRoll > playerDex)             {             InitDegree = "Fail";             }             else              {             InitDegree = "Critical Fail";             }             //TRY AND SEND CHAT VIA TEMPLATE             if (msg.content.indexOf("!CthulhuInitBonus")==0)             {             sendChat(tok.get("name"), "&{template:coc-bonus} {{name=Initiative (Bonus Die)}} {{success=[["+ Math.floor(playerDex) +"]]}} {{hard=[["+ Math.floor(playerDex/2) +"]]}} {{extreme=[["+ Math.floor(playerDex/5) +"]]}} {{roll1=[["+ playerRoll +"]]}} {{PlayerRoll1=[["+ RollOne +"]]}} {{PlayerRoll2=[["+ RollTwo +"]]}}");                            }             else             {             sendChat(tok.get("name"), "&{template:coc-1} {{name=Initiative}} {{success=[["+ Math.floor(playerDex) +"]]}} {{hard=[["+ Math.floor(playerDex/2) +"]]}} {{extreme=[["+ Math.floor(playerDex/5) +"]]}} {{roll1=[["+ playerRoll +"]]}}");             }            //END OF TRY AND SEND CHAT VIA TEMPLATE                          var turnorder; if(Campaign().get("turnorder") == "") turnorder = []; //NOTE: We check to make sure that the turnorder isn't just an empty string first. If it is treat it like an empty array. else turnorder = JSON.parse(Campaign().get("turnorder")); //Add a new custom entry to the end of the turn order. turnorder.push({     id: tok.get("id"),     pr: InitDegree.toString(),     custom: "" }); Campaign().set("turnorder", JSON.stringify(turnorder));                                   }     }); }); Now I want to have a function that sorts the tracker in the following order, respecting highest dexterity for tie breaks. Critical Extreme Hard Success Fail Critical Fail Here's an example of my turn order now. So all the "Critical" scores should go first in order of initiative, then Extreme, then Hard, etc.  Can you please help me with that part of the script? PS: I know my code isn't very pretty, but I am not a programmer, I did this by studying other code and reading online!
1586718261
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
If you have the script display the results like you do in the list above, with the number prepended, the tracker should sort in ascending order. It does for me if I put in those results manually, at least.
Thanks for the reply keithcurtis. Not sure what you mean. do you mean using the sort options that are built in to the turn order? Cause that doesn't work for me.
1586726196

Edited 1586726218
I think he means change the labels that appear in the Turn Tracker from "Critical," "Extreme," "Hard," etc., to "1 Critical," "2 Extreme," "3 Hard," etc., and the Turn Tracker will sort by the numbers.
Ah I see. That still won't sort them according to highest dexterity though. Also, it's not visually pleasing :)
Anyone else that could help with this?
1586810040
The Aaron
Roll20 Production Team
API Scripter
Try this completely untested rewrite of your script... Hopefully it will just work, otherwise I'll install the character sheet tonight and fix it... on("ready",() => { /* eslint-disable no-unused-vars */ const getTurnArray = () => ( '' === Campaign().get('turnorder') ? [] : JSON.parse(Campaign().get('turnorder'))); const setTurnArray = (ta) => Campaign().set({turnorder: JSON.stringify(ta)}); const addTokenTurn = (id, pr) => Campaign().set({ turnorder: JSON.stringify( [...getTurnArray(), {id,pr}]) }); const addCustomTurn = (custom, pr) => Campaign().set({ turnorder: JSON.stringify( [...getTurnArray(), {id:-1,custom,pr}]) }); const removeTokenTurn = (tid) => Campaign().set({ turnorder: JSON.stringify( getTurnArray().filter( (to) => to.id !== tid)) }); const clearTurnOrder = () => Campaign().set({turnorder:'[]'}); const sorter_asc = (a, b) => b.pr - a.pr; const sorter_desc = (a, b) => a.pr - b.pr; const sortTurnOrder = (sortBy = sorter_desc) => Campaign().set({turnorder: JSON.stringify(getTurnArray().sort(sortBy))}); /* eslint-enable no-unused-vars */ const sorter_cthulhu = (a,b) => { if(a.pr !== b.pr){ return degreeToOrder[b.pr] - degreeToOrder[a.pr]; } let aTok = getObj('token',a.id); let bTok = getObj('token',b.id); if(aTok && bTok) { let aDex = findObjs({ type: 'attribute', name: 'dex', characterid: aTok.get('represents')})[0]; let bDex = findObjs({ type: 'attribute', name: 'dex', characterid: bTok.get('represents')})[0]; if(aDex && bDex){ return (parseFloat(aDex.get('current'))||0) - (parseFloat(bDex.get('current'))||0); } return 0; } return 0; }; const degreeToOrder = { Critical : 1, Extreme : 2, Hard : 3, Success : 4, "Critical Fail" : 5, Fail : 6 }; const degreeFromRollAndDex = (r,d) => { if ( r === 1 ) { return "Critical"; } if ( r <= d / 5 ) { return "Extreme"; } if ( r <= d / 2 ) { return "Hard"; } if ( r <= d ) { return "Success"; } if ( r === 100 ) { return "Critical Fail"; } if ( r > d ) { return "Fail"; } return "Critical Fail"; }; on("chat:message",(msg) => { if('api' === msg.type) { let args = msg.content.split(/\s+/); let cmd = args.shift().toLoweCase(); let who = (getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname'); let isBonus = true; switch(cmd) { // CLEAR TURN TRACKER case '!cthulhureset': if(playerIsGM(msg.playerid)){ clearTurnOrder(); } else { sendChat("",`/w "${who}" Only the GM may clear the turn order.`); } break; case '!cthulhuinit': isBonus = false; /* break; // intentional dropthrough */ /* falls through */ case '!cthulhuinitbonus': { let tokens = (msg.selected || []) .map(o=>getObj('graphic',o._id)) .filter(g=>undefined !== g) .map(t => ({ token: t, character: getObj('character', t.get('represents')), dex: findObjs({ type: 'attribute', name: 'dex', characterid: t.get('represents')})[0] })) .filter( o=> (undefined !== o.character && undefined !== o.dex)) .map(o=>Object.assign({}, o, { roll1: randomInteger(100), roll2: (isBonus ? randomInteger(100) : 101)})) .map(o=>Object.assign({}, o, { roll: Math.min(o.roll1, o.roll2)})) .map(o=>Object.assign({}, o, { degree: degreeFromRollAndDex(o.roll,parseFloat(o.dex.get('current'))||0)})) ; if( ! tokens.length) { sendChat("",`/w "${who}" Please select one or more tokens that represent characters with a dex attribute.`); return; } tokens.forEach(o=>{ sendChat(o.token.get('name'),`&{template:coc-${isBonus?'bonus':'1'}}` +`{{name=Initiative${isBonus?' (Bonus Die)':''}}}` +`{{success=[[${Math.floor(parseFloat(o.dex.get('current'))||0)}]]}}` +`{{hard=[[${Math.floor((parseFloat(o.dex.get('current'))||0)/2)}]]}}` +`{{extreme=[[${Math.floor((parseFloat(o.dex.get('current'))||0)/5)}]]}}` +`{{roll1=[[${o.roll}]]}}` + isBonus ? `{{PlayerRoll1=[[${o.roll1}]]}} {{PlayerRoll2=[[${o.roll2}]]}}` : '' ); }); tokens.forEach(o => addTokenTurn(o.token.id,o.degree)); sortTurnOrder(sorter_cthulhu); } } } }); });
1586810090

Edited 1586810663
As for sorting by Dexterity (within each level of success I assume), you can use a trick we use in numerical-based initiative systems and apply the Dexterity as a decimal after the number. If a higher Dexterity is better, you would need to invert the labels I suggested above (so it would be "6 Critical," "5 Extreme,", "4 Hard," etc.) and reverse the sort order in the Turn Tracker to Descending. Then you would get results in order like: 6.16 Critical (for a character with a 16 Dex) 6.09 Critical (for a character with a 9 Dex) (be sure to add a leading zero) 4.15 Hard (for a character with a 15 Dex) 3.18 Success (for a character with an 18 Dex) It's still ugly, but it would work. EDIT: And of course, in the time it took me to write this post, The Aaron writes a whole script! :-D
1586810476
The Aaron
Roll20 Production Team
API Scripter
=D
1586810485
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
He cheated. He has an extra brain in a jar that just projects code directly onto the Internet.
1586810579
The Aaron
Roll20 Production Team
API Scripter
... what do you mean "an"...  =D HAHAHAHAHAAH
First of all ,The Aaron, you are some sort of wizard. This looks magnificent and far better than anything I could have worked out in years. Code had a small typo which I fixed but now when  I roll for initiative with a selected token that has a linked character sheet to it I get the error " Please select one or more tokens that represent characters with a dex attribute." I assume it's not reading the attribute properly but can't for the life of me figure it out in your code and attempt to fix it. The character sheet I am using is the 7th Edition Call of Cthulhu one (NOT the one by roll20). If you could take another look at it I'd be forever grateful!
1586824821
The Aaron
Roll20 Production Team
API Scripter
Ok, I loaded up the Character Sheet and fixed it. =D I had to change a few things about the template being called.  There didn't seem to be a coc-bonus or PlayerRoll1 and PlayerRoll2 fields.  Here's what I ended up with: Not ideal on the second one, but I don't see a way to get a better listing without editing the sheet, which you may have done. Commands (Case-insensitive) and alias (because I can't seem to spell Cthulhu consistently): !CthulhuReset -- Lets the GM clear the Turn Order. Alias: !creset !CthulhuInit -- Lets anyone roll initiative for any selected tokens (supports multiple).  Alias: !ci !CthulhuInitBonus -- Lets anyone roll initiative with bonus for any selected tokens (supports multiple). Alias !cib Cheers! Script: on("ready",() => { /* eslint-disable no-unused-vars */ const getTurnArray = () => ( '' === Campaign().get('turnorder') ? [] : JSON.parse(Campaign().get('turnorder'))); const setTurnArray = (ta) => Campaign().set({turnorder: JSON.stringify(ta)}); const addTokenTurn = (id, pr) => Campaign().set({ turnorder: JSON.stringify( [...getTurnArray(), {id,pr}]) }); const addCustomTurn = (custom, pr) => Campaign().set({ turnorder: JSON.stringify( [...getTurnArray(), {id:-1,custom,pr}]) }); const removeTokenTurn = (tid) => Campaign().set({ turnorder: JSON.stringify( getTurnArray().filter( (to) => to.id !== tid)) }); const clearTurnOrder = () => Campaign().set({turnorder:'[]'}); const sorter_asc = (a, b) => b.pr - a.pr; const sorter_desc = (a, b) => a.pr - b.pr; const sortTurnOrder = (sortBy = sorter_desc) => Campaign().set({turnorder: JSON.stringify(getTurnArray().sort(sortBy))}); /* eslint-enable no-unused-vars */ const sorter_cthulhu = (a,b) => { if(a.pr !== b.pr){ return degreeToOrder[a.pr] - degreeToOrder[b.pr]; } let aTok = getObj('graphic',a.id); let bTok = getObj('graphic',b.id); if(aTok && bTok) { let aDex = getAttrByName(aTok.get('represents'),'dex'); let bDex = getAttrByName(bTok.get('represents'),'dex'); if(aDex && bDex){ return (parseFloat(bDex)||0) - (parseFloat(aDex)||0); } return 0; } return 0; }; const degreeToOrder = { Critical : 1, Extreme : 2, Hard : 3, Success : 4, "Critical Fail" : 5, Fail : 6 }; const degreeFromRollAndDex = (r,d) => { if ( r === 1 ) { return "Critical"; } if ( r <= d / 5 ) { return "Extreme"; } if ( r <= d / 2 ) { return "Hard"; } if ( r <= d ) { return "Success"; } if ( r === 100 ) { return "Critical Fail"; } if ( r > d ) { return "Fail"; } return "Critical Fail"; }; on("chat:message",(msg) => { if('api' === msg.type) { let args = msg.content.split(/\s+/); let cmd = (args.shift()||"").toLowerCase(); let who = (getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname'); let isBonus = true; switch(cmd) { // CLEAR TURN TRACKER case '!creset': case '!cthulhureset': if(playerIsGM(msg.playerid)){ clearTurnOrder(); } else { sendChat("",`/w "${who}" Only the GM may clear the turn order.`); } break; case '!ci': case '!cthulhuinit': isBonus = false; /* break; // intentional dropthrough */ /* falls through */ case '!cib': case '!cthulhuinitbonus': { let tokens = (msg.selected || []) .map(o=>getObj('graphic',o._id)) .filter(g=>undefined !== g) .map(t => ({ token: t, character: getObj('character', t.get('represents')), dex: getAttrByName(t.get('represents'),'dex') })) .filter( o=> (undefined !== o.character && undefined !== o.dex)) .map(o=>Object.assign({}, o, { roll1: randomInteger(100), roll2: (isBonus ? randomInteger(100) : 101)})) .map(o=>Object.assign({}, o, { roll: Math.min(o.roll1, o.roll2)})) .map(o=>Object.assign({}, o, { degree: degreeFromRollAndDex(o.roll,parseFloat(o.dex)||0)})) ; if( ! tokens.length) { sendChat("",`/w "${who}" Please select one or more tokens that represent characters with a dex attribute.`); return; } tokens.forEach(o=>{ sendChat(o.token.get('name'),`&{template:coc${isBonus?'':'-1'}}` +`{{name=Initiative${isBonus?' (Bonus Die)':''}}}` +`{{success=[[${Math.floor(parseFloat(o.dex)||0)}]]}}` +`{{hard=[[${Math.floor((parseFloat(o.dex)||0)/2)}]]}}` +`{{extreme=[[${Math.floor((parseFloat(o.dex)||0)/5)}]]}}` +`{{roll1=[[${o.roll}]]}}` + (isBonus ? `{{roll2=[[${o.roll1}]]}} {{roll3=[[${o.roll2}]]}}` : '') ); }); tokens.forEach(o => addTokenTurn(o.token.id,o.degree)); sortTurnOrder(sorter_cthulhu); } } } }); });
1586852016

Edited 1586853290
Thank you The Aaron! Yes I have edited the sheet for the bonus die, sorry forgot to mention that! Thanks a lot man! Just reading through your code has improved my code writing I think! You're doing things in there that are so elegant man! Thank you so much for taking the time to fix this for me, was driving me insane!
1586866970
The Aaron
Roll20 Production Team
API Scripter
No problem!  Let me know if you need help getting your customizations working with the above. 
The Aaron said: No problem!  Let me know if you need help getting your customizations working with the above.  Got it working! Thank you mate!
1586884715
The Aaron
Roll20 Production Team
API Scripter
No problem! =D