Bug Summary

File:client/cgame/campaign/cp_research.cpp
Location:line 58, column 6
Description:Dereference of null pointer

Annotated Source Code

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