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