2 * @file These functions locate and place the starting entities of players.
6 * Gets the default starting entities for the civ of the given player, as defined by the civ file.
8 function getStartingEntities(playerID)
10 let civ = getCivCode(playerID);
12 if (!g_CivData[civ] || !g_CivData[civ].StartEntities || !g_CivData[civ].StartEntities.length)
14 warn("Invalid or unimplemented civ '" + civ + "' specified, falling back to '" + FALLBACK_CIV + "'");
18 return g_CivData[civ].StartEntities;
22 * Places the given entities at the given location (typically a civic center and starting units).
23 * @param civEntities - An array of objects with the Template property and optionally a Count property.
24 * The first entity is placed in the center, the other ones surround it.
26 function placeStartingEntities(fx, fz, playerID, civEntities, dist = 6, orientation = BUILDING_ORIENTATION)
28 // Place the central structure
30 let firstTemplate = civEntities[i].Template;
31 if (firstTemplate.startsWith("structures/"))
33 placeObject(fx, fz, firstTemplate, playerID, orientation);
37 // Place entities surrounding it
39 for (let j = i; j < civEntities.length; ++j)
41 let angle = orientation - Math.PI * (1 - j / 2);
42 let count = civEntities[j].Count || 1;
44 for (let num = 0; num < count; ++num)
46 fx + dist * Math.cos(angle) + space * (-num + 0.75 * Math.floor(count / 2)) * Math.sin(angle),
47 fz + dist * Math.sin(angle) + space * (num - 0.75 * Math.floor(count / 2)) * Math.cos(angle),
48 civEntities[j].Template,
55 * Places the default starting entities as defined by the civilization definition and walls for Iberians.
57 function placeCivDefaultEntities(fx, fz, playerID, kwargs, dist = 6, orientation = BUILDING_ORIENTATION)
59 placeStartingEntities(fx, fz, playerID, getStartingEntities(playerID - 1), dist, orientation);
61 let civ = getCivCode(playerID - 1);
62 if (civ == 'iber' && getMapSize() > 128)
64 if (kwargs && kwargs.iberWall == 'towers')
65 placePolygonalWall(fx, fz, 15, ['entry'], 'tower', civ, playerID, orientation, 7);
66 else if (!kwargs || kwargs.iberWall)
67 placeGenericFortress(fx, fz, 20, playerID);
72 * Marks the corner and center tiles of an area that is about the size of a Civic Center with the given TileClass.
73 * Used to prevent resource collisions with the Civic Center.
75 function addCivicCenterAreaToClass(ix, iz, tileClass)
77 addToClass(ix, iz, tileClass);
79 addToClass(ix, iz + 5, tileClass);
80 addToClass(ix, iz - 5, tileClass);
82 addToClass(ix + 5, iz, tileClass);
83 addToClass(ix - 5, iz, tileClass);
87 * Sorts an array of player IDs by team index. Players without teams come first.
88 * Randomize order for players of the same team.
90 function sortPlayers(playerIDs)
92 return shuffleArray(playerIDs).sort((p1, p2) => getPlayerTeam(p1 - 1) - getPlayerTeam(p2 - 1));
96 * Randomize playerIDs but sort by team.
98 * @returns {Array} - every item is an array of player indices
100 function sortAllPlayers()
103 for (let i = 0; i < getNumPlayers(); ++i)
106 return sortPlayers(playerIDs);
110 * Rearrange order so that teams of neighboring players alternate (if the given IDs are sorted by team).
112 function primeSortPlayers(playerIDs)
114 if (!playerIDs.length)
118 for (let i = 0; i < Math.ceil(playerIDs.length / 2); ++i)
120 prime.push(playerIDs[i]);
121 prime.push(playerIDs[playerIDs.length - 1 - i]);
127 function primeSortAllPlayers()
129 return primeSortPlayers(sortAllPlayers());
133 * Determine player starting positions on a circular pattern.
135 function radialPlayerPlacement(radius = 0.35, startingAngle = undefined, centerX = 0.5, centerZ = 0.5)
137 let startAngle = startingAngle !== undefined ? startingAngle : randFloat(0, 2 * Math.PI);
138 return [sortAllPlayers(), ...distributePointsOnCircle(getNumPlayers(), startAngle, radius, centerX, centerZ), startAngle];
142 * Returns an array of percent numbers indicating the player location on river maps.
143 * For example [0.2, 0.2, 0.4, 0.4, 0.6, 0.6, 0.8, 0.8] for a 4v4 or
144 * [0.25, 0.33, 0.5, 0.67, 0.75] for a 2v3.
146 function placePlayersRiver()
149 let numPlayers = getNumPlayers();
150 let numPlayersEven = numPlayers % 2 == 0;
152 for (let i = 0; i < numPlayers; ++i)
154 let currentPlayerEven = i % 2 == 0;
156 let offsetDivident = numPlayersEven || currentPlayerEven ? (i + 1) % 2 : 0;
157 let offsetDivisor = numPlayersEven ? 0 : currentPlayerEven ? +1 : -1;
159 playerPos[i] = ((i - 1 + offsetDivident) / 2 + 1) / ((numPlayers + offsetDivisor) / 2 + 1);
166 * Sorts the playerIDs so that team members are as close as possible.
168 function sortPlayersByLocation(startLocations)
170 // Sort start locations to form a "ring"
171 let startLocationOrder = sortPointsShortestCycle(startLocations);
173 let newStartLocations = [];
174 for (let i = 0; i < startLocations.length; ++i)
175 newStartLocations.push(startLocations[startLocationOrder[i]]);
177 startLocations = newStartLocations;
179 // Sort players by team
182 for (let i = 0; i < g_MapSettings.PlayerData.length - 1; ++i)
185 let t = g_MapSettings.PlayerData[i + 1].Team;
186 if (teams.indexOf(t) == -1 && t !== undefined)
190 playerIDs = sortPlayers(playerIDs);
193 return [playerIDs, startLocations];
195 // Minimize maximum distance between players within a team
196 let minDistance = Infinity;
198 for (let s = 0; s < playerIDs.length; ++s)
201 for (let pi = 0; pi < playerIDs.length - 1; ++pi)
203 let p1 = playerIDs[(pi + s) % playerIDs.length] - 1;
204 let t1 = getPlayerTeam(p1);
206 if (teams.indexOf(t1) === -1)
209 for (let pj = pi + 1; pj < playerIDs.length; ++pj)
211 let p2 = playerIDs[(pj + s) % playerIDs.length] - 1;
212 if (t1 != getPlayerTeam(p2))
215 maxTeamDist = Math.max(
217 Math.euclidDistance2D(
218 startLocations[pi].x,
219 startLocations[pi].y,
220 startLocations[pj].x,
221 startLocations[pj].y));
225 if (maxTeamDist < minDistance)
227 minDistance = maxTeamDist;
234 let newPlayerIDs = [];
235 for (let i = 0; i < playerIDs.length; ++i)
236 newPlayerIDs.push(playerIDs[(i + bestShift) % playerIDs.length]);
237 playerIDs = newPlayerIDs;
240 return [playerIDs, startLocations];