📜 ⬆️ ⬇️

Generation of dungeons in Diablo 1

image

Diablo 1 is a classic 1996 roguelike in the hack and slash genre. It was one of the first successful attempts to introduce the broad masses to roguelike, which had previously had niche graphics in the form of ASCII art. The game spawned several sequels and many imitations. She is known for its dark, gloomy atmosphere, thickening as the player descends into the dungeons, located under the city of Tristram. It was one of the first games for me with procedural card generation, and the possibility of generating such believable levels just shook me.

Recently, I learned that due to the detection of various files with debugging symbols, several game fans took on the task of reverse-engineering the source code to clean it up and figure out what the code written by the developers looked like. So began my weekly excursion into the study of how lead developer David Brevik created these levels. Perhaps because of this, the magic of the game for me partially collapsed, but I learned many techniques that will be useful for developers of similar games, so in this article I will share them.

Thanks to David Brevik and the team of Blizard North for creating such an amazing game, as well as galaxyhaxz and the Devilution team for their amazing work in restoring the readable source code of the project.

Introduction


Diablo is a game of isometric tiles. The game consists of 4 stages, each of which has 4 levels. Stages of the game: Cathedral, Catacombs, Caves and Hell. There are also several fixed levels, such as the city of Tristram, which I will not discuss in the article. In Diablo there are separate procedures for generating levels for each of the 4 stages, so I will first talk about their features, and then consider separately the work of each of the stages.
')
I was only interested in how the levels were generated. To learn about quests, monsters, etc. I recommend reading Jarulf's Guide To Diablo and Hellfire , in which these aspects are described in detail.

General features


Although each stage has its own level generator, all of them have common features.

Dungeons and tiles


Each level of the game is generated to fill a grid of 40 x 40 tiles. The X axis corresponds to the southeast, and the Y axis corresponds to the southwest.

These tiles are used only for level generation tasks. After creating a level, each tile is divided into 4 "dungeon fragments" that are rendered on the screen.


This more detailed grid is used to determine passable tiles, and also provides more efficient reuse of graphics memory. Earlier, I wrote about tile sharing schemes .

This means that the standard Diablo map consists of more than a hundred tiles, many of which are minor variations of others to take into account all possible ways of combining them. We will talk about this later.

To my surprise, unlike other games in this series, dungeon maps are not made up of pre-created blocks. Almost everything is created secretly with the help of algorithms.

Multistep process


Each dungeon generation procedure is divided into two stages. "Pregeneration" does not deal with the choice of tiles at all. It simply generates an array in which passable tiles are marked, tiles with doors, and some other high-level details. At the second stage, this dungeon preparation is converted into an array of tiles, after which the generation is performed and changes are made taking into account these tiles.

It should be incredibly convenient for the designer. You can experiment with the floor plan completely regardless of the choice of tiles and style. But in many cases they are interrelated, providing a more holistic sense of level.

All stages of the creation of blanks dungeons begin with a level filled with a solid "solid", after which the individual parts of the level recursively turn into floor tiles. I will tell you more about this below.

Finished Fragments


Finished fragments are pre-created level blocks that are simply inserted into a randomly generated level. They are used for most of the game quests. At each level there can be only one ready fragment, the location of which at each stage is selected separately.


Butcher's Den is one of the first finished fragments found in the game.

Mini-sets


Mini-kits are another way to insert pre-created content into the level. These are small pieces, usually about 3 × 3 in size, randomly inserted into the dungeon. There is a simple pattern matching pattern, due to which they appear only in the “right” places. Often they are encoded with additional requirements, for example, mini-sets cannot be placed close to each other, they cannot be superimposed on the finished fragments, and so on. Some mini-sets always search and replace, while others do so with a fixed probability.

Mini-kits are used in Diablo for many different purposes. They are used to place large objects from tiles, such as stairs, to correct combinations of tiles that do not connect well with each other, as well as to add tiles of random variability.

Themed rooms


Themed rooms are small spaces bounded by a wall and a door. Typically, they are randomly located in some pre-defined objects. For example, in libraries there is always a bookcase with two candles on each side, several random book holders and monsters. Monster den contains monsters and a random item, and so on.

At the athedral stage levels, the generator creates suitable rooms that are recognized by shading and are reused. At other stages, the algorithm finds open spaces in which walls and a door are drawn to create rooms.

Each type of themed room has specific size requirements and the stage at which it is generated.

Tile replacements


