1 /*
2 ===========================================================================
3 Copyright (C) 2000 - 2013, Raven Software, Inc.
4 Copyright (C) 2001 - 2013, Activision, Inc.
5 Copyright (C) 2013 - 2015, OpenJK contributors
6
7 This file is part of the OpenJK source code.
8
9 OpenJK is free software; you can redistribute it and/or modify it
10 under the terms of the GNU General Public License version 2 as
11 published by the Free Software Foundation.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, see <http://www.gnu.org/licenses/>.
20 ===========================================================================
21 */
22
23 // Filename:- cg_credits.cpp
24 //
25 // module for end credits code
26
27 #include "cg_local.h"
28 #include "cg_media.h"
29
30
31 #define fCARD_FADESECONDS 1.0f // fade up time, also fade down time
32 #define fCARD_SUSTAINSECONDS 2.0f // hold time before fade down
33 #define fLINE_SECONDTOSCROLLUP 15.0f // how long one line takes to scroll up the screen
34
35
36 #define MAX_LINE_BYTES 2048
37
38 qhandle_t ghFontHandle = 0;
39 float gfFontScale = 1.0f;
40 vec4_t gv4Color = {0};
41
42 struct StringAndSize_t
43 {
44 int iStrLenPixels;
45 std::string str;
46
StringAndSize_tStringAndSize_t47 StringAndSize_t()
48 {
49 iStrLenPixels = -1;
50 str = "";
51 }
StringAndSize_tStringAndSize_t52 StringAndSize_t(const char *psString)
53 {
54 iStrLenPixels = -1;
55 str = psString;
56 }
operator =StringAndSize_t57 StringAndSize_t & operator = (const char *psString)
58 {
59 iStrLenPixels = -1;
60 str = psString;
61 return *this;
62 }
63
c_strStringAndSize_t64 const char *c_str(void)
65 {
66 return str.c_str();
67 }
68
GetPixelLengthStringAndSize_t69 int GetPixelLength(void)
70 {
71 if (iStrLenPixels == -1)
72 {
73 iStrLenPixels = cgi_R_Font_StrLenPixels(str.c_str(), ghFontHandle, gfFontScale);
74 }
75
76 return iStrLenPixels;
77 }
78
IsEmptyStringAndSize_t79 bool IsEmpty(void)
80 {
81 return str.empty();
82 }
83 };
84
85 struct CreditCard_t
86 {
87 int iTime;
88 StringAndSize_t strTitle;
89 std::vector<StringAndSize_t> vstrText;
90
CreditCard_tCreditCard_t91 CreditCard_t()
92 {
93 iTime = -1; // flag "not set yet"
94 }
95 };
96
97 struct CreditLine_t
98 {
99 int iLine;
100 StringAndSize_t strText;
101 std::vector<StringAndSize_t> vstrText;
102 bool bDotted;
103 };
104
105 typedef std::list <CreditLine_t> CreditLines_t;
106 typedef std::list <CreditCard_t> CreditCards_t;
107
108 struct CreditData_t
109 {
110 int iStartTime;
111
112 CreditCards_t CreditCards;
113 CreditLines_t CreditLines;
114
RunningCreditData_t115 qboolean Running(void)
116 {
117 return (qboolean)(CreditCards.size() || CreditLines.size());
118 }
119 };
120
121 CreditData_t CreditData;
122
123
Capitalize(const char * psTest)124 static const char *Capitalize( const char *psTest )
125 {
126 static char sTemp[MAX_LINE_BYTES];
127
128 Q_strncpyz(sTemp, psTest, sizeof(sTemp));
129
130 if (!cgi_Language_IsAsian())
131 {
132 Q_strupr(sTemp); // capitalise titles (if not asian!!!!)
133 }
134
135 return sTemp;
136 }
137
CountsAsWhiteSpaceForCaps(char c)138 static bool CountsAsWhiteSpaceForCaps( char c )
139 {
140 return !!(isspace(c) || c == '-' || c == '.' || c == '(' || c == ')');
141 }
UpperCaseFirstLettersOnly(const char * psTest)142 static const char *UpperCaseFirstLettersOnly( const char *psTest )
143 {
144 static char sTemp[MAX_LINE_BYTES];
145
146 Q_strncpyz(sTemp, psTest, sizeof(sTemp));
147
148 if (!cgi_Language_IsAsian())
149 {
150 Q_strlwr(sTemp);
151
152 char *p = sTemp;
153 while (*p)
154 {
155 while (*p && CountsAsWhiteSpaceForCaps(*p)) p++;//(isspace(*p) || *p == '-' || *p == '.')) p++; // also copes with hyphenated names (awkward gits)
156 if (*p)
157 {
158 *p = toupper(*p);
159 while (*p && !CountsAsWhiteSpaceForCaps(*p)) p++; // cope with hyphenated names and initials (awkward gits)
160 }
161 }
162 }
163
164 // now restore any weird stuff...
165 //
166 char *p = strstr(sTemp," Mc"); // eg "Mcfarrell" should be "McFarrell"
167 if (p && isalpha(p[3]))
168 {
169 p[3] = toupper(p[3]);
170 }
171 p = strstr(sTemp," O'"); // eg "O'flaherty" should be "O'Flaherty"
172 if (p && isalpha(p[3]))
173 {
174 p[3] = toupper(p[3]);
175 }
176 p = strstr(sTemp,"Lucasarts");
177 if (p)
178 {
179 p[5] = 'A'; // capitalise the 'A' in LucasArts (jeez...)
180 }
181
182 return sTemp;
183 }
184
GetSubString(std::string & strResult)185 static const char *GetSubString(std::string &strResult)
186 {
187 static char sTemp[MAX_LINE_BYTES];
188
189 if (!strlen(strResult.c_str()))
190 return NULL;
191
192 Q_strncpyz(sTemp,strResult.c_str(),sizeof(sTemp));
193
194 char *psSemiColon = strchr(sTemp,';');
195 if ( psSemiColon)
196 {
197 *psSemiColon = '\0';
198
199 strResult.erase(0,(psSemiColon-sTemp)+1);
200 }
201 else
202 {
203 // no semicolon found, probably last entry? (though i think even those have them on, oh well)
204 //
205 strResult.erase();
206 }
207
208 return sTemp;
209 }
210
211 // sort entries by their last name (starts at back of string and moves forward until start or just before whitespace)
212 // ...
SortBySurname(const StringAndSize_t & str1,const StringAndSize_t & str2)213 static bool SortBySurname(const StringAndSize_t &str1, const StringAndSize_t &str2)
214 {
215 std::string::const_reverse_iterator rstart1 = std::find_if(str1.str.rbegin(), str1.str.rend(), isspace);
216 std::string::const_reverse_iterator rstart2 = std::find_if(str2.str.rbegin(), str2.str.rend(), isspace);
217
218
219 return Q_stricmp(&*rstart1.base(), &*rstart2.base()) < 0;
220 }
221
222
223
CG_Credits_Init(const char * psStripReference,vec4_t * pv4Color)224 void CG_Credits_Init( const char *psStripReference, vec4_t *pv4Color )
225 {
226 // could make these into parameters later, but for now...
227 //
228 ghFontHandle = cgs.media.qhFontMedium;
229 gfFontScale = 1.0f;
230
231 memcpy(gv4Color,pv4Color,sizeof(gv4Color)); // memcpy so we can poke into alpha channel
232
233 // first, ask the strlen of the final string...
234 //
235 int iStrLen = cgi_SP_GetStringTextString( psStripReference, NULL, 0 );
236 if (!iStrLen)
237 {
238 #ifndef FINAL_BUILD
239 Com_Printf("WARNING: CG_Credits_Init(): invalid text key :'%s'\n", psStripReference);
240 #endif
241 return;
242 }
243 //
244 // malloc space to hold it...
245 //
246 char *psMallocText = (char *) cgi_Z_Malloc( iStrLen+1, TAG_TEMP_WORKSPACE );
247 //
248 // now get the string...
249 //
250 iStrLen = cgi_SP_GetStringTextString( psStripReference, psMallocText, iStrLen+1 );
251 //ensure we found a match
252 if (!iStrLen)
253 {
254 assert(0); // should never get here now, but wtf?
255 cgi_Z_Free(psMallocText);
256 #ifndef FINAL_BUILD
257 Com_Printf("WARNING: CG_Credits_Init(): invalid text key :'%s'\n", psStripReference);
258 #endif
259 return;
260 }
261
262 // read whole string in and process as cards, lines etc...
263 //
264 typedef enum
265 {
266 eNothing = 0,
267 eLine,
268 eDotEntry,
269 eTitle,
270 eCard,
271 eFinished,
272 } Mode_e;
273 Mode_e eMode = eNothing;
274
275 qboolean bCardsFinished = qfalse;
276 int iLineNumber = 0;
277 const char *psTextParse = psMallocText;
278 while (*psTextParse != '\0')
279 {
280 // read a line...
281 //
282 char sLine[MAX_LINE_BYTES];
283 sLine[0]='\0';
284 qboolean bWasCommand = qtrue;
285 while (1)
286 {
287 qboolean bIsTrailingPunctuation;
288 unsigned int uiLetter = cgi_AnyLanguage_ReadCharFromString(&psTextParse, &bIsTrailingPunctuation);
289
290 // concat onto string so far...
291 //
292 if (uiLetter == 32 && sLine[0] == '\0')
293 {
294 continue; // unless it's a space at the start of a line, in which case ignore it.
295 }
296
297 if (uiLetter == '\n' || uiLetter == '\0' )
298 {
299 // have we got a command word?...
300 //
301 if (!Q_stricmpn(sLine,"(#",2))
302 {
303 // yep...
304 //
305 if (!Q_stricmp(sLine, "(#CARD)"))
306 {
307 if (!bCardsFinished)
308 {
309 eMode = eCard;
310 }
311 else
312 {
313 #ifndef FINAL_BUILD
314 Com_Printf( S_COLOR_YELLOW "CG_Credits_Init(): No current support for cards after scroll!\n" );
315 #endif
316 eMode = eNothing;
317 }
318 break;
319 }
320 else
321 if (!Q_stricmp(sLine, "(#TITLE)"))
322 {
323 eMode = eTitle;
324 bCardsFinished = qtrue;
325 break;
326 }
327 else
328 if (!Q_stricmp(sLine, "(#LINE)"))
329 {
330 eMode = eLine;
331 bCardsFinished = qtrue;
332 break;
333 }
334 else
335 if (!Q_stricmp(sLine, "(#DOTENTRY)"))
336 {
337 eMode = eDotEntry;
338 bCardsFinished = qtrue;
339 break;
340 }
341 else
342 {
343 #ifndef FINAL_BUILD
344 Com_Printf( S_COLOR_YELLOW "CG_Credits_Init(): bad keyword \"%s\"!\n", sLine );
345 #endif
346 eMode = eNothing;
347 }
348 }
349 else
350 {
351 // I guess not...
352 //
353 bWasCommand = qfalse;
354 break;
355 }
356 }
357 else
358 {
359 // must be a letter...
360 //
361 if (uiLetter > 255)
362 {
363 Q_strcat(sLine, sizeof(sLine), va("%c%c",uiLetter >> 8, uiLetter & 0xFF));
364 }
365 else
366 {
367 Q_strcat(sLine, sizeof(sLine), va("%c",uiLetter & 0xFF));
368 }
369 }
370 }
371
372 // command?...
373 //
374 if (bWasCommand)
375 {
376 // this'll just be a mode change, so ignore...
377 //
378 }
379 else
380 {
381 // else we've got some text to display...
382 //
383 switch (eMode)
384 {
385 case eNothing: break;
386 case eLine:
387 {
388 CreditLine_t CreditLine;
389 CreditLine.iLine = iLineNumber++;
390 CreditLine.strText = sLine;
391
392 CreditData.CreditLines.push_back( CreditLine );
393 }
394 break;
395
396 case eDotEntry:
397 {
398 CreditLine_t CreditLine;
399 CreditLine.iLine = iLineNumber;
400 CreditLine.bDotted = true;
401
402 std::string strResult(sLine);
403 const char *p;
404 while ((p=GetSubString(strResult)) != NULL)
405 {
406 if (CreditLine.strText.IsEmpty())
407 {
408 CreditLine.strText = p;
409 }
410 else
411 {
412 CreditLine.vstrText.push_back( UpperCaseFirstLettersOnly(p) );
413 }
414 }
415
416 if (!CreditLine.strText.IsEmpty() && CreditLine.vstrText.size())
417 {
418 // sort entries RHS dotted entries by alpha...
419 //
420 std::sort( CreditLine.vstrText.begin(), CreditLine.vstrText.end(), SortBySurname );
421
422 CreditData.CreditLines.push_back( CreditLine );
423 iLineNumber += CreditLine.vstrText.size();
424 }
425 }
426 break;
427
428 case eTitle:
429 {
430 iLineNumber++; // leading blank line
431
432 CreditLine_t CreditLine;
433 CreditLine.iLine = iLineNumber++;
434 CreditLine.strText = Capitalize(sLine);
435
436 CreditData.CreditLines.push_back( CreditLine );
437
438 iLineNumber++; // trailing blank line
439 break;
440 }
441 case eCard:
442 {
443 CreditCard_t CreditCard;
444
445 std::string strResult(sLine);
446 const char *p;
447 while ((p=GetSubString(strResult)) != NULL)
448 {
449 if (CreditCard.strTitle.IsEmpty())
450 {
451 CreditCard.strTitle = Capitalize( p );
452 }
453 else
454 {
455 CreditCard.vstrText.push_back( UpperCaseFirstLettersOnly( p ) );
456 }
457 }
458
459 if (!CreditCard.strTitle.IsEmpty())
460 {
461 // sort entries by alpha...
462 //
463 std::sort( CreditCard.vstrText.begin(), CreditCard.vstrText.end(), SortBySurname );
464
465 CreditData.CreditCards.push_back(CreditCard);
466 }
467 }
468 break;
469 default:
470 break;
471 }
472 }
473 }
474
475 cgi_Z_Free(psMallocText);
476 CreditData.iStartTime = cg.time;
477 }
478
CG_Credits_Running(void)479 qboolean CG_Credits_Running( void )
480 {
481 return CreditData.Running();
482 }
483
484 // returns qtrue if still drawing...
485 //
CG_Credits_Draw(void)486 qboolean CG_Credits_Draw( void )
487 {
488 if ( CG_Credits_Running() )
489 {
490 const int iFontHeight = (int) (1.5f * (float) cgi_R_Font_HeightPixels(ghFontHandle, gfFontScale)); // taiwanese & japanese need 1.5 fontheight spacing
491
492 // cgi_R_SetColor( *gpv4Color );
493
494 // display cards first...
495 //
496 if (CreditData.CreditCards.size())
497 {
498 // grab first card off the list (we know there's at least one here, so...)
499 //
500 CreditCard_t &CreditCard = (*CreditData.CreditCards.begin());
501
502 if (CreditCard.iTime == -1)
503 {
504 // onceonly time init...
505 //
506 CreditCard.iTime = cg.time;
507 }
508
509 // play with the alpha channel for fade up/down...
510 //
511 const float fMilliSecondsElapsed = cg.time - CreditCard.iTime;
512 const float fSecondsElapsed = fMilliSecondsElapsed / 1000.0f;
513 if (fSecondsElapsed < fCARD_FADESECONDS)
514 {
515 // fading up...
516 //
517 gv4Color[3] = fSecondsElapsed / fCARD_FADESECONDS;
518 // OutputDebugString(va("fade up: %f\n",gv4Color[3]));
519 }
520 else
521 if (fSecondsElapsed > fCARD_FADESECONDS + fCARD_SUSTAINSECONDS)
522 {
523 // fading down...
524 //
525 const float fFadeDownSeconds = fSecondsElapsed - (fCARD_FADESECONDS + fCARD_SUSTAINSECONDS);
526 gv4Color[3] = 1.0f - (fFadeDownSeconds / fCARD_FADESECONDS);
527 // OutputDebugString(va("fade dw: %f\n",gv4Color[3]));
528 }
529 else
530 {
531 gv4Color[3] = 1.0f;
532 // OutputDebugString(va("normal: %f\n",gv4Color[3]));
533 }
534 if (gv4Color[3] < 0.0f)
535 gv4Color[3] = 0.0f; // ... otherwise numbers that have dipped slightly -ve flash up fullbright after fade down
536
537 //
538 // how many lines is it?
539 //
540 int iLines = CreditCard.vstrText.size() + 2; // +2 for title itself & one seperator line
541 //
542 int iYpos = (SCREEN_HEIGHT - (iLines * iFontHeight))/2;
543 //
544 // draw it, title first...
545 //
546 int iWidth = CreditCard.strTitle.GetPixelLength();
547 int iXpos = (SCREEN_WIDTH - iWidth)/2;
548 cgi_R_Font_DrawString(iXpos, iYpos, CreditCard.strTitle.c_str(), gv4Color, ghFontHandle, -1, gfFontScale);
549 //
550 iYpos += iFontHeight*2; // skip blank line then move to main pos
551 //
552 for (size_t i=0; i<CreditCard.vstrText.size(); i++)
553 {
554 StringAndSize_t &StringAndSize = CreditCard.vstrText[i];
555 iWidth = StringAndSize.GetPixelLength();
556 iXpos = (SCREEN_WIDTH - iWidth)/2;
557 cgi_R_Font_DrawString(iXpos, iYpos, StringAndSize.c_str(), gv4Color, ghFontHandle, -1, gfFontScale);
558 iYpos += iFontHeight;
559 }
560
561 // next card?...
562 //
563 if (fSecondsElapsed > fCARD_FADESECONDS + fCARD_SUSTAINSECONDS + fCARD_FADESECONDS)
564 {
565 // yep, so erase the first entry (which will trigger the next one to be initialised on re-entry)...
566 //
567 CreditData.CreditCards.erase( CreditData.CreditCards.begin() );
568
569 if (!CreditData.CreditCards.size())
570 {
571 // all cards gone, so re-init timer for lines...
572 //
573 CreditData.iStartTime = cg.time;
574 }
575 }
576 //
577 return qtrue;
578 }
579 else
580 {
581 // doing scroll text...
582 //
583 if (CreditData.CreditLines.size())
584 {
585 // process all lines...
586 //
587 const float fMilliSecondsElapsed = cg.time - CreditData.iStartTime;
588 const float fSecondsElapsed = fMilliSecondsElapsed / 1000.0f;
589
590 bool bEraseOccured = false;
591 for (CreditLines_t::iterator it = CreditData.CreditLines.begin(); it != CreditData.CreditLines.end(); bEraseOccured ? it : ++it)
592 {
593 CreditLine_t &CreditLine = (*it);
594 bEraseOccured = false;
595
596 static const float fPixelsPerSecond = ((float)SCREEN_HEIGHT / fLINE_SECONDTOSCROLLUP);
597
598 int iYpos = SCREEN_HEIGHT + (CreditLine.iLine * iFontHeight);
599 iYpos-= (int) (fPixelsPerSecond * fSecondsElapsed);
600
601 int iTextLinesThisItem = Q_max( (int)CreditLine.vstrText.size(), 1);
602 if (iYpos + (iTextLinesThisItem * iFontHeight) < 0)
603 {
604 // scrolled off top of screen, so erase it...
605 //
606 it = CreditData.CreditLines.erase( it );
607 bEraseOccured = true;
608 }
609 else
610 if (iYpos < SCREEN_HEIGHT)
611 {
612 // onscreen, so print it...
613 //
614 bool bIsDotted = !!CreditLine.vstrText.size(); // eg "STUNTS ...................... MR ED"
615
616 int iWidth = CreditLine.strText.GetPixelLength();
617 int iXpos = bIsDotted ? 4 : (SCREEN_WIDTH - iWidth)/2;
618
619 gv4Color[3] = 1.0f;
620
621 cgi_R_Font_DrawString(iXpos, iYpos, CreditLine.strText.c_str(), gv4Color, ghFontHandle, -1, gfFontScale);
622
623 // now print any dotted members...
624 //
625 for (size_t i=0; i<CreditLine.vstrText.size(); i++)
626 {
627 StringAndSize_t &StringAndSize = CreditLine.vstrText[i];
628 iWidth = StringAndSize.GetPixelLength();
629 iXpos = (SCREEN_WIDTH-4 - iWidth);
630 cgi_R_Font_DrawString(iXpos, iYpos, StringAndSize.c_str(), gv4Color, ghFontHandle, -1, gfFontScale);
631 iYpos += iFontHeight;
632 }
633 }
634 }
635
636 return qtrue;
637 }
638 }
639 }
640
641 return qfalse;
642 }
643
644
645
646 ////////////////////// eof /////////////////////
647
648