Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/library.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen/library.js (revision 20436) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/library.js (revision 20437) @@ -1,527 +1,551 @@ const PI = Math.PI; const TWO_PI = 2 * Math.PI; const TERRAIN_SEPARATOR = "|"; const SEA_LEVEL = 20.0; const HEIGHT_UNITS_PER_METRE = 92; const MAP_BORDER_WIDTH = 3; const FALLBACK_CIV = "athen"; /** * 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 MAX_HEIGHT = MAX_HEIGHT_RANGE - SEA_LEVEL; // Default angle for buildings const BUILDING_ORIENTATION = - PI / 4; function fractionToTiles(f) { return g_Map.size * f; } function tilesToFraction(t) { return t / g_Map.size; } function fractionToSize(f) { return getMapArea() * f; } function sizeToFraction(s) { return s / getMapArea(); } function scaleByMapSize(min, max, minMapSize = 128, maxMapSize = 512) { return min + (max - min) * (g_Map.size - minMapSize) / (maxMapSize - minMapSize); } function cos(x) { return Math.cos(x); } function sin(x) { return Math.sin(x); } function abs(x) { return Math.abs(x); } function round(x) { return Math.round(x); } function lerp(a, b, t) { return a + (b-a) * t; } function sqrt(x) { return Math.sqrt(x); } function ceil(x) { return Math.ceil(x); } function floor(x) { return Math.floor(x); } function max(a, b) { return a > b ? a : b; } function min(a, b) { return a < b ? a : b; } /** * Retries the given function with those arguments as often as specified. */ function retryPlacing(placeFunc, placeArgs, retryFactor, amount, getResult, behaveDeprecated = false) { if (behaveDeprecated && !(placeArgs.placer instanceof SimpleGroup || placeArgs.placer instanceof RandomGroup)) warn("Deprecated version of createFoo should only be used for SimpleGroup and RandomGroup placers!"); let maxFail = amount * retryFactor; let results = []; let good = 0; let bad = 0; while (good < amount && bad <= maxFail) { let result = placeFunc(placeArgs); if (result !== undefined || behaveDeprecated) { ++good; if (getResult) results.push(result); } else ++bad; } return getResult ? results : good; } /** * Helper function for randomly placing areas and groups on the map. */ function randomizePlacerCoordinates(placer, halfMapSize) { if (!!g_MapSettings.CircularMap) { // Polar coordinates // Uniformly distributed on the disk let r = halfMapSize * Math.sqrt(randFloat(0, 1)); let theta = randFloat(0, 2 * PI); placer.x = Math.floor(r * Math.cos(theta)) + halfMapSize; placer.z = Math.floor(r * Math.sin(theta)) + halfMapSize; } else { // Rectangular coordinates placer.x = randIntExclusive(0, g_Map.size); placer.z = randIntExclusive(0, g_Map.size); } } /** * Helper function for randomly placing areas and groups in the given areas. */ function randomizePlacerCoordinatesFromAreas(placer, areas) { let pt = pickRandom(pickRandom(areas).points); placer.x = pt.x; placer.z = pt.z; } // TODO this is a hack to simulate the old behaviour of those functions // until all old maps are changed to use the correct version of these functions function createObjectGroupsDeprecated(placer, player, constraint, amount, retryFactor = 10) { return createObjectGroups(placer, player, constraint, amount, retryFactor, true); } function createObjectGroupsByAreasDeprecated(placer, player, constraint, amount, retryFactor, areas) { return createObjectGroupsByAreas(placer, player, constraint, amount, retryFactor, areas, true); } /** * Attempts to place the given number of areas in random places of the map. * Returns actually placed areas. */ function createAreas(centeredPlacer, painter, constraint, amount, retryFactor = 10, behaveDeprecated = false) { let placeFunc = function (args) { randomizePlacerCoordinates(args.placer, args.halfMapSize); return createArea(args.placer, args.painter, args.constraint); }; let args = { "placer": centeredPlacer, "painter": painter, "constraint": constraint, "halfMapSize": g_Map.size / 2 }; return retryPlacing(placeFunc, args, retryFactor, amount, true, behaveDeprecated); } /** * Attempts to place the given number of areas in random places of the given areas. * Returns actually placed areas. */ function createAreasInAreas(centeredPlacer, painter, constraint, amount, retryFactor, areas, behaveDeprecated = false) { if (!areas.length) return []; let placeFunc = function (args) { randomizePlacerCoordinatesFromAreas(args.placer, args.areas); return createArea(args.placer, args.painter, args.constraint); }; let args = { "placer": centeredPlacer, "painter": painter, "constraint": constraint, "areas": areas, "halfMapSize": g_Map.size / 2 }; return retryPlacing(placeFunc, args, retryFactor, amount, true, behaveDeprecated); } /** * Attempts to place the given number of groups in random places of the map. * Returns the number of actually placed groups. */ function createObjectGroups(placer, player, constraint, amount, retryFactor = 10, behaveDeprecated = false) { let placeFunc = function (args) { randomizePlacerCoordinates(args.placer, args.halfMapSize); return createObjectGroup(args.placer, args.player, args.constraint); }; let args = { "placer": placer, "player": player, "constraint": constraint, "halfMapSize": getMapSize() / 2 - MAP_BORDER_WIDTH }; return retryPlacing(placeFunc, args, retryFactor, amount, false, behaveDeprecated); } /** * Attempts to place the given number of groups in random places of the given areas. * Returns the number of actually placed groups. */ function createObjectGroupsByAreas(placer, player, constraint, amount, retryFactor, areas, behaveDeprecated = false) { if (!areas.length) return 0; let placeFunc = function (args) { randomizePlacerCoordinatesFromAreas(args.placer, args.areas); return createObjectGroup(args.placer, args.player, args.constraint); }; let args = { "placer": placer, "player": player, "constraint": constraint, "areas": areas }; return retryPlacing(placeFunc, args, retryFactor, amount, false, behaveDeprecated); } function createTerrain(terrain) { if (!(terrain instanceof Array)) return createSimpleTerrain(terrain); return new RandomTerrain(terrain.map(t => createTerrain(t))); } function createSimpleTerrain(terrain) { if (typeof(terrain) != "string") throw new Error("createSimpleTerrain expects string as input, received " + uneval(terrain)); // Split string by pipe | character, this allows specifying terrain + tree type in single string let params = terrain.split(TERRAIN_SEPARATOR, 2); if (params.length != 2) return new SimpleTerrain(terrain); return new SimpleTerrain(params[0], params[1]); } function placeObject(x, z, type, player, angle) { if (g_Map.validT(x, z)) g_Map.addObject(new Entity(type, player, x, z, angle)); } function placeTerrain(x, z, terrainNames) { createTerrain(terrainNames).place(x, z); } function initTerrain(terrainNames) { let terrain = createTerrain(terrainNames); for (let x = 0; x < getMapSize(); ++x) for (let z = 0; z < getMapSize(); ++z) terrain.place(x, z); } function isCircularMap() { return !!g_MapSettings.CircularMap; } function getMapBaseHeight() { return g_MapSettings.BaseHeight; } function createTileClass() { return g_Map.createTileClass(); } function getTileClass(id) { if (!g_Map.validClass(id)) return undefined; return g_Map.tileClasses[id]; } /** * Constructs a new Area shaped by the Placer meeting the Constraint and calls the Painters there. * Supports both Centered and Non-Centered Placers. */ function createArea(placer, painter, constraint) { if (!constraint) constraint = new NullConstraint(); else if (constraint instanceof Array) constraint = new AndConstraint(constraint); let points = placer.place(constraint); if (!points) return undefined; let area = g_Map.createArea(points); if (painter instanceof Array) painter = new MultiPainter(painter); painter.paint(area); return area; } /** + * @param mode is one of the HeightPlacer constants determining whether to exclude the min/max elevation. + */ +function paintTerrainBasedOnHeight(minHeight, maxHeight, mode, terrain) +{ + createArea( + new HeightPlacer(mode, minHeight, maxHeight), + new TerrainPainter(terrain)); +} + +function paintTileClassBasedOnHeight(minHeight, maxHeight, mode, tileClass) +{ + createArea( + new HeightPlacer(mode, minHeight, maxHeight), + new TileClassPainter(getTileClass(tileClass))); +} + +function unPaintTileClassBasedOnHeight(minHeight, maxHeight, mode, tileClass) +{ + createArea( + new HeightPlacer(mode, minHeight, maxHeight), + new TileClassUnPainter(getTileClass(tileClass))); +} + +/** * Places the Entities of the given Group if they meet the Constraint * and sets the given player as the owner. */ function createObjectGroup(group, player, constraint) { if (!constraint) constraint = new NullConstraint(); else if (constraint instanceof Array) constraint = new AndConstraint(constraint); return group.place(player, constraint); } function getMapSize() { return g_Map.size; } function getMapArea() { return g_Map.size * g_Map.size; } function getNumPlayers() { return g_MapSettings.PlayerData.length - 1; } function getCivCode(player) { if (g_MapSettings.PlayerData[player+1].Civ) return g_MapSettings.PlayerData[player+1].Civ; warn("undefined civ specified for player " + (player + 1) + ", falling back to '" + FALLBACK_CIV + "'"); return FALLBACK_CIV; } function areAllies(player1, player2) { if (g_MapSettings.PlayerData[player1+1].Team === undefined || g_MapSettings.PlayerData[player2+1].Team === undefined || g_MapSettings.PlayerData[player2+1].Team == -1 || g_MapSettings.PlayerData[player1+1].Team == -1) return false; return g_MapSettings.PlayerData[player1+1].Team === g_MapSettings.PlayerData[player2+1].Team; } function getPlayerTeam(player) { if (g_MapSettings.PlayerData[player+1].Team === undefined) return -1; return g_MapSettings.PlayerData[player+1].Team; } function getHeight(x, z) { return g_Map.getHeight(x, z); } function setHeight(x, z, height) { g_Map.setHeight(x, z, height); } function initHeight(height) { g_Map.initHeight(height); } /** * Utility functions for classes */ /** * Add point to given class by id */ function addToClass(x, z, id) { let tileClass = getTileClass(id); if (tileClass !== null) tileClass.add(x, z); } /** * Remove point from the given class by id */ function removeFromClass(x, z, id) { let tileClass = getTileClass(id); if (tileClass !== null) tileClass.remove(x, z); } /** * Create a painter for the given class */ function paintClass(id) { return new TileClassPainter(getTileClass(id)); } /** * Create a painter for the given class */ function unPaintClass(id) { return new TileClassUnPainter(getTileClass(id)); } /** * 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); } /** * Checks if the given tile is in class "id" */ function checkIfInClass(x, z, id) { let tileClass = getTileClass(id); if (tileClass === null) return 0; let members = tileClass.countMembersInRadius(x, z, 1); if (members === null) return 0; return members; } function getTerrainTexture(x, y) { return g_Map.getTexture(x, y); } Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/misc.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen/misc.js (revision 20436) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/misc.js (revision 20437) @@ -1,78 +1,43 @@ function placeDefaultChicken(playerX, playerZ, tileClass, constraint = undefined, template = "gaia/fauna_chicken") { for (let j = 0; j < 2; ++j) for (var tries = 0; tries < 10; ++tries) { let aAngle = randFloat(0, TWO_PI); // Roman and ptolemian civic centers have a big footprint! let aDist = 9; let aX = round(playerX + aDist * cos(aAngle)); let aZ = round(playerZ + aDist * sin(aAngle)); let group = new SimpleGroup( [new SimpleObject(template, 5,5, 0,2)], true, tileClass, aX, aZ ); if (createObjectGroup(group, 0, constraint)) break; } } /** * Typically used for placing grass tufts around the civic centers. */ function placeDefaultDecoratives(playerX, playerZ, template, tileclass, radius, constraint = undefined) { for (let i = 0; i < PI * radius * radius / 250; ++i) { let angle = randFloat(0, 2 * PI); let dist = radius - randIntInclusive(5, 11); createObjectGroup( new SimpleGroup( [new SimpleObject(template, 2, 5, 0, 1, -PI/8, PI/8)], false, tileclass, Math.round(playerX + dist * Math.cos(angle)), Math.round(playerZ + dist * Math.sin(angle)) ), 0, constraint); } } - -function modifyTilesBasedOnHeight(minHeight, maxHeight, mode, func) -{ - for (let qx = 0; qx < g_Map.size; ++qx) - for (let qz = 0; qz < g_Map.size; ++qz) - { - let height = g_Map.getHeight(qx, qz); - if (mode == 0 && height > minHeight && height < maxHeight || - mode == 1 && height >= minHeight && height < maxHeight || - mode == 2 && height > minHeight && height <= maxHeight || - mode == 3 && height >= minHeight && height <= maxHeight) - func(qx, qz); - } -} - -function paintTerrainBasedOnHeight(minHeight, maxHeight, mode, terrain) -{ - modifyTilesBasedOnHeight(minHeight, maxHeight, mode, (qx, qz) => { - placeTerrain(qx, qz, terrain); - }); -} - -function paintTileClassBasedOnHeight(minHeight, maxHeight, mode, tileclass) -{ - modifyTilesBasedOnHeight(minHeight, maxHeight, mode, (qx, qz) => { - addToClass(qx, qz, tileclass); - }); -} - -function unPaintTileClassBasedOnHeight(minHeight, maxHeight, mode, tileclass) -{ - modifyTilesBasedOnHeight(minHeight, maxHeight, mode, (qx, qz) => { - removeFromClass(qx, qz, tileclass); - }); -} 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 20436) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/placer_noncentered.js (revision 20437) @@ -1,259 +1,274 @@ /** * @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 between (x1, z1) and (x2, z2) if they meet the Constraint. */ function RectPlacer(x1, z1, x2, z2) { this.x1 = x1; this.z1 = z1; this.x2 = x2; this.z2 = z2; if (x1 > x2 || z1 > z2) throw new Error("RectPlacer: invalid bounds"); } RectPlacer.prototype.place = function(constraint) { if (!g_Map.inMapBounds(this.x1, this.z1) || !g_Map.inMapBounds(this.x2, this.z2)) return undefined; 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 }); else return undefined; return points; }; /** - * The HeightPlacer provides all points between the minimum and maximum elevation that meet the Constraint. + * HeightPlacer constants determining whether the extrema should be included by the placer too. */ -function HeightPlacer(minElevation, maxElevation) +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.minElevation = minElevation; - this.maxElevation = 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 (g_Map.height[x][z] >= this.minElevation && - g_Map.height[x][z] <= this.maxElevation && - constraint.allows(x, 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); }; Index: ps/trunk/binaries/data/mods/public/maps/random/schwarzwald.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/schwarzwald.js (revision 20436) +++ ps/trunk/binaries/data/mods/public/maps/random/schwarzwald.js (revision 20437) @@ -1,386 +1,390 @@ RMS.LoadLibrary('rmgen'); RMS.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 clFood = createTileClass(); var clBaseResource = createTileClass(); var clOpen = createTileClass(); var templateStoneMine = 'gaia/geology_stonemine_alpine_quarry'; var templateMetalMine = 'gaia/geology_metal_alpine_slabs'; var startingResources = ['gaia/flora_tree_pine', 'gaia/flora_tree_pine','gaia/flora_tree_pine', templateStoneMine, 'gaia/flora_bush_grapes', 'gaia/flora_tree_aleppo_pine','gaia/flora_tree_aleppo_pine','gaia/flora_tree_aleppo_pine', 'gaia/flora_bush_berry', templateMetalMine]; 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 mapRadius = mapSize/2; var playableMapRadius = mapRadius - 5; var mapCenterX = mapRadius; var mapCenterZ = mapRadius; var numPlayers = getNumPlayers(); var baseRadius = 15; var minPlayerRadius = min(mapRadius-1.5*baseRadius, 5*mapRadius/8); var maxPlayerRadius = min(mapRadius-baseRadius, 3*mapRadius/4); var playerStartLocX = []; var playerStartLocZ = []; var playerAngle = []; var playerAngleStart = randFloat(0, 2*PI); var playerAngleAddAvrg = 2*PI / numPlayers; var playerAngleMaxOff = playerAngleAddAvrg/4; var pathSucsessRadius = baseRadius/2; var pathAngleOff = 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; // 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 = min(256 * (192 + 8 * numPlayers) / (mapSize * mapSize), 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); RMS.SetProgress(50); ////////// // Setup height limit ////////// // 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++) { playerAngle[i] = (playerAngleStart + i*playerAngleAddAvrg + randFloat(0, playerAngleMaxOff))%(2*PI); var x = round(mapCenterX + randFloat(minPlayerRadius, maxPlayerRadius)*cos(playerAngle[i])); var z = round(mapCenterZ + randFloat(minPlayerRadius, maxPlayerRadius)*sin(playerAngle[i])); playerStartLocX[i] = x; playerStartLocZ[i] = z; rectangularSmoothToHeight({"x": x,"y": z} , 20, 20, playerHeight, 0.8); placeCivDefaultEntities(x, z, i+1, { 'iberWall': false }); // Place base texture var placer = new ClumpPlacer(2*baseRadius*baseRadius, 2/3, 1/8, 10, x, z); var painter = [new TerrainPainter([baseTex], [baseRadius/4, baseRadius/4]), paintClass(clPlayer)]; createArea(placer, painter); // Place starting resources var distToSL = 15; var resStartAngle = playerAngle[i] + PI; var resAddAngle = 2*PI / startingResources.length; for (var rIndex = 0; rIndex < startingResources.length; rIndex++) { var angleOff = randFloat(-resAddAngle/2, resAddAngle/2); var placeX = x + distToSL*cos(resStartAngle + rIndex*resAddAngle + angleOff); var placeZ = z + distToSL*sin(resStartAngle + rIndex*resAddAngle + angleOff); placeObject(placeX, placeZ, startingResources[rIndex], 0, randFloat(0, 2*PI)); addToClass(round(placeX), round(placeZ), clBaseResource); } } // Add further stone and metal mines distributeEntitiesByHeight({ 'min': heighLimits[3], 'max': ((heighLimits[4] + heighLimits[3]) / 2) }, startLocations, 40, [templateStoneMine, templateMetalMine]); distributeEntitiesByHeight({ 'min': ((heighLimits[5] + heighLimits[6]) / 2), 'max': heighLimits[7] }, startLocations, 40, [templateStoneMine, templateMetalMine]); RMS.SetProgress(50); //place water & open terrain textures and assign TileClasses log("Painting textures..."); -var placer = new HeightPlacer(heighLimits[2], (heighLimits[3]+heighLimits[2])/2); -var painter = new LayeredPainter([terrainBase, terrainBaseBorder], [5]); -createArea(placer, painter); -paintTileClassBasedOnHeight(heighLimits[2], (heighLimits[3]+heighLimits[2])/2, 1, clOpen); - -var placer = new HeightPlacer(heightRange.min, heighLimits[2]); -var painter = new LayeredPainter([tWaterBorder, tWater], [2]); -createArea(placer, painter); + +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); RMS.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++) { // 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 += round(pathSucsessRadius*cos(angle)); z += round(pathSucsessRadius*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 += round(cos(angle + randFloat(-pathAngleOff/2, 3*pathAngleOff/2))); z += round(sin(angle + randFloat(-pathAngleOff/2, 3*pathAngleOff/2))); } else // Straight paths { x += round(cos(angle + randFloat(-pathAngleOff, pathAngleOff))); z += round(sin(angle + randFloat(-pathAngleOff, pathAngleOff))); } if (Math.euclidDistance2D(x, z, targetX, targetZ) < pathSucsessRadius) targetReached = true; tries++; } } } RMS.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, -PI/8,PI/8)], [new SimpleObject(aGrass, 2,4, 0,1.8, -PI/8,PI/8), new SimpleObject(aGrassShort, 3,6, 1.2,2.5, -PI/8,PI/8)], [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) ); RMS.SetProgress(80); log("Growing fish..."); createFood ( [ [new SimpleObject(oFish, 2,3, 0,2)] ], [ 100 * numPlayers ], [avoidClasses(clFood, 5), stayClasses(clWater, 4)] ); RMS.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); RMS.SetProgress(90); log("Planting trees..."); 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 minDistToSL = mapSize; for (var i=0; i < numPlayers; i++) minDistToSL = min(minDistToSL, Math.euclidDistance2D(playerStartLocX[i], playerStartLocZ[i], x, z)); // Woods tile based var tDensFactSL = max(min((minDistToSL - baseRadius) / baseRadius, 1), 0); var tDensFactRad = abs((resourceRadius - radius) / resourceRadius); var tDensActual = (maxTreeDensity * tDensFactSL * tDensFactRad)*0.75; if (randBool(tDensActual) && radius < playableMapRadius) { if (tDensActual < randFloat(0, bushChance * maxTreeDensity)) { var placer = new ClumpPlacer(1, 1.0, 1.0, 1, x, z); var painter = [new TerrainPainter(terrainWoodBorder), paintClass(clForest)]; createArea(placer, painter, avoidClasses(clPath, 1, clOpen, 2, clWater,3)); } else { var placer = new ClumpPlacer(1, 1.0, 1.0, 1, x, z); var painter = [new TerrainPainter(terrainWood), paintClass(clForest)]; createArea(placer, painter, avoidClasses(clPath, 2, clOpen, 3, clWater, 4));} } } } RMS.SetProgress(100); ExportMap();