1 Engine.LoadLibrary("rmgen");
2 Engine.LoadLibrary("heightmap");
6 var numPlayers = getNumPlayers();
7 var mapSize = getMapSize();
9 // Function to apply a heightmap
10 function setReliefmap(reliefmap)
12 // g_Map.height = reliefmap;
13 for (var x = 0; x <= mapSize; x++)
14 for (var y = 0; y <= mapSize; y++)
15 setHeight(x, y, reliefmap[x][y]);
18 // Set target min and max height depending on map size to make average stepness the same on all map sizes
19 var heightRange = {"min": MIN_HEIGHT * mapSize / 8192, "max": MAX_HEIGHT * mapSize / 8192};
21 // Set average water coverage
22 var averageWaterCoverage = 1/3; // NOTE: Since errosion is not predictable actual water coverage might differ much with the same value
23 if (mapSize < 200) // Sink the waterlevel on tiny maps to ensure enough space
24 averageWaterCoverage = 2/3 * averageWaterCoverage;
26 var waterHeight = -MIN_HEIGHT + heightRange.min + averageWaterCoverage * (heightRange.max - heightRange.min);
27 var waterHeightAdjusted = waterHeight + MIN_HEIGHT;
28 setWaterHeight(waterHeight);
30 // Prepare terrain texture by height placement
31 var textueByHeight = [];
34 textueByHeight.push({"upperHeightLimit": heightRange.min + 1/3 * (waterHeightAdjusted - heightRange.min), "terrain": "temp_sea_rocks"});
36 // Medium deep water (with fish)
37 var terrains = ["temp_sea_weed"];
38 terrains = terrains.concat(terrains, terrains, terrains, terrains);
39 terrains = terrains.concat(terrains, terrains, terrains, terrains);
40 terrains.push("temp_sea_weed|gaia/fauna_fish");
41 textueByHeight.push({"upperHeightLimit": heightRange.min + 2/3 * (waterHeightAdjusted - heightRange.min), "terrain": terrains});
44 textueByHeight.push({"upperHeightLimit": heightRange.min + 3/3 * (waterHeightAdjusted - heightRange.min), "terrain": "temp_mud_a"});
46 // Water surroundings/bog (with stone/metal some rabits and bushes)
47 var terrains = ["temp_plants_bog", "temp_plants_bog_aut", "temp_dirt_gravel_plants", "temp_grass_d"];
48 terrains = terrains.concat(terrains, terrains, terrains, terrains, terrains);
49 terrains = ["temp_plants_bog|gaia/flora_bush_temperate"].concat(terrains, terrains);
50 terrains = ["temp_dirt_gravel_plants|gaia/geology_metal_temperate", "temp_dirt_gravel_plants|gaia/geology_stone_temperate", "temp_plants_bog|gaia/fauna_rabbit"].concat(terrains, terrains);
51 terrains = ["temp_plants_bog_aut|gaia/flora_tree_dead"].concat(terrains, terrains);
52 textueByHeight.push({"upperHeightLimit": waterHeightAdjusted + 1/6 * (heightRange.max - waterHeightAdjusted), "terrain": terrains});
54 // Juicy grass near bog
55 textueByHeight.push({"upperHeightLimit": waterHeightAdjusted + 2/6 * (heightRange.max - waterHeightAdjusted),
56 "terrain": ["temp_grass", "temp_grass_d", "temp_grass_long_b", "temp_grass_plants"]});
59 // var testActor = "actor|geology/decal_stone_medit_a.xml";
60 textueByHeight.push({"upperHeightLimit": waterHeightAdjusted + 3/6 * (heightRange.max - waterHeightAdjusted),
61 "terrain": ["temp_grass", "temp_grass_b", "temp_grass_c", "temp_grass_mossy"]});
63 // Long grass near forest border
64 textueByHeight.push({"upperHeightLimit": waterHeightAdjusted + 4/6 * (heightRange.max - waterHeightAdjusted),
65 "terrain": ["temp_grass", "temp_grass_b", "temp_grass_c", "temp_grass_d", "temp_grass_long_b", "temp_grass_clovers_2", "temp_grass_mossy", "temp_grass_plants"]});
67 // Forest border (With wood/food plants/deer/rabits)
68 var terrains = ["temp_grass_plants|gaia/flora_tree_euro_beech", "temp_grass_mossy|gaia/flora_tree_poplar", "temp_grass_mossy|gaia/flora_tree_poplar_lombardy",
69 "temp_grass_long|gaia/flora_bush_temperate", "temp_mud_plants|gaia/flora_bush_temperate", "temp_mud_plants|gaia/flora_bush_badlands",
70 "temp_grass_long|gaia/flora_tree_apple", "temp_grass_clovers|gaia/flora_bush_berry", "temp_grass_clovers_2|gaia/flora_bush_grapes",
71 "temp_grass_plants|gaia/fauna_deer", "temp_grass_long_b|gaia/fauna_rabbit"];
73 var numTerrains = terrains.length;
74 for (var i = 0; i < numTerrains; i++)
75 terrains.push("temp_grass_plants");
76 textueByHeight.push({"upperHeightLimit": waterHeightAdjusted + 5/6 * (heightRange.max - waterHeightAdjusted), "terrain": terrains});
79 textueByHeight.push({"upperHeightLimit": waterHeightAdjusted + 6/6 * (heightRange.max - waterHeightAdjusted),
80 "terrain": ["temp_grass_mossy|gaia/flora_tree_oak", "temp_forestfloor_pine|gaia/flora_tree_pine",
81 "temp_grass_mossy|gaia/flora_tree_oak", "temp_forestfloor_pine|gaia/flora_tree_pine",
82 "temp_mud_plants|gaia/flora_tree_dead", "temp_plants_bog|gaia/flora_tree_oak_large",
83 "temp_dirt_gravel_plants|gaia/flora_tree_aleppo_pine", "temp_forestfloor_autumn|gaia/flora_tree_carob"]});
85 var minTerrainDistToBorder = 3;
87 Engine.SetProgress(5);
89 // - Generate Heightmap
90 // - Search valid start position tiles
91 // - Choose a good start position derivation (largest distance between closest players)
92 // - Restart the loop if start positions are invalid or two players are to close to each other
93 var goodStartPositionsFound = false;
94 var minDistBetweenPlayers = 16 + mapSize / 16; // Don't set this higher than 25 for tiny maps! It will take forever with 8 players!
95 var enoughTiles = false;
97 var lowerHeightLimit = textueByHeight[3].upperHeightLimit;
98 var upperHeightLimit = textueByHeight[6].upperHeightLimit;
100 while (!goodStartPositionsFound)
103 log("Starting giant while loop try " + tries);
105 // Generate reliefmap
106 var myReliefmap = clone(g_Map.height);
107 setRandomHeightmap(heightRange.min, heightRange.max, myReliefmap);
108 for (var i = 0; i < 50 + mapSize/4; i++) // Cycles depend on mapsize (more cycles -> bigger structures)
109 globalSmoothHeightmap(0.8, myReliefmap);
111 rescaleHeightmap(heightRange.min, heightRange.max, myReliefmap);
112 setReliefmap(myReliefmap);
114 // Find good start position tiles
115 var possibleStartPositions = [];
116 var neededDistance = 7;
117 var distToBorder = 2 * neededDistance; // Has to be greater than neededDistance! Otherwise the check if low/high ground is near will fail...
119 // Check for valid points by height
120 for (var x = distToBorder + minTerrainDistToBorder; x < mapSize - distToBorder - minTerrainDistToBorder; x++)
121 for (var y = distToBorder + minTerrainDistToBorder; y < mapSize - distToBorder - minTerrainDistToBorder; y++)
123 var actualHeight = getHeight(x, y);
124 if (actualHeight > lowerHeightLimit && actualHeight < upperHeightLimit)
126 // Check for points within a valid area by height (rectangular since faster)
127 var isPossible = true;
128 for (var offX = - neededDistance; offX <= neededDistance; offX++)
129 for (var offY = - neededDistance; offY <= neededDistance; offY++)
131 var testHeight = getHeight(x + offX, y + offY);
132 if (testHeight <= lowerHeightLimit || testHeight >= upperHeightLimit)
140 possibleStartPositions.push([x, y]);
141 // placeTerrain(x, y, "blue"); // For debug reasons. Plz don't remove. // Only works properly for 1 loop
145 // Trying to reduce the number of possible start locations...
147 // Reduce to tiles in a circle of mapSize / 2 distance to the center (to avoid players placed in corners)
148 var possibleStartPositionsTemp = [];
149 var maxDistToCenter = mapSize / 2;
150 for (var i = 0; i < possibleStartPositions.length; i++)
152 if (Math.euclidDistance2D(...possibleStartPositions[i], mapSize / 2, mapSize / 2) < maxDistToCenter)
153 possibleStartPositionsTemp.push(possibleStartPositions[i]);
155 possibleStartPositions = clone(possibleStartPositionsTemp);
156 // Reduce to tiles near low and high ground (Rectangular check since faster) to make sure each player has access to all resource types.
157 var possibleStartPositionsTemp = [];
158 var maxDistToResources = distToBorder; // Has to be <= distToBorder!
159 var minNumLowTiles = 10;
160 var minNumHighTiles = 10;
162 for (var i = 0; i < possibleStartPositions.length; i++)
165 var numHighTiles = 0;
166 for (var dx = - maxDistToResources; dx < maxDistToResources; dx++)
168 for (var dy = - maxDistToResources; dy < maxDistToResources; dy++)
170 var testHeight = getHeight(possibleStartPositions[i][0] + dx, possibleStartPositions[i][1] + dy);
172 if (testHeight < lowerHeightLimit)
175 if (testHeight > upperHeightLimit)
178 if (numLowTiles > minNumLowTiles && numHighTiles > minNumHighTiles)
181 if (numLowTiles > minNumLowTiles && numHighTiles > minNumHighTiles)
184 if (numLowTiles > minNumLowTiles && numHighTiles > minNumHighTiles)
185 possibleStartPositionsTemp.push(possibleStartPositions[i]);
188 possibleStartPositions = clone(possibleStartPositionsTemp);
190 if(possibleStartPositions.length > numPlayers)
195 log("possibleStartPositions.length < numPlayers, possibleStartPositions.length = " + possibleStartPositions.length + ", numPlayers = " + numPlayers);
198 // Find a good start position derivation
201 // Get some random start location derivations. NOTE: Iterating over all possible derivations is just too much (valid points * numPlayers)
202 var maxTries = 100000;
203 var possibleDerivations = [];
204 for (var i = 0; i < maxTries; i++)
207 for (var p = 0; p < numPlayers; p++)
208 vector.push(randIntExclusive(0, possibleStartPositions.length));
209 possibleDerivations.push(vector);
212 // Choose the start location derivation with the greatest minimum distance between players
214 for (var d = 0; d < possibleDerivations.length; d++)
216 var minDist = 2 * mapSize;
217 for (var p1 = 0; p1 < numPlayers - 1; p1++)
219 for (var p2 = p1 + 1; p2 < numPlayers; p2++)
223 minDist = Math.min(minDist, Math.euclidDistance2D(...possibleStartPositions[possibleDerivations[d][p1]], ...possibleStartPositions[possibleDerivations[d][p2]]));
224 if (minDist < maxMinDist)
228 if (minDist < maxMinDist)
231 if (minDist > maxMinDist)
233 maxMinDist = minDist;
234 var bestDerivation = possibleDerivations[d];
237 if (maxMinDist > minDistBetweenPlayers)
239 goodStartPositionsFound = true;
240 log("Exiting giant while loop after " + tries + " tries with a minimum player distance of " + maxMinDist);
243 log("maxMinDist <= " + minDistBetweenPlayers + ", maxMinDist = " + maxMinDist);
247 Engine.SetProgress(60);
249 log("Painting terrain by height and add props...");
250 var propDensity = 1; // 1 means as determined in the loop, less for large maps as set below
253 else if (mapSize > 400)
256 for(var x = minTerrainDistToBorder; x < mapSize - minTerrainDistToBorder; x++)
258 for (var y = minTerrainDistToBorder; y < mapSize - minTerrainDistToBorder; y++)
260 var textureMinHeight = heightRange.min;
261 for (var i = 0; i < textueByHeight.length; i++)
263 if (getHeight(x, y) >= textureMinHeight && getHeight(x, y) <= textueByHeight[i].upperHeightLimit)
265 placeTerrain(x, y, textueByHeight[i].terrain);
266 // Add some props at...
267 if (i == 0) // ...deep water
269 if (randBool(propDensity / 100))
270 placeObject(x, y, "actor|props/flora/pond_lillies_large.xml", 0, randomAngle());
271 else if (randBool(propDensity / 40))
272 placeObject(x, y, "actor|props/flora/water_lillies.xml", 0, randomAngle());
274 if (i == 1) // ...medium water (with fish)
276 if (randBool(propDensity / 200))
277 placeObject(x, y, "actor|props/flora/pond_lillies_large.xml", 0, randomAngle());
278 else if (randBool(propDensity / 100))
279 placeObject(x, y, "actor|props/flora/water_lillies.xml", 0, randomAngle());
281 if (i == 2) // ...low water/mud
283 if (randBool(propDensity / 200))
284 placeObject(x, y, "actor|props/flora/water_log.xml", 0, randomAngle());
285 else if (randBool(propDensity / 100))
286 placeObject(x, y, "actor|props/flora/water_lillies.xml", 0, randomAngle());
287 else if (randBool(propDensity / 40))
288 placeObject(x, y, "actor|geology/highland_c.xml", 0, randomAngle());
289 else if (randBool(propDensity / 20))
290 placeObject(x, y, "actor|props/flora/reeds_pond_lush_b.xml", 0, randomAngle());
291 else if (randBool(propDensity / 10))
292 placeObject(x, y, "actor|props/flora/reeds_pond_lush_a.xml", 0, randomAngle());
294 if (i == 3) // ...water suroundings/bog
296 if (randBool(propDensity / 200))
297 placeObject(x, y, "actor|props/flora/water_log.xml", 0, randomAngle());
298 else if (randBool(propDensity / 100))
299 placeObject(x, y, "actor|geology/highland_c.xml", 0, randomAngle());
300 else if (randBool(propDensity / 40))
301 placeObject(x, y, "actor|props/flora/reeds_pond_lush_a.xml", 0, randomAngle());
303 if (i == 4) // ...low height grass
305 if (randBool(propDensity / 800))
306 placeObject(x, y, "actor|props/flora/grass_field_flowering_tall.xml", 0, randomAngle());
307 else if (randBool(propDensity / 400))
308 placeObject(x, y, "actor|geology/gray_rock1.xml", 0, randomAngle());
309 else if (randBool(propDensity / 200))
310 placeObject(x, y, "actor|props/flora/bush_tempe_sm_lush.xml", 0, randomAngle());
311 else if (randBool(propDensity / 100))
312 placeObject(x, y, "actor|props/flora/bush_tempe_b.xml", 0, randomAngle());
313 else if (randBool(propDensity / 40))
314 placeObject(x, y, "actor|props/flora/grass_soft_small_tall.xml", 0, randomAngle());
316 if (i == 5) // ...medium height grass
318 if (randBool(propDensity / 800))
319 placeObject(x, y, "actor|geology/decal_stone_medit_a.xml", 0, randomAngle());
320 else if (randBool(propDensity / 400))
321 placeObject(x, y, "actor|props/flora/decals_flowers_daisies.xml", 0, randomAngle());
322 else if (randBool(propDensity / 200))
323 placeObject(x, y, "actor|props/flora/bush_tempe_underbrush.xml", 0, randomAngle());
324 else if (randBool(propDensity / 100))
325 placeObject(x, y, "actor|props/flora/grass_soft_small_tall.xml", 0, randomAngle());
326 else if (randBool(propDensity / 40))
327 placeObject(x, y, "actor|props/flora/grass_temp_field.xml", 0, randomAngle());
329 if (i == 6) // ...high height grass
331 if (randBool(propDensity / 400))
332 placeObject(x, y, "actor|geology/stone_granite_boulder.xml", 0, randomAngle());
333 else if (randBool(propDensity / 200))
334 placeObject(x, y, "actor|props/flora/foliagebush.xml", 0, randomAngle());
335 else if (randBool(propDensity / 100))
336 placeObject(x, y, "actor|props/flora/bush_tempe_underbrush.xml", 0, randomAngle());
337 else if (randBool(propDensity / 40))
338 placeObject(x, y, "actor|props/flora/grass_soft_small_tall.xml", 0, randomAngle());
339 else if (randBool(propDensity / 20))
340 placeObject(x, y, "actor|props/flora/ferns.xml", 0, randomAngle());
342 if (i == 7) // ...forest border (with wood/food plants/deer/rabits)
344 if (randBool(propDensity / 400))
345 placeObject(x, y, "actor|geology/highland_c.xml", 0, randomAngle());
346 else if (randBool(propDensity / 200))
347 placeObject(x, y, "actor|props/flora/bush_tempe_a.xml", 0, randomAngle());
348 else if (randBool(propDensity / 100))
349 placeObject(x, y, "actor|props/flora/ferns.xml", 0, randomAngle());
350 else if (randBool(propDensity / 40))
351 placeObject(x, y, "actor|props/flora/grass_soft_tuft_a.xml", 0, randomAngle());
353 if (i == 8) // ...woods
355 if (randBool(propDensity / 200))
356 placeObject(x, y, "actor|geology/highland2_moss.xml", 0, randomAngle());
357 else if (randBool(propDensity / 100))
358 placeObject(x, y, "actor|props/flora/grass_soft_tuft_a.xml", 0, randomAngle());
359 else if (randBool(propDensity / 40))
360 placeObject(x, y, "actor|props/flora/ferns.xml", 0, randomAngle());
365 textureMinHeight = textueByHeight[i].upperHeightLimit;
370 Engine.SetProgress(90);
373 placePlayersNomad(createTileClass(), new HeightConstraint(lowerHeightLimit, upperHeightLimit));
376 log("Placing players and starting resources...");
378 let playerIDs = sortAllPlayers();
379 let resourceDistance = 8;
380 let resourceSpacing = 1;
381 let resourceCount = 4;
383 for (let i = 0; i < numPlayers; ++i)
385 let playerPos = new Vector2D(possibleStartPositions[bestDerivation[i]][0], possibleStartPositions[bestDerivation[i]][1]);
386 placeCivDefaultStartingEntities(playerPos.x, playerPos.y, playerIDs[i], false);
388 for (let j = 1; j <= 4; ++j)
390 let uAngle = BUILDING_ORIENTATION - Math.PI * (2-j) / 2;
391 for (let k = 0; k < resourceCount; ++k)
393 let pos = Vector2D.sum([
395 new Vector2D(resourceDistance, 0).rotate(-uAngle),
396 new Vector2D(k * resourceSpacing, 0).rotate(-uAngle - Math.PI/2),
397 new Vector2D(-0.75 * resourceSpacing * Math.floor(resourceCount / 2), 0).rotate(-uAngle - Math.PI/2)
400 placeObject(pos.x, pos.y, j % 2 ? "gaia/flora_tree_cypress" : "gaia/flora_bush_berry", 0, randomAngle());