Nomad mode on all random map scripts (except Survival of the Fittest), fixes #3591.
[0ad.git] / binaries / data / mods / public / maps / random / alpine_valley.js
blob71734cefb36426b147541e1bdd8dfaebb0395ecf
1 Engine.LoadLibrary("rmgen");
3 TILE_CENTERED_HEIGHT_MAP = true;
5 /**
6  * This class creates random mountainranges without enclosing any area completely.
7  *
8  * To determine their location, a graph is created where each vertex is a possible starting or
9  * ending location of a mountainrange and each edge a possible mountainrange.
10  *
11  * That graph starts nearly complete (i.e almost every vertex is connected to most other vertices).
12  * After a random edge was chosen and placed as a mountainrange,
13  * all edges that intersect, that leave a too small gap to another mountainrange or that are connected to
14  * too many other mountainranges are removed from the graph.
15  * This is repeated until all edges were removed.
16  */
17 function MountainRangeBuilder(args)
19         /**
20          * These parameters paint the mountainranges after their location was determined.
21          */
22         this.pathplacer = args.pathplacer;
23         this.painters = args.painters;
24         this.constraint = args.constraint;
25         this.mountainWidth = args.mountainWidth;
27         /**
28          * Minimum geometric distance between two mountains that don't end in one place (disjoint edges).
29          */
30         this.minDistance = args.mountainWidth + args.passageWidth;
32         /**
33          * Array of Vector2D locations where a mountainrange can start or end.
34          */
35         this.vertices = args.points;
37         /**
38          * Number of mountainranges starting or ending at the given point.
39          */
40         this.vertexDegree = this.vertices.map(p => 0);
42         /**
43          * Highest number of mountainranges that can meet in one point (maximum degree of each vertex).
44          */
45         this.maxDegree = args.maxDegree;
47         /**
48          * Each possible edge is an array containing two vertex indices.
49          * The algorithm adds possible edges consecutively and removes subsequently invalid edges.
50          */
51         this.possibleEdges = [];
52         this.InitPossibleEdges();
54         /**
55          * A two-dimensional array of booleans that are true if the two corresponding vertices may be connected by a new edge (mountainrange).
56          * It is initialized with some points that should never be connected and updated with every placed edge.
57          * The purpose is to rule out any cycles in the graph, i.e. prevent any territory enclosed by mountainranges.
58          */
59         this.verticesConnectable = [];
60         this.InitConnectable();
62         /**
63          * Currently iterated item of possibleEdges that is either used as a mountainrange or removed from the possibleEdges.
64          */
65         this.index = undefined;
67         /**
68          * These variables hold the indices of the two points of that edge and the location of them as a Vector2D.
69          */
70         this.currentEdge = undefined;
71         this.currentEdgeStart = undefined;
72         this.currentEdgeEnd = undefined;
75 MountainRangeBuilder.prototype.InitPossibleEdges = function()
77         for (let i = 0; i < this.vertices.length; ++i)
78                 for (let j = numPlayers; j < this.vertices.length; ++j)
79                         if (j > i)
80                                 this.possibleEdges.push([i, j]);
83 MountainRangeBuilder.prototype.InitConnectable = function()
85         for (let i = 0; i < this.vertices.length; ++i)
86         {
87                 this.verticesConnectable[i] = [];
88                 for (let j = 0; j < this.vertices.length; ++j)
89                         this.verticesConnectable[i][j] = i >= numPlayers || j >= numPlayers || i == j || i != j - 1 && i != j + 1;
90         }
93 MountainRangeBuilder.prototype.SetConnectable = function(isConnectable)
95         this.verticesConnectable[this.currentEdge[0]][this.currentEdge[1]] = isConnectable;
96         this.verticesConnectable[this.currentEdge[1]][this.currentEdge[0]] = isConnectable;
99 MountainRangeBuilder.prototype.UpdateCurrentEdge = function()
101         this.currentEdge = this.possibleEdges[this.index];
102         this.currentEdgeStart = this.vertices[this.currentEdge[0]];
103         this.currentEdgeEnd = this.vertices[this.currentEdge[1]];
107  * Remove all edges that are too close to the current mountainrange or intersect.
108  */
109 MountainRangeBuilder.prototype.RemoveInvalidEdges = function()
111         for (let i = 0; i < this.possibleEdges.length; ++i)
112         {
113                 this.UpdateCurrentEdge();
115                 let comparedEdge = this.possibleEdges[i];
116                 let comparedEdgeStart = this.vertices[comparedEdge[0]];
117                 let comparedEdgeEnd = this.vertices[comparedEdge[1]];
119                 let edge0Equal = this.currentEdgeStart == comparedEdgeStart;
120                 let edge1Equal = this.currentEdgeStart == comparedEdgeEnd;
121                 let edge2Equal = this.currentEdgeEnd == comparedEdgeEnd;
122                 let edge3Equal = this.currentEdgeEnd == comparedEdgeStart;
124                 if (!edge0Equal && !edge2Equal && !edge1Equal && !edge3Equal  && testLineIntersection(this.currentEdgeStart, this.currentEdgeEnd, comparedEdgeStart, comparedEdgeEnd, this.minDistance) ||
125                    ( edge0Equal && !edge2Equal || !edge1Equal &&  edge3Equal) && distanceOfPointFromLine(this.currentEdgeStart, this.currentEdgeEnd, comparedEdgeEnd) < this.minDistance ||
126                    (!edge0Equal &&  edge2Equal ||  edge1Equal && !edge3Equal) && distanceOfPointFromLine(this.currentEdgeStart, this.currentEdgeEnd, comparedEdgeStart) < this.minDistance)
127                 {
128                         this.possibleEdges.splice(i, 1);
129                         --i;
130                         if (this.index > i)
131                                 --this.index;
132                 }
133         }
137  * Tests using depth-first-search if the graph according to pointsConnectable contains a cycle,
138  * i.e. if adding the currentEdge would result in an area enclosed by mountainranges.
139  */
140 MountainRangeBuilder.prototype.HasCycles = function()
142         let tree = [];
143         let backtree = [];
144         let pointQueue = [this.currentEdge[0]];
146         while (pointQueue.length)
147         {
148                 let selectedPoint = pointQueue.shift();
150                 if (tree.indexOf(selectedPoint) == -1)
151                 {
152                         tree.push(selectedPoint);
153                         backtree.push(-1);
154                 }
156                 for (let i = 0; i < this.vertices.length; ++i)
157                 {
158                         if (this.verticesConnectable[selectedPoint][i] || i == backtree[tree.lastIndexOf(selectedPoint)])
159                                 continue;
161                         // If the current point was encountered already, then a cycle was identified.
162                         if (tree.indexOf(i) != -1)
163                                 return true;
165                         // Otherwise visit this point next
166                         pointQueue.unshift(i);
167                         tree.push(i);
168                         backtree.push(selectedPoint);
169                 }
170         }
172         return false;
175 MountainRangeBuilder.prototype.PaintCurrentEdge = function()
177         this.pathplacer.x1 = this.currentEdgeStart.x;
178         this.pathplacer.z1 = this.currentEdgeStart.y;
179         this.pathplacer.x2 = this.currentEdgeEnd.x;
180         this.pathplacer.z2 = this.currentEdgeEnd.y;
181         this.pathplacer.width = this.mountainWidth;
183         log("Creating mountainrange...");
184         if (!createArea(this.pathplacer, this.painters, this.constraint))
185                 return false;
187         log("Creating circular mountains at both ends of that mountainrange...");
188         for (let point of [this.currentEdgeStart, this.currentEdgeEnd])
189                 createArea(
190                         new ClumpPlacer(Math.floor(diskArea(this.mountainWidth / 2)), 0.95, 0.6, 10, point.x, point.y),
191                         this.painters,
192                         this.constraint);
194         return true;
198  * This is the only function meant to be publicly accessible.
199  */
200 MountainRangeBuilder.prototype.CreateMountainRanges = function()
202         while (this.possibleEdges.length)
203         {
204                 this.index = randIntExclusive(0, this.possibleEdges.length);
205                 this.UpdateCurrentEdge();
206                 this.SetConnectable(false);
208                 if (this.vertexDegree[this.currentEdge[0]] < this.maxDegree &&
209                     this.vertexDegree[this.currentEdge[1]] < this.maxDegree &&
210                     !this.HasCycles() &&
211                     this.PaintCurrentEdge())
212                 {
213                         ++this.vertexDegree[this.currentEdge[0]];
214                         ++this.vertexDegree[this.currentEdge[1]];
215                         this.RemoveInvalidEdges();
216                 }
217                 else
218                         this.SetConnectable(true);
220                 this.possibleEdges.splice(this.index, 1);
221         }
224 if (randBool())
226         log("Late spring biome...");
227         var tPrimary = ["alpine_dirt_grass_50"];
228         var tForestFloor = "alpine_forrestfloor";
229         var tCliff = ["alpine_cliff_a", "alpine_cliff_b", "alpine_cliff_c"];
230         var tSecondary = "alpine_grass_rocky";
231         var tHalfSnow = ["alpine_grass_snow_50", "alpine_dirt_snow"];
232         var tSnowLimited = ["alpine_snow_rocky"];
233         var tDirt = "alpine_dirt";
234         var tRoad = "new_alpine_citytile";
235         var tRoadWild = "new_alpine_citytile";
237         var oPine = "gaia/flora_tree_pine";
238         var oBerryBush = "gaia/flora_bush_berry";
239         var oDeer = "gaia/fauna_deer";
240         var oRabbit = "gaia/fauna_rabbit";
241         var oStoneLarge = "gaia/geology_stonemine_alpine_quarry";
242         var oStoneSmall = "gaia/geology_stone_alpine_a";
243         var oMetalLarge = "gaia/geology_metal_alpine_slabs";
245         var aGrass = "actor|props/flora/grass_soft_small_tall.xml";
246         var aGrassShort = "actor|props/flora/grass_soft_large.xml";
247         var aRockLarge = "actor|geology/stone_granite_med.xml";
248         var aRockMedium = "actor|geology/stone_granite_med.xml";
249         var aBushMedium = "actor|props/flora/bush_medit_me.xml";
250         var aBushSmall = "actor|props/flora/bush_medit_sm.xml";
252 else
254         log("Winter biome...");
255         var tPrimary = ["alpine_snow_a", "alpine_snow_b"];
256         var tForestFloor = "alpine_forrestfloor_snow";
257         var tCliff = ["alpine_cliff_snow"];
258         var tSecondary = "alpine_grass_snow_50";
259         var tHalfSnow = ["alpine_grass_snow_50", "alpine_dirt_snow"];
260         var tSnowLimited = ["alpine_snow_a", "alpine_snow_b"];
261         var tDirt = "alpine_dirt";
262         var tRoad = "new_alpine_citytile";
263         var tRoadWild = "new_alpine_citytile";
265         var oPine = "gaia/flora_tree_pine_w";
266         var oBerryBush = "gaia/flora_bush_berry";
267         var oDeer = "gaia/fauna_deer";
268         var oRabbit = "gaia/fauna_rabbit";
269         var oStoneLarge = "gaia/geology_stonemine_alpine_quarry";
270         var oStoneSmall = "gaia/geology_stone_alpine_a";
271         var oMetalLarge = "gaia/geology_metal_alpine_slabs";
273         var aGrass = "actor|props/flora/grass_soft_dry_small_tall.xml";
274         var aGrassShort = "actor|props/flora/grass_soft_dry_large.xml";
275         var aRockLarge = "actor|geology/stone_granite_med.xml";
276         var aRockMedium = "actor|geology/stone_granite_med.xml";
277         var aBushMedium = "actor|props/flora/bush_medit_me_dry.xml";
278         var aBushSmall = "actor|props/flora/bush_medit_sm_dry.xml";
281 const pForest = [tForestFloor + TERRAIN_SEPARATOR + oPine, tForestFloor];
283 InitMap();
285 const numPlayers = getNumPlayers();
286 const mapCenter = getMapCenter();
288 var clPlayer = createTileClass();
289 var clHill = createTileClass();
290 var clForest = createTileClass();
291 var clDirt = createTileClass();
292 var clRock = createTileClass();
293 var clMetal = createTileClass();
294 var clFood = createTileClass();
295 var clBaseResource = createTileClass();
298  * Minimum distance between two mountainranges.
299  */
300 var snowlineHeight = 29;
301 var mountainHeight = 30;
303 initTerrain(tPrimary);
305 var [playerIDs, playerX, playerZ, playerAngle, startAngle] = playerPlacementCircle(0.35);
307 placePlayerBases({
308         "PlayerPlacement": [playerIDs, playerX, playerZ],
309         "PlayerTileClass": clPlayer,
310         "BaseResourceClass": clBaseResource,
311         "CityPatch": {
312                 "outerTerrain": tRoadWild,
313                 "innerTerrain": tRoad
314         },
315         "Chicken": {
316         },
317         "Berries": {
318                 "template": oBerryBush
319         },
320         "Mines": {
321                 "types": [
322                         { "template": oMetalLarge },
323                         { "template": oStoneLarge }
324                 ]
325         },
326         "Trees": {
327                 "template": oPine
328         },
329         "Decoratives": {
330                 "template": aGrassShort
331         }
333 Engine.SetProgress(20);
335 new MountainRangeBuilder({
336         "pathplacer": new PathPlacer(undefined, undefined, undefined, undefined, undefined, 0.4, scaleByMapSize(3, 12), 0.1, 0.1, 0.1),
337         "painters":[
338                 new LayeredPainter([tCliff, tPrimary], [3]),
339                 new SmoothElevationPainter(ELEVATION_SET, mountainHeight, 2),
340                 paintClass(clHill)
341         ],
342         "constraint": avoidClasses(clPlayer, 20),
343         "passageWidth": scaleByMapSize(10, 15),
344         "mountainWidth": scaleByMapSize(9, 15),
345         "maxDegree": 3,
346         "points": [
347                 // Four points near each player
348                 ...distributePointsOnCircle(numPlayers, startAngle + Math.PI / numPlayers, fractionToTiles(0.49), mapCenter)[0],
349                 ...distributePointsOnCircle(numPlayers, startAngle + Math.PI / numPlayers * 1.4, fractionToTiles(0.34), mapCenter)[0],
350                 ...distributePointsOnCircle(numPlayers, startAngle + Math.PI / numPlayers * 0.6, fractionToTiles(0.34), mapCenter)[0],
351                 ...distributePointsOnCircle(numPlayers, startAngle + Math.PI / numPlayers, fractionToTiles(0.18), mapCenter)[0],
352                 mapCenter
353         ]
354 }).CreateMountainRanges();
356 Engine.SetProgress(35);
358 paintTerrainBasedOnHeight(getMapBaseHeight() + 0.1, snowlineHeight, 0, tCliff);
359 paintTerrainBasedOnHeight(snowlineHeight, mountainHeight, 3, tSnowLimited);
361 log("Creating bumps...");
362 createAreas(
363         new ClumpPlacer(scaleByMapSize(20, 50), 0.3, 0.06, 1),
364         new SmoothElevationPainter(ELEVATION_MODIFY, 2, 2),
365         avoidClasses(clPlayer, 10),
366         scaleByMapSize(100, 200));
367 Engine.SetProgress(40);
369 log("Creating hills...");
370 createAreas(
371         new ClumpPlacer(scaleByMapSize(40, 150), 0.2, 0.1, 1),
372         [
373                 new LayeredPainter([tCliff, tSnowLimited], [2]),
374                 new SmoothElevationPainter(ELEVATION_SET, mountainHeight, 2),
375                 paintClass(clHill)
376         ],
377         avoidClasses(clPlayer, 20, clHill, 14),
378         scaleByMapSize(10, 80) * numPlayers
380 Engine.SetProgress(50);
382 log("Creating forests...");
383 var [forestTrees, stragglerTrees] = getTreeCounts(500, 3000, 0.7);
384 var types = [
385         [[tForestFloor, tPrimary, pForest], [tForestFloor, pForest]]
388 var size = forestTrees / (scaleByMapSize(2,8) * numPlayers);
390 var num = Math.floor(size / types.length);
391 for (let type of types)
392         createAreas(
393                 new ClumpPlacer(forestTrees / num, 0.1, 0.1, 1),
394                 [
395                         new LayeredPainter(type, [2]),
396                         paintClass(clForest)
397                 ],
398                 avoidClasses(clPlayer, 12, clForest, 10, clHill, 0),
399                 num);
400 Engine.SetProgress(60);
402 log("Creating dirt patches...");
403 for (let size of [scaleByMapSize(3, 48), scaleByMapSize(5, 84), scaleByMapSize(8, 128)])
404         createAreas(
405                 new ClumpPlacer(size, 0.3, 0.06, 0.5),
406                 [
407                         new LayeredPainter([[tDirt, tHalfSnow], [tHalfSnow, tSnowLimited]], [2]),
408                         paintClass(clDirt)
409                 ],
410                 avoidClasses(clForest, 0, clHill, 0, clDirt, 5, clPlayer, 12),
411                 scaleByMapSize(15, 45));
413 log("Creating grass patches...");
414 for (let size of [scaleByMapSize(2, 32), scaleByMapSize(3, 48), scaleByMapSize(5, 80)])
415         createAreas(
416                 new ClumpPlacer(size, 0.3, 0.06, 0.5),
417                 new TerrainPainter(tSecondary),
418                 avoidClasses(clForest, 0, clHill, 0, clDirt, 5, clPlayer, 12),
419                 scaleByMapSize(15, 45));
421 Engine.SetProgress(65);
423 log("Creating stone mines...");
424 var group = new SimpleGroup([new SimpleObject(oStoneSmall, 0,2, 0,4), new SimpleObject(oStoneLarge, 1,1, 0,4)], true, clRock);
425 createObjectGroupsDeprecated(group, 0,
426         avoidClasses(clForest, 1, clPlayer, 20, clRock, 10, clHill, 1),
427         scaleByMapSize(4,16), 100
430 log("Creating small stone mines...");
431 group = new SimpleGroup([new SimpleObject(oStoneSmall, 2,5, 1,3)], true, clRock);
432 createObjectGroupsDeprecated(group, 0,
433         avoidClasses(clForest, 1, clPlayer, 20, clRock, 10, clHill, 1),
434         scaleByMapSize(4,16), 100
437 log("Creating metal mines...");
438 group = new SimpleGroup([new SimpleObject(oMetalLarge, 1,1, 0,4)], true, clMetal);
439 createObjectGroupsDeprecated(group, 0,
440         avoidClasses(clForest, 1, clPlayer, 20, clMetal, 10, clRock, 5, clHill, 1),
441         scaleByMapSize(4,16), 100
443 Engine.SetProgress(70);
445 log("Creating small decorative rocks...");
446 group = new SimpleGroup(
447         [new SimpleObject(aRockMedium, 1,3, 0,1)],
448         true
450 createObjectGroupsDeprecated(
451         group, 0,
452         avoidClasses(clForest, 0, clPlayer, 0, clHill, 0),
453         scaleByMapSize(16, 262), 50
456 log("Creating large decorative rocks...");
457 group = new SimpleGroup(
458         [new SimpleObject(aRockLarge, 1,2, 0,1), new SimpleObject(aRockMedium, 1,3, 0,2)],
459         true
461 createObjectGroupsDeprecated(
462         group, 0,
463         avoidClasses(clForest, 0, clPlayer, 0, clHill, 0),
464         scaleByMapSize(8, 131), 50
466 Engine.SetProgress(75);
468 log("Creating deer...");
469 group = new SimpleGroup(
470         [new SimpleObject(oDeer, 5,7, 0,4)],
471         true, clFood
473 createObjectGroupsDeprecated(group, 0,
474         avoidClasses(clForest, 0, clPlayer, 10, clHill, 1, clFood, 20),
475         3 * numPlayers, 50
478 log("Creating berry bush...");
479 group = new SimpleGroup(
480         [new SimpleObject(oBerryBush, 5,7, 0,4)],
481         true, clFood
483 createObjectGroupsDeprecated(group, 0,
484         avoidClasses(clForest, 0, clPlayer, 20, clHill, 1, clFood, 10),
485         randIntInclusive(1, 4) * numPlayers + 2, 50
488 log("Creating rabbit...");
489 group = new SimpleGroup(
490         [new SimpleObject(oRabbit, 2,3, 0,2)],
491         true, clFood
493 createObjectGroupsDeprecated(group, 0,
494         avoidClasses(clForest, 0, clPlayer, 10, clHill, 1, clFood, 20),
495         3 * numPlayers, 50
497 Engine.SetProgress(85);
499 createStragglerTrees(
500         [oPine],
501         avoidClasses(clForest, 1, clHill, 1, clPlayer, 12, clMetal, 6, clRock, 6),
502         clForest,
503         stragglerTrees);
505 log("Creating small grass tufts...");
506 var planetm = 1;
508 group = new SimpleGroup(
509         [new SimpleObject(aGrassShort, 1,2, 0,1, -Math.PI / 8, Math.PI / 8)]
511 createObjectGroupsDeprecated(group, 0,
512         avoidClasses(clHill, 2, clPlayer, 2, clDirt, 0),
513         planetm * scaleByMapSize(13, 200)
515 Engine.SetProgress(90);
517 log("Creating large grass tufts...");
518 group = new SimpleGroup(
519         [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)]
521 createObjectGroupsDeprecated(group, 0,
522         avoidClasses(clHill, 2, clPlayer, 2, clDirt, 1, clForest, 0),
523         planetm * scaleByMapSize(13, 200)
525 Engine.SetProgress(95);
527 log("Creating bushes...");
528 group = new SimpleGroup(
529         [new SimpleObject(aBushMedium, 1,2, 0,2), new SimpleObject(aBushSmall, 2,4, 0,2)]
531 createObjectGroupsDeprecated(group, 0,
532         avoidClasses(clHill, 1, clPlayer, 1, clDirt, 1),
533         planetm * scaleByMapSize(13, 200), 50
536 placePlayersNomad(clPlayer, avoidClasses(clForest, 1, clMetal, 4, clRock, 4, clHill, 4, clFood, 2));
538 setSkySet(pickRandom(["cirrus", "cumulus", "sunny"]));
539 setSunRotation(randFloat(0, 2 * Math.PI));
540 setSunElevation(Math.PI * randFloat(1/5, 1/3));
541 setWaterColor(0.0, 0.047, 0.286);                               // dark majestic blue
542 setWaterTint(0.471, 0.776, 0.863);                              // light blue
543 setWaterMurkiness(0.72);
544 setWaterWaviness(2.0);
545 setWaterType("lake");
547 ExportMap();