Event system, aka, how to handle nested "you've activated my trap card" triggers
I'd like to take some time this month to talk about how we're handling the extreme levels of actions / reactions / counter-reactions in the Lancer system. To summarize the problem that we need to handle, playing a round can sometimes go like this:
Weapons and systems that respond to triggers and have a wide variety of effects, which can then themselves be reacted to, and so on. Most of the time these reactions can be resolved sometime after the thing they're reacting to, but sometimes they come first and can cancel the original event (e.g. Vlad's spike damage destroying its attacker before the attack resolves).
In tabletop play, there are some loose guidelines for how to handle the order
of resolution, but a lot of the time they boil down to GM or player
arbitration. In making a computer adaptation of the system, we gotta come up
with a more mechanistic system that can handle the worst case scenarios. For
reference, the comprehensive rules that dictate the similarly-reaction-nested
game of Magic the Gathering weigh in just under 200 pages long. It's not an easy problem, is what I'm saying.
Let's start off with a simple case. Here's a Tokugawa and a Berserker duking it out.
The Toku uses a charged blade to hit the Berserker, which triggers its Aggression trait and it hits right back. Assuming everything hits, here's how the event tree would end up looking in Lancer Tactics:
Nice and linear. Easy peezy. We can resolve them all in a row, one after the
other.
Let's throw something extra in — say that the damage on the Toku was enough to
structure it, giving the player an opportunity to brace*. If they decide to
brace, they'll lose most of their next turn. Now, before the damage event can
resolve, we have to go and resolve this side-branch of deciding whether or not
to brace.
The brace trigger happens because it's listening for damage events going off
on the Toku and will check if those would be enough to structure it,
so it inserts itself and pauses the damage event until the player makes a
decision. If they decide that they don't feel like structuring today, it'll
put temporary buff on the unit to get resistance to the next instance of
damage. Once the damage event resumes, that buff will be factored into the
calculation of how much damage actually gets through.
You might ask why we're breaking up the attack into so many steps (activate, attack, damage); the short answer is "to let various abilities detect and trigger from them individually", but there's some more insidious edge cases doing so hedges out. Let's now take a look at how the system handles multiple targets.
This Bombard is makes an area-of-effect attack that'll catch multiple players.
The attacks and damage resolves on both the Mourning Cloak and the Vlad before
the MC's Expose Singularity kicks in and it teleports away.
Why is it important that we resolve all the attacks and damage before doing triggers like the teleport? Well, if the Bombard was closer to the players, like this:
The explosion would trigger the Vlad's Shrike Armor and do 1 damage to the Bombard before the attacks resolve. If that was enough to destroy the Bombard, the whole attack would be cancelled — including the one on the Mourning Cloak! We'd be in a bad spot if we'd already applied the damage and subsequent movement!
So instead, we queue up all the attacks to be resolved breadth-first and resolve any before-resolution triggers from any of them, like the Vlad's Shrike Armor.
In general, this system has events follow this pattern:
- queue and then resolve all interrupting reactions that happen pre-block (Shrike Armor, Overwatch)
- check to see if we're still valid, abort if not (did the Shrike Armor destroy us?)
- do the thing (move/stabilize/boost)
- queue any events that come from this event, to be resolved either depth-first (being Structured) or breadth-first (all the attacks within our AOE)
- queue any followup reactions (Aggression, Expose Singularity)
* we're currently planning on changing the Brace trigger from any damage to only when you'd be
structured — a slight modification to the rules, but one that'll prevent a
"Brace?" prompt from showing up every time you get hit. That's 95% of the time
when I've seen people brace, anyway.
Status Update
So how's the game looking? Since the last update, we've:
- Worked our way about 70% of the way through the mission editor tasks, including trigger and dialogue editors. The biggest remaining tasks within it is the pilot editor and managing reinforcement groups. (not counting the actual tile painting, which is still being done in Tiled until things become more solid)
- We've added the VERY basic skeleton of turn/round management and player movement; when we hit "playtest" in the editor, we can now actually tell our lil robots to move around.
- And finally, we put in the full order for the coins! 1.5 inch diameter (to make the Lich more legible as a spooky guy) and now in silver. No more-detailed ETA on shipping yet, but we're still on track for before the end of this year.
The next month or two will be slow going development-wise as I'll be
taking a vacation in Ireland to touch some rocks. I love working on the game
itself, but the associated project management is taking a toll on my brain so
this'll hopefully be well-timed. We built in plenty of buffer time for a
reason.
Hope you had a good weekend!