/** * @brief Returns formatted text of a message timestamp * @param[in] text Buffer to hold the final result * @param[in] message The message to convert into text * @param[in] textsize The maximum length for text */ static void MS_TimestampedText (char* text, uiMessageListNodeMessage_t* message, size_t textsize) { dateLong_t date; CP_DateConvertLong(&message->date, &date); Com_sprintf(text, textsize, _("%i %s %02i, %02i:%02i: "), date.year, Date_GetMonthName(date.month - 1), date.day, date.hour, date.min); }
/** * @brief Updates date/time and timescale (=timelapse) on the geoscape menu * @sa SAV_GameLoad * @sa CP_CampaignRun */ void CP_UpdateTime (void) { dateLong_t date; CP_DateConvertLong(&ccs.date, &date); /* Update the timelapse text */ if (ccs.gameLapse >= 0 && ccs.gameLapse < NUM_TIMELAPSE) { cgi->Cvar_Set("mn_timelapse", "%s", _(lapse[ccs.gameLapse].name)); ccs.gameTimeScale = lapse[ccs.gameLapse].scale; cgi->Cvar_SetValue("mn_timelapse_id", ccs.gameLapse); } /* Update the date */ cgi->Cvar_Set("mn_mapdate", _("%i %s %02i"), date.year, Date_GetMonthName(date.month - 1), date.day); /* Update the time. */ cgi->Cvar_Set("mn_maptime", _("%02i:%02i"), date.hour, date.min); }
/** * @brief Adds the event mail to the message stack. This message is going to be added to the savegame. */ void CL_EventAddMail (const char *eventMailId) { eventMail_t* eventMail = CL_GetEventMail(eventMailId); if (!eventMail) { Com_Printf("CL_EventAddMail: Could not find eventmail with id '%s'\n", eventMailId); return; } if (eventMail->sent) { return; } if (!eventMail->from || !eventMail->to || !eventMail->subject || !eventMail->body) { Com_Printf("CL_EventAddMail: mail with id '%s' has incomplete data\n", eventMailId); return; } if (!eventMail->date) { dateLong_t date; char dateBuf[MAX_VAR] = ""; CP_DateConvertLong(&ccs.date, &date); Com_sprintf(dateBuf, sizeof(dateBuf), _("%i %s %02i"), date.year, Date_GetMonthName(date.month - 1), date.day); eventMail->date = Mem_PoolStrDup(dateBuf, cp_campaignPool, 0); } eventMail->sent = true; if (!eventMail->skipMessage) { uiMessageListNodeMessage_t *m = MS_AddNewMessage("", va(_("You've got a new mail: %s"), _(eventMail->subject)), MSG_EVENT); if (m) m->eventMail = eventMail; else Com_Printf("CL_EventAddMail: Could not add message with id: %s\n", eventMailId); } UP_OpenEventMail(eventMailId); }
/** * @brief Binds the mail header (if needed) to the mn.menuText array. * @note if there is a mail header. * @param[in] tech The tech to generate a header for. * @param[in] type The type of mail (research proposal or finished research) * @param[in] mail The mail descriptor structure * @sa UP_ChangeDisplay * @sa CL_EventAddMail_f * @sa CL_GetEventMail */ static void UP_SetMailHeader (technology_t* tech, techMailType_t type, eventMail_t* mail) { static char mailHeader[8 * MAX_VAR] = ""; /* bigger as techMail_t (utf8) */ char dateBuf[MAX_VAR] = ""; const char* subjectType = ""; const char* from, *to, *subject, *model; dateLong_t date; if (mail) { from = mail->from; to = mail->to; model = mail->model; subject = mail->subject; Q_strncpyz(dateBuf, _(mail->date), sizeof(dateBuf)); mail->read = true; /* reread the unread mails in UP_GetUnreadMails */ ccs.numUnreadMails = -1; } else { techMail_t* mail; assert(tech); assert(type < TECHMAIL_MAX); mail = &tech->mail[type]; from = mail->from; to = mail->to; subject = mail->subject; model = mail->model; if (mail->date) { Q_strncpyz(dateBuf, _(mail->date), sizeof(dateBuf)); } else { switch (type) { case TECHMAIL_PRE: CP_DateConvertLong(&tech->preResearchedDate, &date); Com_sprintf(dateBuf, sizeof(dateBuf), _("%i %s %02i"), date.year, Date_GetMonthName(date.month - 1), date.day); break; case TECHMAIL_RESEARCHED: CP_DateConvertLong(&tech->researchedDate, &date); Com_sprintf(dateBuf, sizeof(dateBuf), _("%i %s %02i"), date.year, Date_GetMonthName(date.month - 1), date.day); break; default: cgi->Com_Error(ERR_DROP, "UP_SetMailHeader: unhandled techMailType_t %i for date.", type); } } if (from != nullptr) { if (!mail->read) { mail->read = true; /* reread the unread mails in UP_GetUnreadMails */ ccs.numUnreadMails = -1; } /* only if mail and mail_pre are available */ if (tech->numTechMails == TECHMAIL_MAX) { switch (type) { case TECHMAIL_PRE: subjectType = _("Proposal: "); break; case TECHMAIL_RESEARCHED: subjectType = _("Re: "); break; default: cgi->Com_Error(ERR_DROP, "UP_SetMailHeader: unhandled techMailType_t %i for subject.", type); } } } else { cgi->UI_ResetData(TEXT_UFOPEDIA_MAILHEADER); return; } } from = cgi->CL_Translate(from); to = cgi->CL_Translate(to); subject = cgi->CL_Translate(subject); Com_sprintf(mailHeader, sizeof(mailHeader), _("FROM: %s\nTO: %s\nDATE: %s"), from, to, dateBuf); cgi->Cvar_Set("mn_mail_sender_head", "%s", model ? model : ""); cgi->Cvar_Set("mn_mail_from", "%s", from); cgi->Cvar_Set("mn_mail_subject", "%s%s", subjectType, _(subject)); cgi->Cvar_Set("mn_mail_to", "%s", to); cgi->Cvar_Set("mn_mail_date", "%s", dateBuf); cgi->UI_RegisterText(TEXT_UFOPEDIA_MAILHEADER, mailHeader); }
/** * @brief Start the mailclient * @sa UP_MailClientClick_f * @sa CP_GetUnreadMails * @sa CL_EventAddMail_f * @sa MS_AddNewMessage */ static void UP_OpenMail_f (void) { cgi->UI_ExecuteConfunc("clear_mails"); int idx = 0; for (const uiMessageListNodeMessage_t* m = cgi->UI_MessageGetStack(); m; m = m->next) { dateLong_t date; char headline[256] = ""; char dateBuf[64] = ""; const char* icon; bool read; switch (m->type) { case MSG_RESEARCH_PROPOSAL: { const techMail_t& mail = m->pedia->mail[TECHMAIL_PRE]; if (!mail.from) continue; CP_DateConvertLong(&m->pedia->preResearchedDate, &date); Com_sprintf(headline, sizeof(headline), _("Proposal: %s"), _(m->pedia->mail[TECHMAIL_PRE].subject)); Com_sprintf(dateBuf, sizeof(dateBuf), _("%i %s %02i"), date.year, Date_GetMonthName(date.month - 1), date.day); icon = mail.icon; read = mail.read; break; } case MSG_RESEARCH_FINISHED: { const techMail_t& mail = m->pedia->mail[TECHMAIL_RESEARCHED]; if (!mail.from) continue; CP_DateConvertLong(&m->pedia->researchedDate, &date); Com_sprintf(headline, sizeof(headline), _("Re: %s"), _(m->pedia->mail[TECHMAIL_PRE].subject)); Com_sprintf(dateBuf, sizeof(dateBuf), _("%i %s %02i"), date.year, Date_GetMonthName(date.month - 1), date.day); icon = mail.icon; read = mail.read; break; } case MSG_NEWS: { const techMail_t* mail = &m->pedia->mail[TECHMAIL_PRE]; if (mail->from) { CP_DateConvertLong(&m->pedia->preResearchedDate, &date); } else { CP_DateConvertLong(&m->pedia->researchedDate, &date); mail = &m->pedia->mail[TECHMAIL_RESEARCHED]; } if (!mail->from) continue; Com_sprintf(headline, sizeof(headline), "%s", _(mail->subject)); Com_sprintf(dateBuf, sizeof(dateBuf), _("%i %s %02i"), date.year, Date_GetMonthName(date.month - 1), date.day); icon = mail->icon; read = mail->read; break; } case MSG_EVENT: { assert(m->eventMail); const eventMail_t& mail = *m->eventMail; if (!mail.from) continue; Com_sprintf(headline, sizeof(headline), "%s", _(mail.subject)); Com_sprintf(dateBuf, sizeof(dateBuf), "%s", mail.date); icon = mail.icon; read = mail.read; break; } default: continue; } cgi->UI_ExecuteConfunc("add_mail %i \"%s\" \"%s\" %i \"%s\"", idx++, headline, icon, read, dateBuf); } }
/** * @brief This is a savegame function which stores the game in xml-Format. * @param[in] filename The Filename to save to (without extension) * @param[in] comment Description of the savegame * @param[out] error On failure an errormessage may be set. */ static bool SAV_GameSave (const char *filename, const char *comment, char **error) { xmlNode_t *topNode, *node; char savegame[MAX_OSPATH]; int res; int requiredBufferLength; uLongf bufLen; saveFileHeader_t header; char dummy[2]; int i; dateLong_t date; char message[30]; char timeStampBuffer[32]; if (!CP_IsRunning()) { *error = _("No campaign active."); Com_Printf("Error: No campaign active.\n"); return false; } if (!B_AtLeastOneExists()) { *error = _("Nothing to save yet."); Com_Printf("Error: Nothing to save yet.\n"); return false; } Com_MakeTimestamp(timeStampBuffer, sizeof(timeStampBuffer)); Com_sprintf(savegame, sizeof(savegame), "save/%s.%s", filename, SAVEGAME_EXTENSION); topNode = mxmlNewXML("1.0"); node = XML_AddNode(topNode, SAVE_ROOTNODE); /* writing Header */ XML_AddInt(node, SAVE_SAVEVERSION, SAVE_FILE_VERSION); XML_AddString(node, SAVE_COMMENT, comment); XML_AddString(node, SAVE_UFOVERSION, UFO_VERSION); XML_AddString(node, SAVE_REALDATE, timeStampBuffer); CP_DateConvertLong(&ccs.date, &date); Com_sprintf(message, sizeof(message), _("%i %s %02i"), date.year, Date_GetMonthName(date.month - 1), date.day); XML_AddString(node, SAVE_GAMEDATE, message); /* working through all subsystems. perhaps we should redesign it, order is not important anymore */ Com_Printf("Calling subsystems\n"); for (i = 0; i < saveSubsystemsAmount; i++) { if (!saveSubsystems[i].save(node)) Com_Printf("...subsystem '%s' failed to save the data\n", saveSubsystems[i].name); else Com_Printf("...subsystem '%s' - saved\n", saveSubsystems[i].name); } /* calculate the needed buffer size */ OBJZERO(header); header.compressed = LittleLong(save_compressed->integer); header.version = LittleLong(SAVE_FILE_VERSION); header.subsystems = LittleLong(saveSubsystemsAmount); Q_strncpyz(header.name, comment, sizeof(header.name)); Q_strncpyz(header.gameVersion, UFO_VERSION, sizeof(header.gameVersion)); CP_DateConvertLong(&ccs.date, &date); Com_sprintf(header.gameDate, sizeof(header.gameDate), _("%i %s %02i"), date.year, Date_GetMonthName(date.month - 1), date.day); Q_strncpyz(header.realDate, timeStampBuffer, sizeof(header.realDate)); requiredBufferLength = mxmlSaveString(topNode, dummy, 2, MXML_NO_CALLBACK); header.xmlSize = LittleLong(requiredBufferLength); byte* const buf = Mem_PoolAllocTypeN(byte, requiredBufferLength + 1, cp_campaignPool); if (!buf) { mxmlDelete(topNode); *error = _("Could not allocate enough memory to save this game"); Com_Printf("Error: Could not allocate enough memory to save this game\n"); return false; } res = mxmlSaveString(topNode, (char*)buf, requiredBufferLength + 1, MXML_NO_CALLBACK); mxmlDelete(topNode); Com_Printf("XML Written to buffer (%d Bytes)\n", res); if (header.compressed) bufLen = compressBound(requiredBufferLength); else bufLen = requiredBufferLength; byte* const fbuf = Mem_PoolAllocTypeN(byte, bufLen + sizeof(header), cp_campaignPool); memcpy(fbuf, &header, sizeof(header)); if (header.compressed) { res = compress(fbuf + sizeof(header), &bufLen, buf, requiredBufferLength); Mem_Free(buf); if (res != Z_OK) { Mem_Free(fbuf); *error = _("Memory error compressing save-game data - set save_compressed cvar to 0"); Com_Printf("Memory error compressing save-game data (%s) (Error: %i)!\n", comment, res); return false; } } else { memcpy(fbuf + sizeof(header), buf, requiredBufferLength); Mem_Free(buf); } /* last step - write data */ res = FS_WriteFile(fbuf, bufLen + sizeof(header), savegame); Mem_Free(fbuf); return true; }
/** * @brief Load callback for messages * @param[in] p XML Node structure, where we get the information from * @sa MS_SaveXML * @sa UI_AddNewMessageSound */ bool MS_LoadXML (xmlNode_t* p) { int i; xmlNode_t* n, *sn; n = cgi->XML_GetNode(p, SAVE_MESSAGES_MESSAGES); if (!n) return false; /* we have to set this a little bit higher here, otherwise the samples that are played when adding * a message to the stack would all played a few milliseconds after each other - that doesn't sound * nice */ cgi->S_SetSampleRepeatRate(500); cgi->Com_RegisterConstList(saveMessageConstants); for (sn = cgi->XML_GetNode(n, SAVE_MESSAGES_MESSAGE), i = 0; sn; sn = cgi->XML_GetNextNode(sn, n, SAVE_MESSAGES_MESSAGE), i++) { eventMail_t* mail; const char* type = cgi->XML_GetString(sn, SAVE_MESSAGES_TYPE); int mtype; char title[MAX_VAR]; char text[MAX_MESSAGE_TEXT]; char id[MAX_VAR]; technology_t* tech = nullptr; uiMessageListNodeMessage_t* mess; if (!cgi->Com_GetConstIntFromNamespace(SAVE_MESSAGETYPE_NAMESPACE, type, (int*) &mtype)) { cgi->Com_Printf("Invalid message type '%s'\n", type); continue; } /* can contain high bits due to utf8 */ Q_strncpyz(title, cgi->XML_GetString(sn, SAVE_MESSAGES_TITLE), sizeof(title)); Q_strncpyz(text, cgi->XML_GetString(sn, SAVE_MESSAGES_TEXT), sizeof(text)); if (mtype == MSG_EVENT) { mail = CL_GetEventMail(cgi->XML_GetString(sn, SAVE_MESSAGES_EVENTMAILID)); if (mail) mail->read = cgi->XML_GetBool(sn, SAVE_MESSAGES_EVENTMAILREAD, false); } else mail = nullptr; /* event and not mail means, dynamic mail - we don't save or load them */ if (mtype == MSG_EVENT && !mail) continue; if (mtype == MSG_DEBUG && cgi->Cvar_GetInteger("developer") == 0) continue; Q_strncpyz(id, cgi->XML_GetString(sn, SAVE_MESSAGES_PEDIAID), sizeof(id)); if (id[0] != '\0') tech = RS_GetTechByID(id); if (!tech && (mtype == MSG_RESEARCH_PROPOSAL || mtype == MSG_RESEARCH_FINISHED)) { /** No tech found drop message. */ continue; } mess = MS_AddNewMessage(title, text, (messageType_t)mtype, tech, false, false); mess->eventMail = mail; cgi->XML_GetDate(sn, SAVE_MESSAGES_DATE, &mess->date.day, &mess->date.sec); /* redo timestamp text after setting date */ MS_TimestampedText(mess->timestamp, mess, sizeof(mess->timestamp)); if (mail) { dateLong_t date; char dateBuf[MAX_VAR] = ""; CP_DateConvertLong(&mess->date, &date); Com_sprintf(dateBuf, sizeof(dateBuf), _("%i %s %02i"), date.year, Date_GetMonthName(date.month - 1), date.day); mail->date = cgi->PoolStrDup(dateBuf, cp_campaignPool, 0); } } cgi->Com_UnregisterConstList(saveMessageConstants); /* reset the sample repeat rate */ cgi->S_SetSampleRepeatRate(0); return true; }