cp_research.c

Go to the documentation of this file.
00001 
00012 /*
00013 Copyright (C) 2002-2010 UFO: Alien Invasion.
00014 
00015 This program is free software; you can redistribute it and/or
00016 modify it under the terms of the GNU General Public License
00017 as published by the Free Software Foundation; either version 2
00018 of the License, or (at your option) any later version.
00019 
00020 This program is distributed in the hope that it will be useful,
00021 but WITHOUT ANY WARRANTY; without even the implied warranty of
00022 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00023 
00024 See the GNU General Public License for more details.
00025 
00026 You should have received a copy of the GNU General Public License
00027 along with this program; if not, write to the Free Software
00028 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
00029 
00030 */
00031 
00032 #include "../cl_shared.h"
00033 #include "../ui/ui_main.h"
00034 #include "../ui/ui_popup.h"
00035 #include "../../shared/parse.h"
00036 #include "cp_campaign.h"
00037 #include "cp_research.h"
00038 #include "save/save_research.h"
00039 
00040 #define TECH_HASH_SIZE 64
00041 static technology_t *techHash[TECH_HASH_SIZE];
00042 static technology_t *techHashProvided[TECH_HASH_SIZE];
00043 
00049 static inline void RS_PushNewsWhenResearchedFinished (const technology_t* tech)
00050 {
00051     /*if (tech->time == 0 && tech->type == RS_NEWS)
00052         UP_OpenWith(tech->id);*/
00053 }
00054 
00059 void RS_ResearchFinish (technology_t* tech)
00060 {
00061     /* Remove all scientists from the technology. */
00062     while (tech->scientists > 0)
00063         RS_RemoveScientist(tech, NULL);
00064 
00069     RS_GetDescription(&tech->description);
00070     if (tech->preDescription.usedDescription < 0) {
00071         /* For some reason the research proposal description was not set at this point - we just make sure it _is_ set. */
00072         RS_GetDescription(&tech->preDescription);
00073     }
00074 
00075     /* execute the trigger only if the tech is not yet researched */
00076     if (tech->finishedResearchEvent && tech->statusResearch != RS_FINISH)
00077         Cmd_ExecuteString(tech->finishedResearchEvent);
00078 
00079     tech->statusResearch = RS_FINISH;
00080     tech->researchedDate = ccs.date;
00081     if (!tech->statusResearchable) {
00082         tech->statusResearchable = qtrue;
00083         tech->preResearchedDate = ccs.date;
00084     }
00085     RS_PushNewsWhenResearchedFinished(tech);
00086 
00087     /* send a new message and add it to the mailclient */
00088     if ((tech->mailSent < MAILSENT_FINISHED) && (tech->type != RS_LOGIC)) {
00089         Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("A research project has been completed: %s\n"), _(tech->name));
00090         MSO_CheckAddNewMessage( NT_RESEARCH_COMPLETED, _("Research finished"), cp_messageBuffer, qfalse, MSG_RESEARCH_FINISHED, tech);
00091         tech->mailSent = MAILSENT_FINISHED;
00092     }
00093 }
00094 
00099 void RS_StopResearch (technology_t* tech)
00100 {
00101     assert(tech);
00102     while (tech->scientists > 0)
00103         RS_RemoveScientist(tech, NULL);
00104     assert(tech->statusResearch == RS_PAUSED);
00105 }
00106 
00113 void RS_MarkOneResearchable (technology_t* tech)
00114 {
00115     if (!tech)
00116         return;
00117 
00118     Com_DPrintf(DEBUG_CLIENT, "RS_MarkOneResearchable: \"%s\" marked as researchable.\n", tech->id);
00119 
00120     /* Don't do anything for not researchable techs. */
00121     if (tech->time == -1)
00122         return;
00123 
00124     /* Don't send mail for automatically completed techs. */
00125     if (tech->time == 0)
00126         tech->mailSent = MAILSENT_FINISHED;
00127 
00132     RS_GetDescription(&tech->preDescription);
00133     /* tech->description is checked before a research is finished */
00134 
00135     if (tech->mailSent < MAILSENT_PROPOSAL) {
00136         Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("New research proposal: %s\n"), _(tech->name));
00137         MSO_CheckAddNewMessage(NT_RESEARCH_PROPOSED, _("Unknown Technology researchable"), cp_messageBuffer, qfalse, MSG_RESEARCH_PROPOSAL, tech);
00138         tech->mailSent = MAILSENT_PROPOSAL;
00139     }
00140 
00141     tech->statusResearchable = qtrue;
00142 
00143     /* only change the date if it wasn't set before */
00144     if (tech->preResearchedDate.day == 0) {
00145         tech->preResearchedDate = ccs.date;
00146     }
00147 }
00148 
00158 qboolean RS_RequirementsMet (const requirements_t *requiredAND, const requirements_t *requiredOR, const base_t *base)
00159 {
00160     int i;
00161     qboolean metAND = qfalse;
00162     qboolean metOR = qfalse;
00163 
00164     if (!requiredAND && !requiredOR) {
00165         Com_Printf("RS_RequirementsMet: No requirement list(s) given as parameter.\n");
00166         return qfalse;
00167     }
00168 
00169     /* If there are no requirements defined at all we have 'met' them by default. */
00170     if (requiredAND->numLinks == 0 && requiredOR->numLinks == 0) {
00171         Com_DPrintf(DEBUG_CLIENT, "RS_RequirementsMet: No requirements set for this tech. They are 'met'.\n");
00172         return qtrue;
00173     }
00174 
00175     if (requiredAND->numLinks) {
00176         metAND = qtrue;
00177         for (i = 0; i < requiredAND->numLinks; i++) {
00178             const requirement_t *req = &requiredAND->links[i];
00179             switch (req->type) {
00180             case RS_LINK_TECH:
00181                 assert(req->link);
00182                 Com_DPrintf(DEBUG_CLIENT, "RS_RequirementsMet: ANDtech: %s / %i\n", req->id, ((technology_t*)req->link)->idx);
00183                 if (!RS_IsResearched_ptr(req->link)) {
00184                     Com_DPrintf(DEBUG_CLIENT, "RS_RequirementsMet: this tech not researched ----> %s \n", req->id);
00185                     metAND = qfalse;
00186                 }
00187                 break;
00188             case RS_LINK_TECH_NOT:
00189                 assert(req->link);
00190                 Com_DPrintf(DEBUG_CLIENT, "RS_RequirementsMet: ANDtech NOT: %s / %i\n", req->id, ((technology_t*)req->link)->idx);
00191                 if (RS_IsResearched_ptr(req->link)) {
00192                     Com_DPrintf(DEBUG_CLIENT, "RS_RequirementsMet: this tech researched but it  must not be ----> %s \n", req->id);
00193                     metAND = qfalse;
00194                 }
00195                 break;
00196             case RS_LINK_ITEM:
00197                 assert(req->link);
00198                 assert(base);
00199                 /* The same code is used in "PR_RequirementsMet" */
00200                 Com_DPrintf(DEBUG_CLIENT, "RS_RequirementsMet: ANDitem: %s / %i\n", req->id, ((objDef_t*)req->link)->idx);
00201                 if (B_ItemInBase(req->link, base) < req->amount)
00202                     metAND = qfalse;
00203                 break;
00204             case RS_LINK_ALIEN_DEAD:
00205             case RS_LINK_ALIEN:
00206                 assert(req->link);
00207                 assert(base);
00208                 if (AL_GetAlienAmount(req->link, req->type, base) < req->amount)
00209                     metAND = qfalse;
00210                 break;
00211             case RS_LINK_ALIEN_GLOBAL:
00212                 if (AL_CountAll() < req->amount)
00213                     metAND = qfalse;
00214                 break;
00215             case RS_LINK_UFO:
00216                 assert(req->link);
00217                 if (US_UFOsInStorage(req->link, NULL) < req->amount)
00218                     metAND = qfalse;
00219                 break;
00220             case RS_LINK_ANTIMATTER:
00221                 if (B_AntimatterInBase(base) < req->amount)
00222                     metAND = qfalse;
00223                 break;
00224             default:
00225                 break;
00226             }
00227 
00228             if (!metAND)
00229                 break;
00230         }
00231     }
00232 
00233     if (requiredOR->numLinks)
00234         for (i = 0; i < requiredOR->numLinks; i++) {
00235             const requirement_t *req = &requiredOR->links[i];
00236             switch (req->type) {
00237             case RS_LINK_TECH:
00238                 assert(req->link);
00239                 Com_DPrintf(DEBUG_CLIENT, "RS_RequirementsMet: ORtech: %s / %i\n", req->id, ((technology_t*)req->link)->idx);
00240                 if (RS_IsResearched_ptr(req->link))
00241                     metOR = qtrue;
00242                 break;
00243             case RS_LINK_TECH_NOT:
00244                 assert(req->link);
00245                 Com_DPrintf(DEBUG_CLIENT, "RS_RequirementsMet: ORtech: NOT %s / %i\n", req->id, ((technology_t*)req->link)->idx);
00246                 if (!RS_IsResearched_ptr(req->link))
00247                     metOR = qtrue;
00248                 break;
00249             case RS_LINK_ITEM:
00250                 assert(req->link);
00251                 assert(base);
00252                 /* The same code is used in "PR_RequirementsMet" */
00253                 Com_DPrintf(DEBUG_CLIENT, "RS_RequirementsMet: ORitem: %s / %i\n", req->id, ((objDef_t*)req->link)->idx);
00254                 if (B_ItemInBase(req->link, base) >= req->amount)
00255                     metOR = qtrue;
00256                 break;
00257             case RS_LINK_ALIEN:
00258             case RS_LINK_ALIEN_DEAD:
00259                 assert(req->link);
00260                 assert(base);
00261                 if (AL_GetAlienAmount(req->link, req->type, base) >= req->amount)
00262                     metOR = qtrue;
00263                 break;
00264             case RS_LINK_ALIEN_GLOBAL:
00265                 if (AL_CountAll() >= req->amount)
00266                     metOR = qtrue;
00267                 break;
00268             case RS_LINK_UFO:
00269                 assert(req->link);
00270                 if (US_UFOsInStorage(req->link, NULL) >= req->amount)
00271                     metOR = qtrue;
00272                 break;
00273             case RS_LINK_ANTIMATTER:
00274                 if (B_AntimatterInBase(base) >= req->amount)
00275                     metOR = qtrue;
00276             default:
00277                 break;
00278             }
00279 
00280             if (metOR)
00281                 break;
00282         }
00283     Com_DPrintf(DEBUG_CLIENT, "met_AND is %i, met_OR is %i\n", metAND, metOR);
00284 
00285     return (metAND || metOR);
00286 }
00287 
00292 const char *RS_GetDescription (descriptions_t *desc)
00293 {
00294     int i;
00295 
00296     /* Return (unparsed) default description (0) if nothing is defined.
00297      * it is _always_ set, even if numDescriptions is zero. See RS_ParseTechnologies (standard values). */
00298     if (desc->numDescriptions == 0)
00299         return desc->text[0];
00300 
00301     /* Return already used description if it's defined. */
00302     if (desc->usedDescription >= 0)
00303         return desc->text[desc->usedDescription];
00304 
00305     /* Search for useable description text (first match is returned => order is important)
00306      * The default (0) entry is skipped here. */
00307     for (i = 1; i < desc->numDescriptions; i++) {
00308         const technology_t *tech = RS_GetTechByID(desc->tech[i]);
00309         if (!tech)
00310             continue;
00311 
00312         if (RS_IsResearched_ptr(tech)) {
00313             desc->usedDescription = i;  
00314             return desc->text[i];
00315         }
00316     }
00317 
00318     return desc->text[0];
00319 }
00320 
00328 void RS_MarkCollected (technology_t* tech)
00329 {
00330     assert(tech);
00331 
00332     if (tech->time == 0) /* Don't send mail for automatically completed techs. */
00333         tech->mailSent = MAILSENT_FINISHED;
00334 
00335     if (tech->mailSent < MAILSENT_PROPOSAL) {
00336         if (tech->statusResearch < RS_FINISH) {
00337             Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("New research proposal: %s\n"), _(tech->name));
00338             MSO_CheckAddNewMessage(NT_RESEARCH_PROPOSED, _("Unknown Technology found"), cp_messageBuffer, qfalse, MSG_RESEARCH_PROPOSAL, tech);
00339         }
00340         tech->mailSent = MAILSENT_PROPOSAL;
00341     }
00342 
00343     /* only change the date if it wasn't set before */
00344     if (tech->preResearchedDate.day == 0) {
00345         tech->preResearchedDate = ccs.date;
00346     }
00347 
00348     tech->statusCollected = qtrue;
00349 }
00350 
00358 void RS_MarkResearchable (qboolean init, const base_t* base)
00359 {
00360     int i;
00361     const base_t *thisBase = base;
00362 
00363     /* Set all entries to initial value. */
00364     for (i = 0; i < ccs.numTechnologies; i++) {
00365         technology_t *tech = RS_GetTechByIDX(i);
00366         tech->statusResearchable = qfalse;
00367     }
00368 
00369     for (i = 0; i < ccs.numTechnologies; i++) { /* i = tech-index */
00370         technology_t *tech = RS_GetTechByIDX(i);
00371         if (!tech->statusResearchable) { /* In case we loopback we need to check for already marked techs. */
00372             /* Check for collected items/aliens/etc... */
00373             if (tech->statusResearch != RS_FINISH) {
00374                 Com_DPrintf(DEBUG_CLIENT, "RS_MarkResearchable: handling \"%s\".\n", tech->id);
00375                 /* If required techs are all researched and all other requirements are met, mark this as researchable. */
00376 
00377                 if (tech->base)
00378                     base = tech->base;
00379                 else
00380                     base = thisBase;
00381 
00382                 assert(base);
00383 
00384                 /* All requirements are met. */
00385                 if (RS_RequirementsMet(&tech->requireAND, &tech->requireOR, base)) {
00386                     Com_DPrintf(DEBUG_CLIENT, "RS_MarkResearchable: \"%s\" marked researchable. reason:requirements.\n", tech->id);
00387                     if (init && tech->time == 0)
00388                         tech->mailSent = MAILSENT_PROPOSAL;
00389                     RS_MarkOneResearchable(tech);
00390                 }
00391 
00392                 /* If the tech is a 'free' one (such as ammo for a weapon),
00393                  * mark it as researched and loop back to see if it unlocks
00394                  * any other techs */
00395                 if (tech->statusResearchable && tech->time == 0) {
00396                     if (init)
00397                         tech->mailSent = MAILSENT_FINISHED;
00398                     RS_ResearchFinish(tech);
00399                     Com_DPrintf(DEBUG_CLIENT, "RS_MarkResearchable: automatically researched \"%s\"\n", tech->id);
00400                     /* Restart the loop as this may have unlocked new possibilities. */
00401                     i = -1;
00402                 }
00403             }
00404         }
00405     }
00406     Com_DPrintf(DEBUG_CLIENT, "RS_MarkResearchable: Done.\n");
00407 }
00408 
00413 static void RS_AssignTechLinks (requirements_t *reqs)
00414 {
00415     int i;
00416     requirement_t *req;
00417 
00418     for (i = 0; i < reqs->numLinks; i++) {
00419         req = &reqs->links[i];
00420         switch (req->type) {
00421         case RS_LINK_TECH:
00422         case RS_LINK_TECH_NOT:
00423             /* Get the index in the techtree. */
00424             req->link = RS_GetTechByID(req->id);
00425             if (!req->link)
00426                 Com_Error(ERR_DROP, "RS_AssignTechLinks: Could not get tech definition for '%s'", req->id);
00427             break;
00428         case RS_LINK_ITEM:
00429             /* Get index in item-list. */
00430             req->link = INVSH_GetItemByID(req->id);
00431             if (!req->link)
00432                 Com_Error(ERR_DROP, "RS_AssignTechLinks: Could not get item definition for '%s'", req->id);
00433             break;
00434         case RS_LINK_ALIEN:
00435         case RS_LINK_ALIEN_DEAD:
00436             req->link = Com_GetTeamDefinitionByID(req->id);
00437             if (!req->link)
00438                 Com_Error(ERR_DROP, "RS_AssignTechLinks: Could not get alien type (alien or alien_dead) definition for '%s'", req->id);
00439             break;
00440         case RS_LINK_UFO:
00441             req->link = AIR_GetAircraft(req->id);
00442             break;
00443         default:
00444             break;
00445         }
00446     }
00447 }
00448 
00449 static linkedList_t *redirectedTechs;
00450 
00455 void RS_RequiredLinksAssign (void)
00456 {
00457     linkedList_t* ll = redirectedTechs; 
00458     technology_t *redirectedTech;
00459     int i;
00460 
00461     for (i = 0; i < ccs.numTechnologies; i++) {
00462         technology_t *tech = RS_GetTechByIDX(i);
00463         if (tech->requireAND.numLinks)
00464             RS_AssignTechLinks(&tech->requireAND);
00465         if (tech->requireOR.numLinks)
00466             RS_AssignTechLinks(&tech->requireOR);
00467         if (tech->requireForProduction.numLinks)
00468             RS_AssignTechLinks(&tech->requireForProduction);
00469     }
00470 
00471     /* Link the redirected technologies to their correct "parents" */
00472     while (ll) {
00473         /* Get the data stored in the linked list. */
00474         assert(ll);
00475         redirectedTech = (technology_t *) ll->data;
00476         ll = ll->next;
00477 
00478         assert(redirectedTech);
00479 
00480         assert(ll);
00481         redirectedTech->redirect = RS_GetTechByID((char*)ll->data);
00482         ll = ll->next;
00483     }
00484 
00485     /* clean up redirected techs list as it is no longer needed */
00486     LIST_Delete(&redirectedTechs);
00487 }
00488 
00489 technology_t* RS_GetTechForItem (const objDef_t *item)
00490 {
00491     if (item == NULL)
00492         Com_Error(ERR_DROP, "RS_GetTechForItem: No item given");
00493     if (item->idx < 0 || item->idx > lengthof(ccs.objDefTechs))
00494         Com_Error(ERR_DROP, "RS_GetTechForItem: Buffer overflow");
00495     if (ccs.objDefTechs[item->idx] == NULL)
00496         Com_Error(ERR_DROP, "RS_GetTechForItem: No technology for item %s", item->id);
00497     return ccs.objDefTechs[item->idx];
00498 }
00499 
00509 void RS_InitTree (qboolean load)
00510 {
00511     int i, j;
00512     technology_t *tech;
00513     byte found;
00514     objDef_t *od;
00515 
00516     /* Add links to technologies. */
00517     for (i = 0, od = csi.ods; i < csi.numODs; i++, od++) {
00518         ccs.objDefTechs[od->idx] = RS_GetTechByProvided(od->id);
00519         if (!ccs.objDefTechs[od->idx])
00520             Com_Error(ERR_DROP, "RS_InitTree: Could not find a valid tech for item %s", od->id);
00521     }
00522 
00523     for (i = 0, tech = ccs.technologies; i < ccs.numTechnologies; i++, tech++) {
00524         for (j = 0; j < tech->markResearched.numDefinitions; j++) {
00525             if (tech->markResearched.markOnly[j] && !strcmp(tech->markResearched.campaign[j], ccs.curCampaign->researched)) {
00526                 Com_DPrintf(DEBUG_CLIENT, "...mark %s as researched\n", tech->id);
00527                 RS_ResearchFinish(tech);
00528                 break;
00529             }
00530         }
00531 
00532         /* Save the idx to the id-names of the different requirement-types for quicker access.
00533          * The id-strings themself are not really needed afterwards :-/ */
00534         RS_AssignTechLinks(&tech->requireAND);
00535         RS_AssignTechLinks(&tech->requireOR);
00536 
00537         /* Search in correct data/.ufo */
00538         switch (tech->type) {
00539         case RS_CRAFTITEM:
00540             if (!tech->name)
00541                 Com_DPrintf(DEBUG_CLIENT, "RS_InitTree: \"%s\" A type craftitem needs to have a 'name\txxx' defined.", tech->id);
00542             break;
00543         case RS_NEWS:
00544             if (!tech->name)
00545                 Com_DPrintf(DEBUG_CLIENT, "RS_InitTree: \"%s\" A 'type news' item needs to have a 'name\txxx' defined.", tech->id);
00546             break;
00547         case RS_TECH:
00548             if (!tech->name)
00549                 Com_DPrintf(DEBUG_CLIENT, "RS_InitTree: \"%s\" A 'type tech' item needs to have a 'name\txxx' defined.", tech->id);
00550             break;
00551         case RS_WEAPON:
00552         case RS_ARMOUR:
00553             found = qfalse;
00554             for (j = 0; j < csi.numODs; j++) {  /* j = item index */
00555                 objDef_t *item = INVSH_GetItemByIDX(j);
00556 
00557                 /* This item has been 'provided' -> get the correct data. */
00558                 if (!strcmp(tech->provides, item->id)) {
00559                     found = qtrue;
00560                     if (!tech->name)
00561                         tech->name = Mem_PoolStrDup(item->name, cp_campaignPool, 0);
00562                     if (!tech->mdl)
00563                         tech->mdl = Mem_PoolStrDup(item->model, cp_campaignPool, 0);
00564                     if (!tech->image)
00565                         tech->image = Mem_PoolStrDup(item->image, cp_campaignPool, 0);
00566                     break;
00567                 }
00568             }
00569             /* No id found in csi.ods */
00570             if (!found) {
00571                 tech->name = Mem_PoolStrDup(tech->id, cp_campaignPool, 0);
00572                 Com_Printf("RS_InitTree: \"%s\" - Linked weapon or armour (provided=\"%s\") not found. Tech-id used as name.\n",
00573                         tech->id, tech->provides);
00574             }
00575             break;
00576         case RS_BUILDING:
00577             found = qfalse;
00578             for (j = 0; j < ccs.numBuildingTemplates; j++) {
00579                 building_t *building = &ccs.buildingTemplates[j];
00580                 /* This building has been 'provided'  -> get the correct data. */
00581                 if (!strcmp(tech->provides, building->id)) {
00582                     found = qtrue;
00583                     if (!tech->name)
00584                         tech->name = Mem_PoolStrDup(building->name, cp_campaignPool, 0);
00585                     if (!tech->image)
00586                         tech->image = Mem_PoolStrDup(building->image, cp_campaignPool, 0);
00587                     break;
00588                 }
00589             }
00590             if (!found) {
00591                 tech->name = Mem_PoolStrDup(tech->id, cp_campaignPool, 0);
00592                 Com_DPrintf(DEBUG_CLIENT, "RS_InitTree: \"%s\" - Linked building (provided=\"%s\") not found. Tech-id used as name.\n",
00593                         tech->id, tech->provides);
00594             }
00595             break;
00596         case RS_CRAFT:
00597             found = qfalse;
00598             for (j = 0; j < ccs.numAircraftTemplates; j++) {
00599                 aircraft_t *aircraftTemplate = &ccs.aircraftTemplates[j];
00600                 /* This aircraft has been 'provided'  -> get the correct data. */
00601                 if (!tech->provides)
00602                     Com_Error(ERR_FATAL, "RS_InitTree: \"%s\" - No linked aircraft or craft-upgrade.\n", tech->id);
00603                 if (!strcmp(tech->provides, aircraftTemplate->id)) {
00604                     found = qtrue;
00605                     if (!tech->name)
00606                         tech->name = Mem_PoolStrDup(aircraftTemplate->name, cp_campaignPool, 0);
00607                     if (!tech->mdl) {   /* DEBUG testing */
00608                         tech->mdl = Mem_PoolStrDup(aircraftTemplate->model, cp_campaignPool, 0);
00609                         Com_DPrintf(DEBUG_CLIENT, "RS_InitTree: aircraft model \"%s\" \n", aircraftTemplate->model);
00610                     }
00611                     aircraftTemplate->tech = tech;
00612                     break;
00613                 }
00614             }
00615             if (!found)
00616                 Com_Printf("RS_InitTree: \"%s\" - Linked aircraft or craft-upgrade (provided=\"%s\") not found.\n", tech->id, tech->provides);
00617             break;
00618         case RS_ALIEN:
00619             /* does nothing right now */
00620             break;
00621         case RS_UGV:
00623             break;
00624         case RS_LOGIC:
00625             /* Does not need any additional data. */
00626             break;
00627         }
00628 
00629         /* Check if we finally have a name for the tech. */
00630         if (!tech->name) {
00631             if (tech->type != RS_LOGIC)
00632                 Com_Error(ERR_DROP, "RS_InitTree: \"%s\" - no name found!", tech->id);
00633         } else {
00634             /* Fill in subject lines of tech-mails.
00635              * The tech-name is copied if nothing is defined. */
00636             for (j = 0; j < TECHMAIL_MAX; j++) {
00637                 /* Check if no subject was defined (but it is supposed to be sent) */
00638                 if (!tech->mail[j].subject && tech->mail[j].to) {
00639                     tech->mail[j].subject = tech->name;
00640                 }
00641             }
00642         }
00643 
00644         if (!tech->image && !tech->mdl)
00645             Com_DPrintf(DEBUG_CLIENT, "Tech %s of type %i has no image (%p) and no model (%p) assigned.\n",
00646                     tech->id, tech->type, tech->image, tech->mdl);
00647     }
00648 
00649     if (load) {
00650         /* when you load a savegame right after starting UFO, the aircraft in bases
00651          * and installations don't have any tech assigned */
00652         for (j = 0; j < ccs.numBases; j++) {
00653             base_t *b = B_GetFoundedBaseByIDX(j);
00654             aircraft_t *aircraft;
00655             if (!b)
00656                 continue;
00657 
00658             aircraft = NULL;
00659             while ((aircraft = AIR_GetNextFromBase(b, aircraft)) != NULL) {
00660                 /* if you already played before loading the game, tech are already defined for templates */
00661                 if (!aircraft->tech)
00662                     aircraft->tech = RS_GetTechByProvided(aircraft->id);
00663             }
00664         }
00665     }
00666 
00667     Com_DPrintf(DEBUG_CLIENT, "RS_InitTree: Technology tree initialised. %i entries found.\n", i);
00668 }
00669 
00680 void RS_AssignScientist (technology_t* tech, base_t *base, employee_t *employee)
00681 {
00682 
00683     assert(tech);
00684     Com_DPrintf(DEBUG_CLIENT, "RS_AssignScientist: %i | %s \n", tech->idx, tech->name);
00685 
00686     /* if the tech is already assigned to a base, use that one */
00687     if (tech->base)
00688         base = tech->base;
00689 
00690     assert(base);
00691 
00692     if (!employee)
00693         employee = E_GetUnassignedEmployee(base, EMPL_SCIENTIST);
00694     if (!employee) {
00695         /* No scientists are free in this base. */
00696         Com_DPrintf(DEBUG_CLIENT, "No free scientists in this base (%s) to assign to tech '%s'\n", base->name, tech->id);
00697         return;
00698     }
00699     if (employee->type != EMPL_SCIENTIST) {
00700         Com_Error(ERR_DROP, "Trying to assign a non-scientist to research tech: %s, at base %s\n", tech->id, base->name);
00701     }
00702     if (employee->building) {
00704         Com_Printf("RS_AssignScientist: Scientist %i is already assigned\n", employee->idx);
00705         return;
00706     }
00707 
00708     if (tech->statusResearchable) {
00709         /* Get a free lab from the base. */
00710         building_t *building = B_GetBuildingInBaseByType(base, B_LAB, qtrue);
00711         if (building) {
00712             /* Check the capacity. */
00713             if (base->capacities[CAP_LABSPACE].max > base->capacities[CAP_LABSPACE].cur) {
00714                 tech->scientists++;         /* Assign a scientist to this tech. */
00715                 tech->base = base;          /* Make sure this tech has proper base pointer. */
00716                 base->capacities[CAP_LABSPACE].cur++;   /* Set the amount of currently assigned in capacities. */
00717 
00718                 /* Assign the sci to the lab and set number of used lab-space. */
00719                 employee->building = building;
00720             } else {
00721                 UI_Popup(_("Not enough laboratories"), _("No free space in laboratories left.\nBuild more laboratories.\n"));
00722                 return;
00723             }
00724         } else {
00725             UI_Popup(_("Not enough laboratories"), _("No free space in laboratories left.\nBuild more laboratories.\n"));
00726             return;
00727         }
00728 
00729         tech->statusResearch = RS_RUNNING;
00730     }
00731 }
00732 
00741 void RS_RemoveScientist (technology_t* tech, employee_t *employee)
00742 {
00743     assert(tech);
00744 
00745     /* no need to remove anything, but we can do some check */
00746     if (tech->scientists == 0) {
00747         assert(tech->base == NULL);
00748         assert(tech->statusResearch == RS_PAUSED);
00749         return;
00750     }
00751 
00752     if (!employee)
00753         employee = E_GetAssignedEmployee(tech->base, EMPL_SCIENTIST);
00754     if (employee) {
00755         /* Remove the scientist from the tech. */
00756         tech->scientists--;
00757         /* Update capacity. */
00758         tech->base->capacities[CAP_LABSPACE].cur--;
00759         /* Remove the scientist from the lab and set number of used lab-space. */
00760         employee->building = NULL; /* See also E_RemoveEmployeeFromBuildingOrAircraft */
00761     } else {
00762         Com_Error(ERR_DROP, "No assigned scientists found - serious inconsistency.");
00763     }
00764 
00765     assert(tech->scientists >= 0);
00766 
00767     if (tech->scientists == 0) {
00768         /* Remove the tech from the base if no scientists are left to research it. */
00769         tech->base = NULL;
00770         tech->statusResearch = RS_PAUSED;
00771     }
00772 }
00773 
00782 void RS_RemoveFiredScientist (base_t *base, employee_t *employee)
00783 {
00784     technology_t *tech;
00785     employee_t *freeScientist = E_GetUnassignedEmployee(base, EMPL_SCIENTIST);
00786 
00787     assert(base);
00788     assert(employee);
00789 
00790     /* Get a tech where there is at least one scientist working on (unless no scientist working in this base) */
00791     tech = RS_GetTechWithMostScientists(base);
00792 
00793     /* tech should never be NULL, as there is at least 1 scientist working in base */
00794     assert(tech);
00795     RS_RemoveScientist(tech, employee);
00796 
00797     /* if there is at least one scientist not working on a project, make this one replace removed employee */
00798     if (freeScientist)
00799         RS_AssignScientist(tech, base, freeScientist);
00800 }
00801 
00809 static void RS_MarkResearched (technology_t *tech, const base_t *base)
00810 {
00811     RS_ResearchFinish(tech);
00812     Com_DPrintf(DEBUG_CLIENT, "Research of \"%s\" finished.\n", tech->id);
00813     RS_MarkResearchable(qfalse, base);
00814 }
00815 
00821 qboolean RS_MarkStoryLineEventResearched (const char *techID)
00822 {
00823     technology_t* tech = RS_GetTechByID(techID);
00824     if (!RS_IsResearched_ptr(tech)) {
00825         int j;
00826         for (j = 0; j < ccs.numBases; j++) {
00827             const base_t *base = B_GetFoundedBaseByIDX(j);
00828             if (base) {
00829                 RS_MarkResearched(tech, base);
00830                 return qtrue;
00831             }
00832         }
00833     }
00834     return qfalse;
00835 }
00836 
00837 
00843 void RS_ResearchRun (void)
00844 {
00845     int i, newResearch = 0;
00846     base_t* checkBases[MAX_BASES];
00847 
00848     memset(checkBases, 0, sizeof(checkBases));
00849 
00850     for (i = 0; i < ccs.numTechnologies; i++) {
00851         technology_t *tech = RS_GetTechByIDX(i);
00852 
00853         if (tech->statusResearch != RS_RUNNING)
00854             continue;
00855 
00856         if (!RS_RequirementsMet(&tech->requireAND, &tech->requireOR, tech->base)) {
00857             Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("Research prerequisites of %s do not met at %s. Research halted!"), _(tech->name), tech->base->name);
00858             MSO_CheckAddNewMessage(NT_RESEARCH_HALTED, _("Research halted"), cp_messageBuffer, qfalse, MSG_RESEARCH_HALTED, NULL);
00859 
00860             while (tech->scientists > 0)
00861                 RS_RemoveScientist(tech, NULL);
00862             assert(tech->statusResearch == RS_PAUSED);
00863 
00864             continue;
00865         }
00866 
00867         /* the test hasBuilding[B_LAB] is needed to make sure that labs are active (their dependences are OK) */
00868         if (tech->time > 0 && tech->scientists > 0) {
00869             base_t *base = tech->base;
00870             assert(tech->base); 
00871             if (RS_ResearchAllowed(base)) {
00873                 tech->time -= tech->scientists * 0.8;
00874                 /* Will be a good thing (think of percentage-calculation) once non-integer values are used. */
00875                 if (tech->time <= 0) {
00876                     RS_MarkResearched(tech, base);
00877 
00878                     newResearch++;
00879                     /* Add this base to the list that needs an update call.
00880                      * tech->base was set to NULL with the last call of RS_RemoveScientist. */
00881                     checkBases[base->idx] = base;
00882                     tech->time = 0;
00883                 }
00884             }
00885         }
00886     }
00887 
00888     /* now update the data in all affected bases -- we only need to update tech list and not research menu */
00891     for (i = 0; i < MAX_BASES; i++) {
00892         if (checkBases[i])
00893             RS_MarkResearchable(qfalse, checkBases[i]);
00894     }
00895 }
00896 
00897 #ifdef DEBUG
00898 
00899 static const char *RS_TechTypeToName (researchType_t type)
00900 {
00901     switch(type) {
00902     case RS_TECH:
00903         return "tech";
00904     case RS_WEAPON:
00905         return "weapon";
00906     case RS_ARMOUR:
00907         return "armour";
00908     case RS_CRAFT:
00909         return "craft";
00910     case RS_CRAFTITEM:
00911         return "craftitem";
00912     case RS_BUILDING:
00913         return "building";
00914     case RS_ALIEN:
00915         return "alien";
00916     case RS_UGV:
00917         return "ugv";
00918     case RS_NEWS:
00919         return "news";
00920     case RS_LOGIC:
00921         return "logic";
00922     default:
00923         return "unknown";
00924     }
00925 }
00926 
00927 static const char *RS_TechReqToName (requirement_t *req)
00928 {
00929     assert(req->link);
00930     switch(req->type) {
00931     case RS_LINK_TECH:
00932         return ((technology_t*)req->link)->id;
00933     case RS_LINK_TECH_NOT:
00934         return va("not %s", ((technology_t*)req->link)->id);
00935     case RS_LINK_ITEM:
00936         return ((objDef_t*)req->link)->id;
00937     case RS_LINK_ALIEN:
00938         return ((teamDef_t*)req->link)->id;
00939     case RS_LINK_ALIEN_DEAD:
00940         return ((teamDef_t*)req->link)->id;
00941     case RS_LINK_ALIEN_GLOBAL:
00942         return "global alien count";
00943     case RS_LINK_UFO:
00944         return ((aircraft_t*)req->link)->id;
00945     case RS_LINK_ANTIMATTER:
00946         return "antimatter";
00947     default:
00948         return "unknown";
00949     }
00950 }
00951 
00953 static const char *RS_TechLinkTypeToName (requirementType_t type)
00954 {
00955     switch(type) {
00956     case RS_LINK_TECH:
00957         return "tech";
00958     case RS_LINK_TECH_NOT:
00959         return "tech (not)";
00960     case RS_LINK_ITEM:
00961         return "item";
00962     case RS_LINK_ALIEN:
00963         return "alien";
00964     case RS_LINK_ALIEN_DEAD:
00965         return "alien_dead";
00966     case RS_LINK_ALIEN_GLOBAL:
00967         return "alienglobal";
00968     case RS_LINK_UFO:
00969         return "ufo";
00970     case RS_LINK_ANTIMATTER:
00971         return "antimatter";
00972     default:
00973         return "unknown";
00974     }
00975 }
00976 
00981 static void RS_TechnologyList_f (void)
00982 {
00983     int i, j;
00984     technology_t *tech;
00985     requirements_t *reqs;
00986     dateLong_t date;
00987 
00988     Com_Printf("#techs: %i\n", ccs.numTechnologies);
00989     for (i = 0; i < ccs.numTechnologies; i++) {
00990         tech = RS_GetTechByIDX(i);
00991         Com_Printf("Tech: %s\n", tech->id);
00992         Com_Printf("... time      -> %.2f\n", tech->time);
00993         Com_Printf("... name      -> %s\n", tech->name);
00994         reqs = &tech->requireAND;
00995         Com_Printf("... requires ALL  ->");
00996         for (j = 0; j < reqs->numLinks; j++)
00997             Com_Printf(" %s (%s) %s", reqs->links[j].id, RS_TechLinkTypeToName(reqs->links[j].type), RS_TechReqToName(&reqs->links[j]));
00998         reqs = &tech->requireOR;
00999         Com_Printf("\n");
01000         Com_Printf("... requires ANY  ->");
01001         for (j = 0; j < reqs->numLinks; j++)
01002             Com_Printf(" %s (%s) %s", reqs->links[j].id, RS_TechLinkTypeToName(reqs->links[j].type), RS_TechReqToName(&reqs->links[j]));
01003         Com_Printf("\n");
01004         Com_Printf("... provides  -> %s", tech->provides);
01005         Com_Printf("\n");
01006 
01007         Com_Printf("... type      -> ");
01008         Com_Printf("%s\n", RS_TechTypeToName(tech->type));
01009 
01010         Com_Printf("... researchable -> %i\n", tech->statusResearchable);
01011 
01012         if (tech->statusResearchable) {
01013             CL_DateConvertLong(&tech->preResearchedDate, &date);
01014             Com_Printf("... researchable date: %02i %02i %i\n", date.day, date.month, date.year);
01015         }
01016 
01017         Com_Printf("... research  -> ");
01018         switch (tech->statusResearch) {
01019         case RS_NONE:
01020             Com_Printf("nothing\n");
01021             break;
01022         case RS_RUNNING:
01023             Com_Printf("running\n");
01024             break;
01025         case RS_PAUSED:
01026             Com_Printf("paused\n");
01027             break;
01028         case RS_FINISH:
01029             Com_Printf("done\n");
01030             CL_DateConvertLong(&tech->researchedDate, &date);
01031             Com_Printf("... research date: %02i %02i %i\n", date.day, date.month, date.year);
01032             break;
01033         default:
01034             Com_Printf("unknown\n");
01035             break;
01036         }
01037     }
01038 }
01039 
01045 static void RS_DebugMarkResearchedAll (void)
01046 {
01047     int i;
01048     technology_t *tech;
01049 
01050     for (i = 0; i < ccs.numTechnologies; i++) {
01051         tech = RS_GetTechByIDX(i);
01052         Com_DPrintf(DEBUG_CLIENT, "...mark %s as researched\n", tech->id);
01053         RS_MarkOneResearchable(tech);
01054         RS_ResearchFinish(tech);
01056     }
01057 }
01058 
01063 static void RS_DebugResearchAll_f (void)
01064 {
01065     if (Cmd_Argc() != 2) {
01066         RS_DebugMarkResearchedAll();
01067     } else {
01068         technology_t *tech = RS_GetTechByID(Cmd_Argv(1));
01069         if (!tech)
01070             return;
01071         Com_DPrintf(DEBUG_CLIENT, "...mark %s as researched\n", tech->id);
01072         RS_MarkOneResearchable(tech);
01073         RS_ResearchFinish(tech);
01074     }
01075 }
01076 
01081 static void RS_DebugResearchableAll_f (void)
01082 {
01083     int i;
01084 
01085     if (Cmd_Argc() != 2) {
01086         for (i = 0; i < ccs.numTechnologies; i++) {
01087             technology_t *tech = RS_GetTechByIDX(i);
01088             Com_Printf("...mark %s as researchable\n", tech->id);
01089             RS_MarkOneResearchable(tech);
01090             RS_MarkCollected(tech);
01091         }
01092     } else {
01093         technology_t *tech = RS_GetTechByID(Cmd_Argv(1));
01094         if (tech) {
01095             Com_Printf("...mark %s as researchable\n", tech->id);
01096             RS_MarkOneResearchable(tech);
01097             RS_MarkCollected(tech);
01098         }
01099     }
01100 }
01101 
01102 static void RS_DebugFinishResearches_f (void)
01103 {
01104     int i;
01105 
01106     for (i = 0; i < ccs.numTechnologies; i++) {
01107         technology_t *tech = RS_GetTechByIDX(i);
01108         if (tech->statusResearch == RS_RUNNING) {
01109             assert(tech->base);
01110             Com_DPrintf(DEBUG_CLIENT, "...mark %s as researched\n", tech->id);
01111             RS_MarkResearched(tech, tech->base);
01112         }
01113     }
01114 }
01115 #endif
01116 
01117 
01123 void RS_InitStartup (void)
01124 {
01125     /* add commands and cvars */
01126 #ifdef DEBUG
01127     Cmd_AddCommand("debug_listtech", RS_TechnologyList_f, "Print the current parsed technologies to the game console");
01128     Cmd_AddCommand("debug_researchall", RS_DebugResearchAll_f, "Mark all techs as researched");
01129     Cmd_AddCommand("debug_researchableall", RS_DebugResearchableAll_f, "Mark all techs as researchable");
01130     Cmd_AddCommand("debug_finishresearches", RS_DebugFinishResearches_f, "Mark all running researches as finished");
01131 #endif
01132 }
01133 
01137 void RS_ResetTechs (void)
01138 {
01139     /* they are static - but i'm paranoid - this is called before the techs were parsed */
01140     memset(techHash, 0, sizeof(techHash));
01141     memset(techHashProvided, 0, sizeof(techHashProvided));
01142 
01143     /* delete redirectedTechs, will be filled during parse */
01144     LIST_Delete(&redirectedTechs);
01145 }
01146 
01152 static const value_t valid_tech_vars[] = {
01153     {"name", V_TRANSLATION_STRING, offsetof(technology_t, name), 0},
01154     {"provides", V_CLIENT_HUNK_STRING, offsetof(technology_t, provides), 0},
01155     {"event", V_CLIENT_HUNK_STRING, offsetof(technology_t, finishedResearchEvent), 0},
01156     {"delay", V_INT, offsetof(technology_t, delay), MEMBER_SIZEOF(technology_t, delay)},
01157     {"producetime", V_INT, offsetof(technology_t, produceTime), MEMBER_SIZEOF(technology_t, produceTime)},
01158     {"time", V_FLOAT, offsetof(technology_t, time), MEMBER_SIZEOF(technology_t, time)},
01159     {"image", V_CLIENT_HUNK_STRING, offsetof(technology_t, image), 0},
01160     {"mdl", V_CLIENT_HUNK_STRING, offsetof(technology_t, mdl), 0},
01161 
01162     {NULL, 0, 0, 0}
01163 };
01164 
01168 static const value_t valid_techmail_vars[] = {
01169     {"from", V_TRANSLATION_STRING, offsetof(techMail_t, from), 0},
01170     {"to", V_TRANSLATION_STRING, offsetof(techMail_t, to), 0},
01171     {"subject", V_TRANSLATION_STRING, offsetof(techMail_t, subject), 0},
01172     {"date", V_TRANSLATION_STRING, offsetof(techMail_t, date), 0},
01173     {"icon", V_CLIENT_HUNK_STRING, offsetof(techMail_t, icon), 0},
01174     {"model", V_CLIENT_HUNK_STRING, offsetof(techMail_t, model), 0},
01175 
01176     {NULL, 0, 0, 0}
01177 };
01178 
01187 void RS_ParseTechnologies (const char *name, const char **text)
01188 {
01189     const value_t *vp;
01190     technology_t *tech;
01191     unsigned hash;
01192     const char *errhead = "RS_ParseTechnologies: unexpected end of file.";
01193     const char *token;
01194     requirements_t *requiredTemp;
01195     descriptions_t *descTemp;
01196     int i;
01197 
01198     for (i = 0; i < ccs.numTechnologies; i++) {
01199         if (!strcmp(ccs.technologies[i].id, name)) {
01200             Com_Printf("RS_ParseTechnologies: Second tech with same name found (%s) - second ignored\n", name);
01201             return;
01202         }
01203     }
01204 
01205     if (ccs.numTechnologies >= MAX_TECHNOLOGIES) {
01206         Com_Printf("RS_ParseTechnologies: too many technology entries. limit is %i.\n", MAX_TECHNOLOGIES);
01207         return;
01208     }
01209 
01210     /* get body */
01211     token = Com_Parse(text);
01212     if (!*text || *token != '{') {
01213         Com_Printf("RS_ParseTechnologies: \"%s\" technology def without body ignored.\n", name);
01214         return;
01215     }
01216 
01217     /* New technology (next free entry in global tech-list) */
01218     tech = &ccs.technologies[ccs.numTechnologies];
01219     ccs.numTechnologies++;
01220 
01221     memset(tech, 0, sizeof(*tech));
01222 
01223     /*
01224      * Set standard values
01225      */
01226     tech->idx = ccs.numTechnologies - 1;
01227     tech->id = Mem_PoolStrDup(name, cp_campaignPool, 0);
01228     hash = Com_HashKey(tech->id, TECH_HASH_SIZE);
01229 
01230     /* Set the default string for descriptions (available even if numDescriptions is 0) */
01231     tech->description.text[0] = _("No description available.");
01232     tech->preDescription.text[0] = _("No research proposal available.");
01233     /* Set desc-indices to undef. */
01234     tech->description.usedDescription = -1;
01235     tech->preDescription.usedDescription = -1;
01236 
01237     /* link the variable in */
01238     /* tech_hash should be null on the first run */
01239     tech->hashNext = techHash[hash];
01240     /* set the techHash pointer to the current tech */
01241     /* if there were already others in techHash at position hash, they are now
01242      * accessible via tech->next - loop until tech->next is null (the first tech
01243      * at that position)
01244      */
01245     techHash[hash] = tech;
01246 
01247     tech->type = RS_TECH;
01248     tech->statusResearch = RS_NONE;
01249     tech->statusResearchable = qfalse;
01250 
01251     do {
01252         /* get the name type */
01253         token = Com_EParse(text, errhead, name);
01254         if (!*text)
01255             break;
01256         if (*token == '}')
01257             break;
01258         /* get values */
01259         if (!strcmp(token, "type")) {
01260             /* what type of tech this is */
01261             token = Com_EParse(text, errhead, name);
01262             if (!*text)
01263                 return;
01265             /* redundant, but oh well. */
01266             if (!strcmp(token, "tech"))
01267                 tech->type = RS_TECH;
01268             else if (!strcmp(token, "weapon"))
01269                 tech->type = RS_WEAPON;
01270             else if (!strcmp(token, "news"))
01271                 tech->type = RS_NEWS;
01272             else if (!strcmp(token, "armour"))
01273                 tech->type = RS_ARMOUR;
01274             else if (!strcmp(token, "craft"))
01275                 tech->type = RS_CRAFT;
01276             else if (!strcmp(token, "craftitem"))
01277                 tech->type = RS_CRAFTITEM;
01278             else if (!strcmp(token, "building"))
01279                 tech->type = RS_BUILDING;
01280             else if (!strcmp(token, "alien"))
01281                 tech->type = RS_ALIEN;
01282             else if (!strcmp(token, "ugv"))
01283                 tech->type = RS_UGV;
01284             else if (!strcmp(token, "logic"))
01285                 tech->type = RS_LOGIC;
01286             else
01287                 Com_Printf("RS_ParseTechnologies: \"%s\" unknown techtype: \"%s\" - ignored.\n", name, token);
01288         } else {
01289             if (!strcmp(token, "description") || !strcmp(token, "pre_description")) {
01290                 /* Parse the available descriptions for this tech */
01291 
01292                 /* Link to correct list. */
01293                 if (!strcmp(token, "pre_description")) {
01294                     descTemp = &tech->preDescription;
01295                 } else {
01296                     descTemp = &tech->description;
01297                 }
01298 
01299                 token = Com_EParse(text, errhead, name);
01300                 if (!*text)
01301                     break;
01302                 if (*token != '{')
01303                     break;
01304                 if (*token == '}')
01305                     break;
01306 
01307                 do {    /* Loop through all descriptions in the list.*/
01308                     token = Com_EParse(text, errhead, name);
01309                     if (!*text)
01310                         return;
01311                     if (*token == '}')
01312                         break;
01313 
01314                     if (descTemp->numDescriptions < MAX_DESCRIPTIONS) {
01315                         /* Copy tech string into entry. */
01316                         descTemp->tech[descTemp->numDescriptions] = Mem_PoolStrDup(token, cp_campaignPool, 0);
01317 
01318                         /* Copy description text into the entry. */
01319                         token = Com_EParse(text, errhead, name);
01320                         /* skip translation marker */
01321                         if (*token == '_')
01322                             token++;
01323                         else
01324                             Com_Error(ERR_DROP, "RS_ParseTechnologies: '%s' No gettext string for description '%s'. Abort.\n", name, descTemp->tech[descTemp->numDescriptions]);
01325 
01326                         descTemp->text[descTemp->numDescriptions] = Mem_PoolStrDup(token, cp_campaignPool, 0);
01327                         descTemp->numDescriptions++;
01328                     }
01329                 } while (*text);
01330 
01331             } else if (!strcmp(token, "redirect")) {
01332                 token = Com_EParse(text, errhead, name);
01333                 /* Store this tech and the parsed tech-id of the target of the redirection for later linking. */
01334                 LIST_AddPointer(&redirectedTechs, tech);
01335                 LIST_AddString(&redirectedTechs, token);
01336             } else if (!strcmp(token, "require_AND") || !strcmp(token, "require_OR") || !strcmp(token, "require_for_production")) {
01337                 /* Link to correct list. */
01338                 if (!strcmp(token, "require_AND")) {
01339                     requiredTemp = &tech->requireAND;
01340                 } else if (!strcmp(token, "require_OR")) {
01341                     requiredTemp = &tech->requireOR;
01342                 } else { /* It's "requireForProduction" */
01343                     requiredTemp = &tech->requireForProduction;
01344                 }
01345 
01346                 token = Com_EParse(text, errhead, name);
01347                 if (!*text)
01348                     break;
01349                 if (*token != '{')
01350                     break;
01351                 if (*token == '}')
01352                     break;
01353 
01354                 do {    /* Loop through all 'require' entries.*/
01355                     token = Com_EParse(text, errhead, name);
01356                     if (!*text)
01357                         return;
01358                     if (*token == '}')
01359                         break;
01360 
01361                     if (!strcmp(token, "tech") || !strcmp(token, "tech_not")) {
01362                         if (requiredTemp->numLinks < MAX_TECHLINKS) {
01363                             /* Set requirement-type. */
01364                             if (!strcmp(token, "tech_not"))
01365                                 requiredTemp->links[requiredTemp->numLinks].type = RS_LINK_TECH_NOT;
01366                             else
01367                                 requiredTemp->links[requiredTemp->numLinks].type = RS_LINK_TECH;
01368 
01369                             /* Set requirement-name (id). */
01370                             token = Com_Parse(text);
01371                             requiredTemp->links[requiredTemp->numLinks].id = Mem_PoolStrDup(token, cp_campaignPool, 0);
01372 
01373                             Com_DPrintf(DEBUG_CLIENT, "RS_ParseTechnologies: require-tech ('tech' or 'tech_not')- %s\n", requiredTemp->links[requiredTemp->numLinks].id);
01374 
01375                             requiredTemp->numLinks++;
01376                         } else {
01377                             Com_Printf("RS_ParseTechnologies: \"%s\" Too many 'required' defined. Limit is %i - ignored.\n", name, MAX_TECHLINKS);
01378                         }
01379                     } else if (!strcmp(token, "item")) {
01380                         /* Defines what items need to be collected for this item to be researchable. */
01381                         if (requiredTemp->numLinks < MAX_TECHLINKS) {
01382                             /* Set requirement-type. */
01383                             requiredTemp->links[requiredTemp->numLinks].type = RS_LINK_ITEM;
01384                             /* Set requirement-name (id). */
01385                             token = Com_Parse(text);
01386                             requiredTemp->links[requiredTemp->numLinks].id = Mem_PoolStrDup(token, cp_campaignPool, 0);
01387                             /* Set requirement-amount of item. */
01388                             token = Com_Parse(text);
01389                             requiredTemp->links[requiredTemp->numLinks].amount = atoi(token);
01390                             Com_DPrintf(DEBUG_CLIENT, "RS_ParseTechnologies: require-item - %s - %i\n", requiredTemp->links[requiredTemp->numLinks].id, requiredTemp->links[requiredTemp->numLinks].amount);
01391                             requiredTemp->numLinks++;
01392                         } else {
01393                             Com_Printf("RS_ParseTechnologies: \"%s\" Too many 'required' defined. Limit is %i - ignored.\n", name, MAX_TECHLINKS);
01394                         }
01395                     } else if (!strcmp(token, "alienglobal")) {
01396                         if (requiredTemp->numLinks < MAX_TECHLINKS) {
01397                             /* Set requirement-type. */
01398                             requiredTemp->links[requiredTemp->numLinks].type = RS_LINK_ALIEN_GLOBAL;
01399                             Com_DPrintf(DEBUG_CLIENT, "RS_ParseTechnologies:  require-alienglobal - %i\n", requiredTemp->links[requiredTemp->numLinks].amount);
01400 
01401                             /* Set requirement-amount of item. */
01402                             token = Com_Parse(text);
01403                             requiredTemp->links[requiredTemp->numLinks].amount = atoi(token);
01404                             requiredTemp->numLinks++;
01405                         } else {
01406                             Com_Printf("RS_ParseTechnologies: \"%s\" Too many 'required' defined. Limit is %i - ignored.\n", name, MAX_TECHLINKS);
01407                         }
01408                     } else if (!strcmp(token, "alien_dead") || !strcmp(token, "alien")) { /* Does this only check the beginning of the string? */
01409                         /* Defines what live or dead aliens need to be collected for this item to be researchable. */
01410                         if (requiredTemp->numLinks < MAX_TECHLINKS) {
01411                             /* Set requirement-type. */
01412                             if (!strcmp(token, "alien_dead")) {
01413                                 requiredTemp->links[requiredTemp->numLinks].type = RS_LINK_ALIEN_DEAD;
01414                                 Com_DPrintf(DEBUG_CLIENT, "RS_ParseTechnologies:  require-alien dead - %s - %i\n", requiredTemp->links[requiredTemp->numLinks].id, requiredTemp->links[requiredTemp->numLinks].amount);
01415                             } else {
01416                                 requiredTemp->links[requiredTemp->numLinks].type = RS_LINK_ALIEN;
01417                                 Com_DPrintf(DEBUG_CLIENT, "RS_ParseTechnologies:  require-alien alive - %s - %i\n", requiredTemp->links[requiredTemp->numLinks].id, requiredTemp->links[requiredTemp->numLinks].amount);
01418                             }
01419                             /* Set requirement-name (id). */
01420                             token = Com_Parse(text);
01421                             requiredTemp->links[requiredTemp->numLinks].id = Mem_PoolStrDup(token, cp_campaignPool, 0);
01422                             /* Set requirement-amount of item. */
01423                             token = Com_Parse(text);
01424                             requiredTemp->links[requiredTemp->numLinks].amount = atoi(token);
01425                             requiredTemp->numLinks++;
01426                         } else {
01427                             Com_Printf("RS_ParseTechnologies: \"%s\" Too many 'required' defined. Limit is %i - ignored.\n", name, MAX_TECHLINKS);
01428                         }
01429                     } else if (!strcmp(token, "ufo")) {
01430                         /* Defines what ufos need to be collected for this item to be researchable. */
01431                         if (requiredTemp->numLinks < MAX_TECHLINKS) {
01432                             /* Set requirement-type. */
01433                             requiredTemp->links[requiredTemp->numLinks].type = RS_LINK_UFO;
01434                             /* Set requirement-name (id). */
01435                             token = Com_Parse(text);
01436                             requiredTemp->links[requiredTemp->numLinks].id = Mem_PoolStrDup(token, cp_campaignPool, 0);
01437                             /* Set requirement-amount of item. */
01438                             token = Com_Parse(text);
01439                             requiredTemp->links[requiredTemp->numLinks].amount = atoi(token);
01440                             Com_DPrintf(DEBUG_CLIENT, "RS_ParseTechnologies: require-ufo - %s - %i\n", requiredTemp->links[requiredTemp->numLinks].id, requiredTemp->links[requiredTemp->numLinks].amount);
01441                             requiredTemp->numLinks++;
01442                         }
01443                     } else if (!strcmp(token, "antimatter")) {
01444                         /* Defines what ufos need to be collected for this item to be researchable. */
01445                         if (requiredTemp->numLinks < MAX_TECHLINKS) {
01446                             /* Set requirement-type. */
01447                             requiredTemp->links[requiredTemp->numLinks].type = RS_LINK_ANTIMATTER;
01448                             /* Set requirement-amount of item. */
01449                             token = Com_Parse(text);
01450                             requiredTemp->links[requiredTemp->numLinks].amount = atoi(token);
01451                             Com_DPrintf(DEBUG_CLIENT, "RS_ParseTechnologies: require-antimatter - %i\n", requiredTemp->links[requiredTemp->numLinks].amount);
01452                             requiredTemp->numLinks++;
01453                         }
01454                     } else {
01455                         Com_Printf("RS_ParseTechnologies: \"%s\" unknown requirement-type: \"%s\" - ignored.\n", name, token);
01456                     }
01457                 } while (*text);
01458             } else if (!strcmp(token, "up_chapter")) {
01459                 /* UFOpaedia chapter */
01460                 token = Com_EParse(text, errhead, name);
01461                 if (!*text)
01462                     return;
01463 
01464                 if (*token) {
01465                     /* find chapter */
01466                     for (i = 0; i < ccs.numChapters; i++) {
01467                         if (!strcmp(token, ccs.upChapters[i].id)) {
01468                             /* add entry to chapter */
01469                             tech->upChapter = &ccs.upChapters[i];
01470                             if (!ccs.upChapters[i].first) {
01471                                 ccs.upChapters[i].first = tech;
01472                                 ccs.upChapters[i].last = tech;
01473                                 tech->upPrev = NULL;
01474                                 tech->upNext = NULL;
01475                             } else {
01476                                 /* get "last entry" in chapter */
01477                                 technology_t *techOld = ccs.upChapters[i].last;
01478                                 ccs.upChapters[i].last = tech;
01479                                 techOld->upNext = tech;
01480                                 ccs.upChapters[i].last->upPrev = techOld;
01481                                 ccs.upChapters[i].last->upNext = NULL;
01482                             }
01483                             break;
01484                         }
01485                         if (i == ccs.numChapters)
01486                             Com_Printf("RS_ParseTechnologies: \"%s\" - chapter \"%s\" not found.\n", name, token);
01487                     }
01488                 }
01489             } else if (!strcmp(token, "mail") || !strcmp(token, "mail_pre")) {
01490                 techMail_t* mail;
01491 
01492                 /* how many mails found for this technology
01493                  * used in UFOpaedia to check which article to display */
01494                 tech->numTechMails++;
01495 
01496                 if (tech->numTechMails > TECHMAIL_MAX)
01497                     Com_Printf("RS_ParseTechnologies: more techmail-entries found than supported. \"%s\"\n",  name);
01498 
01499                 if (!strcmp(token, "mail_pre")) {
01500                     mail = &tech->mail[TECHMAIL_PRE];
01501                 } else {
01502                     mail = &tech->mail[TECHMAIL_RESEARCHED];
01503                 }
01504                 token = Com_EParse(text, errhead, name);
01505                 if (!*text || *token != '{')
01506                     return;
01507 
01508                 /* grab the initial mail entry */
01509                 token = Com_EParse(text, errhead, name);
01510                 if (!*text || *token == '}')
01511                     return;
01512                 do {
01513                     for (vp = valid_techmail_vars; vp->string; vp++)
01514                         if (!strcmp(token, vp->string)) {
01515                             /* found a definition */
01516                             token = Com_EParse(text, errhead, name);
01517                             if (!*text)
01518                                 return;
01519 
01520                             switch (vp->type) {
01521                             case V_TRANSLATION_STRING:
01522                                 token++;    
01523                             case V_CLIENT_HUNK_STRING:
01524                                 Mem_PoolStrDupTo(token, (char**) ((char*)mail + (int)vp->ofs), cp_campaignPool, 0);
01525                                 break;
01526                             case V_NULL:
01527                                 Com_Printf("RS_ParseTechnologies Error: - no buffer for technologies - V_NULL not allowed (token: '%s') entry: '%s'\n", token, name);
01528                                 break;
01529                             default:
01530                                 Com_EParseValue(mail, token, vp->type, vp->ofs, vp->size);
01531                             }
01532                             break;
01533                         }
01534                     /* grab the next entry */
01535                     token = Com_EParse(text, errhead, name);
01536                     if (!*text)
01537                         return;
01538                 } while (*text && *token != '}');
01539                 /* default model is navarre */
01540                 if (mail->model == NULL)
01541                     mail->model = "characters/navarre";
01542             } else {
01543                 for (vp = valid_tech_vars; vp->string; vp++)
01544                     if (!strcmp(token, vp->string)) {
01545                         /* found a definition */
01546                         token = Com_EParse(text, errhead, name);
01547                         if (!*text)
01548                             return;
01549 
01550                         if (!vp->ofs)
01551                             break;
01552                         switch (vp->type) {
01553                         case V_TRANSLATION_STRING:
01554                             if (*token == '_')
01555                                 token++;
01556                         case V_CLIENT_HUNK_STRING:
01557                             Mem_PoolStrDupTo(token, (char**) ((char*)tech + (int)vp->ofs), cp_campaignPool, 0);
01558                             break;
01559                         case V_NULL:
01560                             Com_Printf("RS_ParseTechnologies Error: - no buffer for technologies - V_NULL not allowed (token: '%s') entry: '%s'\n", token, name);
01561                             break;
01562                         default:
01563                             Com_EParseValue(tech, token, vp->type, vp->ofs, vp->size);
01564                         }
01565                         break;
01566                     }
01568                 if (!vp->string)
01569                     Com_Printf("RS_ParseTechnologies: unknown token \"%s\" ignored (entry %s)\n", token, name);
01570             }
01571         }
01572     } while (*text);
01573 
01574     if (tech->provides) {
01575         hash = Com_HashKey(tech->provides, TECH_HASH_SIZE);
01576         /* link the variable in */
01577         /* techHashProvided should be null on the first run */
01578         tech->hashProvidedNext = techHashProvided[hash];
01579         /* set the techHashProvided pointer to the current tech */
01580         /* if there were already others in techHashProvided at position hash, they are now
01581         * accessable via tech->next - loop until tech->next is null (the first tech
01582         * at that position)
01583         */
01584         techHashProvided[hash] = tech;
01585     } else {
01586         Com_DPrintf(DEBUG_CLIENT, "tech '%s' doesn't have a provides string\n", tech->id);
01587     }
01588 
01589     /* set the overall reseach time to the one given in the ufo-file. */
01590     tech->overallTime = tech->time;
01591 }
01592 
01593 static inline qboolean RS_IsValidTechIndex (int techIdx)
01594 {
01595     if (techIdx == TECH_INVALID)
01596         return qfalse;
01597     if (techIdx < 0 || techIdx >= ccs.numTechnologies)
01598         return qfalse;
01599     if (techIdx >= MAX_TECHNOLOGIES)
01600         return qfalse;
01601 
01602     return qtrue;
01603 }
01604 
01611 qboolean RS_IsResearched_idx (int techIdx)
01612 {
01613     if (!RS_IsValidTechIndex(techIdx))
01614         return qfalse;
01615 
01616     if (ccs.technologies[techIdx].statusResearch == RS_FINISH)
01617         return qtrue;
01618 
01619     return qfalse;
01620 }
01621 
01627 qboolean RS_IsResearched_ptr (const technology_t * tech)
01628 {
01629     if (tech && tech->statusResearch == RS_FINISH)
01630         return qtrue;
01631     return qfalse;
01632 }
01633 
01638 int RS_Collected_ (const technology_t * tech)
01639 {
01640     if (tech)
01641         return tech->statusCollected;
01642 
01643     Com_DPrintf(DEBUG_CLIENT, "RS_Collected_: NULL technology given.\n");
01644     return -1;
01645 }
01646 
01653 technology_t* RS_GetTechByIDX (int techIdx)
01654 {
01655     if (!RS_IsValidTechIndex(techIdx))
01656         return NULL;
01657     else
01658         return &ccs.technologies[techIdx];
01659 }
01660 
01661 
01667 technology_t *RS_GetTechByID (const char *id)
01668 {
01669     unsigned hash;
01670     technology_t *tech;
01671 
01672     if (!id || id[0] == '\0')
01673         return NULL;
01674 
01675     hash = Com_HashKey(id, TECH_HASH_SIZE);
01676     for (tech = techHash[hash]; tech; tech = tech->hashNext)
01677         if (!Q_strcasecmp(id, tech->id))
01678             return tech;
01679 
01680     Com_Printf("RS_GetTechByID: Could not find a technology with id \"%s\"\n", id);
01681     return NULL;
01682 }
01683 
01689 technology_t *RS_GetTechByProvided (const char *idProvided)
01690 {
01691     unsigned hash;
01692     technology_t *tech;
01693 
01694     if (!idProvided)
01695         return NULL;
01696     /* catch empty strings */
01697     if (idProvided[0] == '\0')
01698         return NULL;
01699 
01700     hash = Com_HashKey(idProvided, TECH_HASH_SIZE);
01701     for (tech = techHashProvided[hash]; tech; tech = tech->hashProvidedNext)
01702         if (!Q_strcasecmp(idProvided, tech->provides))
01703             return tech;
01704 
01705     Com_DPrintf(DEBUG_CLIENT, "RS_GetTechByProvided: %s\n", idProvided);
01706     /* if a building, probably needs another building */
01707     /* if not a building, catch NULL where function is called! */
01708     return NULL;
01709 }
01710 
01716 technology_t *RS_GetTechWithMostScientists (const struct base_s *base)
01717 {
01718     technology_t *tech;
01719     int i, max;
01720 
01721     if (!base)
01722         return NULL;
01723 
01724     tech = NULL;
01725     max = 0;
01726     for (i = 0; i < ccs.numTechnologies; i++) {
01727         technology_t *tech_temp = RS_GetTechByIDX(i);
01728         if (tech_temp->statusResearch == RS_RUNNING && tech_temp->base == base) {
01729             if (tech_temp->scientists > max) {
01730                 tech = tech_temp;
01731                 max = tech->scientists;
01732             }
01733         }
01734     }
01735 
01736     /* this tech has at least one assigned scientist or is a NULL pointer */
01737     return tech;
01738 }
01739 
01744 int RS_GetTechIdxByName (const char *name)
01745 {
01746     technology_t *tech;
01747     const unsigned hash = Com_HashKey(name, TECH_HASH_SIZE);
01748 
01749     for (tech = techHash[hash]; tech; tech = tech->hashNext)
01750         if (!Q_strcasecmp(name, tech->id))
01751             return tech->idx;
01752 
01753     Com_Printf("RS_GetTechIdxByName: Could not find tech '%s'\n", name);
01754     return TECH_INVALID;
01755 }
01756 
01763 int RS_CountScientistsInBase (const base_t *base)
01764 {
01765     int i, counter = 0;
01766 
01767     for (i = 0; i < ccs.numTechnologies; i++) {
01768         const technology_t *tech = &ccs.technologies[i];
01769         if (tech->base == base) {
01770             /* Get a free lab from the base. */
01771             counter += tech->scientists;
01772         }
01773     }
01774 
01775     return counter;
01776 }
01777 
01782 void RS_RemoveScientistsExceedingCapacity (base_t *base)
01783 {
01784     assert(base);
01785 
01786     /* Make sure base->capacities[CAP_LABSPACE].cur is set to proper value */
01787     base->capacities[CAP_LABSPACE].cur = RS_CountScientistsInBase(base);
01788 
01789     while (base->capacities[CAP_LABSPACE].cur > base->capacities[CAP_LABSPACE].max) {
01790         technology_t *tech = RS_GetTechWithMostScientists(base);
01791         RS_RemoveScientist(tech, NULL);
01792     }
01793 }
01794 
01800 qboolean RS_SaveXML (mxml_node_t *parent)
01801 {
01802     int i;
01803     mxml_node_t *node;
01804 
01805     Com_RegisterConstList(saveResearchConstants);
01806     node = mxml_AddNode(parent, SAVE_RESEARCH_RESEARCH);
01807     for (i = 0; i < ccs.numTechnologies; i++) {
01808         int j;
01809         const technology_t *t = RS_GetTechByIDX(i);
01810 
01811         mxml_node_t * snode = mxml_AddNode(node, SAVE_RESEARCH_TECH);
01812         mxml_AddString(snode, SAVE_RESEARCH_ID, t->id);
01813         mxml_AddBoolValue(snode, SAVE_RESEARCH_STATUSCOLLECTED, t->statusCollected);
01814         mxml_AddFloatValue(snode, SAVE_RESEARCH_TIME, t->time);
01815         mxml_AddString(snode, SAVE_RESEARCH_STATUSRESEARCH, Com_GetConstVariable(SAVE_RESEARCHSTATUS_NAMESPACE, t->statusResearch));
01816         if (t->base)
01817             mxml_AddInt(snode, SAVE_RESEARCH_BASE, t->base->idx);
01818         mxml_AddIntValue(snode, SAVE_RESEARCH_SCIENTISTS, t->scientists);
01819         mxml_AddInt(snode, SAVE_RESEARCH_STATUSRESEARCHABLE, t->statusResearchable);
01820         mxml_AddDate(snode, SAVE_RESEARCH_PREDATE, t->preResearchedDate.day, t->preResearchedDate.sec);
01821         mxml_AddDate(snode, SAVE_RESEARCH_DATE, t->researchedDate.day, t->researchedDate.sec);
01822         mxml_AddInt(snode, SAVE_RESEARCH_MAILSENT, t->mailSent);
01823 
01824         /* save which techMails were read */
01826         for (j = 0; j < TECHMAIL_MAX; j++) {
01827             if (t->mail[j].read) {
01828                 mxml_node_t * ssnode = mxml_AddNode(snode, SAVE_RESEARCH_MAIL);
01829                 mxml_AddInt(ssnode, SAVE_RESEARCH_MAIL_ID, j);
01830             }
01831         }
01832     }
01833     Com_UnregisterConstList(saveResearchConstants);
01834 
01835     return qtrue;
01836 }
01837 
01843 qboolean RS_LoadXML (mxml_node_t *parent)
01844 {
01845     mxml_node_t *topnode;
01846     mxml_node_t *snode;
01847     qboolean success = qtrue;
01848 
01849     topnode = mxml_GetNode(parent, SAVE_RESEARCH_RESEARCH);
01850     if (!topnode)
01851         return qfalse;
01852 
01853     Com_RegisterConstList(saveResearchConstants);
01854     for (snode = mxml_GetNode(topnode, SAVE_RESEARCH_TECH); snode; snode = mxml_GetNextNode(snode, topnode, "tech")) {
01855         const char *techString = mxml_GetString(snode, SAVE_RESEARCH_ID);
01856         mxml_node_t * ssnode;
01857         int baseIdx;
01858         technology_t *t = RS_GetTechByID(techString);
01859         const char *type = mxml_GetString(snode, SAVE_RESEARCH_STATUSRESEARCH);
01860 
01861         if (!t) {
01862             Com_Printf("......your game doesn't know anything about tech '%s'\n", techString);
01863             continue;
01864         }
01865 
01866         if (!Com_GetConstIntFromNamespace(SAVE_RESEARCHSTATUS_NAMESPACE, type, (int*) &t->statusResearch)) {
01867             Com_Printf("Invaild research status '%s'\n", type);
01868             success = qfalse;
01869             break;
01870         }
01871 
01872         t->statusCollected = mxml_GetBool(snode, SAVE_RESEARCH_STATUSCOLLECTED, qfalse);
01873         t->time = mxml_GetFloat(snode, SAVE_RESEARCH_TIME, 0.0);
01874         /* Prepare base-index for later pointer-restoration in RS_PostLoadInit. */
01875         baseIdx = mxml_GetInt(snode, SAVE_RESEARCH_BASE, -1);
01876         if (baseIdx >= 0)
01877             /* even if the base is not yet loaded we can set the pointer already */
01878             t->base = B_GetBaseByIDX(baseIdx);
01879         t->scientists = mxml_GetInt(snode, SAVE_RESEARCH_SCIENTISTS, 0);
01880         t->statusResearchable = mxml_GetInt(snode, SAVE_RESEARCH_STATUSRESEARCHABLE, 0);
01881         mxml_GetDate(snode, SAVE_RESEARCH_PREDATE, &t->preResearchedDate.day, &t->preResearchedDate.sec);
01882         mxml_GetDate(snode, SAVE_RESEARCH_DATE, &t->researchedDate.day, &t->researchedDate.sec);
01883         t->mailSent = mxml_GetInt(snode, SAVE_RESEARCH_MAILSENT, 0);
01884 
01885         /* load which techMails were read */
01887         for (ssnode = mxml_GetNode(snode, SAVE_RESEARCH_MAIL); ssnode; ssnode = mxml_GetNextNode(ssnode, snode, SAVE_RESEARCH_MAIL)) {
01888             const int j= mxml_GetInt(ssnode, SAVE_RESEARCH_MAIL_ID, TECHMAIL_MAX);
01889             if (j < TECHMAIL_MAX) {
01890                 const techMailType_t mailType = j;
01891                 t->mail[mailType].read = qtrue;
01892             } else
01893                 Com_Printf("......your save game contains unknown techmail ids... \n");
01894         }
01895 
01896 #ifdef DEBUG
01897         if (t->statusResearch == RS_RUNNING && t->scientists > 0) {
01898             if (!t->base) {
01899                 Com_Printf("No base but research is running and scientists are assigned");
01900                 success = qfalse;
01901                 break;
01902             }
01903         }
01904 #endif
01905     }
01906     Com_UnregisterConstList(saveResearchConstants);
01907 
01908     return success;
01909 }
01910 
01916 qboolean RS_ResearchAllowed (const base_t* base)
01917 {
01918     assert(base);
01919     if (base->baseStatus != BASE_UNDER_ATTACK && B_GetBuildingStatus(base, B_LAB)
01920      && E_CountHired(base, EMPL_SCIENTIST) > 0) {
01921         return qtrue;
01922     } else {
01923         return qfalse;
01924     }
01925 }
01926 
01931 qboolean RS_ScriptSanityCheck (void)
01932 {
01933     int i, error = 0;
01934     technology_t *t;
01935 
01936     for (i = 0, t = ccs.technologies; i < ccs.numTechnologies; i++, t++) {
01937         if (!t->name) {
01938             error++;
01939             Com_Printf("...... technology '%s' has no name\n", t->id);
01940         }
01941         if (!t->provides) {
01942             switch (t->type) {
01943             case RS_TECH:
01944             case RS_NEWS:
01945             case RS_LOGIC:
01946             case RS_ALIEN:
01947                 break;
01948             default:
01949                 error++;
01950                 Com_Printf("...... technology '%s' doesn't provide anything\n", t->id);
01951             }
01952         }
01953 
01954         if (t->produceTime == 0) {
01955             switch (t->type) {
01956             case RS_TECH:
01957             case RS_NEWS:
01958             case RS_LOGIC:
01959             case RS_BUILDING:
01960             case RS_ALIEN:
01961                 break;
01962             default:
01964                 Com_Printf("...... technology '%s' has zero (0) produceTime, is this on purpose?\n", t->id);
01965             }
01966         }
01967 
01968         if (t->type != RS_LOGIC && (!t->description.text[0] || t->description.text[0][0] == '_')) {
01969             if (!t->description.text[0])
01970                 Com_Printf("...... technology '%s' has a strange 'description' value '%s'.\n", t->id, t->description.text[0]);
01971             else
01972                 Com_Printf("...... technology '%s' has no 'description' value.\n", t->id);
01973         }
01974     }
01975 
01976     if (!error)
01977         return qtrue;
01978     else
01979         return qfalse;
01980 }

Generated by  doxygen 1.6.2