1 /**
2  * @file
3  * @brief Deals with the Transfer stuff.
4  * @note Transfer menu functions prefix: TR_
5  * @todo Remove direct access to nodes
6  */
7 
8 /*
9 Copyright (C) 2002-2013 UFO: Alien Invasion.
10 
11 This program is free software; you can redistribute it and/or
12 modify it under the terms of the GNU General Public License
13 as published by the Free Software Foundation; either version 2
14 of the License, or (at your option) any later version.
15 
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19 
20 See the GNU General Public License for more details.
21 
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, write to the Free Software
24 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
25 
26 */
27 
28 #include "../../cl_shared.h"
29 #include "cp_campaign.h"
30 #include "cp_capacity.h"
31 #include "cp_time.h"
32 #include "save/save_transfer.h"
33 #include "cp_transfer_callbacks.h"
34 #include "aliencargo.h"
35 #include "aliencontainment.h"
36 
37 /**
38  * @brief Unloads transfer cargo when finishing the transfer or destroys it when no buildings/base.
39  * @param[in,out] destination The destination base - might be nullptr in case the base
40  * is already destroyed
41  * @param[in] transfer Pointer to transfer in ccs.transfers.
42  * @param[in] success True if the transfer reaches dest base, false if the base got destroyed.
43  * @sa TR_TransferEnd
44  */
TR_EmptyTransferCargo(base_t * destination,transfer_t * transfer,bool success)45 static void TR_EmptyTransferCargo (base_t* destination, transfer_t* transfer, bool success)
46 {
47 	assert(transfer);
48 
49 	if (transfer->hasItems && success) {	/* Items. */
50 		const objDef_t* od = INVSH_GetItemByID(ANTIMATTER_TECH_ID);
51 		int i;
52 
53 		/* antimatter */
54 		if (transfer->itemAmount[od->idx] > 0) {
55 			if (B_GetBuildingStatus(destination, B_ANTIMATTER)) {
56 				B_ManageAntimatter(destination, transfer->itemAmount[od->idx], true);
57 			} else {
58 				Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("%s does not have Antimatter Storage, antimatter are removed!"), destination->name);
59 				MSO_CheckAddNewMessage(NT_TRANSFER_LOST, _("Transport mission"), cp_messageBuffer, MSG_TRANSFERFINISHED);
60 			}
61 		}
62 		/* items */
63 		for (i = 0; i < cgi->csi->numODs; i++) {
64 			od = INVSH_GetItemByIDX(i);
65 
66 			if (transfer->itemAmount[od->idx] <= 0)
67 				continue;
68 			if (!B_ItemIsStoredInBaseStorage(od))
69 				continue;
70 			B_AddToStorage(destination, od, transfer->itemAmount[od->idx]);
71 		}
72 	}
73 
74 	if (transfer->hasEmployees && transfer->srcBase) {	/* Employees. (cannot come from a mission) */
75 		if (!success) {	/* Employees will be unhired. */
76 			for (int i = EMPL_SOLDIER; i < MAX_EMPL; i++) {
77 				const employeeType_t type = (employeeType_t)i;
78 				TR_ForeachEmployee(employee, transfer, type) {
79 					employee->baseHired = transfer->srcBase;	/* Restore back the original baseid. */
80 					employee->transfer = false;
81 					employee->unhire();
82 				}
83 			}
84 		} else {
85 			for (int i = EMPL_SOLDIER; i < MAX_EMPL; i++) {
86 				const employeeType_t type = (employeeType_t)i;
87 				TR_ForeachEmployee(employee, transfer, type) {
88 					employee->baseHired = transfer->srcBase;	/* Restore back the original baseid. */
89 					employee->transfer = false;
90 					employee->unhire();
91 					E_HireEmployee(destination, employee);
92 				}
93 			}
94 		}
95 	}
96 
97 	/* Aliens */
98 	if (transfer->alienCargo != nullptr) {
99 		if (success) {
100 			if (!destination->alienContainment) {
101 				Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("%s does not have Alien Containment, Aliens are removed!"), destination->name);
102 				MSO_CheckAddNewMessage(NT_TRANSFER_LOST, _("Transport mission"), cp_messageBuffer, MSG_TRANSFERFINISHED);
103 			} else {
104 				linkedList_t* cargo = transfer->alienCargo->list();
105 				LIST_Foreach(cargo, alienCargo_t, item) {
106 					destination->alienContainment->add(item->teamDef, item->alive, item->dead);
107 				}
108 				cgi->LIST_Delete(&cargo);
109 			}
110 		}
111 		delete transfer->alienCargo;
112 		transfer->alienCargo = nullptr;
113 	}
114 
115 	TR_ForeachAircraft(aircraft, transfer) {
116 		if (success) {
117 			VectorCopy(destination->pos, aircraft->pos);
118 			aircraft->status = AIR_HOME;
119 			if (!destination->aircraftCurrent)
120 				destination->aircraftCurrent = aircraft;
121 		} else {
122 			AIR_DeleteAircraft(aircraft);
123 		}
124 	}
125 	cgi->LIST_Delete(&transfer->aircraft);
126 }
127 
128 /**
129  * @brief Ends the transfer.
130  * @param[in] transfer Pointer to transfer in ccs.transfers
131  */
TR_TransferEnd(transfer_t * transfer)132 static void TR_TransferEnd (transfer_t* transfer)
133 {
134 	base_t* destination = transfer->destBase;
135 	assert(destination);
136 
137 	if (!destination->founded) {
138 		TR_EmptyTransferCargo(nullptr, transfer, false);
139 		MSO_CheckAddNewMessage(NT_TRANSFER_LOST, _("Transport mission"), _("The destination base no longer exists! Transfer cargo was lost, personnel has been discharged."), MSG_TRANSFERFINISHED);
140 		/** @todo what if source base is lost? we won't be able to unhire transfered employees. */
141 	} else {
142 		char message[256];
143 		TR_EmptyTransferCargo(destination, transfer, true);
144 		Com_sprintf(message, sizeof(message), _("Transport mission ended, unloading cargo in %s"), destination->name);
145 		MSO_CheckAddNewMessage(NT_TRANSFER_COMPLETED_SUCCESS, _("Transport mission"), message, MSG_TRANSFERFINISHED);
146 	}
147 	cgi->LIST_Remove(&ccs.transfers, transfer);
148 }
149 
TR_AddData(transferData_t * transferData,transferCargoType_t type,const void * data)150 bool TR_AddData (transferData_t* transferData, transferCargoType_t type, const void* data)
151 {
152 	if (transferData->trCargoCountTmp >= MAX_CARGO)
153 		return false;
154 	TR_SetData(&transferData->cargo[transferData->trCargoCountTmp], type, data);
155 	transferData->trCargoCountTmp++;
156 
157 	return true;
158 }
159 
160 /**
161  * @brief Starts a transfer
162  * @param[in] srcBase start transfer from this base
163  * @param[in] transData Container holds transfer details
164  */
TR_TransferStart(base_t * srcBase,transferData_t & transData)165 transfer_t* TR_TransferStart (base_t* srcBase, transferData_t &transData)
166 {
167 	transfer_t transfer;
168 	float time;
169 	int i;
170 
171 	if (!transData.transferBase || !srcBase) {
172 		Com_Printf("TR_TransferStart: No base selected!\n");
173 		return nullptr;
174 	}
175 
176 	/* Initialize transfer. */
177 	OBJZERO(transfer);
178 	/* calculate time to go from 1 base to another : 1 day for one quarter of the globe*/
179 	time = GetDistanceOnGlobe(transData.transferBase->pos, srcBase->pos) / 90.0f;
180 	transfer.event.day = ccs.date.day + floor(time);	/* add day */
181 	time = (time - floor(time)) * SECONDS_PER_DAY;	/* convert remaining time in second */
182 	transfer.event.sec = ccs.date.sec + round(time);
183 	/* check if event is not the following day */
184 	if (transfer.event.sec > SECONDS_PER_DAY) {
185 		transfer.event.sec -= SECONDS_PER_DAY;
186 		transfer.event.day++;
187 	}
188 	transfer.destBase = transData.transferBase;	/* Destination base. */
189 	transfer.srcBase = srcBase;	/* Source base. */
190 
191 	int count = 0;
192 	for (i = 0; i < cgi->csi->numODs; i++) {	/* Items. */
193 		if (transData.trItemsTmp[i] > 0) {
194 			transfer.hasItems = true;
195 			transfer.itemAmount[i] = transData.trItemsTmp[i];
196 			count++;
197 		}
198 	}
199 	/* Note that the employee remains hired in source base during the transfer, that is
200 	 * it takes Living Quarters capacity, etc, but it cannot be used anywhere. */
201 	for (i = 0; i < MAX_EMPL; i++) {		/* Employees. */
202 		LIST_Foreach(transData.trEmployeesTmp[i], Employee, employee) {
203 			assert(employee->isHiredInBase(srcBase));
204 
205 			transfer.hasEmployees = true;
206 			employee->unassign();
207 			cgi->LIST_AddPointer(&transfer.employees[i], (void*) employee);
208 			employee->transfer = true;
209 			count++;
210 		}
211 	}
212 
213 	/* Aliens. */
214 	if (transData.alienCargo != nullptr) {
215 		transfer.alienCargo = new AlienCargo(*transData.alienCargo);
216 		count += transData.alienCargo->getAlive();
217 		count += transData.alienCargo->getDead();
218 	}
219 
220 	/* Aircraft */
221 	LIST_Foreach(transData.aircraft, aircraft_t, aircraft) {
222 		const baseCapacities_t capacity = AIR_GetCapacityByAircraftWeight(aircraft);
223 		aircraft->status = AIR_TRANSFER;
224 		AIR_RemoveEmployees(*aircraft);
225 		aircraft->homebase = transData.transferBase;
226 		transfer.hasAircraft = true;
227 		cgi->LIST_AddPointer(&transfer.aircraft, (void*)aircraft);
228 		if (srcBase->aircraftCurrent == aircraft)
229 			srcBase->aircraftCurrent = AIR_GetFirstFromBase(srcBase);
230 		CAP_AddCurrent(srcBase, capacity, -1);
231 
232 		/* This should happen in TR_EmptyTransferCargo but on loading capacities are
233 			calculated based on aircraft->homebase. aircraft->homebase cannot be null yet
234 			there are hidden tests on that. */
235 		CAP_AddCurrent(transData.transferBase, capacity, 1);
236 
237 		count++;
238 	}
239 
240 	/* don't start empty transfer */
241 	if (count == 0)
242 		return nullptr;
243 
244 	/* Recheck if production/research can be done on srcbase (if there are workers/scientists) */
245 	PR_ProductionAllowed(srcBase);
246 	RS_ResearchAllowed(srcBase);
247 
248 	return &LIST_Add(&ccs.transfers, transfer);
249 }
250 
251 /**
252  * @brief Notify that an aircraft has been removed.
253  * @param[in] aircraft Aircraft that was removed from the game
254  * @sa AIR_DeleteAircraft
255  */
TR_NotifyAircraftRemoved(const aircraft_t * aircraft)256 void TR_NotifyAircraftRemoved (const aircraft_t* aircraft)
257 {
258 	if (!aircraft)
259 		return;
260 
261 	TR_Foreach(transfer) {
262 		if (!transfer->hasAircraft)
263 			continue;
264 		if (cgi->LIST_Remove(&transfer->aircraft, aircraft))
265 			return;
266 	}
267 }
268 
269 /**
270  * @brief Checks whether given transfer should be processed.
271  * @sa CP_CampaignRun
272  */
TR_TransferRun(void)273 void TR_TransferRun (void)
274 {
275 	TR_Foreach(transfer) {
276 		if (Date_IsDue(&transfer->event)) {
277 			assert(transfer->destBase);
278 			TR_TransferEnd(transfer);
279 			return;
280 		}
281 	}
282 }
283 
284 #ifdef DEBUG
285 /**
286  * @brief Lists an/all active transfer(s)
287  */
TR_ListTransfers_f(void)288 static void TR_ListTransfers_f (void)
289 {
290 	int transIdx = -1;
291 	int i = 0;
292 
293 	if (cgi->Cmd_Argc() == 2) {
294 		transIdx = atoi(cgi->Cmd_Argv(1));
295 		if (transIdx < 0 || transIdx > cgi->LIST_Count(ccs.transfers)) {
296 			Com_Printf("Usage: %s [transferIDX]\nWithout parameter it lists all.\n", cgi->Cmd_Argv(0));
297 			return;
298 		}
299 	}
300 
301 	TR_Foreach(transfer) {
302 		dateLong_t date;
303 		i++;
304 
305 		if (transIdx >= 0 && i != transIdx)
306 			continue;
307 
308 		/* @todo: we need a strftime feature to make this easier */
309 		CP_DateConvertLong(&transfer->event, &date);
310 
311 		Com_Printf("Transfer #%d\n", i);
312 		Com_Printf("...From %d (%s) To %d (%s) Arrival: %04i-%02i-%02i %02i:%02i:%02i\n",
313 			(transfer->srcBase) ? transfer->srcBase->idx : -1,
314 			(transfer->srcBase) ? transfer->srcBase->name : "(null)",
315 			(transfer->destBase) ? transfer->destBase->idx : -1,
316 			(transfer->destBase) ? transfer->destBase->name : "(null)",
317 			date.year, date.month, date.day, date.hour, date.min, date.sec);
318 
319 		/* ItemCargo */
320 		if (transfer->hasItems) {
321 			int j;
322 			Com_Printf("...ItemCargo:\n");
323 			for (j = 0; j < cgi->csi->numODs; j++) {
324 				const objDef_t* od = INVSH_GetItemByIDX(j);
325 				if (transfer->itemAmount[od->idx])
326 					Com_Printf("......%s: %i\n", od->id, transfer->itemAmount[od->idx]);
327 			}
328 		}
329 		/* Carried Employees */
330 		if (transfer->hasEmployees) {
331 			int i;
332 
333 			Com_Printf("...Carried Employee:\n");
334 			for (i = EMPL_SOLDIER; i < MAX_EMPL; i++) {
335 				const employeeType_t emplType = (employeeType_t)i;
336 				TR_ForeachEmployee(employee, transfer, emplType) {
337 					if (employee->getUGV()) {
338 						/** @todo: improve ugv listing when they're implemented */
339 						Com_Printf("......ugv: %s [ucn: %i]\n", employee->getUGV()->id, employee->chr.ucn);
340 					} else {
341 						Com_Printf("......%s (%s) / %s [ucn: %i]\n", employee->chr.name,
342 							E_GetEmployeeString(employee->getType(), 1),
343 							(employee->getNation()) ? employee->getNation()->id : "(nonation)",
344 							employee->chr.ucn);
345 						if (!employee->isHired())
346 							Com_Printf("Warning: employee^ not hired!\n");
347 						if (!employee->transfer)
348 							Com_Printf("Warning: employee^ not marked as being transfered!\n");
349 					}
350 				}
351 			}
352 		}
353 		/* AlienCargo */
354 		if (transfer->alienCargo != nullptr) {
355 			Com_Printf("...AlienCargo:\n");
356 			linkedList_t* cargo = transfer->alienCargo->list();
357 			LIST_Foreach(cargo, alienCargo_t, item) {
358 				Com_Printf("......%s alive: %i dead: %i\n", item->teamDef->id, item->alive, item->dead);
359 			}
360 			cgi->LIST_Delete(&cargo);
361 		}
362 		/* Transfered Aircraft */
363 		if (transfer->hasAircraft) {
364 			Com_Printf("...Transfered Aircraft:\n");
365 			TR_ForeachAircraft(aircraft, transfer) {
366 				Com_Printf("......%s [idx: %i]\n", aircraft->id, aircraft->idx);
367 			}
368 		}
369 	}
370 }
371 #endif
372 
373 /**
374  * @brief Save callback for xml savegames
375  * @param[out] p XML Node structure, where we write the information to
376  * @sa TR_LoadXML
377  * @sa SAV_GameSaveXML
378  */
TR_SaveXML(xmlNode_t * p)379 bool TR_SaveXML (xmlNode_t* p)
380 {
381 	xmlNode_t* n = cgi->XML_AddNode(p, SAVE_TRANSFER_TRANSFERS);
382 
383 	TR_Foreach(transfer) {
384 		int j;
385 		xmlNode_t* s;
386 
387 		s = cgi->XML_AddNode(n, SAVE_TRANSFER_TRANSFER);
388 		cgi->XML_AddInt(s, SAVE_TRANSFER_DAY, transfer->event.day);
389 		cgi->XML_AddInt(s, SAVE_TRANSFER_SEC, transfer->event.sec);
390 		if (!transfer->destBase) {
391 			Com_Printf("Could not save transfer, no destBase is set\n");
392 			return false;
393 		}
394 		cgi->XML_AddInt(s, SAVE_TRANSFER_DESTBASE, transfer->destBase->idx);
395 		/* scrBase can be nullptr if this is alien (mission->base) transport
396 		 * @sa TR_TransferAlienAfterMissionStart */
397 		if (transfer->srcBase)
398 			cgi->XML_AddInt(s, SAVE_TRANSFER_SRCBASE, transfer->srcBase->idx);
399 		/* save items */
400 		if (transfer->hasItems) {
401 			for (j = 0; j < MAX_OBJDEFS; j++) {
402 				if (transfer->itemAmount[j] > 0) {
403 					const objDef_t* od = INVSH_GetItemByIDX(j);
404 					xmlNode_t* ss = cgi->XML_AddNode(s, SAVE_TRANSFER_ITEM);
405 
406 					assert(od);
407 					cgi->XML_AddString(ss, SAVE_TRANSFER_ITEMID, od->id);
408 					cgi->XML_AddInt(ss, SAVE_TRANSFER_AMOUNT, transfer->itemAmount[j]);
409 				}
410 			}
411 		}
412 		/* save aliens */
413 		if (transfer->alienCargo != nullptr) {
414 			xmlNode_t* alienNode = cgi->XML_AddNode(s, SAVE_TRANSFER_ALIENCARGO);
415 			if (!alienNode)
416 				return false;
417 			transfer->alienCargo->save(alienNode);
418 		}
419 		/* save employee */
420 		if (transfer->hasEmployees) {
421 			for (j = 0; j < MAX_EMPL; j++) {
422 				TR_ForeachEmployee(employee, transfer, j) {
423 					xmlNode_t* ss = cgi->XML_AddNode(s, SAVE_TRANSFER_EMPLOYEE);
424 					cgi->XML_AddInt(ss, SAVE_TRANSFER_UCN, employee->chr.ucn);
425 				}
426 			}
427 		}
428 		/* save aircraft */
429 		if (transfer->hasAircraft) {
430 			TR_ForeachAircraft(aircraft, transfer) {
431 				xmlNode_t* ss = cgi->XML_AddNode(s, SAVE_TRANSFER_AIRCRAFT);
432 				cgi->XML_AddInt(ss, SAVE_TRANSFER_ID, aircraft->idx);
433 			}
434 		}
435 	}
436 	return true;
437 }
438 
439 /**
440  * @brief Load callback for xml savegames
441  * @param[in] p XML Node structure, where we get the information from
442  * @sa TR_SaveXML
443  * @sa SAV_GameLoadXML
444  */
TR_LoadXML(xmlNode_t * p)445 bool TR_LoadXML (xmlNode_t* p)
446 {
447 	xmlNode_t* n, *s;
448 
449 	n = cgi->XML_GetNode(p, SAVE_TRANSFER_TRANSFERS);
450 	if (!n)
451 		return false;
452 
453 	assert(B_AtLeastOneExists());
454 
455 	for (s = cgi->XML_GetNode(n, SAVE_TRANSFER_TRANSFER); s; s = cgi->XML_GetNextNode(s, n, SAVE_TRANSFER_TRANSFER)) {
456 		xmlNode_t* ss;
457 		transfer_t transfer;
458 
459 		OBJZERO(transfer);
460 
461 		transfer.destBase = B_GetBaseByIDX(cgi->XML_GetInt(s, SAVE_TRANSFER_DESTBASE, BYTES_NONE));
462 		if (!transfer.destBase) {
463 			Com_Printf("Error: Transfer has no destBase set\n");
464 			return false;
465 		}
466 		transfer.srcBase = B_GetBaseByIDX(cgi->XML_GetInt(s, SAVE_TRANSFER_SRCBASE, BYTES_NONE));
467 
468 		transfer.event.day = cgi->XML_GetInt(s, SAVE_TRANSFER_DAY, 0);
469 		transfer.event.sec = cgi->XML_GetInt(s, SAVE_TRANSFER_SEC, 0);
470 
471 		/* Initializing some variables */
472 		transfer.hasItems = false;
473 		transfer.hasEmployees = false;
474 		transfer.hasAircraft = false;
475 
476 		/* load items */
477 		/* If there is at last one element, hasItems is true */
478 		ss = cgi->XML_GetNode(s, SAVE_TRANSFER_ITEM);
479 		if (ss) {
480 			transfer.hasItems = true;
481 			for (; ss; ss = cgi->XML_GetNextNode(ss, s, SAVE_TRANSFER_ITEM)) {
482 				const char* itemId = cgi->XML_GetString(ss, SAVE_TRANSFER_ITEMID);
483 				const objDef_t* od = INVSH_GetItemByID(itemId);
484 
485 				if (od)
486 					transfer.itemAmount[od->idx] = cgi->XML_GetInt(ss, SAVE_TRANSFER_AMOUNT, 1);
487 			}
488 		}
489 		/* load aliens */
490 		ss = cgi->XML_GetNode(s, SAVE_TRANSFER_ALIENCARGO);
491 		if (ss) {
492 			transfer.alienCargo = new AlienCargo();
493 			if (transfer.alienCargo == nullptr)
494 				cgi->Com_Error(ERR_DROP, "TR_LoadXML: Cannot create AlienCargo object\n");
495 			transfer.alienCargo->load(ss);
496 		} else {
497 			/** @todo Remove: Fallback for compatibility */
498 			ss = cgi->XML_GetNode(s, SAVE_TRANSFER_ALIEN);
499 			if (ss) {
500 				transfer.alienCargo = new AlienCargo();
501 				if (transfer.alienCargo == nullptr)
502 					cgi->Com_Error(ERR_DROP, "TR_LoadXML: Cannot create AlienCargo object\n");
503 				for (; ss; ss = cgi->XML_GetNextNode(ss, s, SAVE_TRANSFER_ALIEN)) {
504 					const int alive = cgi->XML_GetInt(ss, SAVE_TRANSFER_ALIVEAMOUNT, 0);
505 					const int dead  = cgi->XML_GetInt(ss, SAVE_TRANSFER_DEADAMOUNT, 0);
506 					const char* id = cgi->XML_GetString(ss, SAVE_TRANSFER_ALIENID);
507 					if (!transfer.alienCargo->add(id, alive, dead))
508 						Com_Printf("TR_LoadXML: Cannot add aliens to cargo: %s, alive: %d, dead: %d\n", id, alive, dead);
509 				}
510 			}
511 		}
512 		/* load employee */
513 		ss = cgi->XML_GetNode(s, SAVE_TRANSFER_EMPLOYEE);
514 		if (ss) {
515 			transfer.hasEmployees = true;
516 			for (; ss; ss = cgi->XML_GetNextNode(ss, s, SAVE_TRANSFER_EMPLOYEE)) {
517 				const int ucn = cgi->XML_GetInt(ss, SAVE_TRANSFER_UCN, -1);
518 				Employee* empl = E_GetEmployeeFromChrUCN(ucn);
519 
520 				if (!empl) {
521 					Com_Printf("Error: No employee found with UCN: %i\n", ucn);
522 					return false;
523 				}
524 
525 				cgi->LIST_AddPointer(&transfer.employees[empl->getType()], (void*) empl);
526 				empl->transfer = true;
527 			}
528 		}
529 		/* load aircraft */
530 		ss = cgi->XML_GetNode(s, SAVE_TRANSFER_AIRCRAFT);
531 		if (ss) {
532 			transfer.hasAircraft = true;
533 			for (; ss; ss = cgi->XML_GetNextNode(ss, s, SAVE_TRANSFER_AIRCRAFT)) {
534 				const int j = cgi->XML_GetInt(ss, SAVE_TRANSFER_ID, -1);
535 				aircraft_t* aircraft = AIR_AircraftGetFromIDX(j);
536 
537 				if (aircraft)
538 					cgi->LIST_AddPointer(&transfer.aircraft, (void*)aircraft);
539 			}
540 		}
541 		LIST_Add(&ccs.transfers, transfer);
542 	}
543 
544 	return true;
545 }
546 
547 /**
548  * @brief Defines commands and cvars for the Transfer menu(s).
549  * @sa UI_InitStartup
550  */
TR_InitStartup(void)551 void TR_InitStartup (void)
552 {
553 	TR_InitCallbacks();
554 #ifdef DEBUG
555 	cgi->Cmd_AddCommand("debug_listtransfers", TR_ListTransfers_f, "Lists an/all active transfer(s)");
556 #endif
557 }
558 
559 /**
560  * @brief Closing actions for transfer-subsystem
561  */
TR_Shutdown(void)562 void TR_Shutdown (void)
563 {
564 	TR_Foreach(transfer) {
565 		int i;
566 
567 		if (transfer->alienCargo != nullptr) {
568 			delete transfer->alienCargo;
569 			transfer->alienCargo = nullptr;
570 		}
571 		cgi->LIST_Delete(&transfer->aircraft);
572 		for (i = EMPL_SOLDIER; i < MAX_EMPL; i++) {
573 			cgi->LIST_Delete(&transfer->employees[i]);
574 		}
575 	}
576 
577 	TR_ShutdownCallbacks();
578 #ifdef DEBUG
579 	cgi->Cmd_RemoveCommand("debug_listtransfers");
580 #endif
581 }
582