Abstract
The Dungeons, generally, is a rougelike feature of ArcadiusMC, similar to games like Hades, and Pressure (Roblox), with ULTRAKILL serving as inspiration as well.
Players will enter the randomly-generated dungeons, fight through rooms of enemies until they find a boss room, fight the boss and then advance on to the next level.
The purpose of this document is to outline the methods of implementation for the dungeons.
Dungeon Structure
The structures that will comprise the Dungeons' levels will be made with the "Arcadius-Structures" plugin. This plugin is developed in house and provides support for "Structure Functions" which the dungeon generator can later use to add elements to levels that did not exist before.
The Dungeons is made up of "Room Pieces", which are connected to
eachother with "Doorway Pieces". This should be simple to understand,
rooms are connected with doorways. These doorways are demarked with the
dungeons/connector
structure functions.
Each dungeon piece can have 1 parent piece, an entrance, that it originates from, and many child pieces, the room's exits. This forms the Dungeons as a node-tree structure.
Generation
This section describes the generation process of the dungeon levels.
To even begin, the generator takes in a config that specifies parameters that will be referenced in the coming sections. This config also specifies a list of "Structure Types". With each denoting a structure name and kind.
List of Structure Kinds:
ROOT
- The root room, from which all other rooms are generated from.
MOB_ROOM
- A regular room that is intended for combat, looting. The main room of action for players
BOSS_ROOM
- Rooms that a boss is meant to spawn in.
CONNECTOR
- A hallway, stairway, or any other type of room that is meant as a transitional space.
CLOSED_GATE
- A closed doorway between two rooms.
DECORATED_GATE
- A closed and decorated doorway between two rooms.
OPEN_GATE
- An open doorway for connecting 2 Room Pieces.
Initial Tree Generation
NOTE: This is a messy and incomplete, but general, explanation of the generation process.
This section will walk through the process of generating the initial tree structure of a dungeons level.
For the generation process, a Queue will be used to perform generation in a breadth-first pattern. This queue will contain objects that contain:
originDoor: doorway
- The Doorway the generation piece is being placed at.
sectionType: enum
- The type of the section, one of:
connector
, orroom
- sectionDepth: int
- The amount of pieces in this section placed one after another.
First, the generator is initialized by picking a ROOT
room at
random. If no root room found, generation fails. All the doorways of that
root room are then placed into the generation queue. Initialization is now
finished and the generator enters a loop that continues until the structure
has finished generating, with each loop being considered a "step".
For each step: Poll the first element in the generation queue, if the queue is empty, generation is finished, stop step. Else move onto the genStep. Depending on the result of the gen step, the following happens:
SUCCESS
- The generated piece is attached to the doorway it was generated for. If the
piece is a
connector
, all exits are kept open, otherwise, a random amount of exits betweenconfig.minRoomExits
andconfig.maxRoomExits
are closed.All exits still open are then added to the end of the generation queue.
FAILED
- The
originDoor
is closed and this branch of the structure tree is considered "complete", MAXDEPTH
- If the piece's section depth is less than the "optimal" depth (A random value between the section's min depth and max depth) then rewind the generation back to the root piece of the section and restart generation from there.
MAX_SECTION_DEPTH
- Flip section type and place back into the queue, this time to the front.
Repeat until generation queue is empty. Then move onto structure optimization.
genStep
If the current total structure depth is greater than allowed by the config,
return a MAXDEPTH
. If the current section's depth is greater
than specified by the config, return a MAX_SECTION_DEPTH
error.
Get a list of all potential pieces for this context. If current section's
type is connector
, then only get connector pieces. Drop all room
types that have already been used in the current section. If no rooms have
been found, or if all possible rooms have already been tried, return a
FAILED
error.
Otherwise, iterate through each room type and each doorway of each room.
Randomize iteration order of the doors. For each doorway, align the room by
that doorway to the originDoor
and then check if the room would
overlap any existing room pieces. If not, return a SUCCESS
result. Otherwise, keep iterating. If no rooms match, return a
FAILEd
error.
Structure Optimization
This is a small set of operations performed to make the structure better for players and add missing pieces.
- Attach boss room
- The first post processing pass is to add a boss room. If no boss room types were specified in the config, do not perform this pass. Otherwise, find all room exits that are not connected to any other rooms, sort them by order and iterate through them. Try placing a boss room at each exit, if a valid placement is not found, move onto the next one, if it is, the boss room's location is found.
- Drop dead ends
- This pass uses a loop to iterate over all
connector
type rooms in the current structure and drops them from the structure if they have no exits, or have no exits that are attached to other rooms. - Close ending gates
- Iterate over all dead end exit doorways and close them.
- Decorate gates
- Iterate over all dead end exit gates and randomly swap out closed gates with decorated gates.
Structure decoration and placement
This section outlines the processors and functions used to transform plain dungeon pieces into decorated levels.
NOTE: This is a general explanation of the process, specifics make my head hurt.
Setup
Initially we create a BlockBuffer
Passes
GenerationPasses are loaded from the config and can have any paramaters specified by the user. This section will define the types of generation passes supported and what config options they accept.
Session Flow
This section describes the process of dungeon sessions being created, playing, and closing.
Sessions are started with a player initiating the session. With the initiating player as the only player in the session. This player also becomes the owner of the session. The owner can then modify the session's settings, such as: max players in the session, dungeon settings, and any other future parameters.
When the owner of the session decides to actually start the dungeon, they initiate the generator. The dungeon then generates according to the parameter.
Session State
BEGINNING
- The beginning phase of a session. This starts when a session is created by a player. In this phase, a player can add other players to their session, modify their dungeon generation settings.
GENERATING
- The dungeon is being generated. This state starts when the owner of the session starts the generation process. When the state ends, all players are teleported inside.
ACTIVE
- Dungeon session is active, players are inside the dungeon and fighting monsters, looting and whatever.
INACTIVE
- Session is inactive.
Notes
- A BlockBuffer is an in memory representation of an area of blocks that can be edited async. This is to avoid modifying the world during generation, which is slow. BlockBuffers have a defined area of space that they can represent and they cannot be resized.