Bug Summary

File:client/cgame/campaign/cp_base.cpp
Location:line 172, column 51
Description:Access to field 'data' results in a dereference of a null pointer (loaded from variable 'queue')

Annotated Source Code

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