Roguelike Intelligence – Displaced Actions
One technique that is fairly important, and requires support in the basic architecture of your system, is the use of Displaced Actions. Okay, “fairly important” is an overstatement. This technique is, plain and simple, a hack. But it’s a cool hack, and it can save you a lot of work.
To recap the terminology I’m using in these articles:
*Behaviors* are simply actions that the monster can take. They have titles like “attack-player” or “move-toward-player” or whatever, and a given behavior can be implemented in different ways (by use of different *actions*) for different monsters.
*Tests* are boolean tests for conditions like “player-is-too-powerful” and “can-move-away-from-player” and so on. Tests are much like behaviors in that they can be implemented in different ways for different monsters. For example, “can-move-toward-player” may mean something different for a monster smart enough to do pathfinding.
Different specific code for tests and behaviors is a simple way to represent different monster capabilities or monster intelligence.
*stateless AIs* are basically nested if statements composed of tests and behaviors, in varying degrees of complexity. A stateless AI always picks a behavior to execute, in any conditions. Stateless AIs implement a pattern of different behaviors for a given type of monster; A given stateless AI will call on several to several-dozen tests and behaviors, and may accept as arguments any version of those tests and behaviors; thus the same stateless AI can produce many hundreds or thousands of different kinds of behavior depending on the exact versions of the tests and behaviors it calls on.
*state machine AIs* are transition tables a monster can follow, which switch it between states depending on inputs. Each state contains a stateless AI that selects actions while the monster is in that state, and a set of parameters for the “observe” routine indicating the monster’s sensory abilities and general alertness. The inputs to the state machine AI’s are gathered by a general routine called “observe”, which checks some central communications channel to discover things observed by that monster. I’ve advocated storing sense impressions in the dungeon itself, or in some central message queue, and having only one “observe” routine shared for all purposes by all monsters.
Okay. recap’s over, now I’m going to explain what I mean by “Displaced actions.”
Consider a Behavior called “switch-places-with-other-monster.” When a monster executes this behavior, it is supposed to mean s/he trades places with an adjacent monster; typically this action will appear qualified by Tests such as “more-wounded-than-me” and “same-species-as-me” and “blocks-route-to-player,” enabling a teamwork situation where wounded monsters facing the player are replaced in tag-team style by fresher reinforcements from the rear.
Now, the monster that executes this action has performed something relatively simple: they’ve moved one square (presumably one square closer to the player). But this action also involved the monster with which places were switched, and as far as that monster is concerned this is a “Displaced action:” That is, an action chosen for it and executed on it by some other monster.
The effects of a Displaced action on a monster are basically that the monster executes a Behavior just as though that monster’s AI had chosen that behavior itself; it uses up time, moves the monster, may generate a message, etc.
Now this is a relatively simple idea, but it’s powerful. It allows teamwork amongst your monsters in ways that are very difficult or impossible to achieve otherwise, it allows shoving and maneuvering as well as hitting for damage, and generally adds a lot to the game.
But, carried on to a slightly different conclusion, it allows you to save yourself a lot of coding work by building special behaviors into special objects or monsters.
For example, remember Adom’s Altars? If the player catches a monster stepping onto an altar he can sacrifice it. Presumably a monster can do the same thing to the player, but who has time to build special behavior into a hundred different state machines and extend the “observe” routine to cover the special case? It’s far far easier for the altar itself to be a monster, with a special displaced action that it’s ready to use on any co-aligned, intelligent, adjacent monster if the player-character should be so foolish as to step onto the altar.
Thus, the code for sacrificing the player-character can be built into the altar itself. Now the altar AI is very simple:
ALTAR_AI IF player-standing-on-altar AND adjacent-monster-can-sacrifice AND adjacent-monster-is-coaligned THEN adjacent-creature-sacrifices-player ELSE IF player-standing-on-altar AND adjacent-monster-is-coaligned THEN wait-to-adjacent-monster ELSE IF player-adjacent-to-altar THEN wait-on-player ELSE stand-still
From the player’s point of view this looks like every monster in the dungeon suddenly got smart enough to know how to sacrifice him if he steps onto their altar. But it’s absolutely unnecessary to give any mention of this behavior in the monster AI’s themselves, except for the Altar AI.
Many other bits of your dungeon can work the same way; special levers that operate gates, special gateways that conjure demons, doorways that *somebody* has to lock before the orc guards flee the room, thrones that want one goblin king to sit on them, and all kinds of other things can work with generic monsters, just by having those objects have AI’s that get other monsters to operate themselves using Displaced Actions.
In order for a monster to be eligible to commit a displaced action, it has to have enough time. This is trickier than it seems. In most roguelikes, the monsters choose actions and act, one at a time. This may mean that, by the time the Altar, or doorway, or gate or throne or whatever acts, all the monsters around it have already committed their time for the next round and it has no monster it can make into its operator. So things that use Displaced Actions, in a turn-based system, need to have a pretty high “initiative” score so that they pick their operator before the operator decides to do something else that round. In roguelikes that use time or pulses, then you’re going to have the situation where it becomes the Altar’s turn and there are no monsters around it whose turn it
also is. This means it picks its operator, but the operator isn’t technically able to do anything until its turn comes around.
Here’s how it works. The altar’s turn comes around and it finds nothing on the altar. Therefore it checks the creatures adjacent to it, and if one of them is the player, it does “stand-still” and schedules its next turn just after the player’s action. If it wakes up and the player is standing on it but no adjacent monster can sacrifice him, it checks adjacent monsters and then does “stand-still” to wait until a tiny fraction before the adjacent monster that can go soonest. If it wakes up, and the player is standing on it, and an adjacent monster is within that tiny fraction of time before its turn, then if that adjacent monster is otherwise able to sacrifice the character, then the altar makes a displaced action on the adjacent monster.
The action advances the creature’s next-turn by the amount of time it takes to do, just like a regular action. So there are two special stand-still behaviors that the altar AI has to know to use.
wait-on-monster waits until just after a monster’s turn (and the altar uses it to assure it can react instantly to the player).
wait-to-monster waits until just before a monster’s turn (and the altar uses it to wake up just in time to direct a creature in a displaced action).
Both of these require the ability to query another monster to see when its next turn is, but that’s fairly easy.
When it wakes up an instant before its chosen operator, it uses its Displaced Action, which also uses up the operator’s next turn — and whatever the “instant” is (one ‘pulse’ or ‘phase’ for you guys who break up the round into even numbers of ‘pulses’ and ‘phases’ or maybe a tenth of a second for continuous-time games where dungeon time and next-turn are float values), that can be added to the time needed to complete the Displaced Action so it all comes out even.
Anyway, with Displaced actions, you can fill your dungeon with all kinds of tricky traps and mechanisms, and most of your intelligent monsters will “magically” get smart enough to use them.
In order to support Displaced Actions, you will need a bunch of tests allowing action-displacers to check on the status and location of nearby monsters, and, of course, a bunch of displaced-behaviors that the action-displacer can project. Once you have those, you can implement stateless AI’s and state machines that use displaced tests and displaced actions.
I called this a hack at the beginning of the article, and it certainly can be abused in a very hacky way. For example, you could have arbitrarily complicated sequences of actions associated with “controls” in your dungeon, such as having a lever that floods the next room, another lever that releases piranhas, a third lever that flushes the next room, a fourth lever that uses the rushing water to power a “shredder” so as to properly compost the effluvient, etc…. You can be as creative, cutesy, or vicious as you want, and the nice thing about this technique is that it’s an easy way to code it once and check it only when it’s relevant. (ie, you won’t have to check for it every time an intelligent monster moves).
But hacky or not, it’s still an important technique because it’s an easy way to get monsters to cooperate (such as the “trading places” example). There are also other things that are familiar tropes from for example D&D, which are hard to code otherwise, such as the class ability where a priest gets to force undead to flee. It’s fairly easy if the priest can use a displaced-action on local undead.
Finally, it is really just about the only way you can create items that influence monster behavior in arbitrary ways. For example if you decide you want to be able to create a wand that forces any monster to spend its next few actions singing and dancing (or anything you haven’t specially coded into the AI of all the monsters that could be affected by it) then anything you come up with is going to be equal in its potential for abuse.
Keep in mind that most of the “best” uses of this capability are in fact pretty simple.