Bug Summary

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')

Annotated Source Code

1/**
2 * @file
3 * @brief Campaign missions code
4 */
5
6/*
7Copyright (C) 2002-2011 UFO: Alien Invasion.
8
9This program is free software; you can redistribute it and/or
10modify it under the terms of the GNU General Public License
11as published by the Free Software Foundation; either version 2
12of the License, or (at your option) any later version.
13
14This program is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
18See the GNU General Public License for more details.
19
20You should have received a copy of the GNU General Public License
21along with this program; if not, write to the Free Software
22Foundation, 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) */
43const int MAX_POS_LOOP = 10;
44
45/** Condition limits for crashed UFOs - used for disassemlies */
46static const float MIN_CRASHEDUFO_CONDITION = 0.2f;
47static 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 */
60void 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 */
91void 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 */
143static 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 */
160static 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 */
203static 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 */
223static 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 */
263static 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 */
286static 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 */
315void 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 */
384mission_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 */
402mission_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 */
417mission_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 */
430int MAP_GetIDXByMission (const mission_t *mis)
431{
432 return mis->idx;
433}
434
435/**
436 * @brief Return the type of mission.
437 */
438const 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 */
465static 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 */
511int 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 */
534const 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 */
581static 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 */
625void 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 */
642static 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 */
663static 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 */
679void 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 */
708bool 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 */
749void 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 */
770void 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 */
813void 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 */
836void 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 */
847bool 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 */
862void 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 */
879void 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 */
899void 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 */
953void 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 */
1014void 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 */
1025static 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 */
1038void 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 */
1073void 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 */
1140bool 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 */
1166void 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 */
1192void 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 */
1241void 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 */
1334bool 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 */
1368ufoType_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 */
1449static 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 */
1478mission_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 */
1522static 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 */
1543void 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 */
1586void 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 */
1604static 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 */
1708static void MIS_MissionSetMap_f (void)
1709{
1710 mapDef_t *mapDef;
1711 mission_t *mission;
1712 if (Cmd_Argc() < 3) {
1
Taking false branch
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) {
2
Taking false branch
1719 Com_Printf("Could not find mapdef for %s\n", Cmd_Argv(2));
1720 return;
1721 }
1722 mission->mapDef = mapDef;
3
Access to field 'mapDef' results in a dereference of a null pointer (loaded from variable 'mission')
1723}
1724
1725/**
1726 * @brief List all current mission to console.
1727 * @note Use with debug_missionlist
1728 */
1729static 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 */
1756static 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 */
1774bool 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 */
1846bool 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 */
1989void 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 */
2003void 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}