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