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 @@ -67,16 +67,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 +112,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/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 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 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 = new Array(2 * (args.count || 16)).fill(0).map(() => g_Map.randomCoordinate(false)); + + /** + * 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/Constraint.js =================================================================== --- binaries/data/mods/public/maps/random/rmgen/Constraint.js +++ binaries/data/mods/public/maps/random/rmgen/Constraint.js @@ -3,6 +3,74 @@ */ /** + * The following functions are for easily creating constraints in batches. + */ + +/** + * 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 near 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*/) +{ + 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); +} + +/** * The NullConstraint is always satisfied. */ function NullConstraint() {} @@ -18,7 +86,7 @@ function AndConstraint(constraints) { if (constraints instanceof Array) - this.constraints = constraints + this.constraints = constraints; else if (!constraints) this.constraints = []; else @@ -68,7 +136,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)