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