Skip to main content

What are Modular Dungeons?

Modular dungeons are a dungeon system introduced in BetterStructures 2.0.0 and later. Instead of spawning as one fixed schematic, modular dungeons are dynamically assembled from smaller, handcrafted modules. Modules can form multi-floor dungeons with different layouts on each generation.


How are modular dungeons generated​

  • Modules Each module is typically built at a configurable size (often 16x16x16 blocks, while some content packs use 32x16x32 or other dimensions). Modules are designed with standardized connection points using a border tag system so they can attach to other modules without gaps or overlaps. The dimensions for a dungeon are set using the moduleSizeXZ and moduleSizeY configuration options in the module generator config. Modules can contain corridors, rooms, staircases, or special encounter spaces.

  • Generation process When a modular dungeon generates in a new chunk, BetterStructures uses a Wave Function Collapse (WFC) algorithm to procedurally assemble modules from the available pool. The system randomly selects compatible modules based on border tag matching and "stitches" them together into a complete dungeon. Generation time depends on the radius, vertical range, module pool, constraints and server performance. Layout calculation runs asynchronously; the queued paste shares the configured server-tick time budget. Large or incompatible layouts can still be expensive or fail to assemble.

  • Dungeon size Dungeons can be configured to generate at various sizes using the radius parameter. Larger radius values create significantly bigger dungeons that can span hundreds of blocks. Most dungeons include multiple floors, linked through stair modules or vertical shafts. Vertical connections between floors are achieved through the up/down entries of a module's borders border-tag map: a module's top face joins to a neighbour above it when their opposing border tags match.


Variety and pack integration​

The content catalog includes Dungeoneering Modules Free and Dungeoneering Modules Premium. The shipped dungeoneering generator lists starting schematics from both packs. The catalog also includes Tommyknockers Modules Free and Tommyknockers Modules Premium, a modular mine guarded by EliteMobs bosses; Premium requires the Free pack. Install the modules and dependencies specified by the pack, including any required FreeMinecraftModels content. Adding compatible modules expands the available layouts; installing a schematic alone does not install its companion model or mob content.


Entity and container markers​

Module generation reads the front text of signs and discards blank lines. Write the lowercase marker on the first line and any arguments directly below it. Marker matching is case-sensitive, except for [elitemobs]. The modular paste removes all sign blocks, including ordinary decorative signs and unsupported markers.

Sign tagAdditional linesBehavior
[spawn]Bukkit entity type on line 2Spawns a persistent vanilla entity centred on the sign's block. Dedicated modular worlds also record this location as a spawn marker for integrations.
[chest]NonePlaces a chest and fills it using the module generator's treasureFile.
[trapped_chest]NonePlaces a trapped chest and fills it using the module generator's treasureFile.
[pool: poolname]NonePicks a boss from an EliteMobs spawn pool and spawns it centred on the sign's block. In existing terrain the boss is a one-time EliteMobs regional boss. In a dedicated modular world, see the spawn pool section for how regular and instanced bosses are handled.
[elitemobs]EliteMobs boss filename on the following lines, joined without separatorsOnly in modules pasted into existing terrain (isWorldGeneration: false): spawns a one-time EliteMobs regional boss centred on the sign's block. Dedicated modular worlds ignore it.
[barrel]NoneIn a dedicated modular world, records a barrel location for an integration to materialize with spawnBarrels(). It does not itself place and fill a barrel.
[exit]Up and down component names on lines 2 and 3, without .schemRecords exit locations in a dedicated modular world. An integration must place the components from components/. If either argument line is missing, both names fall back to genericelevator_up and genericelevator_down.

The [barrel] and [exit] markers are processed only when isWorldGeneration: true. Use isWorldGeneration: false for dungeons pasted into existing terrain; [spawn], [chest], [trapped_chest], [pool: poolname] and [elitemobs] work there. Boss signs in existing terrain need EliteMobs enabled; without it, BetterStructures logs one warning per dungeon and spawns no bosses. Modules do not create WorldGuard arena protection. For ordinary loot barrels, place actual barrel blocks in the module and configure barrel loot below.

