Index: ps/trunk/binaries/data/mods/public/maps/random/ardennes_forest.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/ardennes_forest.js (revision 20982) +++ ps/trunk/binaries/data/mods/public/maps/random/ardennes_forest.js (revision 20983) @@ -1,462 +1,462 @@ Engine.LoadLibrary("rmgen"); const tPrimary = ["temp_forestfloor_pine", "temp_forestfloor_pine", "alpine_cliff_c", "alpine_grass_rocky"]; const tGrass = ["new_alpine_grass_b", "new_alpine_grass_c", "new_alpine_grass_d"]; const tPineForestFloor = "temp_forestfloor_pine"; const tForestFloor = [tPineForestFloor, tPineForestFloor, "alpine_dirt_grass_50"]; const tCliff = ["alpine_cliff_c", "alpine_cliff_c", "alpine_grass_rocky"]; const tCity = ["new_alpine_citytile", "new_alpine_grass_dirt_a"]; const tGrassPatch = ["alpine_grass_a", "alpine_grass_b"]; const oBoar = "gaia/fauna_boar"; const oDeer = "gaia/fauna_deer"; const oBear = "gaia/fauna_bear"; const oPig = "gaia/fauna_pig"; const oBerryBush = "gaia/flora_bush_berry"; const oMetalSmall = "gaia/geology_metal_alpine"; const oMetalLarge = "gaia/geology_metal_temperate_slabs"; const oStoneSmall = "gaia/geology_stone_alpine_a"; const oStoneLarge = "gaia/geology_stonemine_temperate_quarry"; const oOak = "gaia/flora_tree_oak"; const oOakLarge = "gaia/flora_tree_oak_large"; const oPine = "gaia/flora_tree_pine"; const oAleppoPine = "gaia/flora_tree_aleppo_pine"; const aTreeA = "actor|flora/trees/oak.xml"; const aTreeB = "actor|flora/trees/oak_large.xml"; const aTreeC = "actor|flora/trees/pine.xml"; const aTreeD = "actor|flora/trees/aleppo_pine.xml"; const aTrees = [aTreeA, aTreeB, aTreeC, aTreeD]; const aGrassLarge = "actor|props/flora/grass_soft_large.xml"; const aWoodLarge = "actor|props/special/eyecandy/wood_pile_1_b.xml"; const aWoodA = "actor|props/special/eyecandy/wood_sm_pile_a.xml"; const aWoodB = "actor|props/special/eyecandy/wood_sm_pile_b.xml"; const aBarrel = "actor|props/special/eyecandy/barrel_a.xml"; const aWheel = "actor|props/special/eyecandy/wheel_laying.xml"; const aCeltHomestead = "actor|structures/celts/homestead.xml"; const aCeltHouse = "actor|structures/celts/house.xml"; const aCeltLongHouse = "actor|structures/celts/longhouse.xml"; var pForest = [ tPineForestFloor+TERRAIN_SEPARATOR+oOak, tForestFloor, tPineForestFloor+TERRAIN_SEPARATOR+oPine, tForestFloor, tPineForestFloor+TERRAIN_SEPARATOR+oAleppoPine, tForestFloor, tForestFloor ]; const heightRavineValley = 2; const heightLand = 30; const heightRavineHill = 40; const heightHill = 50; const heightOffsetRavine = 10; InitMap(heightHill, tPrimary); const numPlayers = getNumPlayers(); const mapSize = getMapSize(); const mapCenter = getMapCenter(); var clPlayer = createTileClass(); var clHill = createTileClass(); var clForest = createTileClass(); var clForestJoin = createTileClass(); var clRock = createTileClass(); var clMetal = createTileClass(); var clFood = createTileClass(); var clBaseResource = createTileClass(); var clHillDeco = createTileClass(); log("Creating the central dip..."); createArea( new ClumpPlacer(diskArea(fractionToTiles(0.42)), 0.94, 0.05, 0.1, mapCenter), [ new LayeredPainter([tCliff, tGrass], [3]), new SmoothElevationPainter(ELEVATION_SET, heightLand, 3) ]); Engine.SetProgress(5); // Find all hills var noise0 = new Noise2D(20); for (var ix = 0; ix < mapSize; ix++) for (var iz = 0; iz < mapSize; iz++) { let position = new Vector2D(ix, iz); - var h = getHeight(ix,iz); + let h = g_Map.getHeight(position); if (h > heightRavineHill) { addToClass(ix,iz,clHill); // Add hill noise var x = ix / (mapSize + 1.0); var z = iz / (mapSize + 1.0); var n = (noise0.get(x, z) - 0.5) * heightRavineHill; g_Map.setHeight(position, h + n); } } var [playerIDs, playerPosition] = playerPlacementCircle(fractionToTiles(0.3)); function distanceToPlayers(x, z) { var r = 10000; for (var i = 0; i < numPlayers; i++) { var dx = x - playerPosition[i].x; var dz = z - playerPosition[i].y; r = Math.min(r, Math.square(dx) + Math.square(dz)); } return Math.sqrt(r); } function playerNearness(x, z) { var d = fractionToTiles(distanceToPlayers(x,z)); if (d < 13) return 0; if (d < 19) return (d-13)/(19-13); return 1; } Engine.SetProgress(10); placePlayerBases({ "PlayerPlacement": [playerIDs, playerPosition], "BaseResourceClass": clBaseResource, // Playerclass marked below "CityPatch": { "outerTerrain": tCity, "innerTerrain": tCity, "radius": scaleByMapSize(5, 6), "smoothness": 0.05 }, "Chicken": { "template": oPig }, "Berries": { "template": oBerryBush, "minCount": 3, "maxCount": 3 }, "Mines": { "types": [ { "template": oMetalLarge }, { "template": oStoneLarge } ], "distance": 16 }, "Trees": { "template": oOak, "count": 2 } // No decoratives }); log("Marking player territory larger than the city patch..."); for (let i = 0; i < numPlayers; ++i) createArea( new ClumpPlacer(250, 0.95, 0.3, 0.1, playerPosition[i]), paintClass(clPlayer)); Engine.SetProgress(30); log("Creating hills..."); for (let size of [scaleByMapSize(50, 800), scaleByMapSize(50, 400), scaleByMapSize(10, 30), scaleByMapSize(10, 30)]) { let mountains = createAreas( new ClumpPlacer(size, 0.1, 0.2, 0.1), [ new LayeredPainter([tCliff, [tForestFloor, tForestFloor, tCliff]], [2]), new SmoothElevationPainter(ELEVATION_SET, heightHill, size < 50 ? 2 : 4), paintClass(clHill) ], avoidClasses(clPlayer, 8, clBaseResource, 2, clHill, 5), scaleByMapSize(1, 4)); if (size > 100 && mountains.length) createAreasInAreas( new ClumpPlacer(size * 0.3, 0.94, 0.05, 0.1), [ new LayeredPainter([tCliff, tForestFloor], [2]), new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetRavine, 3) ], stayClasses(clHill, 4), mountains.length * 2, 20, mountains); let ravine = createAreas( new ClumpPlacer(size, 0.1, 0.2, 0.1), [ new LayeredPainter([tCliff, tForestFloor], [2]), new SmoothElevationPainter(ELEVATION_SET, heightRavineValley, 2), paintClass(clHill) ], avoidClasses(clPlayer, 6, clBaseResource, 2, clHill, 5), scaleByMapSize(1, 3)); if (size > 150 && ravine.length) { log("Placing huts in ravines..."); createObjectGroupsByAreasDeprecated( new RandomGroup( [ new SimpleObject(aCeltHouse, 0, 1, 4, 5), new SimpleObject(aCeltLongHouse, 1, 1, 4, 5) ], true, clHillDeco), 0, [avoidClasses(clHillDeco, 3), stayClasses(clHill, 3)], ravine.length * 5, 20, ravine); createObjectGroupsByAreasDeprecated( new RandomGroup([new SimpleObject(aCeltHomestead, 1, 1, 1, 1)], true, clHillDeco), 0, [avoidClasses(clHillDeco, 5), stayClasses(clHill, 4)], ravine.length * 2, 100, ravine); // Place noise createAreasInAreas( new ClumpPlacer(size * 0.3, 0.94, 0.05, 0.1), [ new LayeredPainter([tCliff, tForestFloor], [2]), new SmoothElevationPainter(ELEVATION_SET, heightRavineValley, 2) ], [avoidClasses(clHillDeco, 2), stayClasses(clHill, 0)], ravine.length * 2, 20, ravine); createAreasInAreas( new ClumpPlacer(size * 0.1, 0.3, 0.05, 0.1), [ new LayeredPainter([tCliff, tForestFloor], [2]), new SmoothElevationPainter(ELEVATION_SET, heightRavineHill, 2), paintClass(clHill) ], [avoidClasses(clHillDeco, 2), borderClasses(clHill, 15, 1)], ravine.length * 2, 50, ravine); } } Engine.SetProgress(50); var explorablePoints = []; var playerClass = getTileClass(clPlayer); var hillDecoClass = getTileClass(clHillDeco); for (var ix = 0; ix < mapSize; ix++) for (var iz = 0; iz < mapSize; iz++) { let position = new Vector2D(ix, iz); - var h = getHeight(ix,iz); + let h = g_Map.getHeight(position); if(h > 15 && h < 45 && playerClass.countMembersInRadius(ix, iz, 1) == 0) explorablePoints.push(position); if (h > 35 && randBool(0.1) || h < 15 && randBool(0.05) && hillDecoClass.countMembersInRadius(ix, iz, 1) == 0) placeObject(ix + randFloat(0, 1), iz + randFloat(0, 1), pickRandom(aTrees), 0, randomAngle()); } var explorableArea = g_Map.createArea(explorablePoints); Engine.SetProgress(55); // Add some general noise - after placing height dependant trees for (var ix = 0; ix < mapSize; ix++) { var x = ix / (mapSize + 1.0); for (var iz = 0; iz < mapSize; iz++) { let position = new Vector2D(ix, iz); var z = iz / (mapSize + 1.0); - var h = getHeight(ix,iz); + var h = g_Map.getHeight(position); var pn = playerNearness(x,z); var n = (noise0.get(x,z) - 0.5) * 10; g_Map.setHeight(position, h + (n * pn)); } } Engine.SetProgress(60); log("Creating forests..."); var [forestTrees, stragglerTrees] = getTreeCounts(400, 6000, 0.8); var [forestTreesJoin, forestTrees] = getTreeCounts(forestTrees, forestTrees, 0.25); var num = forestTrees / (scaleByMapSize(6, 16) * numPlayers); createAreasInAreas( new ClumpPlacer(forestTrees / num, 0.1, 0.1, 1), [ new TerrainPainter(pForest), paintClass(clForest) ], avoidClasses(clPlayer, 5, clBaseResource, 4, clForest, 6, clHill, 4), num, 100, [explorableArea] ); var num = forestTreesJoin / (scaleByMapSize(4,6) * numPlayers); createAreasInAreas( new ClumpPlacer(forestTreesJoin / num, 0.1, 0.1, 1), [ new TerrainPainter(pForest), paintClass(clForest), paintClass(clForestJoin) ], [avoidClasses(clPlayer, 5, clBaseResource, 4, clForestJoin, 5, clHill, 4), borderClasses(clForest, 1, 4)], num, 100, [explorableArea] ); Engine.SetProgress(70); log("Creating grass patches..."); for (let size of [scaleByMapSize(3, 48), scaleByMapSize(5, 84), scaleByMapSize(8, 128)]) createAreas( new ClumpPlacer(size, 0.3, 0.06, 0.5), new LayeredPainter([[tGrass, tGrassPatch], [tGrassPatch, tGrass], [tGrass, tGrassPatch]], [1, 1]), avoidClasses(clForest, 0, clHill, 2, clPlayer, 5), scaleByMapSize(15, 45)); log("Creating chopped forest patches..."); for (let size of [scaleByMapSize(20, 120)]) createAreas( new ClumpPlacer(size, 0.3, 0.06, 0.5), new TerrainPainter(tForestFloor), avoidClasses(clForest, 1, clHill, 2, clPlayer, 5), scaleByMapSize(4, 12)); Engine.SetProgress(75); log("Creating stone mines..."); var group = new SimpleGroup([new SimpleObject(oStoneSmall, 1,2, 0,4), new SimpleObject(oStoneLarge, 0,1, 0,4)], true, clRock); createObjectGroupsByAreasDeprecated(group, 0, [avoidClasses(clHill, 4, clForest, 2, clPlayer, 20, clRock, 10)], scaleByMapSize(6,20), 100, [explorableArea] ); log("Creating small stone mines..."); group = new SimpleGroup([new SimpleObject(oStoneSmall, 2,5, 1,3)], true, clRock); createObjectGroupsByAreasDeprecated(group, 0, [avoidClasses(clHill, 4, clForest, 2, clPlayer, 20, clRock, 10)], scaleByMapSize(6,20), 100, [explorableArea] ); log("Creating metal mines..."); group = new SimpleGroup([new SimpleObject(oMetalSmall, 1,2, 0,4), new SimpleObject(oMetalLarge, 0,1, 0,4)], true, clMetal); createObjectGroupsByAreasDeprecated(group, 0, [avoidClasses(clHill, 4, clForest, 2, clPlayer, 20, clMetal, 10, clRock, 5)], scaleByMapSize(6,20), 100, [explorableArea] ); Engine.SetProgress(80); log("Creating wildlife..."); group = new SimpleGroup( [new SimpleObject(oDeer, 5,7, 0,4)], true, clFood ); createObjectGroupsByAreasDeprecated(group, 0, avoidClasses(clHill, 4, clForest, 0, clPlayer, 0, clBaseResource, 20), 3 * numPlayers, 100, [explorableArea] ); group = new SimpleGroup( [new SimpleObject(oBoar, 2,3, 0,5)], true, clFood ); createObjectGroupsByAreasDeprecated(group, 0, avoidClasses(clHill, 4, clForest, 0, clPlayer, 0, clBaseResource, 15), numPlayers, 50, [explorableArea] ); group = new SimpleGroup( [new SimpleObject(oBear, 1,1, 0,4)], false, clFood ); createObjectGroupsByAreasDeprecated(group, 0, avoidClasses(clHill, 4, clForest, 0, clPlayer, 20), scaleByMapSize(3, 12), 200, [explorableArea] ); Engine.SetProgress(85); log("Creating berry bush..."); group = new SimpleGroup( [new SimpleObject(oBerryBush, 5,7, 0,4)], true, clFood ); createObjectGroupsDeprecated(group, 0, avoidClasses(clForest, 0, clPlayer, 20, clHill, 4, clFood, 20), randIntInclusive(3, 12) * numPlayers + 2, 50 ); log("Creating decorative props..."); group = new SimpleGroup( [ new SimpleObject(aWoodA, 1,2, 0,1), new SimpleObject(aWoodB, 1,3, 0,1), new SimpleObject(aWheel, 0,2, 0,1), new SimpleObject(aWoodLarge, 0,1, 0,1), new SimpleObject(aBarrel, 0,2, 0,1) ], true ); createObjectGroupsByAreasDeprecated( group, 0, avoidClasses(clForest, 0), scaleByMapSize(5, 50), 50, [explorableArea] ); Engine.SetProgress(90); log("Creating straggler trees..."); var types = [oOak, oOakLarge, oPine, oAleppoPine]; var num = Math.floor(stragglerTrees / types.length); for (let type of types) createObjectGroupsByAreasDeprecated( new SimpleGroup([new SimpleObject(type, 1, 1, 0, 3)], true, clForest), 0, avoidClasses(clForest, 4, clHill, 5, clPlayer, 10, clBaseResource, 2, clMetal, 5, clRock, 5), num, 20, [explorableArea]); Engine.SetProgress(95); log("Creating grass tufts..."); group = new SimpleGroup( [new SimpleObject(aGrassLarge, 1,2, 0,1, -Math.PI / 8, Math.PI / 8)] ); createObjectGroupsByAreasDeprecated(group, 0, avoidClasses(clHill, 2, clPlayer, 2), scaleByMapSize(50, 300), 20, [explorableArea] ); placePlayersNomad(clPlayer, avoidClasses(clForest, 1, clMetal, 4, clRock, 4, clHill, 4, clFood, 2)); setTerrainAmbientColor(0.44,0.51,0.56); setUnitsAmbientColor(0.44,0.51,0.56); ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/belgian_uplands.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/belgian_uplands.js (revision 20982) +++ ps/trunk/binaries/data/mods/public/maps/random/belgian_uplands.js (revision 20983) @@ -1,412 +1,413 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("heightmap"); const tPrimary = ["temp_grass", "temp_grass_b", "temp_grass_c", "temp_grass_d", "temp_grass_long_b", "temp_grass_clovers_2", "temp_grass_mossy", "temp_grass_plants"]; const heightLand = 0; InitMap(heightLand, tPrimary); var numPlayers = getNumPlayers(); var mapSize = getMapSize(); var mapCenter = getMapCenter(); // Function to apply a heightmap function setReliefmap(reliefmap) { // g_Map.height = reliefmap; for (var x = 0; x <= mapSize; x++) for (var y = 0; y <= mapSize; y++) g_Map.setHeight(new Vector2D(x, y), reliefmap[x][y]); } // Set target min and max height depending on map size to make average stepness the same on all map sizes var heightRange = {"min": MIN_HEIGHT * mapSize / 8192, "max": MAX_HEIGHT * mapSize / 8192}; // Set average water coverage var averageWaterCoverage = 1/3; // NOTE: Since errosion is not predictable actual water coverage might differ much with the same value if (mapSize < 200) // Sink the waterlevel on tiny maps to ensure enough space averageWaterCoverage = 2/3 * averageWaterCoverage; var heightSeaGround = -MIN_HEIGHT + heightRange.min + averageWaterCoverage * (heightRange.max - heightRange.min); var heightSeaGroundAdjusted = heightSeaGround + MIN_HEIGHT; setWaterHeight(heightSeaGround); // Prepare terrain texture by height placement var textueByHeight = []; // Deep water textueByHeight.push({"upperHeightLimit": heightRange.min + 1/3 * (heightSeaGroundAdjusted - heightRange.min), "terrain": "temp_sea_rocks"}); // Medium deep water (with fish) var terrains = ["temp_sea_weed"]; terrains = terrains.concat(terrains, terrains, terrains, terrains); terrains = terrains.concat(terrains, terrains, terrains, terrains); terrains.push("temp_sea_weed|gaia/fauna_fish"); textueByHeight.push({"upperHeightLimit": heightRange.min + 2/3 * (heightSeaGroundAdjusted - heightRange.min), "terrain": terrains}); // Flat Water textueByHeight.push({"upperHeightLimit": heightRange.min + 3/3 * (heightSeaGroundAdjusted - heightRange.min), "terrain": "temp_mud_a"}); // Water surroundings/bog (with stone/metal some rabits and bushes) var terrains = ["temp_plants_bog", "temp_plants_bog_aut", "temp_dirt_gravel_plants", "temp_grass_d"]; terrains = terrains.concat(terrains, terrains, terrains, terrains, terrains); terrains = ["temp_plants_bog|gaia/flora_bush_temperate"].concat(terrains, terrains); terrains = ["temp_dirt_gravel_plants|gaia/geology_metal_temperate", "temp_dirt_gravel_plants|gaia/geology_stone_temperate", "temp_plants_bog|gaia/fauna_rabbit"].concat(terrains, terrains); terrains = ["temp_plants_bog_aut|gaia/flora_tree_dead"].concat(terrains, terrains); textueByHeight.push({"upperHeightLimit": heightSeaGroundAdjusted + 1/6 * (heightRange.max - heightSeaGroundAdjusted), "terrain": terrains}); // Juicy grass near bog textueByHeight.push({"upperHeightLimit": heightSeaGroundAdjusted + 2/6 * (heightRange.max - heightSeaGroundAdjusted), "terrain": ["temp_grass", "temp_grass_d", "temp_grass_long_b", "temp_grass_plants"]}); // Medium level grass // var testActor = "actor|geology/decal_stone_medit_a.xml"; textueByHeight.push({"upperHeightLimit": heightSeaGroundAdjusted + 3/6 * (heightRange.max - heightSeaGroundAdjusted), "terrain": ["temp_grass", "temp_grass_b", "temp_grass_c", "temp_grass_mossy"]}); // Long grass near forest border textueByHeight.push({"upperHeightLimit": heightSeaGroundAdjusted + 4/6 * (heightRange.max - heightSeaGroundAdjusted), "terrain": ["temp_grass", "temp_grass_b", "temp_grass_c", "temp_grass_d", "temp_grass_long_b", "temp_grass_clovers_2", "temp_grass_mossy", "temp_grass_plants"]}); // Forest border (With wood/food plants/deer/rabits) var terrains = ["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"]; var numTerrains = terrains.length; for (var i = 0; i < numTerrains; i++) terrains.push("temp_grass_plants"); textueByHeight.push({"upperHeightLimit": heightSeaGroundAdjusted + 5/6 * (heightRange.max - heightSeaGroundAdjusted), "terrain": terrains}); // Unpassable woods textueByHeight.push({"upperHeightLimit": heightSeaGroundAdjusted + 6/6 * (heightRange.max - heightSeaGroundAdjusted), "terrain": ["temp_grass_mossy|gaia/flora_tree_oak", "temp_forestfloor_pine|gaia/flora_tree_pine", "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"]}); var minTerrainDistToBorder = 3; Engine.SetProgress(5); // - Generate Heightmap // - Search valid start position tiles // - Choose a good start position derivation (largest distance between closest players) // - Restart the loop if start positions are invalid or two players are to close to each other var goodStartPositionsFound = false; var minDistBetweenPlayers = 16 + mapSize / 16; // Don't set this higher than 25 for tiny maps! It will take forever with 8 players! var enoughTiles = false; var tries = 0; var lowerHeightLimit = textueByHeight[3].upperHeightLimit; var upperHeightLimit = textueByHeight[6].upperHeightLimit; while (!goodStartPositionsFound) { tries++; log("Starting giant while loop try " + tries); // Generate reliefmap var myReliefmap = clone(g_Map.height); setRandomHeightmap(heightRange.min, heightRange.max, myReliefmap); for (var i = 0; i < 50 + mapSize/4; i++) // Cycles depend on mapsize (more cycles -> bigger structures) globalSmoothHeightmap(0.8, myReliefmap); rescaleHeightmap(heightRange.min, heightRange.max, myReliefmap); setReliefmap(myReliefmap); // Find good start position tiles var possibleStartPositions = []; var neededDistance = 7; var distToBorder = 2 * neededDistance; // Has to be greater than neededDistance! Otherwise the check if low/high ground is near will fail... // Check for valid points by height for (var x = distToBorder + minTerrainDistToBorder; x < mapSize - distToBorder - minTerrainDistToBorder; x++) for (var y = distToBorder + minTerrainDistToBorder; y < mapSize - distToBorder - minTerrainDistToBorder; y++) { - var actualHeight = getHeight(x, y); + let position = new Vector2D(x, y); + let actualHeight = g_Map.getHeight(position); if (actualHeight > lowerHeightLimit && actualHeight < upperHeightLimit) { // Check for points within a valid area by height (rectangular since faster) var isPossible = true; for (var offX = - neededDistance; offX <= neededDistance; offX++) for (var offY = - neededDistance; offY <= neededDistance; offY++) { - var testHeight = getHeight(x + offX, y + offY); + var testHeight = g_Map.getHeight(Vector2D.add(position, new Vector2D(offX, offY))); if (testHeight <= lowerHeightLimit || testHeight >= upperHeightLimit) { isPossible = false; break; } } if (isPossible) possibleStartPositions.push([x, y]); } } // Trying to reduce the number of possible start locations... // Reduce to tiles in a circle of mapSize / 2 distance to the center (to avoid players placed in corners) var possibleStartPositionsTemp = []; for (var i = 0; i < possibleStartPositions.length; i++) { if (Math.euclidDistance2D(...possibleStartPositions[i], mapCenter.x, mapCenter.y) < mapSize / 2) possibleStartPositionsTemp.push(possibleStartPositions[i]); } possibleStartPositions = clone(possibleStartPositionsTemp); // Reduce to tiles near low and high ground (Rectangular check since faster) to make sure each player has access to all resource types. var possibleStartPositionsTemp = []; var maxDistToResources = distToBorder; // Has to be <= distToBorder! var minNumLowTiles = 10; var minNumHighTiles = 10; for (var i = 0; i < possibleStartPositions.length; i++) { var numLowTiles = 0; var numHighTiles = 0; for (var dx = - maxDistToResources; dx < maxDistToResources; dx++) { for (var dy = - maxDistToResources; dy < maxDistToResources; dy++) { - var testHeight = getHeight(possibleStartPositions[i][0] + dx, possibleStartPositions[i][1] + dy); + var testHeight = g_Map.getHeight(new Vector2D(possibleStartPositions[i][0] + dx, possibleStartPositions[i][1] + dy)); if (testHeight < lowerHeightLimit) numLowTiles++; if (testHeight > upperHeightLimit) numHighTiles++; if (numLowTiles > minNumLowTiles && numHighTiles > minNumHighTiles) break; } if (numLowTiles > minNumLowTiles && numHighTiles > minNumHighTiles) break; } if (numLowTiles > minNumLowTiles && numHighTiles > minNumHighTiles) possibleStartPositionsTemp.push(possibleStartPositions[i]); } possibleStartPositions = clone(possibleStartPositionsTemp); if(possibleStartPositions.length > numPlayers) enoughTiles = true; else { enoughTiles = false; log("possibleStartPositions.length < numPlayers, possibleStartPositions.length = " + possibleStartPositions.length + ", numPlayers = " + numPlayers); } // Find a good start position derivation if (enoughTiles) { // Get some random start location derivations. NOTE: Iterating over all possible derivations is just too much (valid points * numPlayers) var maxTries = 100000; var possibleDerivations = []; for (var i = 0; i < maxTries; i++) { var vector = []; for (var p = 0; p < numPlayers; p++) vector.push(randIntExclusive(0, possibleStartPositions.length)); possibleDerivations.push(vector); } // Choose the start location derivation with the greatest minimum distance between players var maxMinDist = 0; for (var d = 0; d < possibleDerivations.length; d++) { var minDist = 2 * mapSize; for (var p1 = 0; p1 < numPlayers - 1; p1++) { for (var p2 = p1 + 1; p2 < numPlayers; p2++) { if (p1 != p2) { minDist = Math.min(minDist, Math.euclidDistance2D(...possibleStartPositions[possibleDerivations[d][p1]], ...possibleStartPositions[possibleDerivations[d][p2]])); if (minDist < maxMinDist) break; } } if (minDist < maxMinDist) break; } if (minDist > maxMinDist) { maxMinDist = minDist; var bestDerivation = possibleDerivations[d]; } } if (maxMinDist > minDistBetweenPlayers) { goodStartPositionsFound = true; log("Exiting giant while loop after " + tries + " tries with a minimum player distance of " + maxMinDist); } else log("maxMinDist <= " + minDistBetweenPlayers + ", maxMinDist = " + maxMinDist); } } Engine.SetProgress(60); log("Painting terrain by height and add props..."); var propDensity = 1; // 1 means as determined in the loop, less for large maps as set below if (mapSize > 500) propDensity = 1/4; else if (mapSize > 400) propDensity = 3/4; for(var x = minTerrainDistToBorder; x < mapSize - minTerrainDistToBorder; x++) { for (var y = minTerrainDistToBorder; y < mapSize - minTerrainDistToBorder; y++) { let position = new Vector2D(x, y); var textureMinHeight = heightRange.min; for (var i = 0; i < textueByHeight.length; i++) { - if (getHeight(x, y) >= textureMinHeight && getHeight(x, y) <= textueByHeight[i].upperHeightLimit) + if (g_Map.getHeight(position) >= textureMinHeight && g_Map.getHeight(position) <= textueByHeight[i].upperHeightLimit) { createTerrain(textueByHeight[i].terrain).place(position); // Add some props at... if (i == 0) // ...deep water { if (randBool(propDensity / 100)) placeObject(x, y, "actor|props/flora/pond_lillies_large.xml", 0, randomAngle()); else if (randBool(propDensity / 40)) placeObject(x, y, "actor|props/flora/water_lillies.xml", 0, randomAngle()); } if (i == 1) // ...medium water (with fish) { if (randBool(propDensity / 200)) placeObject(x, y, "actor|props/flora/pond_lillies_large.xml", 0, randomAngle()); else if (randBool(propDensity / 100)) placeObject(x, y, "actor|props/flora/water_lillies.xml", 0, randomAngle()); } if (i == 2) // ...low water/mud { if (randBool(propDensity / 200)) placeObject(x, y, "actor|props/flora/water_log.xml", 0, randomAngle()); else if (randBool(propDensity / 100)) placeObject(x, y, "actor|props/flora/water_lillies.xml", 0, randomAngle()); else if (randBool(propDensity / 40)) placeObject(x, y, "actor|geology/highland_c.xml", 0, randomAngle()); else if (randBool(propDensity / 20)) placeObject(x, y, "actor|props/flora/reeds_pond_lush_b.xml", 0, randomAngle()); else if (randBool(propDensity / 10)) placeObject(x, y, "actor|props/flora/reeds_pond_lush_a.xml", 0, randomAngle()); } if (i == 3) // ...water suroundings/bog { if (randBool(propDensity / 200)) placeObject(x, y, "actor|props/flora/water_log.xml", 0, randomAngle()); else if (randBool(propDensity / 100)) placeObject(x, y, "actor|geology/highland_c.xml", 0, randomAngle()); else if (randBool(propDensity / 40)) placeObject(x, y, "actor|props/flora/reeds_pond_lush_a.xml", 0, randomAngle()); } if (i == 4) // ...low height grass { if (randBool(propDensity / 800)) placeObject(x, y, "actor|props/flora/grass_field_flowering_tall.xml", 0, randomAngle()); else if (randBool(propDensity / 400)) placeObject(x, y, "actor|geology/gray_rock1.xml", 0, randomAngle()); else if (randBool(propDensity / 200)) placeObject(x, y, "actor|props/flora/bush_tempe_sm_lush.xml", 0, randomAngle()); else if (randBool(propDensity / 100)) placeObject(x, y, "actor|props/flora/bush_tempe_b.xml", 0, randomAngle()); else if (randBool(propDensity / 40)) placeObject(x, y, "actor|props/flora/grass_soft_small_tall.xml", 0, randomAngle()); } if (i == 5) // ...medium height grass { if (randBool(propDensity / 800)) placeObject(x, y, "actor|geology/decal_stone_medit_a.xml", 0, randomAngle()); else if (randBool(propDensity / 400)) placeObject(x, y, "actor|props/flora/decals_flowers_daisies.xml", 0, randomAngle()); else if (randBool(propDensity / 200)) placeObject(x, y, "actor|props/flora/bush_tempe_underbrush.xml", 0, randomAngle()); else if (randBool(propDensity / 100)) placeObject(x, y, "actor|props/flora/grass_soft_small_tall.xml", 0, randomAngle()); else if (randBool(propDensity / 40)) placeObject(x, y, "actor|props/flora/grass_temp_field.xml", 0, randomAngle()); } if (i == 6) // ...high height grass { if (randBool(propDensity / 400)) placeObject(x, y, "actor|geology/stone_granite_boulder.xml", 0, randomAngle()); else if (randBool(propDensity / 200)) placeObject(x, y, "actor|props/flora/foliagebush.xml", 0, randomAngle()); else if (randBool(propDensity / 100)) placeObject(x, y, "actor|props/flora/bush_tempe_underbrush.xml", 0, randomAngle()); else if (randBool(propDensity / 40)) placeObject(x, y, "actor|props/flora/grass_soft_small_tall.xml", 0, randomAngle()); else if (randBool(propDensity / 20)) placeObject(x, y, "actor|props/flora/ferns.xml", 0, randomAngle()); } if (i == 7) // ...forest border (with wood/food plants/deer/rabits) { if (randBool(propDensity / 400)) placeObject(x, y, "actor|geology/highland_c.xml", 0, randomAngle()); else if (randBool(propDensity / 200)) placeObject(x, y, "actor|props/flora/bush_tempe_a.xml", 0, randomAngle()); else if (randBool(propDensity / 100)) placeObject(x, y, "actor|props/flora/ferns.xml", 0, randomAngle()); else if (randBool(propDensity / 40)) placeObject(x, y, "actor|props/flora/grass_soft_tuft_a.xml", 0, randomAngle()); } if (i == 8) // ...woods { if (randBool(propDensity / 200)) placeObject(x, y, "actor|geology/highland2_moss.xml", 0, randomAngle()); else if (randBool(propDensity / 100)) placeObject(x, y, "actor|props/flora/grass_soft_tuft_a.xml", 0, randomAngle()); else if (randBool(propDensity / 40)) placeObject(x, y, "actor|props/flora/ferns.xml", 0, randomAngle()); } break; } else textureMinHeight = textueByHeight[i].upperHeightLimit; } } } Engine.SetProgress(90); if (isNomad()) placePlayersNomad(createTileClass(), new HeightConstraint(lowerHeightLimit, upperHeightLimit)); else { log("Placing players and starting resources..."); let playerIDs = sortAllPlayers(); let resourceDistance = 8; let resourceSpacing = 1; let resourceCount = 4; for (let i = 0; i < numPlayers; ++i) { let playerPos = new Vector2D(possibleStartPositions[bestDerivation[i]][0], possibleStartPositions[bestDerivation[i]][1]); placeCivDefaultStartingEntities(playerPos, playerIDs[i], false); for (let j = 1; j <= 4; ++j) { let uAngle = BUILDING_ORIENTATION - Math.PI * (2-j) / 2; for (let k = 0; k < resourceCount; ++k) { let pos = Vector2D.sum([ playerPos, new Vector2D(resourceDistance, 0).rotate(-uAngle), new Vector2D(k * resourceSpacing, 0).rotate(-uAngle - Math.PI/2), new Vector2D(-0.75 * resourceSpacing * Math.floor(resourceCount / 2), 0).rotate(-uAngle - Math.PI/2) ]); placeObject(pos.x, pos.y, j % 2 ? "gaia/flora_tree_cypress" : "gaia/flora_bush_berry", 0, randomAngle()); } } } } ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/corsica.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/corsica.js (revision 20982) +++ ps/trunk/binaries/data/mods/public/maps/random/corsica.js (revision 20983) @@ -1,519 +1,519 @@ Engine.LoadLibrary("rmgen"); var tGrass = ["medit_grass_field", "medit_grass_field_b", "temp_grass_c"]; var tLushGrass = ["medit_grass_field","medit_grass_field_a"]; var tSteepCliffs = ["temp_cliff_b", "temp_cliff_a"]; var tCliffs = ["temp_cliff_b", "medit_cliff_italia", "medit_cliff_italia_grass"]; var tHill = ["medit_cliff_italia_grass","medit_cliff_italia_grass", "medit_grass_field", "medit_grass_field", "temp_grass"]; var tMountain = ["medit_cliff_italia_grass","medit_cliff_italia"]; var tRoad = ["medit_city_tile","medit_rocks_grass","medit_grass_field_b"]; var tRoadWild = ["medit_rocks_grass","medit_grass_field_b"]; var tShoreBlend = ["medit_sand_wet","medit_rocks_wet"]; var tShore = ["medit_rocks","medit_sand","medit_sand"]; var tSandTransition = ["medit_sand","medit_rocks_grass","medit_rocks_grass","medit_rocks_grass"]; var tVeryDeepWater = ["medit_sea_depths","medit_sea_coral_deep"]; var tDeepWater = ["medit_sea_coral_deep","tropic_ocean_coral"]; var tCreekWater = "medit_sea_coral_plants"; var ePine = "gaia/flora_tree_aleppo_pine"; var ePalmTall = "gaia/flora_tree_cretan_date_palm_tall"; var eFanPalm = "gaia/flora_tree_medit_fan_palm"; var eApple = "gaia/flora_tree_apple"; var eBush = "gaia/flora_bush_berry"; var eFish = "gaia/fauna_fish"; var ePig = "gaia/fauna_pig"; var eStoneMine = "gaia/geology_stonemine_medit_quarry"; var eMetalMine = "gaia/geology_metal_mediterranean_slabs"; var aRock = "actor|geology/stone_granite_med.xml"; var aLargeRock = "actor|geology/stone_granite_large.xml"; var aBushA = "actor|props/flora/bush_medit_sm_lush.xml"; var aBushB = "actor|props/flora/bush_medit_me_lush.xml"; var aPlantA = "actor|props/flora/plant_medit_artichoke.xml"; var aPlantB = "actor|props/flora/grass_tufts_a.xml"; var aPlantC = "actor|props/flora/grass_soft_tuft_a.xml"; var aStandingStone = "actor|props/special/eyecandy/standing_stones.xml"; var heightSeaGround = -8; var heightCreeks = -5; var heightBeaches = -1; var heightMain = 5; var heightOffsetMainRelief = 30; var heightOffsetLevel1 = 9; var heightOffsetLevel2 = 8; var heightOffsetBumps = 2; var heightOffsetAntiBumps = -5; InitMap(heightSeaGround, tVeryDeepWater); var numPlayers = getNumPlayers(); var mapSize = getMapSize(); var mapCenter = getMapCenter(); var clIsland = createTileClass(); var clCreek = createTileClass(); var clWater = createTileClass(); var clCliffs = createTileClass(); var clForest = createTileClass(); var clShore = createTileClass(); var clPlayer = createTileClass(); var clBaseResource = createTileClass(); var clPassage = createTileClass(); var clSettlement = createTileClass(); var radiusBeach = fractionToTiles(0.57); var radiusCreeks = fractionToTiles(0.52); var radiusIsland = fractionToTiles(0.4); var radiusLevel1 = fractionToTiles(0.35); var radiusPlayer = fractionToTiles(0.25); var radiusLevel2 = fractionToTiles(0.2); var creeksArea = () => randBool() ? randFloat(10, 50) : scaleByMapSize(75, 100) + randFloat(0, 20); var nbCreeks = scaleByMapSize(6, 15); var nbSubIsland = 5; var nbBeaches = scaleByMapSize(2, 5); var nbPassagesLevel1 = scaleByMapSize(4, 8); var nbPassagesLevel2 = scaleByMapSize(2, 4); log("Creating Corsica and Sardinia..."); var swapAngle = randBool() ? Math.PI / 2 : 0; var islandLocations = [new Vector2D(0.05, 0.05), new Vector2D(0.95, 0.95)].map(v => v.mult(mapSize).rotateAround(-swapAngle, mapCenter)); for (let island = 0; island < 2; ++island) { log("Creating island area..."); createArea( new ClumpPlacer(diskArea(radiusIsland), 1, 0.5, 10, islandLocations[island]), [ new LayeredPainter([tCliffs, tGrass], [2]), new SmoothElevationPainter(ELEVATION_SET, heightMain, 0), paintClass(clIsland) ]); log("Creating subislands..."); for (let i = 0; i < nbSubIsland + 1; ++i) { let angle = Math.PI * (island + i / (nbSubIsland * 2)) + swapAngle; let location = Vector2D.add(islandLocations[island], new Vector2D(radiusIsland, 0).rotate(-angle)); createArea( new ClumpPlacer(diskArea(fractionToTiles(0.09)), 0.6, 0.03, 10, location), [ new LayeredPainter([tCliffs, tGrass], [2]), new SmoothElevationPainter(ELEVATION_SET, heightMain, 1), paintClass(clIsland) ]); } log("Creating creeks..."); for (let i = 0; i < nbCreeks + 1; ++i) { let angle = Math.PI * (island + i * (1 / (nbCreeks * 2))) + swapAngle; let location = Vector2D.add(islandLocations[island], new Vector2D(radiusCreeks, 0).rotate(-angle)); createArea( new ClumpPlacer(creeksArea(), 0.4, 0.01, 10, location), [ new TerrainPainter(tSteepCliffs), new SmoothElevationPainter(ELEVATION_SET, heightCreeks, 0), paintClass(clCreek) ]); } log("Creating beaches..."); for (let i = 0; i < nbBeaches + 1; ++i) { let angle = Math.PI * (island + (i / (nbBeaches * 2.5)) + 1 / (nbBeaches * 6) + randFloat(-1, 1) / (nbBeaches * 7)) + swapAngle; let start = Vector2D.add(islandLocations[island], new Vector2D(radiusIsland, 0).rotate(-angle)); let end = Vector2D.add(islandLocations[island], new Vector2D(radiusBeach, 0).rotate(-angle)); createArea( new ClumpPlacer(130, 0.7, 0.8, 10, Vector2D.add(start, Vector2D.mult(end, 3)).div(4)), new SmoothElevationPainter(ELEVATION_SET, heightBeaches, 5)); createPassage({ "start": start, "end": end, "startWidth": 18, "endWidth": 25, "smoothWidth": 4, "tileClass": clShore }); } log("Creating main relief..."); createArea( new ClumpPlacer(diskArea(radiusIsland), 1, 0.2, 10, islandLocations[island]), new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetMainRelief, fractionToTiles(0.45))); log("Creating first level plateau..."); createArea( new ClumpPlacer(diskArea(radiusLevel1), 0.95, 0.02, 10, islandLocations[island]), new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetLevel1, 1)); log("Creating first level passages..."); for (let i = 0; i <= nbPassagesLevel1; ++i) { let angle = Math.PI * (i / 7 + 1 / 9 + island) + swapAngle; createPassage({ "start": Vector2D.add(islandLocations[island], new Vector2D(radiusLevel1 + 10, 0).rotate(-angle)), "end": Vector2D.add(islandLocations[island], new Vector2D(radiusLevel1 - 4, 0).rotate(-angle)), "startWidth": 10, "endWidth": 6, "smoothWidth": 3, "tileClass": clPassage }); } if (mapSize > 150) { log("Creating second level plateau..."); createArea( new ClumpPlacer(diskArea(radiusLevel2), 0.98, 0.04, 10, islandLocations[island]), [ new LayeredPainter([tCliffs, tGrass], [2]), new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetLevel2, 1) ]); log("Creating second level passages..."); for (let i = 0; i < nbPassagesLevel2; ++i) { let angle = Math.PI * (i / (2 * nbPassagesLevel2) + 1 / (4 * nbPassagesLevel2) + island) + swapAngle; createPassage({ "start": Vector2D.add(islandLocations[island], new Vector2D(radiusLevel2 + 3, 0).rotate(-angle)), "end": Vector2D.add(islandLocations[island], new Vector2D(radiusLevel2 - 6, 0).rotate(-angle)), "startWidth": 4, "endWidth": 6, "smoothWidth": 2, "tileClass": clPassage }); } } } Engine.SetProgress(30); log("Determining player locations..."); var playerIDs = sortAllPlayers(); var playerPosition = []; var playerAngle = []; var p = 0; for (let island = 0; island < 2; ++island) { let playersPerIsland = island == 0 ? Math.ceil(numPlayers / 2) : Math.floor(numPlayers / 2); for (let i = 0; i < playersPerIsland; ++i) { playerAngle[p] = Math.PI * ((i + 0.5) / (2 * playersPerIsland) + island) + swapAngle; playerPosition[p] = Vector2D.add(islandLocations[island], new Vector2D(radiusPlayer).rotate(-playerAngle[p])); ++p; } } placePlayerBases({ "PlayerPlacement": [sortAllPlayers(), playerPosition], "PlayerTileClass": clPlayer, "BaseResourceClass": clBaseResource, "Walls": false, "CityPatch": { "outerTerrain": tRoadWild, "innerTerrain": tRoad, "coherence": 0.8, "radius": 6, "painters": [ paintClass(clSettlement) ] }, "Chicken": { }, "Berries": { "template": eBush }, "Mines": { "types": [ { "template": eMetalMine }, { "template": eStoneMine } ] } // Sufficient starting trees around, no decoratives }); Engine.SetProgress(40); log("Creating bumps..."); createAreas( new ClumpPlacer(70, 0.6, 0.1, 4), [new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetBumps, 3)], [ stayClasses(clIsland, 2), avoidClasses(clPlayer, 6, clPassage, 2) ], scaleByMapSize(20, 100), 5); log("Creating anti bumps..."); createAreas( new ClumpPlacer(120, 0.3, 0.1, 4), new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetAntiBumps, 6), avoidClasses(clPlayer, 6, clPassage, 2, clIsland, 2), scaleByMapSize(20, 100), 5); log("Painting water..."); paintTileClassBasedOnHeight(-Infinity, 0, Elevation_ExcludeMin_ExcludeMax, clWater); log("Painting land..."); for (let mapX = 0; mapX < mapSize; ++mapX) for (let mapZ = 0; mapZ < mapSize; ++mapZ) { let position = new Vector2D(mapX, mapZ); let terrain = getCosricaSardiniaTerrain(mapX, mapZ); if (!terrain) continue; createTerrain(terrain).place(position); if (terrain == tCliffs || terrain == tSteepCliffs) addToClass(mapX, mapZ, clCliffs); } function getCosricaSardiniaTerrain(mapX, mapZ) { let position = new Vector2D(mapX, mapZ); let isWater = getTileClass(clWater).countMembersInRadius(mapX, mapZ, 3); let isShore = getTileClass(clShore).countMembersInRadius(mapX, mapZ, 2); let isPassage = getTileClass(clPassage).countMembersInRadius(mapX, mapZ, 2); let isSettlement = getTileClass(clSettlement).countMembersInRadius(mapX, mapZ, 2); if (isSettlement) return undefined; - let height = getHeight(mapX, mapZ); + let height = g_Map.getHeight(position); let slope = g_Map.getSlope(position); if (height >= 0.5 && height < 1.5 && isShore) return tSandTransition; // Paint land cliffs and grass if (height >= 1 && !isWater) { if (isPassage) return tGrass; if (slope >= 1.25) return height > 25 ? tSteepCliffs : tCliffs; if (height < 17) return tGrass; if (slope < 0.625) return tHill; return tMountain; } if (slope >= 1.125) return tCliffs; if (height >= 1.5) return undefined; if (height >= -0.75) return tShore; if (height >= -3) return tShoreBlend; if (height >= -6) return tCreekWater; if (height > -10 && slope < 0.75) return tDeepWater; return undefined; } Engine.SetProgress(65); log("Creating mines..."); for (let mine of [eMetalMine, eStoneMine]) createObjectGroupsDeprecated( new SimpleGroup( [ new SimpleObject(mine, 1,1, 0,0), new SimpleObject(aBushB, 1,1, 2,2), new SimpleObject(aBushA, 0,2, 1,3) ], true, clBaseResource), 0, [ stayClasses(clIsland, 1), avoidClasses( clWater, 3, clPlayer, 6, clBaseResource, 4, clPassage, 2, clCliffs, 1) ], scaleByMapSize(6, 25), 1000); log("Creating grass patches..."); createAreas( new ClumpPlacer(20, 0.3, 0.06, 0.5), [ new TerrainPainter(tLushGrass), paintClass(clForest) ], avoidClasses( clWater, 1, clPlayer, 6, clBaseResource, 3, clCliffs, 1), scaleByMapSize(10, 40)); log("Creating forests..."); createObjectGroupsDeprecated( new SimpleGroup( [ new SimpleObject(ePine, 3, 6, 1, 3), new SimpleObject(ePalmTall, 1, 3, 1, 3), new SimpleObject(eFanPalm, 0, 2, 0, 2), new SimpleObject(eApple, 0, 1, 1, 2) ], true, clForest), 0, [ stayClasses(clIsland, 3), avoidClasses( clWater, 1, clForest, 0, clPlayer, 3, clBaseResource, 4, clPassage, 2, clCliffs, 2) ], scaleByMapSize(350, 2500), 100); Engine.SetProgress(75); log("Creating small decorative rocks..."); createObjectGroupsDeprecated( new SimpleGroup( [ new SimpleObject(aRock, 1, 3, 0, 1), new SimpleObject(aStandingStone, 0, 2, 0, 3) ], true), 0, avoidClasses( clWater, 0, clForest, 0, clPlayer, 6, clBaseResource, 4, clPassage, 2), scaleByMapSize(16, 262), 50); log("Creating large decorative rocks..."); var rocksGroup = new SimpleGroup( [ new SimpleObject(aLargeRock, 1, 2, 0, 1), new SimpleObject(aRock, 1, 3, 0, 2) ], true); createObjectGroupsDeprecated( rocksGroup, 0, avoidClasses( clWater, 0, clForest, 0, clPlayer, 6, clBaseResource, 4, clPassage, 2), scaleByMapSize(8, 131), 50); createObjectGroupsDeprecated( rocksGroup, 0, borderClasses(clWater, 5, 10), scaleByMapSize(100, 800), 500); log("Creating decorative plants..."); var plantGroups = [ new SimpleGroup( [ new SimpleObject(aPlantA, 3, 7, 0, 3), new SimpleObject(aPlantB, 3,6, 0, 3), new SimpleObject(aPlantC, 1,4, 0, 4) ], true), new SimpleGroup( [ new SimpleObject(aPlantB, 5, 20, 0, 5), new SimpleObject(aPlantC, 4,10, 0,4) ], true) ]; for (let group of plantGroups) createObjectGroupsDeprecated( group, 0, avoidClasses( clWater, 0, clBaseResource, 4, clShore, 3), scaleByMapSize(100, 600), 50); Engine.SetProgress(80); log("Creating animals..."); createObjectGroupsDeprecated( new SimpleGroup([new SimpleObject(ePig, 2,4, 0,3)]), 0, avoidClasses( clWater, 3, clBaseResource, 4, clPlayer, 6), scaleByMapSize(20, 100), 50); log("Creating fish..."); createObjectGroupsDeprecated( new SimpleGroup([new SimpleObject(eFish, 1,2, 0,3)]), 0, [ stayClasses(clWater, 3), avoidClasses(clCreek, 3, clShore, 3) ], scaleByMapSize(50, 150), 100); Engine.SetProgress(95); placePlayersNomad(clPlayer, avoidClasses(clWater, 4, clForest, 1, clBaseResource, 4, clCliffs, 4)); setSkySet(pickRandom(["cumulus", "sunny"])); setSunColor(0.8, 0.66, 0.48); setSunElevation(0.828932); setSunRotation((swapAngle ? 0.288 : 0.788) * Math.PI); setTerrainAmbientColor(0.564706,0.543726,0.419608); setUnitsAmbientColor(0.53,0.55,0.45); setWaterColor(0.2,0.294,0.49); setWaterTint(0.208, 0.659, 0.925); setWaterMurkiness(0.72); setWaterWaviness(2.0); setWaterType("ocean"); ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/deep_forest.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/deep_forest.js (revision 20982) +++ ps/trunk/binaries/data/mods/public/maps/random/deep_forest.js (revision 20983) @@ -1,202 +1,202 @@ Engine.LoadLibrary("rmgen"); 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 terrainPrimary = ["temp_grass", "temp_grass_b", "temp_grass_c", "temp_grass_d", "temp_grass_long_b", "temp_grass_clovers_2", "temp_grass_mossy", "temp_grass_plants"]; 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"]; 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 heightPath = -2; var heightLand = 0; var heightOffsetRandomPath = 1; InitMap(heightLand, terrainPrimary); var mapSize = getMapSize(); var mapRadius = mapSize/2; var mapCenter = getMapCenter(); var clPlayer = createTileClass(); var clPath = createTileClass(); var clHill = createTileClass(); var clForest = createTileClass(); var clBaseResource = createTileClass(); 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 playerPosition = []; var playerAngle = []; var playerAngleStart = randomAngle(); var playerAngleAddAvrg = 2 * Math.PI / numPlayers; var playerAngleMaxOff = playerAngleAddAvrg/4; var radiusEC = Math.max(mapRadius/8, baseRadius/2); var resourceRadius = fractionToTiles(1/3); var resourcePerPlayer = [templateStone, templateMetalMine]; // 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) / Math.square(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 var playerIDs = sortAllPlayers(); for (var i=0; i < numPlayers; i++) { playerAngle[i] = (playerAngleStart + i * playerAngleAddAvrg + randFloat(0, playerAngleMaxOff)) % (2 * Math.PI); playerPosition[i] = Vector2D.add(mapCenter, new Vector2D(randFloat(minPlayerRadius, maxPlayerRadius), 0).rotate(-playerAngle[i]).round()); } Engine.SetProgress(10); placePlayerBases({ "PlayerPlacement": [playerIDs, playerPosition], "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": { }, "Berries": { "template": "gaia/flora_bush_grapes", "minCount": 2, "maxCount": 2, "distance": 12, "minDist": 5, "maxDist": 8 }, "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); 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) { let pathStart = i < numPlayers ? playerPosition[i] : mapCenter; let pathEnd = j < numPlayers ? playerPosition[j] : mapCenter; createArea( new RandomPathPlacer(pathStart, pathEnd, 1.25, baseRadius / 2, pathBlending), [ new TerrainPainter(terrainPath), new SmoothElevationPainter(ELEVATION_SET, heightPath, 2, heightOffsetRandomPath), paintClass(clPath) ], avoidClasses(clHill, 0, clBaseResource, 4)); } Engine.SetProgress(50); log("Placing expansion resources..."); for (let i = 0; i < numPlayers; ++i) for (let rIndex = 0; rIndex < resourcePerPlayer.length; ++rIndex) { 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(position.x, position.y, resourcePerPlayer[rIndex], 0, randomAngle()); createArea( new ClumpPlacer(40, 1/2, 1/8, 1, position), [ new LayeredPainter([terrainHillBorder, terrainHill], [1]), new ElevationPainter(randFloat(1, 2)), paintClass(clHill) ]); } Engine.SetProgress(60); 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, mapCenter), [ 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++) { let position = new Vector2D(x, z); // The 0.5 is a correction for the entities placed on the center of tiles var radius = mapCenter.distanceTo(Vector2D.add(position, new Vector2D(0.5, 0.5))); var minDistToSL = mapSize; for (var i=0; i < numPlayers; i++) minDistToSL = Math.min(minDistToSL, position.distanceTo(playerPosition[i])); // 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 RectPlacer(position.x, position.y, position.x + 1, position.y + 1), [ 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)); - g_Map.setHeight(position, getHeight(x, z) + hVarMiddleHill + hVarHills + 1); + g_Map.setHeight(position, g_Map.getHeight(position) + hVarMiddleHill + hVarHills + 1); } Engine.SetProgress(95); placePlayersNomad(clPlayer, avoidClasses(clForest, 1, clBaseResource, 4)); ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/latium.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/latium.js (revision 20982) +++ ps/trunk/binaries/data/mods/public/maps/random/latium.js (revision 20983) @@ -1,480 +1,480 @@ Engine.LoadLibrary("rmgen"); const tOceanDepths = "medit_sea_depths"; const tOceanRockDeep = "medit_sea_coral_deep"; const tOceanRockShallow = "medit_rocks_wet"; const tOceanCoral = "medit_sea_coral_plants"; const tBeachWet = "medit_sand_wet"; const tBeachDry = "medit_sand"; const tBeachGrass = "medit_rocks_grass"; const tBeachCliff = "medit_dirt"; const tCity = "medit_city_tile"; const tGrassDry = ["medit_grass_field_brown", "medit_grass_field_dry", "medit_grass_field_b"]; const tGrass = ["medit_grass_field_dry", "medit_grass_field_brown", "medit_grass_field_b"]; const tGrassShrubs = ["medit_grass_shrubs", "medit_grass_flowers"]; const tGrassRock = ["medit_rocks_grass"]; const tDirt = "medit_dirt"; const tDirtCliff = "medit_cliff_italia"; const tGrassCliff = "medit_cliff_italia_grass"; const tCliff = ["medit_cliff_italia", "medit_cliff_italia", "medit_cliff_italia_grass"]; const tForestFloor = "medit_grass_wild"; const oBeech = "gaia/flora_tree_euro_beech"; const oBerryBush = "gaia/flora_bush_berry"; const oCarob = "gaia/flora_tree_carob"; const oCypress1 = "gaia/flora_tree_cypress"; const oCypress2 = "gaia/flora_tree_cypress"; const oLombardyPoplar = "gaia/flora_tree_poplar_lombardy"; const oPalm = "gaia/flora_tree_medit_fan_palm"; const oPine = "gaia/flora_tree_aleppo_pine"; const oDeer = "gaia/fauna_deer"; const oFish = "gaia/fauna_fish"; const oSheep = "gaia/fauna_sheep"; const oStoneLarge = "gaia/geology_stonemine_medit_quarry"; const oStoneSmall = "gaia/geology_stone_mediterranean"; const oMetalLarge = "gaia/geology_metal_mediterranean_slabs"; const aBushMedDry = "actor|props/flora/bush_medit_me_dry.xml"; const aBushMed = "actor|props/flora/bush_medit_me.xml"; const aBushSmall = "actor|props/flora/bush_medit_sm.xml"; const aBushSmallDry = "actor|props/flora/bush_medit_sm_dry.xml"; const aGrass = "actor|props/flora/grass_soft_large_tall.xml"; const aGrassDry = "actor|props/flora/grass_soft_dry_large_tall.xml"; const aRockLarge = "actor|geology/stone_granite_large.xml"; const aRockMed = "actor|geology/stone_granite_med.xml"; const aRockSmall = "actor|geology/stone_granite_small.xml"; const pPalmForest = [tForestFloor+TERRAIN_SEPARATOR+oPalm, tGrass]; const pPineForest = [tForestFloor+TERRAIN_SEPARATOR+oPine, tGrass]; const pPoplarForest = [tForestFloor+TERRAIN_SEPARATOR+oLombardyPoplar, tGrass]; const pMainForest = [tForestFloor+TERRAIN_SEPARATOR+oCarob, tForestFloor+TERRAIN_SEPARATOR+oBeech, tGrass, tGrass]; const heightSeaGround = -16; const heightLand = 0; const heightPlayer = 5; const heightHill = 12; InitMap(heightLand, tGrass); const numPlayers = getNumPlayers(); const mapSize = getMapSize(); const mapCenter = getMapCenter(); const mapBounds = getMapBounds(); var clWater = createTileClass(); var clCliff = createTileClass(); var clForest = createTileClass(); var clMetal = createTileClass(); var clRock = createTileClass(); var clFood = createTileClass(); var clPlayer = createTileClass(); var clBaseResource = createTileClass(); var WATER_WIDTH = 0.1; var horizontal = randBool(); log("Creating players..."); var startAngle = randBool() ? 0 : Math.PI / 2; var playerPosition = playerPlacementLine(startAngle + Math.PI / 2, mapCenter, fractionToTiles(randFloat(0.42, 0.46))); function distanceToPlayers(x, z) { let r = Infinity; for (let i = 0; i < numPlayers; ++i) { var dx = x - playerPosition[i].x; var dz = z - playerPosition[i].y; r = Math.min(r, Math.square(dx) + Math.square(dz)); } return Math.sqrt(r); } function playerNearness(x, z) { var d = fractionToTiles(distanceToPlayers(x,z)); if (d < 13) return 0; if (d < 19) return (d-13)/(19-13); return 1; } for (let x of [mapBounds.left, mapBounds.right]) paintRiver({ "parallel": true, "start": new Vector2D(x, mapBounds.top).rotateAround(startAngle, mapCenter), "end": new Vector2D(x, mapBounds.bottom).rotateAround(startAngle, mapCenter), "width": 2 * fractionToTiles(WATER_WIDTH), "fadeDist": 16, "deviation": 0, "heightRiverbed": heightSeaGround, "heightLand": heightLand, "meanderShort": 0, "meanderLong": 0, "waterFunc": (position, height, z) => { addToClass(position.x, position.y, clWater); } }); Engine.SetProgress(10); log("Painting elevation..."); var noise0 = new Noise2D(scaleByMapSize(4, 16)); var noise1 = new Noise2D(scaleByMapSize(8, 32)); var noise2 = new Noise2D(scaleByMapSize(15, 60)); var noise2a = new Noise2D(scaleByMapSize(20, 80)); var noise2b = new Noise2D(scaleByMapSize(35, 140)); var noise3 = new Noise2D(scaleByMapSize(4, 16)); var noise4 = new Noise2D(scaleByMapSize(6, 24)); var noise5 = new Noise2D(scaleByMapSize(11, 44)); for (var ix = 0; ix <= mapSize; ix++) for (var iz = 0; iz <= mapSize; iz++) { let position = new Vector2D(ix, iz); var x = ix / (mapSize + 1.0); var z = iz / (mapSize + 1.0); var pn = playerNearness(x, z); let c = startAngle ? z : x; let distToWater = stayClasses(clWater, 1).allows(position) ? 0 : (0.5 - WATER_WIDTH - Math.abs(c - 0.5)); - let h = distToWater ? heightHill * (1 - Math.abs(c - 0.5) / (0.5 - WATER_WIDTH)) : getHeight(ix, iz); + let h = distToWater ? heightHill * (1 - Math.abs(c - 0.5) / (0.5 - WATER_WIDTH)) : g_Map.getHeight(position); // add some base noise var baseNoise = 16*noise0.get(x,z) + 8*noise1.get(x,z) + 4*noise2.get(x,z) - (16+8+4)/2; if ( baseNoise < 0 ) { baseNoise *= pn; baseNoise *= Math.max(0.1, distToWater / (0.5 - WATER_WIDTH)); } var oldH = h; h += baseNoise; // add some higher-frequency noise on land if ( oldH > 0 ) h += (0.4 * noise2a.get(x,z) + 0.2 * noise2b.get(x,z)) * Math.min(oldH / 10, 1); // create cliff noise if ( h > -10 ) { var cliffNoise = (noise3.get(x,z) + 0.5*noise4.get(x,z)) / 1.5; if (h < 1) { var u = 1 - 0.3*((h-1)/-10); cliffNoise *= u; } cliffNoise += 0.05 * distToWater / (0.5 - WATER_WIDTH); if (cliffNoise > 0.6) { var u = 0.8 * (cliffNoise - 0.6); cliffNoise += u * noise5.get(x,z); cliffNoise /= (1 + u); } cliffNoise -= 0.59; cliffNoise *= pn; if (cliffNoise > 0) h += 19 * Math.min(cliffNoise, 0.045) / 0.045; } g_Map.setHeight(position, h); } Engine.SetProgress(20); log("Painting terrain..."); var noise6 = new Noise2D(scaleByMapSize(10, 40)); var noise7 = new Noise2D(scaleByMapSize(20, 80)); var noise8 = new Noise2D(scaleByMapSize(13, 52)); var noise9 = new Noise2D(scaleByMapSize(26, 104)); var noise10 = new Noise2D(scaleByMapSize(50, 200)); for (var ix = 0; ix < mapSize; ix++) for (var iz = 0; iz < mapSize; iz++) { let position = new Vector2D(ix, iz); var x = ix / (mapSize + 1.0); var z = iz / (mapSize + 1.0); var pn = playerNearness(x, z); // get heights of surrounding vertices - var h00 = getHeight(ix, iz); - var h01 = getHeight(ix, iz+1); - var h10 = getHeight(ix+1, iz); - var h11 = getHeight(ix+1, iz+1); + var h00 = g_Map.getHeight(Vector2D.add(position, new Vector2D(0, 0)); + var h01 = g_Map.getHeight(Vector2D.add(position, new Vector2D(0, 1)); + var h10 = g_Map.getHeight(Vector2D.add(position, new Vector2D(1, 0)); + var h11 = g_Map.getHeight(Vector2D.add(position, new Vector2D(1, 1)); // find min and max height var maxH = Math.max(h00, h01, h10, h11); var minH = Math.min(h00, h01, h10, h11); var diffH = maxH - minH; // figure out if we're at the top of a cliff using min adjacent height var minAdjHeight = minH; if (maxH > 15) { var maxNx = Math.min(ix + 2, mapSize); var maxNz = Math.min(iz + 2, mapSize); for (let nx = Math.max(ix - 1, 0); nx <= maxNx; ++nx) for (let nz = Math.max(iz - 1, 0); nz <= maxNz; ++nz) - minAdjHeight = Math.min(minAdjHeight, getHeight(nx, nz)); + minAdjHeight = Math.min(minAdjHeight, getHeight(new Vector2D(nx, nz))); } // choose a terrain based on elevation var t = tGrass; // water if (maxH < -12) t = tOceanDepths; else if (maxH < -8.8) t = tOceanRockDeep; else if (maxH < -4.7) t = tOceanCoral; else if (maxH < -2.8) t = tOceanRockShallow; else if (maxH < 0.9 && minH < 0.35) t = tBeachWet; else if (maxH < 1.5 && minH < 0.9) t = tBeachDry; else if (maxH < 2.3 && minH < 1.3) t = tBeachGrass; if (minH < 0) addToClass(ix, iz, clWater); // cliffs if (diffH > 2.9 && minH > -7) { t = tCliff; addToClass(ix, iz, clCliff); } else if (diffH > 2.5 && minH > -5 || maxH - minAdjHeight > 2.9 && minH > 0) { if (minH < -1) t = tCliff; else if (minH < 0.5) t = tBeachCliff; else t = [tDirtCliff, tGrassCliff, tGrassCliff, tGrassRock, tCliff]; addToClass(ix, iz, clCliff); } // Don't place resources onto potentially impassable mountains if (minH >= 20) addToClass(ix, iz, clCliff); // forests - if (getHeight(ix, iz) < 11 && diffH < 2 && minH > 1) + if (g_Map.getHeight(position) < 11 && diffH < 2 && minH > 1) { var forestNoise = (noise6.get(x,z) + 0.5*noise7.get(x,z)) / 1.5 * pn - 0.59; // Thin out trees a bit if (forestNoise > 0 && randBool()) { if (minH < 11 && minH >= 4) { var typeNoise = noise10.get(x,z); if (typeNoise < 0.43 && forestNoise < 0.05) t = pPoplarForest; else if (typeNoise < 0.63) t = pMainForest; else t = pPineForest; addToClass(ix, iz, clForest); } else if (minH < 4) { t = pPalmForest; addToClass(ix, iz, clForest); } } } // grass variations if (t == tGrass) { var grassNoise = (noise8.get(x,z) + 0.6*noise9.get(x,z)) / 1.6; if (grassNoise < 0.3) t = (diffH > 1.2) ? tDirtCliff : tDirt; else if (grassNoise < 0.34) { t = (diffH > 1.2) ? tGrassCliff : tGrassDry; if (diffH < 0.5 && randBool(0.02)) placeObject(randFloat(ix, ix + 1), randFloat(iz, iz + 1), aGrassDry, 0, randomAngle()); } else if (grassNoise > 0.61) { t = (diffH > 1.2 ? tGrassRock : tGrassShrubs); } else if (diffH < 0.5 && randBool(0.02)) placeObject(randFloat(ix, ix + 1), randFloat(iz, iz + 1), aGrass, 0, randomAngle()); } createTerrain(t).place(position); } Engine.SetProgress(30); placePlayerBases({ "PlayerPlacement": [sortAllPlayers(), playerPosition], "PlayerTileClass": clPlayer, "BaseResourceClass": clBaseResource, "baseResourceConstraint": avoidClasses(clCliff, 4), "CityPatch": { "radius": 11, "outerTerrain": tGrass, "innerTerrain": tCity, "width": 4, "painters": [ new SmoothElevationPainter(ELEVATION_SET, heightPlayer, 2) ] }, "Chicken": { }, "Berries": { "template": oBerryBush, "distance": 9 }, "Mines": { "types": [ { "template": oMetalLarge }, { "template": oStoneLarge } ] }, "Trees": { "template": oPalm, "count": 5, "minDist": 10, "maxDist": 11 } // No decoratives }); Engine.SetProgress(40); log("Creating bushes..."); var group = new SimpleGroup( [new SimpleObject(aBushSmall, 0,2, 0,2), new SimpleObject(aBushSmallDry, 0,2, 0,2), new SimpleObject(aBushMed, 0,1, 0,2), new SimpleObject(aBushMedDry, 0,1, 0,2)] ); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 4, clCliff, 2), scaleByMapSize(9, 146), 50 ); Engine.SetProgress(45); log("Creating rocks..."); group = new SimpleGroup( [new SimpleObject(aRockSmall, 0,3, 0,2), new SimpleObject(aRockMed, 0,2, 0,2), new SimpleObject(aRockLarge, 0,1, 0,2)] ); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 2, clCliff, 1), scaleByMapSize(9, 146), 50 ); Engine.SetProgress(50); log("Creating large stone mines..."); group = new SimpleGroup([new SimpleObject(oStoneSmall, 0,2, 0,4), new SimpleObject(oStoneLarge, 1,1, 0,4)], true, clRock); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 1, clForest, 4, clPlayer, 40, clRock, 60, clMetal, 10, clCliff, 3), scaleByMapSize(4,16), 100 ); log("Creating small stone mines..."); group = new SimpleGroup([new SimpleObject(oStoneSmall, 2,5, 1,3)], true, clRock); createObjectGroups(group, 0, avoidClasses(clForest, 4, clWater, 1, clPlayer, 40, clRock, 30, clMetal, 10, clCliff, 3), scaleByMapSize(4,16), 100 ); log("Creating metal mines..."); group = new SimpleGroup([new SimpleObject(oMetalLarge, 1,1, 0,2)], true, clMetal); createObjectGroups(group, 0, avoidClasses(clForest, 4, clWater, 1, clPlayer, 40, clMetal, 50, clCliff, 3), scaleByMapSize(4,16), 100 ); Engine.SetProgress(60); createStragglerTrees( [oCarob, oBeech, oLombardyPoplar, oLombardyPoplar, oPine], avoidClasses(clWater, 5, clCliff, 4, clForest, 2, clPlayer, 15, clMetal, 6, clRock, 6), clForest, scaleByMapSize(10, 190)); Engine.SetProgress(70); log("Creating straggler cypresses..."); group = new SimpleGroup( [new SimpleObject(oCypress2, 1,3, 0,3), new SimpleObject(oCypress1, 0,2, 0,2)], true ); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 5, clCliff, 4, clForest, 2, clPlayer, 15, clMetal, 6, clRock, 6), scaleByMapSize(5, 75), 50 ); Engine.SetProgress(80); log("Creating sheep..."); group = new SimpleGroup([new SimpleObject(oSheep, 2,4, 0,2)], true, clFood); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 5, clForest, 2, clCliff, 1, clPlayer, 20, clMetal, 6, clRock, 6, clFood, 8), 3 * numPlayers, 50 ); Engine.SetProgress(85); log("Creating fish..."); var num = scaleByMapSize(4, 16); var offsetX = mapSize * WATER_WIDTH/2; for (let i = 0; i < num; ++i) createObjectGroup( new SimpleGroup( [new SimpleObject(oFish, 1, 1, 0, 1)], true, clFood, randIntInclusive(offsetX / 2, offsetX * 3/2), Math.round((i + 0.5) * mapSize / num)), 0); for (let i = 0; i < num; ++i) createObjectGroup( new SimpleGroup( [new SimpleObject(oFish, 1, 1, 0, 1)], true, clFood, randIntInclusive(mapSize - offsetX * 3/2, mapSize - offsetX / 2), Math.round((i + 0.5) * mapSize / num)), 0); Engine.SetProgress(90); log("Creating deer..."); group = new SimpleGroup( [new SimpleObject(oDeer, 5,7, 0,4)], true, clFood ); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 5, clForest, 2, clCliff, 1, clPlayer, 20, clMetal, 6, clRock, 6, clFood, 8), 3 * numPlayers, 50 ); Engine.SetProgress(95); log("Creating berry bushes..."); group = new SimpleGroup([new SimpleObject(oBerryBush, 5,7, 0,3)], true, clFood); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 5, clForest, 2, clCliff, 1, clPlayer, 20, clMetal, 6, clRock, 6, clFood, 8), 1.5 * numPlayers, 100 ); placePlayersNomad(clPlayer, avoidClasses(clWater, 4, clCliff, 2, clForest, 1, clMetal, 4, clRock, 4, clFood, 2)); setSkySet("sunny"); setWaterColor(0.024,0.262,0.224); setWaterTint(0.133, 0.325,0.255); setWaterWaviness(2.5); setWaterType("ocean"); setWaterMurkiness(0.8); ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/oasis.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/oasis.js (revision 20982) +++ ps/trunk/binaries/data/mods/public/maps/random/oasis.js (revision 20983) @@ -1,333 +1,341 @@ Engine.LoadLibrary("rmgen"); const tSand = ["desert_sand_dunes_100", "desert_dirt_cracks","desert_sand_smooth", "desert_dirt_rough", "desert_dirt_rough_2", "desert_sand_smooth"]; const tDune = ["desert_sand_dunes_50"]; const tForestFloor = "desert_forestfloor_palms"; const tDirt = ["desert_dirt_rough","desert_dirt_rough","desert_dirt_rough", "desert_dirt_rough_2", "desert_dirt_rocks_2"]; const tRoad = "desert_city_tile";; const tRoadWild = "desert_city_tile";; const tShore = "dirta"; const tWater = "desert_sand_wet"; const ePalmShort = "gaia/flora_tree_cretan_date_palm_short"; const ePalmTall = "gaia/flora_tree_cretan_date_palm_tall"; const eBush = "gaia/flora_bush_grapes"; const eCamel = "gaia/fauna_camel"; const eGazelle = "gaia/fauna_gazelle"; const eLion = "gaia/fauna_lion"; const eLioness = "gaia/fauna_lioness"; const eStoneMine = "gaia/geology_stonemine_desert_quarry"; const eMetalMine = "gaia/geology_metal_desert_slabs"; const aFlower1 = "actor|props/flora/decals_flowers_daisies.xml"; const aWaterFlower = "actor|props/flora/water_lillies.xml"; const aReedsA = "actor|props/flora/reeds_pond_lush_a.xml"; const aReedsB = "actor|props/flora/reeds_pond_lush_b.xml"; const aRock = "actor|geology/stone_desert_med.xml"; const aBushA = "actor|props/flora/bush_desert_dry_a.xml"; const aBushB = "actor|props/flora/bush_desert_dry_a.xml"; const aSand = "actor|particle/blowing_sand.xml"; const pForestMain = [tForestFloor + TERRAIN_SEPARATOR + ePalmShort, tForestFloor + TERRAIN_SEPARATOR + ePalmTall, tForestFloor]; const pOasisForestLight = [tForestFloor + TERRAIN_SEPARATOR + ePalmShort, tForestFloor + TERRAIN_SEPARATOR + ePalmTall, tForestFloor,tForestFloor,tForestFloor ,tForestFloor,tForestFloor,tForestFloor,tForestFloor]; const heightSeaGround = -3; +const heightFloraMin = -2.5 +const heightFloraReedsMax = -1.9; +const heightFloraMax = -1; const heightLand = 1; +const heightSand = 3.4; const heightOasisPath = 4; const heightOffsetBump = 4; const heightOffsetDune = 18; InitMap(heightLand, tSand); const numPlayers = getNumPlayers(); const mapSize = getMapSize(); const mapCenter = getMapCenter(); var clPlayer = createTileClass(); var clHill = createTileClass(); var clForest = createTileClass(); var clOasis = createTileClass(); var clPassage = createTileClass(); var clRock = createTileClass(); var clMetal = createTileClass(); var clFood = createTileClass(); var clBaseResource = createTileClass(); var waterRadius = scaleByMapSize(7, 50) var shoreDistance = scaleByMapSize(4, 10); var forestDistance = scaleByMapSize(6, 20); var [playerIDs, playerPosition] = playerPlacementCircle(fractionToTiles(0.35)); log("Creating small oasis near the players...") var forestDist = 1.2 * defaultPlayerBaseRadius(); for (let i = 0; i < numPlayers; ++i) { // Create starting batches of wood let forestPosition; let forestAngle; do { forestAngle = Math.PI / 3 * randFloat(1, 2); forestPosition = Vector2D.add(playerPosition[i], new Vector2D(forestDist, 0).rotate(-forestAngle)); } while ( !createArea( new ClumpPlacer(70, 1, 0.5, 10, forestPosition), [ new LayeredPainter([tForestFloor, pForestMain], [0]), paintClass(clBaseResource) ], avoidClasses(clBaseResource, 0))); log("Creating the water patch explaining the forest for player " + playerIDs[i] + "..."); let waterPosition; do { let waterAngle = forestAngle + randFloat(1, 5) / 3 * Math.PI; waterPosition = Vector2D.add(forestPosition, new Vector2D(6, 0).rotate(-waterAngle)).round(); let flowerPosition = Vector2D.add(forestPosition, new Vector2D(3, 0).rotate(-waterAngle)).round(); createObjectGroup( new SimpleGroup( [new SimpleObject(aFlower1, 1, 5, 0, 3)], true, undefined, flowerPosition.x, flowerPosition.y), 0); let reedsPosition = Vector2D.add(forestPosition, new Vector2D(5, 0).rotate(-waterAngle)).round(); createObjectGroup( new SimpleGroup( [new SimpleObject(aReedsA, 1, 3, 0, 0)], true, undefined, reedsPosition.x, reedsPosition.y), 0); } while ( !createArea( new ClumpPlacer(60, 0.9, 0.4, 5, waterPosition), [ new LayeredPainter([tShore, tWater], [1]), new SmoothElevationPainter(ELEVATION_SET, heightSeaGround, 3) ], avoidClasses(clBaseResource, 0))); } Engine.SetProgress(20); placePlayerBases({ "PlayerPlacement": [playerIDs, playerPosition], "PlayerTileClass": clPlayer, "BaseResourceClass": clBaseResource, "CityPatch": { "outerTerrain": tRoadWild, "innerTerrain": tRoad, "painters": [ paintClass(clPlayer) ] }, "Chicken": { }, "Berries": { "template": eBush }, "Mines": { "types": [ { "template": eMetalMine }, { "template": eStoneMine }, ], "distance": defaultPlayerBaseRadius(), "maxAngle": Math.PI / 2, "groupElements": shuffleArray([aBushA, aBushB, ePalmShort, ePalmTall]).map(t => new SimpleObject(t, 1, 1, 3, 4)) } // Starting trees were set above // No decoratives }); Engine.SetProgress(30); log("Creating central oasis..."); createArea( new ClumpPlacer(diskArea(forestDistance + shoreDistance + waterRadius), 0.8, 0.2, 10, mapCenter), [ new LayeredPainter([pOasisForestLight, tWater], [forestDistance]), new SmoothElevationPainter(ELEVATION_SET, heightSeaGround, forestDistance + shoreDistance), paintClass(clOasis) ]); Engine.SetProgress(40); log("Creating bumps..."); createAreas( new ClumpPlacer(scaleByMapSize(20, 50), 0.3, 0.06, 1), new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetBump, 3), avoidClasses(clPlayer, 10, clBaseResource, 6, clOasis, 4), scaleByMapSize(30, 70)); log("Creating dirt patches..."); createAreas( new ClumpPlacer(80, 0.3, 0.06, 1), new TerrainPainter(tDirt), avoidClasses(clPlayer, 10, clBaseResource, 6, clOasis, 4, clForest, 4), scaleByMapSize(15, 50)); log("Creating dunes..."); createAreas( new ClumpPlacer(120, 0.3, 0.06, 1), [ new TerrainPainter(tDune), new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetDune, 30) ], avoidClasses(clPlayer, 10, clBaseResource, 6, clOasis, 4, clForest, 4), scaleByMapSize(15, 50)); Engine.SetProgress(50); if (mapSize > 150 && randBool()) { log("Creating path though the oasis..."); let pathWidth = scaleByMapSize(7, 18); let points = distributePointsOnCircle(2, randomAngle(), waterRadius + shoreDistance + forestDistance + pathWidth, mapCenter)[0]; createArea( new PathPlacer(points[0], points[1], pathWidth, 0.4, 1, 0.2, 0), [ new TerrainPainter(tSand), new SmoothElevationPainter(ELEVATION_SET, heightOasisPath, 5), paintClass(clPassage) ]); } log("Creating some straggler trees around the passage..."); var group = new SimpleGroup([new SimpleObject(ePalmTall, 1,1, 0,0),new SimpleObject(ePalmShort, 1, 2, 1, 2), new SimpleObject(aBushA, 0,2, 1,3)], true, clForest); createObjectGroupsDeprecated(group, 0, stayClasses(clPassage, 3), scaleByMapSize(60, 250), 100); log("Creating stone mines..."); group = new SimpleGroup([new SimpleObject(eStoneMine, 1,1, 0,0),new SimpleObject(ePalmShort, 1,2, 3,3),new SimpleObject(ePalmTall, 0,1, 3,3) ,new SimpleObject(aBushB, 1,1, 2,2), new SimpleObject(aBushA, 0,2, 1,3)], true, clRock); createObjectGroupsDeprecated(group, 0, avoidClasses(clOasis, 10, clForest, 1, clPlayer, 30, clRock, 10,clBaseResource, 2, clHill, 1), scaleByMapSize(6,25), 100 ); log("Creating metal mines..."); group = new SimpleGroup([new SimpleObject(eMetalMine, 1,1, 0,0),new SimpleObject(ePalmShort, 1,2, 2,3),new SimpleObject(ePalmTall, 0,1, 2,2) ,new SimpleObject(aBushB, 1,1, 2,2), new SimpleObject(aBushA, 0,2, 1,3)], true, clMetal); createObjectGroupsDeprecated(group, 0, avoidClasses(clOasis, 10, clForest, 1, clPlayer, 30, clMetal, 10,clBaseResource, 2, clRock, 10, clHill, 1), scaleByMapSize(6,25), 100 ); Engine.SetProgress(65); log("Creating small decorative rocks..."); group = new SimpleGroup( [new SimpleObject(aRock, 2,4, 0,2)], true, undefined ); createObjectGroupsDeprecated(group, 0, avoidClasses(clOasis, 3, clForest, 0, clPlayer, 10, clHill, 1, clFood, 20), 30, scaleByMapSize(10, 50)); Engine.SetProgress(70); log("Creating camels..."); group = new SimpleGroup( [new SimpleObject(eCamel, 1,2, 0,4)], true, clFood ); createObjectGroupsDeprecated(group, 0, avoidClasses(clOasis, 3, clForest, 0, clPlayer, 10, clHill, 1, clFood, 20), 1 * numPlayers, 50 ); Engine.SetProgress(75); log("Creating gazelles..."); group = new SimpleGroup( [new SimpleObject(eGazelle, 2,4, 0,2)], true, clFood ); createObjectGroupsDeprecated(group, 0, avoidClasses(clOasis, 3, clForest, 0, clPlayer, 10, clHill, 1, clFood, 20), 1 * numPlayers, 50 ); Engine.SetProgress(85); log("Creating oasis animals..."); for (let i = 0; i < scaleByMapSize(5, 30); ++i) { let animalPos = Vector2D.add(mapCenter, new Vector2D(forestDistance + shoreDistance + waterRadius, 0).rotate(randomAngle())); createObjectGroup( new RandomGroup( [ new SimpleObject(eLion, 1, 2, 0, 4), new SimpleObject(eLioness, 1, 2, 2, 4), new SimpleObject(eGazelle, 4, 6, 1, 5), new SimpleObject(eCamel, 1, 2, 1, 5) ], true, clFood, animalPos.x, animalPos.y), 0); } Engine.SetProgress(90); log("Creating bushes..."); var group = new SimpleGroup( [new SimpleObject(aBushB, 1,2, 0,2), new SimpleObject(aBushA, 2,4, 0,2)] ); createObjectGroupsDeprecated(group, 0, avoidClasses(clOasis, 2, clHill, 1, clPlayer, 1, clPassage, 1), scaleByMapSize(10, 40), 20 ); log("Creating sand blows and beautifications"); for (var sandx = 0; sandx < mapSize; sandx += 4) for (var sandz = 0; sandz < mapSize; sandz += 4) { - if (getHeight(sandx,sandz) > 3.4) + let position = new Vector2D(sandx, sandz); + let height = g_Map.getHeight(position); + + if (height > heightSand) { - if (randBool((getHeight(sandx,sandz) - 3.4) / 1.4)) + if (randBool((height - heightSand) / 1.4)) { group = new SimpleGroup( [new SimpleObject(aSand, 0,1, 0,2)], true, undefined, sandx,sandz ); createObjectGroup(group, 0); } } - else if (getHeight(sandx, sandz) > -2.5 && getHeight(sandx,sandz) < -1) + else if (height > heightFloraMin && height < heightFloraMax) { if (randBool(0.4)) { group = new SimpleGroup( [new SimpleObject(aWaterFlower, 1,4, 1,2)], true, undefined, sandx,sandz ); createObjectGroup(group, 0); } - else if (randBool(0.7) && getHeight(sandx,sandz) < -1.9) + else if (randBool(0.7) && height < heightFloraReedsMax) { group = new SimpleGroup( [new SimpleObject(aReedsA, 5,12, 0,2),new SimpleObject(aReedsB, 5,12, 0,2)], true, undefined, sandx,sandz ); createObjectGroup(group, 0); } - if (getTileClass(clPassage).countInRadius(sandx,sandz,2,true) > 0) { + if (getTileClass(clPassage).countMembersInRadius(sandx, sandz, 2) > 0) + { if (randBool(0.4)) { group = new SimpleGroup( [new SimpleObject(aWaterFlower, 1,4, 1,2)], true, undefined, sandx,sandz ); createObjectGroup(group, 0); } - else if (randBool(0.7) && getHeight(sandx,sandz) < -1.9) + else if (randBool(0.7) && height < heightFloraReedsMax) { group = new SimpleGroup( [new SimpleObject(aReedsA, 5,12, 0,2),new SimpleObject(aReedsB, 5,12, 0,2)], true, undefined, sandx,sandz ); createObjectGroup(group, 0); } } } } placePlayersNomad(clPlayer, avoidClasses(clOasis, 4, clForest, 1, clMetal, 4, clRock, 4, clHill, 4, clFood, 2)); setSkySet("sunny"); setSunColor(0.914,0.827,0.639); setSunRotation(Math.PI/3); setSunElevation(0.5); setWaterColor(0, 0.227, 0.843); setWaterTint(0, 0.545, 0.859); setWaterWaviness(1.0); setWaterType("clap"); setWaterMurkiness(0.5); setTerrainAmbientColor(0.45, 0.5, 0.6); setUnitsAmbientColor(0.501961, 0.501961, 0.501961); ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/pyrenean_sierra.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/pyrenean_sierra.js (revision 20982) +++ ps/trunk/binaries/data/mods/public/maps/random/pyrenean_sierra.js (revision 20983) @@ -1,483 +1,483 @@ Engine.LoadLibrary("rmgen"); TILE_CENTERED_HEIGHT_MAP = true; const tGrassSpecific = ["new_alpine_grass_d","new_alpine_grass_d", "new_alpine_grass_e"]; const tGrass = ["new_alpine_grass_d", "new_alpine_grass_b", "new_alpine_grass_e"]; const tGrassMidRange = ["new_alpine_grass_b", "alpine_grass_a"]; const tGrassHighRange = ["new_alpine_grass_a", "alpine_grass_a", "alpine_grass_rocky"]; const tHighRocks = ["alpine_cliff_b", "alpine_cliff_c","alpine_cliff_c", "alpine_grass_rocky"]; const tSnowedRocks = ["alpine_cliff_b", "alpine_cliff_snow"]; const tTopSnow = ["alpine_snow_rocky","alpine_snow_a"]; const tTopSnowOnly = ["alpine_snow_a"]; const tDirtyGrass = ["new_alpine_grass_d","alpine_grass_d","alpine_grass_c", "alpine_grass_b"]; const tLushGrass = ["new_alpine_grass_a","new_alpine_grass_d"]; const tMidRangeCliffs = ["alpine_cliff_b","alpine_cliff_c"]; const tHighRangeCliffs = ["alpine_mountainside","alpine_cliff_snow" ]; const tSand = ["beach_c", "beach_d"]; const tSandTransition = ["beach_scrub_50_"]; const tWater = ["sand_wet_a","sand_wet_b","sand_wet_b","sand_wet_b"]; const tGrassLandForest = "alpine_forrestfloor"; const tGrassLandForest2 = "alpine_grass_d"; const tForestTransition = ["new_alpine_grass_d", "new_alpine_grass_b","alpine_grass_d"]; const tRoad = "new_alpine_citytile"; const tRoadWild = "new_alpine_citytile"; const oBeech = "gaia/flora_tree_euro_beech"; const oPine = "gaia/flora_tree_aleppo_pine"; const oBerryBush = "gaia/flora_bush_berry"; const oDeer = "gaia/fauna_deer"; const oFish = "gaia/fauna_fish"; const oRabbit = "gaia/fauna_rabbit"; const oStoneLarge = "gaia/geology_stonemine_alpine_quarry"; const oStoneSmall = "gaia/geology_stone_alpine_a"; const oMetalLarge = "gaia/geology_metal_alpine_slabs"; const aGrass = "actor|props/flora/grass_soft_small_tall.xml"; const aGrassShort = "actor|props/flora/grass_soft_large.xml"; const aRockLarge = "actor|geology/stone_granite_med.xml"; const aRockMedium = "actor|geology/stone_granite_med.xml"; const aBushMedium = "actor|props/flora/bush_medit_me.xml"; const aBushSmall = "actor|props/flora/bush_medit_sm.xml"; const pForestLand = [tGrassLandForest + TERRAIN_SEPARATOR + oPine,tGrassLandForest + TERRAIN_SEPARATOR + oBeech, tGrassLandForest2 + TERRAIN_SEPARATOR + oPine,tGrassLandForest2 + TERRAIN_SEPARATOR + oBeech, tGrassLandForest,tGrassLandForest2,tGrassLandForest2,tGrassLandForest2]; const pForestLandLight = [tGrassLandForest + TERRAIN_SEPARATOR + oPine,tGrassLandForest + TERRAIN_SEPARATOR + oBeech, tGrassLandForest2 + TERRAIN_SEPARATOR + oPine,tGrassLandForest2 + TERRAIN_SEPARATOR + oBeech, tGrassLandForest,tGrassLandForest2,tForestTransition,tGrassLandForest2, tGrassLandForest,tForestTransition,tGrassLandForest2,tForestTransition, tGrassLandForest2,tGrassLandForest2,tGrassLandForest2,tGrassLandForest2]; const pForestLandVeryLight = [ tGrassLandForest2 + TERRAIN_SEPARATOR + oPine,tGrassLandForest2 + TERRAIN_SEPARATOR + oBeech, tForestTransition,tGrassLandForest2,tForestTransition,tForestTransition,tForestTransition, tGrassLandForest,tForestTransition,tGrassLandForest2,tForestTransition, tGrassLandForest2,tGrassLandForest2,tGrassLandForest2,tGrassLandForest2]; const heightInit = -100; const heightOcean = -22; const heightBase = -6; const heightWaterLevel = 8; const heightPyreneans = 15; const heightGrass = 6; const heightGrassMidRange = 18; const heightGrassHighRange = 30; const heightPassage = scaleByMapSize(25, 40); const heightHighRocks = heightPassage + 5; const heightSnowedRocks = heightHighRocks + 10; const heightMountain = heightHighRocks + 20; const heightOffsetHill = 7; const heightOffsetHillRandom = 2; InitMap(heightInit, tGrass); const numPlayers = getNumPlayers(); const mapSize = getMapSize(); const mapCenter = getMapCenter(); var clDirt = createTileClass(); var clRock = createTileClass(); var clMetal = createTileClass(); var clFood = createTileClass(); var clBaseResource = createTileClass(); var clPass = createTileClass(); var clPyrenneans = createTileClass(); var clPlayer = createTileClass(); var clHill = createTileClass(); var clForest = createTileClass(); var clWater = createTileClass(); var startAngle = randomAngle(); var oceanAngle = startAngle + randFloat(-1, 1) * Math.PI / 12; var mountainLength = fractionToTiles(0.68); var mountainWidth = scaleByMapSize(15, 55); var mountainPeaks = 100 * scaleByMapSize(1, 10); var mountainOffset = randFloat(-1, 1) * scaleByMapSize(1, 12); var passageLength = scaleByMapSize(8, 50); var terrainPerHeight = [ { "maxHeight": heightGrass, "steepness": 5, "terrainGround": tGrass, "terrainSteep": tMidRangeCliffs }, { "maxHeight": heightGrassMidRange, "steepness": 8, "terrainGround": tGrassMidRange, "terrainSteep": tMidRangeCliffs }, { "maxHeight": heightGrassHighRange, "steepness": 8, "terrainGround": tGrassHighRange, "terrainSteep": tMidRangeCliffs }, { "maxHeight": heightHighRocks, "steepness": 8, "terrainGround": tHighRocks, "terrainSteep": tHighRangeCliffs }, { "maxHeight": heightSnowedRocks, "steepness": 7, "terrainGround": tSnowedRocks, "terrainSteep": tHighRangeCliffs }, { "maxHeight": Infinity, "steepness": 6, "terrainGround": tTopSnowOnly, "terrainSteep": tTopSnow } ]; log("Creating initial sinusoidal noise..."); var baseHeights = []; for (var ix = 0; ix < mapSize; ix++) { baseHeights.push([]); for (var iz = 0; iz < mapSize; iz++) { let position = new Vector2D(ix, iz); if (g_Map.inMapBounds(position)) { let height = heightBase + randFloat(-1, 1) + scaleByMapSize(1, 3) * (Math.cos(ix / scaleByMapSize(5, 30)) + Math.sin(iz / scaleByMapSize(5, 30))); g_Map.setHeight(position, height); baseHeights[ix].push(height); } else baseHeights[ix].push(heightInit); } } placePlayerBases({ "PlayerPlacement": [primeSortAllPlayers(), ...playerPlacementCustomAngle( fractionToTiles(0.35), mapCenter, i => oceanAngle + Math.PI * (i % 2 ? 1 : -1) * ((1/2 + 1/3 * (2/numPlayers * (i + 1 - i % 2) - 1))))], "PlayerTileClass": clPlayer, "BaseResourceClass": clBaseResource, "CityPatch": { "outerTerrain": tRoadWild, "innerTerrain": tRoad }, "Chicken": { }, "Berries": { "template": oBerryBush }, "Mines": { "types": [ { "template": oMetalLarge }, { "template": oStoneLarge } ] }, "Trees": { "template": oPine }, "Decoratives": { "template": aGrassShort } }); Engine.SetProgress(30); log("Creating the pyreneans..."); var mountainVec = new Vector2D(mountainLength, 0).rotate(-startAngle); var mountainStart = Vector2D.sub(mapCenter, Vector2D.div(mountainVec, 2)); var mountainDirection = mountainVec.clone().normalize(); createPyreneans(); paintTileClassBasedOnHeight(heightPyreneans, Infinity, Elevation_ExcludeMin_ExcludeMax, clPyrenneans); Engine.SetProgress(40); /** * Generates the mountain peak noise. * * @param {number} x - between 0 and 1 * @returns {number} between 0 and 1 */ function sigmoid(x, peakPosition) { return 1 / (1 + Math.exp(x)) * // If we're too far from the border, we flatten (0.2 - Math.max(0, Math.abs(0.5 - peakPosition) - 0.3)) * 5; } function createPyreneans() { for (let peak = 0; peak < mountainPeaks; ++peak) { let peakPosition = peak / mountainPeaks; let peakHeight = randFloat(0, 10); for (let distance = 0; distance < mountainWidth; distance += 1/3) { let rest = 2 * (1 - distance / mountainWidth); let sigmoidX = - 1 * (rest - 1.9) + - 4 * (rest - randFloat(0.9, 1.1)) * (rest - randFloat(0.9, 1.1)) * (rest - randFloat(0.9, 1.1)); for (let direction of [-1, 1]) { let pos = Vector2D.sum([ Vector2D.add(mountainStart, Vector2D.mult(mountainDirection, peakPosition * mountainLength)), new Vector2D(mountainOffset, 0).rotate(-peakPosition * Math.PI * 4), new Vector2D(distance, 0).rotate(-startAngle - direction * Math.PI / 2) ]).round(); g_Map.setHeight(pos, baseHeights[pos.x][pos.y] + (heightMountain + peakHeight + randFloat(-9, 9)) * sigmoid(sigmoidX, peakPosition)); } } } } log("Smoothing pyreneans..."); for (let ix = 1; ix < mapSize - 1; ++ix) for (let iz = 1; iz < mapSize - 1; ++iz) { let position = new Vector2D(ix, iz); if (g_Map.validH(ix, iz) && getTileClass(clPyrenneans).countMembersInRadius(ix, iz, 1)) { - let height = getHeight(ix, iz); + let height = g_Map.getHeight(position); let index = 1 / (1 + Math.max(0, height / 7)); g_Map.setHeight(position, height * (1 - index) + g_Map.getAverageHeight(position) * index); } } Engine.SetProgress(48); log("Creating passages..."); var passageLocation = 0.35; var passageVec = mountainDirection.perpendicular().mult(passageLength); for (let passLoc of [passageLocation, 1 - passageLocation]) for (let direction of [1, -1]) { let passageStart = Vector2D.add(mountainStart, Vector2D.mult(mountainVec, passLoc)); let passageEnd = Vector2D.add(passageStart, Vector2D.mult(passageVec, direction)); createPassage({ "start": passageStart, "end": passageEnd, "startHeight": heightPassage, "startWidth": 7, "endWidth": 7, "smoothWidth": 2, "tileClass": clPass }); } Engine.SetProgress(50); log("Smoothing the mountains..."); for (let ix = 1; ix < mapSize - 1; ++ix) for (let iz = 1; iz < mapSize - 1; ++iz) { let position = new Vector2D(ix, iz); if (g_Map.inMapBounds(position) && getTileClass(clPyrenneans).countMembersInRadius(ix, iz, 1)) { let heightNeighbor = g_Map.getAverageHeight(position); - let index = 1 / (1 + Math.max(0, (getHeight(ix,iz) - 10) / 7)); - g_Map.setHeight(position, getHeight(ix, iz) * (1 - index) + heightNeighbor * index); + let index = 1 / (1 + Math.max(0, (g_Map.getHeight(position) - 10) / 7)); + g_Map.setHeight(position, g_Map.getHeight(position) * (1 - index) + heightNeighbor * index); } } log("Creating oceans..."); for (let ocean of distributePointsOnCircle(2, oceanAngle, fractionToTiles(0.48), mapCenter)[0]) createArea( new ClumpPlacer(diskArea(fractionToTiles(0.18)), 0.9, 0.05, 10, ocean), [ new ElevationPainter(heightOcean), paintClass(clWater) ]); log("Smoothing around the water..."); var smoothDist = 5; for (let ix = 1; ix < mapSize - 1; ++ix) for (let iz = 1; iz < mapSize - 1; ++iz) { let position = new Vector2D(ix, iz); if (!g_Map.inMapBounds(position) || !getTileClass(clWater).countMembersInRadius(ix, iz, smoothDist)) continue; let averageHeight = 0; let todivide = 0; for (let xx = -smoothDist; xx <= smoothDist; ++xx) for (let yy = -smoothDist; yy <= smoothDist; ++yy) { let smoothPos = Vector2D.add(position, new Vector2D(xx, yy)); if (g_Map.inMapBounds(smoothPos) && (xx != 0 || yy != 0)) { - averageHeight += getHeight(smoothPos.x, smoothPos.y) / (Math.abs(xx) + Math.abs(yy)); + averageHeight += g_Map.getHeight(smoothPos) / (Math.abs(xx) + Math.abs(yy)); todivide += 1 / (Math.abs(xx) + Math.abs(yy)); } } - g_Map.setHeight(position, (averageHeight + 2 * getHeight(ix, iz)) / (todivide + 2)); + g_Map.setHeight(position, (averageHeight + 2 * g_Map.getHeight(position)) / (todivide + 2)); } Engine.SetProgress(55); log("Creating hills..."); createAreas( new ClumpPlacer(scaleByMapSize(60, 120), 0.3, 0.06, 5), [ new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetHill, 4, heightOffsetHillRandom), new TerrainPainter(tGrassSpecific), paintClass(clHill) ], avoidClasses(clWater, 5, clPlayer, 20, clBaseResource, 6, clPyrenneans, 2), scaleByMapSize(5, 35)); log("Creating forests..."); var types = [[tForestTransition, pForestLandVeryLight, pForestLandLight, pForestLand]]; var size = scaleByMapSize(40, 115) * Math.PI; var num = Math.floor(scaleByMapSize(8,40) / types.length); for (let type of types) createAreas( new ClumpPlacer(size, 0.2, 0.1, 1), [ new LayeredPainter(type, [scaleByMapSize(1, 2), scaleByMapSize(3, 6), scaleByMapSize(3, 6)]), paintClass(clForest) ], avoidClasses(clPlayer, 20, clPyrenneans,0, clForest, 7, clWater, 2), num); Engine.SetProgress(60); log("Creating lone trees..."); var num = scaleByMapSize(80,400); var group = new SimpleGroup([new SimpleObject(oPine, 1,2, 1,3),new SimpleObject(oBeech, 1,2, 1,3)], true, clForest); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 3, clForest, 1, clPlayer, 8,clPyrenneans, 1), num, 20 ); log("Painting the map..."); for (let x = 0; x < mapSize; ++x) for (let z = 0; z < mapSize; ++z) { let position = new Vector2D(x, z); - let height = getHeight(x, z); + let height = g_Map.getHeight(position); let heightDiff = g_Map.getSlope(position); if (getTileClass(clPyrenneans).countMembersInRadius(x, z, 2)) { let layer = terrainPerHeight.find(layer => height < layer.maxHeight); createTerrain(heightDiff > layer.steepness ? layer.terrainSteep : layer.terrainGround).place(position); } let terrainShore = getShoreTerrain(height, heightDiff, x, z); if (terrainShore) createTerrain(terrainShore).place(position); } function getShoreTerrain(height, heightDiff, x, z) { if (height <= -14) return tWater; if (height <= -2 && getTileClass(clWater).countMembersInRadius(x, z, 2)) return heightDiff < 2.5 ? tSand : tMidRangeCliffs; if (height <= 0 && getTileClass(clWater).countMembersInRadius(x, z, 3)) return heightDiff < 2.5 ? tSandTransition : tMidRangeCliffs; return undefined; } log("Creating dirt patches..."); for (let size of [scaleByMapSize(3, 20), scaleByMapSize(5, 40), scaleByMapSize(8, 60)]) createAreas( new ClumpPlacer(size, 0.3, 0.06, 0.5), [ new TerrainPainter(tDirtyGrass), paintClass(clDirt) ], avoidClasses(clWater, 3, clForest, 0, clPyrenneans,5, clHill, 0, clDirt, 5, clPlayer, 6), scaleByMapSize(15, 45)); log("Creating grass patches..."); for (let size of [scaleByMapSize(2, 32), scaleByMapSize(3, 48), scaleByMapSize(5, 80)]) createAreas( new ClumpPlacer(size, 0.3, 0.06, 0.5), new TerrainPainter(tLushGrass), avoidClasses(clWater, 3, clForest, 0, clPyrenneans,5, clHill, 0, clDirt, 5, clPlayer, 6), scaleByMapSize(15, 45)); Engine.SetProgress(70); // making more in dirt areas so as to appear different log("Creating small grass tufts..."); var group = new SimpleGroup( [new SimpleObject(aGrassShort, 1,2, 0,1, -Math.PI / 8, Math.PI / 8)] ); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 2, clHill, 2, clPlayer, 5, clDirt, 0, clPyrenneans,2), scaleByMapSize(13, 200) ); createObjectGroupsDeprecated(group, 0, stayClasses(clDirt,1), scaleByMapSize(13, 200),10); log("Creating large grass tufts..."); group = new SimpleGroup( [new SimpleObject(aGrass, 2,4, 0,1.8, -Math.PI / 8, Math.PI / 8), new SimpleObject(aGrassShort, 3,6, 1.2,2.5, -Math.PI / 8, Math.PI / 8)] ); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 3, clHill, 2, clPlayer, 5, clDirt, 1, clForest, 0, clPyrenneans,2), scaleByMapSize(13, 200) ); createObjectGroupsDeprecated(group, 0, stayClasses(clDirt,1), scaleByMapSize(13, 200),10); Engine.SetProgress(75); log("Creating bushes..."); group = new SimpleGroup( [new SimpleObject(aBushMedium, 1,2, 0,2), new SimpleObject(aBushSmall, 2,4, 0,2)] ); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 2, clPlayer, 1, clPyrenneans, 1), scaleByMapSize(13, 200), 50 ); Engine.SetProgress(80); log("Creating stone mines..."); group = new SimpleGroup([new SimpleObject(oStoneSmall, 0,2, 0,4), new SimpleObject(oStoneLarge, 1,1, 0,4)], true, clRock); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 3, clForest, 1, clPlayer, 20, clRock, 8, clPyrenneans, 1), scaleByMapSize(4,16), 100 ); log("Creating small stone quarries..."); group = new SimpleGroup([new SimpleObject(oStoneSmall, 2,5, 1,3)], true, clRock); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 3, clForest, 1, clPlayer, 20, clRock, 8, clPyrenneans, 1), scaleByMapSize(4,16), 100 ); log("Creating metal mines..."); group = new SimpleGroup([new SimpleObject(oMetalLarge, 1,1, 0,4)], true, clMetal); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 3, clForest, 1, clPlayer, 20, clMetal, 8, clRock, 5, clPyrenneans, 1), scaleByMapSize(4,16), 100 ); Engine.SetProgress(85); log("Creating small decorative rocks..."); group = new SimpleGroup( [new SimpleObject(aRockMedium, 1,3, 0,1)], true ); createObjectGroupsDeprecated( group, 0, avoidClasses(clWater, 0, clForest, 0, clPlayer, 0), scaleByMapSize(16, 262), 50 ); log("Creating large decorative rocks..."); group = new SimpleGroup( [new SimpleObject(aRockLarge, 1,2, 0,1), new SimpleObject(aRockMedium, 1,3, 0,2)], true ); createObjectGroupsDeprecated( group, 0, avoidClasses(clWater, 0, clForest, 0, clPlayer, 0), scaleByMapSize(8, 131), 50 ); Engine.SetProgress(90); log("Creating deer..."); group = new SimpleGroup( [new SimpleObject(oDeer, 5,7, 0,4)], true, clFood ); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 3, clForest, 0, clPlayer, 20, clPyrenneans, 1, clFood, 15), 3 * numPlayers, 50 ); log("Creating rabbit..."); group = new SimpleGroup( [new SimpleObject(oRabbit, 2,3, 0,2)], true, clFood ); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 3, clForest, 0, clPlayer, 20, clPyrenneans, 1, clFood,15), 3 * numPlayers, 50 ); log("Creating berry bush..."); group = new SimpleGroup( [new SimpleObject(oBerryBush, 5,7, 0,4)],true, clFood ); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 3, clForest, 0, clPlayer, 20, clPyrenneans, 1, clFood, 10), randIntInclusive(1, 4) * numPlayers + 2, 50); log("Creating fish..."); group = new SimpleGroup( [new SimpleObject(oFish, 2,3, 0,2)], true, clFood ); createObjectGroupsDeprecated(group, 0, [avoidClasses(clFood, 15), stayClasses(clWater, 6)], 20 * numPlayers, 60 ); placePlayersNomad(clPlayer, avoidClasses(clWater, 4, clPyrenneans, 4, clForest, 1, clMetal, 4, clRock, 4, clFood, 2)); setSunElevation(Math.PI * randFloat(1/5, 1/3)); setSunRotation(randomAngle()); setSkySet("cumulus"); setSunColor(0.73,0.73,0.65); setTerrainAmbientColor(0.45,0.45,0.50); setUnitsAmbientColor(0.4,0.4,0.4); setWaterColor(0.263, 0.353, 0.616); setWaterTint(0.104, 0.172, 0.563); setWaterWaviness(5.0); setWaterType("ocean"); setWaterMurkiness(0.83); setWaterHeight(heightWaterLevel); ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/rivers.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rivers.js (revision 20982) +++ ps/trunk/binaries/data/mods/public/maps/random/rivers.js (revision 20983) @@ -1,289 +1,289 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmbiome"); setSelectedBiome(); const tMainTerrain = g_Terrains.mainTerrain; const tForestFloor1 = g_Terrains.forestFloor1; const tForestFloor2 = g_Terrains.forestFloor2; const tCliff = g_Terrains.cliff; const tTier1Terrain = g_Terrains.tier1Terrain; const tTier2Terrain = g_Terrains.tier2Terrain; const tTier3Terrain = g_Terrains.tier3Terrain; const tHill = g_Terrains.hill; const tRoad = g_Terrains.road; const tRoadWild = g_Terrains.roadWild; const tTier4Terrain = g_Terrains.tier4Terrain; var tShore = g_Terrains.shore; var tWater = g_Terrains.water; if (currentBiome() == "tropic") { tShore = "tropic_dirt_b_plants"; tWater = "tropic_dirt_b"; } const oTree1 = g_Gaia.tree1; const oTree2 = g_Gaia.tree2; const oTree3 = g_Gaia.tree3; const oTree4 = g_Gaia.tree4; const oTree5 = g_Gaia.tree5; const oFruitBush = g_Gaia.fruitBush; const oMainHuntableAnimal = g_Gaia.mainHuntableAnimal; const oFish = g_Gaia.fish; const oSecondaryHuntableAnimal = g_Gaia.secondaryHuntableAnimal; const oStoneLarge = g_Gaia.stoneLarge; const oStoneSmall = g_Gaia.stoneSmall; const oMetalLarge = g_Gaia.metalLarge; const aGrass = g_Decoratives.grass; const aGrassShort = g_Decoratives.grassShort; const aReeds = g_Decoratives.reeds; const aLillies = g_Decoratives.lillies; const aRockLarge = g_Decoratives.rockLarge; const aRockMedium = g_Decoratives.rockMedium; const aBushMedium = g_Decoratives.bushMedium; const aBushSmall = g_Decoratives.bushSmall; const pForest1 = [tForestFloor2 + TERRAIN_SEPARATOR + oTree1, tForestFloor2 + TERRAIN_SEPARATOR + oTree2, tForestFloor2]; const pForest2 = [tForestFloor1 + TERRAIN_SEPARATOR + oTree4, tForestFloor1 + TERRAIN_SEPARATOR + oTree5, tForestFloor1]; const heightSeaGround = -3; const heightShallows = -1; const heightLand = 1; InitMap(heightLand, tMainTerrain); const numPlayers = getNumPlayers(); const mapSize = getMapSize(); const mapCenter = getMapCenter(); var clPlayer = createTileClass(); var clHill = createTileClass(); var clForest = createTileClass(); var clWater = createTileClass(); var clDirt = createTileClass(); var clRock = createTileClass(); var clMetal = createTileClass(); var clFood = createTileClass(); var clBaseResource = createTileClass(); var clShallow = createTileClass(); var [playerIDs, playerPosition, playerAngle, startAngle] = playerPlacementCircle(fractionToTiles(0.35)); placePlayerBases({ "PlayerPlacement": [playerIDs, playerPosition], "PlayerTileClass": clPlayer, "BaseResourceClass": clBaseResource, "CityPatch": { "outerTerrain": tRoadWild, "innerTerrain": tRoad }, "Chicken": { }, "Berries": { "template": oFruitBush }, "Mines": { "types": [ { "template": oMetalLarge }, { "template": oStoneLarge } ] }, "Trees": { "template": oTree1, "count": 2 }, "Decoratives": { "template": aGrassShort } }); log("Creating central lake..."); createArea( new ClumpPlacer(diskArea(fractionToTiles(0.075)), 0.7, 0.1, 10, mapCenter), [ new LayeredPainter([tShore, tWater, tWater, tWater], [1, 4, 2]), new SmoothElevationPainter(ELEVATION_SET, heightSeaGround, 4), paintClass(clWater) ]); log("Creating rivers between opponents..."); let numRivers = isNomad() ? randIntInclusive(4, 8) : numPlayers; let rivers = distributePointsOnCircle(numRivers, startAngle + Math.PI / numRivers, fractionToTiles(0.5), mapCenter)[0]; for (let i = 0; i < numRivers; ++i) { if (isNomad() ? randBool() : areAllies(playerIDs[i], playerIDs[(i + 1) % numPlayers])) continue; let shallowLocation = randFloat(0.2, 0.7); let shallowWidth = randFloat(0.12, 0.21); paintRiver({ "parallel": true, "start": rivers[i], "end": mapCenter, "width": scaleByMapSize(10, 30), "fadeDist": 5, "deviation": 0, "heightLand": heightLand, "heightRiverbed": heightSeaGround, "minHeight": heightSeaGround, "meanderShort": 10, "meanderLong": 0, "waterFunc": (position, height, riverFraction) => { addToClass(position.x, position.y, clWater); let isShallow = height < heightShallows && riverFraction > shallowLocation && riverFraction < shallowLocation + shallowWidth; let newHeight = isShallow ? heightShallows : Math.max(height, heightSeaGround); - if (getHeight(position.x, position.y) < newHeight) + if (g_Map.getHeight(position) < newHeight) return; g_Map.setHeight(position, newHeight); createTerrain(height >= 0 ? tShore : tWater).place(position); if (isShallow) addToClass(position.x, position.y, clShallow); } }); } Engine.SetProgress(40); createBumps(avoidClasses(clWater, 2, clPlayer, 20)); if (randBool()) createHills([tMainTerrain, tCliff, tHill], avoidClasses(clPlayer, 20, clHill, 15, clWater, 2), clHill, scaleByMapSize(3, 15)); else createMountains(tCliff, avoidClasses(clPlayer, 20, clHill, 15, clWater, 2), clHill, scaleByMapSize(3, 15)); var [forestTrees, stragglerTrees] = getTreeCounts(...rBiomeTreeCount(1)); createForests( [tMainTerrain, tForestFloor1, tForestFloor2, pForest1, pForest2], avoidClasses(clPlayer, 20, clForest, 17, clHill, 0, clWater, 2), clForest, forestTrees); Engine.SetProgress(50); log("Creating dirt patches..."); createLayeredPatches( [scaleByMapSize(3, 6), scaleByMapSize(5, 10), scaleByMapSize(8, 21)], [[tMainTerrain,tTier1Terrain],[tTier1Terrain,tTier2Terrain], [tTier2Terrain,tTier3Terrain]], [1,1], avoidClasses(clWater, 3, clForest, 0, clHill, 0, clDirt, 5, clPlayer, 12), scaleByMapSize(15, 45), clDirt); log("Creating grass patches..."); createPatches( [scaleByMapSize(2, 4), scaleByMapSize(3, 7), scaleByMapSize(5, 15)], tTier4Terrain, avoidClasses(clWater, 3, clForest, 0, clHill, 0, clDirt, 5, clPlayer, 12), scaleByMapSize(15, 45), clDirt); Engine.SetProgress(55); log("Creating stone mines..."); createMines( [ [new SimpleObject(oStoneSmall, 0,2, 0,4), new SimpleObject(oStoneLarge, 1,1, 0,4)], [new SimpleObject(oStoneSmall, 2,5, 1,3)] ], avoidClasses(clWater, 3, clForest, 1, clPlayer, 20, clRock, 10, clHill, 1), clRock); log("Creating metal mines..."); createMines( [ [new SimpleObject(oMetalLarge, 1,1, 0,4)] ], avoidClasses(clWater, 3, clForest, 1, clPlayer, 20, clMetal, 10, clRock, 5, clHill, 1), clMetal ); Engine.SetProgress(65); var planetm = 1; if (currentBiome() == "tropic") planetm = 8; 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), planetm * scaleByMapSize(13, 200), planetm * scaleByMapSize(13, 200), planetm * scaleByMapSize(13, 200) ], avoidClasses(clWater, 0, clForest, 0, clPlayer, 0, clHill, 0)); createDecoration( [ [new SimpleObject(aReeds, 1, 3, 0, 1)], [new SimpleObject(aLillies, 1, 2, 0, 1)] ], [ scaleByMapSize(800, 12800), scaleByMapSize(800, 12800) ], stayClasses(clShallow, 0)); Engine.SetProgress(70); createFood( [ [new SimpleObject(oMainHuntableAnimal, 5, 7, 0, 4)], [new SimpleObject(oSecondaryHuntableAnimal, 2, 3, 0, 2)] ], [ 3 * numPlayers, 3 * numPlayers ], avoidClasses(clWater, 3, clForest, 0, clPlayer, 20, clHill, 1, clFood, 20), clFood); createFood( [ [new SimpleObject(oFruitBush, 5, 7, 0, 4)] ], [ 3 * numPlayers ], avoidClasses(clWater, 3, clForest, 0, clPlayer, 20, clHill, 1, clFood, 10), clFood); createFood( [ [new SimpleObject(oFish, 2, 3, 0, 2)] ], [ 25 * numPlayers ], [avoidClasses(clFood, 20), stayClasses(clWater, 6)], clFood); Engine.SetProgress(85); createStragglerTrees( [oTree1, oTree2, oTree4, oTree3], avoidClasses(clWater, 5, clForest, 7, clHill, 1, clPlayer, 12, clMetal, 6, clRock, 6), clForest, stragglerTrees); placePlayersNomad(clPlayer, avoidClasses(clWater, 4, clForest, 1, clMetal, 4, clRock, 4, clFood, 2)); setWaterWaviness(3.0); setWaterType("lake"); ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/gaia_terrain.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen/gaia_terrain.js (revision 20982) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/gaia_terrain.js (revision 20983) @@ -1,600 +1,600 @@ /** * @file These functions are often used to create a landscape, for instance shaping mountains, hills, rivers or grass and dirt patches. */ /** * Bumps add slight, diverse elevation differences to otherwise completely level terrain. */ function createBumps(constraint, count, minSize, maxSize, spread, failFraction = 0, elevation = 2) { log("Creating bumps..."); createAreas( new ChainPlacer( minSize || 1, maxSize || Math.floor(scaleByMapSize(4, 6)), spread || Math.floor(scaleByMapSize(2, 5)), failFraction), new SmoothElevationPainter(ELEVATION_MODIFY, elevation, 2), constraint, count || scaleByMapSize(100, 200)); } /** * Hills are elevated, planar, impassable terrain areas. */ function createHills(terrainset, constraint, tileClass, count, minSize, maxSize, spread, failFraction = 0.5, elevation = 18, elevationSmoothing = 2) { log("Creating hills..."); createAreas( new ChainPlacer( minSize || 1, maxSize || Math.floor(scaleByMapSize(4, 6)), spread || Math.floor(scaleByMapSize(16, 40)), failFraction), [ new LayeredPainter(terrainset, [1, elevationSmoothing]), new SmoothElevationPainter(ELEVATION_SET, elevation, elevationSmoothing), paintClass(tileClass) ], constraint, count || scaleByMapSize(1, 4) * getNumPlayers()); } /** * Mountains are impassable smoothened cones. */ function createMountains(terrain, constraint, tileClass, count, maxHeight, minRadius, maxRadius, numCircles) { log("Creating mountains..."); let mapSize = getMapSize(); for (let i = 0; i < (count || scaleByMapSize(1, 4) * getNumPlayers()); ++i) createMountain( maxHeight !== undefined ? maxHeight : Math.floor(scaleByMapSize(30, 50)), minRadius || Math.floor(scaleByMapSize(3, 4)), maxRadius || Math.floor(scaleByMapSize(6, 12)), numCircles || Math.floor(scaleByMapSize(4, 10)), constraint, randIntExclusive(0, mapSize), randIntExclusive(0, mapSize), terrain, tileClass, 14); } /** * Create a mountain using a technique very similar to ChainPlacer. */ function createMountain(maxHeight, minRadius, maxRadius, numCircles, constraints, x, z, terrain, tileClass, fcc = 0, q = []) { let position = new Vector2D(x, z); let constraint = new AndConstraint(constraints); if (!g_Map.inMapBounds(position) || !constraint.allows(position)) return; let mapSize = getMapSize(); let queueEmpty = !q.length; let gotRet = []; for (let i = 0; i < mapSize; ++i) { gotRet[i] = []; for (let j = 0; j < mapSize; ++j) gotRet[i][j] = -1; } --mapSize; minRadius = Math.max(1, Math.min(minRadius, maxRadius)); let edges = [[x, z]]; let circles = []; for (let i = 0; i < numCircles; ++i) { let badPoint = false; let [cx, cz] = pickRandom(edges); let radius; if (queueEmpty) radius = randIntInclusive(minRadius, maxRadius); else { radius = q.pop(); queueEmpty = !q.length; } let sx = Math.max(0, cx - radius); let sz = Math.max(0, cz - radius); let lx = Math.min(cx + radius, mapSize); let lz = Math.min(cz + radius, mapSize); let radius2 = Math.square(radius); for (let ix = sx; ix <= lx; ++ix) { for (let iz = sz; iz <= lz; ++iz) { let pos = new Vector2D(ix, iz); if (Math.euclidDistance2D(ix, iz, cx, cz) > radius2 || !g_Map.inMapBounds(pos)) continue; if (!constraint.allows(pos)) { badPoint = true; break; } let state = gotRet[ix][iz]; if (state == -1) { gotRet[ix][iz] = -2; } else if (state >= 0) { edges.splice(state, 1); gotRet[ix][iz] = -2; for (let k = state; k < edges.length; ++k) --gotRet[edges[k][0]][edges[k][1]]; } } if (badPoint) break; } if (badPoint) continue; circles.push([cx, cz, radius]); for (let ix = sx; ix <= lx; ++ix) for (let iz = sz; iz <= lz; ++iz) { if (gotRet[ix][iz] != -2 || fcc && (x - ix > fcc || ix - x > fcc || z - iz > fcc || iz - z > fcc) || ix > 0 && gotRet[ix-1][iz] == -1 || iz > 0 && gotRet[ix][iz-1] == -1 || ix < mapSize && gotRet[ix+1][iz] == -1 || iz < mapSize && gotRet[ix][iz+1] == -1) continue; edges.push([ix, iz]); gotRet[ix][iz] = edges.length - 1; } } for (let [cx, cz, radius] of circles) { let circlePosition = new Vector2D(cx, cz); let sx = Math.max(0, cx - radius); let sz = Math.max(0, cz - radius); let lx = Math.min(cx + radius, mapSize); let lz = Math.min(cz + radius, mapSize); let clumpHeight = radius / maxRadius * maxHeight * randFloat(0.8, 1.2); for (let ix = sx; ix <= lx; ++ix) for (let iz = sz; iz <= lz; ++iz) { let position = new Vector2D(ix, iz); let distance = position.distanceTo(circlePosition); let newHeight = randIntInclusive(0, 2) + Math.round(2/3 * clumpHeight * (Math.sin(Math.PI * 2/3 * (3/4 - distance / radius)) + 0.5)); if (distance > radius) continue; - if (getHeight(ix, iz) < newHeight) + if (g_Map.getHeight(position) < newHeight) g_Map.setHeight(position, newHeight); - else if (getHeight(ix, iz) >= newHeight && getHeight(ix, iz) < newHeight + 4) + else if (g_Map.getHeight(position) >= newHeight && g_Map.getHeight(position) < newHeight + 4) g_Map.setHeight(position, newHeight + 4); if (terrain !== undefined) createTerrain(terrain).place(position); if (tileClass !== undefined) addToClass(ix, iz, tileClass); } } } /** * Generates a volcano mountain. Smoke and lava are optional. * * @param {number} center - Vector2D location on the tilemap. * @param {number} tileClass - Painted onto every tile that is occupied by the volcano. * @param {string} terrainTexture - The texture painted onto the volcano hill. * @param {array} lavaTextures - Three different textures for the interior, from the outside to the inside. * @param {boolean} smoke - Whether to place smoke particles. * @param {number} elevationType - Elevation painter type, ELEVATION_SET = absolute or ELEVATION_MODIFY = relative. */ function createVolcano(position, tileClass, terrainTexture, lavaTextures, smoke, elevationType) { log("Creating volcano..."); let clLava = createTileClass(); let layers = [ { "clumps": diskArea(scaleByMapSize(18, 25)), "elevation": 15, "tileClass": tileClass, "steepness": 3 }, { "clumps": diskArea(scaleByMapSize(16, 23)), "elevation": 25, "tileClass": createTileClass(), "steepness": 3 }, { "clumps": diskArea(scaleByMapSize(10, 15)), "elevation": 45, "tileClass": createTileClass(), "steepness": 3 }, { "clumps": diskArea(scaleByMapSize(8, 11)), "elevation": 62, "tileClass": createTileClass(), "steepness": 3 }, { "clumps": diskArea(scaleByMapSize(4, 6)), "elevation": 42, "tileClass": clLava, "painter": lavaTextures && new LayeredPainter([terrainTexture, ...lavaTextures], [1, 1, 1]), "steepness": 1 } ]; for (let i = 0; i < layers.length; ++i) createArea( new ClumpPlacer(layers[i].clumps, 0.7, 0.05, 100, position), [ layers[i].painter || new LayeredPainter([terrainTexture, terrainTexture], [3]), new SmoothElevationPainter(elevationType, layers[i].elevation, layers[i].steepness), paintClass(layers[i].tileClass) ], i == 0 ? null : stayClasses(layers[i - 1].tileClass, 1)); if (smoke) { let num = Math.floor(diskArea(scaleByMapSize(3, 5))); createObjectGroup( new SimpleGroup( [new SimpleObject("actor|particle/smoke.xml", num, num, 0, 7)], false, clLava, position.x, position.y), 0, stayClasses(tileClass, 1)); } } /** * Paint the given terrain texture in the given sizes at random places of the map to diversify monotone land texturing. */ function createPatches(sizes, terrain, constraint, count, tileClass, failFraction = 0.5) { for (let size of sizes) createAreas( new ChainPlacer(1, Math.floor(scaleByMapSize(3, 5)), size, failFraction), [ new TerrainPainter(terrain), paintClass(tileClass) ], constraint, count); } /** * Same as createPatches, but each patch consists of a set of textures drawn depending to the distance of the patch border. */ function createLayeredPatches(sizes, terrains, terrainWidths, constraint, count, tileClass, failFraction = 0.5) { for (let size of sizes) createAreas( new ChainPlacer(1, Math.floor(scaleByMapSize(3, 5)), size, failFraction), [ new LayeredPainter(terrains, terrainWidths), paintClass(tileClass) ], constraint, count); } /** * Creates a meandering river at the given location and width. * Optionally calls a function on the affected tiles. * * @property start - A Vector2D in tile coordinates stating where the river starts. * @property end - A Vector2D in tile coordinates stating where the river ends. * @property parallel - Whether the shorelines should be parallel or meander separately. * @property width - Size between the two shorelines. * @property fadeDist - Size of the shoreline. * @property deviation - Fuzz effect on the shoreline if greater than 0. * @property heightRiverbed - Ground height of the riverbed. * @proeprty heightLand - Ground height of the end of the shoreline. * @property meanderShort - Strength of frequent meanders. * @property meanderLong - Strength of less frequent meanders. * @property [constraint] - If given, ignores any tiles that don't satisfy the given Constraint. * @property [waterFunc] - Optional function called on tiles within the river. * Provides location on the tilegrid, new elevation and * the location on the axis parallel to the river as a fraction of the river length. * @property [landFunc] - Optional function called on land tiles, providing ix, iz, shoreDist1, shoreDist2. * @property [minHeight] - If given, only changes the elevation below this height while still calling the given functions. */ function paintRiver(args) { log("Creating river..."); // Model the river meandering as the sum of two sine curves. let meanderShort = fractionToTiles(args.meanderShort / scaleByMapSize(35, 160)); let meanderLong = fractionToTiles(args.meanderLong / scaleByMapSize(35, 100)); // Unless the river is parallel, each riverside will receive an own random seed and starting angle. let seed1 = randFloat(2, 3); let seed2 = randFloat(2, 3); let startingAngle1 = randFloat(0, 1); let startingAngle2 = randFloat(0, 1); // Computes the deflection of the river at a given point. let riverCurve = (riverFraction, startAngle, seed) => meanderShort * rndRiver(startAngle + fractionToTiles(riverFraction) / 128, seed) + meanderLong * rndRiver(startAngle + fractionToTiles(riverFraction) / 256, seed); // Describe river location in vectors. let riverLength = args.start.distanceTo(args.end); let unitVecRiver = Vector2D.sub(args.start, args.end).normalize(); // Describe river boundaries. let riverMinX = Math.min(args.start.x, args.end.x); let riverMinZ = Math.min(args.start.y, args.end.y); let riverMaxX = Math.max(args.start.x, args.end.x); let riverMaxZ = Math.max(args.start.y, args.end.y); let mapSize = getMapSize(); for (let ix = 0; ix < mapSize; ++ix) for (let iz = 0; iz < mapSize; ++iz) { let vecPoint = new Vector2D(ix, iz); if (args.constraint && !args.constraint.allows(vecPoint)) continue; // Compute the shortest distance to the river. let distanceToRiver = distanceOfPointFromLine(args.start, args.end, vecPoint); // Closest point on the river (i.e the foot of the perpendicular). let river = Vector2D.sub(vecPoint, unitVecRiver.perpendicular().mult(distanceToRiver)); // Only process points that actually are perpendicular with the river. if (river.x < riverMinX || river.x > riverMaxX || river.y < riverMinZ || river.y > riverMaxZ) continue; // Coordinate between 0 and 1 on the axis parallel to the river. let riverFraction = river.distanceTo(args.start) / riverLength; // Amplitude of the river at this location. let riverCurve1 = riverCurve(riverFraction, startingAngle1, seed1); let riverCurve2 = args.parallel ? riverCurve1 : riverCurve(riverFraction, startingAngle2, seed2); // Add noise. let deviation = args.deviation * randFloat(-1, 1); // Compute the distance to the shoreline. let sign = Math.sign(distanceToRiver || 1); let shoreDist1 = sign * riverCurve1 + Math.abs(distanceToRiver) - deviation - args.width / 2; let shoreDist2 = sign * riverCurve2 + Math.abs(distanceToRiver) - deviation + args.width / 2; // Create the elevation for the water and the slopy shoreline and call the user functions. if (shoreDist1 < 0 && shoreDist2 > 0) { let height = args.heightRiverbed; if (shoreDist1 > -args.fadeDist) height += (args.heightLand - args.heightRiverbed) * (1 + shoreDist1 / args.fadeDist); else if (shoreDist2 < args.fadeDist) height += (args.heightLand - args.heightRiverbed) * (1 - shoreDist2 / args.fadeDist); if (args.minHeight === undefined || height < args.minHeight) g_Map.setHeight(vecPoint, height); if (args.waterFunc) args.waterFunc(vecPoint, height, riverFraction); } else if (args.landFunc) args.landFunc(vecPoint, shoreDist1, shoreDist2); } } /** * Helper function to create a meandering river. * It works the same as sin or cos function with the difference that it's period is 1 instead of 2 pi. */ function rndRiver(f, seed) { let rndRw = seed; for (let i = 0; i <= f; ++i) rndRw = 10 * (rndRw % 1); let rndRr = f % 1; let retVal = (Math.floor(f) % 2 ? -1 : 1) * rndRr * (rndRr - 1); let rndRe = Math.floor(rndRw) % 5; if (rndRe == 0) retVal *= 2.3 * (rndRr - 0.5) * (rndRr - 0.5); else if (rndRe == 1) retVal *= 2.6 * (rndRr - 0.3) * (rndRr - 0.7); else if (rndRe == 2) retVal *= 22 * (rndRr - 0.2) * (rndRr - 0.3) * (rndRr - 0.3) * (rndRr - 0.8); else if (rndRe == 3) retVal *= 180 * (rndRr - 0.2) * (rndRr - 0.2) * (rndRr - 0.4) * (rndRr - 0.6) * (rndRr - 0.6) * (rndRr - 0.8); else if (rndRe == 4) retVal *= 2.6 * (rndRr - 0.5) * (rndRr - 0.7); return retVal; } /** * Add small rivers with shallows starting at a central river ending at the map border, if the given Constraint is met. */ function createTributaryRivers(riverAngle, riverCount, riverWidth, heightRiverbed, heightRange, maxAngle, tributaryRiverTileClass, shallowTileClass, constraint) { log("Creating tributary rivers..."); let waviness = 0.4; let smoothness = scaleByMapSize(3, 12); let offset = 0.1; let tapering = 0.05; let heightShallow = -2; let mapSize = getMapSize(); let mapCenter = getMapCenter(); let mapBounds = getMapBounds(); let riverConstraint = avoidClasses(tributaryRiverTileClass, 3); if (shallowTileClass) riverConstraint = new AndConstraint([riverConstraint, avoidClasses(shallowTileClass, 2)]); for (let i = 0; i < riverCount; ++i) { // Determining tributary river location let searchCenter = new Vector2D(fractionToTiles(randFloat(tapering, 1 - tapering)), mapCenter.y); let sign = randBool() ? 1 : -1; let distanceVec = new Vector2D(0, sign * tapering); let searchStart = Vector2D.add(searchCenter, distanceVec).rotateAround(riverAngle, mapCenter); let searchEnd = Vector2D.sub(searchCenter, distanceVec).rotateAround(riverAngle, mapCenter); let start = findLocationInDirectionBasedOnHeight(searchStart, searchEnd, heightRange[0], heightRange[1], 4); if (!start) continue; start.round(); let end = Vector2D.add(mapCenter, new Vector2D(mapSize, 0).rotate(riverAngle - sign * randFloat(maxAngle, 2 * Math.PI - maxAngle))).round(); // Create river if (!createArea( new PathPlacer(start, end, riverWidth, waviness, smoothness, offset, tapering), [ new SmoothElevationPainter(ELEVATION_SET, heightRiverbed, 4), paintClass(tributaryRiverTileClass) ], new AndConstraint([constraint, riverConstraint]))) continue; // Create small puddles at the map border to ensure players being separated createArea( new ClumpPlacer(diskArea(riverWidth / 2), 0.95, 0.6, 10, end), new SmoothElevationPainter(ELEVATION_SET, heightRiverbed, 3), constraint); } // Create shallows if (shallowTileClass) for (let z of [0.25, 0.75]) createPassage({ "start": new Vector2D(mapBounds.left, fractionToTiles(z)).rotateAround(riverAngle, mapCenter), "end": new Vector2D(mapBounds.right, fractionToTiles(z)).rotateAround(riverAngle, mapCenter), "startWidth": scaleByMapSize(8, 12), "endWidth": scaleByMapSize(8, 12), "smoothWidth": 2, "startHeight": heightShallow, "endHeight": heightShallow, "maxHeight": heightShallow, "tileClass": shallowTileClass }); } /** * Creates a smooth, passable path between between start and end with the given startWidth and endWidth. * Paints the given tileclass and terrain. * * @property {Vector2D} start - Location of the passage. * @property {Vector2D} end * @property {number} startWidth - Size of the passage (perpendicular to the direction of the passage). * @property {number} endWidth * @property {number} [startHeight] - Fixed height to be used if the height at the location shouldn't be used. * @property {number} [endHeight] * @property {number} [maxHeight] - If given, do not touch any terrain above this height. * @property {number} smoothWidth - Number of tiles at the passage border to apply height interpolation. * @property {number} [tileClass] - Marks the passage with this tile class. * @property {string} [terrain] - Texture to be painted on the passage area. * @property {string} [edgeTerrain] - Texture to be painted on the borders of the passage. */ function createPassage(args) { let bound = x => Math.max(0, Math.min(Math.round(x), getMapSize())); - let startHeight = args.startHeight !== undefined ? args.startHeight : getHeight(bound(args.start.x), bound(args.start.y)); - let endHeight = args.endHeight !== undefined ? args.endHeight : getHeight(bound(args.end.x), bound(args.end.y)); + let startHeight = args.startHeight !== undefined ? args.startHeight : g_Map.getHeight(new Vector2D(bound(args.start.x), bound(args.start.y))); + let endHeight = args.endHeight !== undefined ? args.endHeight : g_Map.getHeight(new Vector2D(bound(args.end.x), bound(args.end.y))); let passageVec = Vector2D.sub(args.end, args.start); let widthDirection = passageVec.perpendicular().normalize(); let lengthStep = 1 / (2 * passageVec.length()); for (let lengthFraction = 0; lengthFraction <= 1; lengthFraction += lengthStep) { let locationLength = Vector2D.add(args.start, Vector2D.mult(passageVec, lengthFraction)); let halfPassageWidth = (args.startWidth + (args.endWidth - args.startWidth) * lengthFraction) / 2; let passageHeight = startHeight + (endHeight - startHeight) * lengthFraction; for (let stepWidth = -halfPassageWidth; stepWidth <= halfPassageWidth; stepWidth += 0.5) { let location = Vector2D.add(locationLength, Vector2D.mult(widthDirection, stepWidth)).round(); if (!g_Map.inMapBounds(location) || - args.maxHeight !== undefined && getHeight(location.x, location.y) > args.maxHeight) + args.maxHeight !== undefined && g_Map.getHeight(location) > args.maxHeight) continue; let smoothDistance = args.smoothWidth + Math.abs(stepWidth) - halfPassageWidth; g_Map.setHeight( location, smoothDistance > 0 ? - (getHeight(location.x, location.y) * smoothDistance + passageHeight / smoothDistance) / (smoothDistance + 1 / smoothDistance) : + (g_Map.getHeight(location) * smoothDistance + passageHeight / smoothDistance) / (smoothDistance + 1 / smoothDistance) : passageHeight); if (args.tileClass !== undefined) addToClass(location.x, location.y, args.tileClass); if (args.edgeTerrain && smoothDistance > 0) createTerrain(args.edgeTerrain).place(location); else if (args.terrain) createTerrain(args.terrain).place(location); } } } /** * Returns the first location between startPoint and endPoint that lies within the given heightrange. */ function findLocationInDirectionBasedOnHeight(startPoint, endPoint, minHeight, maxHeight, offset = 0) { let stepVec = Vector2D.sub(endPoint, startPoint); let distance = Math.ceil(stepVec.length()); stepVec.normalize(); for (let i = 0; i < distance; ++i) { let pos = Vector2D.add(startPoint, Vector2D.mult(stepVec, i)); let ipos = pos.clone().round(); if (g_Map.validH(ipos.x, ipos.y) && - getHeight(ipos.x, ipos.y) >= minHeight && - getHeight(ipos.x, ipos.y) <= maxHeight) + g_Map.getHeight(ipos) >= minHeight && + g_Map.getHeight(ipos) <= maxHeight) return pos.add(stepVec.mult(offset)); } return undefined; } Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/library.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen/library.js (revision 20982) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/library.js (revision 20983) @@ -1,424 +1,415 @@ const TERRAIN_SEPARATOR = "|"; const SEA_LEVEL = 20.0; const HEIGHT_UNITS_PER_METRE = 92; const MAP_BORDER_WIDTH = 3; const g_DamageTypes = new DamageTypes(); /** * 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; /** * Length of one tile of the terrain grid in metres. * Useful to transform footprint sizes of templates to the coordinate system used by getMapSize. */ const TERRAIN_TILE_SIZE = Engine.GetTerrainTileSize(); const MAX_HEIGHT = MAX_HEIGHT_RANGE - SEA_LEVEL; /** * Default angle for buildings. */ const BUILDING_ORIENTATION = -1/4 * Math.PI; const g_CivData = deepfreeze(loadCivFiles(false)); /** * Sets whether setHeight operates on the center of a tile or on the vertices. */ var TILE_CENTERED_HEIGHT_MAP = false; /** * Main RandomMap object. */ var g_Map; function InitMap(baseHeight, baseTerrain) { g_Map = new RandomMap(baseHeight, baseTerrain); } function ExportMap() { g_Map.ExportMap(); } function fractionToTiles(f) { return g_MapSettings.Size * f; } function tilesToFraction(t) { return t / g_MapSettings.Size; } function scaleByMapSize(min, max, minMapSize = 128, maxMapSize = 512) { return min + (max - min) * (g_MapSettings.Size - minMapSize) / (maxMapSize - minMapSize); } /** * Retries the given function with those arguments as often as specified. */ function retryPlacing(placeFunc, retryFactor, amount, getResult, behaveDeprecated = false) { let maxFail = amount * retryFactor; let results = []; let good = 0; let bad = 0; while (good < amount && bad <= maxFail) { let result = placeFunc(); if (result !== undefined || behaveDeprecated) { ++good; if (getResult) results.push(result); } else ++bad; } return getResult ? results : good; } /** * Sets the x and z property of the given object (typically a Placer or Group) to a random point on the map. * @param passableOnly - Should be true for entity placement and false for terrain or elevation operations. */ function randomizeCoordinates(obj, passableOnly) { let border = passableOnly ? MAP_BORDER_WIDTH : 0; if (g_MapSettings.CircularMap) { // Polar coordinates // Uniformly distributed on the disk let halfMapSize = g_Map.size / 2 - border; let r = halfMapSize * Math.sqrt(randFloat(0, 1)); let theta = randomAngle(); obj.x = Math.floor(r * Math.cos(theta)) + halfMapSize; obj.z = Math.floor(r * Math.sin(theta)) + halfMapSize; } else { // Rectangular coordinates obj.x = randIntExclusive(border, g_Map.size - border); obj.z = randIntExclusive(border, g_Map.size - border); } } /** * Sets the x and z property of the given JS object (typically a Placer or Group) to a random point of the area. */ function randomizeCoordinatesFromAreas(obj, areas) { let pt = pickRandom(pickRandom(areas).points); obj.x = pt.x; obj.z = pt.y; } // 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(group, player, constraint, amount, retryFactor = 10) { return createObjectGroups(group, player, constraint, amount, retryFactor, true); } function createObjectGroupsByAreasDeprecated(group, player, constraint, amount, retryFactor, areas) { return createObjectGroupsByAreas(group, 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) { let placeFunc = function() { randomizeCoordinates(centeredPlacer, false); return createArea(centeredPlacer, painter, constraint); }; return retryPlacing(placeFunc, retryFactor, amount, true, false); } /** * 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) { let placeFunc = function() { randomizeCoordinatesFromAreas(centeredPlacer, areas); return createArea(centeredPlacer, painter, constraint); }; return retryPlacing(placeFunc, retryFactor, amount, true, false); } /** * Attempts to place the given number of groups in random places of the map. * Returns the number of actually placed groups. */ function createObjectGroups(group, player, constraint, amount, retryFactor = 10, behaveDeprecated = false) { let placeFunc = function() { randomizeCoordinates(group, true); return createObjectGroup(group, player, constraint); }; return retryPlacing(placeFunc, 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(group, player, constraint, amount, retryFactor, areas, behaveDeprecated = false) { let placeFunc = function() { randomizeCoordinatesFromAreas(group, areas); return createObjectGroup(group, player, constraint); }; return retryPlacing(placeFunc, retryFactor, amount, false, behaveDeprecated); } function createTerrain(terrain) { return typeof terrain == "string" ? new SimpleTerrain(...terrain.split(TERRAIN_SEPARATOR)) : new RandomTerrain(terrain.map(t => createTerrain(t))); } function placeObject(x, z, type, player, angle) { if (g_Map.validT(x, z)) g_Map.addObject(new Entity(type, player, x, z, angle)); } function isCircularMap() { return !!g_MapSettings.CircularMap; } 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 Constraints and calls the Painters there. * Supports both Centered and Non-Centered Placers. */ function createArea(placer, painter, constraints) { let points = placer.place(new AndConstraint(constraints)); 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 Constraints * and sets the given player as the owner. */ function createObjectGroup(group, player, constraints) { return group.place(player, new AndConstraint(constraints)); } function getMapSize() { return g_Map.size; } function getMapCenter() { return deepfreeze(new Vector2D(g_Map.size / 2, g_Map.size / 2)); } function getMapBounds() { return deepfreeze({ "left": fractionToTiles(0), "right": fractionToTiles(1), "top": fractionToTiles(1), "bottom": fractionToTiles(0) }); } function isNomad() { return !!g_MapSettings.Nomad; } function getNumPlayers() { return g_MapSettings.PlayerData.length - 1; } function getCivCode(playerID) { return g_MapSettings.PlayerData[playerID].Civ; } function areAllies(playerID1, playerID2) { return ( g_MapSettings.PlayerData[playerID1].Team !== undefined && g_MapSettings.PlayerData[playerID2].Team !== undefined && g_MapSettings.PlayerData[playerID1].Team != -1 && g_MapSettings.PlayerData[playerID2].Team != -1 && g_MapSettings.PlayerData[playerID1].Team === g_MapSettings.PlayerData[playerID2].Team); } function getPlayerTeam(playerID) { if (g_MapSettings.PlayerData[playerID].Team === undefined) return -1; return g_MapSettings.PlayerData[playerID].Team; } -function getHeight(x, z) -{ - return g_Map.getHeight(x, z); -} - -/** - * 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); } Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/random_map.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen/random_map.js (revision 20982) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/random_map.js (revision 20983) @@ -1,385 +1,386 @@ /** * @file The RandomMap stores the elevation grid, terrain textures and entities that are exported to the engine. * * @param {Number} baseHeight - Initial elevation of the map * @param {String|Array} baseTerrain - One or more texture names */ function RandomMap(baseHeight, baseTerrain) { log("Initializing map..."); // Size must be 0 to 1024, divisible by patches this.size = g_MapSettings.Size; // Create name <-> id maps for textures this.nameToID = {}; this.IDToName = []; // Texture 2D array this.texture = []; for (let x = 0; x < this.size; ++x) { this.texture[x] = new Uint16Array(this.size); for (let z = 0; z < this.size; ++z) this.texture[x][z] = this.getTextureID( typeof baseTerrain == "string" ? baseTerrain : pickRandom(baseTerrain)); } // Create 2D arrays for terrain objects and areas this.terrainObjects = []; this.area = []; for (let i = 0; i < this.size; ++i) { // Area IDs this.area[i] = new Uint16Array(this.size); // Entities this.terrainObjects[i] = []; for (let j = 0; j < this.size; ++j) this.terrainObjects[i][j] = undefined; } // Create 2D array for heightmap let mapSize = this.size; if (!TILE_CENTERED_HEIGHT_MAP) ++mapSize; this.height = []; for (let i = 0; i < mapSize; ++i) { this.height[i] = new Float32Array(mapSize); for (let j = 0; j < mapSize; ++j) this.height[i][j] = baseHeight; } // Array of Entities this.objects = []; // Array of integers this.tileClasses = []; this.areaID = 0; // Starting entity ID, arbitrary number to leave some space for player entities this.entityCount = 150; } /** * Returns the ID of a texture name. * Creates a new ID if there isn't one assigned yet. */ RandomMap.prototype.getTextureID = function(texture) { if (texture in this.nameToID) return this.nameToID[texture]; let id = this.IDToName.length; this.nameToID[texture] = id; this.IDToName[id] = texture; return id; }; /** * Returns the next unused entityID. */ RandomMap.prototype.getEntityID = function() { return this.entityCount++; }; /** * Determines whether the given coordinates are within the given distance of the passable map area. * Should be used to restrict entity placement and path creation. */ RandomMap.prototype.validT = function(x, z, distance = 0) { distance += MAP_BORDER_WIDTH; if (g_MapSettings.CircularMap) { let halfSize = Math.floor(this.size / 2); return Math.round(Math.euclidDistance2D(x, z, halfSize, halfSize)) < halfSize - distance - 1; } else return x >= distance && z >= distance && x < this.size - distance && z < this.size - distance; }; /** * Determines whether the given coordinates are within the tile grid, passable or not. * Should be used to restrict texture painting. */ RandomMap.prototype.inMapBounds = function(position) { return position.x >= 0 && position.y >= 0 && position.x < this.size && position.y < this.size; }; /** * Determines whether the given coordinates are within the heightmap grid. * Should be used to restrict elevation changes. */ RandomMap.prototype.validH = function(x, z) { if (x < 0 || z < 0) return false; if (TILE_CENTERED_HEIGHT_MAP) return x < this.size && z < this.size; return x <= this.size && z <= this.size; }; /** * Tests if there is a tileclass with the given ID. */ RandomMap.prototype.validClass = function(tileClassID) { return tileClassID >= 0 && tileClassID < this.tileClasses.length; }; /** * Returns the name of the texture of the given tile. */ RandomMap.prototype.getTexture = function(x, z) { if (!this.validT(x, z)) throw new Error("getTexture: invalid tile position (" + x + ", " + z + ")"); return this.IDToName[this.texture[x][z]]; }; /** * Paints the given texture on the given tile. */ RandomMap.prototype.setTexture = function(x, z, texture) { if (!this.validT(x, z)) throw new Error("setTexture: invalid tile position (" + x + ", " + z + ")"); this.texture[x][z] = this.getTextureID(texture); }; -RandomMap.prototype.getHeight = function(x, z) +RandomMap.prototype.getHeight = function(position) { - if (!this.validH(x, z)) - throw new Error("getHeight: invalid vertex position (" + x + ", " + z + ")"); + if (!this.validH(position.x, position.y)) + throw new Error("getHeight: invalid vertex position " + uneval(position)); - return this.height[x][z]; + return this.height[position.x][position.y]; }; RandomMap.prototype.setHeight = function(position, height) { if (!this.validH(position.x, position.y)) throw new Error("setHeight: invalid vertex position " + uneval(position)); this.height[position.x][position.y] = height; }; /** * Returns the Entity that was painted by a Terrain class on the given tile or undefined otherwise. */ RandomMap.prototype.getTerrainObject = function(x, z) { if (!this.validT(x, z)) throw new Error("getTerrainObject: invalid tile position (" + x + ", " + z + ")"); return this.terrainObjects[x][z]; }; /** * Places the Entity on the given tile and allows to later replace it if the terrain was painted over. */ RandomMap.prototype.setTerrainObject = function(x, z, object) { if (!this.validT(x, z)) throw new Error("setTerrainObject: invalid tile position (" + x + ", " + z + ")"); this.terrainObjects[x][z] = object; }; /** * Adds the given Entity to the map at the location it defines. */ RandomMap.prototype.addObject = function(obj) { this.objects.push(obj); }; /** * Constructs a new Area object and informs the Map which points correspond to this area. */ RandomMap.prototype.createArea = function(points) { let areaID = ++this.areaID; for (let p of points) this.area[p.x][p.y] = areaID; return new Area(points, areaID); }; /** * Returns an unused tileclass ID. */ RandomMap.prototype.createTileClass = function() { let newID = this.tileClasses.length; this.tileClasses.push(new TileClass(this.size, newID)); return newID; }; /** * Retrieve interpolated height for arbitrary coordinates within the heightmap grid. */ RandomMap.prototype.getExactHeight = function(x, z) { let xi = Math.min(Math.floor(x), this.size); let zi = Math.min(Math.floor(z), this.size); let xf = x - xi; let zf = z - zi; let h00 = this.height[xi][zi]; let h01 = this.height[xi][zi + 1]; let h10 = this.height[xi + 1][zi]; let h11 = this.height[xi + 1][zi + 1]; return (1 - zf) * ((1 - xf) * h00 + xf * h10) + zf * ((1 - xf) * h01 + xf * h11); }; // Converts from the tile centered height map to the corner based height map, used when TILE_CENTERED_HEIGHT_MAP = true RandomMap.prototype.cornerHeight = function(x, z) { let count = 0; let sumHeight = 0; for (let dir of [[-1, -1], [-1, 0], [0, -1], [0, 0]]) if (this.validH(x + dir[0], z + dir[1])) { ++count; sumHeight += this.height[x + dir[0]][z + dir[1]]; } if (count == 0) return 0; return sumHeight / count; }; RandomMap.prototype.getAdjacentPoints = function(position) { let adjacentPositions = []; for (let x = -1; x <= 1; ++x) for (let z = -1; z <= 1; ++z) if (x || z ) { let adjacentPos = Vector2D.add(position, new Vector2D(x, z)).round(); if (this.inMapBounds(adjacentPos)) adjacentPositions.push(adjacentPos); } return adjacentPositions; } /** * Returns the average height of adjacent tiles, helpful for smoothing. */ RandomMap.prototype.getAverageHeight = function(position) { let adjacentPositions = this.getAdjacentPoints(position); if (!adjacentPositions.length) return 0; - return adjacentPositions.reduce((totalHeight, pos) => totalHeight + this.getHeight(pos.x, pos.y), 0) / adjacentPositions.length; + return adjacentPositions.reduce((totalHeight, pos) => totalHeight + this.getHeight(pos), 0) / adjacentPositions.length; } /** * Returns the steepness of the given location, defined as the average height difference of the adjacent tiles. */ RandomMap.prototype.getSlope = function(position) { let adjacentPositions = this.getAdjacentPoints(position); if (!adjacentPositions.length) return 0; - return adjacentPositions.reduce((totalSlope, adjacentPos) => totalSlope + Math.abs(this.getHeight(adjacentPos.x, adjacentPos.y) - this.getHeight(position.x, position.y)), 0) / adjacentPositions.length; + return adjacentPositions.reduce((totalSlope, adjacentPos) => + totalSlope + Math.abs(this.getHeight(adjacentPos) - this.getHeight(position)), 0) / adjacentPositions.length; } /** * Retrieve an array of all Entities placed on the map. */ RandomMap.prototype.exportEntityList = function() { // Change rotation from simple 2d to 3d befor giving to engine for (let obj of this.objects) obj.rotation.y = Math.PI / 2 - obj.rotation.y; // Terrain objects e.g. trees for (let x = 0; x < this.size; ++x) for (let z = 0; z < this.size; ++z) if (this.terrainObjects[x][z]) this.objects.push(this.terrainObjects[x][z]); log("Number of entities: " + this.objects.length); return this.objects; }; /** * Convert the elevation grid to a one-dimensional array. */ RandomMap.prototype.exportHeightData = function() { let heightmapSize = this.size + 1; let heightmap = new Uint16Array(Math.square(heightmapSize)); for (let x = 0; x < heightmapSize; ++x) for (let z = 0; z < heightmapSize; ++z) { let currentHeight = TILE_CENTERED_HEIGHT_MAP ? this.cornerHeight(x, z) : this.height[x][z]; // Correct height by SEA_LEVEL and prevent under/overflow in terrain data heightmap[z * heightmapSize + x] = Math.max(0, Math.min(0xFFFF, Math.floor((currentHeight + SEA_LEVEL) * HEIGHT_UNITS_PER_METRE))); } return heightmap; }; /** * Assemble terrain textures in a one-dimensional array. */ RandomMap.prototype.exportTerrainTextures = function() { let tileIndex = new Uint16Array(Math.square(this.size)); let tilePriority = new Uint16Array(Math.square(this.size)); for (let x = 0; x < this.size; ++x) for (let z = 0; z < this.size; ++z) { // TODO: For now just use the texture's index as priority, might want to do this another way tileIndex[z * this.size + x] = this.texture[x][z]; tilePriority[z * this.size + x] = this.texture[x][z]; } return { "index": tileIndex, "priority": tilePriority }; }; RandomMap.prototype.ExportMap = function() { log("Saving map..."); if (g_Environment.Water.WaterBody.Height === undefined) g_Environment.Water.WaterBody.Height = SEA_LEVEL - 0.1; Engine.ExportMap({ "entities": this.exportEntityList(), "height": this.exportHeightData(), "seaLevel": SEA_LEVEL, "size": this.size, "textureNames": this.IDToName, "tileData": this.exportTerrainTextures(), "Camera": g_Camera, "Environment": g_Environment }); } Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen2/gaia.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen2/gaia.js (revision 20982) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen2/gaia.js (revision 20983) @@ -1,1248 +1,1248 @@ var g_Props = { "barrels": "actor|props/special/eyecandy/barrels_buried.xml", "crate": "actor|props/special/eyecandy/crate_a.xml", "cart": "actor|props/special/eyecandy/handcart_1_broken.xml", "well": "actor|props/special/eyecandy/well_1_c.xml", "skeleton": "actor|props/special/eyecandy/skeleton.xml", }; /** * Create bluffs, i.e. a slope hill reachable from ground level. * Fill it with wood, mines, animals and decoratives. * * @param {Array} constraint - where to place them * @param {number} size - size of the bluffs (1.2 would be 120% of normal) * @param {number} deviation - degree of deviation from the defined size (0.2 would be 20% plus/minus) * @param {number} fill - size of map to fill (1.5 would be 150% of normal) * @param {number} baseHeight - elevation of the floor, making the bluff reachable */ function addBluffs(constraint, size, deviation, fill, baseHeight) { var constrastTerrain = g_Terrains.tier2Terrain; if (currentBiome() == "tropic") constrastTerrain = g_Terrains.dirt; if (currentBiome() == "autumn") constrastTerrain = g_Terrains.tier3Terrain; var count = fill * 15; var minSize = 5; var maxSize = 7; var elevation = 30; var spread = 100; for (var i = 0; i < count; ++i) { var offset = getRandomDeviation(size, deviation); var rendered = createAreas( new ChainPlacer(Math.floor(minSize * offset), Math.floor(maxSize * offset), Math.floor(spread * offset), 0.5), [ new LayeredPainter([g_Terrains.cliff, g_Terrains.mainTerrain, constrastTerrain], [2, 3]), new SmoothElevationPainter(ELEVATION_MODIFY, Math.floor(elevation * offset), 2), paintClass(g_TileClasses.bluff) ], constraint, 1); // Find the bounding box of the bluff if (rendered[0] === undefined) continue; var points = rendered[0].points; var corners = findCorners(points); // Seed an array the size of the bounding box var bb = createBoundingBox(points, corners); // Get a random starting position for the baseline and the endline var angle = randIntInclusive(0, 3); var opAngle = angle - 2; if (angle < 2) opAngle = angle + 2; // Find the edges of the bluff var baseLine; var endLine; // If we can't access the bluff, try different angles var retries = 0; var bluffCat = 2; while (bluffCat != 0 && retries < 5) { baseLine = findClearLine(bb, corners, angle, baseHeight); endLine = findClearLine(bb, corners, opAngle, baseHeight); bluffCat = unreachableBluff(bb, corners, baseLine, endLine); ++angle; if (angle > 3) angle = 0; opAngle = angle - 2; if (angle < 2) opAngle = angle + 2; ++retries; } // Inaccessible, turn it into a plateau if (bluffCat > 0) { removeBluff(points); continue; } // Create an entrance area by using a small margin var margin = 0.08; var ground = createTerrain(g_Terrains.mainTerrain); var slopeLength = (1 - margin) * Math.euclidDistance2D(baseLine.midX, baseLine.midZ, endLine.midX, endLine.midZ); // Adjust the height of each point in the bluff for (var p = 0; p < points.length; ++p) { var pt = points[p]; var position = new Vector2D(pt.x, pt.z); var dist = Math.abs(distanceOfPointFromLine( new Vector2D(baseLine.x1, baseLine.z1), new Vector2D(baseLine.x2, baseLine.z2), position)); - var curHeight = g_Map.getHeight(pt.x, pt.z); + var curHeight = g_Map.getHeight(position); var newHeight = curHeight - curHeight * (dist / slopeLength) - 2; newHeight = Math.max(newHeight, endLine.height); if (newHeight <= endLine.height + 2 && g_Map.validT(pt.x, pt.z) && g_Map.getTexture(pt.x, pt.z).indexOf('cliff') > -1) ground.place(position); g_Map.setHeight(position, newHeight); } // Smooth out the ground around the bluff fadeToGround(bb, corners.minX, corners.minZ, endLine.height); } addElements([ { "func": addHills, "avoid": [ g_TileClasses.hill, 3, g_TileClasses.player, 20, g_TileClasses.valley, 2, g_TileClasses.water, 2 ], "stay": [g_TileClasses.bluff, 3], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": g_AllAmounts } ]); addElements([ { "func": addLayeredPatches, "avoid": [ g_TileClasses.dirt, 5, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12, g_TileClasses.water, 3 ], "stay": [g_TileClasses.bluff, 5], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] } ]); addElements([ { "func": addDecoration, "avoid": [ g_TileClasses.forest, 2, g_TileClasses.player, 12, g_TileClasses.water, 3 ], "stay": [g_TileClasses.bluff, 5], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] } ]); addElements([ { "func": addProps, "avoid": [ g_TileClasses.forest, 2, g_TileClasses.player, 12, g_TileClasses.prop, 40, g_TileClasses.water, 3 ], "stay": [ g_TileClasses.bluff, 7, g_TileClasses.mountain, 7 ], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["scarce"] } ]); addElements(shuffleArray([ { "func": addForests, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 18, g_TileClasses.metal, 5, g_TileClasses.mountain, 5, g_TileClasses.player, 20, g_TileClasses.rock, 5, g_TileClasses.water, 2 ], "stay": [g_TileClasses.bluff, 6], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": ["normal", "many", "tons"] }, { "func": addMetal, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 5, g_TileClasses.mountain, 2, g_TileClasses.player, 50, g_TileClasses.rock, 15, g_TileClasses.metal, 40, g_TileClasses.water, 3 ], "stay": [g_TileClasses.bluff, 6], "sizes": ["normal"], "mixes": ["same"], "amounts": ["normal"] }, { "func": addStone, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 5, g_TileClasses.mountain, 2, g_TileClasses.player, 50, g_TileClasses.rock, 40, g_TileClasses.metal, 15, g_TileClasses.water, 3 ], "stay": [g_TileClasses.bluff, 6], "sizes": ["normal"], "mixes": ["same"], "amounts": ["normal"] } ])); let savanna = currentBiome() == "savanna"; addElements(shuffleArray([ { "func": addStragglerTrees, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 10, g_TileClasses.metal, 5, g_TileClasses.mountain, 1, g_TileClasses.player, 12, g_TileClasses.rock, 5, g_TileClasses.water, 5 ], "stay": [g_TileClasses.bluff, 6], "sizes": savanna ? ["big"] : g_AllSizes, "mixes": savanna ? ["varied"] : g_AllMixes, "amounts": savanna ? ["tons"] : ["normal", "many", "tons"] }, { "func": addAnimals, "avoid": [ g_TileClasses.animals, 20, g_TileClasses.forest, 5, g_TileClasses.mountain, 1, g_TileClasses.player, 20, g_TileClasses.rock, 5, g_TileClasses.metal, 5, g_TileClasses.water, 3 ], "stay": [g_TileClasses.bluff, 6], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": ["normal", "many", "tons"] }, { "func": addBerries, "avoid": [ g_TileClasses.berries, 50, g_TileClasses.forest, 5, g_TileClasses.metal, 10, g_TileClasses.mountain, 2, g_TileClasses.player, 20, g_TileClasses.rock, 10, g_TileClasses.water, 3 ], "stay": [g_TileClasses.bluff, 6], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": ["normal", "many", "tons"] } ])); } /** * Add grass, rocks and bushes. */ function addDecoration(constraint, size, deviation, fill) { var offset = getRandomDeviation(size, deviation); var decorations = [ [ new SimpleObject(g_Decoratives.rockMedium, offset, 3 * offset, 0, offset) ], [ new SimpleObject(g_Decoratives.rockLarge, offset, 2 * offset, 0, offset), new SimpleObject(g_Decoratives.rockMedium, offset, 3 * offset, 0, 2 * offset) ], [ new SimpleObject(g_Decoratives.grassShort, offset, 2 * offset, 0, offset) ], [ new SimpleObject(g_Decoratives.grass, 2 * offset, 4 * offset, 0, 1.8 * offset), new SimpleObject(g_Decoratives.grassShort, 3 * offset, 6 * offset, 1.2 * offset, 2.5 * offset) ], [ new SimpleObject(g_Decoratives.bushMedium, offset, 2 * offset, 0, 2 * offset), new SimpleObject(g_Decoratives.bushSmall, 2 * offset, 4 * offset, 0, 2 * offset) ] ]; var baseCount = 1; if (currentBiome() == "tropic") baseCount = 8; var counts = [ scaleByMapSize(16, 262), scaleByMapSize(8, 131), baseCount * scaleByMapSize(13, 200), baseCount * scaleByMapSize(13, 200), baseCount * scaleByMapSize(13, 200) ]; for (var i = 0; i < decorations.length; ++i) { var decorCount = Math.floor(counts[i] * fill); var group = new SimpleGroup(decorations[i], true); createObjectGroupsDeprecated(group, 0, constraint, decorCount, 5); } } /** * Create varying elevations. * * @param {Array} constraint - avoid/stay-classes * * @param {Object} el - the element to be rendered, for example: * "class": g_TileClasses.hill, * "painter": [g_Terrains.mainTerrain, g_Terrains.mainTerrain], * "size": 1, * "deviation": 0.2, * "fill": 1, * "count": scaleByMapSize(4, 8), * "minSize": Math.floor(scaleByMapSize(3, 8)), * "maxSize": Math.floor(scaleByMapSize(5, 10)), * "spread": Math.floor(scaleByMapSize(10, 20)), * "minElevation": 6, * "maxElevation": 12, * "steepness": 1.5 */ function addElevation(constraint, el) { var count = el.fill * el.count; var minSize = el.minSize; var maxSize = el.maxSize; var spread = el.spread; var elType = ELEVATION_MODIFY; if (el.class == g_TileClasses.water) elType = ELEVATION_SET; var widths = []; // Allow for shore and cliff rendering for (var s = el.painter.length; s > 2; --s) widths.push(1); for (var i = 0; i < count; ++i) { var elevation = randIntExclusive(el.minElevation, el.maxElevation); var smooth = Math.floor(elevation / el.steepness); var offset = getRandomDeviation(el.size, el.deviation); var pMinSize = Math.floor(minSize * offset); var pMaxSize = Math.floor(maxSize * offset); var pSpread = Math.floor(spread * offset); var pSmooth = Math.abs(Math.floor(smooth * offset)); var pElevation = Math.floor(elevation * offset); pElevation = Math.max(el.minElevation, Math.min(pElevation, el.maxElevation)); pMinSize = Math.min(pMinSize, pMaxSize); pMaxSize = Math.min(pMaxSize, el.maxSize); pMinSize = Math.max(pMaxSize, el.minSize); pSmooth = Math.max(pSmooth, 1); createAreas( new ChainPlacer(pMinSize, pMaxSize, pSpread, 0.5), [ new LayeredPainter(el.painter, [widths.concat(pSmooth)]), new SmoothElevationPainter(elType, pElevation, pSmooth), paintClass(el.class) ], constraint, 1); } } /** * Create rolling hills. */ function addHills(constraint, size, deviation, fill) { addElevation(constraint, { "class": g_TileClasses.hill, "painter": [g_Terrains.mainTerrain, g_Terrains.mainTerrain], "size": size, "deviation": deviation, "fill": fill, "count": 8, "minSize": 5, "maxSize": 8, "spread": 20, "minElevation": 6, "maxElevation": 12, "steepness": 1.5 }); } /** * Create random lakes with fish in it. */ function addLakes(constraint, size, deviation, fill) { var lakeTile = g_Terrains.water; if (currentBiome() == "temperate" || currentBiome() == "tropic") lakeTile = g_Terrains.dirt; if (currentBiome() == "mediterranean") lakeTile = g_Terrains.tier2Terrain; if (currentBiome() == "autumn") lakeTile = g_Terrains.shore; addElevation(constraint, { "class": g_TileClasses.water, "painter": [lakeTile, lakeTile], "size": size, "deviation": deviation, "fill": fill, "count": 6, "minSize": 7, "maxSize": 9, "spread": 70, "minElevation": -15, "maxElevation": -2, "steepness": 1.5 }); addElements([ { "func": addFish, "avoid": [ g_TileClasses.fish, 12, g_TileClasses.hill, 8, g_TileClasses.mountain, 8, g_TileClasses.player, 8 ], "stay": [g_TileClasses.water, 7], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": ["normal", "many", "tons"] } ]); var group = new SimpleGroup([new SimpleObject(g_Decoratives.rockMedium, 1, 3, 1, 3)], true, g_TileClasses.dirt); createObjectGroupsDeprecated(group, 0, [stayClasses(g_TileClasses.water, 1), borderClasses(g_TileClasses.water, 4, 3)], 1000, 100); group = new SimpleGroup([new SimpleObject(g_Decoratives.reeds, 10, 15, 1, 3), new SimpleObject(g_Decoratives.rockMedium, 1, 3, 1, 3)], true, g_TileClasses.dirt); createObjectGroupsDeprecated(group, 0, [stayClasses(g_TileClasses.water, 2), borderClasses(g_TileClasses.water, 4, 3)], 1000, 100); } /** * Universal function to create layered patches. */ function addLayeredPatches(constraint, size, deviation, fill) { var minRadius = 1; var maxRadius = Math.floor(scaleByMapSize(3, 5)); var count = fill * scaleByMapSize(15, 45); var patchSizes = [ scaleByMapSize(3, 6), scaleByMapSize(5, 10), scaleByMapSize(8, 21) ]; for (let patchSize of patchSizes) { var offset = getRandomDeviation(size, deviation); var patchMinRadius = Math.floor(minRadius * offset); var patchMaxRadius = Math.floor(maxRadius * offset); createAreas( new ChainPlacer(Math.min(patchMinRadius, patchMaxRadius), patchMaxRadius, Math.floor(patchSize * offset), 0.5), [ new LayeredPainter( [ [g_Terrains.mainTerrain, g_Terrains.tier1Terrain], [g_Terrains.tier1Terrain, g_Terrains.tier2Terrain], [g_Terrains.tier2Terrain, g_Terrains.tier3Terrain], [g_Terrains.tier4Terrain] ], [1, 1]), paintClass(g_TileClasses.dirt) ], constraint, count * offset); } } /** * Create steep mountains. */ function addMountains(constraint, size, deviation, fill) { addElevation(constraint, { "class": g_TileClasses.mountain, "painter": [g_Terrains.cliff, g_Terrains.hill], "size": size, "deviation": deviation, "fill": fill, "count": 8, "minSize": 2, "maxSize": 4, "spread": 100, "minElevation": 100, "maxElevation": 120, "steepness": 4 }); } /** * Create plateaus. */ function addPlateaus(constraint, size, deviation, fill) { var plateauTile = g_Terrains.dirt; if (currentBiome() == "snowy") plateauTile = g_Terrains.tier1Terrain; if (currentBiome() == "alpine" || currentBiome() == "savanna") plateauTile = g_Terrains.tier2Terrain; if (currentBiome() == "autumn") plateauTile = g_Terrains.tier4Terrain; addElevation(constraint, { "class": g_TileClasses.plateau, "painter": [g_Terrains.cliff, plateauTile], "size": size, "deviation": deviation, "fill": fill, "count": 15, "minSize": 2, "maxSize": 4, "spread": 200, "minElevation": 20, "maxElevation": 30, "steepness": 8 }); for (var i = 0; i < 40; ++i) { var hillElevation = randIntInclusive(4, 18); createAreas( new ChainPlacer(3, 15, 1, 0.5), [ new LayeredPainter([plateauTile, plateauTile], [3]), new SmoothElevationPainter(ELEVATION_MODIFY, hillElevation, hillElevation - 2), paintClass(g_TileClasses.hill) ], [ avoidClasses(g_TileClasses.hill, 7), stayClasses(g_TileClasses.plateau, 7) ], 1); } addElements([ { "func": addDecoration, "avoid": [ g_TileClasses.dirt, 15, g_TileClasses.forest, 2, g_TileClasses.player, 12, g_TileClasses.water, 3 ], "stay": [g_TileClasses.plateau, 8], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["tons"] }, { "func": addProps, "avoid": [ g_TileClasses.forest, 2, g_TileClasses.player, 12, g_TileClasses.prop, 40, g_TileClasses.water, 3 ], "stay": [g_TileClasses.plateau, 8], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["scarce"] } ]); } /** * Place less usual decoratives like barrels or crates. */ function addProps(constraint, size, deviation, fill) { var offset = getRandomDeviation(size, deviation); var props = [ [ new SimpleObject(g_Props.skeleton, offset, 5 * offset, 0, 3 * offset + 2), ], [ new SimpleObject(g_Props.barrels, offset, 2 * offset, 2, 3 * offset + 2), new SimpleObject(g_Props.cart, 0, offset, 5, 2.5 * offset + 5), new SimpleObject(g_Props.crate, offset, 2 * offset, 2, 2 * offset + 2), new SimpleObject(g_Props.well, 0, 1, 2, 2 * offset + 2) ] ]; var baseCount = 1; var counts = [ scaleByMapSize(16, 262), scaleByMapSize(8, 131), baseCount * scaleByMapSize(13, 200), baseCount * scaleByMapSize(13, 200), baseCount * scaleByMapSize(13, 200) ]; // Add small props for (var i = 0; i < props.length; ++i) { var propCount = Math.floor(counts[i] * fill); var group = new SimpleGroup(props[i], true); createObjectGroupsDeprecated(group, 0, constraint, propCount, 5); } // Add decorative trees var trees = new SimpleObject(g_Decoratives.tree, 5 * offset, 30 * offset, 2, 3 * offset + 10); createObjectGroupsDeprecated(new SimpleGroup([trees], true), 0, constraint, counts[0] * 5 * fill, 5); } function addValleys(constraint, size, deviation, fill, baseHeight) { if (baseHeight < 6) return; let minElevation = Math.max(-baseHeight, 1 - baseHeight / (size * (deviation + 1))); var valleySlope = g_Terrains.tier1Terrain; var valleyFloor = g_Terrains.tier4Terrain; if (currentBiome() == "desert") { valleySlope = g_Terrains.tier3Terrain; valleyFloor = g_Terrains.dirt; } if (currentBiome() == "mediterranean") { valleySlope = g_Terrains.tier2Terrain; valleyFloor = g_Terrains.dirt; } if (currentBiome() == "alpine" || currentBiome() == "savanna") valleyFloor = g_Terrains.tier2Terrain; if (currentBiome() == "tropic") valleySlope = g_Terrains.dirt; if (currentBiome() == "autumn") valleyFloor = g_Terrains.tier3Terrain; addElevation(constraint, { "class": g_TileClasses.valley, "painter": [valleySlope, valleyFloor], "size": size, "deviation": deviation, "fill": fill, "count": 8, "minSize": 5, "maxSize": 8, "spread": 30, "minElevation": minElevation, "maxElevation": -2, "steepness": 4 }); } /** * Create huntable animals. */ function addAnimals(constraint, size, deviation, fill) { var groupOffset = getRandomDeviation(size, deviation); var animals = [ [new SimpleObject(g_Gaia.mainHuntableAnimal, 5 * groupOffset, 7 * groupOffset, 0, 4 * groupOffset)], [new SimpleObject(g_Gaia.secondaryHuntableAnimal, 2 * groupOffset, 3 * groupOffset, 0, 2 * groupOffset)] ]; for (let animal of animals) createObjectGroupsDeprecated( new SimpleGroup(animal, true, g_TileClasses.animals), 0, constraint, Math.floor(30 * fill), 50); } function addBerries(constraint, size, deviation, fill) { let groupOffset = getRandomDeviation(size, deviation); createObjectGroupsDeprecated( new SimpleGroup([new SimpleObject(g_Gaia.fruitBush, 5 * groupOffset, 5 * groupOffset, 0, 3 * groupOffset)], true, g_TileClasses.berries), 0, constraint, Math.floor(50 * fill), 40); } function addFish(constraint, size, deviation, fill) { var groupOffset = getRandomDeviation(size, deviation); var fishes = [ [new SimpleObject(g_Gaia.fish, groupOffset, 2 * groupOffset, 0, 2 * groupOffset)], [new SimpleObject(g_Gaia.fish, 2 * groupOffset, 4 * groupOffset, 10 * groupOffset, 20 * groupOffset)] ]; for (let fish of fishes) createObjectGroupsDeprecated( new SimpleGroup(fish, true, g_TileClasses.fish), 0, constraint, Math.floor(40 * fill), 50); } function addForests(constraint, size, deviation, fill) { if (currentBiome() == "savanna") return; let treeTypes = [ [ g_Terrains.forestFloor2 + TERRAIN_SEPARATOR + g_Gaia.tree1, g_Terrains.forestFloor2 + TERRAIN_SEPARATOR + g_Gaia.tree2, g_Terrains.forestFloor2 ], [ g_Terrains.forestFloor1 + TERRAIN_SEPARATOR + g_Gaia.tree4, g_Terrains.forestFloor1 + TERRAIN_SEPARATOR + g_Gaia.tree5, g_Terrains.forestFloor1 ] ]; let forestTypes = [ [ [g_Terrains.forestFloor2, g_Terrains.mainTerrain, treeTypes[0]], [g_Terrains.forestFloor2, treeTypes[0]] ], [ [g_Terrains.forestFloor2, g_Terrains.mainTerrain, treeTypes[1]], [g_Terrains.forestFloor1, treeTypes[1]]], [ [g_Terrains.forestFloor1, g_Terrains.mainTerrain, treeTypes[0]], [g_Terrains.forestFloor2, treeTypes[0]]], [ [g_Terrains.forestFloor1, g_Terrains.mainTerrain, treeTypes[1]], [g_Terrains.forestFloor1, treeTypes[1]] ] ]; for (let forestType of forestTypes) { let offset = getRandomDeviation(size, deviation); createAreas( new ChainPlacer(1, Math.floor(scaleByMapSize(3, 5) * offset), Math.floor(50 * offset), 0.5), [ new LayeredPainter(forestType, [2]), paintClass(g_TileClasses.forest) ], constraint, 10 * fill); } } function addMetal(constraint, size, deviation, fill) { var offset = getRandomDeviation(size, deviation); createObjectGroupsDeprecated( new SimpleGroup([new SimpleObject(g_Gaia.metalLarge, offset, offset, 0, 4 * offset)], true, g_TileClasses.metal), 0, constraint, 1 + 20 * fill, 100); } function addSmallMetal(constraint, size, mixes, amounts) { let deviation = getRandomDeviation(size, mixes); createObjectGroupsDeprecated( new SimpleGroup([new SimpleObject(g_Gaia.metalSmall, 2 * deviation, 5 * deviation, deviation, 3 * deviation)], true, g_TileClasses.metal), 0, constraint, 1 + 20 * amounts, 100); } /** * Create stone mines. */ function addStone(constraint, size, deviation, fill) { var offset = getRandomDeviation(size, deviation); var mines = [ [ new SimpleObject(g_Gaia.stoneSmall, 0, 2 * offset, 0, 4 * offset), new SimpleObject(g_Gaia.stoneLarge, offset, offset, 0, 4 * offset) ], [ new SimpleObject(g_Gaia.stoneSmall, 2 * offset, 5 * offset, offset, 3 * offset) ] ]; for (let mine of mines) createObjectGroupsDeprecated( new SimpleGroup(mine, true, g_TileClasses.rock), 0, constraint, 1 + 20 * fill, 100); } /** * Create straggler trees. */ function addStragglerTrees(constraint, size, deviation, fill) { // Ensure minimum distribution on african biome if (currentBiome() == "savanna") { fill = Math.max(fill, 2); size = Math.max(size, 1); } var trees = [g_Gaia.tree1, g_Gaia.tree2, g_Gaia.tree3, g_Gaia.tree4]; var treesPerPlayer = 40; var playerBonus = Math.max(1, (getNumPlayers() - 3) / 2); var offset = getRandomDeviation(size, deviation); var treeCount = treesPerPlayer * playerBonus * fill; var totalTrees = scaleByMapSize(treeCount, treeCount); var count = Math.floor(totalTrees / trees.length) * fill; var min = offset; var max = 4 * offset; var minDist = offset; var maxDist = 5 * offset; // More trees for the african biome if (currentBiome() == "savanna") { min = 3 * offset; max = 5 * offset; minDist = 2 * offset + 1; maxDist = 3 * offset + 2; } for (var i = 0; i < trees.length; ++i) { var treesMax = max; // Don't clump fruit trees if (i == 2 && (currentBiome() == "desert" || currentBiome() == "mediterranean")) treesMax = 1; min = Math.min(min, treesMax); var group = new SimpleGroup([new SimpleObject(trees[i], min, treesMax, minDist, maxDist)], true, g_TileClasses.forest); createObjectGroupsDeprecated(group, 0, constraint, count); } } /////////// // Terrain Helpers /////////// /** * Determine if the endline of the bluff is within the tilemap. * * @returns {Number} 0 if the bluff is reachable, otherwise a positive number */ function unreachableBluff(bb, corners, baseLine, endLine) { // If we couldn't find a slope line if (typeof baseLine.midX === "undefined" || typeof endLine.midX === "undefined") return 1; // If the end points aren't on the tilemap if (!g_Map.validT(endLine.x1, endLine.z1) && !g_Map.validT(endLine.x2, endLine.z2)) return 2; var minTilesInGroup = 1; var insideBluff = false; var outsideBluff = false; // If there aren't enough points in each row for (var x = 0; x < bb.length; ++x) { var count = 0; for (var z = 0; z < bb[x].length; ++z) { if (!bb[x][z].isFeature) continue; var valid = g_Map.validT(x + corners.minX, z + corners.minZ); if (valid) ++count; if (!insideBluff && valid) insideBluff = true; if (outsideBluff && valid) return 3; } // We're expecting the end of the bluff if (insideBluff && count < minTilesInGroup) outsideBluff = true; } var insideBluff = false; var outsideBluff = false; // If there aren't enough points in each column for (var z = 0; z < bb[0].length; ++z) { var count = 0; for (var x = 0; x < bb.length; ++x) { if (!bb[x][z].isFeature) continue; var valid = g_Map.validT(x + corners.minX, z + corners.minZ); if (valid) ++count; if (!insideBluff && valid) insideBluff = true; if (outsideBluff && valid) return 3; } // We're expecting the end of the bluff if (insideBluff && count < minTilesInGroup) outsideBluff = true; } // Bluff is reachable return 0; } /** * Remove the bluff class and turn it into a plateau. */ function removeBluff(points) { for (var i = 0; i < points.length; ++i) addToClass(points[i].x, points[i].z, g_TileClasses.mountain); } /** * Create an array of points the fill a bounding box around a terrain feature. */ function createBoundingBox(points, corners) { var bb = []; var width = corners.maxX - corners.minX + 1; var length = corners.maxZ - corners.minZ + 1; for (var w = 0; w < width; ++w) { bb[w] = []; for (var l = 0; l < length; ++l) { - var curHeight = g_Map.getHeight(w + corners.minX, l + corners.minZ); + var curHeight = g_Map.getHeight(new Vector2D(w + corners.minX, l + corners.minZ)); bb[w][l] = { "height": curHeight, "isFeature": false }; } } // Define the coordinates that represent the bluff for (var p = 0; p < points.length; ++p) { var pt = points[p]; bb[pt.x - corners.minX][pt.z - corners.minZ].isFeature = true; } return bb; } /** * Flattens the ground touching a terrain feature. */ function fadeToGround(bb, minX, minZ, elevation) { var ground = createTerrain(g_Terrains.mainTerrain); for (var x = 0; x < bb.length; ++x) for (var z = 0; z < bb[x].length; ++z) { var pt = bb[x][z]; if (!pt.isFeature && nextToFeature(bb, x, z)) { let position = new Vector2D(x + minX, z + minZ); g_Map.setHeight(position, g_Map.getAverageHeight(position)); ground.place(position); } } } /** * Find a 45 degree line in a bounding box that does not intersect any terrain feature. */ function findClearLine(bb, corners, angle, baseHeight) { // Angle - 0: northwest; 1: northeast; 2: southeast; 3: southwest var z = corners.maxZ; var xOffset = -1; var zOffset = -1; switch(angle) { case 1: xOffset = 1; break; case 2: xOffset = 1; zOffset = 1; z = corners.minZ; break; case 3: zOffset = 1; z = corners.minZ; break; } var clearLine = {}; for (var x = corners.minX; x <= corners.maxX; ++x) { var x2 = x; var z2 = z; var clear = true; while (x2 >= corners.minX && x2 <= corners.maxX && z2 >= corners.minZ && z2 <= corners.maxZ) { var bp = bb[x2 - corners.minX][z2 - corners.minZ]; if (bp.isFeature && g_Map.validT(x2, z2)) { clear = false; break; } x2 = x2 + xOffset; z2 = z2 + zOffset; } if (clear) { var lastX = x2 - xOffset; var lastZ = z2 - zOffset; var midX = Math.floor((x + lastX) / 2); var midZ = Math.floor((z + lastZ) / 2); clearLine = { "x1": x, "z1": z, "x2": lastX, "z2": lastZ, "midX": midX, "midZ": midZ, "height": baseHeight }; } if (clear && (angle == 1 || angle == 2)) break; if (!clear && (angle == 0 || angle == 3)) break; } return clearLine; } /** * Returns the corners of a bounding box. */ function findCorners(points) { // Find the bounding box of the terrain feature var mapSize = getMapSize(); var minX = mapSize + 1; var minZ = mapSize + 1; var maxX = -1; var maxZ = -1; for (var p = 0; p < points.length; ++p) { var pt = points[p]; minX = Math.min(pt.x, minX); minZ = Math.min(pt.z, minZ); maxX = Math.max(pt.x, maxX); maxZ = Math.max(pt.z, maxZ); } return { "minX": minX, "minZ": minZ, "maxX": maxX, "maxZ": maxZ }; } /** * Determines if a point in a bounding box array is next to a terrain feature. */ function nextToFeature(bb, x, z) { for (var xOffset = -1; xOffset <= 1; ++xOffset) for (var zOffset = -1; zOffset <= 1; ++zOffset) { var thisX = x + xOffset; var thisZ = z + zOffset; if (thisX < 0 || thisX >= bb.length || thisZ < 0 || thisZ >= bb[x].length || thisX == 0 && thisZ == 0) continue; if (bb[thisX][thisZ].isFeature) return true; } return false; } /** * Returns a number within a random deviation of a base number. */ function getRandomDeviation(base, deviation) { return base + randFloat(-1, 1) * Math.min(base, deviation); } /** * Import a given digital elevation model. * Scale it to the mapsize and paint the textures specified by coordinate on it. * * @return the ratio of heightmap tiles per map size tiles */ function paintHeightmap(mapName, func = undefined) { /** * @property heightmap - An array with a square number of heights. * @property tilemap - The IDs of the palletmap to be painted for each heightmap tile. * @property pallet - The tile texture names used by the tilemap. */ let mapData = Engine.ReadJSONFile("maps/random/" + mapName + ".hmap"); let mapSize = getMapSize(); // Width of the map in terrain tiles let hmSize = Math.sqrt(mapData.heightmap.length); let scale = hmSize / (mapSize + 1); // There are mapSize + 1 vertices (each 1 tile is surrounded by 2x2 vertices) for (let x = 0; x <= mapSize; ++x) for (let y = 0; y <= mapSize; ++y) { let position = new Vector2D(x, y); let hmPoint = Vector2D.mult(position, scale); let hmTile = new Vector2D(Math.floor(hmPoint.x), Math.floor(hmPoint.y)); let shift = new Vector2D(0, 0); if (hmTile.x == 0) shift.x = 1; else if (hmTile.x == hmSize - 1) shift.x = - 2; else if (hmTile.x == hmSize - 2) shift.x = - 1; if (hmTile.y == 0) shift.y = 1; else if (hmTile.y == hmSize - 1) shift.y = - 2; else if (hmTile.y == hmSize - 2) shift.y = - 1; let neighbors = []; for (let localXi = 0; localXi < 4; ++localXi) for (let localYi = 0; localYi < 4; ++localYi) neighbors.push(mapData.heightmap[(hmTile.x + localXi + shift.x - 1) * hmSize + (hmTile.y + localYi + shift.y - 1)]); g_Map.setHeight(position, bicubicInterpolation(hmPoint.x - hmTile.x - shift.x, hmPoint.y - hmTile.y - shift.y, ...neighbors) / scale); if (x < mapSize && y < mapSize) { let i = hmTile.x * hmSize + hmTile.y; let tile = mapData.pallet[mapData.tilemap[i]]; createTerrain(tile).place(position); if (func) func(tile, x, y); } } return scale; }