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