UFO: Alien Invasion
Loading...
Searching...
No Matches
cp_research.cpp
Go to the documentation of this file.
1
11
12/*
13Copyright (C) 2002-2025 UFO: Alien Invasion.
14
15This program is free software; you can redistribute it and/or
16modify it under the terms of the GNU General Public License
17as published by the Free Software Foundation; either version 2
18of the License, or (at your option) any later version.
19
20This program is distributed in the hope that it will be useful,
21but WITHOUT ANY WARRANTY; without even the implied warranty of
22MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
23
24See the GNU General Public License for more details.
25
26You should have received a copy of the GNU General Public License
27along with this program; if not, write to the Free Software
28Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29*/
30
31#include "../../DateTime.h"
32#include "../../cl_shared.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 */
69 cgi->Cmd_ExecuteString("%s", tech->finishedResearchEvent);
70
72 tech->researchedDate = DateTime(ccs.date);
73 if (!tech->statusResearchable) {
74 tech->statusResearchable = true;
75 tech->preResearchedDate = DateTime(ccs.date);
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)
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);
133 }
134
135 tech->statusResearchable = true;
136
137 /* only change the date if it wasn't set before */
138 if (tech->preResearchedDate.getDateAsDays() == 0) {
139 tech->preResearchedDate = DateTime(ccs.date);
140 }
141}
142
151bool 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;
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;
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;
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;
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. */
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 }
318 }
319
320 /* only change the date if it wasn't set before */
321 if (tech->preResearchedDate.getDateAsDays() == 0) {
322 tech->preResearchedDate = DateTime(ccs.date);
323 }
324
325 tech->statusCollected = true;
326}
327
335void 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++) {
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)
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)
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:
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{
423
424 for (int i = 0; i < ccs.numTechnologies; i++) {
426 if (tech->requireAND.numLinks)
428 if (tech->requireOR.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
491void 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++) {
500 ccs.objDefTechs[od->idx] = RS_GetTechByProvided(od->id);
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
654void 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;
684 employee->setAssigned(true);
686}
687
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;
721 }
722}
723
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
762static 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);
767}
768
774bool 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++) {
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
815{
816 int newResearch = 0;
817
818 for (int i = 0; i < ccs.numTechnologies; 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
854static 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
882static 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;
894 return req->link.td->id;
896 return "global alien count";
897 case RS_LINK_UFO:
898 return req->link.aircraft->id;
900 return "antimatter";
901 default:
902 return "unknown";
903 }
904}
905
907static 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";
919 return "alien_dead";
921 return "alienglobal";
922 case RS_LINK_UFO:
923 return "ufo";
925 return "antimatter";
926 default:
927 return "unknown";
928 }
929}
930
935static 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");
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
998static void RS_DebugMarkResearchedAll (void)
999{
1000 for (int i = 0; i < ccs.numTechnologies; i++) {
1002 cgi->Com_DPrintf(DEBUG_CLIENT, "...mark %s as researched\n", tech->id);
1004 RS_ResearchFinish(tech);
1006 }
1007}
1008
1013static 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);
1023 RS_ResearchFinish(tech);
1024 }
1025}
1026
1031static void RS_DebugResearchableAll_f (void)
1032{
1033 if (cgi->Cmd_Argc() != 2) {
1034 for (int i = 0; i < ccs.numTechnologies; i++) {
1036 cgi->Com_Printf("...mark %s as researchable\n", tech->id);
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);
1045 RS_MarkCollected(tech);
1046 }
1047 }
1048}
1049
1050static void RS_DebugFinishResearches_f (void)
1051{
1052 for (int i = 0; i < ccs.numTechnologies; 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
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
1083void RS_ResetTechs (void)
1084{
1085 /* they are static - but i'm paranoid - this is called before the techs were parsed */
1088
1089 /* delete redirectedTechs, will be filled during parse */
1090 cgi->LIST_Delete(&redirectedTechs);
1091}
1092
1098static 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
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
1134void 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
1143 if (ccs.numTechnologies >= MAX_TECHNOLOGIES) {
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) */
1156 technology_t* tech = &ccs.technologies[ccs.numTechnologies];
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;
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
1543static 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
1561bool 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
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
1622technology_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
1645technology_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
1670int 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
1725bool 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);
1736 cgi->XML_AddString(snode, SAVE_RESEARCH_STATUSRESEARCH, cgi->Com_GetConstVariable(SAVE_RESEARCHSTATUS_NAMESPACE, t->statusResearch));
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);
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
1764bool 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
1840bool 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}
DateTime class definition.
Alien containment class header.
Share stuff between the different cgame implementations.
#define _(String)
Definition cl_shared.h:44
int getAlive(const teamDef_t *team) const
Return number of alive aliens of a type in the cargo.
int getDead(const teamDef_t *team) const
Return number of dead alien bodies of a type in the cargo.
Class describing a point of time.
Definition DateTime.h:31
int getTimeAsSeconds() const
Return the time part of the DateTime as seconds.
Definition DateTime.cpp:54
int getDateAsDays() const
Return the date part of the DateTime as days.
Definition DateTime.cpp:46
character_t chr
void setAssigned(bool assigned)
Definition cp_employee.h:62
csi_t csi
Definition common.cpp:39
#define ERR_DROP
Definition common.h:211
#define ERR_FATAL
Definition common.h:210
const aircraft_t * AIR_GetAircraft(const char *name)
Searches the global array of aircraft types for a given aircraft.
#define AIR_Foreach(var)
iterates trough all aircraft
int AL_CountAll(void)
Counts live aliens in all bases.
base_t * B_GetNext(base_t *lastBase)
Iterates through founded bases.
Definition cp_base.cpp:286
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
int B_AntimatterInBase(const base_t *base)
returns the amount of antimatter stored in a base
Definition cp_base.cpp:2613
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
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 B_IsUnderAttack(base)
Definition cp_base.h:53
@ B_LAB
Definition cp_building.h:53
memPool_t * cp_campaignPool
ccs_t ccs
Header file for single player campaign control.
const cgame_import_t * cgi
void CAP_AddCurrent(base_t *base, baseCapacities_t capacity, int value)
Changes the current (used) capacity on a base.
int CAP_GetFreeCapacity(const base_t *base, baseCapacities_t capacityType)
Returns the free capacity of a type.
void CAP_SetCurrent(base_t *base, baseCapacities_t capacity, int value)
Sets the current (used) capacity on a base.
@ CAP_LABSPACE
Definition cp_capacity.h:33
Employee * E_GetUnassignedEmployee(const base_t *const base, const employeeType_t type)
Gets an assigned employee of a given type from the given base.
int E_CountHired(const base_t *const base, employeeType_t type)
Counts hired employees of a given type in a given base.
Employee * E_GetAssignedEmployee(const base_t *const base, const employeeType_t type)
Gets an unassigned employee of a given type from the given base.
@ EMPL_SCIENTIST
Definition cp_employee.h:32
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...
@ NT_RESEARCH_HALTED
@ NT_RESEARCH_PROPOSED
@ NT_RESEARCH_COMPLETED
char cp_messageBuffer[MAX_MESSAGE_TEXT]
@ MSG_RESEARCH_FINISHED
Definition cp_messages.h:38
@ MSG_RESEARCH_PROPOSAL
Definition cp_messages.h:36
@ MSG_RESEARCH_HALTED
Definition cp_messages.h:37
void CP_Popup(const char *title, const char *text,...)
Wrapper around UI_Popup.
Definition cp_popup.cpp:474
bool RS_SaveXML(xmlNode_t *parent)
Save callback for research and technologies.
void RS_MarkOneResearchable(technology_t *tech)
Marks one tech as researchable.
void RS_MarkCollected(technology_t *tech)
Marks a give technology as collected.
static technology_t * techHash[TECH_HASH_SIZE]
const char * RS_GetDescription(technologyDescriptions_t *desc)
returns the currently used description for a technology.
static const value_t valid_techmail_vars[]
The valid definition names in the research.ufo file for tech mails.
void RS_RemoveScientist(technology_t *tech, Employee *employee)
Remove a scientist from a technology.
int RS_CountScientistsInBase(const base_t *base)
Returns the number of employees searching in labs in given base.
technology_t * RS_GetTechForTeam(const teamDef_t *team)
Returns technology entry for a team.
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.
technology_t * RS_GetTechForItem(const objDef_t *item)
Returns technology entry for an item.
static technology_t * techHashProvided[TECH_HASH_SIZE]
int RS_GetTechIdxByName(const char *name)
Returns the index (idx) of a "tech" entry given it's name.
void RS_ResetTechs(void)
This is called everytime RS_ParseTechnologies is called - to prevent cyclic hash tables.
bool RS_IsResearched_ptr(const technology_t *tech)
Checks whether an item is already researched.
technology_t * RS_GetTechByID(const char *id)
return a pointer to the technology identified by given id string
void RS_StopResearch(technology_t *tech)
Stops a research (Removes scientists from it).
void RS_InitStartup(void)
This is more or less the initial Bind some of the functions in this file to console-commands that you...
bool RS_MarkStoryLineEventResearched(const char *techID)
technology_t * RS_GetTechWithMostScientists(const struct base_s *base)
Searches for the technology that has the most scientists assigned in a given base.
void RS_AssignScientist(technology_t *tech, base_t *base, Employee *employee)
Assigns scientist to the selected research-project.
technology_t * RS_GetTechByIDX(int techIdx)
Returns the technology pointer for a tech index. You can use this instead of "&ccs....
void RS_ResearchFinish(technology_t *tech)
Sets a technology status to researched and updates the date.
int RS_ResearchRun(void)
Checks the research status.
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.
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...
bool RS_ResearchAllowed(const base_t *base)
Returns true if the current base is able to handle research.
technology_t * RS_GetTechByProvided(const char *idProvided)
returns a pointer to the item tech (as listed in "provides")
bool RS_LoadXML(xmlNode_t *parent)
Load callback for research and technologies.
void RS_CheckRequirements(void)
Checks if running researches still meet their requirements.
static void RS_AssignTechLinks(requirements_t *reqs)
Assign required tech/item/etc... pointers for a single requirements list.
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 ...
void RS_RemoveScientistsExceedingCapacity(base_t *base)
Remove all exceeding scientist.
static bool RS_IsValidTechIndex(int techIdx)
static const value_t valid_tech_vars[]
The valid definition names in the research.ufo file.
void RS_RequiredLinksAssign(void)
Assign Link pointers to all required techs/items/etc...
bool RS_IsResearched_idx(int techIdx)
Checks if the technology (tech-index) has been researched.
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...
void RS_RemoveFiredScientist(base_t *base, Employee *employee)
Remove one scientist from research project if needed.
bool RS_ScriptSanityCheck(void)
Checks the parsed tech data for errors.
static linkedList_t * redirectedTechs
#define TECH_HASH_SIZE
Header for research related stuff.
@ RS_FINISH
Definition cp_research.h:44
@ RS_NONE
Definition cp_research.h:41
@ RS_RUNNING
Definition cp_research.h:42
@ RS_PAUSED
Definition cp_research.h:43
requirementType_t
Definition cp_research.h:61
@ RS_LINK_TECH
Definition cp_research.h:62
@ RS_LINK_UFO
Definition cp_research.h:68
@ RS_LINK_ALIEN_GLOBAL
Definition cp_research.h:67
@ RS_LINK_ITEM
Definition cp_research.h:64
@ RS_LINK_ALIEN
Definition cp_research.h:65
@ RS_LINK_ALIEN_DEAD
Definition cp_research.h:66
@ RS_LINK_TECH_NOT
Definition cp_research.h:63
@ RS_LINK_ANTIMATTER
Definition cp_research.h:69
researchType_t
Types of research topics.
Definition cp_research.h:48
@ RS_BUILDING
Definition cp_research.h:54
@ RS_NEWS
Definition cp_research.h:57
@ RS_CRAFTITEM
Definition cp_research.h:53
@ RS_TECH
Definition cp_research.h:49
@ RS_ALIEN
Definition cp_research.h:55
@ RS_UGV
Definition cp_research.h:56
@ RS_LOGIC
Definition cp_research.h:58
@ RS_WEAPON
Definition cp_research.h:50
@ RS_CRAFT
Definition cp_research.h:52
@ RS_ARMOUR
Definition cp_research.h:51
mailSentType_t
@ MAILSENT_FINISHED
@ MAILSENT_PROPOSAL
#define MAX_TECHNOLOGIES
Definition cp_research.h:31
#define MAX_DESCRIPTIONS
Definition cp_research.h:33
#define MAX_TECHLINKS
Definition cp_research.h:32
@ TECHMAIL_PRE
@ TECHMAIL_MAX
@ TECHMAIL_RESEARCHED
#define TECH_INVALID
Definition cp_research.h:35
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
Campaign geoscape time header.
void UP_OpenWith(const char *techID)
Opens the UFOpaedia from everywhere with the entry given through name.
int US_UFOsInStorage(const aircraft_t *ufoTemplate, const installation_t *installation)
Returns the number of UFOs stored (on an installation or anywhere).
#define DEBUG_CLIENT
Definition defines.h:59
void Sys_Error(const char *error,...)
Definition g_main.cpp:421
const objDef_t * INVSH_GetItemByIDX(int index)
Returns the item that belongs to the given index or nullptr if the index is invalid.
const objDef_t * INVSH_GetItemByID(const char *id)
Returns the item that belongs to the given id or nullptr if it wasn't found.
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
Shared parsing functions.
static wrapCache_t * hash[MAX_WRAP_HASH]
Definition r_font.cpp:86
QGL_EXTERN GLint i
Definition r_gl.h:113
QGL_EXTERN GLint GLenum type
Definition r_gl.h:94
QGL_EXTERN GLuint GLsizei GLsizei GLint GLenum GLchar * name
Definition r_gl.h:110
XML tag constants for savegame.
#define SAVE_RESEARCH_BASE
#define SAVE_RESEARCH_MAILSENT
#define SAVE_RESEARCH_PREDATE
#define SAVE_RESEARCH_ID
#define SAVE_RESEARCH_DATE
#define SAVE_RESEARCH_STATUSRESEARCH
#define SAVE_RESEARCH_STATUSRESEARCHABLE
#define SAVE_RESEARCH_TECH
#define SAVE_RESEARCH_SCIENTISTS
static const constListEntry_t saveResearchConstants[]
#define SAVE_RESEARCHSTATUS_NAMESPACE
#define SAVE_RESEARCH_MAIL
#define SAVE_RESEARCH_STATUSCOLLECTED
#define SAVE_RESEARCH_MAIL_ID
#define SAVE_RESEARCH_TIME
#define SAVE_RESEARCH_RESEARCH
@ V_BOOL
Definition scripts.h:50
@ V_FLOAT
Definition scripts.h:54
@ V_TRANSLATION_STRING
Definition scripts.h:59
@ V_HUNK_STRING
Definition scripts.h:69
@ V_NULL
Definition scripts.h:49
@ V_INT
Definition scripts.h:52
#define MEMBER_SIZEOF(TYPE, MEMBER)
Definition scripts.h:34
#define Q_strcasecmp(a, b)
Definition shared.h:131
#define Q_streq(a, b)
Definition shared.h:136
bool Q_strnull(const char *string)
Definition shared.h:138
#define OBJZERO(obj)
Definition shared.h:178
#define lengthof(x)
Definition shared.h:105
unsigned int Com_HashKey(const char *name, int hashsize)
returns hash key for a string
Definition shared.cpp:336
bool Com_sprintf(char *dest, size_t size, const char *fmt,...)
copies formatted string with buffer-size checking
Definition shared.cpp:494
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
An aircraft with all it's data.
char name[MAX_VAR]
char * model
struct technology_s * tech
A base with all it's data.
Definition cp_base.h:84
class AlienContainment * alienContainment
Definition cp_base.h:108
int idx
Definition cp_base.h:85
char name[MAX_VAR]
Definition cp_base.h:86
A building with all it's data.
Definition cp_building.h:73
const char * image
Definition cp_building.h:80
char * name
Definition cp_building.h:79
const char * id
Definition cp_building.h:78
char researched[MAX_VAR]
Human readable time information in the game.
Definition cp_time.h:36
byte month
Definition cp_time.h:38
byte day
Definition cp_time.h:39
short year
Definition cp_time.h:37
void * data
Definition list.h:31
linkedList_t * next
Definition list.h:32
char * campaign[MAX_CAMPAIGNS]
Definition cp_research.h:94
bool markOnly[MAX_CAMPAIGNS]
Definition cp_research.h:93
Defines all attributes of objects used in the inventory.
Definition inv_shared.h:264
const char * image
Definition inv_shared.h:270
const char * model
Definition inv_shared.h:269
const char * id
Definition inv_shared.h:268
const char * name
Definition inv_shared.h:267
union requirement_t::typelink_t link
requirementType_t type
Definition cp_research.h:74
requirement_t links[MAX_TECHLINKS]
Definition cp_research.h:88
char id[MAX_VAR]
Definition chr_shared.h:309
available mails for a tech - mail and mail_pre in script files
const char * model
const char * subject
const char * to
This is the technology parsed from research.ufo.
struct technology_s * upPrev
mailSentType_t mailSent
char * provides
requirements_t requireForProduction
requirements_t requireOR
researchStatus_t statusResearch
float overallTime
struct technology_s * hashProvidedNext
technologyDescriptions_t description
struct technology_s * hashNext
markResearched_t markResearched
struct base_s * base
char * finishedResearchEvent
technologyDescriptions_t preDescription
bool statusResearchable
requirements_t requireAND
struct technology_s * redirect
class DateTime preResearchedDate
bool statusCollected
struct technology_s * upNext
struct pediaChapter_s * upChapter
techMail_t mail[TECHMAIL_MAX]
class DateTime researchedDate
researchType_t type
char * text[MAX_DESCRIPTIONS]
char * tech[MAX_DESCRIPTIONS]
#define xmlNode_t
Definition xml.h:24