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