File: | client/cgame/campaign/cp_missions.cpp |
Location: | line 1722, column 2 |
Description: | Access to field 'mapDef' results in a dereference of a null pointer (loaded from variable 'mission') |
1 | /** | ||
2 | * @file | ||
3 | * @brief Campaign missions code | ||
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 "../../cl_team.h" | ||
27 | #include "../cl_game.h" | ||
28 | #include "../../ui/ui_dataids.h" | ||
29 | #include "cp_campaign.h" | ||
30 | #include "cp_map.h" | ||
31 | #include "cp_ufo.h" | ||
32 | #include "cp_alienbase.h" | ||
33 | #include "cp_alien_interest.h" | ||
34 | #include "cp_missions.h" | ||
35 | #include "cp_mission_triggers.h" | ||
36 | #include "cp_time.h" | ||
37 | #include "cp_xvi.h" | ||
38 | #include "save/save_missions.h" | ||
39 | #include "save/save_interest.h" | ||
40 | #include "cp_mission_callbacks.h" | ||
41 | |||
42 | /** Maximum number of loops to choose a mission position (to avoid infinite loops) */ | ||
43 | const int MAX_POS_LOOP = 10; | ||
44 | |||
45 | /** Condition limits for crashed UFOs - used for disassemlies */ | ||
46 | static const float MIN_CRASHEDUFO_CONDITION = 0.2f; | ||
47 | static const float MAX_CRASHEDUFO_CONDITION = 0.81f; | ||
48 | |||
49 | /*==================================== | ||
50 | * | ||
51 | * Prepare battlescape | ||
52 | * | ||
53 | ====================================*/ | ||
54 | |||
55 | /** | ||
56 | * @brief Set some needed cvars from mission definition | ||
57 | * @param[in] mission mission definition pointer with the needed data to set the cvars to | ||
58 | * @sa CP_StartSelectedMission | ||
59 | */ | ||
60 | void CP_SetMissionVars (const mission_t *mission, const battleParam_t *battleParameters) | ||
61 | { | ||
62 | int i; | ||
63 | |||
64 | assert(mission->mapDef)(__builtin_expect(!(mission->mapDef), 0) ? __assert_rtn(__func__ , "src/client/cgame/campaign/cp_missions.cpp", 64, "mission->mapDef" ) : (void)0); | ||
65 | |||
66 | /* start the map */ | ||
67 | Cvar_SetValue("ai_numaliens", battleParameters->aliens); | ||
68 | Cvar_SetValue("ai_numcivilians", battleParameters->civilians); | ||
69 | Cvar_Set("ai_civilian", battleParameters->civTeam); | ||
70 | Cvar_Set("ai_equipment", battleParameters->alienEquipment); | ||
71 | |||
72 | /* now store the alien teams in the shared csi struct to let the game dll | ||
73 | * have access to this data, too */ | ||
74 | csi.numAlienTeams = 0; | ||
75 | for (i = 0; i < battleParameters->alienTeamGroup->numAlienTeams; i++) { | ||
76 | csi.alienTeams[i] = battleParameters->alienTeamGroup->alienTeams[i]; | ||
77 | csi.numAlienTeams++; | ||
78 | if (csi.numAlienTeams >= MAX_TEAMS_PER_MISSION6) | ||
79 | break; | ||
80 | } | ||
81 | } | ||
82 | |||
83 | /** | ||
84 | * @brief Select the mission type and start the map from mission definition | ||
85 | * @param[in] mission Mission definition to start the map from | ||
86 | * @sa CP_StartSelectedMission | ||
87 | * @note Also sets the terrain textures | ||
88 | * @sa Mod_LoadTexinfo | ||
89 | * @sa B_AssembleMap_f | ||
90 | */ | ||
91 | void CP_StartMissionMap (mission_t* mission, const battleParam_t *battleParameters) | ||
92 | { | ||
93 | const char *param = NULL__null; | ||
94 | |||
95 | /* prepare */ | ||
96 | cgi->UI_InitStack(NULL__null, "singleplayermission", true, false); | ||
97 | |||
98 | assert(mission->mapDef->map)(__builtin_expect(!(mission->mapDef->map), 0) ? __assert_rtn (__func__, "src/client/cgame/campaign/cp_missions.cpp", 98, "mission->mapDef->map" ) : (void)0); | ||
99 | |||
100 | /* base attack maps starts with a dot */ | ||
101 | if (mission->mapDef->map[0] == '.') { | ||
102 | const base_t *base = mission->data.base; | ||
103 | |||
104 | if (mission->category != INTERESTCATEGORY_BASE_ATTACK) | ||
105 | Com_Printf("Baseattack map on non-baseattack mission! (id=%s, category=%d)\n", mission->id, mission->category); | ||
106 | /* assemble a random base */ | ||
107 | if (!base) | ||
108 | Com_Error(ERR_DROP1, "Baseattack map without base!\n"); | ||
109 | /* base must be under attack and might not have been destroyed in the meantime. */ | ||
110 | B_AssembleMap(base); | ||
111 | |||
112 | return; | ||
113 | } | ||
114 | |||
115 | SAV_QuickSave(); | ||
116 | |||
117 | if (battleParameters->param) | ||
118 | param = battleParameters->param; | ||
119 | else | ||
120 | param = mission->mapDef->param; | ||
121 | |||
122 | /* set the mapZone - this allows us to replace the ground texture | ||
123 | * with the suitable terrain texture - just use tex_terrain/dummy for the | ||
124 | * brushes you want the terrain textures on | ||
125 | * @sa R_ModLoadTexinfo */ | ||
126 | Cvar_Set("sv_mapzone", battleParameters->zoneType); | ||
127 | |||
128 | if (mission->mapDef->hurtAliens) | ||
129 | Cvar_Set("sv_hurtaliens", "1"); | ||
130 | else | ||
131 | Cvar_Set("sv_hurtaliens", "0"); | ||
132 | |||
133 | Cbuf_AddText(va("map %s %s %s\n", (MAP_IsNight(mission->pos) ? "night" : "day"), | ||
134 | mission->mapDef->map, param ? param : "")); | ||
135 | } | ||
136 | |||
137 | /** | ||
138 | * @brief Check if an alien team category may be used for a mission category. | ||
139 | * @param[in] cat Pointer to the alien team category. | ||
140 | * @param[in] missionCat Mission category to check. | ||
141 | * @return True if alien Category may be used for this mission category. | ||
142 | */ | ||
143 | static bool CP_IsAlienTeamForCategory (const alienTeamCategory_t *cat, const interestCategory_t missionCat) | ||
144 | { | ||
145 | int i; | ||
146 | |||
147 | for (i = 0; i < cat->numMissionCategories; i++) { | ||
148 | if (missionCat == cat->missionCategories[i]) | ||
149 | return true; | ||
150 | } | ||
151 | |||
152 | return false; | ||
153 | } | ||
154 | |||
155 | /** | ||
156 | * @brief Sets the alien races used for a mission. | ||
157 | * @param[in] mission Pointer to the mission. | ||
158 | * @param[out] battleParameters The battlescape parameter the alien team is stored in | ||
159 | */ | ||
160 | static void CP_SetAlienTeamByInterest (const mission_t *mission, battleParam_t *battleParameters) | ||
161 | { | ||
162 | int i, j; | ||
163 | const int MAX_AVAILABLE_GROUPS = 4; | ||
164 | alienTeamGroup_t *availableGroups[MAX_AVAILABLE_GROUPS]; | ||
165 | int numAvailableGroup = 0; | ||
166 | |||
167 | /* Find all available alien team groups */ | ||
168 | for (i = 0; i < ccs.numAlienCategories; i++) { | ||
169 | alienTeamCategory_t *cat = &ccs.alienCategories[i]; | ||
170 | |||
171 | /* Check if this alien team category may be used */ | ||
172 | if (!CP_IsAlienTeamForCategory(cat, mission->category)) | ||
173 | continue; | ||
174 | |||
175 | /* Find all available team groups for current alien interest | ||
176 | * use mission->initialOverallInterest and not ccs.overallInterest: | ||
177 | * the alien team should not change depending on when you encounter it */ | ||
178 | for (j = 0; j < cat->numAlienTeamGroups; j++) { | ||
179 | if (cat->alienTeamGroups[j].minInterest <= mission->initialOverallInterest | ||
180 | && cat->alienTeamGroups[j].maxInterest > mission->initialOverallInterest) | ||
181 | availableGroups[numAvailableGroup++] = &cat->alienTeamGroups[j]; | ||
182 | } | ||
183 | } | ||
184 | |||
185 | if (!numAvailableGroup) | ||
186 | Com_Error(ERR_DROP1, "CP_SetAlienTeamByInterest: no available alien team for mission '%s': interest = %i -- category = %i", | ||
187 | mission->id, mission->initialOverallInterest, mission->category); | ||
188 | |||
189 | /* Pick up one group randomly */ | ||
190 | i = rand() % numAvailableGroup; | ||
191 | |||
192 | /* store this group for latter use */ | ||
193 | battleParameters->alienTeamGroup = availableGroups[i]; | ||
194 | } | ||
195 | |||
196 | /** | ||
197 | * @brief Check if an alien equipment may be used with a mission. | ||
198 | * @param[in] mission Pointer to the mission. | ||
199 | * @param[in] equip Pointer to the alien equipment to check. | ||
200 | * @param[in] equipPack Equipment definitions that may be used | ||
201 | * @return True if equipment definition is selectable. | ||
202 | */ | ||
203 | static bool CP_IsAlienEquipmentSelectable (const mission_t *mission, const equipDef_t *equip, linkedList_t *equipPack) | ||
204 | { | ||
205 | if (mission->initialOverallInterest > equip->maxInterest || mission->initialOverallInterest <= equip->minInterest) | ||
206 | return false; | ||
207 | |||
208 | LIST_Foreach(equipPack, const char, name)for (bool name__break = false, name__once = true; name__once; name__once = false) for (linkedList_t const* name__iter = (equipPack ); ! name__break && name__iter;) for (const char* const name = ( name__break = name__once = true, (const char*) name__iter ->data); name__once; name__break = name__once = false) if ( name__iter = name__iter->next, false) {} else { | ||
209 | if (Q_strstart(equip->id, name)) | ||
210 | return true; | ||
211 | } | ||
212 | |||
213 | return false; | ||
214 | } | ||
215 | |||
216 | /** | ||
217 | * @brief Set alien equipment for a mission (depends on the interest values) | ||
218 | * @note This function is used to know which equipment pack described in equipment_missions.ufo should be used | ||
219 | * @pre Alien team must be already chosen | ||
220 | * @param[in] equipPack Equipment definitions that may be used | ||
221 | * @sa CP_SetAlienTeamByInterest | ||
222 | */ | ||
223 | static void CP_SetAlienEquipmentByInterest (const mission_t *mission, linkedList_t *equipPack, battleParam_t *battleParameters) | ||
224 | { | ||
225 | int i, randomNum, availableEquipDef = 0; | ||
226 | |||
227 | /* look for all available fitting alien equipment definitions | ||
228 | * use mission->initialOverallInterest and not ccs.overallInterest: the alien equipment should not change depending on | ||
229 | * when you encounter it */ | ||
230 | for (i = 0; i < csi.numEDs; i++) { | ||
231 | const equipDef_t *ed = &csi.eds[i]; | ||
232 | if (CP_IsAlienEquipmentSelectable(mission, ed, equipPack)) | ||
233 | availableEquipDef++; | ||
234 | } | ||
235 | |||
236 | Com_DPrintf(DEBUG_CLIENT0x20, "CP_SetAlienEquipmentByInterest: %i available equipment packs for mission %s\n", availableEquipDef, mission->id); | ||
237 | |||
238 | if (!availableEquipDef) | ||
239 | Com_Error(ERR_DROP1, "CP_SetAlienEquipmentByInterest: no available alien equipment for mission '%s'", mission->id); | ||
240 | |||
241 | /* Choose an alien equipment definition -- between 0 and availableStage - 1 */ | ||
242 | randomNum = rand() % availableEquipDef; | ||
243 | |||
244 | availableEquipDef = 0; | ||
245 | for (i = 0; i < csi.numEDs; i++) { | ||
246 | const equipDef_t *ed = &csi.eds[i]; | ||
247 | if (CP_IsAlienEquipmentSelectable(mission, ed, equipPack)) { | ||
248 | if (availableEquipDef == randomNum) { | ||
249 | Com_sprintf(battleParameters->alienEquipment, sizeof(battleParameters->alienEquipment), "%s", ed->id); | ||
250 | break; | ||
251 | } else | ||
252 | availableEquipDef++; | ||
253 | } | ||
254 | } | ||
255 | } | ||
256 | |||
257 | /** | ||
258 | * @brief Set number of aliens in mission. | ||
259 | * @param[in,out] mission Pointer to the mission that generates the battle. | ||
260 | * @param[in,out] battleParam The battlescape parameter container | ||
261 | * @sa CP_SetAlienTeamByInterest | ||
262 | */ | ||
263 | static void MIS_CreateAlienTeam (mission_t *mission, battleParam_t *battleParam) | ||
264 | { | ||
265 | int numAliens; | ||
266 | |||
267 | assert(mission->posAssigned)(__builtin_expect(!(mission->posAssigned), 0) ? __assert_rtn (__func__, "src/client/cgame/campaign/cp_missions.cpp", 267, "mission->posAssigned" ) : (void)0); | ||
268 | |||
269 | numAliens = 4 + (int) ccs.overallInterest / 50 + (rand() % 3) - 1; | ||
270 | numAliens = std::max(4, numAliens); | ||
271 | if (mission->ufo && mission->ufo->maxTeamSize && numAliens > mission->ufo->maxTeamSize) | ||
272 | numAliens = mission->ufo->maxTeamSize; | ||
273 | if (numAliens > mission->mapDef->maxAliens) | ||
274 | numAliens = mission->mapDef->maxAliens; | ||
275 | battleParam->aliens = numAliens; | ||
276 | |||
277 | CP_SetAlienTeamByInterest(mission, battleParam); | ||
278 | |||
279 | CP_SetAlienEquipmentByInterest(mission, ccs.alienCategories[battleParam->alienTeamGroup->categoryIdx].equipment, battleParam); | ||
280 | } | ||
281 | |||
282 | /** | ||
283 | * @brief Create civilian team. | ||
284 | * @param[in] mission Pointer to the mission that generates the battle | ||
285 | */ | ||
286 | static void CP_CreateCivilianTeam (const mission_t *mission, battleParam_t *param) | ||
287 | { | ||
288 | nation_t *nation; | ||
289 | |||
290 | assert(mission->posAssigned)(__builtin_expect(!(mission->posAssigned), 0) ? __assert_rtn (__func__, "src/client/cgame/campaign/cp_missions.cpp", 290, "mission->posAssigned" ) : (void)0); | ||
291 | |||
292 | param->civilians = MAP_GetCivilianNumberByPosition(mission->pos); | ||
293 | |||
294 | nation = MAP_GetNation(mission->pos); | ||
295 | param->nation = nation; | ||
296 | if (mission->mapDef->civTeam != NULL__null) { | ||
297 | Q_strncpyz(param->civTeam, mission->mapDef->civTeam, sizeof(param->civTeam))Q_strncpyzDebug( param->civTeam, mission->mapDef->civTeam , sizeof(param->civTeam), "src/client/cgame/campaign/cp_missions.cpp" , 297 ); | ||
298 | } else if (nation) { | ||
299 | /** @todo There should always be a nation, no? Otherwise the mission was placed wrong. */ | ||
300 | Q_strncpyz(param->civTeam, nation->id, sizeof(param->civTeam))Q_strncpyzDebug( param->civTeam, nation->id, sizeof(param ->civTeam), "src/client/cgame/campaign/cp_missions.cpp", 300 ); | ||
301 | } else { | ||
302 | Q_strncpyz(param->civTeam, "europe", sizeof(param->civTeam))Q_strncpyzDebug( param->civTeam, "europe", sizeof(param-> civTeam), "src/client/cgame/campaign/cp_missions.cpp", 302 ); | ||
303 | } | ||
304 | } | ||
305 | |||
306 | /** | ||
307 | * @brief Create parameters needed for battle. This is the data that is used for starting | ||
308 | * the tactical part of the mission. | ||
309 | * @param[in] mission Pointer to the mission that generates the battle | ||
310 | * @param[out] param The battle parameters to set | ||
311 | * @param[in] aircraft the aircraft to go to the mission with | ||
312 | * @sa CP_CreateAlienTeam | ||
313 | * @sa CP_CreateCivilianTeam | ||
314 | */ | ||
315 | void CP_CreateBattleParameters (mission_t *mission, battleParam_t *param, const aircraft_t *aircraft) | ||
316 | { | ||
317 | const char *zoneType; | ||
318 | const byte *color; | ||
319 | |||
320 | assert(mission->posAssigned)(__builtin_expect(!(mission->posAssigned), 0) ? __assert_rtn (__func__, "src/client/cgame/campaign/cp_missions.cpp", 320, "mission->posAssigned" ) : (void)0); | ||
321 | assert(mission->mapDef)(__builtin_expect(!(mission->mapDef), 0) ? __assert_rtn(__func__ , "src/client/cgame/campaign/cp_missions.cpp", 321, "mission->mapDef" ) : (void)0); | ||
322 | |||
323 | MIS_CreateAlienTeam(mission, param); | ||
324 | CP_CreateCivilianTeam(mission, param); | ||
325 | |||
326 | /* Reset parameters */ | ||
327 | Mem_Free(param->param)_Mem_Free((param->param),"src/client/cgame/campaign/cp_missions.cpp" ,327); | ||
328 | param->param = NULL__null; | ||
329 | |||
330 | param->mission = mission; | ||
331 | color = MAP_GetColor(mission->pos, MAPTYPE_TERRAIN, NULL__null); | ||
332 | zoneType = MAP_GetTerrainType(color); | ||
333 | param->zoneType = zoneType; /* store to terrain type for texture replacement */ | ||
334 | /* Is there a UFO to recover ? */ | ||
335 | if (mission->ufo) { | ||
336 | const aircraft_t *ufo = mission->ufo; | ||
337 | const char *shortUFOType; | ||
338 | float ufoCondition; | ||
339 | |||
340 | if (mission->crashed) { | ||
341 | shortUFOType = Com_UFOCrashedTypeToShortName(ufo->ufotype); | ||
342 | /* Set random map UFO if this is a random map */ | ||
343 | if (mission->mapDef->map[0] == '+') { | ||
344 | /* set battleParameters.param to the ufo type: used for ufocrash random map */ | ||
345 | if (Q_streq(mission->mapDef->id, "ufocrash")(strcmp(mission->mapDef->id, "ufocrash") == 0)) | ||
346 | param->param = Mem_PoolStrDup(shortUFOType, cp_campaignPool, 0)_Mem_PoolStrDup((shortUFOType),(cp_campaignPool),(0),"src/client/cgame/campaign/cp_missions.cpp" ,346); | ||
347 | } | ||
348 | ufoCondition = frand() * (MAX_CRASHEDUFO_CONDITION - MIN_CRASHEDUFO_CONDITION) + MIN_CRASHEDUFO_CONDITION; | ||
349 | } else { | ||
350 | shortUFOType = Com_UFOTypeToShortName(ufo->ufotype); | ||
351 | ufoCondition = 1.0f; | ||
352 | } | ||
353 | |||
354 | Com_sprintf(mission->onwin, sizeof(mission->onwin), "cp_uforecovery_init %s %f", ufo->id, ufoCondition); | ||
355 | /* Set random map UFO if this is a random map */ | ||
356 | if (mission->mapDef->map[0] == '+') { | ||
357 | /* set rm_ufo to the ufo type used */ | ||
358 | Cvar_Set("rm_ufo", Com_GetRandomMapAssemblyNameForCraft(shortUFOType)); | ||
359 | } | ||
360 | } | ||
361 | |||
362 | /* Set random map aircraft if this is a random map */ | ||
363 | if (mission->mapDef->map[0] == '+') { | ||
364 | if (mission->category == INTERESTCATEGORY_RESCUE) { | ||
365 | Cvar_Set("rm_crashed", Com_GetRandomMapAssemblyNameForCrashedCraft(mission->data.aircraft->id)); | ||
366 | } else { | ||
367 | Cvar_Set("rm_crashed", ""); | ||
368 | } | ||
369 | Cvar_Set("rm_drop", Com_GetRandomMapAssemblyNameForCraft(aircraft->id)); | ||
370 | } | ||
371 | } | ||
372 | |||
373 | /*==================================== | ||
374 | * | ||
375 | * Get informations from mission list | ||
376 | * | ||
377 | ====================================*/ | ||
378 | |||
379 | /** | ||
380 | * @brief Get a mission in ccs.missions by Id without error messages. | ||
381 | * @param[in] missionId Unique string id for the mission | ||
382 | * @return pointer to the mission or NULL if Id was NULL or mission not found | ||
383 | */ | ||
384 | mission_t* CP_GetMissionByIDSilent (const char *missionId) | ||
385 | { | ||
386 | if (!missionId) | ||
387 | return NULL__null; | ||
388 | |||
389 | 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 { | ||
390 | if (Q_streq(mission->id, missionId)(strcmp(mission->id, missionId) == 0)) | ||
391 | return mission; | ||
392 | } | ||
393 | |||
394 | return NULL__null; | ||
395 | } | ||
396 | |||
397 | /** | ||
398 | * @brief Get a mission in ccs.missions by Id. | ||
399 | * @param[in] missionId Unique string id for the mission | ||
400 | * @return pointer to the mission or NULL if Id was NULL or mission not found | ||
401 | */ | ||
402 | mission_t* CP_GetMissionByID (const char *missionId) | ||
403 | { | ||
404 | mission_t *mission = CP_GetMissionByIDSilent(missionId); | ||
405 | |||
406 | if (!missionId) | ||
407 | Com_Printf("CP_GetMissionByID: missionId was NULL!\n"); | ||
408 | else if (!mission) | ||
409 | Com_Printf("CP_GetMissionByID: Could not find mission %s\n", missionId); | ||
410 | |||
411 | return mission; | ||
412 | } | ||
413 | |||
414 | /** | ||
415 | * @brief Find mission corresponding to idx | ||
416 | */ | ||
417 | mission_t* MAP_GetMissionByIDX (int id) | ||
418 | { | ||
419 | 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 { | ||
420 | if (mission->idx == id) | ||
421 | return mission; | ||
422 | } | ||
423 | |||
424 | return NULL__null; | ||
425 | } | ||
426 | |||
427 | /** | ||
428 | * @brief Find idx corresponding to mission | ||
429 | */ | ||
430 | int MAP_GetIDXByMission (const mission_t *mis) | ||
431 | { | ||
432 | return mis->idx; | ||
433 | } | ||
434 | |||
435 | /** | ||
436 | * @brief Return the type of mission. | ||
437 | */ | ||
438 | const char* CP_MissionToTypeString (const mission_t *mission) | ||
439 | { | ||
440 | if (mission->category == INTERESTCATEGORY_RESCUE) | ||
441 | return _("Crashed aircraft")gettext("Crashed aircraft"); | ||
442 | |||
443 | switch (mission->stage) { | ||
444 | case STAGE_RECON_GROUND: | ||
445 | case STAGE_SPREAD_XVI: | ||
446 | return _("Landed UFO")gettext("Landed UFO"); | ||
447 | case STAGE_TERROR_MISSION: | ||
448 | return _("Terror mission")gettext("Terror mission"); | ||
449 | case STAGE_BASE_ATTACK: | ||
450 | return _("Base attack")gettext("Base attack"); | ||
451 | case STAGE_BASE_DISCOVERED: | ||
452 | return _("Alien base")gettext("Alien base"); | ||
453 | case STAGE_HARVEST: | ||
454 | return _("Harvesting mission")gettext("Harvesting mission"); | ||
455 | default: | ||
456 | return _("Crashed UFO")gettext("Crashed UFO"); | ||
457 | } | ||
458 | } | ||
459 | |||
460 | #ifdef DEBUG1 | ||
461 | /** | ||
462 | * @brief Return Name of the category of a mission. | ||
463 | * @note Not translated yet - only for console usage | ||
464 | */ | ||
465 | static const char* CP_MissionStageToName (const missionStage_t stage) | ||
466 | { | ||
467 | switch (stage) { | ||
468 | case STAGE_NOT_ACTIVE: | ||
469 | return "Not active yet"; | ||
470 | case STAGE_COME_FROM_ORBIT: | ||
471 | return "UFO coming from orbit"; | ||
472 | case STAGE_RECON_AIR: | ||
473 | return "Aerial recon underway"; | ||
474 | case STAGE_MISSION_GOTO: | ||
475 | return "Going to mission position"; | ||
476 | case STAGE_RECON_GROUND: | ||
477 | return "Ground recon mission underway"; | ||
478 | case STAGE_TERROR_MISSION: | ||
479 | return "Terror mission underway"; | ||
480 | case STAGE_BUILD_BASE: | ||
481 | return "Building base"; | ||
482 | case STAGE_BASE_ATTACK: | ||
483 | return "Attacking a base"; | ||
484 | case STAGE_SUBVERT_GOV: | ||
485 | return "Subverting a government"; | ||
486 | case STAGE_SUPPLY: | ||
487 | return "Supplying"; | ||
488 | case STAGE_SPREAD_XVI: | ||
489 | return "Spreading XVI"; | ||
490 | case STAGE_INTERCEPT: | ||
491 | return "Intercepting or attacking installation"; | ||
492 | case STAGE_RETURN_TO_ORBIT: | ||
493 | return "Leaving earth"; | ||
494 | case STAGE_BASE_DISCOVERED: | ||
495 | return "Base visible"; | ||
496 | case STAGE_HARVEST: | ||
497 | return "Harvesting"; | ||
498 | case STAGE_OVER: | ||
499 | return "Mission over"; | ||
500 | } | ||
501 | |||
502 | /* Can't reach this point */ | ||
503 | return ""; | ||
504 | } | ||
505 | #endif | ||
506 | |||
507 | /** | ||
508 | * @brief Count the number of mission active and displayed on geoscape. | ||
509 | * @return Number of active mission visible on geoscape | ||
510 | */ | ||
511 | int CP_CountMissionOnGeoscape (void) | ||
512 | { | ||
513 | int counterVisibleMission = 0; | ||
514 | |||
515 | 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 { | ||
516 | if (mission->onGeoscape) { | ||
517 | counterVisibleMission++; | ||
518 | } | ||
519 | } | ||
520 | |||
521 | return counterVisibleMission; | ||
522 | } | ||
523 | |||
524 | |||
525 | /*==================================== | ||
526 | * Functions relative to geoscape | ||
527 | *====================================*/ | ||
528 | |||
529 | /** | ||
530 | * @brief Get mission model that should be shown on the geoscape | ||
531 | * @param[in] mission Pointer to the mission drawn on geoscape | ||
532 | * @sa MAP_DrawMapMarkers | ||
533 | */ | ||
534 | const char* MAP_GetMissionModel (const mission_t *mission) | ||
535 | { | ||
536 | /* Mission shouldn't be drawn on geoscape if mapDef is not defined */ | ||
537 | assert(mission->mapDef)(__builtin_expect(!(mission->mapDef), 0) ? __assert_rtn(__func__ , "src/client/cgame/campaign/cp_missions.cpp", 537, "mission->mapDef" ) : (void)0); | ||
538 | |||
539 | if (mission->crashed) | ||
540 | return "geoscape/ufocrash"; | ||
541 | |||
542 | if (mission->mapDef->storyRelated && mission->category != INTERESTCATEGORY_ALIENBASE) | ||
543 | return "geoscape/icon_story"; | ||
544 | |||
545 | Com_DPrintf(DEBUG_CLIENT0x20, "Mission is %s, %d\n", mission->id, mission->category); | ||
546 | switch (mission->category) { | ||
547 | case INTERESTCATEGORY_RESCUE: | ||
548 | return "geoscape/icon_rescue"; | ||
549 | case INTERESTCATEGORY_BUILDING: | ||
550 | return "geoscape/icon_build_alien_base"; | ||
551 | case INTERESTCATEGORY_ALIENBASE: | ||
552 | /** @todo we have two different alienbase models */ | ||
553 | return "geoscape/alienbase"; | ||
554 | case INTERESTCATEGORY_RECON: | ||
555 | return "geoscape/icon_recon"; | ||
556 | case INTERESTCATEGORY_XVI: | ||
557 | return "geoscape/icon_xvi"; | ||
558 | case INTERESTCATEGORY_HARVEST: | ||
559 | return "geoscape/icon_harvest"; | ||
560 | case INTERESTCATEGORY_UFOCARRIER: | ||
561 | return "geoscape/icon_ufocarrier"; | ||
562 | case INTERESTCATEGORY_TERROR_ATTACK: | ||
563 | return "geoscape/icon_terror"; | ||
564 | /* Should not be reached, these mission categories are not drawn on geoscape */ | ||
565 | case INTERESTCATEGORY_BASE_ATTACK: | ||
566 | return "geoscape/base2"; | ||
567 | case INTERESTCATEGORY_SUPPLY: | ||
568 | case INTERESTCATEGORY_INTERCEPT: | ||
569 | case INTERESTCATEGORY_NONE: | ||
570 | case INTERESTCATEGORY_MAX: | ||
571 | break; | ||
572 | } | ||
573 | Com_Error(ERR_DROP1, "Unknown mission interest category"); | ||
574 | } | ||
575 | |||
576 | /** | ||
577 | * @brief Check if a mission should be visible on geoscape. | ||
578 | * @param[in] mission Pointer to mission we want to check visibility. | ||
579 | * @return see missionDetectionStatus_t. | ||
580 | */ | ||
581 | static missionDetectionStatus_t CP_CheckMissionVisibleOnGeoscape (const mission_t *mission) | ||
582 | { | ||
583 | /* This function could be called before position of the mission is defined */ | ||
584 | if (!mission->posAssigned) | ||
585 | return MISDET_CANT_BE_DETECTED; | ||
586 | |||
587 | if (mission->crashed) | ||
588 | return MISDET_ALWAYS_DETECTED; | ||
589 | |||
590 | if (mission->ufo && mission->ufo->detected && mission->ufo->landed) | ||
591 | return MISDET_ALWAYS_DETECTED; | ||
592 | |||
593 | if (mission->category == INTERESTCATEGORY_RESCUE) | ||
594 | return MISDET_ALWAYS_DETECTED; | ||
595 | |||
596 | switch (mission->stage) { | ||
597 | case STAGE_TERROR_MISSION: | ||
598 | case STAGE_BASE_DISCOVERED: | ||
599 | return MISDET_ALWAYS_DETECTED; | ||
600 | case STAGE_RECON_GROUND: | ||
601 | case STAGE_SUBVERT_GOV: | ||
602 | case STAGE_SPREAD_XVI: | ||
603 | case STAGE_HARVEST: | ||
604 | if (RADAR_CheckRadarSensored(mission->pos)) | ||
605 | return MISDET_MAY_BE_DETECTED; | ||
606 | break; | ||
607 | case STAGE_COME_FROM_ORBIT: | ||
608 | case STAGE_MISSION_GOTO: | ||
609 | case STAGE_INTERCEPT: | ||
610 | case STAGE_RECON_AIR: | ||
611 | case STAGE_RETURN_TO_ORBIT: | ||
612 | case STAGE_NOT_ACTIVE: | ||
613 | case STAGE_BUILD_BASE: | ||
614 | case STAGE_BASE_ATTACK: | ||
615 | case STAGE_OVER: | ||
616 | case STAGE_SUPPLY: | ||
617 | break; | ||
618 | } | ||
619 | return MISDET_CANT_BE_DETECTED; | ||
620 | } | ||
621 | |||
622 | /** | ||
623 | * @brief Removes a mission from geoscape: make it non visible and call notify functions | ||
624 | */ | ||
625 | void CP_MissionRemoveFromGeoscape (mission_t *mission) | ||
626 | { | ||
627 | if (!mission->onGeoscape && mission->category != INTERESTCATEGORY_BASE_ATTACK) | ||
628 | return; | ||
629 | |||
630 | mission->onGeoscape = false; | ||
631 | |||
632 | /* Notifications */ | ||
633 | MAP_NotifyMissionRemoved(mission); | ||
634 | AIR_AircraftsNotifyMissionRemoved(mission); | ||
635 | } | ||
636 | |||
637 | /** | ||
638 | * @brief Decides which message level to take for the given mission | ||
639 | * @param[in] mission The mission to chose the message level for | ||
640 | * @return The message level | ||
641 | */ | ||
642 | static inline messageType_t CP_MissionGetMessageLevel (const mission_t *mission) | ||
643 | { | ||
644 | switch (mission->stage) { | ||
645 | case STAGE_BASE_ATTACK: | ||
646 | return MSG_BASEATTACK; | ||
647 | case STAGE_TERROR_MISSION: | ||
648 | return MSG_TERRORSITE; | ||
649 | default: | ||
650 | break; | ||
651 | } | ||
652 | |||
653 | if (mission->crashed) | ||
654 | return MSG_CRASHSITE; | ||
655 | return MSG_STANDARD; | ||
656 | } | ||
657 | |||
658 | /** | ||
659 | * @brief Assembles a message that is send to the gamer once the given mission is added to geoscape | ||
660 | * @param[in] mission The mission that was added to the geoscape and for that a message should be created | ||
661 | * @return The pointer to the static buffer that holds the message. | ||
662 | */ | ||
663 | static inline const char *CP_MissionGetMessage (const mission_t *mission) | ||
664 | { | ||
665 | if (mission->category == INTERESTCATEGORY_RESCUE) | ||
666 | Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("Go on a rescue mission at %s to save your soldiers, some of whom may still be alive.")gettext("Go on a rescue mission at %s to save your soldiers, some of whom may still be alive." ), mission->location); | ||
667 | else | ||
668 | Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("Alien activity has been detected in %s.")gettext("Alien activity has been detected in %s."), mission->location); | ||
669 | |||
670 | return cp_messageBuffer; | ||
671 | } | ||
672 | |||
673 | /** | ||
674 | * @brief Add a mission to geoscape: make it visible and stop time | ||
675 | * @param[in] mission Pointer to added mission. | ||
676 | * @param[in] force true if the mission should be added even for mission needing a probabilty test to be seen. | ||
677 | * @sa CP_CheckNewMissionDetectedOnGeoscape | ||
678 | */ | ||
679 | void CP_MissionAddToGeoscape (mission_t *mission, bool force) | ||
680 | { | ||
681 | const missionDetectionStatus_t status = CP_CheckMissionVisibleOnGeoscape(mission); | ||
682 | |||
683 | /* don't show a mission spawned by a undetected ufo, unless forced : this may be done at next detection stage */ | ||
684 | if (status == MISDET_CANT_BE_DETECTED || (!force && status == MISDET_MAY_BE_DETECTED && mission->ufo && !mission->ufo->detected)) | ||
685 | return; | ||
686 | |||
687 | #ifdef DEBUG1 | ||
688 | /* UFO that spawned this mission should be close of mission */ | ||
689 | if (mission->ufo && ((fabs(mission->ufo->pos[0] - mission->pos[0]) > 1.0f) || (fabs(mission->ufo->pos[1] - mission->pos[1]) > 1.0f))) { | ||
690 | Com_Printf("Error: mission (stage: %s) spawned is not at the same location as UFO\n", CP_MissionStageToName(mission->stage)); | ||
691 | } | ||
692 | #endif | ||
693 | |||
694 | /* Notify the player */ | ||
695 | MS_AddNewMessage(_("Notice")gettext("Notice"), CP_MissionGetMessage(mission), CP_MissionGetMessageLevel(mission)); | ||
696 | |||
697 | mission->onGeoscape = true; | ||
698 | CP_GameTimeStop(); | ||
699 | MAP_UpdateGeoscapeDock(); | ||
700 | } | ||
701 | |||
702 | /** | ||
703 | * @brief Check if mission has been detected by radar. | ||
704 | * @note called every @c DETECTION_INTERVAL. | ||
705 | * @sa RADAR_CheckUFOSensored | ||
706 | * @return True if a new mission was detected. | ||
707 | */ | ||
708 | bool CP_CheckNewMissionDetectedOnGeoscape (void) | ||
709 | { | ||
710 | /* Probability to detect UFO each DETECTION_INTERVAL | ||
711 | * This correspond to 40 percents each 30 minutes (coded this way to be able to | ||
712 | * change DETECTION_INTERVAL without changing the way radar works) */ | ||
713 | const float missionDetectionProbability = 0.000125f * DETECTION_INTERVAL; | ||
714 | bool newDetection = false; | ||
715 | |||
716 | 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 { | ||
717 | const missionDetectionStatus_t status = CP_CheckMissionVisibleOnGeoscape(mission); | ||
718 | |||
719 | /* only check mission that can be detected, and that are not already detected */ | ||
720 | if (status != MISDET_MAY_BE_DETECTED || mission->onGeoscape) | ||
721 | continue; | ||
722 | |||
723 | /* if there is a ufo assigned, it must not be detected */ | ||
724 | assert(!mission->ufo || !mission->ufo->detected)(__builtin_expect(!(!mission->ufo || !mission->ufo-> detected), 0) ? __assert_rtn(__func__, "src/client/cgame/campaign/cp_missions.cpp" , 724, "!mission->ufo || !mission->ufo->detected") : (void)0); | ||
725 | |||
726 | if (frand() <= missionDetectionProbability) { | ||
727 | /* mission is detected */ | ||
728 | CP_MissionAddToGeoscape(mission, true); | ||
729 | |||
730 | /* maybe radar is not activated yet (as ufo wasn't detected before) */ | ||
731 | if (!MAP_IsRadarOverlayActivated()) | ||
732 | MAP_SetOverlay("radar"); | ||
733 | |||
734 | /* if mission has a UFO, detect the UFO when it takes off */ | ||
735 | if (mission->ufo) | ||
736 | mission->ufo->detected = true; | ||
737 | |||
738 | newDetection = true; | ||
739 | } | ||
740 | } | ||
741 | return newDetection; | ||
742 | } | ||
743 | |||
744 | /** | ||
745 | * @brief Update all mission visible on geoscape (in base radar range). | ||
746 | * @note you can't see a mission with aircraft radar. | ||
747 | * @sa CP_CheckMissionAddToGeoscape | ||
748 | */ | ||
749 | void CP_UpdateMissionVisibleOnGeoscape (void) | ||
750 | { | ||
751 | 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 { | ||
752 | if (mission->onGeoscape && CP_CheckMissionVisibleOnGeoscape(mission) == MISDET_CANT_BE_DETECTED) { | ||
753 | /* remove a mission when radar is destroyed */ | ||
754 | CP_MissionRemoveFromGeoscape(mission); | ||
755 | } else if (!mission->onGeoscape && CP_CheckMissionVisibleOnGeoscape(mission) == MISDET_ALWAYS_DETECTED) { | ||
756 | /* only show mission that are always detected: mission that have a probability to be detected will be | ||
757 | * tested at next half hour */ | ||
758 | CP_MissionAddToGeoscape(mission, false); | ||
759 | } | ||
760 | } | ||
761 | } | ||
762 | |||
763 | /** | ||
764 | * @brief Removes (temporarily or permanently) a UFO from geoscape: make it land and call notify functions. | ||
765 | * @param[in] mission Pointer to mission. | ||
766 | * @param[in] destroyed True if the UFO has been destroyed, false if it's only landed. | ||
767 | * @note We don't destroy the UFO if mission is not deleted because we can use it later, e.g. if it takes off. | ||
768 | * @sa UFO_RemoveFromGeoscape | ||
769 | */ | ||
770 | void CP_UFORemoveFromGeoscape (mission_t *mission, bool destroyed) | ||
771 | { | ||
772 | assert(mission->ufo)(__builtin_expect(!(mission->ufo), 0) ? __assert_rtn(__func__ , "src/client/cgame/campaign/cp_missions.cpp", 772, "mission->ufo" ) : (void)0); | ||
773 | |||
774 | /* UFO is landed even if it's destroyed: crash site mission is spawned */ | ||
775 | mission->ufo->landed = true; | ||
776 | |||
777 | /* Notications */ | ||
778 | AIR_AircraftsNotifyUFORemoved(mission->ufo, destroyed); | ||
779 | MAP_NotifyUFORemoved(mission->ufo, destroyed); | ||
780 | AIRFIGHT_RemoveProjectileAimingAircraft(mission->ufo); | ||
781 | |||
782 | if (destroyed) { | ||
783 | /* remove UFO from radar and update idx of ufo in radar array */ | ||
784 | RADAR_NotifyUFORemoved(mission->ufo, true); | ||
785 | |||
786 | /* Update UFO idx */ | ||
787 | /** @todo remove me once the ufo list is a linked list */ | ||
788 | MIS_Foreach(removedMission)for (bool removedMission__break = false, removedMission__once = true; removedMission__once; removedMission__once = false) for (linkedList_t const* removedMission__iter = (ccs.missions); ! removedMission__break && removedMission__iter;) for ( mission_t* const removedMission = ( removedMission__break = removedMission__once = true, (mission_t*) removedMission__iter->data); removedMission__once ; removedMission__break = removedMission__once = false) if ( removedMission__iter = removedMission__iter->next, false) {} else { | ||
789 | if (removedMission->ufo && (removedMission->ufo > mission->ufo)) | ||
790 | removedMission->ufo--; | ||
791 | } | ||
792 | |||
793 | UFO_RemoveFromGeoscape(mission->ufo); | ||
794 | mission->ufo = NULL__null; | ||
795 | } else if (mission->ufo->detected && !RADAR_CheckRadarSensored(mission->ufo->pos)) { | ||
796 | /* maybe we use a high speed time: UFO was detected, but flied out of radar | ||
797 | * range since last calculation, and mission is spawned outside radar range */ | ||
798 | RADAR_NotifyUFORemoved(mission->ufo, false); | ||
799 | } | ||
800 | } | ||
801 | |||
802 | |||
803 | /*==================================== | ||
804 | * | ||
805 | * Handling missions | ||
806 | * | ||
807 | *====================================*/ | ||
808 | |||
809 | /** | ||
810 | * @brief Removes a mission from mission global array. | ||
811 | * @sa UFO_RemoveFromGeoscape | ||
812 | */ | ||
813 | void CP_MissionRemove (mission_t *mission) | ||
814 | { | ||
815 | /* Destroy UFO */ | ||
816 | if (mission->ufo) | ||
817 | CP_UFORemoveFromGeoscape(mission, true); /* for the notifications */ | ||
818 | |||
819 | /* Remove from battle parameters */ | ||
820 | if (mission == ccs.battleParameters.mission) | ||
821 | ccs.battleParameters.mission = NULL__null; | ||
822 | |||
823 | /* Notifications */ | ||
824 | CP_MissionRemoveFromGeoscape(mission); | ||
825 | |||
826 | if (!LIST_Remove(&ccs.missions, mission)) | ||
827 | Com_Error(ERR_DROP1, "CP_MissionRemove: Could not find mission '%s' to remove.\n", mission->id); | ||
828 | } | ||
829 | |||
830 | /** | ||
831 | * @brief Disable time limit for given mission. | ||
832 | * @note This function is used for better readibility. | ||
833 | * @sa CP_CheckNextStageDestination | ||
834 | * @sa CP_CheckMissionLimitedInTime | ||
835 | */ | ||
836 | void CP_MissionDisableTimeLimit (mission_t *mission) | ||
837 | { | ||
838 | mission->finalDate.day = 0; | ||
839 | } | ||
840 | |||
841 | /** | ||
842 | * @brief Check if mission should end because of limited time. | ||
843 | * @note This function is used for better readibility. | ||
844 | * @sa CP_MissionDisableTimeLimit | ||
845 | * @return true if function should end after finalDate | ||
846 | */ | ||
847 | bool CP_CheckMissionLimitedInTime (const mission_t *mission) | ||
848 | { | ||
849 | return mission->finalDate.day != 0; | ||
850 | } | ||
851 | |||
852 | |||
853 | /*==================================== | ||
854 | * | ||
855 | * Notifications | ||
856 | * | ||
857 | ====================================*/ | ||
858 | |||
859 | /** | ||
860 | * @brief Notify that a base has been removed. | ||
861 | */ | ||
862 | void CP_MissionNotifyBaseDestroyed (const base_t *base) | ||
863 | { | ||
864 | 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 { | ||
865 | /* Check if this is a base attack mission attacking this base */ | ||
866 | if (mission->category == INTERESTCATEGORY_BASE_ATTACK && mission->data.base) { | ||
867 | if (base == mission->data.base) { | ||
868 | /* Aimed base has been destroyed, abort mission */ | ||
869 | CP_BaseAttackMissionLeave(mission); | ||
870 | } | ||
871 | } | ||
872 | } | ||
873 | } | ||
874 | |||
875 | /** | ||
876 | * @brief Notify missions that an installation has been destroyed. | ||
877 | * @param[in] installation Pointer to the installation that has been destroyed. | ||
878 | */ | ||
879 | void CP_MissionNotifyInstallationDestroyed (const installation_t *installation) | ||
880 | { | ||
881 | 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 { | ||
882 | if (mission->category == INTERESTCATEGORY_INTERCEPT && mission->data.installation) { | ||
883 | if (mission->data.installation == installation) | ||
884 | CP_InterceptMissionLeave(mission, false); | ||
885 | } | ||
886 | } | ||
887 | } | ||
888 | |||
889 | /*==================================== | ||
890 | * | ||
891 | * Functions common to several mission type | ||
892 | * | ||
893 | ====================================*/ | ||
894 | |||
895 | /** | ||
896 | * @brief Determine what action should be performed when a mission stage ends. | ||
897 | * @param[in] mission Pointer to the mission which stage ended. | ||
898 | */ | ||
899 | void CP_MissionStageEnd (const campaign_t* campaign, mission_t *mission) | ||
900 | { | ||
901 | Com_DPrintf(DEBUG_CLIENT0x20, "Ending mission category %i, stage %i (time: %i day, %i sec)\n", | ||
902 | mission->category, mission->stage, ccs.date.day, ccs.date.sec); | ||
903 | |||
904 | /* Crash mission is on the map for too long: aliens die or go away. End mission */ | ||
905 | if (mission->crashed) { | ||
906 | CP_MissionIsOver(mission); | ||
907 | return; | ||
908 | } | ||
909 | |||
910 | switch (mission->category) { | ||
911 | case INTERESTCATEGORY_RECON: | ||
912 | CP_ReconMissionNextStage(mission); | ||
913 | break; | ||
914 | case INTERESTCATEGORY_TERROR_ATTACK: | ||
915 | CP_TerrorMissionNextStage(mission); | ||
916 | break; | ||
917 | case INTERESTCATEGORY_BASE_ATTACK: | ||
918 | CP_BaseAttackMissionNextStage(mission); | ||
919 | break; | ||
920 | case INTERESTCATEGORY_BUILDING: | ||
921 | CP_BuildBaseMissionNextStage(campaign, mission); | ||
922 | break; | ||
923 | case INTERESTCATEGORY_SUPPLY: | ||
924 | CP_SupplyMissionNextStage(mission); | ||
925 | break; | ||
926 | case INTERESTCATEGORY_XVI: | ||
927 | CP_XVIMissionNextStage(mission); | ||
928 | break; | ||
929 | case INTERESTCATEGORY_INTERCEPT: | ||
930 | CP_InterceptNextStage(mission); | ||
931 | break; | ||
932 | case INTERESTCATEGORY_HARVEST: | ||
933 | CP_HarvestMissionNextStage(mission); | ||
934 | break; | ||
935 | case INTERESTCATEGORY_RESCUE: | ||
936 | CP_RescueNextStage(mission); | ||
937 | break; | ||
938 | case INTERESTCATEGORY_UFOCARRIER: | ||
939 | CP_UFOCarrierNextStage(mission); | ||
940 | break; | ||
941 | case INTERESTCATEGORY_ALIENBASE: | ||
942 | case INTERESTCATEGORY_NONE: | ||
943 | case INTERESTCATEGORY_MAX: | ||
944 | Com_Printf("CP_MissionStageEnd: Invalid type of mission (%i), remove mission '%s'\n", mission->category, mission->id); | ||
945 | CP_MissionRemove(mission); | ||
946 | } | ||
947 | } | ||
948 | |||
949 | /** | ||
950 | * @brief Mission is finished because Phalanx team won it. | ||
951 | * @param[in] mission Pointer to the mission that is ended. | ||
952 | */ | ||
953 | void CP_MissionIsOver (mission_t *mission) | ||
954 | { | ||
955 | switch (mission->category) { | ||
956 | case INTERESTCATEGORY_RECON: | ||
957 | CP_ReconMissionIsFailure(mission); | ||
958 | break; | ||
959 | case INTERESTCATEGORY_TERROR_ATTACK: | ||
960 | CP_TerrorMissionIsFailure(mission); | ||
961 | break; | ||
962 | case INTERESTCATEGORY_BASE_ATTACK: | ||
963 | if (mission->stage <= STAGE_BASE_ATTACK) | ||
964 | CP_BaseAttackMissionIsFailure(mission); | ||
965 | else | ||
966 | CP_BaseAttackMissionIsSuccess(mission); | ||
967 | break; | ||
968 | case INTERESTCATEGORY_BUILDING: | ||
969 | if (mission->stage <= STAGE_BUILD_BASE) | ||
970 | CP_BuildBaseMissionIsFailure(mission); | ||
971 | else | ||
972 | CP_BuildBaseMissionIsSuccess(mission); | ||
973 | break; | ||
974 | case INTERESTCATEGORY_SUPPLY: | ||
975 | if (mission->stage <= STAGE_SUPPLY) | ||
976 | CP_SupplyMissionIsFailure(mission); | ||
977 | else | ||
978 | CP_SupplyMissionIsSuccess(mission); | ||
979 | break; | ||
980 | case INTERESTCATEGORY_XVI: | ||
981 | if (mission->stage <= STAGE_SPREAD_XVI) | ||
982 | CP_XVIMissionIsFailure(mission); | ||
983 | else | ||
984 | CP_XVIMissionIsSuccess(mission); | ||
985 | break; | ||
986 | case INTERESTCATEGORY_INTERCEPT: | ||
987 | if (mission->stage <= STAGE_INTERCEPT) | ||
988 | CP_InterceptMissionIsFailure(mission); | ||
989 | else | ||
990 | CP_InterceptMissionIsSuccess(mission); | ||
991 | break; | ||
992 | case INTERESTCATEGORY_HARVEST: | ||
993 | CP_HarvestMissionIsFailure(mission); | ||
994 | break; | ||
995 | case INTERESTCATEGORY_ALIENBASE: | ||
996 | CP_BuildBaseMissionBaseDestroyed(mission); | ||
997 | break; | ||
998 | case INTERESTCATEGORY_RESCUE: | ||
999 | CP_MissionRemove(mission); | ||
1000 | break; | ||
1001 | case INTERESTCATEGORY_UFOCARRIER: | ||
1002 | case INTERESTCATEGORY_NONE: | ||
1003 | case INTERESTCATEGORY_MAX: | ||
1004 | Com_Printf("CP_MissionIsOver: Invalid type of mission (%i), remove mission\n", mission->category); | ||
1005 | CP_MissionRemove(mission); | ||
1006 | break; | ||
1007 | } | ||
1008 | } | ||
1009 | |||
1010 | /** | ||
1011 | * @brief Mission is finished because Phalanx team ended it. | ||
1012 | * @param[in] ufocraft Pointer to the UFO involved in this mission | ||
1013 | */ | ||
1014 | void CP_MissionIsOverByUFO (aircraft_t *ufocraft) | ||
1015 | { | ||
1016 | assert(ufocraft->mission)(__builtin_expect(!(ufocraft->mission), 0) ? __assert_rtn( __func__, "src/client/cgame/campaign/cp_missions.cpp", 1016, "ufocraft->mission" ) : (void)0); | ||
1017 | CP_MissionIsOver(ufocraft->mission); | ||
1018 | } | ||
1019 | |||
1020 | /** | ||
1021 | * @brief Checks whether aliens can be stored on a base or should be transfered to another | ||
1022 | * @param[in] base Pointer to the base to check if alien contaiment exists and have enough space | ||
1023 | * @param[in] missionResults Pointer to the mission result struct to get captured alien numbers | ||
1024 | */ | ||
1025 | static inline bool CP_TransferOfAliensToOtherBaseNeeded (const base_t *base, const missionResults_t *missionResults) | ||
1026 | { | ||
1027 | assert(missionResults)(__builtin_expect(!(missionResults), 0) ? __assert_rtn(__func__ , "src/client/cgame/campaign/cp_missions.cpp", 1027, "missionResults" ) : (void)0); | ||
1028 | return (missionResults->aliensStunned > 0 && !AL_CheckAliveFreeSpace(base, NULL__null, missionResults->aliensStunned)) | ||
1029 | || (missionResults->aliensKilled > 0 && !AC_ContainmentAllowed(base)); | ||
1030 | } | ||
1031 | |||
1032 | /** | ||
1033 | * @brief Actions to be done after mission finished | ||
1034 | * @param[in,out] mission Pointer to the finished mission | ||
1035 | * @param[in,out] aircraft Pointer to the dropship done the mission | ||
1036 | * @param[in] won Boolean flag if thew mission was successful (from PHALANX's PoV) | ||
1037 | */ | ||
1038 | void CP_MissionEndActions (mission_t *mission, aircraft_t *aircraft, bool won) | ||
1039 | { | ||
1040 | /* handle base attack mission */ | ||
1041 | if (mission->stage == STAGE_BASE_ATTACK) { | ||
1042 | if (won) { | ||
1043 | /* fake an aircraft return to collect goods and aliens */ | ||
1044 | B_DumpAircraftToHomeBase(aircraft); | ||
1045 | |||
1046 | Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("Defence of base: %s successful!")gettext("Defence of base: %s successful!"), | ||
1047 | aircraft->homebase->name); | ||
1048 | MS_AddNewMessage(_("Notice")gettext("Notice"), cp_messageBuffer); | ||
1049 | CP_BaseAttackMissionIsFailure(mission); | ||
1050 | } else | ||
1051 | CP_BaseAttackMissionDestroyBase(mission); | ||
1052 | |||
1053 | return; | ||
1054 | } | ||
1055 | |||
1056 | if (mission->category == INTERESTCATEGORY_RESCUE) { | ||
1057 | CP_EndRescueMission(mission, aircraft, won); | ||
1058 | } | ||
1059 | |||
1060 | AIR_AircraftReturnToBase(aircraft); | ||
1061 | if (won) | ||
1062 | CP_MissionIsOver(mission); | ||
1063 | } | ||
1064 | |||
1065 | /** | ||
1066 | * @brief Closing actions after fighting a battle | ||
1067 | * @param[in] campaign The capmaign we play | ||
1068 | * @param[in, out] mission The mission the battle was on | ||
1069 | * @param[in] battleParameters Parameters of the battle | ||
1070 | * @param[in] if PHALANX won | ||
1071 | * @note both manual and automatic missions call this through won/lost UI screen | ||
1072 | */ | ||
1073 | void CP_MissionEnd (const campaign_t *campaign, mission_t* mission, const battleParam_t* battleParameters, bool won) | ||
1074 | { | ||
1075 | base_t *base; | ||
1076 | aircraft_t *aircraft; | ||
1077 | int numberOfSoldiers = 0; /* DEBUG */ | ||
1078 | |||
1079 | if (mission->stage == STAGE_BASE_ATTACK) { | ||
1080 | base = mission->data.base; | ||
1081 | assert(base)(__builtin_expect(!(base), 0) ? __assert_rtn(__func__, "src/client/cgame/campaign/cp_missions.cpp" , 1081, "base") : (void)0); | ||
1082 | /* HACK */ | ||
1083 | aircraft = base->aircraftCurrent; | ||
1084 | } else { | ||
1085 | aircraft = MAP_GetMissionAircraft()(ccs.geoscape.missionAircraft); | ||
1086 | base = aircraft->homebase; | ||
1087 | } | ||
1088 | |||
1089 | /* add the looted goods to base storage and market */ | ||
1090 | /** | ||
1091 | * @todo find out why this black-magic with inventory is needed and clean up | ||
1092 | * @sa CP_StartSelectedMission | ||
1093 | * @sa AM_Go | ||
1094 | */ | ||
1095 | base->storage = ccs.eMission; | ||
1096 | |||
1097 | won ? ccs.campaignStats.missionsWon++ : ccs.campaignStats.missionsLost++; | ||
1098 | |||
1099 | CP_HandleNationData(campaign->minhappiness, mission, battleParameters->nation, &ccs.missionResults); | ||
1100 | CP_CheckLostCondition(campaign); | ||
1101 | |||
1102 | /* update the character stats */ | ||
1103 | CP_ParseCharacterData(NULL__null); | ||
1104 | |||
1105 | /* update stats */ | ||
1106 | CP_UpdateCharacterStats(base, aircraft); | ||
1107 | |||
1108 | 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 { | ||
1109 | if (AIR_IsEmployeeInAircraft(employee, aircraft)) | ||
1110 | numberOfSoldiers++; | ||
1111 | |||
1112 | /** @todo replace HP check with some CHRSH->IsDead() function */ | ||
1113 | if (E_IsInBase(employee, base) && (employee->chr.HP <= 0)) | ||
1114 | E_DeleteEmployee(employee); | ||
1115 | } | ||
1116 | Com_DPrintf(DEBUG_CLIENT0x20, "CP_MissionEnd - num %i\n", numberOfSoldiers); | ||
1117 | |||
1118 | /* Check for alien containment in aircraft homebase. */ | ||
1119 | /** @todo this shouldn't be here. Ideally we should check for alien/store space when aircraft arrived | ||
1120 | * home and start a player controlled transfer */ | ||
1121 | if (CP_TransferOfAliensToOtherBaseNeeded(base, &ccs.missionResults)) { | ||
1122 | /* We have captured/killed aliens, but the homebase of this aircraft does not have free alien containment space. | ||
1123 | * Popup aircraft transfer dialog to choose a better base. */ | ||
1124 | Cmd_ExecuteString(va("trans_aliens %i", aircraft->idx)); | ||
1125 | } else { | ||
1126 | /* The aircraft can be safely sent to its homebase without losing aliens */ | ||
1127 | } | ||
1128 | |||
1129 | CP_ExecuteMissionTrigger(mission, won); | ||
1130 | CP_MissionEndActions(mission, aircraft, won); | ||
1131 | } | ||
1132 | |||
1133 | /** | ||
1134 | * @brief Check if a stage mission is over when UFO reached destination. | ||
1135 | * @param[in] campaign The campaign data structure | ||
1136 | * @param[in] ufocraft Pointer to the ufo that reached destination. | ||
1137 | * @sa UFO_CampaignRunUFOs | ||
1138 | * @return True if UFO is removed from global array (and therefore pointer ufocraft can't be used anymore). | ||
1139 | */ | ||
1140 | bool CP_CheckNextStageDestination (const campaign_t* campaign, aircraft_t *ufocraft) | ||
1141 | { | ||
1142 | mission_t *mission; | ||
1143 | |||
1144 | mission = ufocraft->mission; | ||
1145 | assert(mission)(__builtin_expect(!(mission), 0) ? __assert_rtn(__func__, "src/client/cgame/campaign/cp_missions.cpp" , 1145, "mission") : (void)0); | ||
1146 | |||
1147 | switch (mission->stage) { | ||
1148 | case STAGE_COME_FROM_ORBIT: | ||
1149 | case STAGE_MISSION_GOTO: | ||
1150 | CP_MissionStageEnd(campaign, mission); | ||
1151 | return false; | ||
1152 | case STAGE_RETURN_TO_ORBIT: | ||
1153 | CP_MissionStageEnd(campaign, mission); | ||
1154 | return true; | ||
1155 | default: | ||
1156 | /* Do nothing */ | ||
1157 | return false; | ||
1158 | } | ||
1159 | } | ||
1160 | |||
1161 | /** | ||
1162 | * @brief Make UFO proceed with its mission when the fight with another aircraft is over (and UFO survived). | ||
1163 | * @param[in] campaign The campaign data structure | ||
1164 | * @param[in] ufo Pointer to the ufo that should proceed a mission. | ||
1165 | */ | ||
1166 | void CP_UFOProceedMission (const campaign_t* campaign, aircraft_t *ufo) | ||
1167 | { | ||
1168 | /* Every UFO on geoscape must have a mission assigned */ | ||
1169 | assert(ufo->mission)(__builtin_expect(!(ufo->mission), 0) ? __assert_rtn(__func__ , "src/client/cgame/campaign/cp_missions.cpp", 1169, "ufo->mission" ) : (void)0); | ||
1170 | |||
1171 | if (ufo->mission->category == INTERESTCATEGORY_INTERCEPT && !ufo->mission->data.aircraft) { | ||
1172 | const int slotIndex = AIRFIGHT_ChooseWeapon(ufo->weapons, ufo->maxWeapons, ufo->pos, ufo->pos); | ||
1173 | /* This is an Intercept mission where UFO attacks aircraft (not installations) */ | ||
1174 | /* Keep on looking targets until mission is over, unless no more ammo */ | ||
1175 | ufo->status = AIR_TRANSIT; | ||
1176 | if (slotIndex != AIRFIGHT_WEAPON_CAN_NEVER_SHOOT-2) | ||
1177 | UFO_SetRandomDest(ufo); | ||
1178 | else | ||
1179 | CP_MissionStageEnd(campaign, ufo->mission); | ||
1180 | } else if (ufo->mission->stage < STAGE_MISSION_GOTO || | ||
1181 | ufo->mission->stage >= STAGE_RETURN_TO_ORBIT) { | ||
1182 | UFO_SetRandomDest(ufo); | ||
1183 | } else { | ||
1184 | UFO_SendToDestination(ufo, ufo->mission->pos); | ||
1185 | } | ||
1186 | } | ||
1187 | |||
1188 | /** | ||
1189 | * @brief Spawn a new crash site after a UFO has been destroyed. | ||
1190 | * @param[in,out] ufo The ufo to spawn a crash site mission for | ||
1191 | */ | ||
1192 | void CP_SpawnCrashSiteMission (aircraft_t *ufo) | ||
1193 | { | ||
1194 | const date_t minCrashDelay = {7, 0}; | ||
1195 | /* How long the crash mission will stay before aliens leave / die */ | ||
1196 | const date_t crashDelay = {14, 0}; | ||
1197 | const nation_t *nation; | ||
1198 | mission_t *mission; | ||
1199 | |||
1200 | mission = ufo->mission; | ||
1201 | if (!mission) | ||
1202 | Com_Error(ERR_DROP1, "CP_SpawnCrashSiteMission: No mission correspond to ufo '%s'", ufo->id); | ||
1203 | |||
1204 | mission->crashed = true; | ||
1205 | |||
1206 | /* Reset mapDef. CP_ChooseMap don't overwrite if set */ | ||
1207 | mission->mapDef = NULL__null; | ||
1208 | if (!CP_ChooseMap(mission, ufo->pos)) { | ||
1209 | Com_Printf("CP_SpawnCrashSiteMission: No map found, remove mission.\n"); | ||
1210 | CP_MissionRemove(mission); | ||
1211 | return; | ||
1212 | } | ||
1213 | |||
1214 | Vector2Copy(ufo->pos, mission->pos)((mission->pos)[0]=(ufo->pos)[0],(mission->pos)[1]=( ufo->pos)[1]); | ||
1215 | mission->posAssigned = true; | ||
1216 | |||
1217 | nation = MAP_GetNation(mission->pos); | ||
1218 | if (nation) { | ||
1219 | Com_sprintf(mission->location, sizeof(mission->location), "%s", _(nation->name)gettext(nation->name)); | ||
1220 | } else { | ||
1221 | Com_sprintf(mission->location, sizeof(mission->location), "%s", _("No nation")gettext("No nation")); | ||
1222 | } | ||
1223 | |||
1224 | mission->finalDate = Date_Add(ccs.date, Date_Random(minCrashDelay, crashDelay)); | ||
1225 | /* ufo becomes invisible on geoscape, but don't remove it from ufo global array | ||
1226 | * (may be used to know what items are collected from battlefield)*/ | ||
1227 | CP_UFORemoveFromGeoscape(mission, false); | ||
1228 | /* mission appear on geoscape, player can go there */ | ||
1229 | CP_MissionAddToGeoscape(mission, false); | ||
1230 | } | ||
1231 | |||
1232 | |||
1233 | /** | ||
1234 | * @brief Spawn a new rescue mission for a crashed (phalanx) aircraft | ||
1235 | * @param[in] aircraft The crashed aircraft to spawn the rescue mission for. | ||
1236 | * @param[in] ufo The UFO that shot down the phalanx aircraft, can also | ||
1237 | * be @c NULL if the UFO was destroyed. | ||
1238 | * @note Don't use ufo's old mission pointer after this call! It might have been removed. | ||
1239 | * @todo Don't spawn rescue mission every time! It should depend on pilot's manoeuvring (piloting) skill | ||
1240 | */ | ||
1241 | void CP_SpawnRescueMission (aircraft_t *aircraft, aircraft_t *ufo) | ||
1242 | { | ||
1243 | mission_t *mission; | ||
1244 | mission_t *oldMission; | ||
1245 | employee_t *pilot; | ||
1246 | int survivors = 0; | ||
1247 | |||
1248 | /* Handle events about crash */ | ||
1249 | /* Do this first, if noone survived the crash => no mission to spawn */ | ||
1250 | pilot = AIR_GetPilot(aircraft); | ||
1251 | /** @todo don't "kill" everyone - this should depend on luck and a little bit on the skills */ | ||
1252 | E_DeleteEmployee(pilot); | ||
1253 | |||
1254 | LIST_Foreach(aircraft->acTeam, employee_t, employee)for (bool employee__break = false, employee__once = true; employee__once ; employee__once = false) for (linkedList_t const* employee__iter = (aircraft->acTeam); ! 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 { | ||
1255 | #if 0 | ||
1256 | const character_t *chr = &employee->chr; | ||
1257 | const chrScoreGlobal_t *score = &chr->score; | ||
1258 | /** @todo don't "kill" everyone - this should depend on luck and a little bit on the skills */ | ||
1259 | E_DeleteEmployee(employee); | ||
1260 | #else | ||
1261 | (void)employee; | ||
1262 | #endif | ||
1263 | survivors++; | ||
1264 | } | ||
1265 | |||
1266 | aircraft->status = AIR_CRASHED; | ||
1267 | |||
1268 | /* after we set this to AIR_CRASHED we can get the next 'valid' | ||
1269 | * aircraft to correct the pointer in the homebase */ | ||
1270 | if (aircraft->homebase->aircraftCurrent == aircraft) | ||
1271 | aircraft->homebase->aircraftCurrent = AIR_GetFirstFromBase(aircraft->homebase); | ||
1272 | |||
1273 | /* a crashed aircraft is no longer using capacity of the hangars */ | ||
1274 | AIR_UpdateHangarCapForAll(aircraft->homebase); | ||
1275 | |||
1276 | if (MAP_IsAircraftSelected(aircraft)((aircraft) == ccs.geoscape.selectedAircraft)) | ||
1277 | MAP_SetSelectedAircraft(NULL)(ccs.geoscape.selectedAircraft = (__null)); | ||
1278 | |||
1279 | /* Check if ufo was destroyed too */ | ||
1280 | if (!ufo) { | ||
1281 | /** @todo find out what to do in this case */ | ||
1282 | Com_Printf("CP_SpawnRescueMission: UFO was also destroyed.\n"); | ||
1283 | return; | ||
1284 | } | ||
1285 | |||
1286 | if (survivors == 0) { | ||
1287 | AIR_DestroyAircraft(aircraft); | ||
1288 | return; | ||
1289 | } | ||
1290 | |||
1291 | /* create the mission */ | ||
1292 | mission = CP_CreateNewMission(INTERESTCATEGORY_RESCUE, true); | ||
1293 | if (!mission) | ||
1294 | Com_Error(ERR_DROP1, "CP_SpawnRescueMission: mission could not be created"); | ||
1295 | |||
1296 | /* Reset mapDef. CP_ChooseMap don't overwrite if set */ | ||
1297 | mission->mapDef = NULL__null; | ||
1298 | if (!CP_ChooseMap(mission, aircraft->pos)) { | ||
1299 | Com_Printf("CP_SpawnRescueMission: Cannot set mapDef for mission %s, removing.\n", mission->id); | ||
1300 | CP_MissionRemove(mission); | ||
1301 | return; | ||
1302 | } | ||
1303 | |||
1304 | mission->data.aircraft = aircraft; | ||
1305 | Com_sprintf(mission->location, sizeof(mission->location), "%s %s", _("Crashed")gettext("Crashed"), aircraft->name); | ||
1306 | |||
1307 | /* UFO drops it's previous mission and goes for the crashed aircraft */ | ||
1308 | oldMission = ufo->mission; | ||
1309 | oldMission->ufo = NULL__null; | ||
1310 | ufo->mission = mission; | ||
1311 | CP_MissionRemove(oldMission); | ||
1312 | |||
1313 | mission->ufo = ufo; | ||
1314 | mission->stage = STAGE_MISSION_GOTO; | ||
1315 | Vector2Copy(aircraft->pos, mission->pos)((mission->pos)[0]=(aircraft->pos)[0],(mission->pos) [1]=(aircraft->pos)[1]); | ||
1316 | |||
1317 | /* Stage will finish when UFO arrives at destination */ | ||
1318 | CP_MissionDisableTimeLimit(mission); | ||
1319 | } | ||
1320 | |||
1321 | /*==================================== | ||
1322 | * | ||
1323 | * Spawn new missions | ||
1324 | * | ||
1325 | ====================================*/ | ||
1326 | |||
1327 | /** | ||
1328 | * @brief mission begins: UFO arrive on earth. | ||
1329 | * @param[in] mission The mission to change the state for | ||
1330 | * @note Stage 0 -- This function is common to several mission category. | ||
1331 | * @sa CP_MissionChooseUFO | ||
1332 | * @return true if mission was created, false else. | ||
1333 | */ | ||
1334 | bool CP_MissionBegin (mission_t *mission) | ||
1335 | { | ||
1336 | ufoType_t ufoType; | ||
1337 | |||
1338 | mission->stage = STAGE_COME_FROM_ORBIT; | ||
1339 | |||
1340 | ufoType = CP_MissionChooseUFO(mission); | ||
1341 | if (ufoType == UFO_MAX) { | ||
1342 | mission->ufo = NULL__null; | ||
1343 | /* Mission starts from ground: no UFO. Go to next stage on next frame (skip UFO arrives on earth) */ | ||
1344 | mission->finalDate = ccs.date; | ||
1345 | } else { | ||
1346 | mission->ufo = UFO_AddToGeoscape(ufoType, NULL__null, mission); | ||
1347 | if (!mission->ufo) { | ||
1348 | Com_Printf("CP_MissionBegin: Could not add UFO '%s', remove mission %s\n", | ||
1349 | Com_UFOTypeToShortName(ufoType), mission->id); | ||
1350 | CP_MissionRemove(mission); | ||
1351 | return false; | ||
1352 | } | ||
1353 | /* Stage will finish when UFO arrives at destination */ | ||
1354 | CP_MissionDisableTimeLimit(mission); | ||
1355 | } | ||
1356 | |||
1357 | mission->idx = ++ccs.campaignStats.missions; | ||
1358 | return true; | ||
1359 | } | ||
1360 | |||
1361 | /** | ||
1362 | * @brief Choose UFO type for a given mission category. | ||
1363 | * @param[in] mission Pointer to the mission where the UFO will be added | ||
1364 | * @sa CP_MissionChooseUFO | ||
1365 | * @sa CP_SupplyMissionCreate | ||
1366 | * @return ufoType_t of the UFO spawning the mission, UFO_MAX if the mission is spawned from ground | ||
1367 | */ | ||
1368 | ufoType_t CP_MissionChooseUFO (const mission_t *mission) | ||
1369 | { | ||
1370 | ufoType_t ufoTypes[UFO_MAX]; | ||
1371 | int numTypes = 0; | ||
1372 | int idx; | ||
1373 | bool canBeSpawnedFromGround = false; | ||
1374 | float groundProbability = 0.0f; /**< Probability to start a mission from earth (without UFO) */ | ||
1375 | float randNumber; | ||
1376 | |||
1377 | switch (mission->category) { | ||
1378 | case INTERESTCATEGORY_RECON: | ||
1379 | numTypes = CP_ReconMissionAvailableUFOs(mission, ufoTypes); | ||
1380 | canBeSpawnedFromGround = true; | ||
1381 | break; | ||
1382 | case INTERESTCATEGORY_TERROR_ATTACK: | ||
1383 | numTypes = CP_TerrorMissionAvailableUFOs(mission, ufoTypes); | ||
1384 | canBeSpawnedFromGround = true; | ||
1385 | break; | ||
1386 | case INTERESTCATEGORY_BASE_ATTACK: | ||
1387 | numTypes = CP_BaseAttackMissionAvailableUFOs(mission, ufoTypes); | ||
1388 | canBeSpawnedFromGround = true; | ||
1389 | break; | ||
1390 | case INTERESTCATEGORY_BUILDING: | ||
1391 | numTypes = CP_BuildBaseMissionAvailableUFOs(mission, ufoTypes); | ||
1392 | break; | ||
1393 | case INTERESTCATEGORY_SUPPLY: | ||
1394 | numTypes = CP_SupplyMissionAvailableUFOs(mission, ufoTypes); | ||
1395 | break; | ||
1396 | case INTERESTCATEGORY_XVI: | ||
1397 | numTypes = CP_XVIMissionAvailableUFOs(mission, ufoTypes); | ||
1398 | canBeSpawnedFromGround = true; | ||
1399 | break; | ||
1400 | case INTERESTCATEGORY_INTERCEPT: | ||
1401 | numTypes = CP_InterceptMissionAvailableUFOs(mission, ufoTypes); | ||
1402 | break; | ||
1403 | case INTERESTCATEGORY_HARVEST: | ||
1404 | numTypes = CP_HarvestMissionAvailableUFOs(mission, ufoTypes); | ||
1405 | break; | ||
1406 | case INTERESTCATEGORY_ALIENBASE: | ||
1407 | /* can't be spawned: alien base is the result of base building mission */ | ||
1408 | case INTERESTCATEGORY_UFOCARRIER: | ||
1409 | case INTERESTCATEGORY_RESCUE: | ||
1410 | case INTERESTCATEGORY_NONE: | ||
1411 | case INTERESTCATEGORY_MAX: | ||
1412 | Com_Error(ERR_DROP1, "CP_MissionChooseUFO: Wrong mission category %i", mission->category); | ||
1413 | } | ||
1414 | |||
1415 | if (numTypes > UFO_MAX) | ||
1416 | Com_Error(ERR_DROP1, "CP_MissionChooseUFO: Too many values UFOs (%i/%i)", numTypes, UFO_MAX); | ||
1417 | |||
1418 | /* Roll the random number */ | ||
1419 | randNumber = frand(); | ||
1420 | |||
1421 | /* Check if the mission is spawned without UFO */ | ||
1422 | if (canBeSpawnedFromGround) { | ||
1423 | const int XVI_PARAM = 10; /**< Typical XVI average value for spreading mission from earth */ | ||
1424 | /* The higher the XVI rate, the higher the probability to have a mission spawned from ground */ | ||
1425 | groundProbability = 1.0f - exp(-CP_GetAverageXVIRate() / XVI_PARAM); | ||
1426 | groundProbability = std::max(0.1f, groundProbability); | ||
1427 | |||
1428 | /* Mission spawned from ground */ | ||
1429 | if (randNumber < groundProbability) | ||
1430 | return UFO_MAX; | ||
1431 | } | ||
1432 | |||
1433 | /* If we reached this point, then mission will be spawned from space: choose UFO */ | ||
1434 | assert(numTypes)(__builtin_expect(!(numTypes), 0) ? __assert_rtn(__func__, "src/client/cgame/campaign/cp_missions.cpp" , 1434, "numTypes") : (void)0); | ||
1435 | idx = (int) ((numTypes - 1) * randNumber); | ||
1436 | if (idx >= numTypes) | ||
1437 | Sys_Error("CP_MissionChooseUFO: idx exceeded: %i (randNumber: %f, groundProbability: %f, numTypes: %i)", | ||
1438 | idx, randNumber, groundProbability, numTypes); | ||
1439 | |||
1440 | return ufoTypes[idx]; | ||
1441 | } | ||
1442 | |||
1443 | /** | ||
1444 | * @brief Set mission name. | ||
1445 | * @note that mission name must be unique in mission global array | ||
1446 | * @param[out] mission The mission to set the name for | ||
1447 | * @sa CP_CreateNewMission | ||
1448 | */ | ||
1449 | static inline void CP_SetMissionName (mission_t *mission) | ||
1450 | { | ||
1451 | int num = 0; | ||
1452 | |||
1453 | do { | ||
1454 | Com_sprintf(mission->id, sizeof(mission->id), "cat%i_interest%i_%i", | ||
1455 | mission->category, mission->initialOverallInterest, num); | ||
1456 | |||
1457 | /* if mission list is empty, the id is unique for sure */ | ||
1458 | if (LIST_IsEmpty(ccs.missions)) | ||
1459 | return; | ||
1460 | |||
1461 | /* check whether a mission with the same id already exists in the list | ||
1462 | * if so, generate a new name by using an increased num values - otherwise | ||
1463 | * just use the mission->id we just stored - it should be unique now */ | ||
1464 | if (!CP_GetMissionByIDSilent(mission->id)) | ||
1465 | break; | ||
1466 | |||
1467 | num++; | ||
1468 | } while (num); /* fake condition */ | ||
1469 | } | ||
1470 | |||
1471 | /** | ||
1472 | * @brief Create a new mission of given category. | ||
1473 | * @param[in] category category of the mission | ||
1474 | * @param[in] beginNow true if the mission should begin now | ||
1475 | * @sa CP_SpawnNewMissions | ||
1476 | * @sa CP_MissionStageEnd | ||
1477 | */ | ||
1478 | mission_t *CP_CreateNewMission (interestCategory_t category, bool beginNow) | ||
1479 | { | ||
1480 | mission_t mission; | ||
1481 | const date_t minNextSpawningDate = {0, 0}; | ||
1482 | const date_t nextSpawningDate = {3, 0}; /* Delay between 2 mission spawning */ | ||
1483 | |||
1484 | /* Some event are non-occurrence */ | ||
1485 | if (category <= INTERESTCATEGORY_NONE || category >= INTERESTCATEGORY_MAX) | ||
1486 | return NULL__null; | ||
1487 | |||
1488 | OBJZERO(mission)(memset(&((mission)), (0), sizeof((mission)))); | ||
1489 | |||
1490 | /* First fill common datas between all type of missions */ | ||
1491 | mission.category = category; | ||
1492 | mission.initialOverallInterest = ccs.overallInterest; | ||
1493 | mission.initialIndividualInterest = ccs.interest[category]; | ||
1494 | mission.stage = STAGE_NOT_ACTIVE; | ||
1495 | mission.ufo = NULL__null; | ||
1496 | if (beginNow) { | ||
1497 | mission.startDate.day = ccs.date.day; | ||
1498 | mission.startDate.sec = ccs.date.sec; | ||
1499 | } else | ||
1500 | mission.startDate = Date_Add(ccs.date, Date_Random(minNextSpawningDate, nextSpawningDate)); | ||
1501 | mission.finalDate = mission.startDate; | ||
1502 | mission.idx = ++ccs.campaignStats.missions; | ||
1503 | |||
1504 | CP_SetMissionName(&mission); | ||
1505 | |||
1506 | /* Handle mission specific, spawn-time routines */ | ||
1507 | if (mission.category == INTERESTCATEGORY_BUILDING) | ||
1508 | CP_BuildBaseMissionOnSpawn(); | ||
1509 | else if (mission.category == INTERESTCATEGORY_BASE_ATTACK) | ||
1510 | CP_BaseAttackMissionOnSpawn(); | ||
1511 | else if (mission.category == INTERESTCATEGORY_TERROR_ATTACK) | ||
1512 | CP_TerrorMissionOnSpawn(); | ||
1513 | |||
1514 | /* Add mission to global array */ | ||
1515 | return &LIST_Add(&ccs.missions, mission); | ||
1516 | } | ||
1517 | |||
1518 | /** | ||
1519 | * @brief Select new mission type. | ||
1520 | * @sa CP_SpawnNewMissions | ||
1521 | */ | ||
1522 | static interestCategory_t CP_SelectNewMissionType (void) | ||
1523 | { | ||
1524 | int sum = 0; | ||
1525 | int i, randomNumber; | ||
1526 | |||
1527 | for (i = 0; i < INTERESTCATEGORY_MAX; i++) | ||
1528 | sum += ccs.interest[i]; | ||
1529 | |||
1530 | randomNumber = (int) (frand() * (float) sum); | ||
1531 | |||
1532 | for (i = 0; randomNumber >= 0; i++) | ||
1533 | randomNumber -= ccs.interest[i]; | ||
1534 | |||
1535 | return (interestCategory_t)(i - 1); | ||
1536 | } | ||
1537 | |||
1538 | /** | ||
1539 | * @brief Spawn new missions. | ||
1540 | * @sa CP_CampaignRun | ||
1541 | * @note daily called | ||
1542 | */ | ||
1543 | void CP_SpawnNewMissions (void) | ||
1544 | { | ||
1545 | ccs.lastMissionSpawnedDelay++; | ||
1546 | |||
1547 | if (ccs.lastMissionSpawnedDelay > DELAY_BETWEEN_MISSION_SPAWNING8) { | ||
1548 | float nonOccurrence; | ||
1549 | int newMissionNum, i; | ||
1550 | /* Select the amount of missions that will be spawned in the next cycle. */ | ||
1551 | |||
1552 | /* Each mission has a certain probability to not occur. This provides some randomness to the mission density. | ||
1553 | * However, once the campaign passes a certain point, this effect rapidly diminishes. This means that by the | ||
1554 | * end of the game, ALL missions will spawn, quickly overrunning the player. */ | ||
1555 | if (ccs.overallInterest > FINAL_OVERALL_INTEREST1000) | ||
1556 | nonOccurrence = NON_OCCURRENCE_PROBABILITY0.65 / pow(((ccs.overallInterest - FINAL_OVERALL_INTEREST1000 / 30) + 1), 2); | ||
1557 | else | ||
1558 | nonOccurrence = NON_OCCURRENCE_PROBABILITY0.65; | ||
1559 | |||
1560 | /* Increase the alien's interest in supplying their bases for this cycle. | ||
1561 | * The more bases, the greater their interest in supplying them. */ | ||
1562 | INT_ChangeIndividualInterest(AB_GetAlienBaseNumber() * 0.1f, INTERESTCATEGORY_SUPPLY); | ||
1563 | |||
1564 | /* Calculate the amount of missions to be spawned this cycle. | ||
1565 | * Note: This is a function over css.overallInterest. It looks like this: | ||
1566 | * http://www.wolframalpha.com/input/?i=Plot%5B40%2B%285-40%29%2A%28%28x-1000%29%2F%2820-1000%29%29%5E2%2C+%7Bx%2C+0%2C+1100%7D%5D | ||
1567 | */ | ||
1568 | newMissionNum = (int) (MAXIMUM_MISSIONS_PER_CYCLE25 + (MINIMUM_MISSIONS_PER_CYCLE5 - MAXIMUM_MISSIONS_PER_CYCLE25) * pow(((ccs.overallInterest - FINAL_OVERALL_INTEREST1000) / (INITIAL_OVERALL_INTEREST20 - FINAL_OVERALL_INTEREST1000)), 2)); | ||
1569 | Com_DPrintf(DEBUG_CLIENT0x20, "interest = %d, new missions = %d\n", ccs.overallInterest, newMissionNum); | ||
1570 | for (i = 0; i < newMissionNum; i++) { | ||
1571 | if (frand() > nonOccurrence) { | ||
1572 | const interestCategory_t type = CP_SelectNewMissionType(); | ||
1573 | CP_CreateNewMission(type, false); | ||
1574 | } | ||
1575 | } | ||
1576 | |||
1577 | ccs.lastMissionSpawnedDelay -= DELAY_BETWEEN_MISSION_SPAWNING8; | ||
1578 | } | ||
1579 | } | ||
1580 | |||
1581 | /** | ||
1582 | * @brief Initialize spawning delay. | ||
1583 | * @sa CP_SpawnNewMissions | ||
1584 | * @note only called when new game is started, in order to spawn new event on the beginning of the game. | ||
1585 | */ | ||
1586 | void CP_InitializeSpawningDelay (void) | ||
1587 | { | ||
1588 | ccs.lastMissionSpawnedDelay = DELAY_BETWEEN_MISSION_SPAWNING8; | ||
1589 | |||
1590 | ccs.missionSpawnCallback(); | ||
1591 | } | ||
1592 | |||
1593 | |||
1594 | /*==================================== | ||
1595 | * | ||
1596 | * Debug functions | ||
1597 | * | ||
1598 | ====================================*/ | ||
1599 | |||
1600 | #ifdef DEBUG1 | ||
1601 | /** | ||
1602 | * @brief Debug function for creating a mission. | ||
1603 | */ | ||
1604 | static void MIS_SpawnNewMissions_f (void) | ||
1605 | { | ||
1606 | interestCategory_t category; | ||
1607 | int type = 0; | ||
1608 | mission_t *mission; | ||
1609 | |||
1610 | if (Cmd_Argc() < 2) { | ||
1611 | int i; | ||
1612 | Com_Printf("Usage: %s <category> [<type>]\n", Cmd_Argv(0)); | ||
1613 | for (i = INTERESTCATEGORY_RECON; i < INTERESTCATEGORY_MAX; i++) { | ||
1614 | category = (interestCategory_t)i; | ||
1615 | Com_Printf("...%i: %s", category, INT_InterestCategoryToName(category)); | ||
1616 | if (category == INTERESTCATEGORY_RECON) | ||
1617 | Com_Printf(" <0:Random, 1:Aerial, 2:Ground>"); | ||
1618 | else if (category == INTERESTCATEGORY_BUILDING) | ||
1619 | Com_Printf(" <0:Subverse Government, 1:Build Base>"); | ||
1620 | else if (category == INTERESTCATEGORY_INTERCEPT) | ||
1621 | Com_Printf(" <0:Intercept aircraft, 1:Attack installation>"); | ||
1622 | Com_Printf("\n"); | ||
1623 | } | ||
1624 | return; | ||
1625 | } | ||
1626 | |||
1627 | if (Cmd_Argc() >= 3) | ||
1628 | type = atoi(Cmd_Argv(2)); | ||
1629 | |||
1630 | category = (interestCategory_t)atoi(Cmd_Argv(1)); | ||
1631 | |||
1632 | if (category == INTERESTCATEGORY_MAX) | ||
1633 | return; | ||
1634 | |||
1635 | if (category == INTERESTCATEGORY_ALIENBASE) { | ||
1636 | /* spawning an alien base is special */ | ||
1637 | vec2_t pos; | ||
1638 | alienBase_t *base; | ||
1639 | AB_SetAlienBasePosition(pos); /* get base position */ | ||
1640 | base = AB_BuildBase(pos); /* build base */ | ||
1641 | if (!base) { | ||
1642 | Com_Printf("CP_BuildBaseSetUpBase: could not create base\n"); | ||
1643 | return; | ||
1644 | } | ||
1645 | CP_SpawnAlienBaseMission(base); /* make base visible */ | ||
1646 | return; | ||
1647 | } else if (category == INTERESTCATEGORY_RESCUE) { | ||
1648 | const base_t *base = B_GetFoundedBaseByIDX(0); | ||
1649 | aircraft_t *aircraft; | ||
1650 | if (!base) { | ||
1651 | Com_Printf("No base yet\n"); | ||
1652 | return; | ||
1653 | } | ||
1654 | |||
1655 | aircraft = AIR_GetFirstFromBase(base); | ||
1656 | if (!aircraft) { | ||
1657 | Com_Printf("No aircraft in base\n"); | ||
1658 | return; | ||
1659 | } | ||
1660 | CP_SpawnRescueMission(aircraft, NULL__null); | ||
1661 | return; | ||
1662 | } | ||
1663 | |||
1664 | mission = CP_CreateNewMission(category, true); | ||
1665 | if (!mission) { | ||
1666 | Com_Printf("CP_SpawnNewMissions_f: Could not add mission, abort\n"); | ||
1667 | return; | ||
1668 | } | ||
1669 | |||
1670 | if (type) { | ||
1671 | switch (category) { | ||
1672 | case INTERESTCATEGORY_RECON: | ||
1673 | /* Start mission */ | ||
1674 | if (!CP_MissionBegin(mission)) | ||
1675 | return; | ||
1676 | if (type == 1) | ||
1677 | /* Aerial mission */ | ||
1678 | CP_ReconMissionAerial(mission); | ||
1679 | else | ||
1680 | /* This is a ground mission */ | ||
1681 | CP_ReconMissionGroundGo(mission); | ||
1682 | break; | ||
1683 | case INTERESTCATEGORY_BUILDING: | ||
1684 | if (type == 1) | ||
1685 | mission->initialOverallInterest = STARTING_BASEBUILD_INTEREST + 1; | ||
1686 | break; | ||
1687 | case INTERESTCATEGORY_INTERCEPT: | ||
1688 | /* Start mission */ | ||
1689 | if (!CP_MissionBegin(mission)) | ||
1690 | return; | ||
1691 | if (type == 1) { | ||
1692 | mission->ufo->ufotype = UFO_HARVESTER; | ||
1693 | CP_InterceptGoToInstallation(mission); | ||
1694 | } else { | ||
1695 | CP_InterceptAircraftMissionSet(mission); | ||
1696 | } | ||
1697 | break; | ||
1698 | default: | ||
1699 | Com_Printf("Type is not implemented for this category.\n"); | ||
1700 | } | ||
1701 | } | ||
1702 | Com_Printf("Spawned mission with id '%s'\n", mission->id); | ||
1703 | } | ||
1704 | |||
1705 | /** | ||
1706 | * @brief Changes the map for an already spawned mission | ||
1707 | */ | ||
1708 | static void MIS_MissionSetMap_f (void) | ||
1709 | { | ||
1710 | mapDef_t *mapDef; | ||
1711 | mission_t *mission; | ||
1712 | if (Cmd_Argc() < 3) { | ||
| |||
1713 | Com_Printf("Usage: %s <missionid> <mapdef>\n", Cmd_Argv(0)); | ||
1714 | return; | ||
1715 | } | ||
1716 | mission = CP_GetMissionByID(Cmd_Argv(1)); | ||
1717 | mapDef = Com_GetMapDefinitionByID(Cmd_Argv(2)); | ||
1718 | if (mapDef == NULL__null) { | ||
| |||
1719 | Com_Printf("Could not find mapdef for %s\n", Cmd_Argv(2)); | ||
1720 | return; | ||
1721 | } | ||
1722 | mission->mapDef = mapDef; | ||
| |||
1723 | } | ||
1724 | |||
1725 | /** | ||
1726 | * @brief List all current mission to console. | ||
1727 | * @note Use with debug_missionlist | ||
1728 | */ | ||
1729 | static void MIS_MissionList_f (void) | ||
1730 | { | ||
1731 | bool noMission = true; | ||
1732 | |||
1733 | 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 { | ||
1734 | Com_Printf("mission: '%s'\n", mission->id); | ||
1735 | Com_Printf("...category %i. '%s' -- stage %i. '%s'\n", mission->category, | ||
1736 | INT_InterestCategoryToName(mission->category), mission->stage, CP_MissionStageToName(mission->stage)); | ||
1737 | Com_Printf("...mapDef: '%s'\n", mission->mapDef ? mission->mapDef->id : "No mapDef defined"); | ||
1738 | Com_Printf("...location: '%s'\n", mission->location); | ||
1739 | Com_Printf("...start (day = %i, sec = %i), ends (day = %i, sec = %i)\n", | ||
1740 | mission->startDate.day, mission->startDate.sec, mission->finalDate.day, mission->finalDate.sec); | ||
1741 | Com_Printf("...pos (%.02f, %.02f)%s -- mission %son Geoscape\n", mission->pos[0], mission->pos[1], mission->posAssigned ? "(assigned Pos)" : "", mission->onGeoscape ? "" : "not "); | ||
1742 | if (mission->ufo) | ||
1743 | Com_Printf("...UFO: %s (%i/%i)\n", mission->ufo->id, (int) (mission->ufo - ccs.ufos), ccs.numUFOs - 1); | ||
1744 | else | ||
1745 | Com_Printf("...UFO: no UFO\n"); | ||
1746 | noMission = false; | ||
1747 | } | ||
1748 | if (noMission) | ||
1749 | Com_Printf("No mission currently in game.\n"); | ||
1750 | } | ||
1751 | |||
1752 | /** | ||
1753 | * @brief Debug function for deleting all mission in global array. | ||
1754 | * @note called with debug_missionsdel | ||
1755 | */ | ||
1756 | static void MIS_DeleteMissions_f (void) | ||
1757 | { | ||
1758 | 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 { | ||
1759 | CP_MissionRemove(mission); | ||
1760 | } | ||
1761 | |||
1762 | if (ccs.numUFOs != 0) { | ||
1763 | Com_Printf("CP_DeleteMissions_f: Error, there are still %i UFO in game afer removing all missions. Force removal.\n", ccs.numUFOs); | ||
1764 | while (ccs.numUFOs) | ||
1765 | UFO_RemoveFromGeoscape(ccs.ufos); | ||
1766 | } | ||
1767 | } | ||
1768 | #endif | ||
1769 | |||
1770 | /** | ||
1771 | * @brief Save callback for savegames in XML Format | ||
1772 | * @param[out] parent XML Node structure, where we write the information to | ||
1773 | */ | ||
1774 | bool MIS_SaveXML (xmlNode_tmxml_node_t *parent) | ||
1775 | { | ||
1776 | xmlNode_tmxml_node_t *missionsNode = XML_AddNode(parent, SAVE_MISSIONS"missions"); | ||
1777 | |||
1778 | Com_RegisterConstList(saveInterestConstants); | ||
1779 | Com_RegisterConstList(saveMissionConstants); | ||
1780 | 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 { | ||
1781 | xmlNode_tmxml_node_t *missionNode = XML_AddNode(missionsNode, SAVE_MISSIONS_MISSION"mission"); | ||
1782 | |||
1783 | XML_AddInt(missionNode, SAVE_MISSIONS_MISSION_IDX"IDX", mission->idx); | ||
1784 | XML_AddString(missionNode, SAVE_MISSIONS_ID"id", mission->id); | ||
1785 | if (mission->mapDef) | ||
1786 | XML_AddString(missionNode, SAVE_MISSIONS_MAPDEF_ID"mapDefId", mission->mapDef->id); | ||
1787 | XML_AddBool(missionNode, SAVE_MISSIONS_ACTIVE"active", mission->active); | ||
1788 | XML_AddBool(missionNode, SAVE_MISSIONS_POSASSIGNED"posAssigned", mission->posAssigned); | ||
1789 | XML_AddBool(missionNode, SAVE_MISSIONS_CRASHED"crashed", mission->crashed); | ||
1790 | XML_AddString(missionNode, SAVE_MISSIONS_ONWIN"onWin", mission->onwin); | ||
1791 | XML_AddString(missionNode, SAVE_MISSIONS_ONLOSE"onLose", mission->onlose); | ||
1792 | XML_AddString(missionNode, SAVE_MISSIONS_CATEGORY"category", Com_GetConstVariable(SAVE_INTERESTCAT_NAMESPACE"saveInterestCat", mission->category)); | ||
1793 | XML_AddString(missionNode, SAVE_MISSIONS_STAGE"stage", Com_GetConstVariable(SAVE_MISSIONSTAGE_NAMESPACE"saveMissionStage", mission->stage)); | ||
1794 | switch (mission->category) { | ||
1795 | case INTERESTCATEGORY_BASE_ATTACK: | ||
1796 | if (mission->stage == STAGE_MISSION_GOTO || mission->stage == STAGE_BASE_ATTACK) { | ||
1797 | const base_t *base = mission->data.base; | ||
1798 | /* save IDX of base under attack if required */ | ||
1799 | XML_AddShort(missionNode, SAVE_MISSIONS_BASEINDEX"baseIDX", base->idx); | ||
1800 | } | ||
1801 | break; | ||
1802 | case INTERESTCATEGORY_INTERCEPT: | ||
1803 | if (mission->stage == STAGE_MISSION_GOTO || mission->stage == STAGE_INTERCEPT) { | ||
1804 | const installation_t *installation = mission->data.installation; | ||
1805 | if (installation) | ||
1806 | XML_AddShort(missionNode, SAVE_MISSIONS_INSTALLATIONINDEX"installationIDX", installation->idx); | ||
1807 | } | ||
1808 | break; | ||
1809 | case INTERESTCATEGORY_RESCUE: | ||
1810 | { | ||
1811 | const aircraft_t *aircraft = mission->data.aircraft; | ||
1812 | XML_AddShort(missionNode, SAVE_MISSIONS_CRASHED_AIRCRAFT"crashedAircraft", aircraft->idx); | ||
1813 | } | ||
1814 | break; | ||
1815 | case INTERESTCATEGORY_BUILDING: | ||
1816 | case INTERESTCATEGORY_SUPPLY: | ||
1817 | { | ||
1818 | /* save IDX of alien base if required */ | ||
1819 | const alienBase_t *base = mission->data.alienBase; | ||
1820 | /* there may be no base is the mission is a subverting government */ | ||
1821 | if (base) | ||
1822 | XML_AddShort(missionNode, SAVE_MISSIONS_ALIENBASEINDEX"alienbaseIDX", base->idx); | ||
1823 | } | ||
1824 | break; | ||
1825 | default: | ||
1826 | break; | ||
1827 | } | ||
1828 | XML_AddString(missionNode, SAVE_MISSIONS_LOCATION"location", mission->location); | ||
1829 | XML_AddShort(missionNode, SAVE_MISSIONS_INITIALOVERALLINTEREST"initialOverallInterest", mission->initialOverallInterest); | ||
1830 | XML_AddShort(missionNode, SAVE_MISSIONS_INITIALINDIVIDUALINTEREST"initialIndividualInterest", mission->initialIndividualInterest); | ||
1831 | XML_AddDate(missionNode, SAVE_MISSIONS_STARTDATE"startDate", mission->startDate.day, mission->startDate.sec); | ||
1832 | XML_AddDate(missionNode, SAVE_MISSIONS_FINALDATE"finalDate", mission->finalDate.day, mission->finalDate.sec); | ||
1833 | XML_AddPos2(missionNode, SAVE_MISSIONS_POS"pos", mission->pos); | ||
1834 | XML_AddBoolValue(missionNode, SAVE_MISSIONS_ONGEOSCAPE"onGeoscape", mission->onGeoscape); | ||
1835 | } | ||
1836 | Com_UnregisterConstList(saveInterestConstants); | ||
1837 | Com_UnregisterConstList(saveMissionConstants); | ||
1838 | |||
1839 | return true; | ||
1840 | } | ||
1841 | |||
1842 | /** | ||
1843 | * @brief Load callback for savegames in XML Format | ||
1844 | * @param[in] parent XML Node structure, where we get the information from | ||
1845 | */ | ||
1846 | bool MIS_LoadXML (xmlNode_tmxml_node_t *parent) | ||
1847 | { | ||
1848 | xmlNode_tmxml_node_t *missionNode; | ||
1849 | xmlNode_tmxml_node_t *node; | ||
1850 | bool success = true; | ||
1851 | |||
1852 | Com_RegisterConstList(saveInterestConstants); | ||
1853 | Com_RegisterConstList(saveMissionConstants); | ||
1854 | missionNode = XML_GetNode(parent, SAVE_MISSIONS"missions"); | ||
1855 | for (node = XML_GetNode(missionNode, SAVE_MISSIONS_MISSION"mission"); node; | ||
1856 | node = XML_GetNextNode(node, missionNode, SAVE_MISSIONS_MISSION"mission")) { | ||
1857 | const char *name; | ||
1858 | mission_t mission; | ||
1859 | bool defaultAssigned = false; | ||
1860 | const char *categoryId = XML_GetString(node, SAVE_MISSIONS_CATEGORY"category"); | ||
1861 | const char *stageId = XML_GetString(node, SAVE_MISSIONS_STAGE"stage"); | ||
1862 | |||
1863 | OBJZERO(mission)(memset(&((mission)), (0), sizeof((mission)))); | ||
1864 | |||
1865 | Q_strncpyz(mission.id, XML_GetString(node, SAVE_MISSIONS_ID), sizeof(mission.id))Q_strncpyzDebug( mission.id, XML_GetString(node, "id"), sizeof (mission.id), "src/client/cgame/campaign/cp_missions.cpp", 1865 ); | ||
1866 | mission.idx = XML_GetInt(node, SAVE_MISSIONS_MISSION_IDX"IDX", 0); | ||
1867 | if (mission.idx <= 0) { | ||
1868 | Com_Printf("mission has invalid or no index\n"); | ||
1869 | success = false; | ||
1870 | break; | ||
1871 | } | ||
1872 | |||
1873 | name = XML_GetString(node, SAVE_MISSIONS_MAPDEF_ID"mapDefId"); | ||
1874 | if (name && name[0] != '\0') { | ||
1875 | mission.mapDef = Com_GetMapDefinitionByID(name); | ||
1876 | if (!mission.mapDef) { | ||
1877 | Com_Printf("Warning: mapdef \"%s\" for mission \"%s\" doesn't exist. Removing mission!\n", name, mission.id); | ||
1878 | continue; | ||
1879 | } | ||
1880 | } else { | ||
1881 | mission.mapDef = NULL__null; | ||
1882 | } | ||
1883 | |||
1884 | if (!Com_GetConstIntFromNamespace(SAVE_INTERESTCAT_NAMESPACE"saveInterestCat", categoryId, (int*) &mission.category)) { | ||
1885 | Com_Printf("Invalid mission category '%s'\n", categoryId); | ||
1886 | success = false; | ||
1887 | break; | ||
1888 | } | ||
1889 | |||
1890 | if (!Com_GetConstIntFromNamespace(SAVE_MISSIONSTAGE_NAMESPACE"saveMissionStage", stageId, (int*) &mission.stage)) { | ||
1891 | Com_Printf("Invalid mission stage '%s'\n", stageId); | ||
1892 | success = false; | ||
1893 | break; | ||
1894 | } | ||
1895 | |||
1896 | mission.active = XML_GetBool(node, SAVE_MISSIONS_ACTIVE"active", false); | ||
1897 | Q_strncpyz(mission.onwin, XML_GetString(node, SAVE_MISSIONS_ONWIN), sizeof(mission.onwin))Q_strncpyzDebug( mission.onwin, XML_GetString(node, "onWin"), sizeof(mission.onwin), "src/client/cgame/campaign/cp_missions.cpp" , 1897 ); | ||
1898 | Q_strncpyz(mission.onlose, XML_GetString(node, SAVE_MISSIONS_ONLOSE), sizeof(mission.onlose))Q_strncpyzDebug( mission.onlose, XML_GetString(node, "onLose" ), sizeof(mission.onlose), "src/client/cgame/campaign/cp_missions.cpp" , 1898 ); | ||
1899 | |||
1900 | mission.initialOverallInterest = XML_GetInt(node, SAVE_MISSIONS_INITIALOVERALLINTEREST"initialOverallInterest", 0); | ||
1901 | mission.initialIndividualInterest = XML_GetInt(node, SAVE_MISSIONS_INITIALINDIVIDUALINTEREST"initialIndividualInterest", 0); | ||
1902 | |||
1903 | switch (mission.category) { | ||
1904 | case INTERESTCATEGORY_BASE_ATTACK: | ||
1905 | if (mission.stage == STAGE_MISSION_GOTO || mission.stage == STAGE_BASE_ATTACK) { | ||
1906 | /* Load IDX of base under attack */ | ||
1907 | const int baseidx = XML_GetInt(node, SAVE_MISSIONS_BASEINDEX"baseIDX", BYTES_NONE0xFF); | ||
1908 | if (baseidx != BYTES_NONE0xFF) { | ||
1909 | base_t *base = B_GetBaseByIDX(baseidx); | ||
1910 | assert(base)(__builtin_expect(!(base), 0) ? __assert_rtn(__func__, "src/client/cgame/campaign/cp_missions.cpp" , 1910, "base") : (void)0); | ||
1911 | if (mission.stage == STAGE_BASE_ATTACK && !B_IsUnderAttack(base)((base)->baseStatus == BASE_UNDER_ATTACK)) | ||
1912 | Com_Printf("......warning: base %i (%s) is supposedly under attack but base status doesn't match!\n", baseidx, base->name); | ||
1913 | mission.data.base = base; | ||
1914 | } else | ||
1915 | Com_Printf("......warning: Missing BaseIndex\n"); | ||
1916 | } | ||
1917 | break; | ||
1918 | case INTERESTCATEGORY_INTERCEPT: | ||
1919 | if (mission.stage == STAGE_MISSION_GOTO || mission.stage == STAGE_INTERCEPT) { | ||
1920 | const int installationIdx = XML_GetInt(node, SAVE_MISSIONS_INSTALLATIONINDEX"installationIDX", BYTES_NONE0xFF); | ||
1921 | if (installationIdx != BYTES_NONE0xFF) { | ||
1922 | installation_t *installation = INS_GetByIDX(installationIdx); | ||
1923 | if (installation) | ||
1924 | mission.data.installation = installation; | ||
1925 | else { | ||
1926 | Com_Printf("Mission on non-existent installation\n"); | ||
1927 | success = false; | ||
1928 | } | ||
1929 | } | ||
1930 | } | ||
1931 | break; | ||
1932 | case INTERESTCATEGORY_RESCUE: | ||
1933 | { | ||
1934 | const int aircraftIdx = XML_GetInt(node, SAVE_MISSIONS_CRASHED_AIRCRAFT"crashedAircraft", -1); | ||
1935 | mission.data.aircraft = AIR_AircraftGetFromIDX(aircraftIdx); | ||
1936 | if (mission.data.aircraft == NULL__null) { | ||
1937 | Com_Printf("Error while loading rescue mission (missionidx %i, aircraftidx: %i, category: %i, stage: %i)\n", | ||
1938 | mission.idx, aircraftIdx, mission.category, mission.stage); | ||
1939 | success = false; | ||
1940 | } | ||
1941 | } | ||
1942 | break; | ||
1943 | case INTERESTCATEGORY_BUILDING: | ||
1944 | case INTERESTCATEGORY_SUPPLY: | ||
1945 | { | ||
1946 | int baseIDX = XML_GetInt(node, SAVE_MISSIONS_ALIENBASEINDEX"alienbaseIDX", BYTES_NONE0xFF); | ||
1947 | if (baseIDX != BYTES_NONE0xFF) { | ||
1948 | alienBase_t *alienBase = AB_GetByIDX(baseIDX); | ||
1949 | mission.data.alienBase = alienBase; | ||
1950 | } | ||
1951 | if (!mission.data.alienBase && !CP_BasemissionIsSubvertingGovernmentMission(&mission) && mission.stage >= STAGE_BUILD_BASE) { | ||
1952 | Com_Printf("Error while loading Alien Base mission (missionidx %i, baseidx: %i, category: %i, stage: %i)\n", | ||
1953 | mission.idx, baseIDX, mission.category, mission.stage); | ||
1954 | success = false; | ||
1955 | } | ||
1956 | } | ||
1957 | break; | ||
1958 | default: | ||
1959 | break; | ||
1960 | } | ||
1961 | if (!success) | ||
1962 | break; | ||
1963 | |||
1964 | Q_strncpyz(mission.location, XML_GetString(node, SAVE_MISSIONS_LOCATION), sizeof(mission.location))Q_strncpyzDebug( mission.location, XML_GetString(node, "location" ), sizeof(mission.location), "src/client/cgame/campaign/cp_missions.cpp" , 1964 ); | ||
1965 | |||
1966 | XML_GetDate(node, SAVE_MISSIONS_STARTDATE"startDate", &mission.startDate.day, &mission.startDate.sec); | ||
1967 | XML_GetDate(node, SAVE_MISSIONS_FINALDATE"finalDate", &mission.finalDate.day, &mission.finalDate.sec); | ||
1968 | XML_GetPos2(node, SAVE_MISSIONS_POS"pos", mission.pos); | ||
1969 | |||
1970 | mission.crashed = XML_GetBool(node, SAVE_MISSIONS_CRASHED"crashed", false); | ||
1971 | mission.onGeoscape = XML_GetBool(node, SAVE_MISSIONS_ONGEOSCAPE"onGeoscape", false); | ||
1972 | |||
1973 | if (mission.pos[0] > 0.001 || mission.pos[1] > 0.001) | ||
1974 | defaultAssigned = true; | ||
1975 | mission.posAssigned = XML_GetBool(node, SAVE_MISSIONS_POSASSIGNED"posAssigned", defaultAssigned); | ||
1976 | /* Add mission to global array */ | ||
1977 | LIST_Add(&ccs.missions, mission); | ||
1978 | } | ||
1979 | Com_UnregisterConstList(saveInterestConstants); | ||
1980 | Com_UnregisterConstList(saveMissionConstants); | ||
1981 | |||
1982 | return success; | ||
1983 | } | ||
1984 | |||
1985 | /** | ||
1986 | * @brief Init actions for missions-subsystem | ||
1987 | * @sa UI_InitStartup | ||
1988 | */ | ||
1989 | void MIS_InitStartup (void) | ||
1990 | { | ||
1991 | MIS_InitCallbacks(); | ||
1992 | #ifdef DEBUG1 | ||
1993 | Cmd_AddCommand("debug_missionsetmap", MIS_MissionSetMap_f, "Changes the map for a spawned mission"); | ||
1994 | Cmd_AddCommand("debug_missionadd", MIS_SpawnNewMissions_f, "Add a new mission"); | ||
1995 | Cmd_AddCommand("debug_missiondeleteall", MIS_DeleteMissions_f, "Remove all missions from global array"); | ||
1996 | Cmd_AddCommand("debug_missionlist", MIS_MissionList_f, "Debug function to show all missions"); | ||
1997 | #endif | ||
1998 | } | ||
1999 | |||
2000 | /** | ||
2001 | * @brief Closing actions for missions-subsystem | ||
2002 | */ | ||
2003 | void MIS_Shutdown (void) | ||
2004 | { | ||
2005 | LIST_Delete(&ccs.missions); | ||
2006 | |||
2007 | MIS_ShutdownCallbacks(); | ||
2008 | #ifdef DEBUG1 | ||
2009 | Cmd_RemoveCommand("debug_missionsetmap"); | ||
2010 | Cmd_RemoveCommand("debug_missionadd"); | ||
2011 | Cmd_RemoveCommand("debug_missiondeleteall"); | ||
2012 | Cmd_RemoveCommand("debug_missionlist"); | ||
2013 | #endif | ||
2014 | } |