Index: ps/trunk/binaries/data/mods/public/maps/random/belgian_uplands.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/belgian_uplands.js (revision 20901) +++ ps/trunk/binaries/data/mods/public/maps/random/belgian_uplands.js (revision 20902) @@ -1,406 +1,406 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("heightmap"); InitMap(g_MapSettings.BaseHeight, g_MapSettings.BaseTerrain); 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++) setHeight(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 waterHeight = -MIN_HEIGHT + heightRange.min + averageWaterCoverage * (heightRange.max - heightRange.min); var waterHeightAdjusted = waterHeight + MIN_HEIGHT; setWaterHeight(waterHeight); // Prepare terrain texture by height placement var textueByHeight = []; // Deep water textueByHeight.push({"upperHeightLimit": heightRange.min + 1/3 * (waterHeightAdjusted - 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 * (waterHeightAdjusted - heightRange.min), "terrain": terrains}); // Flat Water textueByHeight.push({"upperHeightLimit": heightRange.min + 3/3 * (waterHeightAdjusted - 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": waterHeightAdjusted + 1/6 * (heightRange.max - waterHeightAdjusted), "terrain": terrains}); // Juicy grass near bog textueByHeight.push({"upperHeightLimit": waterHeightAdjusted + 2/6 * (heightRange.max - waterHeightAdjusted), "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": waterHeightAdjusted + 3/6 * (heightRange.max - waterHeightAdjusted), "terrain": ["temp_grass", "temp_grass_b", "temp_grass_c", "temp_grass_mossy"]}); // Long grass near forest border textueByHeight.push({"upperHeightLimit": waterHeightAdjusted + 4/6 * (heightRange.max - waterHeightAdjusted), "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": waterHeightAdjusted + 5/6 * (heightRange.max - waterHeightAdjusted), "terrain": terrains}); // Unpassable woods textueByHeight.push({"upperHeightLimit": waterHeightAdjusted + 6/6 * (heightRange.max - waterHeightAdjusted), "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); 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); if (testHeight <= lowerHeightLimit || testHeight >= upperHeightLimit) { isPossible = false; break; } } if (isPossible) possibleStartPositions.push([x, y]); - // placeTerrain(x, y, "blue"); // For debug reasons. Plz don't remove. // Only works properly for 1 loop } } // 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); 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++) { var textureMinHeight = heightRange.min; for (var i = 0; i < textueByHeight.length; i++) { if (getHeight(x, y) >= textureMinHeight && getHeight(x, y) <= textueByHeight[i].upperHeightLimit) { - placeTerrain(x, y, textueByHeight[i].terrain); + createTerrain(textueByHeight[i].terrain).place(x, y); + // 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/english_channel.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/english_channel.js (revision 20901) +++ ps/trunk/binaries/data/mods/public/maps/random/english_channel.js (revision 20902) @@ -1,262 +1,262 @@ Engine.LoadLibrary("rmgen"); const tGrass = ["temp_grass", "temp_grass", "temp_grass_d"]; const tGrassDForest = "temp_plants_bog"; const tGrassA = "temp_grass_plants"; const tGrassB = "temp_plants_bog"; const tGrassC = "temp_mud_a"; const tHill = ["temp_highlands", "temp_grass_long_b"]; const tCliff = ["temp_cliff_a", "temp_cliff_b"]; const tRoad = "temp_road"; const tRoadWild = "temp_road_overgrown"; const tGrassPatchBlend = "temp_grass_long_b"; const tGrassPatch = ["temp_grass_d", "temp_grass_clovers"]; const tShore = "temp_dirt_gravel"; const tWater = "temp_dirt_gravel_b"; const oBeech = "gaia/flora_tree_euro_beech"; const oPoplar = "gaia/flora_tree_poplar"; const oApple = "gaia/flora_tree_apple"; const oOak = "gaia/flora_tree_oak"; const oBerryBush = "gaia/flora_bush_berry"; const oDeer = "gaia/fauna_deer"; const oFish = "gaia/fauna_fish"; const oGoat = "gaia/fauna_goat"; const oBoar = "gaia/fauna_boar"; const oStoneLarge = "gaia/geology_stonemine_temperate_quarry"; const oStoneSmall = "gaia/geology_stone_temperate"; const oMetalLarge = "gaia/geology_metal_temperate_slabs"; const aGrass = "actor|props/flora/grass_soft_large_tall.xml"; const aGrassShort = "actor|props/flora/grass_soft_large.xml"; const aRockLarge = "actor|geology/stone_granite_large.xml"; const aRockMedium = "actor|geology/stone_granite_med.xml"; const aBushMedium = "actor|props/flora/bush_medit_me_lush.xml"; const aBushSmall = "actor|props/flora/bush_medit_sm_lush.xml"; const aReeds = "actor|props/flora/reeds_pond_lush_a.xml"; const aLillies = "actor|props/flora/water_lillies.xml"; const pForestD = [tGrassDForest + TERRAIN_SEPARATOR + oBeech, tGrassDForest]; InitMap(g_MapSettings.BaseHeight, g_MapSettings.BaseTerrain); const numPlayers = getNumPlayers(); const mapCenter = getMapCenter(); const mapBounds = getMapBounds(); 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 landHeight = 3; var waterHeight = -4; placePlayerBases({ "PlayerPlacement": playerPlacementRiver(Math.PI / 2, fractionToTiles(0.6)), "PlayerTileClass": clPlayer, "BaseResourceClass": clBaseResource, "CityPatch": { "outerTerrain": tRoadWild, "innerTerrain": tRoad }, "Chicken": { }, "Berries": { "template": oBerryBush }, "Mines": { "types": [ { "template": oMetalLarge }, { "template": oStoneLarge } ] }, "Trees": { "template": oOak, "count": 2 }, "Decoratives": { "template": aGrassShort } }); Engine.SetProgress(10); paintRiver({ "parallel": false, "start": new Vector2D(mapBounds.left, mapCenter.y), "end": new Vector2D(mapBounds.right, mapCenter.y), "width": fractionToTiles(0.25), "fadeDist": scaleByMapSize(3, 10), "deviation": 0, "waterHeight": waterHeight, "landHeight": landHeight, "meanderShort": 20, "meanderLong": 0, "waterFunc": (ix, iz, height, riverFraction) => { - placeTerrain(ix, iz, height < -1.5 ? tWater : tShore); + createTerrain(height < -1.5 ? tWater : tShore).place(ix, iz); }, "landFunc": (ix, iz, shoreDist1, shoreDist2) => { setHeight(ix, iz, landHeight + 0.1); } }); Engine.SetProgress(20); createTributaryRivers( true, randIntInclusive(9, scaleByMapSize(13, 21)), scaleByMapSize(10, 20), waterHeight, [-6, -1.5], Math.PI / 5, clWater, clShallow, avoidClasses(clPlayer, 8, clBaseResource, 4)); paintTerrainBasedOnHeight(-5, 1, 1, tWater); paintTerrainBasedOnHeight(1, landHeight, 1, tShore); paintTileClassBasedOnHeight(-6, 0.5, 1, clWater); Engine.SetProgress(25); createBumps(avoidClasses(clWater, 5, clPlayer, 20)); Engine.SetProgress(30); createHills([tCliff, tCliff, tHill], avoidClasses(clPlayer, 20, clHill, 15, clWater, 5), clHill, scaleByMapSize(1, 4) * numPlayers); Engine.SetProgress(50); var [forestTrees, stragglerTrees] = getTreeCounts(500, 3000, 0.7); createForests( [tGrass, tGrassDForest, tGrassDForest, pForestD, pForestD], avoidClasses(clPlayer, 20, clForest, 17, clHill, 0, clWater, 6), clForest, forestTrees); Engine.SetProgress(70); log("Creating dirt patches..."); createLayeredPatches( [scaleByMapSize(3, 6), scaleByMapSize(5, 10), scaleByMapSize(8, 21)], [[tGrass,tGrassA], tGrassB, [tGrassB,tGrassC]], [1,1], avoidClasses(clWater, 1, clForest, 0, clHill, 0, clDirt, 5, clPlayer, 6), scaleByMapSize(15, 45), clDirt); log("Creating grass patches..."); createPatches( [scaleByMapSize(2, 4), scaleByMapSize(3, 7), scaleByMapSize(5, 15)], [tGrassPatchBlend, tGrassPatch], [1], avoidClasses(clWater, 1, clForest, 0, clHill, 0, clDirt, 5, clPlayer, 6), scaleByMapSize(15, 45), clDirt); Engine.SetProgress(80); 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, 2, clForest, 1, clPlayer, 20, clRock, 10, clHill, 2), clRock); log("Creating metal mines..."); createMines( [ [new SimpleObject(oMetalLarge, 1,1, 0,4)] ], avoidClasses(clWater, 2, clForest, 1, clPlayer, 20, clMetal, 10, clRock, 5, clHill, 2), clMetal ); Engine.SetProgress(85); createDecoration( [ [new SimpleObject(aRockMedium, 1, 3, 0, 1)], [new SimpleObject(aRockLarge, 1, 2, 0, 1), new SimpleObject(aRockMedium, 1, 3, 0, 2)], [new SimpleObject(aGrassShort, 1, 2, 0, 1)], [new SimpleObject(aGrass, 2, 4, 0, 1.8), new SimpleObject(aGrassShort, 3, 6, 1.2, 2.5)], [new SimpleObject(aBushMedium, 1, 2, 0, 2), new SimpleObject(aBushSmall, 2, 4, 0, 2)] ], [ scaleByMapSize(16, 262), scaleByMapSize(8, 131), scaleByMapSize(13, 200), scaleByMapSize(13, 200), scaleByMapSize(13, 200) ], avoidClasses(clWater, 1, 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)); createFood( [ [new SimpleObject(oDeer, 5, 7, 0, 4)], [new SimpleObject(oGoat, 2, 3, 0, 2)], [new SimpleObject(oBoar, 2, 3, 0, 2)] ], [ 3 * numPlayers, 3 * numPlayers, 3 * numPlayers ], avoidClasses(clWater, 1, clForest, 0, clPlayer, 20, clHill, 0, clFood, 15), clFood); createFood( [ [new SimpleObject(oBerryBush, 5, 7, 0, 4)] ], [ randIntInclusive(1, 4) * numPlayers + 2 ], avoidClasses(clWater, 3, clForest, 0, clPlayer, 20, clHill, 1, clFood, 10), clFood); createFood( [ [new SimpleObject(oFish, 2, 3, 0, 2)] ], [scaleByMapSize(3, 25) * numPlayers], [avoidClasses(clFood, 6), stayClasses(clWater, 4)], clFood); createStragglerTrees( [oBeech, oPoplar, oApple], avoidClasses(clWater, 1, clForest, 1, clHill, 1, clPlayer, 8, clMetal, 6, clRock, 6), clForest, stragglerTrees); setSkySet("cirrus"); setWaterColor(0.114, 0.192, 0.463); setWaterTint(0.255, 0.361, 0.651); setWaterWaviness(3.0); setWaterType("ocean"); setWaterMurkiness(0.83); setFogThickness(0.35); setFogFactor(0.55); setPPEffect("hdr"); setPPSaturation(0.62); setPPContrast(0.62); setPPBloom(0.37); placePlayersNomad(clPlayer, avoidClasses(clWater, 4, clForest, 1, clMetal, 4, clRock, 4, clHill, 4, clFood, 2)); ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/fortress.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/fortress.js (revision 20901) +++ ps/trunk/binaries/data/mods/public/maps/random/fortress.js (revision 20902) @@ -1,304 +1,304 @@ Engine.LoadLibrary("rmgen"); const tGrass = ["temp_grass_aut", "temp_grass_aut", "temp_grass_d_aut"]; const tForestFloor = "temp_grass_aut"; const tGrassA = "temp_grass_plants_aut"; const tGrassB = "temp_grass_b_aut"; const tGrassC = "temp_grass_c_aut"; const tHill = ["temp_highlands_aut", "temp_grass_long_b_aut"]; const tCliff = ["temp_cliff_a", "temp_cliff_b"]; const tRoad = "temp_road_aut"; const tGrassPatch = "temp_grass_plants_aut"; const tShore = "temp_plants_bog_aut"; const tWater = "temp_mud_a"; const oBeech = "gaia/flora_tree_euro_beech_aut"; const oOak = "gaia/flora_tree_oak_aut"; const oPine = "gaia/flora_tree_pine"; const oDeer = "gaia/fauna_deer"; const oFish = "gaia/fauna_fish"; const oSheep = "gaia/fauna_rabbit"; const oBerryBush = "gaia/flora_bush_berry"; const oStoneLarge = "gaia/geology_stonemine_temperate_quarry"; const oStoneSmall = "gaia/geology_stone_temperate"; const oMetalLarge = "gaia/geology_metal_temperate_slabs"; const oFoodTreasure = "gaia/special_treasure_food_bin"; const oWoodTreasure = "gaia/special_treasure_wood"; const oStoneTreasure = "gaia/special_treasure_stone"; const oMetalTreasure = "gaia/special_treasure_metal"; const aGrass = "actor|props/flora/grass_soft_dry_small_tall.xml"; const aGrassShort = "actor|props/flora/grass_soft_dry_large.xml"; const aRockLarge = "actor|geology/stone_granite_med.xml"; const aRockMedium = "actor|geology/stone_granite_med.xml"; const aReeds = "actor|props/flora/reeds_pond_dry.xml"; const aLillies = "actor|props/flora/water_lillies.xml"; const aBushMedium = "actor|props/flora/bush_medit_me_dry.xml"; const aBushSmall = "actor|props/flora/bush_medit_sm_dry.xml"; const pForestD = [tForestFloor + TERRAIN_SEPARATOR + oBeech, tForestFloor]; const pForestO = [tForestFloor + TERRAIN_SEPARATOR + oOak, tForestFloor]; const pForestP = [tForestFloor + TERRAIN_SEPARATOR + oPine, tForestFloor]; InitMap(g_MapSettings.BaseHeight, g_MapSettings.BaseTerrain); const numPlayers = getNumPlayers(); const mapSize = getMapSize(); 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 treasures = [ { "template": oFoodTreasure, "distance": 5 }, { "template": oWoodTreasure, "distance": 5 }, { "template": oMetalTreasure, "distance": 3 }, { "template": oMetalTreasure, "distance": 2 }, ]; var [playerIDs, playerPosition] = playerPlacementCircle(fractionToTiles(0.35)); for (let i = 0; i < numPlayers; ++i) { if (isNomad()) break; log("Creating base for player " + playerIDs[i] + "..."); for (let dist of [6, 8]) { let ents = getStartingEntities(playerIDs[i]); if (dist == 8) ents = ents.filter(ent => ent.Template.indexOf("female") != -1 || ent.Template.indexOf("infantry") != -1); placeStartingEntities(playerPosition[i], playerIDs[i], ents, dist); } log("Creating treasure for player " + playerIDs[i] + "..."); let bbAngle = BUILDING_ORIENTATION; for (let treasure of treasures) { let position = Vector2D.add(playerPosition[i], new Vector2D(10, 0).rotate(-bbAngle)) createObjectGroup(new SimpleGroup([new SimpleObject(oFoodTreasure, 5, 5, 0, 2)], true, clBaseResource, position.x, position.y), 0); bbAngle += Math.PI / 2; } log("Painting ground texture for player " + playerIDs[i] + "..."); var civ = getCivCode(playerIDs[i]); var tilesSize = civ == "cart" ? 24 : 22; const minBoundX = Math.max(0, playerPosition[i].x - tilesSize); const minBoundY = Math.max(0, playerPosition[i].y - tilesSize); const maxBoundX = Math.min(mapSize, playerPosition[i].x + tilesSize); const maxBoundY = Math.min(mapSize, playerPosition[i].y + tilesSize); for (var tx = minBoundX; tx < maxBoundX; ++tx) for (var ty = minBoundY; ty < maxBoundY; ++ty) { var unboundSumOfXY = tx + ty - minBoundX - minBoundY; if (unboundSumOfXY > tilesSize && unboundSumOfXY < tilesSize * 3 && tx - ty + minBoundY - minBoundX < tilesSize && ty - tx - minBoundY + minBoundX < tilesSize) { - placeTerrain(tx, ty, tRoad); + createTerrain(tRoad).place(tx, ty); addToClass(tx, ty, clPlayer); } } log("Placing fortress for player " + playerIDs[i] + "..."); if (civ == "brit" || civ == "gaul" || civ == "iber") { var wall = ["gate", "tower", "long", "cornerIn", "long", "barracks", "tower", "long", "tower", "house", "long", "cornerIn", "long", "house", "tower", "gate", "tower", "house", "long", "cornerIn", "long", "house", "tower", "long", "tower", "house", "long", "cornerIn", "long", "house", "tower"]; } else { var wall = ["gate", "tower", "long", "cornerIn", "long", "barracks", "tower", "long", "tower", "long", "cornerIn", "long", "house", "tower", "gate", "tower", "long", "cornerIn", "long", "house", "tower", "long", "tower", "long", "cornerIn", "long", "house", "tower"]; } placeCustomFortress(playerPosition[i].x, playerPosition[i].y, new Fortress("Spahbod", wall), civ, playerIDs[i], -Math.PI/4); } log("Creating lakes..."); var numLakes = Math.round(scaleByMapSize(1,4) * numPlayers); var waterAreas = createAreas( new ClumpPlacer(scaleByMapSize(100,250), 0.8, 0.1, 10), [ new LayeredPainter([tShore, tWater, tWater], [1, 1]), new SmoothElevationPainter(ELEVATION_SET, -4, 3), paintClass(clWater) ], avoidClasses(clPlayer, 7, clWater, 20), numLakes); Engine.SetProgress(15); log("Creating reeds..."); createObjectGroupsByAreasDeprecated( new SimpleGroup([new SimpleObject(aReeds, 5,10, 0,4), new SimpleObject(aLillies, 0,1, 0,4)], true), 0, [borderClasses(clWater, 3, 0), stayClasses(clWater, 1)], numLakes, 100, waterAreas); Engine.SetProgress(25); log("Creating fish..."); createObjectGroupsByAreasDeprecated( new SimpleGroup( [new SimpleObject(oFish, 1,1, 0,1)], true, clFood ), 0, [stayClasses(clWater, 4), avoidClasses(clFood, 8)], numLakes / 4, 50, waterAreas); Engine.SetProgress(30); createBumps(avoidClasses(clWater, 2, clPlayer, 5)); Engine.SetProgress(35); log("Creating hills..."); createHills([tCliff, tCliff, tHill], avoidClasses(clPlayer, 5, clWater, 5, clHill, 15), clHill, scaleByMapSize(1, 4) * numPlayers); Engine.SetProgress(40); log("Creating forests..."); var [forestTrees, stragglerTrees] = getTreeCounts(500, 2500, 0.7); var types = [ [[tForestFloor, tGrass, pForestD], [tForestFloor, pForestD]], [[tForestFloor, tGrass, pForestO], [tForestFloor, pForestO]], [[tForestFloor, tGrass, pForestP], [tForestFloor, pForestP]] ]; var size = forestTrees / (scaleByMapSize(3,6) * numPlayers); var num = Math.floor(size / types.length); for (let type of types) createAreas( new ChainPlacer(1, Math.floor(scaleByMapSize(3, 5)), forestTrees / num, 0.5), [ new LayeredPainter(type, [2]), paintClass(clForest) ], avoidClasses(clPlayer, 5, clWater, 3, clForest, 15, clHill, 1), num); Engine.SetProgress(50); log("Creating dirt patches..."); createLayeredPatches( [scaleByMapSize(3, 6), scaleByMapSize(5, 10), scaleByMapSize(8, 21)], [[tGrass,tGrassA],[tGrassA,tGrassB], [tGrassB,tGrassC]], [1,1], avoidClasses(clWater, 1, clForest, 0, clHill, 0, clDirt, 5, clPlayer, 1), scaleByMapSize(15, 45), clDirt); Engine.SetProgress(55); log("Creating grass patches..."); createPatches( [scaleByMapSize(2, 4), scaleByMapSize(3, 7), scaleByMapSize(5, 15)], tGrassPatch, avoidClasses(clWater, 1, clForest, 0, clHill, 0, clDirt, 5, clPlayer, 1), scaleByMapSize(15, 45), clDirt); Engine.SetProgress(60); 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, 0, clForest, 1, clPlayer, 5, clRock, 10, clHill, 1), clRock); Engine.SetProgress(65); log("Creating metal mines..."); createMines( [ [new SimpleObject(oMetalLarge, 1,1, 0,4)] ], avoidClasses(clWater, 0, clForest, 1, clPlayer, 5, clMetal, 10, clRock, 5, clHill, 1), clMetal ); Engine.SetProgress(70); createDecoration( [ [new SimpleObject(aRockMedium, 1, 3, 0, 1)], [new SimpleObject(aRockLarge, 1, 2, 0, 1), new SimpleObject(aRockMedium, 1, 3, 0, 2)], [new SimpleObject(aGrassShort, 1, 2, 0, 1)], [new SimpleObject(aGrass, 2, 4, 0, 1.8), new SimpleObject(aGrassShort, 3, 6, 1.2, 2.5)], [new SimpleObject(aBushMedium, 1, 2, 0, 2), new SimpleObject(aBushSmall, 2, 4, 0, 2)] ], [ scaleByMapSize(16, 262), scaleByMapSize(8, 131), scaleByMapSize(13, 200), scaleByMapSize(13, 200), scaleByMapSize(13, 200) ], avoidClasses(clWater, 0, clForest, 0, clPlayer, 1, clHill, 0)); Engine.SetProgress(80); createFood( [ [new SimpleObject(oSheep, 2, 3, 0, 2)], [new SimpleObject(oDeer, 5, 7, 0, 4)] ], [ 3 * numPlayers, 3 * numPlayers ], avoidClasses(clWater, 0, clForest, 0, clPlayer, 6, clHill, 1, clFood, 20), clFood); Engine.SetProgress(85); createFood( [ [new SimpleObject(oBerryBush, 5, 7, 0, 4)] ], [ randIntInclusive(1, 4) * numPlayers + 2 ], avoidClasses(clWater, 2, clForest, 0, clPlayer, 6, clHill, 1, clFood, 10), clFood); Engine.SetProgress(90); createStragglerTrees( [oOak, oBeech, oPine], avoidClasses(clWater, 1, clForest, 1, clHill, 1, clPlayer, 1, clMetal, 6, clRock, 6), clForest, stragglerTrees); Engine.SetProgress(95); placePlayersNomad(clPlayer, avoidClasses(clWater, 2, clHill, 2, clForest, 1, clMetal, 4, clRock, 4, clHill, 4, clFood, 2)); setSkySet("sunny"); setWaterColor(0.157, 0.149, 0.443); setWaterTint(0.443,0.42,0.824); setWaterWaviness(2.0); setWaterType("lake"); setWaterMurkiness(0.83); setFogFactor(0.35); setFogThickness(0.22); setFogColor(0.82,0.82, 0.73); setPPSaturation(0.56); setPPContrast(0.56); setPPBloom(0.38); setPPEffect("hdr"); ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/guadalquivir_river.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/guadalquivir_river.js (revision 20901) +++ ps/trunk/binaries/data/mods/public/maps/random/guadalquivir_river.js (revision 20902) @@ -1,282 +1,282 @@ Engine.LoadLibrary("rmgen"); const tGrass = ["medit_grass_field_a", "medit_grass_field_b"]; const tForestFloorC = "medit_plants_dirt"; const tForestFloorP = "medit_grass_shrubs"; const tGrassA = "medit_grass_field_b"; const tGrassB = "medit_grass_field_brown"; const tGrassC = "medit_grass_field_dry"; const tRoad = "medit_city_tile"; const tRoadWild = "medit_city_tile"; const tGrassPatch = "medit_grass_shrubs"; const tShore = "sand_grass_25"; const tWater = "medit_sand_wet"; const oPoplar = "gaia/flora_tree_poplar"; const oApple = "gaia/flora_tree_apple"; const oCarob = "gaia/flora_tree_carob"; const oBerryBush = "gaia/flora_bush_berry"; 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 aGrass = "actor|props/flora/grass_soft_large_tall.xml"; const aGrassShort = "actor|props/flora/grass_soft_large.xml"; const aReeds = "actor|props/flora/reeds_pond_lush_a.xml"; const aLillies = "actor|props/flora/water_lillies.xml"; const aRockLarge = "actor|geology/stone_granite_large.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 pForestP = [tForestFloorP + TERRAIN_SEPARATOR + oPoplar, tForestFloorP]; const pForestC = [tForestFloorC + TERRAIN_SEPARATOR + oCarob, tForestFloorC]; InitMap(g_MapSettings.BaseHeight, g_MapSettings.BaseTerrain); const numPlayers = getNumPlayers(); const mapSize = getMapSize(); const mapCenter = getMapCenter(); const mapBounds = getMapBounds(); var clPlayer = createTileClass(); var clForest = createTileClass(); var clDirt = createTileClass(); var clRock = createTileClass(); var clMetal = createTileClass(); var clFood = createTileClass(); var clBaseResource = createTileClass(); var clLand = createTileClass(); var clRiver = createTileClass(); var clShallow = createTileClass(); var landHeight = 3; var shoreHeight = 2; var shallowHeight = -1.5; var waterHeight = -3; log("Create the continent body"); var continentCenter = new Vector2D(fractionToTiles(0.5), fractionToTiles(0.7)).round(); createArea( new ChainPlacer( 2, Math.floor(scaleByMapSize(5, 12)), Math.floor(scaleByMapSize(60, 700)), 1, continentCenter.x, continentCenter.y, 0, [Math.floor(fractionToTiles(0.49))]), [ new LayeredPainter([tGrass, tGrass, tGrass], [4, 2]), new SmoothElevationPainter(ELEVATION_SET, landHeight, 4), paintClass(clLand) ]); placePlayerBases({ "PlayerPlacement": [primeSortAllPlayers(), ...playerPlacementCustomAngle( fractionToTiles(0.35), continentCenter, i => Math.PI * (-0.46 / numPlayers * (i + i % 2) - (i % 2) / 2))], "PlayerTileClass": clPlayer, "BaseResourceClass": clBaseResource, "Walls": false, "CityPatch": { "outerTerrain": tRoadWild, "innerTerrain": tRoad }, "Chicken": { }, "Berries": { "template": oBerryBush }, "Mines": { "types": [ { "template": oMetalLarge }, { "template": oStoneLarge } ] }, "Trees": { "template": oPoplar, "count": 2 }, "Decoratives": { "template": aGrassShort } }); Engine.SetProgress(20); paintRiver({ "parallel": true, "constraint": stayClasses(clLand, 0), "start": new Vector2D(mapCenter.x, mapBounds.top), "end": new Vector2D(mapCenter.x, mapBounds.bottom), "width": fractionToTiles(0.07), "fadeDist": scaleByMapSize(3, 12), "deviation": 1, "waterHeight": waterHeight, "landHeight": shoreHeight, "meanderShort": 12, "meanderLong": 0, "waterFunc": (ix, iz, height, z) => { addToClass(ix, iz, clRiver); - placeTerrain(ix, iz, tWater); + createTerrain(tWater).place(ix, iz); if (height < shallowHeight && ( z > 0.3 && z < 0.4 || z > 0.5 && z < 0.6 || z > 0.7 && z < 0.8)) { setHeight(ix, iz, shallowHeight); addToClass(ix, iz, clShallow); } } }); paintTerrainBasedOnHeight(1, 3, 0, tShore); paintTerrainBasedOnHeight(-8, 1, 2, tWater); createBumps([avoidClasses(clPlayer, 20, clRiver, 1), stayClasses(clLand, 3)]); var [forestTrees, stragglerTrees] = getTreeCounts(500, 3000, 0.7); createForests( [tGrass, tForestFloorP, tForestFloorC, pForestC, pForestP], [avoidClasses(clPlayer, 20, clForest, 17, clRiver, 1), stayClasses(clLand, 7)], clForest, forestTrees); Engine.SetProgress(50); log("Creating dirt patches..."); createLayeredPatches( [scaleByMapSize(3, 6), scaleByMapSize(5, 10), scaleByMapSize(8, 21)], [[tGrass,tGrassA],[tGrassA,tGrassB], [tGrassB,tGrassC]], [1,1], [avoidClasses(clForest, 0, clDirt, 3, clPlayer, 8, clRiver, 1), stayClasses(clLand, 7)], scaleByMapSize(15, 45), clDirt); log("Creating grass patches..."); createPatches( [scaleByMapSize(2, 4), scaleByMapSize(3, 7), scaleByMapSize(5, 15)], tGrassPatch, [avoidClasses(clForest, 0, clDirt, 3, clPlayer, 8, clRiver, 1), stayClasses(clLand, 7)], 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(clForest, 1, clPlayer, 20, clRock, 10, clRiver, 1), stayClasses(clLand, 5)], clRock); log("Creating metal mines..."); createMines( [ [new SimpleObject(oMetalLarge, 1,1, 0,4)] ], [avoidClasses(clForest, 1, clPlayer, 20, clMetal, 10, clRock, 5, clRiver, 1), stayClasses(clLand, 5)], clMetal ); Engine.SetProgress(65); createDecoration( [ [new SimpleObject(aRockMedium, 1, 3, 0, 1)], [new SimpleObject(aRockLarge, 1, 2, 0, 1), new SimpleObject(aRockMedium, 1, 3, 0, 2)], [new SimpleObject(aGrassShort, 1, 2, 0, 1)], [new SimpleObject(aGrass, 2, 4, 0, 1.8), new SimpleObject(aGrassShort, 3, 6, 1.2, 2.5)], [new SimpleObject(aBushMedium, 1, 2, 0, 2), new SimpleObject(aBushSmall, 2, 4, 0, 2)] ], [ scaleByMapSize(16, 262), scaleByMapSize(8, 131), scaleByMapSize(13, 200), scaleByMapSize(13, 200), scaleByMapSize(13, 200) ], [avoidClasses(clPlayer, 1, clDirt, 1, clRiver, 1), stayClasses(clLand, 6)]); log("Create water decoration in the shallow parts"); 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(oDeer, 5, 7, 0, 4)], [new SimpleObject(oSheep, 2, 3, 0, 2)] ], [ 3 * numPlayers, 3 * numPlayers ], [avoidClasses(clForest, 0, clPlayer, 20, clFood, 20, clRiver, 1), stayClasses(clLand, 3)], clFood); createFood( [ [new SimpleObject(oBerryBush, 5, 7, 0, 4)] ], [ randIntInclusive(1, 4) * numPlayers + 2 ], [avoidClasses(clForest, 0, clPlayer, 20, clFood, 10, clRiver, 1), stayClasses(clLand, 3)], clFood); createFood( [ [new SimpleObject(oFish, 2, 3, 0, 2)] ], [ 25 * numPlayers ], avoidClasses(clLand, 2, clRiver, 1), clFood); Engine.SetProgress(85); createStragglerTrees( [oPoplar, oCarob, oApple], [avoidClasses(clForest, 1, clPlayer, 9, clMetal, 6, clRock, 6, clRiver, 1), stayClasses(clLand, 7)], clForest, stragglerTrees); placePlayersNomad( clPlayer, new AndConstraint([ stayClasses(clLand, 4), avoidClasses(clForest, 1, clMetal, 4, clRock, 4, clFood, 2)])); setSkySet("cumulus"); setWaterColor(0.2,0.312,0.522); setWaterTint(0.1,0.1,0.8); setWaterWaviness(4.0); setWaterType("lake"); setWaterMurkiness(0.73); setFogFactor(0.3); setFogThickness(0.25); setPPEffect("hdr"); setPPContrast(0.62); setPPSaturation(0.51); setPPBloom(0.12); ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/hyrcanian_shores.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/hyrcanian_shores.js (revision 20901) +++ ps/trunk/binaries/data/mods/public/maps/random/hyrcanian_shores.js (revision 20902) @@ -1,334 +1,331 @@ Engine.LoadLibrary("rmgen"); const tGrass = ["temp_grass_clovers"]; const tGrassPForest = "temp_plants_bog"; const tGrassDForest = "alpine_dirt_grass_50"; const tCliff = ["temp_cliff_a", "temp_cliff_b"]; const tGrassA = "temp_grass_d"; const tGrassB = "temp_grass_c"; const tGrassC = "temp_grass_clovers_2"; const tHill = ["temp_highlands", "temp_grass_long_b"]; const tRoad = "temp_road"; const tRoadWild = "temp_road_overgrown"; const tGrassPatch = "temp_grass_plants"; const tShore = "medit_sand_wet"; const tWater = "medit_sand_wet"; const oPoplar = "gaia/flora_tree_poplar"; const oPalm = "gaia/flora_tree_cretan_date_palm_short"; const oApple = "gaia/flora_tree_apple"; const oOak = "gaia/flora_tree_oak"; const oBerryBush = "gaia/flora_bush_berry"; const oDeer = "gaia/fauna_deer"; const oFish = "gaia/fauna_fish"; const oGoat = "gaia/fauna_goat"; const oBoar = "gaia/fauna_boar"; const oStoneLarge = "gaia/geology_stonemine_temperate_quarry"; const oStoneSmall = "gaia/geology_stone_temperate"; const oMetalLarge = "gaia/geology_metal_temperate_slabs"; const aGrass = "actor|props/flora/grass_soft_large_tall.xml"; const aGrassShort = "actor|props/flora/grass_soft_large.xml"; const aRockLarge = "actor|geology/stone_granite_large.xml"; const aRockMedium = "actor|geology/stone_granite_med.xml"; const aBushMedium = "actor|props/flora/bush_medit_me_lush.xml"; const aBushSmall = "actor|props/flora/bush_medit_sm_lush.xml"; const pForestD = [tGrassDForest + TERRAIN_SEPARATOR + oPoplar, tGrassDForest]; const pForestP = [tGrassPForest + TERRAIN_SEPARATOR + oOak, tGrassPForest]; InitMap(g_MapSettings.BaseHeight, g_MapSettings.BaseTerrain); const mapCenter = getMapCenter(); const mapBounds = getMapBounds(); const numPlayers = getNumPlayers(); 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 clHighlands = createTileClass(); var highlandsPosition = fractionToTiles(0.25); placePlayerBases({ "PlayerPlacement": playerPlacementLine(true, mapCenter, fractionToTiles(0.2)), "PlayerTileClass": clPlayer, "BaseResourceClass": clBaseResource, "CityPatch": { "outerTerrain": tRoadWild, "innerTerrain": tRoad }, "Chicken": { }, "Berries": { "template": oBerryBush }, "Mines": { "types": [ { "template": oMetalLarge }, { "template": oStoneLarge } ] }, "Trees": { "template": oOak, "count": 2 }, "Decoratives": { "template": aGrassShort } }); Engine.SetProgress(10); paintRiver({ "parallel": true, "start": new Vector2D(mapBounds.left, mapBounds.top), "end": new Vector2D(mapBounds.right, mapBounds.top), "width": fractionToTiles(0.5), "fadeDist": scaleByMapSize(6, 25), "deviation": 0, "waterHeight": -3, "landHeight": 1, "meanderShort": 20, "meanderLong": 0, "waterFunc": (ix, iz, height, riverFraction) => { if (height < 0) addToClass(ix, iz, clWater); - if (height < -1.5) - placeTerrain(ix, iz, tWater); - else - placeTerrain(ix, iz, tShore); + createTerrain(height < -1.5 ? tWater : tShore).place(ix, iz); } }); Engine.SetProgress(20); log("Creating fish..."); for (let i = 0; i < scaleByMapSize(10, 20); ++i) createObjectGroupsDeprecated( new SimpleGroup([new SimpleObject(oFish, 2, 3, 0, 2)], true, clFood), 0, [stayClasses(clWater, 2), avoidClasses(clFood, 3)], numPlayers, 50); Engine.SetProgress(25); createArea( new RectPlacer(mapBounds.left, mapBounds.bottom + highlandsPosition, mapBounds.right, mapBounds.bottom), paintClass(clHighlands)); log("Creating bumps..."); createAreas( new ClumpPlacer(scaleByMapSize(20, 50), 0.3, 0.06, 1), new SmoothElevationPainter(ELEVATION_MODIFY, 4, 3), stayClasses(clHighlands, 1), scaleByMapSize(300, 600)); Engine.SetProgress(30); log("Creating hills..."); createAreas( new ClumpPlacer(scaleByMapSize(20, 150), 0.2, 0.1, 1), [ new LayeredPainter([tCliff, tHill], [2]), new SmoothElevationPainter(ELEVATION_SET, 15, 2), paintClass(clHill) ], avoidClasses(clPlayer, 20, clWater, 5, clHill, 15, clHighlands, 5), scaleByMapSize(1, 4) * numPlayers); Engine.SetProgress(35); log("Creating mainland forests..."); var [forestTrees, stragglerTrees] = getTreeCounts(500, 2500, 0.7); var types = [ [[tGrassDForest, tGrass, pForestD], [tGrassDForest, pForestD]] ]; var size = forestTrees * 1.3 / (scaleByMapSize(2,8) * numPlayers); var num = Math.floor(0.7 * size / types.length); for (let type of types) createAreas( new ClumpPlacer(forestTrees / num, 0.1, 0.1, 1), [ new LayeredPainter(type, [2]), paintClass(clForest) ], avoidClasses(clPlayer, 20, clWater, 3, clForest, 10, clHill, 0, clBaseResource, 3), num); Engine.SetProgress(45); log("Creating highland forests..."); var types = [ [[tGrassDForest, tGrass, pForestP], [tGrassDForest, pForestP]] ]; var size = forestTrees / (scaleByMapSize(2,8) * numPlayers); var num = Math.floor(size / types.length); for (let type of types) createAreas( new ClumpPlacer(forestTrees / num, 0.1, 0.1, 1), [ new LayeredPainter(type, [2]), paintClass(clForest) ], avoidClasses(clPlayer, 20, clWater, 3, clForest, 2, clHill, 0), num); Engine.SetProgress(70); log("Creating dirt 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, tGrassA], [tGrassA, tGrassB], [tGrassB, tGrassC]], [1, 1]), paintClass(clDirt) ], avoidClasses(clWater, 1, clForest, 0, clHill, 0, clDirt, 5, clPlayer, 4), scaleByMapSize(15, 45)); Engine.SetProgress(75); 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 LayeredPainter([tGrassC, tGrassPatch], [2]), avoidClasses(clWater, 1, clForest, 0, clHill, 0, clDirt, 5, clPlayer, 6, clBaseResource, 6), scaleByMapSize(15, 45)); Engine.SetProgress(80); log("Creating stone mines..."); var group = new SimpleGroup([new SimpleObject(oStoneSmall, 0,2, 0,4), new SimpleObject(oStoneLarge, 1,1, 0,4)], true, clRock); createObjectGroupsDeprecated(group, 0, [avoidClasses(clWater, 0, clForest, 1, clPlayer, 20, clRock, 10, clHill, 2)], 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, 0, clForest, 1, clPlayer, 20, clRock, 10, clHill, 2)], 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, 0, clForest, 1, clPlayer, 20, clMetal, 10, clRock, 5, clHill, 2)], 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, clHill, 0), scaleByMapSize(16, 262), 50 ); Engine.SetProgress(90); 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, clHill, 0), scaleByMapSize(8, 131), 50 ); log("Creating deer..."); group = new SimpleGroup( [new SimpleObject(oDeer, 5,7, 0,4)], true, clFood ); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 0, clForest, 0, clPlayer, 20, clHill, 0, clFood, 5), 6 * numPlayers, 50 ); log("Creating sheep..."); group = new SimpleGroup( [new SimpleObject(oGoat, 2,3, 0,2)], true, clFood ); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 0, clForest, 0, clPlayer, 20, clHill, 0, clFood, 20), 3 * numPlayers, 50 ); log("Creating berry bush..."); group = new SimpleGroup( [new SimpleObject(oBerryBush, 5,7, 0,4)], true, clFood ); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 6, clForest, 0, clPlayer, 20, clHill, 1, clFood, 10), randIntInclusive(1, 4) * numPlayers + 2, 50 ); log("Creating boar..."); group = new SimpleGroup( [new SimpleObject(oBoar, 2,3, 0,2)], true, clFood ); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 0, clForest, 0, clPlayer, 20, clHill, 0, clFood, 20), 2 * numPlayers, 50 ); createStragglerTrees( [oPoplar, oPalm, oApple], avoidClasses(clWater, 1, clForest, 1, clHill, 1, clPlayer, 10, clMetal, 6, clRock, 6), clForest, stragglerTrees); log("Creating small grass tufts..."); 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, 2, clDirt, 0), scaleByMapSize(13, 200) ); 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, 2, clDirt, 1, clForest, 0), scaleByMapSize(13, 200) ); Engine.SetProgress(95); 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, 1, clHill, 1, clPlayer, 1, clDirt, 1), scaleByMapSize(13, 200), 50 ); placePlayersNomad(clPlayer, avoidClasses(clWater, 4, clForest, 1, clMetal, 4, clRock, 4, clHill, 4, clFood, 2)); setSkySet("cirrus"); setWaterColor(0.114, 0.192, 0.463); setWaterTint(0.255, 0.361, 0.651); setWaterWaviness(2.0); setWaterType("ocean"); setWaterMurkiness(0.83); ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/latium.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/latium.js (revision 20901) +++ ps/trunk/binaries/data/mods/public/maps/random/latium.js (revision 20902) @@ -1,473 +1,473 @@ 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]; InitMap(g_MapSettings.BaseHeight, g_MapSettings.BaseTerrain); 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 waterHeight = -16; var landHeight = getMapBaseHeight(); var hillHeight = 12; log("Creating players..."); var [playerIDs, playerPosition] = playerPlacementLine(false, mapCenter, fractionToTiles(randFloat(0.42, 0.46))); function distanceToPlayers(x, z) { var r = 10000; 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), "end": new Vector2D(x, mapBounds.bottom), "width": 2 * fractionToTiles(WATER_WIDTH), "fadeDist": 16, "deviation": 0, "waterHeight": waterHeight, "landHeight": landHeight, "meanderShort": 0, "meanderLong": 0, "waterFunc": (ix, iz, height, z) => { addToClass(ix, iz, 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++) { var x = ix / (mapSize + 1.0); var z = iz / (mapSize + 1.0); var pn = playerNearness(x, z); let distToWater = stayClasses(clWater, 1).allows(ix, iz) ? 0 : (0.5 - WATER_WIDTH - Math.abs(x - 0.5)); let h = distToWater ? hillHeight * (1 - Math.abs(x - 0.5) / (0.5 - WATER_WIDTH)) : getHeight(ix, iz); // 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; } setHeight(ix, iz, 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++) { 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); // 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)); } // 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) { 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()); } - placeTerrain(ix, iz, t); + createTerrain(t).place(ix, iz); } Engine.SetProgress(30); placePlayerBases({ "PlayerPlacement": [playerIDs, playerPosition], "PlayerTileClass": clPlayer, "BaseResourceClass": clBaseResource, "baseResourceConstraint": avoidClasses(clCliff, 4), "CityPatch": { "radius": 11, "outerTerrain": tGrass, "innerTerrain": tCity, "width": 4, "painters": [ new SmoothElevationPainter(ELEVATION_SET, 5, 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/pyrenean_sierra.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/pyrenean_sierra.js (revision 20901) +++ ps/trunk/binaries/data/mods/public/maps/random/pyrenean_sierra.js (revision 20902) @@ -1,507 +1,508 @@ 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 tPass = ["alpine_cliff_b", "alpine_cliff_c", "alpine_grass_rocky", "alpine_grass_rocky", "alpine_grass_rocky"]; 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]; InitMap(g_MapSettings.BaseHeight, g_MapSettings.BaseTerrain); 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(); // Initial Terrain Creation // I'll use very basic noised sinusoidal functions to give the terrain a way aspect // It looks like we can't go higher than ≈ 75. Given this I'll lower the ground const baseHeight = -6; setWaterHeight(8); var MoutainAngle = randFloat(0,2 * Math.PI); var oceanAngle = MoutainAngle + randFloat(-1, 1) * Math.PI / 12; var baseHeights = []; for (var ix = 0; ix < mapSize; ix++) { baseHeights.push([]); for (var iz = 0; iz < mapSize; iz++) { if (g_Map.inMapBounds(ix,iz)) { - placeTerrain(ix, iz, tGrass); + createTerrain(tGrass).place(ix, iz); + let height = baseHeight + randFloat(-1, 1) + scaleByMapSize(1, 3) * (Math.cos(ix / scaleByMapSize(5, 30)) + Math.sin(iz / scaleByMapSize(5, 30))); setHeight(ix, iz, height); baseHeights[ix].push(height); } else baseHeights[ix].push(-100); } } 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 mountainLength = fractionToTiles(0.68); var mountainVec = new Vector2D(mountainLength, 0).rotate(-MoutainAngle); var mountainVecHalf = Vector2D.mult(mountainVec, 1/2); var mountainStart = Vector2D.add(mapCenter, mountainVecHalf); var mountainEnd = Vector2D.sub(mapCenter, mountainVecHalf); // Number of peaks var MountainHeight = scaleByMapSize(50, 65); var NumOfIterations = scaleByMapSize(100,1000); var randomNess = randFloat(-scaleByMapSize(1,12),scaleByMapSize(1,12)); for (var i = 0; i < NumOfIterations; i++) { Engine.SetProgress(45 * i/NumOfIterations + 30 * (1-i/NumOfIterations)); var position = i/NumOfIterations; var width = scaleByMapSize(15,55); var randHeight2 = randFloat(0,10) + MountainHeight; for (var dist = 0; dist < width*3; dist++) { var okDist = dist/3; var S1x = Math.round((mountainStart.x * (1 - position) + mountainEnd.x * position) + randomNess * Math.cos(position * Math.PI * 4) + Math.cos(MoutainAngle + Math.PI / 2) * okDist); var S1z = Math.round((mountainStart.y * (1 - position) + mountainEnd.y * position) + randomNess * Math.sin(position * Math.PI * 4) + Math.sin(MoutainAngle + Math.PI / 2) * okDist); var S2x = Math.round((mountainStart.x * (1 - position) + mountainEnd.x * position) + randomNess * Math.cos(position * Math.PI * 4) + Math.cos(MoutainAngle - Math.PI / 2) * okDist); var S2z = Math.round((mountainStart.y * (1 - position) + mountainEnd.y * position) + randomNess * Math.sin(position * Math.PI * 4) + Math.sin(MoutainAngle - Math.PI / 2) * okDist); // complicated sigmoid // Ranges is 0-1, FormX is 0-1 too. var FormX = (-2*(1-okDist/width)+1.9) - 4*(2*(1-okDist/width)-randFloat(0.9,1.1))*(2*(1-okDist/width)-randFloat(0.9,1.1))*(2*(1-okDist/width)-randFloat(0.9,1.1)); var Formula = (1/(1 + Math.exp(FormX))); // If we're too far from the border, we flatten Formula *= (0.2 - Math.max(0, Math.abs(0.5 - position) - 0.3)) * 5; var randHeight = randFloat(-9,9) * Formula; var height = baseHeights[S1x][S1z]; setHeight(S1x,S1z, height + randHeight2 * Formula + randHeight ); var height = baseHeights[S2x][S2z]; setHeight(S2x,S2z, height + randHeight2 * Formula + randHeight ); if (getHeight(S1x,S1z) > 15) addToClass(S1x,S1z, clPyrenneans); if (getHeight(S2x,S2z) > 15) addToClass(S2x,S2z, clPyrenneans); } } // Allright now slight smoothing (decreasing with height) for (var ix = 1; ix < mapSize-1; ix++) { for (var iz = 1; iz < mapSize-1; iz++) { if (g_Map.inMapBounds(ix,iz) && checkIfInClass(ix,iz,clPyrenneans) ) { var NB = getNeighborsHeight(ix,iz); var index = 9/(1 + Math.max(0,getHeight(ix,iz)/7)); setHeight(ix,iz, (getHeight(ix,iz)*(9-index) + NB*index)/9 ); } } } Engine.SetProgress(48); log("Creating passages..."); var passageLocation = 0.35; var passageHeight = MountainHeight - 25; var passageLength = scaleByMapSize(8, 50); var passageVec = mountainVec.perpendicular().normalize().mult(passageLength); for (let passLoc of [passageLocation, 1 - passageLocation]) for (let direction of [1, -1]) { let passageStart = Vector2D.add(mountainEnd, Vector2D.mult(mountainVec, passLoc)); let passageEnd = Vector2D.add(passageStart, Vector2D.mult(passageVec, direction)); createPassage({ "start": passageStart, "end": passageEnd, "startHeight": passageHeight, "startWidth": 7, "endWidth": 7, "smoothWidth": 2, "tileClass": clPass }); } Engine.SetProgress(50); // Smoothing the mountains for (var ix = 1; ix < mapSize-1; ix++) for (var iz = 1; iz < mapSize-1; iz++) { if ( g_Map.inMapBounds(ix,iz) && checkIfInClass(ix,iz,clPyrenneans) ) { var NB = getNeighborsHeight(ix,iz); var index = 9/(1 + Math.max(0,(getHeight(ix,iz)-10)/7)); setHeight(ix,iz, (getHeight(ix,iz)*(9-index) + NB*index)/9 ); baseHeights[ix][iz] = (getHeight(ix,iz)*(9-index) + NB*index)/9; } } 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.x, ocean.y), [ new ElevationPainter(-22), paintClass(clWater) ]); // Smoothing around the water, then going a bit random for (var ix = 1; ix < mapSize-1; ix++) { for (var iz = 1; iz < mapSize-1; iz++) { if ( g_Map.inMapBounds(ix,iz) && getTileClass(clWater).countInRadius(ix,iz,5,true) > 0 ) { var averageHeight = 0; var size = 5; if (getTileClass(clPyrenneans).countInRadius(ix,iz,1,true) > 0) size = 1; else if (getTileClass(clPyrenneans).countInRadius(ix,iz,2,true) > 0) size = 2; else if (getTileClass(clPyrenneans).countInRadius(ix,iz,3,true) > 0) size = 3; else if (getTileClass(clPyrenneans).countInRadius(ix,iz,4,true) > 0) size = 4; var todivide = 0; for (var xx = -size; xx <= size;xx++) for (var yy = -size; yy <= size;yy++) { if (g_Map.inMapBounds(ix + xx,iz + yy) && (xx != 0 || yy != 0)){ averageHeight += getHeight(ix + xx,iz + yy) / (Math.abs(xx) + Math.abs(yy)); todivide += 1 / (Math.abs(xx) + Math.abs(yy)); } } averageHeight += getHeight(ix,iz)*2; averageHeight /= (todivide+2); setHeight(ix,iz, averageHeight ); //baseHeights[ix][iz] = averageHeight; } if ( g_Map.inMapBounds(ix,iz) && getTileClass(clWater).countInRadius(ix,iz,4,true) > 0 && getTileClass(clWater).countInRadius(ix,iz,4) > 0 ) setHeight(ix,iz, getHeight(ix,iz) + randFloat(-1,1)); } } Engine.SetProgress(55); log("Creating hills..."); createAreas( new ClumpPlacer(scaleByMapSize(60, 120), 0.3, 0.06, 5), [ new SmoothElevationPainter(ELEVATION_MODIFY, 7, 4, 2), 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 height = getHeight(x, z); let heightDiff = getHeightDifference(x, z); if (getTileClass(clPyrenneans).countInRadius(x, z, 2, true)) { createTerrain(getPyreneansTerrain(height, heightDiff)).place(x, z); if (height >= 30 && heightDiff < 5 && getTileClass(clPass).countInRadius(x, z, 2, true)) createTerrain(tPass).place(x,z); } let terrainShore = getShoreTerrain(height, heightDiff, x, z); if (terrainShore) createTerrain(terrainShore).place(x, z); } function getPyreneansTerrain(height, heightDiff) { if (height < 6) return heightDiff < 5 ? tGrass : tMidRangeCliffs; if (height < 18) return heightDiff < 8 ? tGrassMidRange : tMidRangeCliffs; if (height < 30) return heightDiff < 8 ? tGrassHighRange : tMidRangeCliffs; if (height < MountainHeight - 20) return heightDiff < 8 ? tHighRocks : tHighRangeCliffs; if (height < MountainHeight - 10) return heightDiff < 7 ? tSnowedRocks : tHighRangeCliffs; return heightDiff < 6 ? tTopSnowOnly : tTopSnow; } function getShoreTerrain(height, heightDiff, x, z) { if (height <= -14) return tWater; if (height <= -2 && getTileClass(clWater).countInRadius(x, z, 2, true)) return heightDiff < 2.5 ? tSand : tMidRangeCliffs; if (height <= 0 && getTileClass(clWater).countInRadius(x, z, 3, true)) 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); ExportMap(); function getNeighborsHeight(x1, z1) { var toCheck = [ [-1,-1], [-1,0], [-1,1], [0,1], [1,1], [1,0], [1,-1], [0,-1] ]; var height = 0; for (var i in toCheck) { var xx = x1 + toCheck[i][0]; var zz = z1 + toCheck[i][1]; height += getHeight(Math.round(xx), Math.round(zz)); } height /= 8; return height; } // no need for preliminary rounding function getHeightDifference(x1, z1) { x1 = Math.round(x1); z1 = Math.round(z1); var height = getHeight(x1,z1); if (!g_Map.inMapBounds(x1,z1)) return 0; // I wanna store the height difference with any neighbor var toCheck = [ [-1,-1], [-1,0], [-1,1], [0,1], [1,1], [1,0], [1,-1], [0,-1] ]; var diff = 0; var todiv = 0; for (var i in toCheck) { var xx = Math.round(x1 + toCheck[i][0]); var zz = Math.round(z1 + toCheck[i][1]); if (g_Map.inMapBounds(xx,zz)) { diff += Math.abs(getHeight(xx,zz) - height); todiv++; } } if (todiv > 0) diff /= todiv; return diff; } Index: ps/trunk/binaries/data/mods/public/maps/random/rivers.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rivers.js (revision 20901) +++ ps/trunk/binaries/data/mods/public/maps/random/rivers.js (revision 20902) @@ -1,288 +1,288 @@ 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]; InitMap(g_MapSettings.BaseHeight, 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 waterHeight = -3; var shallowHeight = -1; 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.x, mapCenter.y), [ new LayeredPainter([tShore, tWater, tWater, tWater], [1, 4, 2]), new SmoothElevationPainter(ELEVATION_SET, waterHeight, 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, "landHeight": getMapBaseHeight(), "waterHeight": waterHeight, "minHeight": waterHeight, "meanderShort": 10, "meanderLong": 0, "waterFunc": (ix, iz, height, riverFraction) => { addToClass(ix, iz, clWater); let isShallow = height < shallowHeight && riverFraction > shallowLocation && riverFraction < shallowLocation + shallowWidth; let newHeight = isShallow ? shallowHeight : Math.max(height, waterHeight); if (getHeight(ix, iz) < newHeight) return; setHeight(ix, iz, newHeight); - placeTerrain(ix, iz, height >= 0 ? tShore : tWater); + createTerrain(height >= 0 ? tShore : tWater).place(ix, iz); if (isShallow) addToClass(ix, iz, 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 20901) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/gaia_terrain.js (revision 20902) @@ -1,609 +1,609 @@ /** * @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 constraint = new AndConstraint(constraints); if (!g_Map.inMapBounds(x, z) || !constraint.allows(x, z)) 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) { if (Math.euclidDistance2D(ix, iz, cx, cz) > radius2 || !g_Map.inMapBounds(ix, iz)) continue; if (!constraint.allows(ix, iz)) { 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 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 distance = Math.euclidDistance2D(ix, iz, cx, cz); 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) setHeight(ix, iz, newHeight); else if (getHeight(ix, iz) >= newHeight && getHeight(ix, iz) < newHeight + 4) setHeight(ix, iz, newHeight + 4); if (terrain !== undefined) - placeTerrain(ix, iz, terrain); + createTerrain(terrain).place(ix, iz); 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 baseSize = getMapArea() / scaleByMapSize(1, 8); let coherence = 0.7; let smoothness = 0.05; let failFraction = 100; let steepness = 3; let clLava = createTileClass(); let layers = [ { "clumps": 0.067, "elevation": 15, "tileClass": tileClass }, { "clumps": 0.05, "elevation": 25, "tileClass": createTileClass() }, { "clumps": 0.02, "elevation": 45, "tileClass": createTileClass() }, { "clumps": 0.011, "elevation": 62, "tileClass": createTileClass() }, { "clumps": 0.003, "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(baseSize * layers[i].clumps, coherence, smoothness, failFraction, position.x, position.y), [ layers[i].painter || new LayeredPainter([terrainTexture, terrainTexture], [3]), new SmoothElevationPainter(elevationType, layers[i].elevation, layers[i].steepness || steepness), paintClass(layers[i].tileClass) ], i == 0 ? null : stayClasses(layers[i - 1].tileClass, 1)); if (smoke) { let num = Math.floor(baseSize * 0.002); 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. * Horizontal locations and widths (including fadeDist, meandering) are fractions of the mapsize. * * @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 waterHeight - Ground height of the riverbed. * @proeprty landHeight - 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 the 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) { if (args.constraint && !args.constraint.allows(ix, iz)) continue; let vecPoint = new Vector2D(ix, iz); // 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.waterHeight; if (shoreDist1 > -args.fadeDist) height += (args.landHeight - args.waterHeight) * (1 + shoreDist1 / args.fadeDist); else if (shoreDist2 < args.fadeDist) height += (args.landHeight - args.waterHeight) * (1 - shoreDist2 / args.fadeDist); if (args.minHeight === undefined || height < args.minHeight) setHeight(ix, iz, height); if (args.waterFunc) args.waterFunc(ix, iz, height, riverFraction); } else if (args.landFunc) args.landFunc(ix, iz, 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(horizontal, riverCount, riverWidth, waterHeight, 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 shallowHeight = -2; let riverAngle = horizontal ? 0 : Math.PI / 2; let mapSize = getMapSize(); let mapCenter = getMapCenter(); 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(randFloat(tapering, 1 - tapering), 0.5); let sign = randBool() ? 1 : -1; let distanceVec = new Vector2D(0, sign * tapering); let searchStart = Vector2D.add(searchCenter, distanceVec).mult(mapSize).rotateAround(riverAngle, mapCenter); let searchEnd = Vector2D.sub(searchCenter, distanceVec).mult(mapSize).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.x, start.y, end.x, end.y, riverWidth, waviness, smoothness, offset, tapering), [ new SmoothElevationPainter(ELEVATION_SET, waterHeight, 4), paintClass(tributaryRiverTileClass) ], new AndConstraint([constraint, riverConstraint]))) continue; // Create small puddles at the map border to ensure players being separated createArea( new ClumpPlacer(Math.floor(diskArea(riverWidth / 2)), 0.95, 0.6, 10, end.x, end.y), new SmoothElevationPainter(ELEVATION_SET, waterHeight, 3), constraint); } // Create shallows if (shallowTileClass) for (let z of [0.25, 0.75]) createPassage({ "start": new Vector2D(0, z).mult(mapSize).rotateAround(riverAngle, mapCenter), "end": new Vector2D(1, z).mult(mapSize).rotateAround(riverAngle, mapCenter), "startWidth": scaleByMapSize(8, 12), "endWidth": scaleByMapSize(8, 12), "smoothWidth": 2, "startHeight": shallowHeight, "endHeight": shallowHeight, "maxHeight": shallowHeight, "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 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.x, location.y) || args.maxHeight !== undefined && getHeight(location.x, location.y) > args.maxHeight) continue; let smoothDistance = args.smoothWidth + Math.abs(stepWidth) - halfPassageWidth; g_Map.setHeight( location.x, location.y, smoothDistance > 0 ? (getHeight(location.x, location.y) * smoothDistance + passageHeight / smoothDistance) / (smoothDistance + 1 / smoothDistance) : passageHeight); if (args.tileClass !== undefined) addToClass(location.x, location.y, args.tileClass); if (args.edgeTerrain && smoothDistance > 0) - placeTerrain(location.x, location.y, args.edgeTerrain); + createTerrain(args.edgeTerrain).place(location.x, location.y); else if (args.terrain) - placeTerrain(location.x, location.y, args.terrain); + createTerrain(args.terrain).place(location.x, location.y); } } } /** * 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) 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 20901) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/library.js (revision 20902) @@ -1,455 +1,445 @@ 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)); function fractionToTiles(f) { return g_Map.size * f; } function tilesToFraction(t) { return t / g_Map.size; } function fractionToSize(f) { return getMapArea() * f; } function sizeToFraction(s) { return s / getMapArea(); } function scaleByMapSize(min, max, minMapSize = 128, maxMapSize = 512) { return min + (max - min) * (g_Map.size - minMapSize) / (maxMapSize - minMapSize); } /** * 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.z; } // TODO this is a hack to simulate the old behaviour of those functions // until all old maps are changed to use the correct version of these functions function createObjectGroupsDeprecated(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 placeTerrain(x, z, terrainNames) -{ - createTerrain(terrainNames).place(x, z); -} - function isCircularMap() { return !!g_MapSettings.CircularMap; } function getMapBaseHeight() { return g_MapSettings.BaseHeight; } function createTileClass() { return g_Map.createTileClass(); } function getTileClass(id) { if (!g_Map.validClass(id)) return undefined; return g_Map.tileClasses[id]; } /** * Constructs a new Area shaped by the Placer meeting the 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 getMapArea() { return Math.square(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); } function setHeight(x, z, height) { g_Map.setHeight(x, z, height); } /** * Utility functions for classes */ /** * Add point to given class by id */ function addToClass(x, z, id) { let tileClass = getTileClass(id); if (tileClass !== null) tileClass.add(x, z); } /** * Remove point from the given class by id */ function removeFromClass(x, z, id) { let tileClass = getTileClass(id); if (tileClass !== null) tileClass.remove(x, z); } /** * Create a painter for the given class */ function paintClass(id) { return new TileClassPainter(getTileClass(id)); } /** * Create a painter for the given class */ function unPaintClass(id) { return new TileClassUnPainter(getTileClass(id)); } /** * Create an avoid constraint for the given classes by the given distances */ function avoidClasses(/*class1, dist1, class2, dist2, etc*/) { let ar = []; for (let i = 0; i < arguments.length/2; ++i) ar.push(new AvoidTileClassConstraint(arguments[2*i], arguments[2*i+1])); // Return single constraint if (ar.length == 1) return ar[0]; return new AndConstraint(ar); } /** * Create a stay constraint for the given classes by the given distances */ function stayClasses(/*class1, dist1, class2, dist2, etc*/) { let ar = []; for (let i = 0; i < arguments.length/2; ++i) ar.push(new StayInTileClassConstraint(arguments[2*i], arguments[2*i+1])); // Return single constraint if (ar.length == 1) return ar[0]; return new AndConstraint(ar); } /** * Create a border constraint for the given classes by the given distances */ function borderClasses(/*class1, idist1, odist1, class2, idist2, odist2, etc*/) { let ar = []; for (let i = 0; i < arguments.length/3; ++i) ar.push(new BorderTileClassConstraint(arguments[3*i], arguments[3*i+1], arguments[3*i+2])); // Return single constraint if (ar.length == 1) return ar[0]; return new AndConstraint(ar); } /** * Checks if the given tile is in class "id" */ function checkIfInClass(x, z, id) { let tileClass = getTileClass(id); if (tileClass === null) return 0; let members = tileClass.countMembersInRadius(x, z, 1); if (members === null) return 0; return members; } - -function getTerrainTexture(x, y) -{ - return g_Map.getTexture(x, y); -} Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen2/gaia.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen2/gaia.js (revision 20901) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen2/gaia.js (revision 20902) @@ -1,1269 +1,1269 @@ 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 dist = Math.abs(distanceOfPointFromLine( new Vector2D(baseLine.x1, baseLine.z1), new Vector2D(baseLine.x2, baseLine.z2), new Vector2D(pt.x, pt.z))); var curHeight = g_Map.getHeight(pt.x, pt.z); 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(pt.x, pt.z); g_Map.setHeight(pt.x, pt.z, 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); 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)) { var newEl = smoothElevation(x + minX, z + minZ); g_Map.setHeight(x + minX, z + minZ, newEl); ground.place(x + minX, z + minZ); } } } /** * 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 }; } /** * Finds the average elevation around a point. */ function smoothElevation(x, z) { var min = g_Map.getHeight(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 (!g_Map.validH(thisX, thisZ)) continue; var height = g_Map.getHeight(thisX, thisZ); if (height < min) min = height; } return min; } /** * 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 hmPoint = { "x": x * scale, "y": y * scale }; let hmTile = { "x": Math.floor(hmPoint.x), "y": Math.floor(hmPoint.y) }; let shift = { "x": 0, "y": 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)]); setHeight(x, y, 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]]; - placeTerrain(x, y, tile); + createTerrain(tile).place(x, y); if (func) func(tile, x, y); } } return scale; } Index: ps/trunk/binaries/data/mods/public/maps/random/the_nile.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/the_nile.js (revision 20901) +++ ps/trunk/binaries/data/mods/public/maps/random/the_nile.js (revision 20902) @@ -1,399 +1,399 @@ Engine.LoadLibrary("rmgen"); var tCity = "desert_city_tile"; var tCityPlaza = "desert_city_tile_plaza"; var tFineSand = "desert_sand_smooth"; var tForestFloor = "desert_forestfloor_palms"; var tGrass = "desert_dirt_rough_2"; var tGrassSand50 = "desert_sand_dunes_50"; var tGrassSand25 = "desert_dirt_rough"; var tDirt = "desert_dirt_rough"; var tDirtCracks = "desert_dirt_cracks"; var tShore = "desert_sand_wet"; var tLush = "desert_grass_a"; var tSLush = "desert_grass_a_sand"; var tSDry = "desert_plants_b"; var oBerryBush = "gaia/flora_bush_berry"; var oCamel = "gaia/fauna_camel"; var oGazelle = "gaia/fauna_gazelle"; var oGoat = "gaia/fauna_goat"; var oStoneLarge = "gaia/geology_stonemine_desert_badlands_quarry"; var oStoneSmall = "gaia/geology_stone_desert_small"; var oMetalLarge = "gaia/geology_metal_desert_slabs"; var oDatePalm = "gaia/flora_tree_date_palm"; var oSDatePalm = "gaia/flora_tree_cretan_date_palm_short"; var eObelisk = "other/obelisk"; var ePyramid = "other/pyramid_minor"; var oWoodTreasure = "gaia/special_treasure_wood"; var oFoodTreasure = "gaia/special_treasure_food_bin"; var aBush1 = "actor|props/flora/bush_desert_a.xml"; var aBush2 = "actor|props/flora/bush_desert_dry_a.xml"; var aBush3 = "actor|props/flora/bush_medit_sm_dry.xml"; var aBush4 = "actor|props/flora/plant_desert_a.xml"; var aDecorativeRock = "actor|geology/stone_desert_med.xml"; var aReeds = "actor|props/flora/reeds_pond_lush_a.xml"; var aLillies = "actor|props/flora/water_lillies.xml"; var pForest = [tForestFloor + TERRAIN_SEPARATOR + oDatePalm, tForestFloor + TERRAIN_SEPARATOR + oSDatePalm, tForestFloor]; InitMap(g_MapSettings.BaseHeight, g_MapSettings.BaseTerrain); const mapSize = getMapSize(); const mapCenter = getMapCenter(); const mapBounds = getMapBounds(); var aPlants = mapSize < 256 ? "actor|props/flora/grass_tropical.xml" : "actor|props/flora/grass_tropic_field_tall.xml"; var numPlayers = getNumPlayers(); var clPlayer = createTileClass(); var clForest = createTileClass(); var clWater = createTileClass(); var clDirt = createTileClass(); var clRock = createTileClass(); var clMetal = createTileClass(); var clFood = createTileClass(); var clBaseResource = createTileClass(); var clGrass = createTileClass(); var clDesert = createTileClass(); var clPond = createTileClass(); var clShore = createTileClass(); var clTreasure = createTileClass(); var desertWidth = fractionToTiles(0.25); placePlayerBases({ "PlayerPlacement": playerPlacementRiver(0, fractionToTiles(0.4)), "PlayerTileClass": clPlayer, "BaseResourceClass": clBaseResource, "CityPatch": { "outerTerrain": tCityPlaza, "innerTerrain": tCity }, "Chicken": { }, "Berries": { "template": oBerryBush }, "Mines": { "types": [ { "template": oMetalLarge }, { "template": oStoneLarge } ] }, "Trees": { "template": oDatePalm, "count": 2 }, "Decoratives": { "template": aBush1 } }); Engine.SetProgress(30); const riverTextures = [ { "left": fractionToTiles(0), "right": fractionToTiles(0.04), "tileClass": tLush }, { "left": fractionToTiles(0.04), "right": fractionToTiles(0.06), "tileClass": tSLush }, { "left": fractionToTiles(0.06), "right": fractionToTiles(0.09), "tileClass": tSDry } ]; const plantFrequency = 2; var plantID = 0; paintRiver({ "parallel": true, "start": new Vector2D(mapCenter.x, mapBounds.top), "end": new Vector2D(mapCenter.x, mapBounds.bottom), "width": fractionToTiles(0.1), "fadeDist": scaleByMapSize(3, 12), "deviation": 0.5, "waterHeight": -3, "landHeight": 2, "meanderShort": 12, "meanderLong": 50, "waterFunc": (ix, iz, height, riverFraction) => { addToClass(ix, iz, clWater); - placeTerrain(ix, iz, tShore); + createTerrain(tShore).place(ix, iz); // Place river bushes if (height <= -0.2 || height >= 0.1) return; if (plantID % plantFrequency == 0) { plantID = 0; placeObject(ix, iz, aPlants, 0, randomAngle()); } ++plantID; }, "landFunc": (ix, iz, shoreDist1, shoreDist2) => { for (let riv of riverTextures) if (riv.left < +shoreDist1 && +shoreDist1 < riv.right || riv.left < -shoreDist2 && -shoreDist2 < riv.right) { - placeTerrain(ix, iz, riv.tileClass); + createTerrain(riv.tileClass).place(ix, iz); addToClass(ix, iz, clShore); } } }); Engine.SetProgress(40); log("Marking desert..."); for (let [left, right] of [[mapBounds.left, mapBounds.left + desertWidth], [mapBounds.right - desertWidth, mapBounds.right]]) createArea( new RectPlacer(left, mapBounds.top, right, mapBounds.bottom), paintClass(clDesert)); log("Creating bumps..."); createAreas( new ClumpPlacer(scaleByMapSize(20, 50), 0.3, 0.06, 1), new SmoothElevationPainter(ELEVATION_MODIFY, 2, 2), avoidClasses(clWater, 2, clPlayer, 6), scaleByMapSize(100, 200)); log("Creating ponds..."); var numLakes = Math.round(scaleByMapSize(1, 4) * numPlayers / 2); var waterAreas = createAreas( new ClumpPlacer(scaleByMapSize(2, 5) * 50, 0.8, 0.1, 10), [ new LayeredPainter([tShore, tShore, tShore], [1, 1]), new SmoothElevationPainter(ELEVATION_SET, -7, 4), paintClass(clPond) ], avoidClasses(clPlayer, 25, clWater, 20, clPond, 10), numLakes); log("Creating reeds..."); createObjectGroupsByAreasDeprecated( new SimpleGroup([new SimpleObject(aReeds, 1, 3, 0, 1)], true), 0, stayClasses(clPond, 1), numLakes, 100, waterAreas); log("Creating lillies..."); createObjectGroupsByAreasDeprecated( new SimpleGroup([new SimpleObject(aLillies, 1, 3, 0, 1)], true), 0, stayClasses(clPond, 1), numLakes, 100, waterAreas); log("Creating forests..."); var [forestTrees, stragglerTrees] = getTreeCounts(700, 3500, 0.5); var num = scaleByMapSize(10,30); createAreas( new ClumpPlacer(forestTrees / num, 0.15, 0.1, 0.5), [ new TerrainPainter([pForest, tForestFloor]), paintClass(clForest) ], avoidClasses(clPlayer, 19, clForest, 4, clWater, 1, clDesert, 5, clPond, 2, clBaseResource, 3), num, 50); Engine.SetProgress(50); 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, tGrassSand50], [tGrassSand50, tGrassSand25], [tGrassSand25, tGrass]], [1, 1]), paintClass(clDirt) ], avoidClasses(clForest, 0, clGrass, 5, clPlayer, 10, clWater, 1, clDirt, 5, clShore, 1, clPond, 1), scaleByMapSize(15, 45)); Engine.SetProgress(55); log("Creating dirt 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( [[tDirt, tDirtCracks], [tDirt, tFineSand], [tDirtCracks, tFineSand]], [1, 1]), paintClass(clDirt) ], avoidClasses(clForest, 0, clDirt, 5, clPlayer, 10, clWater, 1, clGrass, 5, clShore, 1, clPond, 1), scaleByMapSize(15, 45)); Engine.SetProgress(60); log("Creating stone mines..."); var group = new SimpleGroup([new SimpleObject(oStoneSmall, 0, 2, 0, 4), new SimpleObject(oStoneLarge, 1, 1, 0, 4)], true, clRock); createObjectGroupsDeprecated(group, 0, avoidClasses(clForest, 1, clPlayer, 20, clRock, 10, clWater, 1, clPond, 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(clForest, 1, clPlayer, 20, clRock, 10, clWater, 1, clPond, 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(clForest, 1, clPlayer, 20, clMetal, 10, clRock, 5, clWater, 1, clPond, 1), scaleByMapSize(4,16), 100 ); 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(clForest, 1, clPlayer, 20, clRock, 10, clWater, 1, clPond, 1), stayClasses(clDesert, 3)], scaleByMapSize(6,20), 100 ); log("Creating small stone quarries..."); group = new SimpleGroup([new SimpleObject(oStoneSmall, 2,5, 1,3)], true, clRock); createObjectGroupsDeprecated(group, 0, [avoidClasses(clForest, 1, clPlayer, 20, clRock, 10, clWater, 1, clPond, 1), stayClasses(clDesert, 3)], scaleByMapSize(6,20), 100 ); log("Creating metal mines..."); group = new SimpleGroup([new SimpleObject(oMetalLarge, 1,1, 0,4)], true, clMetal); createObjectGroupsDeprecated(group, 0, [avoidClasses(clForest, 1, clPlayer, 20, clMetal, 10, clRock, 5, clWater, 1, clPond, 1), stayClasses(clDesert, 3)], scaleByMapSize(6,20), 100 ); Engine.SetProgress(65); log("Creating small decorative rocks..."); group = new SimpleGroup( [new SimpleObject(aDecorativeRock, 1,3, 0,1)], true ); createObjectGroupsDeprecated( group, 0, avoidClasses(clWater, 1, clForest, 0, clPlayer, 0, clPond, 1), scaleByMapSize(16, 262), 50 ); log("Creating shrubs..."); group = new SimpleGroup( [new SimpleObject(aBush2, 1,2, 0,1), new SimpleObject(aBush1, 1,3, 0,2), new SimpleObject(aBush4, 1,2, 0,1), new SimpleObject(aBush3, 1,3, 0,2)], true ); createObjectGroupsDeprecated( group, 0, avoidClasses(clWater, 1, clPlayer, 0, clPond, 1), scaleByMapSize(20, 180), 50 ); Engine.SetProgress(70); log("Creating gazelles..."); group = new SimpleGroup([new SimpleObject(oGazelle, 5,7, 0,4)], true, clFood); createObjectGroupsDeprecated(group, 0, avoidClasses(clForest, 0, clPlayer, 20, clWater, 1, clFood, 10, clDesert, 5, clPond, 1), 3*scaleByMapSize(5,20), 50 ); log("Creating goats..."); group = new SimpleGroup([new SimpleObject(oGoat, 2,4, 0,3)], true, clFood); createObjectGroupsDeprecated(group, 0, avoidClasses(clForest, 0, clPlayer, 20, clWater, 1, clFood, 10, clDesert, 5, clPond, 1), 3*scaleByMapSize(5,20), 50 ); log("Creating treasures..."); group = new SimpleGroup([new SimpleObject(oFoodTreasure, 1,1, 0,2)], true, clTreasure); createObjectGroupsDeprecated(group, 0, avoidClasses(clForest, 0, clPlayer, 20, clWater, 1, clFood, 2, clDesert, 5, clTreasure, 6, clPond, 1), 3*scaleByMapSize(5,20), 50 ); group = new SimpleGroup([new SimpleObject(oWoodTreasure, 1,1, 0,2)], true, clTreasure); createObjectGroupsDeprecated(group, 0, avoidClasses(clForest, 0, clPlayer, 20, clWater, 1, clFood, 2, clDesert, 5, clTreasure, 6, clPond, 1), 3*scaleByMapSize(5,20), 50 ); log("Creating camels..."); group = new SimpleGroup([new SimpleObject(oCamel, 2,4, 0,2)], true, clFood); createObjectGroupsDeprecated(group, 0, avoidClasses(clForest, 0, clPlayer, 20, clWater, 1, clFood, 10, clDesert, 5, clTreasure, 2, clPond, 1), 3*scaleByMapSize(5,20), 50 ); Engine.SetProgress(90); createStragglerTrees( [oDatePalm, oSDatePalm], avoidClasses(clForest, 0, clWater, 1, clPlayer, 20, clMetal, 6, clDesert, 1, clTreasure, 2, clPond, 1), clForest, stragglerTrees / 2); createStragglerTrees( [oDatePalm, oSDatePalm], avoidClasses(clForest, 0, clWater, 1, clPlayer, 20, clMetal, 6, clTreasure, 2), clForest, stragglerTrees / 10); createStragglerTrees( [oDatePalm, oSDatePalm], borderClasses(clPond, 1, 4), clForest, stragglerTrees); log("Creating obelisks"); group = new SimpleGroup( [new SimpleObject(eObelisk, 1,1, 0,1)], true ); createObjectGroupsDeprecated( group, 0, [avoidClasses(clWater, 4, clForest, 3, clPlayer, 20, clMetal, 6, clRock, 2, clPond, 4, clTreasure, 2), stayClasses(clDesert, 3)], scaleByMapSize(5, 30), 50 ); log("Creating pyramids"); group = new SimpleGroup( [new SimpleObject(ePyramid, 1,1, 0,1)], true ); createObjectGroupsDeprecated( group, 0, [avoidClasses(clWater, 7, clForest, 6, clPlayer, 20, clMetal, 5, clRock, 5, clPond, 7, clTreasure, 2), stayClasses(clDesert, 3)], scaleByMapSize(2, 6), 50 ); placePlayersNomad(clPlayer, avoidClasses(clWater, 4, clForest, 1, clMetal, 4, clRock, 4, clFood, 2)); setSkySet("sunny"); setSunColor(0.711, 0.746, 0.574); setWaterColor(0.541,0.506,0.416); setWaterTint(0.694,0.592,0.522); setWaterMurkiness(1); setWaterWaviness(3.0); setWaterType("lake"); ExportMap();