Engine.LoadLibrary("rmgen");
Engine.LoadLibrary("rmgen-common");
TILE_CENTERED_HEIGHT_MAP = true;
/**
* @class
* This class creates random mountainranges without enclosing any area completely.
*
* To determine their location, a graph is created where each vertex is a possible starting or
* ending location of a mountainrange and each edge a possible mountainrange.
*
* That graph starts nearly complete (i.e almost every vertex is connected to most other vertices).
* After a random edge was chosen and placed as a mountainrange,
* all edges that intersect, that leave a too small gap to another mountainrange or that are connected to
* too many other mountainranges are removed from the graph.
* This is repeated until all edges were removed.
*/
function MountainRangeBuilder(args)
{
/**
* These parameters paint the mountainranges after their location was determined.
*/
this.pathplacer = args.pathplacer;
this.painters = args.painters;
this.constraint = args.constraint;
this.mountainWidth = args.mountainWidth;
/**
* Minimum geometric distance between two mountains that don't end in one place (disjoint edges).
*/
this.minDistance = args.mountainWidth + args.passageWidth;
/**
* Array of Vector2D locations where a mountainrange can start or end.
*/
this.vertices = args.points;
/**
* Number of mountainranges starting or ending at the given point.
*/
this.vertexDegree = this.vertices.map(p => 0);
/**
* Highest number of mountainranges that can meet in one point (maximum degree of each vertex).
*/
this.maxDegree = args.maxDegree;
/**
* Each possible edge is an array containing two vertex indices.
* The algorithm adds possible edges consecutively and removes subsequently invalid edges.
*/
this.possibleEdges = [];
this.InitPossibleEdges();
/**
* A two-dimensional array of booleans that are true if the two corresponding vertices may be connected by a new edge (mountainrange).
* It is initialized with some points that should never be connected and updated with every placed edge.
* The purpose is to rule out any cycles in the graph, i.e. prevent any territory enclosed by mountainranges.
*/
this.verticesConnectable = [];
this.InitConnectable();
/**
* Currently iterated item of possibleEdges that is either used as a mountainrange or removed from the possibleEdges.
*/
this.index = undefined;
/**
* These variables hold the indices of the two points of that edge and the location of them as a Vector2D.
*/
this.currentEdge = undefined;
this.currentEdgeStart = undefined;
this.currentEdgeEnd = undefined;
}
MountainRangeBuilder.prototype.InitPossibleEdges = function()
{
for (let i = 0; i < this.vertices.length; ++i)
for (let j = numPlayers; j < this.vertices.length; ++j)
if (j > i)
this.possibleEdges.push([i, j]);
};
MountainRangeBuilder.prototype.InitConnectable = function()
{
for (let i = 0; i < this.vertices.length; ++i)
{
this.verticesConnectable[i] = [];
for (let j = 0; j < this.vertices.length; ++j)
this.verticesConnectable[i][j] = i >= numPlayers || j >= numPlayers || i == j || i != j - 1 && i != j + 1;
}
};
MountainRangeBuilder.prototype.SetConnectable = function(isConnectable)
{
this.verticesConnectable[this.currentEdge[0]][this.currentEdge[1]] = isConnectable;
this.verticesConnectable[this.currentEdge[1]][this.currentEdge[0]] = isConnectable;
};
MountainRangeBuilder.prototype.UpdateCurrentEdge = function()
{
this.currentEdge = this.possibleEdges[this.index];
this.currentEdgeStart = this.vertices[this.currentEdge[0]];
this.currentEdgeEnd = this.vertices[this.currentEdge[1]];
};
/**
* Remove all edges that are too close to the current mountainrange or intersect.
*/
MountainRangeBuilder.prototype.RemoveInvalidEdges = function()
{
for (let i = 0; i < this.possibleEdges.length; ++i)
{
this.UpdateCurrentEdge();
let comparedEdge = this.possibleEdges[i];
let comparedEdgeStart = this.vertices[comparedEdge[0]];
let comparedEdgeEnd = this.vertices[comparedEdge[1]];
let edge0Equal = this.currentEdgeStart == comparedEdgeStart;
let edge1Equal = this.currentEdgeStart == comparedEdgeEnd;
let edge2Equal = this.currentEdgeEnd == comparedEdgeEnd;
let edge3Equal = this.currentEdgeEnd == comparedEdgeStart;
if (!edge0Equal && !edge2Equal && !edge1Equal && !edge3Equal && testLineIntersection(this.currentEdgeStart, this.currentEdgeEnd, comparedEdgeStart, comparedEdgeEnd, this.minDistance) ||
( edge0Equal && !edge2Equal || !edge1Equal && edge3Equal) && distanceOfPointFromLine(this.currentEdgeStart, this.currentEdgeEnd, comparedEdgeEnd) < this.minDistance ||
(!edge0Equal && edge2Equal || edge1Equal && !edge3Equal) && distanceOfPointFromLine(this.currentEdgeStart, this.currentEdgeEnd, comparedEdgeStart) < this.minDistance)
{
this.possibleEdges.splice(i, 1);
--i;
if (this.index > i)
--this.index;
}
}
};
/**
* Tests using depth-first-search if the graph according to pointsConnectable contains a cycle,
* i.e. if adding the currentEdge would result in an area enclosed by mountainranges.
*/
MountainRangeBuilder.prototype.HasCycles = function()
{
let tree = [];
let backtree = [];
let pointQueue = [this.currentEdge[0]];
while (pointQueue.length)
{
let selectedPoint = pointQueue.shift();
if (tree.indexOf(selectedPoint) == -1)
{
tree.push(selectedPoint);
backtree.push(-1);
}
for (let i = 0; i < this.vertices.length; ++i)
{
if (this.verticesConnectable[selectedPoint][i] || i == backtree[tree.lastIndexOf(selectedPoint)])
continue;
// If the current point was encountered already, then a cycle was identified.
if (tree.indexOf(i) != -1)
return true;
// Otherwise visit this point next
pointQueue.unshift(i);
tree.push(i);
backtree.push(selectedPoint);
}
}
return false;
};
MountainRangeBuilder.prototype.PaintCurrentEdge = function()
{
this.pathplacer.start = this.currentEdgeStart;
this.pathplacer.end = this.currentEdgeEnd;
this.pathplacer.width = this.mountainWidth;
// Creating mountainrange
if (!createArea(this.pathplacer, this.painters, this.constraint))
return false;
// Creating circular mountains at both ends of that mountainrange
for (let point of [this.currentEdgeStart, this.currentEdgeEnd])
createArea(
new ClumpPlacer(diskArea(this.mountainWidth / 2), 0.95, 0.6, Infinity, point),
this.painters,
this.constraint);
return true;
};
/**
* This is the only function meant to be publicly accessible.
*/
MountainRangeBuilder.prototype.CreateMountainRanges = function()
{
g_Map.log("Creating mountainrange with " + this.possibleEdges.length + " possible edges");
let max = this.possibleEdges.length;
while (this.possibleEdges.length)
{
Engine.SetProgress(35 - 15 * this.possibleEdges.length / max);
this.index = randIntExclusive(0, this.possibleEdges.length);
this.UpdateCurrentEdge();
this.SetConnectable(false);
if (this.vertexDegree[this.currentEdge[0]] < this.maxDegree &&
this.vertexDegree[this.currentEdge[1]] < this.maxDegree &&
!this.HasCycles() &&
this.PaintCurrentEdge())
{
++this.vertexDegree[this.currentEdge[0]];
++this.vertexDegree[this.currentEdge[1]];
this.RemoveInvalidEdges();
}
else
this.SetConnectable(true);
this.possibleEdges.splice(this.index, 1);
}
};
if (randBool())
{
RandomMapLogger.prototype.printDirectly("Setting late spring biome.\n");
var tPrimary = ["alpine_dirt_grass_50"];
var tForestFloor = "alpine_forrestfloor";
var tCliff = ["alpine_cliff_a", "alpine_cliff_b", "alpine_cliff_c"];
var tSecondary = "alpine_grass_rocky";
var tHalfSnow = ["alpine_grass_snow_50", "alpine_dirt_snow"];
var tSnowLimited = ["alpine_snow_rocky"];
var tDirt = "alpine_dirt";
var tRoad = "new_alpine_citytile";
var tRoadWild = "new_alpine_citytile";
var oPine = "gaia/tree/pine";
var oBerryBush = "gaia/fruit/berry_01";
var oDeer = "gaia/fauna_deer";
var oRabbit = "gaia/fauna_rabbit";
var oStoneLarge = "gaia/rock/alpine_large";
var oStoneSmall = "gaia/rock/alpine_small";
var oMetalLarge = "gaia/ore/alpine_large";
var aGrass = "actor|props/flora/grass_soft_small_tall.xml";
var aGrassShort = "actor|props/flora/grass_soft_large.xml";
var aRockLarge = "actor|geology/stone_granite_med.xml";
var aRockMedium = "actor|geology/stone_granite_med.xml";
var aBushMedium = "actor|props/flora/bush_medit_me.xml";
var aBushSmall = "actor|props/flora/bush_medit_sm.xml";
}
else
{
RandomMapLogger.prototype.printDirectly("Setting winter biome.\n");
var tPrimary = ["alpine_snow_a", "alpine_snow_b"];
var tForestFloor = "alpine_forrestfloor_snow";
var tCliff = ["alpine_cliff_snow"];
var tSecondary = "alpine_grass_snow_50";
var tHalfSnow = ["alpine_grass_snow_50", "alpine_dirt_snow"];
var tSnowLimited = ["alpine_snow_a", "alpine_snow_b"];
var tDirt = "alpine_dirt";
var tRoad = "new_alpine_citytile";
var tRoadWild = "new_alpine_citytile";
var oPine = "gaia/tree/pine_w";
var oBerryBush = "gaia/fruit/berry_01";
var oDeer = "gaia/fauna_deer";
var oRabbit = "gaia/fauna_rabbit";
var oStoneLarge = "gaia/rock/alpine_large";
var oStoneSmall = "gaia/rock/alpine_small";
var oMetalLarge = "gaia/ore/alpine_large";
var aGrass = "actor|props/flora/grass_soft_dry_small_tall.xml";
var aGrassShort = "actor|props/flora/grass_soft_dry_large.xml";
var aRockLarge = "actor|geology/stone_granite_med.xml";
var aRockMedium = "actor|geology/stone_granite_med.xml";
var aBushMedium = "actor|props/flora/bush_medit_me_dry.xml";
var aBushSmall = "actor|props/flora/bush_medit_sm_dry.xml";
}
var heightLand = 3;
var heightOffsetBump = 2;
var snowlineHeight = 29;
var heightMountain = 30;
const pForest = [tForestFloor + TERRAIN_SEPARATOR + oPine, tForestFloor];
var g_Map = new RandomMap(heightLand, tPrimary);
const numPlayers = getNumPlayers();
const mapCenter = g_Map.getCenter();
var clPlayer = g_Map.createTileClass();
var clHill = g_Map.createTileClass();
var clForest = g_Map.createTileClass();
var clDirt = g_Map.createTileClass();
var clRock = g_Map.createTileClass();
var clMetal = g_Map.createTileClass();
var clFood = g_Map.createTileClass();
var clBaseResource = g_Map.createTileClass();
var [playerIDs, playerPosition, playerAngle, startAngle] = playerPlacementCircle(fractionToTiles(0.35));
placePlayerBases({
"PlayerPlacement": [playerIDs, playerPosition],
"PlayerTileClass": clPlayer,
"BaseResourceClass": clBaseResource,
"CityPatch": {
"outerTerrain": tRoadWild,
"innerTerrain": tRoad
},
"StartingAnimal": {
},
"Berries": {
"template": oBerryBush
},
"Mines": {
"types": [
{ "template": oMetalLarge },
{ "template": oStoneLarge }
]
},
"Trees": {
"template": oPine
},
"Decoratives": {
"template": aGrassShort
}
});
Engine.SetProgress(20);
new MountainRangeBuilder({
"pathplacer": new PathPlacer(undefined, undefined, undefined, 0.4, scaleByMapSize(3, 12), 0.1, 0.1, 0.1),
"painters":[
new LayeredPainter([tCliff, tPrimary], [3]),
new SmoothElevationPainter(ELEVATION_SET, heightMountain, 2),
new TileClassPainter(clHill)
],
"constraint": avoidClasses(clPlayer, 20),
"passageWidth": scaleByMapSize(10, 15),
"mountainWidth": scaleByMapSize(9, 15),
"maxDegree": 3,
"points": [
// Four points near each player
...distributePointsOnCircle(numPlayers, startAngle + Math.PI / numPlayers, fractionToTiles(0.49), mapCenter)[0],
...distributePointsOnCircle(numPlayers, startAngle + Math.PI / numPlayers * 1.4, fractionToTiles(0.34), mapCenter)[0],
...distributePointsOnCircle(numPlayers, startAngle + Math.PI / numPlayers * 0.6, fractionToTiles(0.34), mapCenter)[0],
...distributePointsOnCircle(numPlayers, startAngle + Math.PI / numPlayers, fractionToTiles(0.18), mapCenter)[0],
mapCenter
]
}).CreateMountainRanges();
Engine.SetProgress(35);
paintTerrainBasedOnHeight(heightLand + 0.1, snowlineHeight, 0, tCliff);
paintTerrainBasedOnHeight(snowlineHeight, heightMountain, 3, tSnowLimited);
g_Map.log("Creating bumps");
createAreas(
new ClumpPlacer(scaleByMapSize(20, 50), 0.3, 0.06, Infinity),
new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetBump, 2),
avoidClasses(clPlayer, 10),
scaleByMapSize(100, 200));
Engine.SetProgress(40);
g_Map.log("Creating hills");
createAreas(
new ClumpPlacer(scaleByMapSize(40, 150), 0.2, 0.1, Infinity),
[
new LayeredPainter([tCliff, tSnowLimited], [2]),
new SmoothElevationPainter(ELEVATION_SET, heightMountain, 2),
new TileClassPainter(clHill)
],
avoidClasses(clPlayer, 20, clHill, 14),
scaleByMapSize(10, 80) * numPlayers
);
Engine.SetProgress(50);
g_Map.log("Creating forests");
var [forestTrees, stragglerTrees] = getTreeCounts(500, 3000, 0.7);
var types = [
[[tForestFloor, tPrimary, pForest], [tForestFloor, pForest]]
];
var size = forestTrees / (scaleByMapSize(2,8) * numPlayers);
var num = Math.floor(size / types.length);
for (let type of types)
createAreas(
new ClumpPlacer(forestTrees / num, 0.1, 0.1, Infinity),
[
new LayeredPainter(type, [2]),
new TileClassPainter(clForest)
],
avoidClasses(clPlayer, 12, clForest, 10, clHill, 0),
num);
Engine.SetProgress(60);
g_Map.log("Creating dirt patches");
for (let size of [scaleByMapSize(3, 48), scaleByMapSize(5, 84), scaleByMapSize(8, 128)])
createAreas(
new ClumpPlacer(size, 0.3, 0.06, 0.5),
[
new LayeredPainter([[tDirt, tHalfSnow], [tHalfSnow, tSnowLimited]], [2]),
new TileClassPainter(clDirt)
],
avoidClasses(clForest, 0, clHill, 0, clDirt, 5, clPlayer, 12),
scaleByMapSize(15, 45));
g_Map.log("Creating grass patches");
for (let size of [scaleByMapSize(2, 32), scaleByMapSize(3, 48), scaleByMapSize(5, 80)])
createAreas(
new ClumpPlacer(size, 0.3, 0.06, 0.5),
new TerrainPainter(tSecondary),
avoidClasses(clForest, 0, clHill, 0, clDirt, 5, clPlayer, 12),
scaleByMapSize(15, 45));
Engine.SetProgress(65);
g_Map.log("Creating stone mines");
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);
createObjectGroupsDeprecated(group, 0,
avoidClasses(clForest, 1, clPlayer, 20, clRock, 10, clHill, 1),
scaleByMapSize(4,16), 100
);
g_Map.log("Creating small stone mines");
group = new SimpleGroup([new SimpleObject(oStoneSmall, 2,5, 1,3)], true, clRock);
createObjectGroupsDeprecated(group, 0,
avoidClasses(clForest, 1, clPlayer, 20, clRock, 10, clHill, 1),
scaleByMapSize(4,16), 100
);
g_Map.log("Creating metal mines");
group = new SimpleGroup([new SimpleObject(oMetalLarge, 1,1, 0,4)], true, clMetal);
createObjectGroupsDeprecated(group, 0,
avoidClasses(clForest, 1, clPlayer, 20, clMetal, 10, clRock, 5, clHill, 1),
scaleByMapSize(4,16), 100
);
Engine.SetProgress(70);
g_Map.log("Creating small decorative rocks");
group = new SimpleGroup(
[new SimpleObject(aRockMedium, 1,3, 0,1)],
true
);
createObjectGroupsDeprecated(
group, 0,
avoidClasses(clForest, 0, clPlayer, 0, clHill, 0),
scaleByMapSize(16, 262), 50
);
g_Map.log("Creating large decorative rocks");
group = new SimpleGroup(
[new SimpleObject(aRockLarge, 1,2, 0,1), new SimpleObject(aRockMedium, 1,3, 0,2)],
true
);
createObjectGroupsDeprecated(
group, 0,
avoidClasses(clForest, 0, clPlayer, 0, clHill, 0),
scaleByMapSize(8, 131), 50
);
Engine.SetProgress(75);
g_Map.log("Creating deer");
group = new SimpleGroup(
[new SimpleObject(oDeer, 5,7, 0,4)],
true, clFood
);
createObjectGroupsDeprecated(group, 0,
avoidClasses(clForest, 0, clPlayer, 10, clHill, 1, clFood, 20),
3 * numPlayers, 50
);
g_Map.log("Creating berry bush");
group = new SimpleGroup(
[new SimpleObject(oBerryBush, 5,7, 0,4)],
true, clFood
);
createObjectGroupsDeprecated(group, 0,
avoidClasses(clForest, 0, clPlayer, 20, clHill, 1, clFood, 10),
randIntInclusive(1, 4) * numPlayers + 2, 50
);
g_Map.log("Creating rabbit");
group = new SimpleGroup(
[new SimpleObject(oRabbit, 2,3, 0,2)],
true, clFood
);
createObjectGroupsDeprecated(group, 0,
avoidClasses(clForest, 0, clPlayer, 10, clHill, 1, clFood, 20),
3 * numPlayers, 50
);
Engine.SetProgress(85);
createStragglerTrees(
[oPine],
avoidClasses(clForest, 1, clHill, 1, clPlayer, 12, clMetal, 6, clRock, 6),
clForest,
stragglerTrees);
g_Map.log("Creating small grass tufts");
var planetm = 1;
group = new SimpleGroup(
[new SimpleObject(aGrassShort, 1,2, 0,1, -Math.PI / 8, Math.PI / 8)]
);
createObjectGroupsDeprecated(group, 0,
avoidClasses(clHill, 2, clPlayer, 2, clDirt, 0),
planetm * scaleByMapSize(13, 200)
);
Engine.SetProgress(90);
g_Map.log("Creating large grass tufts");
group = new SimpleGroup(
[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)]
);
createObjectGroupsDeprecated(group, 0,
avoidClasses(clHill, 2, clPlayer, 2, clDirt, 1, clForest, 0),
planetm * scaleByMapSize(13, 200)
);
Engine.SetProgress(95);
g_Map.log("Creating bushes");
group = new SimpleGroup(
[new SimpleObject(aBushMedium, 1,2, 0,2), new SimpleObject(aBushSmall, 2,4, 0,2)]
);
createObjectGroupsDeprecated(group, 0,
avoidClasses(clHill, 1, clPlayer, 1, clDirt, 1),
planetm * scaleByMapSize(13, 200), 50
);
placePlayersNomad(clPlayer, avoidClasses(clForest, 1, clMetal, 4, clRock, 4, clHill, 4, clFood, 2));
setSkySet(pickRandom(["cirrus", "cumulus", "sunny"]));
setSunRotation(randomAngle());
setSunElevation(Math.PI * randFloat(1/5, 1/3));
g_Map.ExportMap();