1 /**
2 * @file
3 * @brief Technology research.
4 *
5 * Handles everything related to the research-tree.
6 * Provides information if items/buildings/etc.. can be researched/used/displayed etc...
7 * Implements the research-system (research new items/etc...)
8 * See base/ufos/research.ufo and base/ufos/ui/research.ufo for the underlying content.
9 * @todo comment on used global variables.
10 */
11
12 /*
13 Copyright (C) 2002-2013 UFO: Alien Invasion.
14
15 This program is free software; you can redistribute it and/or
16 modify it under the terms of the GNU General Public License
17 as published by the Free Software Foundation; either version 2
18 of the License, or (at your option) any later version.
19
20 This program is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
23
24 See the GNU General Public License for more details.
25
26 You should have received a copy of the GNU General Public License
27 along with this program; if not, write to the Free Software
28 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29 */
30
31 #include "../../cl_shared.h"
32 #include "../../../shared/parse.h"
33 #include "cp_campaign.h"
34 #include "cp_capacity.h"
35 #include "cp_research.h"
36 #include "cp_popup.h"
37 #include "cp_time.h"
38 #include "save/save_research.h"
39 #include "aliencontainment.h"
40
41 #define TECH_HASH_SIZE 64
42 static technology_t* techHash[TECH_HASH_SIZE];
43 static technology_t* techHashProvided[TECH_HASH_SIZE];
44
45 static linkedList_t* redirectedTechs;
46
47 /**
48 * @brief Sets a technology status to researched and updates the date.
49 * @param[in] tech The technology that was researched.
50 */
RS_ResearchFinish(technology_t * tech)51 void RS_ResearchFinish (technology_t* tech)
52 {
53 /* Remove all scientists from the technology. */
54 RS_StopResearch(tech);
55
56 /** At this point we define what research-report description is used when displayed. (i.e. "usedDescription" is set here).
57 * That's because this is the first the player will see the research result
58 * and any later changes may make the content inconsistent for the player.
59 * @sa RS_MarkOneResearchable */
60 RS_GetDescription(&tech->description);
61 if (tech->preDescription.usedDescription < 0) {
62 /* For some reason the research proposal description was not set at this point - we just make sure it _is_ set. */
63 RS_GetDescription(&tech->preDescription);
64 }
65
66 /* execute the trigger only if the tech is not yet researched */
67 if (tech->finishedResearchEvent && tech->statusResearch != RS_FINISH)
68 cgi->Cmd_ExecuteString("%s", tech->finishedResearchEvent);
69
70 tech->statusResearch = RS_FINISH;
71 tech->researchedDate = ccs.date;
72 if (!tech->statusResearchable) {
73 tech->statusResearchable = true;
74 tech->preResearchedDate = ccs.date;
75 }
76
77 /* send a new message and add it to the mailclient */
78 if (tech->mailSent < MAILSENT_FINISHED && tech->type != RS_LOGIC) {
79 Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("A research project has been completed: %s\n"), _(tech->name));
80 MSO_CheckAddNewMessage(NT_RESEARCH_COMPLETED, _("Research finished"), cp_messageBuffer, MSG_RESEARCH_FINISHED, tech);
81 tech->mailSent = MAILSENT_FINISHED;
82
83 if (tech->announce) {
84 UP_OpenWith(tech->id);
85 }
86 }
87 }
88
89 /**
90 * @brief Stops a research (Removes scientists from it)
91 * @param[in] tech The technology that is being researched.
92 */
RS_StopResearch(technology_t * tech)93 void RS_StopResearch (technology_t* tech)
94 {
95 assert(tech);
96 while (tech->scientists > 0)
97 RS_RemoveScientist(tech, nullptr);
98 }
99
100 /**
101 * @brief Marks one tech as researchable.
102 * @param tech The technology to be marked.
103 * @sa RS_MarkCollected
104 * @sa RS_MarkResearchable
105 */
RS_MarkOneResearchable(technology_t * tech)106 void RS_MarkOneResearchable (technology_t* tech)
107 {
108 if (!tech)
109 return;
110
111 Com_DPrintf(DEBUG_CLIENT, "RS_MarkOneResearchable: \"%s\" marked as researchable.\n", tech->id);
112
113 /* Don't do anything for not researchable techs. */
114 if (tech->time == -1)
115 return;
116
117 /* Don't send mail for automatically completed techs. */
118 if (tech->time == 0)
119 tech->mailSent = MAILSENT_FINISHED;
120
121 /** At this point we define what research proposal description is used when displayed. (i.e. "usedDescription" is set here).
122 * That's because this is the first the player will see anything from
123 * the tech and any later changes may make the content (proposal) of the tech inconsistent for the player.
124 * @sa RS_ResearchFinish */
125 RS_GetDescription(&tech->preDescription);
126 /* tech->description is checked before a research is finished */
127
128 if (tech->mailSent < MAILSENT_PROPOSAL) {
129 Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("New research proposal: %s\n"), _(tech->name));
130 MSO_CheckAddNewMessage(NT_RESEARCH_PROPOSED, _("Unknown Technology researchable"), cp_messageBuffer, MSG_RESEARCH_PROPOSAL, tech);
131 tech->mailSent = MAILSENT_PROPOSAL;
132 }
133
134 tech->statusResearchable = true;
135
136 /* only change the date if it wasn't set before */
137 if (tech->preResearchedDate.day == 0) {
138 tech->preResearchedDate = ccs.date;
139 }
140 }
141
142 /**
143 * @brief Checks if all requirements of a tech have been met so that it becomes researchable.
144 * @note If there are NO requirements defined at all it will always return true.
145 * @param[in] tech The technology we want to research
146 * @param[in] base In what base to check the "collected" items etc..
147 * @return @c true if all requirements are satisfied otherwise @c false.
148 * @todo Add support for the "delay" value.
149 */
RS_RequirementsMet(const technology_t * tech,const base_t * base)150 bool RS_RequirementsMet (const technology_t* tech, const base_t* base)
151 {
152 int i;
153 bool metAND = false;
154 bool metOR = false;
155 const requirements_t* requiredAND = &tech->requireAND; /* a list of AND-related requirements */
156 const requirements_t* requiredOR = &tech->requireOR; /* a list of OR-related requirements */
157
158 if (!requiredAND && !requiredOR) {
159 Com_Printf("RS_RequirementsMet: No requirement list(s) given as parameter.\n");
160 return false;
161 }
162
163 /* If there are no requirements defined at all we have 'met' them by default. */
164 if (requiredAND->numLinks == 0 && requiredOR->numLinks == 0) {
165 Com_DPrintf(DEBUG_CLIENT, "RS_RequirementsMet: No requirements set for this tech. They are 'met'.\n");
166 return true;
167 }
168
169 if (requiredAND->numLinks) {
170 metAND = true;
171 for (i = 0; i < requiredAND->numLinks; i++) {
172 const requirement_t* req = &requiredAND->links[i];
173 switch (req->type) {
174 case RS_LINK_TECH:
175 /* if a tech that links itself is already marked researchable, we can research it */
176 if (!(Q_streq(req->id, tech->id) && tech->statusResearchable) && !RS_IsResearched_ptr(req->link.tech))
177 metAND = false;
178 break;
179 case RS_LINK_TECH_NOT:
180 if (RS_IsResearched_ptr(req->link.tech))
181 metAND = false;
182 break;
183 case RS_LINK_ITEM:
184 /* The same code is used in "PR_RequirementsMet" */
185 if (!base || B_ItemInBase(req->link.od, base) < req->amount)
186 metAND = false;
187 break;
188 case RS_LINK_ALIEN_DEAD:
189 if (!base || !base->alienContainment || base->alienContainment->getDead(req->link.td) < req->amount)
190 metAND = false;
191 break;
192 case RS_LINK_ALIEN:
193 if (!base || !base->alienContainment || base->alienContainment->getAlive(req->link.td) < req->amount)
194 metAND = false;
195 break;
196 case RS_LINK_ALIEN_GLOBAL:
197 if (AL_CountAll() < req->amount)
198 metAND = false;
199 break;
200 case RS_LINK_UFO:
201 if (US_UFOsInStorage(req->link.aircraft, nullptr) < req->amount)
202 metAND = false;
203 break;
204 case RS_LINK_ANTIMATTER:
205 if (!base || B_AntimatterInBase(base) < req->amount)
206 metAND = false;
207 break;
208 default:
209 break;
210 }
211
212 if (!metAND)
213 break;
214 }
215 }
216
217 if (requiredOR->numLinks)
218 for (i = 0; i < requiredOR->numLinks; i++) {
219 const requirement_t* req = &requiredOR->links[i];
220 switch (req->type) {
221 case RS_LINK_TECH:
222 if (RS_IsResearched_ptr(req->link.tech))
223 metOR = true;
224 break;
225 case RS_LINK_TECH_NOT:
226 if (!RS_IsResearched_ptr(req->link.tech))
227 metOR = true;
228 break;
229 case RS_LINK_ITEM:
230 /* The same code is used in "PR_RequirementsMet" */
231 if (base && B_ItemInBase(req->link.od, base) >= req->amount)
232 metOR = true;
233 break;
234 case RS_LINK_ALIEN:
235 if (base && base->alienContainment && base->alienContainment->getAlive(req->link.td) >= req->amount)
236 metOR = true;
237 break;
238 case RS_LINK_ALIEN_DEAD:
239 if (base && base->alienContainment && base->alienContainment->getDead(req->link.td) >= req->amount)
240 metOR = true;
241 break;
242 case RS_LINK_ALIEN_GLOBAL:
243 if (AL_CountAll() >= req->amount)
244 metOR = true;
245 break;
246 case RS_LINK_UFO:
247 if (US_UFOsInStorage(req->link.aircraft, nullptr) >= req->amount)
248 metOR = true;
249 break;
250 case RS_LINK_ANTIMATTER:
251 if (base && B_AntimatterInBase(base) >= req->amount)
252 metOR = true;
253 break;
254 default:
255 break;
256 }
257
258 if (metOR)
259 break;
260 }
261 Com_DPrintf(DEBUG_CLIENT, "met_AND is %i, met_OR is %i\n", metAND, metOR);
262
263 return (metAND || metOR);
264 }
265
266 /**
267 * @brief returns the currently used description for a technology.
268 * @param[in,out] desc A list of possible descriptions (with tech-links that decide which one is the correct one)
269 */
RS_GetDescription(technologyDescriptions_t * desc)270 const char* RS_GetDescription (technologyDescriptions_t* desc)
271 {
272 int i;
273
274 /* Return (unparsed) default description (0) if nothing is defined.
275 * it is _always_ set, even if numDescriptions is zero. See RS_ParseTechnologies (standard values). */
276 if (desc->numDescriptions == 0)
277 return desc->text[0];
278
279 /* Return already used description if it's defined. */
280 if (desc->usedDescription >= 0)
281 return desc->text[desc->usedDescription];
282
283 /* Search for useable description text (first match is returned => order is important)
284 * The default (0) entry is skipped here. */
285 for (i = 1; i < desc->numDescriptions; i++) {
286 const technology_t* tech = RS_GetTechByID(desc->tech[i]);
287 if (!tech)
288 continue;
289
290 if (RS_IsResearched_ptr(tech)) {
291 desc->usedDescription = i; /**< Stored used description */
292 return desc->text[i];
293 }
294 }
295
296 return desc->text[0];
297 }
298
299 /**
300 * @brief Marks a give technology as collected
301 * @sa CP_AddItemAsCollected_f
302 * @sa MS_AddNewMessage
303 * @sa RS_MarkOneResearchable
304 * @param[in] tech The technology pointer to mark collected
305 */
RS_MarkCollected(technology_t * tech)306 void RS_MarkCollected (technology_t* tech)
307 {
308 assert(tech);
309
310 if (tech->time == 0) /* Don't send mail for automatically completed techs. */
311 tech->mailSent = MAILSENT_FINISHED;
312
313 if (tech->mailSent < MAILSENT_PROPOSAL) {
314 if (tech->statusResearch < RS_FINISH) {
315 Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("New research proposal: %s\n"), _(tech->name));
316 MSO_CheckAddNewMessage(NT_RESEARCH_PROPOSED, _("Unknown Technology found"), cp_messageBuffer, MSG_RESEARCH_PROPOSAL, tech);
317 }
318 tech->mailSent = MAILSENT_PROPOSAL;
319 }
320
321 /* only change the date if it wasn't set before */
322 if (tech->preResearchedDate.day == 0) {
323 tech->preResearchedDate = ccs.date;
324 }
325
326 tech->statusCollected = true;
327 }
328
329 /**
330 * @brief Marks all the techs that can be researched.
331 * Automatically researches 'free' techs such as ammo for a weapon. Not "researchable"-related.
332 * Should be called when a new item is researched (RS_MarkResearched) and after
333 * the tree-initialisation (RS_InitTree)
334 * @sa RS_MarkResearched
335 */
RS_MarkResearchable(const base_t * base,bool init)336 void RS_MarkResearchable (const base_t* base, bool init)
337 {
338 int i;
339 const base_t* thisBase = base;
340
341 for (i = 0; i < ccs.numTechnologies; i++) {
342 technology_t* tech = RS_GetTechByIDX(i);
343 /* In case we loopback we need to check for already marked techs. */
344 if (tech->statusResearchable)
345 continue;
346 /* Check for collected items/aliens/etc... */
347 if (tech->statusResearch == RS_FINISH)
348 continue;
349
350 Com_DPrintf(DEBUG_CLIENT, "RS_MarkResearchable: handling \"%s\".\n", tech->id);
351
352 if (tech->base)
353 base = tech->base;
354 else
355 base = thisBase;
356
357 /* If required techs are all researched and all other requirements are met, mark this as researchable. */
358 /* All requirements are met. */
359 if (RS_RequirementsMet(tech, base)) {
360 Com_DPrintf(DEBUG_CLIENT, "RS_MarkResearchable: \"%s\" marked researchable. reason:requirements.\n", tech->id);
361 if (init && tech->time == 0)
362 tech->mailSent = MAILSENT_PROPOSAL;
363 RS_MarkOneResearchable(tech);
364 }
365
366 /* If the tech is a 'free' one (such as ammo for a weapon),
367 * mark it as researched and loop back to see if it unlocks
368 * any other techs */
369 if (tech->statusResearchable && tech->time == 0) {
370 if (init)
371 tech->mailSent = MAILSENT_FINISHED;
372 RS_ResearchFinish(tech);
373 Com_DPrintf(DEBUG_CLIENT, "RS_MarkResearchable: automatically researched \"%s\"\n", tech->id);
374 /* Restart the loop as this may have unlocked new possibilities. */
375 i = -1;
376 }
377 }
378 Com_DPrintf(DEBUG_CLIENT, "RS_MarkResearchable: Done.\n");
379 }
380
381 /**
382 * @brief Assign required tech/item/etc... pointers for a single requirements list.
383 * @note A function with the same behaviour was formerly also known as RS_InitRequirementList
384 */
RS_AssignTechLinks(requirements_t * reqs)385 static void RS_AssignTechLinks (requirements_t* reqs)
386 {
387 int i;
388
389 for (i = 0; i < reqs->numLinks; i++) {
390 requirement_t* req = &reqs->links[i];
391 switch (req->type) {
392 case RS_LINK_TECH:
393 case RS_LINK_TECH_NOT:
394 /* Get the index in the techtree. */
395 req->link.tech = RS_GetTechByID(req->id);
396 if (!req->link.tech)
397 cgi->Com_Error(ERR_DROP, "RS_AssignTechLinks: Could not get tech definition for '%s'", req->id);
398 break;
399 case RS_LINK_ITEM:
400 /* Get index in item-list. */
401 req->link.od = INVSH_GetItemByID(req->id);
402 if (!req->link.od)
403 cgi->Com_Error(ERR_DROP, "RS_AssignTechLinks: Could not get item definition for '%s'", req->id);
404 break;
405 case RS_LINK_ALIEN:
406 case RS_LINK_ALIEN_DEAD:
407 req->link.td = cgi->Com_GetTeamDefinitionByID(req->id);
408 if (!req->link.td)
409 cgi->Com_Error(ERR_DROP, "RS_AssignTechLinks: Could not get alien type (alien or alien_dead) definition for '%s'", req->id);
410 break;
411 case RS_LINK_UFO:
412 req->link.aircraft = AIR_GetAircraft(req->id);
413 break;
414 default:
415 break;
416 }
417 }
418 }
419
420 /**
421 * @brief Assign Link pointers to all required techs/items/etc...
422 * @note This replaces the RS_InitRequirementList function (since the switch to the _OR and _AND list)
423 */
RS_RequiredLinksAssign(void)424 void RS_RequiredLinksAssign (void)
425 {
426 linkedList_t* ll = redirectedTechs; /**< Use this so we do not change the original redirectedTechs pointer. */
427 int i;
428
429 for (i = 0; i < ccs.numTechnologies; i++) {
430 technology_t* tech = RS_GetTechByIDX(i);
431 if (tech->requireAND.numLinks)
432 RS_AssignTechLinks(&tech->requireAND);
433 if (tech->requireOR.numLinks)
434 RS_AssignTechLinks(&tech->requireOR);
435 if (tech->requireForProduction.numLinks)
436 RS_AssignTechLinks(&tech->requireForProduction);
437 }
438
439 /* Link the redirected technologies to their correct "parents" */
440 while (ll) {
441 /* Get the data stored in the linked list. */
442 assert(ll);
443 technology_t* redirectedTech = (technology_t*) ll->data;
444 ll = ll->next;
445
446 assert(redirectedTech);
447
448 assert(ll);
449 redirectedTech->redirect = RS_GetTechByID((char*)ll->data);
450 ll = ll->next;
451 }
452
453 /* clean up redirected techs list as it is no longer needed */
454 cgi->LIST_Delete(&redirectedTechs);
455 }
456
457 /**
458 * @brief Returns technology entry for an item
459 * @param[in] item Pointer to the item (object) definition
460 */
RS_GetTechForItem(const objDef_t * item)461 technology_t* RS_GetTechForItem (const objDef_t* item)
462 {
463 if (item == nullptr)
464 cgi->Com_Error(ERR_DROP, "RS_GetTechForItem: No item given");
465 if (item->idx < 0 || item->idx > lengthof(ccs.objDefTechs))
466 cgi->Com_Error(ERR_DROP, "RS_GetTechForItem: Buffer overflow");
467 if (ccs.objDefTechs[item->idx] == nullptr)
468 cgi->Com_Error(ERR_DROP, "RS_GetTechForItem: No technology for item %s", item->id);
469 return ccs.objDefTechs[item->idx];
470 }
471
472 /**
473 * @brief Returns technology entry for a team
474 * @param[in] team Pointer to the team definition
475 */
RS_GetTechForTeam(const teamDef_t * team)476 technology_t* RS_GetTechForTeam (const teamDef_t* team)
477 {
478 if (team == nullptr)
479 cgi->Com_Error(ERR_DROP, "RS_GetTechForTeam: No team given");
480 if (team->idx < 0 || team->idx > lengthof(ccs.teamDefTechs))
481 cgi->Com_Error(ERR_DROP, "RS_GetTechForTeam: Buffer overflow");
482 if (ccs.teamDefTechs[team->idx] == nullptr)
483 cgi->Com_Error(ERR_DROP, "RS_GetTechForTeam: No technology for team %s", team->id);
484 return ccs.teamDefTechs[team->idx];
485 }
486
487 /**
488 * @brief Gets all needed names/file-paths/etc... for each technology entry.
489 * Should be executed after the parsing of _all_ the ufo files and e.g. the
490 * research tree/inventory/etc... are initialised.
491 * @param[in] campaign The campaign data structure
492 * @param[in] load true if we are loading a game, false otherwise
493 * @todo Add a function to reset ALL research-stati to RS_NONE; -> to be called after start of a new game.
494 * @sa CP_CampaignInit
495 */
RS_InitTree(const campaign_t * campaign,bool load)496 void RS_InitTree (const campaign_t* campaign, bool load)
497 {
498 int i, j;
499 technology_t* tech;
500 byte found;
501 const objDef_t* od;
502
503 /* Add links to technologies. */
504 for (i = 0, od = cgi->csi->ods; i < cgi->csi->numODs; i++, od++) {
505 ccs.objDefTechs[od->idx] = RS_GetTechByProvided(od->id);
506 if (!ccs.objDefTechs[od->idx])
507 cgi->Com_Error(ERR_DROP, "RS_InitTree: Could not find a valid tech for item %s", od->id);
508 }
509
510 for (i = 0, tech = ccs.technologies; i < ccs.numTechnologies; i++, tech++) {
511 for (j = 0; j < tech->markResearched.numDefinitions; j++) {
512 if (tech->markResearched.markOnly[j] && Q_streq(tech->markResearched.campaign[j], campaign->researched)) {
513 Com_DPrintf(DEBUG_CLIENT, "...mark %s as researched\n", tech->id);
514 RS_ResearchFinish(tech);
515 break;
516 }
517 }
518
519 /* Save the idx to the id-names of the different requirement-types for quicker access.
520 * The id-strings themself are not really needed afterwards :-/ */
521 RS_AssignTechLinks(&tech->requireAND);
522 RS_AssignTechLinks(&tech->requireOR);
523
524 /* Search in correct data/.ufo */
525 switch (tech->type) {
526 case RS_CRAFTITEM:
527 if (!tech->name)
528 Com_DPrintf(DEBUG_CLIENT, "RS_InitTree: \"%s\" A type craftitem needs to have a 'name\txxx' defined.", tech->id);
529 break;
530 case RS_NEWS:
531 if (!tech->name)
532 Com_DPrintf(DEBUG_CLIENT, "RS_InitTree: \"%s\" A 'type news' item needs to have a 'name\txxx' defined.", tech->id);
533 break;
534 case RS_TECH:
535 if (!tech->name)
536 Com_DPrintf(DEBUG_CLIENT, "RS_InitTree: \"%s\" A 'type tech' item needs to have a 'name\txxx' defined.", tech->id);
537 break;
538 case RS_WEAPON:
539 case RS_ARMOUR:
540 found = false;
541 for (j = 0; j < cgi->csi->numODs; j++) { /* j = item index */
542 const objDef_t* item = INVSH_GetItemByIDX(j);
543
544 /* This item has been 'provided' -> get the correct data. */
545 if (Q_streq(tech->provides, item->id)) {
546 found = true;
547 if (!tech->name)
548 tech->name = Mem_PoolStrDup(item->name, cp_campaignPool, 0);
549 if (!tech->mdl)
550 tech->mdl = Mem_PoolStrDup(item->model, cp_campaignPool, 0);
551 if (!tech->image)
552 tech->image = Mem_PoolStrDup(item->image, cp_campaignPool, 0);
553 break;
554 }
555 }
556 /* No id found in cgi->csi->ods */
557 if (!found) {
558 tech->name = Mem_PoolStrDup(tech->id, cp_campaignPool, 0);
559 Com_Printf("RS_InitTree: \"%s\" - Linked weapon or armour (provided=\"%s\") not found. Tech-id used as name.\n",
560 tech->id, tech->provides);
561 }
562 break;
563 case RS_BUILDING:
564 found = false;
565 for (j = 0; j < ccs.numBuildingTemplates; j++) {
566 building_t* building = &ccs.buildingTemplates[j];
567 /* This building has been 'provided' -> get the correct data. */
568 if (Q_streq(tech->provides, building->id)) {
569 found = true;
570 if (!tech->name)
571 tech->name = Mem_PoolStrDup(building->name, cp_campaignPool, 0);
572 if (!tech->image)
573 tech->image = Mem_PoolStrDup(building->image, cp_campaignPool, 0);
574 break;
575 }
576 }
577 if (!found) {
578 tech->name = Mem_PoolStrDup(tech->id, cp_campaignPool, 0);
579 Com_DPrintf(DEBUG_CLIENT, "RS_InitTree: \"%s\" - Linked building (provided=\"%s\") not found. Tech-id used as name.\n",
580 tech->id, tech->provides);
581 }
582 break;
583 case RS_CRAFT:
584 found = false;
585 for (j = 0; j < ccs.numAircraftTemplates; j++) {
586 aircraft_t* aircraftTemplate = &ccs.aircraftTemplates[j];
587 /* This aircraft has been 'provided' -> get the correct data. */
588 if (!tech->provides)
589 cgi->Com_Error(ERR_FATAL, "RS_InitTree: \"%s\" - No linked aircraft or craft-upgrade.\n", tech->id);
590 if (Q_streq(tech->provides, aircraftTemplate->id)) {
591 found = true;
592 if (!tech->name)
593 tech->name = Mem_PoolStrDup(aircraftTemplate->name, cp_campaignPool, 0);
594 if (!tech->mdl) { /* DEBUG testing */
595 tech->mdl = Mem_PoolStrDup(aircraftTemplate->model, cp_campaignPool, 0);
596 Com_DPrintf(DEBUG_CLIENT, "RS_InitTree: aircraft model \"%s\" \n", aircraftTemplate->model);
597 }
598 aircraftTemplate->tech = tech;
599 break;
600 }
601 }
602 if (!found)
603 Com_Printf("RS_InitTree: \"%s\" - Linked aircraft or craft-upgrade (provided=\"%s\") not found.\n", tech->id, tech->provides);
604 break;
605 case RS_ALIEN:
606 /* does nothing right now */
607 break;
608 case RS_UGV:
609 /** @todo Implement me */
610 break;
611 case RS_LOGIC:
612 /* Does not need any additional data. */
613 break;
614 }
615
616 /* Check if we finally have a name for the tech. */
617 if (!tech->name) {
618 if (tech->type != RS_LOGIC)
619 cgi->Com_Error(ERR_DROP, "RS_InitTree: \"%s\" - no name found!", tech->id);
620 } else {
621 /* Fill in subject lines of tech-mails.
622 * The tech-name is copied if nothing is defined. */
623 for (j = 0; j < TECHMAIL_MAX; j++) {
624 /* Check if no subject was defined (but it is supposed to be sent) */
625 if (!tech->mail[j].subject && tech->mail[j].to) {
626 tech->mail[j].subject = tech->name;
627 }
628 }
629 }
630
631 if (!tech->image && !tech->mdl)
632 Com_DPrintf(DEBUG_CLIENT, "Tech %s of type %i has no image (%p) and no model (%p) assigned.\n",
633 tech->id, tech->type, tech->image, tech->mdl);
634 }
635
636 if (load) {
637 /* when you load a savegame right after starting UFO, the aircraft in bases
638 * and installations don't have any tech assigned */
639 AIR_Foreach(aircraft) {
640 /* if you already played before loading the game, tech are already defined for templates */
641 if (!aircraft->tech)
642 aircraft->tech = RS_GetTechByProvided(aircraft->id);
643 }
644 }
645
646 Com_DPrintf(DEBUG_CLIENT, "RS_InitTree: Technology tree initialised. %i entries found.\n", i);
647 }
648
649 /**
650 * @brief Assigns scientist to the selected research-project.
651 * @note The lab will be automatically selected (the first one that has still free space).
652 * @param[in] tech What technology you want to assign the scientist to.
653 * @param[in] base Pointer to base where the research is ongoing.
654 * @param[in] employee Pointer to the scientist to assign. It can be nullptr! That means "any".
655 * @note if employee is nullptr, te system selects an unassigned scientist on the selected (or tech-) base
656 * @sa RS_AssignScientist_f
657 * @sa RS_RemoveScientist
658 */
RS_AssignScientist(technology_t * tech,base_t * base,Employee * employee)659 void RS_AssignScientist (technology_t* tech, base_t* base, Employee* employee)
660 {
661 assert(tech);
662 Com_DPrintf(DEBUG_CLIENT, "RS_AssignScientist: %i | %s \n", tech->idx, tech->name);
663
664 /* if the tech is already assigned to a base, use that one */
665 if (tech->base)
666 base = tech->base;
667
668 assert(base);
669
670 if (!employee)
671 employee = E_GetUnassignedEmployee(base, EMPL_SCIENTIST);
672 if (!employee) {
673 /* No scientists are free in this base. */
674 Com_DPrintf(DEBUG_CLIENT, "No free scientists in this base (%s) to assign to tech '%s'\n", base->name, tech->id);
675 return;
676 }
677
678 if (!tech->statusResearchable)
679 return;
680
681 if (CAP_GetFreeCapacity(base, CAP_LABSPACE) <= 0) {
682 CP_Popup(_("Not enough laboratories"), _("No free space in laboratories left.\nBuild more laboratories.\n"));
683 return;
684 }
685
686 tech->scientists++;
687 tech->base = base;
688 CAP_AddCurrent(base, CAP_LABSPACE, 1);
689 employee->setAssigned(true);
690 tech->statusResearch = RS_RUNNING;
691 }
692
693 /**
694 * @brief Remove a scientist from a technology.
695 * @param[in] tech The technology you want to remove the scientist from.
696 * @param[in] employee Employee you want to remove (nullptr if you don't care which one should be removed).
697 * @sa RS_RemoveScientist_f
698 * @sa RS_AssignScientist
699 */
RS_RemoveScientist(technology_t * tech,Employee * employee)700 void RS_RemoveScientist (technology_t* tech, Employee* employee)
701 {
702 assert(tech);
703
704 /* no need to remove anything, but we can do some check */
705 if (tech->scientists == 0) {
706 assert(tech->base == nullptr);
707 assert(tech->statusResearch == RS_PAUSED);
708 return;
709 }
710
711 if (!employee)
712 employee = E_GetAssignedEmployee(tech->base, EMPL_SCIENTIST);
713 if (!employee)
714 cgi->Com_Error(ERR_DROP, "No assigned scientists found - serious inconsistency.");
715
716 /* Remove the scientist from the tech. */
717 tech->scientists--;
718 /* Update capacity. */
719 CAP_AddCurrent(tech->base, CAP_LABSPACE, -1);
720 employee->setAssigned(false);
721
722 assert(tech->scientists >= 0);
723
724 if (tech->scientists == 0) {
725 /* Remove the tech from the base if no scientists are left to research it. */
726 tech->base = nullptr;
727 tech->statusResearch = RS_PAUSED;
728 }
729 }
730
731 /**
732 * @brief Remove one scientist from research project if needed.
733 * @param[in] base Pointer to base where a scientist should be removed.
734 * @param[in] employee Pointer to the employee that is fired.
735 * @note used when a scientist is fired.
736 * @note This function is called before the employee is actually fired.
737 */
RS_RemoveFiredScientist(base_t * base,Employee * employee)738 void RS_RemoveFiredScientist (base_t* base, Employee* employee)
739 {
740 technology_t* tech;
741 Employee* freeScientist = E_GetUnassignedEmployee(base, EMPL_SCIENTIST);
742
743 assert(base);
744 assert(employee);
745
746 /* Get a tech where there is at least one scientist working on (unless no scientist working in this base) */
747 tech = RS_GetTechWithMostScientists(base);
748
749 /* tech should never be nullptr, as there is at least 1 scientist working in base */
750 assert(tech);
751 RS_RemoveScientist(tech, employee);
752
753 /* if there is at least one scientist not working on a project, make this one replace removed employee */
754 if (freeScientist)
755 RS_AssignScientist(tech, base, freeScientist);
756 }
757
758 /**
759 * @brief Mark technologies as researched. This includes techs that depends on "tech" and have time=0
760 * @param[in] tech Pointer to a technology_t struct.
761 * @param[in] base Pointer to base where we did research.
762 * @todo Base shouldn't be needed here - check RS_MarkResearchable() for that.
763 * @sa RS_ResearchRun
764 */
RS_MarkResearched(technology_t * tech,const base_t * base)765 static void RS_MarkResearched (technology_t* tech, const base_t* base)
766 {
767 RS_ResearchFinish(tech);
768 Com_DPrintf(DEBUG_CLIENT, "Research of \"%s\" finished.\n", tech->id);
769 RS_MarkResearchable(base);
770 }
771
772 /**
773 * Pick a random base to research a story line event tech
774 * @param techID The event technology script id to research
775 * @note If there is no base available the tech is not marked as researched, too
776 */
RS_MarkStoryLineEventResearched(const char * techID)777 bool RS_MarkStoryLineEventResearched (const char* techID)
778 {
779 technology_t* tech = RS_GetTechByID(techID);
780 if (!RS_IsResearched_ptr(tech)) {
781 const base_t* base = B_GetNext(nullptr);
782 if (base != nullptr) {
783 RS_MarkResearched(tech, base);
784 return true;
785 }
786 }
787 return false;
788 }
789
790 /**
791 * @brief Checks if running researches still meet their requirements
792 */
RS_CheckRequirements(void)793 void RS_CheckRequirements (void)
794 {
795 for (int i = 0; i < ccs.numTechnologies; i++) {
796 technology_t* tech = RS_GetTechByIDX(i);
797
798 if (tech->statusResearch != RS_RUNNING)
799 continue;
800
801 if (RS_RequirementsMet(tech, tech->base))
802 continue;
803
804 Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("Research prerequisites of %s do not met at %s. Research halted!"), _(tech->name), tech->base->name);
805 MSO_CheckAddNewMessage(NT_RESEARCH_HALTED, _("Research halted"), cp_messageBuffer, MSG_RESEARCH_HALTED);
806
807 RS_StopResearch(tech);
808 }
809 }
810
811 /**
812 * @brief Checks the research status
813 * @todo Needs to check on the exact time that elapsed since the last check of the status.
814 * @sa RS_MarkResearched
815 * @return The amout of new researched technologies
816 */
RS_ResearchRun(void)817 int RS_ResearchRun (void)
818 {
819 int i, newResearch = 0;
820
821 for (i = 0; i < ccs.numTechnologies; i++) {
822 technology_t* tech = RS_GetTechByIDX(i);
823
824 if (tech->statusResearch != RS_RUNNING)
825 continue;
826
827 if (!RS_RequirementsMet(tech, tech->base)) {
828 Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("Research prerequisites of %s do not met at %s. Research halted!"), _(tech->name), tech->base->name);
829 MSO_CheckAddNewMessage(NT_RESEARCH_HALTED, _("Research halted"), cp_messageBuffer, MSG_RESEARCH_HALTED);
830
831 RS_StopResearch(tech);
832 continue;
833 }
834
835 if (tech->time > 0 && tech->scientists > 0) {
836 /* If there are scientists there _has_ to be a base. */
837 const base_t* base = tech->base;
838 assert(tech->base);
839 if (RS_ResearchAllowed(base)) {
840 tech->time -= tech->scientists * ccs.curCampaign->researchRate;
841 /* Will be a good thing (think of percentage-calculation) once non-integer values are used. */
842 if (tech->time <= 0) {
843 RS_MarkResearched(tech, base);
844
845 newResearch++;
846 tech->time = 0;
847 }
848 }
849 }
850 }
851
852 return newResearch;
853 }
854
855 #ifdef DEBUG
856 /** @todo use cgi->Com_RegisterConstInt(); */
RS_TechTypeToName(researchType_t type)857 static const char* RS_TechTypeToName (researchType_t type)
858 {
859 switch(type) {
860 case RS_TECH:
861 return "tech";
862 case RS_WEAPON:
863 return "weapon";
864 case RS_ARMOUR:
865 return "armour";
866 case RS_CRAFT:
867 return "craft";
868 case RS_CRAFTITEM:
869 return "craftitem";
870 case RS_BUILDING:
871 return "building";
872 case RS_ALIEN:
873 return "alien";
874 case RS_UGV:
875 return "ugv";
876 case RS_NEWS:
877 return "news";
878 case RS_LOGIC:
879 return "logic";
880 default:
881 return "unknown";
882 }
883 }
884
RS_TechReqToName(requirement_t * req)885 static const char* RS_TechReqToName (requirement_t* req)
886 {
887 switch(req->type) {
888 case RS_LINK_TECH:
889 return req->link.tech->id;
890 case RS_LINK_TECH_NOT:
891 return va("not %s", req->link.tech->id);
892 case RS_LINK_ITEM:
893 return req->link.od->id;
894 case RS_LINK_ALIEN:
895 return req->link.td->id;
896 case RS_LINK_ALIEN_DEAD:
897 return req->link.td->id;
898 case RS_LINK_ALIEN_GLOBAL:
899 return "global alien count";
900 case RS_LINK_UFO:
901 return req->link.aircraft->id;
902 case RS_LINK_ANTIMATTER:
903 return "antimatter";
904 default:
905 return "unknown";
906 }
907 }
908
909 /** @todo use cgi->Com_RegisterConstInt(); */
RS_TechLinkTypeToName(requirementType_t type)910 static const char* RS_TechLinkTypeToName (requirementType_t type)
911 {
912 switch(type) {
913 case RS_LINK_TECH:
914 return "tech";
915 case RS_LINK_TECH_NOT:
916 return "tech (not)";
917 case RS_LINK_ITEM:
918 return "item";
919 case RS_LINK_ALIEN:
920 return "alien";
921 case RS_LINK_ALIEN_DEAD:
922 return "alien_dead";
923 case RS_LINK_ALIEN_GLOBAL:
924 return "alienglobal";
925 case RS_LINK_UFO:
926 return "ufo";
927 case RS_LINK_ANTIMATTER:
928 return "antimatter";
929 default:
930 return "unknown";
931 }
932 }
933
934 /**
935 * @brief List all parsed technologies and their attributes in commandline/console.
936 * @note called with debug_listtech
937 */
RS_TechnologyList_f(void)938 static void RS_TechnologyList_f (void)
939 {
940 int i, j;
941 technology_t* tech;
942 requirements_t* reqs;
943 dateLong_t date;
944
945 Com_Printf("#techs: %i\n", ccs.numTechnologies);
946 for (i = 0; i < ccs.numTechnologies; i++) {
947 tech = RS_GetTechByIDX(i);
948 Com_Printf("Tech: %s\n", tech->id);
949 Com_Printf("... time -> %.2f\n", tech->time);
950 Com_Printf("... name -> %s\n", tech->name);
951 reqs = &tech->requireAND;
952 Com_Printf("... requires ALL ->");
953 for (j = 0; j < reqs->numLinks; j++)
954 Com_Printf(" %s (%s) %s", reqs->links[j].id, RS_TechLinkTypeToName(reqs->links[j].type), RS_TechReqToName(&reqs->links[j]));
955 reqs = &tech->requireOR;
956 Com_Printf("\n");
957 Com_Printf("... requires ANY ->");
958 for (j = 0; j < reqs->numLinks; j++)
959 Com_Printf(" %s (%s) %s", reqs->links[j].id, RS_TechLinkTypeToName(reqs->links[j].type), RS_TechReqToName(&reqs->links[j]));
960 Com_Printf("\n");
961 Com_Printf("... provides -> %s", tech->provides);
962 Com_Printf("\n");
963
964 Com_Printf("... type -> ");
965 Com_Printf("%s\n", RS_TechTypeToName(tech->type));
966
967 Com_Printf("... researchable -> %i\n", tech->statusResearchable);
968
969 if (tech->statusResearchable) {
970 CP_DateConvertLong(&tech->preResearchedDate, &date);
971 Com_Printf("... researchable date: %02i %02i %i\n", date.day, date.month, date.year);
972 }
973
974 Com_Printf("... research -> ");
975 switch (tech->statusResearch) {
976 case RS_NONE:
977 Com_Printf("nothing\n");
978 break;
979 case RS_RUNNING:
980 Com_Printf("running\n");
981 break;
982 case RS_PAUSED:
983 Com_Printf("paused\n");
984 break;
985 case RS_FINISH:
986 Com_Printf("done\n");
987 CP_DateConvertLong(&tech->researchedDate, &date);
988 Com_Printf("... research date: %02i %02i %i\n", date.day, date.month, date.year);
989 break;
990 default:
991 Com_Printf("unknown\n");
992 break;
993 }
994 }
995 }
996
997 /**
998 * @brief Mark everything as researched
999 * @sa UI_StartServer
1000 */
RS_DebugMarkResearchedAll(void)1001 static void RS_DebugMarkResearchedAll (void)
1002 {
1003 int i;
1004
1005 for (i = 0; i < ccs.numTechnologies; i++) {
1006 technology_t* tech = RS_GetTechByIDX(i);
1007 Com_DPrintf(DEBUG_CLIENT, "...mark %s as researched\n", tech->id);
1008 RS_MarkOneResearchable(tech);
1009 RS_ResearchFinish(tech);
1010 /** @todo Set all "collected" entries in the requirements to the "amount" value. */
1011 }
1012 }
1013
1014 /**
1015 * @brief Set all items to researched
1016 * @note Just for debugging purposes
1017 */
RS_DebugResearchAll_f(void)1018 static void RS_DebugResearchAll_f (void)
1019 {
1020 if (cgi->Cmd_Argc() != 2) {
1021 RS_DebugMarkResearchedAll();
1022 } else {
1023 technology_t* tech = RS_GetTechByID(cgi->Cmd_Argv(1));
1024 if (!tech)
1025 return;
1026 Com_DPrintf(DEBUG_CLIENT, "...mark %s as researched\n", tech->id);
1027 RS_MarkOneResearchable(tech);
1028 RS_ResearchFinish(tech);
1029 }
1030 }
1031
1032 /**
1033 * @brief Set all item to researched
1034 * @note Just for debugging purposes
1035 */
RS_DebugResearchableAll_f(void)1036 static void RS_DebugResearchableAll_f (void)
1037 {
1038 int i;
1039
1040 if (cgi->Cmd_Argc() != 2) {
1041 for (i = 0; i < ccs.numTechnologies; i++) {
1042 technology_t* tech = RS_GetTechByIDX(i);
1043 Com_Printf("...mark %s as researchable\n", tech->id);
1044 RS_MarkOneResearchable(tech);
1045 RS_MarkCollected(tech);
1046 }
1047 } else {
1048 technology_t* tech = RS_GetTechByID(cgi->Cmd_Argv(1));
1049 if (tech) {
1050 Com_Printf("...mark %s as researchable\n", tech->id);
1051 RS_MarkOneResearchable(tech);
1052 RS_MarkCollected(tech);
1053 }
1054 }
1055 }
1056
RS_DebugFinishResearches_f(void)1057 static void RS_DebugFinishResearches_f (void)
1058 {
1059 int i;
1060
1061 for (i = 0; i < ccs.numTechnologies; i++) {
1062 technology_t* tech = RS_GetTechByIDX(i);
1063 if (tech->statusResearch == RS_RUNNING) {
1064 assert(tech->base);
1065 Com_DPrintf(DEBUG_CLIENT, "...mark %s as researched\n", tech->id);
1066 RS_MarkResearched(tech, tech->base);
1067 }
1068 }
1069 }
1070 #endif
1071
1072
1073 /**
1074 * @brief This is more or less the initial
1075 * Bind some of the functions in this file to console-commands that you can call ingame.
1076 * Called from UI_InitStartup resp. CL_InitLocal
1077 */
RS_InitStartup(void)1078 void RS_InitStartup (void)
1079 {
1080 /* add commands and cvars */
1081 #ifdef DEBUG
1082 cgi->Cmd_AddCommand("debug_listtech", RS_TechnologyList_f, "Print the current parsed technologies to the game console");
1083 cgi->Cmd_AddCommand("debug_researchall", RS_DebugResearchAll_f, "Mark all techs as researched");
1084 cgi->Cmd_AddCommand("debug_researchableall", RS_DebugResearchableAll_f, "Mark all techs as researchable");
1085 cgi->Cmd_AddCommand("debug_finishresearches", RS_DebugFinishResearches_f, "Mark all running researches as finished");
1086 #endif
1087 }
1088
1089 /**
1090 * @brief This is called everytime RS_ParseTechnologies is called - to prevent cyclic hash tables
1091 */
RS_ResetTechs(void)1092 void RS_ResetTechs (void)
1093 {
1094 /* they are static - but i'm paranoid - this is called before the techs were parsed */
1095 OBJZERO(techHash);
1096 OBJZERO(techHashProvided);
1097
1098 /* delete redirectedTechs, will be filled during parse */
1099 cgi->LIST_Delete(&redirectedTechs);
1100 }
1101
1102 /**
1103 * @brief The valid definition names in the research.ufo file.
1104 * @note Handled in parser below.
1105 * description, preDescription, requireAND, requireOR, up_chapter
1106 */
1107 static const value_t valid_tech_vars[] = {
1108 {"name", V_TRANSLATION_STRING, offsetof(technology_t, name), 0},
1109 {"provides", V_HUNK_STRING, offsetof(technology_t, provides), 0},
1110 {"event", V_HUNK_STRING, offsetof(technology_t, finishedResearchEvent), 0},
1111 {"delay", V_INT, offsetof(technology_t, delay), MEMBER_SIZEOF(technology_t, delay)},
1112 {"producetime", V_INT, offsetof(technology_t, produceTime), MEMBER_SIZEOF(technology_t, produceTime)},
1113 {"time", V_FLOAT, offsetof(technology_t, time), MEMBER_SIZEOF(technology_t, time)},
1114 {"announce", V_BOOL, offsetof(technology_t, announce), MEMBER_SIZEOF(technology_t, announce)},
1115 {"image", V_HUNK_STRING, offsetof(technology_t, image), 0},
1116 {"model", V_HUNK_STRING, offsetof(technology_t, mdl), 0},
1117
1118 {nullptr, V_NULL, 0, 0}
1119 };
1120
1121 /**
1122 * @brief The valid definition names in the research.ufo file for tech mails
1123 */
1124 static const value_t valid_techmail_vars[] = {
1125 {"from", V_TRANSLATION_STRING, offsetof(techMail_t, from), 0},
1126 {"to", V_TRANSLATION_STRING, offsetof(techMail_t, to), 0},
1127 {"subject", V_TRANSLATION_STRING, offsetof(techMail_t, subject), 0},
1128 {"date", V_TRANSLATION_STRING, offsetof(techMail_t, date), 0},
1129 {"icon", V_HUNK_STRING, offsetof(techMail_t, icon), 0},
1130 {"model", V_HUNK_STRING, offsetof(techMail_t, model), 0},
1131
1132 {nullptr, V_NULL, 0, 0}
1133 };
1134
1135 /**
1136 * @brief Parses one "tech" entry in the research.ufo file and writes it into the next free entry in technologies (technology_t).
1137 * @param[in] name Unique id of a technology_t. This is parsed from "tech xxx" -> id=xxx
1138 * @param[in] text the whole following text that is part of the "tech" item definition in research.ufo.
1139 * @sa CL_ParseScriptFirst
1140 * @sa GAME_SetMode
1141 * @note write into cp_campaignPool - free on every game restart and reparse
1142 */
RS_ParseTechnologies(const char * name,const char ** text)1143 void RS_ParseTechnologies (const char* name, const char** text)
1144 {
1145 technology_t* tech;
1146 unsigned hash;
1147 const char* errhead = "RS_ParseTechnologies: unexpected end of file.";
1148 const char* token;
1149 requirements_t* requiredTemp;
1150 technologyDescriptions_t* descTemp;
1151 int i;
1152
1153 for (i = 0; i < ccs.numTechnologies; i++) {
1154 if (Q_streq(ccs.technologies[i].id, name)) {
1155 Com_Printf("RS_ParseTechnologies: Second tech with same name found (%s) - second ignored\n", name);
1156 return;
1157 }
1158 }
1159
1160 if (ccs.numTechnologies >= MAX_TECHNOLOGIES) {
1161 Com_Printf("RS_ParseTechnologies: too many technology entries. limit is %i.\n", MAX_TECHNOLOGIES);
1162 return;
1163 }
1164
1165 /* get body */
1166 token = Com_Parse(text);
1167 if (!*text || *token != '{') {
1168 Com_Printf("RS_ParseTechnologies: \"%s\" technology def without body ignored.\n", name);
1169 return;
1170 }
1171
1172 /* New technology (next free entry in global tech-list) */
1173 tech = &ccs.technologies[ccs.numTechnologies];
1174 ccs.numTechnologies++;
1175
1176 OBJZERO(*tech);
1177
1178 /*
1179 * Set standard values
1180 */
1181 tech->idx = ccs.numTechnologies - 1;
1182 tech->id = Mem_PoolStrDup(name, cp_campaignPool, 0);
1183 hash = Com_HashKey(tech->id, TECH_HASH_SIZE);
1184
1185 /* Set the default string for descriptions (available even if numDescriptions is 0) */
1186 tech->description.text[0] = _("No description available.");
1187 tech->preDescription.text[0] = _("No research proposal available.");
1188 /* Set desc-indices to undef. */
1189 tech->description.usedDescription = -1;
1190 tech->preDescription.usedDescription = -1;
1191
1192 /* link the variable in */
1193 /* tech_hash should be null on the first run */
1194 tech->hashNext = techHash[hash];
1195 /* set the techHash pointer to the current tech */
1196 /* if there were already others in techHash at position hash, they are now
1197 * accessible via tech->next - loop until tech->next is null (the first tech
1198 * at that position)
1199 */
1200 techHash[hash] = tech;
1201
1202 tech->type = RS_TECH;
1203 tech->statusResearch = RS_NONE;
1204 tech->statusResearchable = false;
1205
1206 do {
1207 /* get the name type */
1208 token = cgi->Com_EParse(text, errhead, name);
1209 if (!*text)
1210 break;
1211 if (*token == '}')
1212 break;
1213 /* get values */
1214 if (Q_streq(token, "type")) {
1215 /* what type of tech this is */
1216 token = cgi->Com_EParse(text, errhead, name);
1217 if (!*text)
1218 return;
1219 /** @todo use cgi->Com_RegisterConstInt(); */
1220 /* redundant, but oh well. */
1221 if (Q_streq(token, "tech"))
1222 tech->type = RS_TECH;
1223 else if (Q_streq(token, "weapon"))
1224 tech->type = RS_WEAPON;
1225 else if (Q_streq(token, "news"))
1226 tech->type = RS_NEWS;
1227 else if (Q_streq(token, "armour"))
1228 tech->type = RS_ARMOUR;
1229 else if (Q_streq(token, "craft"))
1230 tech->type = RS_CRAFT;
1231 else if (Q_streq(token, "craftitem"))
1232 tech->type = RS_CRAFTITEM;
1233 else if (Q_streq(token, "building"))
1234 tech->type = RS_BUILDING;
1235 else if (Q_streq(token, "alien"))
1236 tech->type = RS_ALIEN;
1237 else if (Q_streq(token, "ugv"))
1238 tech->type = RS_UGV;
1239 else if (Q_streq(token, "logic"))
1240 tech->type = RS_LOGIC;
1241 else
1242 Com_Printf("RS_ParseTechnologies: \"%s\" unknown techtype: \"%s\" - ignored.\n", name, token);
1243 } else {
1244 if (Q_streq(token, "description") || Q_streq(token, "pre_description")) {
1245 /* Parse the available descriptions for this tech */
1246
1247 /* Link to correct list. */
1248 if (Q_streq(token, "pre_description")) {
1249 descTemp = &tech->preDescription;
1250 } else {
1251 descTemp = &tech->description;
1252 }
1253
1254 token = cgi->Com_EParse(text, errhead, name);
1255 if (!*text)
1256 break;
1257 if (*token != '{')
1258 break;
1259
1260 do { /* Loop through all descriptions in the list.*/
1261 token = cgi->Com_EParse(text, errhead, name);
1262 if (!*text)
1263 return;
1264 if (*token == '}')
1265 break;
1266
1267 linkedList_t* list;
1268
1269 if (Q_streq(token, "default")) {
1270 list = nullptr;
1271 cgi->LIST_AddString(&list, token);
1272 token = cgi->Com_EParse(text, errhead, name);
1273 cgi->LIST_AddString(&list, token);
1274 } else if (Q_streq(token, "extra")) {
1275 if (!Com_ParseList(text, &list)) {
1276 cgi->Com_Error(ERR_DROP, "RS_ParseTechnologies: error while reading extra description tuple");
1277 }
1278 if (cgi->LIST_Count(list) != 2) {
1279 cgi->LIST_Delete(&list);
1280 cgi->Com_Error(ERR_DROP, "RS_ParseTechnologies: extra description tuple must contains 2 elements (id string)");
1281 }
1282 } else {
1283 cgi->Com_Error(ERR_DROP, "RS_ParseTechnologies: error while reading description: token \"%s\" not expected", token);
1284 }
1285
1286 if (descTemp->numDescriptions < MAX_DESCRIPTIONS) {
1287 const char* id = (char*)list->data;
1288 const char* description = (char*)list->next->data;
1289
1290 /* Copy tech string into entry. */
1291 descTemp->tech[descTemp->numDescriptions] = Mem_PoolStrDup(id, cp_campaignPool, 0);
1292
1293 /* skip translation marker */
1294 if (description[0] == '_')
1295 description++;
1296
1297 descTemp->text[descTemp->numDescriptions] = Mem_PoolStrDup(description, cp_campaignPool, 0);
1298 descTemp->numDescriptions++;
1299 } else {
1300 Com_Printf("skipped description for tech '%s'\n", tech->id);
1301 }
1302 cgi->LIST_Delete(&list);
1303 } while (*text);
1304
1305 } else if (Q_streq(token, "redirect")) {
1306 token = cgi->Com_EParse(text, errhead, name);
1307 /* Store this tech and the parsed tech-id of the target of the redirection for later linking. */
1308 cgi->LIST_AddPointer(&redirectedTechs, tech);
1309 cgi->LIST_AddString(&redirectedTechs, token);
1310 } else if (Q_streq(token, "require_AND") || Q_streq(token, "require_OR") || Q_streq(token, "require_for_production")) {
1311 /* Link to correct list. */
1312 if (Q_streq(token, "require_AND")) {
1313 requiredTemp = &tech->requireAND;
1314 } else if (Q_streq(token, "require_OR")) {
1315 requiredTemp = &tech->requireOR;
1316 } else { /* It's "requireForProduction" */
1317 requiredTemp = &tech->requireForProduction;
1318 }
1319
1320 token = cgi->Com_EParse(text, errhead, name);
1321 if (!*text)
1322 break;
1323 if (*token != '{')
1324 break;
1325
1326 do { /* Loop through all 'require' entries.*/
1327 token = cgi->Com_EParse(text, errhead, name);
1328 if (!*text)
1329 return;
1330 if (*token == '}')
1331 break;
1332
1333 if (Q_streq(token, "tech") || Q_streq(token, "tech_not")) {
1334 if (requiredTemp->numLinks < MAX_TECHLINKS) {
1335 /* Set requirement-type. */
1336 if (Q_streq(token, "tech_not"))
1337 requiredTemp->links[requiredTemp->numLinks].type = RS_LINK_TECH_NOT;
1338 else
1339 requiredTemp->links[requiredTemp->numLinks].type = RS_LINK_TECH;
1340
1341 /* Set requirement-name (id). */
1342 token = Com_Parse(text);
1343 requiredTemp->links[requiredTemp->numLinks].id = Mem_PoolStrDup(token, cp_campaignPool, 0);
1344
1345 Com_DPrintf(DEBUG_CLIENT, "RS_ParseTechnologies: require-tech ('tech' or 'tech_not')- %s\n", requiredTemp->links[requiredTemp->numLinks].id);
1346
1347 requiredTemp->numLinks++;
1348 } else {
1349 Com_Printf("RS_ParseTechnologies: \"%s\" Too many 'required' defined. Limit is %i - ignored.\n", name, MAX_TECHLINKS);
1350 }
1351 } else if (Q_streq(token, "item")) {
1352 /* Defines what items need to be collected for this item to be researchable. */
1353 if (requiredTemp->numLinks < MAX_TECHLINKS) {
1354 linkedList_t* list;
1355 if (!Com_ParseList(text, &list)) {
1356 cgi->Com_Error(ERR_DROP, "RS_ParseTechnologies: error while reading required item tuple");
1357 }
1358
1359 if (cgi->LIST_Count(list) != 2) {
1360 cgi->Com_Error(ERR_DROP, "RS_ParseTechnologies: required item tuple must contains 2 elements (id pos)");
1361 }
1362
1363 const char* idToken = (char*)list->data;
1364 const char* amountToken = (char*)list->next->data;
1365
1366 /* Set requirement-type. */
1367 requiredTemp->links[requiredTemp->numLinks].type = RS_LINK_ITEM;
1368 /* Set requirement-name (id). */
1369 requiredTemp->links[requiredTemp->numLinks].id = Mem_PoolStrDup(idToken, cp_campaignPool, 0);
1370 /* Set requirement-amount of item. */
1371 requiredTemp->links[requiredTemp->numLinks].amount = atoi(amountToken);
1372 Com_DPrintf(DEBUG_CLIENT, "RS_ParseTechnologies: require-item - %s - %i\n", requiredTemp->links[requiredTemp->numLinks].id, requiredTemp->links[requiredTemp->numLinks].amount);
1373 requiredTemp->numLinks++;
1374 cgi->LIST_Delete(&list);
1375 } else {
1376 Com_Printf("RS_ParseTechnologies: \"%s\" Too many 'required' defined. Limit is %i - ignored.\n", name, MAX_TECHLINKS);
1377 }
1378 } else if (Q_streq(token, "alienglobal")) {
1379 if (requiredTemp->numLinks < MAX_TECHLINKS) {
1380 /* Set requirement-type. */
1381 requiredTemp->links[requiredTemp->numLinks].type = RS_LINK_ALIEN_GLOBAL;
1382 Com_DPrintf(DEBUG_CLIENT, "RS_ParseTechnologies: require-alienglobal - %i\n", requiredTemp->links[requiredTemp->numLinks].amount);
1383
1384 /* Set requirement-amount of item. */
1385 token = Com_Parse(text);
1386 requiredTemp->links[requiredTemp->numLinks].amount = atoi(token);
1387 requiredTemp->numLinks++;
1388 } else {
1389 Com_Printf("RS_ParseTechnologies: \"%s\" Too many 'required' defined. Limit is %i - ignored.\n", name, MAX_TECHLINKS);
1390 }
1391 } else if (Q_streq(token, "alien_dead") || Q_streq(token, "alien")) { /* Does this only check the beginning of the string? */
1392 /* Defines what live or dead aliens need to be collected for this item to be researchable. */
1393 if (requiredTemp->numLinks < MAX_TECHLINKS) {
1394 /* Set requirement-type. */
1395 if (Q_streq(token, "alien_dead")) {
1396 requiredTemp->links[requiredTemp->numLinks].type = RS_LINK_ALIEN_DEAD;
1397 Com_DPrintf(DEBUG_CLIENT, "RS_ParseTechnologies: require-alien dead - %s - %i\n", requiredTemp->links[requiredTemp->numLinks].id, requiredTemp->links[requiredTemp->numLinks].amount);
1398 } else {
1399 requiredTemp->links[requiredTemp->numLinks].type = RS_LINK_ALIEN;
1400 Com_DPrintf(DEBUG_CLIENT, "RS_ParseTechnologies: require-alien alive - %s - %i\n", requiredTemp->links[requiredTemp->numLinks].id, requiredTemp->links[requiredTemp->numLinks].amount);
1401 }
1402
1403 linkedList_t* list;
1404 if (!Com_ParseList(text, &list)) {
1405 cgi->Com_Error(ERR_DROP, "RS_ParseTechnologies: error while reading required alien tuple");
1406 }
1407
1408 if (cgi->LIST_Count(list) != 2) {
1409 cgi->Com_Error(ERR_DROP, "RS_ParseTechnologies: required alien tuple must contains 2 elements (id pos)");
1410 }
1411
1412 const char* idToken = (char*)list->data;
1413 const char* amountToken = (char*)list->next->data;
1414
1415 /* Set requirement-name (id). */
1416 requiredTemp->links[requiredTemp->numLinks].id = Mem_PoolStrDup(idToken, cp_campaignPool, 0);
1417 /* Set requirement-amount of item. */
1418 requiredTemp->links[requiredTemp->numLinks].amount = atoi(amountToken);
1419 requiredTemp->numLinks++;
1420 cgi->LIST_Delete(&list);
1421 } else {
1422 Com_Printf("RS_ParseTechnologies: \"%s\" Too many 'required' defined. Limit is %i - ignored.\n", name, MAX_TECHLINKS);
1423 }
1424 } else if (Q_streq(token, "ufo")) {
1425 /* Defines what ufos need to be collected for this item to be researchable. */
1426 if (requiredTemp->numLinks < MAX_TECHLINKS) {
1427 linkedList_t* list;
1428 if (!Com_ParseList(text, &list)) {
1429 cgi->Com_Error(ERR_DROP, "RS_ParseTechnologies: error while reading required item tuple");
1430 }
1431
1432 if (cgi->LIST_Count(list) != 2) {
1433 cgi->Com_Error(ERR_DROP, "RS_ParseTechnologies: required item tuple must contains 2 elements (id pos)");
1434 }
1435
1436 const char* idToken = (char*)list->data;
1437 const char* amountToken = (char*)list->next->data;
1438
1439 /* Set requirement-type. */
1440 requiredTemp->links[requiredTemp->numLinks].type = RS_LINK_UFO;
1441 /* Set requirement-name (id). */
1442 requiredTemp->links[requiredTemp->numLinks].id = Mem_PoolStrDup(idToken, cp_campaignPool, 0);
1443 /* Set requirement-amount of item. */
1444 requiredTemp->links[requiredTemp->numLinks].amount = atoi(amountToken);
1445 Com_DPrintf(DEBUG_CLIENT, "RS_ParseTechnologies: require-ufo - %s - %i\n", requiredTemp->links[requiredTemp->numLinks].id, requiredTemp->links[requiredTemp->numLinks].amount);
1446 requiredTemp->numLinks++;
1447 }
1448 } else if (Q_streq(token, "antimatter")) {
1449 /* Defines what ufos need to be collected for this item to be researchable. */
1450 if (requiredTemp->numLinks < MAX_TECHLINKS) {
1451 /* Set requirement-type. */
1452 requiredTemp->links[requiredTemp->numLinks].type = RS_LINK_ANTIMATTER;
1453 /* Set requirement-amount of item. */
1454 token = Com_Parse(text);
1455 requiredTemp->links[requiredTemp->numLinks].amount = atoi(token);
1456 Com_DPrintf(DEBUG_CLIENT, "RS_ParseTechnologies: require-antimatter - %i\n", requiredTemp->links[requiredTemp->numLinks].amount);
1457 requiredTemp->numLinks++;
1458 }
1459 } else {
1460 Com_Printf("RS_ParseTechnologies: \"%s\" unknown requirement-type: \"%s\" - ignored.\n", name, token);
1461 }
1462 } while (*text);
1463 } else if (Q_streq(token, "up_chapter")) {
1464 /* UFOpaedia chapter */
1465 token = cgi->Com_EParse(text, errhead, name);
1466 if (!*text)
1467 return;
1468
1469 if (*token) {
1470 /* find chapter */
1471 for (i = 0; i < ccs.numChapters; i++) {
1472 if (Q_streq(token, ccs.upChapters[i].id)) {
1473 /* add entry to chapter */
1474 tech->upChapter = &ccs.upChapters[i];
1475 if (!ccs.upChapters[i].first) {
1476 ccs.upChapters[i].first = tech;
1477 ccs.upChapters[i].last = tech;
1478 tech->upPrev = nullptr;
1479 tech->upNext = nullptr;
1480 } else {
1481 /* get "last entry" in chapter */
1482 technology_t* techOld = ccs.upChapters[i].last;
1483 ccs.upChapters[i].last = tech;
1484 techOld->upNext = tech;
1485 ccs.upChapters[i].last->upPrev = techOld;
1486 ccs.upChapters[i].last->upNext = nullptr;
1487 }
1488 break;
1489 }
1490 if (i == ccs.numChapters)
1491 Com_Printf("RS_ParseTechnologies: \"%s\" - chapter \"%s\" not found.\n", name, token);
1492 }
1493 }
1494 } else if (Q_streq(token, "mail") || Q_streq(token, "mail_pre")) {
1495 techMail_t* mail;
1496
1497 /* how many mails found for this technology
1498 * used in UFOpaedia to check which article to display */
1499 tech->numTechMails++;
1500
1501 if (tech->numTechMails > TECHMAIL_MAX)
1502 Com_Printf("RS_ParseTechnologies: more techmail-entries found than supported. \"%s\"\n", name);
1503
1504 if (Q_streq(token, "mail_pre")) {
1505 mail = &tech->mail[TECHMAIL_PRE];
1506 } else {
1507 mail = &tech->mail[TECHMAIL_RESEARCHED];
1508 }
1509 token = cgi->Com_EParse(text, errhead, name);
1510 if (!*text || *token != '{')
1511 return;
1512
1513 /* grab the initial mail entry */
1514 token = cgi->Com_EParse(text, errhead, name);
1515 if (!*text || *token == '}')
1516 return;
1517 do {
1518 Com_ParseBlockToken(name, text, mail, valid_techmail_vars, cp_campaignPool, token);
1519
1520 /* grab the next entry */
1521 token = cgi->Com_EParse(text, errhead, name);
1522 if (!*text)
1523 return;
1524 } while (*text && *token != '}');
1525 /* default model is navarre */
1526 if (mail->model == nullptr)
1527 mail->model = "characters/navarre";
1528 } else {
1529 if (!Com_ParseBlockToken(name, text, tech, valid_tech_vars, cp_campaignPool, token))
1530 Com_Printf("RS_ParseTechnologies: unknown token \"%s\" ignored (entry %s)\n", token, name);
1531 }
1532 }
1533 } while (*text);
1534
1535 if (tech->provides) {
1536 hash = Com_HashKey(tech->provides, TECH_HASH_SIZE);
1537 /* link the variable in */
1538 /* techHashProvided should be null on the first run */
1539 tech->hashProvidedNext = techHashProvided[hash];
1540 /* set the techHashProvided pointer to the current tech */
1541 /* if there were already others in techHashProvided at position hash, they are now
1542 * accessable via tech->next - loop until tech->next is null (the first tech
1543 * at that position)
1544 */
1545 techHashProvided[hash] = tech;
1546 } else {
1547 if (tech->type == RS_WEAPON || tech->type == RS_ARMOUR) {
1548 Sys_Error("RS_ParseTechnologies: weapon or armour tech without a provides property");
1549 }
1550 Com_DPrintf(DEBUG_CLIENT, "tech '%s' doesn't have a provides string\n", tech->id);
1551 }
1552
1553 /* set the overall reseach time to the one given in the ufo-file. */
1554 tech->overallTime = tech->time;
1555 }
1556
RS_IsValidTechIndex(int techIdx)1557 static inline bool RS_IsValidTechIndex (int techIdx)
1558 {
1559 if (techIdx == TECH_INVALID)
1560 return false;
1561 if (techIdx < 0 || techIdx >= ccs.numTechnologies)
1562 return false;
1563 if (techIdx >= MAX_TECHNOLOGIES)
1564 return false;
1565
1566 return true;
1567 }
1568
1569 /**
1570 * @brief Checks if the technology (tech-index) has been researched.
1571 * @param[in] techIdx index of the technology.
1572 * @return @c true if the technology has been researched, otherwise (or on error) false;
1573 * @sa RS_IsResearched_ptr
1574 */
RS_IsResearched_idx(int techIdx)1575 bool RS_IsResearched_idx (int techIdx)
1576 {
1577 if (!RS_IsValidTechIndex(techIdx))
1578 return false;
1579
1580 if (ccs.technologies[techIdx].statusResearch == RS_FINISH)
1581 return true;
1582
1583 return false;
1584 }
1585
1586 /**
1587 * @brief Checks whether an item is already researched
1588 * @sa RS_IsResearched_idx
1589 * Call this function if you already hold a tech pointer
1590 */
RS_IsResearched_ptr(const technology_t * tech)1591 bool RS_IsResearched_ptr (const technology_t* tech)
1592 {
1593 if (tech && tech->statusResearch == RS_FINISH)
1594 return true;
1595 return false;
1596 }
1597
1598 /**
1599 * @brief Returns the technology pointer for a tech index.
1600 * You can use this instead of "&ccs.technologies[techIdx]" to avoid having to check valid indices.
1601 * @param[in] techIdx Index in the global ccs.technologies[] array.
1602 * @return technology_t pointer or nullptr if an error occurred.
1603 */
RS_GetTechByIDX(int techIdx)1604 technology_t* RS_GetTechByIDX (int techIdx)
1605 {
1606 if (!RS_IsValidTechIndex(techIdx))
1607 return nullptr;
1608 return &ccs.technologies[techIdx];
1609 }
1610
1611
1612 /**
1613 * @brief return a pointer to the technology identified by given id string
1614 * @param[in] id Unique identifier of the tech as defined in the research.ufo file (e.g. "tech xxxx").
1615 * @return technology_t pointer or nullptr if an error occured.
1616 */
RS_GetTechByID(const char * id)1617 technology_t* RS_GetTechByID (const char* id)
1618 {
1619 unsigned hash;
1620 technology_t* tech;
1621
1622 if (Q_strnull(id))
1623 return nullptr;
1624
1625 hash = Com_HashKey(id, TECH_HASH_SIZE);
1626 for (tech = techHash[hash]; tech; tech = tech->hashNext)
1627 if (!Q_strcasecmp(id, tech->id))
1628 return tech;
1629
1630 Com_Printf("RS_GetTechByID: Could not find a technology with id \"%s\"\n", id);
1631 return nullptr;
1632 }
1633
1634 /**
1635 * @brief returns a pointer to the item tech (as listed in "provides")
1636 * @param[in] idProvided Unique identifier of the object the tech is providing
1637 * @return The tech for the given object id or nullptr if not found
1638 */
RS_GetTechByProvided(const char * idProvided)1639 technology_t* RS_GetTechByProvided (const char* idProvided)
1640 {
1641 unsigned hash;
1642 technology_t* tech;
1643
1644 if (!idProvided)
1645 return nullptr;
1646 /* catch empty strings */
1647 if (idProvided[0] == '\0')
1648 return nullptr;
1649
1650 hash = Com_HashKey(idProvided, TECH_HASH_SIZE);
1651 for (tech = techHashProvided[hash]; tech; tech = tech->hashProvidedNext)
1652 if (!Q_strcasecmp(idProvided, tech->provides))
1653 return tech;
1654
1655 Com_DPrintf(DEBUG_CLIENT, "RS_GetTechByProvided: %s\n", idProvided);
1656 /* if a building, probably needs another building */
1657 /* if not a building, catch nullptr where function is called! */
1658 return nullptr;
1659 }
1660
1661 /**
1662 * @brief Searches for the technology that has the most scientists assigned in a given base.
1663 * @param[in] base In what base the tech should be researched.
1664 */
RS_GetTechWithMostScientists(const struct base_s * base)1665 technology_t* RS_GetTechWithMostScientists (const struct base_s* base)
1666 {
1667 technology_t* tech;
1668 int i, max;
1669
1670 if (!base)
1671 return nullptr;
1672
1673 tech = nullptr;
1674 max = 0;
1675 for (i = 0; i < ccs.numTechnologies; i++) {
1676 technology_t* tech_temp = RS_GetTechByIDX(i);
1677 if (tech_temp->statusResearch == RS_RUNNING && tech_temp->base == base) {
1678 if (tech_temp->scientists > max) {
1679 tech = tech_temp;
1680 max = tech->scientists;
1681 }
1682 }
1683 }
1684
1685 /* this tech has at least one assigned scientist or is a nullptr pointer */
1686 return tech;
1687 }
1688
1689 /**
1690 * @brief Returns the index (idx) of a "tech" entry given it's name.
1691 * @param[in] name the name of the tech
1692 */
RS_GetTechIdxByName(const char * name)1693 int RS_GetTechIdxByName (const char* name)
1694 {
1695 technology_t* tech;
1696 const unsigned hash = Com_HashKey(name, TECH_HASH_SIZE);
1697
1698 for (tech = techHash[hash]; tech; tech = tech->hashNext)
1699 if (!Q_strcasecmp(name, tech->id))
1700 return tech->idx;
1701
1702 Com_Printf("RS_GetTechIdxByName: Could not find tech '%s'\n", name);
1703 return TECH_INVALID;
1704 }
1705
1706 /**
1707 * @brief Returns the number of employees searching in labs in given base.
1708 * @param[in] base Pointer to the base
1709 * @sa B_ResetAllStatusAndCapacities_f
1710 * @note must not return 0 if hasBuilding[B_LAB] is false: used to update capacity
1711 */
RS_CountScientistsInBase(const base_t * base)1712 int RS_CountScientistsInBase (const base_t* base)
1713 {
1714 int i, counter = 0;
1715
1716 for (i = 0; i < ccs.numTechnologies; i++) {
1717 const technology_t* tech = &ccs.technologies[i];
1718 if (tech->base == base) {
1719 /* Get a free lab from the base. */
1720 counter += tech->scientists;
1721 }
1722 }
1723
1724 return counter;
1725 }
1726
1727 /**
1728 * @brief Remove all exceeding scientist.
1729 * @param[in, out] base Pointer to base where a scientist should be removed.
1730 */
RS_RemoveScientistsExceedingCapacity(base_t * base)1731 void RS_RemoveScientistsExceedingCapacity (base_t* base)
1732 {
1733 assert(base);
1734
1735 /* Make sure current CAP_LABSPACE capacity is set to proper value */
1736 CAP_SetCurrent(base, CAP_LABSPACE, RS_CountScientistsInBase(base));
1737
1738 while (CAP_GetFreeCapacity(base, CAP_LABSPACE) < 0) {
1739 technology_t* tech = RS_GetTechWithMostScientists(base);
1740 RS_RemoveScientist(tech, nullptr);
1741 }
1742 }
1743
1744 /**
1745 * @brief Save callback for research and technologies
1746 * @param[out] parent XML Node structure, where we write the information to
1747 * @sa RS_LoadXML
1748 */
RS_SaveXML(xmlNode_t * parent)1749 bool RS_SaveXML (xmlNode_t* parent)
1750 {
1751 int i;
1752 xmlNode_t* node;
1753
1754 cgi->Com_RegisterConstList(saveResearchConstants);
1755 node = cgi->XML_AddNode(parent, SAVE_RESEARCH_RESEARCH);
1756 for (i = 0; i < ccs.numTechnologies; i++) {
1757 int j;
1758 const technology_t* t = RS_GetTechByIDX(i);
1759
1760 xmlNode_t* snode = cgi->XML_AddNode(node, SAVE_RESEARCH_TECH);
1761 cgi->XML_AddString(snode, SAVE_RESEARCH_ID, t->id);
1762 cgi->XML_AddBoolValue(snode, SAVE_RESEARCH_STATUSCOLLECTED, t->statusCollected);
1763 cgi->XML_AddFloatValue(snode, SAVE_RESEARCH_TIME, t->time);
1764 cgi->XML_AddString(snode, SAVE_RESEARCH_STATUSRESEARCH, cgi->Com_GetConstVariable(SAVE_RESEARCHSTATUS_NAMESPACE, t->statusResearch));
1765 if (t->base)
1766 cgi->XML_AddInt(snode, SAVE_RESEARCH_BASE, t->base->idx);
1767 cgi->XML_AddIntValue(snode, SAVE_RESEARCH_SCIENTISTS, t->scientists);
1768 cgi->XML_AddBool(snode, SAVE_RESEARCH_STATUSRESEARCHABLE, t->statusResearchable);
1769 cgi->XML_AddDate(snode, SAVE_RESEARCH_PREDATE, t->preResearchedDate.day, t->preResearchedDate.sec);
1770 cgi->XML_AddDate(snode, SAVE_RESEARCH_DATE, t->researchedDate.day, t->researchedDate.sec);
1771 cgi->XML_AddInt(snode, SAVE_RESEARCH_MAILSENT, t->mailSent);
1772
1773 /* save which techMails were read */
1774 /** @todo this should be handled by the mail system */
1775 for (j = 0; j < TECHMAIL_MAX; j++) {
1776 if (t->mail[j].read) {
1777 xmlNode_t* ssnode = cgi->XML_AddNode(snode, SAVE_RESEARCH_MAIL);
1778 cgi->XML_AddInt(ssnode, SAVE_RESEARCH_MAIL_ID, j);
1779 }
1780 }
1781 }
1782 cgi->Com_UnregisterConstList(saveResearchConstants);
1783
1784 return true;
1785 }
1786
1787 /**
1788 * @brief Load callback for research and technologies
1789 * @param[in] parent XML Node structure, where we get the information from
1790 * @sa RS_SaveXML
1791 */
RS_LoadXML(xmlNode_t * parent)1792 bool RS_LoadXML (xmlNode_t* parent)
1793 {
1794 xmlNode_t* topnode;
1795 xmlNode_t* snode;
1796 bool success = true;
1797
1798 topnode = cgi->XML_GetNode(parent, SAVE_RESEARCH_RESEARCH);
1799 if (!topnode)
1800 return false;
1801
1802 cgi->Com_RegisterConstList(saveResearchConstants);
1803 for (snode = cgi->XML_GetNode(topnode, SAVE_RESEARCH_TECH); snode; snode = cgi->XML_GetNextNode(snode, topnode, "tech")) {
1804 const char* techString = cgi->XML_GetString(snode, SAVE_RESEARCH_ID);
1805 xmlNode_t* ssnode;
1806 int baseIdx;
1807 technology_t* t = RS_GetTechByID(techString);
1808 const char* type = cgi->XML_GetString(snode, SAVE_RESEARCH_STATUSRESEARCH);
1809
1810 if (!t) {
1811 Com_Printf("......your game doesn't know anything about tech '%s'\n", techString);
1812 continue;
1813 }
1814
1815 if (!cgi->Com_GetConstIntFromNamespace(SAVE_RESEARCHSTATUS_NAMESPACE, type, (int*) &t->statusResearch)) {
1816 Com_Printf("Invalid research status '%s'\n", type);
1817 success = false;
1818 break;
1819 }
1820
1821 t->statusCollected = cgi->XML_GetBool(snode, SAVE_RESEARCH_STATUSCOLLECTED, false);
1822 t->time = cgi->XML_GetFloat(snode, SAVE_RESEARCH_TIME, 0.0);
1823 /* Prepare base-index for later pointer-restoration in RS_PostLoadInit. */
1824 baseIdx = cgi->XML_GetInt(snode, SAVE_RESEARCH_BASE, -1);
1825 if (baseIdx >= 0)
1826 /* even if the base is not yet loaded we can set the pointer already */
1827 t->base = B_GetBaseByIDX(baseIdx);
1828 t->scientists = cgi->XML_GetInt(snode, SAVE_RESEARCH_SCIENTISTS, 0);
1829 t->statusResearchable = cgi->XML_GetBool(snode, SAVE_RESEARCH_STATUSRESEARCHABLE, false);
1830 cgi->XML_GetDate(snode, SAVE_RESEARCH_PREDATE, &t->preResearchedDate.day, &t->preResearchedDate.sec);
1831 cgi->XML_GetDate(snode, SAVE_RESEARCH_DATE, &t->researchedDate.day, &t->researchedDate.sec);
1832 t->mailSent = (mailSentType_t)cgi->XML_GetInt(snode, SAVE_RESEARCH_MAILSENT, 0);
1833
1834 /* load which techMails were read */
1835 /** @todo this should be handled by the mail system */
1836 for (ssnode = cgi->XML_GetNode(snode, SAVE_RESEARCH_MAIL); ssnode; ssnode = cgi->XML_GetNextNode(ssnode, snode, SAVE_RESEARCH_MAIL)) {
1837 const int j= cgi->XML_GetInt(ssnode, SAVE_RESEARCH_MAIL_ID, TECHMAIL_MAX);
1838 if (j < TECHMAIL_MAX)
1839 t->mail[j].read = true;
1840 else
1841 Com_Printf("......your save game contains unknown techmail ids... \n");
1842 }
1843
1844 #ifdef DEBUG
1845 if (t->statusResearch == RS_RUNNING && t->scientists > 0) {
1846 if (!t->base) {
1847 Com_Printf("No base but research is running and scientists are assigned");
1848 success = false;
1849 break;
1850 }
1851 }
1852 #endif
1853 }
1854 cgi->Com_UnregisterConstList(saveResearchConstants);
1855
1856 return success;
1857 }
1858
1859 /**
1860 * @brief Returns true if the current base is able to handle research
1861 * @sa B_BaseInit_f
1862 * probably menu function, but not for research gui
1863 */
RS_ResearchAllowed(const base_t * base)1864 bool RS_ResearchAllowed (const base_t* base)
1865 {
1866 assert(base);
1867 return !B_IsUnderAttack(base) && B_GetBuildingStatus(base, B_LAB) && E_CountHired(base, EMPL_SCIENTIST) > 0;
1868 }
1869
1870 /**
1871 * @brief Checks the parsed tech data for errors
1872 * @return false if there are errors - true otherwise
1873 */
RS_ScriptSanityCheck(void)1874 bool RS_ScriptSanityCheck (void)
1875 {
1876 int i, error = 0;
1877 technology_t* t;
1878
1879 for (i = 0, t = ccs.technologies; i < ccs.numTechnologies; i++, t++) {
1880 if (!t->name) {
1881 error++;
1882 Com_Printf("...... technology '%s' has no name\n", t->id);
1883 }
1884 if (!t->provides) {
1885 switch (t->type) {
1886 case RS_TECH:
1887 case RS_NEWS:
1888 case RS_LOGIC:
1889 case RS_ALIEN:
1890 break;
1891 default:
1892 error++;
1893 Com_Printf("...... technology '%s' doesn't provide anything\n", t->id);
1894 break;
1895 }
1896 }
1897
1898 if (t->produceTime == 0) {
1899 switch (t->type) {
1900 case RS_TECH:
1901 case RS_NEWS:
1902 case RS_LOGIC:
1903 case RS_BUILDING:
1904 case RS_ALIEN:
1905 break;
1906 default:
1907 /** @todo error++; Crafts still give errors - are there any definitions missing? */
1908 Com_Printf("...... technology '%s' has zero (0) produceTime, is this on purpose?\n", t->id);
1909 break;
1910 }
1911 }
1912
1913 if (t->type != RS_LOGIC && (!t->description.text[0] || t->description.text[0][0] == '_')) {
1914 if (!t->description.text[0])
1915 Com_Printf("...... technology '%s' has a strange 'description' value '%s'.\n", t->id, t->description.text[0]);
1916 else
1917 Com_Printf("...... technology '%s' has no 'description' value.\n", t->id);
1918 }
1919 }
1920
1921 return !error;
1922 }
1923