Fix rmgen2 bug in rP17903 revealed by the 1* removal in rP20281.
[0ad.git] / binaries / data / mods / public / maps / random / rmgen2 / gaia.js
blobe766e06fe5e029f2da8cea78b60951f071024c5d
1 var g_Props = {
2         "barrels": "actor|props/special/eyecandy/barrels_buried.xml",
3         "crate": "actor|props/special/eyecandy/crate_a.xml",
4         "cart": "actor|props/special/eyecandy/handcart_1_broken.xml",
5         "well": "actor|props/special/eyecandy/well_1_c.xml",
6         "skeleton": "actor|props/special/eyecandy/skeleton.xml",
7 };
9 /**
10  * Create bluffs, i.e. a slope hill reachable from ground level.
11  * Fill it with wood, mines, animals and decoratives.
12  *
13  * @param {Array} constraint - where to place them
14  * @param {number} size - size of the bluffs (1.2 would be 120% of normal)
15  * @param {number} deviation - degree of deviation from the defined size (0.2 would be 20% plus/minus)
16  * @param {number} fill - size of map to fill (1.5 would be 150% of normal)
17  * @param {number} baseHeight - elevation of the floor, making the bluff reachable
18  */
19 function addBluffs(constraint, size, deviation, fill, baseHeight)
21         var constrastTerrain = g_Terrains.tier2Terrain;
23         if (currentBiome() == "tropic")
24                 constrastTerrain = g_Terrains.dirt;
26         if (currentBiome() == "autumn")
27                 constrastTerrain = g_Terrains.tier3Terrain;
29         var count = fill * 15;
30         var minSize = 5;
31         var maxSize = 7;
32         var elevation = 30;
33         var spread = 100;
35         for (var i = 0; i < count; ++i)
36         {
37                 var offset = getRandomDeviation(size, deviation);
39                 var pMinSize = Math.floor(minSize * offset);
40                 var pMaxSize = Math.floor(maxSize * offset);
41                 var pSpread = Math.floor(spread * offset);
42                 var pElevation = Math.floor(elevation * offset);
44                 var placer = new ChainPlacer(pMinSize, pMaxSize, pSpread, 0.5);
45                 var terrainPainter = new LayeredPainter([g_Terrains.cliff, g_Terrains.mainTerrain, constrastTerrain], [2, 3]);
46                 var elevationPainter = new SmoothElevationPainter(ELEVATION_MODIFY, pElevation, 2);
47                 var rendered = createAreas(placer, [terrainPainter, elevationPainter, paintClass(g_TileClasses.bluff)], constraint, 1);
49                 // Find the bounding box of the bluff
50                 if (rendered[0] === undefined)
51                         continue;
53                 var points = rendered[0].points;
55                 var corners = findCorners(points);
57                 // Seed an array the size of the bounding box
58                 var bb = createBoundingBox(points, corners);
60                 // Get a random starting position for the baseline and the endline
61                 var angle = randIntInclusive(0, 3);
62                 var opAngle = angle - 2;
63                 if (angle < 2)
64                         opAngle = angle + 2;
66                 // Find the edges of the bluff
67                 var baseLine;
68                 var endLine;
70                 // If we can't access the bluff, try different angles
71                 var retries = 0;
72                 var bluffCat = 2;
73                 while (bluffCat != 0 && retries < 5)
74                 {
75                         baseLine = findClearLine(bb, corners, angle, baseHeight);
76                         endLine = findClearLine(bb, corners, opAngle, baseHeight);
78                         bluffCat = unreachableBluff(bb, corners, baseLine, endLine);
79                         ++angle;
80                         if (angle > 3)
81                                 angle = 0;
83                         opAngle = angle - 2;
84                         if (angle < 2)
85                                 opAngle = angle + 2;
87                         ++retries;
88                 }
90                 // Inaccessible, turn it into a plateau
91                 if (bluffCat > 0)
92                 {
93                         removeBluff(points);
94                         continue;
95                 }
97                 // Create an entrance area by using a small margin
98                 var margin = 0.08;
99                 var ground = createTerrain(g_Terrains.mainTerrain);
100                 var slopeLength = (1 - margin) * getDistance(baseLine.midX, baseLine.midZ, endLine.midX, endLine.midZ);
102                 // Adjust the height of each point in the bluff
103                 for (var p = 0; p < points.length; ++p)
104                 {
105                         var pt = points[p];
106                         var dist = distanceOfPointFromLine(baseLine.x1, baseLine.z1, baseLine.x2, baseLine.z2, pt.x, pt.z);
108                         var curHeight = g_Map.getHeight(pt.x, pt.z);
109                         var newHeight = curHeight - curHeight * (dist / slopeLength) - 2;
111                         newHeight = Math.max(newHeight, endLine.height);
113                         if (newHeight <= endLine.height + 2 && g_Map.validT(pt.x, pt.z) && g_Map.getTexture(pt.x, pt.z).indexOf('cliff') > -1)
114                                 ground.place(pt.x, pt.z);
116                         g_Map.setHeight(pt.x, pt.z, newHeight);
117                 }
119                 // Smooth out the ground around the bluff
120                 fadeToGround(bb, corners.minX, corners.minZ, endLine.height);
121         }
123         addElements([
124                 {
125                         "func": addHills,
126                         "avoid": [
127                                 g_TileClasses.hill, 3,
128                                 g_TileClasses.player, 20,
129                                 g_TileClasses.valley, 2,
130                                 g_TileClasses.water, 2
131                         ],
132                         "stay": [g_TileClasses.bluff, 3],
133                         "sizes": g_AllSizes,
134                         "mixes": g_AllMixes,
135                         "amounts": g_AllAmounts
136                 }
137         ]);
139         addElements([
140                 {
141                         "func": addLayeredPatches,
142                         "avoid": [
143                                 g_TileClasses.dirt, 5,
144                                 g_TileClasses.forest, 2,
145                                 g_TileClasses.mountain, 2,
146                                 g_TileClasses.player, 12,
147                                 g_TileClasses.water, 3
148                         ],
149                         "stay": [g_TileClasses.bluff, 5],
150                         "sizes": ["normal"],
151                         "mixes": ["normal"],
152                         "amounts": ["normal"]
153                 }
154         ]);
156         addElements([
157                 {
158                         "func": addDecoration,
159                         "avoid": [
160                                 g_TileClasses.forest, 2,
161                                 g_TileClasses.player, 12,
162                                 g_TileClasses.water, 3
163                         ],
164                         "stay": [g_TileClasses.bluff, 5],
165                         "sizes": ["normal"],
166                         "mixes": ["normal"],
167                         "amounts": ["normal"]
168                 }
169         ]);
171         addElements([
172                 {
173                         "func": addProps,
174                         "avoid": [
175                                 g_TileClasses.forest, 2,
176                                 g_TileClasses.player, 12,
177                                 g_TileClasses.prop, 40,
178                                 g_TileClasses.water, 3
179                         ],
180                         "stay": [
181                                 g_TileClasses.bluff, 7,
182                                 g_TileClasses.mountain, 7
183                         ],
184                         "sizes": ["normal"],
185                         "mixes": ["normal"],
186                         "amounts": ["scarce"]
187                 }
188         ]);
190         addElements(shuffleArray([
191                 {
192                         "func": addForests,
193                         "avoid": [
194                                 g_TileClasses.berries, 5,
195                                 g_TileClasses.forest, 18,
196                                 g_TileClasses.metal, 5,
197                                 g_TileClasses.mountain, 5,
198                                 g_TileClasses.player, 20,
199                                 g_TileClasses.rock, 5,
200                                 g_TileClasses.water, 2
201                         ],
202                         "stay": [g_TileClasses.bluff, 6],
203                         "sizes": g_AllSizes,
204                         "mixes": g_AllMixes,
205                         "amounts": ["normal", "many", "tons"]
206                 },
207                 {
208                         "func": addMetal,
209                         "avoid": [
210                                 g_TileClasses.berries, 5,
211                                 g_TileClasses.forest, 5,
212                                 g_TileClasses.mountain, 2,
213                                 g_TileClasses.player, 50,
214                                 g_TileClasses.rock, 15,
215                                 g_TileClasses.metal, 40,
216                                 g_TileClasses.water, 3
217                         ],
218                         "stay": [g_TileClasses.bluff, 6],
219                         "sizes": ["normal"],
220                         "mixes": ["same"],
221                         "amounts": ["normal"]
222                 },
223                 {
224                         "func": addStone,
225                         "avoid": [
226                                 g_TileClasses.berries, 5,
227                                 g_TileClasses.forest, 5,
228                                 g_TileClasses.mountain, 2,
229                                 g_TileClasses.player, 50,
230                                 g_TileClasses.rock, 40,
231                                 g_TileClasses.metal, 15,
232                                 g_TileClasses.water, 3
233                         ],
234                         "stay": [g_TileClasses.bluff, 6],
235                         "sizes": ["normal"],
236                         "mixes": ["same"],
237                         "amounts": ["normal"]
238                 }
239         ]));
241         let savanna = currentBiome() == "savanna";
242         addElements(shuffleArray([
243                 {
244                         "func": addStragglerTrees,
245                         "avoid": [
246                                 g_TileClasses.berries, 5,
247                                 g_TileClasses.forest, 10,
248                                 g_TileClasses.metal, 5,
249                                 g_TileClasses.mountain, 1,
250                                 g_TileClasses.player, 12,
251                                 g_TileClasses.rock, 5,
252                                 g_TileClasses.water, 5
253                         ],
254                         "stay": [g_TileClasses.bluff, 6],
255                         "sizes": savanna ? ["big"] : g_AllSizes,
256                         "mixes": savanna ? ["varied"] : g_AllMixes,
257                         "amounts": savanna ? ["tons"] : ["normal", "many", "tons"]
258                 },
259                 {
260                         "func": addAnimals,
261                         "avoid": [
262                                 g_TileClasses.animals, 20,
263                                 g_TileClasses.forest, 5,
264                                 g_TileClasses.mountain, 1,
265                                 g_TileClasses.player, 20,
266                                 g_TileClasses.rock, 5,
267                                 g_TileClasses.metal, 5,
268                                 g_TileClasses.water, 3
269                         ],
270                         "stay": [g_TileClasses.bluff, 6],
271                         "sizes": g_AllSizes,
272                         "mixes": g_AllMixes,
273                         "amounts": ["normal", "many", "tons"]
274                 },
275                 {
276                         "func": addBerries,
277                         "avoid": [
278                                 g_TileClasses.berries, 50,
279                                 g_TileClasses.forest, 5,
280                                 g_TileClasses.metal, 10,
281                                 g_TileClasses.mountain, 2,
282                                 g_TileClasses.player, 20,
283                                 g_TileClasses.rock, 10,
284                                 g_TileClasses.water, 3
285                         ],
286                         "stay": [g_TileClasses.bluff, 6],
287                         "sizes": g_AllSizes,
288                         "mixes": g_AllMixes,
289                         "amounts": ["normal", "many", "tons"]
290                 }
291         ]));
295  * Add grass, rocks and bushes.
296  */
297 function addDecoration(constraint, size, deviation, fill)
299         var offset = getRandomDeviation(size, deviation);
300         var decorations = [
301                 [
302                         new SimpleObject(g_Decoratives.rockMedium, offset, 3 * offset, 0, offset)
303                 ],
304                 [
305                         new SimpleObject(g_Decoratives.rockLarge, offset, 2 * offset, 0, offset),
306                         new SimpleObject(g_Decoratives.rockMedium, offset, 3 * offset, 0, 2 * offset)
307                 ],
308                 [
309                         new SimpleObject(g_Decoratives.grassShort, offset, 2 * offset, 0, offset)
310                 ],
311                 [
312                         new SimpleObject(g_Decoratives.grass, 2 * offset, 4 * offset, 0, 1.8 * offset),
313                         new SimpleObject(g_Decoratives.grassShort, 3 * offset, 6 * offset, 1.2 * offset, 2.5 * offset)
314                 ],
315                 [
316                         new SimpleObject(g_Decoratives.bushMedium, offset, 2 * offset, 0, 2 * offset),
317                         new SimpleObject(g_Decoratives.bushSmall, 2 * offset, 4 * offset, 0, 2 * offset)
318                 ]
319         ];
321         var baseCount = 1;
322         if (currentBiome() == "tropic")
323                 baseCount = 8;
325         var counts = [
326                 scaleByMapSize(16, 262),
327                 scaleByMapSize(8, 131),
328                 baseCount * scaleByMapSize(13, 200),
329                 baseCount * scaleByMapSize(13, 200),
330                 baseCount * scaleByMapSize(13, 200)
331         ];
333         for (var i = 0; i < decorations.length; ++i)
334         {
335                 var decorCount = Math.floor(counts[i] * fill);
336                 var group = new SimpleGroup(decorations[i], true);
337                 createObjectGroupsDeprecated(group, 0, constraint, decorCount, 5);
338         }
342  * Create varying elevations.
344  * @param {Array} constraint - avoid/stay-classes
346  * @param {Object} el - the element to be rendered, for example:
347  *  "class": g_TileClasses.hill,
348  *      "painter": [g_Terrains.mainTerrain, g_Terrains.mainTerrain],
349  *      "size": 1,
350  *      "deviation": 0.2,
351  *      "fill": 1,
352  *      "count": scaleByMapSize(4, 8),
353  *      "minSize": Math.floor(scaleByMapSize(3, 8)),
354  *      "maxSize": Math.floor(scaleByMapSize(5, 10)),
355  *      "spread": Math.floor(scaleByMapSize(10, 20)),
356  *      "minElevation": 6,
357  *      "maxElevation": 12,
358  *      "steepness": 1.5
359  */
361 function addElevation(constraint, el)
363         var count = el.fill * el.count;
364         var minSize = el.minSize;
365         var maxSize = el.maxSize;
366         var spread = el.spread;
368         var elType = ELEVATION_MODIFY;
369         if (el.class == g_TileClasses.water)
370                 elType = ELEVATION_SET;
372         var widths = [];
374         // Allow for shore and cliff rendering
375         for (var s = el.painter.length; s > 2; --s)
376                 widths.push(1);
378         for (var i = 0; i < count; ++i)
379         {
380                 var elevation = randIntExclusive(el.minElevation, el.maxElevation);
381                 var smooth = Math.floor(elevation / el.steepness);
383                 var offset = getRandomDeviation(el.size, el.deviation);
384                 var pMinSize = Math.floor(minSize * offset);
385                 var pMaxSize = Math.floor(maxSize * offset);
386                 var pSpread = Math.floor(spread * offset);
387                 var pSmooth = Math.abs(Math.floor(smooth * offset));
388                 var pElevation = Math.floor(elevation * offset);
390                 pElevation = Math.max(el.minElevation, Math.min(pElevation, el.maxElevation));
392                 pMinSize = Math.min(pMinSize, pMaxSize);
393                 pMaxSize = Math.min(pMaxSize, el.maxSize);
394                 pMinSize = Math.max(pMaxSize, el.minSize);
396                 pSmooth = Math.max(pSmooth, 1);
398                 var pWidths = widths.concat(pSmooth);
400                 var placer = new ChainPlacer(pMinSize, pMaxSize, pSpread, 0.5);
401                 var terrainPainter = new LayeredPainter(el.painter, [pWidths]);
402                 var elevationPainter = new SmoothElevationPainter(elType, pElevation, pSmooth);
403                 createAreas(placer, [terrainPainter, elevationPainter, paintClass(el.class)], constraint, 1);
404         }
408  * Create rolling hills.
409  */
410 function addHills(constraint, size, deviation, fill)
412         addElevation(constraint, {
413                 "class": g_TileClasses.hill,
414                 "painter": [g_Terrains.mainTerrain, g_Terrains.mainTerrain],
415                 "size": size,
416                 "deviation": deviation,
417                 "fill": fill,
418                 "count": 8,
419                 "minSize": 5,
420                 "maxSize": 8,
421                 "spread": 20,
422                 "minElevation": 6,
423                 "maxElevation": 12,
424                 "steepness": 1.5
425         });
429  * Create random lakes with fish in it.
430  */
431 function addLakes(constraint, size, deviation, fill)
433         var lakeTile = g_Terrains.water;
435         if (currentBiome() == "temperate" || currentBiome() == "tropic")
436                 lakeTile = g_Terrains.dirt;
438         if (currentBiome() == "mediterranean")
439                 lakeTile = g_Terrains.tier2Terrain;
441         if (currentBiome() == "autumn")
442                 lakeTile = g_Terrains.shore;
444         addElevation(constraint, {
445                 "class": g_TileClasses.water,
446                 "painter": [lakeTile, lakeTile],
447                 "size": size,
448                 "deviation": deviation,
449                 "fill": fill,
450                 "count": 6,
451                 "minSize": 7,
452                 "maxSize": 9,
453                 "spread": 70,
454                 "minElevation": -15,
455                 "maxElevation": -2,
456                 "steepness": 1.5
457         });
459         addElements([
460                 {
461                         "func": addFish,
462                         "avoid": [
463                                 g_TileClasses.fish, 12,
464                                 g_TileClasses.hill, 8,
465                                 g_TileClasses.mountain, 8,
466                                 g_TileClasses.player, 8
467                         ],
468                         "stay": [g_TileClasses.water, 7],
469                         "sizes": g_AllSizes,
470                         "mixes": g_AllMixes,
471                         "amounts": ["normal", "many", "tons"]
472                 }
473         ]);
475         var group = new SimpleGroup([new SimpleObject(g_Decoratives.rockMedium, 1, 3, 1, 3)], true, g_TileClasses.dirt);
476         createObjectGroupsDeprecated(group, 0, [stayClasses(g_TileClasses.water, 1), borderClasses(g_TileClasses.water, 4, 3)], 1000, 100);
478         group = new SimpleGroup([new SimpleObject(g_Decoratives.reeds, 10, 15, 1, 3), new SimpleObject(g_Decoratives.rockMedium, 1, 3, 1, 3)], true, g_TileClasses.dirt);
479         createObjectGroupsDeprecated(group, 0, [stayClasses(g_TileClasses.water, 2), borderClasses(g_TileClasses.water, 4, 3)], 1000, 100);
483  * Universal function to create layered patches.
484  */
485 function addLayeredPatches(constraint, size, deviation, fill)
487         var minRadius = 1;
488         var maxRadius = Math.floor(scaleByMapSize(3, 5));
489         var count = fill * scaleByMapSize(15, 45);
491         var sizes = [
492                 scaleByMapSize(3, 6),
493                 scaleByMapSize(5, 10),
494                 scaleByMapSize(8, 21)
495         ];
497         for (var i = 0; i < sizes.length; ++i)
498         {
499                 var offset = getRandomDeviation(size, deviation);
500                 var patchMinRadius = Math.floor(minRadius * offset);
501                 var patchMaxRadius = Math.floor(maxRadius * offset);
502                 var patchSize = Math.floor(sizes[i] * offset);
503                 var patchCount = count * offset;
505                 if (patchMinRadius > patchMaxRadius)
506                         patchMinRadius = patchMaxRadius;
508                 var placer = new ChainPlacer(patchMinRadius, patchMaxRadius, patchSize, 0.5);
509                 var painter = new LayeredPainter(
510                         [
511                                 [g_Terrains.mainTerrain, g_Terrains.tier1Terrain],
512                                 [g_Terrains.tier1Terrain, g_Terrains.tier2Terrain],
513                                 [g_Terrains.tier2Terrain, g_Terrains.tier3Terrain],
514                                 [g_Terrains.tier4Terrain]
515                         ],
516                         [1, 1] // widths
517                 );
518                 createAreas(placer, [painter, paintClass(g_TileClasses.dirt)], constraint, patchCount);
519         }
523  * Create steep mountains.
524  */
525 function addMountains(constraint, size, deviation, fill)
527         addElevation(constraint, {
528                 "class": g_TileClasses.mountain,
529                 "painter": [g_Terrains.cliff, g_Terrains.hill],
530                 "size": size,
531                 "deviation": deviation,
532                 "fill": fill,
533                 "count": 8,
534                 "minSize": 2,
535                 "maxSize": 4,
536                 "spread": 100,
537                 "minElevation": 100,
538                 "maxElevation": 120,
539                 "steepness": 4
540         });
544  * Create plateaus.
545  */
546 function addPlateaus(constraint, size, deviation, fill)
548         var plateauTile = g_Terrains.dirt;
550         if (currentBiome() == "snowy")
551                 plateauTile = g_Terrains.tier1Terrain;
553         if (currentBiome() == "alpine" || currentBiome() == "savanna")
554                 plateauTile = g_Terrains.tier2Terrain;
556         if (currentBiome() == "autumn")
557                 plateauTile = g_Terrains.tier4Terrain;
559         addElevation(constraint, {
560                 "class": g_TileClasses.plateau,
561                 "painter": [g_Terrains.cliff, plateauTile],
562                 "size": size,
563                 "deviation": deviation,
564                 "fill": fill,
565                 "count": 15,
566                 "minSize": 2,
567                 "maxSize": 4,
568                 "spread": 200,
569                 "minElevation": 20,
570                 "maxElevation": 30,
571                 "steepness": 8
572         });
574         for (var i = 0; i < 40; ++i)
575         {
576                 var placer = new ChainPlacer(3, 15, 1, 0.5);
577                 var terrainPainter = new LayeredPainter([plateauTile, plateauTile], [3]);
578                 var hillElevation = randIntInclusive(4, 18);
579                 var elevationPainter = new SmoothElevationPainter(ELEVATION_MODIFY, hillElevation, hillElevation - 2);
581                 createAreas(
582                         placer,
583                         [
584                                 terrainPainter,
585                                 elevationPainter,
586                                 paintClass(g_TileClasses.hill)
587                         ],
588                         [
589                                 avoidClasses(g_TileClasses.hill, 7),
590                                 stayClasses(g_TileClasses.plateau, 7)
591                         ],
592                         1
593                 );
594         }
596         addElements([
597                 {
598                         "func": addDecoration,
599                         "avoid": [
600                                 g_TileClasses.dirt, 15,
601                                 g_TileClasses.forest, 2,
602                                 g_TileClasses.player, 12,
603                                 g_TileClasses.water, 3
604                         ],
605                         "stay": [g_TileClasses.plateau, 8],
606                         "sizes": ["normal"],
607                         "mixes": ["normal"],
608                         "amounts": ["tons"]
609                 },
610                 {
611                         "func": addProps,
612                         "avoid": [
613                                 g_TileClasses.forest, 2,
614                                 g_TileClasses.player, 12,
615                                 g_TileClasses.prop, 40,
616                                 g_TileClasses.water, 3
617                         ],
618                         "stay": [g_TileClasses.plateau, 8],
619                         "sizes": ["normal"],
620                         "mixes": ["normal"],
621                         "amounts": ["scarce"]
622                 }
623         ]);
627  * Place less usual decoratives like barrels or crates.
628  */
629 function addProps(constraint, size, deviation, fill)
631         var offset = getRandomDeviation(size, deviation);
633         var props = [
634                 [
635                         new SimpleObject(g_Props.skeleton, offset, 5 * offset, 0, 3 * offset + 2),
636                 ],
637                 [
638                         new SimpleObject(g_Props.barrels, offset, 2 * offset, 2, 3 * offset + 2),
639                         new SimpleObject(g_Props.cart, 0, offset, 5, 2.5 * offset + 5),
640                         new SimpleObject(g_Props.crate, offset, 2 * offset, 2, 2 * offset + 2),
641                         new SimpleObject(g_Props.well, 0, 1, 2, 2 * offset + 2)
642                 ]
643         ];
645         var baseCount = 1;
647         var counts = [
648                 scaleByMapSize(16, 262),
649                 scaleByMapSize(8, 131),
650                 baseCount * scaleByMapSize(13, 200),
651                 baseCount * scaleByMapSize(13, 200),
652                 baseCount * scaleByMapSize(13, 200)
653         ];
655         // Add small props
656         for (var i = 0; i < props.length; ++i)
657         {
658                 var propCount = Math.floor(counts[i] * fill);
659                 var group = new SimpleGroup(props[i], true);
660                 createObjectGroupsDeprecated(group, 0, constraint, propCount, 5);
661         }
663         // Add decorative trees
664         var trees = new SimpleObject(g_Decoratives.tree, 5 * offset, 30 * offset, 2, 3 * offset + 10);
665         createObjectGroupsDeprecated(new SimpleGroup([trees], true), 0, constraint, counts[0] * 5 * fill, 5);
668 function addValleys(constraint, size, deviation, fill, baseHeight)
670         if (baseHeight < 6)
671                 return;
673         let minElevation = Math.max(-baseHeight, 1 - baseHeight / (size * (deviation + 1)));
675         var valleySlope = g_Terrains.tier1Terrain;
676         var valleyFloor = g_Terrains.tier4Terrain;
678         if (currentBiome() == "desert")
679         {
680                 valleySlope = g_Terrains.tier3Terrain;
681                 valleyFloor = g_Terrains.dirt;
682         }
684         if (currentBiome() == "mediterranean")
685         {
686                 valleySlope = g_Terrains.tier2Terrain;
687                 valleyFloor = g_Terrains.dirt;
688         }
690         if (currentBiome() == "alpine" || currentBiome() == "savanna")
691                 valleyFloor = g_Terrains.tier2Terrain;
693         if (currentBiome() == "tropic")
694                 valleySlope = g_Terrains.dirt;
696         if (currentBiome() == "autumn")
697                 valleyFloor = g_Terrains.tier3Terrain;
699         addElevation(constraint, {
700                 "class": g_TileClasses.valley,
701                 "painter": [valleySlope, valleyFloor],
702                 "size": size,
703                 "deviation": deviation,
704                 "fill": fill,
705                 "count": 8,
706                 "minSize": 5,
707                 "maxSize": 8,
708                 "spread": 30,
709                 "minElevation": minElevation,
710                 "maxElevation": -2,
711                 "steepness": 4
712         });
716  * Create huntable animals.
717  */
718 function addAnimals(constraint, size, deviation, fill)
720         var groupOffset = getRandomDeviation(size, deviation);
722         var animals = [
723                 [new SimpleObject(g_Gaia.mainHuntableAnimal, 5 * groupOffset, 7 * groupOffset, 0, 4 * groupOffset)],
724                 [new SimpleObject(g_Gaia.secondaryHuntableAnimal, 2 * groupOffset, 3 * groupOffset, 0, 2 * groupOffset)]
725         ];
727         for (let animal of animals)
728                 createObjectGroupsDeprecated(
729                         new SimpleGroup(animal, true, g_TileClasses.animals),
730                         0,
731                         constraint,
732                         Math.floor(30 * fill),
733                         50);
736 function addBerries(constraint, size, deviation, fill)
738         let groupOffset = getRandomDeviation(size, deviation);
740         createObjectGroupsDeprecated(
741                 new SimpleGroup([new SimpleObject(g_Gaia.fruitBush, 5 * groupOffset, 5 * groupOffset, 0, 3 * groupOffset)], true, g_TileClasses.berries),
742                 0,
743                 constraint,
744                 Math.floor(50 * fill),
745                 40);
748 function addFish(constraint, size, deviation, fill)
750         var groupOffset = getRandomDeviation(size, deviation);
752         var fishes = [
753                 [new SimpleObject(g_Gaia.fish, groupOffset, 2 * groupOffset, 0, 2 * groupOffset)],
754                 [new SimpleObject(g_Gaia.fish, 2 * groupOffset, 4 * groupOffset, 10 * groupOffset, 20 * groupOffset)]
755         ];
757         for (let fish of fishes)
758                 createObjectGroupsDeprecated(
759                         new SimpleGroup(fish, true, g_TileClasses.fish),
760                         0,
761                         constraint,
762                         Math.floor(40 * fill),
763                         50);
766 function addForests(constraint, size, deviation, fill)
768         if (currentBiome() == "savanna")
769                 return;
771         let treeTypes = [
772                 [
773                         g_Terrains.forestFloor2 + TERRAIN_SEPARATOR + g_Gaia.tree1,
774                         g_Terrains.forestFloor2 + TERRAIN_SEPARATOR + g_Gaia.tree2,
775                         g_Terrains.forestFloor2
776                 ],
777                 [
778                         g_Terrains.forestFloor1 + TERRAIN_SEPARATOR + g_Gaia.tree4,
779                         g_Terrains.forestFloor1 + TERRAIN_SEPARATOR + g_Gaia.tree5,
780                         g_Terrains.forestFloor1
781                 ]
782         ];
784         let forestTypes = [
785                 [
786                         [g_Terrains.forestFloor2, g_Terrains.mainTerrain, treeTypes[0]],
787                         [g_Terrains.forestFloor2, treeTypes[0]]
788                 ],
789                 [
790                         [g_Terrains.forestFloor2, g_Terrains.mainTerrain, treeTypes[1]],
791                         [g_Terrains.forestFloor1, treeTypes[1]]],
792                 [
793                         [g_Terrains.forestFloor1, g_Terrains.mainTerrain, treeTypes[0]],
794                         [g_Terrains.forestFloor2, treeTypes[0]]],
795                 [
796                         [g_Terrains.forestFloor1, g_Terrains.mainTerrain, treeTypes[1]],
797                         [g_Terrains.forestFloor1, treeTypes[1]]
798                 ]
799         ];
801         for (let forestType of forestTypes)
802         {
803                 let offset = getRandomDeviation(size, deviation);
804                 createAreas(
805                         new ChainPlacer(1, Math.floor(scaleByMapSize(3, 5) * offset), Math.floor(50 * offset), 0.5),
806                         [
807                                 new LayeredPainter(forestType, [2]),
808                                 paintClass(g_TileClasses.forest)
809                         ],
810                         constraint,
811                         10 * fill);
812         }
815 function addMetal(constraint, size, deviation, fill)
817         var offset = getRandomDeviation(size, deviation);
818         createObjectGroupsDeprecated(
819                 new SimpleGroup([new SimpleObject(g_Gaia.metalLarge, offset, offset, 0, 4 * offset)], true, g_TileClasses.metal),
820                 0,
821                 constraint,
822                 1 + 20 * fill,
823                 100);
826 function addSmallMetal(constraint, size, mixes, amounts)
828         let deviation = getRandomDeviation(size, mixes);
829         createObjectGroupsDeprecated(
830                 new SimpleGroup([new SimpleObject(g_Gaia.metalSmall, 2 * deviation, 5 * deviation, deviation, 3 * deviation)], true, g_TileClasses.metal),
831                 0,
832                 constraint,
833                 1 + 20 * amounts,
834                 100);
838  * Create stone mines.
839  */
840 function addStone(constraint, size, deviation, fill)
842         var offset = getRandomDeviation(size, deviation);
844         var mines = [
845                 [
846                         new SimpleObject(g_Gaia.stoneSmall, 0, 2 * offset, 0, 4 * offset),
847                         new SimpleObject(g_Gaia.stoneLarge, offset, offset, 0, 4 * offset)
848                 ],
849                 [
850                         new SimpleObject(g_Gaia.stoneSmall, 2 * offset, 5 * offset, offset, 3 * offset)
851                 ]
852         ];
854         for (let mine of mines)
855                 createObjectGroupsDeprecated(
856                         new SimpleGroup(mine, true, g_TileClasses.rock),
857                         0,
858                         constraint,
859                         1 + 20 * fill,
860                         100);
864  * Create straggler trees.
865  */
866 function addStragglerTrees(constraint, size, deviation, fill)
868         // Ensure minimum distribution on african biome
869         if (currentBiome() == "savanna")
870         {
871                 fill = Math.max(fill, 2);
872                 size = Math.max(size, 1);
873         }
875         var trees = [g_Gaia.tree1, g_Gaia.tree2, g_Gaia.tree3, g_Gaia.tree4];
877         var treesPerPlayer = 40;
878         var playerBonus = Math.max(1, (getNumPlayers() - MAP_BORDER_WIDTH) / 2);
880         var offset = getRandomDeviation(size, deviation);
881         var treeCount = treesPerPlayer * playerBonus * fill;
882         var totalTrees = scaleByMapSize(treeCount, treeCount);
884         var count = Math.floor(totalTrees / trees.length) * fill;
885         var min = offset;
886         var max = 4 * offset;
887         var minDist = offset;
888         var maxDist = 5 * offset;
890         // More trees for the african biome
891         if (currentBiome() == "savanna")
892         {
893                 min = 3 * offset;
894                 max = 5 * offset;
895                 minDist = 2 * offset + 1;
896                 maxDist = 3 * offset + 2;
897         }
899         for (var i = 0; i < trees.length; ++i)
900         {
901                 var treesMax = max;
903                 // Don't clump fruit trees
904                 if (i == 2 && (currentBiome() == "desert" || currentBiome() == "mediterranean"))
905                         treesMax = 1;
907                 min = Math.min(min, treesMax);
909                 var group = new SimpleGroup([new SimpleObject(trees[i], min, treesMax, minDist, maxDist)], true, g_TileClasses.forest);
910                 createObjectGroupsDeprecated(group, 0, constraint, count);
911         }
914 ///////////
915 // Terrain Helpers
916 ///////////
919  * Determine if the endline of the bluff is within the tilemap.
921  * @returns {Number} 0 if the bluff is reachable, otherwise a positive number
922  */
923 function unreachableBluff(bb, corners, baseLine, endLine)
925         // If we couldn't find a slope line
926         if (typeof baseLine.midX === "undefined" || typeof endLine.midX === "undefined")
927                 return 1;
929         // If the end points aren't on the tilemap
930         if (!g_Map.validT(endLine.x1, endLine.z1) && !g_Map.validT(endLine.x2, endLine.z2))
931                 return 2;
933         var minTilesInGroup = 1;
934         var insideBluff = false;
935         var outsideBluff = false;
937         // If there aren't enough points in each row
938         for (var x = 0; x < bb.length; ++x)
939         {
940                 var count = 0;
941                 for (var z = 0; z < bb[x].length; ++z)
942                 {
943                         if (!bb[x][z].isFeature)
944                                 continue;
946                         var valid = g_Map.validT(x + corners.minX, z + corners.minZ);
948                         if (valid)
949                                 ++count;
951                         if (!insideBluff && valid)
952                                 insideBluff = true;
954                         if (outsideBluff && valid)
955                                 return 3;
956                 }
958                 // We're expecting the end of the bluff
959                 if (insideBluff && count < minTilesInGroup)
960                         outsideBluff = true;
961         }
963         var insideBluff = false;
964         var outsideBluff = false;
966         // If there aren't enough points in each column
967         for (var z = 0; z < bb[0].length; ++z)
968         {
969                 var count = 0;
970                 for (var x = 0; x < bb.length; ++x)
971                 {
972                         if (!bb[x][z].isFeature)
973                                 continue;
975                         var valid = g_Map.validT(x + corners.minX, z + corners.minZ);
977                         if (valid)
978                                 ++count;
980                         if (!insideBluff && valid)
981                                 insideBluff = true;
983                         if (outsideBluff && valid)
984                                 return 3;
985                 }
987                 // We're expecting the end of the bluff
988                 if (insideBluff && count < minTilesInGroup)
989                         outsideBluff = true;
990         }
992         // Bluff is reachable
993         return 0;
997  * Remove the bluff class and turn it into a plateau.
998  */
999 function removeBluff(points)
1001         for (var i = 0; i < points.length; ++i)
1002                 addToClass(points[i].x, points[i].z, g_TileClasses.mountain);
1006  * Create an array of points the fill a bounding box around a terrain feature.
1007  */
1008 function createBoundingBox(points, corners)
1010         var bb = [];
1011         var width = corners.maxX - corners.minX + 1;
1012         var length = corners.maxZ - corners.minZ + 1;
1013         for (var w = 0; w < width; ++w)
1014         {
1015                 bb[w] = [];
1016                 for (var l = 0; l < length; ++l)
1017                 {
1018                         var curHeight = g_Map.getHeight(w + corners.minX, l + corners.minZ);
1019                         bb[w][l] = {
1020                                 "height": curHeight,
1021                                 "isFeature": false
1022                         };
1023                 }
1024         }
1026         // Define the coordinates that represent the bluff
1027         for (var p = 0; p < points.length; ++p)
1028         {
1029                 var pt = points[p];
1030                 bb[pt.x - corners.minX][pt.z - corners.minZ].isFeature = true;
1031         }
1033         return bb;
1037  * Flattens the ground touching a terrain feature.
1038  */
1039 function fadeToGround(bb, minX, minZ, elevation)
1041         var ground = createTerrain(g_Terrains.mainTerrain);
1042         for (var x = 0; x < bb.length; ++x)
1043                 for (var z = 0; z < bb[x].length; ++z)
1044                 {
1045                         var pt = bb[x][z];
1046                         if (!pt.isFeature && nextToFeature(bb, x, z))
1047                         {
1048                                 var newEl = smoothElevation(x + minX, z + minZ);
1049                                 g_Map.setHeight(x + minX, z + minZ, newEl);
1050                                 ground.place(x + minX, z + minZ);
1051                         }
1052                 }
1056  * Find a 45 degree line in a bounding box that does not intersect any terrain feature.
1057  */
1058 function findClearLine(bb, corners, angle, baseHeight)
1060         // Angle - 0: northwest; 1: northeast; 2: southeast; 3: southwest
1061         var z = corners.maxZ;
1062         var xOffset = -1;
1063         var zOffset = -1;
1065         switch(angle)
1066         {
1067                 case 1:
1068                         xOffset = 1;
1069                         break;
1070                 case 2:
1071                         xOffset = 1;
1072                         zOffset = 1;
1073                         z = corners.minZ;
1074                         break;
1075                 case 3:
1076                         zOffset = 1;
1077                         z = corners.minZ;
1078                         break;
1079         }
1081         var clearLine = {};
1083         for (var x = corners.minX; x <= corners.maxX; ++x)
1084         {
1085                 var x2 = x;
1086                 var z2 = z;
1088                 var clear = true;
1090                 while (x2 >= corners.minX && x2 <= corners.maxX && z2 >= corners.minZ && z2 <= corners.maxZ)
1091                 {
1092                         var bp = bb[x2 - corners.minX][z2 - corners.minZ];
1093                         if (bp.isFeature && g_Map.validT(x2, z2))
1094                         {
1095                                 clear = false;
1096                                 break;
1097                         }
1099                         x2 = x2 + xOffset;
1100                         z2 = z2 + zOffset;
1101                 }
1103                 if (clear)
1104                 {
1105                         var lastX = x2 - xOffset;
1106                         var lastZ = z2 - zOffset;
1107                         var midX = Math.floor((x + lastX) / 2);
1108                         var midZ = Math.floor((z + lastZ) / 2);
1109                         clearLine = {
1110                                 "x1": x,
1111                                 "z1": z,
1112                                 "x2": lastX,
1113                                 "z2": lastZ,
1114                                 "midX": midX,
1115                                 "midZ": midZ,
1116                                 "height": baseHeight
1117                         };
1118                 }
1120                 if (clear && (angle == 1 || angle == 2))
1121                         break;
1123                 if (!clear && (angle == 0 || angle == 3))
1124                         break;
1125         }
1127         return clearLine;
1131  * Returns the corners of a bounding box.
1132  */
1133 function findCorners(points)
1135         // Find the bounding box of the terrain feature
1136         var mapSize = getMapSize();
1137         var minX = mapSize + 1;
1138         var minZ = mapSize + 1;
1139         var maxX = -1;
1140         var maxZ = -1;
1142         for (var p = 0; p < points.length; ++p)
1143         {
1144                 var pt = points[p];
1146                 minX = Math.min(pt.x, minX);
1147                 minZ = Math.min(pt.z, minZ);
1149                 maxX = Math.max(pt.x, maxX);
1150                 maxZ = Math.max(pt.z, maxZ);
1151         }
1153         return {
1154                 "minX": minX,
1155                 "minZ": minZ,
1156                 "maxX": maxX,
1157                 "maxZ": maxZ
1158         };
1162  * Finds the average elevation around a point.
1163  */
1164 function smoothElevation(x, z)
1166         var min = g_Map.getHeight(x, z);
1168         for (var xOffset = -1; xOffset <= 1; ++xOffset)
1169                 for (var zOffset = -1; zOffset <= 1; ++zOffset)
1170                 {
1171                         var thisX = x + xOffset;
1172                         var thisZ = z + zOffset;
1173                         if (!g_Map.validH(thisX, thisZ))
1174                                 continue;
1176                         var height = g_Map.getHeight(thisX, thisZ);
1177                         if (height < min)
1178                                 min = height;
1179                 }
1181         return min;
1185  * Determines if a point in a bounding box array is next to a terrain feature.
1186  */
1187 function nextToFeature(bb, x, z)
1189         for (var xOffset = -1; xOffset <= 1; ++xOffset)
1190                 for (var zOffset = -1; zOffset <= 1; ++zOffset)
1191                 {
1192                         var thisX = x + xOffset;
1193                         var thisZ = z + zOffset;
1194                         if (thisX < 0 || thisX >= bb.length || thisZ < 0 || thisZ >= bb[x].length || thisX == 0 && thisZ == 0)
1195                                 continue;
1197                         if (bb[thisX][thisZ].isFeature)
1198                                 return true;
1199                 }
1201         return false;
1205  * Returns a number within a random deviation of a base number.
1206  */
1207 function getRandomDeviation(base, deviation)
1209         return base + randFloat(-1, 1) * Math.min(base, deviation);
1213  * Import a given digital elevation model.
1214  * Scale it to the mapsize and paint the textures specified by coordinate on it.
1216  * @return the ratio of heightmap tiles per map size tiles
1217  */
1218 function paintHeightmap(mapName, func = undefined)
1220         /**
1221          * @property heightmap - An array with a square number of heights.
1222          * @property tilemap - The IDs of the palletmap to be painted for each heightmap tile.
1223          * @property pallet - The tile texture names used by the tilemap.
1224          */
1225         let mapData = RMS.ReadJSONFile("maps/random/" + mapName + ".hmap");
1227         let mapSize = getMapSize(); // Width of the map in terrain tiles
1228         let hmSize = Math.sqrt(mapData.heightmap.length);
1229         let scale = hmSize / (mapSize + 1); // There are mapSize + 1 vertices (each 1 tile is surrounded by 2x2 vertices)
1231         for (let x = 0; x <= mapSize; ++x)
1232                 for (let y = 0; y <= mapSize; ++y)
1233                 {
1234                         let hmPoint = { "x": x * scale, "y": y * scale };
1235                         let hmTile = { "x": Math.floor(hmPoint.x), "y": Math.floor(hmPoint.y) };
1236                         let shift = { "x": 0, "y": 0 };
1238                         if (hmTile.x == 0)
1239                                 shift.x = 1;
1240                         else if (hmTile.x == hmSize - 1)
1241                                 shift.x = - 2;
1242                         else if (hmTile.x == hmSize - 2)
1243                                 shift.x = - 1;
1245                         if (hmTile.y == 0)
1246                                 shift.y = 1;
1247                         else if (hmTile.y == hmSize - 1)
1248                                 shift.y = - 2;
1249                         else if (hmTile.y == hmSize - 2)
1250                                 shift.y = - 1;
1252                         let neighbors = [];
1253                         for (let localXi = 0; localXi < 4; ++localXi)
1254                                 for (let localYi = 0; localYi < 4; ++localYi)
1255                                         neighbors.push(mapData.heightmap[(hmTile.x + localXi + shift.x - 1) * hmSize + (hmTile.y + localYi + shift.y - 1)]);
1257                         setHeight(x, y, bicubicInterpolation(hmPoint.x - hmTile.x - shift.x, hmPoint.y - hmTile.y - shift.y, ...neighbors) / scale);
1259                         if (x < mapSize && y < mapSize)
1260                         {
1261                                 let i = hmTile.x * hmSize + hmTile.y;
1262                                 let tile = mapData.pallet[mapData.tilemap[i]];
1263                                 placeTerrain(x, y, tile);
1265                                 if (func)
1266                                         func(tile, x, y);
1267                         }
1268                 }
1270         return scale;