1 /**
2  * @file
3  * @brief Handles everything that is located in or accessed through an installation.
4  * @note Installation functions prefix: INS_*
5  */
6 
7 /*
8 Copyright (C) 2002-2013 UFO: Alien Invasion.
9 
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU General Public License
12 as published by the Free Software Foundation; either version 2
13 of the License, or (at your option) any later version.
14 
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 
19 See the GNU General Public License for more details.
20 
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
24 
25 */
26 
27 #include "../../cl_shared.h"
28 #include "../../../shared/parse.h"
29 #include "cp_campaign.h"
30 #include "cp_mapfightequip.h"
31 #include "cp_aircraft.h"
32 #include "cp_missions.h"
33 #include "cp_installation.h"
34 #include "save/save_installation.h"
35 
36 /**
37  * @brief Get number of installations
38  */
INS_GetCount(void)39 int INS_GetCount (void)
40 {
41 	return cgi->LIST_Count(ccs.installations);
42 }
43 
INS_GetType(const char * type)44 installationType_t INS_GetType (const char* type)
45 {
46 	if (Q_streq(type, "samsite"))
47 		return INSTALLATION_DEFENCE;
48 	else if (Q_streq(type, "ufoyard"))
49 		return INSTALLATION_UFOYARD;
50 	else if (Q_streq(type, "radartower"))
51 		return INSTALLATION_RADAR;
52 	else if (Q_streq(type, "orbit"))
53 		return INSTALLATION_ORBIT;
54 
55 	Com_Printf("unknown type given '%s'\n", type);
56 	return INSTALLATION_RADAR;
57 }
58 
59 /**
60  * @brief Checks whether any installation is available
61  * @param[in] status Status of installation to search for
62  */
INS_HasAny(installationStatus_t status)63 bool INS_HasAny (installationStatus_t status)
64 {
65 	INS_Foreach(installation) {
66 		if (status == INSTALLATION_NOT_USED || installation->installationStatus == status)
67 			return true;
68 	}
69 
70 	return false;
71 }
72 
73 /**
74  * @brief Checks whether the given installation type is available
75  * @param[in] type Installation type to search for
76  * @param[in] status Status of installation to search for
77  */
INS_HasType(installationType_t type,installationStatus_t status)78 bool INS_HasType (installationType_t type, installationStatus_t status)
79 {
80 	INS_ForeachOfType(installation, type) {
81 		if (status == INSTALLATION_NOT_USED || installation->installationStatus == status)
82 			return true;
83 	}
84 
85 	return false;
86 }
87 
88 /**
89  * @brief Get installation by it's index.
90  * @param[in] idx Instalation's index
91  * @return Pointer to the installation corresponding to idx or @c nullptr if not found.
92  */
INS_GetByIDX(int idx)93 installation_t* INS_GetByIDX (int idx)
94 {
95 	INS_Foreach(installation) {
96 		if (installation->idx == idx)
97 			return installation;
98 	}
99 
100 	return nullptr;
101 }
102 
103 /**
104  * @brief Returns the installation Template for a given installation ID.
105  * @param[in] id ID of the installation template to find.
106  * @return corresponding installation Template, @c nullptr if not found.
107  */
INS_GetInstallationTemplateByID(const char * id)108 const installationTemplate_t* INS_GetInstallationTemplateByID (const char* id)
109 {
110 	int idx;
111 
112 	for (idx = 0; idx < ccs.numInstallationTemplates; idx++) {
113 		const installationTemplate_t* t = &ccs.installationTemplates[idx];
114 		if (Q_streq(t->id, id))
115 			return t;
116 	}
117 
118 	return nullptr;
119 }
120 
121 /**
122  * @brief Returns the installation Template for a given installation type.
123  * @param[in] type Type of the installation template to find.
124  * @return corresponding installation Template, @c nullptr if not found.
125  */
INS_GetInstallationTemplateByType(installationType_t type)126 const installationTemplate_t* INS_GetInstallationTemplateByType (installationType_t type)
127 {
128 	int idx;
129 
130 	for (idx = 0; idx < ccs.numInstallationTemplates; idx++) {
131 		const installationTemplate_t* t = &ccs.installationTemplates[idx];
132 		if (t->type == type)
133 			return t;
134 	}
135 
136 	return nullptr;
137 }
138 
139 /**
140  * @brief Build a new installation
141  * @param[in] installationTemplate Template pointer
142  * @param[in] pos Position on Globe to build at
143  * @param[in] name The name of the installation - might already be in utf-8
144  */
INS_Build(const installationTemplate_t * installationTemplate,const vec2_t pos,const char * name)145 installation_t* INS_Build (const installationTemplate_t* installationTemplate, const vec2_t pos, const char* name)
146 {
147 	installation_t installation;
148 	const int newInstallationAlienInterest = 1.0f;
149 
150 	OBJZERO(installation);
151 
152 	Vector2Copy(pos, installation.pos);
153 	Q_strncpyz(installation.name, name, sizeof(installation.name));
154 	installation.idx = ccs.campaignStats.installationsBuilt;
155 	installation.installationStatus = INSTALLATION_UNDER_CONSTRUCTION;
156 	installation.installationTemplate = installationTemplate;
157 	installation.buildStart = ccs.date.day;
158 
159 	/* a new installation is not discovered (yet) */
160 	installation.alienInterest = newInstallationAlienInterest;
161 
162 	/* intialise hit points */
163 	installation.installationDamage = installation.installationTemplate->maxDamage;
164 
165 	/* Reset Radar */
166 	RADAR_Initialise(&(installation.radar), 0.0f, 0.0f, 0.0f, false);
167 
168 	ccs.campaignStats.installationsBuilt++;
169 	return &LIST_Add(&ccs.installations, installation);
170 }
171 
172 /**
173  * @brief Destroys an installation
174  * @param[in,out] installation Pointer to the installation to be destroyed
175  */
INS_DestroyInstallation(installation_t * installation)176 void INS_DestroyInstallation (installation_t* installation)
177 {
178 	if (!installation)
179 		return;
180 	/* Disable radar */
181 	RADAR_UpdateInstallationRadarCoverage(installation, 0, 0);
182 	/* Destroy stored UFOs */
183 	if (installation->ufoCapacity.max > 0) {
184 		installation->ufoCapacity.max = 0;
185 		US_RemoveUFOsExceedingCapacity(installation);
186 	}
187 	CP_MissionNotifyInstallationDestroyed(installation);
188 
189 	Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("Installation %s was destroyed."), installation->name);
190 	MSO_CheckAddNewMessage(NT_INSTALLATION_DESTROY, _("Installation destroyed"), cp_messageBuffer, MSG_CONSTRUCTION);
191 
192 	cgi->LIST_Remove(&ccs.installations, installation);
193 	cgi->Cvar_Set("mn_installation_count", "%i", INS_GetCount());
194 }
195 
196 /**
197  * @brief Returns the current selected installation
198  */
INS_GetCurrentSelectedInstallation(void)199 installation_t* INS_GetCurrentSelectedInstallation (void)
200 {
201 	INS_Foreach(installation) {
202 		if (installation->selected)
203 			return installation;
204 	}
205 
206 	return nullptr;
207 }
208 
209 /**
210  * @brief Sets the currently selected installation
211  * @param installation Pointer to the installation to select
212  * @sa INS_SelectInstallation
213  */
INS_SetCurrentSelectedInstallation(const installation_t * installation)214 void INS_SetCurrentSelectedInstallation (const installation_t* installation)
215 {
216 	INS_Foreach(ins)
217 		ins->selected = (ins == installation);
218 
219 	if (installation) {
220 		B_SetCurrentSelectedBase(nullptr);
221 		cgi->Cvar_Set("mn_installation_title", "%s", installation->name);
222 		cgi->Cvar_Set("mn_installation_type", "%s", installation->installationTemplate->id);
223 	} else {
224 		cgi->Cvar_Set("mn_installation_title", "");
225 		cgi->Cvar_Set("mn_installation_type", "");
226 	}
227 }
228 
229 /**
230  * @brief Finishes an installation
231  * @param[in,out] installation Pointer to the installation to be finished
232  * @sa INS_UpdateInstallationData
233  * @sa INS_ConstructionFinished_f
234  */
INS_FinishInstallation(installation_t * installation)235 static void INS_FinishInstallation (installation_t* installation)
236 {
237 	if (!installation)
238 		cgi->Com_Error(ERR_DROP, "INS_FinishInstallation: No Installation.\n");
239 	if (!installation->installationTemplate)
240 		cgi->Com_Error(ERR_DROP, "INS_FinishInstallation: No Installation template.\n");
241 	if (installation->installationStatus != INSTALLATION_UNDER_CONSTRUCTION) {
242 		Com_DPrintf(DEBUG_CLIENT, "INS_FinishInstallation: Installation is not being built.\n");
243 		return;
244 	}
245 
246 	installation->installationStatus = INSTALLATION_WORKING;
247 	/* if Radar Tower */
248 	RADAR_UpdateInstallationRadarCoverage(installation, installation->installationTemplate->radarRange, installation->installationTemplate->trackingRange);
249 	/* if SAM Site */
250 	installation->numBatteries = installation->installationTemplate->maxBatteries;
251 	BDEF_InitialiseInstallationSlots(installation);
252 	/* if UFO Yard */
253 	installation->ufoCapacity.max = installation->installationTemplate->maxUFOsStored;
254 }
255 
256 #ifdef DEBUG
257 /**
258  * @brief Just lists all installations with their data
259  * @note called with debug_listinstallation
260  * @note Just for debugging purposes - not needed in game
261  * @todo To be extended for load/save purposes
262  */
INS_InstallationList_f(void)263 static void INS_InstallationList_f (void)
264 {
265 	INS_Foreach(installation) {
266 		Com_Printf("Installation idx %i\n", installation->idx);
267 		Com_Printf("Installation name %s\n", installation->name);
268 		Com_Printf("Installation pos %.02f:%.02f\n", installation->pos[0], installation->pos[1]);
269 		Com_Printf("Installation Alien interest %f\n", installation->alienInterest);
270 
271 		Com_Printf("\nInstallation sensorWidth %i\n", installation->radar.range);
272 		Com_Printf("\nInstallation trackingWidth %i\n", installation->radar.trackingRange);
273 		Com_Printf("Installation numSensoredAircraft %i\n\n", installation->radar.numUFOs);
274 
275 		Com_Printf("\nInstallation numBatteries %i\n", installation->numBatteries);
276 		/** @todo list batteries */
277 
278 		Com_Printf("\nInstallation stored UFOs %i/%i\n", installation->ufoCapacity.cur, installation->ufoCapacity.max);
279 		/** @todo list stored Ufos*/
280 
281 		Com_Printf("\n\n");
282 	}
283 }
284 
285 /**
286  * @brief Finishes the construction of an/all installation(s)
287  */
INS_ConstructionFinished_f(void)288 static void INS_ConstructionFinished_f (void)
289 {
290 	int idx = -1;
291 
292 	if (cgi->Cmd_Argc() == 2) {
293 		idx = atoi(cgi->Cmd_Argv(1));
294 		if (idx < 0) {
295 			Com_Printf("Usage: %s [installationIDX]\nWithout parameter it builds up all.\n", cgi->Cmd_Argv(0));
296 			return;
297 		}
298 	}
299 
300 	INS_Foreach(ins) {
301 		if (idx >= 0 && ins->idx != idx)
302 			continue;
303 
304 		INS_FinishInstallation(ins);
305 	}
306 }
307 #endif
308 
309 /**
310  * @brief returns the first installation with (free) ufostoring capacity
311  * @param[in] free On true it gives the first UFO Yard with free space
312  * @return installation_t Pointer to the UFO Yard
313  */
INS_GetFirstUFOYard(bool free)314 installation_t* INS_GetFirstUFOYard (bool free)
315 {
316 	INS_ForeachOfType(installation, INSTALLATION_UFOYARD) {
317 		if (free && installation->ufoCapacity.cur >= installation->ufoCapacity.max)
318 			continue;
319 
320 		return installation;
321 	}
322 
323 	return nullptr;
324 }
325 
326 /**
327  * @brief Resets console commands.
328  */
INS_InitStartup(void)329 void INS_InitStartup (void)
330 {
331 #ifdef DEBUG
332 	cgi->Cmd_AddCommand("debug_listinstallation", INS_InstallationList_f, "Print installation information to the game console");
333 	cgi->Cmd_AddCommand("debug_finishinstallation", INS_ConstructionFinished_f, "Finish construction of a specified or every installation");
334 #endif
335 }
336 
337 /**
338  * @brief Closing operations for installations subsystem
339  */
INS_Shutdown(void)340 void INS_Shutdown (void)
341 {
342 	cgi->LIST_Delete(&ccs.installations);
343 #ifdef DEBUG
344 	cgi->Cmd_RemoveCommand("debug_listinstallation");
345 	cgi->Cmd_RemoveCommand("debug_finishinstallation");
346 #endif
347 }
348 
349 /**
350  * @brief Check if some installation are build.
351  * @note Daily called.
352  */
INS_UpdateInstallationData(void)353 void INS_UpdateInstallationData (void)
354 {
355 	INS_Foreach(installation) {
356 		if (installation->installationStatus == INSTALLATION_UNDER_CONSTRUCTION
357 		 && installation->buildStart
358 		 && installation->buildStart + installation->installationTemplate->buildTime <= ccs.date.day) {
359 			INS_FinishInstallation(installation);
360 
361 			Com_sprintf(cp_messageBuffer, lengthof(cp_messageBuffer), _("Construction of installation %s finished."), installation->name);
362 			MSO_CheckAddNewMessage(NT_INSTALLATION_BUILDFINISH, _("Installation finished"), cp_messageBuffer, MSG_CONSTRUCTION);
363 		}
364 	}
365 }
366 
367 static const value_t installation_vals[] = {
368 	{"name", V_TRANSLATION_STRING, offsetof(installationTemplate_t, name), 0},
369 	{"description", V_TRANSLATION_STRING, offsetof(installationTemplate_t, description), 0},
370 	{"radar_range", V_INT, offsetof(installationTemplate_t, radarRange), MEMBER_SIZEOF(installationTemplate_t, radarRange)},
371 	{"radar_tracking_range", V_INT, offsetof(installationTemplate_t, trackingRange), MEMBER_SIZEOF(installationTemplate_t, trackingRange)},
372 	{"max_batteries", V_INT, offsetof(installationTemplate_t, maxBatteries), MEMBER_SIZEOF(installationTemplate_t, maxBatteries)},
373 	{"max_ufo_stored", V_INT, offsetof(installationTemplate_t, maxUFOsStored), MEMBER_SIZEOF(installationTemplate_t, maxUFOsStored)},
374 	{"max_damage", V_INT, offsetof(installationTemplate_t, maxDamage), MEMBER_SIZEOF(installationTemplate_t, maxDamage)},
375 	{"cost", V_INT, offsetof(installationTemplate_t, cost), MEMBER_SIZEOF(installationTemplate_t, cost)},
376 	{"buildtime", V_INT, offsetof(installationTemplate_t, buildTime), MEMBER_SIZEOF(installationTemplate_t, buildTime)},
377 	{"once", V_BOOL, offsetof(installationTemplate_t, once), MEMBER_SIZEOF(installationTemplate_t, once)},
378 	{"model", V_HUNK_STRING, offsetof(installationTemplate_t, model), 0},
379 	{"image", V_HUNK_STRING, offsetof(installationTemplate_t, image), 0},
380 
381 	{nullptr, V_NULL, 0, 0}
382 };
383 
384 /**
385  * @brief Copies an entry from the installation description file into the list of installation templates.
386  * @note Parses one "installation" entry in the installation.ufo file and writes
387  * it into the next free entry in installationTemplates.
388  * @param[in] name Unique test-id of a installationTemplate_t.
389  * @param[in] text the rest of the script file that is tokenized here
390  */
INS_ParseInstallations(const char * name,const char ** text)391 void INS_ParseInstallations (const char* name, const char** text)
392 {
393 	installationTemplate_t* installation;
394 	const char* errhead = "INS_ParseInstallations: unexpected end of file (names ";
395 	const char* token;
396 	int i;
397 
398 	/* get id list body */
399 	token = Com_Parse(text);
400 	if (!*text || *token != '{') {
401 		Com_Printf("INS_ParseInstallations: installation \"%s\" without body ignored\n", name);
402 		return;
403 	}
404 
405 	if (!name) {
406 		Com_Printf("INS_ParseInstallations: installation name not specified.\n");
407 		return;
408 	}
409 
410 	if (ccs.numInstallationTemplates >= MAX_INSTALLATION_TEMPLATES) {
411 		Com_Printf("INS_ParseInstallations: too many installation templates\n");
412 		ccs.numInstallationTemplates = MAX_INSTALLATION_TEMPLATES;	/* just in case it's bigger. */
413 		return;
414 	}
415 
416 	for (i = 0; i < ccs.numInstallationTemplates; i++) {
417 		if (Q_streq(ccs.installationTemplates[i].name, name)) {
418 			Com_Printf("INS_ParseInstallations: Second installation with same name found (%s) - second ignored\n", name);
419 			return;
420 		}
421 	}
422 
423 	/* new entry */
424 	installation = &ccs.installationTemplates[ccs.numInstallationTemplates];
425 	OBJZERO(*installation);
426 	installation->id = Mem_PoolStrDup(name, cp_campaignPool, 0);
427 	installation->type = INSTALLATION_RADAR;
428 
429 	Com_DPrintf(DEBUG_CLIENT, "...found installation %s\n", installation->id);
430 
431 	ccs.numInstallationTemplates++;
432 	do {
433 		/* get the name type */
434 		token = cgi->Com_EParse(text, errhead, name);
435 		if (!*text)
436 			break;
437 		if (*token == '}')
438 			break;
439 
440 		/* check for some standard values */
441 		if (!Com_ParseBlockToken(name, text, installation, installation_vals, cp_campaignPool, token)) {
442 			/* other values */
443 			if (Q_streq(token, "type")) {
444 				token = cgi->Com_EParse(text, errhead, name);
445 				if (!*text)
446 					return;
447 
448 				installation->type = INS_GetType(token);
449 			}
450 		}
451 	} while (*text);
452 }
453 
INS_LinkTechnologies(void)454 void INS_LinkTechnologies (void)
455 {
456 	for (int i = 0; i < ccs.numInstallationTemplates; i++) {
457 		installationTemplate_t* ins = &ccs.installationTemplates[i];
458 		technology_t* techLink = RS_GetTechByProvided(ins->id);
459 		if (techLink)
460 			ins->tech = techLink;
461 	}
462 }
463 
464 /**
465  * @brief Save callback for savegames in xml
466  * @param[out] p XML Node structure, where we write the information to
467  * @sa INS_LoadXML
468  * @sa SAV_GameSaveXML
469  */
INS_SaveXML(xmlNode_t * p)470 bool INS_SaveXML (xmlNode_t* p)
471 {
472 	xmlNode_t* n = cgi->XML_AddNode(p, SAVE_INSTALLATION_INSTALLATIONS);
473 	cgi->Com_RegisterConstList(saveInstallationConstants);
474 	INS_Foreach(inst) {
475 		xmlNode_t* s, *ss;
476 
477 		s = cgi->XML_AddNode(n, SAVE_INSTALLATION_INSTALLATION);
478 		cgi->XML_AddString(s, SAVE_INSTALLATION_TEMPLATEID, inst->installationTemplate->id);
479 		cgi->XML_AddInt(s, SAVE_INSTALLATION_IDX, inst->idx);
480 		cgi->XML_AddString(s, SAVE_INSTALLATION_NAME, inst->name);
481 		cgi->XML_AddPos3(s, SAVE_INSTALLATION_POS, inst->pos);
482 		cgi->XML_AddString(s, SAVE_INSTALLATION_STATUS, cgi->Com_GetConstVariable(SAVE_INSTALLATIONSTATUS_NAMESPACE, inst->installationStatus));
483 		cgi->XML_AddInt(s, SAVE_INSTALLATION_DAMAGE, inst->installationDamage);
484 		cgi->XML_AddFloat(s, SAVE_INSTALLATION_ALIENINTEREST, inst->alienInterest);
485 		cgi->XML_AddInt(s, SAVE_INSTALLATION_BUILDSTART, inst->buildStart);
486 
487 		ss = cgi->XML_AddNode(s, SAVE_INSTALLATION_BATTERIES);
488 		cgi->XML_AddIntValue(ss, SAVE_INSTALLATION_NUM, inst->numBatteries);
489 		B_SaveBaseSlotsXML(inst->batteries, inst->numBatteries, ss);
490 	}
491 	cgi->Com_UnregisterConstList(saveInstallationConstants);
492 	return true;
493 }
494 
495 /**
496  * @brief Load callback for savegames
497  * @param[in] p XML Node structure, where we get the information from
498  * @sa INS_SaveXML
499  * @sa SAV_GameLoadXML
500  * @sa INS_LoadItemSlots
501  */
INS_LoadXML(xmlNode_t * p)502 bool INS_LoadXML (xmlNode_t* p)
503 {
504 	xmlNode_t* n = cgi->XML_GetNode(p, SAVE_INSTALLATION_INSTALLATIONS);
505 	xmlNode_t* s;
506 	bool success = true;
507 
508 	if (!n)
509 		return false;
510 
511 	cgi->Com_RegisterConstList(saveInstallationConstants);
512 	for (s = cgi->XML_GetNode(n, SAVE_INSTALLATION_INSTALLATION); s; s = cgi->XML_GetNextNode(s,n, SAVE_INSTALLATION_INSTALLATION)) {
513 		xmlNode_t* ss;
514 		installation_t inst;
515 		const char* instID = cgi->XML_GetString(s, SAVE_INSTALLATION_TEMPLATEID);
516 		const char* instStat = cgi->XML_GetString(s, SAVE_INSTALLATION_STATUS);
517 
518 		inst.idx = cgi->XML_GetInt(s, SAVE_INSTALLATION_IDX, -1);
519 		if (inst.idx < 0) {
520 			Com_Printf("Invalid installation index %i\n", inst.idx);
521 			success = false;
522 			break;
523 		}
524 		const installationType_t type = INS_GetType(instID);
525 		inst.installationTemplate = INS_GetInstallationTemplateByType(type);
526 		if (!inst.installationTemplate) {
527 			Com_Printf("Could not find installation template '%s'\n", instID);
528 			success = false;
529 			break;
530 		}
531 
532 		if (!cgi->Com_GetConstIntFromNamespace(SAVE_INSTALLATIONSTATUS_NAMESPACE, instStat, (int*) &inst.installationStatus)) {
533 			Com_Printf("Invalid installation status '%s'\n", instStat);
534 			success = false;
535 			break;
536 		}
537 
538 		Q_strncpyz(inst.name, cgi->XML_GetString(s, SAVE_INSTALLATION_NAME), sizeof(inst.name));
539 		cgi->XML_GetPos3(s, SAVE_INSTALLATION_POS, inst.pos);
540 
541 		inst.installationDamage = cgi->XML_GetInt(s, SAVE_INSTALLATION_DAMAGE, 0);
542 		inst.alienInterest = cgi->XML_GetFloat(s, SAVE_INSTALLATION_ALIENINTEREST, 0.0);
543 		inst.buildStart = cgi->XML_GetInt(s, SAVE_INSTALLATION_BUILDSTART, 0);
544 
545 		/* Radar */
546 		RADAR_InitialiseUFOs(&inst.radar);
547 		RADAR_Initialise(&(inst.radar), 0.0f, 0.0f, 1.0f, true);
548 		if (inst.installationStatus == INSTALLATION_WORKING) {
549 			RADAR_UpdateInstallationRadarCoverage(&inst, inst.installationTemplate->radarRange, inst.installationTemplate->trackingRange);
550 			/* UFO Yard */
551 			inst.ufoCapacity.max = inst.installationTemplate->maxUFOsStored;
552 		} else {
553 			inst.ufoCapacity.max = 0;
554 		}
555 		inst.ufoCapacity.cur = 0;
556 
557 		/* read battery slots */
558 		ss = cgi->XML_GetNode(s, SAVE_INSTALLATION_BATTERIES);
559 		if (!ss) {
560 			Com_Printf("INS_LoadXML: Batteries not defined!\n");
561 			success = false;
562 			break;
563 		}
564 		inst.numBatteries = cgi->XML_GetInt(ss, SAVE_INSTALLATION_NUM, 0);
565 		if (inst.numBatteries > inst.installationTemplate->maxBatteries) {
566 			Com_Printf("Installation has more batteries than possible, using upper bound\n");
567 			inst.numBatteries = inst.installationTemplate->maxBatteries;
568 		}
569 
570 		installation_t& instp = LIST_Add(&ccs.installations, inst);
571 		BDEF_InitialiseInstallationSlots(&instp);
572 		B_LoadBaseSlotsXML(instp.batteries, instp.numBatteries, ss);
573 	}
574 	cgi->Com_UnregisterConstList(saveInstallationConstants);
575 	cgi->Cvar_Set("mn_installation_count", "%i", INS_GetCount());
576 
577 	return success;
578 }
579