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

General Javascript Question on 'Reentry'

March 07 (3 weeks ago)
Don
Pro

Wondering what methods people use for what I will term 'reentry' in Javascript/Roll20. I'm self-taught, so my abilities are fairly basic.

Lets say I have a routine, "Fight" which has one character attacking another. After collecting stats and rolling dice (in the API) sometimes it needs to prompt in Chat to ask the player if they wish to reroll, or use a special ability, and sometimes it doesn't. So a powercard like bit is printed in chat with a button or buttons for the options if that is needed.

Then after the selection is made, it would presumably 'reenter' the Fight routine and complete the combat rolls/results.

For times where no rerolls are allowed or no special abilities apply, it should go from start to finish without the interruption and reentry.

I have thought to break up the overall routine into sections, each its own function, and reentry would be returning to the appropriate function.

Wondering if there are any other methods that might be suggested or perhaps scripts that use reentry that I can read over and learn from ?

Thanks

March 07 (3 weeks ago)
timmaugh
Pro
API Scripter

I would suggest using an IIFE closure to give you the ability to keep your data resident in memory (until you dispose of it or the sandbox reboots). Then you just need a way to index the data so you know that incoming-message-X needs to be connected with the combat state data package-Y.

If you don't know what an IIFE is, I'd also suggest you use the Revealing Module Pattern. Aaron's RMP template and my own RMP template are both available at this link. That pattern will let you keep an object resident in memory. For each encounter you need to track across chat interactions, you just need to index it with something unique. For instance,  if each character can only be involved in one encounter at a time, you could index it by the character ID. Then when the new message comes in, you can track the encounter to that character. You'd have the index be included in the return message somewhere -- like one of the arguments.

So if you've indexed this encounter to:

123

...any button you render for further user interaction should have that in the command line somewhere:

!scripthandle --dice|[[2d20]] --id|123

As for other scripts that do something like this.... ZeroFrame does something a little more complex with UUIDs generated and used as the script handle. ScriptCards, as well, uses reentrant buttons... so you could start with either of those.

If you want more I do on any of this or have any questions, post back!

March 07 (3 weeks ago)
Don
Pro

