Kenabil's Map Generator
About the Map Generator
The map generator software is open source, and the project is hosted on Bitbucket:
https://bitbucket.org/spirangle-studio/wu-map-generator/
Download the latest version of the map generator software here:
https://awakening.spirangle.net/download/mapgen-1.0.0.zip
Wurm Online forum thread:
https://forum.wurmonline.com/index.php?/topic/165809-map-generator-flavor-alternative/
The map generator builds a Wurm Unlimited terrain. It does not build a server. The run writes five mesh
files and a set of pictures under ./maps/<mapName>/, relative to the directory you started it from.
Players, items, zones, and the rest of a world are not in those files.
The window title is Map Generator for Wurm Unlimited, version 1.0.0. The first argument is the properties
name, without .properties. If you omit it, the name is mapgen, and the file read is mapgen.properties
in the working directory. A missing file is logged and is not replaced by the defaults. A value that
does not parse stops the load at that line.
Create ./maps/<mapName>/ before the first render. The heightmap picture is written before the world
directory is created, and that write does not reliably create the folder.
Do not point it at a world you want to keep. The first time a view has to open the map, the five mesh files in that folder are deleted and created again. Quick Map does this as soon as it shows the terrain. Only Render Map writes the finished terrain back out.
Map size must be a power of two from 1024 to 32768. Those are the sizes the world files can be created at. Anything else will not match the grid being generated.
Two buttons
Nothing starts while the progress bar is short of 100.
Quick Map reloads the properties file, generates a new heightmap, overwrites heightmap.png, and
drops two dirt passes. It does not import an existing heightmap. It does not erode, fracture, place ore,
or run biomes. It does not save a playable map. It is a shape preview, and it will replace a heightmap
you edited.
Render Map does not reload the properties. Restart after editing them, or run Quick Map once, which does reload, before Render Map. The full pass is:
- If
./maps/<mapName>/heightmap.pngexists and is square and the right size, that picture is the terrain. Otherwise, the heightmap is generated and that picture is written. - The rock is eroded, then fractured.
- Dirt is dropped, using
dirtDropCount. - Dirt is eroded.
- Ore is rolled.
- If
biomemap.pngis present, it is painted, and then biomes are generated. - Pictures are written, and the mesh files are saved.
The heightmap picture is not rewritten after erosion. A second Render Map imports the same picture and
erodes it again from that original. Delete heightmap.png when you want a new shape. Set erosionIterations
to 0 and erosionFracture to 0 when you want the picture left alone. Those two settings are shared
with the dirt pass, so this also skips dirt erosion. There is no separate switch.
Tree and bush ages are rolled from the clock at save time. They are not part of the seed. Two renders from the same properties will not have the same ages.
Seeds and the name
heightSeed 0 and an empty mapName take the seed from the clock and use that number as the name. The log
prints it. Keep that number if you want the same terrain again.
heightSeed 0 and a name set the seed from the name's hash. The same name is the same seed, including a
negative hash.
A non-zero heightSeed with an empty name uses the seed as the name.
normalizeSeed, fractureSeed, and biomeSeed of 0 copy the height seed. Set any of them to its own value
to change only that pass. Ore and biomes both start from biomeSeed. The plant rolls and the ore rolls do
not depend on each other.
Height
Every height setting is in the same unit. 0 is the bottom of the range, mapHeight is the top. The default
range is 4096. The water line is waterHeight, default 500. In the mesh, that line is height 0. A tile below
it is stored as a negative height, which is water. The marker's Height number is this same unit, so a shore
tile reads about 500. The height stored in the file is that number minus waterHeight.
mapHeight is also the vertical scale written into the file. Extreme values are what the map code warns
will crash a server. The default sits well inside a signed short.
The shape is simplex noise, heightMapIterations octaves, default 10. The first three octaves use
mapResolution. The rest use landscapeResolution. A larger resolution is a longer wave and broader
hills. A smaller one is choppier. The fallback for both, and for biomeResolution, is 256, which is
the default map size divided by 8. Changing mapSize does not change that fallback. If you change the
size and want the same kind of hills, set the resolutions yourself.
heightMapCompression, default 25, is applied after the octaves. The range is refit to its highest
point, run through a curve that pushes the body of the land down, and the peaks are left standing.
Higher compression does more of that. It is not a percentage of flatness.
moreLand, default true, keeps low noise and shifts it up, instead of cutting it off at zero on
every octave. More of the range ends above the water line. Set it to false and lows are clipped on
every pass. Only the string true is true.
On the last octave, and only when the heightmap is generated rather than imported, the outer
heightMapMinEdge tiles are forced to zero. Default 64. Inside that, height is faded down across
mapSize / heightMapBorderWeight tiles. Default weight 6, so on a 2048 map the fade is about 341
tiles. Normalization and erosion run after this and can lift the faded band. The hard empty strip
is the outer minimum edge. An imported heightmap skips the fade entirely.
heightMapPeaks is not a count of mountains. It is only used by fracture. Strong fracture starts
at height 1 - peaks/100. Default 20 means height 0.80 and above. Raising it brings that band down.
It does not turn fracture off. erosionFracture 0 does.
What a 16-bit height picture contains
The exported heightmap.png is 16-bit grayscale, one pixel per tile, 0 low and 65535 high. That is
the format to edit and put back. A color PNG can be read, but the height is taken from the packed
bytes, so a round trip is the grayscale file. It must be square and exactly mapSize on a side,
or it is refused and a new heightmap is generated instead.
Dirt
Dirt is not a heightmap. It is a count of passes over the rock.
On a slope, less dirt is accepted. The drop to the steepest neighbor is multiplied by cliffRatio,
default 2.5, and compared with the average of maxDirtSlope and maxDirtDiagSlope. At the defaults
that average is 60, so a neighbor drop of 24 or more accepts no dirt, a drop of 12 accepts half
of dirtDropCount, and flat rock accepts all of it. That is why cliffs stay bare. cliffRatio
does not change the rock.
Nothing is added once the combined height passes dirtHeight, default 3300. The last few passes
also taper off just below that.
landSlide, default false, leaves the dirt on the tile that earned it. Set it to true and each bit
walks downhill until no neighbor is lower by maxDirtSlope on a side, or maxDirtDiagSlope on a
diagonal. The walk uses the biome seed. It can run a long way. Leave it off unless you want that.
The passes run at the same time, one thread per pass. The count on a tile is about dirtDropCount,
not a guaranteed stack.
Where dirt does land, the type is taken from height:
| Combined height | Type |
|---|---|
| 0 dirt | Rock |
| Below the water line | Dirt |
Water line up to tundraHeight |
Grass |
tundraHeight up to the packed line |
Tundra |
Packed line up to snowHeight |
Packed dirt |
snowHeight and above |
Snow |
The packed line is not a setting. If snow is above tundra, it sits two-thirds of the way from tundra to snow. Defaults: water 500, tundra 3000, packed about 3594, snow 3900.
Dirt stops at 3300, which is below the snow line. Rock that already sits between 3300 and 3900 never receives dirt and stays rock. Snow above 3900 is applied afterwards, and a bare peak is given one dirt so the snow has something to sit on. Tundra is only where dirt actually climbed into that band. It is not a clean belt around the mountains.
On save, a tile whose rock is higher than its surface is raised to the rock, and a tile with no dirt on any corner is written as rock. A surface type with no dirt depth does not survive the save.
Erosion
erosionMinSlope and erosionMaxSlope are in the same height units. Default 30 and 300. On the rock
pass, a neighbor drop inside that range moves material downhill. Gentler than 30 does not move. Steeper
than 300 does not move, which is what leaves cliffs. erosionMaxSediment, default 100, scales how much
moves per pass. erosionIterations, default 30, is how many passes.
The dirt pass uses the iteration count and ignores the slope and sediment settings. Any lower dirt neighbor can take dirt, including a drop of one unit, and the search may step across up to three rock tiles to find one. Snow and bare tiles are skipped. Raise the minimum slope to protect rock detail. It will not protect dirt detail.
Fracture runs after the rock erosion and before the dirt. erosionFracture, default 20, is the amount of
random vertical noise, five times stronger inside the peaks band and half strength outside it. The map
is refit to the range afterwards. 0 skips it.
Ore
Each ore value is a percentage of cave tiles, not a weight. The roll is 0 to 100, and the percentages are subtracted in this order: ordinary wall, iron, gold, silver, zinc, copper, lead, tin, adamantine, glimmersteel, marble, slate, sandstone, rock salt. Whatever is left is ordinary wall as well. Ordinary wall is not a setting. It is 100 minus the sum of the ore values. If the sum is over 100, ore generation stops and the log says the values are out of range.
Defaults sum to 2.7, so 97.3% of the cave is empty wall. Iron is 1%. Gold is 0.05%. Adamantine and glimmersteel are 0, and do not appear until you set them.
A wall tile is given 40 to 59 mining actions. Any vein or stone type is given 90 to 15089. The whole cave layer is solid. No tunnel, no entrance, and no open cavern is written. The cave picture is that solid layer.
Blue on the cave picture is rock below the water line. It is the same tint the surface pictures use. It does not mean the cave is flooded.
| Color on the cave picture | What was written |
|---|---|
| Dark gray | Cave wall |
| Dark red | Iron |
| Dark yellow | Gold |
| Light gray | Silver |
| Near white | Zinc |
| Green | Copper |
| Dark pink | Lead |
| Gray | Tin |
| Cyan | Adamantine |
| Bright yellow | Glimmersteel |
| White | Marble |
| Black | Slate |
| Dark orange | Sandstone |
| Bright cyan | Rock salt |
| Pink | Open cave, or a type this generator does not write |
Biomes
Biome noise uses biomeResolution, default 256, same rule as the height resolutions. Climate is the long
wave. The environment value is a shorter wave. Both are centered on zero. The log prints the ranges from
the last quarter of the map at the end of the pass.
Temperature is computed from position, distance from the center, those two waves, and height. High ground is pulled toward cold. The halfway lines of the map are generated as four quarters, so a seam in plant cover along the middle is expected. The outer two tiles keep whatever the dirt pass left there.
These four settings are read and do nothing. The code that would have used them is commented out:
biomeDesertTempbiomeSteppeTempbiomeTundraTempbiomeGlacierTemp
Sand, steppe, tundra, snow, and packed dirt are not placed by those temperatures. Tundra, packed dirt, and snow come from the dirt pass, as above. Sand, steppe, clay, peat, and tar come from the rules below, on tiles the dirt pass left as grass, tree, or bush.
A tile is treated as land only when it and its east, south, and southeast neighbors are all on or above the water line. Otherwise the underwater rules apply.
biomeBeaches, default 50, is a threshold on the environment wave, not a width in tiles. 50 puts the
threshold at 0. 0 makes it about −0.125. 100 makes it about 0.125. Higher means more sand.
biomeClayPits moves a different threshold a short distance. Default 50 is 0.10. The useful range of
the setting is about 0.075 to 0.125. Higher means slightly more clay.
On land that is still grass, tree, or bush:
- Sand, if the surface is under 80, the slope is under 30, and the environment value is under the beach threshold.
- Steppe, if the surface is from 5 to 84 and the environment value is just above that threshold. At the default this is a thin strip, not a climate.
- Clay, if the surface is under 180, the slope is under 20, and the environment value is over the clay threshold.
- Peat, if the surface is from 200 to 1199, the slope is under 25, climate is under −0.2, and environment is under −0.11.
- Tar, in the same height and slope band, if climate is over 0.2 and environment is over 0.11.
- Moss, only on a generic tree tile, surface from 5 to 999, slope under 20, climate under −0.2, environment over 0.105.
Slope here is the height difference to the neighboring corner, in the same units as waterHeight. It is
not maxDirtSlope and not the erosion slope. A limit of 20 is flat ground. Peat, tar, and clay are flat
pockets for that reason. Those cutoffs are not settings.
Underwater:
- Sand if the bottom is no more than 200 below the water line and the environment value is under the beach threshold.
- Clay in that same depth band if the environment value is over the clay threshold.
- Reed if the bottom is no more than 20 below the line, the neighbors are also shallow, and either the climate pocket matches or a 5% roll hits.
- Kelp if the bottom is between 10 and 50 below the line, the neighbors stay below the line, and either the climate pocket matches or a small roll hits.
- Otherwise dirt.
Marsh is not generated. It only appears if the paint file places it. A marsh tile turns nearby sand and dirt into clay.
A hole deeper than 10, surrounded by dirt, is filled to about 95% before the types are chosen.
The paint file
./maps/<mapName>/biomemap.png is read at the start of the biome pass, if it is there. It is not overwritten.
The picture written at the end is biomes.png, which is a different palette. Do not feed biomes.png back
in as the paint file. The view colors will not be recognized.
The paint file must be square, exactly mapSize, and 24-bit or 32-bit. Any other color is ignored. It only
paints tiles the dirt pass left as grass, with one exception: marsh is also accepted on other types, and
only if the tile is within 7 height units below the water line. Rock, snow, tundra, and packed dirt are
not paintable. The grass band is the land between the water line and tundraHeight.
| Color | Type |
|---|---|
#66FF66 |
Grass |
#009900 |
Generic tree |
#33FF33 |
Generic bush |
#FFFF00 |
Sand |
#00FF00 |
Steppe |
#FFCC99 |
Dirt |
#CC9966 |
Packed dirt |
#999999 |
Clay |
#663300 |
Peat |
#333333 |
Tar |
#99FF00 |
Moss |
#00FF99 |
Marsh |
#FF00FF |
Mycelium |
#FFFFFF |
Snow |
#CCCCCC |
Tundra |
#FF9900 |
Lava |
Above water, a painted type other than generic tree or generic bush is kept. Tree and bush pixels still go through the plant roll, and the land rules above can turn them into sand, clay, peat, tar, or moss first. Below water, the underwater rules can still replace what you painted. Lava is left alone. Mycelium is kept on land. It does not produce mycelium trees. Trees written by this generator are normal trees.
Without this file there are no generic tree or bush tiles. The dirt pass never makes them.
Plants
biomePlantDensity, default 40, is a percentage, and it does not mean the same thing on every tile.
On grass, only a twentieth of that percentage is planted. At 40, that is 2% of grass tiles. The other 98% stay grass. This is the only planting a render does if you did not paint tree or bush pixels.
On a painted generic tree or bush pixel, the full percentage becomes a specific species. At 40, 60% of those pixels are turned back into grass. Half a tenth of the percentage becomes a bush where a tree was painted, or a tree where a bush was painted. The rest of the percentage becomes the species matching what you painted.
biomeFlowerDensity, default 30, is the percentage of grass that still is grass which receives a
flower, type 1 through 7, equal odds. Flowers are not placed on tiles that became a tree or a bush.
Grass is written at medium growth.
Five percent of tundra becomes lingonberry. That roll is fixed. Density does not scale it.
Which species are allowed is the temperature band. These three settings are the ones that actually select plants. Defaults are subtropical 12, Mediterranean 8, temperate 5.
- Above subtropical: olive, lemon, orange, cedar, camellia, grape, oleander, thorn.
- Above Mediterranean, up to subtropical: those, plus maple, chestnut, walnut, apple, cherry, birch, linden, oak, willow, rose, lavender.
- Above temperate, up to Mediterranean: birch, chestnut, linden, maple, oak, walnut, willow on the warmer side of the environment wave; pine, fir, and birch on the colder side. Bushes are thorn, rose, lavender, hazelnut, raspberry, blueberry.
- At or below temperate: pine, fir, birch, blueberry, thorn.
Cedar prefers a positive environment value. Pine and fir prefer a negative one. The roll inside a band is weighted, not even. Nothing in the cold band is oak, fruit, or grape. Nothing above the subtropical line is pine or fir.
Ages are every foliage age the game defines, including sprout and overaged, picked at save from the clock. The grass stage under a tree or bush is medium. No fruit bit is set.
When the mesh is written, each tile that is not rock, tree, or bush paints its type onto the eight tiles around it, and trees and bushes are written after that, on their own tile. A one-tile paint can be covered by a neighbor, and it also spills onto its neighbors. Leave a margin around a feature you care about.
The window
The bar has to be at 100 before a button does anything. The log is the right-hand pane. The memory line is the percent of the JVM's max heap in use. A large map needs a larger heap. The generator will not pick one for you.
The view buttons do not regenerate anything.
- View Heightmap is the rock, before dirt, as gray. It needs a heightmap.
- View Map is the isometric dump, water tinted. It needs dirt.
- View Topo is flat tile color with a black contour every 250 height units. The interval is not a setting. Water is tinted.
- View Biomes is the generator's own colors, not the paint palette. A flower shifts the grass pixel. It needs dirt.
- View Cave needs ore to have been generated.
- View Errors stays hidden until a line on the error stream contains
xceptionor starts with a tab.
Drag pans. The wheel zooms toward the cursor. Shift zooms faster. Right-click toggles a marker. The label
is tile, player, height. Player is the tile times 4, the in-game meter coordinate for that tile. Tile Y
is measured from the bottom of the picture. Height is the generator unit, the same one as waterHeight.
Grid draws that many cells across the map. Default 8. It is a count, not a tile size. Enter commits the field. It has no effect on the map.
For a map of 32768, the topographic and cave pictures are a random 16384 window, not the whole map. The isometric dump is the whole map. At 16384 and below, the pictures are the whole map.
Files
| File | What it is |
|---|---|
heightmap.png |
Input and output. 16-bit gray. Generated only when missing. Quick Map overwrites it. |
biomemap.png |
Optional paint input. Not overwritten. |
map.png |
Isometric dump. |
topography.png |
Contours every 250, water shown. |
cave.png |
Solid cave layer, water tint shown. |
biomes.png |
View palette. Not a paint file. |
top_layer.map |
Surface type and height. |
rock_layer.map |
Rock height. |
map_cave.map |
Solid cave wall and vein type. |
resources.map |
Mining actions on that vein. |
flags.map |
Created and not filled in. |
saveChanges is the point at which the five mesh files hold the finished terrain. A render stopped after
the first map view may already have wiped them.
Properties
Missing keys fall back to these. A bad value does not.
mapName=
mapSize=2048
mapHeight=4096
mapResolution=256
landscapeResolution=256
heightSeed=0
normalizeSeed=0
fractureSeed=0
heightMapIterations=10
heightMapMinEdge=64
heightMapBorderWeight=6
heightMapCompression=25
heightMapPeaks=20
erosionIterations=30
erosionMinSlope=30
erosionMaxSlope=300
erosionMaxSediment=100
erosionFracture=20
dirtDropCount=60
maxDirtSlope=50
maxDirtDiagSlope=70
waterHeight=500
cliffRatio=2.5
dirtHeight=3300
snowHeight=3900
tundraHeight=3000
moreLand=true
landSlide=false
biomeSeed=0
biomeResolution=256
biomeDesertTemp=19.0
biomeSteppeTemp=17.0
biomeTundraTemp=1.8
biomeGlacierTemp=-1.0
biomeSubtropicalTemp=12.0
biomeMediterranTemp=8.0
biomeTemperateTemp=5.0
biomePlantDensity=40
biomeFlowerDensity=30
biomeBeaches=50
biomeClayPits=50
oreIron=1.0
oreGold=0.05
oreSilver=0.1
oreZinc=0.15
oreCopper=0.2
oreLead=0.2
oreTin=0.2
oreAddy=0.0
oreGlimmer=0.0
oreMarble=0.2
oreSlate=0.2
oreSandstone=0.2
oreRocksalt=0.2
What it will not do
It will not reload properties for Render Map, and it will not leave an existing mesh file in that folder
alone. It will not treat a color screenshot of the biome view as a paint file. It will not paint rock,
snow, tundra, or packed dirt from biomemap.png.
It will not place desert, steppe, tundra, or snow from the four temperature settings that are currently
unused. It will not grow a forest from biomePlantDensity alone. Without painted tree or bush pixels,
that setting plants about a twentieth of itself on grass, and the rest of the grass gets flowers only.
It will not dig a cave, write an entrance, or leave an open cavern. It will not write a deed, a kingdom flag, an item, or a player. It will not reproduce tree ages from the seed. It will not skip rock erosion and still erode dirt. It will not honor the erosion slope settings on dirt.
It will not keep a one-tile painted feature from being covered by the neighbor smear on save, and it will not keep a surface type that has no dirt under it.