Some time ago I wrote about how to implement compound actions. While the model sounded like a good idea, I recently have been encountering more and more cases where it makes things really hard and painful to deal with. As they say, devil is in the details and those details I hadn’t really thought back then.
As a quick recap: a function creates an action. This action can be asked if it’s valid or not. It can also be executed, causing a character to perform that action. Each action has associated action point cost, that is added into internal clock of character. Character can perform an action only when their internal clock is a zero. Some actions are made of two or more simpler actions: lunge is move and attack for example.
There has been cases where I have been thinking how and when code should handle action points (essentially the internal time of character) and if an action is allowed or not to. Lets take lunge for example: if we can’t easily check if the lunge as a whole is allowed action, as the first step (move) already changes the state of the world. We can check before executing subsequent step if it’s still valid, but only after all the previous steps have been taken care of. This could lead to a situation where character moves forward and then doesn’t attack as it’s not valid move. I can live with that though, in a way it even makes sense.
It’s worth noting, that action should not check character’s internal clock to be sure that it’s their turn to act. Depending on when time is added to the clock (end of compound action or during each step), this would make action illegal, even when it should be legal.
But where this model really breaks down is movement, specifically the type of movement that switches two characters around. One would think that nothing is simpler: Alice moves where character Bob is and other way around. Both characters expend some (probably equal) amount of action points. But when you actually think (or start coding) this, things start breaking down left and right.
Rules for traps triggering or not should be the same, regardless if characters enter square on his own turn or as a result of switching places with someone. So it would be more than convenient to have some common piece of code that moves a character from one spot to another, while taking care of traps and other complications. But wait, traps could affect to creatures around them too. Think an exploding mine, that surely should do some area of effect damage too. All involved characters should then move and only after that should traps trigger. This extra complication needs extra management: either have code explicitly handle situation or have a queue of things that should be performed when all characters have moved (I don’t really know what to call that, post-movement-actions maybe?).
What about case, when one of the characters can switch places, but not other one? A large dragon flying next to a knight stading on an edge of a cliff decides to switch places. Should this be allowed, even when it would mean that the knight gets thrown into void? Moving routine for the knight could be queried to check if it’s legal for the knight to move space where dragon is currently, but then we would need to introduce a parameter to turn of checking if space is already occupied. Or, imagine a (not so) subtle bug, where in order for the knight to know if it can switch places with the dragon, code would check if the dragon can switch places. And in order to do that, it would check if knight can do it. And so on until dusk of time.
Compound action model seems to work well, when actions are sort of atomic. Each step is performed in discrete moment of time, completely. Lunge works, because action of attacking happens after action of moving forward. It certainly depends on the success of the movement, but it doesn’t happen at the same time. Switching places on the other hand, has two actions going on that should complete at the same time. In order to resolve this problem, I’m thinking of representing switching places as a single action. It probably will duplicate some code (to what extent, I’ll see when I actually code it).
Do you have any ideas or insights on the problem? Or just general comments? Did you have to tackle similar problem in your game or did you never encounter it? I would be interested in hearing your comments.