Thanks, I will have a look at those. I do use a RMP template (Aaron's I think) as well as Objects/Classes for the characters/models in the game, with the tokenID as the key for the object .

I think my initial run through was a bit linear, and due to all the potential interrupts at different points in a combat, I need to break it up into parts, each with its own const function (I use fat arrow functions) and just work out the 'weave' of the combat in this game. 

So instead of a single "const Fight = () => {function}"  function that is entered using a macro and then spits out the results in Chat, I may go with something like (imaginatively named) Fight, Fight2, Fight3 etc with each 'spaced' where an interrupt might happen whether thats a possible reroll, use of a special ability/'Ploy' in this game etc. and then functions for the Rerolls or Abilities that can be called if needed and feed back to the relevant 'next' part of the Fight code.

March 07 (3 weeks ago)

Edited March 08 (3 weeks ago)
timmaugh
Pro
API Scripter

If you already have an RMP going, you've got the apparatus you need.

If you have four phases of your processing, construct a function for each in the base closure of your IIFE:

const combatPhase1 = (encID, args, msg) => {
  //...
};
const combatPhase2 = (encID, args, msg) => {
  // ...
};
const combatPhase3 = ...
comst combatPhase4 = ...

Then drop them in a library object (also at the base level of the IIFE) so you can later reference them by argument text more easily:

const libPhases = {
  phase1: combatPhase1,
  phase2: combatPhase2,
  phase3: combatPhase3,
  phase4: combatPhase4
};

Create an object to handle storing the data for an encounter between re-entrances (also in the root of the IIFE):

const encounterData = {};

Then your chat handler will need to do a few things:

  • catch the message and determine if this is a message intended for this script
  • detect your line marker to see if this is an initial encounter message or a subsequent message
    • if it's an initial message, designate a unique identifier and create the data block on your encounterData object
    • if it's a subsequent message, keep a handle on the unique identifier (key) that will connect to the data block on the encounterData object
  • parse the arguments into a generic form that all 4 of your decomposed phase functions will expect

At this point, you have a couple of options. If you are only-and-ever going to have the same path through your combat encounter (phase1 => phase2 => phase3 => phase4 => various public mockings => death), then you can store the next-expected function/phase on the data block attached to the encounterData object:

{
    YourKey1: {
        nextPhase: '2',
        targets: [ <<token1>>, <<token2>>, ...],
        // ...
    },
    YourKey2: { //... }
};

...and then call the appropriate function off the libPhases object in your chat handler:

let encID = getIDFromCommandLine() || createNewEncID();
if(encounterData.hasOwnProperty(encID) && libPhases.hasOwnProperty(`phase${encounterData[encID].nextPhase}`)) {
    libPhases[`phase${encounterData[encID].nextPhase}`](encID, args, msg);
}

(I'm passing in the msg object just in case you need other data off of it, like the playerid or roll data.)

Every combatPhaseX function would then need to increment the nextPhase property of the encounter on the encounterData object at the end of whatever else it did. Your final function would need to delete the data block off the encounterData object so that you didn't bloat the memory. (As well, you'd probably want a way to clear old/abandoned data from the object -- a particular GM handle to delete all outstanding blocks from the object or a way to list the active encounters that are ongoing, with buttons to delete them.)

An alternate construction would be if you decompose your functions down to digestible chunks of optional encounter actions, you can have different "paths" through the combat. For instance:

const gatherPartyHelp = (encID, args, msg) => { //... };
const activateMagic = (encID, args, msg) => { //... };
const unintendedConsequences = (encID, args, msg) => { //...};
// ...etc...

Then your messages to the player could be full of buttons representing any remaining optional activities they might want to do, as well as one further button for "finish encounter" that would instruct your script to compute the outcome based on all of the input it has collected from the various buttons the player chose.


With this construction, you could have a centralized processor determining what buttons are available (for instance, not displaying a "Roll Damage" button until the "Roll to Hit" component has been rendered; or determining that if the player has already performed the "Roll to Hit" action, they can no longer collect aid from the rest of the party, so that button would no longer be included; or that normally the button for "Unintended Consequences" is NOT included while the button for "Finish Encounter" IS included... but if the to-hit roll is bad enough to have earned some unintended consequence, the "Unintended Consequences" button becomes available, and the "Finish Encounter" button is hidden -- until the Unintended Consequences are generated for this encounter).

Every one of these decomposed functions would call a central function that sends the above panel to the user, again, this time with the available buttons based on the state of the encounter.

Just riffing on ideas, over here. =D

March 07 (3 weeks ago)
Don
Pro

Great ideas!

I've started with part 1, have a data storage object to hold info and have a attack macro that gathers the info, accesses another routine to roll the dice and modify based on weapon abilities then fills the data object with the results. I'll have to reread your section on the library Object to figure out how to incorporate that. I can see adding the 'next phase' info into the data storage object. Hoping to avoid various public mockings lol.  Thanks!

March 08 (3 weeks ago)
timmaugh
Pro
API Scripter

OH!

I hope that didn't come across like I was saying YOU were going to suffer various public mockings! I had that in the chain of encounter phases as if EVERY encounter would end -- for the *characters* -- in public mocking and then death.

Because everyone needs to have something to look forward to!

March 08 (3 weeks ago)
Don
Pro

Hehe, yep, I thought that was quite funny , realized what you meant. In case you're interested, this is for a hex based version of a tabletop 2 player skirmish game, where each player has a team of 'operatives' that have to take objectives, kill the enemy etc. Skirmish version of Warhammer 40K.

I've taken your ideas re a library Object and the data storage Object (which I imaginatively called "CombatArray"), and so far I've got it to the point where it will roll and display the initial attack rolls of a melee (done simultaneously in RL) and then prompts each player for Rerolls and relevant 'Ploys' if appropriate and then moves on. The reentrant technique seems to be working!

My Head Hurts (queue Monty Python) a bit from the library object concept and using it to 'jump' around to the various functions, but I'm loving the doors that technique has just opened! Thanks again for your help and the examples!





March 08 (3 weeks ago)
timmaugh
Pro
API Scripter

Excellent! Glad it's working for you!

Post back if you get stuck on any of it!