Roguelike Intelligence - Displaced Actions

From RogueBasin
Jump to navigation Jump to search
Roguelike Intelligence, part 2B: Displaced Actions.

One technique that is fairly important, and requires support in the 
basic architecture of your system, is the use of Displaced Actions.

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 better, 
I think, for the altar itself to be a monster, with a special displaced
action that it's ready to use on any co-aligned, 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:

   IF player-standing-on-same-square
   AND adjacent-coaligned-monster-can-sacrifice-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 rogulikes, 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.  

There are a couple of ways to handle this; my own favorite is that 
when the altar/door/gate/whatever chooses "stand-still" because it 
can't find an eligible operator, its "stand-still" behavior waits 
until the instant before its chosen operator's turn comes around.  
(alternatively, this may be a different behavior named wait-upto-
monster or similar: wait-on-monster waits until just *after* the 
monster's turn). This requires a function that can query another 
monster to see when its next turn is, but that's fairly easy.  

Then, 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 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.  You will probably need a 
state variable that specifies which monster of the nearby ones is 
under consideration.  Once you have those, you can implement 
stateless AI's and state machines that use displaced tests and 
displaced actions.  

Uses range from the simple (monsters trading places) to ways 
to implement class abilities (priestly "turn-undead" effects) to
funny (wand that forces any monster to spend its next few actions
singing and dancing) to the arbitrarily complicated (lever that 
floods the next room, other lever that releases piranhas, other 
lever that flushes next room, fourth lever that uses the rushing 
water to power a "shredder" so as to properly compost the 
effluvient...).  Creativity is encouraged, but try to avoid 
getting excessively capricous or vicious.  Most of the "best" 
uses of this capability are in fact pretty simple. 

Ray Dillinger