Spawn Pools​

[pool: poolname] signs read from spawn pool configuration files in the spawn_pools/ folder. Each spawn pool file is a small YAML config with these fields:

FieldDefaultDescription
isEnabled—Not read. Spawn pools have no enable switch.
poolStrings[]List of EliteMobs boss filenames eligible for this pool
minLevel-1Stored but not read.
maxLevel-1Level given to instanced bosses from this pool. Set an explicit valid level.

The handler requires EliteMobs and selects a random boss filename from poolStrings. It appends the generator's spawnPoolSuffix before .yml: a suffix _hard selects poolname_hard.yml. A generator without spawnPoolSuffix looks up poolname.yml. Avoid trailing spaces in the pool name. A missing pool file, an empty poolStrings list or a boss filename EliteMobs does not know logs a warning and spawns nothing for that sign.

In a dedicated modular world, non-instanced bosses spawn during the paste. Instanced bosses are queued until an integration calls spawnInstancedEntities(). Each one spawns at the pool's maxLevel, with an EliteMobs instance difficulty ID picked by how far it sits from the dungeon's starting point. The distance is the larger of the X and Z offsets, measured against the dungeon's width, (2 × radius − 1) × moduleSizeXZ: under a sixth of that width gives 2, under a third gives 1, and anything further out gives 0. useGradientLevels does not affect this.


Model and mob integrations​

The modular paste and the ordinary structure paste use different sign handlers. The modular paste handles [elitemobs] signs only in existing terrain, as listed above. The [mythicmobs] and [custom] structure signs documented in Creating structures are not handled by the modular paste. Use the supported module markers above and follow the content pack's dependency instructions.

A schematic does not by itself recreate arbitrary plugin-owned model entities. Install any model content and companion plugins required by your chosen pack; do not rely on a missing boss automatically becoming a decorative model.


Module Dimensions​

Important: Module size is configurable, not fixed.

Each module generator defines its standard module size using:

  • moduleSizeXZ: Width and depth in blocks (set explicitly to match the schematic)
  • moduleSizeY: Height in blocks (set explicitly to match the schematic)

Common size: 16×16×16 blocks; configure it explicitly Common alternative: 32×16×32 blocks (used in some content packs)

For module creators:

  1. Check your generator config for moduleSizeXZ and moduleSizeY values
  2. Build ALL modules for that generator to match these exact dimensions
  3. Check that the actual schematic dimensions and connection points match; the generator does not validate their geometry for you

Setting custom dimensions: In your module generator config file:

moduleSizeXZ: 32  # Width and depth
moduleSizeY: 16 # Height

Note: All modules in a generator must use the same dimensions. You cannot mix 16×16×16 and 32×16×32 modules in the same dungeon system.


Module Generator Config Fields​

Beyond moduleSizeXZ and moduleSizeY, the module generator config supports the following fields:

FieldDefaultDescription
isEnabledtrueAllows natural selection. The manual command does not check this switch.
radiusWrite explicitlyRadius in module-grid cells, whose horizontal size is moduleSizeXZ. The outer boundary cells are reserved by the lattice.
edgesfalseStored but not read by the current generator. Edge placement uses world_border tags.
startModule—List of starting .schem filenames. One is chosen at random, with rotation 0, at grid position (0, 0, 0).
minChunkY0Inclusive minimum vertical module-grid index, not a world block Y coordinate.
maxChunkY0Inclusive maximum vertical module-grid index. Include grid layer 0 in this range.
moduleSizeXZWrite explicitlyWidth and depth of modules in blocks
moduleSizeYWrite explicitlyHeight of modules in blocks
debugfalseDraws diagnostic blocks and text in the destination world. Use only in a disposable generation area.
useGradientLevelsfalseStored but not read by the current spawn-pool calculation.
spawnPoolSuffix—Suffix appended to spawn pool names. Leave it out or set "" for none.
isWorldGenerationfalsetrue creates a dedicated void world; false pastes into the existing world.
treasureFile—Treasure configuration file for chests in this dungeon
generateLootInBarrelstrueWhether barrels in modules should be filled with loot
barrelTreasureFilenametreasure_barrel_food.ymlTreasure configuration file used to fill barrels in this dungeon
validWorldsempty (all worlds)List of valid world names where the dungeon can generate
validWorldEnvironmentsempty (all environments)List of valid world environments (NORMAL, NETHER, THE_END, CUSTOM). The shipped dungeoneering generator explicitly ships with [NORMAL, CUSTOM], so out of the box dungeons only generate in overworld-type worlds. See the note below for what happens when the key is missing.
centerModuleAltitude0Starting Y coordinate for natural generation into existing terrain. Grid layer 0 is offset upward by half moduleSizeY. Manual placement in an existing world starts at the player's position; a dedicated world starts at (0, 0, 0).

