Index: binaries/data/mods/public/maps/random/empire.js =================================================================== --- binaries/data/mods/public/maps/random/empire.js +++ binaries/data/mods/public/maps/random/empire.js @@ -15,7 +15,7 @@ Engine.SetProgress(10); -const teamsArray = getTeamsArray(); +const teams = getTeams(); const startAngle = randomAngle(); createBasesByPattern("stronghold", fractionToTiles(0.37), fractionToTiles(0.04), startAngle); Engine.SetProgress(20); @@ -23,10 +23,10 @@ // Change the starting angle and add the players again var rotation = Math.PI; -if (teamsArray.length == 2) +if (teams.size == 2) rotation = Math.PI / 2; -if (teamsArray.length == 4) +if (teams.size == 4) rotation = 5/4 * Math.PI; createBasesByPattern("stronghold", fractionToTiles(0.15), fractionToTiles(0.04), startAngle + rotation); Index: binaries/data/mods/public/maps/random/hells_pass.js =================================================================== --- binaries/data/mods/public/maps/random/hells_pass.js +++ binaries/data/mods/public/maps/random/hells_pass.js @@ -20,7 +20,7 @@ Engine.SetProgress(10); -const teamsArray = getTeamsArray(); +const teams = getTeams(); const startAngle = randomAngle(); createBasesByPattern("line", fractionToTiles(0.2), fractionToTiles(0.08), startAngle); Engine.SetProgress(20); @@ -260,7 +260,7 @@ if (currentBiome() == "generic/autumn") spineTerrain = g_Terrains.tier4Terrain; - let spineCount = isNomad() ? randIntInclusive(1, 4) : teamsArray.length; + let spineCount = isNomad() ? randIntInclusive(1, 4) : teams.size; for (let i = 0; i < spineCount; ++i) { Index: binaries/data/mods/public/maps/random/island_stronghold.js =================================================================== --- binaries/data/mods/public/maps/random/island_stronghold.js +++ binaries/data/mods/public/maps/random/island_stronghold.js @@ -67,104 +67,100 @@ var startAngle = randomAngle(); -var teams = getTeamsArray(); -var numTeams = teams.filter(team => team).length; +var teams = getTeams(); +var numTeams = teams.size; var teamPosition = distributePointsOnCircle(numTeams, startAngle, fractionToTiles(0.3), mapCenter)[0]; var teamRadius = fractionToTiles(0.05); -var teamNo = 0; - -g_Map.log("Creating player islands and bases"); - -for (let i = 0; i < teams.length; ++i) +if (!isNomad()) { - if (!teams[i] || isNomad()) - continue; - - ++teamNo; + g_Map.log("Creating player islands and bases"); - let [playerPosition, playerAngle] = distributePointsOnCircle(teams[i].length, startAngle + 2 * Math.PI / teams[i].length, teamRadius, teamPosition[i]); - playerPosition.forEach(position => position.round()); - - for (let p = 0; p < teams[i].length; ++p) + for (const [team, players] of teams) { - addCivicCenterAreaToClass(playerPosition[p], clPlayer); + let [playerPosition, playerAngle] = distributePointsOnCircle(players.length, startAngle + 2 * Math.PI / players.length, teamRadius, teamPosition[i]); + playerPosition.forEach(position => position.round()); - createArea( - new ChainPlacer(2, Math.floor(scaleByMapSize(5, 11)), Math.floor(scaleByMapSize(60, 250)), Infinity, playerPosition[p], Infinity, [defaultPlayerBaseRadius() * 3/4]), - [ - new TerrainPainter(tMainTerrain), - new SmoothElevationPainter(ELEVATION_SET, heightLand, 2), - new TileClassPainter(clLand) - ]); + for (let p = 0; p < players.length; ++p) + { + addCivicCenterAreaToClass(playerPosition[p], clPlayer); - placeCivDefaultStartingEntities(playerPosition[p], teams[i][p], false); - } + createArea( + new ChainPlacer(2, Math.floor(scaleByMapSize(5, 11)), Math.floor(scaleByMapSize(60, 250)), Infinity, playerPosition[p], Infinity, [defaultPlayerBaseRadius() * 3 / 4]), + [ + new TerrainPainter(tMainTerrain), + new SmoothElevationPainter(ELEVATION_SET, heightLand, 2), + new TileClassPainter(clLand) + ]); - let mineAngle = randFloat(-1, 1) * Math.PI / teams[i].length; - let mines = [ - { "template": oMetalLarge, "angle": mineAngle }, - { "template": oStoneLarge, "angle": mineAngle + Math.PI / 4 } - ]; + placeCivDefaultStartingEntities(playerPosition[p], players[p], false); + } - // Mines - for (let p = 0; p < teams[i].length; ++p) - for (let mine of mines) + let mineAngle = randFloat(-1, 1) * Math.PI / players.length; + let mines = [ + { "template": oMetalLarge, "angle": mineAngle }, + { "template": oStoneLarge, "angle": mineAngle + Math.PI / 4 } + ]; + + // Mines + for (let p = 0; p < players.length; ++p) + for (let mine of mines) + { + let position = Vector2D.add(playerPosition[p], new Vector2D(g_InitialMineDistance, 0).rotate(-playerAngle[p] - mine.angle)); + createObjectGroup( + new SimpleGroup([new SimpleObject(mine.template, 1, 1, 0, 4)], true, clBaseResource, position), + 0, + [avoidClasses(clBaseResource, 4, clPlayer, 4), stayClasses(clLand, 5)]); + } + + // Trees + for (let p = 0; p < players.length; ++p) { - let position = Vector2D.add(playerPosition[p], new Vector2D(g_InitialMineDistance, 0).rotate(-playerAngle[p] - mine.angle)); - createObjectGroup( - new SimpleGroup([new SimpleObject(mine.template, 1, 1, 0, 4)], true, clBaseResource, position), - 0, - [avoidClasses(clBaseResource, 4, clPlayer, 4), stayClasses(clLand, 5)]); + let tries = 10; + for (let x = 0; x < tries; ++x) + { + let tAngle = playerAngle[p] + randFloat(-1, 1) * 2 * Math.PI / players.length; + let treePosition = Vector2D.add(playerPosition[p], new Vector2D(16, 0).rotate(-tAngle)).round(); + if (createObjectGroup( + new SimpleGroup([new SimpleObject(oTree2, g_InitialTrees, g_InitialTrees, 0, 7)], true, clBaseResource, treePosition), + 0, + [avoidClasses(clBaseResource, 4, clPlayer, 4), stayClasses(clLand, 4)])) + break; + } } - // Trees - for (let p = 0; p < teams[i].length; ++p) - { - let tries = 10; - for (let x = 0; x < tries; ++x) + for (let p = 0; p < players.length; ++p) + placePlayerBaseBerries({ + "template": oFruitBush, + "playerID": players[p], + "playerPosition": playerPosition[p], + "BaseResourceClass": clBaseResource, + "baseResourceConstraint": new AndConstraint([avoidClasses(clPlayer, 4), stayClasses(clLand, 5)]) + }); + + for (let p = 0; p < players.length; ++p) + placePlayerBaseStartingAnimal({ + "playerID": players[p], + "playerPosition": playerPosition[p], + "BaseResourceClass": clBaseResource, + "baseResourceConstraint": new AndConstraint([avoidClasses(clPlayer, 4), stayClasses(clLand, 5)]) + }); + + // Huntable animals + for (let p = 0; p < players.length; ++p) { - let tAngle = playerAngle[p] + randFloat(-1, 1) * 2 * Math.PI / teams[i].length; - let treePosition = Vector2D.add(playerPosition[p], new Vector2D(16, 0).rotate(-tAngle)).round(); - if (createObjectGroup( - new SimpleGroup([new SimpleObject(oTree2, g_InitialTrees, g_InitialTrees, 0, 7)], true, clBaseResource, treePosition), + createObjectGroup( + new SimpleGroup([new SimpleObject(oMainHuntableAnimal, 2 * numPlayers / numTeams, 2 * numPlayers / numTeams, 0, Math.floor(fractionToTiles(0.2)))], true, clBaseResource, teamPosition[i]), 0, - [avoidClasses(clBaseResource, 4, clPlayer, 4), stayClasses(clLand, 4)])) - break; - } - } + [avoidClasses(clBaseResource, 2, clPlayer, 10), stayClasses(clLand, 5)]); - for (let p = 0; p < teams[i].length; ++p) - placePlayerBaseBerries({ - "template": oFruitBush, - "playerID": teams[i][p], - "playerPosition": playerPosition[p], - "BaseResourceClass": clBaseResource, - "baseResourceConstraint": new AndConstraint([avoidClasses(clPlayer, 4), stayClasses(clLand, 5)]) - }); - - for (let p = 0; p < teams[i].length; ++p) - placePlayerBaseStartingAnimal({ - "playerID": teams[i][p], - "playerPosition": playerPosition[p], - "BaseResourceClass": clBaseResource, - "baseResourceConstraint": new AndConstraint([avoidClasses(clPlayer, 4), stayClasses(clLand, 5)]) - }); - - // Huntable animals - for (let p = 0; p < teams[i].length; ++p) - { - createObjectGroup( - new SimpleGroup([new SimpleObject(oMainHuntableAnimal, 2 * numPlayers / numTeams, 2 * numPlayers / numTeams, 0, Math.floor(fractionToTiles(0.2)))], true, clBaseResource, teamPosition[i]), - 0, - [avoidClasses(clBaseResource, 2, clPlayer, 10), stayClasses(clLand, 5)]); - - createObjectGroup( - new SimpleGroup( - [new SimpleObject(oSecondaryHuntableAnimal, 4 * numPlayers / numTeams, 4 * numPlayers / numTeams, 0, Math.floor(fractionToTiles(0.2)))], - true, clBaseResource, teamPosition[i]), - 0, - [avoidClasses(clBaseResource, 2, clPlayer, 10), stayClasses(clLand, 5)]); + createObjectGroup( + new SimpleGroup( + [new SimpleObject(oSecondaryHuntableAnimal, 4 * numPlayers / numTeams, 4 * numPlayers / numTeams, 0, Math.floor(fractionToTiles(0.2)))], + true, clBaseResource, teamPosition[i]), + 0, + [avoidClasses(clBaseResource, 2, clPlayer, 10), stayClasses(clLand, 5)]); + } } } 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 @@ -58,6 +58,44 @@ return g_MapSettings.PlayerData[playerID].Team; } +/** + * Get all the teams with the player IDs. + * @returns {Map} Map of team IDs and player IDs. + */ +function getTeams() +{ + const teams = new Map(); + const individualPlayers = []; + let highestTeam = -1; + + for (const playerID of sortAllPlayers()) + { + const team = getPlayerTeam(playerID); + highestTeam = Math.max(highestTeam, team); + + if (team === -1) + { + individualPlayers.push(playerID); + continue; + } + + if (teams.has(team)) + { + teams.get(team).push(playerID); + continue; + } + + teams.set(team, [playerID]); + } + + for (const playerID of individualPlayers) + { + teams.set(++highestTeam, [playerID]); + } + + return teams; +} + /** * Gets the default starting entities for the civ of the given player, as defined by the civ file. */ @@ -507,15 +545,11 @@ /** * Randomize playerIDs but sort by team. * - * @returns {Array} - every item is an array of player indices + * @returns {number[]} PlayerIDs sorted by team. */ function sortAllPlayers() { - let playerIDs = []; - for (let i = 0; i < getNumPlayers(); ++i) - playerIDs.push(i+1); - - return sortPlayers(playerIDs); + return sortPlayers(new Array(getNumPlayers()).fill(0).map((p, i) => i + 1)); } /** 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 @@ -56,7 +56,7 @@ var g_PlayerbaseTypes = { "line": { - "getPosition": (distance, groupedDistance, startAngle) => placeLine(getTeamsArray(), distance, groupedDistance, startAngle), + "getPosition": (distance, groupedDistance, startAngle) => placeLine(getTeams(), distance, groupedDistance, startAngle), "distance": fractionToTiles(randFloat(0.2, 0.35)), "groupedDistance": fractionToTiles(randFloat(0.08, 0.1)), "walls": false @@ -74,7 +74,7 @@ "walls": true }, "stronghold": { - "getPosition": (distance, groupedDistance, startAngle) => placeStronghold(getTeamsArray(), distance, groupedDistance, startAngle), + "getPosition": (distance, groupedDistance, startAngle) => placeStronghold(getTeams(), distance, groupedDistance, startAngle), "distance": fractionToTiles(randFloat(0.2, 0.35)), "groupedDistance": fractionToTiles(randFloat(0.08, 0.1)), "walls": false @@ -205,67 +205,37 @@ }); } -/** - * 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); -} - /** * Place teams in a line-pattern. * - * @param {Array} playerIDs - typically randomized indices of players of a single team + * @param {Map} teams - map of team IDs with randomized player IDs * @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) +function placeLine(teams, distance, groupedDistance, startAngle) { let playerIDs = []; let playerPosition = []; - let mapCenter = g_Map.getCenter(); - let dist = fractionToTiles(0.45); + const mapCenter = g_Map.getCenter(); + const dist = fractionToTiles(0.45); - for (let i = 0; i < teamsArray.length; ++i) + let i = 1; + for (const [team, players] of teams) { - var safeDist = distance; - if (distance + teamsArray[i].length * groupedDistance > dist) - safeDist = dist - teamsArray[i].length * groupedDistance; + let safeDist = distance; + if (distance + team.length * groupedDistance > dist) + safeDist = dist - team.length * groupedDistance; - var teamAngle = startAngle + (i + 1) * 2 * Math.PI / teamsArray.length; + const teamAngle = startAngle + (i++) * 2 * Math.PI / teams.size; - for (let p = 0; p < teamsArray[i].length; ++p) + for (let p = 0; p < players.length; ++p) { - playerIDs.push(teamsArray[i][p]); - playerPosition.push(Vector2D.add(mapCenter, new Vector2D(safeDist + p * groupedDistance, 0).rotate(-teamAngle)).round()); + playerIDs.push(players[p]); + playerPosition.push(new Vector2D(safeDist + p * groupedDistance, 0).rotate(-teamAngle).add(mapCenter).round()); } } @@ -275,40 +245,41 @@ /** * Place given players in a stronghold-pattern. * - * @param teamsArray - each item is an array of playerIDs placed per stronghold + * @param {Map} teams - map of team IDs with randomized player IDs * @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) +function placeStronghold(teams, distance, groupedDistance, startAngle) { - var mapCenter = g_Map.getCenter(); + const mapCenter = g_Map.getCenter(); let playerIDs = []; let playerPosition = []; - for (let i = 0; i < teamsArray.length; ++i) + let i = 1; + for (const [team, players] of teams) { - var teamAngle = startAngle + (i + 1) * 2 * Math.PI / teamsArray.length; - var teamPosition = Vector2D.add(mapCenter, new Vector2D(distance, 0).rotate(-teamAngle)); - var teamGroupDistance = groupedDistance; + const teamAngle = startAngle + (i++) * 2 * Math.PI / teams.size; + const teamPosition = new Vector2D(distance, 0).rotate(-teamAngle).add(mapCenter); + let teamGroupDistance = groupedDistance; // If we have a team of above average size, make sure they're spread out - if (teamsArray[i].length > 4) + if (players.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) + if (players.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) + for (let p = 0; p < players.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()); + const angle = startAngle + (p + 1) * 2 * Math.PI / players.length; + playerIDs.push(players[p]); + playerPosition.push(new Vector2D(teamGroupDistance, 0).rotate(-angle).add(teamPosition).round()); } }