Index: ps/trunk/binaries/data/mods/public/maps/random/deep_forest.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/deep_forest.js (revision 20878) +++ ps/trunk/binaries/data/mods/public/maps/random/deep_forest.js (revision 20879) @@ -1,274 +1,198 @@ Engine.LoadLibrary("rmgen"); InitMap(); var clPlayer = createTileClass(); var clPath = createTileClass(); var clHill = createTileClass(); var clForest = createTileClass(); var clBaseResource = createTileClass(); var templateStone = "gaia/geology_stone_temperate"; var templateStoneMine = "gaia/geology_stonemine_temperate_quarry"; var templateMetalMine = "gaia/geology_metal_temperate_slabs"; +var templateTemple = "other/unfinished_greek_temple"; var terrainWood = ['temp_grass_mossy|gaia/flora_tree_oak', 'temp_forestfloor_pine|gaia/flora_tree_pine', 'temp_mud_plants|gaia/flora_tree_dead', 'temp_plants_bog|gaia/flora_tree_oak_large', "temp_dirt_gravel_plants|gaia/flora_tree_aleppo_pine", 'temp_forestfloor_autumn|gaia/flora_tree_carob']; //'temp_forestfloor_autumn|gaia/flora_tree_fig' var terrainWoodBorder = ['temp_grass_plants|gaia/flora_tree_euro_beech', 'temp_grass_mossy|gaia/flora_tree_poplar', 'temp_grass_mossy|gaia/flora_tree_poplar_lombardy', 'temp_grass_long|gaia/flora_bush_temperate', 'temp_mud_plants|gaia/flora_bush_temperate', 'temp_mud_plants|gaia/flora_bush_badlands', 'temp_grass_long|gaia/flora_tree_apple', 'temp_grass_clovers|gaia/flora_bush_berry', 'temp_grass_clovers_2|gaia/flora_bush_grapes', 'temp_grass_plants|gaia/fauna_deer', "temp_grass_long_b|gaia/fauna_rabbit", "temp_grass_plants"]; var terrainBase = ['temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_grass_b|gaia/fauna_pig', 'temp_dirt_gravel|gaia/fauna_chicken']; var terrainBaseBorder = ["temp_grass_b", "temp_grass_b", "temp_grass", "temp_grass_c", "temp_grass_mossy"]; var terrainBaseCenter = ['temp_dirt_gravel', 'temp_dirt_gravel', 'temp_grass_b']; var terrainPath = ['temp_road', "temp_road_overgrown", 'temp_grass_b']; var terrainHill = ["temp_highlands", "temp_highlands", "temp_highlands", "temp_dirt_gravel_b", "temp_cliff_a"]; var terrainHillBorder = ["temp_highlands", "temp_highlands", "temp_highlands", "temp_dirt_gravel_b", "temp_dirt_gravel_plants", "temp_highlands", "temp_highlands", "temp_highlands", "temp_dirt_gravel_b", "temp_dirt_gravel_plants", "temp_highlands", "temp_highlands", "temp_highlands", "temp_cliff_b", "temp_dirt_gravel_plants", "temp_highlands", "temp_highlands", "temp_highlands", "temp_cliff_b", "temp_dirt_gravel_plants", "temp_highlands|gaia/fauna_goat"]; var mapSize = getMapSize(); var mapArea = getMapArea(); var mapRadius = mapSize/2; -var mapCenterX = mapRadius; -var mapCenterZ = mapRadius; +var mapCenter = getMapCenter(); var numPlayers = getNumPlayers(); var baseRadius = 20; var minPlayerRadius = Math.min(mapRadius - 1.5 * baseRadius, 5/8 * mapRadius); var maxPlayerRadius = Math.min(mapRadius - baseRadius, 3/4 * mapRadius); -var playerStartLocX = []; -var playerStartLocZ = []; +var playerPosition = []; var playerAngle = []; var playerAngleStart = randomAngle(); var playerAngleAddAvrg = 2 * Math.PI / numPlayers; var playerAngleMaxOff = playerAngleAddAvrg/4; -// Setup eyecandy -var templateEC = "other/unfinished_greek_temple"; var radiusEC = Math.max(mapRadius/8, baseRadius/2); - -// Setup paths -var pathSucsessRadius = baseRadius/2; -var pathAngleOff = Math.PI / 2; -var pathWidth = 5; // This is not really the path's sickness in tiles but the number of tiles in the clumbs of the path - -// Setup additional resources -var resourceRadius = 2*mapRadius/3; // 3*mapRadius/8; +var resourceRadius = fractionToTiles(1/3); var resourcePerPlayer = [templateStone, templateMetalMine]; -// Setup woods // For large maps there are memory errors with too many trees. A density of 256*192/mapArea works with 0 players. // Around each player there is an area without trees so with more players the max density can increase a bit. var maxTreeDensity = Math.min(256 * (192 + 8 * numPlayers) / mapArea, 1); // Has to be tweeked but works ok var bushChance = 1/3; // 1 means 50% chance in deepest wood, 0.5 means 25% chance in deepest wood -var playerIDs = []; +var playerIDs = sortAllPlayers(); for (var i=0; i < numPlayers; i++) { - playerIDs[i] = i+1; playerAngle[i] = (playerAngleStart + i * playerAngleAddAvrg + randFloat(0, playerAngleMaxOff)) % (2 * Math.PI); - playerStartLocX[i] = mapCenterX + Math.round(randFloat(minPlayerRadius, maxPlayerRadius) * Math.cos(playerAngle[i])); - playerStartLocZ[i] = mapCenterZ + Math.round(randFloat(minPlayerRadius, maxPlayerRadius) * Math.sin(playerAngle[i])); + playerPosition[i] = Vector2D.add(mapCenter, new Vector2D(randFloat(minPlayerRadius, maxPlayerRadius), 0).rotate(-playerAngle[i]).round()); } Engine.SetProgress(10); placePlayerBases({ - "PlayerPlacement": [playerIDs, playerStartLocX.map(tilesToFraction), playerStartLocZ.map(tilesToFraction)], + "PlayerPlacement": [playerIDs, playerPosition.map(p => tilesToFraction(p.x)), playerPosition.map(p => tilesToFraction(p.y))], "BaseResourceClass": clBaseResource, // player class painted below "CityPatch": { "radius": 0.8 * baseRadius, "smoothness": 1/8, "painters": [ new LayeredPainter([terrainBaseBorder, terrainBase, terrainBaseCenter], [baseRadius/4, baseRadius/4]), paintClass(clPlayer) ] }, // Chicken already placed at the base terrain "Berries": { "template": "gaia/flora_bush_grapes", "minCount": 2, "maxCount": 2, "minDist": 10, "maxDist": 10 }, "Mines": { "types": [ { "template": templateMetalMine }, { "template": templateStoneMine } ], "minAngle": Math.PI / 2, "maxAngle": Math.PI }, "Trees": { "template": "gaia/flora_tree_oak_large", "count": 2 } }); Engine.SetProgress(10); -// Place paths -var doublePaths = true; -if (numPlayers > 4) - doublePaths = false; -if (doublePaths == true) - var maxI = numPlayers+1; -else - var maxI = numPlayers; -for (var i = 0; i < maxI; i++) -{ - if (doublePaths == true) - var minJ = 0; - else - var minJ = i+1; - for (var j = minJ; j < numPlayers+1; j++) +log("Painting paths..."); +var pathBlending = numPlayers <= 4; +for (let i = 0; i < numPlayers + (pathBlending ? 1 : 0); ++i) + for (let j = pathBlending ? 0 : i + 1; j < numPlayers + 1; ++j) { - // Setup start and target coordinates - if (i < numPlayers) - { - var x = playerStartLocX[i]; - var z = playerStartLocZ[i]; - } - else - { - var x = mapCenterX; - var z = mapCenterZ; - } - - if (j < numPlayers) - { - var targetX = playerStartLocX[j]; - var targetZ = playerStartLocZ[j]; - } - else - { - var targetX = mapCenterX; - var targetZ = mapCenterZ; - } + let pathStart = i < numPlayers ? playerPosition[i] : mapCenter; + let pathEnd = j < numPlayers ? playerPosition[j] : mapCenter; - // Prepare path placement - var angle = getAngle(x, z, targetX, targetZ); - x += Math.round(pathSucsessRadius * Math.cos(angle)); - z += Math.round(pathSucsessRadius * Math.sin(angle)); - var targetReached = false; - var tries = 0; - // Placing paths - while (targetReached == false && tries < 2*mapSize) - { - createArea( - new ClumpPlacer(pathWidth, 1, 1, 1, x, z), - [ - new TerrainPainter(terrainPath), - new ElevationPainter(randFloat(-1, 0)), - paintClass(clPath) - ], - avoidClasses(clHill, 0, clBaseResource, 4)); - - // Set vars for next loop - angle = getAngle(x, z, targetX, targetZ); - if (doublePaths == true) // Bended paths - { - x += Math.round(Math.cos(angle + randFloat(-pathAngleOff/2, 3*pathAngleOff/2))); - z += Math.round(Math.sin(angle + randFloat(-pathAngleOff/2, 3*pathAngleOff/2))); - } - else // Straight paths - { - x += Math.round(Math.cos(angle + randFloat(-pathAngleOff, pathAngleOff))); - z += Math.round(Math.sin(angle + randFloat(-pathAngleOff, pathAngleOff))); - } - if (Math.euclidDistance2D(x, z, targetX, targetZ) < pathSucsessRadius) - targetReached = true; - tries++; - - } + createArea( + new RandomPathPlacer(pathStart, pathEnd, 1.25, baseRadius / 2, pathBlending), + [ + new TerrainPainter(terrainPath), + new SmoothElevationPainter(ELEVATION_SET, -2, 2, 1), + paintClass(clPath) + ], + avoidClasses(clHill, 0, clBaseResource, 4)); } -} - Engine.SetProgress(50); -// Place expansion resources -for (var i=0; i < numPlayers; i++) -{ - for (var rIndex = 0; rIndex < resourcePerPlayer.length; rIndex++) +log("Placing expansion resources..."); +for (let i = 0; i < numPlayers; ++i) + for (let rIndex = 0; rIndex < resourcePerPlayer.length; ++rIndex) { - if (numPlayers > 1) - var angleDist = (playerAngle[(i+1)%numPlayers] - playerAngle[i] + 2 * Math.PI) % (2 * Math.PI); - else - var angleDist = 2 * Math.PI; - - var placeX = Math.round(mapCenterX + resourceRadius * Math.cos(playerAngle[i] + (rIndex+1)*angleDist/(resourcePerPlayer.length+1))); - var placeZ = Math.round(mapCenterX + resourceRadius * Math.sin(playerAngle[i] + (rIndex+1)*angleDist/(resourcePerPlayer.length+1))); + let angleDist = numPlayers > 1 ? + (playerAngle[(i + 1) % numPlayers] - playerAngle[i] + 2 * Math.PI) % (2 * Math.PI) : + 2 * Math.PI; + + // they are supposed to be in between players on the same radius + let angle = playerAngle[i] + angleDist * (rIndex + 1) / (resourcePerPlayer.length + 1); + let position = Vector2D.add(mapCenter, new Vector2D(resourceRadius, 0).rotate(-angle)).round(); - placeObject(placeX, placeZ, resourcePerPlayer[rIndex], 0, randomAngle()); + placeObject(position.x, position.y, resourcePerPlayer[rIndex], 0, randomAngle()); createArea( - new ClumpPlacer(40, 1/2, 1/8, 1, placeX, placeZ), + new ClumpPlacer(40, 1/2, 1/8, 1, position.x, position.y), [ new LayeredPainter([terrainHillBorder, terrainHill], [1]), new ElevationPainter(randFloat(1, 2)), paintClass(clHill) ]); } -} - Engine.SetProgress(60); -// Place eyecandy -placeObject(mapCenterX, mapCenterZ, templateEC, 0, randomAngle()); -addToClass(mapCenterX, mapCenterZ, clBaseResource); +log("Placing temple..."); +placeObject(mapCenter.x, mapCenter.y, templateTemple, 0, randomAngle()); +addToClass(mapCenter.x, mapCenter.y, clBaseResource); + +log("Creating central mountain..."); createArea( - new ClumpPlacer(Math.square(radiusEC), 1/2, 1/8, 1, mapCenterX, mapCenterZ), + new ClumpPlacer(Math.square(radiusEC), 1/2, 1/8, 1, mapCenter.x, mapCenter.y), [ new LayeredPainter([terrainHillBorder, terrainHill], [radiusEC/4]), new ElevationPainter(randFloat(1, 2)), paintClass(clHill) ]); // Woods and general hight map for (var x = 0; x < mapSize; x++) -{ for (var z = 0;z < mapSize;z++) { // The 0.5 is a correction for the entities placed on the center of tiles - var radius = Math.euclidDistance2D(x + 0.5, z + 0.5, mapCenterX, mapCenterZ); + var radius = Math.euclidDistance2D(x + 0.5, z + 0.5, mapCenter.x, mapCenter.y); var minDistToSL = mapSize; for (var i=0; i < numPlayers; i++) - minDistToSL = Math.min(minDistToSL, Math.euclidDistance2D(x, z, playerStartLocX[i], playerStartLocZ[i])); + minDistToSL = Math.min(minDistToSL, Math.euclidDistance2D(x, z, playerPosition[i].x, playerPosition[i].y)); // Woods tile based var tDensFactSL = Math.max(Math.min((minDistToSL - baseRadius) / baseRadius, 1), 0); var tDensFactRad = Math.abs((resourceRadius - radius) / resourceRadius); var tDensFactEC = Math.max(Math.min((radius - radiusEC) / radiusEC, 1), 0); var tDensActual = maxTreeDensity * tDensFactSL * tDensFactRad * tDensFactEC; if (randBool(tDensActual) && g_Map.validT(x, z)) { let border = tDensActual < randFloat(0, bushChance * maxTreeDensity); createArea( new ClumpPlacer(1, 1, 1, 1, x, z), [ new TerrainPainter(border ? terrainWoodBorder : terrainWood), new ElevationPainter(randFloat(0, 1)), paintClass(clForest) ], avoidClasses(clPath, 1, clHill, border ? 0 : 1)); } // General hight map var hVarMiddleHill = mapSize / 64 * (1 + Math.cos(3/2 * Math.PI * radius / mapRadius)); var hVarHills = 5 * (1 + Math.sin(x / 10) * Math.sin(z / 10)); setHeight(x, z, getHeight(x, z) + hVarMiddleHill + hVarHills + 1); } -} Engine.SetProgress(95); placePlayersNomad(clPlayer, avoidClasses(clForest, 1, clBaseResource, 4)); ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/placer_noncentered.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen/placer_noncentered.js (revision 20878) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/placer_noncentered.js (revision 20879) @@ -1,269 +1,307 @@ /** * @file A Non-Centered Placer generates a shape (array of points) at a fixed location meeting a Constraint and * is typically called by createArea. * Since this type of Placer has no x and z property, its location cannot be randomized using createAreas. */ /** * The RectPlacer provides the points on the tilegrid between (x1, z1) and (x2, z2) that meet the Constraint. */ function RectPlacer(x1, z1, x2, z2) { let mapSize = getMapSize(); this.x1 = Math.round(Math.max(Math.min(x1, x2), 0)); this.x2 = Math.round(Math.min(Math.max(x1, x2), mapSize - 1)); this.z1 = Math.round(Math.max(Math.min(z1, z2), 0)); this.z2 = Math.round(Math.min(Math.max(z1, z2), mapSize - 1)); } RectPlacer.prototype.place = function(constraint) { let points = []; for (let x = this.x1; x <= this.x2; ++x) for (let z = this.z1; z <= this.z2; ++z) if (constraint.allows(x, z)) points.push({ "x": x, "z": z }); return points; }; /** * HeightPlacer constants determining whether the extrema should be included by the placer too. */ const Elevation_ExcludeMin_ExcludeMax = 0; const Elevation_IncludeMin_ExcludeMax = 1; const Elevation_ExcludeMin_IncludeMax = 2; const Elevation_IncludeMin_IncludeMax = 3; /** * The HeightPlacer provides all points between the minimum and maximum elevation that meet the Constraint, * even if they are far from the passable area of the map. */ function HeightPlacer(mode, minElevation, maxElevation) { this.withinHeightRange = mode == Elevation_ExcludeMin_ExcludeMax ? (x, z) => g_Map.height[x][z] > minElevation && g_Map.height[x][z] < maxElevation : mode == Elevation_IncludeMin_ExcludeMax ? (x, z) => g_Map.height[x][z] >= minElevation && g_Map.height[x][z] < maxElevation : mode == Elevation_ExcludeMin_IncludeMax ? (x, z) => g_Map.height[x][z] > minElevation && g_Map.height[x][z] <= maxElevation : mode == Elevation_IncludeMin_IncludeMax ? (x, z) => g_Map.height[x][z] >= minElevation && g_Map.height[x][z] <= maxElevation : undefined; if (!this.withinHeightRange) throw new Error("Invalid HeightPlacer mode: " + mode); } HeightPlacer.prototype.place = function(constraint) { let mapSize = getMapSize(); let points = []; for (let x = 0; x < mapSize; ++x) for (let z = 0; z < mapSize; ++z) if (this.withinHeightRange(x, z) && constraint.allows(x, z)) points.push({ "x": x, "z": z }); return points; }; ///////////////////////////////////////////////////////////////////////////////////////// // PathPlacer // // Class for creating a winding path between two points // // x1,z1: Starting point of path // x2,z2: Ending point of path // width: Width of the path // a: Waviness - how wavy the path will be (higher is wavier, 0.0 is straight line) // b: Smoothness - how smooth the path will be (higher is smoother) // c: Offset - max amplitude of waves along the path (0.0 is straight line) // taper: Tapering - how much the width of the path changes from start to end // if positive, the width will decrease by that factor, if negative the width // will increase by that factor // ///////////////////////////////////////////////////////////////////////////////////////// function PathPlacer(x1, z1, x2, z2, width, a, b, c, taper, failfraction) { this.x1 = x1; this.z1 = z1; this.x2 = x2; this.z2 = z2; this.width = width; this.a = a; this.b = b; this.c = c; this.taper = taper; this.failfraction = (failfraction !== undefined ? failfraction : 5); } PathPlacer.prototype.place = function(constraint) { /*/ Preliminary bounds check if (!g_Map.validT(this.x1, this.z1) || !constraint.allows(this.x1, this.z1) || !g_Map.validT(this.x2, this.z2) || !constraint.allows(this.x2, this.z2)) { return undefined; }*/ var failed = 0; var dx = (this.x2 - this.x1); var dz = (this.z2 - this.z1); var dist = Math.sqrt(dx*dx + dz*dz); dx /= dist; dz /= dist; var numSteps = 1 + Math.floor(dist/4 * this.a); var numISteps = 1 + Math.floor(dist/4 * this.b); var totalSteps = numSteps*numISteps; var offset = 1 + Math.floor(dist/4 * this.c); var size = getMapSize(); var gotRet = []; for (var i = 0; i < size; ++i) gotRet[i] = new Uint8Array(size); // bool / uint8 // Generate random offsets var ctrlVals = new Float32Array(numSteps); //float32 for (var j = 1; j < (numSteps-1); ++j) { ctrlVals[j] = randFloat(-offset, offset); } // Interpolate for smoothed 1D noise var noise = new Float32Array(totalSteps+1); //float32 for (var j = 0; j < numSteps; ++j) for (let k = 0; k < numISteps; ++k) noise[j * numISteps + k] = cubicInterpolation( 1, k / numISteps, ctrlVals[(j + numSteps - 1) % numSteps], ctrlVals[j], ctrlVals[(j + 1) % numSteps], ctrlVals[(j + 2) % numSteps]); var halfWidth = 0.5 * this.width; // Add smoothed noise to straight path var segments1 = []; var segments2 = []; for (var j = 0; j < totalSteps; ++j) { // Interpolated points along straight path var t = j/totalSteps; var tx = this.x1 * (1.0 - t) + this.x2 * t; var tz = this.z1 * (1.0 - t) + this.z2 * t; var t2 = (j+1)/totalSteps; var tx2 = this.x1 * (1.0 - t2) + this.x2 * t2; var tz2 = this.z1 * (1.0 - t2) + this.z2 * t2; // Find noise offset points var nx = (tx - dz * noise[j]); var nz = (tz + dx * noise[j]); var nx2 = (tx2 - dz * noise[j+1]); var nz2 = (tz2 + dx * noise[j+1]); // Find slope of offset points var ndx = (nx2 - nx); var ndz = (nz2 - nz); var dist = Math.sqrt(ndx*ndx + ndz*ndz); ndx /= dist; ndz /= dist; var taperedWidth = (1.0 - t*this.taper) * halfWidth; // Find slope of offset path var px = Math.round(nx - ndz * -taperedWidth); var pz = Math.round(nz + ndx * -taperedWidth); segments1.push({ "x": px, "z": pz }); var px2 = Math.round(nx2 - ndz * taperedWidth); var pz2 = Math.round(nz2 + ndx * taperedWidth); segments2.push({ "x": px2, "z": pz2 }); } var retVec = []; // Draw path segments var num = segments1.length - 1; for (var j = 0; j < num; ++j) { // Fill quad formed by these 4 points // Note the code assumes these points have been rounded to integer values var pt11 = segments1[j]; var pt12 = segments1[j+1]; var pt21 = segments2[j]; var pt22 = segments2[j+1]; var tris = [[pt12, pt11, pt21], [pt12, pt21, pt22]]; for (var t = 0; t < 2; ++t) { // Sort vertices by min z var tri = tris[t].sort( function(a, b) { return a.z - b.z; } ); // Fills in a line from (z, x1) to (z,x2) var fillLine = function(z, x1, x2) { var left = Math.round(Math.min(x1, x2)); var right = Math.round(Math.max(x1, x2)); for (var x = left; x <= right; x++) { if (constraint.allows(x, z)) { if (g_Map.inMapBounds(x, z) && !gotRet[x][z]) { retVec.push({ "x": x, "z": z }); gotRet[x][z] = 1; } } else { failed++; } } }; var A = tri[0]; var B = tri[1]; var C = tri[2]; var dx1 = (B.z != A.z) ? ((B.x - A.x) / (B.z - A.z)) : 0; var dx2 = (C.z != A.z) ? ((C.x - A.x) / (C.z - A.z)) : 0; var dx3 = (C.z != B.z) ? ((C.x - B.x) / (C.z - B.z)) : 0; if (A.z == B.z) { fillLine(A.z, A.x, B.x); } else { for (var z = A.z; z <= B.z; z++) { fillLine(z, A.x + dx1*(z - A.z), A.x + dx2*(z - A.z)); } } if (B.z == C.z) { fillLine(B.z, B.x, C.x); } else { for (var z = B.z + 1; z < C.z; z++) { fillLine(z, B.x + dx3*(z - B.z), A.x + dx2*(z - A.z)); } } } } return ((failed > this.width*this.failfraction*dist) ? undefined : retVec); }; +/** + * Creates a winded path between the given two vectors. + * Uses a random angle at each step, so it can be more random than the sin form of the PathPlacer. + * Omits the given offset after the start and before the end. + */ +function RandomPathPlacer(pathStart, pathEnd, pathWidth, offset, blended) +{ + this.pathStart = Vector2D.add(pathStart, Vector2D.sub(pathEnd, pathStart).normalize().mult(offset)).round(); + this.pathEnd = pathEnd; + this.offset = offset; + this.blended = blended; + this.clumpPlacer = new ClumpPlacer(diskArea(pathWidth), 1, 1, 1); + this.maxPathLength = fractionToTiles(2); +} + +RandomPathPlacer.prototype.place = function(constraint) +{ + let pathLength = 0; + let points = []; + let position = this.pathStart; + + while (position.distanceTo(this.pathEnd) >= this.offset && pathLength++ < this.maxPathLength) + { + position.add( + new Vector2D(1, 0).rotate( + -getAngle(this.pathStart.x, this.pathStart.y, this.pathEnd.x, this.pathEnd.y) + + -Math.PI / 2 * (randFloat(-1, 1) + (this.blended ? 0.5 : 0)))).round(); + + this.clumpPlacer.x = position.x; + this.clumpPlacer.z = position.y; + + for (let point of this.clumpPlacer.place(constraint) || []) + if (points.every(p => p.x != point.x || p.z != point.z)) + points.push(point); + } + + return points; +}; Index: ps/trunk/binaries/data/mods/public/maps/random/schwarzwald.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/schwarzwald.js (revision 20878) +++ ps/trunk/binaries/data/mods/public/maps/random/schwarzwald.js (revision 20879) @@ -1,412 +1,322 @@ Engine.LoadLibrary('rmgen'); Engine.LoadLibrary("heightmap"); log('Initializing map...'); InitMap(); setSkySet("fog"); setFogFactor(0.35); setFogThickness(0.19); setWaterColor(0.501961, 0.501961, 0.501961); setWaterTint(0.25098, 0.501961, 0.501961); setWaterWaviness(0.5); setWaterType("clap"); setWaterMurkiness(0.75); setPPSaturation(0.37); setPPContrast(0.4); setPPBrightness(0.4); setPPEffect("hdr"); setPPBloom(0.4); var clPlayer = createTileClass(); var clPath = createTileClass(); var clForest = createTileClass(); var clWater = createTileClass(); var clMetal = createTileClass(); var clRock = createTileClass(); var clFood = createTileClass(); var clBaseResource = createTileClass(); var clOpen = createTileClass(); var templateStoneMine = 'gaia/geology_stonemine_alpine_quarry'; var templateMetalMine = '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'; var aReeds = 'actor|props/flora/reeds_pond_lush_b.xml'; var oFish = "gaia/fauna_fish"; var terrainWood = ['alpine_forrestfloor|gaia/flora_tree_oak', 'alpine_forrestfloor|gaia/flora_tree_pine']; var terrainWoodBorder = ['new_alpine_grass_mossy|gaia/flora_tree_oak', 'alpine_forrestfloor|gaia/flora_tree_pine', 'temp_grass_long|gaia/flora_bush_temperate', 'temp_grass_clovers|gaia/flora_bush_berry', 'temp_grass_clovers_2|gaia/flora_bush_grapes', 'temp_grass_plants|gaia/fauna_deer', 'temp_grass_plants|gaia/fauna_rabbit', 'new_alpine_grass_dirt_a']; var terrainBase = ['temp_plants_bog', 'temp_grass_plants', 'temp_grass_d', 'temp_grass_plants', 'temp_plants_bog', 'temp_grass_plants', 'temp_grass_plants', 'temp_plants_bog', 'temp_grass_plants', 'temp_grass_plants', 'temp_plants_bog', 'temp_grass_plants', 'temp_grass_plants', 'temp_plants_bog', 'temp_grass_plants', 'temp_grass_plants', 'temp_plants_bog', 'temp_grass_plants', 'temp_grass_plants', 'temp_plants_bog', 'temp_grass_plants', 'temp_grass_d', 'temp_grass_plants', 'temp_plants_bog', 'temp_grass_plants', 'temp_grass_d', 'temp_grass_plants', 'temp_plants_bog', 'temp_grass_plants', 'temp_grass_d', 'temp_grass_plants', 'temp_plants_bog', 'temp_grass_plants', 'temp_grass_d', 'temp_grass_plants', 'temp_plants_bog', 'temp_grass_plants', 'temp_grass_d', 'temp_grass_plants', 'temp_plants_bog', 'temp_grass_plants', 'temp_grass_plants', 'temp_grass_plants|gaia/fauna_sheep']; var terrainBaseBorder = ['temp_plants_bog', 'temp_grass_plants', 'temp_grass_d', 'temp_grass_plants', 'temp_plants_bog', 'temp_grass_plants', 'temp_grass_plants', 'temp_plants_bog', 'temp_grass_plants', 'temp_grass_plants', 'temp_plants_bog', 'temp_grass_plants', 'temp_grass_plants', 'temp_plants_bog', 'temp_grass_plants', 'temp_grass_plants', 'temp_plants_bog', 'temp_grass_plants', 'temp_grass_plants', 'temp_plants_bog', 'temp_grass_plants', 'temp_grass_d', 'temp_grass_plants', 'temp_plants_bog', 'temp_grass_plants', 'temp_grass_d', 'temp_grass_plants', 'temp_plants_bog', 'temp_grass_plants', 'temp_grass_d', 'temp_grass_plants', 'temp_plants_bog', 'temp_grass_plants', 'temp_grass_d', 'temp_grass_plants', 'temp_plants_bog', 'temp_grass_plants', 'temp_grass_d', 'temp_grass_plants', 'temp_plants_bog', 'temp_grass_plants', 'temp_grass_plants']; var baseTex = ['temp_road', 'temp_road_overgrown']; var terrainPath = ['temp_road', 'temp_road_overgrown']; var tWater = ['dirt_brown_d']; var tWaterBorder = ['dirt_brown_d']; var mapSize = getMapSize(); var mapArea = getMapArea(); +var mapCenter = getMapCenter(); var mapRadius = mapSize/2; -var mapCenterX = mapRadius; -var mapCenterZ = mapRadius; var numPlayers = getNumPlayers(); var baseRadius = 15; var minPlayerRadius = Math.min(mapRadius - 1.5 * baseRadius, 5/8 * mapRadius); var maxPlayerRadius = Math.min(mapRadius - baseRadius, 3/4 * mapRadius); -var playerStartLocX = []; -var playerStartLocZ = []; +var playerPosition = []; var playerAngleStart = randomAngle(); var playerAngleAddAvrg = 2 * Math.PI / numPlayers; var playerAngleMaxOff = playerAngleAddAvrg/4; -var pathSucsessRadius = baseRadius/2; -var pathAngleOff = Math.PI/2; -var pathWidth = 10; // This is not really the path's thickness in tiles but the number of tiles in the clumbs of the path - -var resourceRadius = 2/3 * mapRadius; +var resourceRadius = fractionToTiles(1/3); // Setup woods // For large maps there are memory errors with too many trees. A density of 256*192/mapArea works with 0 players. // Around each player there is an area without trees so with more players the max density can increase a bit. var maxTreeDensity = Math.min(256 * (192 + 8 * numPlayers) / mapArea, 1); // Has to be tweeked but works ok var bushChance = 1/3; // 1 means 50% chance in deepest wood, 0.5 means 25% chance in deepest wood -//////////////// // Set height limits and water level by map size -//////////////// // Set target min and max height depending on map size to make average steepness about the same on all map sizes var heightRange = {'min': MIN_HEIGHT * (g_Map.size + 512) / 8192, 'max': MAX_HEIGHT * (g_Map.size + 512) / 8192, 'avg': (MIN_HEIGHT * (g_Map.size + 512) +MAX_HEIGHT * (g_Map.size + 512))/16384}; // Set average water coverage var averageWaterCoverage = 1/5; // NOTE: Since erosion is not predictable actual water coverage might vary much with the same values var waterHeight = -MIN_HEIGHT + heightRange.min + averageWaterCoverage * (heightRange.max - heightRange.min); var waterHeightAdjusted = waterHeight + MIN_HEIGHT; setWaterHeight(waterHeight); -//////////////// -// Generate base terrain -//////////////// - // Setting a 3x3 Grid as initial heightmap var initialReliefmap = [[heightRange.max, heightRange.max, heightRange.max], [heightRange.max, heightRange.min, heightRange.max], [heightRange.max, heightRange.max, heightRange.max]]; setBaseTerrainDiamondSquare(heightRange.min, heightRange.max, initialReliefmap); -// Apply simple erosion + for (var i = 0; i < 5; i++) globalSmoothHeightmap(); -rescaleHeightmap(heightRange.min, heightRange.max); -////////// -// Setup height limit -////////// +rescaleHeightmap(heightRange.min, heightRange.max); -// Height presets var heighLimits = [ heightRange.min + 1/3 * (waterHeightAdjusted - heightRange.min), // 0 Deep water heightRange.min + 2/3 * (waterHeightAdjusted - heightRange.min), // 1 Medium Water heightRange.min + (waterHeightAdjusted - heightRange.min), // 2 Shallow water waterHeightAdjusted + 1/8 * (heightRange.max - waterHeightAdjusted), // 3 Shore waterHeightAdjusted + 2/8 * (heightRange.max - waterHeightAdjusted), // 4 Low ground waterHeightAdjusted + 3/8 * (heightRange.max - waterHeightAdjusted), // 5 Player and path height waterHeightAdjusted + 4/8 * (heightRange.max - waterHeightAdjusted), // 6 High ground waterHeightAdjusted + 5/8 * (heightRange.max - waterHeightAdjusted), // 7 Lower forest border waterHeightAdjusted + 6/8 * (heightRange.max - waterHeightAdjusted), // 8 Forest waterHeightAdjusted + 7/8 * (heightRange.max - waterHeightAdjusted), // 9 Upper forest border waterHeightAdjusted + (heightRange.max - waterHeightAdjusted)]; // 10 Hilltop -////////// -// Place start locations and apply terrain texture and decorative props -////////// - -// Get start locations var startLocations = getStartLocationsByHeightmap({'min': heighLimits[4], 'max': heighLimits[5]}); - var playerHeight = (heighLimits[4] + heighLimits[5]) / 2; -for (var i=0; i < numPlayers; i++) +for (let i = 0; i < numPlayers; ++i) { - let playerAngle = (playerAngleStart + i * playerAngleAddAvrg + randFloat(0, playerAngleMaxOff)) % (2* Math.PI); - let x = Math.round(mapCenterX + randFloat(minPlayerRadius, maxPlayerRadius) * Math.cos(playerAngle)); - let z = Math.round(mapCenterZ + randFloat(minPlayerRadius, maxPlayerRadius) * Math.sin(playerAngle)); - - playerStartLocX[i] = x; - playerStartLocZ[i] = z; + playerPosition[i] = Vector2D.add( + mapCenter, + new Vector2D(randFloat(minPlayerRadius, maxPlayerRadius), 0).rotate( + -((playerAngleStart + i * playerAngleAddAvrg + randFloat(0, playerAngleMaxOff)) % (2 * Math.PI)))).round(); - rectangularSmoothToHeight({"x": x,"y": z} , 20, 20, playerHeight, 0.8); + rectangularSmoothToHeight(playerPosition[i], 20, 20, playerHeight, 0.8); } placePlayerBases({ - "PlayerPlacement": [sortAllPlayers(), playerStartLocX.map(tilesToFraction), playerStartLocZ.map(tilesToFraction)], + "PlayerPlacement": [sortAllPlayers(), playerPosition.map(p => tilesToFraction(p.x)), playerPosition.map(p => tilesToFraction(p.y))], "BaseResourceClass": clBaseResource, "Walls": false, // player class painted below "CityPatch": { "radius": 0.8 * baseRadius, "smoothness": 1/8, "painters": [ new TerrainPainter([baseTex], [baseRadius/4, baseRadius/4]), paintClass(clPlayer) ] }, // No chicken "Berries": { "template": "gaia/flora_bush_berry", "minCount": 2, "maxCount": 2, "minDist": 10, "maxDist": 10 }, "Mines": { "types": [ { "template": templateMetalMine }, { "template": templateStoneMine } ], "distance": 15, "minAngle": Math.PI / 2, "maxAngle": Math.PI }, "Trees": { "template": "gaia/flora_tree_oak_large", "count": 2 } }); log("Creating mines..."); for (let [minHeight, maxHeight] of [[heighLimits[3], (heighLimits[4] + heighLimits[3]) / 2], [(heighLimits[5] + heighLimits[6]) / 2, heighLimits[7]]]) for (let [template, tileClass] of [[templateStoneMine, clRock], [templateMetalMine, clMetal]]) createObjectGroups( new SimpleGroup([new SimpleObject(template, 1, 1, 0, 4)], true, tileClass), 0, [ new HeightConstraint(minHeight, maxHeight), avoidClasses(clForest, 4, clPlayer, 20, clMetal, 40, clRock, 40) ], scaleByMapSize(2, 8), 100, false); Engine.SetProgress(50); -//place water & open terrain textures and assign TileClasses log("Painting textures..."); - var betweenShallowAndShore = (heighLimits[3] + heighLimits[2]) / 2; createArea( new HeightPlacer(Elevation_IncludeMin_IncludeMax, heighLimits[2], betweenShallowAndShore), new LayeredPainter([terrainBase, terrainBaseBorder], [5])); paintTileClassBasedOnHeight(heighLimits[2], betweenShallowAndShore, 1, clOpen); createArea( new HeightPlacer(Elevation_IncludeMin_IncludeMax, heightRange.min, heighLimits[2]), new LayeredPainter([tWaterBorder, tWater], [2])); paintTileClassBasedOnHeight(heightRange.min, heighLimits[2], 1, clWater); - Engine.SetProgress(60); -log("Placing paths..."); - -var doublePaths = true; -if (numPlayers > 4) - doublePaths = false; - -if (doublePaths === true) - var maxI = numPlayers+1; -else - var maxI = numPlayers; - -for (var i = 0; i < maxI; i++) -{ - if (doublePaths === true) - var minJ = 0; - else - var minJ = i+1; - - for (var j = minJ; j < numPlayers+1; j++) +log("Painting paths..."); +var pathBlending = numPlayers <= 4; +for (let i = 0; i < numPlayers + (pathBlending ? 1 : 0); ++i) + for (let j = pathBlending ? 0 : i + 1; j < numPlayers + 1; ++j) { - // Setup start and target coordinates - if (i < numPlayers) - { - var x = playerStartLocX[i]; - var z = playerStartLocZ[i]; - } - else - { - var x = mapCenterX; - var z = mapCenterZ; - } - - if (j < numPlayers) - { - var targetX = playerStartLocX[j]; - var targetZ = playerStartLocZ[j]; - } - else - { - var targetX = mapCenterX; - var targetZ = mapCenterZ; - } - - // Prepare path placement - var angle = getAngle(x, z, targetX, targetZ); - x += Math.round(pathSucsessRadius * Math.cos(angle)); - z += Math.round(pathSucsessRadius * Math.sin(angle)); - - var targetReached = false; - var tries = 0; - - // Placing paths - while (targetReached === false && tries < 2*mapSize) - { - var placer = new ClumpPlacer(pathWidth, 1, 1, 1, x, z); - var painter = [new TerrainPainter(terrainPath), new SmoothElevationPainter(ELEVATION_MODIFY, -0.1, 1.0), paintClass(clPath)]; - createArea(placer, painter, avoidClasses(clPath, 0, clOpen, 0 ,clWater, 4, clBaseResource, 4)); - - // addToClass(x, z, clPath); // Not needed... - // Set vars for next loop - angle = getAngle(x, z, targetX, targetZ); - if (doublePaths === true) // Bended paths - { - x += Math.round(Math.cos(angle + randFloat(-pathAngleOff/2, 3*pathAngleOff/2))); - z += Math.round(Math.sin(angle + randFloat(-pathAngleOff/2, 3*pathAngleOff/2))); - } - else // Straight paths - { - x += Math.round(Math.cos(angle + randFloat(-pathAngleOff, pathAngleOff))); - z += Math.round(Math.sin(angle + randFloat(-pathAngleOff, pathAngleOff))); - } - - if (Math.euclidDistance2D(x, z, targetX, targetZ) < pathSucsessRadius) - targetReached = true; + let pathStart = i < numPlayers ? playerPosition[i] : mapCenter; + let pathEnd = j < numPlayers ? playerPosition[j] : mapCenter; - tries++; - } + createArea( + new RandomPathPlacer(pathStart, pathEnd, 1.75, baseRadius / 2, pathBlending), + [ + new TerrainPainter(terrainPath), + new SmoothElevationPainter(ELEVATION_MODIFY, -0.1, 1), + paintClass(clPath) + ], + avoidClasses(clPath, 0, clOpen, 0 ,clWater, 4, clBaseResource, 4)); } -} - Engine.SetProgress(75); log("Creating decoration..."); createDecoration( [ [new SimpleObject(aRockMedium, 1, 3, 0, 1)], [new SimpleObject(aRockLarge, 1, 2, 0, 1), new SimpleObject(aRockMedium, 1, 3, 0, 2)], [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(16, 262), scaleByMapSize(8, 131), scaleByMapSize(13, 200), scaleByMapSize(13, 200), scaleByMapSize(13, 200) ], avoidClasses(clForest, 1, clPlayer, 0, clPath, 3, clWater, 3)); Engine.SetProgress(80); log("Growing fish..."); createFood( [ [new SimpleObject(oFish, 2, 3, 0, 2)] ], [ 100 * numPlayers ], [avoidClasses(clFood, 5), stayClasses(clWater, 4)], clFood); Engine.SetProgress(85); log("Planting reeds..."); var types = [aReeds]; for (let type of types) createObjectGroupsDeprecated( new SimpleGroup([new SimpleObject(type, 1, 1, 0, 0)], true), 0, borderClasses(clWater, 0, 6), scaleByMapSize(1, 2) * 1000, 1000); Engine.SetProgress(90); log("Planting trees..."); for (var x = 0; x < mapSize; x++) for (var z = 0;z < mapSize;z++) { if (!g_Map.validT(x, z)) continue; // The 0.5 is a correction for the entities placed on the center of tiles - var radius = Math.euclidDistance2D(x + 0.5, z + 0.5, mapCenterX, mapCenterZ); + var radius = Math.euclidDistance2D(x + 0.5, z + 0.5, mapCenter.x, mapCenter.y); var minDistToSL = mapSize; for (var i=0; i < numPlayers; i++) - minDistToSL = Math.min(minDistToSL, Math.euclidDistance2D(playerStartLocX[i], playerStartLocZ[i], x, z)); + minDistToSL = Math.min(minDistToSL, Math.euclidDistance2D(x, z, playerPosition[i].x, playerPosition[i].x)); // Woods tile based var tDensFactSL = Math.max(Math.min((minDistToSL - baseRadius) / baseRadius, 1), 0); var tDensFactRad = Math.abs((resourceRadius - radius) / resourceRadius); var tDensActual = (maxTreeDensity * tDensFactSL * tDensFactRad)*0.75; if (!randBool(tDensActual)) continue; let border = tDensActual < randFloat(0, bushChance * maxTreeDensity); createArea( new RectPlacer(x, z, x, z), [ new TerrainPainter(border ? terrainWoodBorder : terrainWood), paintClass(clForest) ], border ? avoidClasses(clPath, 1, clOpen, 2, clWater, 3, clMetal, 4, clRock, 4) : avoidClasses(clPath, 2, clOpen, 3, clWater, 4, clMetal, 4, clRock, 4)); } placePlayersNomad(clPlayer, avoidClasses(clWater, 4, clForest, 1, clFood, 2, clMetal, 4, clRock, 4)); Engine.SetProgress(100); ExportMap();