Index: ps/trunk/binaries/data/mods/public/maps/random/guadalquivir_river.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/guadalquivir_river.js +++ ps/trunk/binaries/data/mods/public/maps/random/guadalquivir_river.js @@ -61,15 +61,15 @@ g_Map.log("Create the continent body"); var startAngle = randomAngle(); -var continentCenter = new Vector2D(fractionToTiles(0.5), fractionToTiles(0.7)); -var continentCenterR = continentCenter.clone().rotateAround(startAngle, mapCenter).round() +var continentCenter = new Vector2D(fractionToTiles(0.5), fractionToTiles(0.7)).rotateAround(startAngle, mapCenter).round(); + createArea( new ChainPlacer( 2, Math.floor(scaleByMapSize(5, 12)), Math.floor(scaleByMapSize(60, 700)), Infinity, - continentCenterR, + continentCenter, 0, [Math.floor(fractionToTiles(0.49))]), [ @@ -78,13 +78,17 @@ new TileClassPainter(clLand) ]); -var playerPosition = playerPlacementCustomAngle( - fractionToTiles(0.35), +var playerIDs = sortAllPlayers(); +var playerPosition = playerPlacementArcs( + playerIDs, continentCenter, - i => Math.PI * (-0.46 / numPlayers * (i + i % 2) - (i % 2) / 2))[0].map(pos => pos.rotateAround(startAngle, mapCenter)); + fractionToTiles(0.35), + -startAngle - 0.5 * Math.PI, + 0, + 0.65 * Math.PI); placePlayerBases({ - "PlayerPlacement": [primeSortAllPlayers(), playerPosition], + "PlayerPlacement": [playerIDs, playerPosition], "PlayerTileClass": clPlayer, "BaseResourceClass": clBaseResource, "Walls": false, Index: ps/trunk/binaries/data/mods/public/maps/random/jebel_barkal.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/jebel_barkal.js +++ ps/trunk/binaries/data/mods/public/maps/random/jebel_barkal.js @@ -183,7 +183,7 @@ const clMarket = g_Map.createTileClass(); const clDecorative = g_Map.createTileClass(); -const riverAngle = Math.PI * 0.05; +const riverAngle = 0.05 * Math.PI; const hillRadius = scaleByMapSize(40, 120); const positionPyramids = new Vector2D(fractionToTiles(0.15), fractionToTiles(0.75)); @@ -396,11 +396,14 @@ Engine.SetProgress(30); g_Map.log("Computing player locations"); -const playerIDs = primeSortAllPlayers(); -const playerPosition = playerPlacementCustomAngle( - fractionToTiles(0.38), +const playerIDs = sortAllPlayers(); +const playerPosition = playerPlacementArcs( + playerIDs, mapCenter, - i => Math.PI * (-0.42 / numPlayers * (i + i % 2) - (i % 2) / 2))[0]; + fractionToTiles(0.38), + riverAngle - 0.5 * Math.PI, + 0.05 * Math.PI, + 0.55 * Math.PI); if (!isNomad()) { Index: ps/trunk/binaries/data/mods/public/maps/random/pyrenean_sierra.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/pyrenean_sierra.js +++ ps/trunk/binaries/data/mods/public/maps/random/pyrenean_sierra.js @@ -159,11 +159,17 @@ } } +var playerIDs = sortAllPlayers(); +var playerPosition = playerPlacementArcs( + playerIDs, + mapCenter, + fractionToTiles(0.35), + oceanAngle, + 0.1 * Math.PI, + 0.9 * Math.PI); + placePlayerBases({ - "PlayerPlacement": [primeSortAllPlayers(), ...playerPlacementCustomAngle( - fractionToTiles(0.35), - mapCenter, - i => oceanAngle + Math.PI * (i % 2 ? 1 : -1) * ((1/2 + 1/3 * (2/numPlayers * (i + 1 - i % 2) - 1))))], + "PlayerPlacement": [playerIDs, playerPosition], "PlayerTileClass": clPlayer, "BaseResourceClass": clBaseResource, "CityPatch": { 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 +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen-common/player.js @@ -537,6 +537,33 @@ 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], + [[], []]); +} + /** * Determine player starting positions on a circular pattern. */ @@ -568,6 +595,33 @@ } /** + * 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.