// cg_text.c -- // this line must stay at top so the whole PCH thing works... #include "cg_headers.h" #include "cg_media.h" //int precacheWav_i; // Current high index of precacheWav array //precacheWav_t precacheWav[MAX_PRECACHEWAV]; //int precacheText_i; // Current high index of precacheText array //precacheText_t precacheText[MAX_PRECACHETEXT]; extern vec4_t textcolor_caption; extern vec4_t textcolor_center; extern vec4_t textcolor_scroll; #define GAMETEXT_X_START 75.0f #define GAMETEXT_X_END 600.0f #define MAX_NUM_GAMELINES 4 void CG_GameText(int y ) { CG_Printf("CG_GameText() being called. Tell Ste\n"); /* const char *s,*holds; int i, len; float x, w; int numChars; int text_i; char str[MAX_QPATH]; int holdCnt,playingTime; int totalLength,sound,max; Q_strncpyz (str, CG_Argv( 1 ), MAX_QPATH ); cg.gameTextSpeaker = atoi(CG_Argv(2)); cg.gameTextEntNum = atoi(CG_Argv(3)); sound = cgs.sound_precache[atoi(CG_Argv(4))]; text_i = CG_SearchTextPrecache(str); //ensure we found a match if (text_i == -1) { Com_Printf("WARNING: CG_GameText given invalid text key :'%s'",str); return; } cg.gameTextTime = cg.time; cg.printTextY = 5 + SMALLCHAR_HEIGHT; cg.gameTextCurrentLine = 0; // count the number of lines for centering cg.scrollTextLines = 1; memset (cg.printText, 0, sizeof(cg.printText)); // Break into individual lines i = 0; len = 0; s = precacheText[text_i].text; holds = s; playingTime = cgi_S_GetSampleLength(sound); totalLength = strlen(s); if (totalLength == 0) { totalLength = 1; } cg.gameLetterTime = playingTime / totalLength; //We start at column 75 according to DrawGameText x = GAMETEXT_X_START; w = GAMETEXT_X_END - GAMETEXT_X_START; numChars = floor(w/SMALLCHAR_WIDTH); while( *s ) { len++; if (*s == '\n') {//Being told explicitly to start a new line Q_strncpyz( cg.printText[i], holds, len); i++; len = 0; holds = s; holds++; cg.scrollTextLines++; } else if ( len == numChars ) {//Reached max length of this line //step back until we find a space while( len && *s != ' ' ) { s--; len--; } //break the line here Q_strncpyz( cg.printText[i], holds, len); i++; len = 0; holds = s; holds++; cg.scrollTextLines++; } s++; } len++; // So the NULL will be properly placed at the end of the string of Q_strncpyz Q_strncpyz( cg.printText[i], holds, len); // To get the last line //NOTE: This might be able to use the VoiceVolume or TID_VOICE info from the cg.gameTextEntNum // to decide when to drop the text... max = MAX_NUM_GAMELINES; if (max >cg.scrollTextLines) { max = cg.scrollTextLines; } holdCnt = 0; for (i=0;i= cg.scrollTextLines) { cg.gameTextTime = 0; return; } else { max = MAX_NUM_GAMELINES; if ((cg.scrollTextLines - cg.gameTextCurrentLine) < max) { max = cg.scrollTextLines - cg.gameTextCurrentLine; } // Loop through next lines to calc how long to show 'em holdCnt = 0; for (i=cg.gameTextCurrentLine;i<(cg.gameTextCurrentLine + max);++i) { holdCnt += strlen(cg.printText[i]); } cg.gameNextTextTime = cg.time + (holdCnt * cg.gameLetterTime); } } // Give a color if one wasn't given if((textcolor_caption[0] == 0) && (textcolor_caption[1] == 0) && (textcolor_caption[2] == 0) && (textcolor_caption[3] == 0)) { Vector4Copy( colorTable[CT_WHITE], textcolor_caption ); } color[0] = colorTable[CT_BLACK][0]; color[1] = colorTable[CT_BLACK][1]; color[2] = colorTable[CT_BLACK][2]; color[3] = 0.350f; // Set Y of the first line y = cg.printTextY; x = GAMETEXT_X_START; // Background cgi_R_SetColor(color); // Background, CLAMP TO 4 LINES CG_DrawPic( x - 4, y - SMALLCHAR_HEIGHT - 2, (70 * SMALLCHAR_WIDTH),(( ((cg.scrollTextLines>MAX_NUM_GAMELINES)?MAX_NUM_GAMELINES:cg.scrollTextLines) + 1) * SMALLCHAR_HEIGHT) + 4, cgs.media.ammoslider ); sprintf(string, "%s:", speakerTable[cg.gameTextSpeaker].stringID); CG_DrawStringExt( x, y - SMALLCHAR_HEIGHT, string, colorTable[CT_LTPURPLE1], qfalse, qtrue, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT ); for (i= cg.gameTextCurrentLine;i< cg.gameTextCurrentLine + MAX_NUM_GAMELINES;++i) { start = cg.printText[i]; while ( 1 ) { for ( l = 0; l < 80; l++ ) { if ( !start[l] || start[l] == '\n' ) { break; } linebuffer[l] = start[l]; } linebuffer[l] = 0; CG_DrawStringExt( x, y, linebuffer, textcolor_caption, qfalse, qtrue, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT ); y += SMALLCHAR_HEIGHT; while ( *start && ( *start != '\n' ) ) { start++; } if ( !*start ) { break; } start++; } } cgi_R_SetColor( NULL ); */ } // display text in a supplied box, start at top left and going down by however many pixels I feel like internally, // return value is NULL if all fitted, else char * of next char to continue from that didn't fit. // // (coords are in the usual 640x480 virtual space)... // // ( if you get the same char * returned as what you passed in, then none of it fitted at all (box too small) ) // const char *CG_DisplayBoxedText(int iBoxX, int iBoxY, int iBoxWidth, int iBoxHeight, const char *psText, int iFontHandle, float fScale, const vec4_t v4Color) { cgi_R_SetColor( v4Color ); // Setup a reasonable vertical spacing (taiwanese & japanese need 1.5 fontheight, so use that for all)... // const int iFontHeight = cgi_R_Font_HeightPixels(iFontHandle, fScale); const int iFontHeightAdvance = (int) (1.5f * (float) iFontHeight); int iYpos = iBoxY; // start print pos // findmeste // test stuff, remove later // psText = "賽卓哥爾博士已經安全了,我也把所有發現報告給「商店」。很不幸地,瑞士警局有些白癡發現了一些狀況,準備在機場逮捕亞歷西•納克瑞得。他偽裝成外交使節,穿過了層層防備。現在他握有人質,並且威脅要散播病毒。根據最搆sCurrentTextReadPos的報告,納克瑞得以及他的黨羽已經完全佔據了機場。我受命來追捕納克瑞得以及救出所有人質。這並不容易。"; // this could probably be simplified now, but it was converted from something else I didn't originally write, // and it works anyway so wtf... // const char *psCurrentTextReadPos = psText; const char *psReadPosAtLineStart = psCurrentTextReadPos; const char *psBestLineBreakSrcPos = psCurrentTextReadPos; const char *psLastGood_s; // needed if we get a full screen of chars with no punctuation or space (see usage notes) while( *psCurrentTextReadPos && (iYpos + iFontHeight < (iBoxY + iBoxHeight)) ) { char sLineForDisplay[2048]; // ott // construct a line... // psCurrentTextReadPos = psReadPosAtLineStart; sLineForDisplay[0] = '\0'; while ( *psCurrentTextReadPos ) { psLastGood_s = psCurrentTextReadPos; // read letter... // qboolean bIsTrailingPunctuation; unsigned int uiLetter = cgi_AnyLanguage_ReadCharFromString(&psCurrentTextReadPos, &bIsTrailingPunctuation); // concat onto string so far... // if (uiLetter > 255) { Q_strcat(sLineForDisplay, sizeof(sLineForDisplay),va("%c%c",uiLetter >> 8, uiLetter & 0xFF)); } else { Q_strcat(sLineForDisplay, sizeof(sLineForDisplay),va("%c",uiLetter & 0xFF)); } // record last-good linebreak pos... (ie if we've just concat'd a punctuation point (western or asian) or space) // if (bIsTrailingPunctuation || uiLetter == ' ') { psBestLineBreakSrcPos = psCurrentTextReadPos; } if (uiLetter == '\n') { // explicit new line... // sLineForDisplay[ strlen(sLineForDisplay)-1 ] = '\0'; // kill the CR psReadPosAtLineStart = psCurrentTextReadPos; break; // print this line } else if ( cgi_R_Font_StrLenPixels(sLineForDisplay, iFontHandle, fScale) >= (iBoxWidth - 16) ) { // reached screen edge, so cap off string at bytepos after last good position... // if (psBestLineBreakSrcPos == psReadPosAtLineStart) { // aarrrggh!!!!! we'll only get here is someone has fed in a (probably) garbage string, // since it doesn't have a single space or punctuation mark right the way across one line // of the screen. So far, this has only happened in testing when I hardwired a taiwanese // string into this function while the game was running in english (which should NEVER happen // normally). On the other hand I suppose it'psCurrentTextReadPos entirely possible that some taiwanese string // might have no punctuation at all, so... // psBestLineBreakSrcPos = psLastGood_s; // force a break after last good letter } sLineForDisplay[ psBestLineBreakSrcPos - psReadPosAtLineStart ] = '\0'; psReadPosAtLineStart = psCurrentTextReadPos = psBestLineBreakSrcPos; break; // print this line } } // ... and print it... // // int iWidth = cgi_R_Font_StrLenPixels( sLineForDisplay, iFontHandle, iFontScale ); // if (iWidth) // { // int x = BoxX; // ignore this for now -----> (SCREEN_WIDTH-iWidth) / 2; cgi_R_Font_DrawString(iBoxX, iYpos, sLineForDisplay, v4Color, iFontHandle, -1, fScale); iYpos += iFontHeightAdvance; // } // and echo to console in dev mode... // if ( cg_developer.integer ) { Com_Printf( "%psCurrentTextReadPos\n", sLineForDisplay ); } } return psReadPosAtLineStart; } /* =============================================================================== CAPTION TEXT =============================================================================== */ void CG_CaptionTextStop(void) { cg.captionTextTime = 0; } // try and get the correct StripEd text (with retry) for a given reference... // // returns 0 if failed, else strlen... // static int cg_SP_GetStringTextStringWithRetry( LPCSTR psReference, char *psDest, int iSizeofDest) { int iReturn; for (int i=0; i 255) { Q_strcat(cg.captionText[i],sizeof(cg.captionText[i]),va("%c%c",uiLetter >> 8, uiLetter & 0xFF)); } else { Q_strcat(cg.captionText[i],sizeof(cg.captionText[i]),va("%c",uiLetter & 0xFF)); } // record last-good linebreak pos... (ie if we've just concat'd a punctuation point (western or asian) or space) // if (bIsTrailingPunctuation || uiLetter == ' ') { psBestLineBreakSrcPos = s; } if (uiLetter == '\n') { // explicit new line... // cg.captionText[i][ strlen(cg.captionText[i])-1 ] = '\0'; // kill the CR i++; holds = s; cg.scrollTextLines++; } else if ( cgi_R_Font_StrLenPixels(cg.captionText[i], cgs.media.qhFontMedium, fFontScale) >= SCREEN_WIDTH - 16) { // reached screen edge, so cap off string at bytepos after last good position... // if (psBestLineBreakSrcPos == holds) { // aarrrggh!!!!! we'll only get here is someone has fed in a (probably) garbage string, // since it doesn't have a single space or punctuation mark right the way across one line // of the screen. So far, this has only happened in testing when I hardwired a taiwanese // string into this function while the game was running in english (which should NEVER happen // normally). On the other hand I suppose it's entirely possible that some taiwanese string // might have no punctuation at all, so... // psBestLineBreakSrcPos = psLastGood_s; // force a break after last good letter } cg.captionText[i][ psBestLineBreakSrcPos - holds ] = '\0'; holds = s = psBestLineBreakSrcPos; i++; cg.scrollTextLines++; } } // calc the length of time to hold each 2 lines of text on the screen.... presumably this works? // holdTime = strlen(cg.captionText[0]); if (cg.scrollTextLines > 1) { holdTime += strlen(cg.captionText[1]); // strlen is also good for MBCS in this instance, since it's for timing } cg.captionNextTextTime = cg.time + (holdTime * cg.captionLetterTime); cg.scrollTextTime = 0; // No scrolling during captions //Echo to console in dev mode if ( cg_developer.integer ) { Com_Printf( "%s\n", cg.captionText[0] ); // ste: was [i], but surely sentence 0 is more useful than last? } } void CG_DrawCaptionText(void) { int i; int x, y, w; int holdTime; if ( !cg.captionTextTime ) { return; } const float fFontScale = cgi_Language_IsAsian() ? 0.8f : 1.0f; if (cg_skippingcin.value != 0.0f) { cg.captionTextTime = 0; return; } if ( cg.captionNextTextTime < cg.time ) { cg.captionTextCurrentLine += 2; if (cg.captionTextCurrentLine >= cg.scrollTextLines) { cg.captionTextTime = 0; return; } else { holdTime = strlen(cg.captionText[cg.captionTextCurrentLine]); if (cg.scrollTextLines >= cg.captionTextCurrentLine) { // ( strlen is also good for MBCS in this instance, since it's for timing -ste) // holdTime += strlen(cg.captionText[cg.captionTextCurrentLine + 1]); } cg.captionNextTextTime = cg.time + (holdTime * cg.captionLetterTime);//50); } } // Give a color if one wasn't given if((textcolor_caption[0] == 0) && (textcolor_caption[1] == 0) && (textcolor_caption[2] == 0) && (textcolor_caption[3] == 0)) { Vector4Copy( colorTable[CT_WHITE], textcolor_caption ); } cgi_R_SetColor(textcolor_caption); // Set Y of the first line (varies if only printing one line of text) // (this all works, please don't mess with it) const int fontHeight = (int) ((cgi_Language_IsAsian() ? 1.4f : 1.0f) * (float) cgi_R_Font_HeightPixels(cgs.media.qhFontMedium, fFontScale)); const bool bPrinting2Lines = !!(cg.captionText[ cg.captionTextCurrentLine+1 ][0]); y = cg.captionTextY - ( (float)fontHeight * (bPrinting2Lines ? 1 : 0.5f)); // captionTextY was a centered Y pos, not a top one y -= cgi_Language_IsAsian() ? 0 : 4; for (i= cg.captionTextCurrentLine;i< cg.captionTextCurrentLine + 2;++i) { w = cgi_R_Font_StrLenPixels(cg.captionText[i], cgs.media.qhFontMedium, fFontScale); if (w) { x = (SCREEN_WIDTH-w) / 2; cgi_R_Font_DrawString(x, y, cg.captionText[i], textcolor_caption, cgs.media.qhFontMedium, -1, fFontScale); y += fontHeight; } } cgi_R_SetColor( NULL ); } /* =============================================================================== SCROLL TEXT =============================================================================== CG_ScrollText - split text up into seperate lines 'str' arg is StripEd string reference, eg "CREDITS_RAVEN" */ int giScrollTextPixelWidth = SCREEN_WIDTH; void CG_ScrollText( const char *str, int iPixelWidth ) { const char *s,*holds; int i;//, len;//, numChars; giScrollTextPixelWidth = iPixelWidth; // first, ask the strlen of the final string... // i = cg_SP_GetStringTextStringWithRetry( str, NULL, 0 ); //ensure we found a match if (!i) { #ifndef FINAL_BUILD Com_Printf("WARNING: CG_ScrollText given invalid text key :'%s'\n",str); #endif return; } // // malloc space to hold it... // char *psText = (char *) cgi_Z_Malloc( i+1, TAG_STRING ); // // now get the string... // i = cg_SP_GetStringTextStringWithRetry( str, psText, i+1 ); //ensure we found a match if (!i) { assert(0); // should never get here now, but wtf? cgi_Z_Free(psText); #ifndef FINAL_BUILD Com_Printf("WARNING: CG_ScrollText given invalid text key :'%s'\n",str); #endif return; } cg.scrollTextTime = cg.time; cg.printTextY = SCREEN_HEIGHT; cg.scrollTextLines = 1; s = psText; i = 0; holds = s; const char *psBestLineBreakSrcPos = s; const char *psLastGood_s; // needed if we get a full screen of chars with no punctuation or space (see usage notes) while( *s ) { psLastGood_s = s; // read letter... // qboolean bIsTrailingPunctuation; unsigned int uiLetter = cgi_AnyLanguage_ReadCharFromString(&s, &bIsTrailingPunctuation); // concat onto string so far... // if (uiLetter > 255) { Q_strcat(cg.printText[i],sizeof(cg.printText[i]),va("%c%c",uiLetter >> 8, uiLetter & 0xFF)); } else { Q_strcat(cg.printText[i],sizeof(cg.printText[i]),va("%c",uiLetter & 0xFF)); } // record last-good linebreak pos... (ie if we've just concat'd a punctuation point (western or asian) or space) // if (bIsTrailingPunctuation || uiLetter == ' ') { psBestLineBreakSrcPos = s; } if (uiLetter == '\n') { // explicit new line... // cg.printText[i][ strlen(cg.printText[i])-1 ] = '\0'; // kill the CR i++; holds = s; cg.scrollTextLines++; } else if ( cgi_R_Font_StrLenPixels(cg.printText[i], cgs.media.qhFontMedium, 1.0f) >= iPixelWidth) { // reached screen edge, so cap off string at bytepos after last good position... // if (psBestLineBreakSrcPos == holds) { // aarrrggh!!!!! we'll only get here is someone has fed in a (probably) garbage string, // since it doesn't have a single space or punctuation mark right the way across one line // of the screen. So far, this has only happened in testing when I hardwired a taiwanese // string into this function while the game was running in english (which should NEVER happen // normally). On the other hand I suppose it's entirely possible that some taiwanese string // might have no punctuation at all, so... // psBestLineBreakSrcPos = psLastGood_s; // force a break after last good letter } cg.printText[i][ psBestLineBreakSrcPos - holds ] = '\0'; holds = s = psBestLineBreakSrcPos; i++; cg.scrollTextLines++; } } cg.captionTextTime = 0; // No captions during scrolling cgi_Z_Free(psText); } // draws using [textcolor_scroll]... // #define SCROLL_LPM (1/50.0) // 1 line per 50 ms void CG_DrawScrollText(void) { char *start; int i; int x,y; const int fontHeight = (int) (1.5f * (float) cgi_R_Font_HeightPixels(cgs.media.qhFontMedium, 1.0f)); // taiwanese & japanese need 1.5 fontheight spacing if ( !cg.scrollTextTime ) { return; } cgi_R_SetColor( textcolor_scroll ); y = cg.printTextY - (cg.time - cg.scrollTextTime) * SCROLL_LPM; // cgi_R_Font_DrawString(320, 200, va("Scrolltext printing @ %d",y), colorTable[CT_LTGOLD1], cgs.media.qhFontMedium, -1, 1.0f); // See if text has finished scrolling off screen if ((y + cg.scrollTextLines * fontHeight) < 1) { cg.scrollTextTime = 0; return; } for (i=0;i SCREEN_HEIGHT) { break; } start = cg.printText[i]; // w = cgi_R_Font_StrLenPixels(cg.printText[i], cgs.media.qhFontMedium, 1.0f); // if (w) { x = (SCREEN_WIDTH - giScrollTextPixelWidth) / 2; cgi_R_Font_DrawString(x,y, cg.printText[i], textcolor_scroll, cgs.media.qhFontMedium, -1, 1.0f); y += fontHeight; } } cgi_R_SetColor( NULL ); } /* =============================================================================== CENTER PRINTING =============================================================================== */ /* ============== CG_CenterPrint Called for important messages that should stay in the center of the screen for a few moments ============== */ void CG_CenterPrint( const char *str, int y) { char *s; // Find text to match the str given if (*str == '@') { int i; i = cg_SP_GetStringTextStringWithRetry( str+1, cg.centerPrint, sizeof(cg.centerPrint) ); if (!i) { Com_Printf ("CG_CenterPrint: bad key for precached Text '%s'\n",str); return; } } else { Q_strncpyz( cg.centerPrint, str, sizeof(cg.centerPrint) ); } cg.centerPrintTime = cg.time; cg.centerPrintY = y; // count the number of lines for centering cg.centerPrintLines = 1; s = cg.centerPrint; while( *s ) { if (*s == '\n') cg.centerPrintLines++; s++; } } /* =================== CG_DrawCenterString =================== */ void CG_DrawCenterString( void ) { char *start; int l; int x, y, w; float *color; if ( !cg.centerPrintTime ) { return; } color = CG_FadeColor( cg.centerPrintTime, 1000 * cg_centertime.value ); if ( !color ) { return; } if((textcolor_center[0] == 0) && (textcolor_center[1] == 0) && (textcolor_center[2] == 0) && (textcolor_center[3] == 0)) { Vector4Copy( colorTable[CT_WHITE], textcolor_center ); } start = cg.centerPrint; const int fontHeight = cgi_R_Font_HeightPixels(cgs.media.qhFontMedium, 1.0f); y = cg.centerPrintY - (cg.centerPrintLines * fontHeight) / 2; while ( 1 ) { char linebuffer[1024]; // this is kind of unpleasant when dealing with MBCS, but... // const char *psString = start; int iOutIndex = 0; for ( l = 0; l < 40; l++ ) { unsigned int uiLetter = cgi_AnyLanguage_ReadCharFromString(&psString); if (!uiLetter || uiLetter == '\n'){ break; } if (uiLetter > 255) { linebuffer[iOutIndex++] = uiLetter >> 8; linebuffer[iOutIndex++] = uiLetter & 0xFF; } else { linebuffer[iOutIndex++] = uiLetter & 0xFF; } } linebuffer[iOutIndex++] = '\0'; w = cgi_R_Font_StrLenPixels(linebuffer, cgs.media.qhFontMedium, 1.0f); x = ( SCREEN_WIDTH - w ) / 2; cgi_R_Font_DrawString(x,y,linebuffer, textcolor_center, cgs.media.qhFontMedium, -1, 1.0f); y += fontHeight; while ( *start && ( *start != '\n' ) ) { start++; } if ( !*start ) { break; } start++; } }