Overmap Generation
Overview
The overmap is what you see in the game when you use the view map (m) command.
It looks something like this:
...>│<......................│..............P.FFFF├─FF...
..┌─┼─┐.....................│..............FFFFF┌┘FFFF..
..│>│^│.....................│..............FF┌─┬┘FFFFF.F
──┘...└──┐..............>│<.│..............F┌┘F│FFFFFFF─
.........└──┐........vvO>│v.│............FF┌┘FF│FFFFFFFF
............└┐.......─┬──┼─>│<........┌─T──┘FFF└┐FFFFFFF
.............│.......>│<$│S.│<.......┌┘..FFFFFFF│FFF┌─┐F
..O.........┌┴──┐....v│gF│O.│<v......│...FFFFFF┌┘F.F│F└─
.OOOOO.....─┘...└─────┼──┼──┼────────┤....FFFFF│FF..│FFF
.O.O.....dddd.......^p│^>│OO│<^......└─┐FFFFFFF├────┴─FF
.........dddd........>│tt│<>│..........│FFF..FF│FFFFF.FF
.........dddd......┌──┘tT├──...........│FFF..F┌┘FFFFFFFF
.........dddd......│....>│^^..H........└┐...FF│FFFFFFFFF
..................┌┘.....│<.............└─┐FF┌┘FFFFFFFFF
..................│......│................│FF│FFFFFFFFFF
.................┌┘......│................│F┌┘FFFFFFFFFF
...FFF...........│......┌┘...............┌┘.│FFFFFFFFFFF
FFFFFF...........│.....┌┘................│FF│FFFFFFFFFFF
FFFFFF..FFFF.....│.....B..........─┐.....│┌─┘F..FFFFFFFF
In the context of overmap generation, the overmap is the collection of data and features that define the locations in the game world at a more abstract level than the local map that the player directly interacts with, providing the necessary context for local map generation to occur.
By example, the overmap tells the game:
- where the cities are
- where the roads are
- where the buildings are
- what types the buildings are
but it does not tell the game:
- what the actual road terrain looks like
- what the actual building layout looks like
- what items are in the building
It can sometimes be useful to think of the overmap as the outline for the game world which will then be filled in as the player explores. The rest of this document is a discussion of how we can create that outline.
Terminology and Types
First we need to briefly discuss some of the data types used by the overmap.
overmap_terrain
The fundamental unit of the overmap is the overmap_terrain, which defines the id, name, symbol, color (and more, but we’ll get to that) used to represent a single location on the overmap. The overmap is 180 overmap terrains wide, 180 overmap terrains tall, and 21 overmap terrains deep (these are the z-levels).
In this example snippet of an overmap, each character corresponds one entry in the overmap which references a given overmap terrain:
.v>│......FFF│FF
──>│<....FFFF│FF
<.>│<...FFFFF│FF
O.v│vv.vvv┌──┘FF
───┼──────┘.FFFF
F^^|^^^.^..F.FFF
So for example, the F
is a forest which has a definition like this:
{
"type": "overmap_terrain",
"id": "forest",
"name": "forest",
"sym": "F",
"color": "green"
}
and the ^
is a house which has a definition like this:
{
"type": "overmap_terrain",
"id": "house",
"name": "house",
"sym": "^",
"color": "light_green"
}
It’s important to note that a single overmap terrain definition is referenced for every usage of
that overmap terrain in the overmap—if we were to change the color of our forest from green
to
red
, every forest in the overmap would be red.
overmap_special / city_building
The next important concept for the overmap is that of the overmap_special and city_building. These types, which are similar in structure, are the mechanism by which multiple overmap terrains can be collected together into one conceptual entity for placement on the overmap. If you wanted to have a sprawling mansion, a two story house, a large pond, or a secret lair placed under an unassuming bookstore, you would need to use an overmap_special or a city_building.
These types are effectively a list of the overmap terrains that compose them, the placement of those overmap terrains relative to each other, and some data used to drive the placement of the overmap special / city building (e.g. how far from a city, should be it connected to a road, can it be placed in a forest/field/river, etc).
overmap_connection
Speaking of roads, the concept of linear features like roads, sewers, subways, railroads, and forest trails are managed through a combination of overmap terrain attributes and another type called an overmap_connection.
An overmap connection effectively defines the types of overmap terrain on which a given connection can be made, the “cost” to make that connection, and the type of terrain to be placed when making the connection. This, for example, is what allows us to say that:
- a road may be placed on fields, forests, swamps and rivers
- fields are preferred over forests, which are preferred over swamps, which are preferred over rivers
- a road crossing a river will be a bridge
The overmap_connection data will then be used to create a linear road feature connecting two points, applying the previously defined rules.
overmap_location
We’ve previously mentioned defining valid overmap terrain types for the placement of overmap specials, city buildings, and overmap connections, but one thing to clarify is these actually leverage another type called an overmap_location rather than referencing overmap_terrain values directly.
Simply put, an overmap_location is just a named collection of overmap_terrain values.
For example, here are two simple definitions.
{
"type": "overmap_location",
"id": "forest",
"terrains": ["forest"]
},
{
"type": "overmap_location",
"id": "wilderness",
"terrains": ["forest", "field"]
}
The value of these is that they allow for a given overmap terrain to belong to several different locations and provide a level of indirection so that when new overmap terrains are added (or existing ones removed), only the relevant overmap_location entries need to be changed rather than every overmap_connection, overmap_special, and city_building that needs those groups.
For example, with the addition of a new forest overmap terrain forest_thick
, we only have to
update these definitions as follows:
{
"type": "overmap_location",
"id": "forest",
"terrains": ["forest", "forest_thick"]
},
{
"type": "overmap_location",
"id": "wilderness",
"terrains": ["forest", "forest_thick", "field"]
}
Overmap Terrain
Rotation
If an overmap terrain can be rotated (i.e. it does not have the NO_ROTATE
flag), then when the
game loads the definition from JSON, it will create the rotated definitions automatically, suffixing
the id
defined here with _north
, _east
, _south
or _west
. This will be particularly
relevant if the overmap terrains are used in overmap_special or city_building definitions,
because if those are allowed to rotate, it’s desirable to specify a particular rotation for the
referenced overmap terrains (e.g. the _north
version for all).
Fields
Identifier | Description |
---|---|
type | Must be overmap_terrain . |
id | Unique id. |
name | Name for the location shown in game. |
sym | Symbol used when drawing the location, like "F" (or you may use an ASCII value like 70 ). |
color | Color to draw the symbol in. See COLOR.md. |
looks_like | Id of another overmap terrain to be used for the graphical tile, if this doesn’t have one. |
connect_group | Specify that this overmap terrain might be graphically connected to its neighbours, should a tileset wish to. It will connect to any other overmap_terrain with the same connect_group . |
see_cost | Affects player vision on overmap. Higher values obstruct vision more. |
travel_cost | Affects pathfinding cost. Higher values are harder to travel through (reference: Forest = 10 ) |
extras | Reference to a named map_extras in region_settings, defines which map extras can be applied. |
mondensity | Summed with values for adjacent overmap terrains to influence density of monsters spawned here. |
spawns | Spawns added once at mapgen. Monster group, % chance, population range (min/max). |
flags | See Overmap terrains in JSON_FLAGS.md. |
mapgen | Specify a C++ mapgen function. Don’t do this—use JSON. |
mapgen_straight | Specify a C++ mapgen function for a LINEAR feature variation. Prefer JSON instead. |
mapgen_curved | Specify a C++ mapgen function for a LINEAR feature variation. Prefer JSON instead. |
mapgen_end | Specify a C++ mapgen function for a LINEAR feature variation. Prefer JSON instead. |
mapgen_tee | Specify a C++ mapgen function for a LINEAR feature variation. Prefer JSON instead. |
mapgen_four_way | Specify a C++ mapgen function for a LINEAR feature variation. Prefer JSON instead. |
Example
A real overmap_terrain
wouldn’t have all these defined at the same time, but in the interest of an
exhaustive example…
{
"type": "overmap_terrain",
"id": "field",
"name": "field",
"sym": ".",
"color": "brown",
"looks_like": "forest",
"see_cost": 2,
"extras": "field",
"mondensity": 2,
"spawns": { "group": "GROUP_FOREST", "population": [0, 1], "chance": 13 },
"flags": ["NO_ROTATE"],
"mapgen": [{ "method": "builtin", "name": "bridge" }],
"mapgen_straight": [{ "method": "builtin", "name": "road_straight" }],
"mapgen_curved": [{ "method": "builtin", "name": "road_curved" }],
"mapgen_end": [{ "method": "builtin", "name": "road_end" }],
"mapgen_tee": [{ "method": "builtin", "name": "road_tee" }],
"mapgen_four_way": [{ "method": "builtin", "name": "road_four_way" }]
}
Overmap Special
An overmap special is an entity that is placed in the overmap after the city generation process has completed—they are the “non-city” counterpart to the city_building type. They commonly are made up of multiple overmap terrains (though not always), may have overmap connections (e.g. roads, sewers, subways), and have JSON-defined rules guiding their placement.
Placement rules
For each special, mapgen first decides how many instances it wants to place by rolling a random
number between min
and max
of its occurrences. This number will be adjusted with a few
multipliers: configured specials density, terrain ratio, and crowdedness ratio.
The “terrain ratio” is a number representing the ratio between land and lake tiles on the overmap. A 30% flooded overmap will have a 0.7 multiplier for land specials and a 0.3 multiplier for lake specials.
The “crowdedness ratio” is a number representing the ratio between the total overmap area and the expected average area required to place all specials with the current range and density settings. It is capped and normally stays at x1. However, trying to spawn more specials than physically possible will cause it to decrease.
After considering all these factors, it may result in a number such as 2.4, which means that the
overmap will have a 60% chance to place 2 instances and a 40% chance to place 3 instances of that
special. The final number will never be lower than the raw min
value of occurrences.
Once the exact amount is chosen, mapgen will search for places to spawn specials. If a special depends on a city, its instances will be distributed in the vicinity of different matching cities. For example, if mapgen wants to place three instances and the overmap has two cities of the required size, it won’t place more than two instances near the same city.
Fixed vs mutable specials
There are two subtypes of overmap special: fixed and mutable. Fixed overmap specials have a fixed defined layout which can span many OMTs, and can rotate (see below) but will always look essentially the same.
Mutable overmap specials have a more flexible layout. They are defined in terms of a collection of overmap terrains and the ways they can fit together, like a jigsaw puzzle where pieces can fit together in multiple ways. This can be used to form more organic shapes, such as tunnels dug underground by giant ants, or larger sprawling buildings with mismatched wings and extensions.
Mutable specials require a lot more care to design such that they can be reliably placed without error.
Rotation
In general, it is desirable to define your overmap special as allowing rotation—this will provide
variety in the game world and allow for more possible valid locations when attempting to place the
overmap special. A consequence of the relationship between rotating an overmap special and rotating
the underlying overmap terrains that make up the special is that the overmap special should
reference a specific rotated version of the associated overmap terrain—generally, this is the
_north
rotation as it corresponds to the way in which the JSON for mapgen is defined.
Locations
The overmap special has two mechanisms for specifying the valid locations (overmap_location
) that
it may be placed on. Each individual entry in overmaps
may have the valid locations specified,
which is useful if different parts of a special are allowed on different types of terrain (e.g. a
dock that should have one part on the shore and one part in the water). If all values are the same,
locations may instead be specified for the entire special using the top level locations
key, The
value for an individual entry takes precedence over the top level value, so you may define the top
level value and then only specify it for individual entries that differ.
Fields
Identifier | Description |
---|---|
type | Must be "overmap_special" . |
id | Unique id. |
connections | List of overmap connections and their relative [ x, y, z ] location within the special. |
place_nested | Array of { "point": [x, y, z], "special": id } with nested specials, relative to this one. |
subtype | Either "fixed" or "mutable" . Defaults to "fixed" if not specified. |
locations | List of overmap_location ids that the special may be placed on. |
city_distance | Min/max distance from a city that the special may be placed. Use -1 for unbounded. |
city_sizes | Min/max city size for a city that the special may be placed near. Use -1 for unbounded. |
occurrences | Min/max number of occurrences when placing the special. If UNIQUE flag is set, becomes X of Y chance. |
flags | See Overmap specials in JSON_FLAGS.md. |
rotate | Whether the special can rotate. True if not specified. |
Depending on the subtype, there are further relevant fields:
Further fields for fixed overmap specials
Identifier | Description |
---|---|
overmaps | List of overmap terrains and their relative [ x, y, z ] location within the special. |
Further fields for mutable overmap specials
Identifier | Description |
---|---|
check_for_locations | List of pairs [ [ x, y, z ], [ locations, ... ] ] defining the locations that must exist for initial placement. |
check_for_locations_area | List of check_for_locations area objects to be considered in addition to the explicit check_for_locations pairs. |
overmaps | Definitions of the various overmaps and how they join to one another. |
root | The initial overmap from which the mutable special will be grown. |
shared | List of multipliers defined as "id": value that can be used to scale some parts of the special. |
phases | A specification of how to grow the overmap special from the root OMT. |
Example fixed special
[
{
"type": "overmap_special",
"id": "campground",
"overmaps": [
{ "point": [0, 0, 0], "overmap": "campground_1a_north", "locations": ["forest_edge"] },
{ "point": [1, 0, 0], "overmap": "campground_1b_north" },
{ "point": [0, 1, 0], "overmap": "campground_2a_north" },
{ "point": [1, 1, 0], "overmap": "campground_2b_north" }
],
"connections": [{ "point": [1, -1, 0], "connection": "local_road", "from": [1, 0, 0] }],
"locations": ["forest"],
"city_distance": [10, -1],
"city_sizes": [3, 12],
"occurrences": [0, 5],
"flags": ["CLASSIC"],
"rotate": true
}
]
Fixed special overmaps
Identifier | Description |
---|---|
point | [ x, y, z] of the overmap terrain within the special. |
overmap | Id of the overmap_terrain to place at the location. |
locations | List of overmap_location ids that this overmap terrain may be placed on. |
Connections
Identifier | Description |
---|---|
point | [ x, y, z] of the connection end point. Cannot overlap an overmap terrain entry for the special. |
connection | Id of the overmap_connection to build. |
from | Optional point [ x, y, z] within the special to treat as the origin of the connection. |
Example mutable special
[
{
"type": "overmap_special",
"id": "anthill",
"subtype": "mutable",
"locations": ["subterranean_empty"],
"city_distance": [25, -1],
"city_sizes": [0, 20],
"occurrences": [0, 1],
"flags": ["CLASSIC", "WILDERNESS"],
"check_for_locations": [
[[0, 0, 0], ["land"]],
[[0, 0, -1], ["subterranean_empty"]],
[[1, 0, -1], ["subterranean_empty"]],
[[0, 1, -1], ["subterranean_empty"]],
[[-1, 0, -1], ["subterranean_empty"]],
[[0, -1, -1], ["subterranean_empty"]]
],
"//1": "Same as writing out 'check_for_locations' 9 times with different points.",
"check_for_locations_area": [
[ { "type": [ "subterranean_empty" ], "from": [ 1, 1, -2 ], "to": [ -1, -1, -2 ] } ]
],
"//2": "The anthill will have 3 possible sizes",
"shared": { "size": [ 1, 3 ] },
"joins": ["surface_to_tunnel", "tunnel_to_tunnel"],
"overmaps": {
"surface": { "overmap": "anthill", "below": "surface_to_tunnel", "locations": ["land"] },
"below_entrance": {
"overmap": "ants_nesw",
"above": "surface_to_tunnel",
"north": "tunnel_to_tunnel",
"east": "tunnel_to_tunnel",
"south": "tunnel_to_tunnel",
"west": "tunnel_to_tunnel"
},
"crossroads": {
"overmap": "ants_nesw",
"north": "tunnel_to_tunnel",
"east": "tunnel_to_tunnel",
"south": "tunnel_to_tunnel",
"west": "tunnel_to_tunnel"
},
"tee": {
"overmap": "ants_nes",
"north": "tunnel_to_tunnel",
"east": "tunnel_to_tunnel",
"south": "tunnel_to_tunnel"
},
"straight_tunnel": {
"overmap": "ants_ns",
"north": "tunnel_to_tunnel",
"south": "tunnel_to_tunnel"
},
"corner": { "overmap": "ants_ne", "north": "tunnel_to_tunnel", "east": "tunnel_to_tunnel" },
"dead_end": { "overmap": "ants_end_south", "north": "tunnel_to_tunnel" },
"queen": { "overmap": "ants_queen", "north": "tunnel_to_tunnel" },
"larvae": { "overmap": "ants_larvae", "north": "tunnel_to_tunnel" },
"food": { "overmap": "ants_food", "north": "tunnel_to_tunnel" }
},
"root": "surface",
"phases": [
[{ "overmap": "below_entrance", "max": 1 }],
[
"//1": "Shared multiplier 'size' will affect the size",
"//2": "of the tunnel system generated in this phase.",
{ "overmap": "straight_tunnel", "max": 10, "scale": "size" },
{ "overmap": "corner", "max": { "poisson": 2.5 }, "scale": "size" },
{ "overmap": "tee", "max": 5, "scale": "size" }
],
[{ "overmap": "queen", "max": 1 }],
[{ "overmap": "food", "max": 5 }, { "overmap": "larvae", "max": 5 }],
[
{ "overmap": "dead_end", "weight": 2000 },
{ "overmap": "straight_tunnel", "weight": 100 },
{ "overmap": "corner", "weight": 100 },
{ "overmap": "tee", "weight": 10 },
{ "overmap": "crossroads", "weight": 1 }
]
]
}
]
How mutable specials are placed
Overmaps and joins
Note: “overmap” in the following context should not be confused with “overmap level” or “overmap” as a 180x180 chunk of OMTs that the near-infinite world map is divided into.
A mutable special has a collection of overmaps which define the OMTs used to build it and joins which define the way in which they are permitted to connect with one another. Each overmap may specify a join for each of its edges (four cardinal directions, above, and below). These joins must match the opposite join for the adjacent overmap in that direction.
In the above example, we see that the surface
overmap specifies "below": "surface_to_tunnel"
,
meaning that the join below it must be surface_to_tunnel
. So, the overmap below must specify
"above": "surface_to_tunnel"
. Only the below_entrance
overmap does that, so we know that overmap
must be placed beneath the surface
overmap.
Overmaps can always be rotated, so a north
constraint can correspond to other directions. So, the
above dead_end
overmap can represent a dead end tunnel in any direction, but it’s important that
the chosen OMT ants_end_south
is consistent with the north
join for the generated map to make
sense.
Overmaps can also specify connections. For example, an overmap might be defined as:
"where_road_connects": {
"overmap": "road_end_north",
"west": "parking_lot_to_road",
"connections": { "north": { "connection": "local_road" } }
}
Once the mutable special placement is complete, a local_road
connection will be built from the
north edge of this overmap (again, ‘north’ is a relative term, that will rotate as the overmap is
rotated) in the same way as connections are built for fixed specials. ‘Existing’ connections are not
supported for mutable specials.
Layout phases
After all the joins and overmaps are defined, the manner in which the special is laid out is given
by root
and phases
.
root
specifies the overmap which is placed first, at the origin point for this special.
Then phases
gives a list of growth phases used to place more overmaps. These phases are processed
strictly in order.
Each phase is a list of rules. Each rule specifies an overmap and an integer max
and/or
weight
.
Weight must always be a simple integer, but max
may also be an object defining a probability
distribution over integers. Each time the special is spawned, a value is sampled from that
distribution. Poisson distribution is supported via an object such as { "poisson": 5 }
where 5
will be the mean of the distribution (λ). Flat distribution is supported via [min, max]
pairs.
max
can also be scale
d by another multiplier shared
within the special, allowing to scale
amounts of multiple different overmaps proportionaly to each other. Each shared
multiplier can be
defined as a probability distribution same way as the max
value, and it is also sampled once on
special placement.
Within each phase, the game looks for unsatisfied joins from the existing overmaps and attempts to find an overmap from amongst those available in its rules to satisfy that join. Priority is given to whichever joins are listed first in the list which defines the joins for this special, but if multiple joins of the same (highest priority) id are present then one is chosen at random.
First the rules are filtered to contain only those which can satisfy the joins for a particular
location, and then a weighted selection from the filtered list is made. The weight is given by the
minimum of max
and weight
specified in the rule. The difference between max
and weight
is
that each time a rule is used, max
is decremented by one. Therefore, it limits the number of times
that rule can be chosen. Rules which only specify weight
can be chosen an arbitrary number of
times.
If no rule in the current phase is able to satisfy the joins for a particular location, that location is set aside to be tried again in later phases.
Once all joins are either satisfied or set aside, the phase ends and generation proceeds to the next phase.
If all phases complete and unsatisfied joins remain, this is considered an error and a debugmsg will be displayed with more details.
Chunks
A placement rule in the phases can specify multiple overmaps to be placed in a particular configuration. This is useful if you want to place some feature that’s larger than a single OMT. Here is an example from the microlab:
{
"name": "subway_chunk_at_-2",
"chunk": [
{ "overmap": "microlab_sub_entry", "pos": [0, 0, 0], "rot": "north" },
{ "overmap": "microlab_sub_station", "pos": [0, -1, 0] },
{ "overmap": "microlab_subway", "pos": [0, -2, 0] }
],
"max": 1
}
The "name"
of a chunk is only for debugging messages when something goes wrong. "max"
and
"weight"
are handled as above.
The new feature is "chunk"
which specifies a list of overmaps and their relative positions and
rotations. The overmaps are taken from the ones defined for this special. Rotation of "north"
is
the default, so specifying that has no effect, but it’s included here to demonstrate the syntax.
The postions and rotations are relative. The chunk can be placed at any offset and rotation, so long as all the overmaps are shifted and rotated together like a rigid body.
Techniques to avoid placement errors
To help avoid these errors, some additional features of the mutable special placement can help you.
check_for_locations
check_for_locations
defines a list of extra constraints that are checked before the special is
attempted to be placed. Each constraint is a pair of a position (relative to the root) and a set of
locations. The existing OMT in each postion must fall into one of the given locations, else the
attempted placement is aborted.
The check_for_locations
constraints ensure that the below_entrance
overmap can be placed below
the root and that all four cardinal-adjacent OMTs are subterranean_empty
, which is needed to add
any further overmaps satisfying the four other joins of below_entrance
.
check_for_locations_area
lets you define an area to check instead of having to repeat
check_for_locations
for individual points.
into_locations
Each join also has an associated list of locations. This defaults to the locations for the special, but it can be overridden for a particular join like this:
"joins": [
{ "id": "surface_to_surface", "into_locations": [ "land" ] },
"tunnel_to_tunnel"
]
For an overmap to be placed when satisfying an unresolved join, it is not sufficient for it to satisfy the existing joins adjacent to a particular location. Any residual joins it possesses beyond those which already match up must point to OMTs with terrains consistent with that join’s locations.
For the particular case of the anthill example above, we can see how these two additions ensure that placement is always successful and no unsatisfied joins remain.
The next few phases of placement will attempt to place various tunnels. The join constraints will
ensure that the unsatisfied joins (the open ends of tunnels) will always point into
subterranean_empty
OMTs.
Ensuring complete coverage in the final phase
In the final phase, we have five different rules intended to cap off any unsatisfied joins without
growing the anthill further. It is important that the rules whose overmaps have fewer joins get
higher weights. In the normal case, every unsatisfied join will be simply closed off using
dead_end
. However, we also have to cater for the possibility that two unsatisfied joins point to
the same OMT, in which case dead_end
will not fit (and will be filtered out), but
straight_tunnel
or corner
will, and one of those will likely be chosen since they have the
highest weights. We don’t want tee
to be chosen (even though it might fit) because that would lead
to a new unsatisfied join and further grow the tunnels. But it’s not a big problem if tee
is
chosen occasionally in this situation; the new join will likely simply be satisfied using
dead_end
.
When designing your own mutable overmap specials, you will have to think through these permutations to ensure that all joins will be satisfied by the end of the last phase.
Optional joins
Rather than having lots of rules designed to satisfy all possible situations in the final phase, in some situations you can make this easier using optional joins. This feature can also be used in other phases.
When specifying the joins associated with an overmap in a mutable special, you can elaborate with a
type, like this example from the Crater
overmap special:
"overmaps": {
"crater_core": {
"overmap": "crater_core",
"north": "crater_to_crater",
"east": "crater_to_crater",
"south": "crater_to_crater",
"west": "crater_to_crater"
},
"crater_edge": {
"overmap": "crater",
"north": "crater_to_crater",
"east": { "id": "crater_to_crater", "type": "available" },
"south": { "id": "crater_to_crater", "type": "available" },
"west": { "id": "crater_to_crater", "type": "available" }
}
},
The definition of crater_edge
has one mandatory join to the north, and three ‘available’ joins to
the other cardinal directions. The semantics of an ‘available’ join are that it will not be
considered an unresolved join, and therefore will never cause more overmaps to be placed, but it can
satisfy other joins into a particular tile when necessary to allow an existing unresolved join to be
satisfied.
The overmap will always be rotated in such a way that as many of its mandatory joins as possible are satisfied and available joins are left to point in other directions that don’t currently need joins.
As such, this crater_edge
overmap can satisfy any unresolved joins for the Crater
special
without generating any new unresolved joins of its own. This makes it great to finish off the
special in the final phase.
Third type of joins - ‘optional’, it’s a mix of both above - they do actively generate new unresolved joins to build upon, but such joins are not mandatory, and can be left unvesolved.
Since ‘optional’ and ‘available’ joins does not require any kind of resolving they may end in undesired places, that can be preventrd by adding pseudo-join of ‘reject’ type, which will forbid other joins of same id linking to it.
Asymmetric joins
Sometimes you want two different OMTs to connect, but wouldn’t want either to connect with themselves. In this case you wouldn’t want to use the same join on both. Instead, you can define two joins which form a pair, by specifying one as the opposite of the other.
Another situation where this can arise is when the two sides of a join need different location constraints. For example, in the anthill, the surface and subterranean components need different locations. We could improve the definition of its joins by making the join between surface and tunnels asymmetric, like this:
"joins": [
{ "id": "surface_to_tunnel", "opposite": "tunnel_to_surface" },
{ "id": "tunnel_to_surface", "opposite": "surface_to_tunnel", "into_locations": [ "land" ] },
"tunnel_to_tunnel"
],
As you can see, the tunnel_to_surface
part of the pair needs to override the default value of
into_locations
because it points towards the surface.
Alternative joins
Sometimes you want the next phase(s) of a mutable special to be able to link to existing unresolved joins without themselves generating any unresolved joins of that type. This helps to create a clean break between the old and the new.
For example, this happens in the microlab_mutable
special. This special has some structured
hallway
OMTs surrounded by a clump of microlab
OMTs. The hallways have hallway_to_microlab
joins pointing out to their sides, so we need microlab
OMTs to have microlab_to_hallway
joins
(the opposite of hallway_to_microlab
) in order to match them.
However, we don’t want the unresolved edges of a microlab
OMT to require more hallways all around,
so we mostly want them to use microlab_to_microlab
joins. How can we satisfy these apparently
conflicting requirements without making many different variants of microlab
with different numbers
of each type of join? Alternative joins can help us here.
The definition of the microlab
overmap might look like this:
"microlab": {
"overmap": "microlab_generic",
"north": { "id": "microlab_to_microlab", "alternatives": [ "microlab_to_hallway" ] },
"east": { "id": "microlab_to_microlab", "alternatives": [ "microlab_to_hallway" ] },
"south": { "id": "microlab_to_microlab", "alternatives": [ "microlab_to_hallway" ] },
"west": { "id": "microlab_to_microlab", "alternatives": [ "microlab_to_hallway" ] }
},
This allows it to join with hallways which are already placed on the overmap, but new unresolved
joins will only match more microlab
s.
Testing your new mutable special
If you want to exhaustively test your mutable special for placement errors, and you are in a
position to compile the game, then an easy way to do so is to use the existing test in
tests/overmap_test.cpp
.
In that file, look for TEST_CASE( "mutable_overmap_placement"
. At the start of that function there
is a list of mutable special ids that tests tries spawning. Replace one of them with your new
special’s id, recompile and run the test.
The test will attempt to place your special a few thousand times, and should find most ways in which placement might fail.
Joins
A join definition can be a simple string, which will be its id. Alternatively, it can be a dictionary with some of these keys:
Identifier | Description |
---|---|
id | Id of the join being defined. |
opposite | Id of the join which must match this one from the adjacent terrain. |
into_locations | List of overmap_location ids that this join may point towards. |
Mutable special overmaps
The overmaps are a JSON dictionary. Each overmap must have an id (local to this special) which is the JSON dictionary key, and then the fields within the value may be:
Identifier | Description |
---|---|
overmap | Id of the overmap_terrain to place at the location. |
locations | List of overmap_location ids that this overmap terrain may be placed on. If not specified, defaults to the locations value from the special definition. |
north | Join which must align with the north edge of this OMT |
east | Join which must align with the east edge of this OMT |
south | Join which must align with the south edge of this OMT |
west | Join which must align with the west edge of this OMT |
above | Join which must link this to the OMT above |
below | Join which must link this to the OMT below |
Each join associated with a direction can be a simple string, interpreted as a join id. Alternatively it can be a JSON object with the following keys:
Identifier | Description |
---|---|
id | Id of the join used here. |
type | Either "mandatory" or "available" . Default: "mandatory" . |
alternatives | List of join ids that may be used instead of the one listed under id , but only when placing this overmap. Unresolved joins created by its placement will only be the primary join id . |
Generation rules
Identifier | Description |
---|---|
overmap or chunk | Id of the overmap to place, chunk configuration. |
join | Id of join which must be resolved during current phase. |
z | Z level restrictions for this phase. |
om_pos | Absolute coordinates [ x, y ] of the overmap (180x180 chunk of the world) this phase is allowed to run in. |
rotate | True or false, whether current piece can be rotated or not. Takes a priority over special’s rotatable property. |
max | Maximum number of times this rule should be used. |
scale | Id of the shared multiplier to scale the max by. |
weight | Weight with which to select this rule. |
Z level restrictions supports numbers, ["min", "mix"]
ranges with absolute coordinate
restrictions, “top""bottom” strings refering to boundaries of other tiles of special placed so far,
and objects with “top""bottom” properties and offsets from above boundaries.
One of max
and weight
must be specified. max
will be used as the weight when weight
is not
specified.
City Building
A city building is an entity that is placed in the overmap during the city generation process—they are the “city” counterpart to the overmap_special type. The definition for a city building is a subset of that for an overmap special, and consequently will not be repeated in detail here.
Mandatory Overmap Specials / Region Settings
City buildings are not subject to the same quantity limitations as overmap specials, and in fact the
occurrences attribute does not apply at all. Instead, the placement of city buildings is driven by
the frequency assigned to the city building within the region_settings
. Consult
REGION_SETTINGS.md for more details.
Fields
Identifier | Description |
---|---|
type | Must be “city_building”. |
id | Unique id. |
overmaps | As in overmap_special , with one caveat: all point x and y values must be >= 0. |
locations | As in overmap_special . |
flags | As in overmap_special . |
rotate | As in overmap_special . |
Example
[
{
"type": "city_building",
"id": "zoo",
"locations": ["land"],
"overmaps": [
{ "point": [0, 0, 0], "overmap": "zoo_0_0_north" },
{ "point": [1, 0, 0], "overmap": "zoo_1_0_north" },
{ "point": [2, 0, 0], "overmap": "zoo_2_0_north" },
{ "point": [0, 1, 0], "overmap": "zoo_0_1_north" },
{ "point": [1, 1, 0], "overmap": "zoo_1_1_north" },
{ "point": [2, 1, 0], "overmap": "zoo_2_1_north" },
{ "point": [0, 2, 0], "overmap": "zoo_0_2_north" },
{ "point": [1, 2, 0], "overmap": "zoo_1_2_north" },
{ "point": [2, 2, 0], "overmap": "zoo_2_2_north" }
],
"flags": ["CLASSIC"],
"rotate": true
}
]
Overmap Connection
Fields
Identifier | Description |
---|---|
type | Must be “overmap_connection”. |
id | Unique id. |
default_terrain | Default overmap_terrain to use for undirected connections and existance checks. |
subtypes | List of entries used to determine valid locations, terrain cost, and resulting overmap terrain. |
layout | (Optional) Connections layout, default is city . |
With city
layout each connection point will be linked to the center of closest city. With p2p
layout each connection point will be linked to the closest connection of same type.
Example
[
{
"type": "overmap_connection",
"id": "local_road",
"subtypes": [
{ "terrain": "road", "locations": ["field", "road"] },
{ "terrain": "road", "locations": ["forest_without_trail"], "basic_cost": 20 },
{ "terrain": "road", "locations": ["forest_trail"], "basic_cost": 25 },
{ "terrain": "road", "locations": ["swamp"], "basic_cost": 40 },
{ "terrain": "road_nesw_manhole", "locations": [] },
{ "terrain": "bridge", "locations": ["water"], "basic_cost": 120 }
]
},
{
"type": "overmap_connection",
"id": "subway_tunnel",
"subtypes": [
{ "terrain": "subway", "locations": ["subterranean_subway"], "flags": ["ORTHOGONAL"] }
]
}
]
Subtypes
Identifier | Description |
---|---|
terrain | overmap_terrain to be placed when the placement location matches locations . |
locations | List of overmap_location that this subtype applies to. Can be empty; signifies terrain is valid as is. |
basic_cost | Cost of this subtype when pathfinding a route. Default 0. |
flags | See Overmap connections in JSON_FLAGS.md. |
Overmap Location
Fields
Identifier | Description |
---|---|
type | Must be “overmap_location”. |
id | Unique id. |
terrains | List of overmap_terrain that can be considered part of this location. |
Example
[
{
"type": "overmap_location",
"id": "wilderness",
"terrains": ["forest", "forest_thick", "field", "forest_trail"]
}
]