Index: binaries/data/mods/public/maps/random/rmgen-common/gaia_entities.js =================================================================== --- binaries/data/mods/public/maps/random/rmgen-common/gaia_entities.js +++ binaries/data/mods/public/maps/random/rmgen-common/gaia_entities.js @@ -15,38 +15,40 @@ * Generates two variants of forests from the given terrain textures and tree templates. * The forest border has less trees than the inside. */ -function createForests(terrainSet, constraint, tileClass, treeCount, retryFactor) +function createForests(mainTerrain, forestFloor, trees, constraint, tileClass, treeCount) { if (!treeCount) return; - // Construct different forest types from the terrain textures and template names. - let [mainTerrain, terrainForestFloor1, terrainForestFloor2, terrainForestTree1, terrainForestTree2] = terrainSet; + let borderTerrain = []; + let interiorTerrain = []; - // The painter will pick a random Terrain for each part of the forest. - let forestVariants = [ - { - "borderTerrains": [terrainForestFloor2, mainTerrain, terrainForestTree1], - "interiorTerrains": [terrainForestFloor2, terrainForestTree1] - }, - { - "borderTerrains": [terrainForestFloor1, mainTerrain, terrainForestTree2], - "interiorTerrains": [terrainForestFloor1, terrainForestTree2] - } - ]; + // Construct the terrain/tree brushes + for (let tree of trees) + { + borderTerrain.push(mainTerrain); + borderTerrain.push(mainTerrain); + borderTerrain.push(mainTerrain); + borderTerrain.push(forestFloor); + borderTerrain.push(forestFloor + TERRAIN_SEPARATOR + tree); + + interiorTerrain.push(forestFloor); + interiorTerrain.push(forestFloor); + interiorTerrain.push(mainTerrain); + interiorTerrain.push(forestFloor + TERRAIN_SEPARATOR + tree); + } - g_Map.log("Creating forests"); - let numberOfForests = Math.floor(treeCount / (scaleByMapSize(3, 6) * getNumPlayers() * forestVariants.length)); - for (let forestVariant of forestVariants) - createAreas( - new ChainPlacer(1, Math.floor(scaleByMapSize(3, 5)), treeCount / numberOfForests, 0.5), - [ - new LayeredPainter([forestVariant.borderTerrains, forestVariant.interiorTerrains], [2]), - new TileClassPainter(tileClass) - ], - constraint, - numberOfForests, - retryFactor); + let numberOfForests = Math.floor(treeCount / (3 * getNumPlayers())); + createAreas( + new ChainPlacer(2, 4, treeCount / numberOfForests, 0.5), + [ + new LayeredPainter([borderTerrain, interiorTerrain], [1]), + new TileClassPainter(tileClass) + ], + constraint, + numberOfForests, + 10 + ); } /** @@ -67,16 +69,28 @@ /** * Places a SimpleGroup consisting of the given number of the given Objects * at random locations that meet the given Constraint. + * The optional areas parameter restricts placement to specific areas. */ -function createMines(objects, constraint, tileClass, count) +function createMines(objects, constraint, tileClass, count, areas) { for (let object of objects) - createObjectGroupsDeprecated( - new SimpleGroup(object, true, tileClass), - 0, - constraint, - count || scaleByMapSize(4, 16), - 70); + if (areas) + createObjectGroupsByAreas( + new SimpleGroup(object, true, tileClass), + 0, + constraint, + count || scaleByMapSize(4, 16), + 1000, + areas + ); + else + createObjectGroups( + new SimpleGroup(object, true, tileClass), + 0, + constraint, + count || scaleByMapSize(4, 16), + 1000 + ); } /** @@ -100,32 +114,53 @@ /** * Places the given amounts of the given Objects at random locations meeting the given Constraint. + * The optional areas parameter restricts placement to specific areas, useful for placing fish in lakes for example. */ -function createFood(objects, counts, constraint, tileClass) +function createFood(objects, counts, constraint, tileClass, areas) { - g_Map.log("Creating food"); for (let i = 0; i < objects.length; ++i) - createObjectGroupsDeprecated( - new SimpleGroup(objects[i], true, tileClass), - 0, - constraint, - counts[i], - 50); + if (areas) + createObjectGroupsByAreas( + new SimpleGroup(objects[i], true, tileClass), + 0, + constraint, + counts[i] || scaleByMapSize(4, 16), + 500, + areas + ); + else + createObjectGroups( + new SimpleGroup(objects[i], true, tileClass), + 0, + constraint, + counts[i] || scaleByMapSize(4, 16), + 500 + ); } /** * Same as createFood, but doesn't mark the terrain with a TileClass. */ -function createDecoration(objects, counts, constraint) +function createDecoration(objects, counts, constraint, areas) { - g_Map.log("Creating decoration"); for (let i = 0; i < objects.length; ++i) - createObjectGroupsDeprecated( - new SimpleGroup(objects[i], true), - 0, - constraint, - counts[i], - 5); + if (areas) + createObjectGroupsByAreas( + new SimpleGroup(objects[i], true), + 0, + constraint, + counts[i] || scaleByMapSize(16, 200), + 250, + areas + ); + else + createObjectGroups( + new SimpleGroup(objects[i], true), + 0, + constraint, + counts[i] || scaleByMapSize(16, 200), + 250 + ); } /** Index: binaries/data/mods/public/maps/random/rmgen-common/gaia_terrain.js =================================================================== --- binaries/data/mods/public/maps/random/rmgen-common/gaia_terrain.js +++ binaries/data/mods/public/maps/random/rmgen-common/gaia_terrain.js @@ -278,6 +278,92 @@ } /** + * This function creates random lines of mountains 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. + * + * 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 range segments 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 createMountainRanges(args) +{ + // These parameters paint the mountainranges after their location was determined. + let height = args.height || scaleByMapSize(60, 120); + let mountainWidth = args.width || height/4; + + let pathplacer = new PathPlacer(undefined, undefined, undefined, args.bumpiness || 0.4, scaleByMapSize(10, 25), args.waviness || 0.75, 0.2, 0.1); + let painters = [ + new TerrainPainter(args.terrain), + new SmoothElevationPainter(ELEVATION_SET, height, 4 * height/mountainWidth, 3), + new TileClassPainter(args.tileclass) + ]; + let constraint = args.constraint; + + // Array of Vector2D locations where a mountainrange can start or end. + let vertices = new Array(2 * (args.count || 16)).fill(0).map(() => g_Map.randomCoordinate(false)); + + // Maximum length that a mountain path can have. + let maxLength = args.maxLength || fractionToTiles(0.6); + + // Number of mountainranges starting or ending at the given point. + let vertexDegree = vertices.map(p => 0); + + // Init possible edges. + let possibleEdges = []; + let order = sortPointsShortestCycle(vertices); + let length = 0; + for (let i = 0; i < order.length - 1; ++i) + { + length += vertices[order[i]].distanceTo(vertices[order[i+1]]); + if (length <= maxLength) + { + possibleEdges.push([order[i], order[i+1]]); + }else + { + length = 0; + } + } + + g_Map.log("Creating mountain ranges with " + possibleEdges.length + " possible edges"); + + // Create the mountain ranges. + for (let i = 0; i < possibleEdges.length; ++i) + { + let currentEdge = possibleEdges[i]; + + pathplacer.start = vertices[currentEdge[0]]; + pathplacer.end = vertices[currentEdge[1]]; + pathplacer.width = mountainWidth; + + if (createArea(pathplacer, painters, constraint)) + { + ++vertexDegree[currentEdge[0]]; + ++vertexDegree[currentEdge[1]]; + } + } + + // Create circular mountains to connect ranges that share vertices. + for (let i = 0; i < vertexDegree.length; ++i) + { + if (vertexDegree[i] > 1) + { + createArea( + new ClumpPlacer(diskArea(mountainWidth / 2), 0.95, 0.6, Infinity, vertices[i]), + painters, + constraint); + } + } +} + +/** * Paint the given terrain texture in the given sizes at random places of the map to diversify monotone land texturing. */ function createPatches(sizes, terrain, constraints, count, tileClass, failFraction = 0.5) Index: binaries/data/mods/public/maps/random/rmgen-common/player.js =================================================================== --- binaries/data/mods/public/maps/random/rmgen-common/player.js +++ binaries/data/mods/public/maps/random/rmgen-common/player.js @@ -202,7 +202,7 @@ */ function getPlayerBaseArgs(playerBaseArgs) { - let baseResourceConstraint = playerBaseArgs.BaseResourceClass && avoidClasses(playerBaseArgs.BaseResourceClass, 4); + let baseResourceConstraint = playerBaseArgs.BaseResourceClass && getConstraints({avoid: [playerBaseArgs.BaseResourceClass, 4]}); if (playerBaseArgs.baseResourceConstraint) baseResourceConstraint = new AndConstraint([baseResourceConstraint, playerBaseArgs.baseResourceConstraint]); @@ -477,7 +477,7 @@ let group = new SimpleGroup(objects, true, playerClass); let success = false; for (let distanceFactor of [1, 1/2, 1/4, 0]) - if (createObjectGroups(group, playerIDs[i], new AndConstraint([constraint, avoidClasses(playerClass, distance * distanceFactor)]), 1, 200, false).length) + if (createObjectGroups(group, playerIDs[i], new AndConstraint([constraint, getConstraints({avoid: [playerClass, distance * distanceFactor]})]), 1, 200, false).length) { success = true; playerPosition[i] = group.centerPosition; Index: binaries/data/mods/public/maps/random/rmgen/Constraint.js =================================================================== --- binaries/data/mods/public/maps/random/rmgen/Constraint.js +++ binaries/data/mods/public/maps/random/rmgen/Constraint.js @@ -2,6 +2,51 @@ * @file A Constraint decides if a tile satisfies a condition defined by the class. */ +const avoid = "avoid"; +const stay = "stay"; +const near = "near"; +const border = "border"; + + /** + * This function returns constraints based on the specifications given by the arguments. + * + * Arguments: + * @param "avoid": {[tileclass1, mindist1, tileclass2, mindist2..etc]} avoid these tileclasses, by no less than the minimum defined distance in all directions. + * @param "stay": {[tileclass1, mindist1, tileclass2, mindist2..etc]} stay within the borders of these tileclasses, by at least the minimum defined distance in all directions. + * @param "near": {[tileclass1, maxdist1, tileclass2, maxdist2..etc]} stay near these tileclasses, by no farther than the maximum defined distance. + * @param "border": {[tileclass1, innerdist1, outerdist1, tileclass2, innerdist2, outerdist2..etc]} stay near the perimeter of these tileclasses, within the innerdist inside and the outerdist outside. + */ +function getConstraints(args) +{ + let constraints = []; + let current; + + current = args.avoid; + if (current) + for (let i = 0; i < current.length/2; ++i) + constraints.push(new AvoidTileClassConstraint(current[2*i], current[2*i+1])); + + current = args.stay; + if (current) + for (let i = 0; i < current.length/2; ++i) + constraints.push(new StayInTileClassConstraint(current[2*i], current[2*i+1])); + + current = args.near; + if (current) + for (let i = 0; i < current.length/2; ++i) + constraints.push(new NearTileClassConstraint(current[2*i], current[2*i+1])); + + current = args.border; + if (current) + for (let i = 0; i < current.length/3; ++i) + constraints.push(new BorderTileClassConstraint(current[3*i], current[3*i+1], current[3*i+2])); + + if (constraints.length == 1) + return constraints[0]; + + return new AndConstraint(constraints); +} + /** * The NullConstraint is always satisfied. */ @@ -18,7 +63,7 @@ function AndConstraint(constraints) { if (constraints instanceof Array) - this.constraints = constraints + this.constraints = constraints; else if (!constraints) this.constraints = []; else @@ -68,7 +113,7 @@ AvoidAreasConstraint.prototype.allows = function(position) { - return this.areas.every(area => !area.contains(position)) + return this.areas.every(area => !area.contains(position)); }; /** 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)); } @@ -247,54 +247,6 @@ } /** - * Create an avoid constraint for the given classes by the given distances - */ -function avoidClasses(/*class1, dist1, class2, dist2, etc*/) -{ - let ar = []; - for (let i = 0; i < arguments.length/2; ++i) - ar.push(new AvoidTileClassConstraint(arguments[2*i], arguments[2*i+1])); - - // Return single constraint - if (ar.length == 1) - return ar[0]; - - return new AndConstraint(ar); -} - -/** - * Create a stay constraint for the given classes by the given distances - */ -function stayClasses(/*class1, dist1, class2, dist2, etc*/) -{ - let ar = []; - for (let i = 0; i < arguments.length/2; ++i) - ar.push(new StayInTileClassConstraint(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*/) -{ - let ar = []; - for (let i = 0; i < arguments.length/3; ++i) - ar.push(new BorderTileClassConstraint(arguments[3*i], arguments[3*i+1], arguments[3*i+2])); - - // Return single constraint - if (ar.length == 1) - return ar[0]; - - return new AndConstraint(ar); -} - -/** * Returns a subset of the given heightmap. */ function extractHeightmap(heightmap, topLeft, size)