Missing keys use the current field value from the generator definition. Premade generators initialize those values for their own content. A hand-written generator starts with 0 for radius, moduleSizeXZ, and moduleSizeY; those zeroes may also be written into the file. Do not assume a missing size becomes 16. Missing validWorlds and validWorldEnvironments impose no restriction, so omission does not limit a generator to NORMAL/CUSTOM. Start from a complete working generator and explicitly set the radius, dimensions, starting modules, and world restrictions.


Per-Module Config Fields​

Each individual module can define the following configuration fields:

FieldDefaultDescription
isEnabledtrueWhether this module is enabled
weight100Selection weight for this module during generation. A negative or non-finite value logs a warning and becomes 0, which removes the module from selection.
repetitionPenalty0Added to selection weight for each already selected adjacent module with the same schematic filename. Positive values encourage repetition; negative values reduce it. Keep the resulting weight positive.
noRepeatfalseExcludes this candidate next to a selected module with the same configuration identity, including clones. Does not enforce one occurrence per dungeon.
enforceVerticalRotationfalseRequires matching Y-axis rotations for modules above/below one another when either module enables it.
enforceHorizontalRotationfalseRequires matching Y-axis rotations for horizontal neighbors when either module enables it.
minY-4Inclusive minimum module-grid layer for candidate selection.
maxY20Inclusive maximum module-grid layer for candidate selection.
biomedefaultStored but not used to restrict module selection.
minecraftBiomenullStored but not used to restrict module selection.
treasureFile—Loaded as module configuration, but marker chests in the current modular paste use the module generator's treasureFile.
generateLootInBarrelstrueWhether barrels in this module should be filled with loot
barrelTreasureFilenametreasure_barrel_food.ymlTreasure configuration file used to fill barrels in this module
borders—Border tag map defining connection compatibility
cloneConfig—Clone settings from another module config file
compoundModule—Stored but not used to assemble compound modules by the current generator.
isAutomaticallyPlacedtrueAllows ordinary candidate selection. A configured starting module bypasses this filter.

Define all six borders entries: north, south, east, west, up and down, each containing a list of tags. Ordinary tags match exactly, including case. For example, corridor matches corridor; no-mirror_corridor matches corridor but cannot connect to another no-mirror_corridor. Each module receives four Y-axis rotations: 0, 90, 180 and 270 degrees.

nothing permits an empty neighbor. world_border marks modules for the outermost placeable horizontal ring; every outward-facing side must contain only world_border tags. Use nothing for closed top/bottom boundaries. A radius of 5 allows up to 9 × 9 horizontal module cells, with the surrounding boundary left empty. These rules filter ordinary candidates; the initial module is placed directly, so its configuration and geometry must suit the origin.

Note on cloneConfig: point it at another module's .yml filename and this module reads every cloned setting from that file instead of its own. When the clone target resolves, BetterStructures deletes the now-redundant keys from this module's file on disk - treasureFile, barrelTreasureFilename, generateLootInBarrels, borders, minY, maxY, weight, repetitionPenalty, the two rotation toggles, noRepeat, the six *IsPassable keys and isAutomaticallyPlaced. isEnabled, biome, minecraftBiome, cloneConfig and compoundModule remain in the file. Runtime compound-module lookup follows the clone target as well. If the named file does not exist the clone setting is ignored with a warning and the module keeps its own values.

