# Storing stateful function-based actions in a JSON

System: My game uses an "ECS + event queue" architecture where every tick, various events change the state of JSON-able data. Plain JS objects.

Problem: I want to be able to save the game-state as JSON, and there is one place where I run into an issue: multi-turn actions. For example, the player has an "intent" array that stores the next few stages:

// pseudocode:
let player = {
health: 5,
damage: 2,
actor: true,
intent: [ () => wait(), () => wait(), () => uniqueContextualStatefulAction()],
}
(...)
every tick
for every actor with intent:
execute and remove entity.intent[0]()


If all actions took a single "turn" or tick, there would be no problem, as intent would be cleared. But every tick, the function (callback?) of every actor is called (with some more complexity on the side). These callbacks are often unique and contextual, for example waiting for 5 turns, then crafting a custom item out of two others, then deleting the ingredients, all modified by status effects at the time of execution (rather than creation).

My question is as such. What is an "ECS-friendly", save-game-friendly, and sanity-friendly way to handle this system? At the extreme, storing every instance of every function and its arguments as strings, but I really don't... want to?

There's gotta be a better way. This is also deterring me from advancing the game systems, and I tend to go into over-engineering to avoid this problem.

Side-note: I am open to suggestions about changing some of the architecture altogether, since I can't imagine how to do this in other architectures either. Thank you!

An example of actual in-game code:

    function createWait(ticks) {
return {
effect: () => {},
duration: ticks,
pause: timing.msPerTick,
}
}

durations: [
{ baseName: "1 tick", dur: 1 },
{ baseName: "3 ticks", dur: 3 },
{ baseName: "6 ticks", dur: 6 },
{ baseName: "12 ticks", dur: 12 },
{ baseName: "60 ticks", dur: 60 },
],
intents: function() {
let intents = [];
for (let duration of this.durations) {
function effect() {
newLine(You wait \${duration.baseName})
}
intents.push({
representation: [game.word("wait"), game.word(duration.baseName)],
sequence: [{ effect }, createWait(duration.dur)]
});
}
return intents;
}
});
$$$$


If you need to serialize the actions, you should probably represent them as data (i.e. plain JS objects, not necessarily strings) instead of functions. Since you have different kinds of actions with different "arguments", the discriminated union pattern will help. This essentially means every action has a kind property, and you switch on action.kind in functions that deal with an action (e.g. to display it or to enact it).
• You can still have arbitrary code associated with each action that runs when the action is performed. In your tick function you'll just replace action() with enact(action), and write a function enact that switches on action.kind` and delegates to the relevant implementation. By "unique and contextual" do you mean your existing action functions are closures with references to other objects which might change before the action is enacted? If so, you'll need a way to serialize those references too (e.g. using ids) to store on the action.