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 @@ -137,11 +137,15 @@ function placePlayerBases(playerBaseArgs) { g_Map.log("Creating playerbases"); - const [playerIDs, playerPosition] = playerBaseArgs.PlayerPlacement; + // if (!(playerBaseArgs.playerPlacement instanceof PlayerPlacement)) + // throw new Error("playerPosition is not of class `PlayerPlacement`"); + // playerBaseArgs.PlayerPlacement.validate(); for (let i = 0; i < getNumPlayers(); ++i) { + // playerBaseArgs.playerID = playerBaseArgs.PlayerPlacement.playerIDs[i]; + // playerBaseArgs.playerPosition = playerBaseArgs.PlayerPlacement.playerPositions[i]; playerBaseArgs.playerID = playerIDs[i]; playerBaseArgs.playerPosition = playerPosition[i]; placePlayerBase(playerBaseArgs); @@ -604,6 +608,43 @@ return teams.filter(team => true); } +function PlayerPlacement() { + this.playerIDs = []; + this.playerPositions = []; + this.playerAngles = []; + this.startAngle = undefined; + this.placeWalls = undefined; +} + +PlayerPlacement.prototype.validate = function() +{ + const uniquePlayerIDs = new Set(this.playerIDs); + if (uniquePlayerIDs.size != this.playerIDs.length) + throw new Error("playerIDs are not unique"); + + if (this.playerIDs.some(id => id < 1 || id > getNumPlayers() || !Number.isInteger(id))) + throw new Error("playerIDs invalid"); + + if (this.playerIDs.length != this.playerPositions.length) + throw new Error("playerIDs and playerPositions have different length"); + + if (this.playerPositions.some(pos => !(pos instanceof Vector2D))) + throw new Error("playerPositions invalid"); + + if (this.playerIDs.length != this.playerAngles.length) + throw new Error("playerIDs and playerAngles have different length"); + const twoPI = 2 * Math.PI; + + if (this.playerAngles && this.playerAngles.some(angle => angle < -twoPI || angle > twoPI)) + throw new Error("playerAngles invalid"); + + if (this.startAngle && this.startAngle < -twoPI || this.startAngle > twoPI) + throw new Error("startAngle invalid"); + + if (this.placeWalls && typeof this.placeWalls != "boolean") + throw new Error("placeWalls invalid"); +} + /** * Determine player starting positions based on the specified pattern. */ @@ -632,58 +673,69 @@ /** * Determine player starting positions on a circular pattern. */ -function playerPlacementCircle(radius, startingAngle = undefined, center = undefined) +PlayerPlacement.prototype.circle = function(playerIDs, radius, startAngle = randomAngle(), center = g_Map.getCenter()) { - const startAngle = startingAngle !== undefined ? startingAngle : randomAngle(); - const [playerPosition, playerAngle] = distributePointsOnCircle(getNumPlayers(), startAngle, radius, center || g_Map.getCenter()); - return [sortAllPlayers(), playerPosition.map(p => p.round()), playerAngle, startAngle]; + const [playerPosition, playerAngle] = distributePointsOnCircle(getNumPlayers(), startAngle, radius, center); + this.playerIDs = playerIDs; + this.playerPositions = playerPosition.map(p => p.round()); + this.playerAngles = playerAngle; + this.startAngle = startAngle; } /** * Determine player starting positions on a circular pattern, with a custom angle for each player. * Commonly used for gulf terrains. */ -function playerPlacementCustomAngle(radius, center, playerAngleFunc) +PlayerPlacement.prototype.customAngle = function(playerIDs, radius, playerAngleFunc, center = g_Map.getCenter()) { - const playerPosition = []; - const playerAngle = []; - const numPlayers = getNumPlayers(); for (let i = 0; i < numPlayers; ++i) { - playerAngle[i] = playerAngleFunc(i); - playerPosition[i] = Vector2D.add(center, new Vector2D(radius, 0).rotate(-playerAngle[i])).round(); + this.playerAngles[i] = playerAngleFunc(i); + this.playerPositions[i] = Vector2D.add(center, new Vector2D(radius, 0).rotate(-playerAngles[i])).round(); } - - return [playerPosition, playerAngle]; + this.playerIDs = playerIDs; } /** * Returns player starting positions equally spaced along an arc. */ -function playerPlacementArc(playerIDs, center, radius, startAngle, endAngle) +PlayerPlacement.prototype.arc = function(playerIDs, radius, startAngle, endAngle, center = g_Map.getCenter()) { - return distributePointsOnCircularSegment( + const [points, angle] = distributePointsOnCircularSegment( playerIDs.length + 2, endAngle - startAngle, startAngle, radius, center )[0].slice(1, -1).map(p => p.round()); + this.playerIDs = playerIDs; + this.playerPositions = points; + this.playerAngles = angle; + this.startAngle = startAngle; } /** * Returns player starting positions located on two symmetrically placed arcs, with teammates placed on the same arc. */ -function playerPlacementArcs(playerIDs, center, radius, mapAngle, startAngle, endAngle) +PlayerPlacement.prototype.arcs = function(playerIDs, radius, mapAngle, startAngle, endAngle, center = g_Map.getCenter()) { + const eastPosition = new PlayerPlacement(); + const westPosition = new PlayerPlacement(); const [east, west] = partitionPlayers(playerIDs); - const eastPosition = playerPlacementArc(east, center, radius, mapAngle + startAngle, mapAngle + endAngle); - const westPosition = playerPlacementArc(west, center, radius, mapAngle - startAngle, mapAngle - endAngle); - return playerIDs.map(playerID => east.indexOf(playerID) != -1 ? - eastPosition[east.indexOf(playerID)] : - westPosition[west.indexOf(playerID)]); + eastPosition.arc(east, center, radius, mapAngle + startAngle, mapAngle + endAngle); + westPosition.arc(west, center, radius, mapAngle - startAngle, mapAngle - endAngle); + this.playerPositions = playerIDs.map(playerID => east.indexOf(playerID) != -1 ? + eastPosition.playerPositions[east.indexOf(playerID)] : + westPosition.playerPositions[west.indexOf(playerID)]); + + this.playerAngles = playerIDs.map(playerID => east.indexOf(playerID) != -1 ? + eastPosition.playerAngles[east.indexOf(playerID)] : + westPosition.playerAngles[west.indexOf(playerID)]); + + this.playerIDs = playerIDs; + this.startAngle = startAngle; } /** @@ -691,12 +743,11 @@ * 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. */ -function playerPlacementRiver(angle, width, center = undefined) +PlayerPlacement.prototype.parallelLines = function(playerIDs, width, angle, center = g_Map.getCenter()) { const numPlayers = getNumPlayers(); const numPlayersEven = numPlayers % 2 == 0; const mapSize = g_Map.getSize(); - const centerPosition = center || g_Map.getCenter(); const playerPosition = []; for (let i = 0; i < numPlayers; ++i) @@ -709,17 +760,19 @@ playerPosition[i] = new Vector2D( width * (i % 2) + (mapSize - width) / 2, fractionToTiles(((i - 1 + offsetDivident) / 2 + 1) / ((numPlayers + offsetDivisor) / 2 + 1)) - ).rotateAround(angle, centerPosition).round(); + ).rotateAround(angle, center).round(); } - - return groupPlayersByArea(new Array(numPlayers).fill(0).map((_p, i) => i + 1), playerPosition); + const [IDs, locations] = groupPlayersByArea(playerIDs, playerPosition); + this.playerIDs = IDs; + this.playerPositions = locations; + this.startAngle = angle; } /** * Returns starting positions located on two parallel lines. * The locations on the first line are shifted in comparison to the other line. */ -function playerPlacementLine(angle, center, width) +PlayerPlacement.prototype.shiftedLines = function(playerIDs, width, angle, center = g_Map.getCenter()) { const playerPosition = []; const numPlayers = getNumPlayers(); @@ -733,27 +786,26 @@ ).rotate(angle) ).round(); - return playerPosition; + this.playerIDs = playerIDs; + this.playerPositions = playerPosition; + this.startAngle = angle; } /** * Place teams in a line-pattern. * - * @param {Array} playerIDs - typically randomized indices of players of a single team + * @param {Array} teamsArray - array of arrays, each containing the playerIndices of a 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) +PlayerPlacement.prototype.Line = function(playerIDs, distance, groupedDistance, startAngle, center = g_Map.getCenter()) { - const playerIDs = []; - const playerPosition = []; - - const mapCenter = g_Map.getCenter(); + const teamsArray = getTeamsArray(); const dist = fractionToTiles(0.45); - + this.startAngle = startAngle; for (let i = 0; i < teamsArray.length; ++i) { let safeDist = distance; @@ -764,12 +816,10 @@ 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()); + this.playerIDs.push(teamsArray[i][p]); + this.playerPositions.push(Vector2D.add(center, new Vector2D(safeDist + p * groupedDistance, 0).rotate(-teamAngle)).round()); } } - - return [playerIDs, playerPosition]; } /** @@ -780,17 +830,14 @@ * @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) +PlayerPlacement.prototype.stronghold = function(playerIDs, distance, groupedDistance, startAngle, center = g_Map.getCenter()) { - let mapCenter = g_Map.getCenter(); + const teamsArray = getTeamsArray(); - const playerIDs = []; - const playerPosition = []; - for (let i = 0; i < teamsArray.length; ++i) { - let teamAngle = startAngle + (i + 1) * 2 * Math.PI / teamsArray.length; - let teamPosition = Vector2D.add(mapCenter, new Vector2D(distance, 0).rotate(-teamAngle)); + const teamAngle = startAngle + (i + 1) * 2 * Math.PI / teamsArray.length; + const teamPosition = Vector2D.add(center, new Vector2D(distance, 0).rotate(-teamAngle)); let teamGroupDistance = groupedDistance; // If we have a team of above average size, make sure they're spread out @@ -807,26 +854,24 @@ for (let p = 0; p < teamsArray[i].length; ++p) { let 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()); + this.playerIDs.push(teamsArray[i][p]); + this.playerPositions.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. */ -function playerPlacementRandom(playerIDs, constraints = undefined) +PlayerPlacement.prototype.random = function(playerIDs, minimumDistance = fractionToTiles(0.25), constraints = undefined) { let locations = []; let attempts = 0; let resets = 0; const mapCenter = g_Map.getCenter(); - let playerMinDistSquared = Math.square(fractionToTiles(0.25)); + let playerMinDistSquared = Math.square(minimumDistance); const borderDistance = fractionToTiles(0.08); const area = createArea(new MapBoundsPlacer(), undefined, new AndConstraint(constraints)); @@ -865,9 +910,92 @@ locations[i] = position; } - return groupPlayersByArea(playerIDs, locations); + const [IDs, positions] = groupPlayersByArea(playerIDs, playerPosition); + this.playerIDs = IDs; + this.playerPositions = positions; } +// deprecated functions +function deprecationWarning() +{ + warn("PlayerPlacement API has changed and the old API will be deprecated, please adjust your maps"); + +} + +function playerPlacementCircle(radius, startingAngle = undefined, center = undefined) +{ + deprecationWarning(); + const placement = new PlayerPlacement(); + placement.circle(sortAllPlayers(), radius, startingAngle, center); + return [placement.playerIDs, placement.playerPositions, placement.playerAngles, placement.startAngle]; +} + + +function playerPlacementCustomAngle(radius, center, playerAngleFunc) +{ + deprecationWarning(); + const placement = new PlayerPlacement(); + placement.customAngle(sortAllPlayers(), radius, playerAngleFunc, center); + return [placement.playerPositions, placement.playerAngles]; +} + +function playerPlacementArc(playerIDs, center, radius, startAngle, endAngle) +{ + deprecationWarning(); + const placement = new PlayerPlacement(); + placement.arc(playerIDs, radius, startAngle, endAngle, center); + return [placement.playerPositions, placement.playerAngles]; +} + +function playerPlacementArcs(playerIDs, center, radius, mapAngle, startAngle, endAngle) +{ + deprecationWarning(); + const placement = new PlayerPlacement(); + placement.arcs(playerIDs, radius, mapAngle, startAngle, endAngle, center); + return placement.playerPositions; +} + +function playerPlacementRiver(angle, width, center = undefined) +{ + deprecationWarning(); + const placement = new PlayerPlacement(); + placement.parallelLines(sortAllPlayers(), width, angle, center); + return [placement.playerIDs, placement.playerPositions]; +} + +function playerPlacementLine(angle, center, width) +{ + deprecationWarning(); + const placement = new PlayerPlacement(); + placement.shiftedLines(sortAllPlayers(), width, angle, center); + return placement.playerPositions; +} + +function placeLine(teamsArray, distance, groupedDistance, startAngle) +{ + deprecationWarning(); + const placement = new PlayerPlacement(); + placement.Line(sortAllPlayers(), distance, groupedDistance, startAngle); + return [placement.playerIDs, placement.playerPositions]; +} + +function placeStronghold(teamsArray, distance, groupedDistance, startAngle) +{ + deprecationWarning(); + const placement = new PlayerPlacement(); + placement.stronghold(sortAllPlayers(), distance, groupedDistance, startAngle); + return [placement.playerIDs, placement.playerPositions]; +} + + +function playerPlacementRandom(playerIDs, constraints = undefined) +{ + deprecationWarning(); + const placement = new PlayerPlacement(); + placement.random(playerIDs, fractionToTiles(0.25), constraints); + return [placement.playerIDs, placement.playerPositions]; +} + /** * Pick locations from the given set so that teams end up grouped. */ 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 @@ -54,33 +54,6 @@ var g_TileClasses; -var g_PlayerbaseTypes = { - "line": { - "getPosition": (distance, groupedDistance, startAngle) => placeLine(getTeamsArray(), distance, groupedDistance, startAngle), - "distance": fractionToTiles(randFloat(0.2, 0.35)), - "groupedDistance": fractionToTiles(randFloat(0.08, 0.1)), - "walls": false - }, - "radial": { - "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)), - "groupedDistance": fractionToTiles(randFloat(0.08, 0.1)), - "walls": true - }, - "stronghold": { - "getPosition": (distance, groupedDistance, startAngle) => placeStronghold(getTeamsArray(), distance, groupedDistance, startAngle), - "distance": fractionToTiles(randFloat(0.2, 0.35)), - "groupedDistance": fractionToTiles(randFloat(0.08, 0.1)), - "walls": false - } -}; - /** * Adds an array of elements to the map. */