Some cards have a special adaptation of the tiles, but they all have a common feature: some tiles can be replaced with similar variations. The most standard tiles (for example, floors and flat walls) have various variations that allow to reduce the level of monotony. Tile replacements are never reused next to each other.

Pattern matching and “fixes”


As stated above, mini-sets are used as a search and replace mechanism that corrects the generator’s shortcomings. But at most stages there are procedures that detect and fix more specific problems. I will not go into details, because they are just a bunch. Suffice it to say that Diablo is quite buggy, and closer to release it became obvious that it would be easier to add recognition of specific problems than to eliminate their fundamental causes.

One of the most common “fixes” was “lockout removal” (lockout): it checked if it was possible to go through the entire dungeon, and started the generation again if it was not.

Quests


Most of the quests were created with the help of ready-made fragments, but some of them used their own logic. For example, Zhar the Mad generates themed library rooms, the entrance to Poisoned Water is generated by a mini-set, and Anvil of Fury has a specific level generation code. I will not go into details, but the code is full of similar checks for quests.

Cathedral


The cathedral is probably the most iconic of the stages of Diablo. It is characterized by long rows of Gothic arches, cramped rooms and many narrow spaces in the form of doorways. Let's see how it was created.

image

Dungeon Harvesting


The first thing the algorithm does is draw the "core" of the card. He randomly selects up to three 10 Ă— 10 rooms in predetermined positions along the central axis X or Y. A wide corridor connecting the rooms is also drawn along the axis. If there is a ready-made fragment on the level, then it is always located in the center of one of these rooms. These central dungeon rooms are marked as unsuitable for new walls, so that they always remain large open spaces.

All other rooms are generated by the “budding” recursive technique in a function called L5roomGen , which is initiated in each of these rooms, and the choice of axis.

L5roomGen function



In the end, this procedure begins with several rooms carved inside the "solid" tiles, and then repeatedly sticks new rectangles to cut new passable areas. At this stage, all the "rooms" are open on the one hand, because each new room is located directly next to the previous one, without gaps to accommodate the walls.

Then the generator counts the number of generated passable tiles. If it is below the minimum threshold, which increases with each level, the dungeon is destroyed and an attempt to generate is performed anew.

Underground


So far, the algorithm has only tiles "hard" and gender. We need to replace them with real wall tiles. For this, Diablo uses the marching squares algorithm, which I described in a previous article .

However, the game uses an unusual variation of it. Taylset of the cathedral contains walls, but they are always located on the far edge of the tile. Therefore, there is a tile with a wall on the northeast face, but there are no tiles with a wall on the southeast face. To create walls on other sides, you need to find a “hard” tile that has an additional wall that extends outward from the border of the tile. It sounds strange, but in fact it is very convenient to sort the walls by depth.


To cope with this, Diablo on the marching cubes stage skips some walls:


Additional walls are added later, at the “patch” stage. I think this simple procedure has the most effect on the “style” of the cathedral. Since the opposite walls are attached to solid tiles, in the case of two rooms separated by one wall tile, the opposite wall will simply not be created. This means that the dividers between rooms are “thinner” than can usually be obtained in the resolution at which marching squares are performed.

After placing the main walls, the generator adds four free-standing columns to each of the central rooms and the colonnade of arches for the central corridor. This allows you to give the cathedral a sense of being designed more thoughtfully than other levels, and also helps players navigate.

Then the generator randomly adds separating walls. Splitting walls always run straight along the axis from one wall to another. They start from the corners, thanks to which the areas are beautifully divided into rooms. In 25% of cases, the wall is a series of arches, in 25% of cases it is a series of arches with a grill that overlaps the entrance, and in all other cases it is a solid wall. In the case of gratings and solid walls, a door or arch is randomly added somewhere along the dividing wall to make the space passable.

The fill procedure detects potential themed rooms.

Stairs are placed to connect with other levels. If it is impossible to place them, the attempt to generate the dungeon begins anew.

As for the placement of mini-sets, “fixes” and replacements, I will not discuss them in detail. My favorite is PANCREAS1 mini kit, which has a 1% chance of placing a pile of bloody flesh on the floor tile. At the end, 5-10 lamp objects are placed to decorate the dungeon.

Catacombs


Unlike the Cathedral, seeming to be a projected building, the Catacombs feel much more natural. They are characterized by square rooms connected to each other by a multitude of winding corridors. In many places, instead of the doors are wide openings. increasing the likelihood that a player will be surrounded by many enemies.

To generate the catacombs, the most complex generation algorithm in the game was used. I suspect that the lack of time has forced developers to use simpler solutions in the subsequent stages.

