Index: binaries/data/mods/public/gui/gamesettings/attributes/TeamPlacement.js =================================================================== --- binaries/data/mods/public/gui/gamesettings/attributes/TeamPlacement.js +++ binaries/data/mods/public/gui/gamesettings/attributes/TeamPlacement.js @@ -57,6 +57,11 @@ "Name": translateWithContext("team placement", "Circle"), "Description": translate("Allied players are grouped and placed with opposing players on one circle spanning the map.") }, + { + "Id": "tight_arc", + "Name": translateWithContext("team placement", "Tight Arc"), + "Description": translate("Allied players are grouped closely together equidistant to opposing teams.") + }, { "Id": "line", "Name": translateWithContext("team placement", "Line"), Index: binaries/data/mods/public/maps/random/african_plains.js =================================================================== --- binaries/data/mods/public/maps/random/african_plains.js +++ binaries/data/mods/public/maps/random/african_plains.js @@ -68,41 +68,80 @@ var clFood = g_Map.createTileClass(); var clBaseResource = g_Map.createTileClass(); -placePlayerBases({ - "PlayerPlacement": playerPlacementCircle(fractionToTiles(0.35)), - "PlayerTileClass": clPlayer, - "BaseResourceClass": clBaseResource, - "CityPatch": { - "outerTerrain": tPrimary, - "innerTerrain": tCitytiles - }, - "Chicken": { - }, - "Berries": { - "template": oBerryBush - }, - "Mines": { - "types": [ - { - "template": oMetalLarge - }, - { - "type": "stone_formation", - "template": oStoneSmall, - "terrain": tDirt4 - } - ] - }, - "Trees": { - "template": oBaobab, - "count": scaleByMapSize(3, 12), - "minDistGroup": 2, - "maxDistGroup": 6, - "minDist": 15, - "maxDist": 16 - } - // No decoratives -}); +if (g_MapSettings.EvenPlayerSpacing) { + placePlayerBases({ + "PlayerPlacement": playerPlacementCircle(fractionToTiles(0.35)), + "PlayerTileClass": clPlayer, + "BaseResourceClass": clBaseResource, + "CityPatch": { + "outerTerrain": tPrimary, + "innerTerrain": tCitytiles + }, + "Chicken": { + }, + "Berries": { + "template": oBerryBush + }, + "Mines": { + "types": [ + { + "template": oMetalLarge + }, + { + "type": "stone_formation", + "template": oStoneSmall, + "terrain": tDirt4 + } + ] + }, + "Trees": { + "template": oBaobab, + "count": scaleByMapSize(3, 12), + "minDistGroup": 2, + "maxDistGroup": 6, + "minDist": 15, + "maxDist": 16 + } + // No decoratives + }); +} +else { + placePlayerBases({ + "PlayerPlacement": [sortAllPlayers(), ...playerPlacementMultiArcs(fractionToTiles(0.35), 0, 0.75)], + "PlayerTileClass": clPlayer, + "BaseResourceClass": clBaseResource, + "CityPatch": { + "outerTerrain": tPrimary, + "innerTerrain": tCitytiles + }, + "Chicken": { + }, + "Berries": { + "template": oBerryBush + }, + "Mines": { + "types": [ + { + "template": oMetalLarge + }, + { + "type": "stone_formation", + "template": oStoneSmall, + "terrain": tDirt4 + } + ] + }, + "Trees": { + "template": oBaobab, + "count": scaleByMapSize(3, 12), + "minDistGroup": 2, + "maxDistGroup": 6, + "minDist": 15, + "maxDist": 16 + } + // No decoratives + }); +} Engine.SetProgress(20); // The specificity of this map is a bunch of watering holes & hills making it somewhat cluttered. Index: binaries/data/mods/public/maps/random/mainland.js =================================================================== --- binaries/data/mods/public/maps/random/mainland.js +++ binaries/data/mods/public/maps/random/mainland.js @@ -55,7 +55,7 @@ var clBaseResource = g_Map.createTileClass(); placePlayerBases({ - "PlayerPlacement": playerPlacementCircle(fractionToTiles(0.35)), + "PlayerPlacement": g_MapSettings.TeamPlacement == "radial" ? playerPlacementCircle(fractionToTiles(0.35)) : [sortAllPlayers(), ...playerPlacementMultiArcs(fractionToTiles(0.35), 0, 0.75)], "PlayerTileClass": clPlayer, "BaseResourceClass": clBaseResource, "CityPatch": { @@ -81,6 +81,7 @@ "template": aGrassShort } }); + Engine.SetProgress(20); createBumps(avoidClasses(clPlayer, 20)); Index: binaries/data/mods/public/maps/random/mainland.json =================================================================== --- binaries/data/mods/public/maps/random/mainland.json +++ binaries/data/mods/public/maps/random/mainland.json @@ -6,6 +6,7 @@ "Preview" : "mainland.png", "Keywords": ["multiplayer"], "SupportedBiomes": "generic/", - "CircularMap" : true + "CircularMap" : true, + "TeamPlacements": ["radial", "tight_arc"] } } 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 @@ -843,3 +843,78 @@ return [playerIDs, startLocations]; } + +/** + * Place on an arc on a circle, 1 arc per team. Each team member is on the same arc. + * @param {Array[int]} playerIDs + * @param {float} center + * @param {float} radius + * @param {float} mapAngle + * @param {float} teamGapRatio Ratio difference between team gap and players on the same team Should be 0 to 1. + * e.g. 0.8 means the ratio team gap:team player gap is 8:2. n.b. < 0.5 means enemies are closer + * than team members are to each other + */ +function playerPlacementMultiArcs(radius, mapAngle, teamGapFrac) { + var playerIDs = sortAllPlayers(); + let playerTeams = playerIDs.map(getPlayerTeam); + let uniqueTeams = new Set(playerTeams); + let nTeams = uniqueTeams.size; + let nPlayers = playerIDs.length; + + let teamIntMap = {}; + let teamFreqPlayers = {}; + let teamPlayersIntMap = {}; + + // Shuffle team order. + let teamShuffle = function(length) { + let i = 0; + let array = Array.from(Array(length), () => i++); + for(let i = array.length - 1; i > 0; i--){ + const j = Math.round(Math.random() * (array.length-1)) + const temp = array[i] + array[i] = array[j] + array[j] = temp + } + return array; + }(nTeams); + + // Team to array (random) index map. + Array.from(uniqueTeams).map((val, i) => {teamIntMap[val] = teamShuffle[i];}); + // Player frequency in teams. + playerTeams.map((v) => teamFreqPlayers[v] ? teamFreqPlayers[v] += 1 : teamFreqPlayers[v] = 1); + // Team player int map. This is useful when positioning players on a team. + Array.from(uniqueTeams).map((val) => {teamPlayersIntMap[val] = 0;}); + + // I don't know at this point. Trust my brain. It's smarter than my brain. + // Something-something add the previous team player combos. + // It's some kind of "cumulative frequency" of player teams idk. + for (let key in teamPlayersIntMap) { + if (teamPlayersIntMap.hasOwnProperty(key)) { + for (let key2 in teamPlayersIntMap) { + if (teamPlayersIntMap.hasOwnProperty(key)) { + if (teamIntMap[key2] > teamIntMap[key]) { + teamPlayersIntMap[key2] += teamFreqPlayers[key]-1; + } + } + } + } + } + + const teamPlayerGapFrac = 1 - teamGapFrac; + + const totalGapCount = teamGapFrac*nTeams + teamPlayerGapFrac*(nPlayers-nTeams); + + const teamGapAngle = 2*Math.PI*teamGapFrac/totalGapCount; + const teamPlayerGapAngle = 2*Math.PI*teamPlayerGapFrac/totalGapCount; + + + function playerAngle(i) { + + return mapAngle + teamGapAngle*teamIntMap[playerTeams[i]] + teamPlayerGapAngle*((teamPlayersIntMap[playerTeams[i]]++)); + } + + return playerPlacementCustomAngle( + radius, + g_Map.getCenter(), + playerAngle); +} Index: binaries/data/mods/public/maps/random/rmgen2/setup.js =================================================================== --- binaries/data/mods/public/maps/random/rmgen2/setup.js +++ binaries/data/mods/public/maps/random/rmgen2/setup.js @@ -67,6 +67,12 @@ "groupedDistance": fractionToTiles(randFloat(0.08, 0.1)), "walls": true }, + "tight_arc": { + "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)),