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