00001
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028 #include "../cl_shared.h"
00029 #include "../cl_inventory.h"
00030 #include "../ui/ui_main.h"
00031 #include "../ui/ui_icon.h"
00032 #include "../../shared/parse.h"
00033 #include "cp_campaign.h"
00034 #include "cp_mapfightequip.h"
00035 #include "cp_time.h"
00036
00037 static cvar_t *mn_uppretext;
00038 static cvar_t *mn_uppreavailable;
00039
00040 static pediaChapter_t *upChaptersDisplayList[MAX_PEDIACHAPTERS];
00041 static int numChaptersDisplayList;
00042
00043 static technology_t *upCurrentTech;
00044 static pediaChapter_t *currentChapter;
00045
00046 #define MAX_UPTEXT 4096
00047 static char upBuffer[MAX_UPTEXT];
00048
00049 #define MAIL_LENGTH 256
00050 #define MAIL_BUFFER_SIZE 0x4000
00051 static char mailBuffer[MAIL_BUFFER_SIZE];
00052 #define CHECK_MAIL_EOL if (tempBuf[MAIL_LENGTH-3] != '\n') tempBuf[MAIL_LENGTH-2] = '\n';
00053 #define MAIL_CLIENT_LINES 15
00054
00059 enum {
00060 UFOPEDIA_CHAPTERS,
00061 UFOPEDIA_INDEX,
00062 UFOPEDIA_ARTICLE,
00063
00064 UFOPEDIA_DISPLAYEND
00065 };
00066 static int upDisplay = UFOPEDIA_CHAPTERS;
00067
00075 static qboolean UP_TechGetsDisplayed (const technology_t *tech)
00076 {
00077 const objDef_t *item;
00078
00079 assert(tech);
00080
00081 item = INVSH_GetItemByIDSilent(tech->provides);
00082 if (item && item->isVirtual)
00083 return qfalse;
00084
00085
00086 return (RS_IsResearched_ptr(tech) || RS_Collected_(tech)
00087 || (tech->statusResearchable && tech->preDescription.numDescriptions > 0))
00088 && tech->type != RS_LOGIC && !tech->redirect;
00089 }
00090
00095 static void UP_ChangeDisplay (int newDisplay)
00096 {
00097 if (newDisplay < UFOPEDIA_DISPLAYEND && newDisplay >= 0)
00098 upDisplay = newDisplay;
00099 else
00100 Com_Printf("Error in UP_ChangeDisplay (%i)\n", newDisplay);
00101
00102 Cvar_SetValue("mn_uppreavailable", 0);
00103
00104
00105 UI_ResetData(TEXT_UFOPEDIA_MAILHEADER);
00106 UI_ResetData(TEXT_UFOPEDIA_MAIL);
00107 UI_ResetData(TEXT_UFOPEDIA_REQUIREMENT);
00108 UI_ResetData(TEXT_ITEMDESCRIPTION);
00109 UI_ResetData(TEXT_UFOPEDIA);
00110
00111 switch (upDisplay) {
00112 case UFOPEDIA_CHAPTERS:
00113 currentChapter = NULL;
00114 upCurrentTech = NULL;
00115 Cvar_Set("mn_upmodel_top", "");
00116 Cvar_Set("mn_upmodel_bottom", "");
00117 Cvar_Set("mn_upimage_top", "base/empty");
00118 UI_ExecuteConfunc("mn_up_empty");
00119 Cvar_Set("mn_uptitle", _("UFOpaedia"));
00120 break;
00121 case UFOPEDIA_INDEX:
00122 Cvar_Set("mn_upmodel_top", "");
00123 Cvar_Set("mn_upmodel_bottom", "");
00124 Cvar_Set("mn_upimage_top", "base/empty");
00125
00126 case UFOPEDIA_ARTICLE:
00127 UI_ExecuteConfunc("mn_up_article");
00128 break;
00129 }
00130 Cvar_SetValue("mn_updisplay", upDisplay);
00131 }
00132
00138 static const char* CL_AircraftStatToName (int stat)
00139 {
00140 switch (stat) {
00141 case AIR_STATS_SPEED:
00142 return _("Cruising speed");
00143 case AIR_STATS_MAXSPEED:
00144 return _("Maximum speed");
00145 case AIR_STATS_SHIELD:
00146 return _("Armour");
00147 case AIR_STATS_ECM:
00148 return _("ECM");
00149 case AIR_STATS_DAMAGE:
00150 return _("Aircraft damage");
00151 case AIR_STATS_ACCURACY:
00152 return _("Accuracy");
00153 case AIR_STATS_FUELSIZE:
00154 return _("Fuel size");
00155 case AIR_STATS_WRANGE:
00156 return _("Weapon range");
00157 default:
00158 return _("Unknown weapon skill");
00159 }
00160 }
00161
00166 static const char* CL_AircraftSizeToName (int aircraftSize)
00167 {
00168 switch (aircraftSize) {
00169 case AIRCRAFT_SMALL:
00170 return _("Small");
00171 case AIRCRAFT_LARGE:
00172 return _("Large");
00173 default:
00174 return _("Unknown aircraft size");
00175 }
00176 }
00177
00184 static void UP_DisplayTechTree (const technology_t* t)
00185 {
00186 linkedList_t *upTechtree;
00187 const requirements_t *required;
00188
00189 required = &t->requireAND;
00190 upTechtree = NULL;
00191
00192 if (required->numLinks <= 0)
00193 LIST_AddString(&upTechtree, _("No requirements"));
00194 else {
00195 int i;
00196 for (i = 0; i < required->numLinks; i++) {
00197 const requirement_t *req = &required->links[i];
00198 if (req->type == RS_LINK_TECH) {
00199 technology_t *techRequired = req->link;
00200 if (!techRequired)
00201 Com_Error(ERR_DROP, "Could not find the tech for '%s'", req->id);
00202
00205 if (!UP_TechGetsDisplayed(techRequired))
00206 continue;
00207
00208 LIST_AddString(&upTechtree, _(techRequired->name));
00209 }
00210 }
00211 }
00212
00213
00214 Cvar_Set("mn_uprequirement", "1");
00215 UI_RegisterLinkedListText(TEXT_UFOPEDIA_REQUIREMENT, upTechtree);
00216 }
00217
00222 static void UP_BuildingDescription (const technology_t* t)
00223 {
00224 const building_t* b = B_GetBuildingTemplate(t->provides);
00225
00226 if (!b) {
00227 Com_sprintf(upBuffer, sizeof(upBuffer), _("Error - could not find building"));
00228 } else {
00229 Com_sprintf(upBuffer, sizeof(upBuffer), _("Needs:\t%s\n"), b->dependsBuilding ? _(b->dependsBuilding->name) : _("None"));
00230 Q_strcat(upBuffer, va(ngettext("Construction time:\t%i day\n", "Construction time:\t%i days\n", b->buildTime), b->buildTime), sizeof(upBuffer));
00231 Q_strcat(upBuffer, va(_("Cost:\t%i c\n"), b->fixCosts), sizeof(upBuffer));
00232 Q_strcat(upBuffer, va(_("Running costs:\t%i c\n"), b->varCosts), sizeof(upBuffer));
00233 }
00234
00235 Cvar_Set("mn_upmetadata", "1");
00236 UI_RegisterText(TEXT_ITEMDESCRIPTION, upBuffer);
00237 UP_DisplayTechTree(t);
00238 }
00239
00248 void UP_AircraftItemDescription (const objDef_t *item)
00249 {
00250 static char itemText[1024];
00251 const technology_t *tech;
00252
00253
00254 INV_ItemDescription(NULL);
00255 *itemText = '\0';
00256
00257
00258 if (!item) {
00259 Cvar_Set("mn_item", "");
00260 Cvar_Set("mn_itemname", "");
00261 Cvar_Set("mn_upmodel_top", "");
00262 UI_ResetData(TEXT_ITEMDESCRIPTION);
00263 return;
00264 }
00265
00266 tech = RS_GetTechForItem(item);
00267
00268 assert(item->craftitem.type >= 0);
00269 Cvar_Set("mn_item", item->id);
00270 Cvar_Set("mn_itemname", _(tech->name));
00271 if (tech->mdl)
00272 Cvar_Set("mn_upmodel_top", tech->mdl);
00273 else
00274 Cvar_Set("mn_upmodel_top", "");
00275
00276
00277 if (RS_IsResearched_ptr(tech)) {
00278 int i;
00279 if (item->craftitem.type == AC_ITEM_WEAPON)
00280 Q_strcat(itemText, va(_("Weight:\t%s\n"), AII_WeightToName(AII_GetItemWeightBySize(item))), sizeof(itemText));
00281 else if (item->craftitem.type == AC_ITEM_AMMO) {
00282
00283 Q_strcat(itemText, va(_("Ammo:\t%i\n"), item->ammo), sizeof(itemText));
00284 if (!equal(item->craftitem.weaponDamage, 0))
00285 Q_strcat(itemText, va(_("Damage:\t%i\n"), (int) item->craftitem.weaponDamage), sizeof(itemText));
00286 Q_strcat(itemText, va(_("Reloading time:\t%i\n"), (int) item->craftitem.weaponDelay), sizeof(itemText));
00287 } else if (item->craftitem.type < AC_ITEM_WEAPON) {
00288 Q_strcat(itemText, _("Weapon for base defence system\n"), sizeof(itemText));
00289 }
00290
00291 if (!equal(item->craftitem.stats[AIR_STATS_WRANGE], 0))
00292 Q_strcat(itemText, va("%s:\t%i\n", CL_AircraftStatToName(AIR_STATS_WRANGE),
00293 CL_AircraftMenuStatsValues(item->craftitem.stats[AIR_STATS_WRANGE], AIR_STATS_WRANGE)), sizeof(itemText));
00294
00295
00296 for (i = 0; i < AIR_STATS_MAX; i++) {
00297 const char *statsName = CL_AircraftStatToName(i);
00298 if (i == AIR_STATS_WRANGE)
00299 continue;
00300 if (item->craftitem.stats[i] > 2.0f)
00301 Q_strcat(itemText, va("%s:\t+%i\n", statsName, CL_AircraftMenuStatsValues(item->craftitem.stats[i], i)), sizeof(itemText));
00302 else if (item->craftitem.stats[i] < -2.0f)
00303 Q_strcat(itemText, va("%s:\t%i\n", statsName, CL_AircraftMenuStatsValues(item->craftitem.stats[i], i)), sizeof(itemText));
00304 else if (item->craftitem.stats[i] > 1.0f)
00305 Q_strcat(itemText, va(_("%s:\t+%i %%\n"), statsName, (int)(item->craftitem.stats[i] * 100) - 100), sizeof(itemText));
00306 else if (!equal(item->craftitem.stats[i], 0))
00307 Q_strcat(itemText, va(_("%s:\t%i %%\n"), statsName, (int)(item->craftitem.stats[i] * 100) - 100), sizeof(itemText));
00308 }
00309 } else {
00310 Q_strcat(itemText, _("Unknown - need to research this"), sizeof(itemText));
00311 }
00312
00313 Cvar_Set("mn_upmetadata", "1");
00314 UI_RegisterText(TEXT_ITEMDESCRIPTION, itemText);
00315 }
00316
00323 void UP_AircraftDescription (const technology_t* tech)
00324 {
00325 INV_ItemDescription(NULL);
00326
00327
00328 upBuffer[0] = '\0';
00329
00330 if (RS_IsResearched_ptr(tech)) {
00331 const aircraft_t* aircraft = AIR_GetAircraft(tech->provides);
00332 int i;
00333 for (i = 0; i < AIR_STATS_MAX; i++) {
00334 switch (i) {
00335 case AIR_STATS_SPEED:
00336
00337 Q_strcat(upBuffer, va(_("%s:\t%i km/h\n"), CL_AircraftStatToName(i),
00338 CL_AircraftMenuStatsValues(aircraft->stats[i], i)), sizeof(upBuffer));
00339 break;
00340 case AIR_STATS_MAXSPEED:
00341
00342 Q_strcat(upBuffer, va(_("%s:\t%i km/h\n"), CL_AircraftStatToName(i),
00343 CL_AircraftMenuStatsValues(aircraft->stats[i], i)), sizeof(upBuffer));
00344 break;
00345 case AIR_STATS_FUELSIZE:
00346 Q_strcat(upBuffer, va(_("Operational range:\t%i km\n"),
00347 AIR_GetOperationRange(aircraft)), sizeof(upBuffer));
00348 case AIR_STATS_ACCURACY:
00349 Q_strcat(upBuffer, va(_("%s:\t%i\n"), CL_AircraftStatToName(i),
00350 CL_AircraftMenuStatsValues(aircraft->stats[i], i)), sizeof(upBuffer));
00351 break;
00352 default:
00353 break;
00354 }
00355 }
00356 Q_strcat(upBuffer, va(_("Aircraft size:\t%s\n"), CL_AircraftSizeToName(aircraft->size)), sizeof(upBuffer));
00357
00358
00359 if (!AIR_IsUFO(aircraft))
00360 Q_strcat(upBuffer, va(_("Max. soldiers:\t%i\n"), aircraft->maxTeamSize), sizeof(upBuffer));
00361 } else if (RS_Collected_(tech)) {
00363 Com_sprintf(upBuffer, sizeof(upBuffer), _("Unknown - need to research this"));
00364 } else {
00365 Com_sprintf(upBuffer, sizeof(upBuffer), _("Unknown - need to research this"));
00366 }
00367
00368 Cvar_Set("mn_upmetadata", "1");
00369 UI_RegisterText(TEXT_ITEMDESCRIPTION, upBuffer);
00370 UP_DisplayTechTree(tech);
00371 }
00372
00379 void UP_UGVDescription (const ugv_t *ugvType)
00380 {
00381 static char itemText[512];
00382 const technology_t *tech;
00383
00384 assert(ugvType);
00385
00386 tech = RS_GetTechByProvided(ugvType->id);
00387 assert(tech);
00388
00389 INV_ItemDescription(NULL);
00390
00391
00392 Cvar_Set("mn_itemname", _(tech->name));
00393 Cvar_Set("mn_item", tech->provides);
00394
00395 Cvar_Set("mn_upmetadata", "1");
00396 if (RS_IsResearched_ptr(tech)) {
00398 Com_sprintf(itemText, sizeof(itemText), _("%s\n%s"), _(tech->name), ugvType->weapon);
00399 } else if (RS_Collected_(tech)) {
00401 Com_sprintf(itemText, sizeof(itemText), _("Unknown - need to research this"));
00402 } else {
00403 Com_sprintf(itemText, sizeof(itemText), _("Unknown - need to research this"));
00404 }
00405 UI_RegisterText(TEXT_ITEMDESCRIPTION, itemText);
00406 }
00407
00414 int UP_GetUnreadMails (void)
00415 {
00416 const message_t *m = cp_messageStack;
00417
00418 if (ccs.numUnreadMails != -1)
00419 return ccs.numUnreadMails;
00420
00421 ccs.numUnreadMails = 0;
00422
00423 while (m) {
00424 switch (m->type) {
00425 case MSG_RESEARCH_PROPOSAL:
00426 assert(m->pedia);
00427 if (m->pedia->mail[TECHMAIL_PRE].from && !m->pedia->mail[TECHMAIL_PRE].read)
00428 ccs.numUnreadMails++;
00429 break;
00430 case MSG_RESEARCH_FINISHED:
00431 assert(m->pedia);
00432 if (m->pedia->mail[TECHMAIL_RESEARCHED].from && RS_IsResearched_ptr(m->pedia) && !m->pedia->mail[TECHMAIL_RESEARCHED].read)
00433 ccs.numUnreadMails++;
00434 break;
00435 case MSG_NEWS:
00436 assert(m->pedia);
00437 if (m->pedia->mail[TECHMAIL_PRE].from && !m->pedia->mail[TECHMAIL_PRE].read)
00438 ccs.numUnreadMails++;
00439 if (m->pedia->mail[TECHMAIL_RESEARCHED].from && !m->pedia->mail[TECHMAIL_RESEARCHED].read)
00440 ccs.numUnreadMails++;
00441 break;
00442 case MSG_EVENT:
00443 assert(m->eventMail);
00444 if (!m->eventMail->read)
00445 ccs.numUnreadMails++;
00446 break;
00447 default:
00448 break;
00449 }
00450 m = m->next;
00451 }
00452
00453
00454 Cvar_Set("mn_upunreadmail", va("%i", ccs.numUnreadMails));
00455 return ccs.numUnreadMails;
00456 }
00457
00468 static void UP_SetMailHeader (technology_t* tech, techMailType_t type, eventMail_t* mail)
00469 {
00470 static char mailHeader[8 * MAX_VAR] = "";
00471 char dateBuf[MAX_VAR] = "";
00472 const char *subjectType = "";
00473 const char *from, *to, *subject, *model;
00474 dateLong_t date;
00475
00476 if (mail) {
00477 from = mail->from;
00478 to = mail->to;
00479 model = mail->model;
00480 subject = mail->subject;
00481 Q_strncpyz(dateBuf, _(mail->date), sizeof(dateBuf));
00482 mail->read = qtrue;
00483
00484 ccs.numUnreadMails = -1;
00485 } else {
00486 assert(tech);
00487 assert(type < TECHMAIL_MAX);
00488
00489 from = tech->mail[type].from;
00490 to = tech->mail[type].to;
00491 subject = tech->mail[type].subject;
00492 model = tech->mail[type].model;
00493
00494 if (tech->mail[type].date) {
00495 Q_strncpyz(dateBuf, _(tech->mail[type].date), sizeof(dateBuf));
00496 } else {
00497 switch (type) {
00498 case TECHMAIL_PRE:
00499 CL_DateConvertLong(&tech->preResearchedDate, &date);
00500 Com_sprintf(dateBuf, sizeof(dateBuf), _("%i %s %02i"),
00501 date.year, Date_GetMonthName(date.month - 1), date.day);
00502 break;
00503 case TECHMAIL_RESEARCHED:
00504 CL_DateConvertLong(&tech->researchedDate, &date);
00505 Com_sprintf(dateBuf, sizeof(dateBuf), _("%i %s %02i"),
00506 date.year, Date_GetMonthName(date.month - 1), date.day);
00507 break;
00508 default:
00509 Com_Error(ERR_DROP, "UP_SetMailHeader: unhandled techMailType_t %i for date.", type);
00510 }
00511 }
00512 if (from != NULL) {
00513 if (!tech->mail[type].read) {
00514 tech->mail[type].read = qtrue;
00515
00516 ccs.numUnreadMails = -1;
00517 }
00518
00519 if (tech->numTechMails == TECHMAIL_MAX) {
00520 switch (type) {
00521 case TECHMAIL_PRE:
00522 subjectType = _("Proposal: ");
00523 break;
00524 case TECHMAIL_RESEARCHED:
00525 subjectType = _("Re: ");
00526 break;
00527 default:
00528 Com_Error(ERR_DROP, "UP_SetMailHeader: unhandled techMailType_t %i for subject.", type);
00529 }
00530 }
00531 } else {
00532 UI_ResetData(TEXT_UFOPEDIA_MAILHEADER);
00533 return;
00534 }
00535 }
00536 Com_sprintf(mailHeader, sizeof(mailHeader), _("FROM: %s\nTO: %s\nDATE: %s"),
00537 _(from), _(to), dateBuf);
00538 Cvar_Set("mn_mail_sender_head", model ? model : "");
00539 Cvar_Set("mn_mail_from", _(from));
00540 Cvar_Set("mn_mail_subject", va("%s%s", subjectType, _(subject)));
00541 Cvar_Set("mn_mail_to", _(to));
00542 Cvar_Set("mn_mail_date", dateBuf);
00543 UI_RegisterText(TEXT_UFOPEDIA_MAILHEADER, mailHeader);
00544 }
00545
00551 static void UP_DrawAssociatedAmmo (const technology_t* tech)
00552 {
00553 const objDef_t *od = INVSH_GetItemByID(tech->provides);
00554
00555 if (od->numAmmos > 0) {
00556 const technology_t *associated = RS_GetTechForItem(od->ammos[0]);
00557 Cvar_Set("mn_upmodel_bottom", associated->mdl);
00558 }
00559 }
00560
00567 static void UP_Article (technology_t* tech, eventMail_t *mail)
00568 {
00569 UP_ChangeDisplay(UFOPEDIA_ARTICLE);
00570
00571 if (tech) {
00572 if (tech->mdl)
00573 Cvar_Set("mn_upmodel_top", tech->mdl);
00574 else
00575 Cvar_Set("mn_upmodel_top", "");
00576
00577 if (tech->image)
00578 Cvar_Set("mn_upimage_top", tech->image);
00579 else
00580 Cvar_Set("mn_upimage_top", "");
00581
00582 Cvar_Set("mn_upmodel_bottom", "");
00583
00584 if (tech->type == RS_WEAPON)
00585 UP_DrawAssociatedAmmo(tech);
00586 Cvar_Set("mn_uprequirement", "");
00587 Cvar_Set("mn_upmetadata", "");
00588 }
00589
00590 UI_ResetData(TEXT_UFOPEDIA);
00591 UI_ResetData(TEXT_UFOPEDIA_REQUIREMENT);
00592
00593 if (mail) {
00594
00595 Cvar_SetValue("mn_uppreavailable", 0);
00596 Cvar_SetValue("mn_updisplay", UFOPEDIA_CHAPTERS);
00597 UP_SetMailHeader(NULL, 0, mail);
00598 UI_RegisterText(TEXT_UFOPEDIA, _(mail->body));
00599
00600
00601 upDisplay = UFOPEDIA_INDEX;
00602 } else if (tech) {
00603 currentChapter = tech->upChapter;
00604 upCurrentTech = tech;
00605
00606
00607 UI_ExecuteConfunc("itemdesc_view 0 0;");
00608 if (RS_IsResearched_ptr(tech)) {
00609 int i;
00610 Cvar_Set("mn_uptitle", va("%s: %s %s", _("UFOpaedia"), _(tech->name), _("(complete)")));
00611
00612 UI_RegisterText(TEXT_UFOPEDIA, _(RS_GetDescription(&tech->description)));
00613 if (tech->preDescription.numDescriptions > 0) {
00614
00615 if (mn_uppretext->integer) {
00616 UI_RegisterText(TEXT_UFOPEDIA, _(RS_GetDescription(&tech->preDescription)));
00617 UP_SetMailHeader(tech, TECHMAIL_PRE, NULL);
00618 } else {
00619 UP_SetMailHeader(tech, TECHMAIL_RESEARCHED, NULL);
00620 }
00621 Cvar_SetValue("mn_uppreavailable", 1);
00622 } else {
00623
00624 Cvar_SetValue("mn_uppreavailable", 0);
00625 Cvar_SetValue("mn_updisplay", UFOPEDIA_CHAPTERS);
00626 UP_SetMailHeader(tech, TECHMAIL_RESEARCHED, NULL);
00627 }
00628
00629 switch (tech->type) {
00630 case RS_ARMOUR:
00631 case RS_WEAPON:
00632 for (i = 0; i < csi.numODs; i++) {
00633 const objDef_t *od = INVSH_GetItemByIDX(i);
00634 if (!strcmp(tech->provides, od->id)) {
00635 INV_ItemDescription(od);
00636 UP_DisplayTechTree(tech);
00637 Cvar_Set("mn_upmetadata", "1");
00638 break;
00639 }
00640 }
00641 break;
00642 case RS_TECH:
00643 UP_DisplayTechTree(tech);
00644 break;
00645 case RS_CRAFT:
00646 UP_AircraftDescription(tech);
00647 break;
00648 case RS_CRAFTITEM:
00649 UP_AircraftItemDescription(INVSH_GetItemByID(tech->provides));
00650 break;
00651 case RS_BUILDING:
00652 UP_BuildingDescription(tech);
00653 break;
00654 case RS_UGV:
00655 UP_UGVDescription(Com_GetUGVByIDSilent(tech->provides));
00656 break;
00657 default:
00658 break;
00659 }
00660
00661 } else if (RS_Collected_(tech) || (tech->statusResearchable && tech->preDescription.numDescriptions > 0)) {
00662
00663 Cvar_Set("mn_uptitle", va("%s: %s", _("UFOpaedia"), _(tech->name)));
00664
00665 if (tech->preDescription.numDescriptions > 0) {
00666 UI_RegisterText(TEXT_UFOPEDIA, _(RS_GetDescription(&tech->preDescription)));
00667 UP_SetMailHeader(tech, TECHMAIL_PRE, NULL);
00668 } else {
00669 UI_RegisterText(TEXT_UFOPEDIA, _("No pre-research description available."));
00670 }
00671 } else {
00672 Cvar_Set("mn_uptitle", va("%s: %s", _("UFOpaedia"), _(tech->name)));
00673 UI_ResetData(TEXT_UFOPEDIA);
00674 }
00675 } else {
00676 Com_Error(ERR_DROP, "UP_Article: No mail or tech given");
00677 }
00678 }
00679
00683 void UP_OpenEventMail (const char *eventMailID)
00684 {
00685 eventMail_t* mail;
00686 mail = CL_GetEventMail(eventMailID, qfalse);
00687 if (!mail)
00688 return;
00689
00690 UI_PushWindow("mail", NULL);
00691 UP_Article(NULL, mail);
00692 }
00693
00699 static void UP_OpenMailWith (const char *techID)
00700 {
00701 if (!techID)
00702 return;
00703
00704 UI_PushWindow("mail", NULL);
00705 Cbuf_AddText(va("ufopedia %s\n", techID));
00706 }
00707
00713 void UP_OpenWith (const char *techID)
00714 {
00715 if (!techID)
00716 return;
00717
00718 UI_PushWindow("ufopedia", NULL);
00719 Cbuf_AddText(va("ufopedia %s; update_ufopedia_layout;\n", techID));
00720 }
00721
00727 void UP_OpenCopyWith (const char *techID)
00728 {
00729 Cmd_ExecuteString("mn_push ufopedia");
00730 Cbuf_AddText(va("ufopedia %s\n", techID));
00731 }
00732
00733
00737 static void UP_FindEntry_f (void)
00738 {
00739 const char *id;
00740 technology_t *tech;
00741
00742 if (Cmd_Argc() < 2) {
00743 Com_Printf("Usage: %s <id>\n", Cmd_Argv(0));
00744 return;
00745 }
00746
00747
00748 id = Cmd_Argv(1);
00749
00750
00751 if (id[0] == '\0') {
00752 Com_Printf("UP_FindEntry_f: No UFOpaedia entry given as parameter\n");
00753 return;
00754 }
00755
00756 tech = RS_GetTechByID(id);
00757 if (!tech) {
00758 Com_DPrintf(DEBUG_CLIENT, "UP_FindEntry_f: No UFOpaedia entry found for %s\n", id);
00759 return;
00760 }
00761
00762 if (tech->redirect)
00763 tech = tech->redirect;
00764
00765 UP_Article(tech, NULL);
00766 }
00767
00773 static uiNode_t* UP_GenerateArticlesSummary (pediaChapter_t *parentChapter)
00774 {
00775 technology_t *tech = parentChapter->first;
00776 uiNode_t* first = NULL;
00777
00778 while (tech) {
00779 if (UP_TechGetsDisplayed(tech)) {
00780 const char* id = va("@%i", tech->idx);
00781 UI_AddOption(&first, id, va("_%s", tech->name), id);
00782 }
00783 tech = tech->upNext;
00784 }
00785
00786 UI_SortOptions(&first);
00787
00788 return first;
00789 }
00790
00795 static void UP_GenerateSummary (void)
00796 {
00797 int i;
00798 uiNode_t *chapters = NULL;
00799 int num = 0;
00800
00801 numChaptersDisplayList = 0;
00802
00803 for (i = 0; i < ccs.numChapters; i++) {
00804
00805 uiNode_t *chapter;
00806 qboolean researchedEntries = qfalse;
00807 upCurrentTech = ccs.upChapters[i].first;
00808 do {
00809 if (UP_TechGetsDisplayed(upCurrentTech)) {
00810 researchedEntries = qtrue;
00811 break;
00812 }
00813 upCurrentTech = upCurrentTech->upNext;
00814 } while (upCurrentTech);
00815
00816
00817 if (researchedEntries) {
00818 if (numChaptersDisplayList >= MAX_PEDIACHAPTERS)
00819 Com_Error(ERR_DROP, "MAX_PEDIACHAPTERS hit");
00820 upChaptersDisplayList[numChaptersDisplayList++] = &ccs.upChapters[i];
00821
00822
00823 chapter = UI_AddOption(&chapters, ccs.upChapters[i].id, va("_%s", ccs.upChapters[i].name), va("%i", num));
00824 OPTIONEXTRADATA(chapter).icon = UI_GetIconByName(va("ufopedia_%s", ccs.upChapters[i].id));
00825 chapter->firstChild = UP_GenerateArticlesSummary(&ccs.upChapters[i]);
00826
00827 num++;
00828 }
00829 }
00830
00831 UI_RegisterOption(OPTION_UFOPEDIA, chapters);
00832 Cvar_Set("mn_uptitle", _("UFOpaedia"));
00833 }
00834
00838 static void UP_Content_f (void)
00839 {
00840 UP_GenerateSummary();
00841 UP_ChangeDisplay(UFOPEDIA_CHAPTERS);
00842 }
00843
00849 static void UP_Click_f (void)
00850 {
00851 if (Cmd_Argc() < 2)
00852 return;
00853
00854
00855 if (Cmd_Argv(1)[0] == '@') {
00856 const int techId = atoi(Cmd_Argv(1) + 1);
00857 technology_t* tech;
00858 assert(techId >= 0);
00859 assert(techId < ccs.numTechnologies);
00860 tech = &ccs.technologies[techId];
00861 if (tech)
00862 UP_Article(tech, NULL);
00863 return;
00864 } else {
00865
00866 UI_ExecuteConfunc("itemdesc_view 0 0;");
00867 }
00868
00869
00870 UP_ChangeDisplay(UFOPEDIA_CHAPTERS);
00871 }
00872
00876 static void UP_TechTreeClick_f (void)
00877 {
00878 int num;
00879 int i;
00880 requirements_t *required_AND;
00881 technology_t *techRequired;
00882
00883 if (Cmd_Argc() < 2)
00884 return;
00885 num = atoi(Cmd_Argv(1));
00886
00887 if (!upCurrentTech)
00888 return;
00889
00890 required_AND = &upCurrentTech->requireAND;
00891 if (num < 0 || num >= required_AND->numLinks)
00892 return;
00893
00894
00895 for (i = 0; i <= num; i++) {
00896 if (required_AND->links[i].type != RS_LINK_TECH
00897 && required_AND->links[i].type != RS_LINK_TECH_NOT)
00898 num++;
00899 }
00900
00901 techRequired = required_AND->links[num].link;
00902 if (!techRequired)
00903 Com_Error(ERR_DROP, "Could not find the tech for '%s'", required_AND->links[num].id);
00904
00905
00906 if (!techRequired->upChapter)
00907 return;
00908
00909 UP_OpenWith(techRequired->id);
00910 }
00911
00915 static void UP_Update_f (void)
00916 {
00917 if (upCurrentTech)
00918 UP_Article(upCurrentTech, NULL);
00919 }
00920
00925 static void UP_MailClientClick_f (void)
00926 {
00927 message_t *m = cp_messageStack;
00928 int num;
00929 int cnt = -1;
00930
00931 if (Cmd_Argc() < 2)
00932 return;
00933
00934 num = atoi(Cmd_Argv(1));
00935
00936 while (m) {
00937 switch (m->type) {
00938 case MSG_RESEARCH_PROPOSAL:
00939 if (!m->pedia->mail[TECHMAIL_PRE].from)
00940 break;
00941 cnt++;
00942 if (cnt == num) {
00943 Cvar_SetValue("mn_uppretext", 1);
00944 UP_OpenMailWith(m->pedia->id);
00945 return;
00946 }
00947 break;
00948 case MSG_RESEARCH_FINISHED:
00949 if (!m->pedia->mail[TECHMAIL_RESEARCHED].from)
00950 break;
00951 cnt++;
00952 if (cnt == num) {
00953 Cvar_SetValue("mn_uppretext", 0);
00954 UP_OpenMailWith(m->pedia->id);
00955 return;
00956 }
00957 break;
00958 case MSG_NEWS:
00959 if (m->pedia->mail[TECHMAIL_PRE].from || m->pedia->mail[TECHMAIL_RESEARCHED].from) {
00960 cnt++;
00961 if (cnt >= num) {
00962 UP_OpenMailWith(m->pedia->id);
00963 return;
00964 }
00965 }
00966 break;
00967 case MSG_EVENT:
00968 cnt++;
00969 if (cnt >= num) {
00970 UP_OpenEventMail(m->eventMail->id);
00971 return;
00972 }
00973 break;
00974 default:
00975 break;
00976 }
00977 m = m->next;
00978 }
00979 }
00980
00984 static void UP_ResearchedLinkClick_f (void)
00985 {
00986 objDef_t *od;
00987
00988 if (!upCurrentTech)
00989 return;
00990
00991 od = INVSH_GetItemByID(upCurrentTech->provides);
00992 assert(od);
00993
00994 if (INV_IsAmmo(od)) {
00995 const technology_t *t = RS_GetTechForItem(od->weapons[0]);
00996 if (UP_TechGetsDisplayed(t))
00997 UP_OpenWith(t->id);
00998 } else if (od->weapon && od->reload) {
00999 const technology_t *t = RS_GetTechForItem(od->ammos[0]);
01000 if (UP_TechGetsDisplayed(t))
01001 UP_OpenWith(t->id);
01002 }
01003 }
01004
01011 static void UP_SetMailButtons_f (void)
01012 {
01013 int i = 0, num;
01014 const message_t *m = cp_messageStack;
01015
01016 if (Cmd_Argc() != 2) {
01017 Com_Printf("Usage: %s <pos>\n", Cmd_Argv(0));
01018 return;
01019 }
01020
01021 num = atoi(Cmd_Argv(1));
01022
01023 while (m && (i < MAIL_CLIENT_LINES)) {
01024 switch (m->type) {
01025 case MSG_RESEARCH_PROPOSAL:
01026 if (!m->pedia->mail[TECHMAIL_PRE].from)
01027 break;
01028 if (num) {
01029 num--;
01030 } else {
01031 Cvar_Set(va("mn_mail_read%i", i), m->pedia->mail[TECHMAIL_PRE].read ? "1": "0");
01032 Cvar_Set(va("mn_mail_icon%i", i++), m->pedia->mail[TECHMAIL_PRE].icon);
01033 }
01034 break;
01035 case MSG_RESEARCH_FINISHED:
01036 if (!m->pedia->mail[TECHMAIL_RESEARCHED].from)
01037 break;
01038 if (num) {
01039 num--;
01040 } else {
01041 Cvar_Set(va("mn_mail_read%i", i), m->pedia->mail[TECHMAIL_RESEARCHED].read ? "1": "0");
01042 Cvar_Set(va("mn_mail_icon%i", i++), m->pedia->mail[TECHMAIL_RESEARCHED].icon);
01043 }
01044 break;
01045 case MSG_NEWS:
01046 if (m->pedia->mail[TECHMAIL_PRE].from) {
01047 if (num) {
01048 num--;
01049 } else {
01050 Cvar_Set(va("mn_mail_read%i", i), m->pedia->mail[TECHMAIL_PRE].read ? "1": "0");
01051 Cvar_Set(va("mn_mail_icon%i", i++), m->pedia->mail[TECHMAIL_PRE].icon);
01052 }
01053 } else if (m->pedia->mail[TECHMAIL_RESEARCHED].from) {
01054 if (num) {
01055 num--;
01056 } else {
01057 Cvar_Set(va("mn_mail_read%i", i), m->pedia->mail[TECHMAIL_RESEARCHED].read ? "1": "0");
01058 Cvar_Set(va("mn_mail_icon%i", i++), m->pedia->mail[TECHMAIL_RESEARCHED].icon);
01059 }
01060 }
01061 break;
01062 case MSG_EVENT:
01063 if (m->eventMail->from) {
01064 if (num) {
01065 num--;
01066 } else {
01067 Cvar_Set(va("mn_mail_read%i", i), m->eventMail->read ? "1": "0");
01068 Cvar_Set(va("mn_mail_icon%i", i++), m->eventMail->icon ? m->eventMail->icon : "");
01069 }
01070 }
01071 break;
01072 default:
01073 break;
01074 }
01075 m = m->next;
01076 }
01077 while (i < MAIL_CLIENT_LINES) {
01078 Cvar_Set(va("mn_mail_read%i", i), "-1");
01079 Cvar_Set(va("mn_mail_icon%i", i++), "");
01080 }
01081 }
01082
01090 static void UP_OpenMail_f (void)
01091 {
01092 char tempBuf[MAIL_LENGTH] = "";
01093 const message_t *m = cp_messageStack;
01094 dateLong_t date;
01095
01096 mailBuffer[0] = '\0';
01097
01098 while (m) {
01099 switch (m->type) {
01100 case MSG_RESEARCH_PROPOSAL:
01101 if (!m->pedia->mail[TECHMAIL_PRE].from)
01102 break;
01103 CL_DateConvertLong(&m->pedia->preResearchedDate, &date);
01104 if (!m->pedia->mail[TECHMAIL_PRE].read)
01105 Com_sprintf(tempBuf, sizeof(tempBuf), _("^BProposal: %s\t%i %s %02i\n"),
01106 _(m->pedia->mail[TECHMAIL_PRE].subject),
01107 date.year, Date_GetMonthName(date.month - 1), date.day);
01108 else
01109 Com_sprintf(tempBuf, sizeof(tempBuf), _("Proposal: %s\t%i %s %02i\n"),
01110 _(m->pedia->mail[TECHMAIL_PRE].subject),
01111 date.year, Date_GetMonthName(date.month - 1), date.day);
01112 CHECK_MAIL_EOL
01113 Q_strcat(mailBuffer, tempBuf, sizeof(mailBuffer));
01114 break;
01115 case MSG_RESEARCH_FINISHED:
01116 if (!m->pedia->mail[TECHMAIL_RESEARCHED].from)
01117 break;
01118 CL_DateConvertLong(&m->pedia->researchedDate, &date);
01119 if (!m->pedia->mail[TECHMAIL_RESEARCHED].read)
01120 Com_sprintf(tempBuf, sizeof(tempBuf), _("^BRe: %s\t%i %s %02i\n"),
01121 _(m->pedia->mail[TECHMAIL_RESEARCHED].subject),
01122 date.year, Date_GetMonthName(date.month - 1), date.day);
01123 else
01124 Com_sprintf(tempBuf, sizeof(tempBuf), _("Re: %s\t%i %s %02i\n"),
01125 _(m->pedia->mail[TECHMAIL_RESEARCHED].subject),
01126 date.year, Date_GetMonthName(date.month - 1), date.day);
01127 CHECK_MAIL_EOL
01128 Q_strcat(mailBuffer, tempBuf, sizeof(mailBuffer));
01129 break;
01130 case MSG_NEWS:
01131 if (m->pedia->mail[TECHMAIL_PRE].from) {
01132 CL_DateConvertLong(&m->pedia->preResearchedDate, &date);
01133 if (!m->pedia->mail[TECHMAIL_PRE].read)
01134 Com_sprintf(tempBuf, sizeof(tempBuf), _("^B%s\t%i %s %02i\n"),
01135 _(m->pedia->mail[TECHMAIL_PRE].subject),
01136 date.year, Date_GetMonthName(date.month - 1), date.day);
01137 else
01138 Com_sprintf(tempBuf, sizeof(tempBuf), _("%s\t%i %s %02i\n"),
01139 _(m->pedia->mail[TECHMAIL_PRE].subject),
01140 date.year, Date_GetMonthName(date.month - 1), date.day);
01141 CHECK_MAIL_EOL
01142 Q_strcat(mailBuffer, tempBuf, sizeof(mailBuffer));
01143 } else if (m->pedia->mail[TECHMAIL_RESEARCHED].from) {
01144 CL_DateConvertLong(&m->pedia->researchedDate, &date);
01145 if (!m->pedia->mail[TECHMAIL_RESEARCHED].read)
01146 Com_sprintf(tempBuf, sizeof(tempBuf), _("^B%s\t%i %s %02i\n"),
01147 _(m->pedia->mail[TECHMAIL_RESEARCHED].subject),
01148 date.year, Date_GetMonthName(date.month - 1), date.day);
01149 else
01150 Com_sprintf(tempBuf, sizeof(tempBuf), _("%s\t%i %s %02i\n"),
01151 _(m->pedia->mail[TECHMAIL_RESEARCHED].subject),
01152 date.year, Date_GetMonthName(date.month - 1), date.day);
01153 CHECK_MAIL_EOL
01154 Q_strcat(mailBuffer, tempBuf, sizeof(mailBuffer));
01155 }
01156 break;
01157 case MSG_EVENT:
01158 assert(m->eventMail);
01159 if (!m->eventMail->from)
01160 break;
01161 if (!m->eventMail->read)
01162 Com_sprintf(tempBuf, sizeof(tempBuf), _("^B%s\t%s\n"),
01163 _(m->eventMail->subject), _(m->eventMail->date));
01164 else
01165 Com_sprintf(tempBuf, sizeof(tempBuf), _("%s\t%s\n"),
01166 _(m->eventMail->subject), _(m->eventMail->date));
01167 CHECK_MAIL_EOL
01168 Q_strcat(mailBuffer, tempBuf, sizeof(mailBuffer));
01169 break;
01170 default:
01171 break;
01172 }
01173 m = m->next;
01174 }
01175 UI_RegisterText(TEXT_UFOPEDIA_MAIL, mailBuffer);
01176
01177 UP_SetMailButtons_f();
01178 }
01179
01183 static void UP_SetAllMailsRead_f (void)
01184 {
01185 const message_t *m = cp_messageStack;
01186
01187 while (m) {
01188 switch (m->type) {
01189 case MSG_RESEARCH_PROPOSAL:
01190 assert(m->pedia);
01191 m->pedia->mail[TECHMAIL_PRE].read = qtrue;
01192 break;
01193 case MSG_RESEARCH_FINISHED:
01194 assert(m->pedia);
01195 m->pedia->mail[TECHMAIL_RESEARCHED].read = qtrue;
01196 break;
01197 case MSG_NEWS:
01198 assert(m->pedia);
01199 m->pedia->mail[TECHMAIL_PRE].read = qtrue;
01200 m->pedia->mail[TECHMAIL_RESEARCHED].read = qtrue;
01201 break;
01202 case MSG_EVENT:
01203 assert(m->eventMail);
01204 m->eventMail->read = qtrue;
01205 break;
01206 default:
01207 break;
01208 }
01209 m = m->next;
01210 }
01211
01212 ccs.numUnreadMails = 0;
01213 Cvar_Set("mn_upunreadmail", va("%i", ccs.numUnreadMails));
01214 UP_OpenMail_f();
01215 }
01216
01220 void UP_InitStartup (void)
01221 {
01222
01223 Cmd_AddCommand("mn_upcontent", UP_Content_f, "Shows the UFOpaedia chapters");
01224 Cmd_AddCommand("mn_upupdate", UP_Update_f, "Redraw the current UFOpaedia article");
01225 Cmd_AddCommand("ufopedia", UP_FindEntry_f, "Open the UFOpaedia with the given article");
01226 Cmd_AddCommand("ufopedia_click", UP_Click_f, NULL);
01227 Cmd_AddCommand("mailclient_click", UP_MailClientClick_f, NULL);
01228 Cmd_AddCommand("mn_mail_readall", UP_SetAllMailsRead_f, "Mark all mails read");
01229 Cmd_AddCommand("ufopedia_openmail", UP_OpenMail_f, "Start the mailclient");
01230 Cmd_AddCommand("ufopedia_scrollmail", UP_SetMailButtons_f, NULL);
01231 Cmd_AddCommand("techtree_click", UP_TechTreeClick_f, NULL);
01232 Cmd_AddCommand("mn_upgotoresearchedlink", UP_ResearchedLinkClick_f, NULL);
01233
01234 mn_uppretext = Cvar_Get("mn_uppretext", "0", 0, "Show the pre-research text in the UFOpaedia");
01235 mn_uppreavailable = Cvar_Get("mn_uppreavailable", "0", 0, "True if there is a pre-research text available");
01236 Cvar_Set("mn_uprequirement", "");
01237 Cvar_Set("mn_upmetadata", "");
01238 }
01239
01243 void UP_Shutdown (void)
01244 {
01245
01246 Cmd_RemoveCommand("mn_upcontent");
01247 Cmd_RemoveCommand("mn_upupdate");
01248 Cmd_RemoveCommand("ufopedia");
01249 Cmd_RemoveCommand("ufopedia_click");
01250 Cmd_RemoveCommand("mailclient_click");
01251 Cmd_RemoveCommand("mn_mail_readall");
01252 Cmd_RemoveCommand("ufopedia_openmail");
01253 Cmd_RemoveCommand("ufopedia_scrollmail");
01254 Cmd_RemoveCommand("techtree_click");
01255 Cmd_RemoveCommand("mn_upgotoresearchedlink");
01256
01257 Cvar_Delete("mn_uppretext");
01258 Cvar_Delete("mn_uppreavailable");
01259 Cvar_Delete("mn_uprequirement");
01260 Cvar_Delete("mn_upmetadata");
01261 }
01262
01269 void UP_ParseChapters (const char *name, const char **text)
01270 {
01271 const char *errhead = "UP_ParseChapters: unexpected end of file (names ";
01272 const char *token;
01273
01274
01275 token = Com_Parse(text);
01276
01277 if (!*text || *token !='{') {
01278 Com_Printf("UP_ParseChapters: chapter def \"%s\" without body ignored\n", name);
01279 return;
01280 }
01281
01282 do {
01283
01284 token = Com_EParse(text, errhead, name);
01285 if (!*text)
01286 break;
01287 if (*token == '}')
01288 break;
01289
01290
01291 if (ccs.numChapters >= MAX_PEDIACHAPTERS) {
01292 Com_Printf("UP_ParseChapters: too many chapter defs\n");
01293 return;
01294 }
01295 memset(&ccs.upChapters[ccs.numChapters], 0, sizeof(ccs.upChapters[ccs.numChapters]));
01296 ccs.upChapters[ccs.numChapters].id = Mem_PoolStrDup(token, cp_campaignPool, 0);
01297 ccs.upChapters[ccs.numChapters].idx = ccs.numChapters;
01298
01299
01300 token = Com_EParse(text, errhead, name);
01301 if (!*text)
01302 break;
01303 if (*token == '}')
01304 break;
01305 if (*token == '_')
01306 token++;
01307 if (!*token)
01308 continue;
01309 ccs.upChapters[ccs.numChapters].name = Mem_PoolStrDup(token, cp_campaignPool, 0);
01310
01311 ccs.numChapters++;
01312 } while (*text);
01313 }