kts.LayoutDungeon – generate the dungeon layout for a new game
kts.LayoutDungeon {
layout = <layout table>,
wall = <tile>,
horiz_door = <tile>,
vert_door = <tile>,
segments = <segments>,
special_segments = <segments>,
entry_type = <string>,
allow_rotate = <boolean>
}
A dungeon layout consists of a grid of up to 3x3 "cells". Each cell is labelled either block, edge or special (the meaning of these terms will be discussed below). Also, each of the four borders of each cell can be considered either "open" or "closed".
Here are some example dungeon layouts:
In the above diagram, dotted lines indicate "open" borders between cells, and solid lines indicate "closed" borders.
(Note that a "Dungeon Type" in the Knights quest selection menu might correspond to more than one possible "dungeon layout" in the sense being discussed here. For example, there are three different "Ring" dungeon layouts, and five different "Long Snakes"; the above diagram shows only one of each.)
Dungeons are made by filling in each cell of the chosen layout with a different, randomly selected "segment". A segment is a pre-designed set of dungeon rooms. For example, this is a segment:
The standard Knights data files include over 200 different segments, allowing for plenty of variety in games, and users can also add more segments by editing the data files if they wish.
Here is an example of the "Long Snake" layout shown above being filled in with some segments, to make a dungeon:
Here, the segment boundaries have been highlighted in red. Observe how each of the seven cells in the "Long Snake" dungeon layout (see previous diagram) have now been filled in with a different, randomly chosen dungeon segment. Observe also the one-square-thick borders that have been added between the segments. In cases where there is a "closed" border (solid line on the dungeon layout diagram), the border squares are completely filled with solid walls, but on the "open" borders (dotted lines on the layout), there is a mix of solid walls and (randomly placed) wooden door tiles. This allows the knights to travel between the different dungeon segments. (In a Long Snake layout, the pattern of open and closed borders is designed such that the knights have to walk a long, winding path to get from one end of the dungeon to the other. In most other layouts, most of the borders are open, allowing the knights to traverse the dungeon more easily.)
We now explain what the different cell types (block, edge and special) mean. In many cases, the cell types don't actually make any difference, and the dungeon generator just fills each cell with a randomly chosen segment, regardless of the cell type. However, there are some circumstances in which the cell types matter, as follows:
edge or special cell, if possible. Only if there are none of those cell types remaining will a block cell be used. edge cells, and each knight will start in a different edge cell. (This is why the "Away from Other" option is limited to a maximum of four players; none of the standard dungeon layouts contains more than four edge cells.) edge cell if possible, or a block cell if no edge cells are available. (Again, this entry point option is limited to four players, this time because none of the standard dungeon segments contains more than four "home" tiles.) An additional rule is that if a special cell is not filled with a "required" dungeon segment then it is removed from the layout entirely. This comes into play for the "Ring" dungeon type (which is the only dungeon type that uses the special cells currently). For example, in the "Ring" dungeon layout shown above, the centre cell is special. If the quest has at least one "required" segment (such as a guarded exit point), then this will appear in the centre of the ring, otherwise the centre of the ring will be left empty.
In Lua, a dungeon layout is represented by a table, as in the following examples:
-- Tiny Dungeon
tiny_layout = {
width = 1,
height = 1,
data = {
{ type="block" }
}
}
-- Basic Dungeon
basic_layout = {
width=2,
height=2,
data={
{ type="edge" }, { type="edge" },
{ type="edge" }, { type="edge" }
}
}
-- Long Snake Dungeon
long_snake_layout = {
width=3,
height=3,
data={
{ type="block" }, { type="edge", exits="w" }, { type="none" },
{ type="block" }, { type="block", exits="we" }, { type="block" },
{ type="none" }, { type="edge", exits="e" }, { type="block"
}
}
-- Ring Dungeon
ring_layout = {
width=3,
height=3,
data={
{type="block" }, {type="block" }, {type="special"},
{type="block", exits="ns"}, {type="block", exits="ns"}, {type="none" },
{type="block" }, {type="block" }, {type="none" }
}
}
The table contains fields width and height giving the size of the layout (in cells) and then a data field containing the cells themselves (in order, left to right then top to bottom). Each cell is itself represented by a table containing a type field, which must be set to one of the strings "block", "edge" or "special" (representing a cell of the corresponding type) or else "none" (representing a cell which is not actually part of the current layout). Each cell may also contain a field exits which is a string containing one or more of the letters "n", "e", "s" and/or "w" (for north, east, south and west) which determines the borders of the cell that are considered "open". The default (if exits is not specified) is for all borders to neighbouring cells to be considered open. Note that exits must be consistent, e.g. if one cell has an exit to the north, then the neighbouring cell just above must have an exit to the south (if this is not the case then an error message will be generated).
Calling the kts.LayoutDungeon function kicks off the dungeon generation process and places the map segments into the dungeon. The players' homes (dungeon entry points) are also assigned at this time.
Before this function is called, the dungeon should be completely empty, and also the kts.DUNGEON_ERROR variable should be nil. kts.WipeDungeon can be called beforehand, if necessary, to make sure that this is the case.
kts.LayoutDungeon takes exactly one parameter, which should be a table containing the following fields:
layout is a dungeon layout table, as described above. wall is a Tile representing a solid wall. This tile will be used to fill in the borders between dungeon segments. horiz_door and vert_door are Tiles representing horizontally and vertically aligned wooden doors. These tiles are placed (at random locations) along any "open" borders in the layout, allowing knights to travel from one segment to another. segments and special_segments are lists of dungeon segments (as returned by a previous kts.LoadSegments call). special_segments represents segments that must be included in the dungeon as part of the current quest, e.g. guarded exit segments or liche tombs etc. (Normally this will be a fairly short list, perhaps containing only one or two segments, or perhaps none at all for a simpler quest.) As described above, these segments will be placed in special or edge map cells if possible, or block cells otherwise. (If there are not enough cells in the layout for all of the special_segments to fit, then dungeon generation fails.) segments contains a list of "normal" segments that are available for the dungeon generator to use. (Normally this will be a large list, potentially containing hundreds of segments.) After all special_segments have been placed, the dungeon generator will fill every remaining block or edge cell (but not special cells) with randomly chosen segments from the segments list. The dungeon generator will take care to ensure that there are enough segments containing "home" tiles, so that all knights have entry points into the dungeon, but other than that, the dungeon generator basically just chooses at random from this list. entry_type is one of the strings "none", "close", "away" or "random" giving the method of selecting dungeon entry points ("homes") for the knights. They have the following meanings: "none" means that knights won't be given homes at all – in this case, kts.SetRespawnType must be used to set the respawn type to "different" or "anywhere" (otherwise knights won't be able to enter the dungeon and the game won't work). "close" means that knight homes will all be placed in the same dungeon segment – an edge segment if possible, or a block otherwise. "away" means that each knight home must be in a different segment. In this case all homes must also be on edge segments. "random" means that knights can be assigned to any available "home" tile in the dungeon at random. The only limit on number of players is the size of the dungeon and the number of homes that it contains. allow_rotate is a boolean indicating whether the dungeon generator is allowed to randomly rotate and/or reflect segments before placing them into the dungeon. This is optional; if it is missing (or nil) then it defaults to true. allow_rotate setting determines whether the segments themselves can be rotated and/or reflected before being placed into the dungeon. If this is enabled, then information from previous calls to kts.SetRotate and kts.SetReflect will be used to determine how to rotate and/or reflect individual dungeon tiles. None.
If the parameters to the function are invalid (for example, an incorrect dungeon layout table is given, or an invalid entry_type string is used), then a Lua error will be raised.
If the parameters are valid, but dungeon generation itself fails, no error is raised; instead, the variable kts.DUNGEON_ERROR is set to a string indicating the reason for the failure. (If dungeon generation is successful, kts.DUNGEON_ERROR will be left as nil.)
A full dungeon generation system will not only need to call kts.LayoutDungeon, but it will also need to call other functions such as kts.GenerateLocksAndTraps, kts.AddItem, kts.AddStuff, kts.AddMonsters and kts.ConnectivityCheck to populate the dungeon with items and monsters, and ensure that all rooms are accessible to players.
Also, calling the dungeon generator multiple times is sometimes necessary, as there might be cases where dungeon generation fails (just due to random chance) but retrying it succeeds. If dungeon generation fails more than, say, 25 times in a row, then it is likely that the current layout is simply not big enough to fit in everything needed for the current quest, and in that case, starting over with a larger dungeon layout is recommended.
This is all taken care of by the standard Knights Lua files; the code can be found in dungeon_setup.lua. (In particular, the function generate_dungeon in that file is the starting point for dungeon generation.)
The standard dungeon layouts ("Tiny", "Basic", "Big", "Huge", "Snake", "Ring" and so on) can be found in dungeon_layouts.lua. Note that the actual dungeon layout table is returned by the function marked func in each case (the rest of the structure in that file is internally used by the Lua code, and is not part of the data that is passed to kts.LayoutDungeon). Also note that for many of the dungeon types, there is not just one dungeon layout, but several, which are selected from at random when a game with that dungeon type is played. For example, there are three possible "Huge" dungeon layouts, one of which is selected at random whenever a quest with a "Huge" dungeon is played.