Index: ps/trunk/binaries/data/mods/public/maps/random/ambush.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/ambush.js (revision 21205) +++ ps/trunk/binaries/data/mods/public/maps/random/ambush.js (revision 21206) @@ -1,210 +1,210 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen2"); Engine.LoadLibrary("rmbiome"); setSelectedBiome(); var heightLand = 2; var g_Map = new RandomMap(heightLand, g_Terrains.mainTerrain); initTileClasses(); createArea( new MapBoundsPlacer(), new TileClassPainter(g_TileClasses.land)); Engine.SetProgress(10); -const pos = randomStartingPositionPattern(getTeamsArray()); -addBases(pos.setup, pos.distance, pos.separation, randomAngle()); +const playerbasePattern = randomStartingPositionPattern(getTeamsArray()); +createBasesByPattern(playerbasePattern.setup, playerbasePattern.distance, playerbasePattern.separation, randomAngle()); Engine.SetProgress(20); addElements([ { "func": addBluffs, "baseHeight": heightLand, "avoid": [ g_TileClasses.bluff, 12, g_TileClasses.hill, 5, g_TileClasses.player, 35 ], "sizes": ["normal", "big", "huge"], "mixes": ["same"], "amounts": ["tons"] }, { "func": addHills, "avoid": [ g_TileClasses.bluff, 5, g_TileClasses.hill, 15, g_TileClasses.player, 20 ], "sizes": ["normal", "big"], "mixes": ["normal"], "amounts": ["tons"] } ]); Engine.SetProgress(30); addElements([ { "func": addLayeredPatches, "avoid": [ g_TileClasses.bluff, 2, g_TileClasses.dirt, 5, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] }, { "func": addDecoration, "avoid": [ g_TileClasses.bluff, 2, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] } ]); Engine.SetProgress(50); addElements(shuffleArray([ { "func": addMetal, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.player, 30, g_TileClasses.rock, 10, g_TileClasses.metal, 20, g_TileClasses.water, 3 ], "stay": [g_TileClasses.bluff, 5], "sizes": ["normal"], "mixes": ["same"], "amounts": ["tons"] }, { "func": addStone, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.player, 30, g_TileClasses.rock, 20, g_TileClasses.metal, 10, g_TileClasses.water, 3 ], "stay": [g_TileClasses.bluff, 5], "sizes": ["normal"], "mixes": ["same"], "amounts": ["tons"] }, // Forests on bluffs { "func": addForests, "avoid": [ g_TileClasses.forest, 6, g_TileClasses.metal, 3, g_TileClasses.mountain, 5, g_TileClasses.player, 20, g_TileClasses.rock, 3, g_TileClasses.water, 2 ], "stay": [g_TileClasses.bluff, 5], "sizes": ["big"], "mixes": ["normal"], "amounts": ["tons"] }, // Forests on mainland { "func": addForests, "avoid": [ g_TileClasses.bluff, 10, g_TileClasses.forest, 10, g_TileClasses.metal, 3, g_TileClasses.mountain, 5, g_TileClasses.player, 20, g_TileClasses.rock, 3, g_TileClasses.water, 2 ], "sizes": ["small"], "mixes": ["same"], "amounts": ["normal"] } ])); Engine.SetProgress(70); addElements(shuffleArray([ { "func": addBerries, "avoid": [ g_TileClasses.bluff, 5, g_TileClasses.forest, 5, g_TileClasses.metal, 10, g_TileClasses.mountain, 2, g_TileClasses.player, 20, g_TileClasses.rock, 10, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["few"] }, { "func": addAnimals, "avoid": [ g_TileClasses.bluff, 5, g_TileClasses.forest, 2, g_TileClasses.metal, 2, g_TileClasses.mountain, 1, g_TileClasses.player, 12, g_TileClasses.rock, 2, g_TileClasses.water, 3 ], "sizes": ["small"], "mixes": ["similar"], "amounts": ["normal", "many"] }, { "func": addStragglerTrees, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 7, g_TileClasses.metal, 2, g_TileClasses.mountain, 1, g_TileClasses.player, 12, g_TileClasses.rock, 2, g_TileClasses.water, 5 ], "sizes": ["tiny"], "mixes": ["same"], "amounts": ["many"] } ])); Engine.SetProgress(90); placePlayersNomad( g_TileClasses.player, avoidClasses( g_TileClasses.bluff, 4, g_TileClasses.water, 4, g_TileClasses.forest, 1, g_TileClasses.metal, 4, g_TileClasses.rock, 4, g_TileClasses.mountain, 4, g_TileClasses.animals, 2)); g_Map.ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/bahrain.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/bahrain.js (revision 21205) +++ ps/trunk/binaries/data/mods/public/maps/random/bahrain.js (revision 21206) @@ -1,476 +1,478 @@ /** * Heightmap image source: * Imagery by Jesse Allen, NASA's Earth Observatory, * using data from the General Bathymetric Chart of the Oceans (GEBCO) * produced by the British Oceanographic Data Centre. * https://visibleearth.nasa.gov/view.php?id=73934 * * Licensing: Public Domain, https://visibleearth.nasa.gov/useterms.php * * The heightmap image is reproduced using: * wget https://eoimages.gsfc.nasa.gov/images/imagerecords/73000/73934/gebco_08_rev_elev_C1_grey_geo.tif * lat=25.574723; lon=50.65; width=1.5; * lat1=$(bc <<< ";scale=5;$lat-$width/2"); lon1=$(bc <<< ";scale=5;$lon+$width/2"); lat2=$(bc <<< ";scale=5;$lat+$width/2"); lon2=$(bc <<< ";scale=5;$lon-$width/2") * gdal_translate -projwin $lon2 $lat2 $lon1 $lat1 gebco_08_rev_elev_C1_grey_geo.tif bahrain.tif * convert bahrain.tif -resize 512 -contrast-stretch 0 bahrain.png * No further changes should be applied to the image to keep it easily interchangeable. */ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen2"); Engine.LoadLibrary("rmbiome"); setBiome("generic/desert"); setLandBiome(); function setLandBiome() { g_Terrains.mainTerrain = new Array(3).fill("desert_dirt_rough_2").concat(["desert_dirt_rocks_3", "desert_sand_stones"]); g_Terrains.forestFloor1 = "grass_dead"; g_Terrains.forestFloor2 = "desert_dirt_persia_1"; g_Terrains.tier1Terrain = "desert_sand_dunes_stones"; g_Terrains.tier2Terrain = "desert_sand_scrub"; g_Terrains.tier3Terrain = "desert_plants_b"; g_Terrains.tier4Terrain = "medit_dirt_dry"; } function setIslandBiome() { g_Terrains.mainTerrain = "sand"; g_Terrains.forestFloor1 = "desert_wave"; g_Terrains.forestFloor2 = "desert_sahara"; g_Terrains.tier1Terrain = "sand_scrub_25"; g_Terrains.tier2Terrain = "sand_scrub_75"; g_Terrains.tier3Terrain = "sand_scrub_50"; g_Terrains.tier4Terrain = "sand"; } g_Terrains.roadWild = "desert_city_tile_pers_dirt"; g_Terrains.road = "desert_city_tile_pers"; g_Gaia.mainHuntableAnimal = "gaia/fauna_camel"; g_Gaia.secondaryHuntableAnimal = "gaia/fauna_gazelle"; g_Gaia.fish = "gaia/fauna_fish"; g_Gaia.tree1 = "gaia/flora_tree_cretan_date_palm_tall"; g_Gaia.tree2 = "gaia/flora_tree_cretan_date_palm_short"; g_Gaia.tree3 = "gaia/flora_tree_cretan_date_palm_patch"; g_Gaia.tree4 = "gaia/flora_tree_cretan_date_palm_tall"; g_Gaia.tree5 = "gaia/flora_tree_cretan_date_palm_short"; g_Gaia.fruitBush = "gaia/flora_bush_grapes"; g_Gaia.woodTreasure = "gaia/treasure/wood"; g_Gaia.foodTreasure = "gaia/treasure/food_bin"; g_Gaia.shipWreck = "gaia/treasure/shipwreck_sail_boat_cut"; g_Decoratives.grass = "actor|props/flora/grass_field_parched_short.xml"; g_Decoratives.grassShort = "actor|props/flora/grass_field_parched_short.xml"; g_Decoratives.rockLarge = "actor|geology/stone_savanna_med.xml"; g_Decoratives.rockMedium = "actor|geology/stone_granite_greek_small.xml"; g_Decoratives.bushMedium = "actor|props/flora/bush_desert_dry_a.xml"; g_Decoratives.bushSmall = "actor|props/flora/bush_medit_la_dry"; const heightScale = num => num * g_MapSettings.Size / 320; const heightSeaGround = heightScale(-6); const heightWaterLevel = heightScale(0); const heightShoreline = heightScale(0.5); var g_Map = new RandomMap(0, g_Terrains.mainTerrain); var mapBounds = g_Map.getBounds(); var mapCenter = g_Map.getCenter(); var numPlayers = getNumPlayers(); g_Map.LoadHeightmapImage("bahrain.png", 0, 15); Engine.SetProgress(15); initTileClasses(["island", "shoreline"]); g_Map.log("Lowering sea ground"); createArea( new MapBoundsPlacer(), new SmoothElevationPainter(ELEVATION_SET, heightSeaGround, 2), new HeightConstraint(-Infinity, heightWaterLevel)); Engine.SetProgress(20); g_Map.log("Smoothing heightmap"); createArea( new MapBoundsPlacer(), new SmoothingPainter(1, scaleByMapSize(0.1, 0.5), 1)); Engine.SetProgress(25); g_Map.log("Marking water"); createArea( new MapBoundsPlacer(), new TileClassPainter(g_TileClasses.water), new HeightConstraint(-Infinity, heightWaterLevel)); Engine.SetProgress(30); g_Map.log("Marking land"); createArea( new MapBoundsPlacer(), new TileClassPainter(g_TileClasses.land), avoidClasses(g_TileClasses.water, 0)); Engine.SetProgress(35); g_Map.log("Marking island"); var areaIsland = createArea( new RectPlacer(new Vector2D(fractionToTiles(0.4), mapBounds.top), new Vector2D(fractionToTiles(0.6), mapCenter.y), Infinity), new TileClassPainter(g_TileClasses.island), avoidClasses(g_TileClasses.water, 0)); Engine.SetProgress(35); g_Map.log("Painting shoreline"); createArea( new MapBoundsPlacer(), [ new TerrainPainter(g_Terrains.water), new TileClassPainter(g_TileClasses.shoreline) ], new HeightConstraint(-Infinity, heightShoreline)); Engine.SetProgress(40); g_Map.log("Painting cliffs"); createArea( new MapBoundsPlacer(), [ new TerrainPainter(g_Terrains.cliff), new TileClassPainter(g_TileClasses.mountain), ], [ avoidClasses(g_TileClasses.water, 2), new SlopeConstraint(2, Infinity) ]); Engine.SetProgress(45); if (!isNomad()) { g_Map.log("Placing players"); - let playerBases = placeRandom( - sortAllPlayers(), - [ - avoidClasses(g_TileClasses.island, 5), - stayClasses(g_TileClasses.land, defaultPlayerBaseRadius() / 2) - ]); + let [playerIDs, playerPosition] = createBases( + ...playerPlacementRandom( + sortAllPlayers(), + [ + avoidClasses(g_TileClasses.island, 5), + stayClasses(g_TileClasses.land, defaultPlayerBaseRadius() / 2) + ]), + "towers"); g_Map.log("Flatten the initial CC area..."); - for (let player of playerBases) + for (let position of playerPosition) createArea( - new ClumpPlacer(diskArea(defaultPlayerBaseRadius() * 0.8), 0.95, 0.6, Infinity, player.position), - new SmoothElevationPainter(ELEVATION_SET, g_Map.getHeight(player.position), 6)); + new ClumpPlacer(diskArea(defaultPlayerBaseRadius() * 0.8), 0.95, 0.6, Infinity, position), + new SmoothElevationPainter(ELEVATION_SET, g_Map.getHeight(position), 6)); } addElements([ { "func": addLayeredPatches, "avoid": [ g_TileClasses.dirt, 5, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12, g_TileClasses.water, 3, g_TileClasses.island, 2 ], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["many"] }, { "func": addDecoration, "avoid": [ g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12, g_TileClasses.water, 3, g_TileClasses.island, 2 ], "sizes": ["small"], "mixes": ["same"], "amounts": ["many"] } ]); addElements(shuffleArray([ { "func": addMetal, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.player, 30, g_TileClasses.rock, 20, g_TileClasses.metal, 30, g_TileClasses.water, 3, g_TileClasses.island, 2 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["normal"] }, { "func": addStone, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.player, 30, g_TileClasses.rock, 30, g_TileClasses.metal, 20, g_TileClasses.water, 3, g_TileClasses.island, 2 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["normal"] } ])); addElements([ { "func": addForests, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 35, g_TileClasses.metal, 3, g_TileClasses.player, 20, g_TileClasses.rock, 3, g_TileClasses.water, 2, g_TileClasses.island, 2 ], "sizes": ["big"], "mixes": ["similar"], "amounts": ["few"] }, { "func": addForests, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 18, g_TileClasses.metal, 3, g_TileClasses.player, 20, g_TileClasses.rock, 3, g_TileClasses.water, 2, g_TileClasses.island, 2 ], "sizes": ["normal"], "mixes": ["similar"], "amounts": ["many"] }, ]); addElements(shuffleArray([ { "func": addBerries, "avoid": [ g_TileClasses.berries, 30, g_TileClasses.forest, 5, g_TileClasses.metal, 10, g_TileClasses.player, 20, g_TileClasses.rock, 10, g_TileClasses.water, 3, g_TileClasses.island, 2 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["few"] }, { "func": addAnimals, "avoid": [ g_TileClasses.animals, 20, g_TileClasses.forest, 2, g_TileClasses.metal, 2, g_TileClasses.mountain, 1, g_TileClasses.player, 20, g_TileClasses.rock, 2, g_TileClasses.water, 3, g_TileClasses.island, 2 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["tons"] }, { "func": addFish, "avoid": [ g_TileClasses.fish, 18, g_TileClasses.player, 8 ], "stay": [g_TileClasses.water, 4], "sizes": ["normal"], "mixes": ["same"], "amounts": ["normal"] }, { "func": addStragglerTrees, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 7, g_TileClasses.metal, 2, g_TileClasses.player, 12, g_TileClasses.rock, 2, g_TileClasses.water, 5, g_TileClasses.island, 2 ], "sizes": ["small"], "mixes": ["same"], "amounts": ["normal"] } ])); Engine.SetProgress(65); g_Map.log("Painting island..."); setIslandBiome(); addElements([ { "func": addLayeredPatches, "avoid": [ g_TileClasses.dirt, 5, g_TileClasses.forest, 2, g_TileClasses.player, 12, g_TileClasses.water, 3 ], "stay": [g_TileClasses.island, 2], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["few"] }, { "func": addDecoration, "avoid": [ g_TileClasses.forest, 2, g_TileClasses.player, 12, g_TileClasses.water, 3 ], "stay": [g_TileClasses.island, 2], "sizes": ["tiny"], "mixes": ["same"], "amounts": ["scarce"] } ]); g_Map.log("Creating island mines"); for (let i = 0; i < scaleByMapSize(4, 10); ++i) createObjectGroupsByAreas( randBool() ? new SimpleGroup([new SimpleObject(g_Gaia.metalLarge, 1, 1, 0, 4)], true, g_TileClasses.metal) : new SimpleGroup([new SimpleObject(g_Gaia.stoneLarge, 1, 1, 0, 4)], true, g_TileClasses.rock), 0, [avoidClasses(g_TileClasses.rock, 8, g_TileClasses.metal, 8), stayClasses(g_TileClasses.island, 4)], 1, 40, [areaIsland]); addElements(shuffleArray([ { "func": addForests, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 10, g_TileClasses.metal, 3, g_TileClasses.player, 20, g_TileClasses.rock, 3, g_TileClasses.water, 2 ], "stay": [g_TileClasses.island, 2], "sizes": ["normal"], "mixes": ["similar"], "amounts": ["normal"] }, { "func": addStragglerTrees, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 7, g_TileClasses.metal, 2, g_TileClasses.player, 12, g_TileClasses.rock, 2, g_TileClasses.water, 5 ], "stay": [g_TileClasses.island, 2], "sizes": ["normal"], "mixes": ["same"], "amounts": ["many"] } ])); Engine.SetProgress(80); g_Map.log("Adding more decoratives..."); createObjectGroups( new SimpleGroup( [ new SimpleObject("actor|props/special/eyecandy/awning_wood_small.xml", 1, 1, 1, 7), new SimpleObject("actor|props/special/eyecandy/barrels_buried.xml", 1, 2, 1, 7) ], true, g_TileClasses.dirt), 0, avoidClasses( g_TileClasses.water, 2, g_TileClasses.player, 10, g_TileClasses.mountain, 2, g_TileClasses.forest, 2), 2 * scaleByMapSize(1, 4), 20); Engine.SetProgress(85); g_Map.log("Creating treasures..."); for (let treasure of [g_Gaia.woodTreasure, g_Gaia.foodTreasure]) createObjectGroups( new SimpleGroup([new SimpleObject(treasure, 1, 1, 0, 2)], true), 0, avoidClasses( g_TileClasses.water, 2, g_TileClasses.player, 25, g_TileClasses.forest, 2), randIntInclusive(1, numPlayers), 20); Engine.SetProgress(90); g_Map.log("Creating shipwrecks..."); createObjectGroups( new SimpleGroup([new SimpleObject(g_Gaia.shipWreck, 1, 1, 0, 1)], true), 0, stayClasses(g_TileClasses.water, 2), randIntInclusive(0, 1), 20); Engine.SetProgress(95); placePlayersNomad( g_Map.createTileClass(), [ stayClasses(g_TileClasses.land, 5), avoidClasses( g_TileClasses.island, 0, g_TileClasses.forest, 2, g_TileClasses.rock, 4, g_TileClasses.metal, 4, g_TileClasses.berries, 1, g_TileClasses.animals, 1) ]); setSunColor(0.733, 0.746, 0.574); setSkySet("cloudless"); // Prevent the water from disappearing on the tiny mapsize while maximizing land on greater sizes setWaterHeight(scaleByMapSize(20, 18)); setWaterTint(0.37, 0.67, 0.73); setWaterColor(0.24, 0.44, 0.56); setWaterWaviness(9); setWaterMurkiness(0.8); setWaterType("lake"); setTerrainAmbientColor(0.521, 0.475, 0.322); setSunRotation(Math.PI); setSunElevation(Math.PI / 6.25); setFogFactor(0); setFogThickness(0); setFogColor(0.69, 0.616, 0.541); setPPEffect("hdr"); setPPContrast(0.67); setPPSaturation(0.42); setPPBloom(0.23); g_Map.ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/belgian_uplands.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/belgian_uplands.js (revision 21205) +++ ps/trunk/binaries/data/mods/public/maps/random/belgian_uplands.js (revision 21206) @@ -1,282 +1,282 @@ Engine.LoadLibrary("rmgen"); -Engine.LoadLibrary("rmgen2"); Engine.LoadLibrary("heightmap"); const tPrimary = ["temp_grass", "temp_grass_b", "temp_grass_c", "temp_grass_d", "temp_grass_long_b", "temp_grass_clovers_2", "temp_grass_mossy", "temp_grass_plants"]; const heightLand = 0; var g_Map = new RandomMap(heightLand, tPrimary); var numPlayers = getNumPlayers(); var mapSize = g_Map.getSize(); var mapCenter = g_Map.getCenter(); // 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}; // Since erosion is not predictable, actual water coverage can differ much with the same value var averageWaterCoverage = scaleByMapSize(1/5, 1/3); var heightSeaGround = -MIN_HEIGHT + heightRange.min + averageWaterCoverage * (heightRange.max - heightRange.min); var heightSeaGroundAdjusted = heightSeaGround + MIN_HEIGHT; setWaterHeight(heightSeaGround); var textueByHeight = []; // Deep water textueByHeight.push({"upperHeightLimit": heightRange.min + 1/3 * (heightSeaGroundAdjusted - heightRange.min), "terrain": "temp_sea_rocks"}); // Medium deep water (with fish) var terrains = ["temp_sea_weed"]; terrains = terrains.concat(terrains, terrains, terrains, terrains); terrains = terrains.concat(terrains, terrains, terrains, terrains); terrains.push("temp_sea_weed|gaia/fauna_fish"); textueByHeight.push({"upperHeightLimit": heightRange.min + 2/3 * (heightSeaGroundAdjusted - heightRange.min), "terrain": terrains}); // Flat Water textueByHeight.push({"upperHeightLimit": heightRange.min + 3/3 * (heightSeaGroundAdjusted - heightRange.min), "terrain": "temp_mud_a"}); // Water surroundings/bog (with stone/metal some rabits and bushes) var terrains = ["temp_plants_bog", "temp_plants_bog_aut", "temp_dirt_gravel_plants", "temp_grass_d"]; terrains = terrains.concat(terrains, terrains, terrains, terrains, terrains); terrains = ["temp_plants_bog|gaia/flora_bush_temperate"].concat(terrains, terrains); terrains = ["temp_dirt_gravel_plants|gaia/geology_metal_temperate", "temp_dirt_gravel_plants|gaia/geology_stone_temperate", "temp_plants_bog|gaia/fauna_rabbit"].concat(terrains, terrains); terrains = ["temp_plants_bog_aut|gaia/flora_tree_dead"].concat(terrains, terrains); textueByHeight.push({"upperHeightLimit": heightSeaGroundAdjusted + 1/6 * (heightRange.max - heightSeaGroundAdjusted), "terrain": terrains}); // Juicy grass near bog textueByHeight.push({"upperHeightLimit": heightSeaGroundAdjusted + 2/6 * (heightRange.max - heightSeaGroundAdjusted), "terrain": ["temp_grass", "temp_grass_d", "temp_grass_long_b", "temp_grass_plants"]}); // Medium level grass // var testActor = "actor|geology/decal_stone_medit_a.xml"; textueByHeight.push({"upperHeightLimit": heightSeaGroundAdjusted + 3/6 * (heightRange.max - heightSeaGroundAdjusted), "terrain": ["temp_grass", "temp_grass_b", "temp_grass_c", "temp_grass_mossy"]}); // Long grass near forest border textueByHeight.push({"upperHeightLimit": heightSeaGroundAdjusted + 4/6 * (heightRange.max - heightSeaGroundAdjusted), "terrain": ["temp_grass", "temp_grass_b", "temp_grass_c", "temp_grass_d", "temp_grass_long_b", "temp_grass_clovers_2", "temp_grass_mossy", "temp_grass_plants"]}); // Forest border (With wood/food plants/deer/rabits) var terrains = ["temp_grass_plants|gaia/flora_tree_euro_beech", "temp_grass_mossy|gaia/flora_tree_poplar", "temp_grass_mossy|gaia/flora_tree_poplar_lombardy", "temp_grass_long|gaia/flora_bush_temperate", "temp_mud_plants|gaia/flora_bush_temperate", "temp_mud_plants|gaia/flora_bush_badlands", "temp_grass_long|gaia/flora_tree_apple", "temp_grass_clovers|gaia/flora_bush_berry", "temp_grass_clovers_2|gaia/flora_bush_grapes", "temp_grass_plants|gaia/fauna_deer", "temp_grass_long_b|gaia/fauna_rabbit"]; var numTerrains = terrains.length; for (var i = 0; i < numTerrains; i++) terrains.push("temp_grass_plants"); textueByHeight.push({"upperHeightLimit": heightSeaGroundAdjusted + 5/6 * (heightRange.max - heightSeaGroundAdjusted), "terrain": terrains}); // Unpassable woods textueByHeight.push({"upperHeightLimit": heightSeaGroundAdjusted + 6/6 * (heightRange.max - heightSeaGroundAdjusted), "terrain": ["temp_grass_mossy|gaia/flora_tree_oak", "temp_forestfloor_pine|gaia/flora_tree_pine", "temp_grass_mossy|gaia/flora_tree_oak", "temp_forestfloor_pine|gaia/flora_tree_pine", "temp_mud_plants|gaia/flora_tree_dead", "temp_plants_bog|gaia/flora_tree_oak_large", "temp_dirt_gravel_plants|gaia/flora_tree_aleppo_pine", "temp_forestfloor_autumn|gaia/flora_tree_carob"]}); Engine.SetProgress(5); var lowerHeightLimit = textueByHeight[3].upperHeightLimit; var upperHeightLimit = textueByHeight[6].upperHeightLimit; -var playerPositions; +var playerPosition; +var playerIDs; + while (true) { g_Map.log("Randomizing heightmap") createArea( new MapBoundsPlacer(), new RandomElevationPainter(heightRange.min, heightRange.max)); // More cycles yield bigger structures g_Map.log("Smoothing map"); createArea( new MapBoundsPlacer(), new SmoothingPainter(2, 1, 20)); g_Map.log("Rescaling map"); rescaleHeightmap(heightRange.min, heightRange.max, g_Map.height); g_Map.log("Mark valid heightrange for player starting positions"); let tHeightRange = g_Map.createTileClass(); let area = createArea( new ClumpPlacer(diskArea(fractionToTiles(0.5) - MAP_BORDER_WIDTH), 1, 1, Infinity, mapCenter), new TileClassPainter(tHeightRange), new HeightConstraint(lowerHeightLimit, upperHeightLimit)); - if (area) - try { - playerPositions = randomPlayerLocations(sortAllPlayers(), new stayClasses(tHeightRange, 15)); - break; - } - catch (e) { - } + let players = area && playerPlacementRandom(sortAllPlayers(), stayClasses(tHeightRange, 15), true); + if (players) + { + [playerIDs, playerPosition] = players; + break; + } g_Map.log("Too few starting locations"); } Engine.SetProgress(60); g_Map.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 (let x = 0; x < mapSize; ++x) for (let y = 0; y < mapSize; ++y) { let position = new Vector2D(x, y); if (!g_Map.validHeight(position)) continue; var textureMinHeight = heightRange.min; for (var i = 0; i < textueByHeight.length; i++) { if (g_Map.getHeight(position) >= textureMinHeight && g_Map.getHeight(position) <= textueByHeight[i].upperHeightLimit) { createTerrain(textueByHeight[i].terrain).place(position); let template; if (i == 0) // ...deep water { if (randBool(propDensity / 100)) template = "actor|props/flora/pond_lillies_large.xml"; else if (randBool(propDensity / 40)) template = "actor|props/flora/water_lillies.xml"; } if (i == 1) // ...medium water (with fish) { if (randBool(propDensity / 200)) template = "actor|props/flora/pond_lillies_large.xml"; else if (randBool(propDensity / 100)) template = "actor|props/flora/water_lillies.xml"; } if (i == 2) // ...low water/mud { if (randBool(propDensity / 200)) template = "actor|props/flora/water_log.xml"; else if (randBool(propDensity / 100)) template = "actor|props/flora/water_lillies.xml"; else if (randBool(propDensity / 40)) template = "actor|geology/highland_c.xml"; else if (randBool(propDensity / 20)) template = "actor|props/flora/reeds_pond_lush_b.xml"; else if (randBool(propDensity / 10)) template = "actor|props/flora/reeds_pond_lush_a.xml"; } if (i == 3) // ...water suroundings/bog { if (randBool(propDensity / 200)) template = "actor|props/flora/water_log.xml"; else if (randBool(propDensity / 100)) template = "actor|geology/highland_c.xml"; else if (randBool(propDensity / 40)) template = "actor|props/flora/reeds_pond_lush_a.xml"; } if (i == 4) // ...low height grass { if (randBool(propDensity / 800)) template = "actor|props/flora/grass_field_flowering_tall.xml"; else if (randBool(propDensity / 400)) template = "actor|geology/gray_rock1.xml"; else if (randBool(propDensity / 200)) template = "actor|props/flora/bush_tempe_sm_lush.xml"; else if (randBool(propDensity / 100)) template = "actor|props/flora/bush_tempe_b.xml"; else if (randBool(propDensity / 40)) template = "actor|props/flora/grass_soft_small_tall.xml"; } if (i == 5) // ...medium height grass { if (randBool(propDensity / 800)) template = "actor|geology/decal_stone_medit_a.xml"; else if (randBool(propDensity / 400)) template = "actor|props/flora/decals_flowers_daisies.xml"; else if (randBool(propDensity / 200)) template = "actor|props/flora/bush_tempe_underbrush.xml"; else if (randBool(propDensity / 100)) template = "actor|props/flora/grass_soft_small_tall.xml"; else if (randBool(propDensity / 40)) template = "actor|props/flora/grass_temp_field.xml"; } if (i == 6) // ...high height grass { if (randBool(propDensity / 400)) template = "actor|geology/stone_granite_boulder.xml"; else if (randBool(propDensity / 200)) template = "actor|props/flora/foliagebush.xml"; else if (randBool(propDensity / 100)) template = "actor|props/flora/bush_tempe_underbrush.xml"; else if (randBool(propDensity / 40)) template = "actor|props/flora/grass_soft_small_tall.xml"; else if (randBool(propDensity / 20)) template = "actor|props/flora/ferns.xml"; } if (i == 7) // ...forest border (with wood/food plants/deer/rabits) { if (randBool(propDensity / 400)) template = "actor|geology/highland_c.xml"; else if (randBool(propDensity / 200)) template = "actor|props/flora/bush_tempe_a.xml"; else if (randBool(propDensity / 100)) template = "actor|props/flora/ferns.xml"; else if (randBool(propDensity / 40)) template = "actor|props/flora/grass_soft_tuft_a.xml"; } if (i == 8) // ...woods { if (randBool(propDensity / 200)) template = "actor|geology/highland2_moss.xml"; else if (randBool(propDensity / 100)) template = "actor|props/flora/grass_soft_tuft_a.xml"; else if (randBool(propDensity / 40)) template = "actor|props/flora/ferns.xml"; } if (template) g_Map.placeEntityAnywhere(template, 0, position, randomAngle()); break; } else textureMinHeight = textueByHeight[i].upperHeightLimit; } } Engine.SetProgress(90); if (isNomad()) placePlayersNomad(g_Map.createTileClass(), new HeightConstraint(lowerHeightLimit, upperHeightLimit)); else { g_Map.log("Placing players and starting resources"); let resourceDistance = 8; let resourceSpacing = 1; let resourceCount = 4; for (let i = 0; i < numPlayers; ++i) { - placeCivDefaultStartingEntities(playerPositions[i].position, playerPositions[i].id, false); + placeCivDefaultStartingEntities(playerPosition[i], 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([ - playerPositions[i].position, + playerPosition[i], 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) ]); g_Map.placeEntityPassable(j % 2 ? "gaia/flora_tree_cypress" : "gaia/flora_bush_berry", 0, pos, randomAngle()); } } } } g_Map.ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/caledonian_meadows.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/caledonian_meadows.js (revision 21205) +++ ps/trunk/binaries/data/mods/public/maps/random/caledonian_meadows.js (revision 21206) @@ -1,429 +1,429 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmbiome"); Engine.LoadLibrary("heightmap"); var tGrove = "temp_grass_plants"; var tPath = "road_rome_a"; var oGroveEntities = ["structures/gaul_outpost", "gaia/flora_tree_oak_new"]; var g_Map = new RandomMap(0, "whiteness"); /** * Design resource spots */ // Mines let decorations = [ "actor|geology/gray1.xml", "actor|geology/gray_rock1.xml", "actor|geology/highland1.xml", "actor|geology/highland2.xml", "actor|geology/highland3.xml", "actor|geology/highland_c.xml", "actor|geology/highland_d.xml", "actor|geology/highland_e.xml", "actor|props/flora/bush.xml", "actor|props/flora/bush_dry_a.xml", "actor|props/flora/bush_highlands.xml", "actor|props/flora/bush_tempe_a.xml", "actor|props/flora/bush_tempe_b.xml", "actor|props/flora/ferns.xml" ]; function placeMine(point, centerEntity) { g_Map.placeEntityPassable(centerEntity, 0, point, randomAngle()); let quantity = randIntInclusive(11, 23); let dAngle = 2 * Math.PI / quantity; for (let i = 0; i < quantity; ++i) g_Map.placeEntityPassable( pickRandom(decorations), 0, Vector2D.add(point, new Vector2D(randFloat(2, 5), 0).rotate(-dAngle * randFloat(i, i + 1))), randomAngle()); } // Food, fences with domestic animals g_WallStyles.other = { "overlap": 0, "fence": readyWallElement("other/fence_long", "gaia"), "fence_short": readyWallElement("other/fence_short", "gaia"), "bench": { "angle": Math.PI / 2, "length": 1.5, "indent": 0, "bend": 0, "templateName": "other/bench" }, "sheep": { "angle": 0, "length": 0, "indent": 0.75, "bend": 0, "templateName": "gaia/fauna_sheep" }, "foodBin": { "angle": Math.PI / 2, "length": 1.5, "indent": 0, "bend": 0, "templateName": "gaia/treasure/food_bin" }, "farmstead": { "angle": Math.PI, "length": 0, "indent": -3, "bend": 0, "templateName": "structures/brit_farmstead" } }; let fences = [ new Fortress("fence", [ "foodBin", "farmstead", "bench", "turn_0.25", "sheep", "turn_0.25", "fence", "turn_0.25", "sheep", "turn_0.25", "fence", "turn_0.25", "sheep", "turn_0.25", "fence" ]), new Fortress("fence", [ "foodBin", "farmstead", "fence", "turn_0.25", "sheep", "turn_0.25", "fence", "turn_0.25", "sheep", "turn_0.25", "bench", "sheep", "fence", "turn_0.25", "sheep", "turn_0.25", "fence" ]), new Fortress("fence", [ "foodBin", "farmstead", "turn_0.5", "bench", "turn_-0.5", "fence_short", "turn_0.25", "sheep", "turn_0.25", "fence", "turn_0.25", "sheep", "turn_0.25", "fence", "turn_0.25", "sheep", "turn_0.25", "fence_short", "sheep", "fence" ]), new Fortress("fence", [ "foodBin", "farmstead", "turn_0.5", "fence_short", "turn_-0.5", "bench", "turn_0.25", "sheep", "turn_0.25", "fence", "turn_0.25", "sheep", "turn_0.25", "fence", "turn_0.25", "sheep", "turn_0.25", "fence_short", "sheep", "fence" ]), new Fortress("fence", [ "foodBin", "farmstead", "fence", "turn_0.25", "sheep", "turn_0.25", "bench", "sheep", "fence", "turn_0.25", "sheep", "turn_0.25", "fence_short", "sheep", "fence", "turn_0.25", "sheep", "turn_0.25", "fence_short", "sheep", "fence" ]) ]; let num = fences.length; for (let i = 0; i < num; ++i) fences.push(new Fortress("fence", clone(fences[i].wall).reverse())); // Groves, only Wood let groveEntities = ["gaia/flora_bush_temperate", "gaia/flora_tree_euro_beech"]; let groveActors = [ "actor|geology/highland1_moss.xml", "actor|geology/highland2_moss.xml", "actor|props/flora/bush.xml", "actor|props/flora/bush_dry_a.xml", "actor|props/flora/bush_highlands.xml", "actor|props/flora/bush_tempe_a.xml", "actor|props/flora/bush_tempe_b.xml", "actor|props/flora/ferns.xml" ]; let clGrove = g_Map.createTileClass(); function placeGrove(point) { g_Map.placeEntityPassable(pickRandom(oGroveEntities), 0, point, randomAngle()); let quantity = randIntInclusive(20, 30); let dAngle = 2 * Math.PI / quantity; for (let i = 0; i < quantity; ++i) { let angle = dAngle * randFloat(i, i + 1); let dist = randFloat(2, 5); let objectList = groveEntities; if (i % 3 == 0) objectList = groveActors; let position = Vector2D.add(point, new Vector2D(dist, 0).rotate(-angle)); g_Map.placeEntityPassable(pickRandom(objectList), 0, position, randomAngle()); createArea( new ClumpPlacer(5, 1, 1, Infinity, position), [ new TerrainPainter(tGrove), new TileClassPainter(clGrove) ]); } } // Camps with fire and gold treasure function placeCamp(point, centerEntity = "actor|props/special/eyecandy/campfire.xml", otherEntities = ["gaia/treasure/metal", "gaia/treasure/standing_stone", "units/brit_infantry_slinger_b", "units/brit_infantry_javelinist_b", "units/gaul_infantry_slinger_b", "units/gaul_infantry_javelinist_b", "units/gaul_champion_fanatic", "actor|props/special/common/waypoint_flag.xml", "actor|props/special/eyecandy/barrel_a.xml", "actor|props/special/eyecandy/basket_celt_a.xml", "actor|props/special/eyecandy/crate_a.xml", "actor|props/special/eyecandy/dummy_a.xml", "actor|props/special/eyecandy/handcart_1.xml", "actor|props/special/eyecandy/handcart_1_broken.xml", "actor|props/special/eyecandy/sack_1.xml", "actor|props/special/eyecandy/sack_1_rough.xml" ] ) { g_Map.placeEntityPassable(centerEntity, 0, point, randomAngle()); let quantity = randIntInclusive(5, 11); let dAngle = 2 * Math.PI / quantity; for (let i = 0; i < quantity; ++i) { let angle = dAngle * randFloat(i, i + 1); let dist = randFloat(1, 3); g_Map.placeEntityPassable(pickRandom(otherEntities), 0, Vector2D.add(point, new Vector2D(dist, 0).rotate(-angle)), randomAngle()); } } function placeStartLocationResources(point, foodEntities = ["gaia/flora_bush_berry", "gaia/fauna_chicken", "gaia/fauna_chicken"]) { let currentAngle = randomAngle(); // Stone and chicken let dAngle = 4/9 * Math.PI; let angle = currentAngle + randFloat(1, 3) * dAngle / 4; let stonePosition = Vector2D.add(point, new Vector2D(12, 0).rotate(-angle)); placeMine(stonePosition, "gaia/geology_stonemine_temperate_quarry"); currentAngle += dAngle; // Wood let quantity = 80; dAngle = 2 * Math.PI / quantity / 3; for (let i = 0; i < quantity; ++i) { angle = currentAngle + randFloat(0, dAngle); let objectList = groveEntities; if (i % 2 == 0) objectList = groveActors; let woodPosition = Vector2D.add(point, new Vector2D(randFloat(10, 15), 0).rotate(-angle)); g_Map.placeEntityPassable(pickRandom(objectList), 0, woodPosition, randomAngle()); createArea( new ClumpPlacer(5, 1, 1, Infinity, woodPosition), [ new TerrainPainter("temp_grass_plants"), new TileClassPainter(clGrove) ]); currentAngle += dAngle; } // Metal and chicken dAngle = 2 * Math.PI * 2 / 9; angle = currentAngle + dAngle * randFloat(1, 3) / 4; let metalPosition = Vector2D.add(point, new Vector2D(13, 0).rotate(-angle)); placeMine(metalPosition, "gaia/geology_metal_temperate_slabs"); currentAngle += dAngle; // Berries quantity = 15; dAngle = 2 * Math.PI / quantity * 2 / 9; for (let i = 0; i < quantity; ++i) { angle = currentAngle + randFloat(0, dAngle); let berriesPosition = Vector2D.add(point, new Vector2D(randFloat(10, 15), 0).rotate(-angle)); g_Map.placeEntityPassable(pickRandom(foodEntities), 0, berriesPosition, randomAngle()); currentAngle += dAngle; } } /** * Environment settings */ setBiome("generic/alpine"); g_Environment.Fog.FogColor = { "r": 0.8, "g": 0.8, "b": 0.8, "a": 0.01 }; g_Environment.Water.WaterBody.Colour = { "r" : 0.3, "g" : 0.05, "b" : 0.1, "a" : 0.1 }; g_Environment.Water.WaterBody.Murkiness = 0.4; /** * Base terrain shape generation and settings */ let heightScale = (g_Map.size + 256) / 768 / 4; let heightRange = { "min": MIN_HEIGHT * heightScale, "max": MAX_HEIGHT * heightScale }; // Water coverage let averageWaterCoverage = 1/5; // NOTE: Since terrain generation is quite unpredictable actual water coverage might vary much with the same value let heightSeaGround = -MIN_HEIGHT + heightRange.min + averageWaterCoverage * (heightRange.max - heightRange.min); // Water height in environment and the engine let heightSeaGroundAdjusted = heightSeaGround + MIN_HEIGHT; // Water height in RMGEN setWaterHeight(heightSeaGround); g_Map.log("Generating terrain using diamon-square"); let medH = (heightRange.min + heightRange.max) / 2; let initialHeightmap = [[medH, medH], [medH, medH]]; setBaseTerrainDiamondSquare(heightRange.min, heightRange.max, initialHeightmap, 0.8); g_Map.log("Apply erosion"); for (let i = 0; i < 5; ++i) splashErodeMap(0.1); rescaleHeightmap(heightRange.min, heightRange.max); Engine.SetProgress(25); let heighLimits = [ heightRange.min + 1/3 * (heightSeaGroundAdjusted - heightRange.min), // 0 Deep water heightRange.min + 2/3 * (heightSeaGroundAdjusted - heightRange.min), // 1 Medium Water heightRange.min + (heightSeaGroundAdjusted - heightRange.min), // 2 Shallow water heightSeaGroundAdjusted + 1/8 * (heightRange.max - heightSeaGroundAdjusted), // 3 Shore heightSeaGroundAdjusted + 2/8 * (heightRange.max - heightSeaGroundAdjusted), // 4 Low ground heightSeaGroundAdjusted + 3/8 * (heightRange.max - heightSeaGroundAdjusted), // 5 Player and path height heightSeaGroundAdjusted + 4/8 * (heightRange.max - heightSeaGroundAdjusted), // 6 High ground heightSeaGroundAdjusted + 5/8 * (heightRange.max - heightSeaGroundAdjusted), // 7 Lower forest border heightSeaGroundAdjusted + 6/8 * (heightRange.max - heightSeaGroundAdjusted), // 8 Forest heightSeaGroundAdjusted + 7/8 * (heightRange.max - heightSeaGroundAdjusted), // 9 Upper forest border heightSeaGroundAdjusted + (heightRange.max - heightSeaGroundAdjusted)]; // 10 Hilltop let playerHeight = (heighLimits[4] + heighLimits[5]) / 2; // Average player height g_Map.log("Determining height-dependent biome"); // Texture and actor presets let myBiome = []; myBiome.push({ // 0 Deep water "texture": ["shoreline_stoney_a"], "actor": [["gaia/fauna_fish", "actor|geology/stone_granite_boulder.xml"], 0.02], "textureHS": ["alpine_mountainside"], "actorHS": [["gaia/fauna_fish"], 0.1] }); myBiome.push({ // 1 Medium Water "texture": ["shoreline_stoney_a", "alpine_shore_rocks"], "actor": [["actor|geology/stone_granite_boulder.xml", "actor|geology/stone_granite_med.xml"], 0.03], "textureHS": ["alpine_mountainside"], "actorHS": [["actor|geology/stone_granite_boulder.xml", "actor|geology/stone_granite_med.xml"], 0.0] }); myBiome.push({ // 2 Shallow water "texture": ["alpine_shore_rocks"], "actor": [["actor|props/flora/reeds_pond_dry.xml", "actor|geology/stone_granite_large.xml", "actor|geology/stone_granite_med.xml", "actor|props/flora/reeds_pond_lush_b.xml"], 0.2], "textureHS": ["alpine_mountainside"], "actorHS": [["actor|props/flora/reeds_pond_dry.xml", "actor|geology/stone_granite_med.xml"], 0.1] }); myBiome.push({ // 3 Shore "texture": ["alpine_shore_rocks_grass_50", "alpine_grass_rocky"], "actor": [["gaia/flora_tree_pine", "gaia/flora_bush_badlands", "actor|geology/highland1_moss.xml", "actor|props/flora/grass_soft_tuft_a.xml", "actor|props/flora/bush.xml"], 0.3], "textureHS": ["alpine_mountainside"], "actorHS": [["actor|props/flora/grass_soft_tuft_a.xml"], 0.1] }); myBiome.push({ // 4 Low ground "texture": ["alpine_dirt_grass_50", "alpine_grass_rocky"], "actor": [["actor|geology/stone_granite_med.xml", "actor|props/flora/grass_soft_tuft_a.xml", "actor|props/flora/bush.xml", "actor|props/flora/grass_medit_flowering_tall.xml"], 0.2], "textureHS": ["alpine_grass_rocky"], "actorHS": [["actor|geology/stone_granite_med.xml", "actor|props/flora/grass_soft_tuft_a.xml"], 0.1] }); myBiome.push({ // 5 Player and path height "texture": ["new_alpine_grass_c", "new_alpine_grass_b", "new_alpine_grass_d"], "actor": [["actor|geology/stone_granite_small.xml", "actor|props/flora/grass_soft_small.xml", "actor|props/flora/grass_medit_flowering_tall.xml"], 0.2], "textureHS": ["alpine_grass_rocky"], "actorHS": [["actor|geology/stone_granite_small.xml", "actor|props/flora/grass_soft_small.xml"], 0.1] }); myBiome.push({ // 6 High ground "texture": ["new_alpine_grass_a", "alpine_grass_rocky"], "actor": [["actor|geology/stone_granite_med.xml", "actor|props/flora/grass_tufts_a.xml", "actor|props/flora/bush_highlands.xml", "actor|props/flora/grass_medit_flowering_tall.xml"], 0.2], "textureHS": ["alpine_grass_rocky"], "actorHS": [["actor|geology/stone_granite_med.xml", "actor|props/flora/grass_tufts_a.xml"], 0.1] }); myBiome.push({ // 7 Lower forest border "texture": ["new_alpine_grass_mossy", "alpine_grass_rocky"], "actor": [["gaia/flora_tree_pine", "gaia/flora_tree_oak", "actor|props/flora/grass_tufts_a.xml", "gaia/flora_bush_berry", "actor|geology/highland2_moss.xml", "gaia/fauna_goat", "actor|props/flora/bush_tempe_underbrush.xml"], 0.3], "textureHS": ["alpine_cliff_c"], "actorHS": [["actor|props/flora/grass_tufts_a.xml", "actor|geology/highland2_moss.xml"], 0.1] }); myBiome.push({ // 8 Forest "texture": ["alpine_forrestfloor"], "actor": [["gaia/flora_tree_pine", "gaia/flora_tree_pine", "gaia/flora_tree_pine", "gaia/flora_tree_pine", "actor|geology/highland2_moss.xml", "actor|props/flora/bush_highlands.xml"], 0.5], "textureHS": ["alpine_cliff_c"], "actorHS": [["actor|geology/highland2_moss.xml", "actor|geology/stone_granite_med.xml"], 0.1] }); myBiome.push({ // 9 Upper forest border "texture": ["alpine_forrestfloor_snow", "new_alpine_grass_dirt_a"], "actor": [["gaia/flora_tree_pine", "actor|geology/snow1.xml"], 0.3], "textureHS": ["alpine_cliff_b"], "actorHS": [["actor|geology/stone_granite_med.xml", "actor|geology/snow1.xml"], 0.1] }); myBiome.push({ // 10 Hilltop "texture": ["alpine_cliff_a", "alpine_cliff_snow"], "actor": [["actor|geology/highland1.xml"], 0.05], "textureHS": ["alpine_cliff_c"], "actorHS": [["actor|geology/highland1.xml"], 0.0] }); -let [playerIDs, playerPosition] = sortPlayersByLocation(getStartLocationsByHeightmap({ "min": heighLimits[4], "max": heighLimits[5] }, 1000, 30)); +let [playerIDs, playerPosition] = groupPlayersCycle(getStartLocationsByHeightmap({ "min": heighLimits[4], "max": heighLimits[5] }, 1000, 30)); Engine.SetProgress(30); g_Map.log("Smoothing player locations"); for (let position of playerPosition) createArea( new ClumpPlacer(diskArea(35), 1, 1, Infinity, position), new SmoothElevationPainter(ELEVATION_SET, g_Map.getHeight(position), 35)); g_Map.log("Creating paths between players"); let clPath = g_Map.createTileClass(); for (let i = 0; i < playerPosition.length; ++i) createArea( new RandomPathPlacer(playerPosition[i], playerPosition[(i + 1) % playerPosition.length], 4, 2, false), [ new TerrainPainter(tPath), new ElevationBlendingPainter(playerHeight, 0.4), new TileClassPainter(clPath) ]); g_Map.log("Smoothing paths"); createArea( new MapBoundsPlacer(), new SmoothingPainter(5, 1, 1), new NearTileClassConstraint(clPath, 5)); Engine.SetProgress(45); g_Map.log("Determining resource locations"); let avoidPoints = playerPosition.map(pos => pos.clone()); for (let i = 0; i < avoidPoints.length; ++i) avoidPoints[i].dist = 30; let resourceSpots = getPointsByHeight({ "min": (heighLimits[3] + heighLimits[4]) / 2, "max": (heighLimits[5] + heighLimits[6]) / 2 }, avoidPoints, clPath); Engine.SetProgress(55); /** * Divide tiles in areas by height and avoid paths */ let tchm = getTileCenteredHeightmap(); let areas = heighLimits.map(heightLimit => []); for (let x = 0; x < tchm.length; ++x) for (let y = 0; y < tchm[0].length; ++y) { let position = new Vector2D(x, y); if (!avoidClasses(clPath, 0).allows(position)) continue; let minHeight = heightRange.min; for (let h = 0; h < heighLimits.length; ++h) { if (tchm[x][y] >= minHeight && tchm[x][y] <= heighLimits[h]) { areas[h].push(position); break; } minHeight = heighLimits[h]; } } /** * Get max slope of each area */ let slopeMap = getSlopeMap(); let minSlope = []; let maxSlope = []; for (let h = 0; h < heighLimits.length; ++h) { minSlope[h] = Infinity; maxSlope[h] = 0; for (let point of areas[h]) { let slope = slopeMap[point.x][point.y]; if (slope > maxSlope[h]) maxSlope[h] = slope; if (slope < minSlope[h]) minSlope[h] = slope; } } g_Map.log("Painting areas by height and slope"); for (let h = 0; h < heighLimits.length; ++h) for (let point of areas[h]) { let actor; let texture = pickRandom(myBiome[h].texture); if (slopeMap[point.x][point.y] < 0.4 * (minSlope[h] + maxSlope[h])) { if (randBool(myBiome[h].actor[1])) actor = pickRandom(myBiome[h].actor[0]); } else { texture = pickRandom(myBiome[h].textureHS); if (randBool(myBiome[h].actorHS[1])) actor = pickRandom(myBiome[h].actorHS[0]); } g_Map.setTexture(point, texture); if (actor) g_Map.placeEntityAnywhere(actor, 0, randomPositionOnTile(point), randomAngle()); } Engine.SetProgress(80); g_Map.log("Placing players"); if (isNomad()) placePlayersNomad(g_Map.createTileClass(), new HeightConstraint(heighLimits[4], heighLimits[5])); else for (let p = 0; p < playerIDs.length; ++p) { placeCivDefaultStartingEntities(playerPosition[p], playerIDs[p], true); placeStartLocationResources(playerPosition[p]); } g_Map.log("Placing resources, farmsteads, groves and camps"); for (let i = 0; i < resourceSpots.length; ++i) { let pos = new Vector2D(resourceSpots[i].x, resourceSpots[i].y); let choice = i % 5; if (choice == 0) placeMine(pos, "gaia/geology_stonemine_temperate_formation"); if (choice == 1) placeMine(pos, "gaia/geology_metal_temperate_slabs"); if (choice == 2) placeCustomFortress(pos, pickRandom(fences), "other", 0, randomAngle()); if (choice == 3) placeGrove(pos); if (choice == 4) placeCamp(pos); } g_Map.ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/empire.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/empire.js (revision 21205) +++ ps/trunk/binaries/data/mods/public/maps/random/empire.js (revision 21206) @@ -1,233 +1,233 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen2"); Engine.LoadLibrary("rmbiome"); setSelectedBiome(); var g_Map = new RandomMap(2, g_Terrains.mainTerrain); initTileClasses(); createArea( new MapBoundsPlacer(), new TileClassPainter(g_TileClasses.land)); Engine.SetProgress(10); const teamsArray = getTeamsArray(); const startAngle = randomAngle(); -addBases("stronghold", fractionToTiles(0.37), fractionToTiles(0.04), startAngle); +createBasesByPattern("stronghold", fractionToTiles(0.37), fractionToTiles(0.04), startAngle); Engine.SetProgress(20); // Change the starting angle and add the players again var rotation = Math.PI; if (teamsArray.length == 2) rotation = Math.PI / 2; if (teamsArray.length == 4) rotation = 5/4 * Math.PI; -addBases("stronghold", fractionToTiles(0.15), fractionToTiles(0.04), startAngle + rotation); +createBasesByPattern("stronghold", fractionToTiles(0.15), fractionToTiles(0.04), startAngle + rotation); Engine.SetProgress(40); addElements(shuffleArray([ { "func": addHills, "avoid": [ g_TileClasses.bluff, 5, g_TileClasses.hill, 15, g_TileClasses.mountain, 2, g_TileClasses.plateau, 5, g_TileClasses.player, 20, g_TileClasses.valley, 2, g_TileClasses.water, 2 ], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": ["tons"] }, { "func": addMountains, "avoid": [ g_TileClasses.bluff, 20, g_TileClasses.mountain, 25, g_TileClasses.plateau, 20, g_TileClasses.player, 20, g_TileClasses.valley, 10, g_TileClasses.water, 15 ], "sizes": ["huge"], "mixes": ["same", "similar"], "amounts": ["tons"] }, { "func": addPlateaus, "avoid": [ g_TileClasses.bluff, 20, g_TileClasses.mountain, 25, g_TileClasses.plateau, 20, g_TileClasses.player, 40, g_TileClasses.valley, 10, g_TileClasses.water, 15 ], "sizes": ["huge"], "mixes": ["same", "similar"], "amounts": ["tons"] } ])); Engine.SetProgress(50); addElements([ { "func": addLayeredPatches, "avoid": [ g_TileClasses.bluff, 2, g_TileClasses.dirt, 5, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 12, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] }, { "func": addDecoration, "avoid": [ g_TileClasses.bluff, 2, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 12, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] } ]); Engine.SetProgress(60); addElements(shuffleArray([ { "func": addMetal, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.player, 30, g_TileClasses.rock, 10, g_TileClasses.metal, 20, g_TileClasses.plateau, 2, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": g_AllAmounts }, { "func": addStone, "avoid": [g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.player, 30, g_TileClasses.rock, 20, g_TileClasses.metal, 10, g_TileClasses.plateau, 2, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": g_AllAmounts }, { "func": addForests, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 18, g_TileClasses.metal, 3, g_TileClasses.mountain, 5, g_TileClasses.plateau, 2, g_TileClasses.player, 20, g_TileClasses.rock, 3, g_TileClasses.water, 2 ], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": ["few", "normal", "many", "tons"] } ])); Engine.SetProgress(80); addElements(shuffleArray([ { "func": addBerries, "avoid": [ g_TileClasses.berries, 30, g_TileClasses.bluff, 5, g_TileClasses.forest, 5, g_TileClasses.metal, 10, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 20, g_TileClasses.rock, 10, g_TileClasses.water, 3 ], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": g_AllAmounts }, { "func": addAnimals, "avoid": [ g_TileClasses.animals, 20, g_TileClasses.bluff, 5, g_TileClasses.forest, 2, g_TileClasses.metal, 2, g_TileClasses.mountain, 1, g_TileClasses.plateau, 2, g_TileClasses.player, 20, g_TileClasses.rock, 2, g_TileClasses.water, 3 ], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": g_AllAmounts }, { "func": addStragglerTrees, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 7, g_TileClasses.metal, 2, g_TileClasses.mountain, 1, g_TileClasses.plateau, 2, g_TileClasses.player, 12, g_TileClasses.rock, 2, g_TileClasses.water, 5 ], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": g_AllAmounts } ])); Engine.SetProgress(90); placePlayersNomad( g_TileClasses.player, avoidClasses( g_TileClasses.plateau, 4, g_TileClasses.forest, 1, g_TileClasses.metal, 4, g_TileClasses.rock, 4, g_TileClasses.mountain, 4, g_TileClasses.animals, 2)); g_Map.ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/frontier.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/frontier.js (revision 21205) +++ ps/trunk/binaries/data/mods/public/maps/random/frontier.js (revision 21206) @@ -1,281 +1,281 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen2"); Engine.LoadLibrary("rmbiome"); setSelectedBiome(); // Random elevation with a bias towards lower elevations var randElevation = randIntInclusive(0, 29); if (randElevation < 25) randElevation = randIntInclusive(1, 4); var g_Map = new RandomMap(randElevation, g_Terrains.mainTerrain); initTileClasses(); createArea( new MapBoundsPlacer(), new TileClassPainter(g_TileClasses.land)); Engine.SetProgress(20); -const startPositions = randomStartingPositionPattern(getTeamsArray()); -addBases(startPositions.setup, startPositions.distance, startPositions.separation, randomAngle()); +const playerbasePattern = randomStartingPositionPattern(getTeamsArray()); +createBasesByPattern(playerbasePattern.setup, playerbasePattern.distance, playerbasePattern.separation, randomAngle()); Engine.SetProgress(40); var features = [ { "func": addBluffs, "baseHeight": randElevation, "avoid": [ g_TileClasses.bluff, 20, g_TileClasses.hill, 10, g_TileClasses.mountain, 20, g_TileClasses.plateau, 15, g_TileClasses.player, 30, g_TileClasses.valley, 5, g_TileClasses.water, 7 ], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": g_AllAmounts }, { "func": addHills, "avoid": [ g_TileClasses.bluff, 5, g_TileClasses.hill, 15, g_TileClasses.mountain, 2, g_TileClasses.plateau, 15, g_TileClasses.player, 20, g_TileClasses.valley, 2, g_TileClasses.water, 2 ], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": g_AllAmounts }, { "func": addMountains, "avoid": [ g_TileClasses.bluff, 20, g_TileClasses.mountain, 25, g_TileClasses.plateau, 15, g_TileClasses.player, 20, g_TileClasses.valley, 10, g_TileClasses.water, 15 ], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": g_AllAmounts }, { "func": addPlateaus, "avoid": [ g_TileClasses.bluff, 20, g_TileClasses.mountain, 25, g_TileClasses.plateau, 25, g_TileClasses.plateau, 25, g_TileClasses.player, 40, g_TileClasses.valley, 10, g_TileClasses.water, 15 ], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": g_AllAmounts } ]; if (randElevation < 4) features.push({ "func": addLakes, "avoid": [ g_TileClasses.bluff, 7, g_TileClasses.hill, 2, g_TileClasses.mountain, 15, g_TileClasses.plateau, 10, g_TileClasses.player, 20, g_TileClasses.valley, 10, g_TileClasses.water, 25 ], "sizes": ["small"], "mixes": g_AllMixes, "amounts": g_AllAmounts }); if (randElevation > 20) features.push({ "func": addValleys, "baseHeight": randElevation, "avoid": [ g_TileClasses.bluff, 5, g_TileClasses.hill, 5, g_TileClasses.mountain, 25, g_TileClasses.plateau, 20, g_TileClasses.player, 40, g_TileClasses.valley, 15, g_TileClasses.water, 10 ], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": g_AllAmounts }); addElements(shuffleArray(features)); Engine.SetProgress(50); addElements([ { "func": addLayeredPatches, "avoid": [ g_TileClasses.bluff, 2, g_TileClasses.dirt, 5, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 12, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] }, { "func": addDecoration, "avoid": [ g_TileClasses.bluff, 2, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 12, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] } ]); Engine.SetProgress(60); addElements(shuffleArray([ { "func": addMetal, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 30, g_TileClasses.rock, 10, g_TileClasses.metal, 20, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": g_AllAmounts }, { "func": addStone, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 30, g_TileClasses.rock, 20, g_TileClasses.metal, 10, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": g_AllAmounts }, { "func": addForests, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 18, g_TileClasses.metal, 3, g_TileClasses.mountain, 5, g_TileClasses.plateau, 5, g_TileClasses.player, 20, g_TileClasses.rock, 3, g_TileClasses.water, 2 ], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": ["few", "normal", "many", "tons"] } ])); Engine.SetProgress(70); addElements(shuffleArray([ { "func": addBerries, "avoid": [ g_TileClasses.berries, 30, g_TileClasses.bluff, 5, g_TileClasses.forest, 5, g_TileClasses.metal, 10, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 20, g_TileClasses.rock, 10, g_TileClasses.water, 3 ], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": g_AllAmounts }, { "func": addAnimals, "avoid": [ g_TileClasses.animals, 20, g_TileClasses.bluff, 5, g_TileClasses.forest, 2, g_TileClasses.metal, 2, g_TileClasses.mountain, 1, g_TileClasses.plateau, 2, g_TileClasses.player, 20, g_TileClasses.rock, 2, g_TileClasses.water, 3 ], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": g_AllAmounts }, { "func": addStragglerTrees, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 7, g_TileClasses.metal, 2, g_TileClasses.mountain, 1, g_TileClasses.plateau, 2, g_TileClasses.player, 12, g_TileClasses.rock, 2, g_TileClasses.water, 5 ], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": g_AllAmounts } ])); Engine.SetProgress(90); placePlayersNomad( g_TileClasses.player, avoidClasses( g_TileClasses.bluff, 4, g_TileClasses.water, 4, g_TileClasses.forest, 1, g_TileClasses.metal, 4, g_TileClasses.rock, 4, g_TileClasses.mountain, 4, g_TileClasses.plateau, 4, g_TileClasses.animals, 2)); g_Map.ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/harbor.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/harbor.js (revision 21205) +++ ps/trunk/binaries/data/mods/public/maps/random/harbor.js (revision 21206) @@ -1,417 +1,417 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen2"); Engine.LoadLibrary("rmbiome"); setSelectedBiome(); var heightSeaGround = -18; var heightLand = 2; var heightOffsetHarbor = -11; var g_Map = new RandomMap(heightLand, g_Terrains.mainTerrain); initTileClasses(); setFogFactor(0.04); createArea( new MapBoundsPlacer(), new TileClassPainter(g_TileClasses.land)); Engine.SetProgress(10); const mapSize = g_Map.getSize(); const mapCenter = g_Map.getCenter(); const startAngle = randomAngle(); -const players = addBases("radial", fractionToTiles(0.38), fractionToTiles(0.05), startAngle); +const [playerIDs, playerPosition] = createBasesByPattern("radial", fractionToTiles(0.38), fractionToTiles(0.05), startAngle); Engine.SetProgress(20); addCenterLake(); Engine.SetProgress(30); if (mapSize >= 192) { - addHarbors(players); + addHarbors(); Engine.SetProgress(40); } addSpines(); Engine.SetProgress(50); addElements([ { "func": addFish, "avoid": [ g_TileClasses.fish, 12, g_TileClasses.hill, 8, g_TileClasses.mountain, 8, g_TileClasses.player, 8, g_TileClasses.spine, 4 ], "stay": [g_TileClasses.water, 7], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": ["many"] } ]); addElements(shuffleArray([ { "func": addHills, "avoid": [ g_TileClasses.bluff, 5, g_TileClasses.hill, 15, g_TileClasses.mountain, 2, g_TileClasses.plateau, 5, g_TileClasses.player, 20, g_TileClasses.spine, 5, g_TileClasses.valley, 2, g_TileClasses.water, 2 ], "sizes": ["tiny", "small"], "mixes": g_AllMixes, "amounts": g_AllAmounts }, { "func": addMountains, "avoid": [ g_TileClasses.bluff, 20, g_TileClasses.mountain, 25, g_TileClasses.plateau, 20, g_TileClasses.player, 20, g_TileClasses.spine, 20, g_TileClasses.valley, 10, g_TileClasses.water, 15 ], "sizes": ["small"], "mixes": g_AllMixes, "amounts": g_AllAmounts }, { "func": addPlateaus, "avoid": [ g_TileClasses.bluff, 20, g_TileClasses.mountain, 25, g_TileClasses.plateau, 20, g_TileClasses.player, 40, g_TileClasses.spine, 20, g_TileClasses.valley, 10, g_TileClasses.water, 15 ], "sizes": ["small"], "mixes": g_AllMixes, "amounts": g_AllAmounts }, { "func": addBluffs, "baseHeight": heightLand, "avoid": [ g_TileClasses.bluff, 20, g_TileClasses.mountain, 25, g_TileClasses.plateau, 20, g_TileClasses.player, 40, g_TileClasses.spine, 20, g_TileClasses.valley, 10, g_TileClasses.water, 15 ], "sizes": ["normal"], "mixes": g_AllMixes, "amounts": g_AllAmounts } ])); Engine.SetProgress(60); addElements(shuffleArray([ { "func": addMetal, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 30, g_TileClasses.rock, 10, g_TileClasses.spine, 5, g_TileClasses.metal, 20, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["normal", "many"] }, { "func": addStone, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 30, g_TileClasses.rock, 20, g_TileClasses.spine, 5, g_TileClasses.metal, 10, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["normal", "many"] }, { "func": addForests, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 8, g_TileClasses.metal, 3, g_TileClasses.mountain, 5, g_TileClasses.plateau, 5, g_TileClasses.player, 20, g_TileClasses.rock, 3, g_TileClasses.spine, 5, g_TileClasses.water, 2 ], "sizes": ["normal"], "mixes": ["similar"], "amounts": ["many"] } ])); Engine.SetProgress(70); addElements(shuffleArray([ { "func": addBerries, "avoid": [ g_TileClasses.berries, 30, g_TileClasses.bluff, 5, g_TileClasses.forest, 5, g_TileClasses.metal, 10, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 20, g_TileClasses.rock, 10, g_TileClasses.spine, 2, g_TileClasses.water, 3 ], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": g_AllAmounts }, { "func": addAnimals, "avoid": [ g_TileClasses.animals, 20, g_TileClasses.bluff, 5, g_TileClasses.forest, 2, g_TileClasses.metal, 2, g_TileClasses.mountain, 1, g_TileClasses.plateau, 2, g_TileClasses.player, 20, g_TileClasses.rock, 2, g_TileClasses.spine, 2, g_TileClasses.water, 3 ], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": g_AllAmounts }, { "func": addStragglerTrees, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 7, g_TileClasses.metal, 2, g_TileClasses.mountain, 1, g_TileClasses.plateau, 2, g_TileClasses.player, 12, g_TileClasses.rock, 2, g_TileClasses.spine, 2, g_TileClasses.water, 5 ], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": g_AllAmounts } ])); Engine.SetProgress(80); addElements([ { "func": addLayeredPatches, "avoid": [ g_TileClasses.bluff, 2, g_TileClasses.dirt, 5, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 12, g_TileClasses.spine, 5, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] }, { "func": addDecoration, "avoid": [ g_TileClasses.bluff, 2, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 12, g_TileClasses.spine, 5, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] } ]); Engine.SetProgress(90); placePlayersNomad( g_TileClasses.player, avoidClasses( g_TileClasses.bluff, 4, g_TileClasses.water, 4, g_TileClasses.spine, 4, g_TileClasses.plateau, 4, g_TileClasses.forest, 1, g_TileClasses.metal, 4, g_TileClasses.rock, 4, g_TileClasses.mountain, 4, g_TileClasses.animals, 2)); g_Map.ExportMap(); function addCenterLake() { createArea( new ChainPlacer( 2, Math.floor(scaleByMapSize(2, 12)), Math.floor(scaleByMapSize(35, 160)), Infinity, mapCenter, 0, [Math.floor(fractionToTiles(0.2))]), [ new LayeredPainter( [ g_Terrains.shore, g_Terrains.water, g_Terrains.water ], [1, 100] ), new SmoothElevationPainter(ELEVATION_SET, heightSeaGround, 10), new TileClassPainter(g_TileClasses.water) ], avoidClasses(g_TileClasses.player, 20) ); let fDist = 50; if (mapSize <= 192) fDist = 20; } -function addHarbors(players) +function addHarbors() { - for (let player of players) + for (let position of playerPosition) { - let harborPosition = Vector2D.add(player.position, Vector2D.sub(mapCenter, player.position).div(2.5).round()); + let harborPosition = Vector2D.add(position, Vector2D.sub(mapCenter, position).div(2.5).round()); createArea( new ClumpPlacer(1200, 0.5, 0.5, Infinity, harborPosition), [ new LayeredPainter([g_Terrains.shore, g_Terrains.water], [2]), new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetHarbor, 3), new TileClassPainter(g_TileClasses.water) ], avoidClasses( g_TileClasses.player, 15, g_TileClasses.hill, 1 ) ); } } function addSpines() { let smallSpines = mapSize <= 192; let spineSize = smallSpines ? 0.02 : 0.5; let spineTapering = smallSpines ?-0.1 : -1.4; let heightOffsetSpine = smallSpines ? 20 : 35; let numPlayers = getNumPlayers(); let spineTile = g_Terrains.dirt; if (currentBiome() == "generic/snowy") spineTile = g_Terrains.tier1Terrain; if (currentBiome() == "generic/alpine" || currentBiome() == "generic/savanna") spineTile = g_Terrains.tier2Terrain; if (currentBiome() == "generic/autumn") spineTile = g_Terrains.tier4Terrain; let split = 1; if (numPlayers <= 3 || mapSize >= 320 && numPlayers <= 4) split = 2; for (let i = 0; i < numPlayers * split; ++i) { let tang = startAngle + (i + 0.5) * 2 * Math.PI / (numPlayers * split); let start = Vector2D.add(mapCenter, new Vector2D(fractionToTiles(0.12), 0).rotate(-tang)); let end = Vector2D.add(mapCenter, new Vector2D(fractionToTiles(0.4), 0).rotate(-tang)); createArea( new PathPlacer(start, end, scaleByMapSize(14, spineSize), 0.6, 0.1, 0.4, spineTapering), [ new LayeredPainter([g_Terrains.cliff, spineTile], [3]), new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetSpine, 3), new TileClassPainter(g_TileClasses.spine) ], avoidClasses(g_TileClasses.player, 5) ); } addElements([ { "func": addDecoration, "avoid": [ g_TileClasses.bluff, 2, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12, g_TileClasses.water, 3 ], "stay": [g_TileClasses.spine, 5], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] } ]); addElements([ { "func": addProps, "avoid": [ g_TileClasses.forest, 2, g_TileClasses.player, 2, g_TileClasses.prop, 20, g_TileClasses.water, 3 ], "stay": [g_TileClasses.spine, 8], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["scarce"] } ]); } Index: ps/trunk/binaries/data/mods/public/maps/random/hells_pass.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/hells_pass.js (revision 21205) +++ ps/trunk/binaries/data/mods/public/maps/random/hells_pass.js (revision 21206) @@ -1,331 +1,331 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen2"); Engine.LoadLibrary("rmbiome"); setSelectedBiome(); const heightLand = 1; const heightBarrier = 30; var g_Map = new RandomMap(heightLand, g_Terrains.mainTerrain); initTileClasses(); const mapCenter = g_Map.getCenter(); createArea( new MapBoundsPlacer(), new TileClassPainter(g_TileClasses.land)); Engine.SetProgress(10); const teamsArray = getTeamsArray(); const startAngle = randomAngle(); -addBases("line", fractionToTiles(0.2), fractionToTiles(0.08), startAngle); +createBasesByPattern("line", fractionToTiles(0.2), fractionToTiles(0.08), startAngle); Engine.SetProgress(20); placeBarriers(); Engine.SetProgress(40); addElements(shuffleArray([ { "func": addBluffs, "baseHeight": heightLand, "avoid": [ g_TileClasses.bluff, 20, g_TileClasses.hill, 5, g_TileClasses.mountain, 20, g_TileClasses.plateau, 20, g_TileClasses.player, 30, g_TileClasses.spine, 15, g_TileClasses.valley, 5, g_TileClasses.water, 7 ], "sizes": ["normal", "big"], "mixes": ["varied"], "amounts": ["few"] }, { "func": addHills, "avoid": [ g_TileClasses.bluff, 5, g_TileClasses.hill, 15, g_TileClasses.mountain, 2, g_TileClasses.plateau, 5, g_TileClasses.player, 20, g_TileClasses.spine, 15, g_TileClasses.valley, 2, g_TileClasses.water, 2 ], "sizes": ["normal", "big"], "mixes": ["varied"], "amounts": ["few"] }, { "func": addLakes, "avoid": [ g_TileClasses.bluff, 7, g_TileClasses.hill, 2, g_TileClasses.mountain, 15, g_TileClasses.plateau, 10, g_TileClasses.player, 20, g_TileClasses.spine, 15, g_TileClasses.valley, 10, g_TileClasses.water, 25 ], "sizes": ["big", "huge"], "mixes": ["varied", "unique"], "amounts": ["few"] } ])); Engine.SetProgress(50); addElements([ { "func": addLayeredPatches, "avoid": [ g_TileClasses.bluff, 2, g_TileClasses.dirt, 5, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 12, g_TileClasses.spine, 5, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] }, { "func": addDecoration, "avoid": [ g_TileClasses.bluff, 2, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 12, g_TileClasses.spine, 5, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] } ]); Engine.SetProgress(60); addElements(shuffleArray([ { "func": addMetal, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 30, g_TileClasses.rock, 10, g_TileClasses.metal, 20, g_TileClasses.spine, 5, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": g_AllAmounts }, { "func": addStone, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 30, g_TileClasses.rock, 20, g_TileClasses.metal, 10, g_TileClasses.spine, 5, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": g_AllAmounts }, { "func": addForests, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 18, g_TileClasses.metal, 3, g_TileClasses.mountain, 5, g_TileClasses.plateau, 5, g_TileClasses.player, 20, g_TileClasses.rock, 3, g_TileClasses.spine, 5, g_TileClasses.water, 2 ], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": ["few", "normal", "many", "tons"] } ])); Engine.SetProgress(80); addElements(shuffleArray([ { "func": addBerries, "avoid": [ g_TileClasses.berries, 30, g_TileClasses.bluff, 5, g_TileClasses.forest, 5, g_TileClasses.metal, 10, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 20, g_TileClasses.rock, 10, g_TileClasses.spine, 2, g_TileClasses.water, 3 ], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": g_AllAmounts }, { "func": addAnimals, "avoid": [ g_TileClasses.animals, 20, g_TileClasses.bluff, 5, g_TileClasses.forest, 2, g_TileClasses.metal, 2, g_TileClasses.mountain, 1, g_TileClasses.plateau, 2, g_TileClasses.player, 20, g_TileClasses.rock, 2, g_TileClasses.spine, 2, g_TileClasses.water, 3 ], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": g_AllAmounts }, { "func": addStragglerTrees, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 7, g_TileClasses.metal, 2, g_TileClasses.mountain, 1, g_TileClasses.plateau, 2, g_TileClasses.player, 12, g_TileClasses.rock, 2, g_TileClasses.spine, 2, g_TileClasses.water, 5 ], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": g_AllAmounts } ])); Engine.SetProgress(90); placePlayersNomad( g_TileClasses.player, avoidClasses( g_TileClasses.bluff, 4, g_TileClasses.water, 4, g_TileClasses.spine, 4, g_TileClasses.plateau, 4, g_TileClasses.forest, 1, g_TileClasses.metal, 4, g_TileClasses.rock, 4, g_TileClasses.mountain, 4, g_TileClasses.animals, 2)); g_Map.ExportMap(); function placeBarriers() { var spineTerrain = g_Terrains.dirt; if (currentBiome() == "generic/snowy") spineTerrain = g_Terrains.tier1Terrain; if (currentBiome() == "generic/alpine" || currentBiome() == "generic/savanna") spineTerrain = g_Terrains.tier2Terrain; if (currentBiome() == "generic/autumn") spineTerrain = g_Terrains.tier4Terrain; let spineCount = isNomad() ? randIntInclusive(1, 4) : teamsArray.length; for (let i = 0; i < spineCount; ++i) { var mSize = 8; var mWaviness = 0.6; var mOffset = 0.5; var mTaper = -1.5; if (spineCount > 3 || g_Map.getSize() <= 192) { mWaviness = 0.2; mOffset = 0.2; mTaper = -1; } if (spineCount >= 5) { mSize = 4; mWaviness = 0.2; mOffset = 0.2; mTaper = -0.7; } let angle = startAngle + (i + 0.5) * 2 * Math.PI / spineCount; let start = Vector2D.add(mapCenter, new Vector2D(fractionToTiles(0.075), 0).rotate(-angle)); let end = Vector2D.add(mapCenter, new Vector2D(fractionToTiles(0.42), 0).rotate(-angle)); createArea( new PathPlacer(start, end, scaleByMapSize(14, mSize), mWaviness, 0.1, mOffset, mTaper), [ new LayeredPainter([g_Terrains.cliff, spineTerrain], [2]), new SmoothElevationPainter(ELEVATION_SET, heightBarrier, 2), new TileClassPainter(g_TileClasses.spine) ], avoidClasses(g_TileClasses.player, 5, g_TileClasses.baseResource, 5)); } addElements([ { "func": addDecoration, "avoid": [ g_TileClasses.bluff, 2, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12, g_TileClasses.water, 3 ], "stay": [g_TileClasses.spine, 5], "sizes": ["huge"], "mixes": ["unique"], "amounts": ["tons"] } ]); addElements([ { "func": addProps, "avoid": [ g_TileClasses.forest, 2, g_TileClasses.player, 2, g_TileClasses.prop, 20, g_TileClasses.water, 3 ], "stay": [g_TileClasses.spine, 8], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["scarce"] } ]); } Index: ps/trunk/binaries/data/mods/public/maps/random/lions_den.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/lions_den.js (revision 21205) +++ ps/trunk/binaries/data/mods/public/maps/random/lions_den.js (revision 21206) @@ -1,566 +1,566 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen2"); Engine.LoadLibrary("rmbiome"); setSelectedBiome(); const topTerrain = g_Terrains.tier2Terrain; const heightValley = 0; const heightPath = 10; const heightDen = 15; const heightHill = 50; var g_Map = new RandomMap(heightHill, topTerrain); const mapCenter = g_Map.getCenter(); const numPlayers = getNumPlayers(); const startAngle = randomAngle(); initTileClasses(["step"]); createArea( new MapBoundsPlacer(), new TileClassPainter(g_TileClasses.land)); Engine.SetProgress(10); -addBases("radial", fractionToTiles(0.4), fractionToTiles(randFloat(0.05, 0.1)), startAngle); +createBasesByPattern("radial", fractionToTiles(0.4), fractionToTiles(randFloat(0.05, 0.1)), startAngle); Engine.SetProgress(20); createSunkenTerrain(); Engine.SetProgress(30); addElements([ { "func": addLayeredPatches, "avoid": [ g_TileClasses.baseResource, 5, g_TileClasses.dirt, 5, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12, g_TileClasses.step, 5 ], "stay": [g_TileClasses.valley, 7], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] }, { "func": addLayeredPatches, "avoid": [ g_TileClasses.baseResource, 5, g_TileClasses.dirt, 5, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12 ], "stay": [g_TileClasses.settlement, 7], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] }, { "func": addLayeredPatches, "avoid": [ g_TileClasses.baseResource, 5, g_TileClasses.dirt, 5, g_TileClasses.forest, 2 ], "stay": [g_TileClasses.player, 1], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] }, { "func": addDecoration, "avoid": [ g_TileClasses.baseResource, 5, g_TileClasses.forest, 2 ], "stay": [g_TileClasses.player, 1], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] }, { "func": addDecoration, "avoid": [ g_TileClasses.baseResource, 5, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12, g_TileClasses.step, 2 ], "stay": [g_TileClasses.valley, 7], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] }, { "func": addDecoration, "avoid": [ g_TileClasses.baseResource, 5, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12 ], "stay": [g_TileClasses.settlement, 7], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] }, { "func": addDecoration, "avoid": [ g_TileClasses.baseResource, 5, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12 ], "stay": [g_TileClasses.step, 7], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["scarce"] } ]); Engine.SetProgress(40); addElements(shuffleArray([ { "func": addMetal, "avoid": [ g_TileClasses.baseResource, 5, g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.player, 30, g_TileClasses.rock, 10, g_TileClasses.metal, 20 ], "stay": [g_TileClasses.settlement, 7], "sizes": ["normal"], "mixes": ["same"], "amounts": ["tons"] }, { "func": addMetal, "avoid": [ g_TileClasses.baseResource, 5, g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.player, 10, g_TileClasses.rock, 10, g_TileClasses.metal, 20, g_TileClasses.mountain, 5, g_TileClasses.step, 5 ], "stay": [g_TileClasses.valley, 7], "sizes": ["normal"], "mixes": ["same"], "amounts": g_AllAmounts }, { "func": addStone, "avoid": [ g_TileClasses.baseResource, 5, g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.player, 30, g_TileClasses.rock, 20, g_TileClasses.metal, 10 ], "stay": [g_TileClasses.settlement, 7], "sizes": ["normal"], "mixes": ["same"], "amounts": ["tons"] }, { "func": addStone, "avoid": [ g_TileClasses.baseResource, 5, g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.player, 10, g_TileClasses.rock, 20, g_TileClasses.metal, 10, g_TileClasses.mountain, 5, g_TileClasses.step, 5 ], "stay": [g_TileClasses.valley, 7], "sizes": ["normal"], "mixes": ["same"], "amounts": g_AllAmounts }, { "func": addForests, "avoid": [ g_TileClasses.baseResource, 5, g_TileClasses.berries, 5, g_TileClasses.forest, 18, g_TileClasses.metal, 3, g_TileClasses.player, 20, g_TileClasses.rock, 3 ], "stay": [g_TileClasses.settlement, 7], "sizes": ["normal", "big"], "mixes": ["same"], "amounts": ["tons"] }, { "func": addForests, "avoid": [ g_TileClasses.baseResource, 5, g_TileClasses.berries, 3, g_TileClasses.forest, 18, g_TileClasses.metal, 3, g_TileClasses.mountain, 5, g_TileClasses.player, 5, g_TileClasses.rock, 3, g_TileClasses.step, 1 ], "stay": [g_TileClasses.valley, 7], "sizes": ["normal", "big"], "mixes": ["same"], "amounts": ["tons"] } ])); Engine.SetProgress(60); addElements(shuffleArray([ { "func": addBerries, "avoid": [ g_TileClasses.baseResource, 5, g_TileClasses.berries, 30, g_TileClasses.forest, 5, g_TileClasses.metal, 10, g_TileClasses.player, 20, g_TileClasses.rock, 10 ], "stay": [g_TileClasses.settlement, 7], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": ["tons"] }, { "func": addBerries, "avoid": [ g_TileClasses.baseResource, 5, g_TileClasses.berries, 30, g_TileClasses.forest, 5, g_TileClasses.metal, 10, g_TileClasses.mountain, 5, g_TileClasses.player, 10, g_TileClasses.rock, 10, g_TileClasses.step, 5 ], "stay": [g_TileClasses.valley, 7], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": g_AllAmounts }, { "func": addAnimals, "avoid": [ g_TileClasses.animals, 20, g_TileClasses.baseResource, 5, g_TileClasses.forest, 0, g_TileClasses.metal, 1, g_TileClasses.player, 20, g_TileClasses.rock, 1 ], "stay": [g_TileClasses.settlement, 7], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": ["tons"] }, { "func": addAnimals, "avoid": [ g_TileClasses.animals, 20, g_TileClasses.baseResource, 5, g_TileClasses.forest, 0, g_TileClasses.metal, 1, g_TileClasses.mountain, 5, g_TileClasses.player, 10, g_TileClasses.rock, 1, g_TileClasses.step, 5 ], "stay": [g_TileClasses.valley, 7], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": g_AllAmounts }, { "func": addStragglerTrees, "avoid": [ g_TileClasses.baseResource, 5, g_TileClasses.berries, 5, g_TileClasses.forest, 7, g_TileClasses.metal, 3, g_TileClasses.player, 12, g_TileClasses.rock, 3 ], "stay": [g_TileClasses.settlement, 7], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": ["tons"] }, { "func": addStragglerTrees, "avoid": [ g_TileClasses.baseResource, 5, g_TileClasses.berries, 5, g_TileClasses.forest, 7, g_TileClasses.metal, 3, g_TileClasses.mountain, 5, g_TileClasses.player, 10, g_TileClasses.rock, 3, g_TileClasses.step, 5 ], "stay": [g_TileClasses.valley, 7], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": ["normal", "many", "tons"] }, { "func": addStragglerTrees, "avoid": [ g_TileClasses.player, 10, g_TileClasses.baseResource, 5, g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.metal, 5, g_TileClasses.rock, 5 ], "stay": [g_TileClasses.player, 1], "sizes": ["huge"], "mixes": ["same"], "amounts": ["tons"] } ])); Engine.SetProgress(75); addElements([ { "func": addDecoration, "avoid": [ g_TileClasses.baseResource, 5, g_TileClasses.valley, 4, g_TileClasses.player, 4, g_TileClasses.settlement, 4, g_TileClasses.step, 4 ], "stay": [g_TileClasses.land, 2], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["tons"] } ]); Engine.SetProgress(80); addElements([ { "func": addProps, "avoid": [ g_TileClasses.baseResource, 5, g_TileClasses.valley, 4, g_TileClasses.player, 4, g_TileClasses.settlement, 4, g_TileClasses.step, 4 ], "stay": [g_TileClasses.land, 2], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["scarce"] } ]); Engine.SetProgress(85); addElements([ { "func": addDecoration, "avoid": [ g_TileClasses.baseResource, 5, g_TileClasses.player, 4, g_TileClasses.settlement, 4, g_TileClasses.step, 4 ], "stay": [g_TileClasses.mountain, 2], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["tons"] } ]); Engine.SetProgress(90); addElements([ { "func": addProps, "avoid": [ g_TileClasses.baseResource, 5, g_TileClasses.player, 4, g_TileClasses.settlement, 4, g_TileClasses.step, 4 ], "stay": [g_TileClasses.mountain, 2], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["scarce"] } ]); Engine.SetProgress(95); placePlayersNomad( g_TileClasses.player, [ new HeightConstraint(heightValley, heightPath), avoidClasses( g_TileClasses.forest, 1, g_TileClasses.metal, 4, g_TileClasses.rock, 4, g_TileClasses.animals, 2) ]); g_Map.ExportMap(); function createSunkenTerrain() { var base = g_Terrains.mainTerrain; var middle = g_Terrains.dirt; var lower = g_Terrains.tier2Terrain; var road = g_Terrains.road; if (currentBiome() == "generic/snowy") { middle = g_Terrains.tier2Terrain; lower = g_Terrains.tier1Terrain; } if (currentBiome() == "generic/alpine") { middle = g_Terrains.shore; lower = g_Terrains.tier4Terrain; } if (currentBiome() == "generic/mediterranean") { middle = g_Terrains.tier1Terrain; lower = g_Terrains.forestFloor1; } if (currentBiome() == "generic/savanna") { middle = g_Terrains.tier2Terrain; lower = g_Terrains.tier4Terrain; } if (currentBiome() == "generic/tropic" || currentBiome() == "generic/autumn") road = g_Terrains.roadWild; if (currentBiome() == "generic/autumn") middle = g_Terrains.shore; var expSize = diskArea(fractionToTiles(0.14)) / numPlayers; var expDist = 0.1 + numPlayers / 200; var expAngle = 0.75; if (numPlayers <= 2) { expSize = diskArea(fractionToTiles(0.075)); expAngle = 0.72; } var nRoad = 0.44; var nExp = 0.425; if (numPlayers < 4) { nRoad = 0.42; nExp = 0.4; } g_Map.log("Creating central valley"); createArea( new ClumpPlacer(diskArea(fractionToTiles(0.29)), 1, 1, Infinity, mapCenter), [ new LayeredPainter([g_Terrains.cliff, lower], [3]), new SmoothElevationPainter(ELEVATION_SET, heightValley, 3), new TileClassPainter(g_TileClasses.valley) ]); g_Map.log("Creating central hill"); createArea( new ClumpPlacer(diskArea(fractionToTiles(0.21)), 1, 1, Infinity, mapCenter), [ new LayeredPainter([g_Terrains.cliff, topTerrain], [3]), new SmoothElevationPainter(ELEVATION_SET, heightHill, 3), new TileClassPainter(g_TileClasses.mountain) ]); let getCoords = (distance, playerID, playerIDOffset) => { let angle = startAngle + (playerID + playerIDOffset) * 2 * Math.PI / numPlayers; return Vector2D.add(mapCenter, new Vector2D(fractionToTiles(distance), 0).rotate(-angle)).round(); }; for (let i = 0; i < numPlayers; ++i) { let playerPosition = getCoords(0.4, i, 0); // Path from player to expansion let expansionPosition = getCoords(expDist, i, expAngle); createArea( new PathPlacer(playerPosition, expansionPosition, 12, 0.7, 0.5, 0.1, -1), [ new LayeredPainter([g_Terrains.cliff, middle, road], [3, 4]), new SmoothElevationPainter(ELEVATION_SET, heightPath, 3), new TileClassPainter(g_TileClasses.step) ]); // Path from player to neighbor for (let neighborOffset of [-0.5, 0.5]) { let neighborPosition = getCoords(nRoad, i, neighborOffset); let pathPosition = getCoords(0.47, i, 0); createArea( new PathPlacer(pathPosition, neighborPosition, 19, 0.4, 0.5, 0.1, -0.6), [ new LayeredPainter([g_Terrains.cliff, middle, road], [3, 6]), new SmoothElevationPainter(ELEVATION_SET, heightPath, 3), new TileClassPainter(g_TileClasses.step) ]); } // Den createArea( new ClumpPlacer(diskArea(fractionToTiles(0.1)) / (isNomad() ? 2 : 1), 0.9, 0.3, Infinity, playerPosition), [ new LayeredPainter([g_Terrains.cliff, base], [3]), new SmoothElevationPainter(ELEVATION_SET, heightDen, 3), new TileClassPainter(g_TileClasses.valley) ]); // Expansion createArea( new ClumpPlacer(expSize, 0.9, 0.3, Infinity, expansionPosition), [ new LayeredPainter([g_Terrains.cliff, base], [3]), new SmoothElevationPainter(ELEVATION_SET, heightDen, 3), new TileClassPainter(g_TileClasses.settlement) ], [avoidClasses(g_TileClasses.settlement, 2)]); } g_Map.log("Creating the expansions between players"); for (let i = 0; i < numPlayers; ++i) { let position = getCoords(nExp, i, 0.5); createArea( new ClumpPlacer(expSize, 0.9, 0.3, Infinity, position), [ new LayeredPainter([g_Terrains.cliff, lower], [3]), new SmoothElevationPainter(ELEVATION_SET, heightValley, 3), new TileClassPainter(g_TileClasses.settlement) ]); } } Index: ps/trunk/binaries/data/mods/public/maps/random/marmara.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/marmara.js (revision 21205) +++ ps/trunk/binaries/data/mods/public/maps/random/marmara.js (revision 21206) @@ -1,366 +1,368 @@ /** * Heightmap image source: * Imagery by Jesse Allen, NASA's Earth Observatory, * using data from the General Bathymetric Chart of the Oceans (GEBCO) * produced by the British Oceanographic Data Centre. * https://visibleearth.nasa.gov/view.php?id=73934 * * Licensing: Public Domain, https://visibleearth.nasa.gov/useterms.php * * The heightmap image is reproduced using: * wget https://eoimages.gsfc.nasa.gov/images/imagerecords/73000/73934/gebco_08_rev_elev_C1_grey_geo.tif * lat=41; lon=28; width=5; * gdal_translate -projwin $((lon-width/2)) $((lat+width/2)) $((lon+width/2)) $((lat-width/2)) gebco_08_rev_elev_C1_grey_geo.tif marmara.tif * convert marmara.tif -resize 512 -contrast-stretch 0 marmara.png * No further changes should be applied to the image to keep it easily interchangeable. */ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen2"); Engine.LoadLibrary("rmbiome"); setBiome("generic/mediterranean"); g_Terrains.mainTerrain = ["grass_mediterranean_dry_1024test", "grass_field_dry","new_savanna_grass_b"]; g_Terrains.forestFloor1 = "steppe_grass_dirt_66"; g_Terrains.forestFloor2 = "steppe_dirt_a"; g_Terrains.tier1Terrain = "medit_grass_field_b"; g_Terrains.tier2Terrain = "medit_grass_field_dry"; g_Terrains.tier3Terrain = "medit_shrubs_golden"; g_Terrains.tier4Terrain = "steppe_dirt_b"; g_Terrains.cliff = "medit_cliff_a"; g_Terrains.roadWild = "road_med_a"; g_Terrains.road = "road2"; g_Terrains.water = "medit_sand_messy"; g_Gaia.mainHuntableAnimal = "gaia/fauna_horse"; g_Gaia.secondaryHuntableAnimal = "gaia/fauna_boar"; g_Gaia.fish = "gaia/fauna_fish"; g_Gaia.tree1 = "gaia/flora_tree_carob"; g_Gaia.tree2 = "gaia/flora_tree_poplar_lombardy"; g_Gaia.tree3 = "gaia/flora_tree_dead"; g_Gaia.tree4 = "gaia/flora_tree_dead"; g_Gaia.tree5 = "gaia/flora_tree_carob"; g_Gaia.fruitBush = "gaia/flora_bush_grapes"; g_Gaia.metalSmall = "gaia/geology_metal_desert_small"; g_Decoratives.grass = "actor|props/special/eyecandy/block_limestone.xml"; g_Decoratives.grassShort = "actor|props/special/eyecandy/blocks_sandstone_pile_a.xml"; g_Decoratives.rockLarge = "actor|geology/stone_savanna_med.xml"; g_Decoratives.rockMedium = "actor|geology/stone_granite_small.xml"; g_Decoratives.bushMedium = "actor|props/flora/bush_medit_me_dry.xml"; g_Decoratives.bushSmall = "actor|props/flora/bush_medit_sm_dry.xml"; g_Decoratives.reeds = "actor|props/flora/reeds_pond_lush_a.xml"; const heightScale = num => num * g_MapSettings.Size / 320; const heightSeaGround = heightScale(scaleByMapSize(-6, -4)); const heightWaterLevel = heightScale(0); const heightShoreline = heightScale(0); var g_Map = new RandomMap(0, g_Terrains.mainTerrain); var mapCenter = g_Map.getCenter(); initTileClasses(["shoreline"]); g_Map.LoadHeightmapImage("marmara.png", 0, 10); Engine.SetProgress(15); g_Map.log("Lowering sea ground"); createArea( new MapBoundsPlacer(), // Keep water impassable on all mapsizes new SmoothElevationPainter(ELEVATION_SET, heightSeaGround, scaleByMapSize(1, 3)), new HeightConstraint(-Infinity, heightWaterLevel)); Engine.SetProgress(20); g_Map.log("Smoothing heightmap"); createArea( new MapBoundsPlacer(), new SmoothingPainter(1, scaleByMapSize(0.1, 0.2), 1)); Engine.SetProgress(25); g_Map.log("Marking water"); createArea( new MapBoundsPlacer(), new TileClassPainter(g_TileClasses.water), new HeightConstraint(-Infinity, heightWaterLevel)); Engine.SetProgress(30); g_Map.log("Marking land"); createArea( new MapBoundsPlacer(), new TileClassPainter(g_TileClasses.land), avoidClasses(g_TileClasses.water, 0)); Engine.SetProgress(35); g_Map.log("Painting shoreline"); createArea( new MapBoundsPlacer(), [ new TerrainPainter(g_Terrains.water), new TileClassPainter(g_TileClasses.shoreline) ], new HeightConstraint(-Infinity, heightShoreline)); Engine.SetProgress(40); g_Map.log("Painting cliffs"); createArea( new MapBoundsPlacer(), [ new TerrainPainter(g_Terrains.cliff), new TileClassPainter(g_TileClasses.mountain), ], [ avoidClasses(g_TileClasses.water, 2), new SlopeConstraint(2, Infinity) ]); Engine.SetProgress(45); if (!isNomad()) { g_Map.log("Placing players"); - let playerBases = placeRandom( - sortAllPlayers(), - [ - avoidClasses(g_TileClasses.mountain, 5), - stayClasses(g_TileClasses.land, scaleByMapSize(3, 25)) - ]); + let [playerIDs, playerPosition] = createBases( + ...playerPlacementRandom( + sortAllPlayers(), + [ + avoidClasses(g_TileClasses.mountain, 5), + stayClasses(g_TileClasses.land, scaleByMapSize(6, 25)) + ]), + true); g_Map.log("Flatten the initial CC area..."); - for (let player of playerBases) + for (let position of playerPosition) createArea( - new ClumpPlacer(diskArea(defaultPlayerBaseRadius() * 0.8), 0.95, 0.6, Infinity, player.position), - new SmoothElevationPainter(ELEVATION_SET, g_Map.getHeight(player.position), 6)); + new ClumpPlacer(diskArea(defaultPlayerBaseRadius() * 0.8), 0.95, 0.6, Infinity, position), + new SmoothElevationPainter(ELEVATION_SET, g_Map.getHeight(position), 6)); } Engine.SetProgress(50); addElements([ { "func": addLayeredPatches, "avoid": [ g_TileClasses.bluff, 2, g_TileClasses.dirt, 5, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 12, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["many"] }, { "func": addDecoration, "avoid": [ g_TileClasses.bluff, 2, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 12, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["many"] } ]); Engine.SetProgress(60); addElements(shuffleArray([ { "func": addMetal, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 30, g_TileClasses.rock, 20, g_TileClasses.metal, 30, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["normal", "many"] }, { "func": addSmallMetal, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 30, g_TileClasses.rock, 20, g_TileClasses.metal, 30, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["normal", "many"] }, { "func": addStone, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 30, g_TileClasses.rock, 30, g_TileClasses.metal, 20, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["normal", "many"] }, { "func": addForests, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 10, g_TileClasses.metal, 3, g_TileClasses.mountain, 5, g_TileClasses.plateau, 5, g_TileClasses.player, 20, g_TileClasses.rock, 3, g_TileClasses.water, 2 ], "sizes": ["normal"], "mixes": ["similar"], "amounts": ["many"] } ])); Engine.SetProgress(70); addElements(shuffleArray([ { "func": addBerries, "avoid": [ g_TileClasses.berries, 30, g_TileClasses.bluff, 5, g_TileClasses.forest, 5, g_TileClasses.metal, 10, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 20, g_TileClasses.rock, 10, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["normal", "many"] }, { "func": addAnimals, "avoid": [ g_TileClasses.animals, 20, g_TileClasses.bluff, 5, g_TileClasses.forest, 2, g_TileClasses.metal, 2, g_TileClasses.mountain, 1, g_TileClasses.plateau, 2, g_TileClasses.player, 20, g_TileClasses.rock, 2, g_TileClasses.water, 3 ], "sizes": ["huge"], "mixes": ["unique"], "amounts": ["tons"] }, { "func": addFish, "avoid": [ g_TileClasses.fish, 12, g_TileClasses.player, 8 ], "stay": [g_TileClasses.water, 4], "sizes": ["normal"], "mixes": ["same"], "amounts": ["many"] }, { "func": addStragglerTrees, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 5, g_TileClasses.metal, 2, g_TileClasses.mountain, 1, g_TileClasses.plateau, 2, g_TileClasses.player, 12, g_TileClasses.rock, 2, g_TileClasses.water, 5 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["tons"] } ])); Engine.SetProgress(80); log("Adding reeds..."); createObjectGroups( new SimpleGroup( [ new SimpleObject(g_Decoratives.reeds, 5, 12, 1, 2), new SimpleObject(g_Decoratives.rockMedium, 1, 2, 1, 3) ], true, g_TileClasses.dirt ), 0, [ stayClasses(g_TileClasses.water, 0), borderClasses(g_TileClasses.water, scaleByMapSize(2,8), scaleByMapSize(2,8)) ], scaleByMapSize(50, 400), 2); Engine.SetProgress(85); placePlayersNomad( g_Map.createTileClass(), [ stayClasses(g_TileClasses.land, 5), avoidClasses( g_TileClasses.forest, 2, g_TileClasses.rock, 4, g_TileClasses.metal, 4, g_TileClasses.berries, 2, g_TileClasses.animals, 2, g_TileClasses.mountain, 2) ]); setSunColor(0.753, 0.586, 0.584); setSkySet("sunset"); // Inverted so that water appears on tiny maps and passages are maximized on larger maps setWaterHeight(scaleByMapSize(20, 18)); setWaterTint(0.25, 0.67, 0.65); setWaterColor(0.18, 0.36, 0.39); setWaterWaviness(8); setWaterMurkiness(0.99); setWaterType("lake"); setTerrainAmbientColor(0.521, 0.475, 0.322); setSunRotation(Math.PI * 0.85); setSunElevation(Math.PI / 14); setFogFactor(0.15); setFogThickness(0); setFogColor(0.64, 0.5, 0.35); setPPEffect("hdr"); setPPContrast(0.67); setPPSaturation(0.42); setPPBloom(0.23); g_Map.ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/mediterranean.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/mediterranean.js (revision 21205) +++ ps/trunk/binaries/data/mods/public/maps/random/mediterranean.js (revision 21206) @@ -1,472 +1,467 @@ /** * Heightmap image source: * Imagery by Jesse Allen, NASA's Earth Observatory, * using data from the General Bathymetric Chart of the Oceans (GEBCO) * produced by the British Oceanographic Data Centre. * https://visibleearth.nasa.gov/view.php?id=73934 * * Licensing: Public Domain, https://visibleearth.nasa.gov/useterms.php * * The heightmap image is reproduced using: * wget https://eoimages.gsfc.nasa.gov/images/imagerecords/73000/73934/gebco_08_rev_elev_{A,B,C,D}{1..2}_grey_geo.tif * gdal_merge.py -o world.tif gebco_08_rev_elev_{A,B,C,D}{1..2}_grey_geo.tif * lat=46; lon=14; width=58; * gdal_translate -projwin $((lon-width/2)) $((lat+width/2)) $((lon+width/2)) $((lat-width/2)) world.tif mediterranean.tif * convert mediterranean.tif -resize 512 -contrast-stretch 0 mediterranean.png * No further changes should be applied to the image to keep it easily interchangeable. */ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen2"); Engine.LoadLibrary("rmbiome"); TILE_CENTERED_HEIGHT_MAP = true; const tWater = "medit_sand_wet"; const tSnowedRocks = ["alpine_cliff_b", "alpine_cliff_snow"]; setBiome("generic/mediterranean"); const heightScale = num => num * g_MapSettings.Size / 320; const heightSeaGround = heightScale(-6); const heightWaterLevel = heightScale(0); const heightShoreline = heightScale(0.5); const heightSnow = heightScale(10); var g_Map = new RandomMap(heightWaterLevel, g_Terrains.mainTerrain); var mapSize = g_Map.getSize(); var mapCenter = g_Map.getCenter(); var mapBounds = g_Map.getBounds(); g_Map.LoadHeightmapImage("mediterranean.png", 0, 40); Engine.SetProgress(15); initTileClasses(["autumn", "desert", "medit", "polar", "steppe", "temp", "shoreline", "africa", "northern_europe", "southern_europe", "western_europe", "eastern_europe"]); var northernTopLeft = new Vector2D(fractionToTiles(0.3), fractionToTiles(0.7)); var westernTopLeft = new Vector2D(fractionToTiles(0.7), fractionToTiles(0.47)); var africaTop = fractionToTiles(0.33); var climateZones = [ { "tileClass": g_TileClasses.northern_europe, "position1": new Vector2D(northernTopLeft.x, mapBounds.top), "position2": new Vector2D(mapBounds.right, northernTopLeft.y), "biome": "generic/snowy", "constraint": new NullConstraint() }, { "tileClass": g_TileClasses.western_europe, "position1": new Vector2D(mapBounds.left, mapBounds.top), "position2": westernTopLeft, "biome": "generic/temperate", "constraint": avoidClasses(g_TileClasses.northern_europe, 0) }, { "tileClass": g_TileClasses.eastern_europe, "position1": new Vector2D(westernTopLeft.x, mapBounds.top), "position2": new Vector2D(mapBounds.right, westernTopLeft.y), "biome": "generic/autumn", "constraint": avoidClasses(g_TileClasses.northern_europe, 0) }, { "tileClass": g_TileClasses.southern_europe, "position1": new Vector2D(mapBounds.left, africaTop), "position2": new Vector2D(mapBounds.right, westernTopLeft.y), "biome": "generic/mediterranean", "constraint": new NullConstraint() }, { "tileClass": g_TileClasses.africa, "position1": new Vector2D(mapBounds.left, africaTop), "position2": new Vector2D(mapBounds.right, mapBounds.bottom), "biome": "generic/desert", "constraint": new NullConstraint() } ]; g_Map.log("Lowering sea ground"); createArea( new MapBoundsPlacer(), new SmoothElevationPainter(ELEVATION_SET, heightSeaGround, 2), new HeightConstraint(-Infinity, heightWaterLevel)); Engine.SetProgress(20); g_Map.log("Smoothing heightmap"); createArea( new MapBoundsPlacer(), new SmoothingPainter(1, scaleByMapSize(0.3, 0.8), 1)); Engine.SetProgress(25); g_Map.log("Marking water"); createArea( new MapBoundsPlacer(), new TileClassPainter(g_TileClasses.water), new HeightConstraint(-Infinity, heightWaterLevel)); Engine.SetProgress(30); g_Map.log("Marking land"); createArea( new ClumpPlacer(diskArea(fractionToTiles(0.5)), 1, 1, Infinity, mapCenter), new TileClassPainter(g_TileClasses.land), avoidClasses(g_TileClasses.water, 0)); Engine.SetProgress(35); g_Map.log("Marking climate zones"); for (let zone of climateZones) { setBiome(zone.biome); createArea( new RectPlacer(zone.position1, zone.position2, Infinity), new TileClassPainter(zone.tileClass), zone.constraint); createArea( new RectPlacer(zone.position1, zone.position2, Infinity), new TerrainPainter(g_Terrains.mainTerrain), [ new HeightConstraint(heightWaterLevel, Infinity), zone.constraint ]); } Engine.SetProgress(40); g_Map.log("Fuzzing biome borders"); for (let zone of climateZones) { setBiome(zone.biome); createLayeredPatches( [scaleByMapSize(3, 6), scaleByMapSize(5, 10), scaleByMapSize(8, 21)], [ [g_Terrains.mainTerrain, g_Terrains.tier1Terrain], [g_Terrains.tier1Terrain, g_Terrains.tier2Terrain], [g_Terrains.tier2Terrain, g_Terrains.tier3Terrain] ], [1, 1], [ avoidClasses( g_TileClasses.forest, 2, g_TileClasses.water, 2, g_TileClasses.mountain, 2, g_TileClasses.dirt, 5, g_TileClasses.player, 8), borderClasses(zone.tileClass, 3, 3), ], scaleByMapSize(20, 60), g_TileClasses.dirt); } Engine.SetProgress(45); if (!isNomad()) { - g_Map.log("Placing players"); + g_Map.log("Finding player positions"); - let playerBases = randomPlayerLocations( + let [playerIDs, playerPosition] = playerPlacementRandom( sortAllPlayers(), [ avoidClasses(g_TileClasses.mountain, 5), stayClasses(g_TileClasses.land, scaleByMapSize(8, 25)) ]); - g_Map.log("Flatten the initial CC area..."); - for (let player of playerBases) + g_Map.log("Flatten the initial CC area and placing playerbases..."); + for (let i = 0; i < getNumPlayers(); ++i) { - for (let zone of climateZones) - if (stayClasses(zone.tileClass, 1).allows(player.position)) - { - g_Map.logger.printDuration(); - setBiome(zone.biome); - break; - } + g_Map.logger.printDuration(); + setBiome(climateZones.find(zone => stayClasses(zone.tileClass, 1).allows(playerPosition[i])).biome); createArea( - new ClumpPlacer(diskArea(defaultPlayerBaseRadius() * 0.8), 0.95, 0.6, Infinity, player.position), - new SmoothElevationPainter(ELEVATION_SET, g_Map.getHeight(player.position), 6)); + new ClumpPlacer(diskArea(defaultPlayerBaseRadius() * 0.8), 0.95, 0.6, Infinity, playerPosition[i]), + new SmoothElevationPainter(ELEVATION_SET, g_Map.getHeight(playerPosition[i]), 6)); - createBase(player, mapSize >= 384); + createBase(playerIDs[i], playerPosition[i], mapSize >= 384); } } Engine.SetProgress(50); for (let zone of climateZones) { setBiome(zone.biome); g_Map.log("Painting shoreline"); createArea( new MapBoundsPlacer(), [ new TerrainPainter(g_Terrains.shore), new TileClassPainter(g_TileClasses.shoreline) ], [ stayClasses(zone.tileClass, 0), new HeightConstraint(-Infinity, heightShoreline) ]); g_Map.log("Painting cliffs"); createArea( new MapBoundsPlacer(), [ new TerrainPainter(g_Terrains.cliff), new TileClassPainter(g_TileClasses.mountain), ], [ stayClasses(zone.tileClass, 0), avoidClasses(g_TileClasses.water, 2), new SlopeConstraint(2, Infinity) ]); g_Map.log("Placing resources"); addElements([ { "func": addMetal, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.player, 30, g_TileClasses.rock, 10, g_TileClasses.metal, 25, g_TileClasses.water, 4 ], "stay": [zone.tileClass, 0], "sizes": ["normal"], "mixes": ["same"], "amounts": ["many"] }, { "func": addStone, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.player, 30, g_TileClasses.rock, 10, g_TileClasses.metal, 25, g_TileClasses.water, 4 ], "stay": [zone.tileClass, 0], "sizes": ["normal"], "mixes": ["same"], "amounts": ["many"] }, { "func": addForests, "avoid": [ g_TileClasses.berries, 3, g_TileClasses.forest, 15, g_TileClasses.metal, 3, g_TileClasses.mountain, 2, g_TileClasses.player, 12, g_TileClasses.rock, 2, g_TileClasses.water, 2 ], "stay": [zone.tileClass, 0], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] }, { "func": addSmallMetal, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.player, 30, g_TileClasses.rock, 10, g_TileClasses.metal, 15, g_TileClasses.water, 4 ], "stay": [zone.tileClass, 0], "sizes": ["normal"], "mixes": ["same"], "amounts": ["few", "normal", "many"] }, { "func": addBerries, "avoid": [ g_TileClasses.berries, 30, g_TileClasses.forest, 2, g_TileClasses.metal, 4, g_TileClasses.mountain, 2, g_TileClasses.player, 20, g_TileClasses.rock, 4, g_TileClasses.water, 2 ], "stay": [zone.tileClass, 0], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["many"] }, { "func": addAnimals, "avoid": [ g_TileClasses.animals, 10, g_TileClasses.forest, 1, g_TileClasses.metal, 2, g_TileClasses.mountain, 1, g_TileClasses.player, 15, g_TileClasses.rock, 2, g_TileClasses.water, 3 ], "stay": [zone.tileClass, 0], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["many"] }, { "func": addAnimals, "avoid": [ g_TileClasses.animals, 10, g_TileClasses.forest, 1, g_TileClasses.metal, 2, g_TileClasses.mountain, 1, g_TileClasses.player, 15, g_TileClasses.rock, 2, g_TileClasses.water, 1 ], "stay": [zone.tileClass, 0], "sizes": ["small"], "mixes": ["normal"], "amounts": ["tons"] }, { "func": addStragglerTrees, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 5, g_TileClasses.metal, 2, g_TileClasses.mountain, 1, g_TileClasses.player, 12, g_TileClasses.rock, 2, g_TileClasses.water, 3 ], "stay": [zone.tileClass, 0], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["some"] }, { "func": addLayeredPatches, "avoid": [ g_TileClasses.dirt, 5, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12, g_TileClasses.water, 3 ], "stay": [zone.tileClass, 0], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["tons"] }, { "func": addDecoration, "avoid": [ g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12, g_TileClasses.water, 4 ], "stay": [zone.tileClass, 0], "sizes": ["small"], "mixes": ["same"], "amounts": ["normal"] } ]); } Engine.SetProgress(60); g_Map.log("Painting water"); createArea( new MapBoundsPlacer(), new TerrainPainter(tWater), new HeightConstraint(-Infinity, heightWaterLevel)); g_Map.log("Painting snow on mountains"); createArea( new MapBoundsPlacer(), new TerrainPainter(tSnowedRocks), [ new HeightConstraint(heightSnow, Infinity), avoidClasses( g_TileClasses.africa, 0, g_TileClasses.southern_europe, 0, g_TileClasses.player, 6) ]); Engine.SetProgress(70); g_Map.log("Placing fish..."); g_Gaia.fish = "gaia/fauna_fish"; addElements([ { "func": addFish, "avoid": [ g_TileClasses.fish, 10, ], "stay": [g_TileClasses.water, 4], "sizes": ["normal"], "mixes": ["similar"], "amounts": ["many"] } ]); Engine.SetProgress(85); g_Map.log("Placing whale..."); g_Gaia.fish = "gaia/fauna_whale_fin"; addElements([ { "func": addFish, "avoid": [ g_TileClasses.fish, 2, g_TileClasses.desert, 50, g_TileClasses.steppe, 50 ], "stay": [g_TileClasses.water, 7], "sizes": ["small"], "mixes": ["same"], "amounts": ["scarce"] } ]); Engine.SetProgress(95); placePlayersNomad( g_Map.createTileClass(), [ stayClasses(g_TileClasses.land, 5), avoidClasses( g_TileClasses.forest, 2, g_TileClasses.rock, 4, g_TileClasses.metal, 4, g_TileClasses.berries, 2, g_TileClasses.animals, 2, g_TileClasses.mountain, 2) ]); setWindAngle(-0.589049); setWaterTint(0.556863, 0.615686, 0.643137); setWaterColor(0.494118, 0.639216, 0.713726); setWaterWaviness(8); setWaterMurkiness(0.87); setWaterType("ocean"); setTerrainAmbientColor(0.72, 0.72, 0.82); setSunColor(0.733, 0.746, 0.574); setSunRotation(Math.PI * 0.95); setSunElevation(Math.PI / 6); setSkySet("cumulus"); setFogFactor(0); setFogThickness(0); setFogColor(0.69, 0.616, 0.541); setPPEffect("hdr"); setPPContrast(0.67); setPPSaturation(0.42); setPPBloom(0.23); g_Map.ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/ngorongoro.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/ngorongoro.js (revision 21205) +++ ps/trunk/binaries/data/mods/public/maps/random/ngorongoro.js (revision 21206) @@ -1,571 +1,573 @@ /** * Heightmap image source: * Imagery by Jesse Allen, NASA's Earth Observatory, * using data from the General Bathymetric Chart of the Oceans (GEBCO) * produced by the British Oceanographic Data Centre. * https://visibleearth.nasa.gov/view.php?id=73934 * * Licensing: Public Domain, https://visibleearth.nasa.gov/useterms.php * * The heightmap image is reproduced using: * wget https://eoimages.gsfc.nasa.gov/images/imagerecords/73000/73934/gebco_08_rev_elev_C2_grey_geo.tif * lat=-3.177437; lon=35.574687; width=0.7 * lat1=$(bc <<< ";scale=5;$lat-$width/2"); lon1=$(bc <<< ";scale=5;$lon+$width/2"); lat2=$(bc <<< ";scale=5;$lat+$width/2"); lon2=$(bc <<< ";scale=5;$lon-$width/2") * gdal_translate -projwin $lon2 $lat2 $lon1 $lat1 gebco_08_rev_elev_C2_grey_geo.tif ngorongoro.tif * convert ngorongoro.tif -resize 512 -contrast-stretch 0 ngorongoro.png * No further changes should be applied to the image to keep it easily interchangeable. */ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen2"); Engine.LoadLibrary("rmbiome"); setBiome("generic/savanna"); // ["dirta","savanna_wash_a","savanna_dirt_b","savanna_riparian_bank","savanna_grass_b","grass b soft dirt 50","grass1_spring","grass_field","grass1_spring","savanna_grass_a_wetseason","savanna_grass_b_wetseason","savanna_grass_a","new_savanna_grass_a","new_savanna_grass_b","new_savanna_grass_c","steppe_grass_dirt_66","peat_temp"]; g_Terrains.roadWild = "savanna_riparian_dry"; g_Terrains.road = "road2"; g_Gaia.metalLarge = "gaia/geology_metal_savanna_slabs"; g_Gaia.metalSmall = "gaia/geology_metal_tropic"; g_Gaia.fish = "gaia/fauna_fish_tilapia"; g_Gaia.tree1 = "gaia/flora_tree_baobab"; g_Gaia.tree2 = "gaia/flora_tree_baobab"; g_Gaia.tree3 = "gaia/flora_tree_baobab"; g_Gaia.tree4 = "gaia/flora_tree_baobab"; g_Gaia.tree5 = "gaia/flora_tree_baobab"; g_Decoratives.grass = "actor|props/flora/grass_savanna.xml"; g_Decoratives.grassShort = "actor|props/flora/grass_soft_dry_tuft_a.xml"; g_Decoratives.rockLarge = "actor|geology/stone_savanna_med.xml"; g_Decoratives.rockMedium = "actor|geology/stone_savanna_med.xml"; g_Decoratives.bushMedium = "actor|props/flora/bush_desert_dry_a.xml"; g_Decoratives.bushSmall = "actor|props/flora/bush_dry_a.xml"; const heightScale = num => num * g_MapSettings.Size / 320; const heightHighlands = heightScale(45); const heightEden = heightScale(60); const heightMax = 150; function setBiomeLowlands() { g_Gaia.mainHuntableAnimal = "gaia/fauna_giraffe"; g_Gaia.secondaryHuntableAnimal = "gaia/fauna_zebra"; g_Terrains.mainTerrain = "savanna_riparian_bank"; g_Terrains.forestFloor1 = "savanna_dirt_rocks_b"; g_Terrains.forestFloor2 = "savanna_dirt_rocks_c"; g_Terrains.tier1Terrain = "savanna_dirt_rocks_a"; g_Terrains.tier2Terrain = "savanna_grass_a"; g_Terrains.tier3Terrain = "savanna_grass_b"; g_Terrains.tier4Terrain = "savanna_forest_floor_a"; } function setBiomeHighlands() { g_Gaia.mainHuntableAnimal = "gaia/fauna_lioness"; g_Gaia.secondaryHuntableAnimal = "gaia/fauna_lion"; g_Terrains.mainTerrain = "savanna_grass_a_wetseason"; g_Terrains.forestFloor1 = "savanna_grass_a"; g_Terrains.forestFloor2 = "savanna_grass_b"; g_Terrains.tier1Terrain = "savanna_grass_a_wetseason"; g_Terrains.tier2Terrain = "savanna_grass_b_wetseason"; g_Terrains.tier3Terrain = "savanna_shrubs_a_wetseason"; g_Terrains.tier4Terrain = "savanna_shrubs_b"; } function setBiomeEden() { g_Gaia.mainHuntableAnimal = "gaia/fauna_rhino"; g_Gaia.secondaryHuntableAnimal = "gaia/fauna_elephant_african_bush"; } var g_Map = new RandomMap(0, g_Terrains.mainTerrain); var mapCenter = g_Map.getCenter(); initTileClasses(["eden", "highlands"]); g_Map.LoadHeightmapImage("ngorongoro.png", 0, heightMax); Engine.SetProgress(15); g_Map.log("Smoothing heightmap"); createArea( new MapBoundsPlacer(), new SmoothingPainter(1, scaleByMapSize(0.1, 0.5), 1)); Engine.SetProgress(25); g_Map.log("Marking land"); createArea( new MapBoundsPlacer(), new TileClassPainter(g_TileClasses.land)); Engine.SetProgress(40); g_Map.log("Marking eden"); createArea( new ClumpPlacer(diskArea(fractionToTiles(0.14)), 1, 1, Infinity, mapCenter), new TileClassPainter(g_TileClasses.eden), new HeightConstraint(-Infinity, heightEden)); Engine.SetProgress(45); g_Map.log("Marking highlands"); createArea( new MapBoundsPlacer(), new TileClassPainter(g_TileClasses.highlands), [ new HeightConstraint(heightHighlands, Infinity), avoidClasses(g_TileClasses.eden, 0) ]); Engine.SetProgress(50); g_Map.log("Painting cliffs"); createArea( new MapBoundsPlacer(), [ new TerrainPainter(g_Terrains.cliff), new TileClassPainter(g_TileClasses.mountain), ], new SlopeConstraint(2, Infinity)); Engine.SetProgress(45); if (!isNomad()) { g_Map.log("Placing players"); - let playerBases = placeRandom( - sortAllPlayers(), - [ - avoidClasses( + let [playerIDs, playerPosition] = createBases( + ...playerPlacementRandom( + sortAllPlayers(), + [ + avoidClasses( g_TileClasses.mountain, 5, g_TileClasses.highlands, 5, g_TileClasses.eden, 5), - stayClasses(g_TileClasses.land, defaultPlayerBaseRadius()) - ]); + stayClasses(g_TileClasses.land, defaultPlayerBaseRadius()) + ]), + true); g_Map.log("Flatten the initial CC area..."); - for (let player of playerBases) + for (let position of playerPosition) createArea( - new ClumpPlacer(diskArea(defaultPlayerBaseRadius() * 0.8), 0.95, 0.6, Infinity, player.position), - new SmoothElevationPainter(ELEVATION_SET, g_Map.getHeight(player.position), 6)); + new ClumpPlacer(diskArea(defaultPlayerBaseRadius() * 0.8), 0.95, 0.6, Infinity, position), + new SmoothElevationPainter(ELEVATION_SET, g_Map.getHeight(position), 6)); } log("Render lowlands..."); setBiomeLowlands(); addElements([ { "func": addLayeredPatches, "avoid": [ g_TileClasses.dirt, 5, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12, g_TileClasses.eden, 2, g_TileClasses.highlands, 2 ], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["many"] }, { "func": addDecoration, "avoid": [ g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12, g_TileClasses.eden, 2, g_TileClasses.highlands, 2 ], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["many"] } ]); addElements(shuffleArray([ { "func": addSmallMetal, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 6, g_TileClasses.player, 30, g_TileClasses.rock, 20, g_TileClasses.metal, 10, g_TileClasses.eden, 2, g_TileClasses.highlands, 2 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["few"] }, { "func": addStone, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 6, g_TileClasses.player, 30, g_TileClasses.rock, 20, g_TileClasses.metal, 10, g_TileClasses.eden, 2, g_TileClasses.highlands, 2 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["scarce"] }, { "func": addForests, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 8, g_TileClasses.metal, 3, g_TileClasses.mountain, 6, g_TileClasses.player, 20, g_TileClasses.rock, 3, g_TileClasses.eden, 2, g_TileClasses.highlands, 2 ], "sizes": ["normal"], "mixes": ["similar"], "amounts": ["normal"] } ])); addElements(shuffleArray([ { "func": addAnimals, "avoid": [ g_TileClasses.animals, 20, g_TileClasses.forest, 2, g_TileClasses.metal, 2, g_TileClasses.mountain, 6, g_TileClasses.player, 20, g_TileClasses.rock, 2, g_TileClasses.eden, 2, g_TileClasses.highlands, 2 ], "sizes": ["big"], "mixes": ["similar"], "amounts": ["many"] }, { "func": addAnimals, "avoid": [ g_TileClasses.animals, 20, g_TileClasses.forest, 2, g_TileClasses.metal, 2, g_TileClasses.mountain, 6, g_TileClasses.player, 20, g_TileClasses.rock, 2, g_TileClasses.eden, 2, g_TileClasses.highlands, 2 ], "sizes": ["normal"], "mixes": ["unique"], "amounts": ["many"] }, { "func": addStragglerTrees, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 4, g_TileClasses.metal, 2, g_TileClasses.mountain, 6, g_TileClasses.player, 12, g_TileClasses.rock, 2, g_TileClasses.eden, 2, g_TileClasses.highlands, 2 ], "sizes": ["big"], "mixes": ["same"], "amounts": ["many"] } ])); Engine.SetProgress(60); log("Render highlands..."); setBiomeHighlands(); addElements([ { "func": addLayeredPatches, "avoid": [ g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12 ], "stay": [g_TileClasses.highlands, 2], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["many"] }, { "func": addDecoration, "avoid": [ g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12 ], "stay": [g_TileClasses.highlands, 2], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["many"] } ]); addElements(shuffleArray([ { "func": addSmallMetal, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 3, g_TileClasses.player, 30, g_TileClasses.rock, 10, g_TileClasses.metal, 20 ], "stay": [g_TileClasses.highlands, 2], "sizes": ["normal"], "mixes": ["same"], "amounts": ["many"] }, { "func": addStone, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 3, g_TileClasses.player, 30, g_TileClasses.rock, 20, g_TileClasses.metal, 10 ], "stay": [g_TileClasses.highlands, 2], "sizes": ["normal"], "mixes": ["same"], "amounts": ["many"] }, { "func": addForests, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 8, g_TileClasses.metal, 3, g_TileClasses.mountain, 3, g_TileClasses.player, 20, g_TileClasses.rock, 3 ], "stay": [g_TileClasses.highlands, 2], "sizes": ["huge"], "mixes": ["similar"], "amounts": ["many"] } ])); addElements(shuffleArray([ { "func": addAnimals, "avoid": [ g_TileClasses.animals, 20, g_TileClasses.forest, 2, g_TileClasses.metal, 2, g_TileClasses.mountain, 3, g_TileClasses.player, 20, g_TileClasses.rock, 2 ], "stay": [g_TileClasses.highlands, 2], "sizes": ["huge"], "mixes": ["same"], "amounts": ["many"] }, { "func": addStragglerTrees, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 2, g_TileClasses.metal, 2, g_TileClasses.mountain, 3, g_TileClasses.player, 12, g_TileClasses.rock, 2 ], "stay": [g_TileClasses.highlands, 2], "sizes": ["huge"], "mixes": ["same"], "amounts": ["many"] } ])); Engine.SetProgress(70); log("Render eden..."); setBiomeEden(); addElements([ { "func": addLayeredPatches, "avoid": [ g_TileClasses.dirt, 5, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12 ], "stay": [g_TileClasses.eden, 2], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["many"] }, { "func": addDecoration, "avoid": [ g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12 ], "stay": [g_TileClasses.eden, 2], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["many"] } ]); addElements(shuffleArray([ { "func": addMetal, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.player, 20, g_TileClasses.rock, 3, g_TileClasses.metal, 3 ], "stay": [g_TileClasses.eden, 2], "sizes": ["normal"], "mixes": ["same"], "amounts": ["tons"] }, { "func": addMetal, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.player, 20, g_TileClasses.rock, 3, g_TileClasses.metal, 3 ], "stay": [g_TileClasses.eden, 2], "sizes": ["normal"], "mixes": ["same"], "amounts": ["tons"] }, { "func": addSmallMetal, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.player, 20, g_TileClasses.rock, 3, g_TileClasses.metal, 3 ], "stay": [g_TileClasses.eden, 2], "sizes": ["normal"], "mixes": ["same"], "amounts": ["tons"] }, { "func": addStone, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.player, 30, g_TileClasses.rock, 3, g_TileClasses.metal, 3 ], "stay": [g_TileClasses.eden, 2], "sizes": ["normal"], "mixes": ["same"], "amounts": ["few"] }, { "func": addForests, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 8, g_TileClasses.metal, 3, g_TileClasses.mountain, 8, g_TileClasses.player, 20, g_TileClasses.rock, 3 ], "stay": [g_TileClasses.eden, 2], "sizes": ["huge"], "mixes": ["similar"], "amounts": ["scarce"] } ])); addElements(shuffleArray([ { "func": addAnimals, "avoid": [ g_TileClasses.animals, 2, g_TileClasses.forest, 2, g_TileClasses.metal, 2, g_TileClasses.mountain, 3, g_TileClasses.player, 20, g_TileClasses.rock, 2 ], "stay": [g_TileClasses.eden, 2], "sizes": ["normal"], "mixes": ["same"], "amounts": ["many"] }, { "func": addStragglerTrees, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 2, g_TileClasses.metal, 2, g_TileClasses.mountain, 8, g_TileClasses.player, 12, g_TileClasses.rock, 2 ], "stay": [g_TileClasses.eden, 2], "sizes": ["huge"], "mixes": ["same"], "amounts": ["scarce"] } ])); Engine.SetProgress(80); placePlayersNomad( g_Map.createTileClass(), [ stayClasses(g_TileClasses.land, 5), avoidClasses( g_TileClasses.forest, 2, g_TileClasses.rock, 4, g_TileClasses.metal, 4, g_TileClasses.berries, 2, g_TileClasses.animals, 2, g_TileClasses.mountain, 2) ]); setTerrainAmbientColor(0.521, 0.475, 0.322); setSunColor(0.733, 0.746, 0.574); setSunRotation(Math.PI); setSunElevation(1/2); setFogFactor(0); setFogThickness(0); setFogColor(0.69, 0.616, 0.541); setPPEffect("hdr"); setPPContrast(0.67); setPPSaturation(0.42); setPPBloom(0.23); g_Map.ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/pompeii.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/pompeii.js (revision 21205) +++ ps/trunk/binaries/data/mods/public/maps/random/pompeii.js (revision 21206) @@ -1,474 +1,476 @@ /** * Heightmap image source: * Imagery by Jesse Allen, NASA's Earth Observatory, * using data from the General Bathymetric Chart of the Oceans (GEBCO) * produced by the British Oceanographic Data Centre. * https://visibleearth.nasa.gov/view.php?id=73934 * * Licensing: Public Domain, https://visibleearth.nasa.gov/useteEngine.php * * The heightmap image is reproduced using: * wget https://eoimages.gsfc.nasa.gov/images/imagerecords/73000/73934/gebco_08_rev_elev_C1_grey_geo.tif * lat=41.1; lon=14.25; width=1.4; * lat1=$(bc <<< ";scale=5;$lat-$width/2"); lon1=$(bc <<< ";scale=5;$lon+$width/2"); lat2=$(bc <<< ";scale=5;$lat+$width/2"); lon2=$(bc <<< ";scale=5;$lon-$width/2") * gdal_translate -projwin $lon2 $lat2 $lon1 $lat1 gebco_08_rev_elev_C1_grey_geo.tif pompeii.tif * convert pompeii.tif -resize 512 -contrast-stretch 0 pompeii.png * No further changes should be applied to the image to keep it easily interchangeable. */ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen2"); Engine.LoadLibrary("rmbiome"); setBiome("generic/mediterranean"); g_Terrains.lavaOuter = "LavaTest06"; g_Terrains.lavaInner = "LavaTest05"; g_Terrains.lavaCenter = "LavaTest04"; g_Terrains.mainTerrain = "ocean_rock_a"; g_Terrains.forestFloor1 = "dirt_burned"; g_Terrains.forestFloor2 = "shoreline_stoney_a"; g_Terrains.tier1Terrain = "rock_metamorphic"; g_Terrains.tier2Terrain = "fissures"; g_Terrains.tier3Terrain = "LavaTest06"; g_Terrains.tier4Terrain = "ocean_rock_b"; g_Terrains.roadWild = "road1"; g_Terrains.road = "road1"; g_Terrains.water = "ocean_rock_a"; g_Terrains.cliff = "ocean_rock_b"; g_Gaia.mainHuntableAnimal = "gaia/fauna_goat"; g_Gaia.secondaryHuntableAnimal = "gaia/fauna_hawk"; g_Gaia.fruitBush = "gaia/fauna_chicken"; g_Gaia.fish = "gaia/fauna_fish"; g_Gaia.tree1 = "gaia/flora_tree_dead"; g_Gaia.tree2 = "gaia/flora_tree_oak_dead"; g_Gaia.tree3 = "gaia/flora_tree_dead"; g_Gaia.tree4 = "gaia/flora_tree_oak_dead"; g_Gaia.tree5 = "gaia/flora_tree_dead"; g_Gaia.stoneSmall = "gaia/geology_stone_alpine_a"; g_Gaia.columnsDoric = "gaia/ruins/column_doric"; g_Gaia.romanStatue = "gaia/ruins/stone_statues_roman"; g_Gaia.unfinishedTemple = "gaia/ruins/unfinished_greek_temple"; g_Gaia.dock = "structures/rome_dock"; g_Gaia.dockRubble = "rubble/rubble_rome_dock"; g_Decoratives.smoke1 = "actor|particle/smoke_volcano.xml"; g_Decoratives.smoke2 = "actor|particle/smoke_curved.xml"; g_Decoratives.grass = "actor|props/flora/grass_field_parched_short.xml"; g_Decoratives.grassShort = "actor|props/flora/grass_soft_dry_tuft_a.xml"; g_Decoratives.bushMedium = "actor|props/special/eyecandy/barrels_buried.xml"; g_Decoratives.bushSmall = "actor|props/special/eyecandy/handcart_1_broken.xml"; g_Decoratives.skeleton = "actor|props/special/eyecandy/skeleton.xml"; g_Decoratives.shipwrecks = [ "actor|props/special/eyecandy/shipwreck_hull.xml", "actor|props/special/eyecandy/shipwreck_ram_side.xml", "actor|props/special/eyecandy/shipwreck_sail_boat.xml", "actor|props/special/eyecandy/shipwreck_sail_boat_cut.xml", "actor|props/special/eyecandy/barrels_floating.xml" ]; g_Decoratives.statues = [ "actor|props/special/eyecandy/statue_aphrodite_huge.xml", "actor|props/special/eyecandy/sele_colonnade.xml", "actor|props/special/eyecandy/well_1_b.xml", "actor|props/special/eyecandy/anvil.xml", "actor|props/special/eyecandy/wheel_laying.xml", "actor|props/special/eyecandy/vase_rome_a.xml" ]; const heightScale = num => num * g_MapSettings.Size / 320; const heightSeaGround = heightScale(-30); const heightShorelineMin = heightScale(-1); const heightShorelineMax = heightScale(0); const heightWaterLevel = heightScale(0); const heightLavaVesuv = heightScale(38); const heightMountains = 140; var g_Map = new RandomMap(0, g_Terrains.mainTerrain); var mapCenter = g_Map.getCenter(); initTileClasses(["decorative", "lava", "dock"]); g_Map.LoadHeightmapImage("pompeii.png", 0, heightMountains); Engine.SetProgress(15); g_Map.log("Lowering sea ground"); createArea( new MapBoundsPlacer(), new SmoothElevationPainter(ELEVATION_SET, heightSeaGround, 2), new HeightConstraint(-Infinity, heightWaterLevel)); Engine.SetProgress(20); g_Map.log("Smoothing heightmap"); createArea( new MapBoundsPlacer(), new SmoothingPainter(1, 0.8, 1)); Engine.SetProgress(25); g_Map.log("Marking water"); createArea( new MapBoundsPlacer(), new TileClassPainter(g_TileClasses.water), new HeightConstraint(-Infinity, heightWaterLevel)); Engine.SetProgress(30); g_Map.log("Marking shoreline"); var areaShoreline = createArea( new MapBoundsPlacer(), undefined, new HeightConstraint(heightShorelineMin, heightShorelineMax)); Engine.SetProgress(35); g_Map.log("Marking land"); createArea( new MapBoundsPlacer(), new TileClassPainter(g_TileClasses.land), avoidClasses(g_TileClasses.water, 0)); Engine.SetProgress(40); g_Map.log("Marking dock search location"); var areaDockStart = createArea( new ClumpPlacer(diskArea(fractionToTiles(0.5)) - 10, 1, 1, Infinity, mapCenter), undefined, stayClasses(g_TileClasses.land, 6)); g_Map.log("Painting cliffs"); createArea( new MapBoundsPlacer(), [ new TerrainPainter(g_Terrains.cliff), new TileClassPainter(g_TileClasses.mountain), ], [ avoidClasses(g_TileClasses.water, 2), new SlopeConstraint(2, Infinity) ]); Engine.SetProgress(45); g_Map.log("Painting lava"); var areaVesuv = createArea( new RectPlacer(new Vector2D(mapCenter.x, fractionToTiles(0.3)), new Vector2D(fractionToTiles(0.7), fractionToTiles(0.15))), [ new LayeredPainter([g_Terrains.lavaOuter,g_Terrains.lavaInner, g_Terrains.lavaCenter], [scaleByMapSize(1, 3), 2]), new TileClassPainter(g_TileClasses.lava) ], new HeightConstraint(heightLavaVesuv, Infinity)); Engine.SetProgress(20); g_Map.log("Adding smoke..."); createObjectGroupsByAreas( new SimpleGroup( [ new SimpleObject(g_Decoratives.smoke1, 1, 1, 0, 4), new SimpleObject(g_Decoratives.smoke2, 2, 2, 0, 4) ], false), 0, stayClasses(g_TileClasses.lava, 0), scaleByMapSize(4, 12), 20, [areaVesuv]); g_Map.log("Creating docks"); for (let i = 0; i < scaleByMapSize(2, 4); ++i) { let positionLand = pickRandom(areaDockStart.points); // Find closest point on the shoreline and face the resulting direction let dockPosition = areaShoreline.points[0]; let shortestDistance = Infinity; for (let positionShoreline of areaShoreline.points) { let currentDistance = positionLand.distanceToSquared(positionShoreline); if (currentDistance < shortestDistance) { shortestDistance = currentDistance; dockPosition = positionShoreline; } } if (!avoidClasses(g_TileClasses.mountain, scaleByMapSize(4, 6), g_TileClasses.dock, 10).allows(dockPosition)) { --i; continue; } g_Map.placeEntityPassable(randBool(0.4) ? g_Gaia.dock : g_Gaia.dockRubble, 0, dockPosition, -positionLand.angleTo(dockPosition) + Math.PI / 2); g_TileClasses.dock.add(dockPosition); } Engine.SetProgress(10); if (!isNomad()) { g_Map.log("Placing players"); - let playerBases = placeRandom( - sortAllPlayers(), - [ - avoidClasses(g_TileClasses.mountain, 5), - stayClasses(g_TileClasses.land, scaleByMapSize(5, 15)) - ]); + let [playerIDs, playerPosition] = createBases( + ...playerPlacementRandom( + sortAllPlayers(), + [ + avoidClasses(g_TileClasses.mountain, 5), + stayClasses(g_TileClasses.land, scaleByMapSize(5, 15)) + ]), + false); g_Map.log("Flatten the initial CC area..."); - for (let player of playerBases) + for (let position of playerPosition) createArea( - new ClumpPlacer(diskArea(defaultPlayerBaseRadius() * 0.8), 0.95, 0.6, Infinity, player.position), - new SmoothElevationPainter(ELEVATION_SET, g_Map.getHeight(player.position), 6)); + new ClumpPlacer(diskArea(defaultPlayerBaseRadius() * 0.8), 0.95, 0.6, Infinity, position), + new SmoothElevationPainter(ELEVATION_SET, g_Map.getHeight(position), 6)); } Engine.SetProgress(50); addElements([ { "func": addLayeredPatches, "avoid": [ g_TileClasses.dirt, 5, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12, g_TileClasses.lava, 2, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] }, { "func": addDecoration, "avoid": [ g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12, g_TileClasses.lava, 2, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] } ]); Engine.SetProgress(50); addElements(shuffleArray([ { "func": addMetal, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.player, 30, g_TileClasses.rock, 10, g_TileClasses.metal, 20, g_TileClasses.lava, 5, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["few"] }, { "func": addStone, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.player, 30, g_TileClasses.rock, 20, g_TileClasses.metal, 10, g_TileClasses.lava, 5, g_TileClasses.water, 5 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["few"] }, { "func": addForests, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 18, g_TileClasses.metal, 3, g_TileClasses.mountain, 5, g_TileClasses.player, 20, g_TileClasses.rock, 3, g_TileClasses.water, 2 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["many"] } ])); Engine.SetProgress(60); addElements(shuffleArray([ { "func": addAnimals, "avoid": [ g_TileClasses.animals, 20, g_TileClasses.forest, 2, g_TileClasses.metal, 2, g_TileClasses.mountain, 1, g_TileClasses.player, 20, g_TileClasses.rock, 2, g_TileClasses.lava, 10, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["few"] }, { "func": addFish, "avoid": [ g_TileClasses.fish, 12, g_TileClasses.player, 8 ], "stay": [g_TileClasses.water, 4], "sizes": ["normal"], "mixes": ["same"], "amounts": ["few"] }, { "func": addStragglerTrees, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 7, g_TileClasses.metal, 2, g_TileClasses.mountain, 1, g_TileClasses.player, 12, g_TileClasses.rock, 2, g_TileClasses.lava, 5, g_TileClasses.water, 5 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["tons"] } ])); Engine.SetProgress(65); g_Map.log("Adding gatherable stone statues..."); createObjectGroups( new SimpleGroup( [new SimpleObject(g_Gaia.romanStatue, 1, 1, 1, 4)], true, g_TileClasses.metal ), 0, avoidClasses( g_TileClasses.water, 2, g_TileClasses.player, 20, g_TileClasses.mountain, 3, g_TileClasses.forest, 2, g_TileClasses.lava, 5, g_TileClasses.metal, 20 ), 5 * scaleByMapSize(1, 4), 50); Engine.SetProgress(75); g_Map.log("Adding stone ruins..."); createObjectGroups( new SimpleGroup( [ new SimpleObject(g_Gaia.unfinishedTemple, 0, 1, 1, 4), new SimpleObject(g_Gaia.columnsDoric, 1, 1, 1, 4) ], true, g_TileClasses.decorative ), 0, avoidClasses( g_TileClasses.water, 2, g_TileClasses.player, 20, g_TileClasses.mountain, 5, g_TileClasses.forest, 2, g_TileClasses.lava, 5, g_TileClasses.decorative, 20 ), scaleByMapSize(1, 4), 20); Engine.SetProgress(80); g_Map.log("Adding shipwrecks..."); createObjectGroups( new SimpleGroup(g_Decoratives.shipwrecks.map(shipwreck => new SimpleObject(shipwreck, 0, 1, 1, 20)), true, g_TileClasses.decorative), 0, [ avoidClasses(g_TileClasses.decorative, 20), stayClasses(g_TileClasses.water, 0) ], scaleByMapSize(1, 5), 20); Engine.SetProgress(85); g_Map.log("Adding more statues..."); createObjectGroups( new SimpleGroup(g_Decoratives.statues.map(ruin => new SimpleObject(ruin, 0, 1, 1, 20)), true, g_TileClasses.decorative), 0, avoidClasses( g_TileClasses.water, 2, g_TileClasses.player, 20, g_TileClasses.mountain, 2, g_TileClasses.forest, 2, g_TileClasses.lava, 5, g_TileClasses.decorative, 20 ), scaleByMapSize(3, 15), 30); Engine.SetProgress(90); g_Map.log("Adding skeletons..."); createObjectGroups( new SimpleGroup( [new SimpleObject(g_Decoratives.skeleton, 3, 10, 1, 7)], true, g_TileClasses.dirt ), 0, avoidClasses( g_TileClasses.water, 2, g_TileClasses.player, 10, g_TileClasses.mountain, 2, g_TileClasses.forest, 2, g_TileClasses.decorative, 2 ), scaleByMapSize(1, 5), 50); Engine.SetProgress(95); placePlayersNomad( g_Map.createTileClass(), [ stayClasses(g_TileClasses.land, 5), avoidClasses( g_TileClasses.forest, 1, g_TileClasses.rock, 4, g_TileClasses.metal, 4, g_TileClasses.animals, 2, g_TileClasses.mountain, 2) ]); setWaterTint(0.5, 0.5, 0.5); setWaterColor(0.3, 0.3, 0.3); setWaterWaviness(8); setWaterMurkiness(0.87); setWaterType("lake"); setTerrainAmbientColor(0.3, 0.3, 0.3); setUnitsAmbientColor(0.3, 0.3, 0.3); setSunColor(0.8, 0.8, 0.8); setSunRotation(Math.PI); setSunElevation(1/2); setFogFactor(0); setFogThickness(0); setFogColor(0.69, 0.616, 0.541); setSkySet("stormy"); setPPEffect("hdr"); setPPContrast(0.67); setPPSaturation(0.42); setPPBloom(0.23); g_Map.ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/red_sea.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/red_sea.js (revision 21205) +++ ps/trunk/binaries/data/mods/public/maps/random/red_sea.js (revision 21206) @@ -1,385 +1,387 @@ /** * Heightmap image source: * Imagery by Jesse Allen, NASA's Earth Observatory, * using data from the General Bathymetric Chart of the Oceans (GEBCO) * produced by the British Oceanographic Data Centre. * https://visibleearth.nasa.gov/view.php?id=73934 * * Licensing: Public Domain, https://visibleearth.nasa.gov/useterms.php * * The heightmap image is reproduced using: * wget https://eoimages.gsfc.nasa.gov/images/imagerecords/73000/73934/gebco_08_rev_elev_C1_grey_geo.tif * lat=22; lon=41; width=30; * gdal_translate -projwin $((lon-width/2)) $((lat+width/2)) $((lon+width/2)) $((lat-width/2)) gebco_08_rev_elev_C1_grey_geo.tif red_sea.tif * convert red_sea.tif -resize 512 -contrast-stretch 0 red_sea.png * No further changes should be applied to the image to keep it easily interchangeable. */ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen2"); Engine.LoadLibrary("rmbiome"); TILE_CENTERED_HEIGHT_MAP = true; setBiome("generic/desert"); g_Terrains.mainTerrain = new Array(4).fill("desert_sand_dunes_50").concat(["desert_sand_dunes_rocks", "desert_dirt_rough_2"]); g_Terrains.forestFloor1 = "desert_grass_a_sand"; g_Terrains.cliff = "desert_cliff_3_dirty"; g_Terrains.forestFloor2 = "desert_grass_a_sand"; g_Terrains.tier1Terrain = "desert_dirt_rocks_2"; g_Terrains.tier2Terrain = "desert_dirt_rough"; g_Terrains.tier3Terrain = "desert_dirt_rough"; g_Terrains.tier4Terrain = "desert_sand_stones"; g_Terrains.roadWild = "road2"; g_Terrains.road = "road2"; g_Terrains.additionalDirt1 = "desert_plants_b"; g_Terrains.additionalDirt2 = "desert_sand_scrub"; g_Gaia.tree1 = "gaia/flora_tree_date_palm"; g_Gaia.tree2 = "gaia/flora_tree_senegal_date_palm"; g_Gaia.tree3 = "gaia/flora_tree_fig"; g_Gaia.tree4 = "gaia/flora_tree_cretan_date_palm_tall"; g_Gaia.tree5 = "gaia/flora_tree_cretan_date_palm_short"; g_Gaia.fruitBush = "gaia/flora_bush_grapes"; g_Decoratives.grass = "actor|props/flora/grass_field_dry_tall_b.xml"; g_Decoratives.grassShort = "actor|props/flora/grass_field_parched_short.xml"; g_Decoratives.rockLarge = "actor|geology/stone_desert_med.xml"; g_Decoratives.rockMedium = "actor|geology/stone_savanna_med.xml"; g_Decoratives.bushMedium = "actor|props/flora/bush_desert_dry_a.xml"; g_Decoratives.bushSmall = "actor|props/flora/bush_medit_sm_dry.xml"; g_Decoratives.dust = "actor|particle/dust_storm_reddish.xml"; const heightScale = num => num * g_MapSettings.Size / 320; const heightSeaGround = heightScale(-4); const heightReedsMin = heightScale(-2); const heightReedsMax = heightScale(-0.5); const heightWaterLevel = heightScale(0); const heightShoreline = heightScale(0.5); const heightHills = heightScale(16); var g_Map = new RandomMap(0, g_Terrains.mainTerrain); var mapCenter = g_Map.getCenter(); initTileClasses(["shoreline"]); g_Map.LoadHeightmapImage("red_sea.png", 0, 25); Engine.SetProgress(15); g_Map.log("Lowering sea ground"); createArea( new MapBoundsPlacer(), new SmoothElevationPainter(ELEVATION_SET, heightSeaGround, 2), new HeightConstraint(-Infinity, heightWaterLevel)); Engine.SetProgress(20); g_Map.log("Smoothing heightmap"); createArea( new MapBoundsPlacer(), new SmoothingPainter(1, scaleByMapSize(0.1, 0.5), 1)); Engine.SetProgress(25); g_Map.log("Marking water"); createArea( new MapBoundsPlacer(), new TileClassPainter(g_TileClasses.water), new HeightConstraint(-Infinity, heightWaterLevel)); Engine.SetProgress(30); g_Map.log("Marking land"); createArea( new ClumpPlacer(diskArea(fractionToTiles(0.5)), 1, 1, Infinity, mapCenter), new TileClassPainter(g_TileClasses.land), avoidClasses(g_TileClasses.water, 0)); Engine.SetProgress(35); g_Map.log("Painting shoreline"); createArea( new MapBoundsPlacer(), [ new TerrainPainter(g_Terrains.water), new TileClassPainter(g_TileClasses.shoreline) ], new HeightConstraint(-Infinity, heightShoreline)); Engine.SetProgress(40); g_Map.log("Painting cliffs"); createArea( new MapBoundsPlacer(), [ new TerrainPainter(g_Terrains.cliff), new TileClassPainter(g_TileClasses.mountain), ], [ avoidClasses(g_TileClasses.water, 2), new SlopeConstraint(2, Infinity) ]); Engine.SetProgress(45); if (!isNomad()) { g_Map.log("Placing players"); - let playerBases = placeRandom( - sortAllPlayers(), - [ - avoidClasses(g_TileClasses.mountain, scaleByMapSize(5, 10)), - stayClasses(g_TileClasses.land, defaultPlayerBaseRadius()) - ]); + let [playerIDs, playerPosition] = createBases( + ...playerPlacementRandom( + sortAllPlayers(), + [ + avoidClasses(g_TileClasses.mountain, scaleByMapSize(5, 10)), + stayClasses(g_TileClasses.land, defaultPlayerBaseRadius()) + ]), + true); g_Map.log("Flatten the initial CC area..."); - for (let player of playerBases) + for (let position of playerPosition) createArea( - new ClumpPlacer(diskArea(defaultPlayerBaseRadius() * 0.8), 0.95, 0.6, Infinity, player.position), - new SmoothElevationPainter(ELEVATION_SET, g_Map.getHeight(player.position), 6)); + new ClumpPlacer(diskArea(defaultPlayerBaseRadius() * 0.8), 0.95, 0.6, Infinity, position), + new SmoothElevationPainter(ELEVATION_SET, g_Map.getHeight(position), 6)); } addElements(shuffleArray([ { "func": addMetal, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.player, 30, g_TileClasses.rock, 10, g_TileClasses.metal, 20, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["normal"] }, { "func": addStone, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.player, 30, g_TileClasses.rock, 20, g_TileClasses.metal, 10, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["normal"] }, { "func": addForests, "avoid": [ g_TileClasses.berries, 3, g_TileClasses.forest, 20, g_TileClasses.metal, 4, g_TileClasses.mountain, 3, g_TileClasses.player, 20, g_TileClasses.rock, 4, g_TileClasses.water, 2 ], "sizes": ["big"], "mixes": ["similar"], "amounts": ["few"] } ])); Engine.SetProgress(60); // Ensure initial forests addElements([{ "func": addForests, "avoid": [ g_TileClasses.berries, 2, g_TileClasses.forest, 25, g_TileClasses.metal, 4, g_TileClasses.mountain, 5, g_TileClasses.player, 15, g_TileClasses.rock, 4, g_TileClasses.water, 2 ], "sizes": ["small"], "mixes": ["similar"], "amounts": ["tons"] }]); Engine.SetProgress(65); addElements(shuffleArray([ { "func": addBerries, "avoid": [ g_TileClasses.berries, 30, g_TileClasses.forest, 5, g_TileClasses.metal, 10, g_TileClasses.mountain, 2, g_TileClasses.player, 20, g_TileClasses.rock, 10, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["normal", "many"] }, { "func": addAnimals, "avoid": [ g_TileClasses.animals, 20, g_TileClasses.forest, 2, g_TileClasses.metal, 2, g_TileClasses.mountain, 1, g_TileClasses.player, 20, g_TileClasses.rock, 4, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["many"] }, { "func": addFish, "avoid": [ g_TileClasses.fish, 12, g_TileClasses.player, 8 ], "stay": [g_TileClasses.water, 4], "sizes": ["normal"], "mixes": ["same"], "amounts": ["many"] }, { "func": addStragglerTrees, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 15, g_TileClasses.metal, 2, g_TileClasses.mountain, 1, g_TileClasses.player, 20, g_TileClasses.rock, 4, g_TileClasses.water, 5 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["many"] } ])); Engine.SetProgress(70); addElements([ { "func": addLayeredPatches, "avoid": [ g_TileClasses.dirt, 5, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12, g_TileClasses.water, 3, g_TileClasses.shoreline, 2 ], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["tons"] }, { "func": addDecoration, "avoid": [ g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["similar"], "amounts": ["many"] } ]); Engine.SetProgress(80); g_Map.log("Painting dirt patches"); var dirtPatches = [ { "sizes": [2, 4], "count": scaleByMapSize(2, 5), "terrain": g_Terrains.additionalDirt1 }, { "sizes": [4, 6, 8], "count": scaleByMapSize(4, 8), "terrain": g_Terrains.additionalDirt2 } ]; for (let dirtPatch of dirtPatches) createPatches( dirtPatch.sizes, dirtPatch.terrain, [ stayClasses(g_TileClasses.land, 6), avoidClasses( g_TileClasses.mountain, 4, g_TileClasses.forest, 2, g_TileClasses.shoreline, 2, g_TileClasses.player, 12) ], dirtPatch.count, g_TileClasses.dirt, 0.5); Engine.SetProgress(85); g_Map.log("Adding reeds"); createObjectGroups( new SimpleGroup( [ new SimpleObject(g_Decoratives.reeds, 5, 12, 1, 4), new SimpleObject(g_Decoratives.rockMedium, 1, 2, 1, 5) ], false, g_TileClasses.dirt), 0, new HeightConstraint(heightReedsMin, heightReedsMax), scaleByMapSize(10, 25), 5); Engine.SetProgress(90); g_Map.log("Adding dust..."); createObjectGroups( new SimpleGroup([new SimpleObject(g_Decoratives.dust, 1, 1, 1, 4)], false), 0, [ stayClasses(g_TileClasses.land, 5), avoidClasses(g_TileClasses.player, 10) ], scaleByMapSize(10, 50), 20); Engine.SetProgress(95); placePlayersNomad( g_Map.createTileClass(), [ stayClasses(g_TileClasses.land, 5), avoidClasses( g_TileClasses.forest, 2, g_TileClasses.rock, 4, g_TileClasses.metal, 4, g_TileClasses.berries, 2, g_TileClasses.animals, 2, g_TileClasses.mountain, 2) ]); setWindAngle(-0.43); setWaterTint(0.161, 0.286, 0.353); setWaterColor(0.129, 0.176, 0.259); setWaterWaviness(8); setWaterMurkiness(0.87); setWaterType("lake"); setTerrainAmbientColor(0.58, 0.443, 0.353); setSunColor(0.733, 0.746, 0.574); setSunRotation(Math.PI * 1.1); setSunElevation(Math.PI / 7); setFogFactor(0); setFogThickness(0); setFogColor(0.69, 0.616, 0.541); setPPEffect("hdr"); setPPContrast(0.67); setPPSaturation(0.42); setPPBloom(0.23); g_Map.ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/player.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen/player.js (revision 21205) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/player.js (revision 21206) @@ -1,692 +1,788 @@ /** * @file These functions locate and place the starting entities of players. */ var g_NomadTreasureTemplates = { "food": "gaia/treasure/food_jars", "wood": "gaia/treasure/wood", "stone": "gaia/treasure/stone", "metal": "gaia/treasure/metal" }; /** * These are identifiers of functions that can generate parts of a player base. * There must be a function starting with placePlayerBase and ending with this name. * This is a global so mods can extend this from external files. */ var g_PlayerBaseFunctions = [ // Possibly mark player class first here and use it afterwards "CityPatch", // Create the largest and most important entities first "Trees", "Mines", "Treasures", "Berries", "Chicken", "Decoratives" ]; 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; } /** * Gets the default starting entities for the civ of the given player, as defined by the civ file. */ function getStartingEntities(playerID) { return g_CivData[getCivCode(playerID)].StartEntities; } /** * Places the given entities at the given location (typically a civic center and starting units). * @param location - A Vector2D specifying tile coordinates. * @param civEntities - An array of objects with the Template property and optionally a Count property. * The first entity is placed in the center, the other ones surround it. */ function placeStartingEntities(location, playerID, civEntities, dist = 6, orientation = BUILDING_ORIENTATION) { // Place the central structure let i = 0; let firstTemplate = civEntities[i].Template; if (firstTemplate.startsWith("structures/")) { g_Map.placeEntityPassable(firstTemplate, playerID, location, orientation); ++i; } // Place entities surrounding it let space = 2; for (let j = i; j < civEntities.length; ++j) { let angle = orientation - Math.PI * (1 - j / 2); let count = civEntities[j].Count || 1; for (let num = 0; num < count; ++num) { let position = Vector2D.sum([ location, new Vector2D(dist, 0).rotate(-angle), new Vector2D(space * (-num + 0.75 * Math.floor(count / 2)), 0).rotate(angle) ]); g_Map.placeEntityPassable(civEntities[j].Template, playerID, position, angle); } } } /** * Places the default starting entities as defined by the civilization definition, optionally including city walls. */ function placeCivDefaultStartingEntities(position, playerID, wallType, dist = 6, orientation = BUILDING_ORIENTATION) { placeStartingEntities(position, playerID, getStartingEntities(playerID), dist, orientation); placeStartingWalls(position, playerID, wallType, orientation); } /** * If the map is large enough and the civilization defines them, places the initial city walls or towers. * @param {string|boolean} wallType - Either "towers" to only place the wall turrets or a boolean indicating enclosing city walls. */ function placeStartingWalls(position, playerID, wallType, orientation = BUILDING_ORIENTATION) { let civ = getCivCode(playerID); if (civ != "iber" || g_Map.getSize() <= 128) return; if (wallType == "towers") placePolygonalWall(position, 15, ["entry"], "tower", civ, playerID, orientation, 7); else if (wallType) placeGenericFortress(position, 20, playerID); } /** * Places the civic center and starting resources for all given players. */ function placePlayerBases(playerBaseArgs) { g_Map.log("Creating playerbases"); let [playerIDs, playerPosition] = playerBaseArgs.PlayerPlacement; for (let i = 0; i < getNumPlayers(); ++i) { playerBaseArgs.playerID = playerIDs[i]; playerBaseArgs.playerPosition = playerPosition[i]; placePlayerBase(playerBaseArgs); } } /** * Places the civic center and starting resources. */ function placePlayerBase(playerBaseArgs) { if (isNomad()) return; placeCivDefaultStartingEntities(playerBaseArgs.playerPosition, playerBaseArgs.playerID, playerBaseArgs.Walls !== undefined ? playerBaseArgs.Walls : true); if (playerBaseArgs.PlayerTileClass) addCivicCenterAreaToClass(playerBaseArgs.playerPosition, playerBaseArgs.PlayerTileClass); for (let functionID of g_PlayerBaseFunctions) { let funcName = "placePlayerBase" + functionID; let func = global[funcName]; if (!func) throw new Error("Could not find " + funcName); if (!playerBaseArgs[functionID]) continue; let args = playerBaseArgs[functionID]; // Copy some global arguments to the arguments for each function for (let prop of ["playerID", "playerPosition", "BaseResourceClass", "baseResourceConstraint"]) args[prop] = playerBaseArgs[prop]; func(args); } } function defaultPlayerBaseRadius() { return scaleByMapSize(15, 25); } /** * Marks the corner and center tiles of an area that is about the size of a Civic Center with the given TileClass. * Used to prevent resource collisions with the Civic Center. */ function addCivicCenterAreaToClass(position, tileClass) { createArea( new ClumpPlacer(diskArea(5), 1, 1, Infinity, position), new TileClassPainter(tileClass)); } /** * Helper function. */ function getPlayerBaseArgs(playerBaseArgs) { let baseResourceConstraint = playerBaseArgs.BaseResourceClass && avoidClasses(playerBaseArgs.BaseResourceClass, 4); if (playerBaseArgs.baseResourceConstraint) baseResourceConstraint = new AndConstraint([baseResourceConstraint, playerBaseArgs.baseResourceConstraint]); return [ (property, defaultVal) => playerBaseArgs[property] === undefined ? defaultVal : playerBaseArgs[property], playerBaseArgs.playerPosition, baseResourceConstraint ]; } function placePlayerBaseCityPatch(args) { let [get, basePosition, baseResourceConstraint] = getPlayerBaseArgs(args); let painters = []; if (args.outerTerrain && args.innerTerrain) painters.push(new LayeredPainter([args.outerTerrain, args.innerTerrain], [get("width", 1)])); if (args.painters) painters = painters.concat(args.painters); createArea( new ClumpPlacer( Math.floor(diskArea(get("radius", defaultPlayerBaseRadius() / 3))), get("coherence", 0.6), get("smoothness", 0.3), get("failFraction", Infinity), basePosition), painters); } function placePlayerBaseChicken(args) { let [get, basePosition, baseResourceConstraint] = getPlayerBaseArgs(args); for (let i = 0; i < get("groupCount", 2); ++i) { let success = false; for (let tries = 0; tries < get("maxTries", 30); ++tries) { let position = new Vector2D(0, get("distance", 9)).rotate(randomAngle()).add(basePosition); if (createObjectGroup( new SimpleGroup( [ new SimpleObject( get("template", "gaia/fauna_chicken"), get("minGroupCount", 5), get("maxGroupCount", 5), get("minGroupDistance", 0), get("maxGroupDistance", 2)) ], true, args.BaseResourceClass, position), 0, baseResourceConstraint)) { success = true; break; } } if (!success) { error("Could not place chicken for player " + args.playerID); return; } } } function placePlayerBaseBerries(args) { let [get, basePosition, baseResourceConstraint] = getPlayerBaseArgs(args); for (let tries = 0; tries < get("maxTries", 30); ++tries) { let position = new Vector2D(0, get("distance", 12)).rotate(randomAngle()).add(basePosition); if (createObjectGroup( new SimpleGroup( [new SimpleObject(args.template, get("minCount", 5), get("maxCount", 5), get("maxDist", 1), get("maxDist", 3))], true, args.BaseResourceClass, position), 0, baseResourceConstraint)) return; } error("Could not place berries for player " + args.playerID); } function placePlayerBaseMines(args) { let [get, basePosition, baseResourceConstraint] = getPlayerBaseArgs(args); let angleBetweenMines = randFloat(get("minAngle", Math.PI / 6), get("maxAngle", Math.PI / 3)); let mineCount = args.types.length; let groupElements = []; if (args.groupElements) groupElements = groupElements.concat(args.groupElements); for (let tries = 0; tries < get("maxTries", 75); ++tries) { // First find a place where all mines can be placed let pos = []; let startAngle = randomAngle(); for (let i = 0; i < mineCount; ++i) { let angle = startAngle + angleBetweenMines * (i + (mineCount - 1) / 2); pos[i] = new Vector2D(0, get("distance", 12)).rotate(angle).add(basePosition).round(); if (!g_Map.validTilePassable(pos[i]) || !baseResourceConstraint.allows(pos[i])) { pos = undefined; break; } } if (!pos) continue; // Place the mines for (let i = 0; i < mineCount; ++i) { if (args.types[i].type && args.types[i].type == "stone_formation") { createStoneMineFormation(pos[i], args.types[i].template, args.types[i].terrain); args.BaseResourceClass.add(pos[i]); continue; } createObjectGroup( new SimpleGroup( [new SimpleObject(args.types[i].template, 1, 1, 0, 0)].concat(groupElements), true, args.BaseResourceClass, pos[i]), 0); } return; } error("Could not place mines for player " + args.playerID); } function placePlayerBaseTrees(args) { let [get, basePosition, baseResourceConstraint] = getPlayerBaseArgs(args); let num = Math.floor(get("count", scaleByMapSize(7, 20))); for (let x = 0; x < get("maxTries", 30); ++x) { let position = new Vector2D(0, randFloat(get("minDist", 11), get("maxDist", 13))).rotate(randomAngle()).add(basePosition).round(); if (createObjectGroup( new SimpleGroup( [new SimpleObject(args.template, num, num, get("minDistGroup", 0), get("maxDistGroup", 5))], false, args.BaseResourceClass, position), 0, baseResourceConstraint)) return; } error("Could not place starting trees for player " + args.playerID); } function placePlayerBaseTreasures(args) { let [get, basePosition, baseResourceConstraint] = getPlayerBaseArgs(args); for (let resourceTypeArgs of args.types) { get = (property, defaultVal) => resourceTypeArgs[property] === undefined ? defaultVal : resourceTypeArgs[property]; let success = false; for (let tries = 0; tries < get("maxTries", 30); ++tries) { let position = new Vector2D(0, randFloat(get("minDist", 11), get("maxDist", 13))).rotate(randomAngle()).add(basePosition).round(); if (createObjectGroup( new SimpleGroup( [new SimpleObject(resourceTypeArgs.template, get("count", 14), get("count", 14), get("minDistGroup", 1), get("maxDistGroup", 3))], false, args.BaseResourceClass, position), 0, baseResourceConstraint)) { success = true; break; } } if (!success) { error("Could not place treasure " + resourceTypeArgs.template + " for player " + args.playerID); return; } } } /** * Typically used for placing grass tufts around the civic centers. */ function placePlayerBaseDecoratives(args) { let [get, basePosition, baseResourceConstraint] = getPlayerBaseArgs(args); for (let i = 0; i < get("count", scaleByMapSize(2, 5)); ++i) { let success = false; for (let x = 0; x < get("maxTries", 30); ++x) { let position = new Vector2D(0, randIntInclusive(get("minDist", 8), get("maxDist", 11))).rotate(randomAngle()).add(basePosition).round(); if (createObjectGroup( new SimpleGroup( [new SimpleObject(args.template, get("minCount", 2), get("maxCount", 5), 0, 1)], false, args.BaseResourceClass, position), 0, baseResourceConstraint)) { success = true; break; } } if (!success) // Don't warn since the decoratives are not important return; } } function placePlayersNomad(playerClass, constraints) { if (!isNomad()) return undefined; g_Map.log("Placing nomad starting units"); let distance = scaleByMapSize(60, 240); let constraint = new AndConstraint(constraints); let numPlayers = getNumPlayers(); let playerIDs = shuffleArray(sortAllPlayers()); let playerPosition = []; for (let i = 0; i < numPlayers; ++i) { let objects = getStartingEntities(playerIDs[i]).filter(ents => ents.Template.startsWith("units/")).map( ents => new SimpleObject(ents.Template, ents.Count || 1, ents.Count || 1, 1, 3)); // Add treasure if too few resources for a civic center let ccCost = Engine.GetTemplate("structures/" + getCivCode(playerIDs[i]) + "_civil_centre").Cost.Resources; for (let resourceType in ccCost) { let treasureTemplate = g_NomadTreasureTemplates[resourceType]; let count = Math.max(0, Math.ceil( (ccCost[resourceType] - (g_MapSettings.StartingResources || 0)) / Engine.GetTemplate(treasureTemplate).ResourceSupply.Amount)); objects.push(new SimpleObject(treasureTemplate, count, count, 3, 5)); } // Try place these entities at a random location let group = new SimpleGroup(objects, true, playerClass); let success = false; for (let distanceFactor of [1, 1/2, 1/4, 0]) { if (createObjectGroups(group, playerIDs[i], new AndConstraint([constraint, avoidClasses(playerClass, distance * distanceFactor)]), 1, 200, false)) { success = true; playerPosition[i] = group.centerPosition; break; } } if (!success) throw new Error("Could not place starting units for player " + playerIDs[i] + "!"); } return [playerIDs, playerPosition]; } /** * Sorts an array of player IDs by team index. Players without teams come first. * Randomize order for players of the same team. */ function sortPlayers(playerIDs) { return shuffleArray(playerIDs).sort((playerID1, playerID2) => getPlayerTeam(playerID1) - getPlayerTeam(playerID2)); } /** * Randomize playerIDs but sort by team. * * @returns {Array} - every item is an array of player indices */ function sortAllPlayers() { let playerIDs = []; for (let i = 0; i < getNumPlayers(); ++i) playerIDs.push(i+1); return sortPlayers(playerIDs); } /** * Rearrange order so that teams of neighboring players alternate (if the given IDs are sorted by team). */ function primeSortPlayers(playerIDs) { if (!playerIDs.length) return []; let prime = []; for (let i = 0; i < Math.ceil(playerIDs.length / 2); ++i) { prime.push(playerIDs[i]); prime.push(playerIDs[playerIDs.length - 1 - i]); } return prime; } function primeSortAllPlayers() { return primeSortPlayers(sortAllPlayers()); } /** * Determine player starting positions on a circular pattern. */ function playerPlacementCircle(radius, startingAngle = undefined, center = undefined) { let startAngle = startingAngle !== undefined ? startingAngle : randomAngle(); let [playerPosition, playerAngle] = distributePointsOnCircle(getNumPlayers(), startAngle, radius, center || g_Map.getCenter()); return [sortAllPlayers(), playerPosition.map(p => p.round()), playerAngle, startAngle]; } /** * Determine player starting positions on a circular pattern, with a custom angle for each player. * Commonly used for gulf terrains. */ function playerPlacementCustomAngle(radius, center, playerAngleFunc) { let playerPosition = []; let playerAngle = []; let numPlayers = getNumPlayers(); for (let i = 0; i < numPlayers; ++i) { playerAngle[i] = playerAngleFunc(i); playerPosition[i] = Vector2D.add(center, new Vector2D(radius, 0).rotate(-playerAngle[i])).round(); } return [playerPosition, playerAngle]; } /** * Returns player starting positions located on two parallel lines, typically used by central river maps. * If there are two teams with an equal number of players, each team will occupy exactly one line. * Angle 0 means the players are placed in north to south direction, i.e. along the Z axis. */ function playerPlacementRiver(angle, width, center = undefined) { let numPlayers = getNumPlayers(); let numPlayersEven = numPlayers % 2 == 0; let mapSize = g_Map.getSize(); let centerPosition = center || g_Map.getCenter(); let playerPosition = []; for (let i = 0; i < numPlayers; ++i) { let currentPlayerEven = i % 2 == 0; let offsetDivident = numPlayersEven || currentPlayerEven ? (i + 1) % 2 : 0; let offsetDivisor = numPlayersEven ? 0 : currentPlayerEven ? +1 : -1; playerPosition[i] = new Vector2D( width * (i % 2) + (mapSize - width) / 2, fractionToTiles(((i - 1 + offsetDivident) / 2 + 1) / ((numPlayers + offsetDivisor) / 2 + 1)) ).rotateAround(angle, centerPosition).round(); } return [primeSortAllPlayers(), playerPosition]; } /*** * Returns starting positions located on two parallel lines. * The locations on the first line are shifted in comparison to the other line. */ function playerPlacementLine(angle, center, width) { let playerPosition = []; let numPlayers = getNumPlayers(); for (let i = 0; i < numPlayers; ++i) playerPosition[i] = Vector2D.add( center, new Vector2D( fractionToTiles((i + 1) / (numPlayers + 1) - 0.5), width * (i % 2 - 1/2) ).rotate(angle) ).round(); return playerPosition; } /** - * Sorts the playerIDs so that team members are as close as possible. + * Returns a random location for each player that meets the given constraints and + * orders the playerIDs so that players become grouped by team. */ -function sortPlayersByLocation(startLocations) +function playerPlacementRandom(playerIDs, constraints = undefined) +{ + let locations = []; + let attempts = 0; + let resets = 0; + + let mapCenter = g_Map.getCenter(); + let playerMinDist = fractionToTiles(0.25); + let borderDistance = fractionToTiles(0.08); + + let area = createArea(new MapBoundsPlacer(), undefined, new AndConstraint(constraints)); + + for (let i = 0; i < getNumPlayers(); ++i) + { + let position = pickRandom(area.points); + + // Minimum distance between initial bases must be a quarter of the map diameter + if (locations.some(loc => loc.distanceTo(position) < playerMinDist) || + position.distanceTo(mapCenter) > mapCenter.x - borderDistance) + { + --i; + ++attempts; + + // Reset if we're in what looks like an infinite loop + if (attempts > 500) + { + locations = []; + i = -1; + attempts = 0; + ++resets; + + // Reduce minimum player distance progressively + if (resets % 25 == 0) + playerMinDist *= 0.975; + + // If we only pick bad locations, stop trying to place randomly + if (resets == 500) + return undefined; + } + continue; + } + + locations[i] = position; + } + return groupPlayersByArea(playerIDs, locations); +} + +/** + * Pick locations from the given set so that teams end up grouped. + */ +function groupPlayersByArea(playerIDs, locations) +{ + playerIDs = sortPlayers(playerIDs); + + let minDist = Infinity; + let minLocations; + + // Of all permutations of starting locations, find the one where + // the sum of the distances between allies is minimal, weighted by teamsize. + heapsPermute(shuffleArray(locations).slice(0, playerIDs.length), v => v.clone(), permutation => + { + let dist = 0; + let teamDist = 0; + let teamSize = 0; + + for (let i = 1; i < playerIDs.length; ++i) + { + let team1 = getPlayerTeam(playerIDs[i - 1]); + let team2 = getPlayerTeam(playerIDs[i]); + ++teamSize; + if (team1 != -1 && team1 == team2) + teamDist += permutation[i - 1].distanceTo(permutation[i]); + else + { + dist += teamDist / teamSize; + teamDist = 0; + teamSize = 0; + } + } + + if (teamSize) + dist += teamDist / teamSize; + + if (dist < minDist) + { + minDist = dist; + minLocations = permutation; + } + }); + + return [playerIDs, minLocations]; +} + +/** + * Sorts the playerIDs so that team members are as close as possible on a ring. + */ +function groupPlayersCycle(startLocations) { - // Sort start locations to form a "ring" let startLocationOrder = sortPointsShortestCycle(startLocations); let newStartLocations = []; for (let i = 0; i < startLocations.length; ++i) newStartLocations.push(startLocations[startLocationOrder[i]]); startLocations = newStartLocations; // Sort players by team let playerIDs = []; let teams = []; for (let i = 0; i < g_MapSettings.PlayerData.length - 1; ++i) { playerIDs.push(i+1); let t = g_MapSettings.PlayerData[i + 1].Team; if (teams.indexOf(t) == -1 && t !== undefined) teams.push(t); } playerIDs = sortPlayers(playerIDs); if (!teams.length) return [playerIDs, startLocations]; // Minimize maximum distance between players within a team let minDistance = Infinity; let bestShift; for (let s = 0; s < playerIDs.length; ++s) { let maxTeamDist = 0; for (let pi = 0; pi < playerIDs.length - 1; ++pi) { let t1 = getPlayerTeam(playerIDs[(pi + s) % playerIDs.length]); if (teams.indexOf(t1) === -1) continue; for (let pj = pi + 1; pj < playerIDs.length; ++pj) { if (t1 != getPlayerTeam(playerIDs[(pj + s) % playerIDs.length])) continue; maxTeamDist = Math.max( maxTeamDist, Math.euclidDistance2D( startLocations[pi].x, startLocations[pi].y, startLocations[pj].x, startLocations[pj].y)); } } if (maxTeamDist < minDistance) { minDistance = maxTeamDist; bestShift = s; } } if (bestShift) { let newPlayerIDs = []; for (let i = 0; i < playerIDs.length; ++i) newPlayerIDs.push(playerIDs[(i + bestShift) % playerIDs.length]); playerIDs = newPlayerIDs; } return [playerIDs, startLocations]; } Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen2/setup.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen2/setup.js (revision 21205) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen2/setup.js (revision 21206) @@ -1,496 +1,342 @@ var g_Amounts = { "scarce": 0.2, "few": 0.5, "normal": 1, "many": 1.75, "tons": 3 }; var g_Mixes = { "same": 0, "similar": 0.1, "normal": 0.25, "varied": 0.5, "unique": 0.75 }; var g_Sizes = { "tiny": 0.5, "small": 0.75, "normal": 1, "big": 1.25, "huge": 1.5, }; var g_AllAmounts = Object.keys(g_Amounts); var g_AllMixes = Object.keys(g_Mixes); var g_AllSizes = Object.keys(g_Sizes); var g_DefaultTileClasses = [ "animals", "baseResource", "berries", "bluff", "bluffSlope", "dirt", "fish", "food", "forest", "hill", "land", "map", "metal", "mountain", "plateau", "player", "prop", "ramp", "rock", "settlement", "spine", "valley", "water" ]; var g_TileClasses; +var g_PlayerbaseTypes = { + "line": { + "available": () => g_Map.getSize() >= 384 && getTeamsArray().length >= 2 && getNumPlayers() >= 4, + "getPosition": (distance, groupedDistance, startAngle) => placeLine(getTeamsArray(), distance, groupedDistance, startAngle), + "walls": false + }, + "radial": { + "available": () => true, + "getPosition": (distance, groupedDistance, startAngle) => playerPlacementCircle(distance, startAngle), + "walls": true + }, + "random": { + "available": () => g_Map.getSize() >= 256 && (getTeamsArray().length >= 3 || getNumPlayers() > 4), + "getPosition": (distance, groupedDistance, startAngle) => playerPlacementRandom(sortAllPlayers()) || playerPlacementCircle(distance, startAngle), + "walls": true + }, + "stronghold": { + "available": () => g_Map.getSize() >= 256 && getTeamsArray().length >= 2 && getNumPlayers() >= 4, + "getPosition": (distance, groupedDistance, startAngle) => placeStronghold(getTeamsArray(), distance, groupedDistance, startAngle), + "walls": false + } +}; + /** * Adds an array of elements to the map. */ function addElements(elements) { for (let element of elements) element.func( [ avoidClasses.apply(null, element.avoid), stayClasses.apply(null, element.stay || null) ], pickSize(element.sizes), pickMix(element.mixes), pickAmount(element.amounts), element.baseHeight || 0); } /** * Converts "amount" terms to numbers. */ function pickAmount(amounts) { let amount = pickRandom(amounts); if (amount in g_Amounts) return g_Amounts[amount]; return g_Amounts.normal; } /** * Converts "mix" terms to numbers. */ function pickMix(mixes) { let mix = pickRandom(mixes); if (mix in g_Mixes) return g_Mixes[mix]; return g_Mixes.normal; } /** * Converts "size" terms to numbers. */ function pickSize(sizes) { let size = pickRandom(sizes); if (size in g_Sizes) return g_Sizes[size]; return g_Sizes.normal; } /** * Choose starting locations for all players. * * @param {string} type - "radial", "line", "stronghold", "random" * @param {number} distance - radial distance from the center of the map * @param {number} groupedDistance - space between players within a team * @param {number} startAngle - determined by the map that might want to place something between players * @returns {Array|undefined} - If successful, each element is an object that contains id, angle, x, z for each player */ -function addBases(type, distance, groupedDistance, startAngle) +function createBasesByPattern(type, distance, groupedDistance, startAngle) +{ + return createBases(...g_PlayerbaseTypes[type].getPosition(distance, groupedDistance, startAngle), g_PlayerbaseTypes[type].walls); +} + +function createBases(playerIDs, playerPosition, walls) { g_Map.log("Creating bases"); - let playerIDs = sortAllPlayers(); - let teamsArray = getTeamsArray(); + for (let i = 0; i < getNumPlayers(); ++i) + createBase(playerIDs[i], playerPosition[i], walls); - switch(type) - { - case "line": - return placeLine(teamsArray, distance, groupedDistance, startAngle); - case "radial": - return placeRadial(playerIDs, distance, startAngle); - case "random": - return placeRandom(playerIDs) || placeRadial(playerIDs, distance, startAngle); - case "stronghold": - return placeStronghold(teamsArray, distance, groupedDistance, startAngle); - default: - warn("Unknown base placement type:" + type); - return undefined; - } + return [playerIDs, playerPosition]; } /** * Create the base for a single player. * * @param {Object} player - contains id, angle, x, z * @param {boolean} walls - Whether or not iberian gets starting walls */ -function createBase(player, walls = true) +function createBase(playerID, playerPosition, walls) { placePlayerBase({ - "playerID": player.id, - "playerPosition": player.position, + "playerID": playerID, + "playerPosition": playerPosition, "PlayerTileClass": g_TileClasses.player, "BaseResourceClass": g_TileClasses.baseResource, "baseResourceConstraint": avoidClasses(g_TileClasses.water, 0, g_TileClasses.mountain, 0), "Walls": g_Map.getSize() > 192 && walls, "CityPatch": { "outerTerrain": g_Terrains.roadWild, "innerTerrain": g_Terrains.road, "painters": [ new TileClassPainter(g_TileClasses.player) ] }, "Chicken": { "template": g_Gaia.chicken }, "Berries": { "template": g_Gaia.fruitBush }, "Mines": { "types": [ { "template": g_Gaia.metalLarge }, { "template": g_Gaia.stoneLarge } ] }, "Trees": { "template": g_Gaia.tree1, "count": currentBiome() == "generic/savanna" ? 5 : 15 }, "Decoratives": { "template": g_Decoratives.grassShort } }); } /** * Return an array where each element is an array of playerIndices of a team. */ function getTeamsArray() { var playerIDs = sortAllPlayers(); var numPlayers = getNumPlayers(); // Group players by team var teams = []; for (let i = 0; i < numPlayers; ++i) { let team = getPlayerTeam(playerIDs[i]); if (team == -1) continue; if (!teams[team]) teams[team] = []; teams[team].push(playerIDs[i]); } // Players without a team get a custom index for (let i = 0; i < numPlayers; ++i) if (getPlayerTeam(playerIDs[i]) == -1) teams.push([playerIDs[i]]); // Remove unused indices return teams.filter(team => true); } /** * Choose a random pattern for placing the bases of the players. */ function randomStartingPositionPattern(teamsArray) { - var formats = ["radial"]; - var mapSize = g_Map.getSize(); - var numPlayers = getNumPlayers(); - - // Enable stronghold if we have a few teams and a big enough map - if (teamsArray.length >= 2 && numPlayers >= 4 && mapSize >= 256) - formats.push("stronghold"); - - // Enable random if we have enough teams or enough players on a big enough map - if (mapSize >= 256 && (teamsArray.length >= 3 || numPlayers > 4)) - formats.push("random"); - - // Enable line if we have enough teams and players on a big enough map - if (teamsArray.length >= 2 && numPlayers >= 4 && mapSize >= 384) - formats.push("line"); - return { - "setup": pickRandom(formats), + "setup": pickRandom(Object.keys(g_PlayerbaseTypes).filter(type => g_PlayerbaseTypes[type].available())), "distance": fractionToTiles(randFloat(0.2, 0.35)), "separation": fractionToTiles(randFloat(0.05, 0.1)) }; } /** * Place teams in a line-pattern. * * @param {Array} playerIDs - typically randomized indices of players of a single team * @param {number} distance - radial distance from the center of the map * @param {number} groupedDistance - distance between players * @param {number} startAngle - determined by the map that might want to place something between players. * * @returns {Array} - contains id, angle, x, z for every player */ function placeLine(teamsArray, distance, groupedDistance, startAngle) { - let players = []; + let playerIDs = []; + let playerPosition = []; + let mapCenter = g_Map.getCenter(); let dist = fractionToTiles(0.45); for (let i = 0; i < teamsArray.length; ++i) { var safeDist = distance; if (distance + teamsArray[i].length * groupedDistance > dist) safeDist = dist - teamsArray[i].length * groupedDistance; var teamAngle = startAngle + (i + 1) * 2 * Math.PI / teamsArray.length; - // Create player base for (let p = 0; p < teamsArray[i].length; ++p) { - players[teamsArray[i][p]] = { - "id": teamsArray[i][p], - "position": Vector2D.add(mapCenter, new Vector2D(safeDist + p * groupedDistance, 0).rotate(-teamAngle)).round() - }; - createBase(players[teamsArray[i][p]], false); + playerIDs.push(teamsArray[i][p]); + playerPosition.push(Vector2D.add(mapCenter, new Vector2D(safeDist + p * groupedDistance, 0).rotate(-teamAngle)).round()); } } - return players; -} - -/** - * Place players in a circle-pattern. - * - * @param {Array} playerIDs - order of playerIDs to be placed - * @param {number} distance - radial distance from the center of the map - * @param {number} startAngle - determined by the map that might want to place something between players - */ -function placeRadial(playerIDs, distance, startAngle) -{ - let mapCenter = g_Map.getCenter(); - let players = []; - let numPlayers = getNumPlayers(); - - for (let i = 0; i < numPlayers; ++i) - { - let angle = startAngle + i * 2 * Math.PI / numPlayers; - players[i] = { - "id": playerIDs[i], - "position": Vector2D.add(mapCenter, new Vector2D(distance, 0).rotate(-angle)).round() - }; - createBase(players[i]); - } - - return players; -} - -/** - * Place playerbases on random locations on the map meeting the given constraints. - */ -function placeRandom(playerIDs, constraints = undefined) -{ - let players = randomPlayerLocations(playerIDs, constraints); - if (!players) - return undefined; - - for (let player of players) - createBase(player); - - return players; -} - -/** - * Choose arbitrary starting locations. - */ -function randomPlayerLocations(playerIDs, constraints = undefined) -{ - let locations = []; - let attempts = 0; - let resets = 0; - - let mapCenter = g_Map.getCenter(); - let playerMinDist = fractionToTiles(0.25); - let borderDistance = fractionToTiles(0.08); - - let area = createArea(new MapBoundsPlacer(), undefined, new AndConstraint(constraints)); - - for (let i = 0; i < getNumPlayers(); ++i) - { - let position = pickRandom(area.points); - - // Minimum distance between initial bases must be a quarter of the map diameter - if (locations.some(loc => loc.distanceTo(position) < playerMinDist) || - position.distanceTo(mapCenter) > mapCenter.x - borderDistance) - { - --i; - ++attempts; - - // Reset if we're in what looks like an infinite loop - if (attempts > 500) - { - locations = []; - i = -1; - attempts = 0; - ++resets; - - // Reduce minimum player distance progressively - if (resets % 25 == 0) - playerMinDist *= 0.975; - - // If we only pick bad locations, stop trying to place randomly - if (resets == 500) - { - throw new Error("Could not find suitable playerbase locations!"); - return undefined; - } - } - continue; - } - - locations[i] = position; - } - return groupPlayersByLocations(playerIDs, locations); -} - -/** - * Pick locations from the given set so that teams end up grouped. - * - * @param {Array} playerIDs - sorted by teams. - * @param {Array} locations - array of Vector2D of possible starting locations. - */ -function groupPlayersByLocations(playerIDs, locations) -{ - playerIDs = sortPlayers(playerIDs); - - let minDist = Infinity; - let minLocations; - - // Of all permutations of starting locations, find the one where - // the sum of the distances between allies is minimal, weighted by teamsize. - heapsPermute(shuffleArray(locations).slice(0, playerIDs.length), v => v.clone(), permutation => - { - let dist = 0; - let teamDist = 0; - let teamSize = 0; - - for (let i = 1; i < playerIDs.length; ++i) - { - let team1 = getPlayerTeam(playerIDs[i - 1]); - let team2 = getPlayerTeam(playerIDs[i]); - ++teamSize; - if (team1 != -1 && team1 == team2) - teamDist += permutation[i - 1].distanceTo(permutation[i]); - else - { - dist += teamDist / teamSize; - teamDist = 0; - teamSize = 0; - } - } - - if (teamSize) - dist += teamDist / teamSize; - - if (dist < minDist) - { - minDist = dist; - minLocations = permutation; - } - }); - - let players = []; - for (let i = 0; i < playerIDs.length; ++i) - players[i] = { - "id": playerIDs[i], - "position": minLocations[i] - }; - - return players; + return [playerIDs, playerPosition]; } /** * Place given players in a stronghold-pattern. * * @param teamsArray - each item is an array of playerIDs placed per stronghold * @param distance - radial distance from the center of the map * @param groupedDistance - distance between neighboring players * @param {number} startAngle - determined by the map that might want to place something between players */ function placeStronghold(teamsArray, distance, groupedDistance, startAngle) { - var players = []; var mapCenter = g_Map.getCenter(); + let playerIDs = []; + let playerPosition = []; + for (let i = 0; i < teamsArray.length; ++i) { var teamAngle = startAngle + (i + 1) * 2 * Math.PI / teamsArray.length; var teamPosition = Vector2D.add(mapCenter, new Vector2D(distance, 0).rotate(-teamAngle)); var teamGroupDistance = groupedDistance; // If we have a team of above average size, make sure they're spread out if (teamsArray[i].length > 4) teamGroupDistance = Math.max(fractionToTiles(0.08), groupedDistance); // If we have a solo player, place them on the center of the team's location if (teamsArray[i].length == 1) teamGroupDistance = fractionToTiles(0); // TODO: Ensure players are not placed outside of the map area, similar to placeLine // Create player base for (var p = 0; p < teamsArray[i].length; ++p) { var angle = startAngle + (p + 1) * 2 * Math.PI / teamsArray[i].length; - players[teamsArray[i][p]] = { - "id": teamsArray[i][p], - "position": Vector2D.add(teamPosition, new Vector2D(teamGroupDistance, 0).rotate(-angle)).round() - }; - createBase(players[teamsArray[i][p]], false); + playerIDs.push(teamsArray[i][p]); + playerPosition.push(Vector2D.add(teamPosition, new Vector2D(teamGroupDistance, 0).rotate(-angle)).round()); } } - return players; + return [playerIDs, playerPosition]; } /** * Creates tileClass for the default classes and every class given. * * @param {Array} newClasses * @returns {Object} - maps from classname to ID */ function initTileClasses(newClasses) { var classNames = g_DefaultTileClasses; if (newClasses) classNames = classNames.concat(newClasses); g_TileClasses = {}; for (var className of classNames) g_TileClasses[className] = g_Map.createTileClass(); } Index: ps/trunk/binaries/data/mods/public/maps/random/stronghold.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/stronghold.js (revision 21205) +++ ps/trunk/binaries/data/mods/public/maps/random/stronghold.js (revision 21206) @@ -1,259 +1,259 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen2"); Engine.LoadLibrary("rmbiome"); setSelectedBiome(); const heightLand = 30; var g_Map = new RandomMap(heightLand, g_Terrains.mainTerrain); initTileClasses(); createArea( new MapBoundsPlacer(), new TileClassPainter(g_TileClasses.land)); Engine.SetProgress(20); -addBases("stronghold", fractionToTiles(randFloat(0.2, 0.35)), fractionToTiles(randFloat(0.05, 0.1)), randomAngle()); +createBasesByPattern("stronghold", fractionToTiles(randFloat(0.2, 0.35)), fractionToTiles(randFloat(0.05, 0.1)), randomAngle()); Engine.SetProgress(30); addElements(shuffleArray([ { "func": addBluffs, "baseHeight": heightLand, "avoid": [ g_TileClasses.bluff, 20, g_TileClasses.hill, 5, g_TileClasses.mountain, 20, g_TileClasses.plateau, 20, g_TileClasses.player, 30, g_TileClasses.valley, 5, g_TileClasses.water, 7 ], "sizes": ["big", "huge"], "mixes": g_AllMixes, "amounts": g_AllAmounts }, { "func": addHills, "avoid": [ g_TileClasses.bluff, 5, g_TileClasses.hill, 15, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 20, g_TileClasses.valley, 2, g_TileClasses.water, 2 ], "sizes": ["normal", "big"], "mixes": g_AllMixes, "amounts": g_AllAmounts }, { "func": addMountains, "avoid": [ g_TileClasses.bluff, 20, g_TileClasses.mountain, 25, g_TileClasses.plateau, 20, g_TileClasses.player, 20, g_TileClasses.valley, 10, g_TileClasses.water, 15 ], "sizes": ["big", "huge"], "mixes": g_AllMixes, "amounts": g_AllAmounts }, { "func": addPlateaus, "avoid": [ g_TileClasses.bluff, 20, g_TileClasses.mountain, 25, g_TileClasses.plateau, 25, g_TileClasses.player, 40, g_TileClasses.valley, 10, g_TileClasses.water, 15 ], "sizes": ["big", "huge"], "mixes": g_AllMixes, "amounts": g_AllAmounts }, { "func": addValleys, "baseHeight": heightLand, "avoid": [ g_TileClasses.bluff, 5, g_TileClasses.hill, 5, g_TileClasses.mountain, 25, g_TileClasses.plateau, 10, g_TileClasses.player, 40, g_TileClasses.valley, 15, g_TileClasses.water, 10 ], "sizes": ["normal", "big"], "mixes": g_AllMixes, "amounts": g_AllAmounts } ])); Engine.SetProgress(60); addElements([ { "func": addLayeredPatches, "avoid": [ g_TileClasses.bluff, 2, g_TileClasses.dirt, 5, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 12, g_TileClasses.valley, 5, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] }, { "func": addDecoration, "avoid": [ g_TileClasses.bluff, 2, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 12, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] } ]); Engine.SetProgress(70); addElements(shuffleArray([ { "func": addMetal, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 30, g_TileClasses.rock, 10, g_TileClasses.metal, 20, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": g_AllAmounts }, { "func": addStone, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 30, g_TileClasses.rock, 20, g_TileClasses.metal, 10, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": g_AllAmounts }, { "func": addForests, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 18, g_TileClasses.metal, 3, g_TileClasses.mountain, 5, g_TileClasses.plateau, 5, g_TileClasses.player, 20, g_TileClasses.rock, 3, g_TileClasses.water, 2 ], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": ["few", "normal", "many", "tons"] } ])); Engine.SetProgress(80); addElements(shuffleArray([ { "func": addBerries, "avoid": [ g_TileClasses.berries, 30, g_TileClasses.bluff, 5, g_TileClasses.forest, 5, g_TileClasses.metal, 10, g_TileClasses.mountain, 2, g_TileClasses.plateau, 2, g_TileClasses.player, 20, g_TileClasses.rock, 10, g_TileClasses.spine, 2, g_TileClasses.water, 3 ], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": g_AllAmounts }, { "func": addAnimals, "avoid": [ g_TileClasses.animals, 20, g_TileClasses.bluff, 5, g_TileClasses.forest, 2, g_TileClasses.metal, 2, g_TileClasses.mountain, 1, g_TileClasses.plateau, 2, g_TileClasses.player, 20, g_TileClasses.rock, 2, g_TileClasses.spine, 2, g_TileClasses.water, 3 ], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": g_AllAmounts }, { "func": addStragglerTrees, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.forest, 7, g_TileClasses.metal, 2, g_TileClasses.mountain, 1, g_TileClasses.plateau, 2, g_TileClasses.player, 12, g_TileClasses.rock, 2, g_TileClasses.spine, 2, g_TileClasses.water, 5 ], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": g_AllAmounts } ])); Engine.SetProgress(90); placePlayersNomad( g_TileClasses.player, avoidClasses( g_TileClasses.bluff, 4, g_TileClasses.plateau, 4, g_TileClasses.forest, 1, g_TileClasses.metal, 4, g_TileClasses.rock, 4, g_TileClasses.mountain, 4, g_TileClasses.animals, 2)); g_Map.ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/wild_lake.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/wild_lake.js (revision 21205) +++ ps/trunk/binaries/data/mods/public/maps/random/wild_lake.js (revision 21206) @@ -1,642 +1,642 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmbiome"); Engine.LoadLibrary("heightmap"); var g_Map = new RandomMap(0, "whiteness"); /** * getArray - To ensure a terrain texture is contained within an array */ function getArray(stringOrArrayOfStrings) { if (typeof stringOrArrayOfStrings == "string") return [stringOrArrayOfStrings]; return stringOrArrayOfStrings; } setSelectedBiome(); // Terrain, entities and actors let wildLakeBiome = [ // 0 Deep water { "texture": getArray(g_Terrains.water), "actor": [[g_Gaia.fish], 0.01], "textureHS": getArray(g_Terrains.water), "actorHS": [[g_Gaia.fish], 0.03] }, // 1 Shallow water { "texture": getArray(g_Terrains.water), "actor": [[g_Decoratives.lillies, g_Decoratives.reeds], 0.3], "textureHS": getArray(g_Terrains.water), "actorHS": [[g_Decoratives.lillies], 0.1] }, // 2 Shore { "texture": getArray(g_Terrains.shore), "actor": [ [ g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree2, g_Gaia.tree2, g_Gaia.mainHuntableAnimal, g_Decoratives.grass, g_Decoratives.grass, g_Decoratives.rockMedium, g_Decoratives.rockMedium, g_Decoratives.bushMedium, g_Decoratives.bushMedium ], 0.3 ], "textureHS": getArray(g_Terrains.cliff), "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1] }, // 3 Low ground { "texture": getArray(g_Terrains.tier1Terrain), "actor": [ [ g_Decoratives.grass, g_Decoratives.grassShort, g_Decoratives.rockLarge, g_Decoratives.rockMedium, g_Decoratives.bushMedium, g_Decoratives.bushSmall ], 0.2 ], "textureHS": getArray(g_Terrains.cliff), "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1] }, // 4 Mid ground. Player and path height { "texture": getArray(g_Terrains.mainTerrain), "actor": [ [ g_Decoratives.grass, g_Decoratives.grassShort, g_Decoratives.rockLarge, g_Decoratives.rockMedium, g_Decoratives.bushMedium, g_Decoratives.bushSmall ], 0.2 ], "textureHS": getArray(g_Terrains.cliff), "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1] }, // 5 High ground { "texture": getArray(g_Terrains.tier2Terrain), "actor": [ [ g_Decoratives.grass, g_Decoratives.grassShort, g_Decoratives.rockLarge, g_Decoratives.rockMedium, g_Decoratives.bushMedium, g_Decoratives.bushSmall ], 0.2 ], "textureHS": getArray(g_Terrains.cliff), "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1] }, // 6 Lower hilltop forest border { "texture": getArray(g_Terrains.dirt), "actor": [ [ g_Gaia.tree1, g_Gaia.tree3, g_Gaia.fruitBush, g_Gaia.secondaryHuntableAnimal, g_Decoratives.grass, g_Decoratives.rockMedium, g_Decoratives.bushMedium ], 0.3 ], "textureHS": getArray(g_Terrains.cliff), "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1] }, // 7 Hilltop forest { "texture": getArray(g_Terrains.forestFloor1), "actor": [ [ g_Gaia.tree1, g_Gaia.tree2, g_Gaia.tree3, g_Gaia.tree4, g_Gaia.tree5, g_Decoratives.tree, g_Decoratives.grass, g_Decoratives.rockMedium, g_Decoratives.bushMedium ], 0.5 ], "textureHS": getArray(g_Terrains.cliff), "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1] } ]; var mercenaryCampGuards = { "generic/temperate": [ { "Template" : "structures/merc_camp_egyptian" }, { "Template" : "units/mace_infantry_javelinist_b", "Count" : 4 }, { "Template" : "units/mace_cavalry_spearman_e", "Count" : 3 }, { "Template" : "units/mace_infantry_archer_a", "Count" : 4 }, { "Template" : "units/mace_champion_infantry_a", "Count" : 3 } ], "generic/snowy": [ { "Template" : "structures/ptol_mercenary_camp" }, { "Template" : "units/brit_infantry_javelinist_b", "Count" : 4 }, { "Template" : "units/brit_cavalry_swordsman_e", "Count" : 3 }, { "Template" : "units/brit_infantry_slinger_a", "Count" : 4 }, { "Template" : "units/brit_champion_infantry", "Count" : 3 } ], "generic/desert": [ { "Template" : "structures/ptol_mercenary_camp" }, { "Template" : "units/pers_infantry_javelinist_b", "Count" : 4 }, { "Template" : "units/pers_cavalry_swordsman_e", "Count" : 3 }, { "Template" : "units/pers_infantry_archer_a", "Count" : 4 }, { "Template" : "units/pers_champion_infantry", "Count" : 3 } ], "generic/alpine": [ { "Template" : "structures/ptol_mercenary_camp" }, { "Template" : "units/rome_infantry_swordsman_b", "Count" : 4 }, { "Template" : "units/rome_cavalry_spearman_e", "Count" : 3 }, { "Template" : "units/rome_infantry_javelinist_a", "Count" : 4 }, { "Template" : "units/rome_champion_infantry", "Count" : 3 } ], "generic/mediterranean": [ { "Template" : "structures/merc_camp_egyptian" }, { "Template" : "units/iber_infantry_javelinist_b", "Count" : 4 }, { "Template" : "units/iber_cavalry_spearman_e", "Count" : 3 }, { "Template" : "units/iber_infantry_slinger_a", "Count" : 4 }, { "Template" : "units/iber_champion_infantry", "Count" : 3 } ], "generic/savanna": [ { "Template" : "structures/merc_camp_egyptian" }, { "Template" : "units/sele_infantry_javelinist_b", "Count" : 4 }, { "Template" : "units/sele_cavalry_spearman_merc_e", "Count" : 3 }, { "Template" : "units/sele_infantry_spearman_a", "Count" : 4 }, { "Template" : "units/sele_champion_infantry_swordsman", "Count" : 3 } ], "generic/tropic": [ { "Template" : "structures/merc_camp_egyptian" }, { "Template" : "units/ptol_infantry_javelinist_b", "Count" : 4 }, { "Template" : "units/ptol_camelry_archer_e", "Count" : 3 }, { "Template" : "units/ptol_infantry_slinger_a", "Count" : 4 }, { "Template" : "units/ptol_champion_infantry_pikeman", "Count" : 3 } ], "generic/autumn": [ { "Template" : "structures/ptol_mercenary_camp" }, { "Template" : "units/gaul_infantry_javelinist_b", "Count" : 4 }, { "Template" : "units/gaul_cavalry_swordsman_e", "Count" : 3 }, { "Template" : "units/gaul_infantry_slinger_a", "Count" : 4 }, { "Template" : "units/gaul_champion_infantry", "Count" : 3 } ] }; /** * Resource spots and other points of interest */ function placeMine(position, centerEntity, decorativeActors = [ g_Decoratives.grass, g_Decoratives.grassShort, g_Decoratives.rockLarge, g_Decoratives.rockMedium, g_Decoratives.bushMedium, g_Decoratives.bushSmall ] ) { g_Map.placeEntityPassable(centerEntity, 0, position, randomAngle()); let quantity = randIntInclusive(11, 23); let dAngle = 2 * Math.PI / quantity; for (let i = 0; i < quantity; ++i) g_Map.placeEntityPassable( pickRandom(decorativeActors), 0, Vector2D.add(position, new Vector2D(randFloat(2, 5), 0).rotate(-dAngle * randFloat(i, i + 1))), randomAngle()); } // Groves, only Wood let groveActors = [g_Decoratives.grass, g_Decoratives.rockMedium, g_Decoratives.bushMedium]; let clGrove = g_Map.createTileClass(); let clGaiaCamp = g_Map.createTileClass(); function placeGrove(point, groveEntities = [ g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree2, g_Gaia.tree2, g_Gaia.tree2, g_Gaia.tree2, g_Gaia.tree3, g_Gaia.tree3, g_Gaia.tree3, g_Gaia.tree4, g_Gaia.tree4, g_Gaia.tree5 ], groveActors = [g_Decoratives.grass, g_Decoratives.rockMedium, g_Decoratives.bushMedium], groveTileClass = undefined, groveTerrainTexture = getArray(g_Terrains.forestFloor1) ) { let position = new Vector2D(point.x, point.y); g_Map.placeEntityPassable(pickRandom(["structures/gaul_outpost", "gaia/flora_tree_oak_new"]), 0, position, randomAngle()); let quantity = randIntInclusive(20, 30); let dAngle = 2 * Math.PI / quantity; for (let i = 0; i < quantity; ++i) { let angle = dAngle * randFloat(i, i + 1); let dist = randFloat(2, 5); let objectList = groveEntities; if (i % 3 == 0) objectList = groveActors; let pos = Vector2D.add(position, new Vector2D(dist, 0).rotate(-angle)); g_Map.placeEntityPassable(pickRandom(objectList), 0, pos, randomAngle()); let painters = [new TerrainPainter(groveTerrainTexture)]; if (groveTileClass) painters.push(new TileClassPainter(groveTileClass)); createArea( new ClumpPlacer(5, 1, 1, Infinity, pos), painters); } } var farmEntities = { "generic/temperate": { "building": "structures/mace_farmstead", "animal": "gaia/fauna_pig" }, "generic/snowy": { "building": "structures/brit_farmstead", "animal": "gaia/fauna_sheep" }, "generic/desert": { "building": "structures/pers_farmstead", "animal": "gaia/fauna_camel" }, "generic/alpine": { "building": "structures/rome_farmstead", "animal": "gaia/fauna_sheep" }, "generic/mediterranean": { "building": "structures/iber_farmstead", "animal": "gaia/fauna_pig" }, "generic/savanna": { "building": "structures/sele_farmstead", "animal": "gaia/fauna_horse" }, "generic/tropic": { "building": "structures/ptol_farmstead", "animal": "gaia/fauna_camel" }, "generic/autumn": { "building": "structures/gaul_farmstead", "animal": "gaia/fauna_horse" } }; g_WallStyles.other = { "overlap": 0, "fence": readyWallElement("other/fence_long", "gaia"), "fence_short": readyWallElement("other/fence_short", "gaia"), "bench": { "angle": Math.PI / 2, "length": 1.5, "indent": 0, "bend": 0, "templateName": "other/bench" }, "foodBin": { "angle": Math.PI / 2, "length": 1.5, "indent": 0, "bend": 0, "templateName": "gaia/treasure/food_bin" }, "animal": { "angle": 0, "length": 0, "indent": 0.75, "bend": 0, "templateName": farmEntities[currentBiome()].animal }, "farmstead": { "angle": Math.PI, "length": 0, "indent": -3, "bend": 0, "templateName": farmEntities[currentBiome()].building } }; let fences = [ new Fortress("fence", [ "foodBin", "farmstead", "bench", "turn_0.25", "animal", "turn_0.25", "fence", "turn_0.25", "animal", "turn_0.25", "fence", "turn_0.25", "animal", "turn_0.25", "fence" ]), new Fortress("fence", [ "foodBin", "farmstead", "fence", "turn_0.25", "animal", "turn_0.25", "fence", "turn_0.25", "animal", "turn_0.25", "bench", "animal", "fence", "turn_0.25", "animal", "turn_0.25", "fence" ]), new Fortress("fence", [ "foodBin", "farmstead", "turn_0.5", "bench", "turn_-0.5", "fence_short", "turn_0.25", "animal", "turn_0.25", "fence", "turn_0.25", "animal", "turn_0.25", "fence", "turn_0.25", "animal", "turn_0.25", "fence_short", "animal", "fence" ]), new Fortress("fence", [ "foodBin", "farmstead", "turn_0.5", "fence_short", "turn_-0.5", "bench", "turn_0.25", "animal", "turn_0.25", "fence", "turn_0.25", "animal", "turn_0.25", "fence", "turn_0.25", "animal", "turn_0.25", "fence_short", "animal", "fence" ]), new Fortress("fence", [ "foodBin", "farmstead", "fence", "turn_0.25", "animal", "turn_0.25", "bench", "animal", "fence", "turn_0.25", "animal", "turn_0.25", "fence_short", "animal", "fence", "turn_0.25", "animal", "turn_0.25", "fence_short", "animal", "fence" ]) ]; let num = fences.length; for (let i = 0; i < num; ++i) fences.push(new Fortress("fence", clone(fences[i].wall).reverse())); // Camps with fire and gold treasure function placeCamp(position, centerEntity = "actor|props/special/eyecandy/campfire.xml", otherEntities = ["gaia/treasure/metal", "gaia/treasure/standing_stone", "units/brit_infantry_slinger_b", "units/brit_infantry_javelinist_b", "units/gaul_infantry_slinger_b", "units/gaul_infantry_javelinist_b", "units/gaul_champion_fanatic", "actor|props/special/common/waypoint_flag.xml", "actor|props/special/eyecandy/barrel_a.xml", "actor|props/special/eyecandy/basket_celt_a.xml", "actor|props/special/eyecandy/crate_a.xml", "actor|props/special/eyecandy/dummy_a.xml", "actor|props/special/eyecandy/handcart_1.xml", "actor|props/special/eyecandy/handcart_1_broken.xml", "actor|props/special/eyecandy/sack_1.xml", "actor|props/special/eyecandy/sack_1_rough.xml" ] ) { g_Map.placeEntityPassable(centerEntity, 0, position, randomAngle()); let quantity = randIntInclusive(5, 11); let dAngle = 2 * Math.PI / quantity; for (let i = 0; i < quantity; ++i) { let angle = dAngle * randFloat(i, i + 1); let dist = randFloat(1, 3); g_Map.placeEntityPassable(pickRandom(otherEntities), 0, Vector2D.add(position, new Vector2D(dist, 0).rotate(-angle)), randomAngle()); } addCivicCenterAreaToClass(position, clGaiaCamp); } function placeStartLocationResources( point, foodEntities = [g_Gaia.fruitBush, g_Gaia.chicken], groveEntities = [ g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree2, g_Gaia.tree2, g_Gaia.tree2, g_Gaia.tree2, g_Gaia.tree3, g_Gaia.tree3, g_Gaia.tree3, g_Gaia.tree4, g_Gaia.tree4, g_Gaia.tree5 ], groveTerrainTexture = getArray(g_Terrains.forestFloor1), averageDistToCC = 10, dAverageDistToCC = 2 ) { function getRandDist() { return averageDistToCC + randFloat(-dAverageDistToCC, dAverageDistToCC); } let currentAngle = randomAngle(); // Stone let dAngle = 4/9 * Math.PI; let angle = currentAngle + randFloat(dAngle / 4, 3 * dAngle / 4); placeMine(Vector2D.add(point, new Vector2D(averageDistToCC, 0).rotate(-angle)), g_Gaia.stoneLarge); currentAngle += dAngle; // Wood let quantity = 80; dAngle = 2/3 * Math.PI / quantity; for (let i = 0; i < quantity; ++i) { angle = currentAngle + randFloat(0, dAngle); let dist = getRandDist(); let objectList = groveEntities; if (i % 2 == 0) objectList = groveActors; let position = Vector2D.add(point, new Vector2D(dist, 0).rotate(-angle)); g_Map.placeEntityPassable(pickRandom(objectList), 0, position, randomAngle()); createArea( new ClumpPlacer(5, 1, 1, Infinity, position), [ new TerrainPainter(groveTerrainTexture), new TileClassPainter(clGrove) ]); currentAngle += dAngle; } // Metal dAngle = 4/9 * Math.PI; angle = currentAngle + randFloat(dAngle / 4, 3 * dAngle / 4); placeMine(Vector2D.add(point, new Vector2D(averageDistToCC, 0).rotate(-angle)), g_Gaia.metalLarge); currentAngle += dAngle; // Berries and domestic animals quantity = 15; dAngle = 4/9 * Math.PI / quantity; for (let i = 0; i < quantity; ++i) { angle = currentAngle + randFloat(0, dAngle); let dist = getRandDist(); g_Map.placeEntityPassable(pickRandom(foodEntities), 0, Vector2D.add(point, new Vector2D(dist, 0).rotate(-angle)), randomAngle()); currentAngle += dAngle; } } /** * Base terrain shape generation and settings */ // Height range by map size let heightScale = (g_Map.size + 256) / 768 / 4; let heightRange = { "min": MIN_HEIGHT * heightScale, "max": MAX_HEIGHT * heightScale }; // Water coverage let averageWaterCoverage = 1/5; // NOTE: Since terrain generation is quite unpredictable actual water coverage might vary much with the same value let heightSeaGround = -MIN_HEIGHT + heightRange.min + averageWaterCoverage * (heightRange.max - heightRange.min); // Water height in environment and the engine let heightSeaGroundAdjusted = heightSeaGround + MIN_HEIGHT; // Water height as terrain height setWaterHeight(heightSeaGround); // Generate base terrain shape let lowH = heightRange.min; let medH = (heightRange.min + heightRange.max) / 2; // Lake let initialHeightmap = [ [medH, medH, medH, medH, medH, medH], [medH, medH, medH, medH, medH, medH], [medH, medH, lowH, lowH, medH, medH], [medH, medH, lowH, lowH, medH, medH], [medH, medH, medH, medH, medH, medH], [medH, medH, medH, medH, medH, medH], ]; if (g_Map.size < 256) { initialHeightmap = [ [medH, medH, medH, medH, medH], [medH, medH, medH, medH, medH], [medH, medH, lowH, medH, medH], [medH, medH, medH, medH, medH], [medH, medH, medH, medH, medH] ]; } if (g_Map.size >= 384) { initialHeightmap = [ [medH, medH, medH, medH, medH, medH, medH, medH], [medH, medH, medH, medH, medH, medH, medH, medH], [medH, medH, medH, medH, medH, medH, medH, medH], [medH, medH, medH, lowH, lowH, medH, medH, medH], [medH, medH, medH, lowH, lowH, medH, medH, medH], [medH, medH, medH, medH, medH, medH, medH, medH], [medH, medH, medH, medH, medH, medH, medH, medH], [medH, medH, medH, medH, medH, medH, medH, medH], ]; } setBaseTerrainDiamondSquare(heightRange.min, heightRange.max, initialHeightmap, 0.8); g_Map.log("Eroding map"); for (let i = 0; i < 5; ++i) splashErodeMap(0.1); g_Map.log("Smoothing map"); createArea( new MapBoundsPlacer(), new SmoothingPainter(1, 0.8, 1)); g_Map.log("Rescaling map"); rescaleHeightmap(heightRange.min, heightRange.max); Engine.SetProgress(25); /** * Prepare terrain texture placement */ let heighLimits = [ heightRange.min + 3/4 * (heightSeaGroundAdjusted - heightRange.min), // 0 Deep water heightSeaGroundAdjusted, // 1 Shallow water heightSeaGroundAdjusted + 2/8 * (heightRange.max - heightSeaGroundAdjusted), // 2 Shore heightSeaGroundAdjusted + 3/8 * (heightRange.max - heightSeaGroundAdjusted), // 3 Low ground heightSeaGroundAdjusted + 4/8 * (heightRange.max - heightSeaGroundAdjusted), // 4 Player and path height heightSeaGroundAdjusted + 6/8 * (heightRange.max - heightSeaGroundAdjusted), // 5 High ground heightSeaGroundAdjusted + 7/8 * (heightRange.max - heightSeaGroundAdjusted), // 6 Lower forest border heightRange.max // 7 Forest ]; let playerHeightRange = { "min" : heighLimits[3], "max" : heighLimits[4] }; let resourceSpotHeightRange = { "min" : (heighLimits[2] + heighLimits[3]) / 2, "max" : (heighLimits[4] + heighLimits[5]) / 2 }; let playerHeight = (playerHeightRange.min + playerHeightRange.max) / 2; // Average player height g_Map.log("Chosing starting locations"); -let [playerIDs, playerPosition] = sortPlayersByLocation(getStartLocationsByHeightmap(playerHeightRange, 1000, 30)); +let [playerIDs, playerPosition] = groupPlayersCycle(getStartLocationsByHeightmap(playerHeightRange, 1000, 30)); g_Map.log("Smoothing starting locations before height calculation"); for (let position of playerPosition) createArea( new ClumpPlacer(diskArea(20), 0.8, 0.8, Infinity, position), new SmoothElevationPainter(ELEVATION_SET, g_Map.getHeight(position), 20)); Engine.SetProgress(30); /** * Calculate tile centered height map after start position smoothing but before placing paths * This has nothing to to with TILE_CENTERED_HEIGHT_MAP which should be false! */ let tchm = getTileCenteredHeightmap(); g_Map.log("Get points per height"); let areas = heighLimits.map(heightLimit => []); for (let x = 0; x < tchm.length; ++x) for (let y = 0; y < tchm[0].length; ++y) { let minHeight = heightRange.min; for (let h = 0; h < heighLimits.length; ++h) { if (tchm[x][y] >= minHeight && tchm[x][y] <= heighLimits[h]) { areas[h].push(new Vector2D(x, y)); break; } minHeight = heighLimits[h]; } } g_Map.log("Get slope limits per heightrange"); let slopeMap = getSlopeMap(); let minSlope = []; let maxSlope = []; for (let h = 0; h < heighLimits.length; ++h) { minSlope[h] = Infinity; maxSlope[h] = 0; for (let point of areas[h]) { let slope = slopeMap[point.x][point.y]; if (slope > maxSlope[h]) maxSlope[h] = slope; if (slope < minSlope[h]) minSlope[h] = slope; } } g_Map.log("Paint areas by height and slope"); for (let h = 0; h < heighLimits.length; ++h) for (let point of areas[h]) { let actor; let texture = pickRandom(wildLakeBiome[h].texture); if (slopeMap[point.x][point.y] < (minSlope[h] + maxSlope[h]) / 2) { if (randBool(wildLakeBiome[h].actor[1])) actor = pickRandom(wildLakeBiome[h].actor[0]); } else { texture = pickRandom(wildLakeBiome[h].textureHS); if (randBool(wildLakeBiome[h].actorHS[1])) actor = pickRandom(wildLakeBiome[h].actorHS[0]); } g_Map.setTexture(point, texture); if (actor) g_Map.placeEntityAnywhere(actor, 0, randomPositionOnTile(point), randomAngle()); } Engine.SetProgress(80); g_Map.log("Placing resources"); let avoidPoints = playerPosition.map(pos => pos.clone()); for (let i = 0; i < avoidPoints.length; ++i) avoidPoints[i].dist = 30; let resourceSpots = getPointsByHeight(resourceSpotHeightRange, avoidPoints).map(point => new Vector2D(point.x, point.y)); Engine.SetProgress(55); g_Map.log("Placing players"); if (isNomad()) placePlayersNomad( g_Map.createTileClass(), [ new HeightConstraint(playerHeightRange.min, playerHeightRange.max), avoidClasses(clGaiaCamp, 8) ]); else for (let p = 0; p < playerIDs.length; ++p) { placeCivDefaultStartingEntities(playerPosition[p], playerIDs[p], g_Map.size > 192); placeStartLocationResources(playerPosition[p]); } let mercenaryCamps = isNomad() ? 0 : Math.ceil(g_Map.size / 256); g_Map.log("Placing at most " + mercenaryCamps + " mercenary camps"); for (let i = 0; i < resourceSpots.length; ++i) { let radius; let choice = i % 5; if (choice == 0) placeMine(resourceSpots[i], g_Gaia.stoneLarge); if (choice == 1) placeMine(resourceSpots[i], g_Gaia.metalLarge); if (choice == 2) placeGrove(resourceSpots[i]); if (choice == 3) { placeCamp(resourceSpots[i]); radius = 5; } if (choice == 4) { if (mercenaryCamps) { placeStartingEntities(resourceSpots[i], 0, mercenaryCampGuards[currentBiome()]); radius = 15; --mercenaryCamps; } else { placeCustomFortress(resourceSpots[i], pickRandom(fences), "other", 0, randomAngle()); radius = 10; } } if (radius) createArea( new ClumpPlacer(diskArea(radius), 1, 1, Infinity, resourceSpots[i]), - new SmoothElevationPainter(ELEVATION_SET, g_Map.getHeight(resourceSpots[i]), radius)); + new SmoothElevationPainter(ELEVATION_SET, g_Map.getHeight(resourceSpots[i]), radius / 3)); } g_Map.ExportMap();