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