File: | client/cgame/campaign/cp_campaign.cpp |
Location: | line 264, column 2 |
Description: | Access to field 'timesAlreadyUsed' results in a dereference of a null pointer (loaded from field 'mapDef') |
1 | /** | ||
2 | * @file | ||
3 | * @brief Single player campaign control. | ||
4 | */ | ||
5 | |||
6 | /* | ||
7 | Copyright (C) 2002-2011 UFO: Alien Invasion. | ||
8 | |||
9 | This program is free software; you can redistribute it and/or | ||
10 | modify it under the terms of the GNU General Public License | ||
11 | as published by the Free Software Foundation; either version 2 | ||
12 | of the License, or (at your option) any later version. | ||
13 | |||
14 | This program is distributed in the hope that it will be useful, | ||
15 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | ||
17 | |||
18 | See the GNU General Public License for more details. | ||
19 | |||
20 | You should have received a copy of the GNU General Public License | ||
21 | along with this program; if not, write to the Free Software | ||
22 | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | ||
23 | */ | ||
24 | |||
25 | #include "../../cl_shared.h" | ||
26 | #include "../../ui/ui_main.h" | ||
27 | #include "../cgame.h" | ||
28 | #include "cp_campaign.h" | ||
29 | #include "cp_capacity.h" | ||
30 | #include "cp_overlay.h" | ||
31 | #include "cp_mapfightequip.h" | ||
32 | #include "cp_hospital.h" | ||
33 | #include "cp_hospital_callbacks.h" | ||
34 | #include "cp_base_callbacks.h" | ||
35 | #include "cp_basedefence_callbacks.h" | ||
36 | #include "cp_team.h" | ||
37 | #include "cp_team_callbacks.h" | ||
38 | #include "cp_popup.h" | ||
39 | #include "cp_map.h" | ||
40 | #include "cp_ufo.h" | ||
41 | #include "cp_installation_callbacks.h" | ||
42 | #include "cp_alien_interest.h" | ||
43 | #include "cp_missions.h" | ||
44 | #include "cp_mission_triggers.h" | ||
45 | #include "cp_nation.h" | ||
46 | #include "cp_statistics.h" | ||
47 | #include "cp_time.h" | ||
48 | #include "cp_xvi.h" | ||
49 | #include "cp_fightequip_callbacks.h" | ||
50 | #include "cp_produce_callbacks.h" | ||
51 | #include "cp_transfer.h" | ||
52 | #include "cp_market_callbacks.h" | ||
53 | #include "cp_research_callbacks.h" | ||
54 | #include "cp_uforecovery.h" | ||
55 | #include "save/save_campaign.h" | ||
56 | #include "cp_auto_mission.h" | ||
57 | |||
58 | memPool_t* cp_campaignPool; /**< reset on every game restart */ | ||
59 | ccs_t ccs; | ||
60 | cvar_t *cp_campaign; | ||
61 | cvar_t *cp_start_employees; | ||
62 | cvar_t *cp_missiontest; | ||
63 | |||
64 | typedef struct { | ||
65 | int ucn; | ||
66 | int HP; | ||
67 | int STUN; | ||
68 | int morale; | ||
69 | |||
70 | chrScoreGlobal_t chrscore; | ||
71 | } updateCharacter_t; | ||
72 | |||
73 | /** | ||
74 | * @brief Parses the character data which was send by G_MatchSendResults using G_SendCharacterData | ||
75 | * @param[in] msg The network buffer message. If this is NULL the character is updated, if this | ||
76 | * is not NULL the data is stored in a temp buffer because the player can choose to retry | ||
77 | * the mission and we have to catch this situation to not update the character data in this case. | ||
78 | * @sa G_SendCharacterData | ||
79 | * @sa GAME_SendCurrentTeamSpawningInfo | ||
80 | * @sa E_Save | ||
81 | */ | ||
82 | void CP_ParseCharacterData (struct dbuffer *msg) | ||
83 | { | ||
84 | static linkedList_t *updateCharacters = NULL__null; | ||
85 | |||
86 | if (!msg) { | ||
87 | LIST_Foreach(updateCharacters, updateCharacter_t, c)for (bool c__break = false, c__once = true; c__once; c__once = false) for (linkedList_t const* c__iter = (updateCharacters) ; ! c__break && c__iter;) for (updateCharacter_t* const c = ( c__break = c__once = true, (updateCharacter_t*) c__iter ->data); c__once; c__break = c__once = false) if ( c__iter = c__iter->next, false) {} else { | ||
88 | employee_t *employee = E_GetEmployeeFromChrUCN(c->ucn); | ||
89 | character_t* chr; | ||
90 | |||
91 | if (!employee) { | ||
92 | Com_Printf("Warning: Could not get character with ucn: %i.\n", c->ucn); | ||
93 | continue; | ||
94 | } | ||
95 | |||
96 | chr = &employee->chr; | ||
97 | chr->HP = std::min(c->HP, chr->maxHP); | ||
98 | chr->STUN = c->STUN; | ||
99 | chr->morale = c->morale; | ||
100 | |||
101 | memcpy(chr->score.experience, c->chrscore.experience, sizeof(chr->score.experience)); | ||
102 | memcpy(chr->score.skills, c->chrscore.skills, sizeof(chr->score.skills)); | ||
103 | memcpy(chr->score.kills, c->chrscore.kills, sizeof(chr->score.kills)); | ||
104 | memcpy(chr->score.stuns, c->chrscore.stuns, sizeof(chr->score.stuns)); | ||
105 | chr->score.assignedMissions = c->chrscore.assignedMissions; | ||
106 | } | ||
107 | LIST_Delete(&updateCharacters); | ||
108 | } else { | ||
109 | int i, j; | ||
110 | const int num = NET_ReadByte(msg); | ||
111 | |||
112 | if (num < 0) | ||
113 | Com_Error(ERR_DROP1, "CP_ParseCharacterData: NET_ReadShort error (%i)\n", num); | ||
114 | |||
115 | LIST_Delete(&updateCharacters); | ||
116 | |||
117 | for (i = 0; i < num; i++) { | ||
118 | updateCharacter_t c; | ||
119 | OBJZERO(c)(memset(&((c)), (0), sizeof((c)))); | ||
120 | c.ucn = NET_ReadShort(msg); | ||
121 | c.HP = NET_ReadShort(msg); | ||
122 | c.STUN = NET_ReadByte(msg); | ||
123 | c.morale = NET_ReadByte(msg); | ||
124 | |||
125 | for (j = 0; j < SKILL_NUM_TYPES + 1; j++) | ||
126 | c.chrscore.experience[j] = NET_ReadLong(msg); | ||
127 | for (j = 0; j < SKILL_NUM_TYPES; j++) | ||
128 | c.chrscore.skills[j] = NET_ReadByte(msg); | ||
129 | for (j = 0; j < KILLED_NUM_TYPES; j++) | ||
130 | c.chrscore.kills[j] = NET_ReadShort(msg); | ||
131 | for (j = 0; j < KILLED_NUM_TYPES; j++) | ||
132 | c.chrscore.stuns[j] = NET_ReadShort(msg); | ||
133 | c.chrscore.assignedMissions = NET_ReadShort(msg); | ||
134 | LIST_Add(&updateCharacters, c); | ||
135 | } | ||
136 | } | ||
137 | } | ||
138 | |||
139 | /** | ||
140 | * @brief Checks whether a campaign mode game is running | ||
141 | */ | ||
142 | bool CP_IsRunning (void) | ||
143 | { | ||
144 | return ccs.curCampaign != NULL__null; | ||
145 | } | ||
146 | |||
147 | /** | ||
148 | * @brief Check if a map may be selected for mission. | ||
149 | * @param[in] mission Pointer to the mission where mapDef should be added | ||
150 | * @param[in] pos position of the mission (NULL if the position will be chosen afterwards) | ||
151 | * @param[in] mapIdx idx of the map in the mapdef array | ||
152 | * @return false if map is not selectable | ||
153 | */ | ||
154 | static bool CP_MapIsSelectable (mission_t *mission, mapDef_t *md, const vec2_t pos) | ||
155 | { | ||
156 | if (md->storyRelated) | ||
157 | return false; | ||
158 | |||
159 | if (pos && !MAP_PositionFitsTCPNTypes(pos, md->terrains, md->cultures, md->populations, NULL__null)) | ||
160 | return false; | ||
161 | |||
162 | if (!mission->ufo) { | ||
163 | /* a mission without UFO should not use a map with UFO */ | ||
164 | if (md->ufos) | ||
165 | return false; | ||
166 | } else if (md->ufos) { | ||
167 | /* A mission with UFO should use a map with UFO | ||
168 | * first check that list is not empty */ | ||
169 | const ufoType_t type = mission->ufo->ufotype; | ||
170 | const char *ufoID; | ||
171 | |||
172 | if (mission->crashed) | ||
173 | ufoID = Com_UFOCrashedTypeToShortName(type); | ||
174 | else | ||
175 | ufoID = Com_UFOTypeToShortName(type); | ||
176 | |||
177 | if (!LIST_ContainsString(md->ufos, ufoID)) | ||
178 | return false; | ||
179 | } | ||
180 | |||
181 | return true; | ||
182 | } | ||
183 | |||
184 | /** | ||
185 | * @brief Choose a map for given mission. | ||
186 | * @param[in,out] mission Pointer to the mission where a new map should be added | ||
187 | * @param[in] pos position of the mission (NULL if the position will be chosen afterwards) | ||
188 | * @return false if could not set mission | ||
189 | */ | ||
190 | bool CP_ChooseMap (mission_t *mission, const vec2_t pos) | ||
191 | { | ||
192 | if (mission->mapDef) | ||
| |||
193 | return true; | ||
194 | |||
195 | int countMinimal = 0; /**< Number of maps fulfilling mission conditions and appeared less often during game. */ | ||
196 | int minMapDefAppearance = -1; | ||
197 | mapDef_t* md = NULL__null; | ||
198 | MapDef_ForeachSingleplayerCampaign(md)for (int md__loopvar = 0; (md) = __null, md__loopvar < csi .numMDs; md__loopvar++) if ((md) = &csi.mds[md__loopvar], !((md)->singleplayer && (md)->campaign)) {} else { | ||
199 | /* Check if mission fulfill conditions */ | ||
200 | if (!CP_MapIsSelectable(mission, md, pos)) | ||
| |||
201 | continue; | ||
202 | |||
203 | if (minMapDefAppearance < 0 || md->timesAlreadyUsed < minMapDefAppearance) { | ||
| |||
204 | minMapDefAppearance = md->timesAlreadyUsed; | ||
205 | countMinimal = 1; | ||
206 | continue; | ||
| |||
207 | } | ||
208 | if (md->timesAlreadyUsed > minMapDefAppearance) | ||
209 | continue; | ||
210 | countMinimal++; | ||
211 | } | ||
212 | |||
213 | if (countMinimal == 0) { | ||
| |||
214 | /* no map fulfill the conditions */ | ||
215 | if (mission->category == INTERESTCATEGORY_RESCUE) { | ||
216 | /* default map for rescue mission is the rescue random map assembly */ | ||
217 | mission->mapDef = Com_GetMapDefinitionByID("rescue"); | ||
218 | if (!mission->mapDef) | ||
219 | Com_Error(ERR_DROP1, "Could not find mapdef: rescue"); | ||
220 | mission->mapDef->timesAlreadyUsed++; | ||
221 | return true; | ||
222 | } | ||
223 | if (mission->crashed) { | ||
224 | /* default map for crashsite mission is the crashsite random map assembly */ | ||
225 | mission->mapDef = Com_GetMapDefinitionByID("ufocrash"); | ||
226 | if (!mission->mapDef) | ||
227 | Com_Error(ERR_DROP1, "Could not find mapdef: ufocrash"); | ||
228 | mission->mapDef->timesAlreadyUsed++; | ||
229 | return true; | ||
230 | } | ||
231 | |||
232 | Com_Printf("CP_ChooseMap: Could not find map with required conditions:\n"); | ||
233 | Com_Printf(" ufo: %s -- pos: ", mission->ufo ? Com_UFOTypeToShortName(mission->ufo->ufotype) : "none"); | ||
234 | if (pos) | ||
235 | Com_Printf("%s", MapIsWater(MAP_GetColor(pos, MAPTYPE_TERRAIN, NULL))(MAP_GetColor(pos, MAPTYPE_TERRAIN, __null)[0] == 0 && MAP_GetColor(pos, MAPTYPE_TERRAIN, __null)[1] == 0 && MAP_GetColor(pos, MAPTYPE_TERRAIN, __null)[2] == 64) ? " (in water) " : ""); | ||
236 | if (pos) | ||
237 | Com_Printf("(%.02f, %.02f)\n", pos[0], pos[1]); | ||
238 | else | ||
239 | Com_Printf("none\n"); | ||
240 | return false; | ||
241 | } | ||
242 | |||
243 | /* select a map randomly from the selected */ | ||
244 | int randomNum = rand() % countMinimal; | ||
245 | md = NULL__null; | ||
246 | MapDef_ForeachSingleplayerCampaign(md)for (int md__loopvar = 0; (md) = __null, md__loopvar < csi .numMDs; md__loopvar++) if ((md) = &csi.mds[md__loopvar], !((md)->singleplayer && (md)->campaign)) {} else { | ||
247 | /* Check if mission fulfill conditions */ | ||
248 | if (!CP_MapIsSelectable(mission, md, pos)) | ||
249 | continue; | ||
250 | if (md->timesAlreadyUsed > minMapDefAppearance) | ||
251 | continue; | ||
252 | /* There shouldn't be mission fulfilling conditions used less time than minMissionAppearance */ | ||
253 | assert(md->timesAlreadyUsed == minMapDefAppearance)(__builtin_expect(!(md->timesAlreadyUsed == minMapDefAppearance ), 0) ? __assert_rtn(__func__, "src/client/cgame/campaign/cp_campaign.cpp" , 253, "md->timesAlreadyUsed == minMapDefAppearance") : (void )0); | ||
254 | |||
255 | if (randomNum == 0) { | ||
256 | mission->mapDef = md; | ||
257 | break; | ||
258 | } else { | ||
259 | randomNum--; | ||
260 | } | ||
261 | } | ||
262 | |||
263 | /* A mission must have been selected */ | ||
264 | mission->mapDef->timesAlreadyUsed++; | ||
| |||
265 | if (cp_missiontest->integer) | ||
266 | Com_Printf("Selected map '%s' (among %i possible maps)\n", mission->mapDef->id, countMinimal); | ||
267 | else | ||
268 | Com_DPrintf(DEBUG_CLIENT0x20, "Selected map '%s' (among %i possible maps)\n", mission->mapDef->id, countMinimal); | ||
269 | |||
270 | return true; | ||
271 | } | ||
272 | |||
273 | /** | ||
274 | * @brief Function to handle the campaign end | ||
275 | */ | ||
276 | void CP_EndCampaign (bool won) | ||
277 | { | ||
278 | Cmd_ExecuteString("game_exit"); | ||
279 | |||
280 | if (won) | ||
281 | cgi->UI_InitStack("endgame", NULL__null, true, true); | ||
282 | else | ||
283 | cgi->UI_InitStack("lostgame", NULL__null, true, true); | ||
284 | |||
285 | Com_Drop(); | ||
286 | } | ||
287 | |||
288 | /** | ||
289 | * @brief Checks whether the player has lost the campaign | ||
290 | */ | ||
291 | void CP_CheckLostCondition (const campaign_t *campaign) | ||
292 | { | ||
293 | bool endCampaign = false; | ||
294 | /* fraction of nation that can be below min happiness before the game is lost */ | ||
295 | const float nationBelowLimitPercentage = 0.5f; | ||
296 | |||
297 | if (cp_missiontest->integer) | ||
298 | return; | ||
299 | |||
300 | if (!endCampaign && ccs.credits < -campaign->negativeCreditsUntilLost) { | ||
301 | cgi->UI_RegisterText(TEXT_STANDARD, _("You've gone too far into debt.")gettext("You've gone too far into debt.")); | ||
302 | endCampaign = true; | ||
303 | } | ||
304 | |||
305 | /** @todo Should we make the campaign lost when a player loses all his bases? | ||
306 | * until he has set up a base again, the aliens might have invaded the whole | ||
307 | * world ;) - i mean, removing the credits check here. */ | ||
308 | if (ccs.credits < campaign->basecost - campaign->negativeCreditsUntilLost && !B_AtLeastOneExists()(B_GetNext(__null) != __null)) { | ||
309 | cgi->UI_RegisterText(TEXT_STANDARD, _("You've lost your bases and don't have enough money to build new ones.")gettext("You've lost your bases and don't have enough money to build new ones." )); | ||
310 | endCampaign = true; | ||
311 | } | ||
312 | |||
313 | if (!endCampaign) { | ||
314 | if (CP_GetAverageXVIRate() > campaign->maxAllowedXVIRateUntilLost) { | ||
315 | cgi->UI_RegisterText(TEXT_STANDARD, _("You have failed in your charter to protect Earth."gettext("You have failed in your charter to protect Earth." " Our home and our people have fallen to the alien infection. Only a handful" " of people on Earth remain human, and the remaining few no longer have a" " chance to stem the tide. Your command is no more; PHALANX is no longer" " able to operate as a functioning unit. Nothing stands between the aliens" " and total victory.") | ||
316 | " Our home and our people have fallen to the alien infection. Only a handful"gettext("You have failed in your charter to protect Earth." " Our home and our people have fallen to the alien infection. Only a handful" " of people on Earth remain human, and the remaining few no longer have a" " chance to stem the tide. Your command is no more; PHALANX is no longer" " able to operate as a functioning unit. Nothing stands between the aliens" " and total victory.") | ||
317 | " of people on Earth remain human, and the remaining few no longer have a"gettext("You have failed in your charter to protect Earth." " Our home and our people have fallen to the alien infection. Only a handful" " of people on Earth remain human, and the remaining few no longer have a" " chance to stem the tide. Your command is no more; PHALANX is no longer" " able to operate as a functioning unit. Nothing stands between the aliens" " and total victory.") | ||
318 | " chance to stem the tide. Your command is no more; PHALANX is no longer"gettext("You have failed in your charter to protect Earth." " Our home and our people have fallen to the alien infection. Only a handful" " of people on Earth remain human, and the remaining few no longer have a" " chance to stem the tide. Your command is no more; PHALANX is no longer" " able to operate as a functioning unit. Nothing stands between the aliens" " and total victory.") | ||
319 | " able to operate as a functioning unit. Nothing stands between the aliens"gettext("You have failed in your charter to protect Earth." " Our home and our people have fallen to the alien infection. Only a handful" " of people on Earth remain human, and the remaining few no longer have a" " chance to stem the tide. Your command is no more; PHALANX is no longer" " able to operate as a functioning unit. Nothing stands between the aliens" " and total victory.") | ||
320 | " and total victory.")gettext("You have failed in your charter to protect Earth." " Our home and our people have fallen to the alien infection. Only a handful" " of people on Earth remain human, and the remaining few no longer have a" " chance to stem the tide. Your command is no more; PHALANX is no longer" " able to operate as a functioning unit. Nothing stands between the aliens" " and total victory.")); | ||
321 | endCampaign = true; | ||
322 | } else { | ||
323 | /* check for nation happiness */ | ||
324 | int j, nationBelowLimit = 0; | ||
325 | for (j = 0; j < ccs.numNations; j++) { | ||
326 | const nation_t *nation = NAT_GetNationByIDX(j); | ||
327 | const nationInfo_t *stats = NAT_GetCurrentMonthInfo(nation); | ||
328 | if (stats->happiness < campaign->minhappiness) { | ||
329 | nationBelowLimit++; | ||
330 | } | ||
331 | } | ||
332 | if (nationBelowLimit >= nationBelowLimitPercentage * ccs.numNations) { | ||
333 | /* lost the game */ | ||
334 | cgi->UI_RegisterText(TEXT_STANDARD, _("Under your command, PHALANX operations have"gettext("Under your command, PHALANX operations have" " consistently failed to protect nations." " The UN, highly unsatisfied with your performance, has decided to remove" " you from command and subsequently disbands the PHALANX project as an" " effective task force. No further attempts at global cooperation are made." " Earth's nations each try to stand alone against the aliens, and eventually" " fall one by one.") | ||
335 | " consistently failed to protect nations."gettext("Under your command, PHALANX operations have" " consistently failed to protect nations." " The UN, highly unsatisfied with your performance, has decided to remove" " you from command and subsequently disbands the PHALANX project as an" " effective task force. No further attempts at global cooperation are made." " Earth's nations each try to stand alone against the aliens, and eventually" " fall one by one.") | ||
336 | " The UN, highly unsatisfied with your performance, has decided to remove"gettext("Under your command, PHALANX operations have" " consistently failed to protect nations." " The UN, highly unsatisfied with your performance, has decided to remove" " you from command and subsequently disbands the PHALANX project as an" " effective task force. No further attempts at global cooperation are made." " Earth's nations each try to stand alone against the aliens, and eventually" " fall one by one.") | ||
337 | " you from command and subsequently disbands the PHALANX project as an"gettext("Under your command, PHALANX operations have" " consistently failed to protect nations." " The UN, highly unsatisfied with your performance, has decided to remove" " you from command and subsequently disbands the PHALANX project as an" " effective task force. No further attempts at global cooperation are made." " Earth's nations each try to stand alone against the aliens, and eventually" " fall one by one.") | ||
338 | " effective task force. No further attempts at global cooperation are made."gettext("Under your command, PHALANX operations have" " consistently failed to protect nations." " The UN, highly unsatisfied with your performance, has decided to remove" " you from command and subsequently disbands the PHALANX project as an" " effective task force. No further attempts at global cooperation are made." " Earth's nations each try to stand alone against the aliens, and eventually" " fall one by one.") | ||
339 | " Earth's nations each try to stand alone against the aliens, and eventually"gettext("Under your command, PHALANX operations have" " consistently failed to protect nations." " The UN, highly unsatisfied with your performance, has decided to remove" " you from command and subsequently disbands the PHALANX project as an" " effective task force. No further attempts at global cooperation are made." " Earth's nations each try to stand alone against the aliens, and eventually" " fall one by one.") | ||
340 | " fall one by one.")gettext("Under your command, PHALANX operations have" " consistently failed to protect nations." " The UN, highly unsatisfied with your performance, has decided to remove" " you from command and subsequently disbands the PHALANX project as an" " effective task force. No further attempts at global cooperation are made." " Earth's nations each try to stand alone against the aliens, and eventually" " fall one by one.")); | ||
341 | endCampaign = true; | ||
342 | } | ||
343 | } | ||
344 | } | ||
345 | |||
346 | if (endCampaign) | ||
347 | CP_EndCampaign(false); | ||
348 | } | ||
349 | |||
350 | /* Initial fraction of the population in the country where a mission has been lost / won */ | ||
351 | #define XVI_LOST_START_PERCENTAGE0.20f 0.20f | ||
352 | #define XVI_WON_START_PERCENTAGE0.05f 0.05f | ||
353 | |||
354 | /** | ||
355 | * @brief Updates each nation's happiness. | ||
356 | * Should be called at the completion or expiration of every mission. | ||
357 | * The nation where the mission took place will be most affected, | ||
358 | * surrounding nations will be less affected. | ||
359 | * @todo Scoring should eventually be expanded to include such elements as | ||
360 | * infected humans and mission objectives other than xenocide. | ||
361 | */ | ||
362 | void CP_HandleNationData (float minHappiness, mission_t * mis, const nation_t *affectedNation, const missionResults_t *results) | ||
363 | { | ||
364 | int i; | ||
365 | const float civilianSum = (float) (results->civiliansSurvived + results->civiliansKilled + results->civiliansKilledFriendlyFire); | ||
366 | const float alienSum = (float) (results->aliensSurvived + results->aliensKilled + results->aliensStunned); | ||
367 | float performance, performanceAlien, performanceCivilian; | ||
368 | float deltaHappiness = 0.0f; | ||
369 | float happinessDivisor = 5.0f; | ||
370 | |||
371 | /** @todo HACK: This should be handled properly, i.e. civilians should only factor into the scoring | ||
372 | * if the mission objective is actually to save civilians. */ | ||
373 | if (civilianSum == 0) { | ||
374 | Com_DPrintf(DEBUG_CLIENT0x20, "CP_HandleNationData: Warning, civilianSum == 0, score for this mission will default to 0.\n"); | ||
375 | performance = 0.0f; | ||
376 | } else { | ||
377 | /* Calculate how well the mission went. */ | ||
378 | performanceCivilian = (2 * civilianSum - results->civiliansKilled - 2 | ||
379 | * results->civiliansKilledFriendlyFire) * 3 / (2 * civilianSum) - 2; | ||
380 | /** @todo The score for aliens is always negative or zero currently, but this | ||
381 | * should be dependent on the mission objective. | ||
382 | * In a mission that has a special objective, the amount of killed aliens should | ||
383 | * only serve to increase the score, not reduce the penalty. */ | ||
384 | performanceAlien = results->aliensKilled + results->aliensStunned - alienSum; | ||
385 | performance = performanceCivilian + performanceAlien; | ||
386 | } | ||
387 | |||
388 | /* Calculate the actual happiness delta. The bigger the mission, the more potential influence. */ | ||
389 | deltaHappiness = 0.004 * civilianSum + 0.004 * alienSum; | ||
390 | |||
391 | /* There is a maximum base happiness delta. */ | ||
392 | if (deltaHappiness > HAPPINESS_MAX_MISSION_IMPACT0.07) | ||
393 | deltaHappiness = HAPPINESS_MAX_MISSION_IMPACT0.07; | ||
394 | |||
395 | for (i = 0; i < ccs.numNations; i++) { | ||
396 | nation_t *nation = NAT_GetNationByIDX(i); | ||
397 | const nationInfo_t *stats = NAT_GetCurrentMonthInfo(nation); | ||
398 | float happinessFactor; | ||
399 | |||
400 | /* update happiness. */ | ||
401 | if (nation == affectedNation) | ||
402 | happinessFactor = deltaHappiness; | ||
403 | else | ||
404 | happinessFactor = deltaHappiness / happinessDivisor; | ||
405 | |||
406 | NAT_SetHappiness(minHappiness, nation, stats->happiness + performance * happinessFactor); | ||
407 | } | ||
408 | } | ||
409 | |||
410 | /** | ||
411 | * @brief Check for missions that have a timeout defined | ||
412 | */ | ||
413 | static void CP_CheckMissionEnd (const campaign_t* campaign) | ||
414 | { | ||
415 | MIS_Foreach(mission)for (bool mission__break = false, mission__once = true; mission__once ; mission__once = false) for (linkedList_t const* mission__iter = (ccs.missions); ! mission__break && mission__iter; ) for (mission_t* const mission = ( mission__break = mission__once = true, (mission_t*) mission__iter->data); mission__once; mission__break = mission__once = false) if ( mission__iter = mission__iter->next, false) {} else { | ||
416 | if (CP_CheckMissionLimitedInTime(mission) && Date_LaterThan(&ccs.date, &mission->finalDate)) | ||
417 | CP_MissionStageEnd(campaign, mission); | ||
418 | } | ||
419 | } | ||
420 | |||
421 | /* =========================================================== */ | ||
422 | |||
423 | /** | ||
424 | * @brief Functions that should be called with a minimum time lapse (will be called at least every DETECTION_INTERVAL) | ||
425 | * @param[in] campaign The campaign data structure | ||
426 | * @param[in] dt Elapsed game seconds since last call. | ||
427 | * @param[in] updateRadarOverlay true if radar overlay should be updated (only for drawing purpose) | ||
428 | * @sa CP_CampaignRun | ||
429 | */ | ||
430 | static void CP_CampaignFunctionPeriodicCall (campaign_t* campaign, int dt, bool updateRadarOverlay) | ||
431 | { | ||
432 | UFO_CampaignRunUFOs(campaign, dt); | ||
433 | AIR_CampaignRun(campaign, dt, updateRadarOverlay); | ||
434 | |||
435 | AIRFIGHT_CampaignRunBaseDefence(dt); | ||
436 | AIRFIGHT_CampaignRunProjectiles(campaign, dt); | ||
437 | CP_CheckNewMissionDetectedOnGeoscape(); | ||
438 | |||
439 | /* Update alien interest for bases */ | ||
440 | UFO_UpdateAlienInterestForAllBasesAndInstallations(); | ||
441 | |||
442 | /* Update how phalanx troop know alien bases */ | ||
443 | AB_UpdateStealthForAllBase(); | ||
444 | |||
445 | UFO_CampaignCheckEvents(); | ||
446 | } | ||
447 | |||
448 | /** | ||
449 | * @brief Returns if we are currently on the Geoscape | ||
450 | * @todo This relies on scripted content. Should work other way! | ||
451 | */ | ||
452 | bool CP_OnGeoscape (void) | ||
453 | { | ||
454 | return Q_streq("geoscape", cgi->UI_GetActiveWindowName())(strcmp("geoscape", cgi->UI_GetActiveWindowName()) == 0); | ||
455 | } | ||
456 | |||
457 | /** | ||
458 | * @brief delay between actions that must be executed independently of time scale | ||
459 | * @sa RADAR_CheckUFOSensored | ||
460 | * @sa UFO_UpdateAlienInterestForAllBasesAndInstallations | ||
461 | * @sa AB_UpdateStealthForAllBase | ||
462 | */ | ||
463 | const int DETECTION_INTERVAL = (SECONDS_PER_HOUR3600 / 2); | ||
464 | |||
465 | /** | ||
466 | * @brief Ensure that the day always matches the seconds. If the seconds | ||
467 | * per day limit is reached, the seconds are reset and the day is increased. | ||
468 | * @param seconds The seconds to add to the campaign date | ||
469 | */ | ||
470 | static inline void CP_AdvanceTimeBySeconds (int seconds) | ||
471 | { | ||
472 | ccs.date.sec += seconds; | ||
473 | while (ccs.date.sec >= SECONDS_PER_DAY86400) { | ||
474 | ccs.date.sec -= SECONDS_PER_DAY86400; | ||
475 | ccs.date.day++; | ||
476 | } | ||
477 | } | ||
478 | |||
479 | /** | ||
480 | * @return @c true if a month has passed | ||
481 | */ | ||
482 | static inline bool CP_IsBudgetDue (const dateLong_t *oldDate, const dateLong_t *date) | ||
483 | { | ||
484 | if (oldDate->year < date->year) { | ||
485 | return true; | ||
486 | } | ||
487 | return oldDate->month < date->month; | ||
488 | } | ||
489 | |||
490 | /** | ||
491 | * @brief Called every frame when we are in geoscape view | ||
492 | * @note Called for node types cgi->UI_MAP and cgi->UI_3DMAP | ||
493 | * @sa NAT_HandleBudget | ||
494 | * @sa B_UpdateBaseData | ||
495 | * @sa AIR_CampaignRun | ||
496 | */ | ||
497 | void CP_CampaignRun (campaign_t *campaign, float secondsSinceLastFrame) | ||
498 | { | ||
499 | /* advance time */ | ||
500 | ccs.frametime = secondsSinceLastFrame; | ||
501 | ccs.timer += secondsSinceLastFrame * ccs.gameTimeScale; | ||
502 | |||
503 | UP_GetUnreadMails(); | ||
504 | |||
505 | if (ccs.timer >= 1.0) { | ||
506 | /* calculate new date */ | ||
507 | int currenthour; | ||
508 | int currentmin; | ||
509 | int currentsecond = ccs.date.sec; | ||
510 | int currentday = ccs.date.day; | ||
511 | int i; | ||
512 | const int currentinterval = (int)floor(currentsecond) % DETECTION_INTERVAL; | ||
513 | int dt = DETECTION_INTERVAL - currentinterval; | ||
514 | dateLong_t date, oldDate; | ||
515 | const int checks = (currentinterval + (int)floor(ccs.timer)) / DETECTION_INTERVAL; | ||
516 | |||
517 | CP_DateConvertLong(&ccs.date, &oldDate); | ||
518 | |||
519 | currenthour = (int)floor(currentsecond / SECONDS_PER_HOUR3600); | ||
520 | currentmin = (int)floor(currentsecond / SECONDS_PER_MINUTE60); | ||
521 | |||
522 | /* Execute every actions that needs to be independent of time speed : every DETECTION_INTERVAL | ||
523 | * - Run UFOs and craft at least every DETECTION_INTERVAL. If detection occurred, break. | ||
524 | * - Check if any new mission is detected | ||
525 | * - Update stealth value of phalanx bases and installations ; alien bases */ | ||
526 | for (i = 0; i < checks; i++) { | ||
527 | ccs.timer -= dt; | ||
528 | currentsecond += dt; | ||
529 | CP_AdvanceTimeBySeconds(dt); | ||
530 | CP_CampaignFunctionPeriodicCall(campaign, dt, false); | ||
531 | |||
532 | /* if something stopped time, we must stop here the loop */ | ||
533 | if (CP_IsTimeStopped()) { | ||
534 | ccs.timer = 0.0f; | ||
535 | break; | ||
536 | } | ||
537 | dt = DETECTION_INTERVAL; | ||
538 | } | ||
539 | |||
540 | dt = (int)floor(ccs.timer); | ||
541 | |||
542 | CP_AdvanceTimeBySeconds(dt); | ||
543 | currentsecond += dt; | ||
544 | ccs.timer -= dt; | ||
545 | |||
546 | /* compute minutely events */ | ||
547 | /* (this may run multiple times if the time stepping is > 1 minute at a time) */ | ||
548 | while (currentmin < (int)floor(currentsecond / SECONDS_PER_MINUTE60)) { | ||
549 | currentmin++; | ||
550 | PR_ProductionRun(); | ||
551 | B_UpdateBaseData(); | ||
552 | } | ||
553 | |||
554 | /* compute hourly events */ | ||
555 | /* (this may run multiple times if the time stepping is > 1 hour at a time) */ | ||
556 | while (currenthour < (int)floor(currentsecond / SECONDS_PER_HOUR3600)) { | ||
557 | currenthour++; | ||
558 | RS_ResearchRun(); | ||
559 | UR_ProcessActive(); | ||
560 | AII_UpdateInstallationDelay(); | ||
561 | AII_RepairAircraft(); | ||
562 | TR_TransferRun(); | ||
563 | INT_IncreaseAlienInterest(campaign); | ||
564 | } | ||
565 | |||
566 | /* daily events */ | ||
567 | for (i = currentday; i < ccs.date.day; i++) { | ||
568 | /* every day */ | ||
569 | INS_UpdateInstallationData(); | ||
570 | HOS_HospitalRun(); | ||
571 | ccs.missionSpawnCallback(); | ||
572 | CP_SpreadXVI(); | ||
573 | NAT_UpdateHappinessForAllNations(campaign->minhappiness); | ||
574 | AB_BaseSearchedByNations(); | ||
575 | CP_CampaignRunMarket(campaign); | ||
576 | CP_CheckCampaignEvents(campaign); | ||
577 | CP_ReduceXVIEverywhere(); | ||
578 | /* should be executed after all daily event that could | ||
579 | * change XVI overlay */ | ||
580 | CP_UpdateNationXVIInfection(); | ||
581 | } | ||
582 | |||
583 | if (dt > 0) { | ||
584 | /* check for campaign events | ||
585 | * aircraft and UFO already moved during radar detection (see above), | ||
586 | * just make them move the missing part -- if any */ | ||
587 | CP_CampaignFunctionPeriodicCall(campaign, dt, true); | ||
588 | } | ||
589 | |||
590 | CP_CheckMissionEnd(campaign); | ||
591 | CP_CheckLostCondition(campaign); | ||
592 | /* Check if there is a base attack mission */ | ||
593 | CP_CheckBaseAttacks(); | ||
594 | /* check if any stores are full */ | ||
595 | CAP_CheckOverflow(); | ||
596 | BDEF_AutoSelectTarget(); | ||
597 | |||
598 | CP_DateConvertLong(&ccs.date, &date); | ||
599 | /* every new month we have to handle the budget */ | ||
600 | if (CP_IsBudgetDue(&oldDate, &date) && ccs.paid && B_AtLeastOneExists()(B_GetNext(__null) != __null)) { | ||
601 | NAT_BackupMonthlyData(); | ||
602 | NAT_HandleBudget(campaign); | ||
603 | ccs.paid = false; | ||
604 | } else if (date.day > 1) | ||
605 | ccs.paid = true; | ||
606 | |||
607 | CP_UpdateXVIMapButton(); | ||
608 | /* set time cvars */ | ||
609 | CP_UpdateTime(); | ||
610 | } | ||
611 | } | ||
612 | |||
613 | /** | ||
614 | * @brief Checks whether you have enough credits for something | ||
615 | * @param[in] costs costs to check | ||
616 | */ | ||
617 | bool CP_CheckCredits (int costs) | ||
618 | { | ||
619 | if (costs > ccs.credits) | ||
620 | return false; | ||
621 | return true; | ||
622 | } | ||
623 | |||
624 | /** | ||
625 | * @brief Sets credits and update mn_credits cvar | ||
626 | * @param[in] credits The new credits value | ||
627 | * Checks whether credits are bigger than MAX_CREDITS | ||
628 | */ | ||
629 | void CP_UpdateCredits (int credits) | ||
630 | { | ||
631 | /* credits */ | ||
632 | if (credits > MAX_CREDITS10000000) | ||
633 | credits = MAX_CREDITS10000000; | ||
634 | ccs.credits = credits; | ||
635 | Cvar_Set("mn_credits", va(_("%i c")gettext("%i c"), ccs.credits)); | ||
636 | } | ||
637 | |||
638 | /** | ||
639 | * @brief Load mapDef statistics | ||
640 | * @param[in] parent XML Node structure, where we get the information from | ||
641 | */ | ||
642 | static bool CP_LoadMapDefStatXML (xmlNode_tmxml_node_t *parent) | ||
643 | { | ||
644 | xmlNode_tmxml_node_t *node; | ||
645 | |||
646 | for (node = XML_GetNode(parent, SAVE_CAMPAIGN_MAPDEF"mapDef"); node; node = XML_GetNextNode(node, parent, SAVE_CAMPAIGN_MAPDEF"mapDef")) { | ||
647 | const char *s = XML_GetString(node, SAVE_CAMPAIGN_MAPDEF_ID"id"); | ||
648 | mapDef_t *map; | ||
649 | |||
650 | if (s[0] == '\0') { | ||
651 | Com_Printf("Warning: MapDef with no id in xml!\n"); | ||
652 | continue; | ||
653 | } | ||
654 | map = Com_GetMapDefinitionByID(s); | ||
655 | if (!map) { | ||
656 | Com_Printf("Warning: No MapDef with id '%s'!\n", s); | ||
657 | continue; | ||
658 | } | ||
659 | map->timesAlreadyUsed = XML_GetInt(node, SAVE_CAMPAIGN_MAPDEF_COUNT"count", 0); | ||
660 | } | ||
661 | |||
662 | return true; | ||
663 | } | ||
664 | |||
665 | /** | ||
666 | * @brief Load callback for savegames in XML Format | ||
667 | * @param[in] parent XML Node structure, where we get the information from | ||
668 | */ | ||
669 | bool CP_LoadXML (xmlNode_tmxml_node_t *parent) | ||
670 | { | ||
671 | xmlNode_tmxml_node_t *campaignNode; | ||
672 | xmlNode_tmxml_node_t *mapNode; | ||
673 | const char *name; | ||
674 | campaign_t *campaign; | ||
675 | xmlNode_tmxml_node_t *mapDefStat; | ||
676 | |||
677 | campaignNode = XML_GetNode(parent, SAVE_CAMPAIGN_CAMPAIGN"campaign"); | ||
678 | if (!campaignNode) { | ||
679 | Com_Printf("Did not find campaign entry in xml!\n"); | ||
680 | return false; | ||
681 | } | ||
682 | if (!(name = XML_GetString(campaignNode, SAVE_CAMPAIGN_ID"id"))) { | ||
683 | Com_Printf("couldn't locate campaign name in savegame\n"); | ||
684 | return false; | ||
685 | } | ||
686 | |||
687 | campaign = CP_GetCampaign(name); | ||
688 | if (!campaign) { | ||
689 | Com_Printf("......campaign \"%s\" doesn't exist.\n", name); | ||
690 | return false; | ||
691 | } | ||
692 | |||
693 | CP_CampaignInit(campaign, true); | ||
694 | /* init the map images and reset the map actions */ | ||
695 | MAP_Reset(campaign->map); | ||
696 | |||
697 | /* read credits */ | ||
698 | CP_UpdateCredits(XML_GetLong(campaignNode, SAVE_CAMPAIGN_CREDITS"credits", 0)); | ||
699 | ccs.paid = XML_GetBool(campaignNode, SAVE_CAMPAIGN_PAID"paid", false); | ||
700 | |||
701 | cgi->SetNextUniqueCharacterNumber(XML_GetInt(campaignNode, SAVE_CAMPAIGN_NEXTUNIQUECHARACTERNUMBER"nextUniqueCharacterNumber", 0)); | ||
702 | |||
703 | XML_GetDate(campaignNode, SAVE_CAMPAIGN_DATE"date", &ccs.date.day, &ccs.date.sec); | ||
704 | |||
705 | /* read other campaign data */ | ||
706 | ccs.civiliansKilled = XML_GetInt(campaignNode, SAVE_CAMPAIGN_CIVILIANSKILLED"civiliansKilled", 0); | ||
707 | ccs.aliensKilled = XML_GetInt(campaignNode, SAVE_CAMPAIGN_ALIENSKILLED"aliensKilled", 0); | ||
708 | |||
709 | Com_DPrintf(DEBUG_CLIENT0x20, "CP_LoadXML: Getting position\n"); | ||
710 | |||
711 | /* read map view */ | ||
712 | mapNode = XML_GetNode(campaignNode, SAVE_CAMPAIGN_MAP"map"); | ||
713 | ccs.center[0] = XML_GetFloat(mapNode, SAVE_CAMPAIGN_CENTER0"center0", 0.0); | ||
714 | ccs.center[1] = XML_GetFloat(mapNode, SAVE_CAMPAIGN_CENTER1"center1", 0.0); | ||
715 | ccs.angles[0] = XML_GetFloat(mapNode, SAVE_CAMPAIGN_ANGLES0"angles0", 0.0); | ||
716 | ccs.angles[1] = XML_GetFloat(mapNode, SAVE_CAMPAIGN_ANGLES1"angles1", 0.0); | ||
717 | ccs.zoom = XML_GetFloat(mapNode, SAVE_CAMPAIGN_ZOOM"zoom", 0.0); | ||
718 | /* restore the overlay. | ||
719 | * do not use Cvar_SetValue, because this function check if value->string are equal to skip calculation | ||
720 | * and we never set r_geoscape_overlay->string in game: cl_geoscape_overlay won't be updated if the loaded | ||
721 | * value is 0 (and that's a problem if you're loading a game when cl_geoscape_overlay is set to another value */ | ||
722 | cl_geoscape_overlay->integer = XML_GetInt(mapNode, SAVE_CAMPAIGN_CL_GEOSCAPE_OVERLAY"r_geoscape_overlay", 0); | ||
723 | radarOverlayWasSet = XML_GetBool(mapNode, SAVE_CAMPAIGN_RADAROVERLAYWASSET"radarOverlayWasSet", false); | ||
724 | ccs.XVIShowMap = XML_GetBool(mapNode, SAVE_CAMPAIGN_XVISHOWMAP"XVIShowmap", false); | ||
725 | CP_UpdateXVIMapButton(); | ||
726 | |||
727 | mapDefStat = XML_GetNode(campaignNode, SAVE_CAMPAIGN_MAPDEFSTAT"mapDefStat"); | ||
728 | if (mapDefStat && !CP_LoadMapDefStatXML(mapDefStat)) | ||
729 | return false; | ||
730 | |||
731 | mxmlDelete(campaignNode); | ||
732 | return true; | ||
733 | } | ||
734 | |||
735 | /** | ||
736 | * @brief Save mapDef statistics | ||
737 | * @param[out] parent XML Node structure, where we write the information to | ||
738 | */ | ||
739 | static bool CP_SaveMapDefStatXML (xmlNode_tmxml_node_t *parent) | ||
740 | { | ||
741 | const mapDef_t *md; | ||
742 | |||
743 | MapDef_ForeachSingleplayerCampaign(md)for (int md__loopvar = 0; (md) = __null, md__loopvar < csi .numMDs; md__loopvar++) if ((md) = &csi.mds[md__loopvar], !((md)->singleplayer && (md)->campaign)) {} else { | ||
744 | if (md->timesAlreadyUsed > 0) { | ||
745 | xmlNode_tmxml_node_t *node = XML_AddNode(parent, SAVE_CAMPAIGN_MAPDEF"mapDef"); | ||
746 | XML_AddString(node, SAVE_CAMPAIGN_MAPDEF_ID"id", md->id); | ||
747 | XML_AddInt(node, SAVE_CAMPAIGN_MAPDEF_COUNT"count", md->timesAlreadyUsed); | ||
748 | } | ||
749 | } | ||
750 | |||
751 | return true; | ||
752 | } | ||
753 | |||
754 | /** | ||
755 | * @brief Save callback for savegames in XML Format | ||
756 | * @param[out] parent XML Node structure, where we write the information to | ||
757 | */ | ||
758 | bool CP_SaveXML (xmlNode_tmxml_node_t *parent) | ||
759 | { | ||
760 | xmlNode_tmxml_node_t *campaign; | ||
761 | xmlNode_tmxml_node_t *map; | ||
762 | xmlNode_tmxml_node_t *mapDefStat; | ||
763 | |||
764 | campaign = XML_AddNode(parent, SAVE_CAMPAIGN_CAMPAIGN"campaign"); | ||
765 | |||
766 | XML_AddString(campaign, SAVE_CAMPAIGN_ID"id", ccs.curCampaign->id); | ||
767 | XML_AddDate(campaign, SAVE_CAMPAIGN_DATE"date", ccs.date.day, ccs.date.sec); | ||
768 | XML_AddLong(campaign, SAVE_CAMPAIGN_CREDITS"credits", ccs.credits); | ||
769 | XML_AddShort(campaign, SAVE_CAMPAIGN_PAID"paid", ccs.paid); | ||
770 | XML_AddShortValue(campaign, SAVE_CAMPAIGN_NEXTUNIQUECHARACTERNUMBER"nextUniqueCharacterNumber", cgi->GetNextUniqueCharacterNumber()); | ||
771 | |||
772 | XML_AddIntValue(campaign, SAVE_CAMPAIGN_CIVILIANSKILLED"civiliansKilled", ccs.civiliansKilled); | ||
773 | XML_AddIntValue(campaign, SAVE_CAMPAIGN_ALIENSKILLED"aliensKilled", ccs.aliensKilled); | ||
774 | |||
775 | /* Map and user interface */ | ||
776 | map = XML_AddNode(campaign, SAVE_CAMPAIGN_MAP"map"); | ||
777 | XML_AddFloat(map, SAVE_CAMPAIGN_CENTER0"center0", ccs.center[0]); | ||
778 | XML_AddFloat(map, SAVE_CAMPAIGN_CENTER1"center1", ccs.center[1]); | ||
779 | XML_AddFloat(map, SAVE_CAMPAIGN_ANGLES0"angles0", ccs.angles[0]); | ||
780 | XML_AddFloat(map, SAVE_CAMPAIGN_ANGLES1"angles1", ccs.angles[1]); | ||
781 | XML_AddFloat(map, SAVE_CAMPAIGN_ZOOM"zoom", ccs.zoom); | ||
782 | XML_AddShort(map, SAVE_CAMPAIGN_CL_GEOSCAPE_OVERLAY"r_geoscape_overlay", cl_geoscape_overlay->integer); | ||
783 | XML_AddBool(map, SAVE_CAMPAIGN_RADAROVERLAYWASSET"radarOverlayWasSet", radarOverlayWasSet); | ||
784 | XML_AddBool(map, SAVE_CAMPAIGN_XVISHOWMAP"XVIShowmap", ccs.XVIShowMap); | ||
785 | |||
786 | mapDefStat = XML_AddNode(campaign, SAVE_CAMPAIGN_MAPDEFSTAT"mapDefStat"); | ||
787 | if (!CP_SaveMapDefStatXML(mapDefStat)) | ||
788 | return false; | ||
789 | |||
790 | return true; | ||
791 | } | ||
792 | |||
793 | /** | ||
794 | * @brief Starts a selected mission | ||
795 | * @note Checks whether a dropship is near the landing zone and whether | ||
796 | * it has a team on board | ||
797 | * @sa CP_SetMissionVars | ||
798 | */ | ||
799 | void CP_StartSelectedMission (void) | ||
800 | { | ||
801 | mission_t *mis; | ||
802 | aircraft_t *aircraft = MAP_GetMissionAircraft()(ccs.geoscape.missionAircraft); | ||
803 | base_t *base; | ||
804 | battleParam_t *battleParam = &ccs.battleParameters; | ||
805 | |||
806 | if (!aircraft) { | ||
807 | Com_Printf("CP_StartSelectedMission: No mission aircraft\n"); | ||
808 | return; | ||
809 | } | ||
810 | |||
811 | base = aircraft->homebase; | ||
812 | |||
813 | if (MAP_GetSelectedMission()(ccs.geoscape.selectedMission) == NULL__null) | ||
814 | MAP_SetSelectedMission(aircraft->mission)(ccs.geoscape.selectedMission = (aircraft->mission)); | ||
815 | |||
816 | mis = MAP_GetSelectedMission()(ccs.geoscape.selectedMission); | ||
817 | if (!mis) { | ||
818 | Com_Printf("CP_StartSelectedMission: No mission selected\n"); | ||
819 | return; | ||
820 | } | ||
821 | |||
822 | /* Before we start, we should clear the missionResults array. */ | ||
823 | OBJZERO(ccs.missionResults)(memset(&((ccs.missionResults)), (0), sizeof((ccs.missionResults )))); | ||
824 | |||
825 | /* Various sanity checks. */ | ||
826 | if (!mis->active) { | ||
827 | Com_Printf("CP_StartSelectedMission: Dropship not near landing zone: mis->active: %i\n", mis->active); | ||
828 | return; | ||
829 | } | ||
830 | if (AIR_GetTeamSize(aircraft) == 0) { | ||
831 | Com_Printf("CP_StartSelectedMission: No team in dropship.\n"); | ||
832 | return; | ||
833 | } | ||
834 | |||
835 | /* if we retry a mission we have to drop from the current game before */ | ||
836 | SV_Shutdown("Server quit.", false); | ||
837 | cgi->CL_Disconnect(); | ||
838 | |||
839 | CP_CreateBattleParameters(mis, battleParam, aircraft); | ||
840 | CP_SetMissionVars(mis, battleParam); | ||
841 | |||
842 | /* manage inventory */ | ||
843 | /** | ||
844 | * @todo find out why this black-magic with inventory is needed and clean up | ||
845 | * @sa AM_Go | ||
846 | * @sa CP_MissionEnd | ||
847 | */ | ||
848 | ccs.eMission = base->storage; /* copied, including arrays inside! */ | ||
849 | CP_CleanTempInventory(base); | ||
850 | CP_CleanupAircraftCrew(aircraft, &ccs.eMission); | ||
851 | CP_StartMissionMap(mis, battleParam); | ||
852 | } | ||
853 | |||
854 | /** | ||
855 | * @brief Checks whether a soldier should be promoted | ||
856 | * @param[in] rank The rank to check for | ||
857 | * @param[in] chr The character to check a potential promotion for | ||
858 | * @todo (Zenerka 20080301) extend ranks and change calculations here. | ||
859 | */ | ||
860 | static bool CP_ShouldUpdateSoldierRank (const rank_t *rank, const character_t* chr) | ||
861 | { | ||
862 | if (rank->type != EMPL_SOLDIER) | ||
863 | return false; | ||
864 | |||
865 | /* mind is not yet enough */ | ||
866 | if (chr->score.skills[ABILITY_MIND] < rank->mind) | ||
867 | return false; | ||
868 | |||
869 | /* not enough killed enemies yet */ | ||
870 | if (chr->score.kills[KILLED_ENEMIES] < rank->killedEnemies) | ||
871 | return false; | ||
872 | |||
873 | /* too many civilians and team kills */ | ||
874 | if (chr->score.kills[KILLED_CIVILIANS] + chr->score.kills[KILLED_TEAM] > rank->killedOthers) | ||
875 | return false; | ||
876 | |||
877 | return true; | ||
878 | } | ||
879 | |||
880 | /** | ||
881 | * @brief Update employees stats after mission. | ||
882 | * @param[in] base The base where the team lives. | ||
883 | * @param[in] aircraft The aircraft used for the mission. | ||
884 | * @note Soldier promotion is being done here. | ||
885 | */ | ||
886 | void CP_UpdateCharacterStats (const base_t *base, const aircraft_t *aircraft) | ||
887 | { | ||
888 | assert(aircraft)(__builtin_expect(!(aircraft), 0) ? __assert_rtn(__func__, "src/client/cgame/campaign/cp_campaign.cpp" , 888, "aircraft") : (void)0); | ||
889 | |||
890 | /* only soldiers have stats and ranks, ugvs not */ | ||
891 | E_Foreach(EMPL_SOLDIER, employee)for (bool employee__break = false, employee__once = true; employee__once ; employee__once = false) for (linkedList_t const* employee__iter = (ccs.employees[EMPL_SOLDIER]); ! employee__break && employee__iter;) for (employee_t* const employee = ( employee__break = employee__once = true, (employee_t*) employee__iter->data ); employee__once; employee__break = employee__once = false) if ( employee__iter = employee__iter->next, false) {} else { | ||
892 | if (!E_IsInBase(employee, aircraft->homebase)) | ||
893 | continue; | ||
894 | if (AIR_IsEmployeeInAircraft(employee, aircraft)) { | ||
895 | character_t *chr = &employee->chr; | ||
896 | |||
897 | /* Remember the number of assigned mission for this character. */ | ||
898 | chr->score.assignedMissions++; | ||
899 | |||
900 | /** @todo use chrScore_t to determine negative influence on soldier here, | ||
901 | * like killing too many civilians and teammates can lead to unhire and disband | ||
902 | * such soldier, or maybe rank degradation. */ | ||
903 | |||
904 | /* Check if the soldier meets the requirements for a higher rank | ||
905 | * and do a promotion. */ | ||
906 | if (ccs.numRanks >= 2) { | ||
907 | int j; | ||
908 | for (j = ccs.numRanks - 1; j > chr->score.rank; j--) { | ||
909 | const rank_t *rank = CL_GetRankByIdx(j); | ||
910 | if (CP_ShouldUpdateSoldierRank(rank, chr)) { | ||
911 | chr->score.rank = j; | ||
912 | if (chr->HP > 0) | ||
913 | Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("%s has been promoted to %s.\n")gettext("%s has been promoted to %s.\n"), chr->name, _(rank->name)gettext(rank->name)); | ||
914 | else | ||
915 | Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("%s has been awarded the posthumous rank of %s\nfor inspirational gallantry in the face of overwhelming odds.\n")gettext("%s has been awarded the posthumous rank of %s\nfor inspirational gallantry in the face of overwhelming odds.\n" ), chr->name, _(rank->name)gettext(rank->name)); | ||
916 | MS_AddNewMessage(_("Soldier promoted")gettext("Soldier promoted"), cp_messageBuffer, MSG_PROMOTION); | ||
917 | break; | ||
918 | } | ||
919 | } | ||
920 | } | ||
921 | } | ||
922 | } | ||
923 | Com_DPrintf(DEBUG_CLIENT0x20, "CP_UpdateCharacterStats: Done\n"); | ||
924 | } | ||
925 | |||
926 | #ifdef DEBUG1 | ||
927 | /** | ||
928 | * @brief Debug function to add one item of every type to base storage and mark them collected. | ||
929 | * @note Command to call this: debug_additems | ||
930 | */ | ||
931 | static void CP_DebugAllItems_f (void) | ||
932 | { | ||
933 | int i; | ||
934 | base_t *base; | ||
935 | |||
936 | if (Cmd_Argc() < 2) { | ||
937 | Com_Printf("Usage: %s <baseID>\n", Cmd_Argv(0)); | ||
938 | return; | ||
939 | } | ||
940 | |||
941 | i = atoi(Cmd_Argv(1)); | ||
942 | if (i >= B_GetCount()) { | ||
943 | Com_Printf("invalid baseID (%s)\n", Cmd_Argv(1)); | ||
944 | return; | ||
945 | } | ||
946 | base = B_GetBaseByIDX(i); | ||
947 | |||
948 | for (i = 0; i < csi.numODs; i++) { | ||
949 | const objDef_t *obj = INVSH_GetItemByIDX(i); | ||
950 | if (!obj->weapon && !obj->numWeapons) | ||
951 | continue; | ||
952 | B_UpdateStorageAndCapacity(base, obj, 1, true); | ||
953 | if (B_ItemInBase(obj, base) > 0) { | ||
954 | technology_t *tech = RS_GetTechForItem(obj); | ||
955 | RS_MarkCollected(tech); | ||
956 | } | ||
957 | } | ||
958 | } | ||
959 | |||
960 | /** | ||
961 | * @brief Debug function to show items in base storage. | ||
962 | * @note Command to call this: debug_listitem | ||
963 | */ | ||
964 | static void CP_DebugShowItems_f (void) | ||
965 | { | ||
966 | int i; | ||
967 | base_t *base; | ||
968 | |||
969 | if (Cmd_Argc() < 2) { | ||
970 | Com_Printf("Usage: %s <baseID>\n", Cmd_Argv(0)); | ||
971 | return; | ||
972 | } | ||
973 | |||
974 | i = atoi(Cmd_Argv(1)); | ||
975 | if (i >= B_GetCount()) { | ||
976 | Com_Printf("invalid baseID (%s)\n", Cmd_Argv(1)); | ||
977 | return; | ||
978 | } | ||
979 | base = B_GetBaseByIDX(i); | ||
980 | |||
981 | for (i = 0; i < csi.numODs; i++) { | ||
982 | const objDef_t *obj = INVSH_GetItemByIDX(i); | ||
983 | Com_Printf("%i. %s: %i\n", i, obj->id, B_ItemInBase(obj, base)); | ||
984 | } | ||
985 | } | ||
986 | |||
987 | /** | ||
988 | * @brief Debug function to set the credits to max | ||
989 | */ | ||
990 | static void CP_DebugFullCredits_f (void) | ||
991 | { | ||
992 | CP_UpdateCredits(MAX_CREDITS10000000); | ||
993 | } | ||
994 | #endif | ||
995 | |||
996 | /* ===================================================================== */ | ||
997 | |||
998 | /* these commands are only available in singleplayer */ | ||
999 | static const cmdList_t game_commands[] = { | ||
1000 | {"update_base_radar_coverage", RADAR_UpdateBaseRadarCoverage_f, "Update base radar coverage"}, | ||
1001 | {"addeventmail", CL_EventAddMail_f, "Add a new mail (event trigger) - e.g. after a mission"}, | ||
1002 | {"stats_update", CP_StatsUpdate_f, NULL__null}, | ||
1003 | {"game_go", CP_StartSelectedMission, NULL__null}, | ||
1004 | {"game_timestop", CP_GameTimeStop, NULL__null}, | ||
1005 | {"game_timeslow", CP_GameTimeSlow, NULL__null}, | ||
1006 | {"game_timefast", CP_GameTimeFast, NULL__null}, | ||
1007 | {"game_settimeid", CP_SetGameTime_f, NULL__null}, | ||
1008 | {"map_center", MAP_CenterOnPoint_f, "Centers the geoscape view on items on the geoscape - and cycle through them"}, | ||
1009 | {"map_zoom", MAP_Zoom_f, NULL__null}, | ||
1010 | {"map_scroll", MAP_Scroll_f, NULL__null}, | ||
1011 | {"cp_start_xvi_spreading", CP_StartXVISpreading_f, "Start XVI spreading"}, | ||
1012 | {"cp_spawn_ufocarrier", CP_SpawnUFOCarrier_f, "Spawns a UFO-Carrier on the geoscape"}, | ||
1013 | {"cp_attack_ufocarrier", CP_AttackUFOCarrier_f, "Attack the UFO-Carrier"}, | ||
1014 | #ifdef DEBUG1 | ||
1015 | {"debug_fullcredits", CP_DebugFullCredits_f, "Debug function to give the player full credits"}, | ||
1016 | {"debug_additems", CP_DebugAllItems_f, "Debug function to add one item of every type to base storage and mark related tech collected"}, | ||
1017 | {"debug_listitem", CP_DebugShowItems_f, "Debug function to show all items in base storage"}, | ||
1018 | #endif | ||
1019 | {NULL__null, NULL__null, NULL__null} | ||
1020 | }; | ||
1021 | |||
1022 | /** | ||
1023 | * @brief registers callback commands that are used by campaign | ||
1024 | * @todo callbacks should be registered on menu push | ||
1025 | * (what about sideeffects for commands that are called from different menus?) | ||
1026 | * @sa CP_AddCampaignCommands | ||
1027 | * @sa CP_RemoveCampaignCallbackCommands | ||
1028 | */ | ||
1029 | static void CP_AddCampaignCallbackCommands (void) | ||
1030 | { | ||
1031 | AIM_InitCallbacks(); | ||
1032 | B_InitCallbacks(); | ||
1033 | BDEF_InitCallbacks(); | ||
1034 | BS_InitCallbacks(); | ||
1035 | CP_TEAM_InitCallbacks(); | ||
1036 | HOS_InitCallbacks(); | ||
1037 | INS_InitCallbacks(); | ||
1038 | PR_InitCallbacks(); | ||
1039 | RS_InitCallbacks(); | ||
1040 | } | ||
1041 | |||
1042 | static void CP_AddCampaignCommands (void) | ||
1043 | { | ||
1044 | const cmdList_t *commands; | ||
1045 | |||
1046 | for (commands = game_commands; commands->name; commands++) | ||
1047 | Cmd_AddCommand(commands->name, commands->function, commands->description); | ||
1048 | |||
1049 | CP_AddCampaignCallbackCommands(); | ||
1050 | } | ||
1051 | |||
1052 | /** | ||
1053 | * @brief registers callback commands that are used by campaign | ||
1054 | * @todo callbacks should be removed on menu pop | ||
1055 | * (what about sideeffects for commands that are called from different menus?) | ||
1056 | * @sa CP_AddCampaignCommands | ||
1057 | * @sa CP_RemoveCampaignCallbackCommands | ||
1058 | */ | ||
1059 | static void CP_RemoveCampaignCallbackCommands (void) | ||
1060 | { | ||
1061 | AIM_ShutdownCallbacks(); | ||
1062 | B_ShutdownCallbacks(); | ||
1063 | BDEF_ShutdownCallbacks(); | ||
1064 | BS_ShutdownCallbacks(); | ||
1065 | CP_TEAM_ShutdownCallbacks(); | ||
1066 | HOS_ShutdownCallbacks(); | ||
1067 | INS_ShutdownCallbacks(); | ||
1068 | PR_ShutdownCallbacks(); | ||
1069 | RS_ShutdownCallbacks(); | ||
1070 | MSO_Shutdown(); | ||
1071 | UP_Shutdown(); | ||
1072 | } | ||
1073 | |||
1074 | static void CP_RemoveCampaignCommands (void) | ||
1075 | { | ||
1076 | const cmdList_t *commands; | ||
1077 | |||
1078 | for (commands = game_commands; commands->name; commands++) | ||
1079 | Cmd_RemoveCommand(commands->name); | ||
1080 | |||
1081 | CP_RemoveCampaignCallbackCommands(); | ||
1082 | } | ||
1083 | |||
1084 | /** | ||
1085 | * @brief Called at new game and load game | ||
1086 | * @param[in] load @c true if we are loading game, @c false otherwise | ||
1087 | * @param[in] campaign Pointer to campaign - it will be set to @c ccs.curCampaign here. | ||
1088 | */ | ||
1089 | void CP_CampaignInit (campaign_t *campaign, bool load) | ||
1090 | { | ||
1091 | ccs.curCampaign = campaign; | ||
1092 | |||
1093 | CP_ReadCampaignData(campaign); | ||
1094 | |||
1095 | /* initialise date */ | ||
1096 | ccs.date = campaign->date; | ||
1097 | /* get day */ | ||
1098 | while (ccs.date.sec > SECONDS_PER_DAY86400) { | ||
1099 | ccs.date.sec -= SECONDS_PER_DAY86400; | ||
1100 | ccs.date.day++; | ||
1101 | } | ||
1102 | CP_UpdateTime(); | ||
1103 | |||
1104 | RS_InitTree(campaign, load); /**< Initialise all data in the research tree. */ | ||
1105 | |||
1106 | CP_AddCampaignCommands(); | ||
1107 | |||
1108 | CP_GameTimeStop(); | ||
1109 | |||
1110 | /* Init popup and map/geoscape */ | ||
1111 | CL_PopupInit(); | ||
1112 | |||
1113 | CP_InitOverlay(); | ||
1114 | |||
1115 | CP_XVIInit(); | ||
1116 | |||
1117 | cgi->UI_InitStack("geoscape", "campaign_main", true, true); | ||
1118 | |||
1119 | if (load) { | ||
1120 | return; | ||
1121 | } | ||
1122 | |||
1123 | CL_EventAddMail("prolog"); | ||
1124 | |||
1125 | RS_MarkResearchable(NULL__null, true); | ||
1126 | BS_InitMarket(campaign); | ||
1127 | |||
1128 | /* create initial employees */ | ||
1129 | E_InitialEmployees(campaign); | ||
1130 | /* initialise view angle for 3D geoscape so that europe is seen */ | ||
1131 | ccs.angles[YAW1] = GLOBE_ROTATE-90; | ||
1132 | |||
1133 | MAP_Reset(campaign->map); | ||
1134 | PR_ProductionInit(); | ||
1135 | |||
1136 | /* set map view */ | ||
1137 | ccs.center[0] = ccs.center[1] = 0.5; | ||
1138 | ccs.zoom = 1.0; | ||
1139 | Vector2Set(ccs.smoothFinal2DGeoscapeCenter, 0.5, 0.5)((ccs.smoothFinal2DGeoscapeCenter)[0]=(0.5), (ccs.smoothFinal2DGeoscapeCenter )[1]=(0.5)); | ||
1140 | VectorSet(ccs.smoothFinalGlobeAngle, 0, GLOBE_ROTATE, 0)((ccs.smoothFinalGlobeAngle)[0]=(0), (ccs.smoothFinalGlobeAngle )[1]=(-90), (ccs.smoothFinalGlobeAngle)[2]=(0)); | ||
1141 | |||
1142 | CP_UpdateCredits(campaign->credits); | ||
1143 | |||
1144 | /* Initialize alien interest */ | ||
1145 | INT_ResetAlienInterest(); | ||
1146 | |||
1147 | /* Initialize XVI overlay */ | ||
1148 | Cvar_SetValue("mn_xvimap", ccs.XVIShowMap); | ||
1149 | CP_InitializeXVIOverlay(NULL__null); | ||
1150 | |||
1151 | /* create a base as first step */ | ||
1152 | B_SelectBase(NULL__null); | ||
1153 | |||
1154 | /* Spawn first missions of the game */ | ||
1155 | CP_InitializeSpawningDelay(); | ||
1156 | |||
1157 | /* now check the parsed values for errors that are not caught at parsing stage */ | ||
1158 | if (!load) | ||
1159 | CP_ScriptSanityCheck(); | ||
1160 | } | ||
1161 | |||
1162 | /** | ||
1163 | * @brief Campaign closing actions | ||
1164 | */ | ||
1165 | void CP_Shutdown (void) | ||
1166 | { | ||
1167 | if (CP_IsRunning()) { | ||
1168 | int i; | ||
1169 | |||
1170 | AB_Shutdown(); | ||
1171 | AIR_Shutdown(); | ||
1172 | INS_Shutdown(); | ||
1173 | INT_Shutdown(); | ||
1174 | NAT_Shutdown(); | ||
1175 | MIS_Shutdown(); | ||
1176 | TR_Shutdown(); | ||
1177 | UR_Shutdown(); | ||
1178 | AM_Shutdown(); | ||
1179 | |||
1180 | /** @todo Where does this belong? */ | ||
1181 | for (i = 0; i < ccs.numAlienCategories; i++) { | ||
1182 | alienTeamCategory_t *alienCat = &ccs.alienCategories[i]; | ||
1183 | LIST_Delete(&alienCat->equipment); | ||
1184 | } | ||
1185 | |||
1186 | cl_geoscape_overlay->integer = 0; | ||
1187 | /* singleplayer commands are no longer available */ | ||
1188 | Com_DPrintf(DEBUG_CLIENT0x20, "Remove game commands\n"); | ||
1189 | CP_RemoveCampaignCommands(); | ||
1190 | } | ||
1191 | |||
1192 | MAP_Shutdown(); | ||
1193 | CP_ShutdownOverlay(); | ||
1194 | } | ||
1195 | |||
1196 | /** | ||
1197 | * @brief Returns the campaign pointer from global campaign array | ||
1198 | * @param name Name of the campaign | ||
1199 | * @return campaign_t pointer to campaign with name or NULL if not found | ||
1200 | */ | ||
1201 | campaign_t* CP_GetCampaign (const char* name) | ||
1202 | { | ||
1203 | campaign_t* campaign; | ||
1204 | int i; | ||
1205 | |||
1206 | for (i = 0, campaign = ccs.campaigns; i < ccs.numCampaigns; i++, campaign++) | ||
1207 | if (Q_streq(name, campaign->id)(strcmp(name, campaign->id) == 0)) | ||
1208 | break; | ||
1209 | |||
1210 | if (i == ccs.numCampaigns) { | ||
1211 | Com_Printf("CL_GetCampaign: Campaign \"%s\" doesn't exist.\n", name); | ||
1212 | return NULL__null; | ||
1213 | } | ||
1214 | return campaign; | ||
1215 | } | ||
1216 | |||
1217 | /** | ||
1218 | * @brief Will clear most of the parsed singleplayer data | ||
1219 | * @sa INV_InitInventory | ||
1220 | * @sa CP_ParseCampaignData | ||
1221 | */ | ||
1222 | void CP_ResetCampaignData (void) | ||
1223 | { | ||
1224 | int i; | ||
1225 | mapDef_t *md; | ||
1226 | |||
1227 | cp_messageStack = NULL__null; | ||
1228 | |||
1229 | /* cleanup dynamic mails */ | ||
1230 | CP_FreeDynamicEventMail(); | ||
1231 | |||
1232 | Mem_FreePool(cp_campaignPool)_Mem_FreePool((cp_campaignPool),"src/client/cgame/campaign/cp_campaign.cpp" ,1232); | ||
1233 | |||
1234 | /* called to flood the hash list - because the parse tech function | ||
1235 | * was maybe already called */ | ||
1236 | RS_ResetTechs(); | ||
1237 | E_ResetEmployees(); | ||
1238 | |||
1239 | OBJZERO(ccs)(memset(&((ccs)), (0), sizeof((ccs)))); | ||
1240 | |||
1241 | ccs.missionSpawnCallback = CP_SpawnNewMissions; | ||
1242 | |||
1243 | /* Collect and count Alien team definitions. */ | ||
1244 | for (i = 0; i < csi.numTeamDefs; i++) { | ||
1245 | teamDef_t *td = &csi.teamDef[i]; | ||
1246 | if (CHRSH_IsTeamDefAlien(td)) | ||
1247 | ccs.alienTeams[ccs.numAliensTD++] = td; | ||
1248 | } | ||
1249 | /* Clear mapDef usage statistics */ | ||
1250 | MapDef_ForeachSingleplayerCampaign(md)for (int md__loopvar = 0; (md) = __null, md__loopvar < csi .numMDs; md__loopvar++) if ((md) = &csi.mds[md__loopvar], !((md)->singleplayer && (md)->campaign)) {} else { | ||
1251 | md->timesAlreadyUsed = 0; | ||
1252 | } | ||
1253 | } | ||
1254 | |||
1255 | #ifdef DEBUG1 | ||
1256 | /** | ||
1257 | * @brief Debug function to increase the kills and test the ranks | ||
1258 | */ | ||
1259 | static void CP_DebugChangeCharacterStats_f (void) | ||
1260 | { | ||
1261 | int j; | ||
1262 | base_t *base = B_GetCurrentSelectedBase(); | ||
1263 | |||
1264 | if (!base) | ||
1265 | return; | ||
1266 | |||
1267 | E_Foreach(EMPL_SOLDIER, employee)for (bool employee__break = false, employee__once = true; employee__once ; employee__once = false) for (linkedList_t const* employee__iter = (ccs.employees[EMPL_SOLDIER]); ! employee__break && employee__iter;) for (employee_t* const employee = ( employee__break = employee__once = true, (employee_t*) employee__iter->data ); employee__once; employee__break = employee__once = false) if ( employee__iter = employee__iter->next, false) {} else { | ||
1268 | character_t *chr; | ||
1269 | |||
1270 | if (!E_IsInBase(employee, base)) | ||
1271 | continue; | ||
1272 | |||
1273 | chr = &(employee->chr); | ||
1274 | assert(chr)(__builtin_expect(!(chr), 0) ? __assert_rtn(__func__, "src/client/cgame/campaign/cp_campaign.cpp" , 1274, "chr") : (void)0); | ||
1275 | |||
1276 | for (j = 0; j < KILLED_NUM_TYPES; j++) | ||
1277 | chr->score.kills[j]++; | ||
1278 | } | ||
1279 | if (base->aircraftCurrent) | ||
1280 | CP_UpdateCharacterStats(base, base->aircraftCurrent); | ||
1281 | } | ||
1282 | |||
1283 | #endif /* DEBUG */ | ||
1284 | |||
1285 | /** | ||
1286 | * @brief Determines a random position on geoscape | ||
1287 | * @param[out] pos The position that will be overwritten. pos[0] is within -180, +180. pos[1] within -90, +90. | ||
1288 | * @param[in] noWater True if the position should not be on water | ||
1289 | * @sa CP_GetRandomPosOnGeoscapeWithParameters | ||
1290 | * @note The random positions should be roughly uniform thanks to the non-uniform distribution used. | ||
1291 | * @note This function always returns a value. | ||
1292 | */ | ||
1293 | void CP_GetRandomPosOnGeoscape (vec2_t pos, bool noWater) | ||
1294 | { | ||
1295 | do { | ||
1296 | pos[0] = (frand() - 0.5f) * 360.0f; | ||
1297 | pos[1] = asin((frand() - 0.5f) * 2.0f) * todeg(180.0f/3.14159265358979323846264338327950288); | ||
1298 | } while (noWater && MapIsWater(MAP_GetColor(pos, MAPTYPE_TERRAIN, NULL))(MAP_GetColor(pos, MAPTYPE_TERRAIN, __null)[0] == 0 && MAP_GetColor(pos, MAPTYPE_TERRAIN, __null)[1] == 0 && MAP_GetColor(pos, MAPTYPE_TERRAIN, __null)[2] == 64)); | ||
1299 | |||
1300 | Com_DPrintf(DEBUG_CLIENT0x20, "CP_GetRandomPosOnGeoscape: Get random position on geoscape %.2f:%.2f\n", pos[0], pos[1]); | ||
1301 | } | ||
1302 | |||
1303 | /** | ||
1304 | * @brief Determines a random position on geoscape that fulfills certain criteria given via parameters | ||
1305 | * @param[out] pos The position that will be overwritten with the random point fulfilling the criteria. pos[0] is within -180, +180. pos[1] within -90, +90. | ||
1306 | * @param[in] terrainTypes A linkedList_t containing a list of strings determining the acceptable terrain types (e.g. "grass") May be NULL. | ||
1307 | * @param[in] cultureTypes A linkedList_t containing a list of strings determining the acceptable culture types (e.g. "western") May be NULL. | ||
1308 | * @param[in] populationTypes A linkedList_t containing a list of strings determining the acceptable population types (e.g. "suburban") May be NULL. | ||
1309 | * @param[in] nations A linkedList_t containing a list of strings determining the acceptable nations (e.g. "asia"). May be NULL | ||
1310 | * @return true if a location was found, otherwise false | ||
1311 | * @note There may be no position fitting the parameters. The higher RASTER, the lower the probability to find a position. | ||
1312 | * @sa LIST_AddString | ||
1313 | * @sa LIST_Delete | ||
1314 | * @note When all parameters are NULL, the algorithm assumes that it does not need to include "water" terrains when determining a random position | ||
1315 | * @note You should rather use CP_GetRandomPosOnGeoscape if there are no parameters (except water) to choose a random position | ||
1316 | */ | ||
1317 | bool CP_GetRandomPosOnGeoscapeWithParameters (vec2_t pos, const linkedList_t* terrainTypes, const linkedList_t* cultureTypes, const linkedList_t* populationTypes, const linkedList_t* nations) | ||
1318 | { | ||
1319 | float x, y; | ||
1320 | int num; | ||
1321 | int randomNum; | ||
1322 | |||
1323 | /* RASTER might reduce amount of tested locations to get a better performance */ | ||
1324 | /* Number of points in latitude and longitude that will be tested. Therefore, the total number of position tried | ||
1325 | * will be numPoints * numPoints */ | ||
1326 | const float numPoints = 360.0 / RASTER2; | ||
1327 | /* RASTER is minimizing the amount of locations, so an offset is introduced to enable access to all locations, depending on a random factor */ | ||
1328 | const float offsetX = frand() * RASTER2; | ||
1329 | const float offsetY = -1.0 + frand() * 2.0 / numPoints; | ||
1330 | vec2_t posT; | ||
1331 | int hits = 0; | ||
1332 | |||
1333 | /* check all locations for suitability in 2 iterations */ | ||
1334 | /* prepare 1st iteration */ | ||
1335 | |||
1336 | /* ITERATION 1 */ | ||
1337 | for (y = 0; y < numPoints; y++) { | ||
1338 | const float posY = asin(2.0 * y / numPoints + offsetY) * todeg(180.0f/3.14159265358979323846264338327950288); /* Use non-uniform distribution otherwise we favour the poles */ | ||
1339 | for (x = 0; x < numPoints; x++) { | ||
1340 | const float posX = x * RASTER2 - 180.0 + offsetX; | ||
1341 | |||
1342 | Vector2Set(posT, posX, posY)((posT)[0]=(posX), (posT)[1]=(posY)); | ||
1343 | |||
1344 | if (MAP_PositionFitsTCPNTypes(posT, terrainTypes, cultureTypes, populationTypes, nations)) { | ||
1345 | /* the location given in pos belongs to the terrain, culture, population types and nations | ||
1346 | * that are acceptable, so count it */ | ||
1347 | /** @todo - cache the counted hits */ | ||
1348 | hits++; | ||
1349 | } | ||
1350 | } | ||
1351 | } | ||
1352 | |||
1353 | /* if there have been no hits, the function failed to find a position */ | ||
1354 | if (hits == 0) | ||
1355 | return false; | ||
1356 | |||
1357 | /* the 2nd iteration goes through the locations again, but does so only until a random point */ | ||
1358 | /* prepare 2nd iteration */ | ||
1359 | randomNum = num = rand() % hits; | ||
1360 | |||
1361 | /* ITERATION 2 */ | ||
1362 | for (y = 0; y < numPoints; y++) { | ||
1363 | const float posY = asin(2.0 * y / numPoints + offsetY) * todeg(180.0f/3.14159265358979323846264338327950288); | ||
1364 | for (x = 0; x < numPoints; x++) { | ||
1365 | const float posX = x * RASTER2 - 180.0 + offsetX; | ||
1366 | |||
1367 | Vector2Set(posT,posX,posY)((posT)[0]=(posX), (posT)[1]=(posY)); | ||
1368 | |||
1369 | if (MAP_PositionFitsTCPNTypes(posT, terrainTypes, cultureTypes, populationTypes, nations)) { | ||
1370 | num--; | ||
1371 | |||
1372 | if (num < 1) { | ||
1373 | Vector2Set(pos, posX, posY)((pos)[0]=(posX), (pos)[1]=(posY)); | ||
1374 | Com_DPrintf(DEBUG_CLIENT0x20, "CP_GetRandomPosOnGeoscapeWithParameters: New random coords for a mission are %.0f:%.0f, chosen as #%i out of %i possible locations\n", | ||
1375 | pos[0], pos[1], randomNum, hits); | ||
1376 | return true; | ||
1377 | } | ||
1378 | } | ||
1379 | } | ||
1380 | } | ||
1381 | |||
1382 | Com_DPrintf(DEBUG_CLIENT0x20, "CP_GetRandomPosOnGeoscapeWithParameters: New random coordinates for a mission are %.0f:%.0f, chosen as #%i out of %i possible locations\n", | ||
1383 | pos[0], pos[1], num, hits); | ||
1384 | |||
1385 | /** @todo add EQUAL_EPSILON here? */ | ||
1386 | /* Make sure that position is within bounds */ | ||
1387 | assert(pos[0] >= -180)(__builtin_expect(!(pos[0] >= -180), 0) ? __assert_rtn(__func__ , "src/client/cgame/campaign/cp_campaign.cpp", 1387, "pos[0] >= -180" ) : (void)0); | ||
1388 | assert(pos[0] <= 180)(__builtin_expect(!(pos[0] <= 180), 0) ? __assert_rtn(__func__ , "src/client/cgame/campaign/cp_campaign.cpp", 1388, "pos[0] <= 180" ) : (void)0); | ||
1389 | assert(pos[1] >= -90)(__builtin_expect(!(pos[1] >= -90), 0) ? __assert_rtn(__func__ , "src/client/cgame/campaign/cp_campaign.cpp", 1389, "pos[1] >= -90" ) : (void)0); | ||
1390 | assert(pos[1] <= 90)(__builtin_expect(!(pos[1] <= 90), 0) ? __assert_rtn(__func__ , "src/client/cgame/campaign/cp_campaign.cpp", 1390, "pos[1] <= 90" ) : (void)0); | ||
1391 | |||
1392 | return true; | ||
1393 | } | ||
1394 | |||
1395 | const city_t * CP_GetCity (const char *id) | ||
1396 | { | ||
1397 | LIST_Foreach(ccs.cities, city_t, city)for (bool city__break = false, city__once = true; city__once; city__once = false) for (linkedList_t const* city__iter = (ccs .cities); ! city__break && city__iter;) for (city_t* const city = ( city__break = city__once = true, (city_t*) city__iter ->data); city__once; city__break = city__once = false) if ( city__iter = city__iter->next, false) {} else { | ||
1398 | if (Q_streq(city->id, id)(strcmp(city->id, id) == 0)) | ||
1399 | return city; | ||
1400 | } | ||
1401 | |||
1402 | return NULL__null; | ||
1403 | } | ||
1404 | |||
1405 | int CP_GetSalaryAdministrative (const salary_t *salary) | ||
1406 | { | ||
1407 | int i, costs; | ||
1408 | |||
1409 | costs = salary->adminInitial; | ||
1410 | for (i = 0; i < MAX_EMPL; i++) { | ||
1411 | const employeeType_t type = (employeeType_t)i; | ||
1412 | costs += E_CountByType(type) * CP_GetSalaryAdminEmployee(salary, type); | ||
1413 | } | ||
1414 | return costs; | ||
1415 | } | ||
1416 | |||
1417 | int CP_GetSalaryBaseEmployee (const salary_t *salary, employeeType_t type) | ||
1418 | { | ||
1419 | return salary->base[type]; | ||
1420 | } | ||
1421 | |||
1422 | int CP_GetSalaryAdminEmployee (const salary_t *salary, employeeType_t type) | ||
1423 | { | ||
1424 | return salary->admin[type]; | ||
1425 | } | ||
1426 | |||
1427 | int CP_GetSalaryRankBonusEmployee (const salary_t *salary, employeeType_t type) | ||
1428 | { | ||
1429 | return salary->rankBonus[type]; | ||
1430 | } | ||
1431 | |||
1432 | int CP_GetSalaryUpKeepBase (const salary_t *salary, const base_t *base) | ||
1433 | { | ||
1434 | int cost = salary->baseUpkeep; /* base cost */ | ||
1435 | building_t *building = NULL__null; | ||
1436 | while ((building = B_GetNextBuilding(base, building))) { | ||
1437 | if (building->buildingStatus == B_STATUS_WORKING | ||
1438 | || building->buildingStatus == B_STATUS_CONSTRUCTION_FINISHED) | ||
1439 | cost += building->varCosts; | ||
1440 | } | ||
1441 | return cost; | ||
1442 | } | ||
1443 | |||
1444 | /** @todo remove me and move all the included stuff to proper places */ | ||
1445 | void CP_InitStartup (void) | ||
1446 | { | ||
1447 | cp_campaignPool = Mem_CreatePool("Client: Local (per game)")_Mem_CreatePool(("Client: Local (per game)"),"src/client/cgame/campaign/cp_campaign.cpp" ,1447); | ||
1448 | |||
1449 | SAV_Init(); | ||
1450 | |||
1451 | /* commands */ | ||
1452 | #ifdef DEBUG1 | ||
1453 | Cmd_AddCommand("debug_statsupdate", CP_DebugChangeCharacterStats_f, "Debug function to increase the kills and test the ranks"); | ||
1454 | #endif | ||
1455 | |||
1456 | cp_missiontest = Cvar_Get("cp_missiontest", "0", CVAR_DEVELOPER32, "This will never stop the time on geoscape and print information about spawned missions"); | ||
1457 | |||
1458 | /* init subsystems */ | ||
1459 | MS_MessageInit(); | ||
1460 | |||
1461 | MIS_InitStartup(); | ||
1462 | UP_InitStartup(); | ||
1463 | B_InitStartup(); | ||
1464 | INS_InitStartup(); | ||
1465 | RS_InitStartup(); | ||
1466 | E_InitStartup(); | ||
1467 | HOS_InitStartup(); | ||
1468 | INT_InitStartup(); | ||
1469 | AC_InitStartup(); | ||
1470 | MAP_InitStartup(); | ||
1471 | UFO_InitStartup(); | ||
1472 | TR_InitStartup(); | ||
1473 | AB_InitStartup(); | ||
1474 | AIR_InitStartup(); | ||
1475 | AIRFIGHT_InitStartup(); | ||
1476 | NAT_InitStartup(); | ||
1477 | TR_InitStartup(); | ||
1478 | STATS_InitStartup(); | ||
1479 | UR_InitStartup(); | ||
1480 | AM_InitStartup(); | ||
1481 | } |