You will also see northIsPassable, southIsPassable, eastIsPassable, westIsPassable, upIsPassable and downIsPassable written into generated module configs. They default to true and nothing in the generator reads them - changing them has no effect. Use borders instead.

Do not create a cloneConfig cycle. A cycle such as a.yml -> b.yml -> a.yml stops module loading with a "Cyclic module clone configuration" error that names the chain.


Generating a dungeon manually with /bs generateModules​

/bs generateModules <ModuleGeneratorsConfigFile.yml> runs a module generator on demand instead of waiting for one to appear during world generation. Tab completion lists every loaded module generator file.

What happens depends on the generator's isWorldGeneration setting:

  • isWorldGeneration: true - BetterStructures creates a brand new world for the dungeon. The world is named after the generator file with _0, _1, _2... appended, picking the first suffix whose folder does not already exist. It is a void world with NORMAL environment, spawn chunks not kept in memory and autosave disabled. The command puts the player into spectator mode but does not teleport them into the new world.
  • isWorldGeneration: false - generation happens in the world you are standing in.

A boss bar tracks layout assembly and disappears once the paste is queued. The assembly message does not mean the blocks or entities have finished pasting. If no configured starting schematics are installed, generation stops; a disabled starting module can also cause generation to cancel because its container is unavailable.

The command requires a player with betterstructures.generatemodules and is refused during a content reload. Manual generation bypasses natural placement checks, including generator enablement, world restrictions and footprint protection. Choose the destination deliberately.

Where module files live​

Modules go in plugins/BetterStructures/modules, which is scanned recursively - a content pack can keep its .schem files in subfolders and the matching .yml configs are generated alongside them.

Module schematic filenames must be unique across the whole modules tree. Duplicate filenames or an unreadable schematic abort module loading. Schematics are read before checking isEnabled, so disabling a module does not bypass a corrupt file. The folder of a content package with isEnabled: false is skipped entirely, so its modules are neither read nor loaded.

Natural dungeon placement and protection​

Natural generation checks the complete configured footprint. Placement is skipped if it overlaps the configured protection radius around world coordinates (0, 0), a named WorldGuard region, or generated chunks not recently observed as new. Immediately before queuing the paste, it checks WorldGuard again and rejects the run if an initially ungenerated chunk has since generated. This is a chunk-state check, not a scan for player-built blocks or other protection plugins. Creating a new chunk beside an old base does not make the whole footprint eligible.

Keep naturally selected generators on isWorldGeneration: false. The natural selector does not exclude generators with true; that setting still creates a separate world instead of pasting into the checked terrain.

Blocks, block entities, loot, entities and marker processing share the configured paste time budget. WorldGenerationFinishEvent is emitted after a successful dedicated-world paste and its immediate post-processing. It does not mean an integration has placed exits or spawned queued instanced bosses. Large builds can take several ticks after layout calculation completes.

Module loot precedence​

Marker chests and trapped chests use the module generator's treasureFile. For actual barrel blocks, both the generator and the module must allow generateLootInBarrels. The module's nonempty barrelTreasureFilename wins over the generator's value; generated module files default to treasure_barrel_food.yml. Both chest and barrel fills emit the cancellable ChestFillEvent before applying their inventory snapshots.

Use chest markers for generated chest loot. Ordinary chest, trapped-chest and barrel inventories from the schematic are not preserved by the current modular paste. Barrier blocks are skipped; bedrock markers become stone only where the destination is not solid. Air carves interiors in existing worlds and is skipped in dedicated void worlds.

Entities saved in a module, such as armor stands, paintings and item frames, are pasted with it. Paintings and item frames turn with the module's rotation so they stay attached to their wall.