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

[Script] WIP Groundhog Day Token reset

1582064049

Edited 1582067300
Hey there, I'm planning a groundhog day style adventure and I thought it would be nice to save a token state were I can return to as soon as the loop starts over. If this already exists please tell me but I didn't found such a script :) The script is really simple: !groundhog save : saves the state of all tokens on the current players page (the one with the ribbon) I accidentally saved multiple times the wrong state so I'm using this macro now: !groundhog ?{Save and overwrite?|yes,save| }  !groundhog morning : resets all saved tokens to the save state with the position, status & health I've also added a 1d3  (or 1)  health reduction for each iteration but it is commented right now. As I'm planning to run the adventure 2-3 month from now I still have time to add more features and optimize it. Right now I'm planning: config menu attribute save selection health reduction configuration multiple save states (right now only one is kept) state cleanup If you guys have any suggestions please tell me as I cant talk to my players about it... obviously :D groundhog.js on ( 'chat:message' ,  function  ( msg ) {      if  ( msg . type  ==  "api"  &&  msg . content . startsWith ( "!groundhog" )) {          if  (! state . Groundhog ) {             state . Groundhog  = {                  tokens : []             };         }          if  ( playerIsGM ( msg . playerid )) {              let   args  =  msg . content . replace ( /  + (?=   ) / g ,  '' ). split ( "   " );              option  =  args [ 1 ]              let   currentPageID  =  Campaign (). get ( 'playerpageid' ),                  currentPage  =  getObj ( 'page' ,  currentPageID );              let   object_tokens  =  findObjs ({                  _pageid :  currentPageID ,                  _type :  "graphic"             });              if  ( option  ==  "save" ) {                 state . Groundhog . tokens  = [];                 _ . each ( object_tokens ,  function  ( token ) {                      let   keys  = [ "_id" ,  "_pageid" ,  "left" ,  "top" ,  "rotation" ,  "layer" ,  "bar1_value" ,  "bar2_value" ,  "statusmarkers" ,  "currentSide" ]                      var   token_state  = {}                     keys . forEach ( function  ( item ) {                         token_state [ item ] =  token . get ( item );                     });                     state . Groundhog . tokens . push ( token_state );                 });             }                       if  ( option  ==  "morning" ) {                 _ . each ( object_tokens ,  function  ( token ) {                      function   isSaved ( o ) {                          return  o . _id  ===  token . get ( "_id" );                     };                      saved  =  state . Groundhog . tokens . find ( isSaved );                      if ( saved ) {                          let   savedCopy  =  Object . assign ({},  saved );                          // decreasing health by 1 or 1d3 for each iteration                          //                          // savedCopy.bar1_value--;                          // savedCopy.bar1_value = savedCopy.bar1_value - randomInteger(3)                          // Object.assign(saved, savedCopy);                         token . set ( savedCopy );                     }                 });             }         }     }    });
1582064396
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
What a great idea for GMs who run scenarios for multiple groups.
1582065048

Edited 1582067247
Hehe I thought so too and I will use it for this purpose but right now I didn't include a complex cleanup function. For my first version I didn't want to blow up the state variable. But it will be so handy to save a map before starting a one shot :)
1582080196
The Aaron
Roll20 Production Team
API Scripter
Rather than look at the Campaign's player page id, I'd look at the msg.playerid's player object's lastpage property. That will let you operate on the gm's current page so you can manipulate pages your players can't see. One problem is if tokens get deleted.  You can catch that event and give warnings for sure, but you could also recreate the token off screen if the image isn't from the marketplace. (Be sure to update the state with the new id)  It's pretty easy to support multiple pages separately by having your state object contain a property for each page id you're manipulating. You could similarly support multiple save points by using a property within that using something like a save name or time stamp. I have some similar logic for saving and manipulating turn orders in GroupInitiative.  Speaking of state objects, I recommend moving you're state check and initialization outside the message handler. You only need to do it once, so no need to have it there. You should wrap your whole script in: on('ready',()=>{  /* code here */ }); (Well formatted of course. =D). This will let your script start executing once the API is fully spun up.  Hope that helps!
I like the adventure concept.  I'll follow this topic in hopes of stealing your script!  Thanks for a cool idea.
1582205634

Edited 1582205679
Thanks for the helpful tips! One problem is if tokens get deleted. &nbsp;You can catch that event and give warnings for sure, but you could also recreate the token off screen if the image isn't from the marketplace. (Be sure to update the state with the new id)&nbsp; Wasn't a problem after all. I'm now saving the whole object and recreate it if it isn't found. Seems to work fine as long as I don't mess up the id's :D I don't know yet what you've meant with the lastpage property as I didn't found it in the campaign or msg. Can you give me a tip here? The only thing I'm worried about is the state variable size. I'm now saving an own array for each page but the tokens saving hash could get REALLY big. I've added a cleanup command but nobody will do this :D Tomorrow I'll look at a good way of deleting objects which dont exist in the saved state. Deleting needs some more time and care so I dont want to rush it :) My current version is this one but I didn't test too much yet. <a href="https://gist.github.com/elcool0r/c5f17411e0d6bfff08671ed8dd259d3a" rel="nofollow">https://gist.github.com/elcool0r/c5f17411e0d6bfff08671ed8dd259d3a</a>
1582254285
The Aaron
Roll20 Production Team
API Scripter
Recreating works great, provided the image is in a user's library.&nbsp; If it's in the Marketplace, the API can't create it. Here's a few functions I've written to deal with player pages: const getActivePages = () =&gt; [...new Set([ Campaign().get('playerpageid'), ...Object.values(Campaign().get('playerspecificpages')), ...findObjs({ type: 'player', online: true }) .filter((p)=&gt;playerIsGM(p.id)) .map((p)=&gt;p.get('lastpage')) ]) ]; const getPageForPlayer = (playerid) =&gt; { let player = getObj('player',playerid); if(playerIsGM(playerid)){ return player.get('lastpage'); } let psp = Campaign().get('playerspecificpages'); if(psp[playerid]){ return psp[playerid]; } return Campaign().get('playerpageid'); }; const getPlayersOnPage = (pageid) =&gt; { let pages = {}; let ribbonPage = Campaign().get('playerpageid'); let psp = Campaign().get('playerspecificpages'); findObjs({type:'player'}) .forEach(p=&gt;{ if(playerIsGM(p.id)){ const lp = p.get('lasgtpage'); pages[lp]=pages[lp]||[]; pages[lp].push(p.id); } else if(psp.hasOwnProperty(p.id)){ pages[psp[p.id]]=pages[psp[p.id]]||[]; pages[psp[p.id]].push(p.id); } else { pages[ribbonPage]=pages[ribbonPage]||[]; pages[ribbonPage].push(p.id); } }); if(pageid){ return pages[pageid]||[]; } return pages; }; const getGMPlayers = (pageid) =&gt; findObjs({type:'player'}) .filter((p)=&gt;playerIsGM(p.id)) .filter((p)=&gt;undefined === pageid || p.get('lastpage') === pageid) .map(p=&gt;p.id) ; If you use getPageForPlayer() on the playerid, it will give you back the page that player is on.&nbsp; In the case of the GM, it pulls it from the player's lastpage property, which is only updated for GMs and tells you the last page they loaded.&nbsp; That's almost always the page that the GM is on (it would only be wrong if they had loaded a page in another browser since loading this one, which almost never happens). The state can be 2megs in size, so you're probably OK for the most part.&nbsp; You could encode you page data into Handouts that are archived.&nbsp; They wouldn't count against your state size, and would be reasonably easy to remove, though there would be some fiddling with looking them up and such.
Thanks for the tips! As I'm not a developer this is really helpful. Also the chaining looks so helpful.... I have a long way to go :)
1582311479
The Aaron
Roll20 Production Team
API Scripter
Everyone starts somewhere. When I first did API scripts, I'd done little more than validate text input in a browser. =D. Ask questions, read articles, try things out. Javascript can be a pretty elegant language if done correctly. I'm happy to talk about programming in all my (admittedly limited lately) spare time!