Unify Caledonian Meadows and Wild Lake player location duplication from rP19704 ...
[0ad.git] / binaries / data / mods / public / maps / random / rmgen / player.js
blobfadc3565e58a74af0983005df23d08ddadedb0f1
1 /**
2  * @file These functions locate and place the starting entities of players.
3  */
5 /**
6  * Gets the default starting entities for the civ of the given player, as defined by the civ file.
7  */
8 function getStartingEntities(playerID)
10         let civ = getCivCode(playerID);
12         if (!g_CivData[civ] || !g_CivData[civ].StartEntities || !g_CivData[civ].StartEntities.length)
13         {
14                 warn("Invalid or unimplemented civ '" + civ + "' specified, falling back to '" + FALLBACK_CIV + "'");
15                 civ = FALLBACK_CIV;
16         }
18         return g_CivData[civ].StartEntities;
21 /**
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.
25  */
26 function placeStartingEntities(fx, fz, playerID, civEntities, dist = 6, orientation = BUILDING_ORIENTATION)
28         // Place the central structure
29         let i = 0;
30         let firstTemplate = civEntities[i].Template;
31         if (firstTemplate.startsWith("structures/"))
32         {
33                 placeObject(fx, fz, firstTemplate, playerID, orientation);
34                 ++i;
35         }
37         // Place entities surrounding it
38         let space = 2;
39         for (let j = i; j < civEntities.length; ++j)
40         {
41                 let angle = orientation - Math.PI * (1 - j / 2);
42                 let count = civEntities[j].Count || 1;
44                 for (let num = 0; num < count; ++num)
45                         placeObject(
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,
49                                 playerID,
50                                 angle);
51         }
54 /**
55  * Places the default starting entities as defined by the civilization definition and walls for Iberians.
56  */
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)
63         {
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);
68         }
71 /**
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.
74  */
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);
86 /**
87  * Sorts an array of player IDs by team index. Players without teams come first.
88  * Randomize order for players of the same team.
89  */
90 function sortPlayers(playerIDs)
92         return shuffleArray(playerIDs).sort((p1, p2) => getPlayerTeam(p1 - 1) - getPlayerTeam(p2 - 1));
95 /**
96  * Randomize playerIDs but sort by team.
97  *
98  * @returns {Array} - every item is an array of player indices
99  */
100 function sortAllPlayers()
102         let playerIDs = [];
103         for (let i = 0; i < getNumPlayers(); ++i)
104                 playerIDs.push(i+1);
106         return sortPlayers(playerIDs);
110  * Rearrange order so that teams of neighboring players alternate (if the given IDs are sorted by team).
111  */
112 function primeSortPlayers(playerIDs)
114         if (!playerIDs.length)
115                 return [];
117         let prime = [];
118         for (let i = 0; i < Math.ceil(playerIDs.length / 2); ++i)
119         {
120                 prime.push(playerIDs[i]);
121                 prime.push(playerIDs[playerIDs.length - 1 - i]);
122         }
124         return prime;
127 function primeSortAllPlayers()
129         return primeSortPlayers(sortAllPlayers());
133  * Determine player starting positions on a circular pattern.
134  */
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.
145  */
146 function placePlayersRiver()
148         let playerPos = [];
149         let numPlayers = getNumPlayers();
150         let numPlayersEven = numPlayers % 2 == 0;
152         for (let i = 0; i < numPlayers; ++i)
153         {
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);
160         }
162         return playerPos;
166  * Sorts the playerIDs so that team members are as close as possible.
167  */
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
180         let playerIDs = [];
181         let teams = [];
182         for (let i = 0; i < g_MapSettings.PlayerData.length - 1; ++i)
183         {
184                 playerIDs.push(i+1);
185                 let t = g_MapSettings.PlayerData[i + 1].Team;
186                 if (teams.indexOf(t) == -1 && t !== undefined)
187                         teams.push(t);
188         }
190         playerIDs = sortPlayers(playerIDs);
192         if (!teams.length)
193                 return [playerIDs, startLocations];
195         // Minimize maximum distance between players within a team
196         let minDistance = Infinity;
197         let bestShift;
198         for (let s = 0; s < playerIDs.length; ++s)
199         {
200                 let maxTeamDist = 0;
201                 for (let pi = 0; pi < playerIDs.length - 1; ++pi)
202                 {
203                         let p1 = playerIDs[(pi + s) % playerIDs.length] - 1;
204                         let t1 = getPlayerTeam(p1);
206                         if (teams.indexOf(t1) === -1)
207                                 continue;
209                         for (let pj = pi + 1; pj < playerIDs.length; ++pj)
210                         {
211                                 let p2 = playerIDs[(pj + s) % playerIDs.length] - 1;
212                                 if (t1 != getPlayerTeam(p2))
213                                         continue;
215                                 maxTeamDist = Math.max(
216                                         maxTeamDist,
217                                         Math.euclidDistance2D(
218                                                 startLocations[pi].x,
219                                                 startLocations[pi].y,
220                                                 startLocations[pj].x,
221                                                 startLocations[pj].y));
222                         }
223                 }
225                 if (maxTeamDist < minDistance)
226                 {
227                         minDistance = maxTeamDist;
228                         bestShift = s;
229                 }
230         }
232         if (bestShift)
233         {
234                 let newPlayerIDs = [];
235                 for (let i = 0; i < playerIDs.length; ++i)
236                         newPlayerIDs.push(playerIDs[(i + bestShift) % playerIDs.length]);
237                 playerIDs = newPlayerIDs;
238         }
240         return [playerIDs, startLocations];