Index: binaries/data/mods/public/maps/random/guadalquivir_river.js =================================================================== --- binaries/data/mods/public/maps/random/guadalquivir_river.js +++ binaries/data/mods/public/maps/random/guadalquivir_river.js @@ -78,13 +78,19 @@ new TileClassPainter(clLand) ]); -var playerPosition = playerPlacementCustomAngle( - fractionToTiles(0.35), - continentCenter, - i => Math.PI * (-0.46 / numPlayers * (i + i % 2) - (i % 2) / 2))[0].map(pos => pos.rotateAround(startAngle, mapCenter)); +var playerIDs = sortAllPlayers(); +var playerPosition = playerPlacementArcs( + playerIDs, + continentCenterR, + fractionToTiles(0.35), + -startAngle + Math.PI * 0.15, + -startAngle - Math.PI * 0.5, + -startAngle - Math.PI * 0.5, + -startAngle - Math.PI * 1.15); + placePlayerBases({ - "PlayerPlacement": [primeSortAllPlayers(), playerPosition], + "PlayerPlacement": [playerIDs, playerPosition], "PlayerTileClass": clPlayer, "BaseResourceClass": clBaseResource, "Walls": false, Index: binaries/data/mods/public/maps/random/jebel_barkal.js =================================================================== --- binaries/data/mods/public/maps/random/jebel_barkal.js +++ binaries/data/mods/public/maps/random/jebel_barkal.js @@ -392,11 +392,16 @@ Engine.SetProgress(30); g_Map.log("Computing player locations"); -const playerIDs = primeSortAllPlayers(); -const playerPosition = playerPlacementCustomAngle( - fractionToTiles(0.38), - mapCenter, - i => Math.PI * (-0.42 / numPlayers * (i + i % 2) - (i % 2) / 2))[0]; +const playerIDs = sortAllPlayers(); +const playerCenter = new Vector2D(0, fractionToTiles(0.05)).rotate(-riverAngle).add(mapCenter).round(); +const playerPosition = playerPlacementArcs( + playerIDs, + playerCenter, + fractionToTiles(0.4), + riverAngle + Math.PI * 0.1, + riverAngle - Math.PI * 0.45, + riverAngle - Math.PI * 0.55, + riverAngle - Math.PI * 1.1); if (!isNomad()) { Index: binaries/data/mods/public/maps/random/pyrenean_sierra.js =================================================================== --- binaries/data/mods/public/maps/random/pyrenean_sierra.js +++ binaries/data/mods/public/maps/random/pyrenean_sierra.js @@ -159,11 +159,18 @@ } } +var playerIDs = sortAllPlayers(); +var playerPosition = playerPlacementArcs( + playerIDs, + mapCenter, + fractionToTiles(0.35), + oceanAngle + Math.PI * 0.1, + oceanAngle + Math.PI * 0.9, + oceanAngle - Math.PI * 0.1, + oceanAngle - Math.PI * 0.9); + 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: binaries/data/mods/public/maps/random/rmgen-common/player.js =================================================================== --- binaries/data/mods/public/maps/random/rmgen-common/player.js +++ binaries/data/mods/public/maps/random/rmgen-common/player.js @@ -534,6 +534,31 @@ return primeSortPlayers(sortAllPlayers()); } +/* + * Separates playerIDs into two arrays such that teammates are in the same array. + * This assumes that playerIDs already has teammates grouped together. + */ +function partitionPlayers(playerIDs) +{ + let teams = playerIDs.reduce((list, i) => { + let length = list.length; + let team = getPlayerTeam(i); + if (!length || team == -1 || team != getPlayerTeam(list[length - 1][0])) + list.push([i]); + else + list[length - 1].push(i); + return list; + }, []); + + // Use the greedy algorithm + teams.sort((a, b) => b.length - a.length); + return teams.reduce(([a, b], c) => { + if (a.length > b.length) + return [a, b.concat(c)]; + return [a.concat(c), b]; + }, [[], []]); +} + /** * Determine player starting positions on a circular pattern. */ @@ -565,6 +590,34 @@ } /** + * Returns player starting positions located on two arcs, with teammates placed on the same arc. + */ +function playerPlacementArcs(playerIDs, center, radius, startAngle1, endAngle1, startAngle2, endAngle2) +{ + let [east, west] = partitionPlayers(playerIDs); + let eastPosition = distributePointsOnCircularSegment( + east.length + 1, + endAngle1 - startAngle1, + startAngle1, + radius, + center + )[0].slice(1); + let westPosition = distributePointsOnCircularSegment( + west.length + 1, + endAngle2 - startAngle2, + startAngle2, + radius, + center + )[0].slice(1); + return playerIDs.map(id => { + let i = east.indexOf(id); + if (i != -1) + return eastPosition[i].round(); + return westPosition[west.indexOf(id)].round(); + }); +} + +/** * 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.