.. Universal Map Generator documentation master file, created by sphinx-quickstart on Wed Aug 26 19:56:39 2020. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Universal Map Generator ======================= UMG is a scripting language dedicated to procedural generation of grid-based maps. .. image:: screen1.png It is based on a number of 'generators' that can be composed in a similar way to HTML or to how widgets are used to build UI applications. When a UMG script is run, its output is a map in which each cell contains an ordered list of tokens. The interpretation of these tokens is up to the consumer of the map, typically they will represent some physical objects. Each time a script is run, it can generate a different, random map, with the degree of randomness dependent on the script's logic. The UMG software can also render an ASCII representation of the map, seen above, but it only serves as a quick preview of the output, with only the top token being shown. Basics ====== The most basic UMG script fills the entire map with a single token: .. code-block:: umg Set("floor") Note that the size of the map is not encoded in the script, but passed in to UMG when the script is run. Therefore the script can generate maps of various sizes, much like a HTML document adjusts itself to the size of the browser window. Every script consists of a single top-level generator. In the above example, it is a single *Set* command, but a generator can also be formed by a number of sub-generators chained together, which are executed in that order. .. code-block:: umg { Set("floor") Set("wall") Remove("wall") } Many generators take sub-generators as arguments and execute them in sub-areas of the map. This script will surround a floor-filled level with a wall of width 1. .. code-block:: umg { Set("floor") Border(1, Set("wall")) } Here, *Border* is a generator that takes two arguments: the width of the border, and the generator that is executed on the border. So whatever map this script is executed on, it will place a wall on its outer-most cells. It is possible to place a number of sub-generators of given sizes inside a map, which is useful for placing buildings or rooms. .. code-block:: umg Place( minSize = {3 3} maxSize = {6 6} generator = Border(1, Set("wood_wall")) count = 3 minSpacing = 1 ) The above script will randomly place three wooden buildings with height and width between 3 and 6. *Place* always makes sure that its sub-generators don't overlap. Generators ========== None ~~~~ Does nothing. Set ~~~ .. code-block:: umg Set("token1", "token2", "token3") Sets given tokens. They are added to the back of the list of tokens at each cell. Remove ~~~~~~ .. code-block:: umg Remove("token1", "token2", "token3") Removes given tokens from all cells. SetFront ~~~~~~~~ .. code-block:: umg SetFront("token") Adds a given token to the front of the list of tokens. Reset ~~~~~ .. code-block:: umg Reset("token1", "token2", "token3") Removes all previous tokens and sets given tokens. Filter ~~~~~~ .. code-block:: umg Filter(Predicate, Generator, [GeneratorIfNot]) # Last argument is optional Runs the given generator, but only on cells that satisfy *Predicate*. See :ref:`Predicates` If *GeneratorIfNot* is present, it is run on all the rest of the cells. Example: .. code-block:: umg Filter(On("floor"), Set("treasure")) Margin ~~~~~~ .. code-block:: umg Margin(Width, BorderGenerator, InsideGenerator) Margin([TOP, BOTTOM, LEFT or RIGHT], Width, BorderGenerator, InsideGenerator) Takes two generators, one is run on the border of given width, the other on the rest of the area. With the *Position* argument present, the margin is present only on the given side. Example: .. code-block:: umg Margin(TOP, 3, Set("water"), Margin(TOP, 10, Set("sand"), Set("grass")) SplitH ~~~~~~ .. code-block:: umg SplitH(Ratio, LeftGenerator, RightGenerator) Splits the area horizontally according to *Ratio*, and runs the corresponding generators. SplitV ~~~~~~ .. code-block:: umg SplitV(Ratio, TopGenerator, BottomGenerator) Splits the area vertically according to *Ratio*, and runs the corresponding generators. Position ~~~~~~~~ .. code-block:: umg Position( [MIDDLE, LEFT_CENTER, RIGHT_CENTER, TOP_CENTER, BOTTOM_CENTER, MIDDLE_V, MIDDLE_H], Size, Generator, MinSize, # Optional. If Size is none, then it is randomly picked between MinSize and MaxSize MaxSize # Optional. ) Places the given generator in the area in the chosen position. In the options *MIDDLE_V* and *MIDDLE_H*, the sub-area's size is stretched vertically or horizontally to the whole area. In the other cases the size is fixed. Example: .. code-block:: umg # Places a 10x10 lake in the middle Position(MIDDLE, {10 10}, Set("lake")) # Places a wall of width between 2 and 4 running vertically through the middle. Position( MIDDLE_V, none, Set("wall"), {2 0}, {4 0} ) Place ~~~~~ .. code-block:: umg Place( Size, Generator, Count, Predicate, # Optional. MinSize, # Optional. If Size is none, then it is randomly picked between MinSize and MaxSize MaxSize, # Optional. minSpacing # Optional. ) Place( (---as above---), . . (---as above---) ) Run the sub-generator at a given number of randomly picked areas that satisfy *Predicate*. The second version takes multiple sub-generators with identical arguments, enclosed in brackets. Example: .. code-block:: umg # Fills the level with rock and places 3-7 rooms of size 5-10, a minimum of two tiles apart. { Set("rock") Place(none, Reset("floor"), {3 7}, True, {5 5}, {10 10}, 2) } .. code-block:: umg # Similar to the above, but also with an extra 4x4 lava lake. Place( ({4 4}, Reset("lava"), 1, True) (none, Reset("floor"), {3 7}, True, {5 5}, {10 10}, 2) ) NoiseMap ~~~~~~~~ .. code-block:: umg NoiseMap( (Lower, Upper, Generator), . . (Lower, Upper, Generator) ) Generates a height map using Perlin noise and runs generators according to given altitude range. The total range spans from 0 to 1. Example: .. code-block:: umg NoiseMap( (0.0, 0.3, Set("water")), (0.3, 0.4, Set("sand")), (0.4, 0.8, Set("grass")), (0.8, 1.0, Set("mountain)) ) Choose ~~~~~~ .. code-block:: umg Choose( [Probability1] Generator1, # The probabilities are optional, defaults to uniform distribution . . [ProbabilityN] GeneratorN ) Runs a randomly chosen generator among specified. Example: .. code-block:: umg Choose( 0.3 Set("water"), 0.7 Set("lava") ) Connect ~~~~~~~ .. code-block:: umg Connect( ToConnectPredicate, Cost, EntryPredicate, EntryGenerator ) Connect( ToConnectPredicate, (Cost1, EntryPredicate1, EntryGenerator1), . . (CostN, EntryPredicateN, EntryGeneratorN) ) Runs a pathfinding algorithm to connect all of the grid cells specified by *ToConnectPredicate*. By default, the cost of traversing each cell is 1, but if the cell matches an *EntryPredicateX*, then it will be traversed with cost *CostX*, and *EntryGeneratorX* will be executed on that cell. Example: Fills the map with rock; places a 10x10 lake in the middle, and five 4x4 rooms; connects the rooms with cost of digging the rock equal to two, and cost of crossing the water equal to five. .. code-block:: umg { Set("rock") Position(MIDDLE, {10 10}, Set("water")) Place(5, Reset("floor"), Not On("water"), {4 4}) Connect(On("floor"), (2, On("rock"), Reset("corridor")), (5, On("water"), Reset("bridge")) ) } Repeat ~~~~~~ .. code-block:: umg Repeat(N, Generator) Runs *Generator* *N* times. FloodFill ~~~~~~~~~ .. code-block:: umg FloodFill(Predicate, Generator) Starting from cells matching *Predicate*, flood-fills the entire map, and runs *Generator* on the filled tiles. Predicates ========== A predicate returns either true or false for a given cell. On ~~ .. code-block:: umg On("token") Returns true, if cell contains token. Not ~~~ .. code-block:: umg Not Predicate Negates the result of *Predicate*. True ~~~~ Always returns true. And ~~~ .. code-block:: umg And(Predicate1, ..., PredicateN) Returns true, if all sub-predicates return true. Or ~~ .. code-block:: umg Or(Predicate1, ..., PredicateN) Returns true, if at least one sub-predicate returns true. Chance ~~~~~~ .. code-block:: umg Chance(Probability) Returns true randomly, according to *Probability*. Area ~~~~ .. code-block:: umg Area(Radius, Predicate, [MinCount]) # MinCount is optional, by default set to 1. Returns true if at least *MinCount* cells within *Radius* satisfy *Predicate*. Example: Runs a cellular automaton algorithm according to ``_. .. code-block:: umg { Filter(Chance(0.45), Set("cell_added")) Repeat(5, { Filter(Area(1, On("cell_added"), 5), Set("cell_added2")) Filter(On("cell_added2"), Set("cell_added"), Remove("cell_added")) Remove("cell_added2") }) } Macros ====== Macros are a useful way to organize and reuse code. You can find a short tutorial here: ``_. Using UMG in KeeperRL ===================== It is possible to use UMG to design new maps and level in KeeperRL. The game uses a mapping to translate from tokens to in-game level generator actions, such as placing furniture, items or creatures. The generators are defined in the file *random_layouts.txt*, for example: .. code-block:: umg "village" { Set("grass") ... } "castle" { Margin(1, Set("castle_wall"), Set("castle_floor")) Place({1 1}, Choose(Set("sword"), Set("potion")), {5 10}) ... } The mapping is defined in *layout_mapping.txt*, for example: .. code-block:: umg "default_mapping" { "grass" Place "GRASS" "castle_wall" Place "CASTLE_WALL" "castle_floor" Place "FLOOR_STONE1" "sword" Items 1 "Sword" "potion" Items 1 Potion Heal FLESH } You can use your layout for an enemy, by referencing it in *enemies.txt*. For example: .. code-block:: umg "ORC_VILLAGE" { settlement = { type = RandomLayout { "village" {20 15} "default_mapping" } ... } ... } You can use multiple mappings to reskin your levels in various ways. .. code-block:: umg "super_level_mapping" inherit "default_mapping" { "castle_wall" Place "MY_SUPER_WALL" "sword" Items 1 "MySuperSword" } .. code-block:: umg "SUPER_VILLAGE" { settlement = { type = RandomLayout { "village" {20 15} "super_level_mapping" } ... } ... } Testing layouts using the command line ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you open cmd.exe on Windows or a terminal window on Mac or Linux, you can generate a chosen layout in KeeperRL and draw its ASCII representation. For example: .. code-block:: umg ./keeper --layout_name "castle" --layout_size 20:15 Your tokens must be defined in the file *data_free/glyphs.txt*, otherwise they will not be rendered. Example glyphs.txt: .. code-block:: umg "grass" ~ green "castle_wall" # gray "castle_floor" . gray "sword" ) blue "potion" ! red