Index: ps/trunk/binaries/data/mods/public/maps/random/ambush.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/ambush.js (revision 27942) +++ ps/trunk/binaries/data/mods/public/maps/random/ambush.js (revision 27943) @@ -1,244 +1,243 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen-common"); Engine.LoadLibrary("rmgen2"); Engine.LoadLibrary("rmbiome"); setSelectedBiome(); var heightLand = 2; var g_Map = new RandomMap(heightLand, g_Terrains.mainTerrain); var mapCenter = g_Map.getCenter(); var mapSize = g_Map.getSize(); initTileClasses(["bluffsPassage", "nomadArea"]); createArea( new MapBoundsPlacer(), new TileClassPainter(g_TileClasses.land)); Engine.SetProgress(10); -var playerIDs; -var playerPosition; if (!isNomad()) { - let pattern = g_MapSettings.TeamPlacement || pickRandom(Object.keys(g_PlayerbaseTypes)); - - [playerIDs, playerPosition] = - createBasesByPattern( - pattern, - g_PlayerbaseTypes[pattern].distance, - g_PlayerbaseTypes[pattern].groupedDistance, - randomAngle()); + var [playerIDs, playerPosition] = + createBases( + ...playerPlacementByPattern( + g_MapSettings.TeamPlacement, + fractionToTiles(randFloat(0.2, 0.35)), + fractionToTiles(randFloat(0.08, 0.1)), + randomAngle(), + undefined), + undefined); markPlayerAvoidanceArea(playerPosition, defaultPlayerBaseRadius()); } Engine.SetProgress(20); addElements([ { "func": addBluffs, "baseHeight": heightLand, "avoid": [g_TileClasses.bluffIgnore, 0], "sizes": ["normal", "big", "huge"], "mixes": ["same"], "amounts": ["many"] }, { "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); if (!isNomad()) createBluffsPassages(playerPosition); addElements([ { "func": addLayeredPatches, "avoid": [ g_TileClasses.bluff, 2, g_TileClasses.bluffsPassage, 4, 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.bluffsPassage, 4, 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.bluffsPassage, 4, 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.bluffsPassage, 4, 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.bluffsPassage, 4, 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.bluffsPassage, 4, 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.bluffsPassage, 4, 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.bluffsPassage, 4, 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": ["few"] }, { "func": addStragglerTrees, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.bluff, 5, g_TileClasses.bluffsPassage, 4, 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); if (isNomad()) { g_Map.log("Preventing units to be spawned at the map border"); createArea( new DiskPlacer(mapSize / 2 - scaleByMapSize(15, 35), mapCenter), new TileClassPainter(g_TileClasses.nomadArea)); placePlayersNomad( g_TileClasses.player, [ stayClasses(g_TileClasses.nomadArea, 0), avoidClasses( g_TileClasses.bluff, 2, 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/empire.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/empire.js (revision 27942) +++ ps/trunk/binaries/data/mods/public/maps/random/empire.js (revision 27943) @@ -1,234 +1,248 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen-common"); 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(); -createBasesByPattern("stronghold", fractionToTiles(0.37), fractionToTiles(0.04), startAngle); +createBases( + ...playerPlacementByPattern( + "stronghold", + fractionToTiles(0.37), + fractionToTiles(0.04), + startAngle, + undefined), + undefined); 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; -createBasesByPattern("stronghold", fractionToTiles(0.15), fractionToTiles(0.04), startAngle + rotation); +createBases( + ...playerPlacementByPattern( + "stronghold", + fractionToTiles(0.15), + fractionToTiles(0.04), + startAngle + rotation, + undefined), + undefined); 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 27942) +++ ps/trunk/binaries/data/mods/public/maps/random/frontier.js (revision 27943) @@ -1,289 +1,292 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen-common"); 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); if (!isNomad()) { - let pattern = g_MapSettings.TeamPlacement || pickRandom(Object.keys(g_PlayerbaseTypes)); - createBasesByPattern( - pattern, - g_PlayerbaseTypes[pattern].distance, - g_PlayerbaseTypes[pattern].groupedDistance, - randomAngle()); + let pattern = g_MapSettings.TeamPlacement; + createBases( + ...playerPlacementByPattern( + pattern, + fractionToTiles(randFloat(0.2, 0.35)), + fractionToTiles(randFloat(0.08, 0.1)), + randomAngle(), + undefined), + undefined); } 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 27942) +++ ps/trunk/binaries/data/mods/public/maps/random/harbor.js (revision 27943) @@ -1,410 +1,418 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen-common"); 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 [playerIDs, playerPosition] = createBasesByPattern("radial", fractionToTiles(0.38), fractionToTiles(0.05), startAngle); +const [playerIDs, playerPosition] = + createBases( + ...playerPlacementByPattern( + "radial", + fractionToTiles(0.38), + fractionToTiles(0.05), + startAngle, + undefined), + undefined); Engine.SetProgress(20); addCenterLake(); Engine.SetProgress(30); if (mapSize >= 192) { 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], [1]), 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() { for (let position of playerPosition) { 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/arctic") 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 27942) +++ ps/trunk/binaries/data/mods/public/maps/random/hells_pass.js (revision 27943) @@ -1,332 +1,339 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen-common"); 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(); -createBasesByPattern("line", fractionToTiles(0.2), fractionToTiles(0.08), startAngle); +createBases( + ...playerPlacementByPattern( + "line", + fractionToTiles(0.2), + fractionToTiles(0.08), + startAngle, + undefined), + undefined); 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/arctic") 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 27942) +++ ps/trunk/binaries/data/mods/public/maps/random/lions_den.js (revision 27943) @@ -1,567 +1,574 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen-common"); 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); -createBasesByPattern("radial", fractionToTiles(0.4), fractionToTiles(randFloat(0.05, 0.1)), startAngle); +createBases( + ...playerPlacementByPattern( + "radial", + fractionToTiles(0.4), + fractionToTiles(randFloat(0.05, 0.1)), + startAngle, + undefined), + undefined); 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/arctic") { middle = g_Terrains.tier2Terrain; lower = g_Terrains.tier1Terrain; } if (currentBiome() == "generic/alpine") { middle = g_Terrains.shore; lower = g_Terrains.tier4Terrain; } if (currentBiome() == "generic/aegean") { middle = g_Terrains.tier1Terrain; lower = g_Terrains.forestFloor1; } if (currentBiome() == "generic/savanna") { middle = g_Terrains.tier2Terrain; lower = g_Terrains.tier4Terrain; } if (currentBiome() == "generic/india" || 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 DiskPlacer(fractionToTiles(0.29), 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 DiskPlacer(fractionToTiles(0.21), 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/rmgen-common/player.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen-common/player.js (revision 27942) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen-common/player.js (revision 27943) @@ -1,964 +1,989 @@ /** * @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", "StartingAnimal", "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 + (count - 1) / 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; // TODO: should prevent trees inside walls // When fixing, remove the DeleteUponConstruction flag from template_gaia_flora.xml 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, 6, BUILDING_ORIENTATION); 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 DiskPlacer(5, 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 placePlayerBaseStartingAnimal(args) { let [get, basePosition, baseResourceConstraint] = getPlayerBaseArgs(args); const template = get("template", "gaia/fauna_chicken"); const count = template === "gaia/fauna_chicken" ? 5 : Math.round(5 * (Engine.GetTemplate("gaia/fauna_chicken").ResourceSupply.Max / Engine.GetTemplate(get("template")).ResourceSupply.Max)) 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( template, get("minGroupCount", count), get("maxGroupCount", count), get("minGroupDistance", 0), get("maxGroupDistance", 2)) ], true, args.BaseResourceClass, position), 0, baseResourceConstraint)) { success = true; break; } } if (!success) { error("Could not place startingAnimal 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 StaticConstraint(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).Treasure.Resources[resourceType])); 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).length) { 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) { let prime = []; for (let i = 0; i < Math.floor(playerIDs.length / 2); ++i) { prime.push(playerIDs[i]); prime.push(playerIDs[playerIDs.length - 1 - i]); } if (playerIDs.length % 2) prime.push(playerIDs[Math.floor(playerIDs.length / 2)]); return prime; } function primeSortAllPlayers() { return primeSortPlayers(sortAllPlayers()); } /* * Separates playerIDs into two arrays such that teammates are in the same array, * unless everyone's on the same team in which case they'll be split in half. */ function partitionPlayers(playerIDs) { let teamIDs = Array.from(new Set(playerIDs.map(getPlayerTeam))); let teams = teamIDs.map(teamID => playerIDs.filter(playerID => getPlayerTeam(playerID) == teamID)); if (teamIDs.indexOf(-1) != -1) teams = teams.concat(teams.splice(teamIDs.indexOf(-1), 1)[0].map(playerID => [playerID])); if (teams.length == 1) { let idx = Math.floor(teams[0].length / 2); teams = [teams[0].slice(idx), teams[0].slice(0, idx)]; } teams.sort((a, b) => b.length - a.length); // Use the greedy algorithm: add the next team to the side with fewer players return teams.reduce(([east, west], team) => east.length > west.length ? [east, west.concat(team)] : [east.concat(team), west], [[], []]); } /** * 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); } /** + * Determine player starting positions based on the specified pattern. + */ +function playerPlacementByPattern(patternName, distance = undefined, groupedDistance = undefined, angle = undefined, center = undefined) +{ + if (patternName === undefined) + patternName = g_MapSettings.TeamPlacement; + + switch (patternName) + { + case "radial": + return playerPlacementCircle(distance, angle, center); + case "river": + return playerPlacementRiver(angle, distance, center); + case "line": + return placeLine(getTeamsArray(), distance, groupedDistance, angle); + case "stronghold": + return placeStronghold(getTeamsArray(), distance, groupedDistance, angle); + case "randomGroup": + return playerPlacementRandom(sortAllPlayers(), undefined); + default: + throw new Error("Unknown placement pattern: " + patternName); + } +} + +/** * 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 equally spaced along an arc. */ function playerPlacementArc(playerIDs, center, radius, startAngle, endAngle) { return distributePointsOnCircularSegment( playerIDs.length + 2, endAngle - startAngle, startAngle, radius, center )[0].slice(1, -1).map(p => p.round()); } /** * Returns player starting positions located on two symmetrically placed arcs, with teammates placed on the same arc. */ function playerPlacementArcs(playerIDs, center, radius, mapAngle, startAngle, endAngle) { let [east, west] = partitionPlayers(playerIDs); let eastPosition = playerPlacementArc(east, center, radius, mapAngle + startAngle, mapAngle + endAngle); let westPosition = playerPlacementArc(west, center, radius, mapAngle - startAngle, mapAngle - endAngle); return playerIDs.map(playerID => east.indexOf(playerID) != -1 ? eastPosition[east.indexOf(playerID)] : westPosition[west.indexOf(playerID)]); } /** * 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 groupPlayersByArea(new Array(numPlayers).fill(0).map((_p, i) => i + 1), 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; } /** * 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 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; for (let p = 0; p < teamsArray[i].length; ++p) { playerIDs.push(teamsArray[i][p]); playerPosition.push(Vector2D.add(mapCenter, new Vector2D(safeDist + p * groupedDistance, 0).rotate(-teamAngle)).round()); } } 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 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; playerIDs.push(teamsArray[i][p]); playerPosition.push(Vector2D.add(teamPosition, new Vector2D(teamGroupDistance, 0).rotate(-angle)).round()); } } return [playerIDs, playerPosition]; } /** * Returns a random location for each player that meets the given constraints and * orders the playerIDs so that players become grouped by team. */ function playerPlacementRandom(playerIDs, constraints = undefined) { let locations = []; let attempts = 0; let resets = 0; let mapCenter = g_Map.getCenter(); let playerMinDistSquared = Math.square(fractionToTiles(0.25)); let borderDistance = fractionToTiles(0.08); let area = createArea(new MapBoundsPlacer(), undefined, new AndConstraint(constraints)); for (let i = 0; i < getNumPlayers(); ++i) { const position = pickRandom(area.getPoints()); if (!position) return undefined; // Minimum distance between initial bases must be a quarter of the map diameter if (locations.some(loc => loc.distanceToSquared(position) < playerMinDistSquared) || position.distanceToSquared(mapCenter) > Math.square(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) playerMinDistSquared *= 0.95; // 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) { 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 27942) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen2/setup.js (revision 27943) @@ -1,224 +1,232 @@ 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", "bluffIgnore", // performance improvement "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": { "getPosition": (distance, groupedDistance, startAngle) => placeLine(getTeamsArray(), distance, groupedDistance, startAngle), "distance": fractionToTiles(randFloat(0.2, 0.35)), "groupedDistance": fractionToTiles(randFloat(0.08, 0.1)), "walls": false }, "radial": { "getPosition": (distance, groupedDistance, startAngle) => playerPlacementCircle(distance, startAngle), "distance": fractionToTiles(randFloat(0.25, 0.35)), "groupedDistance": fractionToTiles(randFloat(0.08, 0.1)), "walls": true }, "randomGroup": { "getPosition": (distance, groupedDistance, startAngle) => playerPlacementRandom(sortAllPlayers()) || playerPlacementCircle(distance, startAngle), "distance": fractionToTiles(randFloat(0.25, 0.35)), "groupedDistance": fractionToTiles(randFloat(0.08, 0.1)), "walls": true }, "stronghold": { "getPosition": (distance, groupedDistance, startAngle) => placeStronghold(getTeamsArray(), distance, groupedDistance, startAngle), "distance": fractionToTiles(randFloat(0.2, 0.35)), "groupedDistance": fractionToTiles(randFloat(0.08, 0.1)), "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", "randomGroup" * @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 createBasesByPattern(type, distance, groupedDistance, startAngle) { - return createBases(...g_PlayerbaseTypes[type].getPosition(distance, groupedDistance, startAngle), g_PlayerbaseTypes[type].walls); + error("createBasesByPattern() has been deprecated. Use playerPlacementByPattern() instead."); + return createBases( + ...playerPlacementByPattern( + type, // patternName + distance, // distance + groupedDistance, + startAngle, // angle + undefined), // center + undefined); // walls } function createBases(playerIDs, playerPosition, walls) { g_Map.log("Creating bases"); for (let i = 0; i < getNumPlayers(); ++i) createBase(playerIDs[i], playerPosition[i], walls); 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(playerID, playerPosition, walls) { placePlayerBase({ "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) ] }, "StartingAnimal": { "template": g_Gaia.startingAnimal }, "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 } }); } /** * 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 27942) +++ ps/trunk/binaries/data/mods/public/maps/random/stronghold.js (revision 27943) @@ -1,266 +1,274 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen-common"); 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); -const [playerIDs, playerPosition] = createBasesByPattern("stronghold", fractionToTiles(randFloat(0.2, 0.35)), fractionToTiles(randFloat(0.05, 0.1)), randomAngle()); +const [playerIDs, playerPosition] = + createBases( + ...playerPlacementByPattern( + "stronghold", + fractionToTiles(randFloat(0.2, 0.35)), + fractionToTiles(randFloat(0.05, 0.1)), + randomAngle(), + undefined), + undefined); markPlayerAvoidanceArea(playerPosition, defaultPlayerBaseRadius()); 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 } ])); if (!isNomad()) createBluffsPassages(playerPosition); 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();