image

Dungeon Harvesting


The procedure for creating a blank dungeon for the catacombs is quite unique. In all other stages there is a single boolean value indicating the presence or absence of the floor (plus a set of bits for additional details). Catacombs store the dungeon preparation in the form of an ASCII card, almost like a classic roguelike. And this is not particularly surprising, given the recognition of David Brevik that he drew inspiration for Diablo from Angband .

The main room generator is again a recursive algorithm, but this time a recursive partition. The CreateRoom function CreateRoom called for the entire dungeon area of ​​40 × 40 minus 1 border tile.

CreateRoom function



If there is a ready-made fragment on the map, then it will always be in the first room created by CreateRoom, and its dimensions are made such that it contains this fragment.

After calls to CreateRoom an ASCII array is obtained, similar to the one shown below (thanks nomdenom for extracting it from the code):

 A##B #..# A####B #..# #....# #..# #....# C##E #....# C####EA#####B #.....# #.....# A########B #.....# #........# #.....# #........# C#####E #........# A#B #........# #.# #........# #.# #........# #.# #........# #.# #........# #.# C########E #.# #.# A#BC#E #.# A####B #.# #....# #.# #....# #.# #....# #.# C####E #.# #.# A#####BC#E #.....# A###B #.....# #...# #.....# C###E #.....# C#####E 

In this case, the “root” room, originally created, was the lowest.

Then, the previously collected information on corridors is applied. A line is drawn between each recorded pair of points. When it crosses a wall, a 'D' written, and when it crosses a solid tile, ',' . Corridors have a random width: 1, 2, or 3. Corner tiles are used to help navigate.

If the doors are next door to another door, then they are skipped, and the corridors can overlap each other, which allows you to hide the simplicity of the generator.

After recording all the corridors, the ASCII card is cleared. Corner tiles become wall tiles, and tiles next to ',' also become walls. Finally, the characters ',' are replaced by the characters '.' . So we get the dungeon plan shown below.

 #### #..# ###### #..# #....# #..# #....# #D## #....# #.# #D#### ##D#### #..# #.....# #..# ###.....# ##D######## #.D.....# #........# #.#.....# #........# ### #.##D#### #........# #.### #.##...# #........###.D.# #.##...# #........##..#.# #.##...# #........##..#.# #.##...# #........##..#.# #.##...# #........##..#.# #.##...# ###D#######..#.# #.##...# #.......#..#.# #.###D## ###.....#..### #.# #.# #######D##.# #.# #.# #....#.# #.# #.# #....D.# #.# #.# #....###### #.# #.# ##D####....## #.# #.# #...........# #.# #.# ####....###D####.# ### ######.....##.# #####.D.....##.# #...D.#.....D..# #####.#.....#### ######### 

As you can see, this procedure can leave quite a lot of empty space on the map. Therefore, the next step of the generator is “filling the voids”. He searches for any adjacent wall segments to which additional floor rectangles can be glued. Rectangles can be at least 5 × 5 and not more than 12 × 14.

Void filling continues until a minimum of 700 tiles is reached, or the retry limit is reached. If the generator failed to reach 700 tiles, the dungeon generation starts from scratch.

This gives us the dungeon preparation shown below.

  ########## #........# ########### #........# #.........# #........# #.........# #........# #.........# #### #........# #.........# #..# #######..###.........# #..# #..............# #..# #..............# #D## #..............# #.# #D####.........# ##D#### #..# ########### #.....# #..# ###.....# ##D######## #.D.....# #........# #.#.....# ########........# ### #.##D#### #...............# #.### #.##...# #...............###.D.# #.##...# #...............##..#.# #.##...# #...............##..#.# #.##...# #...............##..#.# #.##...# #...............##..#.# #.##...# #......###D#######..#.# #.##...# #......# #.......#..#.# #.###D## #......# ###.....#..### #.# #.# #......#########D##.# #.# #.# #.........# #....#.# #.# #.# #.........# #....D.# #.# #.# #.........# #....###### #.# #.# #.........# ##D####....## #.# #.# #.........# #...........# #.# #.# #.........# ####....###D####.# ### #.........# ######.....##.# #.........# #####.D.....##.# #.........# #...D.#.....D..# ########### #####.#.....#### ######### 

Underground


