1 Engine.LoadLibrary("rmgen");
3 TILE_CENTERED_HEIGHT_MAP = true;
6 * This class creates random mountainranges without enclosing any area completely.
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.
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.
17 function MountainRangeBuilder(args)
20 * These parameters paint the mountainranges after their location was determined.
22 this.pathplacer = args.pathplacer;
23 this.painters = args.painters;
24 this.constraint = args.constraint;
25 this.mountainWidth = args.mountainWidth;
28 * Minimum geometric distance between two mountains that don't end in one place (disjoint edges).
30 this.minDistance = args.mountainWidth + args.passageWidth;
33 * Array of Vector2D locations where a mountainrange can start or end.
35 this.vertices = args.points;
38 * Number of mountainranges starting or ending at the given point.
40 this.vertexDegree = this.vertices.map(p => 0);
43 * Highest number of mountainranges that can meet in one point (maximum degree of each vertex).
45 this.maxDegree = args.maxDegree;
48 * Each possible edge is an array containing two vertex indices.
49 * The algorithm adds possible edges consecutively and removes subsequently invalid edges.
51 this.possibleEdges = [];
52 this.InitPossibleEdges();
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.
59 this.verticesConnectable = [];
60 this.InitConnectable();
63 * Currently iterated item of possibleEdges that is either used as a mountainrange or removed from the possibleEdges.
65 this.index = undefined;
68 * These variables hold the indices of the two points of that edge and the location of them as a Vector2D.
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)
80 this.possibleEdges.push([i, j]);
83 MountainRangeBuilder.prototype.InitConnectable = function()
85 for (let i = 0; i < this.vertices.length; ++i)
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;
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.
109 MountainRangeBuilder.prototype.RemoveInvalidEdges = function()
111 for (let i = 0; i < this.possibleEdges.length; ++i)
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)
128 this.possibleEdges.splice(i, 1);
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.
140 MountainRangeBuilder.prototype.HasCycles = function()
144 let pointQueue = [this.currentEdge[0]];
146 while (pointQueue.length)
148 let selectedPoint = pointQueue.shift();
150 if (tree.indexOf(selectedPoint) == -1)
152 tree.push(selectedPoint);
156 for (let i = 0; i < this.vertices.length; ++i)
158 if (this.verticesConnectable[selectedPoint][i] || i == backtree[tree.lastIndexOf(selectedPoint)])
161 // If the current point was encountered already, then a cycle was identified.
162 if (tree.indexOf(i) != -1)
165 // Otherwise visit this point next
166 pointQueue.unshift(i);
168 backtree.push(selectedPoint);
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))
187 log("Creating circular mountains at both ends of that mountainrange...");
188 for (let point of [this.currentEdgeStart, this.currentEdgeEnd])
190 new ClumpPlacer(Math.floor(diskArea(this.mountainWidth / 2)), 0.95, 0.6, 10, point.x, point.y),
198 * This is the only function meant to be publicly accessible.
200 MountainRangeBuilder.prototype.CreateMountainRanges = function()
202 while (this.possibleEdges.length)
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 &&
211 this.PaintCurrentEdge())
213 ++this.vertexDegree[this.currentEdge[0]];
214 ++this.vertexDegree[this.currentEdge[1]];
215 this.RemoveInvalidEdges();
218 this.SetConnectable(true);
220 this.possibleEdges.splice(this.index, 1);
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";
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];
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.
300 var snowlineHeight = 29;
301 var mountainHeight = 30;
303 initTerrain(tPrimary);
305 var [playerIDs, playerX, playerZ, playerAngle, startAngle] = playerPlacementCircle(0.35);
308 "PlayerPlacement": [playerIDs, playerX, playerZ],
309 "PlayerTileClass": clPlayer,
310 "BaseResourceClass": clBaseResource,
312 "outerTerrain": tRoadWild,
313 "innerTerrain": tRoad
318 "template": oBerryBush
322 { "template": oMetalLarge },
323 { "template": oStoneLarge }
330 "template": aGrassShort
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),
338 new LayeredPainter([tCliff, tPrimary], [3]),
339 new SmoothElevationPainter(ELEVATION_SET, mountainHeight, 2),
342 "constraint": avoidClasses(clPlayer, 20),
343 "passageWidth": scaleByMapSize(10, 15),
344 "mountainWidth": scaleByMapSize(9, 15),
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],
354 }).CreateMountainRanges();
356 Engine.SetProgress(35);
358 paintTerrainBasedOnHeight(getMapBaseHeight() + 0.1, snowlineHeight, 0, tCliff);
359 paintTerrainBasedOnHeight(snowlineHeight, mountainHeight, 3, tSnowLimited);
361 log("Creating bumps...");
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...");
371 new ClumpPlacer(scaleByMapSize(40, 150), 0.2, 0.1, 1),
373 new LayeredPainter([tCliff, tSnowLimited], [2]),
374 new SmoothElevationPainter(ELEVATION_SET, mountainHeight, 2),
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);
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)
393 new ClumpPlacer(forestTrees / num, 0.1, 0.1, 1),
395 new LayeredPainter(type, [2]),
398 avoidClasses(clPlayer, 12, clForest, 10, clHill, 0),
400 Engine.SetProgress(60);
402 log("Creating dirt patches...");
403 for (let size of [scaleByMapSize(3, 48), scaleByMapSize(5, 84), scaleByMapSize(8, 128)])
405 new ClumpPlacer(size, 0.3, 0.06, 0.5),
407 new LayeredPainter([[tDirt, tHalfSnow], [tHalfSnow, tSnowLimited]], [2]),
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)])
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)],
450 createObjectGroupsDeprecated(
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)],
461 createObjectGroupsDeprecated(
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)],
473 createObjectGroupsDeprecated(group, 0,
474 avoidClasses(clForest, 0, clPlayer, 10, clHill, 1, clFood, 20),
478 log("Creating berry bush...");
479 group = new SimpleGroup(
480 [new SimpleObject(oBerryBush, 5,7, 0,4)],
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)],
493 createObjectGroupsDeprecated(group, 0,
494 avoidClasses(clForest, 0, clPlayer, 10, clHill, 1, clFood, 20),
497 Engine.SetProgress(85);
499 createStragglerTrees(
501 avoidClasses(clForest, 1, clHill, 1, clPlayer, 12, clMetal, 6, clRock, 6),
505 log("Creating small grass tufts...");
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");