2 * Heightmap image source:
3 * Imagery by Jesse Allen, NASA's Earth Observatory,
4 * using data from the General Bathymetric Chart of the Oceans (GEBCO)
5 * produced by the British Oceanographic Data Centre.
6 * https://visibleearth.nasa.gov/view.php?id=73934
7 * https://visibleearth.nasa.gov/view.php?id=74393
8 * Licensing: Public Domain, https://visibleearth.nasa.gov/useterms.php
10 * Since the elevation does not correlate with water distribution in lower_nubia,
11 * this map additionally uses composite photography to paint the water correctly.
13 * To reproduce the heightmaps, first set the coordinates:
14 * lat=23.25; lon=31.75; width=6;
15 * lat1=$(bc <<< ";scale=5;$lat-$width/2"); lon1=$(bc <<< ";scale=5;$lon+$width/2"); lat2=$(bc <<< ";scale=5;$lat+$width/2"); lon2=$(bc <<< ";scale=5;$lon-$width/2")
17 * The land heightmap image is reproduced using:
18 * wget https://eoimages.gsfc.nasa.gov/images/imagerecords/73000/73934/gebco_08_rev_elev_C1_grey_geo.tif
19 * gdal_translate -projwin $lon2 $lat2 $lon1 $lat1 gebco_08_rev_elev_C1_grey_geo.tif lower_nubia.tif
20 * convert lower_nubia.tif -resize 512 -contrast-stretch 0 lower_nubia_heightmap.png
21 * convert lower_nubia_heightmap.png -threshold 25% lower_nubia_land_threshold.png
23 * The watermap image is reproduced using:
24 * wget https://eoimages.gsfc.nasa.gov/images/imagerecords/74000/74393/world.topo.200407.3x21600x21600.C1.jpg
25 * gdal_translate -a_srs EPSG:4326 -a_ullr 0 90 90 0 world.topo.200407.3x21600x21600.C1.jpg world.topo.200407.3x21600x21600.C1.jpg.tif
26 * gdal_translate -projwin $lon2 $lat2 $lon1 $lat1 world.topo.200407.3x21600x21600.C1.jpg.tif lower_nubia_water.tif
27 * convert lower_nubia_water.tif -set colorspace Gray -resize 512 -separate -average -threshold 51% lower_nubia_water_threshold.png
29 * No further changes should be applied to the images to keep them easily interchangeable.
32 Engine.LoadLibrary("rmgen");
33 Engine.LoadLibrary("rmgen-common");
35 TILE_CENTERED_HEIGHT_MAP = true;
37 const tSand = "desert_sand_dunes_100";
38 const tPlateau = ["savanna_dirt_a", "savanna_dirt_b"];
39 const tNilePlants = "desert_plants_a";
40 const tCliffUpper = ["medit_cliff_italia", "medit_cliff_italia", "medit_cliff_italia_grass"];
41 const tRoad = "savanna_tile_a";
42 const tWater = "desert_sand_wet";
44 const oAcacia = "gaia/flora_tree_acacia";
45 const oTreeDead = "gaia/flora_tree_dead";
46 const oBerryBush = "gaia/flora_bush_berry_desert";
48 "gaia/flora_tree_cretan_date_palm_tall",
49 "gaia/flora_tree_cretan_date_palm_short",
50 "gaia/flora_tree_palm_tropic",
51 "gaia/flora_tree_date_palm",
52 "gaia/flora_tree_senegal_date_palm",
53 "gaia/flora_tree_medit_fan_palm"
55 const oStoneLarge = "gaia/geology_stonemine_savanna_quarry";
56 const oStoneSmall = "gaia/geology_stone_desert_small";
57 const oMetalLarge = "gaia/geology_metal_savanna_slabs";
58 const oMetalSmall = "gaia/geology_metal_desert_small";
59 const oWoodTreasure = "gaia/treasure/wood";
60 const oGazelle = "gaia/fauna_gazelle";
61 const oElephant = "gaia/fauna_elephant_african_bush";
62 const oElephantInfant = "gaia/fauna_elephant_african_infant";
63 const oLion = "gaia/fauna_lion";
64 const oLioness = "gaia/fauna_lioness";
65 const oHawk = "gaia/fauna_hawk";
66 const oPyramid = "structures/kush_pyramid_large";
68 const aRock = actorTemplate("geology/stone_savanna_med");
70 "props/flora/bush_dry_a",
71 "props/flora/bush_medit_la_dry",
72 "props/flora/bush_medit_me_dry",
73 "props/flora/bush_medit_sm",
74 "props/flora/bush_medit_sm_dry",
75 "props/flora/bush_tempe_me_dry",
76 "props/flora/grass_soft_dry_large_tall",
77 "props/flora/grass_soft_dry_small_tall"
80 const heightScale = num => num * g_MapSettings.Size / 320;
82 const heightSeaGround = heightScale(-3);
83 const heightWaterLevel = heightScale(0);
84 const heightNileForests = heightScale(15);
85 const heightPlateau2 = heightScale(38);
87 const maxHeight = 150;
89 const g_Map = new RandomMap(0, tSand);
90 const mapCenter = g_Map.getCenter();
91 const mapBounds = g_Map.getBounds();
93 const clWater = g_Map.createTileClass();
94 const clCliff = g_Map.createTileClass();
95 const clPlayer = g_Map.createTileClass();
96 const clBaseResource = g_Map.createTileClass();
97 const clForest = g_Map.createTileClass();
98 const clRock = g_Map.createTileClass();
99 const clMetal = g_Map.createTileClass();
100 const clFood = g_Map.createTileClass();
101 const clPyramid = g_Map.createTileClass();
103 g_Map.log("Loading heightmaps");
104 const heightmapLand = convertHeightmap1Dto2D(Engine.LoadHeightmapImage("maps/random/lower_nubia_heightmap.png"));
105 const heightmapLandThreshold = convertHeightmap1Dto2D(Engine.LoadHeightmapImage("maps/random/lower_nubia_land_threshold.png"));
106 const heightmapWaterThreshold = convertHeightmap1Dto2D(Engine.LoadHeightmapImage("maps/random/lower_nubia_water_threshold.png"));
108 var heightmapCombined = [];
109 for (let x = 0; x < heightmapLand.length; ++x)
111 heightmapCombined[x] = new Float32Array(heightmapLand.length);
112 for (let y = 0; y < heightmapLand.length; ++y)
113 // Reduce ahistorical Lake Nasser and lakes in the valleys west of the Nile.
114 // The heightmap does not correlate with water distribution in this arid climate at all.
115 heightmapCombined[x][y] = heightmapLandThreshold[x][y] || heightmapWaterThreshold[x][y] ? heightmapLand[x][y] : minHeight;
118 g_Map.log("Copying heightmap");
120 new MapBoundsPlacer(),
121 new HeightmapPainter(heightmapCombined, minHeight, maxHeight));
123 g_Map.log("Lowering sea ground");
125 new MapBoundsPlacer(),
127 new SmoothElevationPainter(ELEVATION_SET, heightSeaGround, 3),
128 new TileClassPainter(clWater)
130 new HeightConstraint(-Infinity, heightSeaGround));
132 g_Map.log("Creating shallows...");
133 const riverAngle = Math.PI * 3 / 4;
134 for (let i = 0; i < scaleByMapSize(3, 8); ++i)
136 let x = fractionToTiles(randFloat(0, 1));
138 "start": new Vector2D(x, mapBounds.bottom).rotateAround(riverAngle, mapCenter),
139 "end": new Vector2D(x, mapBounds.top).rotateAround(riverAngle, mapCenter),
140 "startWidth": scaleByMapSize(4, 6),
141 "endWidth": scaleByMapSize(4, 6),
143 "startHeight": heightNileForests,
144 "endHeight": heightNileForests,
145 "constraint": new NearTileClassConstraint(clWater, 2)
149 g_Map.log("Smoothing heightmap");
151 new MapBoundsPlacer(),
152 new SmoothingPainter(1, scaleByMapSize(0.5, 1), 1));
154 g_Map.log("Smoothing plateau passage");
155 const passageStart = new Vector2D(263, 189).div(heightmapLand.length);
156 const passageEnd = new Vector2D(110, 350).div(heightmapLand.length);
159 new Vector2D(fractionToTiles(passageStart.x), mapBounds.top - fractionToTiles(passageStart.y)),
160 new Vector2D(fractionToTiles(passageEnd.x), mapBounds.top - fractionToTiles(passageEnd.y)),
161 scaleByMapSize(10, 40),
163 scaleByMapSize(3, 9),
166 new SmoothingPainter(4, 1, 1));
168 g_Map.log("Marking water");
170 new MapBoundsPlacer(),
171 new TileClassPainter(clWater),
172 new HeightConstraint(-Infinity, heightSeaGround));
174 g_Map.log("Marking cliffs");
176 new MapBoundsPlacer(),
177 new TileClassPainter(clCliff),
178 new SlopeConstraint(2, Infinity));
180 g_Map.log("Painting water and shoreline");
182 new MapBoundsPlacer(),
183 new TerrainPainter(tWater),
184 new HeightConstraint(-Infinity, heightWaterLevel));
186 g_Map.log("Painting plateau");
188 new MapBoundsPlacer(),
189 new TerrainPainter(tPlateau),
190 new HeightConstraint(heightPlateau2, Infinity));
193 var playerPosition = [];
196 g_Map.log("Finding player locations...");
197 [playerIDs, playerPosition] = playerPlacementRandom(sortAllPlayers(), avoidClasses(clWater, scaleByMapSize(8, 12), clCliff, scaleByMapSize(8, 12)));
199 g_Map.log("Flatten the initial CC area...");
200 for (let position of playerPosition)
202 new ClumpPlacer(diskArea(defaultPlayerBaseRadius() * 0.8), 0.95, 0.6, Infinity, position),
203 new SmoothElevationPainter(ELEVATION_SET, g_Map.getHeight(position), 6));
207 "PlayerPlacement": [playerIDs, playerPosition],
208 "PlayerTileClass": clPlayer,
209 "BaseResourceClass": clBaseResource,
210 "baseResourceConstraint": avoidClasses(clCliff, 0, clWater, 0),
212 "outerTerrain": tRoad,
213 "innerTerrain": tRoad
216 "template": oGazelle,
218 "minGroupDistance": 2,
219 "maxGroupDistance": 4,
224 "template": oBerryBush
228 { "template": oMetalLarge },
229 { "template": oStoneLarge }
234 "count": scaleByMapSize(3, 12),
243 "template": oWoodTreasure,
249 "template": pickRandom(aBushes)
253 g_Map.log("Painting lower cliffs");
255 new MapBoundsPlacer(),
256 new TerrainPainter(tNilePlants),
258 new SlopeConstraint(2, Infinity),
259 new NearTileClassConstraint(clWater, 2)
262 g_Map.log("Painting upper cliffs");
264 new MapBoundsPlacer(),
265 new TerrainPainter(tCliffUpper),
267 avoidClasses(clWater, 2),
268 new SlopeConstraint(2, Infinity)
271 g_Map.log("Creating stone mines");
274 [new SimpleObject(oStoneSmall, 0, 2, 0, 4, 0, 2 * Math.PI, 1), new SimpleObject(oStoneLarge, 1, 1, 0, 4, 0, 2 * Math.PI, 4)],
275 [new SimpleObject(oStoneSmall, 3, 6, 1, 3, 0, 2 * Math.PI, 1)]
277 avoidClasses(clWater, 4, clCliff, 4, clPlayer, 20, clRock, 10),
279 scaleByMapSize(10, 30));
281 g_Map.log("Creating metal mines");
284 [new SimpleObject(oMetalSmall, 0, 2, 0, 4, 0, 2 * Math.PI, 1), new SimpleObject(oMetalLarge, 1, 1, 0, 4, 0, 2 * Math.PI, 4)],
285 [new SimpleObject(oMetalSmall, 3, 6, 1, 3, 0, 2 * Math.PI, 1)]
287 avoidClasses(clWater, 4, clCliff, 4, clPlayer, 20, clMetal, 10, clRock, 5),
289 scaleByMapSize(10, 30));
291 g_Map.log("Creating pyramid");
293 new SimpleGroup([new SimpleObject(oPyramid, 1, 1, 1, 1)], true, clPyramid),
295 [new NearTileClassConstraint(clWater, 10), avoidClasses(clWater, 6, clCliff, 6, clPlayer, 30, clMetal, 6, clRock, 6)],
299 g_Map.log("Creating forests");
301 new SimpleGroup([new RandomObject(oPalms, 1, 2, 1, 1)], true, clForest),
304 new NearTileClassConstraint(clWater, scaleByMapSize(1, 8)),
305 new HeightConstraint(heightNileForests, Infinity),
306 avoidClasses(clWater, 0, clCliff, 0, clForest, 1, clPlayer, 12, clBaseResource, 5, clPyramid, 6)
308 scaleByMapSize(100, 1000),
311 createStragglerTrees(
312 [oAcacia, oTreeDead],
313 avoidClasses(clWater, 10, clCliff, 1, clPlayer, 12, clBaseResource, 5, clPyramid, 6),
315 scaleByMapSize(15, 400),
318 const avoidCollisions = avoidClasses(clPlayer, 12, clBaseResource, 5, clWater, 1, clForest, 1, clRock, 4, clMetal, 4, clFood, 6, clCliff, 0, clPyramid, 6);
320 g_Map.log("Creating gazelles");
322 new SimpleGroup([new SimpleObject(oGazelle, 5, 7, 2, 4)], true, clFood),
325 scaleByMapSize(2, 10),
330 g_Map.log("Creating lions");
332 new SimpleGroup([new SimpleObject(oLion, 1, 2, 2, 4), new SimpleObject(oLioness, 2, 3, 2, 4)], true, clFood),
335 scaleByMapSize(2, 10),
339 g_Map.log("Creating elephants");
341 new SimpleGroup([new SimpleObject(oElephant, 2, 3, 2, 4), new SimpleObject(oElephantInfant, 2, 3, 2, 4)], true, clFood),
344 scaleByMapSize(2, 10),
347 placePlayersNomad(clPlayer, avoidClasses(clWater, 4, clForest, 2, clRock, 4, clMetal, 4, clFood, 2, clCliff, 2));
349 g_Map.log("Creating hawk");
350 for (let i = 0; i < scaleByMapSize(0, 2); ++i)
351 g_Map.placeEntityAnywhere(oHawk, 0, mapCenter, randomAngle());
354 aBushes.map(bush => [new SimpleObject(bush, 0, 3, 2, 4)]),
355 aBushes.map(bush => scaleByMapSize(200, 800) * randIntInclusive(1, 3)),
357 new NearTileClassConstraint(clWater, 2),
358 new HeightConstraint(heightWaterLevel, Infinity),
359 avoidClasses(clForest, 0)
363 [[new SimpleObject(aRock, 0, 4, 2, 4)]],
364 [[scaleByMapSize(100, 600)]],
365 avoidClasses(clWater, 0));
368 setWaterTint(0.161, 0.286, 0.353);
369 setWaterColor(0.129, 0.176, 0.259);
371 setWaterMurkiness(0.87);
372 setWaterType("lake");
374 setTerrainAmbientColor(0.58, 0.443, 0.353);
376 setSunColor(0.733, 0.746, 0.574);
377 setSunRotation(Math.PI * 1.1);
378 setSunElevation(Math.PI / 7);
382 setFogColor(0.69, 0.616, 0.541);
386 setPPSaturation(0.42);