Again, the catacombs are different from the standard formula levels. Instead of the marching squares algorithm (which assigns tiles based on squares of 2 Ă— 2 dungeon blanks), their own pattern matching procedure is used, which examines each of the 3 Ă— 3 rectangles of the dungeon blanks and selects the corresponding tile for the dungeon. Patterns determine what each dungeon blank tile should be: wall, floor, door, solid tile, or a combination of both. I do not quite understand why.

The rest of the function will seem familiar to you from the Cathedral. Stairs are placed on the map up and down, the dungeon is checked for the possibility of complete passage, and various corrections are made. As described in the “General Features” section, rooms are inserted, and then a number of mini-sets and tile replacements are inserted.

I think that mini-kits somehow affect the doorways, but they are pretty boring to read without tools, so I didn’t go deep into this topic.

Caves


Caves levels are filled with vast open spaces and lava rivers. The walls of this area are rough and wavy. The only rectangular components here are wooden platforms, which apparently appeared later than the caves themselves.

This stage is one of the most beautiful in the game thanks to animated lava. It is felt that it is very different from the previous levels made up of rooms. Therefore, I was very surprised that the main part of the generation is modeled according to the principles of the Cathedral stage, and the cards are given a more “caveman” look using tricky tricks.

image

Dungeon Harvesting


Creating a cave level begins with one random 2 Ă— 2 room located somewhere near the center of the map. Then the generator calls DRLG_L3CreateBlock procedure for each edge of this block.

At drawing at this level of rectangles usual filling is not used. The entrails are always solid, but each tile on the border has a 50% chance of becoming a floor, and otherwise remains solid.

DRLG_L3CreateBlock procedure



Although this procedure is similar to L5roomGen , using a much smaller block size and drawing rough edges gives it a much more organic look. In addition, it does not include a border in 1 tile for walls, therefore, unlike previous generators, it can create loops.

After creating a rough outline of a dungeon form, the generator applies erosion procedures:


All these procedures add more floor tiles, so the map becomes more open.

If it has less than 600 floor tiles, then the map is generated anew.

Underground


The dungeon blank is converted into tiles using marching squares. Unlike Cathedral and Catacombs, tilesets are much more convenient here, and contain almost all the combinations required for marching squares. There are no tiles for diagonally opposite walls, but they were eliminated in the blank phase, so they never appear.

Then, as usual, there is a lot of mini-sets. In the code there are several mini-sets that define a separate section of the walls and replace it with stalagmites and a floor, which increases the openness of the spaces.

Lava lakes are added. The algorithm searches for an adjacent section of the walls using a fill . If he manages to find a section of walls / solid tiles less than 40 tiles, completely surrounded by floor tiles, then they are replaced by lava. If the lake of lava is impossible to find, the generation of the dungeon begins anew.


A wall of 3 Ă— 3 turns into a lake of lava

Then add some lava rivers. The generator is making several attempts to paint a river starting from the lake of lava and ending in the wall. The following requirements are made to the river: it must not cross itself, the river has a length of 7-100 tiles, and there must be a suitable place on it to house the bridge. Bridge Tile ensures that the entire map remains passable. If space is available, up to four rivers can be added to the map.

Then themed rooms are placed. At this stage, the walls of thematic rooms are wooden fences, through which it is impossible to pass, but you can watch. Wooden fences are also used in the two following procedures. The first one installs a fence on all the remaining sections of the walls that have fairly long straight sections. The second draws a line of fences on the map, from one wall to the opposite, and then inserts a door. Unlike the procedure for generating a cathedral, it does not look for angles to begin creating these walls.

Hell


Hell is the last stage of Diablo. It focuses on the monsters, and level design goes into the background. This stage has the smallest tileset of all, and most of it is used for huge stairs and pentagrams. Hellish levels usually consist of several square rooms and have a symmetrical scheme.

image

Dungeon Harvesting


Hell generation begins with a random room of 5-6 tiles on each side (more if there is a ready-made fragment for the quest), and then the same recursive budding is used as in Cathedral. However, generation is limited to an area of ​​20 × 20.

A vertical and horizontal corridor is added, stretched to the edge of the 20 Ă— 20 area.

Then the dungeon blank is mirrored horizontally and vertically to get full size.

Underground


The dungeon's blank is again converted into tiles using marching squares. Then, similarly to the creation of the Cathedral, walls are added, as in the Catacombs and Caves.

Conclusion


I really enjoyed reading this code. Although there are obviously bugs in it, the names are assigned randomly, and some parts cannot be recreated in high-quality source code, it is clearly visible that this code has undergone a serious battle test and is full of clever ideas.

Here are the most important lessons for me:

Source: https://habr.com/ru/post/460038/


All Articles