Wednesday 26 December 2007

Unangband Dungeon Generation - Part Six (Features)

(You probably want to start with part one, then read parts two, three, four and five).

Implementing the terrain features in Unangband is what drove me to create an Angband variant in the first place. My initial attempts were to limited to adding acid, lava, water, chasms and ice, but the feature set has grown to over 950 different terrain types and keeps expanding all the time. What sorts of features are currently available? Well, everything from chests, wall decorations, traps, levers, machines, boiling acid, hidden vents, waterfalls, chasms, poisonous waters, vine-choked corridors, and mud-filled bogs.

The link between features and objects is a subtle one. In fact, there is arguably a continuum between between features and objects. In Angband and variants, both features and objects are laid out in the dungeon in a rectangle grid array. Objects however are allowed to stack in a pile, and can be picked up by monsters and the player. Features are not - only one feature can exist per cave grid (exactly one in fact). But many other tile-based games allow up to three features to exist on a grid, particularly for games with an isometric display (the three features are usually a floor type, a wall type and a wall decoration).

So whether a 'thing' is a feature or object is plastic, and very much subject to differing points of view. For instance, where does a chest lie on this continuum?

In Angband, the argument is a chest is an object. It can be picked up by the player, and sold. But in Unangband, a chest is a feature. The reasoning is that this simplifies the open and closing code, and allows chests to use the feature trap selection code. Traps are similarly a mixed proposition, and Angband itself has had versions that treated traps as objects, and more recently as terrain features. Unangband does both. Traps that the player finds within the dungeon are features. But traps that the player sets themselves consist of both a trap feature, and an object. Each object type has a corresponding trap type, so that, for instance, trip wires are associated with wands, and dragon armour can be set as strange visages. Many variants hold traps in a separate structure completely, and this may ultimately be what Unangband does.

Whether a 'thing' in Unangband is a feature or an object depends as much on how features and objects are defined. In Unangband, features can obstruct player movement, line of sight and line of fire, whereas objects cannot. Features can also 'transition' from one feature type to another. Much like most things in Unangband, features are defined through a data-centric approach and loaded from a text file (lib/edit/terraint.txt) at the start of the game.

A few example features are given below:

N:405:unlit brazier
#$# brazier
#$# wooden deck over chasm

#$# magma vein
#$# lava
#$# lava fall
#$# deep lava under a magma crust
#$# deep lava under a stone bridge

N:956:the hut of Radagast the Brown
D:A note on the door says 'Refugees welcome. Back soon. Radagast'.

The N: line defines the feature index and feature name. The G: line defines the graphic and colour of the feature (colours are encoded as various letters). The O: line defines an object associated with the feature, which may be recoverable from the feature if it has a GET_FEAT flag set, or allows the feature to act like an object if it has the USE_FEAT flag set.

The W: line defines the frequency which the feature appears in the dungeon, and priority when displaying it on the mini-map. The K: lines define the state transitions for the feature - to help I've included comments under each transition as to what the feature it turns into, when affected by a particular transition.

The F: lines define various flags associated with the feature. There are 96 possible flags in all (but should really be increased), ranging from what the player and monsters can do to the feature (such as OPEN it), how the feature can be moved through (CAN_FLY, CAN_OOZE), how the feature affects objects in the grid (if HIDE_ITEM, it causes them to disappear from view) and so on.

And the D: line provides a description if stepping on the feature.

The list of features in Unangband has grown large because features serve a number of different functions:

1. They define the dungeon topology.

This can be as simple as dividing up the dungeon into rooms and impassable locations. This is done by attaching properties to various feature types such as allows line of sight, allows movement, and so on, to directly affect the ability of the player and monsters to traverse the feature types. Similarly, the topology can be manipulated through the player (or monsters) interacting with the terrain. A simple example is doors, which can be opened, or closed, or spiked shut or bashed through.

2. Features are associated with various monster types.

The relationship between monster types and features is defined in the room description code outlined earlier. With a large variety of feature types, a large number of different associations can be made.

3. Features provide a reason to explore the dungeon.

Much like chests can contain objects, many other terrain features require interaction with the player to release their contents. Mineral veins can contain buried gold. Similarly rubble can contain buried objects, fountains, middens and basins can hide items inside them, and painted walls must occasionally be bashed down to reveal hidden treasures.

4. Different features are used to distinguish degree as well as kind.

Instead of creating additional data structures to represent information that may only be relevant for particular feature types, several feature types can be used to encode these values. For instance, there are 8 doors types in Angband that are used only to distinguish the difficulty for picking the lock on the door. A scalar quantity like this could be encoded with a separate cave_power array for each grid, but this would be useless for 99% of other feature types. In fact, eight different door types is probably too many: it's only really work distinguishing three or four different difficulty levels from the player's point of view.

5. Different features are used to distinguish different terrain 'states'.

If you have a feature set that contains water, sooner or later you'll make waves. One of the earliest decisions I made around the implementation of features in Unangband was to define feature transitions as a finite state automata. Each feature has a list of transition types such as OPEN, CLOSE, SPIKE, HURT_FIRE, HURT_ACID and so on, which when a particular event occurs. This allows, for instance, wooden features to catch fire, metal features to dissolve through acid, and so on, by defining the results of each of these in data, as opposed to code.

But not all transitions have to be player activated. Five transitions are time dependent and independent of player or monster interaction. These transitions occur either all of the time (INSTANT, ADJACENT) or randomly (ERUPT, STRIKE, TIMED). The difference between each of these transitions is the way it applies the feature 'blow type', while transitioning. Adjacent affects all adjacent squares with the 'blow type', erupt out to radius 2, strike looks like a lightning bolt.

Because these transitions occur on a regular grid, it becomes possible to define Game of Life -like and machine-like behaviour. There is a set of features that through opening and closing adjacent features, can be used to link together complex on map behaviours. These include chains, valves, gates and so on.

And with these combinations, it becomes possible to make dynamic waves in water. Any water terrain hit by water damage (HURT_WATER) turns into the crest of a wave. The crest of water hits all adjacent grids with water damage, and transitions into a wave. The wave transitions the following turn into 'rough water'. And the rough water does nothing, but transition the following turn into normal water. This double-transition is designed to create the trailing edge of the wave, as a 'buffer' zone to prevent the crest advancing back against itself.

Similar transitions are used to create pools of lava which dynamically heat and cool to allow a safe crust traversable by the player, some of the time, and glacier-filled lakes, which feature blocks of ice crashing from the ceiling, and ice growing up from the floor. The dynamic terrain also allows clouds of smoke, acid and steam to swirl around cave floors, without having to specify a separate cave_air structure, and fires to burn across grass or through forests.

6. Features are easy to build in a 'data-centric' design

I've mentioned previously that the Unangband approach has been to take a data-centric design approach. This often results in me designing the data file structure first, including some samples of what I want to achieve, then writing a parser for the data file, and finally implementing the code required to implement the attributes that this data has.

This data-centric approach allows rapid development of additional feature types. In fact, what has driven most of the terrain development has been combining ASCII characters and the fifteen colours that Angband currently supports, and trying to figure out what the result may be. (Hmmm... light green asterisk. What could a light green asterisk in the game be? The answer ended up being a vine-covered granite wall.)

But as important as a data-centric approach has been merging the possible game spaces. What do I mean by this? You'll have to read part seven.

No comments: