UFO: Alien Invasion
cp_research.cpp
Go to the documentation of this file.
1 
12 /*
13 Copyright (C) 2002-2022 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 "../../DateTime.h"
32 #include "../../cl_shared.h"
33 #include "../../../shared/parse.h"
34 #include "cp_campaign.h"
35 #include "cp_capacity.h"
36 #include "cp_research.h"
37 #include "cp_popup.h"
38 #include "cp_time.h"
39 #include "save/save_research.h"
40 #include "aliencontainment.h"
41 
42 #define TECH_HASH_SIZE 64
45 
47 
53 {
54  /* Remove all scientists from the technology. */
55  RS_StopResearch(tech);
56 
62  if (tech->preDescription.usedDescription < 0) {
63  /* For some reason the research proposal description was not set at this point - we just make sure it _is_ set. */
65  }
66 
67  /* execute the trigger only if the tech is not yet researched */
68  if (tech->finishedResearchEvent && tech->statusResearch != RS_FINISH)
69  cgi->Cmd_ExecuteString("%s", tech->finishedResearchEvent);
70 
71  tech->statusResearch = RS_FINISH;
73  if (!tech->statusResearchable) {
74  tech->statusResearchable = true;
76  }
77 
78  /* send a new message and add it to the mailclient */
79  if (tech->mailSent < MAILSENT_FINISHED && tech->type != RS_LOGIC) {
80  Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("A research project has been completed: %s\n"), _(tech->name));
83 
84  if (tech->announce) {
85  UP_OpenWith(tech->id);
86  }
87  }
88 }
89 
95 {
96  assert(tech);
97  while (tech->scientists > 0)
98  RS_RemoveScientist(tech, nullptr);
99 }
100 
108 {
109  if (!tech)
110  return;
111 
112  cgi->Com_DPrintf(DEBUG_CLIENT, "RS_MarkOneResearchable: \"%s\" marked as researchable.\n", tech->id);
113 
114  /* Don't do anything for not researchable techs. */
115  if (tech->time == -1)
116  return;
117 
118  /* Don't send mail for automatically completed techs. */
119  if (tech->time == 0)
120  tech->mailSent = MAILSENT_FINISHED;
121 
127  /* tech->description is checked before a research is finished */
128 
129  if (tech->mailSent < MAILSENT_PROPOSAL) {
130  Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("New research proposal: %s\n"), _(tech->name));
131  MSO_CheckAddNewMessage(NT_RESEARCH_PROPOSED, _("Unknown Technology researchable"), cp_messageBuffer, MSG_RESEARCH_PROPOSAL, tech);
132  tech->mailSent = MAILSENT_PROPOSAL;
133  }
134 
135  tech->statusResearchable = true;
136 
137  /* only change the date if it wasn't set before */
138  if (tech->preResearchedDate.getDateAsDays() == 0) {
140  }
141 }
142 
151 bool RS_RequirementsMet (const technology_t* tech, const base_t* base)
152 {
153  int i;
154  bool metAND = false;
155  bool metOR = false;
156  const requirements_t* requiredAND = &tech->requireAND; /* a list of AND-related requirements */
157  const requirements_t* requiredOR = &tech->requireOR; /* a list of OR-related requirements */
158 
159  if (!requiredAND && !requiredOR) {
160  cgi->Com_Printf("RS_RequirementsMet: No requirement list(s) given as parameter.\n");
161  return false;
162  }
163 
164  /* If there are no requirements defined at all we have 'met' them by default. */
165  if (requiredAND->numLinks == 0 && requiredOR->numLinks == 0) {
166  cgi->Com_DPrintf(DEBUG_CLIENT, "RS_RequirementsMet: No requirements set for this tech. They are 'met'.\n");
167  return true;
168  }
169 
170  if (requiredAND->numLinks) {
171  metAND = true;
172  for (i = 0; i < requiredAND->numLinks; i++) {
173  const requirement_t* req = &requiredAND->links[i];
174  switch (req->type) {
175  case RS_LINK_TECH:
176  /* if a tech that links itself is already marked researchable, we can research it */
177  if (!(Q_streq(req->id, tech->id) && tech->statusResearchable) && !RS_IsResearched_ptr(req->link.tech))
178  metAND = false;
179  break;
180  case RS_LINK_TECH_NOT:
181  if (RS_IsResearched_ptr(req->link.tech))
182  metAND = false;
183  break;
184  case RS_LINK_ITEM:
185  /* The same code is used in "PR_RequirementsMet" */
186  if (!base || B_ItemInBase(req->link.od, base) < req->amount)
187  metAND = false;
188  break;
189  case RS_LINK_ALIEN_DEAD:
190  if (!base || !base->alienContainment || base->alienContainment->getDead(req->link.td) < req->amount)
191  metAND = false;
192  break;
193  case RS_LINK_ALIEN:
194  if (!base || !base->alienContainment || base->alienContainment->getAlive(req->link.td) < req->amount)
195  metAND = false;
196  break;
198  if (AL_CountAll() < req->amount)
199  metAND = false;
200  break;
201  case RS_LINK_UFO:
202  if (US_UFOsInStorage(req->link.aircraft, nullptr) < req->amount)
203  metAND = false;
204  break;
205  case RS_LINK_ANTIMATTER:
206  if (!base || B_AntimatterInBase(base) < req->amount)
207  metAND = false;
208  break;
209  default:
210  break;
211  }
212 
213  if (!metAND)
214  break;
215  }
216  }
217 
218  if (requiredOR->numLinks)
219  for (i = 0; i < requiredOR->numLinks; i++) {
220  const requirement_t* req = &requiredOR->links[i];
221  switch (req->type) {
222  case RS_LINK_TECH:
223  if (RS_IsResearched_ptr(req->link.tech))
224  metOR = true;
225  break;
226  case RS_LINK_TECH_NOT:
227  if (!RS_IsResearched_ptr(req->link.tech))
228  metOR = true;
229  break;
230  case RS_LINK_ITEM:
231  /* The same code is used in "PR_RequirementsMet" */
232  if (base && B_ItemInBase(req->link.od, base) >= req->amount)
233  metOR = true;
234  break;
235  case RS_LINK_ALIEN:
236  if (base && base->alienContainment && base->alienContainment->getAlive(req->link.td) >= req->amount)
237  metOR = true;
238  break;
239  case RS_LINK_ALIEN_DEAD:
240  if (base && base->alienContainment && base->alienContainment->getDead(req->link.td) >= req->amount)
241  metOR = true;
242  break;
244  if (AL_CountAll() >= req->amount)
245  metOR = true;
246  break;
247  case RS_LINK_UFO:
248  if (US_UFOsInStorage(req->link.aircraft, nullptr) >= req->amount)
249  metOR = true;
250  break;
251  case RS_LINK_ANTIMATTER:
252  if (base && B_AntimatterInBase(base) >= req->amount)
253  metOR = true;
254  break;
255  default:
256  break;
257  }
258 
259  if (metOR)
260  break;
261  }
262  cgi->Com_DPrintf(DEBUG_CLIENT, "met_AND is %i, met_OR is %i\n", metAND, metOR);
263 
264  return (metAND || metOR);
265 }
266 
272 {
273  /* Return (unparsed) default description (0) if nothing is defined.
274  * it is _always_ set, even if numDescriptions is zero. See RS_ParseTechnologies (standard values). */
275  if (desc->numDescriptions == 0)
276  return desc->text[0];
277 
278  /* Return already used description if it's defined. */
279  if (desc->usedDescription >= 0)
280  return desc->text[desc->usedDescription];
281 
282  /* Search for useable description text (first match is returned => order is important)
283  * The default (0) entry is skipped here. */
284  for (int i = 1; i < desc->numDescriptions; i++) {
285  const technology_t* tech = RS_GetTechByID(desc->tech[i]);
286  if (!tech)
287  continue;
288 
289  if (RS_IsResearched_ptr(tech)) {
290  desc->usedDescription = i;
291  return desc->text[i];
292  }
293  }
294 
295  return desc->text[0];
296 }
297 
306 {
307  assert(tech);
308 
309  if (tech->time == 0) /* Don't send mail for automatically completed techs. */
310  tech->mailSent = MAILSENT_FINISHED;
311 
312  if (tech->mailSent < MAILSENT_PROPOSAL) {
313  if (tech->statusResearch < RS_FINISH) {
314  Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("New research proposal: %s\n"), _(tech->name));
316  }
317  tech->mailSent = MAILSENT_PROPOSAL;
318  }
319 
320  /* only change the date if it wasn't set before */
321  if (tech->preResearchedDate.getDateAsDays() == 0) {
323  }
324 
325  tech->statusCollected = true;
326 }
327 
335 void RS_MarkResearchable (const base_t* base, bool init)
336 {
337  const base_t* thisBase = base;
338 
339  for (int i = 0; i < ccs.numTechnologies; i++) {
340  technology_t* tech = RS_GetTechByIDX(i);
341  /* In case we loopback we need to check for already marked techs. */
342  if (tech->statusResearchable)
343  continue;
344  /* Check for collected items/aliens/etc... */
345  if (tech->statusResearch == RS_FINISH)
346  continue;
347 
348  cgi->Com_DPrintf(DEBUG_CLIENT, "RS_MarkResearchable: handling \"%s\".\n", tech->id);
349 
350  if (tech->base)
351  base = tech->base;
352  else
353  base = thisBase;
354 
355  /* If required techs are all researched and all other requirements are met, mark this as researchable. */
356  /* All requirements are met. */
357  if (RS_RequirementsMet(tech, base)) {
358  cgi->Com_DPrintf(DEBUG_CLIENT, "RS_MarkResearchable: \"%s\" marked researchable. reason:requirements.\n", tech->id);
359  if (init && tech->time == 0)
360  tech->mailSent = MAILSENT_PROPOSAL;
362  }
363 
364  /* If the tech is a 'free' one (such as ammo for a weapon),
365  * mark it as researched and loop back to see if it unlocks
366  * any other techs */
367  if (tech->statusResearchable && tech->time == 0) {
368  if (init)
369  tech->mailSent = MAILSENT_FINISHED;
370  RS_ResearchFinish(tech);
371  cgi->Com_DPrintf(DEBUG_CLIENT, "RS_MarkResearchable: automatically researched \"%s\"\n", tech->id);
372  /* Restart the loop as this may have unlocked new possibilities. */
373  i = -1;
374  }
375  }
376  cgi->Com_DPrintf(DEBUG_CLIENT, "RS_MarkResearchable: Done.\n");
377 }
378 
384 {
385  for (int i = 0; i < reqs->numLinks; i++) {
386  requirement_t* req = &reqs->links[i];
387  switch (req->type) {
388  case RS_LINK_TECH:
389  case RS_LINK_TECH_NOT:
390  /* Get the index in the techtree. */
391  req->link.tech = RS_GetTechByID(req->id);
392  if (!req->link.tech)
393  cgi->Com_Error(ERR_DROP, "RS_AssignTechLinks: Could not get tech definition for '%s'", req->id);
394  break;
395  case RS_LINK_ITEM:
396  /* Get index in item-list. */
397  req->link.od = INVSH_GetItemByID(req->id);
398  if (!req->link.od)
399  cgi->Com_Error(ERR_DROP, "RS_AssignTechLinks: Could not get item definition for '%s'", req->id);
400  break;
401  case RS_LINK_ALIEN:
402  case RS_LINK_ALIEN_DEAD:
403  req->link.td = cgi->Com_GetTeamDefinitionByID(req->id);
404  if (!req->link.td)
405  cgi->Com_Error(ERR_DROP, "RS_AssignTechLinks: Could not get alien type (alien or alien_dead) definition for '%s'", req->id);
406  break;
407  case RS_LINK_UFO:
408  req->link.aircraft = AIR_GetAircraft(req->id);
409  break;
410  default:
411  break;
412  }
413  }
414 }
415 
421 {
424  for (int i = 0; i < ccs.numTechnologies; i++) {
425  technology_t* tech = RS_GetTechByIDX(i);
426  if (tech->requireAND.numLinks)
428  if (tech->requireOR.numLinks)
430  if (tech->requireForProduction.numLinks)
432  }
433 
434  /* Link the redirected technologies to their correct "parents" */
435  while (ll) {
436  /* Get the data stored in the linked list. */
437  assert(ll);
438  technology_t* redirectedTech = (technology_t*) ll->data;
439  ll = ll->next;
440 
441  assert(redirectedTech);
442 
443  assert(ll);
444  redirectedTech->redirect = RS_GetTechByID((char*)ll->data);
445  ll = ll->next;
446  }
447 
448  /* clean up redirected techs list as it is no longer needed */
449  cgi->LIST_Delete(&redirectedTechs);
450 }
451 
457 {
458  if (item == nullptr)
459  cgi->Com_Error(ERR_DROP, "RS_GetTechForItem: No item given");
460  if (item->idx < 0 || item->idx > lengthof(ccs.objDefTechs))
461  cgi->Com_Error(ERR_DROP, "RS_GetTechForItem: Buffer overflow");
462  if (ccs.objDefTechs[item->idx] == nullptr)
463  cgi->Com_Error(ERR_DROP, "RS_GetTechForItem: No technology for item %s", item->id);
464  return ccs.objDefTechs[item->idx];
465 }
466 
472 {
473  if (team == nullptr)
474  cgi->Com_Error(ERR_DROP, "RS_GetTechForTeam: No team given");
475  if (team->idx < 0 || team->idx > lengthof(ccs.teamDefTechs))
476  cgi->Com_Error(ERR_DROP, "RS_GetTechForTeam: Buffer overflow");
477  if (ccs.teamDefTechs[team->idx] == nullptr)
478  cgi->Com_Error(ERR_DROP, "RS_GetTechForTeam: No technology for team %s", team->id);
479  return ccs.teamDefTechs[team->idx];
480 }
481 
491 void RS_InitTree (const campaign_t* campaign, bool load)
492 {
493  int i, j;
494  technology_t* tech;
495  byte found;
496  const objDef_t* od;
497 
498  /* Add links to technologies. */
499  for (i = 0, od = cgi->csi->ods; i < cgi->csi->numODs; i++, od++) {
501  if (!ccs.objDefTechs[od->idx])
502  cgi->Com_Error(ERR_DROP, "RS_InitTree: Could not find a valid tech for item %s", od->id);
503  }
504 
505  for (i = 0, tech = ccs.technologies; i < ccs.numTechnologies; i++, tech++) {
506  for (j = 0; j < tech->markResearched.numDefinitions; j++) {
507  if (tech->markResearched.markOnly[j] && Q_streq(tech->markResearched.campaign[j], campaign->researched)) {
508  cgi->Com_DPrintf(DEBUG_CLIENT, "...mark %s as researched\n", tech->id);
509  RS_ResearchFinish(tech);
510  break;
511  }
512  }
513 
514  /* Save the idx to the id-names of the different requirement-types for quicker access.
515  * The id-strings themself are not really needed afterwards :-/ */
518 
519  /* Search in correct data/.ufo */
520  switch (tech->type) {
521  case RS_CRAFTITEM:
522  if (!tech->name)
523  cgi->Com_DPrintf(DEBUG_CLIENT, "RS_InitTree: \"%s\" A type craftitem needs to have a 'name\txxx' defined.", tech->id);
524  break;
525  case RS_NEWS:
526  if (!tech->name)
527  cgi->Com_DPrintf(DEBUG_CLIENT, "RS_InitTree: \"%s\" A 'type news' item needs to have a 'name\txxx' defined.", tech->id);
528  break;
529  case RS_TECH:
530  if (!tech->name)
531  cgi->Com_DPrintf(DEBUG_CLIENT, "RS_InitTree: \"%s\" A 'type tech' item needs to have a 'name\txxx' defined.", tech->id);
532  break;
533  case RS_WEAPON:
534  case RS_ARMOUR:
535  found = false;
536  for (j = 0; j < cgi->csi->numODs; j++) { /* j = item index */
537  const objDef_t* item = INVSH_GetItemByIDX(j);
538 
539  /* This item has been 'provided' -> get the correct data. */
540  if (Q_streq(tech->provides, item->id)) {
541  found = true;
542  if (!tech->name)
543  tech->name = cgi->PoolStrDup(item->name, cp_campaignPool, 0);
544  if (!tech->mdl)
545  tech->mdl = cgi->PoolStrDup(item->model, cp_campaignPool, 0);
546  if (!tech->image)
547  tech->image = cgi->PoolStrDup(item->image, cp_campaignPool, 0);
548  break;
549  }
550  }
551  /* No id found in cgi->csi->ods */
552  if (!found) {
553  tech->name = cgi->PoolStrDup(tech->id, cp_campaignPool, 0);
554  cgi->Com_Printf("RS_InitTree: \"%s\" - Linked weapon or armour (provided=\"%s\") not found. Tech-id used as name.\n",
555  tech->id, tech->provides);
556  }
557  break;
558  case RS_BUILDING:
559  found = false;
560  for (j = 0; j < ccs.numBuildingTemplates; j++) {
561  building_t* building = &ccs.buildingTemplates[j];
562  /* This building has been 'provided' -> get the correct data. */
563  if (Q_streq(tech->provides, building->id)) {
564  found = true;
565  if (!tech->name)
566  tech->name = cgi->PoolStrDup(building->name, cp_campaignPool, 0);
567  if (!tech->image)
568  tech->image = cgi->PoolStrDup(building->image, cp_campaignPool, 0);
569  break;
570  }
571  }
572  if (!found) {
573  tech->name = cgi->PoolStrDup(tech->id, cp_campaignPool, 0);
574  cgi->Com_DPrintf(DEBUG_CLIENT, "RS_InitTree: \"%s\" - Linked building (provided=\"%s\") not found. Tech-id used as name.\n",
575  tech->id, tech->provides);
576  }
577  break;
578  case RS_CRAFT:
579  found = false;
580  for (j = 0; j < ccs.numAircraftTemplates; j++) {
581  aircraft_t* aircraftTemplate = &ccs.aircraftTemplates[j];
582  /* This aircraft has been 'provided' -> get the correct data. */
583  if (!tech->provides)
584  cgi->Com_Error(ERR_FATAL, "RS_InitTree: \"%s\" - No linked aircraft or craft-upgrade.\n", tech->id);
585  if (Q_streq(tech->provides, aircraftTemplate->id)) {
586  found = true;
587  if (!tech->name)
588  tech->name = cgi->PoolStrDup(aircraftTemplate->name, cp_campaignPool, 0);
589  if (!tech->mdl) { /* DEBUG testing */
590  tech->mdl = cgi->PoolStrDup(aircraftTemplate->model, cp_campaignPool, 0);
591  cgi->Com_DPrintf(DEBUG_CLIENT, "RS_InitTree: aircraft model \"%s\" \n", aircraftTemplate->model);
592  }
593  aircraftTemplate->tech = tech;
594  break;
595  }
596  }
597  if (!found)
598  cgi->Com_Printf("RS_InitTree: \"%s\" - Linked aircraft or craft-upgrade (provided=\"%s\") not found.\n", tech->id, tech->provides);
599  break;
600  case RS_ALIEN:
601  /* does nothing right now */
602  break;
603  case RS_UGV:
605  break;
606  case RS_LOGIC:
607  /* Does not need any additional data. */
608  break;
609  }
610 
611  /* Check if we finally have a name for the tech. */
612  if (!tech->name) {
613  if (tech->type != RS_LOGIC)
614  cgi->Com_Error(ERR_DROP, "RS_InitTree: \"%s\" - no name found!", tech->id);
615  } else {
616  /* Fill in subject lines of tech-mails.
617  * The tech-name is copied if nothing is defined. */
618  for (j = 0; j < TECHMAIL_MAX; j++) {
619  /* Check if no subject was defined (but it is supposed to be sent) */
620  if (!tech->mail[j].subject && tech->mail[j].to) {
621  tech->mail[j].subject = tech->name;
622  }
623  }
624  }
625 
626  if (!tech->image && !tech->mdl)
627  cgi->Com_DPrintf(DEBUG_CLIENT, "Tech %s of type %i has no image (%p) and no model (%p) assigned.\n",
628  tech->id, tech->type, tech->image, tech->mdl);
629  }
630 
631  if (load) {
632  /* when you load a savegame right after starting UFO, the aircraft in bases
633  * and installations don't have any tech assigned */
634  AIR_Foreach(aircraft) {
635  /* if you already played before loading the game, tech are already defined for templates */
636  if (!aircraft->tech)
637  aircraft->tech = RS_GetTechByProvided(aircraft->id);
638  }
639  }
640 
641  cgi->Com_DPrintf(DEBUG_CLIENT, "RS_InitTree: Technology tree initialised. %i entries found.\n", i);
642 }
643 
654 void RS_AssignScientist (technology_t* tech, base_t* base, Employee* employee)
655 {
656  assert(tech);
657  cgi->Com_DPrintf(DEBUG_CLIENT, "RS_AssignScientist: %i | %s \n", tech->idx, tech->name);
658 
659  /* if the tech is already assigned to a base, use that one */
660  if (tech->base)
661  base = tech->base;
662 
663  assert(base);
664 
665  if (!employee)
666  employee = E_GetUnassignedEmployee(base, EMPL_SCIENTIST);
667  if (!employee) {
668  /* No scientists are free in this base. */
669  cgi->Com_DPrintf(DEBUG_CLIENT, "No free scientists in this base (%s) to assign to tech '%s'\n", base->name, tech->id);
670  return;
671  }
672 
673  if (!tech->statusResearchable)
674  return;
675 
676  if (CAP_GetFreeCapacity(base, CAP_LABSPACE) <= 0) {
677  CP_Popup(_("Not enough laboratories"), _("No free space in laboratories left.\nBuild more laboratories.\n"));
678  return;
679  }
680 
681  tech->scientists++;
682  tech->base = base;
683  CAP_AddCurrent(base, CAP_LABSPACE, 1);
684  employee->setAssigned(true);
685  tech->statusResearch = RS_RUNNING;
686 }
687 
695 void RS_RemoveScientist (technology_t* tech, Employee* employee)
696 {
697  assert(tech);
698 
699  /* no need to remove anything, but we can do some check */
700  if (tech->scientists == 0) {
701  assert(tech->base == nullptr);
702  assert(tech->statusResearch == RS_PAUSED);
703  return;
704  }
705 
706  if (!employee)
707  employee = E_GetAssignedEmployee(tech->base, EMPL_SCIENTIST);
708  if (!employee)
709  cgi->Com_Printf("RS_RemoveScientist: No assigned scientists found - serious inconsistency.\n");
710  else
711  employee->setAssigned(false);
712 
713  tech->scientists--;
714  CAP_AddCurrent(tech->base, CAP_LABSPACE, -1);
715 
716  assert(tech->scientists >= 0);
717  if (tech->scientists == 0) {
718  /* Remove the tech from the base if no scientists are left to research it. */
719  tech->base = nullptr;
720  tech->statusResearch = RS_PAUSED;
721  }
722 }
723 
731 void RS_RemoveFiredScientist (base_t* base, Employee* employee)
732 {
733  technology_t* tech;
734  Employee* freeScientist = E_GetUnassignedEmployee(base, EMPL_SCIENTIST);
735 
736  assert(base);
737  assert(employee);
738 
739  /* Get a tech where there is at least one scientist working on (unless no scientist working in this base) */
740  tech = RS_GetTechWithMostScientists(base);
741 
742  /* tech should never be nullptr, as there is at least 1 scientist working in base */
743  if (tech == nullptr) {
744  cgi->Com_Printf("RS_RemoveFiredScientist: Cannot unassign scientist %d no tech is being researched in base %d\n", employee->chr.ucn, base->idx);
745  employee->setAssigned(false);
746  } else {
747  RS_RemoveScientist(tech, employee);
748  }
749 
750  /* if there is at least one scientist not working on a project, make this one replace removed employee */
751  if (freeScientist)
752  RS_AssignScientist(tech, base, freeScientist);
753 }
754 
762 static void RS_MarkResearched (technology_t* tech, const base_t* base)
763 {
764  RS_ResearchFinish(tech);
765  cgi->Com_DPrintf(DEBUG_CLIENT, "Research of \"%s\" finished.\n", tech->id);
766  RS_MarkResearchable(base);
767 }
768 
774 bool RS_MarkStoryLineEventResearched (const char* techID)
775 {
776  technology_t* tech = RS_GetTechByID(techID);
777  if (!RS_IsResearched_ptr(tech)) {
778  const base_t* base = B_GetNext(nullptr);
779  if (base != nullptr) {
780  RS_MarkResearched(tech, base);
781  return true;
782  }
783  }
784  return false;
785 }
786 
791 {
792  for (int i = 0; i < ccs.numTechnologies; i++) {
793  technology_t* tech = RS_GetTechByIDX(i);
794 
795  if (tech->statusResearch != RS_RUNNING)
796  continue;
797 
798  if (RS_RequirementsMet(tech, tech->base))
799  continue;
800 
801  Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("Research prerequisites of %s are not met at %s. Research halted!"), _(tech->name), tech->base->name);
803 
804  RS_StopResearch(tech);
805  }
806 }
807 
814 int RS_ResearchRun (void)
815 {
816  int newResearch = 0;
817 
818  for (int i = 0; i < ccs.numTechnologies; i++) {
819  technology_t* tech = RS_GetTechByIDX(i);
820 
821  if (tech->statusResearch != RS_RUNNING)
822  continue;
823 
824  if (!RS_RequirementsMet(tech, tech->base)) {
825  Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("Research prerequisites of %s are not met at %s. Research halted!"), _(tech->name), tech->base->name);
827 
828  RS_StopResearch(tech);
829  continue;
830  }
831 
832  if (tech->time > 0 && tech->scientists > 0) {
833  /* If there are scientists there _has_ to be a base. */
834  const base_t* base = tech->base;
835  assert(tech->base);
836  if (RS_ResearchAllowed(base)) {
837  tech->time -= tech->scientists * ccs.curCampaign->researchRate;
838  /* Will be a good thing (think of percentage-calculation) once non-integer values are used. */
839  if (tech->time <= 0) {
840  RS_MarkResearched(tech, base);
841 
842  newResearch++;
843  tech->time = 0;
844  }
845  }
846  }
847  }
848 
849  return newResearch;
850 }
851 
852 #ifdef DEBUG
853 
854 static const char* RS_TechTypeToName (researchType_t type)
855 {
856  switch(type) {
857  case RS_TECH:
858  return "tech";
859  case RS_WEAPON:
860  return "weapon";
861  case RS_ARMOUR:
862  return "armour";
863  case RS_CRAFT:
864  return "craft";
865  case RS_CRAFTITEM:
866  return "craftitem";
867  case RS_BUILDING:
868  return "building";
869  case RS_ALIEN:
870  return "alien";
871  case RS_UGV:
872  return "ugv";
873  case RS_NEWS:
874  return "news";
875  case RS_LOGIC:
876  return "logic";
877  default:
878  return "unknown";
879  }
880 }
881 
882 static const char* RS_TechReqToName (requirement_t* req)
883 {
884  switch(req->type) {
885  case RS_LINK_TECH:
886  return req->link.tech->id;
887  case RS_LINK_TECH_NOT:
888  return va("not %s", req->link.tech->id);
889  case RS_LINK_ITEM:
890  return req->link.od->id;
891  case RS_LINK_ALIEN:
892  return req->link.td->id;
893  case RS_LINK_ALIEN_DEAD:
894  return req->link.td->id;
896  return "global alien count";
897  case RS_LINK_UFO:
898  return req->link.aircraft->id;
899  case RS_LINK_ANTIMATTER:
900  return "antimatter";
901  default:
902  return "unknown";
903  }
904 }
905 
907 static const char* RS_TechLinkTypeToName (requirementType_t type)
908 {
909  switch(type) {
910  case RS_LINK_TECH:
911  return "tech";
912  case RS_LINK_TECH_NOT:
913  return "tech (not)";
914  case RS_LINK_ITEM:
915  return "item";
916  case RS_LINK_ALIEN:
917  return "alien";
918  case RS_LINK_ALIEN_DEAD:
919  return "alien_dead";
921  return "alienglobal";
922  case RS_LINK_UFO:
923  return "ufo";
924  case RS_LINK_ANTIMATTER:
925  return "antimatter";
926  default:
927  return "unknown";
928  }
929 }
930 
935 static void RS_TechnologyList_f (void)
936 {
937  cgi->Com_Printf("#techs: %i\n", ccs.numTechnologies);
938  for (int i = 0; i < ccs.numTechnologies; i++) {
939  int j;
940  technology_t* tech;
941  requirements_t* reqs;
942  dateLong_t date;
943 
944  tech = RS_GetTechByIDX(i);
945  cgi->Com_Printf("Tech: %s\n", tech->id);
946  cgi->Com_Printf("... time -> %.2f\n", tech->time);
947  cgi->Com_Printf("... name -> %s\n", tech->name);
948  reqs = &tech->requireAND;
949  cgi->Com_Printf("... requires ALL ->");
950  for (j = 0; j < reqs->numLinks; j++)
951  cgi->Com_Printf(" %s (%s) %s", reqs->links[j].id, RS_TechLinkTypeToName(reqs->links[j].type), RS_TechReqToName(&reqs->links[j]));
952  reqs = &tech->requireOR;
953  cgi->Com_Printf("\n");
954  cgi->Com_Printf("... requires ANY ->");
955  for (j = 0; j < reqs->numLinks; j++)
956  cgi->Com_Printf(" %s (%s) %s", reqs->links[j].id, RS_TechLinkTypeToName(reqs->links[j].type), RS_TechReqToName(&reqs->links[j]));
957  cgi->Com_Printf("\n");
958  cgi->Com_Printf("... provides -> %s", tech->provides);
959  cgi->Com_Printf("\n");
960 
961  cgi->Com_Printf("... type -> ");
962  cgi->Com_Printf("%s\n", RS_TechTypeToName(tech->type));
963 
964  cgi->Com_Printf("... researchable -> %i\n", tech->statusResearchable);
965 
966  if (tech->statusResearchable) {
968  cgi->Com_Printf("... researchable date: %02i %02i %i\n", date.day, date.month, date.year);
969  }
970 
971  cgi->Com_Printf("... research -> ");
972  switch (tech->statusResearch) {
973  case RS_NONE:
974  cgi->Com_Printf("nothing\n");
975  break;
976  case RS_RUNNING:
977  cgi->Com_Printf("running\n");
978  break;
979  case RS_PAUSED:
980  cgi->Com_Printf("paused\n");
981  break;
982  case RS_FINISH:
983  cgi->Com_Printf("done\n");
984  CP_DateConvertLong(tech->researchedDate, &date);
985  cgi->Com_Printf("... research date: %02i %02i %i\n", date.day, date.month, date.year);
986  break;
987  default:
988  cgi->Com_Printf("unknown\n");
989  break;
990  }
991  }
992 }
993 
998 static void RS_DebugMarkResearchedAll (void)
999 {
1000  for (int i = 0; i < ccs.numTechnologies; i++) {
1001  technology_t* tech = RS_GetTechByIDX(i);
1002  cgi->Com_DPrintf(DEBUG_CLIENT, "...mark %s as researched\n", tech->id);
1003  RS_MarkOneResearchable(tech);
1004  RS_ResearchFinish(tech);
1006  }
1007 }
1008 
1013 static void RS_DebugResearchAll_f (void)
1014 {
1015  if (cgi->Cmd_Argc() != 2) {
1016  RS_DebugMarkResearchedAll();
1017  } else {
1018  technology_t* tech = RS_GetTechByID(cgi->Cmd_Argv(1));
1019  if (!tech)
1020  return;
1021  cgi->Com_DPrintf(DEBUG_CLIENT, "...mark %s as researched\n", tech->id);
1022  RS_MarkOneResearchable(tech);
1023  RS_ResearchFinish(tech);
1024  }
1025 }
1026 
1031 static void RS_DebugResearchableAll_f (void)
1032 {
1033  if (cgi->Cmd_Argc() != 2) {
1034  for (int i = 0; i < ccs.numTechnologies; i++) {
1035  technology_t* tech = RS_GetTechByIDX(i);
1036  cgi->Com_Printf("...mark %s as researchable\n", tech->id);
1037  RS_MarkOneResearchable(tech);
1038  RS_MarkCollected(tech);
1039  }
1040  } else {
1041  technology_t* tech = RS_GetTechByID(cgi->Cmd_Argv(1));
1042  if (tech) {
1043  cgi->Com_Printf("...mark %s as researchable\n", tech->id);
1044  RS_MarkOneResearchable(tech);
1045  RS_MarkCollected(tech);
1046  }
1047  }
1048 }
1049 
1050 static void RS_DebugFinishResearches_f (void)
1051 {
1052  for (int i = 0; i < ccs.numTechnologies; i++) {
1053  technology_t* tech = RS_GetTechByIDX(i);
1054  if (tech->statusResearch == RS_RUNNING) {
1055  assert(tech->base);
1056  cgi->Com_DPrintf(DEBUG_CLIENT, "...mark %s as researched\n", tech->id);
1057  RS_MarkResearched(tech, tech->base);
1058  }
1059  }
1060 }
1061 #endif
1062 
1063 
1069 void RS_InitStartup (void)
1070 {
1071  /* add commands and cvars */
1072 #ifdef DEBUG
1073  cgi->Cmd_AddCommand("debug_listtech", RS_TechnologyList_f, "Print the current parsed technologies to the game console");
1074  cgi->Cmd_AddCommand("debug_researchall", RS_DebugResearchAll_f, "Mark all techs as researched");
1075  cgi->Cmd_AddCommand("debug_researchableall", RS_DebugResearchableAll_f, "Mark all techs as researchable");
1076  cgi->Cmd_AddCommand("debug_finishresearches", RS_DebugFinishResearches_f, "Mark all running researches as finished");
1077 #endif
1078 }
1079 
1083 void RS_ResetTechs (void)
1084 {
1085  /* they are static - but i'm paranoid - this is called before the techs were parsed */
1086  OBJZERO(techHash);
1088 
1089  /* delete redirectedTechs, will be filled during parse */
1090  cgi->LIST_Delete(&redirectedTechs);
1091 }
1092 
1098 static const value_t valid_tech_vars[] = {
1099  {"name", V_TRANSLATION_STRING, offsetof(technology_t, name), 0},
1100  {"provides", V_HUNK_STRING, offsetof(technology_t, provides), 0},
1101  {"event", V_HUNK_STRING, offsetof(technology_t, finishedResearchEvent), 0},
1102  {"delay", V_INT, offsetof(technology_t, delay), MEMBER_SIZEOF(technology_t, delay)},
1103  {"producetime", V_INT, offsetof(technology_t, produceTime), MEMBER_SIZEOF(technology_t, produceTime)},
1104  {"time", V_FLOAT, offsetof(technology_t, time), MEMBER_SIZEOF(technology_t, time)},
1105  {"announce", V_BOOL, offsetof(technology_t, announce), MEMBER_SIZEOF(technology_t, announce)},
1106  {"image", V_HUNK_STRING, offsetof(technology_t, image), 0},
1107  {"model", V_HUNK_STRING, offsetof(technology_t, mdl), 0},
1108 
1109  {nullptr, V_NULL, 0, 0}
1110 };
1111 
1115 static const value_t valid_techmail_vars[] = {
1116  {"from", V_TRANSLATION_STRING, offsetof(techMail_t, from), 0},
1117  {"to", V_TRANSLATION_STRING, offsetof(techMail_t, to), 0},
1118  {"subject", V_TRANSLATION_STRING, offsetof(techMail_t, subject), 0},
1119  {"date", V_TRANSLATION_STRING, offsetof(techMail_t, date), 0},
1120  {"icon", V_HUNK_STRING, offsetof(techMail_t, icon), 0},
1121  {"model", V_HUNK_STRING, offsetof(techMail_t, model), 0},
1122 
1123  {nullptr, V_NULL, 0, 0}
1124 };
1125 
1134 void RS_ParseTechnologies (const char* name, const char** text)
1135 {
1136  for (int i = 0; i < ccs.numTechnologies; i++) {
1137  if (Q_streq(ccs.technologies[i].id, name)) {
1138  cgi->Com_Printf("RS_ParseTechnologies: Second tech with same name found (%s) - second ignored\n", name);
1139  return;
1140  }
1141  }
1142 
1144  cgi->Com_Printf("RS_ParseTechnologies: too many technology entries. limit is %i.\n", MAX_TECHNOLOGIES);
1145  return;
1146  }
1147 
1148  /* get body */
1149  const char* token = Com_Parse(text);
1150  if (!*text || *token != '{') {
1151  cgi->Com_Printf("RS_ParseTechnologies: \"%s\" technology def without body ignored.\n", name);
1152  return;
1153  }
1154 
1155  /* New technology (next free entry in global tech-list) */
1157  ccs.numTechnologies++;
1158 
1159  OBJZERO(*tech);
1160 
1161  /*
1162  * Set standard values
1163  */
1164  tech->idx = ccs.numTechnologies - 1;
1165  tech->id = cgi->PoolStrDup(name, cp_campaignPool, 0);
1166  unsigned hash = Com_HashKey(tech->id, TECH_HASH_SIZE);
1167 
1168  /* Set the default string for descriptions (available even if numDescriptions is 0) */
1169  tech->description.text[0] = _("No description available.");
1170  tech->preDescription.text[0] = _("No research proposal available.");
1171  /* Set desc-indices to undef. */
1172  tech->description.usedDescription = -1;
1173  tech->preDescription.usedDescription = -1;
1174 
1175  /* link the variable in */
1176  /* tech_hash should be null on the first run */
1177  tech->hashNext = techHash[hash];
1178  /* set the techHash pointer to the current tech */
1179  /* if there were already others in techHash at position hash, they are now
1180  * accessible via tech->next - loop until tech->next is null (the first tech
1181  * at that position)
1182  */
1183  techHash[hash] = tech;
1184 
1185  tech->type = RS_TECH;
1186  tech->statusResearch = RS_NONE;
1187  tech->statusResearchable = false;
1188 
1189  const char* errhead = "RS_ParseTechnologies: unexpected end of file.";
1190  do {
1191  /* get the name type */
1192  token = cgi->Com_EParse(text, errhead, name);
1193  if (!*text)
1194  break;
1195  if (*token == '}')
1196  break;
1197  /* get values */
1198  if (Q_streq(token, "type")) {
1199  /* what type of tech this is */
1200  token = cgi->Com_EParse(text, errhead, name);
1201  if (!*text)
1202  return;
1204  /* redundant, but oh well. */
1205  if (Q_streq(token, "tech"))
1206  tech->type = RS_TECH;
1207  else if (Q_streq(token, "weapon"))
1208  tech->type = RS_WEAPON;
1209  else if (Q_streq(token, "news"))
1210  tech->type = RS_NEWS;
1211  else if (Q_streq(token, "armour"))
1212  tech->type = RS_ARMOUR;
1213  else if (Q_streq(token, "craft"))
1214  tech->type = RS_CRAFT;
1215  else if (Q_streq(token, "craftitem"))
1216  tech->type = RS_CRAFTITEM;
1217  else if (Q_streq(token, "building"))
1218  tech->type = RS_BUILDING;
1219  else if (Q_streq(token, "alien"))
1220  tech->type = RS_ALIEN;
1221  else if (Q_streq(token, "ugv"))
1222  tech->type = RS_UGV;
1223  else if (Q_streq(token, "logic"))
1224  tech->type = RS_LOGIC;
1225  else
1226  cgi->Com_Printf("RS_ParseTechnologies: \"%s\" unknown techtype: \"%s\" - ignored.\n", name, token);
1227  } else {
1228  if (Q_streq(token, "description") || Q_streq(token, "pre_description")) {
1229  /* Parse the available descriptions for this tech */
1230  technologyDescriptions_t* descTemp;
1231 
1232  /* Link to correct list. */
1233  if (Q_streq(token, "pre_description")) {
1234  descTemp = &tech->preDescription;
1235  } else {
1236  descTemp = &tech->description;
1237  }
1238 
1239  token = cgi->Com_EParse(text, errhead, name);
1240  if (!*text)
1241  break;
1242  if (*token != '{')
1243  break;
1244 
1245  do { /* Loop through all descriptions in the list.*/
1246  token = cgi->Com_EParse(text, errhead, name);
1247  if (!*text)
1248  return;
1249  if (*token == '}')
1250  break;
1251 
1252  linkedList_t* list;
1253 
1254  if (Q_streq(token, "default")) {
1255  list = nullptr;
1256  cgi->LIST_AddString(&list, token);
1257  token = cgi->Com_EParse(text, errhead, name);
1258  cgi->LIST_AddString(&list, token);
1259  } else if (Q_streq(token, "extra")) {
1260  if (!cgi->Com_ParseList(text, &list)) {
1261  cgi->Com_Error(ERR_DROP, "RS_ParseTechnologies: error while reading extra description tuple");
1262  }
1263  if (cgi->LIST_Count(list) != 2) {
1264  cgi->LIST_Delete(&list);
1265  cgi->Com_Error(ERR_DROP, "RS_ParseTechnologies: extra description tuple must contains 2 elements (id string)");
1266  }
1267  } else {
1268  cgi->Com_Error(ERR_DROP, "RS_ParseTechnologies: error while reading description: token \"%s\" not expected", token);
1269  }
1270 
1271  if (descTemp->numDescriptions < MAX_DESCRIPTIONS) {
1272  const char* id = (char*)list->data;
1273  const char* description = (char*)list->next->data;
1274 
1275  /* Copy tech string into entry. */
1276  descTemp->tech[descTemp->numDescriptions] = cgi->PoolStrDup(id, cp_campaignPool, 0);
1277 
1278  /* skip translation marker */
1279  if (description[0] == '_')
1280  description++;
1281 
1282  descTemp->text[descTemp->numDescriptions] = cgi->PoolStrDup(description, cp_campaignPool, 0);
1283  descTemp->numDescriptions++;
1284  } else {
1285  cgi->Com_Printf("skipped description for tech '%s'\n", tech->id);
1286  }
1287  cgi->LIST_Delete(&list);
1288  } while (*text);
1289 
1290  } else if (Q_streq(token, "redirect")) {
1291  token = cgi->Com_EParse(text, errhead, name);
1292  /* Store this tech and the parsed tech-id of the target of the redirection for later linking. */
1293  cgi->LIST_AddPointer(&redirectedTechs, tech);
1294  cgi->LIST_AddString(&redirectedTechs, token);
1295  } else if (Q_streq(token, "require_AND") || Q_streq(token, "require_OR") || Q_streq(token, "require_for_production")) {
1296  requirements_t* requiredTemp;
1297  /* Link to correct list. */
1298  if (Q_streq(token, "require_AND")) {
1299  requiredTemp = &tech->requireAND;
1300  } else if (Q_streq(token, "require_OR")) {
1301  requiredTemp = &tech->requireOR;
1302  } else { /* It's "requireForProduction" */
1303  requiredTemp = &tech->requireForProduction;
1304  }
1305 
1306  token = cgi->Com_EParse(text, errhead, name);
1307  if (!*text)
1308  break;
1309  if (*token != '{')
1310  break;
1311 
1312  do { /* Loop through all 'require' entries.*/
1313  token = cgi->Com_EParse(text, errhead, name);
1314  if (!*text)
1315  return;
1316  if (*token == '}')
1317  break;
1318 
1319  if (Q_streq(token, "tech") || Q_streq(token, "tech_not")) {
1320  if (requiredTemp->numLinks < MAX_TECHLINKS) {
1321  /* Set requirement-type. */
1322  if (Q_streq(token, "tech_not"))
1323  requiredTemp->links[requiredTemp->numLinks].type = RS_LINK_TECH_NOT;
1324  else
1325  requiredTemp->links[requiredTemp->numLinks].type = RS_LINK_TECH;
1326 
1327  /* Set requirement-name (id). */
1328  token = Com_Parse(text);
1329  requiredTemp->links[requiredTemp->numLinks].id = cgi->PoolStrDup(token, cp_campaignPool, 0);
1330 
1331  cgi->Com_DPrintf(DEBUG_CLIENT, "RS_ParseTechnologies: require-tech ('tech' or 'tech_not')- %s\n", requiredTemp->links[requiredTemp->numLinks].id);
1332 
1333  requiredTemp->numLinks++;
1334  } else {
1335  cgi->Com_Printf("RS_ParseTechnologies: \"%s\" Too many 'required' defined. Limit is %i - ignored.\n", name, MAX_TECHLINKS);
1336  }
1337  } else if (Q_streq(token, "item")) {
1338  /* Defines what items need to be collected for this item to be researchable. */
1339  if (requiredTemp->numLinks < MAX_TECHLINKS) {
1340  linkedList_t* list;
1341  if (!cgi->Com_ParseList(text, &list)) {
1342  cgi->Com_Error(ERR_DROP, "RS_ParseTechnologies: error while reading required item tuple");
1343  }
1344 
1345  if (cgi->LIST_Count(list) != 2) {
1346  cgi->Com_Error(ERR_DROP, "RS_ParseTechnologies: required item tuple must contains 2 elements (id pos)");
1347  }
1348 
1349  const char* idToken = (char*)list->data;
1350  const char* amountToken = (char*)list->next->data;
1351 
1352  /* Set requirement-type. */
1353  requiredTemp->links[requiredTemp->numLinks].type = RS_LINK_ITEM;
1354  /* Set requirement-name (id). */
1355  requiredTemp->links[requiredTemp->numLinks].id = cgi->PoolStrDup(idToken, cp_campaignPool, 0);
1356  /* Set requirement-amount of item. */
1357  requiredTemp->links[requiredTemp->numLinks].amount = atoi(amountToken);
1358  cgi->Com_DPrintf(DEBUG_CLIENT, "RS_ParseTechnologies: require-item - %s - %i\n", requiredTemp->links[requiredTemp->numLinks].id, requiredTemp->links[requiredTemp->numLinks].amount);
1359  requiredTemp->numLinks++;
1360  cgi->LIST_Delete(&list);
1361  } else {
1362  cgi->Com_Printf("RS_ParseTechnologies: \"%s\" Too many 'required' defined. Limit is %i - ignored.\n", name, MAX_TECHLINKS);
1363  }
1364  } else if (Q_streq(token, "alienglobal")) {
1365  if (requiredTemp->numLinks < MAX_TECHLINKS) {
1366  /* Set requirement-type. */
1367  requiredTemp->links[requiredTemp->numLinks].type = RS_LINK_ALIEN_GLOBAL;
1368  cgi->Com_DPrintf(DEBUG_CLIENT, "RS_ParseTechnologies: require-alienglobal - %i\n", requiredTemp->links[requiredTemp->numLinks].amount);
1369 
1370  /* Set requirement-amount of item. */
1371  token = Com_Parse(text);
1372  requiredTemp->links[requiredTemp->numLinks].amount = atoi(token);
1373  requiredTemp->numLinks++;
1374  } else {
1375  cgi->Com_Printf("RS_ParseTechnologies: \"%s\" Too many 'required' defined. Limit is %i - ignored.\n", name, MAX_TECHLINKS);
1376  }
1377  } else if (Q_streq(token, "alien_dead") || Q_streq(token, "alien")) { /* Does this only check the beginning of the string? */
1378  /* Defines what live or dead aliens need to be collected for this item to be researchable. */
1379  if (requiredTemp->numLinks < MAX_TECHLINKS) {
1380  /* Set requirement-type. */
1381  if (Q_streq(token, "alien_dead")) {
1382  requiredTemp->links[requiredTemp->numLinks].type = RS_LINK_ALIEN_DEAD;
1383  cgi->Com_DPrintf(DEBUG_CLIENT, "RS_ParseTechnologies: require-alien dead - %s - %i\n", requiredTemp->links[requiredTemp->numLinks].id, requiredTemp->links[requiredTemp->numLinks].amount);
1384  } else {
1385  requiredTemp->links[requiredTemp->numLinks].type = RS_LINK_ALIEN;
1386  cgi->Com_DPrintf(DEBUG_CLIENT, "RS_ParseTechnologies: require-alien alive - %s - %i\n", requiredTemp->links[requiredTemp->numLinks].id, requiredTemp->links[requiredTemp->numLinks].amount);
1387  }
1388 
1389  linkedList_t* list;
1390  if (!cgi->Com_ParseList(text, &list)) {
1391  cgi->Com_Error(ERR_DROP, "RS_ParseTechnologies: error while reading required alien tuple");
1392  }
1393 
1394  if (cgi->LIST_Count(list) != 2) {
1395  cgi->Com_Error(ERR_DROP, "RS_ParseTechnologies: required alien tuple must contains 2 elements (id pos)");
1396  }
1397 
1398  const char* idToken = (char*)list->data;
1399  const char* amountToken = (char*)list->next->data;
1400 
1401  /* Set requirement-name (id). */
1402  requiredTemp->links[requiredTemp->numLinks].id = cgi->PoolStrDup(idToken, cp_campaignPool, 0);
1403  /* Set requirement-amount of item. */
1404  requiredTemp->links[requiredTemp->numLinks].amount = atoi(amountToken);
1405  requiredTemp->numLinks++;
1406  cgi->LIST_Delete(&list);
1407  } else {
1408  cgi->Com_Printf("RS_ParseTechnologies: \"%s\" Too many 'required' defined. Limit is %i - ignored.\n", name, MAX_TECHLINKS);
1409  }
1410  } else if (Q_streq(token, "ufo")) {
1411  /* Defines what ufos need to be collected for this item to be researchable. */
1412  if (requiredTemp->numLinks < MAX_TECHLINKS) {
1413  linkedList_t* list;
1414  if (!cgi->Com_ParseList(text, &list)) {
1415  cgi->Com_Error(ERR_DROP, "RS_ParseTechnologies: error while reading required item tuple");
1416  }
1417 
1418  if (cgi->LIST_Count(list) != 2) {
1419  cgi->Com_Error(ERR_DROP, "RS_ParseTechnologies: required item tuple must contains 2 elements (id pos)");
1420  }
1421 
1422  const char* idToken = (char*)list->data;
1423  const char* amountToken = (char*)list->next->data;
1424 
1425  /* Set requirement-type. */
1426  requiredTemp->links[requiredTemp->numLinks].type = RS_LINK_UFO;
1427  /* Set requirement-name (id). */
1428  requiredTemp->links[requiredTemp->numLinks].id = cgi->PoolStrDup(idToken, cp_campaignPool, 0);
1429  /* Set requirement-amount of item. */
1430  requiredTemp->links[requiredTemp->numLinks].amount = atoi(amountToken);
1431  cgi->Com_DPrintf(DEBUG_CLIENT, "RS_ParseTechnologies: require-ufo - %s - %i\n", requiredTemp->links[requiredTemp->numLinks].id, requiredTemp->links[requiredTemp->numLinks].amount);
1432  requiredTemp->numLinks++;
1433  }
1434  } else if (Q_streq(token, "antimatter")) {
1435  /* Defines what ufos need to be collected for this item to be researchable. */
1436  if (requiredTemp->numLinks < MAX_TECHLINKS) {
1437  /* Set requirement-type. */
1438  requiredTemp->links[requiredTemp->numLinks].type = RS_LINK_ANTIMATTER;
1439  /* Set requirement-amount of item. */
1440  token = Com_Parse(text);
1441  requiredTemp->links[requiredTemp->numLinks].amount = atoi(token);
1442  cgi->Com_DPrintf(DEBUG_CLIENT, "RS_ParseTechnologies: require-antimatter - %i\n", requiredTemp->links[requiredTemp->numLinks].amount);
1443  requiredTemp->numLinks++;
1444  }
1445  } else {
1446  cgi->Com_Printf("RS_ParseTechnologies: \"%s\" unknown requirement-type: \"%s\" - ignored.\n", name, token);
1447  }
1448  } while (*text);
1449  } else if (Q_streq(token, "up_chapter")) {
1450  /* UFOpaedia chapter */
1451  token = cgi->Com_EParse(text, errhead, name);
1452  if (!*text)
1453  return;
1454 
1455  if (*token) {
1456  /* find chapter */
1457  for (int i = 0; i < ccs.numChapters; i++) {
1458  if (Q_streq(token, ccs.upChapters[i].id)) {
1459  /* add entry to chapter */
1460  tech->upChapter = &ccs.upChapters[i];
1461  if (!ccs.upChapters[i].first) {
1462  ccs.upChapters[i].first = tech;
1463  ccs.upChapters[i].last = tech;
1464  tech->upPrev = nullptr;
1465  tech->upNext = nullptr;
1466  } else {
1467  /* get "last entry" in chapter */
1468  technology_t* techOld = ccs.upChapters[i].last;
1469  ccs.upChapters[i].last = tech;
1470  techOld->upNext = tech;
1471  ccs.upChapters[i].last->upPrev = techOld;
1472  ccs.upChapters[i].last->upNext = nullptr;
1473  }
1474  break;
1475  }
1476  if (i == ccs.numChapters)
1477  cgi->Com_Printf("RS_ParseTechnologies: \"%s\" - chapter \"%s\" not found.\n", name, token);
1478  }
1479  }
1480  } else if (Q_streq(token, "mail") || Q_streq(token, "mail_pre")) {
1481  techMail_t* mail;
1482 
1483  /* how many mails found for this technology
1484  * used in UFOpaedia to check which article to display */
1485  tech->numTechMails++;
1486 
1487  if (tech->numTechMails > TECHMAIL_MAX)
1488  cgi->Com_Printf("RS_ParseTechnologies: more techmail-entries found than supported. \"%s\"\n", name);
1489 
1490  if (Q_streq(token, "mail_pre")) {
1491  mail = &tech->mail[TECHMAIL_PRE];
1492  } else {
1493  mail = &tech->mail[TECHMAIL_RESEARCHED];
1494  }
1495  token = cgi->Com_EParse(text, errhead, name);
1496  if (!*text || *token != '{')
1497  return;
1498 
1499  /* grab the initial mail entry */
1500  token = cgi->Com_EParse(text, errhead, name);
1501  if (!*text || *token == '}')
1502  return;
1503  do {
1504  cgi->Com_ParseBlockToken(name, text, mail, valid_techmail_vars, cp_campaignPool, token);
1505 
1506  /* grab the next entry */
1507  token = cgi->Com_EParse(text, errhead, name);
1508  if (!*text)
1509  return;
1510  } while (*text && *token != '}');
1511  /* default model is navarre */
1512  if (mail->model == nullptr)
1513  mail->model = "characters/navarre";
1514  } else {
1515  if (!cgi->Com_ParseBlockToken(name, text, tech, valid_tech_vars, cp_campaignPool, token))
1516  cgi->Com_Printf("RS_ParseTechnologies: unknown token \"%s\" ignored (entry %s)\n", token, name);
1517  }
1518  }
1519  } while (*text);
1520 
1521  if (tech->provides) {
1523  /* link the variable in */
1524  /* techHashProvided should be null on the first run */
1526  /* set the techHashProvided pointer to the current tech */
1527  /* if there were already others in techHashProvided at position hash, they are now
1528  * accessable via tech->next - loop until tech->next is null (the first tech
1529  * at that position)
1530  */
1531  techHashProvided[hash] = tech;
1532  } else {
1533  if (tech->type == RS_WEAPON || tech->type == RS_ARMOUR) {
1534  Sys_Error("RS_ParseTechnologies: weapon or armour tech without a provides property");
1535  }
1536  cgi->Com_DPrintf(DEBUG_CLIENT, "tech '%s' doesn't have a provides string\n", tech->id);
1537  }
1538 
1539  /* set the overall reseach time to the one given in the ufo-file. */
1540  tech->overallTime = tech->time;
1541 }
1542 
1543 static inline bool RS_IsValidTechIndex (int techIdx)
1544 {
1545  if (techIdx == TECH_INVALID)
1546  return false;
1547  if (techIdx < 0 || techIdx >= ccs.numTechnologies)
1548  return false;
1549  if (techIdx >= MAX_TECHNOLOGIES)
1550  return false;
1551 
1552  return true;
1553 }
1554 
1561 bool RS_IsResearched_idx (int techIdx)
1562 {
1563  if (!RS_IsValidTechIndex(techIdx))
1564  return false;
1565 
1566  if (ccs.technologies[techIdx].statusResearch == RS_FINISH)
1567  return true;
1568 
1569  return false;
1570 }
1571 
1578 {
1579  if (tech && tech->statusResearch == RS_FINISH)
1580  return true;
1581  return false;
1582 }
1583 
1591 {
1592  if (!RS_IsValidTechIndex(techIdx))
1593  return nullptr;
1594  return &ccs.technologies[techIdx];
1595 }
1596 
1597 
1603 technology_t* RS_GetTechByID (const char* id)
1604 {
1605  if (Q_strnull(id))
1606  return nullptr;
1607 
1608  unsigned hash = Com_HashKey(id, TECH_HASH_SIZE);
1609  for (technology_t* tech = techHash[hash]; tech; tech = tech->hashNext)
1610  if (!Q_strcasecmp(id, tech->id))
1611  return tech;
1612 
1613  cgi->Com_Printf("RS_GetTechByID: Could not find a technology with id \"%s\"\n", id);
1614  return nullptr;
1615 }
1616 
1622 technology_t* RS_GetTechByProvided (const char* idProvided)
1623 {
1624  if (!idProvided)
1625  return nullptr;
1626  /* catch empty strings */
1627  if (idProvided[0] == '\0')
1628  return nullptr;
1629 
1630  unsigned hash = Com_HashKey(idProvided, TECH_HASH_SIZE);
1631  for (technology_t* tech = techHashProvided[hash]; tech; tech = tech->hashProvidedNext)
1632  if (!Q_strcasecmp(idProvided, tech->provides))
1633  return tech;
1634 
1635  cgi->Com_DPrintf(DEBUG_CLIENT, "RS_GetTechByProvided: %s\n", idProvided);
1636  /* if a building, probably needs another building */
1637  /* if not a building, catch nullptr where function is called! */
1638  return nullptr;
1639 }
1640 
1645 technology_t* RS_GetTechWithMostScientists (const struct base_s* base)
1646 {
1647  if (!base)
1648  return nullptr;
1649 
1650  technology_t* tech = nullptr;
1651  int max = 0;
1652  for (int i = 0; i < ccs.numTechnologies; i++) {
1653  technology_t* tech_temp = RS_GetTechByIDX(i);
1654  if (tech_temp->statusResearch == RS_RUNNING && tech_temp->base == base) {
1655  if (tech_temp->scientists > max) {
1656  tech = tech_temp;
1657  max = tech->scientists;
1658  }
1659  }
1660  }
1661 
1662  /* this tech has at least one assigned scientist or is a nullptr pointer */
1663  return tech;
1664 }
1665 
1670 int RS_GetTechIdxByName (const char* name)
1671 {
1672  const unsigned hash = Com_HashKey(name, TECH_HASH_SIZE);
1673 
1674  for (technology_t* tech = techHash[hash]; tech; tech = tech->hashNext)
1675  if (!Q_strcasecmp(name, tech->id))
1676  return tech->idx;
1677 
1678  cgi->Com_Printf("RS_GetTechIdxByName: Could not find tech '%s'\n", name);
1679  return TECH_INVALID;
1680 }
1681 
1689 {
1690  int counter = 0;
1691 
1692  for (int i = 0; i < ccs.numTechnologies; i++) {
1693  const technology_t* tech = &ccs.technologies[i];
1694  if (tech->base == base) {
1695  /* Get a free lab from the base. */
1696  counter += tech->scientists;
1697  }
1698  }
1699 
1700  return counter;
1701 }
1702 
1708 {
1709  assert(base);
1710 
1711  /* Make sure current CAP_LABSPACE capacity is set to proper value */
1713 
1714  while (CAP_GetFreeCapacity(base, CAP_LABSPACE) < 0) {
1716  RS_RemoveScientist(tech, nullptr);
1717  }
1718 }
1719 
1725 bool RS_SaveXML (xmlNode_t* parent)
1726 {
1727  cgi->Com_RegisterConstList(saveResearchConstants);
1728  xmlNode_t* node = cgi->XML_AddNode(parent, SAVE_RESEARCH_RESEARCH);
1729  for (int i = 0; i < ccs.numTechnologies; i++) {
1730  const technology_t* t = RS_GetTechByIDX(i);
1731 
1732  xmlNode_t* snode = cgi->XML_AddNode(node, SAVE_RESEARCH_TECH);
1733  cgi->XML_AddString(snode, SAVE_RESEARCH_ID, t->id);
1734  cgi->XML_AddBoolValue(snode, SAVE_RESEARCH_STATUSCOLLECTED, t->statusCollected);
1735  cgi->XML_AddFloatValue(snode, SAVE_RESEARCH_TIME, t->time);
1737  if (t->base)
1738  cgi->XML_AddInt(snode, SAVE_RESEARCH_BASE, t->base->idx);
1739  cgi->XML_AddIntValue(snode, SAVE_RESEARCH_SCIENTISTS, t->scientists);
1740  cgi->XML_AddBool(snode, SAVE_RESEARCH_STATUSRESEARCHABLE, t->statusResearchable);
1743  cgi->XML_AddInt(snode, SAVE_RESEARCH_MAILSENT, t->mailSent);
1744 
1745  /* save which techMails were read */
1747  for (int j = 0; j < TECHMAIL_MAX; j++) {
1748  if (t->mail[j].read) {
1749  xmlNode_t* ssnode = cgi->XML_AddNode(snode, SAVE_RESEARCH_MAIL);
1750  cgi->XML_AddInt(ssnode, SAVE_RESEARCH_MAIL_ID, j);
1751  }
1752  }
1753  }
1754  cgi->Com_UnregisterConstList(saveResearchConstants);
1755 
1756  return true;
1757 }
1758 
1764 bool RS_LoadXML (xmlNode_t* parent)
1765 {
1766  xmlNode_t* topnode;
1767  xmlNode_t* snode;
1768  bool success = true;
1769 
1770  topnode = cgi->XML_GetNode(parent, SAVE_RESEARCH_RESEARCH);
1771  if (!topnode)
1772  return false;
1773 
1774  cgi->Com_RegisterConstList(saveResearchConstants);
1775  for (snode = cgi->XML_GetNode(topnode, SAVE_RESEARCH_TECH); snode; snode = cgi->XML_GetNextNode(snode, topnode, "tech")) {
1776  const char* techString = cgi->XML_GetString(snode, SAVE_RESEARCH_ID);
1777  xmlNode_t* ssnode;
1778  int baseIdx;
1779  technology_t* t = RS_GetTechByID(techString);
1780  const char* type = cgi->XML_GetString(snode, SAVE_RESEARCH_STATUSRESEARCH);
1781 
1782  if (!t) {
1783  cgi->Com_Printf("......your game doesn't know anything about tech '%s'\n", techString);
1784  continue;
1785  }
1786 
1787  if (!cgi->Com_GetConstIntFromNamespace(SAVE_RESEARCHSTATUS_NAMESPACE, type, (int*) &t->statusResearch)) {
1788  cgi->Com_Printf("Invalid research status '%s'\n", type);
1789  success = false;
1790  break;
1791  }
1792 
1793  t->statusCollected = cgi->XML_GetBool(snode, SAVE_RESEARCH_STATUSCOLLECTED, false);
1794  t->time = cgi->XML_GetFloat(snode, SAVE_RESEARCH_TIME, 0.0);
1795  /* Prepare base-index for later pointer-restoration in RS_PostLoadInit. */
1796  baseIdx = cgi->XML_GetInt(snode, SAVE_RESEARCH_BASE, -1);
1797  if (baseIdx >= 0)
1798  /* even if the base is not yet loaded we can set the pointer already */
1799  t->base = B_GetBaseByIDX(baseIdx);
1800  t->scientists = cgi->XML_GetInt(snode, SAVE_RESEARCH_SCIENTISTS, 0);
1801  t->statusResearchable = cgi->XML_GetBool(snode, SAVE_RESEARCH_STATUSRESEARCHABLE, false);
1802  int date;
1803  int time;
1804  cgi->XML_GetDate(snode, SAVE_RESEARCH_PREDATE, &date, &time);
1805  t->preResearchedDate = DateTime(date, time);
1806  cgi->XML_GetDate(snode, SAVE_RESEARCH_DATE, &date, &time);
1807  t->researchedDate = DateTime(date, time);
1808  t->mailSent = (mailSentType_t)cgi->XML_GetInt(snode, SAVE_RESEARCH_MAILSENT, 0);
1809 
1810  /* load which techMails were read */
1812  for (ssnode = cgi->XML_GetNode(snode, SAVE_RESEARCH_MAIL); ssnode; ssnode = cgi->XML_GetNextNode(ssnode, snode, SAVE_RESEARCH_MAIL)) {
1813  const int j= cgi->XML_GetInt(ssnode, SAVE_RESEARCH_MAIL_ID, TECHMAIL_MAX);
1814  if (j < TECHMAIL_MAX)
1815  t->mail[j].read = true;
1816  else
1817  cgi->Com_Printf("......your save game contains unknown techmail ids... \n");
1818  }
1819 
1820 #ifdef DEBUG
1821  if (t->statusResearch == RS_RUNNING && t->scientists > 0) {
1822  if (!t->base) {
1823  cgi->Com_Printf("No base but research is running and scientists are assigned");
1824  success = false;
1825  break;
1826  }
1827  }
1828 #endif
1829  }
1830  cgi->Com_UnregisterConstList(saveResearchConstants);
1831 
1832  return success;
1833 }
1834 
1840 bool RS_ResearchAllowed (const base_t* base)
1841 {
1842  assert(base);
1843  return !B_IsUnderAttack(base) && B_GetBuildingStatus(base, B_LAB) && E_CountHired(base, EMPL_SCIENTIST) > 0;
1844 }
1845 
1851 {
1852  int i, error = 0;
1853  technology_t* t;
1854 
1855  for (i = 0, t = ccs.technologies; i < ccs.numTechnologies; i++, t++) {
1856  if (!t->name) {
1857  error++;
1858  cgi->Com_Printf("...... technology '%s' has no name\n", t->id);
1859  }
1860  if (!t->provides) {
1861  switch (t->type) {
1862  case RS_TECH:
1863  case RS_NEWS:
1864  case RS_LOGIC:
1865  case RS_ALIEN:
1866  break;
1867  default:
1868  error++;
1869  cgi->Com_Printf("...... technology '%s' doesn't provide anything\n", t->id);
1870  break;
1871  }
1872  }
1873 
1874  if (t->produceTime == 0) {
1875  switch (t->type) {
1876  case RS_TECH:
1877  case RS_NEWS:
1878  case RS_LOGIC:
1879  case RS_BUILDING:
1880  case RS_ALIEN:
1881  break;
1882  default:
1884  cgi->Com_Printf("...... technology '%s' has zero (0) produceTime, is this on purpose?\n", t->id);
1885  break;
1886  }
1887  }
1888 
1889  if (t->type != RS_LOGIC && (!t->description.text[0] || t->description.text[0][0] == '_')) {
1890  if (!t->description.text[0])
1891  cgi->Com_Printf("...... technology '%s' has a strange 'description' value '%s'.\n", t->id, t->description.text[0]);
1892  else
1893  cgi->Com_Printf("...... technology '%s' has no 'description' value.\n", t->id);
1894  }
1895  }
1896 
1897  return !error;
1898 }
bool Q_strnull(const char *string)
Definition: shared.h:138
const char * name
Definition: inv_shared.h:267
int AL_CountAll(void)
Counts live aliens in all bases.
xmlNode_t *IMPORT * XML_GetNode(xmlNode_t *parent, const char *name)
int numChapters
Definition: cp_campaign.h:325
#define SAVE_RESEARCH_STATUSCOLLECTED
Definition: save_research.h:30
int B_ItemInBase(const objDef_t *item, const base_t *base)
Check if the item has been collected (i.e it is in the storage) in the given base.
Definition: cp_base.cpp:2133
const char * RS_GetDescription(technologyDescriptions_t *desc)
returns the currently used description for a technology.
building_t buildingTemplates[MAX_BUILDINGS]
Definition: cp_campaign.h:339
const char * to
Definition: cp_research.h:112
struct technology_s * redirect
Definition: cp_research.h:147
bool RS_IsResearched_idx(int techIdx)
Checks if the technology (tech-index) has been researched.
void Sys_Error(const char *error,...)
Definition: g_main.cpp:421
struct technology_s * hashProvidedNext
Definition: cp_research.h:200
#define TECH_INVALID
Definition: cp_research.h:35
void UP_OpenWith(const char *techID)
Opens the UFOpaedia from everywhere with the entry given through name.
csi_t * csi
Definition: cgame.h:100
technology_t * RS_GetTechByIDX(int techIdx)
Returns the technology pointer for a tech index. You can use this instead of "&ccs.technologies[techIdx]" to avoid having to check valid indices.
int numAircraftTemplates
Definition: cp_campaign.h:385
uiMessageListNodeMessage_t * MSO_CheckAddNewMessage(const notify_t messagecategory, const char *title, const char *text, messageType_t type, technology_t *pedia, bool popup)
Adds a new message to message stack. It uses message settings to verify whether sound should be playe...
requirement_t links[MAX_TECHLINKS]
Definition: cp_research.h:88
#define SAVE_RESEARCHSTATUS_NAMESPACE
Definition: save_research.h:42
bool RS_IsResearched_ptr(const technology_t *tech)
Checks whether an item is already researched.
const objDef_t * INVSH_GetItemByID(const char *id)
Returns the item that belongs to the given id or nullptr if it wasn&#39;t found.
Definition: inv_shared.cpp:282
QGL_EXTERN GLint GLenum type
Definition: r_gl.h:94
Defines all attributes of objects used in the inventory.
Definition: inv_shared.h:264
bool B_GetBuildingStatus(const base_t *const base, const buildingType_t buildingType)
Get the status associated to a building.
Definition: cp_base.cpp:478
#define SAVE_RESEARCH_MAILSENT
Definition: save_research.h:38
struct technology_s * upNext
Definition: cp_research.h:194
byte month
Definition: cp_time.h:38
void RS_MarkOneResearchable(technology_t *tech)
Marks one tech as researchable.
const char * image
Definition: inv_shared.h:270
const char * va(const char *format,...)
does a varargs printf into a temp buffer, so I don&#39;t need to have varargs versions of all text functi...
Definition: shared.cpp:410
A base with all it&#39;s data.
Definition: cp_base.h:84
void CAP_AddCurrent(base_t *base, baseCapacities_t capacity, int value)
Changes the current (used) capacity on a base.
short year
Definition: cp_time.h:37
void RS_InitStartup(void)
This is more or less the initial Bind some of the functions in this file to console-commands that you...
#define _(String)
Definition: cl_shared.h:44
char * id
Definition: cp_aircraft.h:120
void * data
Definition: list.h:31
requirements_t requireAND
Definition: cp_research.h:150
csi_t csi
Definition: common.cpp:39
bool Com_sprintf(char *dest, size_t size, const char *fmt,...)
copies formatted string with buffer-size checking
Definition: shared.cpp:494
#define B_IsUnderAttack(base)
Definition: cp_base.h:53
struct base_s * base
Definition: cp_research.h:169
int numTechnologies
Definition: cp_campaign.h:278
struct technology_s * first
Definition: cp_ufopedia.h:34
static const constListEntry_t saveResearchConstants[]
Definition: save_research.h:43
void RS_MarkResearchable(const base_t *base, bool init)
Marks all the techs that can be researched. Automatically researches &#39;free&#39; techs such as ammo for a ...
#define TECH_HASH_SIZE
Definition: cp_research.cpp:42
bool RS_LoadXML(xmlNode_t *parent)
Load callback for research and technologies.
#define SAVE_RESEARCH_TECH
Definition: save_research.h:28
Class describing a point of time.
Definition: DateTime.h:30
char * model
Definition: cp_aircraft.h:124
character_t chr
Definition: cp_employee.h:119
char name[MAX_VAR]
Definition: cp_base.h:86
const aircraft_t * AIR_GetAircraft(const char *name)
Searches the global array of aircraft types for a given aircraft.
bool RS_RequirementsMet(const technology_t *tech, const base_t *base)
Checks if all requirements of a tech have been met so that it becomes researchable.
requirements_t requireForProduction
Definition: cp_research.h:181
int US_UFOsInStorage(const aircraft_t *ufoTemplate, const installation_t *installation)
Returns the number of UFOs stored (on an installation or anywhere)
byte day
Definition: cp_time.h:39
const char *IMPORT * Cmd_Argv(int n)
Header for research related stuff.
#define SAVE_RESEARCH_BASE
Definition: save_research.h:33
char name[MAX_VAR]
Definition: cp_aircraft.h:121
int getTimeAsSeconds() const
Return the time part of the DateTime as seconds.
Definition: DateTime.cpp:54
technology_t * teamDefTechs[MAX_TEAMDEFS]
Definition: cp_campaign.h:373
void RS_RemoveScientistsExceedingCapacity(base_t *base)
Remove all exceeding scientist.
#define MAX_DESCRIPTIONS
Definition: cp_research.h:33
available mails for a tech - mail and mail_pre in script files
Definition: cp_research.h:110
memPool_t * cp_campaignPool
Definition: cp_campaign.cpp:62
static const value_t valid_tech_vars[]
The valid definition names in the research.ufo file.
static const value_t valid_techmail_vars[]
The valid definition names in the research.ufo file for tech mails.
#define ERR_FATAL
Definition: common.h:210
const char * id
Definition: inv_shared.h:268
int E_CountHired(const base_t *const base, employeeType_t type)
Counts hired employees of a given type in a given base.
mailSentType_t mailSent
Definition: cp_research.h:180
technology_t technologies[MAX_TECHNOLOGIES]
Definition: cp_campaign.h:277
int CAP_GetFreeCapacity(const base_t *base, baseCapacities_t capacityType)
Returns the free capacity of a type.
char *IMPORT * PoolStrDup(const char *in, memPool_t *pool, const int tagNum)
int getAlive(const teamDef_t *team) const
Return number of alive aliens of a type in the cargo.
Definition: aliencargo.cpp:100
int idx
Definition: cp_base.h:85
#define xmlNode_t
Definition: xml.h:24
#define MAX_TECHLINKS
Definition: cp_research.h:32
static linkedList_t * redirectedTechs
Definition: cp_research.cpp:46
markResearched_t markResearched
Definition: cp_research.h:189
struct technology_s * upPrev
Definition: cp_research.h:193
int RS_CountScientistsInBase(const base_t *base)
Returns the number of employees searching in labs in given base.
void RS_ParseTechnologies(const char *name, const char **text)
Parses one "tech" entry in the research.ufo file and writes it into the next free entry in technologi...
char * finishedResearchEvent
Definition: cp_research.h:166
technology_t * RS_GetTechForTeam(const teamDef_t *team)
Returns technology entry for a team.
char id[MAX_VAR]
Definition: chr_shared.h:309
const char * model
Definition: inv_shared.h:269
int getDead(const teamDef_t *team) const
Return number of dead alien bodies of a type in the cargo.
Definition: aliencargo.cpp:117
#define SAVE_RESEARCH_MAIL_ID
Definition: save_research.h:40
#define ERR_DROP
Definition: common.h:211
#define DEBUG_CLIENT
Definition: defines.h:59
int numBuildingTemplates
Definition: cp_campaign.h:340
bool RS_MarkStoryLineEventResearched(const char *techID)
void RS_RemoveFiredScientist(base_t *base, Employee *employee)
Remove one scientist from research project if needed.
#define OBJZERO(obj)
Definition: shared.h:178
class DateTime researchedDate
Definition: cp_research.h:187
technology_t * RS_GetTechWithMostScientists(const struct base_s *base)
Searches for the technology that has the most scientists assigned in a given base.
xmlNode_t *IMPORT * XML_GetDate(xmlNode_t *parent, const char *name, int *day, int *sec)
static void RS_AssignTechLinks(requirements_t *reqs)
Assign required tech/item/etc... pointers for a single requirements list.
#define SAVE_RESEARCH_SCIENTISTS
Definition: save_research.h:34
void CP_Popup(const char *title, const char *text,...)
Wrapper around UI_Popup.
Definition: cp_popup.cpp:474
int getDateAsDays() const
Return the date part of the DateTime as days.
Definition: DateTime.cpp:46
char * text[MAX_DESCRIPTIONS]
Definition: cp_research.h:134
base_t * B_GetNext(base_t *lastBase)
Iterates through founded bases.
Definition: cp_base.cpp:286
bool RS_ScriptSanityCheck(void)
Checks the parsed tech data for errors.
technologyDescriptions_t description
Definition: cp_research.h:143
static wrapCache_t * hash[MAX_WRAP_HASH]
Definition: r_font.cpp:86
void RS_MarkCollected(technology_t *tech)
Marks a give technology as collected.
const char *IMPORT * XML_GetString(xmlNode_t *parent, const char *name)
Human readable time information in the game.
Definition: cp_time.h:36
const cgame_import_t * cgi
struct pediaChapter_s * upChapter
Definition: cp_research.h:192
mailSentType_t
Definition: cp_research.h:122
Employee * E_GetAssignedEmployee(const base_t *const base, const employeeType_t type)
Gets an unassigned employee of a given type from the given base.
researchType_t type
Definition: cp_research.h:145
technology_t * objDefTechs[MAX_OBJDEFS]
Definition: cp_campaign.h:376
void setAssigned(bool assigned)
Definition: cp_employee.h:62
techMail_t mail[TECHMAIL_MAX]
Definition: cp_research.h:196
char * image
Definition: cp_research.h:172
#define SAVE_RESEARCH_STATUSRESEARCHABLE
Definition: save_research.h:35
void RS_ResearchFinish(technology_t *tech)
Sets a technology status to researched and updates the date.
Definition: cp_research.cpp:52
ccs_t ccs
Definition: cp_campaign.cpp:63
#define SAVE_RESEARCH_TIME
Definition: save_research.h:31
class DateTime preResearchedDate
Definition: cp_research.h:186
int numODs
Definition: q_shared.h:518
aircraft_t aircraftTemplates[MAX_AIRCRAFT]
Definition: cp_campaign.h:384
base_t * B_GetBaseByIDX(int baseIdx)
Array bound check for the base index. Will also return unfounded bases as long as the index is in the...
Definition: cp_base.cpp:313
Campaign geoscape time header.
const teamDef_t *IMPORT * Com_GetTeamDefinitionByID(const char *team)
#define Q_strcasecmp(a, b)
Definition: shared.h:131
xmlNode_t *IMPORT * XML_GetNextNode(xmlNode_t *current, xmlNode_t *parent, const char *name)
technologyDescriptions_t preDescription
Definition: cp_research.h:144
technology_t * RS_GetTechByID(const char *id)
return a pointer to the technology identified by given id string
char * name
Definition: cp_building.h:79
struct technology_s * last
Definition: cp_ufopedia.h:35
const char * subject
Definition: cp_research.h:113
char cp_messageBuffer[MAX_MESSAGE_TEXT]
Definition: cp_messages.cpp:33
Alien containment class header.
objDef_t ods[MAX_OBJDEFS]
Definition: q_shared.h:517
static technology_t * techHashProvided[TECH_HASH_SIZE]
Definition: cp_research.cpp:44
#define SAVE_RESEARCH_RESEARCH
Definition: save_research.h:27
char * provides
Definition: cp_research.h:156
void RS_CheckRequirements(void)
Checks if running researches still meet their requirements.
#define SAVE_RESEARCH_ID
Definition: save_research.h:29
An aircraft with all it&#39;s data.
Definition: cp_aircraft.h:115
struct technology_s * tech
Definition: cp_aircraft.h:163
bool RS_ResearchAllowed(const base_t *base)
Returns true if the current base is able to handle research.
const char * Com_Parse(const char *data_p[], char *target, size_t size, bool replaceWhitespaces)
Parse a token out of a string.
Definition: parse.cpp:107
Definition: scripts.h:49
requirementType_t type
Definition: cp_research.h:74
int RS_GetTechIdxByName(const char *name)
Returns the index (idx) of a "tech" entry given it&#39;s name.
char * campaign[MAX_CAMPAIGNS]
Definition: cp_research.h:94
int B_AntimatterInBase(const base_t *base)
returns the amount of antimatter stored in a base
Definition: cp_base.cpp:2613
static technology_t * techHash[TECH_HASH_SIZE]
Definition: cp_research.cpp:43
static bool RS_IsValidTechIndex(int techIdx)
campaign_t * curCampaign
Definition: cp_campaign.h:378
void RS_InitTree(const campaign_t *campaign, bool load)
Gets all needed names/file-paths/etc... for each technology entry. Should be executed after the parsi...
const char * image
Definition: cp_building.h:80
QGL_EXTERN GLint i
Definition: r_gl.h:113
Definition: scripts.h:50
void CAP_SetCurrent(base_t *base, baseCapacities_t capacity, int value)
Sets the current (used) capacity on a base.
Definition: cp_capacity.cpp:97
#define MAX_TECHNOLOGIES
Definition: cp_research.h:31
This is the technology parsed from research.ufo.
Definition: cp_research.h:139
void RS_RequiredLinksAssign(void)
Assign Link pointers to all required techs/items/etc...
bool markOnly[MAX_CAMPAIGNS]
Definition: cp_research.h:93
QGL_EXTERN GLuint GLsizei GLsizei GLint GLenum GLchar * name
Definition: r_gl.h:110
void RS_StopResearch(technology_t *tech)
Stops a research (Removes scientists from it)
Definition: cp_research.cpp:94
union requirement_t::typelink_t link
class DateTime date
Definition: cp_campaign.h:246
const char * id
Definition: cp_building.h:78
static void RS_MarkResearched(technology_t *tech, const base_t *base)
Mark technologies as researched. This includes techs that depends on "tech" and have time=0...
unsigned int Com_HashKey(const char *name, int hashsize)
returns hash key for a string
Definition: shared.cpp:336
#define MEMBER_SIZEOF(TYPE, MEMBER)
Definition: scripts.h:34
linkedList_t * next
Definition: list.h:32
Header file for single player campaign control.
float overallTime
Definition: cp_research.h:158
bool RS_SaveXML(xmlNode_t *parent)
Save callback for research and technologies.
#define SAVE_RESEARCH_STATUSRESEARCH
Definition: save_research.h:32
Definition: scripts.h:52
char * tech[MAX_DESCRIPTIONS]
Definition: cp_research.h:135
const objDef_t * INVSH_GetItemByIDX(int index)
Returns the item that belongs to the given index or nullptr if the index is invalid.
Definition: inv_shared.cpp:266
void RS_ResetTechs(void)
This is called everytime RS_ParseTechnologies is called - to prevent cyclic hash tables.
Employee * E_GetUnassignedEmployee(const base_t *const base, const employeeType_t type)
Gets an assigned employee of a given type from the given base.
technology_t * RS_GetTechByProvided(const char *idProvided)
returns a pointer to the item tech (as listed in "provides")
researchType_t
Types of research topics.
Definition: cp_research.h:48
#define lengthof(x)
Definition: shared.h:105
xmlNode_t *IMPORT * XML_AddNode(xmlNode_t *parent, const char *name)
A building with all it&#39;s data.
Definition: cp_building.h:73
char researched[MAX_VAR]
Definition: cp_campaign.h:170
#define Q_streq(a, b)
Definition: shared.h:136
#define SAVE_RESEARCH_MAIL
Definition: save_research.h:39
bool statusResearchable
Definition: cp_research.h:177
void CP_DateConvertLong(const DateTime &date, dateLong_t *dateLong)
Converts a date from the engine in a (longer) human-readable format.
Definition: cp_time.cpp:73
#define AIR_Foreach(var)
iterates trough all aircraft
Definition: cp_aircraft.h:193
const char * model
Definition: cp_research.h:118
pediaChapter_t upChapters[MAX_PEDIACHAPTERS]
Definition: cp_campaign.h:323
int RS_ResearchRun(void)
Checks the research status.
class AlienContainment * alienContainment
Definition: cp_base.h:108
technology_t * RS_GetTechForItem(const objDef_t *item)
Returns technology entry for an item.
#define SAVE_RESEARCH_DATE
Definition: save_research.h:37
void RS_AssignScientist(technology_t *tech, base_t *base, Employee *employee)
Assigns scientist to the selected research-project.
requirements_t requireOR
Definition: cp_research.h:151
const char *IMPORT * Com_GetConstVariable(const char *space, int value)
const char *IMPORT * Com_EParse(const char **text, const char *errhead, const char *errinfo)
researchStatus_t statusResearch
Definition: cp_research.h:165
uint8_t byte
Definition: ufotypes.h:34
requirementType_t
Definition: cp_research.h:61
float researchRate
Definition: cp_campaign.h:198
XML tag constants for savegame.
#define SAVE_RESEARCH_PREDATE
Definition: save_research.h:36
void RS_RemoveScientist(technology_t *tech, Employee *employee)
Remove a scientist from a technology.
struct technology_s * hashNext
Definition: cp_research.h:199
bool statusCollected
Definition: cp_research.h:152