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/african_plains.json =================================================================== --- binaries/data/mods/public/maps/random/african_plains.json +++ binaries/data/mods/public/maps/random/african_plains.json @@ -5,6 +5,7 @@ "Description" : "The central region of the vast continent of Africa, birthplace of humanity. Players start in a lush area teeming with vegetation and wildlife.", "SupportedBiomes": ["generic/savanna", "generic/sahara", "generic/nubia"], "Preview" : "african_plains.png", - "CircularMap" : true + "CircularMap" : true, + "EvenPlayerSpacing" : true } } Index: binaries/data/mods/public/maps/random/african_plains_tg.json =================================================================== --- /dev/null +++ binaries/data/mods/public/maps/random/african_plains_tg.json @@ -0,0 +1,11 @@ +{ + "settings" : { + "Name" : "African Plains TG", + "Script" : "african_plains.js", + "Description" : "The central region of the vast continent of Africa, birthplace of humanity. Players start in a lush area teeming with vegetation and wildlife; teammates are placed more closely together.", + "SupportedBiomes": ["generic/savanna", "generic/sahara", "generic/nubia"], + "Preview" : "african_plains.png", + "CircularMap" : true, + "EvenPlayerSpacing" : false + } +} 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 @@ -54,33 +54,65 @@ var clFood = g_Map.createTileClass(); var clBaseResource = g_Map.createTileClass(); -placePlayerBases({ - "PlayerPlacement": playerPlacementCircle(fractionToTiles(0.35)), - "PlayerTileClass": clPlayer, - "BaseResourceClass": clBaseResource, - "CityPatch": { - "outerTerrain": tRoadWild, - "innerTerrain": tRoad - }, - "Chicken": { - }, - "Berries": { - "template": oFruitBush - }, - "Mines": { - "types": [ - { "template": oMetalLarge }, - { "template": oStoneLarge } - ] - }, - "Trees": { - "template": oTree1, - "count": 5 - }, - "Decoratives": { - "template": aGrassShort - } -}); +if (g_MapSettings.EvenPlayerSpacing) { + placePlayerBases({ + "PlayerPlacement": playerPlacementCircle(fractionToTiles(0.35)), + "PlayerTileClass": clPlayer, + "BaseResourceClass": clBaseResource, + "CityPatch": { + "outerTerrain": tRoadWild, + "innerTerrain": tRoad + }, + "Chicken": { + }, + "Berries": { + "template": oFruitBush + }, + "Mines": { + "types": [ + { "template": oMetalLarge }, + { "template": oStoneLarge } + ] + }, + "Trees": { + "template": oTree1, + "count": 5 + }, + "Decoratives": { + "template": aGrassShort + } + }); +} +else { + placePlayerBases({ + "PlayerPlacement": [sortAllPlayers(), ...playerPlacementMultiArcs(fractionToTiles(0.35), 0, 0.75)], + "PlayerTileClass": clPlayer, + "BaseResourceClass": clBaseResource, + "CityPatch": { + "outerTerrain": tRoadWild, + "innerTerrain": tRoad + }, + "Chicken": { + }, + "Berries": { + "template": oFruitBush + }, + "Mines": { + "types": [ + { "template": oMetalLarge }, + { "template": oStoneLarge } + ] + }, + "Trees": { + "template": oTree1, + "count": 5 + }, + "Decoratives": { + "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, + "EvenPlayerSpacing" : true } } Index: binaries/data/mods/public/maps/random/mainland_tg.json =================================================================== --- /dev/null +++ binaries/data/mods/public/maps/random/mainland_tg.json @@ -0,0 +1,12 @@ +{ + "settings" : { + "Name" : "Mainland TG", + "Script" : "mainland.js", + "Description" : "A typical map without any water; teammates are placed more closely together.", + "Preview" : "mainland.png", + "Keywords": ["multiplayer"], + "SupportedBiomes": "generic/", + "CircularMap" : true, + "EvenPlayerSpacing" : false + } +} 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); +}