Index: binaries/data/mods/public/gamesettings/attributes/TeamPlacement.js =================================================================== --- binaries/data/mods/public/gamesettings/attributes/TeamPlacement.js +++ binaries/data/mods/public/gamesettings/attributes/TeamPlacement.js @@ -43,7 +43,7 @@ if (this.settings.map.map === "random" || this.value !== "random") return false; - this.value = pickRandom(this.available).Id; + this.value = pickRandom(Array.from(this.available)); return true; } }; @@ -51,14 +51,24 @@ GameSettings.prototype.Attributes.TeamPlacement.prototype.StartingPositions = [ { - "Id": "radial", - "Name": translateWithContext("team placement", "Circle"), + "Id": "groupedCircle", + "Name": translateWithContext("team placement", "Grouped circle"), "Description": translate("Allied players are grouped and placed with opposing players on one circle spanning the map.") }, { - "Id": "line", - "Name": translateWithContext("team placement", "Line"), - "Description": translate("Allied players are placed in a linear pattern."), + "Id": "alternatingCircle", + "Name": translateWithContext("team placement", "Alternating circle"), + "Description": translate("Players are placed alternating with opposing players on one circle spanning the map.") + }, + { + "Id": "river", + "Name": translateWithContext("team placement", "River"), + "Description": translate("Players are placed on two parallel lines. Teams are mostly grouped.") + }, + { + "Id": "groupedLines", + "Name": translateWithContext("team placement", "Grouped lines"), + "Description": translate("Allied players are grouped together in a linear pattern."), }, { "Id": "randomGroup", Index: binaries/data/mods/public/maps/random/ambush.js =================================================================== --- binaries/data/mods/public/maps/random/ambush.js +++ binaries/data/mods/public/maps/random/ambush.js @@ -22,14 +22,16 @@ var playerPosition; if (!isNomad()) { - let pattern = g_MapSettings.TeamPlacement || pickRandom(Object.keys(g_PlayerbaseTypes)); - - [playerIDs, playerPosition] = - createBasesByPattern( - pattern, - g_PlayerbaseTypes[pattern].distance, - g_PlayerbaseTypes[pattern].groupedDistance, - randomAngle()); + let pattern = g_MapSettings.TeamPlacement; + const [playerIDs, playerPosition] = + createBases( + ...playerPlacementByPattern( + pattern, + fractionToTiles(randFloat(0.2, 0.35)), + fractionToTiles(randFloat(0.08, 0.1)), + randomAngle(), + undefined), + undefined); markPlayerAvoidanceArea(playerPosition, defaultPlayerBaseRadius()); } Index: binaries/data/mods/public/maps/random/ambush.json =================================================================== --- binaries/data/mods/public/maps/random/ambush.json +++ binaries/data/mods/public/maps/random/ambush.json @@ -6,6 +6,6 @@ "Preview" : "ambush.png", "SupportedBiomes": "generic/", "CircularMap" : true, - "TeamPlacements": ["radial", "line", "randomGroup", "stronghold"] + "TeamPlacements": ["groupedCircle", "groupedLines", "randomGroup", "stronghold"] } } Index: binaries/data/mods/public/maps/random/bahrain.js =================================================================== --- binaries/data/mods/public/maps/random/bahrain.js +++ binaries/data/mods/public/maps/random/bahrain.js @@ -147,7 +147,7 @@ g_Map.log("Placing players"); let [playerIDs, playerPosition] = createBases( ...playerPlacementRandom( - sortAllPlayers(), + getPlayerIDs(), [ avoidClasses(g_TileClasses.island, 5), stayClasses(g_TileClasses.land, defaultPlayerBaseRadius() / 2) Index: binaries/data/mods/public/maps/random/belgian_uplands.js =================================================================== --- binaries/data/mods/public/maps/random/belgian_uplands.js +++ binaries/data/mods/public/maps/random/belgian_uplands.js @@ -106,7 +106,7 @@ new TileClassPainter(tHeightRange), new HeightConstraint(lowerHeightLimit, upperHeightLimit)); - let players = area && playerPlacementRandom(sortAllPlayers(), stayClasses(tHeightRange, 15), true); + let players = area && playerPlacementRandom(getPlayerIDs(), stayClasses(tHeightRange, 15), true); if (players) { [playerIDs, playerPosition] = players; Index: binaries/data/mods/public/maps/random/corsica.js =================================================================== --- binaries/data/mods/public/maps/random/corsica.js +++ binaries/data/mods/public/maps/random/corsica.js @@ -198,7 +198,6 @@ Engine.SetProgress(30); g_Map.log("Determining player locations"); -var playerIDs = sortAllPlayers(); var playerPosition = []; var playerAngle = []; var p = 0; Index: binaries/data/mods/public/maps/random/dodecanese.js =================================================================== --- binaries/data/mods/public/maps/random/dodecanese.js +++ binaries/data/mods/public/maps/random/dodecanese.js @@ -97,7 +97,7 @@ const bridgeLength = 16; const maxBridges = scaleByMapSize(2, 12); -var [playerIDs, playerPosition] = playerPlacementRandom(sortAllPlayers()); +var [playerIDs, playerPosition] = playerPlacementRandom(getPlayerIDs()); g_Map.log("Creating player islands"); for (let position of playerPosition) 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 @@ -17,7 +17,14 @@ const teamsArray = getTeamsArray(); const startAngle = randomAngle(); -createBasesByPattern("stronghold", fractionToTiles(0.37), fractionToTiles(0.04), startAngle); +createBases( + ...playerPlacementByPattern( + "stronghold", + fractionToTiles(0.37), + fractionToTiles(0.04), + startAngle, + undefined), + undefined); Engine.SetProgress(20); // Change the starting angle and add the players again @@ -29,7 +36,14 @@ if (teamsArray.length == 4) rotation = 5/4 * Math.PI; -createBasesByPattern("stronghold", fractionToTiles(0.15), fractionToTiles(0.04), startAngle + rotation); +createBases( + ...playerPlacementByPattern( + "stronghold", + fractionToTiles(0.15), + fractionToTiles(0.04), + startAngle + rotation, + undefined), + undefined); Engine.SetProgress(40); addElements(shuffleArray([ Index: binaries/data/mods/public/maps/random/fields_of_meroe.js =================================================================== --- binaries/data/mods/public/maps/random/fields_of_meroe.js +++ binaries/data/mods/public/maps/random/fields_of_meroe.js @@ -173,7 +173,7 @@ [areaPassage]); } -var [playerIDs, playerPosition] = playerPlacementRandom(sortAllPlayers(), avoidClasses(clRiver, 15, clPlayer, 30)); +var [playerIDs, playerPosition] = playerPlacementRandom(getPlayerIDs(), avoidClasses(clRiver, 15, clPlayer, 30)); placePlayerBases({ "PlayerPlacement": [playerIDs, playerPosition], "BaseResourceClass": clBaseResource, Index: binaries/data/mods/public/maps/random/frontier.js =================================================================== --- binaries/data/mods/public/maps/random/frontier.js +++ binaries/data/mods/public/maps/random/frontier.js @@ -21,12 +21,16 @@ if (!isNomad()) { - let pattern = g_MapSettings.TeamPlacement || pickRandom(Object.keys(g_PlayerbaseTypes)); - createBasesByPattern( - pattern, - g_PlayerbaseTypes[pattern].distance, - g_PlayerbaseTypes[pattern].groupedDistance, - randomAngle()); + let pattern = g_MapSettings.TeamPlacement; + const [playerIDs, playerPosition] = + createBases( + ...playerPlacementByPattern( + pattern, + fractionToTiles(randFloat(0.2, 0.35)), + fractionToTiles(randFloat(0.08, 0.1)), + randomAngle(), + undefined), + undefined); } Engine.SetProgress(40); Index: binaries/data/mods/public/maps/random/frontier.json =================================================================== --- binaries/data/mods/public/maps/random/frontier.json +++ binaries/data/mods/public/maps/random/frontier.json @@ -6,6 +6,6 @@ "Preview" : "frontier.png", "SupportedBiomes": "generic/", "CircularMap" : true, - "TeamPlacements": ["radial", "line", "randomGroup", "stronghold"] + "TeamPlacements": ["groupedCircle", "groupedLines", "randomGroup", "stronghold"] } } Index: binaries/data/mods/public/maps/random/harbor.js =================================================================== --- binaries/data/mods/public/maps/random/harbor.js +++ binaries/data/mods/public/maps/random/harbor.js @@ -25,7 +25,15 @@ const mapCenter = g_Map.getCenter(); const startAngle = randomAngle(); -const [playerIDs, playerPosition] = createBasesByPattern("radial", fractionToTiles(0.38), fractionToTiles(0.05), startAngle); +const [playerIDs, playerPosition] = + createBases( + ...playerPlacementByPattern( + "groupedCircle", + fractionToTiles(0.38), + fractionToTiles(0.05), + startAngle, + undefined), + undefined); Engine.SetProgress(20); addCenterLake(); Index: binaries/data/mods/public/maps/random/hellas.js =================================================================== --- binaries/data/mods/public/maps/random/hellas.js +++ binaries/data/mods/public/maps/random/hellas.js @@ -79,7 +79,7 @@ var [minLandRatio, maxLandRatio] = mapStyles.filter(mapStyle => mapSize >= mapStyle.minMapSize).sort((a, b) => a.enabled - b.enabled).pop().landRatio; var [minCliffRatio, maxCliffRatio] = [maxLandRatio < 0.75 ? 0 : 0.08, 0.18]; -var playerIDs = sortAllPlayers(); +var playerIDs = getPlayerIDs(); var playerPosition; // Pick a random subset of the heightmap that meets the mapStyle and has space for all players 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 @@ -22,7 +22,14 @@ const teamsArray = getTeamsArray(); const startAngle = randomAngle(); -createBasesByPattern("line", fractionToTiles(0.2), fractionToTiles(0.08), startAngle); +createBases( + ...playerPlacementByPattern( + "groupedLines", + fractionToTiles(0.2), + fractionToTiles(0.08), + startAngle, + undefined), + undefined); Engine.SetProgress(20); placeBarriers(); Index: binaries/data/mods/public/maps/random/lions_den.js =================================================================== --- binaries/data/mods/public/maps/random/lions_den.js +++ binaries/data/mods/public/maps/random/lions_den.js @@ -25,7 +25,14 @@ Engine.SetProgress(10); -createBasesByPattern("radial", fractionToTiles(0.4), fractionToTiles(randFloat(0.05, 0.1)), startAngle); +createBases( + ...playerPlacementByPattern( + "groupedCircle", + fractionToTiles(0.4), + fractionToTiles(randFloat(0.05, 0.1)), + startAngle, + undefined), + undefined); Engine.SetProgress(20); createSunkenTerrain(); Index: binaries/data/mods/public/maps/random/lower_nubia.js =================================================================== --- binaries/data/mods/public/maps/random/lower_nubia.js +++ binaries/data/mods/public/maps/random/lower_nubia.js @@ -202,7 +202,7 @@ if (!isNomad()) { g_Map.log("Finding player locations"); - [playerIDs, playerPosition] = playerPlacementRandom(sortAllPlayers(), avoidClasses(clWater, scaleByMapSize(8, 12), clCliff, scaleByMapSize(8, 12))); + [playerIDs, playerPosition] = playerPlacementRandom(getPlayerIDs(), avoidClasses(clWater, scaleByMapSize(8, 12), clCliff, scaleByMapSize(8, 12))); g_Map.log("Flatten the initial CC area"); for (let position of playerPosition) 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,8 +54,20 @@ var clFood = g_Map.createTileClass(); var clBaseResource = g_Map.createTileClass(); + +const pattern = g_MapSettings.TeamPlacement; +const [playerIDs, playerPosition] = + playerPlacementByPattern( + pattern, + fractionToTiles(0.35), + fractionToTiles(0.1), + randomAngle(), + undefined); + +const walls = safeToPlaceWalls(playerPosition) + placePlayerBases({ - "PlayerPlacement": playerPlacementCircle(fractionToTiles(0.35)), + "PlayerPlacement": [playerIDs, playerPosition], "PlayerTileClass": clPlayer, "BaseResourceClass": clBaseResource, "CityPatch": { @@ -76,7 +88,8 @@ "Trees": { "template": oTree1, "count": 5 - } + }, + "Walls": walls // No decoratives }); Engine.SetProgress(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,14 @@ "Preview" : "mainland.png", "Keywords": ["multiplayer"], "SupportedBiomes": "generic/", - "CircularMap" : true + "CircularMap" : true, + "TeamPlacements": [ + "groupedCircle", + "alternatingCircle", + "river", + "groupedLines", + "randomGroup", + "stronghold" + ] } } Index: binaries/data/mods/public/maps/random/marmara.js =================================================================== --- binaries/data/mods/public/maps/random/marmara.js +++ binaries/data/mods/public/maps/random/marmara.js @@ -123,7 +123,7 @@ g_Map.log("Placing players"); let [playerIDs, playerPosition] = createBases( ...playerPlacementRandom( - sortAllPlayers(), + getPlayerIDs(), [ avoidClasses(g_TileClasses.mountain, 5), stayClasses(g_TileClasses.land, scaleByMapSize(6, 25)) Index: binaries/data/mods/public/maps/random/mediterranean.js =================================================================== --- binaries/data/mods/public/maps/random/mediterranean.js +++ binaries/data/mods/public/maps/random/mediterranean.js @@ -164,7 +164,7 @@ g_Map.log("Finding player positions"); let [playerIDs, playerPosition] = playerPlacementRandom( - sortAllPlayers(), + getPlayerIDs(), [ avoidClasses(g_TileClasses.mountain, 5), stayClasses(g_TileClasses.land, scaleByMapSize(8, 25)) Index: binaries/data/mods/public/maps/random/ngorongoro.js =================================================================== --- binaries/data/mods/public/maps/random/ngorongoro.js +++ binaries/data/mods/public/maps/random/ngorongoro.js @@ -137,7 +137,7 @@ g_Map.log("Placing players"); let [playerIDs, playerPosition] = createBases( ...playerPlacementRandom( - sortAllPlayers(), + getPlayerIDs(), [ avoidClasses( g_TileClasses.mountain, 5, Index: binaries/data/mods/public/maps/random/pompeii.js =================================================================== --- binaries/data/mods/public/maps/random/pompeii.js +++ binaries/data/mods/public/maps/random/pompeii.js @@ -166,7 +166,7 @@ g_Map.log("Placing players"); let [playerIDs, playerPosition] = createBases( ...playerPlacementRandom( - sortAllPlayers(), + getPlayerIDs(), [ avoidClasses(g_TileClasses.mountain, 5), stayClasses(g_TileClasses.land, scaleByMapSize(5, 15)) Index: binaries/data/mods/public/maps/random/red_sea.js =================================================================== --- binaries/data/mods/public/maps/random/red_sea.js +++ binaries/data/mods/public/maps/random/red_sea.js @@ -122,7 +122,7 @@ g_Map.log("Placing players"); let [playerIDs, playerPosition] = createBases( ...playerPlacementRandom( - sortAllPlayers(), + getPlayerIDs(), [ avoidClasses(g_TileClasses.mountain, scaleByMapSize(5, 10)), stayClasses(g_TileClasses.land, defaultPlayerBaseRadius()) 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 @@ -130,6 +130,29 @@ else if (wallType) placeGenericFortress(position, 20, playerID); } +/** + * Calulate the min distance between the player positions and decide if it's safe to palce walls + * @param {string|boolean} playerPositions array of player positions + */ +function safeToPlaceWalls(playerPositions) +{ + // calulate the min distance between the player positions + // and decide if it's safe to palce walls + let distance = Infinity; + // inspired by: https://stackoverflow.com/q/38858411 + // CC-BY-SA https://stackoverflow.com/users/5393293/haloor + for (let i = 0; i < playerPositions.length; ++i) + for (let j = i + 1; j < playerPositions.length; ++j) + distance = Math.min(distance, playerPositions[i].distanceToSquared(playerPositions[j])); + + distance = Math.sqrt(distance); + if (distance < 50) + { + g_Map.log("Players close together, disable placing of starting walls"); + return false; + } + return true; +} /** * Places the civic center and starting resources for all given players. @@ -456,7 +479,7 @@ let constraint = new StaticConstraint(constraints); let numPlayers = getNumPlayers(); - let playerIDs = shuffleArray(sortAllPlayers()); + let playerIDs = shuffleArray(getPlayerIDs()); let playerPosition = []; for (let i = 0; i < numPlayers; ++i) @@ -495,6 +518,20 @@ return [playerIDs, playerPosition]; } +/** + * Get the player IDs + * + * @returns {Array} - an array with sequential integers from 1 to the number of players + */ +function getPlayerIDs() +{ + let playerIDs = []; + for (let i = 0; i < getNumPlayers(); ++i) + playerIDs.push(i+1); + + return playerIDs; +} + /** * Sorts an array of player IDs by team index. Players without teams come first. * Randomize order for players of the same team. @@ -511,10 +548,7 @@ */ function sortAllPlayers() { - let playerIDs = []; - for (let i = 0; i < getNumPlayers(); ++i) - playerIDs.push(i+1); - + const playerIDs = getPlayerIDs(); return sortPlayers(playerIDs); } @@ -568,14 +602,68 @@ [[], []]); } +/** + * 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); +} + +/** + * Determine player starting positions on a circular pattern. + */ +function playerPlacementByPattern(patternName, distanceFromCenter = undefined, groupedDistance = undefined, angle = undefined, center = undefined) +{ + if (patternName === undefined) + patternName = g_MapSettings.TeamPlacement; + if (patternName == "groupedCircle") + return playerPlacementCircle(distanceFromCenter, angle, center); + if (patternName == "alternatingCircle") + return playerPlacementCircle(distanceFromCenter, angle, center, true); + if (patternName == "river") + return playerPlacementRiver(angle, distanceFromCenter * 2, center); + else if (patternName == "groupedLines") + return playerPlacementGroupedLines(getTeamsArray(), distanceFromCenter, groupedDistance, angle); + else if (patternName == "stronghold") + return playerPlacementStronghold(getTeamsArray(), distanceFromCenter, groupedDistance, angle); + else if (patternName == "randomGroup") + return playerPlacementRandom(getPlayerIDs(), undefined); + else + throw new Error("Unknown placement pattern: " + patternName); +} + /** * Determine player starting positions on a circular pattern. */ -function playerPlacementCircle(radius, startingAngle = undefined, center = undefined) +function playerPlacementCircle(radius, startingAngle = undefined, center = undefined, grouped = false) { let startAngle = startingAngle !== undefined ? startingAngle : randomAngle(); let [playerPosition, playerAngle] = distributePointsOnCircle(getNumPlayers(), startAngle, radius, center || g_Map.getCenter()); - return [sortAllPlayers(), playerPosition.map(p => p.round()), playerAngle, startAngle]; + return [grouped ? sortAllPlayers() : primeSortAllPlayers(), playerPosition.map(p => p.round()), playerAngle, startAngle]; } /** @@ -651,7 +739,7 @@ ).rotateAround(angle, centerPosition).round(); } - return groupPlayersByArea(new Array(numPlayers).fill(0).map((_p, i) => i + 1), playerPosition); + return groupPlayersByArea(getPlayerIDs(), playerPosition); } /** @@ -675,6 +763,85 @@ return playerPosition; } +/** + * Place teams in a line-pattern. + * + * @param {Array} playerIDs - typically randomized indices of players of a single team + * @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 playerPlacementGroupedLines(teamsArray, distance, groupedDistance, startAngle) +{ + let playerIDs = []; + let playerPosition = []; + + let mapCenter = g_Map.getCenter(); + let dist = fractionToTiles(0.45); + + for (let i = 0; i < teamsArray.length; ++i) + { + var safeDist = distance; + if (distance + teamsArray[i].length * groupedDistance > dist) + safeDist = dist - teamsArray[i].length * groupedDistance; + + var teamAngle = startAngle + (i + 1) * 2 * Math.PI / teamsArray.length; + + for (let p = 0; p < teamsArray[i].length; ++p) + { + playerIDs.push(teamsArray[i][p]); + playerPosition.push(Vector2D.add(mapCenter, new Vector2D(safeDist + p * groupedDistance, 0).rotate(-teamAngle)).round()); + } + } + + return [playerIDs, playerPosition]; +} + +/** + * Place given players in a stronghold-pattern. + * + * @param teamsArray - each item is an array of playerIDs placed per stronghold + * @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 playerPlacementStronghold(teamsArray, distance, groupedDistance, startAngle) +{ + var mapCenter = g_Map.getCenter(); + + let playerIDs = []; + let playerPosition = []; + + for (let i = 0; i < teamsArray.length; ++i) + { + var teamAngle = startAngle + (i + 1) * 2 * Math.PI / teamsArray.length; + var teamPosition = Vector2D.add(mapCenter, new Vector2D(distance, 0).rotate(-teamAngle)); + var teamGroupDistance = groupedDistance; + + // If we have a team of above average size, make sure they're spread out + if (teamsArray[i].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) + teamGroupDistance = fractionToTiles(0); + + // TODO: Ensure players are not placed outside of the map area, similar to playerPlacementGroupedLines + + // Create player base + for (var p = 0; p < teamsArray[i].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()); + } + } + + return [playerIDs, playerPosition]; +} + /** * Returns a random location for each player that meets the given constraints and * orders the playerIDs so that players become grouped by team. 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 @@ -55,13 +55,13 @@ var g_TileClasses; var g_PlayerbaseTypes = { - "line": { - "getPosition": (distance, groupedDistance, startAngle) => placeLine(getTeamsArray(), distance, groupedDistance, startAngle), + "groupedLines": { + "getPosition": (distance, groupedDistance, startAngle) => playerPlacementGroupedLines(getTeamsArray(), distance, groupedDistance, startAngle), "distance": fractionToTiles(randFloat(0.2, 0.35)), "groupedDistance": fractionToTiles(randFloat(0.08, 0.1)), "walls": false }, - "radial": { + "groupedCircle": { "getPosition": (distance, groupedDistance, startAngle) => playerPlacementCircle(distance, startAngle), "distance": fractionToTiles(randFloat(0.25, 0.35)), "groupedDistance": fractionToTiles(randFloat(0.08, 0.1)), @@ -74,7 +74,7 @@ "walls": true }, "stronghold": { - "getPosition": (distance, groupedDistance, startAngle) => placeStronghold(getTeamsArray(), distance, groupedDistance, startAngle), + "getPosition": (distance, groupedDistance, startAngle) => playerPlacementStronghold(getTeamsArray(), distance, groupedDistance, startAngle), "distance": fractionToTiles(randFloat(0.2, 0.35)), "groupedDistance": fractionToTiles(randFloat(0.08, 0.1)), "walls": false @@ -140,19 +140,30 @@ /** * Choose starting locations for all players. * - * @param {string} type - "radial", "line", "stronghold", "randomGroup" + * @param {string} type - "groupedCircle", "groupedLines", "stronghold", "randomGroup" * @param {number} distance - radial distance from the center of the map * @param {number} groupedDistance - space between players within a team * @param {number} startAngle - determined by the map that might want to place something between players * @returns {Array|undefined} - If successful, each element is an object that contains id, angle, x, z for each player */ -function createBasesByPattern(type, distance, groupedDistance, startAngle) +function createBasesByPatternDeprecated(type, distance, groupedDistance, startAngle) { - return createBases(...g_PlayerbaseTypes[type].getPosition(distance, groupedDistance, startAngle), g_PlayerbaseTypes[type].walls); + error("createBasesByPattern() has been deprecated. Use createBases(...playerPlacementByPattern()) instead."); + return createBases( + ...playerPlacementByPattern( + type, // patternName + distance, // distanceFromCenter + groupedDistance, + startAngle, // angle + undefined), // center + undefined); // walls } function createBases(playerIDs, playerPosition, walls) { + if (walls === undefined) + walls = safeToPlaceWalls(playerPosition); + g_Map.log("Creating bases"); for (let i = 0; i < getNumPlayers(); ++i) @@ -205,116 +216,6 @@ }); } -/** - * 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 {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) -{ - let playerIDs = []; - let playerPosition = []; - - let mapCenter = g_Map.getCenter(); - let dist = fractionToTiles(0.45); - - for (let i = 0; i < teamsArray.length; ++i) - { - var safeDist = distance; - if (distance + teamsArray[i].length * groupedDistance > dist) - safeDist = dist - teamsArray[i].length * groupedDistance; - - var teamAngle = startAngle + (i + 1) * 2 * Math.PI / teamsArray.length; - - for (let p = 0; p < teamsArray[i].length; ++p) - { - playerIDs.push(teamsArray[i][p]); - playerPosition.push(Vector2D.add(mapCenter, new Vector2D(safeDist + p * groupedDistance, 0).rotate(-teamAngle)).round()); - } - } - - return [playerIDs, playerPosition]; -} - -/** - * Place given players in a stronghold-pattern. - * - * @param teamsArray - each item is an array of playerIDs placed per stronghold - * @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) -{ - var mapCenter = g_Map.getCenter(); - - let playerIDs = []; - let playerPosition = []; - - for (let i = 0; i < teamsArray.length; ++i) - { - var teamAngle = startAngle + (i + 1) * 2 * Math.PI / teamsArray.length; - var teamPosition = Vector2D.add(mapCenter, new Vector2D(distance, 0).rotate(-teamAngle)); - var teamGroupDistance = groupedDistance; - - // If we have a team of above average size, make sure they're spread out - if (teamsArray[i].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) - 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) - { - 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()); - } - } - - return [playerIDs, playerPosition]; -} - /** * Creates tileClass for the default classes and every class given. * Index: binaries/data/mods/public/maps/random/stronghold.js =================================================================== --- binaries/data/mods/public/maps/random/stronghold.js +++ binaries/data/mods/public/maps/random/stronghold.js @@ -17,7 +17,15 @@ Engine.SetProgress(20); -const [playerIDs, playerPosition] = createBasesByPattern("stronghold", fractionToTiles(randFloat(0.2, 0.35)), fractionToTiles(randFloat(0.05, 0.1)), randomAngle()); +const [playerIDs, playerPosition] = + createBases( + ...playerPlacementByPattern( + "stronghold", + fractionToTiles(randFloat(0.2, 0.35)), + fractionToTiles(randFloat(0.05, 0.1)), + randomAngle(), + undefined), + undefined); markPlayerAvoidanceArea(playerPosition, defaultPlayerBaseRadius()); Engine.SetProgress(30);