Fix the two incorrect replacements in rP21434, refs #4950.
[0ad.git] / binaries / data / mods / public / maps / random / alpine_valley.js
blobcf41c3159f6f397c11a0657108050566879e779f
1 Engine.LoadLibrary("rmgen");
2 Engine.LoadLibrary("rmgen-common");
4 TILE_CENTERED_HEIGHT_MAP = true;
6 /**
7  * This class creates random mountainranges without enclosing any area completely.
8  *
9  * To determine their location, a graph is created where each vertex is a possible starting or
10  * ending location of a mountainrange and each edge a possible mountainrange.
11  *
12  * That graph starts nearly complete (i.e almost every vertex is connected to most other vertices).
13  * After a random edge was chosen and placed as a mountainrange,
14  * all edges that intersect, that leave a too small gap to another mountainrange or that are connected to
15  * too many other mountainranges are removed from the graph.
16  * This is repeated until all edges were removed.
17  */
18 function MountainRangeBuilder(args)
20         /**
21          * These parameters paint the mountainranges after their location was determined.
22          */
23         this.pathplacer = args.pathplacer;
24         this.painters = args.painters;
25         this.constraint = args.constraint;
26         this.mountainWidth = args.mountainWidth;
28         /**
29          * Minimum geometric distance between two mountains that don't end in one place (disjoint edges).
30          */
31         this.minDistance = args.mountainWidth + args.passageWidth;
33         /**
34          * Array of Vector2D locations where a mountainrange can start or end.
35          */
36         this.vertices = args.points;
38         /**
39          * Number of mountainranges starting or ending at the given point.
40          */
41         this.vertexDegree = this.vertices.map(p => 0);
43         /**
44          * Highest number of mountainranges that can meet in one point (maximum degree of each vertex).
45          */
46         this.maxDegree = args.maxDegree;
48         /**
49          * Each possible edge is an array containing two vertex indices.
50          * The algorithm adds possible edges consecutively and removes subsequently invalid edges.
51          */
52         this.possibleEdges = [];
53         this.InitPossibleEdges();
55         /**
56          * A two-dimensional array of booleans that are true if the two corresponding vertices may be connected by a new edge (mountainrange).
57          * It is initialized with some points that should never be connected and updated with every placed edge.
58          * The purpose is to rule out any cycles in the graph, i.e. prevent any territory enclosed by mountainranges.
59          */
60         this.verticesConnectable = [];
61         this.InitConnectable();
63         /**
64          * Currently iterated item of possibleEdges that is either used as a mountainrange or removed from the possibleEdges.
65          */
66         this.index = undefined;
68         /**
69          * These variables hold the indices of the two points of that edge and the location of them as a Vector2D.
70          */
71         this.currentEdge = undefined;
72         this.currentEdgeStart = undefined;
73         this.currentEdgeEnd = undefined;
76 MountainRangeBuilder.prototype.InitPossibleEdges = function()
78         for (let i = 0; i < this.vertices.length; ++i)
79                 for (let j = numPlayers; j < this.vertices.length; ++j)
80                         if (j > i)
81                                 this.possibleEdges.push([i, j]);
84 MountainRangeBuilder.prototype.InitConnectable = function()
86         for (let i = 0; i < this.vertices.length; ++i)
87         {
88                 this.verticesConnectable[i] = [];
89                 for (let j = 0; j < this.vertices.length; ++j)
90                         this.verticesConnectable[i][j] = i >= numPlayers || j >= numPlayers || i == j || i != j - 1 && i != j + 1;
91         }
94 MountainRangeBuilder.prototype.SetConnectable = function(isConnectable)
96         this.verticesConnectable[this.currentEdge[0]][this.currentEdge[1]] = isConnectable;
97         this.verticesConnectable[this.currentEdge[1]][this.currentEdge[0]] = isConnectable;
100 MountainRangeBuilder.prototype.UpdateCurrentEdge = function()
102         this.currentEdge = this.possibleEdges[this.index];
103         this.currentEdgeStart = this.vertices[this.currentEdge[0]];
104         this.currentEdgeEnd = this.vertices[this.currentEdge[1]];
108  * Remove all edges that are too close to the current mountainrange or intersect.
109  */
110 MountainRangeBuilder.prototype.RemoveInvalidEdges = function()
112         for (let i = 0; i < this.possibleEdges.length; ++i)
113         {
114                 this.UpdateCurrentEdge();
116                 let comparedEdge = this.possibleEdges[i];
117                 let comparedEdgeStart = this.vertices[comparedEdge[0]];
118                 let comparedEdgeEnd = this.vertices[comparedEdge[1]];
120                 let edge0Equal = this.currentEdgeStart == comparedEdgeStart;
121                 let edge1Equal = this.currentEdgeStart == comparedEdgeEnd;
122                 let edge2Equal = this.currentEdgeEnd == comparedEdgeEnd;
123                 let edge3Equal = this.currentEdgeEnd == comparedEdgeStart;
125                 if (!edge0Equal && !edge2Equal && !edge1Equal && !edge3Equal  && testLineIntersection(this.currentEdgeStart, this.currentEdgeEnd, comparedEdgeStart, comparedEdgeEnd, this.minDistance) ||
126                    ( edge0Equal && !edge2Equal || !edge1Equal &&  edge3Equal) && distanceOfPointFromLine(this.currentEdgeStart, this.currentEdgeEnd, comparedEdgeEnd) < this.minDistance ||
127                    (!edge0Equal &&  edge2Equal ||  edge1Equal && !edge3Equal) && distanceOfPointFromLine(this.currentEdgeStart, this.currentEdgeEnd, comparedEdgeStart) < this.minDistance)
128                 {
129                         this.possibleEdges.splice(i, 1);
130                         --i;
131                         if (this.index > i)
132                                 --this.index;
133                 }
134         }
138  * Tests using depth-first-search if the graph according to pointsConnectable contains a cycle,
139  * i.e. if adding the currentEdge would result in an area enclosed by mountainranges.
140  */
141 MountainRangeBuilder.prototype.HasCycles = function()
143         let tree = [];
144         let backtree = [];
145         let pointQueue = [this.currentEdge[0]];
147         while (pointQueue.length)
148         {
149                 let selectedPoint = pointQueue.shift();
151                 if (tree.indexOf(selectedPoint) == -1)
152                 {
153                         tree.push(selectedPoint);
154                         backtree.push(-1);
155                 }
157                 for (let i = 0; i < this.vertices.length; ++i)
158                 {
159                         if (this.verticesConnectable[selectedPoint][i] || i == backtree[tree.lastIndexOf(selectedPoint)])
160                                 continue;
162                         // If the current point was encountered already, then a cycle was identified.
163                         if (tree.indexOf(i) != -1)
164                                 return true;
166                         // Otherwise visit this point next
167                         pointQueue.unshift(i);
168                         tree.push(i);
169                         backtree.push(selectedPoint);
170                 }
171         }
173         return false;
176 MountainRangeBuilder.prototype.PaintCurrentEdge = function()
178         this.pathplacer.start = this.currentEdgeStart;
179         this.pathplacer.end = this.currentEdgeEnd;
180         this.pathplacer.width = this.mountainWidth;
182         // Creating mountainrange
183         if (!createArea(this.pathplacer, this.painters, this.constraint))
184                 return false;
186         // Creating circular mountains at both ends of that mountainrange
187         for (let point of [this.currentEdgeStart, this.currentEdgeEnd])
188                 createArea(
189                         new ClumpPlacer(diskArea(this.mountainWidth / 2), 0.95, 0.6, Infinity, point),
190                         this.painters,
191                         this.constraint);
193         return true;
197  * This is the only function meant to be publicly accessible.
198  */
199 MountainRangeBuilder.prototype.CreateMountainRanges = function()
201         g_Map.log("Creating mountainrange with " + this.possibleEdges.length + " possible edges");
203         let max = this.possibleEdges.length
205         while (this.possibleEdges.length)
206         {
207                 Engine.SetProgress(35 - 15 * this.possibleEdges.length / max);
209                 this.index = randIntExclusive(0, this.possibleEdges.length);
210                 this.UpdateCurrentEdge();
211                 this.SetConnectable(false);
213                 if (this.vertexDegree[this.currentEdge[0]] < this.maxDegree &&
214                     this.vertexDegree[this.currentEdge[1]] < this.maxDegree &&
215                     !this.HasCycles() &&
216                     this.PaintCurrentEdge())
217                 {
218                         ++this.vertexDegree[this.currentEdge[0]];
219                         ++this.vertexDegree[this.currentEdge[1]];
220                         this.RemoveInvalidEdges();
221                 }
222                 else
223                         this.SetConnectable(true);
225                 this.possibleEdges.splice(this.index, 1);
226         }
229 if (randBool())
231         RandomMapLogger.prototype.printDirectly("Setting late spring biome.\n");
232         var tPrimary = ["alpine_dirt_grass_50"];
233         var tForestFloor = "alpine_forrestfloor";
234         var tCliff = ["alpine_cliff_a", "alpine_cliff_b", "alpine_cliff_c"];
235         var tSecondary = "alpine_grass_rocky";
236         var tHalfSnow = ["alpine_grass_snow_50", "alpine_dirt_snow"];
237         var tSnowLimited = ["alpine_snow_rocky"];
238         var tDirt = "alpine_dirt";
239         var tRoad = "new_alpine_citytile";
240         var tRoadWild = "new_alpine_citytile";
242         var oPine = "gaia/flora_tree_pine";
243         var oBerryBush = "gaia/flora_bush_berry";
244         var oDeer = "gaia/fauna_deer";
245         var oRabbit = "gaia/fauna_rabbit";
246         var oStoneLarge = "gaia/geology_stonemine_alpine_quarry";
247         var oStoneSmall = "gaia/geology_stone_alpine_a";
248         var oMetalLarge = "gaia/geology_metal_alpine_slabs";
250         var aGrass = "actor|props/flora/grass_soft_small_tall.xml";
251         var aGrassShort = "actor|props/flora/grass_soft_large.xml";
252         var aRockLarge = "actor|geology/stone_granite_med.xml";
253         var aRockMedium = "actor|geology/stone_granite_med.xml";
254         var aBushMedium = "actor|props/flora/bush_medit_me.xml";
255         var aBushSmall = "actor|props/flora/bush_medit_sm.xml";
257 else
259         RandomMapLogger.prototype.printDirectly("Setting winter biome.\n");
260         var tPrimary = ["alpine_snow_a", "alpine_snow_b"];
261         var tForestFloor = "alpine_forrestfloor_snow";
262         var tCliff = ["alpine_cliff_snow"];
263         var tSecondary = "alpine_grass_snow_50";
264         var tHalfSnow = ["alpine_grass_snow_50", "alpine_dirt_snow"];
265         var tSnowLimited = ["alpine_snow_a", "alpine_snow_b"];
266         var tDirt = "alpine_dirt";
267         var tRoad = "new_alpine_citytile";
268         var tRoadWild = "new_alpine_citytile";
270         var oPine = "gaia/flora_tree_pine_w";
271         var oBerryBush = "gaia/flora_bush_berry";
272         var oDeer = "gaia/fauna_deer";
273         var oRabbit = "gaia/fauna_rabbit";
274         var oStoneLarge = "gaia/geology_stonemine_alpine_quarry";
275         var oStoneSmall = "gaia/geology_stone_alpine_a";
276         var oMetalLarge = "gaia/geology_metal_alpine_slabs";
278         var aGrass = "actor|props/flora/grass_soft_dry_small_tall.xml";
279         var aGrassShort = "actor|props/flora/grass_soft_dry_large.xml";
280         var aRockLarge = "actor|geology/stone_granite_med.xml";
281         var aRockMedium = "actor|geology/stone_granite_med.xml";
282         var aBushMedium = "actor|props/flora/bush_medit_me_dry.xml";
283         var aBushSmall = "actor|props/flora/bush_medit_sm_dry.xml";
286 var heightLand = 3;
287 var heightOffsetBump = 2;
288 var snowlineHeight = 29;
289 var heightMountain = 30;
291 const pForest = [tForestFloor + TERRAIN_SEPARATOR + oPine, tForestFloor];
293 var g_Map = new RandomMap(heightLand, tPrimary);
295 const numPlayers = getNumPlayers();
296 const mapCenter = g_Map.getCenter();
298 var clPlayer = g_Map.createTileClass();
299 var clHill = g_Map.createTileClass();
300 var clForest = g_Map.createTileClass();
301 var clDirt = g_Map.createTileClass();
302 var clRock = g_Map.createTileClass();
303 var clMetal = g_Map.createTileClass();
304 var clFood = g_Map.createTileClass();
305 var clBaseResource = g_Map.createTileClass();
307 var [playerIDs, playerPosition, playerAngle, startAngle] = playerPlacementCircle(fractionToTiles(0.35));
309 placePlayerBases({
310         "PlayerPlacement": [playerIDs, playerPosition],
311         "PlayerTileClass": clPlayer,
312         "BaseResourceClass": clBaseResource,
313         "CityPatch": {
314                 "outerTerrain": tRoadWild,
315                 "innerTerrain": tRoad
316         },
317         "Chicken": {
318         },
319         "Berries": {
320                 "template": oBerryBush
321         },
322         "Mines": {
323                 "types": [
324                         { "template": oMetalLarge },
325                         { "template": oStoneLarge }
326                 ]
327         },
328         "Trees": {
329                 "template": oPine
330         },
331         "Decoratives": {
332                 "template": aGrassShort
333         }
335 Engine.SetProgress(20);
337 new MountainRangeBuilder({
338         "pathplacer": new PathPlacer(undefined, undefined, undefined, 0.4, scaleByMapSize(3, 12), 0.1, 0.1, 0.1),
339         "painters":[
340                 new LayeredPainter([tCliff, tPrimary], [3]),
341                 new SmoothElevationPainter(ELEVATION_SET, heightMountain, 2),
342                 new TileClassPainter(clHill)
343         ],
344         "constraint": avoidClasses(clPlayer, 20),
345         "passageWidth": scaleByMapSize(10, 15),
346         "mountainWidth": scaleByMapSize(9, 15),
347         "maxDegree": 3,
348         "points": [
349                 // Four points near each player
350                 ...distributePointsOnCircle(numPlayers, startAngle + Math.PI / numPlayers, fractionToTiles(0.49), mapCenter)[0],
351                 ...distributePointsOnCircle(numPlayers, startAngle + Math.PI / numPlayers * 1.4, fractionToTiles(0.34), mapCenter)[0],
352                 ...distributePointsOnCircle(numPlayers, startAngle + Math.PI / numPlayers * 0.6, fractionToTiles(0.34), mapCenter)[0],
353                 ...distributePointsOnCircle(numPlayers, startAngle + Math.PI / numPlayers, fractionToTiles(0.18), mapCenter)[0],
354                 mapCenter
355         ]
356 }).CreateMountainRanges();
358 Engine.SetProgress(35);
360 paintTerrainBasedOnHeight(heightLand + 0.1, snowlineHeight, 0, tCliff);
361 paintTerrainBasedOnHeight(snowlineHeight, heightMountain, 3, tSnowLimited);
363 g_Map.log("Creating bumps");
364 createAreas(
365         new ClumpPlacer(scaleByMapSize(20, 50), 0.3, 0.06, Infinity),
366         new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetBump, 2),
367         avoidClasses(clPlayer, 10),
368         scaleByMapSize(100, 200));
369 Engine.SetProgress(40);
371 g_Map.log("Creating hills");
372 createAreas(
373         new ClumpPlacer(scaleByMapSize(40, 150), 0.2, 0.1, Infinity),
374         [
375                 new LayeredPainter([tCliff, tSnowLimited], [2]),
376                 new SmoothElevationPainter(ELEVATION_SET, heightMountain, 2),
377                 new TileClassPainter(clHill)
378         ],
379         avoidClasses(clPlayer, 20, clHill, 14),
380         scaleByMapSize(10, 80) * numPlayers
382 Engine.SetProgress(50);
384 g_Map.log("Creating forests");
385 var [forestTrees, stragglerTrees] = getTreeCounts(500, 3000, 0.7);
386 var types = [
387         [[tForestFloor, tPrimary, pForest], [tForestFloor, pForest]]
390 var size = forestTrees / (scaleByMapSize(2,8) * numPlayers);
392 var num = Math.floor(size / types.length);
393 for (let type of types)
394         createAreas(
395                 new ClumpPlacer(forestTrees / num, 0.1, 0.1, Infinity),
396                 [
397                         new LayeredPainter(type, [2]),
398                         new TileClassPainter(clForest)
399                 ],
400                 avoidClasses(clPlayer, 12, clForest, 10, clHill, 0),
401                 num);
402 Engine.SetProgress(60);
404 g_Map.log("Creating dirt patches");
405 for (let size of [scaleByMapSize(3, 48), scaleByMapSize(5, 84), scaleByMapSize(8, 128)])
406         createAreas(
407                 new ClumpPlacer(size, 0.3, 0.06, 0.5),
408                 [
409                         new LayeredPainter([[tDirt, tHalfSnow], [tHalfSnow, tSnowLimited]], [2]),
410                         new TileClassPainter(clDirt)
411                 ],
412                 avoidClasses(clForest, 0, clHill, 0, clDirt, 5, clPlayer, 12),
413                 scaleByMapSize(15, 45));
415 g_Map.log("Creating grass patches");
416 for (let size of [scaleByMapSize(2, 32), scaleByMapSize(3, 48), scaleByMapSize(5, 80)])
417         createAreas(
418                 new ClumpPlacer(size, 0.3, 0.06, 0.5),
419                 new TerrainPainter(tSecondary),
420                 avoidClasses(clForest, 0, clHill, 0, clDirt, 5, clPlayer, 12),
421                 scaleByMapSize(15, 45));
423 Engine.SetProgress(65);
425 g_Map.log("Creating stone mines");
426 var group = new SimpleGroup([new SimpleObject(oStoneSmall, 0, 2, 0, 4, 0, 2 * Math.PI, 1), new SimpleObject(oStoneLarge, 1, 1, 0, 4, 0, 2 * Math.PI, 4)], true, clRock);
427 createObjectGroupsDeprecated(group, 0,
428         avoidClasses(clForest, 1, clPlayer, 20, clRock, 10, clHill, 1),
429         scaleByMapSize(4,16), 100
432 g_Map.log("Creating small stone mines");
433 group = new SimpleGroup([new SimpleObject(oStoneSmall, 2,5, 1,3)], true, clRock);
434 createObjectGroupsDeprecated(group, 0,
435         avoidClasses(clForest, 1, clPlayer, 20, clRock, 10, clHill, 1),
436         scaleByMapSize(4,16), 100
439 g_Map.log("Creating metal mines");
440 group = new SimpleGroup([new SimpleObject(oMetalLarge, 1,1, 0,4)], true, clMetal);
441 createObjectGroupsDeprecated(group, 0,
442         avoidClasses(clForest, 1, clPlayer, 20, clMetal, 10, clRock, 5, clHill, 1),
443         scaleByMapSize(4,16), 100
445 Engine.SetProgress(70);
447 g_Map.log("Creating small decorative rocks");
448 group = new SimpleGroup(
449         [new SimpleObject(aRockMedium, 1,3, 0,1)],
450         true
452 createObjectGroupsDeprecated(
453         group, 0,
454         avoidClasses(clForest, 0, clPlayer, 0, clHill, 0),
455         scaleByMapSize(16, 262), 50
458 g_Map.log("Creating large decorative rocks");
459 group = new SimpleGroup(
460         [new SimpleObject(aRockLarge, 1,2, 0,1), new SimpleObject(aRockMedium, 1,3, 0,2)],
461         true
463 createObjectGroupsDeprecated(
464         group, 0,
465         avoidClasses(clForest, 0, clPlayer, 0, clHill, 0),
466         scaleByMapSize(8, 131), 50
468 Engine.SetProgress(75);
470 g_Map.log("Creating deer");
471 group = new SimpleGroup(
472         [new SimpleObject(oDeer, 5,7, 0,4)],
473         true, clFood
475 createObjectGroupsDeprecated(group, 0,
476         avoidClasses(clForest, 0, clPlayer, 10, clHill, 1, clFood, 20),
477         3 * numPlayers, 50
480 g_Map.log("Creating berry bush");
481 group = new SimpleGroup(
482         [new SimpleObject(oBerryBush, 5,7, 0,4)],
483         true, clFood
485 createObjectGroupsDeprecated(group, 0,
486         avoidClasses(clForest, 0, clPlayer, 20, clHill, 1, clFood, 10),
487         randIntInclusive(1, 4) * numPlayers + 2, 50
490 g_Map.log("Creating rabbit");
491 group = new SimpleGroup(
492         [new SimpleObject(oRabbit, 2,3, 0,2)],
493         true, clFood
495 createObjectGroupsDeprecated(group, 0,
496         avoidClasses(clForest, 0, clPlayer, 10, clHill, 1, clFood, 20),
497         3 * numPlayers, 50
499 Engine.SetProgress(85);
501 createStragglerTrees(
502         [oPine],
503         avoidClasses(clForest, 1, clHill, 1, clPlayer, 12, clMetal, 6, clRock, 6),
504         clForest,
505         stragglerTrees);
507 g_Map.log("Creating small grass tufts");
508 var planetm = 1;
510 group = new SimpleGroup(
511         [new SimpleObject(aGrassShort, 1,2, 0,1, -Math.PI / 8, Math.PI / 8)]
513 createObjectGroupsDeprecated(group, 0,
514         avoidClasses(clHill, 2, clPlayer, 2, clDirt, 0),
515         planetm * scaleByMapSize(13, 200)
517 Engine.SetProgress(90);
519 g_Map.log("Creating large grass tufts");
520 group = new SimpleGroup(
521         [new SimpleObject(aGrass, 2,4, 0,1.8, -Math.PI / 8, Math.PI / 8), new SimpleObject(aGrassShort, 3,6, 1.2,2.5, -Math.PI / 8, Math.PI / 8)]
523 createObjectGroupsDeprecated(group, 0,
524         avoidClasses(clHill, 2, clPlayer, 2, clDirt, 1, clForest, 0),
525         planetm * scaleByMapSize(13, 200)
527 Engine.SetProgress(95);
529 g_Map.log("Creating bushes");
530 group = new SimpleGroup(
531         [new SimpleObject(aBushMedium, 1,2, 0,2), new SimpleObject(aBushSmall, 2,4, 0,2)]
533 createObjectGroupsDeprecated(group, 0,
534         avoidClasses(clHill, 1, clPlayer, 1, clDirt, 1),
535         planetm * scaleByMapSize(13, 200), 50
538 placePlayersNomad(clPlayer, avoidClasses(clForest, 1, clMetal, 4, clRock, 4, clHill, 4, clFood, 2));
540 setSkySet(pickRandom(["cirrus", "cumulus", "sunny"]));
541 setSunRotation(randomAngle());
542 setSunElevation(Math.PI * randFloat(1/5, 1/3));
543 setWaterColor(0.0, 0.047, 0.286);                               // dark majestic blue
544 setWaterTint(0.471, 0.776, 0.863);                              // light blue
545 setWaterMurkiness(0.72);
546 setWaterWaviness(2.0);
547 setWaterType("lake");
549 g_Map.ExportMap();