Add randomAngle helper function to abbreviate calls.
[0ad.git] / binaries / data / mods / public / maps / random / belgian_uplands.js
blob7dc355d4f177be2adc739882e92e0da282271670
1 Engine.LoadLibrary("rmgen");
2 Engine.LoadLibrary("heightmap");
4 InitMap();
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 = [];
33 // Deep water
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});
43 // Flat Water
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"]});
58 // Medium level grass
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});
78 // Unpassable woods
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;
96 var tries = 0;
97 var lowerHeightLimit = textueByHeight[3].upperHeightLimit;
98 var upperHeightLimit = textueByHeight[6].upperHeightLimit;
100 while (!goodStartPositionsFound)
102         tries++;
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++)
122                 {
123                         var actualHeight = getHeight(x, y);
124                         if (actualHeight > lowerHeightLimit && actualHeight < upperHeightLimit)
125                         {
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++)
130                                         {
131                                                 var testHeight = getHeight(x + offX, y + offY);
132                                                 if (testHeight <= lowerHeightLimit || testHeight >= upperHeightLimit)
133                                                 {
134                                                         isPossible = false;
135                                                         break;
136                                                 }
137                                         }
139                                 if (isPossible)
140                                         possibleStartPositions.push([x, y]);
141                                         // placeTerrain(x, y, "blue"); // For debug reasons. Plz don't remove. // Only works properly for 1 loop
142                         }
143                 }
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++)
151         {
152                 if (Math.euclidDistance2D(...possibleStartPositions[i], mapSize / 2, mapSize / 2) < maxDistToCenter)
153                         possibleStartPositionsTemp.push(possibleStartPositions[i]);
154         }
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++)
163         {
164                 var numLowTiles = 0;
165                 var numHighTiles = 0;
166                 for (var dx = - maxDistToResources; dx < maxDistToResources; dx++)
167                 {
168                         for (var dy = - maxDistToResources; dy < maxDistToResources; dy++)
169                         {
170                                 var testHeight = getHeight(possibleStartPositions[i][0] + dx, possibleStartPositions[i][1] + dy);
172                                 if (testHeight < lowerHeightLimit)
173                                         numLowTiles++;
175                                 if (testHeight > upperHeightLimit)
176                                         numHighTiles++;
178                                 if (numLowTiles > minNumLowTiles && numHighTiles > minNumHighTiles)
179                                         break;
180                         }
181                         if (numLowTiles > minNumLowTiles && numHighTiles > minNumHighTiles)
182                                 break;
183                 }
184                 if (numLowTiles > minNumLowTiles && numHighTiles > minNumHighTiles)
185                         possibleStartPositionsTemp.push(possibleStartPositions[i]);
186         }
188         possibleStartPositions = clone(possibleStartPositionsTemp);
190         if(possibleStartPositions.length > numPlayers)
191                 enoughTiles = true;
192         else
193         {
194                 enoughTiles = false;
195                 log("possibleStartPositions.length < numPlayers, possibleStartPositions.length = " + possibleStartPositions.length + ", numPlayers = " + numPlayers);
196         }
198         // Find a good start position derivation
199         if (enoughTiles)
200         {
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++)
205                 {
206                         var vector = [];
207                         for (var p = 0; p < numPlayers; p++)
208                                 vector.push(randIntExclusive(0, possibleStartPositions.length));
209                         possibleDerivations.push(vector);
210                 }
212                 // Choose the start location derivation with the greatest minimum distance between players
213                 var maxMinDist = 0;
214                 for (var d = 0; d < possibleDerivations.length; d++)
215                 {
216                         var minDist = 2 * mapSize;
217                         for (var p1 = 0; p1 < numPlayers - 1; p1++)
218                         {
219                                 for (var p2 = p1 + 1; p2 < numPlayers; p2++)
220                                 {
221                                         if (p1 != p2)
222                                         {
223                                                 minDist = Math.min(minDist, Math.euclidDistance2D(...possibleStartPositions[possibleDerivations[d][p1]], ...possibleStartPositions[possibleDerivations[d][p2]]));
224                                                 if (minDist < maxMinDist)
225                                                         break;
226                                         }
227                                 }
228                                 if (minDist < maxMinDist)
229                                         break;
230                         }
231                         if (minDist > maxMinDist)
232                         {
233                                 maxMinDist = minDist;
234                                 var bestDerivation = possibleDerivations[d];
235                         }
236                 }
237                 if (maxMinDist > minDistBetweenPlayers)
238                 {
239                         goodStartPositionsFound = true;
240                         log("Exiting giant while loop after " +  tries + " tries with a minimum player distance of " + maxMinDist);
241                 }
242                 else
243                         log("maxMinDist <= " + minDistBetweenPlayers + ", maxMinDist = " + maxMinDist);
244         }
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
251 if (mapSize > 500)
252         propDensity = 1/4;
253 else if (mapSize > 400)
254         propDensity = 3/4;
256 for(var x = minTerrainDistToBorder; x < mapSize - minTerrainDistToBorder; x++)
258         for (var y = minTerrainDistToBorder; y < mapSize - minTerrainDistToBorder; y++)
259         {
260                 var textureMinHeight = heightRange.min;
261                 for (var i = 0; i < textueByHeight.length; i++)
262                 {
263                         if (getHeight(x, y) >= textureMinHeight && getHeight(x, y) <= textueByHeight[i].upperHeightLimit)
264                         {
265                                 placeTerrain(x, y, textueByHeight[i].terrain);
266                                 // Add some props at...
267                                 if (i == 0) // ...deep water
268                                 {
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());
273                                 }
274                                 if (i == 1) // ...medium water (with fish)
275                                 {
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());
280                                 }
281                                 if (i == 2) // ...low water/mud
282                                 {
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());
293                                 }
294                                 if (i == 3) // ...water suroundings/bog
295                                 {
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());
302                                 }
303                                 if (i == 4) // ...low height grass
304                                 {
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());
315                                 }
316                                 if (i == 5) // ...medium height grass
317                                 {
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());
328                                 }
329                                 if (i == 6) // ...high height grass
330                                 {
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());
341                                 }
342                                 if (i == 7) // ...forest border (with wood/food plants/deer/rabits)
343                                 {
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());
352                                 }
353                                 if (i == 8) // ...woods
354                                 {
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());
361                                 }
362                                 break;
363                         }
364                         else
365                                 textureMinHeight = textueByHeight[i].upperHeightLimit;
366                 }
367         }
370 Engine.SetProgress(90);
372 if (isNomad())
373         placePlayersNomad(createTileClass(), new HeightConstraint(lowerHeightLimit, upperHeightLimit));
374 else
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)
384         {
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)
389                 {
390                         let uAngle = BUILDING_ORIENTATION - Math.PI * (2-j) / 2;
391                         for (let k = 0; k < resourceCount; ++k)
392                         {
393                                 let pos = Vector2D.sum([
394                                         playerPos,
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)
398                                 ]);
400                                 placeObject(pos.x, pos.y, j % 2 ? "gaia/flora_tree_cypress" : "gaia/flora_bush_berry", 0, randomAngle());
401                         }
402                 }
403         }
406 ExportMap();