1 /**
2  * @file
3  * @brief Menu related console command callbacks
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_base_callbacks.h"
28 #include "cp_base.h"
29 #include "cp_capacity.h"
30 #include "cp_geoscape.h"
31 #include "cp_popup.h"
32 #include "cp_time.h"
33 #include "cp_ufo.h"
34 #include "../../ui/ui_dataids.h"
35 #include "aliencontainment.h"
36 
37 /** @brief Used from menu scripts as parameter for mn_base_select */
38 #define CREATE_NEW_BASE_ID -1
39 
40 static cvar_t* mn_base_title;
41 static cvar_t* cl_start_buildings;
42 static building_t* buildingConstructionList[MAX_BUILDINGS];
43 static int buildingNumber = 0;
44 
45 /**
46  * @brief onDestroy Callback for Antimatter Storage
47  */
B_Destroy_AntimaterStorage_f(void)48 static void B_Destroy_AntimaterStorage_f (void)
49 {
50 	base_t* base;
51 	const float prob = frand();
52 
53 	if (cgi->Cmd_Argc() < 4) {	/** note: third parameter not used but we must be sure we have probability parameter */
54 		Com_Printf("Usage: %s <probability> <baseID> <buildingType>\n", cgi->Cmd_Argv(0));
55 		return;
56 	}
57 
58 	base = B_GetFoundedBaseByIDX(atoi(cgi->Cmd_Argv(2)));
59 	if (!base)
60 		return;
61 	if (CAP_GetCurrent(base, CAP_ANTIMATTER) <= 0)
62 		return;
63 
64 	CAP_RemoveAntimatterExceedingCapacity(base);
65 
66 	if (base->baseStatus != BASE_WORKING)
67 		return;
68 
69 	if (prob < atof(cgi->Cmd_Argv(1))) {
70 		MS_AddNewMessage(_("Notice"), va(_("%s has been destroyed by an antimatter storage breach."), base->name));
71 		cgi->UI_PopWindow(false);
72 		B_Destroy(base);
73 	}
74 }
75 
76 /**
77  * @brief Handles the list of constructable buildings.
78  * @param[in] buildingList list of buildings to upate
79  * @param[in] building Add this building to the construction list
80  * @note Called everytime a building was constructed and thus maybe other buildings
81  * get available. The content is updated everytime B_BuildingInit is called
82  * (i.e everytime the buildings-list is displayed/updated)
83  */
B_BuildingAddToList(linkedList_t ** buildingList,building_t * building)84 static void B_BuildingAddToList (linkedList_t** buildingList, building_t* building)
85 {
86 	int count;
87 	assert(building);
88 	assert(building->name);
89 
90 	count = cgi->LIST_Count(*buildingList);
91 	cgi->LIST_AddPointer(buildingList, _(building->name));
92 	buildingConstructionList[count] = building->tpl;
93 }
94 
95 /**
96  * @brief Called when a base is opened or a new base is created on geoscape.
97  * For a new base the baseID is -1.
98  */
B_SelectBase_f(void)99 static void B_SelectBase_f (void)
100 {
101 	int baseID;
102 
103 	if (cgi->Cmd_Argc() < 2) {
104 		Com_Printf("Usage: %s <baseID>\n", cgi->Cmd_Argv(0));
105 		return;
106 	}
107 	baseID = atoi(cgi->Cmd_Argv(1));
108 	/* check against MAX_BASES here! - only -1 will create a new base
109 	 * if we would check against ccs.numBases here, a click on the base summary
110 	 * base nodes would try to select unfounded bases */
111 	if (baseID >= 0 && baseID < MAX_BASES) {
112 		const base_t* base = B_GetFoundedBaseByIDX(baseID);
113 		/* don't create a new base if the index was valid */
114 		if (base)
115 			B_SelectBase(base);
116 	} else if (baseID == CREATE_NEW_BASE_ID) {
117 		/* create a new base */
118 		B_SelectBase(nullptr);
119 	}
120 }
121 
122 /**
123  * @brief Cycles to the next base.
124  * @sa B_PrevBase
125  * @sa B_SelectBase_f
126  */
B_NextBase_f(void)127 static void B_NextBase_f (void)
128 {
129 	base_t* base = B_GetCurrentSelectedBase();
130 
131 	if (!base)
132 		return;
133 
134 	base = B_GetNext(base);
135 	/* if it was the last base, select the first */
136 	if (!base)
137 		base = B_GetNext(nullptr);
138 	if (base)
139 		B_SelectBase(base);
140 }
141 
142 /**
143  * @brief Cycles to the previous base.
144  * @sa B_NextBase
145  * @sa B_SelectBase_f
146  * @todo This should not rely on base->idx!
147  */
B_PrevBase_f(void)148 static void B_PrevBase_f (void)
149 {
150 	const base_t* currentBase = B_GetCurrentSelectedBase();
151 	const base_t* prevBase;
152 	base_t* base;
153 
154 	if (!currentBase)
155 		return;
156 
157 	prevBase = nullptr;
158 	base = nullptr;
159 	while ((base = B_GetNext(base)) != nullptr) {
160 		if (base == currentBase)
161 			break;
162 		prevBase = base;
163 	}
164 	/* if it was the first base, select the last */
165 	if (!prevBase) {
166 		while ((base = B_GetNext(base)) != nullptr) {
167 			prevBase = base;
168 		}
169 	}
170 
171 	if (prevBase)
172 		B_SelectBase(prevBase);
173 }
174 
175 /**
176  * @brief Sets the title of the base to a cvar to prepare the rename menu.
177  */
B_SetBaseTitle_f(void)178 static void B_SetBaseTitle_f (void)
179 {
180 	int baseCount = B_GetCount();
181 
182 	if (baseCount < MAX_BASES) {
183 		char baseName[MAX_VAR];
184 
185 		if (baseCount > 0) {
186 			int j;
187 			int i = 2;
188 			do {
189 				j = 0;
190 				Com_sprintf(baseName, lengthof(baseName), _("Base #%i"), i);
191 				while (j <= baseCount && !Q_streq(baseName, ccs.bases[j].name)) {
192 					j++;
193 				}
194 			} while (i++ <= baseCount && j <= baseCount);
195 		} else {
196 			Q_strncpyz(baseName, _("Home"), lengthof(baseName));
197 		}
198 
199 		cgi->Cvar_Set("mn_base_title", "%s", baseName);
200 	} else {
201 		MS_AddNewMessage(_("Notice"), _("You've reached the base limit."));
202 		cgi->UI_PopWindow(false);		/* remove the new base popup */
203 	}
204 }
205 
206 /**
207  * @brief Constructs a new base.
208  * @sa B_NewBase
209  */
B_BuildBase_f(void)210 static void B_BuildBase_f (void)
211 {
212 	const campaign_t* campaign = ccs.curCampaign;
213 
214 	if (ccs.mapAction == MA_NEWBASE)
215 		ccs.mapAction = MA_NONE;
216 
217 	if (ccs.credits - campaign->basecost > 0) {
218 		const nation_t* nation;
219 		const char* baseName = mn_base_title->string;
220 		base_t* base;
221 		/* there may be no " in the base name */
222 		if (!Com_IsValidName(baseName))
223 			baseName = _("Base");
224 
225 		base = B_Build(campaign, ccs.newBasePos, baseName);
226 		if (!base)
227 			cgi->Com_Error(ERR_DROP, "Cannot build base");
228 
229 		CP_UpdateCredits(ccs.credits - campaign->basecost);
230 		nation = GEO_GetNation(base->pos);
231 		if (nation)
232 			Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("A new base has been built: %s (nation: %s)"), mn_base_title->string, _(nation->name));
233 		else
234 			Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("A new base has been built: %s"), mn_base_title->string);
235 		MS_AddNewMessage(_("Base built"), cp_messageBuffer, MSG_CONSTRUCTION);
236 
237 		/* First base */
238 		if (ccs.campaignStats.basesBuilt == 1)
239 			B_SetUpFirstBase(campaign, base);
240 
241 		cgi->Cvar_SetValue("mn_base_count", B_GetCount());
242 		B_SelectBase(base);
243 	} else {
244 		if (GEO_IsRadarOverlayActivated())
245 			GEO_SetOverlay("radar");
246 
247 		CP_PopupList(_("Notice"), _("Not enough credits to set up a new base."));
248 	}
249 }
250 
251 /**
252  * @brief Creates console command to change the name of a base.
253  * Copies the value of the cvar mn_base_title over as the name of the
254  * current selected base
255  */
B_ChangeBaseName_f(void)256 static void B_ChangeBaseName_f (void)
257 {
258 	base_t* base = B_GetCurrentSelectedBase();
259 
260 	/* maybe called without base initialized or active */
261 	if (!base)
262 		return;
263 
264 	/* basename should not contain " */
265 	if (!Com_IsValidName(cgi->Cvar_GetString("mn_base_title"))) {
266 		cgi->Cvar_Set("mn_base_title", "%s", base->name);
267 		return;
268 	}
269 
270 	Q_strncpyz(base->name, cgi->Cvar_GetString("mn_base_title"), sizeof(base->name));
271 }
272 
273 /**
274  * @brief Resets the currently selected building.
275  *
276  * Is called e.g. when leaving the build-menu
277  */
B_ResetBuildingCurrent_f(void)278 static void B_ResetBuildingCurrent_f (void)
279 {
280 	base_t* base = B_GetCurrentSelectedBase();
281 
282 	B_ResetBuildingCurrent(base);
283 }
284 
285 /**
286  * @brief Initialises base.
287  * @note This command is executed in the init node of the base menu.
288  * It is called everytime the base menu pops up and sets the cvars.
289  * @todo integrate building status tooltips from B_CheckBuildingStatusForMenu_f into this one
290  */
B_BaseInit_f(void)291 static void B_BaseInit_f (void)
292 {
293 	int i;
294 	base_t* base = B_GetCurrentSelectedBase();
295 
296 	if (!base)
297 		return;
298 
299 	/* make sure the credits cvar is up-to-date */
300 	CP_UpdateCredits(ccs.credits);
301 
302 	cgi->Cvar_SetValue("mn_base_num_aircraft", AIR_BaseCountAircraft(base));
303 
304 	/* activate or deactivate the aircraft button */
305 	if (AIR_AircraftAllowed(base)) {
306 		if (AIR_BaseHasAircraft(base))
307 			cgi->UI_ExecuteConfunc("update_basebutton aircraft false \"%s\"", _("Aircraft management and crew equipment"));
308 		else
309 			cgi->UI_ExecuteConfunc("update_basebutton aircraft true \"%s\"", _("Buy or produce at least one aircraft first."));
310 	} else {
311 		cgi->UI_ExecuteConfunc("update_basebutton aircraft true \"%s\"", _("No Hangar functional in base."));
312 	}
313 
314 	if (BS_BuySellAllowed(base))
315 		cgi->UI_ExecuteConfunc("update_basebutton buysell false \"%s\"", _("Buy/Sell equipment, aircraft and UGV"));
316 	else
317 		cgi->UI_ExecuteConfunc("update_basebutton buysell true \"%s\"", va(_("No %s functional in base."), _("Storage")));
318 
319 	if (B_GetCount() > 1)
320 		cgi->UI_ExecuteConfunc("update_basebutton transfer false \"%s\"", _("Transfer equipment, vehicles, aliens and employees to other bases"));
321 	else
322 		cgi->UI_ExecuteConfunc("update_basebutton transfer true \"%s\"", _("Build at least a second base to transfer equipment or personnel"));
323 
324 	if (RS_ResearchAllowed(base))
325 		cgi->UI_ExecuteConfunc("update_basebutton research false \"%s\"", _("Research new technology"));
326 	else
327 		cgi->UI_ExecuteConfunc("update_basebutton research true \"%s\"", va(_("No %s functional in base."), _("Laboratory")));
328 
329 	if (PR_ProductionAllowed(base))
330 		cgi->UI_ExecuteConfunc("update_basebutton production false \"%s\"", _("Produce equipment, aircraft and UGV"));
331 	else
332 		cgi->UI_ExecuteConfunc("update_basebutton production true \"%s\"", va(_("No %s functional in base."), _("Workshop")));
333 
334 	if (E_HireAllowed(base))
335 		cgi->UI_ExecuteConfunc("update_basebutton hire false \"%s\"", _("Hire or dismiss employees"));
336 	else
337 		cgi->UI_ExecuteConfunc("update_basebutton hire true \"%s\"", va(_("No %s functional in base."), _("Living Quarters")));
338 
339 	if (AC_ContainmentAllowed(base))
340 		cgi->UI_ExecuteConfunc("update_basebutton containment false \"%s\"", _("Manage captured aliens"));
341 	else
342 		cgi->UI_ExecuteConfunc("update_basebutton containment true \"%s\"", va(_("No %s functional in base."), _("Containment")));
343 
344 	if (HOS_HospitalAllowed(base))
345 		cgi->UI_ExecuteConfunc("update_basebutton hospital false \"%s\"", _("Treat wounded soldiers and perform implant surgery"));
346 	else
347 		cgi->UI_ExecuteConfunc("update_basebutton hospital true \"%s\"", va(_("No %s functional in base."), _("Hospital")));
348 
349 	/*
350 	 * Gather data on current/max space for living quarters, storage, lab and workshop
351 	 * clear_bld_space ensures 0/0 data for facilities which may not exist in base
352 	 */
353 	cgi->UI_ExecuteConfunc("clear_bld_space");
354 	for (i = 0; i < ccs.numBuildingTemplates; i++) {
355 		const building_t* b = &ccs.buildingTemplates[i];
356 		const baseCapacities_t capType = B_GetCapacityFromBuildingType(b->buildingType);
357 		capacities_t cap;
358 
359 		if (capType == MAX_CAP)
360 			continue;
361 		/* Check if building matches one of our four types */
362 		if (b->buildingType != B_QUARTERS && b->buildingType != B_STORAGE && b->buildingType != B_WORKSHOP && b->buildingType != B_LAB && b->buildingType != B_ANTIMATTER)
363 			continue;
364 		/* only show already researched buildings */
365 		if (!RS_IsResearched_ptr(b->tech))
366 			continue;
367 		cap = *CAP_Get(base, capType);
368 
369 		assert(b->tpl);
370 		const int count = B_GetNumberOfBuildingsInBaseByTemplate(base, b->tpl);
371 		if (count < 1)
372 			continue;
373 
374 		cgi->UI_ExecuteConfunc("show_bld_space \"%s\" \"%s\" %i %i %i %i", _(b->name), b->id, cap.cur, cap.max, count, b->tpl->maxCount);
375 	}
376 
377 	/*
378 	 * Get the number of different employees in the base
379 	 * @todo: Get the number of injured soldiers if hospital exists
380 	 */
381 	cgi->UI_ExecuteConfunc("current_employees %i %i %i %i", E_CountHired(base, EMPL_SOLDIER), E_CountHired(base, EMPL_PILOT), E_CountHired(base, EMPL_SCIENTIST), E_CountHired(base, EMPL_WORKER));
382 
383 	/*
384 	 * List the first five aircraft in the base if they exist
385 	 */
386 	cgi->UI_ExecuteConfunc("clear_aircraft");
387 	if (AIR_AircraftAllowed(base)) {
388 		if (AIR_BaseHasAircraft(base)) {
389 			i = 0;
390 			AIR_ForeachFromBase(aircraft, base) {
391 				if (i > 5)
392 					break;
393 				/*
394 				 * UI node should use global IDX to identify aircraft but it uses order of aircraft in base (i)
395 				 * See @todo in cp_aircraft_callbacks.c in AIR_AircraftSelect()
396 				 */
397 				cgi->UI_ExecuteConfunc("show_aircraft %i \"%s\" \"%s\" \"%s\" %i", i, aircraft->name, aircraft->id, AIR_AircraftStatusToName(aircraft), AIR_IsAircraftInBase(aircraft));
398 				i++;
399 			}
400 		}
401 	}
402 
403 	/* Get the research item closest to completion in the base if it exists */
404 	cgi->UI_ExecuteConfunc("clear_research");
405 	if (RS_ResearchAllowed(base)) {
406 		const technology_t* closestTech = nullptr;
407 		double finished = -1;
408 		for (i = 0; i < ccs.numTechnologies; i++) {
409 			const technology_t* tech = RS_GetTechByIDX(i);
410 			if (!tech)
411 				continue;
412 			if (tech->base != base)
413 				continue;
414 			if (tech->statusResearch == RS_RUNNING) {
415 				const double percent = (1 - tech->time / tech->overallTime) * 100;
416 				if (percent > finished) {
417 					finished = percent;
418 					closestTech = tech;
419 				}
420 			}
421 		}
422 		if (closestTech != nullptr)
423 			cgi->UI_ExecuteConfunc("show_research \"%s\" %i %3.0f", closestTech->name, closestTech->scientists, finished);
424 	}
425 
426 	/* Get the production item closest to completion in the base if it exists */
427 	cgi->UI_ExecuteConfunc("clear_production");
428 	if (PR_ProductionAllowed(base)) {
429 		const production_queue_t* queue = PR_GetProductionForBase(base);
430 		if (queue->numItems > 0) {
431 			const production_t* production = &queue->items[0];
432 			cgi->UI_ExecuteConfunc("show_production \"%s\" %3.0f", PR_GetName(&production->data), PR_GetProgress(production) * 100);
433 		}
434 	}
435 }
436 
437 /**
438  * @brief Update the display of building space for all researched facilities
439  * @sa B_BaseInit_f
440  */
B_BuildingSpace_f(void)441 static void B_BuildingSpace_f (void)
442 {
443 	int i;
444 	base_t* base = B_GetCurrentSelectedBase();
445 
446 	if (!base)
447 		return;
448 
449 	// Clear existing entries from the UI panel
450 	cgi->UI_ExecuteConfunc("clear_bld_space");
451 	for (i = 0; i < ccs.numBuildingTemplates; i++) {
452 		const building_t* b = &ccs.buildingTemplates[i];
453 		const baseCapacities_t capType = B_GetCapacityFromBuildingType(b->buildingType);
454 		capacities_t cap;
455 
456 		/* skip mandatory buildings (like Entrance) which are built automatically */
457 		if (b->mandatory)
458 			continue;
459 		/* only show already researched buildings */
460 		if (!RS_IsResearched_ptr(b->tech))
461 			continue;
462 
463 		if (capType != MAX_CAP)
464 			cap = *CAP_Get(base, capType);
465 		else
466 			OBJZERO(cap);
467 
468 		assert(b->tpl);
469 		const int count = B_GetNumberOfBuildingsInBaseByTemplate(base, b->tpl);
470 
471 		cgi->UI_ExecuteConfunc("show_bld_space \"%s\" \"%s\" %i %i %i %i", _(b->name), b->id, cap.cur, cap.max, count, b->tpl->maxCount);
472 	}
473 }
474 
475 /**
476  * @brief Update the building-list.
477  * @sa B_BuildingInit_f
478  */
B_BuildingInit(base_t * base)479 static void B_BuildingInit (base_t* base)
480 {
481 	int i;
482 	linkedList_t* buildingList = nullptr;
483 
484 	/* maybe someone call this command before the bases are parsed?? */
485 	if (!base)
486 		return;
487 
488 	for (i = 0; i < ccs.numBuildingTemplates; i++) {
489 		building_t* tpl = &ccs.buildingTemplates[i];
490 		/* make an entry in list for this building */
491 
492 		if (tpl->mapPart) {
493 			const int numSameBuildings = B_GetNumberOfBuildingsInBaseByTemplate(base, tpl);
494 
495 			if (tpl->maxCount >= 0 && tpl->maxCount <= numSameBuildings)
496 				continue;
497 			/* skip if limit of BASE_SIZE*BASE_SIZE exceeded */
498 			if (numSameBuildings >= BASE_SIZE * BASE_SIZE)
499 				continue;
500 
501 			/* if the building is researched add it to the list */
502 			if (RS_IsResearched_ptr(tpl->tech))
503 				B_BuildingAddToList(&buildingList, tpl);
504 		}
505 	}
506 	if (base->buildingCurrent)
507 		B_DrawBuilding(base->buildingCurrent);
508 	else
509 		cgi->UI_ExecuteConfunc("mn_buildings_reset");
510 
511 	buildingNumber = cgi->LIST_Count(buildingList);
512 	cgi->UI_RegisterLinkedListText(TEXT_BUILDINGS, buildingList);
513 }
514 
515 /**
516  * @brief Script command binding for B_BuildingInit
517  */
B_BuildingInit_f(void)518 static void B_BuildingInit_f (void)
519 {
520 	base_t* base = B_GetCurrentSelectedBase();
521 
522 	if (!base)
523 		return;
524 
525 	B_BuildingInit(base);
526 }
527 
528 /**
529  * @brief Opens the UFOpedia for the current selected building.
530  */
B_BuildingInfoClick_f(void)531 static void B_BuildingInfoClick_f (void)
532 {
533 	base_t* base = B_GetCurrentSelectedBase();
534 
535 	if (!base)
536 		return;
537 
538 	if (base->buildingCurrent)
539 		UP_OpenWith(base->buildingCurrent->pedia);
540 }
541 
542 /**
543  * @brief Script function for clicking the building list text field.
544  */
B_BuildingClick_f(void)545 static void B_BuildingClick_f (void)
546 {
547 	building_t* building;
548 	base_t* base = B_GetCurrentSelectedBase();
549 
550 	if (!base)
551 		return;
552 
553 	if (cgi->Cmd_Argc() < 2) {
554 		Com_Printf("Usage: %s <building_id|building list index>\n", cgi->Cmd_Argv(0));
555 		return;
556 	}
557 
558 	/* which building? */
559 	building = B_GetBuildingTemplateSilent(cgi->Cmd_Argv(1));
560 	if (!building) {
561 		int num = atoi(cgi->Cmd_Argv(1));
562 		if (num > buildingNumber || num < 0) {
563 			Com_DPrintf(DEBUG_CLIENT, "B_BuildingClick_f: max exceeded %i/%i\n", num, buildingNumber);
564 			return;
565 		}
566 		building = buildingConstructionList[num];
567 	}
568 
569 	base->buildingCurrent = building;
570 	B_DrawBuilding(building);
571 
572 	ccs.baseAction = BA_NEWBUILDING;
573 }
574 
575 /**
576  * @brief Mark a building for destruction - you only have to confirm it now
577  * @param[in] building Pointer to the base to destroy
578  */
B_MarkBuildingDestroy(building_t * building)579 static void B_MarkBuildingDestroy (building_t* building)
580 {
581 	baseCapacities_t cap;
582 	base_t* base = building->base;
583 
584 	if (building == nullptr) {
585 		Com_Printf("B_MarkBuildingDestroy: Trying to destroy non-existing building");
586 		return;
587 	}
588 
589 	/* you can't destroy buildings if base is under attack */
590 	if (B_IsUnderAttack(base)) {
591 		CP_Popup(_("Notice"), _("Base is under attack, you can't destroy buildings!"));
592 		return;
593 	}
594 
595 	cap = B_GetCapacityFromBuildingType(building->buildingType);
596 	/* store the pointer to the building you wanna destroy */
597 	base->buildingCurrent = building;
598 
599 	/** @todo: make base destroyable by destroying entrance */
600 	if (building->buildingType == B_ENTRANCE) {
601 		CP_Popup(_("Destroy Entrance"), _("You can't destroy the entrance of the base!"));
602 		return;
603 	}
604 
605 	if (!B_IsBuildingDestroyable(building)) {
606 			CP_Popup(_("Notice"), _("You can't destroy this building! It is the only connection to other buildings!"));
607 			return;
608 	}
609 
610 	if (building->buildingStatus == B_STATUS_WORKING) {
611 		const bool hasMoreBases = B_GetCount() > 1;
612 		switch (building->buildingType) {
613 		case B_HANGAR:
614 		case B_SMALL_HANGAR:
615 			if (CAP_GetFreeCapacity(base, cap) <= 0) {
616 				cgi->UI_PopupButton(_("Destroy Hangar"), _("If you destroy this hangar, you will also destroy the aircraft inside.\nAre you sure you want to destroy this building?"),
617 					"ui_pop;ui_push aircraft;aircraft_select;", _("Go to hangar"), _("Go to hangar without destroying building"),
618 					va("ui_pop; building_destroy %i %i confirmed;", base->idx, building->idx), _("Destroy"), _("Destroy the building"),
619 					hasMoreBases ? "ui_pop;ui_push transfer;" : nullptr, hasMoreBases ? _("Transfer") : nullptr,
620 					_("Go to transfer menu without destroying the building"));
621 				return;
622 			}
623 			break;
624 		case B_QUARTERS:
625 			if (CAP_GetFreeCapacity(base, cap) < building->capacity) {
626 				cgi->UI_PopupButton(_("Destroy Quarter"), _("If you destroy this Quarters, every employee inside will be killed.\nAre you sure you want to destroy this building?"),
627 					"ui_pop;ui_push employees;employee_list 0;", _("Dismiss"), _("Go to hiring menu without destroying building"),
628 					va("ui_pop; building_destroy %i %i confirmed;", base->idx, building->idx), _("Destroy"), _("Destroy the building"),
629 					hasMoreBases ? "ui_pop;ui_push transfer;" : nullptr, hasMoreBases ? _("Transfer") : nullptr,
630 					_("Go to transfer menu without destroying the building"));
631 				return;
632 			}
633 			break;
634 		case B_STORAGE:
635 			if (CAP_GetFreeCapacity(base, cap) < building->capacity) {
636 				cgi->UI_PopupButton(_("Destroy Storage"), _("If you destroy this Storage, every items inside will be destroyed.\nAre you sure you want to destroy this building?"),
637 					"ui_pop;ui_push market;buy_type *mn_itemtype", _("Go to storage"), _("Go to buy/sell menu without destroying building"),
638 					va("ui_pop; building_destroy %i %i confirmed;", base->idx, building->idx), _("Destroy"), _("Destroy the building"),
639 					hasMoreBases ? "ui_pop;ui_push transfer;" : nullptr, hasMoreBases ? _("Transfer") : nullptr,
640 					_("Go to transfer menu without destroying the building"));
641 				return;
642 			}
643 			break;
644 		default:
645 			break;
646 		}
647 	}
648 
649 	cgi->UI_PopupButton(_("Destroy building"), _("Are you sure you want to destroy this building?"),
650 		nullptr, nullptr, nullptr,
651 		va("ui_pop; building_destroy %i %i confirmed;", base->idx, building->idx), _("Destroy"), _("Destroy the building"),
652 		nullptr, nullptr, nullptr);
653 }
654 
655 /**
656  * @brief Destroy a base building
657  * @sa B_MarkBuildingDestroy
658  * @sa B_BuildingDestroy
659  */
B_BuildingDestroy_f(void)660 static void B_BuildingDestroy_f (void)
661 {
662 	base_t* base;
663 	building_t* building;
664 
665 	if (cgi->Cmd_Argc() < 3) {
666 		Com_DPrintf(DEBUG_CLIENT, "Usage: %s <baseID> <buildingID> [confirmed]\n", cgi->Cmd_Argv(0));
667 		return;
668 	} else {
669 		const int baseID = atoi(cgi->Cmd_Argv(1));
670 		const int buildingID = atoi(cgi->Cmd_Argv(2));
671 		base = B_GetBaseByIDX(baseID);
672 		assert(base);
673 		building = &ccs.buildings[baseID][buildingID];
674 	}
675 
676 	if (base == nullptr || building == nullptr)
677 		return;
678 
679 	if (cgi->Cmd_Argc() == 4 && Q_streq(cgi->Cmd_Argv(3), "confirmed")) {
680 		B_BuildingDestroy(building);
681 		B_ResetBuildingCurrent(base);
682 		B_BuildingInit(base);
683 	} else {
684 		B_MarkBuildingDestroy(building);
685 	}
686 }
687 
688 /**
689  * @brief Console callback for B_BuildingStatus
690  * @sa B_BuildingStatus
691  */
B_BuildingStatus_f(void)692 static void B_BuildingStatus_f (void)
693 {
694 	const base_t* base = B_GetCurrentSelectedBase();
695 
696 	/* maybe someone called this command before the buildings are parsed?? */
697 	if (!base || !base->buildingCurrent)
698 		return;
699 
700 	B_BuildingStatus(base->buildingCurrent);
701 }
702 
703 /**
704  * @brief Builds a base map for tactical combat.
705  * @sa SV_AssembleMap
706  * @sa CP_BaseAttackChooseBase
707  */
B_AssembleMap_f(void)708 static void B_AssembleMap_f (void)
709 {
710 	const base_t* base;
711 
712 	if (cgi->Cmd_Argc() < 2) {
713 		Com_DPrintf(DEBUG_CLIENT, "Usage: %s <baseID>\n", cgi->Cmd_Argv(0));
714 		base = B_GetCurrentSelectedBase();
715 	} else {
716 		const int baseID = atoi(cgi->Cmd_Argv(1));
717 		base = B_GetBaseByIDX(baseID);
718 	}
719 
720 	char maps[2048];
721 	char coords[2048];
722 	B_AssembleMap(maps, sizeof(maps), coords, sizeof(coords), base);
723 	cgi->Cbuf_AddText("map %s \"%s\" \"%s\"\n", (GEO_IsNight(base->pos) ? "night" : "day"), maps, coords);
724 }
725 
726 /**
727  * @brief Checks why a button in base menu is disabled, and create a popup to inform player
728  */
B_CheckBuildingStatusForMenu_f(void)729 static void B_CheckBuildingStatusForMenu_f (void)
730 {
731 	int num;
732 	const char* buildingID;
733 	const building_t* building;
734 	const base_t* base = B_GetCurrentSelectedBase();
735 
736 	if (cgi->Cmd_Argc() != 2) {
737 		Com_Printf("Usage: %s <buildingID>\n", cgi->Cmd_Argv(0));
738 		return;
739 	}
740 
741 	buildingID = cgi->Cmd_Argv(1);
742 	building = B_GetBuildingTemplate(buildingID);
743 
744 	if (!building || !base)
745 		return;
746 
747 	/* Maybe base is under attack ? */
748 	if (B_IsUnderAttack(base)) {
749 		CP_Popup(_("Notice"), _("Base is under attack, you can't access this building !"));
750 		return;
751 	}
752 
753 	if (building->buildingType == B_HANGAR) {
754 		/* this is an exception because you must have a small or large hangar to enter aircraft menu */
755 		CP_Popup(_("Notice"), _("You need at least one Hangar (and its dependencies) to use aircraft."));
756 		return;
757 	}
758 
759 	num = B_GetNumberOfBuildingsInBaseByBuildingType(base, building->buildingType);
760 	if (num > 0) {
761 		int numUnderConstruction;
762 		/* maybe all buildings of this type are under construction ? */
763 		B_CheckBuildingTypeStatus(base, building->buildingType, B_STATUS_UNDER_CONSTRUCTION, &numUnderConstruction);
764 		if (numUnderConstruction == num) {
765 			int minDay = 99999;
766 			building_t* b = nullptr;
767 
768 			while ((b = B_GetNextBuildingByType(base, b, building->buildingType))) {
769 				if (b->buildingStatus == B_STATUS_UNDER_CONSTRUCTION) {
770 					const float remaining = B_GetConstructionTimeRemain(b);
771 					minDay = std::min(minDay, (int)std::max(0.0f, remaining));
772 				}
773 			}
774 
775 			CP_Popup(_("Notice"), ngettext("Construction of building will be over in %i day.\nPlease wait to enter.", "Construction of building will be over in %i days.\nPlease wait to enter.",
776 					minDay), minDay);
777 			return;
778 		}
779 
780 		if (!B_CheckBuildingDependencesStatus(building)) {
781 			const building_t* dependenceBuilding = building->dependsBuilding;
782 			assert(building->dependsBuilding);
783 			if (B_GetNumberOfBuildingsInBaseByBuildingType(base, dependenceBuilding->buildingType) <= 0) {
784 				/* the dependence of the building is not built */
785 				CP_Popup(_("Notice"), _("You need a building %s to make building %s functional."), _(dependenceBuilding->name), _(building->name));
786 				return;
787 			} else {
788 				/* maybe the dependence of the building is under construction
789 				 * note that we can't use B_STATUS_UNDER_CONSTRUCTION here, because this value
790 				 * is not use for every building (for exemple Command Centre) */
791 				building_t* b = nullptr;
792 
793 				while ((b = B_GetNextBuildingByType(base, b, dependenceBuilding->buildingType))) {
794 					if (!B_IsBuildingBuiltUp(b)) {
795 						CP_Popup(_("Notice"), _("Building %s is not finished yet, and is needed to use building %s."),
796 								_(dependenceBuilding->name), _(building->name));
797 						return;
798 					}
799 				}
800 				/* the dependence is built but doesn't work - must be because of their dependendes */
801 				CP_Popup(_("Notice"), _("Make sure that the dependencies of building %s (%s) are operational, so that building %s may be used."),
802 						_(dependenceBuilding->name), _(dependenceBuilding->dependsBuilding->name), _(building->name));
803 				return;
804 			}
805 		}
806 		/* all buildings are OK: employees must be missing */
807 		if (building->buildingType == B_WORKSHOP && E_CountHired(base, EMPL_WORKER) <= 0) {
808 			CP_Popup(_("Notice"), _("You need to recruit %s to use building %s."),
809 					E_GetEmployeeString(EMPL_WORKER, 2), _(building->name));
810 			return;
811 		} else if (building->buildingType == B_LAB && E_CountHired(base, EMPL_SCIENTIST) <= 0) {
812 			CP_Popup(_("Notice"), _("You need to recruit %s to use building %s."),
813 					E_GetEmployeeString(EMPL_SCIENTIST, 2), _(building->name));
814 			return;
815 		}
816 	} else {
817 		CP_Popup(_("Notice"), _("Build a %s first."), _(building->name));
818 		return;
819 	}
820 }
821 
822 /** BaseSummary Callbacks: */
823 
824 /**
825  * @brief Base Summary menu init function.
826  * @note Should be called whenever the Base Summary menu gets active.
827  */
BaseSummary_Init(const base_t * base)828 static void BaseSummary_Init (const base_t* base)
829 {
830 	/* Init base switcher */
831 	cgi->UI_ExecuteConfunc("bsum_clear_bases");
832 	base_t* otherBase = NULL;
833 	while ((otherBase = B_GetNext(otherBase))) {
834 		cgi->UI_ExecuteConfunc("bsum_add_base %d %d", otherBase->idx, (otherBase == base ? 1 : 0));
835 	}
836 
837 	int i;
838 	/* Main */
839 	cgi->UI_ExecuteConfunc("bsum_main_clearlines");
840 	int line = 0;
841 
842 	cgi->UI_ExecuteConfunc("bsum_main_addline %d \"%s\" \"%s\" \"%s\" %d", line++, _("Buildings"), _("Capacity"), _("Amount"), 1);
843 	bool anyBuilding = false;
844 	for (i = 0; i < ccs.numBuildingTemplates; i++) {
845 		const building_t* b = &ccs.buildingTemplates[i];
846 
847 		/* only show already researched buildings */
848 		if (!RS_IsResearched_ptr(b->tech))
849 			continue;
850 		const baseCapacities_t cap = B_GetCapacityFromBuildingType(b->buildingType);
851 		if (cap == MAX_CAP)
852 			continue;
853 		if (!B_GetNumberOfBuildingsInBaseByBuildingType(base, b->buildingType))
854 			continue;
855 
856 		cgi->UI_ExecuteConfunc("bsum_main_addline %d \"%s\" \"%i/%i\" \"%d\" %d", line++, _(b->name),
857 			CAP_GetCurrent(base, cap), CAP_GetMax(base, cap),
858 			B_GetNumberOfBuildingsInBaseByBuildingType(base, b->buildingType), 0);
859 		anyBuilding = true;
860 	}
861 	if (!anyBuilding)
862 		cgi->UI_ExecuteConfunc("bsum_main_addline %d \"%s\" \"\" \"\" %d", line++, _("No report"), 0);
863 	cgi->UI_ExecuteConfunc("bsum_main_addline %d \"\" \"\" \"\" 0", line++);
864 
865 	cgi->UI_ExecuteConfunc("bsum_main_addline %d \"%s\" \"%s\" \"%s\" %d", line++, _("Production"), _("Quantity"), _("Percent"), 1);
866 	const production_queue_t* queue = PR_GetProductionForBase(base);
867 	if (queue->numItems > 0) {
868 		for (i = 0; i < queue->numItems; i++) {
869 			const production_t* production = &queue->items[i];
870 			const char* name = PR_GetName(&production->data);
871 			cgi->UI_ExecuteConfunc("bsum_main_addline %d \"%s\" \"%d\" \"%.2f%%\" %d", line++, name,
872 				production->amount, PR_GetProgress(production) * 100, 0);
873 		}
874 	} else {
875 		cgi->UI_ExecuteConfunc("bsum_main_addline %d \"%s\" \"\" \"\" %d", line++, _("Nothing"), 0);
876 	}
877 	cgi->UI_ExecuteConfunc("bsum_main_addline %d \"\" \"\" \"\" 0", line++);
878 
879 	cgi->UI_ExecuteConfunc("bsum_main_addline %d \"%s\" \"%s\" \"%s\" %d", line++, _("Research"), _("Scientists"), _("Percent"), 1);
880 	bool anyResearch = false;
881 	for (i = 0; i < ccs.numTechnologies; i++) {
882 		const technology_t* tech = RS_GetTechByIDX(i);
883 		if (tech->base == base && (tech->statusResearch == RS_RUNNING || tech->statusResearch == RS_PAUSED)) {
884 			cgi->UI_ExecuteConfunc("bsum_main_addline %d \"%s\" \"%d\" \"%.2f%%\" %d", line++, _(tech->name),
885 				tech->scientists, (1.0f - tech->time / tech->overallTime) * 100, 0);
886 			anyResearch = true;
887 		}
888 	}
889 	if (!anyResearch)
890 		cgi->UI_ExecuteConfunc("bsum_main_addline %d \"%s\" \"\" \"\" %d", line++, _("Nothing"), 0);
891 
892 	static char textInfoBuffer[1024];
893 	textInfoBuffer[0] = 0;
894 
895 	Q_strcat(textInfoBuffer, sizeof(textInfoBuffer), _("^BAircraft\n"));
896 	for (i = 0; i <= MAX_HUMAN_AIRCRAFT_TYPE; i++) {
897 		const aircraftType_t airType = (aircraftType_t)i;
898 		const int count = AIR_CountTypeInBase(base, airType);
899 		if (count == 0)
900 			continue;
901 		Q_strcat(textInfoBuffer, sizeof(textInfoBuffer), "\t%s:\t\t\t\t%i\n", AIR_GetAircraftString(airType),
902 			count);
903 	}
904 	Q_strcat(textInfoBuffer, sizeof(textInfoBuffer), "\n");
905 
906 	Q_strcat(textInfoBuffer, sizeof(textInfoBuffer), _("^BEmployees\n"));
907 	for (i = 0; i < MAX_EMPL; i++) {
908 		const employeeType_t emplType = (employeeType_t)i;
909 		const int cnt = E_CountHired(base, emplType);
910 		if (cnt == 0)
911 			continue;
912 		const char* desc = E_GetEmployeeString(emplType, cnt);
913 		Q_strcat(textInfoBuffer, sizeof(textInfoBuffer), "\t%s:\t\t\t\t%i\n", desc, cnt);
914 	}
915 	Q_strcat(textInfoBuffer, sizeof(textInfoBuffer), "\n");
916 
917 	Q_strcat(textInfoBuffer, sizeof(textInfoBuffer), _("^BAliens\n"));
918 	if (base->alienContainment) {
919 		linkedList_t* list = base->alienContainment->list();
920 		LIST_Foreach(list, alienCargo_t, item) {
921 			Q_strcat(textInfoBuffer, sizeof(textInfoBuffer), "\t%s:\t\t\t\t%i/%i\n",
922 				_(item->teamDef->name), item->alive, item->dead);
923 		}
924 		cgi->LIST_Delete(&list);
925 	}
926 	cgi->UI_RegisterText(TEXT_STANDARD, textInfoBuffer);
927 }
928 
929 /**
930  * @brief Open menu for basesummary.
931  */
BaseSummary_SelectBase_f(void)932 static void BaseSummary_SelectBase_f (void)
933 {
934 	const base_t* base;
935 
936 	if (cgi->Cmd_Argc() >= 2) {
937 		const int i = atoi(cgi->Cmd_Argv(1));
938 		base = B_GetFoundedBaseByIDX(i);
939 		if (base == nullptr) {
940 			Com_Printf("Invalid base index given (%i).\n", i);
941 			return;
942 		}
943 	} else {
944 		base = B_GetCurrentSelectedBase();
945 	}
946 
947 	if (base != nullptr) {
948 		BaseSummary_Init(base);
949 	}
950 }
951 
952 /**
953  * @brief Makes a mapshot - called by basemapshot script command
954  * @note Load a basemap and execute 'basemapshot' in console
955  */
B_MakeBaseMapShot_f(void)956 static void B_MakeBaseMapShot_f (void)
957 {
958 	if (!cgi->Com_ServerState()) {
959 		Com_Printf("Load the base map before you try to use this function\n");
960 		return;
961 	}
962 
963 	cgi->Cmd_ExecuteString("camsetangles %i %i", 60, 90);
964 	cgi->Cvar_SetValue("r_isometric", 1);
965 	/* we are interested in the second level only */
966 	cgi->Cvar_SetValue("cl_worldlevel", 1);
967 	cgi->UI_PushWindow("hud_nohud");
968 	/* hide any active console */
969 	cgi->Cmd_ExecuteString("toggleconsole");
970 	cgi->Cmd_ExecuteString("r_screenshot tga");
971 }
972 
973 /** Init/Shutdown functions */
974 
975 /** @todo unify the names into mn_base_* */
B_InitCallbacks(void)976 void B_InitCallbacks (void)
977 {
978 	mn_base_title = cgi->Cvar_Get("mn_base_title", "", 0, "The title of the current base");
979 	cl_start_buildings = cgi->Cvar_Get("cl_start_buildings", "1", CVAR_ARCHIVE, "Start with initial buildings in your first base");
980 	cgi->Cvar_Set("mn_base_cost", _("%i c"), ccs.curCampaign->basecost);
981 	cgi->Cvar_SetValue("mn_base_count", B_GetCount());
982 	cgi->Cvar_SetValue("mn_base_max", MAX_BASES);
983 
984 	cgi->Cmd_AddCommand("basemapshot", B_MakeBaseMapShot_f, "Command to make a screenshot for the baseview with the correct angles");
985 	cgi->Cmd_AddCommand("mn_base_prev", B_PrevBase_f, "Go to the previous base");
986 	cgi->Cmd_AddCommand("mn_base_next", B_NextBase_f, "Go to the next base");
987 	cgi->Cmd_AddCommand("mn_base_select", B_SelectBase_f, "Select a founded base by index");
988 	cgi->Cmd_AddCommand("mn_base_build", B_BuildBase_f, nullptr);
989 	cgi->Cmd_AddCommand("mn_set_base_title", B_SetBaseTitle_f, nullptr);
990 	cgi->Cmd_AddCommand("base_changename", B_ChangeBaseName_f, "Called after editing the cvar base name");
991 	cgi->Cmd_AddCommand("base_init", B_BaseInit_f, nullptr);
992 	cgi->Cmd_AddCommand("base_assemble", B_AssembleMap_f, "Called to assemble the current selected base");
993 	cgi->Cmd_AddCommand("base_building_space", B_BuildingSpace_f, "Called to display building capacity in current selected base");
994 	cgi->Cmd_AddCommand("building_init", B_BuildingInit_f, nullptr);
995 	cgi->Cmd_AddCommand("building_status", B_BuildingStatus_f, nullptr);
996 	cgi->Cmd_AddCommand("building_destroy", B_BuildingDestroy_f, "Function to destroy a building (select via right click in baseview first)");
997 	cgi->Cmd_AddCommand("building_amdestroy", B_Destroy_AntimaterStorage_f, "Function called if antimatter storage destroyed");
998 	cgi->Cmd_AddCommand("building_ufopedia", B_BuildingInfoClick_f, "Opens the UFOpedia for the current selected building");
999 	cgi->Cmd_AddCommand("check_building_status", B_CheckBuildingStatusForMenu_f, "Create a popup to inform player why he can't use a button");
1000 	cgi->Cmd_AddCommand("buildings_click", B_BuildingClick_f, "Opens the building information window in construction mode");
1001 	cgi->Cmd_AddCommand("reset_building_current", B_ResetBuildingCurrent_f, nullptr);
1002 	cgi->Cmd_AddCommand("basesummary_selectbase", BaseSummary_SelectBase_f, "Opens Base Statistics menu in base");
1003 }
1004 
1005 /** @todo unify the names into mn_base_* */
B_ShutdownCallbacks(void)1006 void B_ShutdownCallbacks (void)
1007 {
1008 	cgi->Cmd_RemoveCommand("basemapshot");
1009 	cgi->Cmd_RemoveCommand("basesummary_selectbase");
1010 	cgi->Cmd_RemoveCommand("mn_base_prev");
1011 	cgi->Cmd_RemoveCommand("mn_base_next");
1012 	cgi->Cmd_RemoveCommand("mn_base_select");
1013 	cgi->Cmd_RemoveCommand("mn_base_build");
1014 	cgi->Cmd_RemoveCommand("base_changename");
1015 	cgi->Cmd_RemoveCommand("mn_set_base_title");
1016 	cgi->Cmd_RemoveCommand("base_init");
1017 	cgi->Cmd_RemoveCommand("base_assemble");
1018 	cgi->Cmd_RemoveCommand("base_building_space");
1019 	cgi->Cmd_RemoveCommand("building_init");
1020 	cgi->Cmd_RemoveCommand("building_status");
1021 	cgi->Cmd_RemoveCommand("building_destroy");
1022 	cgi->Cmd_RemoveCommand("building_ufopedia");
1023 	cgi->Cmd_RemoveCommand("check_building_status");
1024 	cgi->Cmd_RemoveCommand("buildings_click");
1025 	cgi->Cmd_RemoveCommand("reset_building_current");
1026 	cgi->Cvar_Delete("mn_base_max");
1027 	cgi->Cvar_Delete("mn_base_cost");
1028 	cgi->Cvar_Delete("mn_base_title");
1029 	cgi->Cvar_Delete("mn_base_count");
1030 }
1031