1 /**
2  * @file
3  * @brief Manage popups
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 "cp_campaign.h"
27 #include "cp_mapfightequip.h"
28 #include "cp_geoscape.h"
29 #include "cp_popup.h"
30 #include "cp_missions.h"
31 #include "cp_time.h"
32 #include "cp_aircraft_callbacks.h"
33 #include "../../ui/ui_dataids.h"
34 
35 /* popup_aircraft display the actions availables for an aircraft */
36 
37 /** Max items displayed in popup_aircraft */
38 #define POPUP_AIRCRAFT_MAX_ITEMS	10
39 /** Max size of text displayed in popup_aircraft */
40 #define POPUP_AIRCRAFT_MAX_TEXT		2048
41 
42 /**
43  * @brief Enumerate type of actions available for popup_aircraft
44  */
45 typedef enum {
46 	POPUP_AIRCRAFT_ACTION_BACKTOBASE = 1,	/**< Aircraft back to base */
47 	POPUP_AIRCRAFT_ACTION_STOP = 2,			/**< Aircraft stops */
48 	POPUP_AIRCRAFT_ACTION_MOVETOMISSION = 3,/**< Aircraft move to a mission */
49 	POPUP_AIRCRAFT_CHANGE_HOMEBASE = 4,		/**< Change aircraft homebase */
50 	POPUP_AIRCRAFT_ACTION_NONE,				/**< Do nothing */
51 
52 	POPUP_AIRCRAFT_ACTION_MAX
53 } popup_aircraft_action_e;
54 
55 /**
56  * @brief Structure to store information about popup_aircraft
57  */
58 typedef struct popup_aircraft_s {
59 	int numItems;			/**< Count of items displayed in popup_aircraft */
60 	aircraft_t* aircraft;		/**< Aircraft linked to popup_aircraft */
61 	popup_aircraft_action_e itemsAction[POPUP_AIRCRAFT_MAX_ITEMS];	/**< Action type of items */
62 	int itemsId[POPUP_AIRCRAFT_MAX_ITEMS];		/**< IDs corresponding to items */
63 	char textPopup[POPUP_AIRCRAFT_MAX_TEXT];	/**< Text displayed in popup_aircraft */
64 } popup_aircraft_t;
65 
66 /** @todo Save me
67  * why? The popup stuff should be regenerated from the campaign data --mattn */
68 static popup_aircraft_t popupAircraft; /**< Data about popup_aircraft */
69 
70 /* popup_intercept display list of aircraft availables to move to a mission or a UFO */
71 
72 /** Max aircraft in popup list */
73 #define POPUP_INTERCEPT_MAX_AIRCRAFT 64
74 
75 typedef struct popup_intercept_s {
76 	int numAircraft;	/**< Count of aircraft displayed in list */
77 	aircraft_t* aircraft[POPUP_INTERCEPT_MAX_AIRCRAFT];	/**< List of aircrafts. */
78 	mission_t* mission;	/**< Mission the selected aircraft have to move to */
79 	aircraft_t* ufo;		/**< UFO the selected aircraft have to move to */
80 } popup_intercept_t;
81 
82 static popup_intercept_t popupIntercept;	/**< Data about popup_intercept */
83 
84 /** Reservation-popup info */
85 static int popupNum;							/**< Number of entries in the popup list */
86 static linkedList_t* popupListData = nullptr;		/**< Further datas needed when popup is clicked */
87 static uiNode_t* popupListNode = nullptr;		/**< Node used for popup */
88 
89 static int INVALID_BASE = -1;
90 
91 /*========================================
92 POPUP_HOMEBASE
93 ========================================*/
94 
95 /**
96  * @brief Display the popup_homebase
97  * @param[in] aircraft Pointer to aircraft we want to change homebase.
98  * @param[in] alwaysDisplay False if popup should be displayed only if at least one base is available.
99  * @return true if popup is displayed.
100  */
CL_DisplayHomebasePopup(aircraft_t * aircraft,bool alwaysDisplay)101 bool CL_DisplayHomebasePopup (aircraft_t* aircraft, bool alwaysDisplay)
102 {
103 	int homebase;
104 	int numAvailableBases = 0;
105 	linkedList_t* popupListText = nullptr;
106 	base_t* base;
107 
108 	assert(aircraft);
109 
110 	cgi->LIST_Delete(&popupListData);
111 
112 	popupNum = 0;
113 	homebase = -1;
114 
115 	base = nullptr;
116 	while ((base = B_GetNext(base)) != nullptr) {
117 		char text[MAX_VAR];
118 		char const* msg;
119 
120 		if (base == aircraft->homebase) {
121 			msg = _("current homebase of aircraft");
122 			LIST_Add(&popupListData, INVALID_BASE);
123 			homebase = popupNum;
124 		} else {
125 			msg = AIR_CheckMoveIntoNewHomebase(aircraft, base);
126 			if (!msg) {
127 				msg = _("base can hold aircraft");
128 				LIST_Add(&popupListData, base->idx);
129 				numAvailableBases++;
130 			} else {
131 				LIST_Add(&popupListData, INVALID_BASE);
132 			}
133 		}
134 
135 		Com_sprintf(text, sizeof(text), "%s\t%s", base->name, msg);
136 		cgi->LIST_AddString(&popupListText, text);
137 		popupNum++;
138 	}
139 
140 	if (alwaysDisplay || numAvailableBases > 0) {
141 		CP_GameTimeStop();
142 		popupListNode = cgi->UI_PopupList(_("Change homebase of aircraft"), _("Base\tStatus"), popupListText, "change_homebase <lineselected>;");
143 		VectorSet(popupListNode->selectedColor, 0.0, 0.78, 0.0);	/**< Set color for selected entry. */
144 		popupListNode->selectedColor[3] = 1.0;
145 		cgi->UI_TextNodeSelectLine(popupListNode, homebase);
146 		GEO_SelectAircraft(aircraft);
147 		return true;
148 	}
149 
150 	return false;
151 }
152 
153 /**
154  * @brief User select a base in the popup_homebase
155  * change homebase to selected base.
156  */
CL_PopupChangeHomebase_f(void)157 static void CL_PopupChangeHomebase_f (void)
158 {
159 	linkedList_t* data = popupListData;	/**< Use this so we do not change the original popupListData pointer. */
160 	int selectedPopupIndex;
161 	int i;
162 	base_t* base;
163 	int baseIdx;
164 	aircraft_t* aircraft = GEO_GetSelectedAircraft();
165 
166 	/* If popup is opened, that means an aircraft is selected */
167 	if (!aircraft) {
168 		Com_Printf("CL_PopupChangeHomebase_f: An aircraft must be selected\n");
169 		return;
170 	}
171 
172 	if (cgi->Cmd_Argc() < 2) {
173 		Com_Printf("Usage: %s <popupIndex>\tpopupIndex=num in base list\n", cgi->Cmd_Argv(0));
174 		return;
175 	}
176 
177 	/* read and range check */
178 	selectedPopupIndex = atoi(cgi->Cmd_Argv(1));
179 	Com_DPrintf(DEBUG_CLIENT, "CL_PopupHomebaseClick_f (popupNum %i, selectedPopupIndex %i)\n", popupNum, selectedPopupIndex);
180 	if (selectedPopupIndex < 0 || selectedPopupIndex >= popupNum)
181 		return;
182 
183 	/* Convert list index to base idx */
184 	baseIdx = INVALID_BASE;
185 	for (i = 0; data; data = data->next, i++) {
186 		if (i == selectedPopupIndex) {
187 			baseIdx = *(int*)data->data;
188 			break;
189 		}
190 	}
191 
192 	base = B_GetFoundedBaseByIDX(baseIdx);
193 	if (base == nullptr)
194 		return;
195 
196 	if (!AIR_CheckMoveIntoNewHomebase(aircraft, base))
197 		AIR_MoveAircraftIntoNewHomebase(aircraft, base);
198 
199 	cgi->UI_PopWindow(false);
200 	CL_DisplayHomebasePopup(aircraft, true);
201 }
202 
203 /*========================================
204 POPUP_AIRCRAFT
205 ========================================*/
206 
207 /**
208  * @brief Display the popup_aircraft
209  * @sa CL_DisplayPopupIntercept
210  */
CL_DisplayPopupAircraft(aircraft_t * aircraft)211 void CL_DisplayPopupAircraft (aircraft_t* aircraft)
212 {
213 	/* Initialise popup_aircraft datas */
214 	if (!aircraft)
215 		return;
216 	popupAircraft.aircraft = aircraft;
217 	popupAircraft.numItems = 0;
218 	OBJZERO(popupAircraft.textPopup);
219 	cgi->UI_RegisterText(TEXT_POPUP, popupAircraft.textPopup);
220 
221 	/* Set static datas in popup_aircraft */
222 	popupAircraft.itemsAction[popupAircraft.numItems++] = POPUP_AIRCRAFT_ACTION_BACKTOBASE;
223 	Q_strcat(popupAircraft.textPopup, lengthof(popupAircraft.textPopup), _("Back to base\t%s\n"), aircraft->homebase->name);
224 	popupAircraft.itemsAction[popupAircraft.numItems++] = POPUP_AIRCRAFT_ACTION_STOP;
225 	Q_strcat(popupAircraft.textPopup, lengthof(popupAircraft.textPopup), _("Stop\n"));
226 	popupAircraft.itemsAction[popupAircraft.numItems++] = POPUP_AIRCRAFT_CHANGE_HOMEBASE;
227 	Q_strcat(popupAircraft.textPopup, lengthof(popupAircraft.textPopup), _("Change homebase\n"));
228 
229 	/* Set missions in popup_aircraft */
230 	if (AIR_GetTeamSize(aircraft) > 0) {
231 		MIS_Foreach(tempMission) {
232 			if (tempMission->stage == STAGE_NOT_ACTIVE || !tempMission->onGeoscape)
233 				continue;
234 
235 			if (tempMission->pos) {
236 				popupAircraft.itemsId[popupAircraft.numItems] = MIS_GetIdx(tempMission);
237 				popupAircraft.itemsAction[popupAircraft.numItems++] = POPUP_AIRCRAFT_ACTION_MOVETOMISSION;
238 				Q_strcat(popupAircraft.textPopup, lengthof(popupAircraft.textPopup), _("Mission\t%s\n"), MIS_GetName(tempMission));
239 			}
240 		}
241 	}
242 
243 	/* Display popup_aircraft menu */
244 	cgi->UI_PushWindow("popup_aircraft");
245 }
246 
247 /**
248  * @brief User just select an item in the popup_aircraft
249  */
CL_PopupAircraftClick_f(void)250 static void CL_PopupAircraftClick_f (void)
251 {
252 	int num;
253 	aircraft_t* aircraft;
254 	mission_t* mission;
255 
256 	Com_DPrintf(DEBUG_CLIENT, "CL_PopupAircraftClick\n");
257 
258 	/* Get num of item selected in list */
259 	if (cgi->Cmd_Argc() < 2)
260 		return;
261 
262 	num = atoi(cgi->Cmd_Argv(1));
263 	if (num < 0 || num >= popupAircraft.numItems)
264 		return;
265 
266 	cgi->UI_PopWindow(false); /* Close popup */
267 
268 	/* Get aircraft associated with the popup_aircraft */
269 	aircraft = popupAircraft.aircraft;
270 	if (aircraft == nullptr)
271 		return;
272 
273 	/* Execute action corresponding to item selected */
274 	switch (popupAircraft.itemsAction[num]) {
275 	case POPUP_AIRCRAFT_ACTION_BACKTOBASE:	/* Aircraft back to base */
276 		AIR_AircraftReturnToBase(aircraft);
277 		break;
278 	case POPUP_AIRCRAFT_ACTION_STOP:		/* Aircraft stop */
279 		aircraft->status = AIR_IDLE;
280 		break;
281 	case POPUP_AIRCRAFT_CHANGE_HOMEBASE:		/* Change Aircraft homebase */
282 		CL_DisplayHomebasePopup(aircraft, true);
283 		break;
284 	case POPUP_AIRCRAFT_ACTION_MOVETOMISSION:	/* Aircraft move to mission */
285 		mission = MIS_GetByIdx(popupAircraft.itemsId[num]);
286 		if (mission)
287 			AIR_SendAircraftToMission(aircraft, mission);
288 		break;
289 	case POPUP_AIRCRAFT_ACTION_NONE:
290 		break;
291 	default:
292 		Com_Printf("CL_PopupAircraftClick: type of action unknow %i\n", popupAircraft.itemsAction[num]);
293 		break;
294 	}
295 }
296 
297 /*========================================
298 POPUP_INTERCEPT
299 ========================================*/
300 
AIR_SortByDistance(linkedList_t * aircraftEntry1,linkedList_t * aircraftEntry2,const void * userData)301 static int AIR_SortByDistance (linkedList_t* aircraftEntry1, linkedList_t* aircraftEntry2, const void* userData)
302 {
303 	const vec_t* pos = (const vec_t*)userData;
304 	const aircraft_t* aircraft1 = (const aircraft_t*)aircraftEntry1->data;
305 	const aircraft_t* aircraft2 = (const aircraft_t*)aircraftEntry2->data;
306 
307 	return GetDistanceOnGlobe(aircraft1->pos, pos) - GetDistanceOnGlobe(aircraft2->pos, pos);
308 }
309 
310 /**
311  * @brief Display the popup_mission
312  * @sa CL_DisplayPopupAircraft
313  */
CL_DisplayPopupInterceptMission(mission_t * mission)314 void CL_DisplayPopupInterceptMission (mission_t* mission)
315 {
316 	linkedList_t* aircraftList = nullptr;
317 	linkedList_t* aircraftListSorted;
318 
319 	if (!mission)
320 		return;
321 
322 	popupIntercept.mission = mission;
323 	popupIntercept.ufo = nullptr;
324 
325 	/* Create the list of aircraft, and write the text to display in popup */
326 	popupIntercept.numAircraft = 0;
327 
328 	AIR_ForeachSorted(aircraft, AIR_SortByDistance, mission->pos, aircraftListSorted) {
329 		const int teamSize = AIR_GetTeamSize(aircraft);
330 
331 		if (aircraft->status == AIR_CRASHED)
332 			continue;
333 		/* if aircraft is empty we can't send it on a ground mission */
334 		if (teamSize > 0 && AIR_CanIntercept(aircraft)) {
335 			char aircraftListText[256] = "";
336 			const float distance = GetDistanceOnGlobe(aircraft->pos, mission->pos);
337 			const char* statusName = AIR_AircraftStatusToName(aircraft);
338 			const char* time = CP_SecondConvert((float)SECONDS_PER_HOUR * distance / aircraft->stats[AIR_STATS_SPEED]);
339 			Com_sprintf(aircraftListText, sizeof(aircraftListText), _("%s (%i/%i)\t%s\t%s\t%s"), aircraft->name,
340 					teamSize, aircraft->maxTeamSize, statusName, aircraft->homebase->name, time);
341 			cgi->LIST_AddString(&aircraftList, aircraftListText);
342 			popupIntercept.aircraft[popupIntercept.numAircraft] = aircraft;
343 			popupIntercept.numAircraft++;
344 			if (popupIntercept.numAircraft >= POPUP_INTERCEPT_MAX_AIRCRAFT)
345 				break;
346 		}
347 	}
348 	cgi->LIST_Delete(&aircraftListSorted);
349 
350 	if (popupIntercept.numAircraft)
351 		cgi->UI_RegisterLinkedListText(TEXT_AIRCRAFT_LIST, aircraftList);
352 	else
353 		cgi->UI_RegisterText(TEXT_AIRCRAFT_LIST, _("No craft available, no pilot assigned, or no tactical teams assigned to available craft."));
354 
355 	/* Stop time */
356 	CP_GameTimeStop();
357 
358 	/* Display the popup */
359 	cgi->UI_PushWindow("popup_mission");
360 }
361 
362 
363 /**
364  * @brief Display the popup_intercept
365  * @sa CL_DisplayPopupAircraft
366  */
CL_DisplayPopupInterceptUFO(aircraft_t * ufo)367 void CL_DisplayPopupInterceptUFO (aircraft_t* ufo)
368 {
369 	linkedList_t* aircraftList = nullptr;
370 	linkedList_t* aircraftListSorted;
371 	linkedList_t* baseList = nullptr;
372 	base_t* base;
373 
374 	if (!ufo)
375 		return;
376 
377 	popupIntercept.mission = nullptr;
378 	popupIntercept.ufo = ufo;
379 
380 	/* Create the list of aircraft, and write the text to display in popup */
381 	popupIntercept.numAircraft = 0;
382 
383 	AIR_ForeachSorted(aircraft, AIR_SortByDistance, ufo->pos, aircraftListSorted) {
384 		if (AIR_CanIntercept(aircraft)) {
385 			char aircraftListText[256] = "";
386 			/* don't show aircraft with no weapons or no ammo, or crafts that
387 			 * can't even reach the target */
388 			const char* enoughFuelMarker = "^B";
389 
390 			/* Does the aircraft has weapons and ammo ? */
391 			if (AIRFIGHT_ChooseWeapon(aircraft->weapons, aircraft->maxWeapons, aircraft->pos, aircraft->pos) == AIRFIGHT_WEAPON_CAN_NEVER_SHOOT) {
392 				Com_DPrintf(DEBUG_CLIENT, "CL_DisplayPopupIntercept: No useable weapon found in craft '%s' (%i)\n", aircraft->id, aircraft->maxWeapons);
393 				continue;
394 			}
395 			/* now check the aircraft range */
396 			if (!AIR_AircraftHasEnoughFuel(aircraft, ufo->pos)) {
397 				Com_DPrintf(DEBUG_CLIENT, "CL_DisplayPopupIntercept: Target out of reach for craft '%s'\n", aircraft->id);
398 				enoughFuelMarker = "";
399 			}
400 
401 			Com_sprintf(aircraftListText, sizeof(aircraftListText), _("%s%s (%i/%i)\t%s\t%s"), enoughFuelMarker, aircraft->name,
402 				AIR_GetTeamSize(aircraft), aircraft->maxTeamSize, AIR_AircraftStatusToName(aircraft), aircraft->homebase->name);
403 			cgi->LIST_AddString(&aircraftList, aircraftListText);
404 			popupIntercept.aircraft[popupIntercept.numAircraft] = aircraft;
405 			popupIntercept.numAircraft++;
406 			if (popupIntercept.numAircraft >= POPUP_INTERCEPT_MAX_AIRCRAFT)
407 				break;
408 		}
409 	}
410 	cgi->LIST_Delete(&aircraftListSorted);
411 
412 	base = nullptr;
413 	while ((base = B_GetNext(base)) != nullptr) {
414 		/* Check if the base should be displayed in base list
415 		 * don't check range because maybe UFO will get closer */
416 		if (AII_BaseCanShoot(base))
417 			cgi->LIST_AddString(&baseList, va("^B%s", base->name));
418 	}	/* bases */
419 
420 	if (popupIntercept.numAircraft)
421 		cgi->UI_RegisterLinkedListText(TEXT_AIRCRAFT_LIST, aircraftList);
422 	else
423 		cgi->UI_RegisterText(TEXT_AIRCRAFT_LIST, _("No craft available, no pilot assigned, or no weapon or ammo equipped."));
424 
425 	INS_Foreach(installation) {
426 		/* Check if the installation should be displayed in base list
427 		 * don't check range because maybe UFO will get closer */
428 		if (AII_InstallationCanShoot(installation))
429 			cgi->LIST_AddString(&baseList, va("^B%s", installation->name));
430 	}
431 
432 	if (baseList)
433 		cgi->UI_RegisterLinkedListText(TEXT_BASE_LIST, baseList);
434 	else
435 		cgi->UI_RegisterText(TEXT_BASE_LIST, _("No defence system operational or no weapon or ammo equipped."));
436 
437 	/* Stop time */
438 	CP_GameTimeStop();
439 
440 	/* Display the popup */
441 	cgi->UI_PushWindow("popup_intercept");
442 }
443 
444 /**
445  * @brief return the selected aircraft in popup_intercept
446  * Close the popup if required
447  */
CL_PopupInterceptGetAircraft(void)448 static aircraft_t* CL_PopupInterceptGetAircraft (void)
449 {
450 	int num;
451 
452 	if (cgi->Cmd_Argc() < 2)
453 		return nullptr;
454 
455 	/* Get the selected aircraft */
456 	num = atoi(cgi->Cmd_Argv(1));
457 	if (num < 0 || num >= popupIntercept.numAircraft)
458 		return nullptr;
459 
460 	cgi->UI_PopWindow(false);
461 	if (!popupIntercept.aircraft[num])
462 		return nullptr;
463 	return popupIntercept.aircraft[num];
464 }
465 
466 /**
467  * @brief User select an item in the popup_aircraft
468  * Make the aircraft attack the corresponding mission or UFO
469  */
CL_PopupInterceptClick_f(void)470 static void CL_PopupInterceptClick_f (void)
471 {
472 	aircraft_t* aircraft;
473 	base_t* base;
474 
475 	/* Get the selected aircraft */
476 	aircraft = CL_PopupInterceptGetAircraft();
477 	if (aircraft == nullptr)
478 		return;
479 
480 	/* Aircraft can start if only Command Centre in base is operational. */
481 	base = aircraft->homebase;
482 	if (!B_GetBuildingStatus(base, B_COMMAND)) {
483 		/** @todo are these newlines really needed? at least the first should be handled by the menu code */
484 		CP_Popup(_("Notice"), _("No Command Centre operational in homebase\nof this aircraft.\n\nAircraft cannot start.\n"));
485 		return;
486 	}
487 
488 	/* Set action to aircraft */
489 	if (popupIntercept.mission)
490 		AIR_SendAircraftToMission(aircraft, popupIntercept.mission);	/* Aircraft move to mission */
491 	else if (popupIntercept.ufo)
492 		AIR_SendAircraftPursuingUFO(aircraft, popupIntercept.ufo);	/* Aircraft purchase ufo */
493 }
494 
495 /**
496  * @brief User select an item in the popup_aircraft with right click
497  * Opens up the aircraft menu
498  */
CL_PopupInterceptRClick_f(void)499 static void CL_PopupInterceptRClick_f (void)
500 {
501 	aircraft_t* aircraft;
502 
503 	/* Get the selected aircraft */
504 	aircraft = CL_PopupInterceptGetAircraft();
505 	if (aircraft == nullptr)
506 		return;
507 
508 	/* Display aircraft menu */
509 	AIR_AircraftSelect(aircraft);
510 	GEO_ResetAction();
511 	B_SelectBase(aircraft->homebase);
512 	cgi->UI_PushWindow("aircraft");
513 }
514 
515 /**
516  * @brief User select a base in the popup_aircraft
517  * Make the base attack the corresponding UFO
518  */
CL_PopupInterceptBaseClick_f(void)519 static void CL_PopupInterceptBaseClick_f (void)
520 {
521 	int num, i;
522 	base_t* base;
523 	installation_t* installation;
524 	bool atLeastOneBase = false;
525 
526 	if (cgi->Cmd_Argc() < 2) {
527 		Com_Printf("Usage: %s <num>\tnum=num in base list\n", cgi->Cmd_Argv(0));
528 		return;
529 	}
530 
531 	/* If popup is opened, that means that ufo is selected on geoscape */
532 	if (GEO_GetSelectedUFO() == nullptr)
533 		return;
534 
535 	num = atoi(cgi->Cmd_Argv(1));
536 
537 	base = nullptr;
538 	while ((base = B_GetNext(base)) != nullptr) {
539 		/* Check if the base should be displayed in base list */
540 		if (AII_BaseCanShoot(base)) {
541 			num--;
542 			atLeastOneBase = true;
543 			if (num < 0)
544 				break;
545 		}
546 	}
547 
548 	installation = nullptr;
549 	if (num >= 0) { /* don't try to find an installation if we already found the right base */
550 		INS_Foreach(inst) {
551 			/* Check if the installation should be displayed in base list */
552 			if (AII_InstallationCanShoot(inst)) {
553 				num--;
554 				atLeastOneBase = true;
555 				if (num < 0) {
556 					installation = inst;
557 					break;
558 				}
559 			}
560 		}
561 	}
562 
563 	if (!atLeastOneBase && !num) {
564 		/* no base in list: no error message
565 		 * note that num should always be 0 if we enter this loop, unless this function is called from console
566 		 * so 2nd part of the test should be useless in most case */
567 		return;
568 	} else if (num >= 0) {
569 		Com_Printf("CL_PopupInterceptBaseClick_f: Number given in argument (%i) is bigger than number of base in list.\n", num);
570 		return;
571 	}
572 
573 	assert(base || installation);
574 	if (installation) {
575 		for (i = 0; i < installation->installationTemplate->maxBatteries; i++)
576 			installation->batteries[i].target = GEO_GetSelectedUFO();
577 	} else {
578 		for (i = 0; i < base->numBatteries; i++)
579 			base->batteries[i].target = GEO_GetSelectedUFO();
580 		for (i = 0; i < base->numLasers; i++)
581 			base->lasers[i].target = GEO_GetSelectedUFO();
582 	}
583 
584 	cgi->UI_PopWindow(false);
585 }
586 
587 /**
588  * @brief Initialise popups
589  */
CL_PopupInit(void)590 void CL_PopupInit (void)
591 {
592 	/* popup_aircraft commands */
593 	cgi->Cmd_AddCommand("popup_aircraft_action_click", CL_PopupAircraftClick_f, nullptr);
594 
595 	/* popup_intercept commands */
596 	cgi->Cmd_AddCommand("ships_click", CL_PopupInterceptClick_f, nullptr);
597 	cgi->Cmd_AddCommand("ships_rclick", CL_PopupInterceptRClick_f, nullptr);
598 	cgi->Cmd_AddCommand("bases_click", CL_PopupInterceptBaseClick_f, nullptr);
599 
600 	/* popup_homebase commands */
601 	cgi->Cmd_AddCommand("change_homebase", CL_PopupChangeHomebase_f, nullptr);
602 
603 	OBJZERO(popupIntercept);
604 	OBJZERO(popupAircraft);
605 }
606 
607 /**
608  * @brief Wrapper around @c UI_Popup which also stops the time
609  */
CP_PopupList(const char * title,const char * text)610 void CP_PopupList (const char* title, const char* text)
611 {
612 	CP_GameTimeStop();
613 	CP_Popup(title, "%s", text);
614 }
615 
616 /**
617  * @brief Wrapper around @c UI_Popup
618  */
CP_Popup(const char * title,const char * text,...)619 void CP_Popup (const char* title, const char* text, ...)
620 {
621 	static char msg[1024];
622 	va_list argptr;
623 
624 	va_start(argptr, text);
625 	Q_vsnprintf(msg, sizeof(msg), text, argptr);
626 	va_end(argptr);
627 
628 	cgi->UI_Popup(title, msg);
629 }
630