Remove rmgen euclidian distance helper function, refs rP20328 / D969.
[0ad.git] / binaries / data / mods / public / maps / random / wild_lake.js
bloba1b5ec9f3a788a88863bc94d5bfc157d159a5b27
1 RMS.LoadLibrary("rmgen");
2 RMS.LoadLibrary("rmbiome");
3 RMS.LoadLibrary("heightmap");
5 InitMap();
7 let genStartTime = Date.now();
9 /**
10  * getArray - To ensure a terrain texture is contained within an array
11  */
12 function getArray(stringOrArrayOfStrings)
14         if (typeof stringOrArrayOfStrings == "string")
15                 return [stringOrArrayOfStrings];
16         return stringOrArrayOfStrings;
19 setSelectedBiome();
21 // Terrain, entities and actors
22 let wildLakeBiome = [
23         // 0 Deep water
24         {
25                 "texture": getArray(g_Terrains.water),
26                 "actor": [[g_Gaia.fish], 0.01],
27                 "textureHS": getArray(g_Terrains.water),
28                 "actorHS": [[g_Gaia.fish], 0.03]
29         },
30         // 1 Shallow water
31         {
32                 "texture": getArray(g_Terrains.water),
33                 "actor": [[g_Decoratives.lillies, g_Decoratives.reeds], 0.3],
34                 "textureHS": getArray(g_Terrains.water),
35                 "actorHS": [[g_Decoratives.lillies], 0.1]
36         },
37         // 2 Shore
38         {
39                 "texture": getArray(g_Terrains.shore),
40                 "actor": [
41                         [
42                                 g_Gaia.tree1, g_Gaia.tree1,
43                                 g_Gaia.tree2, g_Gaia.tree2,
44                                 g_Gaia.mainHuntableAnimal,
45                                 g_Decoratives.grass, g_Decoratives.grass,
46                                 g_Decoratives.rockMedium, g_Decoratives.rockMedium,
47                                 g_Decoratives.bushMedium, g_Decoratives.bushMedium
48                         ],
49                         0.3
50                 ],
51                 "textureHS": getArray(g_Terrains.cliff),
52                 "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1]
53         },
54         // 3 Low ground
55         {
56                 "texture": getArray(g_Terrains.tier1Terrain),
57                 "actor": [
58                         [
59                                 g_Decoratives.grass,
60                                 g_Decoratives.grassShort,
61                                 g_Decoratives.rockLarge,
62                                 g_Decoratives.rockMedium,
63                                 g_Decoratives.bushMedium,
64                                 g_Decoratives.bushSmall
65                         ],
66                         0.2
67                 ],
68                 "textureHS": getArray(g_Terrains.cliff),
69                 "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1]
70         },
71         // 4 Mid ground. Player and path height
72         {
73                 "texture": getArray(g_Terrains.mainTerrain),
74                 "actor": [
75                         [
76                                 g_Decoratives.grass,
77                                 g_Decoratives.grassShort,
78                                 g_Decoratives.rockLarge,
79                                 g_Decoratives.rockMedium,
80                                 g_Decoratives.bushMedium,
81                                 g_Decoratives.bushSmall
82                         ],
83                         0.2
84                 ],
85                 "textureHS": getArray(g_Terrains.cliff),
86                 "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1]
87         },
88         // 5 High ground
89         {
90                 "texture": getArray(g_Terrains.tier2Terrain),
91                 "actor": [
92                         [
93                                 g_Decoratives.grass,
94                                 g_Decoratives.grassShort,
95                                 g_Decoratives.rockLarge,
96                                 g_Decoratives.rockMedium,
97                                 g_Decoratives.bushMedium,
98                                 g_Decoratives.bushSmall
99                         ],
100                         0.2
101                 ],
102                 "textureHS": getArray(g_Terrains.cliff),
103                 "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1]
104         },
105         // 6 Lower hilltop forest border
106         {
107                 "texture": getArray(g_Terrains.dirt),
108                 "actor": [
109                         [
110                                 g_Gaia.tree1,
111                                 g_Gaia.tree3,
112                                 g_Gaia.fruitBush,
113                                 g_Gaia.secondaryHuntableAnimal,
114                                 g_Decoratives.grass,
115                                 g_Decoratives.rockMedium,
116                                 g_Decoratives.bushMedium
117                         ],
118                         0.3
119                 ],
120                 "textureHS": getArray(g_Terrains.cliff),
121                 "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1]
122         },
123         // 7 Hilltop forest
124         {
125                 "texture": getArray(g_Terrains.forestFloor1),
126                 "actor": [
127                         [
128                                 g_Gaia.tree1,
129                                 g_Gaia.tree2,
130                                 g_Gaia.tree3,
131                                 g_Gaia.tree4,
132                                 g_Gaia.tree5,
133                                 g_Decoratives.tree,
134                                 g_Decoratives.grass,
135                                 g_Decoratives.rockMedium,
136                                 g_Decoratives.bushMedium
137                         ],
138                         0.5
139                 ],
140                 "textureHS": getArray(g_Terrains.cliff),
141                 "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1]
142         }
145 var mercenaryCampGuards = {
146         "temperate": [
147                 { "Template" : "structures/merc_camp_egyptian" },
148                 { "Template" : "units/mace_infantry_javelinist_b", "Count" : 4 },
149                 { "Template" : "units/mace_cavalry_spearman_e", "Count" : 3 },
150                 { "Template" : "units/mace_infantry_archer_a", "Count" : 4 },
151                 { "Template" : "units/mace_champion_infantry_a", "Count" : 3 }
152         ],
153         "snowy": [
154                 { "Template" : "structures/ptol_mercenary_camp" },
155                 { "Template" : "units/brit_infantry_javelinist_b", "Count" : 4 },
156                 { "Template" : "units/brit_cavalry_swordsman_e", "Count" : 3 },
157                 { "Template" : "units/brit_infantry_slinger_a", "Count" : 4 },
158                 { "Template" : "units/brit_champion_infantry", "Count" : 3 }
159         ],
160         "desert": [
161                 { "Template" : "structures/ptol_mercenary_camp" },
162                 { "Template" : "units/pers_infantry_javelinist_b", "Count" : 4 },
163                 { "Template" : "units/pers_cavalry_swordsman_e", "Count" : 3 },
164                 { "Template" : "units/pers_infantry_archer_a", "Count" : 4 },
165                 { "Template" : "units/pers_champion_infantry", "Count" : 3 }
166         ],
167         "alpine": [
168                 { "Template" : "structures/ptol_mercenary_camp" },
169                 { "Template" : "units/rome_infantry_swordsman_b", "Count" : 4 },
170                 { "Template" : "units/rome_cavalry_spearman_e", "Count" : 3 },
171                 { "Template" : "units/rome_infantry_javelinist_a", "Count" : 4 },
172                 { "Template" : "units/rome_champion_infantry", "Count" : 3 }
173         ],
174         "mediterranean": [
175                 { "Template" : "structures/merc_camp_egyptian" },
176                 { "Template" : "units/iber_infantry_javelinist_b", "Count" : 4 },
177                 { "Template" : "units/iber_cavalry_spearman_e", "Count" : 3 },
178                 { "Template" : "units/iber_infantry_slinger_a", "Count" : 4 },
179                 { "Template" : "units/iber_champion_infantry", "Count" : 3 }
180         ],
181         "savanna": [
182                 { "Template" : "structures/merc_camp_egyptian" },
183                 { "Template" : "units/sele_infantry_javelinist_b", "Count" : 4 },
184                 { "Template" : "units/sele_cavalry_spearman_merc_e", "Count" : 3 },
185                 { "Template" : "units/sele_infantry_spearman_a", "Count" : 4 },
186                 { "Template" : "units/sele_champion_infantry_swordsman", "Count" : 3 }
187         ],
188         "tropic": [
189                 { "Template" : "structures/merc_camp_egyptian" },
190                 { "Template" : "units/ptol_infantry_javelinist_b", "Count" : 4 },
191                 { "Template" : "units/ptol_cavalry_archer_e", "Count" : 3 },
192                 { "Template" : "units/ptol_infantry_slinger_a", "Count" : 4 },
193                 { "Template" : "units/ptol_champion_infantry_pikeman", "Count" : 3 }
194         ],
195         "autumn": [
196                 { "Template" : "structures/ptol_mercenary_camp" },
197                 { "Template" : "units/gaul_infantry_javelinist_b", "Count" : 4 },
198                 { "Template" : "units/gaul_cavalry_swordsman_e", "Count" : 3 },
199                 { "Template" : "units/gaul_infantry_slinger_a", "Count" : 4 },
200                 { "Template" : "units/gaul_champion_infantry", "Count" : 3 }
201         ]
205  * Resource spots and other points of interest
206  */
208 // Mines
209 function placeMine(point, centerEntity,
210         decorativeActors = [
211                 g_Decoratives.grass, g_Decoratives.grassShort,
212                 g_Decoratives.rockLarge, g_Decoratives.rockMedium,
213                 g_Decoratives.bushMedium, g_Decoratives.bushSmall
214         ]
217         placeObject(point.x, point.y, centerEntity, 0, randFloat(0, TWO_PI));
218         let quantity = randIntInclusive(11, 23);
219         let dAngle = TWO_PI / quantity;
220         for (let i = 0; i < quantity; ++i)
221         {
222                 let angle = dAngle * randFloat(i, i + 1);
223                 let dist = randFloat(2, 5);
224                 placeObject(point.x + dist * Math.cos(angle), point.y + dist * Math.sin(angle), pickRandom(decorativeActors), 0, randFloat(0, 2 * PI));
225         }
228 // Groves, only Wood
229 let groveActors = [g_Decoratives.grass, g_Decoratives.rockMedium, g_Decoratives.bushMedium];
230 let clGrove = createTileClass();
232 function placeGrove(point,
233         groveEntities = [
234                 g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1,
235                 g_Gaia.tree2, g_Gaia.tree2, g_Gaia.tree2, g_Gaia.tree2,
236                 g_Gaia.tree3, g_Gaia.tree3, g_Gaia.tree3,
237                 g_Gaia.tree4, g_Gaia.tree4, g_Gaia.tree5
238         ],
239         groveActors = [g_Decoratives.grass, g_Decoratives.rockMedium, g_Decoratives.bushMedium], groveTileClass = undefined,
240         groveTerrainTexture = getArray(g_Terrains.forestFloor1)
243         placeObject(point.x, point.y, pickRandom(["structures/gaul_outpost", "gaia/flora_tree_oak_new"]), 0, randFloat(0, 2 * PI));
244         let quantity = randIntInclusive(20, 30);
245         let dAngle = TWO_PI / quantity;
246         for (let i = 0; i < quantity; ++i)
247         {
248                 let angle = dAngle * randFloat(i, i + 1);
249                 let dist = randFloat(2, 5);
250                 let objectList = groveEntities;
251                 if (i % 3 == 0)
252                         objectList = groveActors;
253                 let x = point.x + dist * Math.cos(angle);
254                 let y = point.y + dist * Math.sin(angle);
255                 placeObject(x, y, pickRandom(objectList), 0, randFloat(0, 2 * PI));
256                 if (groveTileClass)
257                         createArea(new ClumpPlacer(5, 1, 1, 1, floor(x), floor(y)), [new TerrainPainter(groveTerrainTexture), paintClass(groveTileClass)]);
258                 else
259                         createArea(new ClumpPlacer(5, 1, 1, 1, floor(x), floor(y)), [new TerrainPainter(groveTerrainTexture)]);
260         }
263 var farmEntities = {
264         "temperate": { "building": "structures/mace_farmstead", "animal": "gaia/fauna_pig" },
265         "snowy": { "building": "structures/brit_farmstead", "animal": "gaia/fauna_sheep" },
266         "desert": { "building": "structures/pers_farmstead", "animal": "gaia/fauna_camel" },
267         "alpine": { "building": "structures/rome_farmstead", "animal": "gaia/fauna_sheep" },
268         "mediterranean": { "building": "structures/iber_farmstead", "animal": "gaia/fauna_pig" },
269         "savanna": { "building": "structures/sele_farmstead", "animal": "gaia/fauna_horse" },
270         "tropic": { "building": "structures/ptol_farmstead", "animal": "gaia/fauna_camel" },
271         "autumn": { "building": "structures/gaul_farmstead", "animal": "gaia/fauna_horse" }
274 wallStyles.other.sheepIn = new WallElement("sheepIn", farmEntities[currentBiome()].animal, PI / 4, -1.5, 0.75, PI/2);
275 wallStyles.other.foodBin = new WallElement("foodBin", "gaia/special_treasure_food_bin", PI/2, 1.5);
276 wallStyles.other.sheep = new WallElement("sheep", farmEntities[currentBiome()].animal, 0, 0, 0.75);
277 wallStyles.other.farm = new WallElement("farm", farmEntities[currentBiome()].building, PI, 0, -3);
278 let fences = [
279         new Fortress("fence", ["foodBin", "farm", "bench", "sheepIn", "fence", "sheepIn", "fence", "sheepIn", "fence"]),
280         new Fortress("fence", ["foodBin", "farm", "fence", "sheepIn", "fence", "sheepIn", "bench", "sheep", "fence", "sheepIn", "fence"]),
281         new Fortress("fence", [
282                 "foodBin", "farm", "cornerIn", "bench", "cornerOut", "fence_short", "sheepIn", "fence", "sheepIn",
283                 "fence", "sheepIn", "fence_short", "sheep", "fence"
284         ]),
285         new Fortress("fence", [
286                 "foodBin", "farm", "cornerIn", "fence_short", "cornerOut", "bench", "sheepIn", "fence", "sheepIn",
287                 "fence", "sheepIn", "fence_short", "sheep", "fence"
288         ]),
289         new Fortress("fence", [
290                 "foodBin", "farm", "fence", "sheepIn", "bench", "sheep", "fence", "sheepIn",
291                 "fence_short", "sheep", "fence", "sheepIn", "fence_short", "sheep", "fence"
292         ])
294 let num = fences.length;
295 for (let i = 0; i < num; ++i)
296         fences.push(new Fortress("fence", clone(fences[i].wall).reverse()));
298 // Camps with fire and gold treasure
299 function placeCamp(point,
300         centerEntity = "actor|props/special/eyecandy/campfire.xml",
301         otherEntities = ["gaia/special_treasure_metal", "gaia/special_treasure_standing_stone",
302                 "units/brit_infantry_slinger_b", "units/brit_infantry_javelinist_b", "units/gaul_infantry_slinger_b", "units/gaul_infantry_javelinist_b", "units/gaul_champion_fanatic",
303                 "actor|props/special/common/waypoint_flag.xml", "actor|props/special/eyecandy/barrel_a.xml", "actor|props/special/eyecandy/basket_celt_a.xml", "actor|props/special/eyecandy/crate_a.xml", "actor|props/special/eyecandy/dummy_a.xml", "actor|props/special/eyecandy/handcart_1.xml", "actor|props/special/eyecandy/handcart_1_broken.xml", "actor|props/special/eyecandy/sack_1.xml", "actor|props/special/eyecandy/sack_1_rough.xml"
304         ]
307         placeObject(point.x, point.y, centerEntity, 0, randFloat(0, TWO_PI));
308         let quantity = randIntInclusive(5, 11);
309         let dAngle = TWO_PI / quantity;
310         for (let i = 0; i < quantity; ++i)
311         {
312                 let angle = dAngle * randFloat(i, i + 1);
313                 let dist = randFloat(1, 3);
314                 placeObject(point.x + dist * Math.cos(angle), point.y + dist * Math.sin(angle), pickRandom(otherEntities), 0, randFloat(0, 2 * PI));
315         }
318 function placeStartLocationResources(
319         point,
320         foodEntities = [g_Gaia.fruitBush, g_Gaia.chicken],
321         groveEntities = [
322                 g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1,
323                 g_Gaia.tree2, g_Gaia.tree2, g_Gaia.tree2, g_Gaia.tree2,
324                 g_Gaia.tree3, g_Gaia.tree3, g_Gaia.tree3,
325                 g_Gaia.tree4, g_Gaia.tree4, g_Gaia.tree5
326         ],
327         groveTerrainTexture = getArray(g_Terrains.forestFloor1),
328         averageDistToCC = 10,
329         dAverageDistToCC = 2
332         function getRandDist()
333         {
334                 return averageDistToCC + randFloat(-dAverageDistToCC, dAverageDistToCC);
335         }
337         let currentAngle = randFloat(0, TWO_PI);
338         // Stone
339         let dAngle = TWO_PI * 2 / 9;
340         let angle = currentAngle + randFloat(dAngle / 4, 3 * dAngle / 4);
341         placeMine({ "x": point.x + averageDistToCC * Math.cos(angle), "y": point.y + averageDistToCC * Math.sin(angle) }, g_Gaia.stoneLarge);
343         currentAngle += dAngle;
345         // Wood
346         let quantity = 80;
347         dAngle = TWO_PI / quantity / 3;
348         for (let i = 0; i < quantity; ++i)
349         {
350                 angle = currentAngle + randFloat(0, dAngle);
351                 let dist = getRandDist();
352                 let objectList = groveEntities;
353                 if (i % 2 == 0)
354                         objectList = groveActors;
355                 let x = point.x + dist * Math.cos(angle);
356                 let y = point.y + dist * Math.sin(angle);
357                 placeObject(x, y, pickRandom(objectList), 0, randFloat(0, 2 * PI));
358                 createArea(new ClumpPlacer(5, 1, 1, 1, floor(x), floor(y)), [new TerrainPainter(groveTerrainTexture), paintClass(clGrove)]);
359                 currentAngle += dAngle;
360         }
362         // Metal
363         dAngle = TWO_PI * 2 / 9;
364         angle = currentAngle + randFloat(dAngle / 4, 3 * dAngle / 4);
365         placeMine({ "x": point.x + averageDistToCC * Math.cos(angle), "y": point.y + averageDistToCC * Math.sin(angle) }, g_Gaia.metalLarge);
366         currentAngle += dAngle;
368         // Berries and domestic animals
369         quantity = 15;
370         dAngle = TWO_PI / quantity * 2 / 9;
371         for (let i = 0; i < quantity; ++i)
372         {
373                 angle = currentAngle + randFloat(0, dAngle);
374                 let dist = getRandDist();
375                 placeObject(point.x + dist * Math.cos(angle), point.y + dist * Math.sin(angle), pickRandom(foodEntities), 0, randFloat(0, 2 * PI));
376                 currentAngle += dAngle;
377         }
380 log("Functions loaded after " + ((Date.now() - genStartTime) / 1000) + "s");
383  * Base terrain shape generation and settings
384  */
385  // Height range by map size
386 let heightScale = (g_Map.size + 256) / 768 / 4;
387 let heightRange = { "min": MIN_HEIGHT * heightScale, "max": MAX_HEIGHT * heightScale };
389 // Water coverage
390 let averageWaterCoverage = 1/5; // NOTE: Since terrain generation is quite unpredictable actual water coverage might vary much with the same value
391 let waterHeight = -MIN_HEIGHT + heightRange.min + averageWaterCoverage * (heightRange.max - heightRange.min); // Water height in environment and the engine
392 let waterHeightAdjusted = waterHeight + MIN_HEIGHT; // Water height as terrain height
393 setWaterHeight(waterHeight);
395 // Generate base terrain shape
396 let lowH = heightRange.min;
397 let medH = (heightRange.min + heightRange.max) / 2;
399 // Lake
400 let initialHeightmap = [
401         [medH, medH, medH, medH, medH, medH],
402         [medH, medH, medH, medH, medH, medH],
403         [medH, medH, lowH, lowH, medH, medH],
404         [medH, medH, lowH, lowH, medH, medH],
405         [medH, medH, medH, medH, medH, medH],
406         [medH, medH, medH, medH, medH, medH],
408 if (g_Map.size < 256)
410         initialHeightmap = [
411                 [medH, medH, medH, medH, medH],
412                 [medH, medH, medH, medH, medH],
413                 [medH, medH, lowH, medH, medH],
414                 [medH, medH, medH, medH, medH],
415                 [medH, medH, medH, medH, medH]
416         ];
418 if (g_Map.size >= 384)
420         initialHeightmap = [
421                 [medH, medH, medH, medH, medH, medH, medH, medH],
422                 [medH, medH, medH, medH, medH, medH, medH, medH],
423                 [medH, medH, medH, medH, medH, medH, medH, medH],
424                 [medH, medH, medH, lowH, lowH, medH, medH, medH],
425                 [medH, medH, medH, lowH, lowH, medH, medH, medH],
426                 [medH, medH, medH, medH, medH, medH, medH, medH],
427                 [medH, medH, medH, medH, medH, medH, medH, medH],
428                 [medH, medH, medH, medH, medH, medH, medH, medH],
429         ];
432 setBaseTerrainDiamondSquare(heightRange.min, heightRange.max, initialHeightmap, 0.8);
434 // Apply simple erosion
435 for (let i = 0; i < 5; ++i)
436         splashErodeMap(0.1);
437 globalSmoothHeightmap();
439 // Final rescale
440 rescaleHeightmap(heightRange.min, heightRange.max);
442 RMS.SetProgress(25);
445  * Prepare terrain texture placement
446  */
447 let heighLimits = [
448         heightRange.min + 3/4 * (waterHeightAdjusted - heightRange.min), // 0 Deep water
449         waterHeightAdjusted, // 1 Shallow water
450         waterHeightAdjusted + 2/8 * (heightRange.max - waterHeightAdjusted), // 2 Shore
451         waterHeightAdjusted + 3/8 * (heightRange.max - waterHeightAdjusted), // 3 Low ground
452         waterHeightAdjusted + 4/8 * (heightRange.max - waterHeightAdjusted), // 4 Player and path height
453         waterHeightAdjusted + 6/8 * (heightRange.max - waterHeightAdjusted), // 5 High ground
454         waterHeightAdjusted + 7/8 * (heightRange.max - waterHeightAdjusted), // 6 Lower forest border
455         heightRange.max // 7 Forest
457 let playerHeightRange = { "min" : heighLimits[3], "max" : heighLimits[4] };
458 let resourceSpotHeightRange = { "min" : (heighLimits[2] + heighLimits[3]) / 2, "max" : (heighLimits[4] + heighLimits[5]) / 2 };
459 let playerHeight = (playerHeightRange.min + playerHeightRange.max) / 2; // Average player height
461 log("Terrain shape generation and biome presets after " + ((Date.now() - genStartTime) / 1000) + "s");
464  * Get start locations
465  */
466 let startLocations = getStartLocationsByHeightmap(playerHeightRange, 1000, 30);
468 // Sort start locations to form a "ring"
469 let startLocationOrder = getOrderOfPointsForShortestClosePath(startLocations);
470 let newStartLocations = [];
471 for (let i = 0; i < startLocations.length; ++i)
472         newStartLocations.push(startLocations[startLocationOrder[i]]);
473 startLocations = newStartLocations;
475 // Sort players by team
476 let playerIDs = [];
477 let teams = [];
478 for (let i = 0; i < g_MapSettings.PlayerData.length - 1; ++i)
480         playerIDs.push(i+1);
481         let t = g_MapSettings.PlayerData[i + 1].Team;
482         if (teams.indexOf(t) == -1 && t !== undefined)
483                 teams.push(t);
485 playerIDs = sortPlayers(playerIDs);
487 // Minimize maximum distance between players within a team
488 if (teams.length)
490         let minDistance = Infinity;
491         let bestShift;
492         for (let s = 0; s < playerIDs.length; ++s)
493         {
494                 let maxTeamDist = 0;
495                 for (let pi = 0; pi < playerIDs.length - 1; ++pi)
496                 {
497                         let p1 = playerIDs[(pi + s) % playerIDs.length] - 1;
498                         let t1 = getPlayerTeam(p1);
500                         if (teams.indexOf(t1) === -1)
501                                 continue;
503                         for (let pj = pi + 1; pj < playerIDs.length; ++pj)
504                         {
505                                 let p2 = playerIDs[(pj + s) % playerIDs.length] - 1;
506                                 let t2 = getPlayerTeam(p2);
507                                 if (t2 != t1)
508                                         continue;
510                                 maxTeamDist = Math.max(
511                                         maxTeamDist,
512                                         Math.euclidDistance2D(
513                                                 startLocations[pi].x, startLocations[pi].y,
514                                                 startLocations[pj].x, startLocations[pj].y));
515                         }
516                 }
518                 if (maxTeamDist < minDistance)
519                 {
520                         minDistance = maxTeamDist;
521                         bestShift = s;
522                 }
523         }
524         if (bestShift)
525         {
526                 let newPlayerIDs = [];
527                 for (let i = 0; i < playerIDs.length; ++i)
528                         newPlayerIDs.push(playerIDs[(i + bestShift) % playerIDs.length]);
529                 playerIDs = newPlayerIDs;
530         }
533 log("Start location chosen after " + ((Date.now() - genStartTime) / 1000) + "s");
534 RMS.SetProgress(30);
537  * Smooth Start Locations before height region calculation
538  */
539 let playerBaseRadius = 35;
540 if (g_Map.size < 256)
541         playerBaseRadius = 25;
542 for (let p = 0; p < playerIDs.length; ++p)
543         rectangularSmoothToHeight(startLocations[p], playerBaseRadius, playerBaseRadius, playerHeight, 0.7);
546  * Calculate tile centered height map after start position smoothing but before placing paths
547  * This has nothing to to with TILE_CENTERED_HEIGHT_MAP which should be false!
548  */
549 let tchm = getTileCenteredHeightmap();
552  * Add paths (If any)
553  */
554 let clPath = createTileClass();
557  * Divide tiles in areas by height and avoid paths
558  */
559 let areas = [];
560 for (let h = 0; h < heighLimits.length; ++h)
561         areas.push([]);
563 for (let x = 0; x < tchm.length; ++x)
565         for (let y = 0; y < tchm[0].length; ++y)
566         {
567                 if (g_Map.tileClasses[clPath].inclusionCount[x][y] > 0) // Avoid paths
568                         continue;
570                 let minHeight = heightRange.min;
571                 for (let h = 0; h < heighLimits.length; ++h)
572                 {
573                         if (tchm[x][y] >= minHeight && tchm[x][y] <= heighLimits[h])
574                         {
575                                 areas[h].push({ "x": x, "y": y });
576                                 break;
577                         }
578                         else
579                                 minHeight = heighLimits[h];
580                 }
581         }
585  * Get max slope of each area
586  */
587 let slopeMap = getSlopeMap();
588 let minSlope = [];
589 let maxSlope = [];
590 for (let h = 0; h < heighLimits.length; ++h)
592         minSlope[h] = Infinity;
593         maxSlope[h] = 0;
594         for (let t = 0; t < areas[h].length; ++t)
595         {
596                 let x = areas[h][t].x;
597                 let y = areas[h][t].y;
598                 let slope = slopeMap[x][y];
600                 if (slope > maxSlope[h])
601                         maxSlope[h] = slope;
603                 if (slope < minSlope[h])
604                         minSlope[h] = slope;
605         }
609  * Paint areas by height and slope
610  */
611 for (let h = 0; h < heighLimits.length; ++h)
613         for (let t = 0; t < areas[h].length; ++t)
614         {
615                 let x = areas[h][t].x;
616                 let y = areas[h][t].y;
617                 let actor;
618                 let texture = pickRandom(wildLakeBiome[h].texture);
620                 if (slopeMap[x][y] < 0.5 * (minSlope[h] + maxSlope[h]))
621                 {
622                         if (randBool(wildLakeBiome[h].actor[1]))
623                                 actor = pickRandom(wildLakeBiome[h].actor[0]);
624                 }
625                 else
626                 {
627                         texture = pickRandom(wildLakeBiome[h].textureHS);
628                         if (randBool(wildLakeBiome[h].actorHS[1]))
629                                 actor = pickRandom(wildLakeBiome[h].actorHS[0]);
630                 }
632                 g_Map.texture[x][y] = g_Map.getTextureID(texture);
634                 if (actor)
635                         placeObject(randFloat(x, x + 1), randFloat(y, y + 1), actor, 0, randFloat(0, 2 * PI));
636         }
639 log("Terrain texture placement finished after " + ((Date.now() - genStartTime) / 1000) + "s");
640 RMS.SetProgress(80);
643  * Get resource spots after players start locations calculation and paths
644  */
645 let avoidPoints = clone(startLocations);
646 for (let i = 0; i < avoidPoints.length; ++i)
647         avoidPoints[i].dist = 30;
648 let resourceSpots = getPointsByHeight(resourceSpotHeightRange, avoidPoints, clPath);
650 log("Resource spots chosen after " + ((Date.now() - genStartTime) / 1000) + "s");
651 RMS.SetProgress(55);
654  * Add start locations and resource spots after terrain texture and path painting
655  */
656 for (let p = 0; p < playerIDs.length; ++p)
658         let point = startLocations[p];
659         placeCivDefaultEntities(point.x, point.y, playerIDs[p], { "iberWall": g_Map.size > 192 });
660         placeStartLocationResources(point);
663 let mercenaryCamps = ceil(g_Map.size / 256);
664 log("Maximum number of mercenary camps: " + uneval(mercenaryCamps));
665 for (let i = 0; i < resourceSpots.length; ++i)
667         let choice = i % 5;
668         if (choice == 0)
669                 placeMine(resourceSpots[i], g_Gaia.stoneLarge);
670         if (choice == 1)
671                 placeMine(resourceSpots[i], g_Gaia.metalLarge);
672         if (choice == 2)
673                 placeGrove(resourceSpots[i]);
674         if (choice == 3)
675         {
676                 placeCamp(resourceSpots[i]);
677                 rectangularSmoothToHeight(resourceSpots[i], 5, 5, g_Map.height[resourceSpots[i].x][resourceSpots[i].y] - 10, 0.5);
678         }
679         if (choice == 4)
680         {
681                 if (mercenaryCamps)
682                 {
683                         createStartingPlayerEntities(resourceSpots[i].x, resourceSpots[i].y, 0, mercenaryCampGuards[currentBiome()]);
684                         rectangularSmoothToHeight(resourceSpots[i], 15, 15, g_Map.height[resourceSpots[i].x][resourceSpots[i].y], 0.5);
685                         --mercenaryCamps;
686                 }
687                 else
688                 {
689                         placeCustomFortress(resourceSpots[i].x, resourceSpots[i].y, pickRandom(fences), "other", 0, randFloat(0, 2 * PI));
690                         rectangularSmoothToHeight(resourceSpots[i], 10, 10, g_Map.height[resourceSpots[i].x][resourceSpots[i].y], 0.5);
691                 }
692         }
695 log("Map generation finished after " + ((Date.now() - genStartTime) / 1000) + "s");
697 ExportMap();