Remove rmgen euclidian distance helper function, refs rP20328 / D969.
[0ad.git] / binaries / data / mods / public / maps / random / rmgen2 / gaia.js
blob91bfca5a65cbcc56fdb2603bd002495c93a8bc92
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 rendered = createAreas(
40                         new ChainPlacer(Math.floor(minSize * offset), Math.floor(maxSize * offset), Math.floor(spread * offset), 0.5),
41                         [
42                                 new LayeredPainter([g_Terrains.cliff, g_Terrains.mainTerrain, constrastTerrain], [2, 3]),
43                                 new SmoothElevationPainter(ELEVATION_MODIFY, Math.floor(elevation * offset), 2),
44                                 paintClass(g_TileClasses.bluff)
45                         ],
46                         constraint,
47                         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) * Math.euclidDistance2D(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));
391                 pMinSize = Math.min(pMinSize, pMaxSize);
392                 pMaxSize = Math.min(pMaxSize, el.maxSize);
393                 pMinSize = Math.max(pMaxSize, el.minSize);
394                 pSmooth = Math.max(pSmooth, 1);
396                 createAreas(
397                         new ChainPlacer(pMinSize, pMaxSize, pSpread, 0.5),
398                         [
399                                 new LayeredPainter(el.painter, [widths.concat(pSmooth)]),
400                                 new SmoothElevationPainter(elType, pElevation, pSmooth),
401                                 paintClass(el.class)
402                         ],
403                         constraint,
404                         1);
405         }
409  * Create rolling hills.
410  */
411 function addHills(constraint, size, deviation, fill)
413         addElevation(constraint, {
414                 "class": g_TileClasses.hill,
415                 "painter": [g_Terrains.mainTerrain, g_Terrains.mainTerrain],
416                 "size": size,
417                 "deviation": deviation,
418                 "fill": fill,
419                 "count": 8,
420                 "minSize": 5,
421                 "maxSize": 8,
422                 "spread": 20,
423                 "minElevation": 6,
424                 "maxElevation": 12,
425                 "steepness": 1.5
426         });
430  * Create random lakes with fish in it.
431  */
432 function addLakes(constraint, size, deviation, fill)
434         var lakeTile = g_Terrains.water;
436         if (currentBiome() == "temperate" || currentBiome() == "tropic")
437                 lakeTile = g_Terrains.dirt;
439         if (currentBiome() == "mediterranean")
440                 lakeTile = g_Terrains.tier2Terrain;
442         if (currentBiome() == "autumn")
443                 lakeTile = g_Terrains.shore;
445         addElevation(constraint, {
446                 "class": g_TileClasses.water,
447                 "painter": [lakeTile, lakeTile],
448                 "size": size,
449                 "deviation": deviation,
450                 "fill": fill,
451                 "count": 6,
452                 "minSize": 7,
453                 "maxSize": 9,
454                 "spread": 70,
455                 "minElevation": -15,
456                 "maxElevation": -2,
457                 "steepness": 1.5
458         });
460         addElements([
461                 {
462                         "func": addFish,
463                         "avoid": [
464                                 g_TileClasses.fish, 12,
465                                 g_TileClasses.hill, 8,
466                                 g_TileClasses.mountain, 8,
467                                 g_TileClasses.player, 8
468                         ],
469                         "stay": [g_TileClasses.water, 7],
470                         "sizes": g_AllSizes,
471                         "mixes": g_AllMixes,
472                         "amounts": ["normal", "many", "tons"]
473                 }
474         ]);
476         var group = new SimpleGroup([new SimpleObject(g_Decoratives.rockMedium, 1, 3, 1, 3)], true, g_TileClasses.dirt);
477         createObjectGroupsDeprecated(group, 0, [stayClasses(g_TileClasses.water, 1), borderClasses(g_TileClasses.water, 4, 3)], 1000, 100);
479         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);
480         createObjectGroupsDeprecated(group, 0, [stayClasses(g_TileClasses.water, 2), borderClasses(g_TileClasses.water, 4, 3)], 1000, 100);
484  * Universal function to create layered patches.
485  */
486 function addLayeredPatches(constraint, size, deviation, fill)
488         var minRadius = 1;
489         var maxRadius = Math.floor(scaleByMapSize(3, 5));
490         var count = fill * scaleByMapSize(15, 45);
492         var patchSizes = [
493                 scaleByMapSize(3, 6),
494                 scaleByMapSize(5, 10),
495                 scaleByMapSize(8, 21)
496         ];
498         for (let patchSize of patchSizes)
499         {
500                 var offset = getRandomDeviation(size, deviation);
501                 var patchMinRadius = Math.floor(minRadius * offset);
502                 var patchMaxRadius = Math.floor(maxRadius * offset);
504                 createAreas(
505                         new ChainPlacer(Math.min(patchMinRadius, patchMaxRadius), patchMaxRadius, Math.floor(patchSize * offset), 0.5),
506                         [
507                                 new LayeredPainter(
508                                         [
509                                                 [g_Terrains.mainTerrain, g_Terrains.tier1Terrain],
510                                                 [g_Terrains.tier1Terrain, g_Terrains.tier2Terrain],
511                                                 [g_Terrains.tier2Terrain, g_Terrains.tier3Terrain],
512                                                 [g_Terrains.tier4Terrain]
513                                         ],
514                                         [1, 1]),
515                                 paintClass(g_TileClasses.dirt)
516                         ],
517                         constraint,
518                         count * offset);
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 hillElevation = randIntInclusive(4, 18);
577                 createAreas(
578                         new ChainPlacer(3, 15, 1, 0.5),
579                         [
580                                 new LayeredPainter([plateauTile, plateauTile], [3]),
581                                 new SmoothElevationPainter(ELEVATION_MODIFY, hillElevation, hillElevation - 2),
582                                 paintClass(g_TileClasses.hill)
583                         ],
584                         [
585                                 avoidClasses(g_TileClasses.hill, 7),
586                                 stayClasses(g_TileClasses.plateau, 7)
587                         ],
588                         1);
589         }
591         addElements([
592                 {
593                         "func": addDecoration,
594                         "avoid": [
595                                 g_TileClasses.dirt, 15,
596                                 g_TileClasses.forest, 2,
597                                 g_TileClasses.player, 12,
598                                 g_TileClasses.water, 3
599                         ],
600                         "stay": [g_TileClasses.plateau, 8],
601                         "sizes": ["normal"],
602                         "mixes": ["normal"],
603                         "amounts": ["tons"]
604                 },
605                 {
606                         "func": addProps,
607                         "avoid": [
608                                 g_TileClasses.forest, 2,
609                                 g_TileClasses.player, 12,
610                                 g_TileClasses.prop, 40,
611                                 g_TileClasses.water, 3
612                         ],
613                         "stay": [g_TileClasses.plateau, 8],
614                         "sizes": ["normal"],
615                         "mixes": ["normal"],
616                         "amounts": ["scarce"]
617                 }
618         ]);
622  * Place less usual decoratives like barrels or crates.
623  */
624 function addProps(constraint, size, deviation, fill)
626         var offset = getRandomDeviation(size, deviation);
628         var props = [
629                 [
630                         new SimpleObject(g_Props.skeleton, offset, 5 * offset, 0, 3 * offset + 2),
631                 ],
632                 [
633                         new SimpleObject(g_Props.barrels, offset, 2 * offset, 2, 3 * offset + 2),
634                         new SimpleObject(g_Props.cart, 0, offset, 5, 2.5 * offset + 5),
635                         new SimpleObject(g_Props.crate, offset, 2 * offset, 2, 2 * offset + 2),
636                         new SimpleObject(g_Props.well, 0, 1, 2, 2 * offset + 2)
637                 ]
638         ];
640         var baseCount = 1;
642         var counts = [
643                 scaleByMapSize(16, 262),
644                 scaleByMapSize(8, 131),
645                 baseCount * scaleByMapSize(13, 200),
646                 baseCount * scaleByMapSize(13, 200),
647                 baseCount * scaleByMapSize(13, 200)
648         ];
650         // Add small props
651         for (var i = 0; i < props.length; ++i)
652         {
653                 var propCount = Math.floor(counts[i] * fill);
654                 var group = new SimpleGroup(props[i], true);
655                 createObjectGroupsDeprecated(group, 0, constraint, propCount, 5);
656         }
658         // Add decorative trees
659         var trees = new SimpleObject(g_Decoratives.tree, 5 * offset, 30 * offset, 2, 3 * offset + 10);
660         createObjectGroupsDeprecated(new SimpleGroup([trees], true), 0, constraint, counts[0] * 5 * fill, 5);
663 function addValleys(constraint, size, deviation, fill, baseHeight)
665         if (baseHeight < 6)
666                 return;
668         let minElevation = Math.max(-baseHeight, 1 - baseHeight / (size * (deviation + 1)));
670         var valleySlope = g_Terrains.tier1Terrain;
671         var valleyFloor = g_Terrains.tier4Terrain;
673         if (currentBiome() == "desert")
674         {
675                 valleySlope = g_Terrains.tier3Terrain;
676                 valleyFloor = g_Terrains.dirt;
677         }
679         if (currentBiome() == "mediterranean")
680         {
681                 valleySlope = g_Terrains.tier2Terrain;
682                 valleyFloor = g_Terrains.dirt;
683         }
685         if (currentBiome() == "alpine" || currentBiome() == "savanna")
686                 valleyFloor = g_Terrains.tier2Terrain;
688         if (currentBiome() == "tropic")
689                 valleySlope = g_Terrains.dirt;
691         if (currentBiome() == "autumn")
692                 valleyFloor = g_Terrains.tier3Terrain;
694         addElevation(constraint, {
695                 "class": g_TileClasses.valley,
696                 "painter": [valleySlope, valleyFloor],
697                 "size": size,
698                 "deviation": deviation,
699                 "fill": fill,
700                 "count": 8,
701                 "minSize": 5,
702                 "maxSize": 8,
703                 "spread": 30,
704                 "minElevation": minElevation,
705                 "maxElevation": -2,
706                 "steepness": 4
707         });
711  * Create huntable animals.
712  */
713 function addAnimals(constraint, size, deviation, fill)
715         var groupOffset = getRandomDeviation(size, deviation);
717         var animals = [
718                 [new SimpleObject(g_Gaia.mainHuntableAnimal, 5 * groupOffset, 7 * groupOffset, 0, 4 * groupOffset)],
719                 [new SimpleObject(g_Gaia.secondaryHuntableAnimal, 2 * groupOffset, 3 * groupOffset, 0, 2 * groupOffset)]
720         ];
722         for (let animal of animals)
723                 createObjectGroupsDeprecated(
724                         new SimpleGroup(animal, true, g_TileClasses.animals),
725                         0,
726                         constraint,
727                         Math.floor(30 * fill),
728                         50);
731 function addBerries(constraint, size, deviation, fill)
733         let groupOffset = getRandomDeviation(size, deviation);
735         createObjectGroupsDeprecated(
736                 new SimpleGroup([new SimpleObject(g_Gaia.fruitBush, 5 * groupOffset, 5 * groupOffset, 0, 3 * groupOffset)], true, g_TileClasses.berries),
737                 0,
738                 constraint,
739                 Math.floor(50 * fill),
740                 40);
743 function addFish(constraint, size, deviation, fill)
745         var groupOffset = getRandomDeviation(size, deviation);
747         var fishes = [
748                 [new SimpleObject(g_Gaia.fish, groupOffset, 2 * groupOffset, 0, 2 * groupOffset)],
749                 [new SimpleObject(g_Gaia.fish, 2 * groupOffset, 4 * groupOffset, 10 * groupOffset, 20 * groupOffset)]
750         ];
752         for (let fish of fishes)
753                 createObjectGroupsDeprecated(
754                         new SimpleGroup(fish, true, g_TileClasses.fish),
755                         0,
756                         constraint,
757                         Math.floor(40 * fill),
758                         50);
761 function addForests(constraint, size, deviation, fill)
763         if (currentBiome() == "savanna")
764                 return;
766         let treeTypes = [
767                 [
768                         g_Terrains.forestFloor2 + TERRAIN_SEPARATOR + g_Gaia.tree1,
769                         g_Terrains.forestFloor2 + TERRAIN_SEPARATOR + g_Gaia.tree2,
770                         g_Terrains.forestFloor2
771                 ],
772                 [
773                         g_Terrains.forestFloor1 + TERRAIN_SEPARATOR + g_Gaia.tree4,
774                         g_Terrains.forestFloor1 + TERRAIN_SEPARATOR + g_Gaia.tree5,
775                         g_Terrains.forestFloor1
776                 ]
777         ];
779         let forestTypes = [
780                 [
781                         [g_Terrains.forestFloor2, g_Terrains.mainTerrain, treeTypes[0]],
782                         [g_Terrains.forestFloor2, treeTypes[0]]
783                 ],
784                 [
785                         [g_Terrains.forestFloor2, g_Terrains.mainTerrain, treeTypes[1]],
786                         [g_Terrains.forestFloor1, treeTypes[1]]],
787                 [
788                         [g_Terrains.forestFloor1, g_Terrains.mainTerrain, treeTypes[0]],
789                         [g_Terrains.forestFloor2, treeTypes[0]]],
790                 [
791                         [g_Terrains.forestFloor1, g_Terrains.mainTerrain, treeTypes[1]],
792                         [g_Terrains.forestFloor1, treeTypes[1]]
793                 ]
794         ];
796         for (let forestType of forestTypes)
797         {
798                 let offset = getRandomDeviation(size, deviation);
799                 createAreas(
800                         new ChainPlacer(1, Math.floor(scaleByMapSize(3, 5) * offset), Math.floor(50 * offset), 0.5),
801                         [
802                                 new LayeredPainter(forestType, [2]),
803                                 paintClass(g_TileClasses.forest)
804                         ],
805                         constraint,
806                         10 * fill);
807         }
810 function addMetal(constraint, size, deviation, fill)
812         var offset = getRandomDeviation(size, deviation);
813         createObjectGroupsDeprecated(
814                 new SimpleGroup([new SimpleObject(g_Gaia.metalLarge, offset, offset, 0, 4 * offset)], true, g_TileClasses.metal),
815                 0,
816                 constraint,
817                 1 + 20 * fill,
818                 100);
821 function addSmallMetal(constraint, size, mixes, amounts)
823         let deviation = getRandomDeviation(size, mixes);
824         createObjectGroupsDeprecated(
825                 new SimpleGroup([new SimpleObject(g_Gaia.metalSmall, 2 * deviation, 5 * deviation, deviation, 3 * deviation)], true, g_TileClasses.metal),
826                 0,
827                 constraint,
828                 1 + 20 * amounts,
829                 100);
833  * Create stone mines.
834  */
835 function addStone(constraint, size, deviation, fill)
837         var offset = getRandomDeviation(size, deviation);
839         var mines = [
840                 [
841                         new SimpleObject(g_Gaia.stoneSmall, 0, 2 * offset, 0, 4 * offset),
842                         new SimpleObject(g_Gaia.stoneLarge, offset, offset, 0, 4 * offset)
843                 ],
844                 [
845                         new SimpleObject(g_Gaia.stoneSmall, 2 * offset, 5 * offset, offset, 3 * offset)
846                 ]
847         ];
849         for (let mine of mines)
850                 createObjectGroupsDeprecated(
851                         new SimpleGroup(mine, true, g_TileClasses.rock),
852                         0,
853                         constraint,
854                         1 + 20 * fill,
855                         100);
859  * Create straggler trees.
860  */
861 function addStragglerTrees(constraint, size, deviation, fill)
863         // Ensure minimum distribution on african biome
864         if (currentBiome() == "savanna")
865         {
866                 fill = Math.max(fill, 2);
867                 size = Math.max(size, 1);
868         }
870         var trees = [g_Gaia.tree1, g_Gaia.tree2, g_Gaia.tree3, g_Gaia.tree4];
872         var treesPerPlayer = 40;
873         var playerBonus = Math.max(1, (getNumPlayers() - 3) / 2);
875         var offset = getRandomDeviation(size, deviation);
876         var treeCount = treesPerPlayer * playerBonus * fill;
877         var totalTrees = scaleByMapSize(treeCount, treeCount);
879         var count = Math.floor(totalTrees / trees.length) * fill;
880         var min = offset;
881         var max = 4 * offset;
882         var minDist = offset;
883         var maxDist = 5 * offset;
885         // More trees for the african biome
886         if (currentBiome() == "savanna")
887         {
888                 min = 3 * offset;
889                 max = 5 * offset;
890                 minDist = 2 * offset + 1;
891                 maxDist = 3 * offset + 2;
892         }
894         for (var i = 0; i < trees.length; ++i)
895         {
896                 var treesMax = max;
898                 // Don't clump fruit trees
899                 if (i == 2 && (currentBiome() == "desert" || currentBiome() == "mediterranean"))
900                         treesMax = 1;
902                 min = Math.min(min, treesMax);
904                 var group = new SimpleGroup([new SimpleObject(trees[i], min, treesMax, minDist, maxDist)], true, g_TileClasses.forest);
905                 createObjectGroupsDeprecated(group, 0, constraint, count);
906         }
909 ///////////
910 // Terrain Helpers
911 ///////////
914  * Determine if the endline of the bluff is within the tilemap.
916  * @returns {Number} 0 if the bluff is reachable, otherwise a positive number
917  */
918 function unreachableBluff(bb, corners, baseLine, endLine)
920         // If we couldn't find a slope line
921         if (typeof baseLine.midX === "undefined" || typeof endLine.midX === "undefined")
922                 return 1;
924         // If the end points aren't on the tilemap
925         if (!g_Map.validT(endLine.x1, endLine.z1) && !g_Map.validT(endLine.x2, endLine.z2))
926                 return 2;
928         var minTilesInGroup = 1;
929         var insideBluff = false;
930         var outsideBluff = false;
932         // If there aren't enough points in each row
933         for (var x = 0; x < bb.length; ++x)
934         {
935                 var count = 0;
936                 for (var z = 0; z < bb[x].length; ++z)
937                 {
938                         if (!bb[x][z].isFeature)
939                                 continue;
941                         var valid = g_Map.validT(x + corners.minX, z + corners.minZ);
943                         if (valid)
944                                 ++count;
946                         if (!insideBluff && valid)
947                                 insideBluff = true;
949                         if (outsideBluff && valid)
950                                 return 3;
951                 }
953                 // We're expecting the end of the bluff
954                 if (insideBluff && count < minTilesInGroup)
955                         outsideBluff = true;
956         }
958         var insideBluff = false;
959         var outsideBluff = false;
961         // If there aren't enough points in each column
962         for (var z = 0; z < bb[0].length; ++z)
963         {
964                 var count = 0;
965                 for (var x = 0; x < bb.length; ++x)
966                 {
967                         if (!bb[x][z].isFeature)
968                                 continue;
970                         var valid = g_Map.validT(x + corners.minX, z + corners.minZ);
972                         if (valid)
973                                 ++count;
975                         if (!insideBluff && valid)
976                                 insideBluff = true;
978                         if (outsideBluff && valid)
979                                 return 3;
980                 }
982                 // We're expecting the end of the bluff
983                 if (insideBluff && count < minTilesInGroup)
984                         outsideBluff = true;
985         }
987         // Bluff is reachable
988         return 0;
992  * Remove the bluff class and turn it into a plateau.
993  */
994 function removeBluff(points)
996         for (var i = 0; i < points.length; ++i)
997                 addToClass(points[i].x, points[i].z, g_TileClasses.mountain);
1001  * Create an array of points the fill a bounding box around a terrain feature.
1002  */
1003 function createBoundingBox(points, corners)
1005         var bb = [];
1006         var width = corners.maxX - corners.minX + 1;
1007         var length = corners.maxZ - corners.minZ + 1;
1008         for (var w = 0; w < width; ++w)
1009         {
1010                 bb[w] = [];
1011                 for (var l = 0; l < length; ++l)
1012                 {
1013                         var curHeight = g_Map.getHeight(w + corners.minX, l + corners.minZ);
1014                         bb[w][l] = {
1015                                 "height": curHeight,
1016                                 "isFeature": false
1017                         };
1018                 }
1019         }
1021         // Define the coordinates that represent the bluff
1022         for (var p = 0; p < points.length; ++p)
1023         {
1024                 var pt = points[p];
1025                 bb[pt.x - corners.minX][pt.z - corners.minZ].isFeature = true;
1026         }
1028         return bb;
1032  * Flattens the ground touching a terrain feature.
1033  */
1034 function fadeToGround(bb, minX, minZ, elevation)
1036         var ground = createTerrain(g_Terrains.mainTerrain);
1037         for (var x = 0; x < bb.length; ++x)
1038                 for (var z = 0; z < bb[x].length; ++z)
1039                 {
1040                         var pt = bb[x][z];
1041                         if (!pt.isFeature && nextToFeature(bb, x, z))
1042                         {
1043                                 var newEl = smoothElevation(x + minX, z + minZ);
1044                                 g_Map.setHeight(x + minX, z + minZ, newEl);
1045                                 ground.place(x + minX, z + minZ);
1046                         }
1047                 }
1051  * Find a 45 degree line in a bounding box that does not intersect any terrain feature.
1052  */
1053 function findClearLine(bb, corners, angle, baseHeight)
1055         // Angle - 0: northwest; 1: northeast; 2: southeast; 3: southwest
1056         var z = corners.maxZ;
1057         var xOffset = -1;
1058         var zOffset = -1;
1060         switch(angle)
1061         {
1062                 case 1:
1063                         xOffset = 1;
1064                         break;
1065                 case 2:
1066                         xOffset = 1;
1067                         zOffset = 1;
1068                         z = corners.minZ;
1069                         break;
1070                 case 3:
1071                         zOffset = 1;
1072                         z = corners.minZ;
1073                         break;
1074         }
1076         var clearLine = {};
1078         for (var x = corners.minX; x <= corners.maxX; ++x)
1079         {
1080                 var x2 = x;
1081                 var z2 = z;
1083                 var clear = true;
1085                 while (x2 >= corners.minX && x2 <= corners.maxX && z2 >= corners.minZ && z2 <= corners.maxZ)
1086                 {
1087                         var bp = bb[x2 - corners.minX][z2 - corners.minZ];
1088                         if (bp.isFeature && g_Map.validT(x2, z2))
1089                         {
1090                                 clear = false;
1091                                 break;
1092                         }
1094                         x2 = x2 + xOffset;
1095                         z2 = z2 + zOffset;
1096                 }
1098                 if (clear)
1099                 {
1100                         var lastX = x2 - xOffset;
1101                         var lastZ = z2 - zOffset;
1102                         var midX = Math.floor((x + lastX) / 2);
1103                         var midZ = Math.floor((z + lastZ) / 2);
1104                         clearLine = {
1105                                 "x1": x,
1106                                 "z1": z,
1107                                 "x2": lastX,
1108                                 "z2": lastZ,
1109                                 "midX": midX,
1110                                 "midZ": midZ,
1111                                 "height": baseHeight
1112                         };
1113                 }
1115                 if (clear && (angle == 1 || angle == 2))
1116                         break;
1118                 if (!clear && (angle == 0 || angle == 3))
1119                         break;
1120         }
1122         return clearLine;
1126  * Returns the corners of a bounding box.
1127  */
1128 function findCorners(points)
1130         // Find the bounding box of the terrain feature
1131         var mapSize = getMapSize();
1132         var minX = mapSize + 1;
1133         var minZ = mapSize + 1;
1134         var maxX = -1;
1135         var maxZ = -1;
1137         for (var p = 0; p < points.length; ++p)
1138         {
1139                 var pt = points[p];
1141                 minX = Math.min(pt.x, minX);
1142                 minZ = Math.min(pt.z, minZ);
1144                 maxX = Math.max(pt.x, maxX);
1145                 maxZ = Math.max(pt.z, maxZ);
1146         }
1148         return {
1149                 "minX": minX,
1150                 "minZ": minZ,
1151                 "maxX": maxX,
1152                 "maxZ": maxZ
1153         };
1157  * Finds the average elevation around a point.
1158  */
1159 function smoothElevation(x, z)
1161         var min = g_Map.getHeight(x, z);
1163         for (var xOffset = -1; xOffset <= 1; ++xOffset)
1164                 for (var zOffset = -1; zOffset <= 1; ++zOffset)
1165                 {
1166                         var thisX = x + xOffset;
1167                         var thisZ = z + zOffset;
1168                         if (!g_Map.validH(thisX, thisZ))
1169                                 continue;
1171                         var height = g_Map.getHeight(thisX, thisZ);
1172                         if (height < min)
1173                                 min = height;
1174                 }
1176         return min;
1180  * Determines if a point in a bounding box array is next to a terrain feature.
1181  */
1182 function nextToFeature(bb, x, z)
1184         for (var xOffset = -1; xOffset <= 1; ++xOffset)
1185                 for (var zOffset = -1; zOffset <= 1; ++zOffset)
1186                 {
1187                         var thisX = x + xOffset;
1188                         var thisZ = z + zOffset;
1189                         if (thisX < 0 || thisX >= bb.length || thisZ < 0 || thisZ >= bb[x].length || thisX == 0 && thisZ == 0)
1190                                 continue;
1192                         if (bb[thisX][thisZ].isFeature)
1193                                 return true;
1194                 }
1196         return false;
1200  * Returns a number within a random deviation of a base number.
1201  */
1202 function getRandomDeviation(base, deviation)
1204         return base + randFloat(-1, 1) * Math.min(base, deviation);
1208  * Import a given digital elevation model.
1209  * Scale it to the mapsize and paint the textures specified by coordinate on it.
1211  * @return the ratio of heightmap tiles per map size tiles
1212  */
1213 function paintHeightmap(mapName, func = undefined)
1215         /**
1216          * @property heightmap - An array with a square number of heights.
1217          * @property tilemap - The IDs of the palletmap to be painted for each heightmap tile.
1218          * @property pallet - The tile texture names used by the tilemap.
1219          */
1220         let mapData = RMS.ReadJSONFile("maps/random/" + mapName + ".hmap");
1222         let mapSize = getMapSize(); // Width of the map in terrain tiles
1223         let hmSize = Math.sqrt(mapData.heightmap.length);
1224         let scale = hmSize / (mapSize + 1); // There are mapSize + 1 vertices (each 1 tile is surrounded by 2x2 vertices)
1226         for (let x = 0; x <= mapSize; ++x)
1227                 for (let y = 0; y <= mapSize; ++y)
1228                 {
1229                         let hmPoint = { "x": x * scale, "y": y * scale };
1230                         let hmTile = { "x": Math.floor(hmPoint.x), "y": Math.floor(hmPoint.y) };
1231                         let shift = { "x": 0, "y": 0 };
1233                         if (hmTile.x == 0)
1234                                 shift.x = 1;
1235                         else if (hmTile.x == hmSize - 1)
1236                                 shift.x = - 2;
1237                         else if (hmTile.x == hmSize - 2)
1238                                 shift.x = - 1;
1240                         if (hmTile.y == 0)
1241                                 shift.y = 1;
1242                         else if (hmTile.y == hmSize - 1)
1243                                 shift.y = - 2;
1244                         else if (hmTile.y == hmSize - 2)
1245                                 shift.y = - 1;
1247                         let neighbors = [];
1248                         for (let localXi = 0; localXi < 4; ++localXi)
1249                                 for (let localYi = 0; localYi < 4; ++localYi)
1250                                         neighbors.push(mapData.heightmap[(hmTile.x + localXi + shift.x - 1) * hmSize + (hmTile.y + localYi + shift.y - 1)]);
1252                         setHeight(x, y, bicubicInterpolation(hmPoint.x - hmTile.x - shift.x, hmPoint.y - hmTile.y - shift.y, ...neighbors) / scale);
1254                         if (x < mapSize && y < mapSize)
1255                         {
1256                                 let i = hmTile.x * hmSize + hmTile.y;
1257                                 let tile = mapData.pallet[mapData.tilemap[i]];
1258                                 placeTerrain(x, y, tile);
1260                                 if (func)
1261                                         func(tile, x, y);
1262                         }
1263                 }
1265         return scale;