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