Index: binaries/data/mods/public/maps/random/alpine_valley.js =================================================================== --- binaries/data/mods/public/maps/random/alpine_valley.js +++ binaries/data/mods/public/maps/random/alpine_valley.js @@ -1,294 +1,53 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen-common"); +Engine.LoadLibrary("heightmap"); TILE_CENTERED_HEIGHT_MAP = true; -/** - * 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; +var tPrimary = ["new_alpine_grass_a", "new_alpine_grass_mossy"]; +var tSecondary = ["alpine_snow_a", "alpine_snow_b"]; +var tForestFloor = "alpine_forrestfloor"; +var tSnowyForestFloor = "alpine_cliff_snow"; +var tCliff = ["alpine_cliff", "alpine_mountainside"]; +var tRoughSnow = ["polar_snow_a"]; +var tSnowLine = ["path a"]; +var tSnowLineTrans = ["polar_snow_glacial", "alpine_snow_b", "alpine_snow_a"]; +var tSnowCaps = ["polar_snow_glacial"]; +var tFarmland = "temp_farmland"; +var tRoad = "temp_road_broken"; +var tRoadWild = "new_alpine_grass_a"; +var tLakeBed = "alpine_shore_rocks"; +var tShore = "alpine_shore_rocks_icy"; - /** - * Minimum geometric distance between two mountains that don't end in one place (disjoint edges). - */ - this.minDistance = args.mountainWidth + args.passageWidth; +var oPine = "gaia/flora_tree_pine"; +var oAleppoPine = "gaia/flora_tree_aleppo_pine"; +var oWinterPine = "gaia/flora_tree_pine_w"; +var oBerryBush = "gaia/flora_bush_berry"; +var oSheep = "gaia/fauna_sheep"; +var oDeer = "gaia/fauna_deer"; +var oRabbit = "gaia/fauna_rabbit"; +var oBear = "gaia/fauna_bear"; +var oFish = "gaia/fauna_fish"; +var oStoneLarge = "gaia/geology_stonemine_alpine_quarry"; +var oStoneSmall = "gaia/geology_stone_alpine_a"; +var oMetalLarge = "gaia/geology_metal_alpine_slabs"; - /** - * Array of Vector2D locations where a mountainrange can start or end. - */ - this.vertices = args.points; +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"; - /** - * 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/flora_tree_pine"; - var oBerryBush = "gaia/flora_bush_berry"; - var oDeer = "gaia/fauna_deer"; - var oRabbit = "gaia/fauna_rabbit"; - var oStoneLarge = "gaia/geology_stonemine_alpine_quarry"; - var oStoneSmall = "gaia/geology_stone_alpine_a"; - var oMetalLarge = "gaia/geology_metal_alpine_slabs"; - - 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/flora_tree_pine_w"; - var oBerryBush = "gaia/flora_bush_berry"; - var oDeer = "gaia/fauna_deer"; - var oRabbit = "gaia/fauna_rabbit"; - var oStoneLarge = "gaia/geology_stonemine_alpine_quarry"; - var oStoneSmall = "gaia/geology_stone_alpine_a"; - var oMetalLarge = "gaia/geology_metal_alpine_slabs"; - - 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; +var heightLake = -5; +var heightOffsetBump = scaleByMapSize(10, 75); +var heightMountain = scaleByMapSize(60, 120); +var snowlineHeight = heightMountain * 0.6; const pForest = [tForestFloor + TERRAIN_SEPARATOR + oPine, tForestFloor]; +const pForestSnow = [tForestFloor + TERRAIN_SEPARATOR + oWinterPine, tSnowyForestFloor]; var g_Map = new RandomMap(heightLand, tPrimary); @@ -296,14 +55,73 @@ const mapCenter = g_Map.getCenter(); var clPlayer = g_Map.createTileClass(); +var clGrass = g_Map.createTileClass(); +var clFarm = g_Map.createTileClass(); var clHill = g_Map.createTileClass(); +var clWater = g_Map.createTileClass(); var clForest = g_Map.createTileClass(); -var clDirt = g_Map.createTileClass(); +var clSnow = g_Map.createTileClass(); +var clRoughSnow = g_Map.createTileClass(); var clRock = g_Map.createTileClass(); var clMetal = g_Map.createTileClass(); var clFood = g_Map.createTileClass(); var clBaseResource = g_Map.createTileClass(); +paintTileClassBasedOnHeight(0, Infinity, 0, clGrass); + +// Setup environment settings +g_Map.log("Configuring environment settings"); +setWaterType("clap"); +setWaterColor(0.713726, 0.854902, 0.803922); +setWaterTint(0.913725, 0.976471, 0.996078); +setWaterWaviness(3.29102); +setWaterMurkiness(0.674805); +setPPEffect("hdr"); + +var env = randIntInclusive(0, 2); +if (env == 0) +{ + g_Map.log("Using Lighting Set Morning"); + setSkySet("cirrus"); + setSunColor(1, 0.886275, 0.556863); + setSunElevation(0.469398); + setSunRotation(-1.42353); + setTerrainAmbientColor(0.329412, 0.419608, 0.501961); + setUnitsAmbientColor(0.439216, 0.521569, 0.556863); + setFogColor(0.8, 0.8, 0.894118); + setFogFactor(0.4); + setFogThickness(0.2); + setPPBloom(0.35); +} +else if (env == 1) +{ + g_Map.log("Using lighting set Afternoon"); + setSkySet("cloudless"); + setSunColor(1, 0.921569, 0.682353); + setSunElevation(0.670645); + setSunRotation(0.416702); + setTerrainAmbientColor(0.329412, 0.419608, 0.501961); + setUnitsAmbientColor(0.439216, 0.521569, 0.556863); + setFogColor(0.8, 0.8, 0.894118); + setFogFactor(0.3); + setFogThickness(0.2); + setPPBloom(0.25); +} +else +{ + g_Map.log("Using Lighting set Evening"); + setSkySet("sunset"); + setSunColor(1.2652, 0.873236, 0.486234); + setSunElevation(0.378635); + setSunRotation(1.10222); + setTerrainAmbientColor(0.329412, 0.419608, 0.501961); + setUnitsAmbientColor(0.439216, 0.521569, 0.556863); + setFogColor(0.839216, 0.780392, 0.729412); + setFogFactor(0.3); + setFogThickness(0.22); + setPPBloom(0.35); +} + var [playerIDs, playerPosition, playerAngle, startAngle] = playerPlacementCircle(fractionToTiles(0.35)); placePlayerBases({ @@ -315,6 +133,7 @@ "innerTerrain": tRoad }, "Chicken": { + "template": oSheep }, "Berries": { "template": oBerryBush @@ -328,217 +147,226 @@ "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 - ] + "height": heightMountain, + "width": heightMountain * 0.25, + "maxLength": fractionToTiles(0.6), + "count": 16, + "bumpiness": 0.4, + "waviness": 0.75, + "terrain": tCliff, + "tileclass": clHill, + "constraint": avoidClasses(clPlayer, 15) }).CreateMountainRanges(); Engine.SetProgress(35); -paintTerrainBasedOnHeight(heightLand + 0.1, snowlineHeight, 0, tCliff); -paintTerrainBasedOnHeight(snowlineHeight, heightMountain, 3, tSnowLimited); +g_Map.log("Creating lakes"); +var numLakes = randIntInclusive(0, Math.round(scaleByMapSize(1, 7))); +if (numLakes > 0) + createAreas( + new ChainPlacer(1, Math.floor(scaleByMapSize(4, 8)), Math.floor(scaleByMapSize(30, 120)), 0.7), + [ + new LayeredPainter([tShore, tLakeBed], [1]), + new SmoothElevationPainter(ELEVATION_SET, heightLake, 5), + new TileClassPainter(clWater) + ], + avoidClasses(clPlayer, 20, clWater, 30, clHill, 7), + numLakes, + 1); +createMountains(tCliff, avoidClasses(clPlayer, 20, clHill, 4, clWater, 4), clHill, scaleByMapSize(80, 320), Math.floor(scaleByMapSize(50, 90)), Math.floor(scaleByMapSize(4, 8)), Math.floor(scaleByMapSize(8, 18)), Math.floor(scaleByMapSize(4, 8))); + +Engine.SetProgress(40); + +// Paint the snow caps. +paintTerrainBasedOnHeight(snowlineHeight - 7, snowlineHeight, 3, tSnowLine); +paintTerrainBasedOnHeight(snowlineHeight, snowlineHeight + 5, 3, tSnowLineTrans); +paintTerrainBasedOnHeight(snowlineHeight + 5, Infinity, 3, tSnowCaps); + 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), + new ClumpPlacer(scaleByMapSize(75, 300), 0.3, 0.06, Infinity), + new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetBump * 2, scaleByMapSize(35, 150)), + avoidClasses(clPlayer, 10, clWater, 2), + scaleByMapSize(50, 100)); + +createAreas( + new ClumpPlacer(scaleByMapSize(50, 200), 0.3, 0.06, Infinity), + new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetBump, scaleByMapSize(25, 100)), + avoidClasses(clPlayer, 10, clWater, 2), scaleByMapSize(100, 200)); -Engine.SetProgress(40); -g_Map.log("Creating hills"); +Engine.SetProgress(50); + +g_Map.log("Creating texture variation"); +// Create snow drift areas. createAreas( - new ClumpPlacer(scaleByMapSize(40, 150), 0.2, 0.1, Infinity), + new ChainPlacer(fractionToTiles(0.05), fractionToTiles(0.1), scaleByMapSize(7, 25), 0.75), [ - new LayeredPainter([tCliff, tSnowLimited], [2]), - new SmoothElevationPainter(ELEVATION_SET, heightMountain, 2), - new TileClassPainter(clHill) + new TerrainPainter(tSecondary), + new TileClassPainter(clSnow) ], - avoidClasses(clPlayer, 20, clHill, 14), - scaleByMapSize(10, 80) * numPlayers -); -Engine.SetProgress(50); + [avoidClasses(clPlayer, 40, clHill, 0, clWater, 0)], + randIntInclusive(scaleByMapSize(1, 3), scaleByMapSize(2, 6))); +Engine.SetProgress(60); +// Decorate snow areas +createAreas( + new ChainPlacer(fractionToTiles(0.005), fractionToTiles(0.01), scaleByMapSize(7, 25), 0.75), + [ + new TerrainPainter(tRoughSnow), + new TileClassPainter(clRoughSnow) + ], + [avoidClasses(clRoughSnow, 10), stayClasses(clSnow, 0)], + randIntInclusive(scaleByMapSize(3, 10), scaleByMapSize(20, 60))); +Engine.SetProgress(60); + +// Decorate grass areas with farmland +createAreas( + new ChainPlacer(4, 6, 10, 0.35), + [ + new TerrainPainter(tFarmland), + new TileClassPainter(clFarm) + ], + [avoidClasses(clPlayer, 15, clHill, 5, clFarm, 50, clWater, 10, clSnow, 5), stayClasses(clGrass, 0)], + randIntInclusive(scaleByMapSize(2, 4), scaleByMapSize(4, 8))); +Engine.SetProgress(60); + g_Map.log("Creating forests"); -var [forestTrees, stragglerTrees] = getTreeCounts(500, 3000, 0.7); -var types = [ - [[tForestFloor, tPrimary, pForest], [tForestFloor, pForest]] -]; +var [forestTrees, stragglerTrees] = getTreeCounts(750, 4000, 0.7); -var size = forestTrees / (scaleByMapSize(2,8) * numPlayers); +// Create grassland forests +createForests( + [tPrimary, tForestFloor, tForestFloor, pForest, pForest], + [avoidClasses(clPlayer, 20, clForest, 3, clWater, 2, clHill, 0, clSnow, 1, clRoughSnow, 1, clFarm, 3), stayClasses(clGrass, 0), nearClasses(clHill, 7)], + clForest, + forestTrees * 0.65); -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); +createStragglerTrees( + [oPine, oAleppoPine], + [avoidClasses(clForest, 2, clHill, 1, clPlayer, 12, clWater, 3, clSnow, 1, clFarm, 3), nearClasses(clForest, 7)], + clForest, + stragglerTrees * 0.65); -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)); +// Create snowdrift forests +createForests( + [tSecondary, tSnowyForestFloor, tSnowyForestFloor, pForestSnow, pForestSnow], + [avoidClasses(clPlayer, 20, clForest, 3, clWater, 2, clHill, 0, clFarm, 3), stayClasses(clSnow, 0), nearClasses(clHill, 7)], + clForest, + forestTrees * 0.35); -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)); +createStragglerTrees( + [oWinterPine], + [avoidClasses(clForest, 2, clHill, 1, clPlayer, 12, clWater, 3, clFarm, 3), stayClasses(clSnow, 0), nearClasses(clForest, 7)], + clForest, + stragglerTrees * 0.35); 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 -); +createMines( + [ + [new SimpleObject(oStoneSmall, 0, 2, 0, 4, 0, 2 * Math.PI, 1), new SimpleObject(oStoneLarge, 1, 1, 0, 4, 0, 2 * Math.PI, 4)] + ], + avoidClasses(clWater, 3, clForest, 1, clPlayer, 25, clRock, 25, clHill, 3, clFarm, 5), + clRock); -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 +createMines( + [ + [new SimpleObject(oMetalLarge, 1, 1, 0, 4)] + ], + avoidClasses(clWater, 3, clForest, 1, clPlayer, 20, clMetal, 10, clRock, 5, clHill, 3, clFarm, 5), + clMetal ); 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 decorations"); +createDecoration( + [ + [new SimpleObject(aRockMedium, 1, 3, 0, 1)], + [new SimpleObject(aRockLarge, 1, 2, 0, 1), new SimpleObject(aRockMedium, 1, 3, 0, 2)], + ], + [ + scaleByMapSize(16, 262), + scaleByMapSize(8, 131), + ], + avoidClasses(clWater, 0, clForest, 0, clPlayer, 15, clHill, 1, clFarm, 1)); -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 -); +createDecoration( + [ + [new SimpleObject(aGrassShort, 1, 2, 0, 1)], + [new SimpleObject(aGrass, 2, 4, 0, 1.8), new SimpleObject(aGrassShort, 3, 6, 1.2, 2.5)], + [new SimpleObject(aBushMedium, 1, 2, 0, 2), new SimpleObject(aBushSmall, 2, 4, 0, 2)] + ], + [ + scaleByMapSize(13, 200), + scaleByMapSize(13, 200), + scaleByMapSize(13, 200) + ], + avoidClasses(clWater, 0, clForest, 0, clPlayer, 15, clHill, 0, clSnow, 1, clFarm, 1)); + 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 food"); +createFood( + [ + [new SimpleObject(oDeer, 5, 7, 0, 4)], + [new SimpleObject(oRabbit, 2, 3, 0, 2)] + ], + [ + scaleByMapSize(8, 24), + scaleByMapSize(8, 24) + ], + avoidClasses(clWater, 3, clForest, 0, clPlayer, 25, clHill, 3, clFood, 10), + clFood); -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 -); +createFood( + [ + [new SimpleObject(oBear, 1, 1, 0, 4)] + ], + [ + scaleByMapSize(1, 7) + ], + avoidClasses(clWater, 3, clForest, 0, clPlayer, 25, clHill, 3), + clFood); -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); +createFood( + [ + [new SimpleObject(oBerryBush, 4, 6, 1, 4)] + ], + [ + scaleByMapSize(2, 6) * randIntInclusive(1, 4) * numPlayers + ], + avoidClasses(clWater, 3, clForest, 0, clPlayer, 20, clHill, 2, clFood, 10), + clFood); -createStragglerTrees( - [oPine], - avoidClasses(clForest, 1, clHill, 1, clPlayer, 12, clMetal, 6, clRock, 6), - clForest, - stragglerTrees); +createFood( + [ + [new SimpleObject(oSheep, 1, 1, 2, 3)] + ], + [ + scaleByMapSize(50, 200) + ], + [nearClasses(clFarm, 5)], + clFood); -g_Map.log("Creating small grass tufts"); -var planetm = 1; +createFood( + [ + [new SimpleObject(oFish, 1, 3, 4, 6)] + ], + [ + scaleByMapSize(50, 400) * numLakes + ], + [avoidClasses(clFood, 4), stayClasses(clWater, 5)], + clFood); -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); +Engine.SetProgress(85); -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(); Index: binaries/data/mods/public/maps/random/rmgen-common/mountain_range_builder.js =================================================================== --- binaries/data/mods/public/maps/random/rmgen-common/mountain_range_builder.js +++ binaries/data/mods/public/maps/random/rmgen-common/mountain_range_builder.js @@ -0,0 +1,138 @@ +Engine.LoadLibrary("rmgen"); + +/** + * @file This class creates random mountainranges ensuring that there are enough breaks to pass between them. + * + * To determine their location, random points are connected in shortest-cycle order and then trimmed to max length. + * + * Constructor arguments: + * @param {number} "height": how tall the mountains should be, in absolute height (ie set not modify). + * @param {number} "width": How wide the mountains will be. + * @param {number} "count": How many mountain ranges to create. + * @param {number} "maxLength": The longest that any connected mountain strip is allowed to be. Use to ensure that passable gaps are created. + * @param {number} "bumpiness": (0..1) How much the height of each peak should vary. + * @param {number} "waviness": (0..1) How much the mountain ranges should deviate from a straight line. + * @param {array strings} "terrain": String or array of two or more strings specifying terrain textures to paint. + * @param {number} "tileclass": The tileclass that the mountains should be assigned. + * @param {array constraints} "constraint": List of tileclasses to avoid painting over. + */ +function MountainRangeBuilder(args) +{ + /** + * These parameters paint the mountainranges after their location was determined. + */ + var height = args.height || scaleByMapSize(60, 120); + this.mountainWidth = args.width || height/4; + + this.pathplacer = new PathPlacer(undefined, undefined, undefined, args.bumpiness || 0.4, scaleByMapSize(10, 25), args.waviness || 0.75, 0.2, 0.1); + this.painters = [ + new TerrainPainter(args.terrain), + new SmoothElevationPainter(ELEVATION_SET, height, 4 * height/this.mountainWidth, 3), + new TileClassPainter(args.tileclass) + ]; + this.constraint = args.constraint; + + /** + * Array of Vector2D locations where a mountainrange can start or end. + */ + this.vertices = getRandomMapPoints(2 * (args.count || 16)); + + /** + * Maximum length that a mountain path can have. + */ + this.maxLength = args.maxLength || fractionToTiles(0.6); + + /** + * Number of mountainranges starting or ending at the given point. + */ + this.vertexDegree = this.vertices.map(p => 0); + + /** + * 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(); + + /** + * 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() +{ + var order = sortPointsShortestCycle(this.vertices); + var length = 0; + for (let i = 0; i < order.length - 1; ++i) + { + length += this.vertices[order[i]].distanceTo(this.vertices[order[i+1]]); + if (length <= this.maxLength) + { + this.possibleEdges.push([order[i], order[i+1]]); + }else + { + length = 0; + } + } +}; + +MountainRangeBuilder.prototype.UpdateCurrentEdge = function() +{ + this.currentEdge = this.possibleEdges[this.index]; + this.currentEdgeStart = this.vertices[this.currentEdge[0]]; + this.currentEdgeEnd = this.vertices[this.currentEdge[1]]; +}; + +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; + + return true; +}; + +/** + * This is the only function meant to be publicly accessible. + */ +MountainRangeBuilder.prototype.CreateMountainRanges = function() +{ + g_Map.log("Creating mountain ranges with " + this.possibleEdges.length + " possible edges"); + + for (let i = 0; i < this.possibleEdges.length; ++i) + { + + this.index = i; + this.UpdateCurrentEdge(); + + if (this.PaintCurrentEdge()) + { + ++this.vertexDegree[this.currentEdge[0]]; + ++this.vertexDegree[this.currentEdge[1]]; + } + } + + // Create circular mountains to connect ranges that share vertices. + for (let i = 0; i < this.vertexDegree.length; ++i) + { + if (this.vertexDegree[i] > 1) + { + createArea( + new ClumpPlacer(diskArea(this.mountainWidth / 2), 0.95, 0.6, Infinity, this.vertices[i]), + this.painters, + this.constraint); + } + } +}; \ No newline at end of file Index: binaries/data/mods/public/maps/random/rmgen/library.js =================================================================== --- binaries/data/mods/public/maps/random/rmgen/library.js +++ binaries/data/mods/public/maps/random/rmgen/library.js @@ -32,7 +32,7 @@ * Constants needed for heightmap_manipulation.js */ const MAX_HEIGHT_RANGE = 0xFFFF / HEIGHT_UNITS_PER_METRE; // Engine limit, Roughly 700 meters -const MIN_HEIGHT = - SEA_LEVEL; +const MIN_HEIGHT = -SEA_LEVEL; /** * Length of one tile of the terrain grid in metres. @@ -68,12 +68,12 @@ let obstructionSize = obstruction.Static ? new Vector2D(obstruction.Static["@depth"], obstruction.Static["@width"]) : - // Used for gates, should consider the position too - obstruction.Obstructions ? - new Vector2D( - Object.keys(obstruction.Obstructions).reduce((depth, key) => Math.max(depth, +obstruction.Obstructions[key]["@depth"]), 0), - Object.keys(obstruction.Obstructions).reduce((width, key) => width + +obstruction.Obstructions[key]["@width"], 0)) : - new Vector2D(0, 0); + // Used for gates, should consider the position too + obstruction.Obstructions ? + new Vector2D( + Object.keys(obstruction.Obstructions).reduce((depth, key) => Math.max(depth, +obstruction.Obstructions[key]["@depth"]), 0), + Object.keys(obstruction.Obstructions).reduce((width, key) => width + (+obstruction.Obstructions[key]["@width"]), 0)) : + new Vector2D(0, 0); return obstructionSize.div(TERRAIN_TILE_SIZE).add(new Vector2D(2, 2).mult(margin)); } @@ -249,7 +249,7 @@ /** * Create an avoid constraint for the given classes by the given distances */ -function avoidClasses(/*class1, dist1, class2, dist2, etc*/) +function avoidClasses(/* class1, dist1, class2, dist2, etc*/) { let ar = []; for (let i = 0; i < arguments.length/2; ++i) @@ -265,7 +265,7 @@ /** * Create a stay constraint for the given classes by the given distances */ -function stayClasses(/*class1, dist1, class2, dist2, etc*/) +function stayClasses(/* class1, dist1, class2, dist2, etc*/) { let ar = []; for (let i = 0; i < arguments.length/2; ++i) @@ -279,9 +279,25 @@ } /** + * Create a stay constraint for the given classes by the given distances + */ +function nearClasses(/* class1, dist1, class2, dist2, etc*/) +{ + let ar = []; + for (let i = 0; i < arguments.length/2; ++i) + ar.push(new NearTileClassConstraint(arguments[2*i], arguments[2*i+1])); + + // Return single constraint + if (ar.length == 1) + return ar[0]; + + return new AndConstraint(ar); +} + +/** * Create a border constraint for the given classes by the given distances */ -function borderClasses(/*class1, idist1, odist1, class2, idist2, odist2, etc*/) +function borderClasses(/* class1, idist1, odist1, class2, idist2, odist2, etc*/) { let ar = []; for (let i = 0; i < arguments.length/3; ++i) Index: binaries/data/mods/public/maps/random/rmgen/math.js =================================================================== --- binaries/data/mods/public/maps/random/rmgen/math.js +++ binaries/data/mods/public/maps/random/rmgen/math.js @@ -57,6 +57,15 @@ return [points, angle]; } +function getRandomMapPoints(pointCount) +{ + let points = []; + let mapSize = g_Map.getSize(); + for (let i = 0; i < pointCount; ++i) + points[i] = new Vector2D(randIntExclusive(0, mapSize), randIntExclusive(0, mapSize)); + return points; +} + /** * Returns the shortest distance from a point to a line. * The sign of the return value determines the direction!