1 /**
2  * @file
3  * @brief Handles everything that is located in or accessed trough a base.
4  * @note Basemanagement functions prefix: B_
5  * @note See "base/ufos/basemanagement.ufo" for the underlying content.
6  */
7 
8 /*
9 Copyright (C) 2002-2013 UFO: Alien Invasion.
10 
11 This program is free software; you can redistribute it and/or
12 modify it under the terms of the GNU General Public License
13 as published by the Free Software Foundation; either version 2
14 of the License, or (at your option) any later version.
15 
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19 
20 See the GNU General Public License for more details.
21 
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, write to the Free Software
24 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
25 */
26 
27 #include "../../cl_shared.h"
28 #include "../../cl_inventory.h" /* INV_GetEquipmentDefinitionByID */
29 #include "../../ui/ui_dataids.h"
30 #include "../../../shared/parse.h"
31 #include "cp_campaign.h"
32 #include "cp_mapfightequip.h"
33 #include "cp_aircraft.h"
34 #include "cp_missions.h"
35 #include "cp_geoscape.h"
36 #include "cp_popup.h"
37 #include "cp_radar.h"
38 #include "cp_time.h"
39 #include "cp_base_callbacks.h"
40 #include "cp_ufo.h"
41 #include "save/save_base.h"
42 #include "aliencontainment.h"
43 
44 #define B_GetBuildingByIDX(baseIdx, buildingIdx) (&ccs.buildings[(baseIdx)][(buildingIdx)])
45 #define B_GetBuildingIDX(base, building) ((ptrdiff_t)((building) - ccs.buildings[base->idx]))
46 #define B_GetBaseIDX(base) ((ptrdiff_t)((base) - ccs.bases))
47 
48 static void B_InitialEquipment(aircraft_t* aircraft, const equipDef_t* ed);
49 
50 /**
51  * @brief Returns the neighbourhood of a building
52  * @param[in] building The building we ask neighbours
53  * @returns a linkedList with neighbouring buildings
54  * @note for unfinished building it returns an empty list
55  */
B_GetNeighbours(const building_t * building)56 static linkedList_t* B_GetNeighbours (const building_t* building)
57 {
58 	if (!building || !B_IsBuildingBuiltUp(building))
59 		return nullptr;
60 
61 	const int x = building->pos[0];
62 	const int y = building->pos[1];
63 	base_t* base = building->base;
64 	linkedList_t* neighbours = nullptr;
65 
66 	assert(base);
67 	for (int i = x; i < x + building->size[0]; i++) {
68 		/* North */
69 		if (y > 0 && B_GetBuildingAt(base, i, y - 1) != nullptr)
70 			cgi->LIST_AddPointer(&neighbours, (void*)B_GetBuildingAt(base, i, y - 1));
71 		/* South */
72 		if (y < BASE_SIZE - building->size[1] && B_GetBuildingAt(base, i, y + building->size[1]) != nullptr)
73 			cgi->LIST_AddPointer(&neighbours, (void*)B_GetBuildingAt(base, i, y + building->size[1]));
74 	}
75 	for (int i = y; i < y + building->size[1]; i++) {
76 		/* West */
77 		if (x > 0 && B_GetBuildingAt(base, x - 1, i) != nullptr)
78 			cgi->LIST_AddPointer(&neighbours, (void*)B_GetBuildingAt(base, x - 1, i));
79 		/* East */
80 		if (x < BASE_SIZE - building->size[0] && B_GetBuildingAt(base, x + building->size[0], i) != nullptr)
81 			cgi->LIST_AddPointer(&neighbours, (void*)B_GetBuildingAt(base, x + building->size[0], i));
82 	}
83 	return neighbours;
84 }
85 
86 #ifdef DEBUG
87 /**
88  * @brief Check if base coherent (every building connected to eachothers)
89  * @param[in] base Pointer to the base to check
90  */
B_IsCoherent(const base_t * base)91 static bool B_IsCoherent (const base_t* base)
92 {
93 	int found[MAX_BUILDINGS];
94 	linkedList_t* queue = nullptr;
95 	linkedList_t* neighbours;
96 	building_t* bldg = nullptr;
97 
98 	OBJZERO(found);
99 	while ((bldg = B_GetNextBuilding(base, bldg)) != nullptr) {
100 		cgi->LIST_AddPointer(&queue, (void*)bldg);
101 		break;
102 	}
103 	if (!bldg)
104 		return true;
105 
106 	while (!cgi->LIST_IsEmpty(queue)) {
107 		bldg = (building_t*)queue->data;
108 		found[bldg->idx] = 1;
109 		cgi->LIST_RemoveEntry(&queue, queue);
110 
111 		neighbours = B_GetNeighbours(bldg);
112 		LIST_Foreach(neighbours, building_t, bldg) {
113 			if (found[bldg->idx] == 0) {
114 				found[bldg->idx] = 1;
115 				cgi->LIST_AddPointer(&queue, (void*)bldg);
116 			}
117 		}
118 		cgi->LIST_Delete(&neighbours);
119 	}
120 	cgi->LIST_Delete(&queue);
121 
122 	for (int i = 0; i < ccs.numBuildings[base->idx]; i++) {
123 		if (found[i] != 1)
124 			return false;
125 	}
126 	return true;
127 }
128 #endif
129 
130 /**
131  * @brief Check and add blocked tile to the base
132  * @param[in,out] base The base to add blocked tile to
133  * @param[in] row Row position of the tile
134  * @param[in] column Column position of the tile
135  * @return bool value of success
136  */
B_AddBlockedTile(base_t * base,int row,int column)137 static bool B_AddBlockedTile (base_t* base, int row, int column)
138 {
139 	assert(base);
140 
141 	if (B_GetBuildingAt(base, column, row) != nullptr)
142 		return false;
143 
144 	int found[BASE_SIZE][BASE_SIZE];
145 	OBJZERO(found);
146 	found[row][column] = -1;
147 
148 	linkedList_t* queue = nullptr;
149 	/* Get first non blocked tile */
150 	for (int y = 0; y < BASE_SIZE && cgi->LIST_IsEmpty(queue); y++) {
151 		for (int x = 0; x < BASE_SIZE && cgi->LIST_IsEmpty(queue); x++) {
152 			if (x == column && y == row)
153 				continue;
154 			if (!B_IsTileBlocked(base, x, y))
155 				cgi->LIST_AddPointer(&queue, &base->map[y][x]);
156 		}
157 	}
158 
159 	if (cgi->LIST_IsEmpty(queue))
160 		return false;
161 
162 	/* BS Traversal */
163 	while (!cgi->LIST_IsEmpty(queue)) {
164 		baseBuildingTile_t* tile = (baseBuildingTile_t*)queue->data;
165 		cgi->LIST_RemoveEntry(&queue, queue);
166 		const int x = tile->posX;
167 		const int y = tile->posY;
168 
169 		found[y][x] = 1;
170 		/* West */
171 		if (x > 0 && !B_IsTileBlocked(base, x - 1, y) && found[y][x - 1] == 0)
172 			cgi->LIST_AddPointer(&queue, (void*)&base->map[y][x - 1]);
173 		/* East */
174 		if (x < BASE_SIZE - 1 && !B_IsTileBlocked(base, x + 1, y) && found[y][x + 1] == 0)
175 			cgi->LIST_AddPointer(&queue, (void*)&base->map[y][x + 1]);
176 		/* North */
177 		if (y > 0 && !B_IsTileBlocked(base, x, y - 1) && found[y - 1][x] == 0)
178 			cgi->LIST_AddPointer(&queue, (void*)&base->map[y - 1][x]);
179 		/* South */
180 		if (y < BASE_SIZE - 1 && !B_IsTileBlocked(base, x, y + 1) && found[y + 1][x] == 0)
181 			cgi->LIST_AddPointer(&queue, (void*)&base->map[y + 1][x]);
182 	}
183 	cgi->LIST_Delete(&queue);
184 
185 	/* Check for unreached areas */
186 	for (int y = 0; y < BASE_SIZE; y++) {
187 		for (int x = 0; x < BASE_SIZE; x++) {
188 			if (!B_IsTileBlocked(base, x, y) && found[y][x] == 0)
189 				return false;
190 		}
191 	}
192 	base->map[row][column].blocked = true;
193 	return true;
194 }
195 
196 /**
197  * @brief Fuction to put blocked tiles on basemap
198  * @param[out] base The base to fill
199  * @param[in] count Number of blocked tiles to add
200  */
B_AddBlockedTiles(base_t * base,int count)201 static void B_AddBlockedTiles (base_t* base, int count)
202 {
203 	assert(base);
204 
205 	for (int placed = 0; placed < count; placed++) {
206 		const int x = rand() % BASE_SIZE;
207 		const int y = rand() % BASE_SIZE;
208 
209 		if (B_IsTileBlocked(base, x, y))
210 			continue;
211 
212 		if (B_GetBuildingAt(base, x, y) != nullptr)
213 			continue;
214 
215 		B_AddBlockedTile(base, y, x);
216 	}
217 }
218 
219 /**
220  * @brief Returns if a base building is destroyable
221  * @param[in] building Pointer to the building to check
222  */
B_IsBuildingDestroyable(const building_t * building)223 bool B_IsBuildingDestroyable (const building_t* building)
224 {
225 	assert(building);
226 	base_t* base = building->base;
227 	assert(base);
228 
229 	if (base->baseStatus == BASE_DESTROYED)
230 		return true;
231 
232 	linkedList_t* queue = nullptr;
233 	building_t* bldg = nullptr;
234 
235 	while ((bldg = B_GetNextBuilding(base, bldg)) != nullptr) {
236 		if (bldg != building) {
237 			cgi->LIST_AddPointer(&queue, (void*)bldg);
238 			break;
239 		}
240 	}
241 	if (!bldg)
242 		return true;
243 
244 	int found[MAX_BUILDINGS];
245 	OBJZERO(found);
246 	/* prevents adding building to be removed to the queue */
247 	found[building->idx] = 1;
248 
249 	while (!cgi->LIST_IsEmpty(queue)) {
250 		bldg = (building_t*)queue->data;
251 		found[bldg->idx] = 1;
252 		cgi->LIST_RemoveEntry(&queue, queue);
253 
254 		linkedList_t* neighbours = B_GetNeighbours(bldg);
255 		LIST_Foreach(neighbours, building_t, bldg) {
256 			if (found[bldg->idx] == 0) {
257 				found[bldg->idx] = 1;
258 				cgi->LIST_AddPointer(&queue, (void*)bldg);
259 			}
260 		}
261 		cgi->LIST_Delete(&neighbours);
262 	}
263 	cgi->LIST_Delete(&queue);
264 
265 	for (int i = 0; i < ccs.numBuildings[base->idx]; i++) {
266 		if (found[i] != 1)
267 			return false;
268 	}
269 
270 	return true;
271 }
272 
273 /**
274  * @brief Returns the count of founded bases
275  */
B_GetCount(void)276 int B_GetCount (void)
277 {
278 	return ccs.numBases;
279 }
280 
281 /**
282  * @brief Iterates through founded bases
283  * @param[in] lastBase Pointer of the base to iterate from. call with nullptr to get the first one.
284  */
B_GetNext(base_t * lastBase)285 base_t* B_GetNext (base_t* lastBase)
286 {
287 	base_t* endOfBases = &ccs.bases[B_GetCount()];
288 	base_t* base;
289 
290 	if (!B_GetCount())
291 		return nullptr;
292 
293 	if (!lastBase)
294 		return ccs.bases;
295 	assert(lastBase >= ccs.bases);
296 	assert(lastBase < endOfBases);
297 
298 	base = lastBase;
299 
300 	base++;
301 	if (base >= endOfBases)
302 		return nullptr;
303 	else
304 		return base;
305 }
306 
307 /**
308  * @brief Array bound check for the base index. Will also return unfounded bases as
309  * long as the index is in the valid ranges,
310  * @param[in] baseIdx Index to check
311  * @return Pointer to the base corresponding to baseIdx.
312  */
B_GetBaseByIDX(int baseIdx)313 base_t* B_GetBaseByIDX (int baseIdx)
314 {
315 	if (baseIdx >= MAX_BASES || baseIdx < 0)
316 		return nullptr;
317 
318 	return &ccs.bases[baseIdx];
319 }
320 
321 /**
322  * @brief Array bound check for the base index.
323  * @param[in] baseIdx Index to check
324  * @return Pointer to the base corresponding to baseIdx if base is founded, nullptr else.
325  */
B_GetFoundedBaseByIDX(int baseIdx)326 base_t* B_GetFoundedBaseByIDX (int baseIdx)
327 {
328 	if (baseIdx >= B_GetCount())
329 		return nullptr;
330 
331 	return B_GetBaseByIDX(baseIdx);
332 }
333 
334 /**
335  * @brief Iterates through buildings in a base
336  * @param[in] base Pointer to the base which buildings asked
337  * @param[in] lastBuilding Pointer to the building iterate from. Call with nullptr to get the first one.
338  */
B_GetNextBuilding(const base_t * base,building_t * lastBuilding)339 building_t* B_GetNextBuilding (const base_t* base, building_t* lastBuilding)
340 {
341 	building_t* endOfBuildings = B_GetBuildingByIDX(base->idx, ccs.numBuildings[base->idx]);
342 	building_t* building;
343 
344 	if (!ccs.numBuildings[base->idx])
345 		return nullptr;
346 
347 	if (!lastBuilding)
348 		return ccs.buildings[base->idx];
349 	assert(lastBuilding >= ccs.buildings[base->idx]);
350 	assert(lastBuilding < endOfBuildings);
351 
352 	building = lastBuilding;
353 
354 	building++;
355 	if (building >= endOfBuildings)
356 		return nullptr;
357 	return building;
358 }
359 
360 /**
361  * @brief Iterates throught buildings of a type in a base
362  * @param[in] base Pointer to the base which buildings asked
363  * @param[in] lastBuilding Pointer to the building iterate from. Call with nullptr to get the first one.
364  * @param[in] buildingType Type of the buildings to search
365  * @sa buildingType_t
366  */
B_GetNextBuildingByType(const base_t * base,building_t * lastBuilding,buildingType_t buildingType)367 building_t* B_GetNextBuildingByType (const base_t* base, building_t* lastBuilding, buildingType_t buildingType)
368 {
369 	building_t* building = lastBuilding;
370 
371 	while ((building = B_GetNextBuilding(base, building))) {
372 		if (building->buildingType == buildingType)
373 			break;
374 	}
375 	return building;
376 }
377 
378 /**
379  * @brief Searches the base for a given building type with the given status
380  * @param[in] base Base to search
381  * @param[in] type Building type to search
382  * @param[in] status The status the building should have
383  * @param[out] cnt This is a pointer to an int value which will hold the building
384  * count of that type with the status you are searching - might also be nullptr
385  * if you are not interested in this value
386  * @note If you are searching for a quarter (e.g.) you should perform a
387  * <code>if (hasBuilding[B_QUARTERS])</code> check - this should speed things up a lot
388  * @return true if building with status was found
389  */
B_CheckBuildingTypeStatus(const base_t * const base,buildingType_t type,buildingStatus_t status,int * cnt)390 bool B_CheckBuildingTypeStatus (const base_t* const base, buildingType_t type, buildingStatus_t status, int* cnt)
391 {
392 	int cntlocal = 0;
393 	building_t* building = nullptr;
394 
395 	while ((building = B_GetNextBuildingByType(base, building, type))) {
396 		if (building->buildingStatus != status)
397 			continue;
398 		cntlocal++;
399 		/* don't count any further - the caller doesn't want to know the value */
400 		if (!cnt)
401 			return true;
402 	}
403 
404 	/* set the cnt pointer if the caller wants to know this value */
405 	if (cnt)
406 		*cnt = cntlocal;
407 
408 	return cntlocal ? true : false;
409 }
410 
411 /**
412  * @brief Get the capacity associated to a building type
413  * @param[in] type The type of the building
414  * @return capacity (baseCapacities_t), or MAX_CAP if building has no capacity
415  */
B_GetCapacityFromBuildingType(buildingType_t type)416 baseCapacities_t B_GetCapacityFromBuildingType (buildingType_t type)
417 {
418 	switch (type) {
419 	case B_LAB:
420 		return CAP_LABSPACE;
421 	case B_QUARTERS:
422 		return CAP_EMPLOYEES;
423 	case B_STORAGE:
424 		return CAP_ITEMS;
425 	case B_WORKSHOP:
426 		return CAP_WORKSPACE;
427 	case B_HANGAR:
428 		return CAP_AIRCRAFT_BIG;
429 	case B_ALIEN_CONTAINMENT:
430 		return CAP_ALIENS;
431 	case B_SMALL_HANGAR:
432 		return CAP_AIRCRAFT_SMALL;
433 	case B_ANTIMATTER:
434 		return CAP_ANTIMATTER;
435 	default:
436 		return MAX_CAP;
437 	}
438 }
439 
440 /**
441  * @brief Get building type by base capacity.
442  * @param[in] cap Enum type of baseCapacities_t.
443  * @return Enum type of buildingType_t.
444  * @sa B_UpdateBaseCapacities
445  * @sa B_PrintCapacities_f
446  */
B_GetBuildingTypeByCapacity(baseCapacities_t cap)447 buildingType_t B_GetBuildingTypeByCapacity (baseCapacities_t cap)
448 {
449 	switch (cap) {
450 	case CAP_ALIENS:
451 		return B_ALIEN_CONTAINMENT;
452 	case CAP_AIRCRAFT_SMALL:
453 		return B_SMALL_HANGAR;
454 	case CAP_AIRCRAFT_BIG:
455 		return B_HANGAR;
456 	case CAP_EMPLOYEES:
457 		return B_QUARTERS;
458 	case CAP_ITEMS:
459 		return B_STORAGE;
460 	case CAP_LABSPACE:
461 		return B_LAB;
462 	case CAP_WORKSPACE:
463 		return B_WORKSHOP;
464 	case CAP_ANTIMATTER:
465 		return B_ANTIMATTER;
466 	default:
467 		return MAX_BUILDING_TYPE;
468 	}
469 }
470 
471 /**
472  * @brief Get the status associated to a building
473  * @param[in] base The base to search for the given building type
474  * @param[in] buildingType value of building->buildingType
475  * @return true if the building is functional
476  * @sa B_SetBuildingStatus
477  */
B_GetBuildingStatus(const base_t * const base,const buildingType_t buildingType)478 bool B_GetBuildingStatus (const base_t* const base, const buildingType_t buildingType)
479 {
480 	assert(base);
481 
482 	if (buildingType == B_MISC)
483 		return true;
484 
485 	if (buildingType < MAX_BUILDING_TYPE)
486 		return base->hasBuilding[buildingType];
487 
488 	Com_Printf("B_GetBuildingStatus: Building-type %i does not exist.\n", buildingType);
489 	return false;
490 }
491 
492 /**
493  * @brief Set status associated to a building
494  * @param[in] base Base to check
495  * @param[in] buildingType value of building->buildingType
496  * @param[in] newStatus New value of the status
497  * @sa B_GetBuildingStatus
498  */
B_SetBuildingStatus(base_t * const base,const buildingType_t buildingType,bool newStatus)499 void B_SetBuildingStatus (base_t* const base, const buildingType_t buildingType, bool newStatus)
500 {
501 	assert(base);
502 
503 	if (buildingType == B_MISC) {
504 		Com_Printf("B_SetBuildingStatus: No status is associated to B_MISC type of building.\n");
505 	} else if (buildingType < MAX_BUILDING_TYPE) {
506 		base->hasBuilding[buildingType] = newStatus;
507 		Com_DPrintf(DEBUG_CLIENT, "B_SetBuildingStatus: set status for %i to %i\n", buildingType, newStatus);
508 	} else {
509 		Com_Printf("B_SetBuildingStatus: Type of building %i does not exist\n", buildingType);
510 	}
511 }
512 
513 /**
514  * @brief Resets the buildingCurrent variable and baseAction
515  * @param[in,out] base Pointer to the base needs buildingCurrent to be reseted
516  * @note Make sure you are not doing anything with the buildingCurrent pointer
517  * in this function, the pointer might already be invalid
518  */
B_ResetBuildingCurrent(base_t * base)519 void B_ResetBuildingCurrent (base_t* base)
520 {
521 	if (base)
522 		base->buildingCurrent = nullptr;
523 	ccs.baseAction = BA_NONE;
524 }
525 
526 /**
527  * @brief Get the maximum level of a building type in a base.
528  * @param[in] base Pointer to base.
529  * @param[in] type Building type to get the maximum level for.
530  * @note This function checks base status for particular buildings.
531  * @return 0.0f if there is no (operational) building of the requested type in the base, otherwise the maximum level.
532  */
B_GetMaxBuildingLevel(const base_t * base,const buildingType_t type)533 float B_GetMaxBuildingLevel (const base_t* base, const buildingType_t type)
534 {
535 	float max = 0.0f;
536 
537 	if (B_GetBuildingStatus(base, type)) {
538 		building_t* building = nullptr;
539 		while ((building = B_GetNextBuildingByType(base, building, type)))
540 			if (building->buildingStatus == B_STATUS_WORKING)
541 				max = std::max(building->level, max);
542 	}
543 
544 	return max;
545 }
546 
547 /**
548  * @brief Adds a map to the given position to the map string
549  * @param[out] maps The map output string
550  * @param[in] mapsLength The length of the maps string
551  * @param[out] coords The coords output string
552  * @param[in] coordsLength The length of the coords string
553  * @param[in] map The map tile to add
554  * @param[in] col The col to spawn the map at
555  * @param[in] row The row to spawn the map at
556  * @sa SV_Map_f
557  * @sa SV_Map
558  */
B_AddMap(char * maps,size_t mapsLength,char * coords,size_t coordsLength,const char * map,int col,int row)559 static inline void B_AddMap (char* maps, size_t mapsLength, char* coords, size_t coordsLength, const char* map, int col, int row)
560 {
561 	Q_strcat(coords, coordsLength, "%i %i %i ", col * BASE_TILE_UNITS, (BASE_SIZE - row - 1) * BASE_TILE_UNITS, 0);
562 	Q_strcat(maps, mapsLength, "%s", map);
563 }
564 
565 /**
566  * @brief Perform the base assembling in case of an alien attack
567  * @param[in,out] base The base to assemble
568  * @return @c true if the assembly was successful, @c false if it failed
569  * @todo Search a empty field and add a alien craft there
570  * @todo If a building is still under construction, it will be assembled as a finished part.
571  * Otherwise we need mapparts for all the maps under construction.
572  */
B_AssembleMap(char * maps,size_t mapsLength,char * coords,size_t coordsLength,const base_t * base)573 bool B_AssembleMap (char* maps, size_t mapsLength, char* coords, size_t coordsLength, const base_t* base)
574 {
575 	if (!base) {
576 		Com_Printf("B_AssembleMap: No base to assemble\n");
577 		return false;
578 	}
579 
580 	bool used[MAX_BUILDINGS];
581 
582 	maps[0] = '\0';
583 	coords[0] = '\0';
584 
585 	/* reset the used flag */
586 	OBJZERO(used);
587 
588 	for (int row = 0; row < BASE_SIZE; ++row) {
589 		for (int col = 0; col < BASE_SIZE; ++col) {
590 			const building_t* entry = base->map[row][col].building;
591 			if (!entry) {
592 				B_AddMap(maps, mapsLength, coords, coordsLength, "b/empty ", col, row);
593 				continue;
594 			}
595 			if (!B_IsBuildingBuiltUp(entry)) {
596 				B_AddMap(maps, mapsLength, coords, coordsLength, "b/construction ", col, row);
597 				continue;
598 			}
599 			/* basemaps with needs are not (like the images in B_DrawBase) two maps - but one
600 			 * this is why we check the used flag and continue if it was set already */
601 			if (B_BuildingGetUsed(used, entry->idx))
602 				continue;
603 			B_BuildingSetUsed(used, entry->idx);
604 
605 			if (!entry->mapPart)
606 				cgi->Com_Error(ERR_DROP, "MapPart for building '%s' is missing'", entry->id);
607 
608 			B_AddMap(maps, mapsLength, coords, coordsLength, va("b/%s ", entry->mapPart), col, row);
609 		}
610 	}
611 
612 	return true;
613 }
614 
615 /**
616  * @brief Check base status for particular buildings as well as capacities.
617  * @param[in] building Pointer to building.
618  * @return true if a base status has been modified (but do not check capacities)
619  */
B_CheckUpdateBuilding(building_t * building)620 static bool B_CheckUpdateBuilding (building_t* building)
621 {
622 	bool oldValue;
623 
624 	assert(building);
625 
626 	/* Status of Miscellenious buildings cannot change. */
627 	if (building->buildingType == B_MISC)
628 		return false;
629 
630 	oldValue = B_GetBuildingStatus(building->base, building->buildingType);
631 	if (building->buildingStatus == B_STATUS_WORKING
632 	 && B_CheckBuildingDependencesStatus(building))
633 		B_SetBuildingStatus(building->base, building->buildingType, true);
634 	else
635 		B_SetBuildingStatus(building->base, building->buildingType, false);
636 
637 	if (B_GetBuildingStatus(building->base, building->buildingType) != oldValue) {
638 		Com_DPrintf(DEBUG_CLIENT, "Status of building %s is changed to %i.\n",
639 			building->name, B_GetBuildingStatus(building->base, building->buildingType));
640 		return true;
641 	}
642 
643 	return false;
644 }
645 
646 /**
647  * @brief Update status of every building when a building has been built/destroyed
648  * @param[in] base
649  * @param[in] buildingType The building-type that has been built / removed.
650  * @param[in] onBuilt true if building has been built, false else
651  * @sa B_BuildingDestroy
652  * @sa B_UpdateAllBaseBuildingStatus
653  * @return true if at least one building status has been modified
654  */
B_UpdateStatusBuilding(base_t * base,buildingType_t buildingType,bool onBuilt)655 static bool B_UpdateStatusBuilding (base_t* base, buildingType_t buildingType, bool onBuilt)
656 {
657 	bool test = false;
658 	bool returnValue = false;
659 	building_t* building = nullptr;
660 
661 	/* Construction / destruction may have changed the status of other building
662 	 * We check that, but only for buildings which needed building */
663 	while ((building = B_GetNextBuilding(base, building))) {
664 		const building_t* dependsBuilding = building->dependsBuilding;
665 		if (dependsBuilding && buildingType == dependsBuilding->buildingType) {
666 			/* ccs.buildings[base->idx][i] needs built/removed building */
667 			if (onBuilt && !B_GetBuildingStatus(base, building->buildingType)) {
668 				/* we can only activate a non operational building */
669 				if (B_CheckUpdateBuilding(building)) {
670 					B_FireEvent(building, base, B_ONENABLE);
671 					test = true;
672 					returnValue = true;
673 				}
674 			} else if (!onBuilt && B_GetBuildingStatus(base, building->buildingType)) {
675 				/* we can only deactivate an operational building */
676 				if (B_CheckUpdateBuilding(building)) {
677 					B_FireEvent(building, base, B_ONDISABLE);
678 					test = true;
679 					returnValue = true;
680 				}
681 			}
682 		}
683 	}
684 	/* and maybe some updated status have changed status of other building.
685 	 * So we check again, until nothing changes. (no condition here for check, it's too complex) */
686 	while (test) {
687 		test = false;
688 		building = nullptr;
689 		while ((building = B_GetNextBuilding(base, building))) {
690 			if (onBuilt && !B_GetBuildingStatus(base, building->buildingType)) {
691 				/* we can only activate a non operational building */
692 				if (B_CheckUpdateBuilding(building)) {
693 					B_FireEvent(building, base, B_ONENABLE);
694 					test = true;
695 				}
696 			} else if (!onBuilt && B_GetBuildingStatus(base, building->buildingType)) {
697 				/* we can only deactivate an operational building */
698 				if (B_CheckUpdateBuilding(building)) {
699 					B_FireEvent(building, base, B_ONDISABLE);
700 					test = true;
701 				}
702 			}
703 		}
704 	}
705 
706 	return returnValue;
707 }
708 
709 /**
710  * @brief Update Antimatter Capacity.
711  * @param[in] base Pointer to the base
712  * @sa B_ResetAllStatusAndCapacities_f
713  */
B_UpdateAntimatterCap(base_t * base)714 static void B_UpdateAntimatterCap (base_t* base)
715 {
716 	const objDef_t* od = INVSH_GetItemByID(ANTIMATTER_TECH_ID);
717 	if (od != nullptr)
718 		CAP_SetCurrent(base, CAP_ANTIMATTER, B_ItemInBase(od, base));
719 }
720 
721 /**
722  * @brief Recalculate status and capacities of one base
723  * @param[in] base Pointer to the base where status and capacities must be recalculated
724  * @param[in] firstEnable true if this is the first time the function is called for this base
725  */
B_ResetAllStatusAndCapacities(base_t * base,bool firstEnable)726 void B_ResetAllStatusAndCapacities (base_t* base, bool firstEnable)
727 {
728 	bool test = true;
729 
730 	assert(base);
731 
732 	Com_DPrintf(DEBUG_CLIENT, "Reseting base %s:\n", base->name);
733 
734 	/* reset all values of hasBuilding[] */
735 	for (int i = 0; i < MAX_BUILDING_TYPE; i++) {
736 		const buildingType_t type = (buildingType_t)i;
737 		if (type != B_MISC)
738 			B_SetBuildingStatus(base, type, false);
739 	}
740 	/* activate all buildings that needs to be activated */
741 	while (test) {
742 		building_t* building = nullptr;
743 		test = false;
744 		while ((building = B_GetNextBuilding(base, building))) {
745 			if (!B_GetBuildingStatus(base, building->buildingType)
746 			 && B_CheckUpdateBuilding(building)) {
747 				if (firstEnable)
748 					B_FireEvent(building, base, B_ONENABLE);
749 				test = true;
750 			}
751 		}
752 	}
753 
754 	/* Update all capacities of base */
755 	B_UpdateBaseCapacities(MAX_CAP, base);
756 
757 	/* calculate capacities.cur for every capacity */
758 
759 	/* Current CAP_ALIENS (live alien capacity) is managed by AlienContainment class */
760 	/* Current aircraft capacities should not need recounting */
761 
762 	if (B_GetBuildingStatus(base, B_GetBuildingTypeByCapacity(CAP_EMPLOYEES)))
763 		CAP_SetCurrent(base, CAP_EMPLOYEES, E_CountAllHired(base));
764 
765 	if (B_GetBuildingStatus(base, B_GetBuildingTypeByCapacity(CAP_ITEMS)))
766 		CAP_UpdateStorageCap(base);
767 
768 	if (B_GetBuildingStatus(base, B_GetBuildingTypeByCapacity(CAP_LABSPACE)))
769 		CAP_SetCurrent(base, CAP_LABSPACE, RS_CountScientistsInBase(base));
770 
771 	if (B_GetBuildingStatus(base, B_GetBuildingTypeByCapacity(CAP_WORKSPACE)))
772 		PR_UpdateProductionCap(base);
773 
774 	if (B_GetBuildingStatus(base, B_GetBuildingTypeByCapacity(CAP_ANTIMATTER)))
775 		B_UpdateAntimatterCap(base);
776 
777 	/* Check that current capacity is possible -- if we changed values in *.ufo */
778 	for (int i = 0; i < MAX_CAP; i++) {
779 		const baseCapacities_t cap = (baseCapacities_t)i;
780 		if (CAP_GetFreeCapacity(base, cap) < 0)
781 			Com_Printf("B_ResetAllStatusAndCapacities: Warning, capacity of %i is bigger than maximum capacity\n", i);
782 	}
783 }
784 
785 /**
786  * @brief Removes a building from the given base
787  * @param[in] building The building to remove
788  * @note Also updates capacities and sets the hasBuilding[] values in base_t
789  * @sa B_BuildingDestroy_f
790  */
B_BuildingDestroy(building_t * building)791 bool B_BuildingDestroy (building_t* building)
792 {
793 	const buildingType_t buildingType = building->buildingType;
794 	const building_t* buildingTemplate = building->tpl;
795 	const bool runDisableCommand = building->buildingStatus == B_STATUS_WORKING;
796 	base_t* base = building->base;
797 
798 	/* Don't allow to destroy a mandatory building. */
799 	if (building->mandatory)
800 		return false;
801 
802 	if (base->map[(int)building->pos[1]][(int)building->pos[0]].building != building) {
803 		cgi->Com_Error(ERR_DROP, "B_BuildingDestroy: building mismatch at base %i pos %i,%i.",
804 			base->idx, (int)building->pos[0], (int)building->pos[1]);
805 	}
806 
807 	/* Refuse destroying if it hurts coherency - only exception is when the whole base destroys */
808 	if (!B_IsBuildingDestroyable(building)) {
809 		return false;
810 	}
811 
812 	building->buildingStatus = B_STATUS_NOT_SET;
813 
814 	/* Update buildingCurrent */
815 	if (base->buildingCurrent > building)
816 		base->buildingCurrent--;
817 	else if (base->buildingCurrent == building)
818 		base->buildingCurrent = nullptr;
819 
820 	int const baseIDX = base->idx;
821 	building_t* const buildings = ccs.buildings[baseIDX];
822 	int const idx = building->idx;
823 
824 	for (int y = building->pos[1]; y < building->pos[1] + building->size[1]; y++)
825 		for (int x = building->pos[0]; x < building->pos[0] + building->size[0]; x++)
826 			base->map[y][x].building = nullptr;
827 
828 	REMOVE_ELEM(buildings, idx, ccs.numBuildings[baseIDX]);
829 
830 	/* Update the link of other buildings */
831 	const int cntBldgs = ccs.numBuildings[baseIDX];
832 	for (int i = 0; i < cntBldgs; i++) {
833 		building_t* bldg = &buildings[i];
834 		if (bldg->idx < idx)
835 			continue;
836 		bldg->idx--;
837 
838 		for (int y = bldg->pos[1]; y < bldg->pos[1] + bldg->size[1]; y++)
839 			for (int x = (int)bldg->pos[0]; x < bldg->pos[0] + bldg->size[0]; x++)
840 				base->map[y][x].building = bldg;
841 	}
842 	building = nullptr;
843 
844 	/* Don't use the building pointer after this point - it's zeroed. */
845 
846 	if (buildingType != B_MISC && buildingType != MAX_BUILDING_TYPE) {
847 		if (B_GetNumberOfBuildingsInBaseByBuildingType(base, buildingType) <= 0) {
848 			/* there is no more building of this type */
849 			B_SetBuildingStatus(base, buildingType, false);
850 			/* check if this has an impact on other buildings */
851 			B_UpdateStatusBuilding(base, buildingType, false);
852 			/* we may have changed status of several building: update all capacities */
853 			B_UpdateBaseCapacities(MAX_CAP, base);
854 		} else {
855 			/* there is still at least one other building of the same type in base: just update capacity */
856 			const baseCapacities_t cap = B_GetCapacityFromBuildingType(buildingType);
857 			if (cap != MAX_CAP)
858 				B_UpdateBaseCapacities(cap, base);
859 		}
860 	}
861 
862 	/* call ondisable trigger only if building is not under construction
863 	 * (we do that after base capacity has been updated) */
864 	if (runDisableCommand) {
865 		if (B_FireEvent(buildingTemplate, base, B_ONDISABLE))
866 			Com_DPrintf(DEBUG_CLIENT, "B_BuildingDestroy: %s %i %i;\n",	buildingTemplate->onDisable, base->idx, buildingType);
867 	}
868 	if (B_FireEvent(buildingTemplate, base, B_ONDESTROY))
869 		Com_DPrintf(DEBUG_CLIENT, "B_BuildingDestroy: %s %i %i;\n",	buildingTemplate->onDestroy, base->idx, buildingType);
870 
871 	CAP_CheckOverflow();
872 
873 	cgi->Cmd_ExecuteString("base_init");
874 
875 	return true;
876 }
877 
878 /**
879  * @brief Will ensure that aircraft on geoscape are not stored
880  * in a base that no longer has any hangar left
881  * @param[in] base The base that is going to be destroyed
882  * @todo this should be merged into CAP_RemoveAircraftExceedingCapacity
883  */
B_MoveAircraftOnGeoscapeToOtherBases(const base_t * base)884 static void B_MoveAircraftOnGeoscapeToOtherBases (const base_t* base)
885 {
886 	AIR_ForeachFromBase(aircraft, base) {
887 		if (!AIR_IsAircraftOnGeoscape(aircraft))
888 			continue;
889 		base_t* newbase = nullptr;
890 		bool moved = false;
891 		while ((newbase = B_GetNext(newbase)) != nullptr) {
892 			/* found a new homebase? */
893 			if (base != newbase && !AIR_CheckMoveIntoNewHomebase(aircraft, newbase)) {
894 				AIR_MoveAircraftIntoNewHomebase(aircraft, newbase);
895 				moved = true;
896 				break;
897 			}
898 		}
899 
900 		if (moved)
901 			continue;
902 
903 		/* No base can hold this aircraft */
904 		UFO_NotifyPhalanxAircraftRemoved(aircraft);
905 		if (!MapIsWater(GEO_GetColor(aircraft->pos, MAPTYPE_TERRAIN, nullptr))) {
906 			CP_SpawnRescueMission(aircraft, nullptr);
907 		} else {
908 			/* Destroy the aircraft and everything onboard - the aircraft pointer
909 			 * is no longer valid after this point */
910 			/** @todo Pilot skills; really kill pilot in this case? */
911 			AIR_DestroyAircraft(aircraft);
912 		}
913 	}
914 }
915 
916 /**
917  * @brief Destroy a base.
918  * @param[in] base Pointer to base to be destroyed.
919  * @note If you want to sell items or unhire employees, you should do it before
920  * calling this function - they are going to be killed / destroyed.
921  */
B_Destroy(base_t * base)922 void B_Destroy (base_t* base)
923 {
924 	int buildingIdx;
925 
926 	assert(base);
927 	base->baseStatus = BASE_DESTROYED;
928 
929 	CP_MissionNotifyBaseDestroyed(base);
930 	B_MoveAircraftOnGeoscapeToOtherBases(base);
931 
932 	/* do a reverse loop as buildings are going to be destroyed */
933 	for (buildingIdx = ccs.numBuildings[base->idx] - 1; buildingIdx >= 0; buildingIdx--) {
934 		building_t* building = B_GetBuildingByIDX(base->idx, buildingIdx);
935 		B_BuildingDestroy(building);
936 	}
937 
938 	E_DeleteAllEmployees(base);
939 
940 	AIR_ForeachFromBase(aircraft, base) {
941 		AIR_DeleteAircraft(aircraft);
942 	}
943 
944 	if (base->alienContainment != nullptr) {
945 		delete base->alienContainment;
946 		base->alienContainment = nullptr;
947 	}
948 
949 	OBJZERO(base->storage);
950 	CAP_SetCurrent(base, CAP_ITEMS, 0);
951 
952 	/** @todo Destroy the base. For this we need to check all the dependencies and references.
953 	 * Should be only done after putting bases into a linkedList
954 	 */
955 }
956 
957 #ifdef DEBUG
958 /**
959  * @brief Debug command for destroying a base.
960  */
B_Destroy_f(void)961 static void B_Destroy_f (void)
962 {
963 	if (cgi->Cmd_Argc() < 2) {
964 		Com_Printf("Usage: %s <baseIdx>\n", cgi->Cmd_Argv(0));
965 		return;
966 	}
967 
968 	const int baseIdx = atoi(cgi->Cmd_Argv(1));
969 	if (baseIdx < 0 || baseIdx >= MAX_BASES) {
970 		Com_Printf("B_Destroy_f: baseIdx %i is outside bounds\n", baseIdx);
971 		return;
972 	}
973 
974 	base_t* base = B_GetFoundedBaseByIDX(baseIdx);
975 	if (!base) {
976 		Com_Printf("B_Destroy_f: Base %i not founded\n", baseIdx);
977 		return;
978 	}
979 
980 	B_Destroy(base);
981 }
982 #endif
983 
984 /**
985  * @brief Displays the status of a building for baseview.
986  * @note updates the cvar mn_building_status which is used in the building
987  * construction menu to display the status of the given building
988  * @note also script command function binding for 'building_status'
989  */
B_BuildingStatus(const building_t * building)990 void B_BuildingStatus (const building_t* building)
991 {
992 	assert(building);
993 
994 	cgi->Cvar_Set("mn_building_status", _("Not set"));
995 
996 	switch (building->buildingStatus) {
997 	case B_STATUS_NOT_SET: {
998 		const int numberOfBuildings = B_GetNumberOfBuildingsInBaseByTemplate(B_GetCurrentSelectedBase(), building->tpl);
999 		if (numberOfBuildings >= 0)
1000 			cgi->Cvar_Set("mn_building_status", _("Already %i in base"), numberOfBuildings);
1001 		break;
1002 	}
1003 	case B_STATUS_UNDER_CONSTRUCTION:
1004 		cgi->Cvar_Set("mn_building_status", "");
1005 		break;
1006 	case B_STATUS_CONSTRUCTION_FINISHED:
1007 		cgi->Cvar_Set("mn_building_status", _("Construction finished"));
1008 		break;
1009 	case B_STATUS_WORKING:
1010 		if (B_CheckBuildingDependencesStatus(building)) {
1011 			cgi->Cvar_Set("mn_building_status", "%s", _("Working 100%"));
1012 		} else {
1013 			assert(building->dependsBuilding);
1014 			/** @todo shorten text or provide more space in overview popup */
1015 			cgi->Cvar_Set("mn_building_status",_("Not operational, depends on %s"), _(building->dependsBuilding->name));
1016 		}
1017 		break;
1018 	case B_STATUS_DOWN:
1019 		cgi->Cvar_Set("mn_building_status", _("Down"));
1020 		break;
1021 	default:
1022 		break;
1023 	}
1024 }
1025 
1026 /**
1027  * @brief Updates base status for particular buildings as well as capacities.
1028  * @param[in] building Pointer to building.
1029  * @param[in] status Enum of buildingStatus_t which is status of given building.
1030  * @note This function checks whether a building has B_STATUS_WORKING status, and
1031  * then updates base status for particular buildings and base capacities.
1032  */
B_UpdateAllBaseBuildingStatus(building_t * building,buildingStatus_t status)1033 static void B_UpdateAllBaseBuildingStatus (building_t* building, buildingStatus_t status)
1034 {
1035 	building->buildingStatus = status;
1036 
1037 	/* we update the status of the building (we'll call this building building 1) */
1038 	const bool test = B_CheckUpdateBuilding(building);
1039 	if (test) {
1040 		base_t* base = building->base;
1041 		B_FireEvent(building, base, B_ONENABLE);
1042 		/* now, the status of this building may have changed the status of other building.
1043 		 * We check that, but only for buildings which needed building 1 */
1044 		B_UpdateStatusBuilding(base, building->buildingType, true);
1045 		/* we may have changed status of several building: update all capacities */
1046 		B_UpdateBaseCapacities(MAX_CAP, base);
1047 	} else {
1048 		/* no other status than status of building 1 has been modified
1049 		 * update only status of building 1 */
1050 		const baseCapacities_t cap = B_GetCapacityFromBuildingType(building->buildingType);
1051 		if (cap != MAX_CAP) {
1052 			base_t* base = building->base;
1053 			B_UpdateBaseCapacities(cap, base);
1054 		}
1055 	}
1056 }
1057 
1058 /**
1059  * @brief Build starting building in the first base, and hire employees.
1060  * @param[in,out] base The base to put the new building into
1061  * @param[in] buildingTemplate The building template to create a new building with
1062  * @param[in] hire Hire employees for the building we create from the template
1063  * @param[in] pos The position on the base grid
1064  */
B_AddBuildingToBasePos(base_t * base,const building_t * buildingTemplate,bool hire,const vec2_t pos)1065 static void B_AddBuildingToBasePos (base_t* base, const building_t* buildingTemplate, bool hire, const vec2_t pos)
1066 {
1067 	/* new building in base (not a template) */
1068 	building_t* buildingNew;
1069 
1070 	/* fake a click to basemap */
1071 	buildingNew = B_SetBuildingByClick(base, buildingTemplate, (int)pos[1], (int)pos[0]);
1072 	if (!buildingNew)
1073 		return;
1074 	buildingNew->timeStart.day = 0;
1075 	buildingNew->timeStart.sec = 0;
1076 	B_UpdateAllBaseBuildingStatus(buildingNew, B_STATUS_WORKING);
1077 	Com_DPrintf(DEBUG_CLIENT, "Base %i new building: %s at (%.0f:%.0f)\n",
1078 			base->idx, buildingNew->id, buildingNew->pos[0], buildingNew->pos[1]);
1079 
1080 	if (hire)
1081 		E_HireForBuilding(base, buildingNew, -1);
1082 
1083 	/* now call the onenable trigger */
1084 	if (B_FireEvent(buildingNew, base, B_ONENABLE))
1085 		Com_DPrintf(DEBUG_CLIENT, "B_AddBuildingToBasePos: %s %i;\n", buildingNew->onEnable, base->idx);
1086 }
1087 
1088 /**
1089  * @brief builds a base from template
1090  * @param[out] base The base to build
1091  * @param[in] templateName Templated used for building. If @c nullptr no template
1092  * will be used.
1093  * @param[in] hire If hiring employee needed
1094  * @note It builds an empty base on nullptr (or empty) templatename
1095  */
B_BuildFromTemplate(base_t * base,const char * templateName,bool hire)1096 static void B_BuildFromTemplate (base_t* base, const char* templateName, bool hire)
1097 {
1098 	const baseTemplate_t* baseTemplate = B_GetBaseTemplate(templateName);
1099 	int freeSpace = BASE_SIZE * BASE_SIZE;
1100 	int i;
1101 
1102 	assert(base);
1103 
1104 	if (baseTemplate) {
1105 		/* find each building in the template */
1106 		for (i = 0; i < baseTemplate->numBuildings; i++) {
1107 			const baseBuildingTile_t* buildingTile = &baseTemplate->buildings[i];
1108 
1109 			if (base->map[buildingTile->posY][buildingTile->posX].building)
1110 				continue;
1111 
1112 			vec2_t pos;
1113 			Vector2Set(pos, buildingTile->posX, buildingTile->posY);
1114 			B_AddBuildingToBasePos(base, buildingTile->building, hire, pos);
1115 			freeSpace--;
1116 		}
1117 	}
1118 
1119 	/* we need to set up the mandatory buildings */
1120 	for (i = 0; i < ccs.numBuildingTemplates; i++) {
1121 		building_t* building = &ccs.buildingTemplates[i];
1122 		vec2_t pos;
1123 
1124 		if (!building->mandatory || B_GetBuildingStatus(base, building->buildingType))
1125 			continue;
1126 
1127 		while (freeSpace > 0 && !B_GetBuildingStatus(base, building->buildingType)) {
1128 			const int x = rand() % BASE_SIZE;
1129 			const int y = rand() % BASE_SIZE;
1130 			Vector2Set(pos, x, y);
1131 			if (base->map[y][x].building)
1132 				continue;
1133 			B_AddBuildingToBasePos(base, building, hire, pos);
1134 			freeSpace--;
1135 		}
1136 		/** @todo if there is no more space for mandatory building, remove a non mandatory one
1137 		 * or build mandatory ones first */
1138 		if (!B_GetBuildingStatus(base, building->buildingType))
1139 			cgi->Com_Error(ERR_DROP, "B_BuildFromTemplate: Cannot build base. No space for it's buildings!");
1140 	}
1141 
1142 	/* set building tile positions */
1143 	for (i = 0; i < BASE_SIZE; i++) {
1144 		for (int j = 0; j < BASE_SIZE; j++) {
1145 			base->map[i][j].posY = i;
1146 			base->map[i][j].posX = j;
1147 		}
1148 	}
1149 
1150 	/* Create random blocked fields in the base.
1151 	 * The first base never has blocked fields so we skip it. */
1152 	if (ccs.campaignStats.basesBuilt >= 1) {
1153 		const int j = round((frand() * (MAX_BLOCKEDFIELDS - MIN_BLOCKEDFIELDS)) + MIN_BLOCKEDFIELDS);
1154 		B_AddBlockedTiles(base, j);
1155 	}
1156 }
1157 
1158 /**
1159  * @brief Setup aircraft and equipment for first base. Uses the campaign
1160  * scriptable equipmentlist.
1161  * @param[in] campaign The campaign data structure
1162  * @param[in,out] base The base to set up
1163  */
B_SetUpFirstBase(const campaign_t * campaign,base_t * base)1164 void B_SetUpFirstBase (const campaign_t* campaign, base_t* base)
1165 {
1166 	const equipDef_t* ed;
1167 
1168 	/* Find the initial equipment definition for current campaign. */
1169 	ed = cgi->INV_GetEquipmentDefinitionByID(campaign->equipment);
1170 	/* Copy it to base storage. */
1171 	base->storage = *ed;
1172 
1173 	/* Add aircraft to the first base */
1174 	/** @todo move aircraft to .ufo */
1175 	/* buy two first aircraft and hire pilots for them. */
1176 	if (B_GetBuildingStatus(base, B_HANGAR)) {
1177 		const equipDef_t* equipDef = cgi->INV_GetEquipmentDefinitionByID(campaign->soldierEquipment);
1178 		const char* firebird = cgi->Com_DropShipTypeToShortName(DROPSHIP_FIREBIRD);
1179 		const aircraft_t* firebirdAircraft = AIR_GetAircraft(firebird);
1180 		aircraft_t* aircraft = AIR_NewAircraft(base, firebirdAircraft);
1181 		CP_UpdateCredits(ccs.credits - firebirdAircraft->price);
1182 		if (!E_HireEmployeeByType(base, EMPL_PILOT))
1183 			cgi->Com_Error(ERR_DROP, "B_SetUpFirstBase: Hiring pilot failed.");
1184 		/* refuel initial aicraft instantly */
1185 		aircraft->fuel = aircraft->stats[AIR_STATS_FUELSIZE];
1186 		/* Assign and equip soldiers on Dropships */
1187 		AIR_AssignInitial(aircraft);
1188 		B_InitialEquipment(aircraft, equipDef);
1189 	}
1190 	if (B_GetBuildingStatus(base, B_SMALL_HANGAR)) {
1191 		const char* stiletto = cgi->Com_DropShipTypeToShortName(INTERCEPTOR_STILETTO);
1192 		const aircraft_t* stilettoAircraft = AIR_GetAircraft(stiletto);
1193 		aircraft_t* aircraft = AIR_NewAircraft(base, stilettoAircraft);
1194 		CP_UpdateCredits(ccs.credits - stilettoAircraft->price);
1195 		if (!E_HireEmployeeByType(base, EMPL_PILOT))
1196 			cgi->Com_Error(ERR_DROP, "B_SetUpFirstBase: Hiring pilot failed.");
1197 		/* refuel initial aicraft instantly */
1198 		aircraft->fuel = aircraft->stats[AIR_STATS_FUELSIZE];
1199 		AIM_AutoEquipAircraft(aircraft);
1200 	}
1201 }
1202 
1203 /**
1204  * @brief Counts the actual installation count limit
1205  * @returns int number of installations can be built
1206  */
B_GetInstallationLimit(void)1207 int B_GetInstallationLimit (void)
1208 {
1209 	int limit = 0;
1210 	base_t* base = nullptr;
1211 
1212 	/* count working Command Centers */
1213 	while ((base = B_GetNext(base)) != nullptr) {
1214 		if (B_GetBuildingStatus(base, B_COMMAND))
1215 			limit++;
1216 	}
1217 
1218 	return limit * MAX_INSTALLATIONS_PER_BASE;
1219 }
1220 
1221 /**
1222  * @brief Set the base name
1223  * @param[out] base The base to set the name for
1224  * @param[in] name The name for the base. This might already be in utf-8 as
1225  * it's the user input from the UI
1226  */
B_SetName(base_t * base,const char * name)1227 void B_SetName (base_t* base, const char* name)
1228 {
1229 	Q_strncpyz(base->name, name, sizeof(base->name));
1230 }
1231 
1232 /**
1233  * @brief Build new base, uses template for the first base
1234  * @param[in] campaign The campaign data structure
1235  * @param[in] pos Position (on Geoscape) the base built at
1236  * @param[in] name The name of the new base, this string might already be in utf-8
1237  */
B_Build(const campaign_t * campaign,const vec2_t pos,const char * name)1238 base_t* B_Build (const campaign_t* campaign, const vec2_t pos, const char* name)
1239 {
1240 	if (!campaign)
1241 		cgi->Com_Error(ERR_DROP, "You can only build a base in an active campaign");
1242 
1243 	base_t* base = B_GetFirstUnfoundedBase();
1244 	if (!base)
1245 		cgi->Com_Error(ERR_DROP, "Cannot build more bases");
1246 
1247 	B_SetName(base, name);
1248 	Vector2Copy(pos, base->pos);
1249 
1250 	base->idx = ccs.campaignStats.basesBuilt;
1251 	base->founded = true;
1252 
1253 	/* increase this early because a lot of functions rely on this
1254 	 * value to get the base we are setting up here */
1255 	ccs.numBases++;
1256 
1257 	/* reset capacities */
1258 	for (int i = 0; i < MAX_CAP; i++) {
1259 		const baseCapacities_t cap = (baseCapacities_t)i;
1260 		CAP_SetCurrent(base, cap, 0);
1261 	}
1262 
1263 	/* setup for first base */
1264 	if (ccs.campaignStats.basesBuilt == 0) {
1265 		if (campaign->firstBaseTemplate[0] == '\0')
1266 			cgi->Com_Error(ERR_DROP, "No base template for setting up the first base given");
1267 		B_BuildFromTemplate(base, campaign->firstBaseTemplate, true);
1268 	} else {
1269 		B_BuildFromTemplate(base, nullptr, true);
1270 	}
1271 	base->baseStatus = BASE_WORKING;
1272 
1273 	/* a new base is not discovered (yet) */
1274 	base->alienInterest = BASE_INITIALINTEREST;
1275 
1276 	BDEF_InitialiseBaseSlots(base);
1277 
1278 	/* Reset Radar range */
1279 	const float level = B_GetMaxBuildingLevel(base, B_RADAR);
1280 	RADAR_Initialise(&base->radar, RADAR_BASERANGE, RADAR_BASETRACKINGRANGE, level, false);
1281 	RADAR_InitialiseUFOs(&base->radar);
1282 
1283 	B_ResetAllStatusAndCapacities(base, true);
1284 
1285 	PR_UpdateProductionCap(base);
1286 
1287 	ccs.campaignStats.basesBuilt++;
1288 
1289 	return base;
1290 }
1291 
1292 /**
1293  * @brief Returns the baseTemplate in the global baseTemplate list that has the unique name baseTemplateID.
1294  * @param[in] baseTemplateID The unique id of the building (baseTemplate_t->name).
1295  * @return baseTemplate_t If a Template was found it is returned, otherwise->nullptr.
1296  */
B_GetBaseTemplate(const char * baseTemplateID)1297 const baseTemplate_t* B_GetBaseTemplate (const char* baseTemplateID)
1298 {
1299 	if (!baseTemplateID)
1300 		return nullptr;
1301 
1302 	for (int i = 0; i < ccs.numBaseTemplates; i++)
1303 		if (Q_streq(ccs.baseTemplates[i].id, baseTemplateID))
1304 			return &ccs.baseTemplates[i];
1305 
1306 	Com_Printf("Base Template %s not found\n", baseTemplateID);
1307 	return nullptr;
1308 }
1309 
1310 /**
1311  * @brief Check a base cell
1312  * @return True if the cell is free to build
1313  */
B_MapIsCellFree(const base_t * base,int col,int row)1314 bool B_MapIsCellFree (const base_t* base, int col, int row)
1315 {
1316 	return col >= 0 && col < BASE_SIZE
1317 	 && row >= 0 && row < BASE_SIZE
1318 	 && B_GetBuildingAt(base, col, row) == nullptr
1319 	 && !B_IsTileBlocked(base, col, row);
1320 }
1321 
1322 /**
1323  * @brief Set the currently selected building.
1324  * @param[in,out] base The base to place the building in
1325  * @param[in] buildingTemplate The template of the building to place at the given location
1326  * @param[in] row Set building to row
1327  * @param[in] col Set building to col
1328  * @return building created in base (this is not a building template)
1329  */
B_SetBuildingByClick(base_t * base,const building_t * buildingTemplate,int row,int col)1330 building_t* B_SetBuildingByClick (base_t* base, const building_t* buildingTemplate, int row, int col)
1331 {
1332 #ifdef DEBUG
1333 	if (!base)
1334 		cgi->Com_Error(ERR_DROP, "no current base\n");
1335 	if (!buildingTemplate)
1336 		cgi->Com_Error(ERR_DROP, "no current building\n");
1337 #endif
1338 	if (!CP_CheckCredits(buildingTemplate->fixCosts)) {
1339 		CP_Popup(_("Notice"), _("Not enough credits to build this\n"));
1340 		return nullptr;
1341 	}
1342 
1343 	/* template should really be a template */
1344 	/*assert(template == template->tpl);*/
1345 
1346 	if (0 <= row && row < BASE_SIZE && 0 <= col && col < BASE_SIZE) {
1347 		/* new building in base (not a template) */
1348 		building_t* buildingNew = B_GetBuildingByIDX(base->idx, ccs.numBuildings[base->idx]);
1349 
1350 		/* copy building from template list to base-buildings-list */
1351 		*buildingNew = *buildingTemplate;
1352 		/* self-link to building-list in base */
1353 		buildingNew->idx = B_GetBuildingIDX(base, buildingNew);
1354 		/* Link to the base. */
1355 		buildingNew->base = base;
1356 
1357 		if (!B_IsTileBlocked(base, col, row) && B_GetBuildingAt(base, col, row) == nullptr) {
1358 			int y, x;
1359 
1360 			if (col + buildingNew->size[0] > BASE_SIZE)
1361 				return nullptr;
1362 			if (row + buildingNew->size[1] > BASE_SIZE)
1363 				return nullptr;
1364 			for (y = row; y < row + buildingNew->size[1]; y++)
1365 				for (x = col; x < col + buildingNew->size[0]; x++)
1366 					if (B_GetBuildingAt(base, x, y) != nullptr || B_IsTileBlocked(base, x, y))
1367 						return nullptr;
1368 			/* No building in this place */
1369 
1370 			/* set building position */
1371 			buildingNew->pos[0] = col;
1372 			buildingNew->pos[1] = row;
1373 
1374 			/* Refuse adding if it hurts coherency */
1375 			if (base->baseStatus == BASE_WORKING) {
1376 				linkedList_t* neighbours;
1377 				bool coherent = false;
1378 
1379 				neighbours = B_GetNeighbours(buildingNew);
1380 				LIST_Foreach(neighbours, building_t, bldg) {
1381 					if (B_IsBuildingBuiltUp(bldg)) {
1382 						coherent = true;
1383 						break;
1384 					}
1385 				}
1386 				cgi->LIST_Delete(&neighbours);
1387 
1388 				if (!coherent) {
1389 					CP_Popup(_("Notice"), _("You must build next to existing buildings."));
1390 					return nullptr;
1391 				}
1392 			}
1393 
1394 			/* set building position */
1395 			for (y = row; y < row + buildingNew->size[1]; y++)
1396 				for (x = col; x < col + buildingNew->size[0]; x++) {
1397 					assert(!B_IsTileBlocked(base, x, y));
1398 					base->map[y][x].building = buildingNew;
1399 				}
1400 
1401 			/* status and build (start) time */
1402 			buildingNew->buildingStatus = B_STATUS_UNDER_CONSTRUCTION;
1403 			buildingNew->timeStart = ccs.date;
1404 
1405 			/* pay */
1406 			CP_UpdateCredits(ccs.credits - buildingNew->fixCosts);
1407 			/* Update number of buildings on the base. */
1408 			ccs.numBuildings[base->idx]++;
1409 
1410 			B_BuildingStatus(buildingNew);
1411 			B_ResetBuildingCurrent(base);
1412 			cgi->Cmd_ExecuteString("base_init");
1413 			cgi->Cmd_ExecuteString("building_init");
1414 			B_FireEvent(buildingNew, base, B_ONCONSTRUCT);
1415 
1416 			return buildingNew;
1417 		}
1418 	}
1419 	return nullptr;
1420 }
1421 
1422 #define MAX_BUILDING_INFO_TEXT_LENGTH 512
1423 
1424 /**
1425  * @brief Draws a building.
1426  * @param[in] building The building to draw
1427  */
B_DrawBuilding(const building_t * building)1428 void B_DrawBuilding (const building_t* building)
1429 {
1430 	static char buildingText[MAX_BUILDING_INFO_TEXT_LENGTH];
1431 
1432 	/* maybe someone call this command before the buildings are parsed?? */
1433 	if (!building)
1434 		return;
1435 
1436 	buildingText[0] = '\0';
1437 
1438 	B_BuildingStatus(building);
1439 
1440 	Com_sprintf(buildingText, sizeof(buildingText), "%s\n", _(building->name));
1441 
1442 	if (building->buildingStatus < B_STATUS_UNDER_CONSTRUCTION && building->fixCosts)
1443 		Com_sprintf(buildingText, sizeof(buildingText), _("Costs:\t%i c\n"), building->fixCosts);
1444 
1445 	if (building->buildingStatus == B_STATUS_UNDER_CONSTRUCTION || building->buildingStatus == B_STATUS_NOT_SET)
1446 		Q_strcat(buildingText, sizeof(buildingText), ngettext("%i Day to build\n", "%i Days to build\n", building->buildTime), building->buildTime);
1447 
1448 	if (building->varCosts)
1449 		Q_strcat(buildingText, sizeof(buildingText), _("Running costs:\t%i c\n"), building->varCosts);
1450 
1451 	if (building->dependsBuilding)
1452 		Q_strcat(buildingText, sizeof(buildingText), _("Needs:\t%s\n"), _(building->dependsBuilding->name));
1453 
1454 	if (building->name)
1455 		cgi->Cvar_Set("mn_building_name", "%s", _(building->name));
1456 
1457 	if (building->image)
1458 		cgi->Cvar_Set("mn_building_image", "%s", building->image);
1459 	else
1460 		cgi->Cvar_Set("mn_building_image", "base/empty");
1461 
1462 	/* link into menu text array */
1463 	cgi->UI_RegisterText(TEXT_BUILDING_INFO, buildingText);
1464 }
1465 
1466 /**
1467  * @brief Counts the number of buildings of a particular type in a base.
1468  * @param[in] base Which base to count in.
1469  * @param[in] tpl The template type in the ccs.buildingTemplates list.
1470  * @return The number of buildings or -1 on error (e.g. base index out of range)
1471  */
B_GetNumberOfBuildingsInBaseByTemplate(const base_t * base,const building_t * tpl)1472 int B_GetNumberOfBuildingsInBaseByTemplate (const base_t* base, const building_t* tpl)
1473 {
1474 	if (!base) {
1475 		Com_Printf("B_GetNumberOfBuildingsInBaseByTemplate: No base given!\n");
1476 		return -1;
1477 	}
1478 
1479 	if (!tpl) {
1480 		Com_Printf("B_GetNumberOfBuildingsInBaseByTemplate: no building-type given!\n");
1481 		return -1;
1482 	}
1483 
1484 	/* Check if the template really is one. */
1485 	if (tpl != tpl->tpl) {
1486 		Com_Printf("B_GetNumberOfBuildingsInBaseByTemplate: No building-type given as parameter. It's probably a normal building!\n");
1487 		return -1;
1488 	}
1489 
1490 	int numberOfBuildings = 0;
1491 	building_t* building = nullptr;
1492 	while ((building = B_GetNextBuilding(base, building))) {
1493 		if (building->tpl == tpl && building->buildingStatus != B_STATUS_NOT_SET)
1494 			numberOfBuildings++;
1495 	}
1496 	return numberOfBuildings;
1497 }
1498 
1499 /**
1500  * @brief Counts the number of buildings of a particular building type in a base.
1501  * @param[in] base Which base to count in.
1502  * @param[in] buildingType Building type value.
1503  * @return The number of buildings or -1 on error (e.g. base index out of range)
1504  */
B_GetNumberOfBuildingsInBaseByBuildingType(const base_t * base,const buildingType_t buildingType)1505 int B_GetNumberOfBuildingsInBaseByBuildingType (const base_t* base, const buildingType_t buildingType)
1506 {
1507 	if (!base) {
1508 		Com_Printf("B_GetNumberOfBuildingsInBaseByBuildingType: No base given!\n");
1509 		return -1;
1510 	}
1511 
1512 	if (buildingType >= MAX_BUILDING_TYPE) {
1513 		Com_Printf("B_GetNumberOfBuildingsInBaseByBuildingType: no sane building-type given!\n");
1514 		return -1;
1515 	}
1516 
1517 	int numberOfBuildings = 0;
1518 	building_t* building = nullptr;
1519 	while ((building = B_GetNextBuildingByType(base, building, buildingType)))
1520 		if (building->buildingStatus != B_STATUS_NOT_SET)
1521 			numberOfBuildings++;
1522 
1523 	return numberOfBuildings;
1524 }
1525 
1526 /**
1527  * @brief Gets a building of a given type in the given base
1528  * @param[in] base The base to search the building in
1529  * @param[in] buildingType What building-type to get.
1530  * @param[in] onlyWorking If we're looking only for working buildings
1531  * @return The building or nullptr if base has no building of that type
1532  */
B_GetBuildingInBaseByType(const base_t * base,buildingType_t buildingType,bool onlyWorking)1533 const building_t* B_GetBuildingInBaseByType (const base_t* base, buildingType_t buildingType, bool onlyWorking)
1534 {
1535 	/* we maybe only want to get the working building (e.g. it might the
1536 	 * case that we don't have a powerplant and thus the searched building
1537 	 * is not functional) */
1538 	if (onlyWorking && !B_GetBuildingStatus(base, buildingType))
1539 		return nullptr;
1540 
1541 	building_t* building = nullptr;
1542 	while ((building = B_GetNextBuildingByType(base, building, buildingType)))
1543 		return building;
1544 
1545 	return nullptr;
1546 }
1547 
1548 /**
1549  * @brief Reads a base layout template
1550  * @param[in] name The script id of the base template
1551  * @param[in] text The script block to parse
1552  * @sa CL_ParseScriptFirst
1553  */
B_ParseBaseTemplate(const char * name,const char ** text)1554 void B_ParseBaseTemplate (const char* name, const char** text)
1555 {
1556 	const char* errhead = "B_ParseBaseTemplate: unexpected end of file (names ";
1557 	const char* token;
1558 	baseTemplate_t* baseTemplate;
1559 	vec2_t pos;
1560 	bool map[BASE_SIZE][BASE_SIZE];
1561 	byte buildingNums[MAX_BUILDINGS];
1562 
1563 	/* get token */
1564 	token = Com_Parse(text);
1565 
1566 	if (!*text || *token != '{') {
1567 		Com_Printf("B_ParseBaseTemplate: Template \"%s\" without body ignored\n", name);
1568 		return;
1569 	}
1570 
1571 	if (ccs.numBaseTemplates >= MAX_BASETEMPLATES)
1572 		cgi->Com_Error(ERR_DROP, "B_ParseBaseTemplate: too many base templates");
1573 
1574 	/* create new Template */
1575 	baseTemplate = &ccs.baseTemplates[ccs.numBaseTemplates];
1576 	baseTemplate->id = Mem_PoolStrDup(name, cp_campaignPool, 0);
1577 
1578 	/* clear map for checking duplicate positions and buildingNums for checking moreThanOne constraint */
1579 	OBJZERO(map);
1580 	OBJZERO(buildingNums);
1581 
1582 	ccs.numBaseTemplates++;
1583 
1584 	do {
1585 		/* get the building */
1586 		token = cgi->Com_EParse(text, errhead, baseTemplate->id);
1587 		if (!*text)
1588 			break;
1589 		if (*token == '}')
1590 			break;
1591 
1592 		if (!Q_streq(token, "building")) {
1593 			cgi->Com_Error(ERR_DROP, "B_ParseBaseTemplate: \"building\" token expected but \"%s\" found", token);
1594 		}
1595 
1596 		linkedList_t* list;
1597 		if (!Com_ParseList(text, &list)) {
1598 			cgi->Com_Error(ERR_DROP, "B_ParseBaseTemplate: error while reading building tuple");
1599 		}
1600 
1601 		if (cgi->LIST_Count(list) != 2) {
1602 			cgi->Com_Error(ERR_DROP, "B_ParseBaseTemplate: building tuple must contains 2 elements (id pos)");
1603 		}
1604 
1605 		const char* buildingToken = (char*)list->data;
1606 		const char* positionToken = (char*)list->next->data;
1607 
1608 		if (baseTemplate->numBuildings >= MAX_BASEBUILDINGS)
1609 			cgi->Com_Error(ERR_DROP, "B_ParseBaseTemplate: too many buildings");
1610 
1611 		/* check if building type is known */
1612 		baseBuildingTile_t* tile = &baseTemplate->buildings[baseTemplate->numBuildings];
1613 		baseTemplate->numBuildings++;
1614 
1615 		for (int i = 0; i < ccs.numBuildingTemplates; i++)
1616 			if (Q_streq(ccs.buildingTemplates[i].id, buildingToken)) {
1617 				tile->building = &ccs.buildingTemplates[i];
1618 				if (tile->building->maxCount >= 0 && tile->building->maxCount <= buildingNums[i])
1619 					cgi->Com_Error(ERR_DROP, "B_ParseBaseTemplate: Found more %s than allowed in template %s (%d))", buildingToken, baseTemplate->id, tile->building->maxCount);
1620 				buildingNums[i]++;
1621 				break;
1622 			}
1623 
1624 		if (!tile->building)
1625 			cgi->Com_Error(ERR_DROP, "B_ParseBaseTemplate: Could not find building with id %s\n", baseTemplate->id);
1626 
1627 		/* get the position */
1628 		cgi->Com_EParseValue(pos, positionToken, V_POS, 0, sizeof(vec2_t));
1629 		tile->posX = pos[0];
1630 		tile->posY = pos[1];
1631 		if (tile->posX < 0 || tile->posX >= BASE_SIZE || tile->posY < 0 || tile->posY >= BASE_SIZE)
1632 			cgi->Com_Error(ERR_DROP, "Invalid template coordinates for building %s in template %s given",
1633 					tile->building->id, baseTemplate->id);
1634 
1635 		/* check for buildings on same position */
1636 		if (map[tile->posY][tile->posX])
1637 			cgi->Com_Error(ERR_DROP, "Base template '%s' has ambiguous positions for buildings set.", baseTemplate->id);
1638 		map[tile->posY][tile->posX] = true;
1639 
1640 		cgi->LIST_Delete(&list);
1641 	} while (*text);
1642 
1643 	/* templates without the must-have buildings can't be used */
1644 	for (int i = 0; i < ccs.numBuildingTemplates; i++) {
1645 		const building_t* building = &ccs.buildingTemplates[i];
1646 		if (building && building->mandatory && !buildingNums[i]) {
1647 			cgi->Com_Error(ERR_DROP, "Every base template needs one '%s'! '%s' has none.", building->id, baseTemplate->id);
1648 		}
1649 	}
1650 }
1651 
1652 /**
1653  * @brief Get the first unfounded base
1654  * @return first unfounded base or nullptr if every available base slot is already filled
1655  */
B_GetFirstUnfoundedBase(void)1656 base_t* B_GetFirstUnfoundedBase (void)
1657 {
1658 	int baseIdx;
1659 
1660 	for (baseIdx = 0; baseIdx < MAX_BASES; baseIdx++) {
1661 		base_t* base = B_GetBaseByIDX(baseIdx);
1662 		if (!base->founded)
1663 			return base;
1664 	}
1665 
1666 	return nullptr;
1667 }
1668 
1669 /**
1670  * @brief Sets the selected base
1671  * @param[in] base The base that is going to be selected
1672  * @sa B_SelectBase
1673  */
B_SetCurrentSelectedBase(const base_t * base)1674 void B_SetCurrentSelectedBase (const base_t* base)
1675 {
1676 	base_t* b = nullptr;
1677 	while ((b = B_GetNext(b)) != nullptr) {
1678 		if (b == base) {
1679 			b->selected = true;
1680 			if (b->aircraftCurrent == nullptr)
1681 				b->aircraftCurrent = AIR_GetFirstFromBase(b);
1682 		} else
1683 			b->selected = false;
1684 	}
1685 
1686 	if (base) {
1687 		INS_SetCurrentSelectedInstallation(nullptr);
1688 		cgi->Cvar_Set("mn_base_title", "%s", base->name);
1689 		cgi->Cvar_SetValue("mn_base_status_id", base->baseStatus);
1690 	} else {
1691 		cgi->Cvar_Set("mn_base_title", "");
1692 		cgi->Cvar_Set("mn_base_status_id", "");
1693 	}
1694 }
1695 
1696 /**
1697  * @brief returns the currently selected base
1698  */
B_GetCurrentSelectedBase(void)1699 base_t* B_GetCurrentSelectedBase (void)
1700 {
1701 	base_t* base = nullptr;
1702 	while ((base = B_GetNext(base)) != nullptr)
1703 		if (base->selected)
1704 			return base;
1705 
1706 	return nullptr;
1707 }
1708 
1709 /**
1710  * @brief Select and opens a base
1711  * @param[in] base If this is @c nullptr we want to build a new base
1712  */
B_SelectBase(const base_t * base)1713 void B_SelectBase (const base_t* base)
1714 {
1715 	/* set up a new base */
1716 	if (!base) {
1717 		/* if player hit the "create base" button while creating base mode is enabled
1718 		 * that means that player wants to quit this mode */
1719 		if (ccs.mapAction == MA_NEWBASE) {
1720 			GEO_ResetAction();
1721 			return;
1722 		}
1723 
1724 		if (B_GetCount() < MAX_BASES) {
1725 			/* show radar overlay (if not already displayed) */
1726 			if (!GEO_IsRadarOverlayActivated())
1727 				GEO_SetOverlay("radar");
1728 			ccs.mapAction = MA_NEWBASE;
1729 		} else {
1730 			ccs.mapAction = MA_NONE;
1731 		}
1732 	} else {
1733 		Com_DPrintf(DEBUG_CLIENT, "B_SelectBase_f: select base with id %i\n", base->idx);
1734 		ccs.mapAction = MA_NONE;
1735 		cgi->UI_PushWindow("bases");
1736 		B_SetCurrentSelectedBase(base);
1737 	}
1738 }
1739 
1740 /**
1741  * @brief Swaps one skill from character1 to character 2 and vice versa.
1742  */
CL_SwapSkill(character_t * cp1,character_t * cp2,abilityskills_t skill)1743 static void CL_SwapSkill (character_t* cp1, character_t* cp2, abilityskills_t skill)
1744 {
1745 	int tmp1, tmp2;
1746 	tmp1 = cp1->score.skills[skill];
1747 	tmp2 = cp2->score.skills[skill];
1748 	cp1->score.skills[skill] = tmp2;
1749 	cp2->score.skills[skill] = tmp1;
1750 
1751 	tmp1 = cp1->score.initialSkills[skill];
1752 	tmp2 = cp2->score.initialSkills[skill];
1753 	cp1->score.initialSkills[skill] = tmp2;
1754 	cp2->score.initialSkills[skill] = tmp1;
1755 
1756 	tmp1 = cp1->score.experience[skill];
1757 	tmp2 = cp2->score.experience[skill];
1758 	cp1->score.experience[skill] = tmp2;
1759 	cp2->score.experience[skill] = tmp1;
1760 }
1761 
CL_DoSwapSkills(character_t * cp1,character_t * cp2,const abilityskills_t skill)1762 static void CL_DoSwapSkills (character_t* cp1, character_t* cp2, const abilityskills_t skill)
1763 {
1764 	if (cp1->score.skills[skill] < cp2->score.skills[skill])
1765 		CL_SwapSkill(cp1, cp2, skill);
1766 
1767 	switch (skill) {
1768 	case SKILL_CLOSE:
1769 		if (cp1->score.skills[ABILITY_SPEED] < cp2->score.skills[ABILITY_SPEED])
1770 			CL_SwapSkill(cp1, cp2, ABILITY_SPEED);
1771 		break;
1772 	case SKILL_HEAVY:
1773 		if (cp1->score.skills[ABILITY_POWER] < cp2->score.skills[ABILITY_POWER])
1774 			CL_SwapSkill(cp1, cp2, ABILITY_POWER);
1775 		break;
1776 	case SKILL_ASSAULT:
1777 		/* no related basic attribute */
1778 		break;
1779 	case SKILL_SNIPER:
1780 		if (cp1->score.skills[ABILITY_ACCURACY] < cp2->score.skills[ABILITY_ACCURACY])
1781 			CL_SwapSkill(cp1, cp2, ABILITY_ACCURACY);
1782 		break;
1783 	case SKILL_EXPLOSIVE:
1784 		if (cp1->score.skills[ABILITY_MIND] < cp2->score.skills[ABILITY_MIND])
1785 			CL_SwapSkill(cp1, cp2, ABILITY_MIND);
1786 		break;
1787 	default:
1788 		cgi->Com_Error(ERR_DROP, "CL_SwapSkills: illegal skill %i.\n", skill);
1789 	}
1790 }
1791 
1792 /**
1793  * @brief Assembles a skill indicator for the given character and its wore weapons in correlation to the given skill
1794  * @param[in] chr The character to get the skill indicator for
1795  * @param[in] skill The skill to get the indicator for
1796  * @return -1 if the given character does not wear the needed weapons, otherwise a weighted indicator that tell us
1797  * how good the weapons fit for the given skill.
1798  * @todo This currently always uses exactly the first two firemodes (see fmode1+fmode2) for calculation. This needs to be adapted to support less (1) or more 3+ firemodes. I think the function will even  break on only one firemode .. never tested it.
1799  * @todo i think currently also the different ammo/firedef types for each weapon (different weaponr_fd_idx and weaponr_fd_idx values) are ignored.
1800  */
CL_GetSkillIndicator(const character_t * chr,abilityskills_t skill)1801 static int CL_GetSkillIndicator (const character_t* chr, abilityskills_t skill)
1802 {
1803 	const fireDef_t* fdRight = nullptr;
1804 	const fireDef_t* fdHolster = nullptr;
1805 	const Item* rightHand = chr->inv.getRightHandContainer();
1806 	const Item* holster = chr->inv.getHolsterContainer();
1807 
1808 	if (rightHand && rightHand->ammoDef() && rightHand->def())
1809 		fdRight = rightHand->getFiredefs();
1810 	if (holster && holster->ammoDef() && holster->def())
1811 		fdHolster = holster->getFiredefs();
1812 	/* disregard left hand, or dual-wielding guys are too good */
1813 
1814 	if (fdHolster == nullptr)
1815 		return -1;
1816 	if (fdRight == nullptr)
1817 		return -1;
1818 
1819 	const byte fmode1 = 0;
1820 	const byte fmode2 = 1;
1821 	int no = 0;
1822 	if (rightHand != nullptr) {
1823 		const fireDef_t* fd = rightHand->ammoDef()->fd[fdRight->weapFdsIdx];
1824 		no += 2 * (skill == fd[fmode1].weaponSkill);
1825 		no += 2 * (skill == fd[fmode2].weaponSkill);
1826 	}
1827 	if (holster != nullptr && holster->isReloadable()) {
1828 		const fireDef_t* fd = holster->ammoDef()->fd[fdHolster->weapFdsIdx];
1829 		no += (skill == fd[fmode1].weaponSkill);
1830 		no += (skill == fd[fmode2].weaponSkill);
1831 	}
1832 	return no;
1833 }
1834 
1835 /**
1836  * @brief Swaps skills of the initial team of soldiers so that they match inventories
1837  */
CL_SwapSkills(linkedList_t * team)1838 static void CL_SwapSkills (linkedList_t* team)
1839 {
1840 	const int teamSize = cgi->LIST_Count(team);
1841 
1842 	for (int i = 0; i < teamSize; i++) {
1843 		/* running the loops below is not enough, we need transitive closure */
1844 		/* I guess num times is enough --- could anybody prove this? */
1845 		/* or perhaps 2 times is enough as long as weapons have 1 skill? */
1846 		for (int x = ABILITY_NUM_TYPES; x < SKILL_NUM_TYPES; x++) {
1847 			const abilityskills_t skill = (abilityskills_t)x;
1848 			LIST_Foreach(team, character_t, cp1) {
1849 				if (cp1__iter == nullptr)
1850 					continue;
1851 
1852 				const int no1 = CL_GetSkillIndicator(cp1, skill);
1853 				if (no1 == -1)
1854 					continue;
1855 
1856 				LIST_Foreach(cp1__iter->next, character_t, cp2) {
1857 					const int no2 = CL_GetSkillIndicator(cp2, skill);
1858 					if (no2 == -1)
1859 						continue;
1860 
1861 					if (no1 > no2 /* more use of this skill */
1862 						 || (no1 && no1 == no2)) { /* or earlier on list */
1863 						CL_DoSwapSkills(cp1, cp2, skill);
1864 					} else if (no1 < no2) {
1865 						CL_DoSwapSkills(cp2, cp1, skill);
1866 					}
1867 				}
1868 			}
1869 		}
1870 	}
1871 }
1872 
1873 /**
1874  * @brief Prepares initial equipment for initial team the beginning of the campaign.
1875  * @param[in,out] aircraft aircraft on which the soldiers (to equip) are
1876  * @param[in] ed Initial equipment definition
1877  */
B_InitialEquipment(aircraft_t * aircraft,const equipDef_t * ed)1878 static void B_InitialEquipment (aircraft_t* aircraft, const equipDef_t* ed)
1879 {
1880 	base_t* homebase;
1881 	linkedList_t* chrListTemp = nullptr;
1882 
1883 	assert(aircraft);
1884 	homebase = aircraft->homebase;
1885 	assert(homebase);
1886 	assert(ed);
1887 
1888 	LIST_Foreach(aircraft->acTeam, Employee, employee) {
1889 		character_t* chr = &employee->chr;
1890 
1891 		/* pack equipment */
1892 		Com_DPrintf(DEBUG_CLIENT, "B_InitialEquipment: Packing initial equipment for %s.\n", chr->name);
1893 		cgi->INV_EquipActor(chr, ed, cgi->GAME_GetChrMaxLoad(chr));
1894 		cgi->LIST_AddPointer(&chrListTemp, (void*)chr);
1895 	}
1896 
1897 	AIR_MoveEmployeeInventoryIntoStorage(*aircraft, homebase->storage);
1898 	CL_SwapSkills(chrListTemp);
1899 	cgi->LIST_Delete(&chrListTemp);
1900 	CAP_UpdateStorageCap(homebase);
1901 }
1902 
1903 /**
1904  * @brief Sets the baseStatus to BASE_NOT_USED
1905  * @param[in] base Which base should be reseted?
1906  * @sa CL_CampaignRemoveMission
1907  */
B_BaseResetStatus(base_t * const base)1908 void B_BaseResetStatus (base_t* const base)
1909 {
1910 	assert(base);
1911 	base->baseStatus = BASE_NOT_USED;
1912 	if (ccs.mapAction == MA_BASEATTACK)
1913 		ccs.mapAction = MA_NONE;
1914 }
1915 
1916 #ifdef DEBUG
1917 /**
1918  * @brief Just lists all buildings with their data
1919  * @note called with debug_listbuilding
1920  * @note Just for debugging purposes - not needed in game
1921  */
B_BuildingList_f(void)1922 static void B_BuildingList_f (void)
1923 {
1924 	base_t* base = nullptr;
1925 	while ((base = B_GetNext(base)) != nullptr) {
1926 		Com_Printf("\nBase id %i: %s\n", base->idx, base->name);
1927 		/** @todo building count should not depend on base->idx. base->idx will not be an array index! */
1928 		for (int j = 0; j < ccs.numBuildings[base->idx]; j++) {
1929 			const building_t* building = B_GetBuildingByIDX(base->idx, j);
1930 
1931 			Com_Printf("...Building: %s #%i - id: %i\n", building->id,
1932 				B_GetNumberOfBuildingsInBaseByTemplate(base, building->tpl), base->idx);
1933 			Com_Printf("...image: %s\n", building->image);
1934 			Com_Printf(".....Status:\n");
1935 
1936 			for (int k = 0; k < BASE_SIZE * BASE_SIZE; k++) {
1937 				if (k > 1 && k % BASE_SIZE == 0)
1938 					Com_Printf("\n");
1939 				Com_Printf("%i ", building->buildingStatus);
1940 				if (!building->buildingStatus)
1941 					break;
1942 			}
1943 			Com_Printf("\n");
1944 		}
1945 	}
1946 }
1947 
1948 /**
1949  * @brief Just lists all bases with their data
1950  * @note called with debug_listbase
1951  * @note Just for debugging purposes - not needed in game
1952  */
B_BaseList_f(void)1953 static void B_BaseList_f (void)
1954 {
1955 	base_t* base = nullptr;
1956 	while ((base = B_GetNext(base)) != nullptr) {
1957 		if (!base->founded) {
1958 			Com_Printf("Base idx %i not founded\n\n", base->idx);
1959 			continue;
1960 		}
1961 
1962 		Com_Printf("Base idx %i\n", base->idx);
1963 		Com_Printf("Base name %s\n", base->name);
1964 		Com_Printf("Base founded %i\n", base->founded);
1965 		Com_Printf("Base numMissileBattery %i\n", base->numBatteries);
1966 		Com_Printf("Base numLaserBattery %i\n", base->numLasers);
1967 		Com_Printf("Base radarRange %i\n", base->radar.range);
1968 		Com_Printf("Base trackingRange %i\n", base->radar.trackingRange);
1969 		Com_Printf("Base numSensoredAircraft %i\n", base->radar.numUFOs);
1970 		Com_Printf("Base Alien interest %f\n", base->alienInterest);
1971 		Com_Printf("Base hasBuilding[]:\n");
1972 		Com_Printf("Misc  Lab Quar Stor Work Hosp Hang Cont SHgr UHgr SUHg Powr  cgi->Cmd AMtr Entr Miss Lasr  Rdr Team\n");
1973 		for (int j = 0; j < MAX_BUILDING_TYPE; j++) {
1974 			const buildingType_t type = (buildingType_t)j;
1975 			Com_Printf("  %i  ", B_GetBuildingStatus(base, type));
1976 		}
1977 		Com_Printf("\n");
1978 		Com_Printf("Base pos %.02f:%.02f\n", base->pos[0], base->pos[1]);
1979 		Com_Printf("Base map:\n");
1980 		for (int row = 0; row < BASE_SIZE; row++) {
1981 			if (row > 0)
1982 				Com_Printf("\n");
1983 			for (int col = 0; col < BASE_SIZE; col++)
1984 				Com_Printf("%2i (%3i: %3i)  ", (base->map[row][col].building ? base->map[row][col].building->idx : -1),
1985 					base->map[row][col].posX, base->map[row][col].posY);
1986 		}
1987 		Com_Printf("\n\n");
1988 	}
1989 }
1990 #endif
1991 
1992 /**
1993  * @brief Checks whether the building menu or the pedia entry should be called
1994  * when you click a building in the baseview
1995  * @param[in] building The building we have clicked
1996  */
B_BuildingOpenAfterClick(const building_t * building)1997 void B_BuildingOpenAfterClick (const building_t* building)
1998 {
1999 	const base_t* base = building->base;
2000 	if (!B_GetBuildingStatus(base, building->buildingType)) {
2001 		UP_OpenWith(building->pedia);
2002 		return;
2003 	}
2004 
2005 	switch (building->buildingType) {
2006 	case B_LAB:
2007 		if (RS_ResearchAllowed(base))
2008 			cgi->UI_PushWindow("research");
2009 		else
2010 			UP_OpenWith(building->pedia);
2011 		break;
2012 	case B_HOSPITAL:
2013 		if (HOS_HospitalAllowed(base))
2014 			cgi->UI_PushWindow("hospital");
2015 		else
2016 			UP_OpenWith(building->pedia);
2017 		break;
2018 	case B_ALIEN_CONTAINMENT:
2019 		if (AC_ContainmentAllowed(base))
2020 			cgi->UI_PushWindow("aliencont");
2021 		else
2022 			UP_OpenWith(building->pedia);
2023 		break;
2024 	case B_QUARTERS:
2025 		if (E_HireAllowed(base))
2026 			cgi->UI_PushWindow("employees");
2027 		else
2028 			UP_OpenWith(building->pedia);
2029 		break;
2030 	case B_WORKSHOP:
2031 		if (PR_ProductionAllowed(base))
2032 			cgi->UI_PushWindow("production");
2033 		else
2034 			UP_OpenWith(building->pedia);
2035 		break;
2036 	case B_DEFENCE_LASER:
2037 	case B_DEFENCE_MISSILE:
2038 		cgi->UI_PushWindow("basedefence");
2039 		break;
2040 	case B_HANGAR:
2041 	case B_SMALL_HANGAR:
2042 		if (!AIR_AircraftAllowed(base)) {
2043 			UP_OpenWith(building->pedia);
2044 		} else if (AIR_BaseHasAircraft(base)) {
2045 			cgi->UI_PushWindow("aircraft");
2046 		} else {
2047 			cgi->UI_PushWindow("buyaircraft");
2048 			/* transfer is only possible when there are at least two bases */
2049 			if (B_GetCount() > 1)
2050 				CP_Popup(_("Note"), _("No aircraft in this base - You first have to purchase or transfer an aircraft\n"));
2051 			else
2052 				CP_Popup(_("Note"), _("No aircraft in this base - You first have to purchase an aircraft\n"));
2053 		}
2054 		break;
2055 	case B_STORAGE:
2056 		if (BS_BuySellAllowed(base))
2057 			cgi->UI_PushWindow("market");
2058 		else
2059 			UP_OpenWith(building->pedia);
2060 		break;
2061 	case B_ANTIMATTER:
2062 		CP_Popup(_("Information"), "%s %d/%d", _("Antimatter (current/max):"), CAP_GetCurrent(base, CAP_ANTIMATTER), CAP_GetMax(base, CAP_ANTIMATTER));
2063 		break;
2064 	default:
2065 		UP_OpenWith(building->pedia);
2066 		break;
2067 	}
2068 }
2069 
2070 #ifdef DEBUG
2071 /**
2072  * @brief Debug function for printing all capacities in given base.
2073  * @note called with debug_listcapacities
2074  */
B_PrintCapacities_f(void)2075 static void B_PrintCapacities_f (void)
2076 {
2077 	if (cgi->Cmd_Argc() < 2) {
2078 		Com_Printf("Usage: %s <baseID>\n", cgi->Cmd_Argv(0));
2079 		return;
2080 	}
2081 
2082 	const int baseIdx = atoi(cgi->Cmd_Argv(1));
2083 	if (baseIdx >= B_GetCount()) {
2084 		Com_Printf("invalid baseID (%s)\n", cgi->Cmd_Argv(1));
2085 		return;
2086 	}
2087 	base_t* base = B_GetBaseByIDX(baseIdx);
2088 	for (int i = 0; i < MAX_CAP; i++) {
2089 		const baseCapacities_t cap = (baseCapacities_t)i;
2090 		const buildingType_t buildingType = B_GetBuildingTypeByCapacity(cap);
2091 		if (buildingType >= MAX_BUILDING_TYPE) {
2092 			Com_Printf("B_PrintCapacities_f: Could not find building associated with capacity %i\n", i);
2093 			continue;
2094 		}
2095 		for (int j = 0; j < ccs.numBuildingTemplates; j++) {
2096 			if (ccs.buildingTemplates[j].buildingType != buildingType)
2097 				continue;
2098 
2099 			Com_Printf("Building: %s, capacity max: %i, capacity cur: %i\n",
2100 					ccs.buildingTemplates[j].id, CAP_GetMax(base, i), CAP_GetCurrent(base, cap));
2101 			break;
2102 		}
2103 	}
2104 }
2105 
2106 /**
2107  * @brief Finishes the construction of every building in the base
2108  */
B_BuildingConstructionFinished_f(void)2109 static void B_BuildingConstructionFinished_f (void)
2110 {
2111 	base_t* base = B_GetCurrentSelectedBase();
2112 
2113 	if (!base)
2114 		return;
2115 
2116 	for (int i = 0; i < ccs.numBuildings[base->idx]; i++) {
2117 		building_t* building = B_GetBuildingByIDX(base->idx, i);
2118 		if (building->buildingStatus != B_STATUS_UNDER_CONSTRUCTION)
2119 			continue;
2120 
2121 		B_UpdateAllBaseBuildingStatus(building, B_STATUS_WORKING);
2122 		building->timeStart.day = 0;
2123 		building->timeStart.sec = 0;
2124 		base->buildingCurrent = building;
2125 		B_FireEvent(building, base, B_ONENABLE);
2126 	}
2127 	/* update menu */
2128 	B_SelectBase(base);
2129 }
2130 
2131 /**
2132  * @brief Recalculate status and capacities
2133  * @note function called with debug_basereset or after loading
2134  */
B_ResetAllStatusAndCapacities_f(void)2135 static void B_ResetAllStatusAndCapacities_f (void)
2136 {
2137 	base_t* base;
2138 
2139 	base = nullptr;
2140 	while ((base = B_GetNext(base)) != nullptr) {
2141 		/* set buildingStatus[] and capacities.max values */
2142 		B_ResetAllStatusAndCapacities(base, false);
2143 	}
2144 }
2145 
2146 /**
2147  * @brief Check base coherency
2148  */
B_CheckCoherency_f(void)2149 static void B_CheckCoherency_f (void)
2150 {
2151 	base_t* base;
2152 
2153 	if (cgi->Cmd_Argc() >= 2) {
2154 		const int i = atoi(cgi->Cmd_Argv(1));
2155 
2156 		if (i < 0 || i >= B_GetCount()) {
2157 			Com_Printf("Usage: %s [baseIdx]\nWithout baseIdx the current base is selected.\n", cgi->Cmd_Argv(0));
2158 			return;
2159 		}
2160 		base = B_GetFoundedBaseByIDX(i);
2161 	} else {
2162 		base = B_GetCurrentSelectedBase();
2163 	}
2164 
2165 	if (base)
2166 		Com_Printf("Base '%s' (idx:%i) is %scoherent.\n", base->name, base->idx, (B_IsCoherent(base)) ? "" : "not ");
2167 	else
2168 		Com_Printf("No base selected.\n");
2169 }
2170 #endif
2171 
2172 /**
2173  * @brief Resets console commands.
2174  */
B_InitStartup(void)2175 void B_InitStartup (void)
2176 {
2177 #ifdef DEBUG
2178 	cgi->Cmd_AddCommand("debug_listbase", B_BaseList_f, "Print base information to the game console");
2179 	cgi->Cmd_AddCommand("debug_listbuilding", B_BuildingList_f, "Print building information to the game console");
2180 	cgi->Cmd_AddCommand("debug_listcapacities", B_PrintCapacities_f, "Debug function to show all capacities in given base");
2181 	cgi->Cmd_AddCommand("debug_basereset", B_ResetAllStatusAndCapacities_f, "Reset building status and capacities of all bases");
2182 	cgi->Cmd_AddCommand("debug_destroybase", B_Destroy_f, "Destroy a base");
2183 	cgi->Cmd_AddCommand("debug_buildingfinished", B_BuildingConstructionFinished_f, "Finish construction for every building in the current base");
2184 	cgi->Cmd_AddCommand("debug_baseiscoherent", B_CheckCoherency_f, "Checks if all buildings are connected on a base");
2185 #endif
2186 }
2187 
2188 /**
2189  * @brief Checks whether the construction of a building is finished.
2190  * Calls the onEnable functions and assign workers, too.
2191  */
B_CheckBuildingConstruction(building_t * building)2192 static bool B_CheckBuildingConstruction (building_t* building)
2193 {
2194 	if (building->buildingStatus != B_STATUS_UNDER_CONSTRUCTION)
2195 		return false;
2196 
2197 	if (!B_IsBuildingBuiltUp(building))
2198 		return false;
2199 
2200 	base_t* base = building->base;
2201 	base->buildingCurrent = building;
2202 
2203 	B_UpdateAllBaseBuildingStatus(building, B_STATUS_WORKING);
2204 	if (B_FireEvent(building, base, B_ONENABLE))
2205 		Com_DPrintf(DEBUG_CLIENT, "B_CheckBuildingConstruction: %s %i;\n", building->onEnable, base->idx);
2206 
2207 	cgi->Cmd_ExecuteString("building_init");
2208 
2209 	return true;
2210 }
2211 
2212 /**
2213  * @brief Updates base data
2214  * @sa CP_CampaignRun
2215  * @note called every "day"
2216  * @sa AIRFIGHT_ProjectileHitsBase
2217  */
B_UpdateBaseData(void)2218 void B_UpdateBaseData (void)
2219 {
2220 	base_t* base = nullptr;
2221 	while ((base = B_GetNext(base)) != nullptr) {
2222 		building_t* building = nullptr;
2223 		while ((building = B_GetNextBuilding(base, building))) {
2224 			if (!B_CheckBuildingConstruction(building))
2225 				continue;
2226 
2227 			Com_sprintf(cp_messageBuffer, lengthof(cp_messageBuffer),
2228 					_("Construction of %s building finished in %s."), _(building->name), base->name);
2229 			MSO_CheckAddNewMessage(NT_BUILDING_FINISHED,_("Building finished"), cp_messageBuffer);
2230 		}
2231 	}
2232 }
2233 
2234 /**
2235  * @brief Sell items to the market or add them to base storage.
2236  * @param[in] aircraft Pointer to an aircraft landing in base.
2237  * @sa B_AircraftReturnedToHomeBase
2238  */
B_SellOrAddItems(aircraft_t * aircraft)2239 static void B_SellOrAddItems (aircraft_t* aircraft)
2240 {
2241 	int numitems = 0;
2242 	int gained = 0;
2243 
2244 	assert(aircraft);
2245 	base_t* base = aircraft->homebase;
2246 	assert(base);
2247 
2248 	itemsTmp_t* cargo = aircraft->itemcargo;
2249 
2250 	for (int i = 0; i < aircraft->itemTypes; i++) {
2251 		const objDef_t* item = cargo[i].item;
2252 		const int amount = cargo[i].amount;
2253 		technology_t* tech = RS_GetTechForItem(item);
2254 		/* If the related technology is NOT researched, don't sell items. */
2255 		if (!RS_IsResearched_ptr(tech)) {
2256 			/* Items not researched cannot be thrown out even if not enough space in storage. */
2257 			B_AddToStorage(base, item, amount);
2258 			if (amount > 0)
2259 				RS_MarkCollected(tech);
2260 			continue;
2261 		} else {
2262 			/* If the related technology is researched, check the autosell option. */
2263 			if (ccs.eMarket.autosell[item->idx]) { /* Sell items if autosell is enabled. */
2264 				BS_SellItem(item, nullptr, amount);
2265 				gained += BS_GetItemSellingPrice(item) * amount;
2266 				numitems += amount;
2267 			} else {
2268 				B_AddToStorage(base, item, amount);
2269 			}
2270 			continue;
2271 		}
2272 	}
2273 
2274 	if (numitems > 0) {
2275 		Com_sprintf(cp_messageBuffer, lengthof(cp_messageBuffer), _("By selling %s you gathered %i credits."),
2276 			va(ngettext("%i collected item", "%i collected items", numitems), numitems), gained);
2277 		MS_AddNewMessage(_("Notice"), cp_messageBuffer);
2278 	}
2279 
2280 	/* ship no longer has cargo aboard */
2281 	aircraft->itemTypes = 0;
2282 
2283 	/* Mark new technologies researchable. */
2284 	RS_MarkResearchable(aircraft->homebase);
2285 	/* Recalculate storage capacity, to fix wrong capacity if a soldier drops something on the ground */
2286 	CAP_UpdateStorageCap(aircraft->homebase);
2287 }
2288 
2289 /**
2290  * @brief Will unload all cargo to the homebase
2291  * @param[in,out] aircraft The aircraft to dump
2292  */
B_DumpAircraftToHomeBase(aircraft_t * aircraft)2293 void B_DumpAircraftToHomeBase (aircraft_t* aircraft)
2294 {
2295 	/* Don't call cargo functions if aircraft is not a transporter. */
2296 	if (aircraft->type != AIRCRAFT_TRANSPORTER)
2297 		return;
2298 
2299 	/* Add aliens to Alien Containment. */
2300 	AL_AddAliens(aircraft);
2301 	/* Sell collected items or add them to storage. */
2302 	B_SellOrAddItems(aircraft);
2303 
2304 	/* Now empty alien/item cargo just in case. */
2305 	OBJZERO(aircraft->itemcargo);
2306 }
2307 
2308 /**
2309  * @brief Do anything when dropship returns to base
2310  * @param[in] aircraft Returning aircraft.
2311  * @note Place here any stuff, which should be called when Drophip returns to base.
2312  * @sa AIR_CampaignRun
2313  */
B_AircraftReturnedToHomeBase(aircraft_t * aircraft)2314 void B_AircraftReturnedToHomeBase (aircraft_t* aircraft)
2315 {
2316 	/* AA Missiles should miss */
2317 	AIRFIGHT_RemoveProjectileAimingAircraft(aircraft);
2318 	/* Reset UFO sensored on radar */
2319 	RADAR_InitialiseUFOs(&aircraft->radar);
2320 	/* Reload weapons */
2321 	AII_ReloadAircraftWeapons(aircraft);
2322 
2323 	B_DumpAircraftToHomeBase(aircraft);
2324 }
2325 
2326 /**
2327  * @brief Check if an item is available on a base
2328  * @param[in] base Pointer to the base to check at
2329  * @param[in] item Pointer to the item to check
2330  */
B_BaseHasItem(const base_t * base,const objDef_t * item)2331 bool B_BaseHasItem (const base_t* base, const objDef_t* item)
2332 {
2333 	if (item->isVirtual)
2334 		return true;
2335 
2336 	return B_ItemInBase(item, base) > 0;
2337 }
2338 
2339 /**
2340  * @brief Check if the item has been collected (i.e it is in the storage) in the given base.
2341  * @param[in] item The item to check
2342  * @param[in] base The base to search in.
2343  * @return amount Number of available items in base
2344  */
B_ItemInBase(const objDef_t * item,const base_t * base)2345 int B_ItemInBase (const objDef_t* item, const base_t* base)
2346 {
2347 	const equipDef_t* ed;
2348 
2349 	if (!item)
2350 		return -1;
2351 	if (item->isVirtual)
2352 		return -1;
2353 	if (!base)
2354 		return -1;
2355 
2356 	ed = &base->storage;
2357 
2358 	if (!ed)
2359 		return -1;
2360 
2361 	return ed->numItems[item->idx];
2362 }
2363 
2364 /**
2365  * @brief Updates base capacities.
2366  * @param[in] cap Enum type of baseCapacities_t.
2367  * @param[in] base Pointer to the base.
2368  * @sa B_UpdateAllBaseBuildingStatus
2369  * @sa B_BuildingDestroy_f
2370  * @note If hasBuilding is false, the capacity is still increase: if power plant is destroyed and rebuilt, you shouldn't have to hire employees again
2371  */
B_UpdateBaseCapacities(baseCapacities_t cap,base_t * base)2372 void B_UpdateBaseCapacities (baseCapacities_t cap, base_t* base)
2373 {
2374 	int capacity = 0;
2375 
2376 	switch (cap) {
2377 	case CAP_ALIENS:		/**< Update Aliens capacity in base. */
2378 	case CAP_EMPLOYEES:		/**< Update employees capacity in base. */
2379 	case CAP_LABSPACE:		/**< Update laboratory space capacity in base. */
2380 	case CAP_WORKSPACE:		/**< Update workshop space capacity in base. */
2381 	case CAP_ITEMS:			/**< Update items capacity in base. */
2382 	case CAP_AIRCRAFT_SMALL:	/**< Update aircraft capacity in base. */
2383 	case CAP_AIRCRAFT_BIG:		/**< Update aircraft capacity in base. */
2384 	case CAP_ANTIMATTER:		/**< Update antimatter capacity in base. */
2385 	{
2386 		int buildingTemplateIDX = -1;
2387 		const buildingType_t buildingType = B_GetBuildingTypeByCapacity(cap);
2388 
2389 		/* Reset given capacity in current base. */
2390 		CAP_SetMax(base, cap, 0);
2391 		/* Get building capacity. */
2392 		for (int i = 0; i < ccs.numBuildingTemplates; i++) {
2393 			const building_t* b = &ccs.buildingTemplates[i];
2394 			if (b->buildingType == buildingType) {
2395 				capacity = b->capacity;
2396 				Com_DPrintf(DEBUG_CLIENT, "Building: %s capacity: %i\n", b->id, capacity);
2397 				buildingTemplateIDX = i;
2398 				break;
2399 			}
2400 		}
2401 		/* Finally update capacity. */
2402 		building_t* building = nullptr;
2403 		while ((building = B_GetNextBuildingByType(base, building, buildingType)))
2404 			if (building->buildingStatus >= B_STATUS_CONSTRUCTION_FINISHED)
2405 				CAP_AddMax(base, cap, capacity);
2406 
2407 		if (buildingTemplateIDX != -1)
2408 			Com_DPrintf(DEBUG_CLIENT, "B_UpdateBaseCapacities: updated capacity of %s: %i\n",
2409 				ccs.buildingTemplates[buildingTemplateIDX].id, CAP_GetMax(base, cap));
2410 
2411 		if (cap == CAP_ALIENS) {
2412 			const capacities_t* alienCap = CAP_Get(base, CAP_ALIENS);
2413 			if (base->alienContainment != nullptr && alienCap->max == 0 && alienCap->cur == 0) {
2414 				delete base->alienContainment;
2415 				base->alienContainment = nullptr;
2416 			} else if (base->alienContainment == nullptr && alienCap->max > 0) {
2417 				base->alienContainment = new AlienContainment(CAP_Get(base, CAP_ALIENS), nullptr);
2418 			}
2419 		}
2420 		break;
2421 	}
2422 	case MAX_CAP:			/**< Update all capacities in base. */
2423 		Com_DPrintf(DEBUG_CLIENT, "B_UpdateBaseCapacities: going to update ALL capacities.\n");
2424 		/* Loop through all capacities and update them. */
2425 		for (int i = 0; i < cap; i++) {
2426 			const baseCapacities_t cap = (baseCapacities_t) i;
2427 			B_UpdateBaseCapacities(cap, base);
2428 		}
2429 		break;
2430 	default:
2431 		cgi->Com_Error(ERR_DROP, "Unknown capacity limit for this base: %i \n", cap);
2432 	}
2433 }
2434 
2435 /**
2436  * @brief Saves the missile and laser slots of a base or sam site.
2437  * @param[in] weapons Defence weapons array
2438  * @param[in] numWeapons Number of entries in weapons array
2439  * @param[out] node XML Node structure, where we write the information to
2440  */
B_SaveBaseSlotsXML(const baseWeapon_t * weapons,const int numWeapons,xmlNode_t * node)2441 void B_SaveBaseSlotsXML (const baseWeapon_t* weapons, const int numWeapons, xmlNode_t* node)
2442 {
2443 	int i;
2444 
2445 	for (i = 0; i < numWeapons; i++) {
2446 		xmlNode_t* sub = cgi->XML_AddNode(node, SAVE_BASES_WEAPON);
2447 		AII_SaveOneSlotXML(sub, &weapons[i].slot, true);
2448 		cgi->XML_AddBool(sub, SAVE_BASES_AUTOFIRE, weapons[i].autofire);
2449 		if (weapons[i].target)
2450 			cgi->XML_AddInt(sub, SAVE_BASES_TARGET, weapons[i].target->idx);
2451 	}
2452 }
2453 
2454 /**
2455  * @brief Saves base storage
2456  * @param[out] parent XML Node structure, where we write the information to
2457  * @param[in] equip Storage to save
2458  */
B_SaveStorageXML(xmlNode_t * parent,const equipDef_t & equip)2459 bool B_SaveStorageXML (xmlNode_t* parent, const equipDef_t &equip)
2460 {
2461 	int k;
2462 	for (k = 0; k < cgi->csi->numODs; k++) {
2463 		const objDef_t* od = INVSH_GetItemByIDX(k);
2464 		if (equip.numItems[k] || equip.numItemsLoose[k]) {
2465 			xmlNode_t* node = cgi->XML_AddNode(parent, SAVE_BASES_ITEM);
2466 
2467 			cgi->XML_AddString(node, SAVE_BASES_ODS_ID, od->id);
2468 			cgi->XML_AddIntValue(node, SAVE_BASES_NUM, equip.numItems[k]);
2469 			cgi->XML_AddByteValue(node, SAVE_BASES_NUMLOOSE, equip.numItemsLoose[k]);
2470 		}
2471 	}
2472 	return true;
2473 }
2474 
2475 /**
2476  * @brief Save callback for saving in xml format.
2477  * @param[out] parent XML Node structure, where we write the information to
2478  */
B_SaveXML(xmlNode_t * parent)2479 bool B_SaveXML (xmlNode_t* parent)
2480 {
2481 	xmlNode_t* bases;
2482 	base_t* b;
2483 
2484 	bases = cgi->XML_AddNode(parent, SAVE_BASES_BASES);
2485 	b = nullptr;
2486 	while ((b = B_GetNext(b)) != nullptr) {
2487 		int row;
2488 		xmlNode_t* act_base;
2489 		xmlNode_t* node;
2490 		building_t* building;
2491 
2492 		if (!b->founded) {
2493 			Com_Printf("B_SaveXML: Base (idx: %i) not founded!\n", b->idx);
2494 			return false;
2495 		}
2496 
2497 		cgi->Com_RegisterConstList(saveBaseConstants);
2498 
2499 		act_base = cgi->XML_AddNode(bases, SAVE_BASES_BASE);
2500 		cgi->XML_AddInt(act_base, SAVE_BASES_IDX, b->idx);
2501 		cgi->XML_AddString(act_base, SAVE_BASES_NAME, b->name);
2502 		cgi->XML_AddPos3(act_base, SAVE_BASES_POS, b->pos);
2503 		cgi->XML_AddString(act_base, SAVE_BASES_BASESTATUS, cgi->Com_GetConstVariable(SAVE_BASESTATUS_NAMESPACE, b->baseStatus));
2504 		cgi->XML_AddFloat(act_base, SAVE_BASES_ALIENINTEREST, b->alienInterest);
2505 
2506 		/* building space */
2507 		node = cgi->XML_AddNode(act_base, SAVE_BASES_BUILDINGSPACE);
2508 		for (row = 0; row < BASE_SIZE; row++) {
2509 			int column;
2510 			for (column = 0; column < BASE_SIZE; column++) {
2511 				xmlNode_t* snode = cgi->XML_AddNode(node, SAVE_BASES_BUILDING);
2512 				/** @todo save it as vec2t if needed, also it's opposite */
2513 				cgi->XML_AddInt(snode, SAVE_BASES_X, row);
2514 				cgi->XML_AddInt(snode, SAVE_BASES_Y, column);
2515 				if (B_GetBuildingAt(b, column, row))
2516 					cgi->XML_AddInt(snode, SAVE_BASES_BUILDINGINDEX, B_GetBuildingAt(b, column, row)->idx);
2517 				cgi->XML_AddBoolValue(snode, SAVE_BASES_BLOCKED, B_IsTileBlocked(b, column, row));
2518 			}
2519 		}
2520 		/* buildings */
2521 		node = cgi->XML_AddNode(act_base, SAVE_BASES_BUILDINGS);
2522 		building = nullptr;
2523 		while ((building = B_GetNextBuilding(b, building))) {
2524 			xmlNode_t* snode;
2525 
2526 			if (!building->tpl)
2527 				continue;
2528 
2529 			snode = cgi->XML_AddNode(node, SAVE_BASES_BUILDING);
2530 			cgi->XML_AddString(snode, SAVE_BASES_BUILDINGTYPE, building->tpl->id);
2531 			cgi->XML_AddInt(snode, SAVE_BASES_BUILDING_PLACE, building->idx);
2532 			cgi->XML_AddString(snode, SAVE_BASES_BUILDINGSTATUS, cgi->Com_GetConstVariable(SAVE_BUILDINGSTATUS_NAMESPACE, building->buildingStatus));
2533 			cgi->XML_AddDate(snode, SAVE_BASES_BUILDINGTIMESTART, building->timeStart.day, building->timeStart.sec);
2534 			cgi->XML_AddInt(snode, SAVE_BASES_BUILDINGBUILDTIME, building->buildTime);
2535 			cgi->XML_AddFloatValue(snode, SAVE_BASES_BUILDINGLEVEL, building->level);
2536 			cgi->XML_AddPos2(snode, SAVE_BASES_POS, building->pos);
2537 		}
2538 		/* base defences */
2539 		node = cgi->XML_AddNode(act_base, SAVE_BASES_BATTERIES);
2540 		B_SaveBaseSlotsXML(b->batteries, b->numBatteries, node);
2541 		node = cgi->XML_AddNode(act_base, SAVE_BASES_LASERS);
2542 		B_SaveBaseSlotsXML(b->lasers, b->numLasers, node);
2543 		/* store equipment */
2544 		node = cgi->XML_AddNode(act_base, SAVE_BASES_STORAGE);
2545 		B_SaveStorageXML(node, b->storage);
2546 		/* radar */
2547 		cgi->XML_AddIntValue(act_base, SAVE_BASES_RADARRANGE, b->radar.range);
2548 		cgi->XML_AddIntValue(act_base, SAVE_BASES_TRACKINGRANGE, b->radar.trackingRange);
2549 		/* alien containment */
2550 		if (b->alienContainment) {
2551 			node = cgi->XML_AddNode(act_base, SAVE_BASES_ALIENCONTAINMENT);
2552 			b->alienContainment->save(node);
2553 		}
2554 
2555 		cgi->Com_UnregisterConstList(saveBaseConstants);
2556 	}
2557 	return true;
2558 }
2559 
2560 /**
2561  * @brief Loads the missile and laser slots of a base or sam site.
2562  * @param[out] weapons Defence weapons array
2563  * @param[out] max Number of entries in weapons array
2564  * @param[in] p XML Node structure, where we load the information from
2565  * @sa B_Load
2566  * @sa B_SaveBaseSlots
2567  */
B_LoadBaseSlotsXML(baseWeapon_t * weapons,int max,xmlNode_t * p)2568 int B_LoadBaseSlotsXML (baseWeapon_t* weapons, int max, xmlNode_t* p)
2569 {
2570 	int i;
2571 	xmlNode_t* s;
2572 	for (i = 0, s = cgi->XML_GetNode(p, SAVE_BASES_WEAPON); s && i < max; i++, s = cgi->XML_GetNextNode(s, p, SAVE_BASES_WEAPON)) {
2573 		const int target = cgi->XML_GetInt(s, SAVE_BASES_TARGET, -1);
2574 		AII_LoadOneSlotXML(s, &weapons[i].slot, true);
2575 		weapons[i].autofire = cgi->XML_GetBool(s, SAVE_BASES_AUTOFIRE, true);
2576 		weapons[i].target = (target >= 0) ? UFO_GetByIDX(target) : nullptr;
2577 	}
2578 	return i;
2579 }
2580 
2581 /**
2582  * @brief Set the capacity stuff for all the bases after loading a savegame
2583  * @sa B_PostLoadInit
2584  */
B_PostLoadInitCapacity(void)2585 static bool B_PostLoadInitCapacity (void)
2586 {
2587 	base_t* base = nullptr;
2588 	while ((base = B_GetNext(base)) != nullptr)
2589 		B_ResetAllStatusAndCapacities(base, true);
2590 
2591 	return true;
2592 }
2593 
2594 /**
2595  * @brief Set the capacity stuff for all the bases after loading a savegame
2596  * @sa SAV_GameActionsAfterLoad
2597  */
B_PostLoadInit(void)2598 bool  B_PostLoadInit (void)
2599 {
2600 	return B_PostLoadInitCapacity();
2601 }
2602 
2603 /**
2604  * @brief Loads base storage
2605  * @param[in] parent XML Node structure, where we get the information from
2606  * @param[out] equip Storage to load
2607  */
B_LoadStorageXML(xmlNode_t * parent,equipDef_t * equip)2608 bool B_LoadStorageXML (xmlNode_t* parent, equipDef_t* equip)
2609 {
2610 	xmlNode_t* node;
2611 	for (node = cgi->XML_GetNode(parent, SAVE_BASES_ITEM); node; node = cgi->XML_GetNextNode(node, parent, SAVE_BASES_ITEM)) {
2612 		const char* s = cgi->XML_GetString(node, SAVE_BASES_ODS_ID);
2613 		const objDef_t* od = INVSH_GetItemByID(s);
2614 
2615 		if (!od) {
2616 			Com_Printf("B_Load: Could not find item '%s'\n", s);
2617 		} else {
2618 			equip->numItems[od->idx] = cgi->XML_GetInt(node, SAVE_BASES_NUM, 0);
2619 			equip->numItemsLoose[od->idx] = cgi->XML_GetInt(node, SAVE_BASES_NUMLOOSE, 0);
2620 		}
2621 	}
2622 	return true;
2623 }
2624 
2625 /**
2626  * @brief Loads base data
2627  * @param[in] parent XML Node structure, where we get the information from
2628  */
B_LoadXML(xmlNode_t * parent)2629 bool B_LoadXML (xmlNode_t* parent)
2630 {
2631 	int buildingIdx;
2632 	xmlNode_t* bases;
2633 
2634 	bases = cgi->XML_GetNode(parent, "bases");
2635 	if (!bases) {
2636 		Com_Printf("Error: Node 'bases' wasn't found in savegame\n");
2637 		return false;
2638 	}
2639 
2640 	ccs.numBases = 0;
2641 
2642 	cgi->Com_RegisterConstList(saveBaseConstants);
2643 	FOREACH_XMLNODE(base, bases, SAVE_BASES_BASE) {
2644 		const int i = ccs.numBases;
2645 		base_t* const b = B_GetBaseByIDX(i);
2646 		if (b == nullptr)
2647 			break;
2648 
2649 		ccs.numBases++;
2650 
2651 		b->idx = cgi->XML_GetInt(base, SAVE_BASES_IDX, -1);
2652 		if (b->idx < 0) {
2653 			Com_Printf("Invalid base index %i\n", b->idx);
2654 			cgi->Com_UnregisterConstList(saveBaseConstants);
2655 			return false;
2656 		}
2657 		b->founded = true;
2658 		const char* str = cgi->XML_GetString(base, SAVE_BASES_BASESTATUS);
2659 		if (!cgi->Com_GetConstIntFromNamespace(SAVE_BASESTATUS_NAMESPACE, str, (int*) &b->baseStatus)) {
2660 			Com_Printf("Invalid base status '%s'\n", str);
2661 			cgi->Com_UnregisterConstList(saveBaseConstants);
2662 			return false;
2663 		}
2664 
2665 		Q_strncpyz(b->name, cgi->XML_GetString(base, SAVE_BASES_NAME), sizeof(b->name));
2666 		cgi->XML_GetPos3(base, SAVE_BASES_POS, b->pos);
2667 		b->alienInterest = cgi->XML_GetFloat(base, SAVE_BASES_ALIENINTEREST, 0.0);
2668 		b->aircraftCurrent = nullptr;
2669 
2670 		/* building space*/
2671 		xmlNode_t* node = cgi->XML_GetNode(base, SAVE_BASES_BUILDINGSPACE);
2672 		FOREACH_XMLNODE(snode, node, SAVE_BASES_BUILDING) {
2673 			/** @todo save it as vec2t if needed, also it's opposite */
2674 			const int k = cgi->XML_GetInt(snode, SAVE_BASES_X, 0);
2675 			const int l = cgi->XML_GetInt(snode, SAVE_BASES_Y, 0);
2676 			baseBuildingTile_t* tile = &b->map[k][l];
2677 			buildingIdx = cgi->XML_GetInt(snode, SAVE_BASES_BUILDINGINDEX, -1);
2678 
2679 			tile->posX = l;
2680 			tile->posY = k;
2681 			if (buildingIdx != -1)
2682 				/* The buildings are actually parsed _below_. (See PRE_MAXBUI loop) */
2683 				tile->building = B_GetBuildingByIDX(i, buildingIdx);
2684 			else
2685 				tile->building = nullptr;
2686 			tile->blocked = cgi->XML_GetBool(snode, SAVE_BASES_BLOCKED, false);
2687 			if (tile->blocked && tile->building != nullptr) {
2688 				Com_Printf("inconstent base layout found\n");
2689 				cgi->Com_UnregisterConstList(saveBaseConstants);
2690 				return false;
2691 			}
2692 		}
2693 		/* buildings */
2694 		node = cgi->XML_GetNode(base, SAVE_BASES_BUILDINGS);
2695 
2696 		ccs.numBuildings[i] = 0;
2697 		FOREACH_XMLNODE(snode, node, SAVE_BASES_BUILDING) {
2698 			const int buildId = cgi->XML_GetInt(snode, SAVE_BASES_BUILDING_PLACE, MAX_BUILDINGS);
2699 			building_t* building;
2700 			const building_t* buildingTemplate;
2701 			char buildingType[MAX_VAR];
2702 
2703 			if (buildId >= MAX_BUILDINGS) {
2704 				Com_Printf("building ID is greater than MAX buildings\n");
2705 				cgi->Com_UnregisterConstList(saveBaseConstants);
2706 				return false;
2707 			}
2708 
2709 			Q_strncpyz(buildingType, cgi->XML_GetString(snode, SAVE_BASES_BUILDINGTYPE), sizeof(buildingType));
2710 			if (buildingType[0] == '\0') {
2711 				Com_Printf("No buildingtype set\n");
2712 				cgi->Com_UnregisterConstList(saveBaseConstants);
2713 				return false;
2714 			}
2715 
2716 			buildingTemplate = B_GetBuildingTemplate(buildingType);
2717 			if (!buildingTemplate)
2718 				continue;
2719 
2720 			ccs.buildings[i][buildId] = *buildingTemplate;
2721 			building = B_GetBuildingByIDX(i, buildId);
2722 			building->idx = B_GetBuildingIDX(b, building);
2723 			if (building->idx != buildId) {
2724 				Com_Printf("building ID doesn't match\n");
2725 				cgi->Com_UnregisterConstList(saveBaseConstants);
2726 				return false;
2727 			}
2728 			building->base = b;
2729 
2730 			str = cgi->XML_GetString(snode, SAVE_BASES_BUILDINGSTATUS);
2731 			if (!cgi->Com_GetConstIntFromNamespace(SAVE_BUILDINGSTATUS_NAMESPACE, str, (int*) &building->buildingStatus)) {
2732 				Com_Printf("Invalid building status '%s'\n", str);
2733 				cgi->Com_UnregisterConstList(saveBaseConstants);
2734 				return false;
2735 			}
2736 
2737 			cgi->XML_GetDate(snode, SAVE_BASES_BUILDINGTIMESTART, &building->timeStart.day, &building->timeStart.sec);
2738 
2739 			building->buildTime = cgi->XML_GetInt(snode, SAVE_BASES_BUILDINGBUILDTIME, 0);
2740 			building->level = cgi->XML_GetFloat(snode, SAVE_BASES_BUILDINGLEVEL, 0);
2741 			cgi->XML_GetPos2(snode, SAVE_BASES_POS, building->pos);
2742 			ccs.numBuildings[i]++;
2743 		}
2744 
2745 		BDEF_InitialiseBaseSlots(b);
2746 		/* read missile battery slots */
2747 		node = cgi->XML_GetNode(base, SAVE_BASES_BATTERIES);
2748 		if (node)
2749 			b->numBatteries = B_LoadBaseSlotsXML(b->batteries, MAX_BASE_SLOT, node);
2750 		/* read laser battery slots */
2751 		node = cgi->XML_GetNode(base, SAVE_BASES_LASERS);
2752 		if (node)
2753 			b->numLasers = B_LoadBaseSlotsXML(b->lasers, MAX_BASE_SLOT, node);
2754 		/* read equipment */
2755 		node = cgi->XML_GetNode(base, SAVE_BASES_STORAGE);
2756 		B_LoadStorageXML(node, &(b->storage));
2757 		/* read radar info */
2758 		RADAR_InitialiseUFOs(&b->radar);
2759 		RADAR_Initialise(&b->radar, cgi->XML_GetInt(base, SAVE_BASES_RADARRANGE, 0), cgi->XML_GetInt(base, SAVE_BASES_TRACKINGRANGE, 0), B_GetMaxBuildingLevel(b, B_RADAR), true);
2760 
2761 		node = cgi->XML_GetNode(base, SAVE_BASES_ALIENCONTAINMENT);
2762 		if (node) {
2763 			b->alienContainment = new AlienContainment(CAP_Get(b, CAP_ALIENS), nullptr);
2764 			b->alienContainment->load(node);
2765 		}
2766 
2767 		/* clear the mess of stray loaded pointers */
2768 		b->bEquipment.init();
2769 	}
2770 	cgi->Com_UnregisterConstList(saveBaseConstants);
2771 	cgi->Cvar_SetValue("mn_base_count", B_GetCount());
2772 	return true;
2773 }
2774 
2775 /**
2776  * @brief Check if an item is stored in storage.
2777  * @param[in] obj Pointer to the item to check.
2778  * @return True if item is stored in storage.
2779  */
B_ItemIsStoredInBaseStorage(const objDef_t * obj)2780 bool B_ItemIsStoredInBaseStorage (const objDef_t* obj)
2781 {
2782 	/* antimatter is stored in antimatter storage */
2783 	if (obj->isVirtual || Q_streq(obj->id, ANTIMATTER_TECH_ID))
2784 		return false;
2785 
2786 	return true;
2787 }
2788 
2789 /**
2790  * @brief Add/remove items to/from the storage.
2791  * @param[in] base The base which storage and capacity should be updated
2792  * @param[in] obj The item.
2793  * @param[in] amount Amount to be added to removed
2794  * @return the added/removed amount
2795  */
B_AddToStorage(base_t * base,const objDef_t * obj,int amount)2796 int B_AddToStorage (base_t* base, const objDef_t* obj, int amount)
2797 {
2798 	capacities_t* cap;
2799 
2800 	assert(base);
2801 	assert(obj);
2802 
2803 	if (!B_ItemIsStoredInBaseStorage(obj))
2804 		return 0;
2805 
2806 	cap = CAP_Get(base, CAP_ITEMS);
2807 	if (amount > 0) {
2808 		if (obj->size > 0)
2809 			cap->cur += (amount * obj->size);
2810 		base->storage.numItems[obj->idx] += amount;
2811 	} else if (amount < 0) {
2812 		/* correct amount */
2813 		const int itemInBase = B_ItemInBase(obj, base);
2814 		amount = std::max(amount, -itemInBase);
2815 		if (obj->size > 0)
2816 			cap->cur += (amount * obj->size);
2817 		base->storage.numItems[obj->idx] += amount;
2818 
2819 		if (base->storage.numItems[obj->idx] == 0) {
2820 			technology_t* tech = RS_GetTechForItem(obj);
2821 			if (tech->statusResearch == RS_RUNNING && tech->base == base)
2822 				RS_StopResearch(tech);
2823 		}
2824 	}
2825 
2826 	return amount;
2827 }
2828 
2829 /**
2830  * @brief returns the amount of antimatter stored in a base
2831  * @param[in] base Pointer to the base to check
2832  */
B_AntimatterInBase(const base_t * base)2833 int B_AntimatterInBase (const base_t* base)
2834 {
2835 #ifdef DEBUG
2836 	const objDef_t* od;
2837 
2838 	od = INVSH_GetItemByID(ANTIMATTER_TECH_ID);
2839 	if (od == nullptr)
2840 		cgi->Com_Error(ERR_DROP, "Could not find " ANTIMATTER_TECH_ID " object definition");
2841 
2842 	assert(base);
2843 	assert(B_ItemInBase(od, base) == CAP_GetCurrent(base, CAP_ANTIMATTER));
2844 #endif
2845 
2846 	return CAP_GetCurrent(base, CAP_ANTIMATTER);
2847 }
2848 
2849 /**
2850  * @brief Manages antimatter (adding, removing) through Antimatter Storage Facility.
2851  * @param[in,out] base Pointer to the base.
2852  * @param[in] amount quantity of antimatter to add/remove (> 0 even if antimatter is removed)
2853  * @param[in] add True if we are adding antimatter, false when removing.
2854  * @note This function should be called whenever we add or remove antimatter from Antimatter Storage Facility.
2855  * @note Call with amount = 0 if you want to remove ALL antimatter from given base.
2856  */
B_ManageAntimatter(base_t * base,int amount,bool add)2857 void B_ManageAntimatter (base_t* base, int amount, bool add)
2858 {
2859 	const objDef_t* od;
2860 	capacities_t* cap;
2861 
2862 	assert(base);
2863 
2864 	if (add && !B_GetBuildingStatus(base, B_ANTIMATTER)) {
2865 		Com_sprintf(cp_messageBuffer, lengthof(cp_messageBuffer),
2866 			_("%s does not have Antimatter Storage Facility. %i units of antimatter got removed."),
2867 			base->name, amount);
2868 		MS_AddNewMessage(_("Notice"), cp_messageBuffer);
2869 		return;
2870 	}
2871 
2872 	od = INVSH_GetItemByIDSilent(ANTIMATTER_TECH_ID);
2873 	if (od == nullptr)
2874 		cgi->Com_Error(ERR_DROP, "Could not find " ANTIMATTER_TECH_ID " object definition");
2875 
2876 	cap = CAP_Get(base, CAP_ANTIMATTER);
2877 	if (add) {	/* Adding. */
2878 		const int a = std::min(amount, cap->max - cap->cur);
2879 		base->storage.numItems[od->idx] += a;
2880 		cap->cur += a;
2881 	} else {	/* Removing. */
2882 		if (amount == 0) {
2883 			cap->cur = 0;
2884 			base->storage.numItems[od->idx] = 0;
2885 		} else {
2886 			const int a = std::min(amount, cap->cur);
2887 			cap->cur -= a;
2888 			base->storage.numItems[od->idx] -= a;
2889 		}
2890 	}
2891 }
2892