1 /*
2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
3 *
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on the
6 * source.
7 *
8 */
9
10
11
12 #include <cstdlib>
13
14 #include "cfile/cfile.h"
15 #include "freespace.h"
16 #include "gamesequence/gamesequence.h"
17 #include "gamesnd/eventmusic.h" /* for Master_event_music_volume */
18 #include "gamesnd/gamesnd.h"
19 #include "globalincs/alphacolors.h"
20 #include "graphics/font.h"
21 #include "io/key.h"
22 #include "io/timer.h"
23 #include "localization/localize.h"
24 #include "menuui/credits.h"
25 #include "missionui/missionscreencommon.h"
26 #include "parse/parselo.h"
27 #include "playerman/player.h"
28 #include "sound/audiostr.h"
29 #include "ui/ui.h"
30
31 #include <lua.h>
32
33
34 // This is the fs2_open credit list, please only add yourself if you have actually contributed code
35 // Rules!
36 const char *fs2_open_credit_text =
37 "SOURCE CODE PROJECT STAFF:\n"
38 "\n"
39 "\n"
40 "Project Leader:\n"
41 "\n"
42 "Tyler \"DahBlount\" Blount\n"
43 "\n"
44 "Senior Advisors:\n"
45 "\n"
46 "Ian \"Goober5000\" Warfield\n"
47 "Fabian \"The E\" Woltermann\n"
48 "Cliff \"chief1983\" Gordon\n"
49 "Taylor Richards\n"
50 "Michael \"Zacam\" LaFleur\n"
51 "Edward \"Inquisitor\" Gardner\n"
52 "\n"
53 "Programmers:\n"
54 "\n"
55 "Mike \"Bobboau\" Abegg\n"
56 "Hassan \"Karajorma\" Kazmi\n"
57 "Derek \"Kazan\" Meek\n"
58 "Nick \"phreak\" Iannetta\n"
59 "argv[-1], Backslash, Baezon\n"
60 "CommanderDJ, Cyborg, DTP\n"
61 "Echelon9, EdrickV, Eternal1\n"
62 "Flaming_Sword, Fry_Day, FUBAR\n"
63 "Hery, Iss Mneur, jg18\n"
64 "m!m, MageKing17, mrduckman\n"
65 "niffiwan, penguin, portej05\n"
66 "RandomTiger, Righteous1, Sesquipedalian\n"
67 "Shade, Sticks, Sushi\n"
68 "Swifty, UnknownPlayer, Valathil\n"
69 "Wanderer, WMCoolmon, wookieejedi\n"
70 "Yarn, z64555, zookeeper\n"
71 "\n"
72 "\n"
73 "Web Support:\n"
74 "\n"
75 "http://www.hard-light.net/\n"
76 "http://scp.indiegames.us/\n"
77 "\n"
78 "\n"
79 "Special thanks to:\n"
80 "\n"
81 "Fury\n"
82 "kasperl\n"
83 "QuantumDelta\n"
84 "redmenace\n"
85 "\n"
86 "\n"
87 "Linux and OSX version with the support of\n"
88 "the icculus.org FreeSpace2 project:\n"
89 "Steven \"relnev\" Fuller\n"
90 "Ryan Gordon\n"
91 "Charles Mason\n"
92 "Dan Olson\n"
93 "Taylor Richards\n"
94 "\"tigital\"\n"
95 "\n"
96 "\n"
97 "Very special thanks to:\n"
98 "\n"
99 "Volition for making FS2 such a great game\n"
100 "Dave Baranec for giving us the code and keeping us sanity checked\n"
101 "\n"
102 "\n"
103 "FS2_Open makes use of the following technologies:\n"
104 "\n"
105 "Ogg Vorbis - (C) 2005, Xiph.Org Foundation\n"
106 "JPEG - Independent JPEG Group, (C) 1991-1998, Thomas G. Lane\n"
107 "libpng - Copyright (C) 1998-2010 Glenn Randers-Pehrson\n"
108 "liblua (" LUA_RELEASE ") - " LUA_COPYRIGHT "\n"
109 "zlib - Copyright (C) 1995-2005 Jean-loup Gailly and Mark Adler\n"
110 "FXAA - Copyright (c) 2010 NVIDIA Corporation. All rights reserved.\n"
111 "libpcp - Copyright (c) 2013 by Cisco Systems, Inc.\n"
112 "This software uses libraries from the FFmpeg project under the LGPLv2.1\n"
113 "\n"
114 "\n"
115 "\n";
116
117 const char *unmodified_credits = "ORIGINAL VOLITION STAFF:\n\n\n";
118 const char *mod_check = "Design:";
119
120 #define NUM_BUTTONS 5
121 #define DEFAULT_NUM_IMAGES 46
122
123 #define TECH_DATABASE_BUTTON 0
124 #define SIMULATOR_BUTTON 1
125 #define CUTSCENES_BUTTON 2
126 #define CREDITS_BUTTON 3
127 #define EXIT_BUTTON 4
128
129 // inidicies for coordinates
130 #define CREDITS_X_COORD 0
131 #define CREDITS_Y_COORD 1
132 #define CREDITS_W_COORD 2
133 #define CREDITS_H_COORD 3
134
135 static const char* Credits_bitmap_fname[GR_NUM_RESOLUTIONS] = {
136 "Credits", // GR_640
137 "2_Credits"
138 };
139
140 static const char* Credits_bitmap_mask_fname[GR_NUM_RESOLUTIONS] = {
141 "Credits-M", // GR_640
142 "2_Credits-M"
143 };
144
145 int Credits_image_coords[GR_NUM_RESOLUTIONS][4] = {
146 {
147 219, 15, 394, 286 // GR_640
148 },
149 {
150 351, 25, 629, 455 // GR_1024
151 }
152 };
153
154 // x, y, w, h
155 int Credits_text_coords[GR_NUM_RESOLUTIONS][4] = {
156 {
157 26, 316, 482, 157 // GR_640
158 },
159 {
160 144, 507, 568, 249 // GR_640
161 }
162 };
163
164 struct credits_screen_buttons {
165 const char *filename;
166 int x, y, xt, yt;
167 int hotspot;
168 UI_BUTTON button; // because we have a class inside this struct, we need the constructor below..
169
credits_screen_buttonscredits_screen_buttons170 credits_screen_buttons(const char *name, int x1, int y1, int xt1, int yt1, int h) : filename(name), x(x1), y(y1), xt(xt1), yt(yt1), hotspot(h) {}
171 };
172
173 static int Background_bitmap;
174 static UI_WINDOW Ui_window;
175
176 static credits_screen_buttons Buttons[NUM_BUTTONS][GR_NUM_RESOLUTIONS] = {
177 //XSTR:OFF
178 {
179 credits_screen_buttons("TDB_00", 7, 3, 37, 7, 0), // GR_640
180 credits_screen_buttons("2_TDB_00", 12, 5, 59, 12, 0) // GR_1024
181 },
182 {
183 credits_screen_buttons("TDB_01", 7, 18, 37, 23, 1), // GR_640
184 credits_screen_buttons("2_TDB_01", 12, 31, 59, 37, 1) // GR_1024
185 },
186 {
187 credits_screen_buttons("TDB_02", 7, 34, 37, 38, 2), // GR_640
188 credits_screen_buttons("2_TDB_02", 12, 56, 59, 62, 2) // GR_1024
189 },
190 {
191 credits_screen_buttons("TDB_03", 7, 49, 37, 54, 3), // GR_640
192 credits_screen_buttons("2_TDB_03", 12, 81, 59, 88, 3) // GR_1024
193 },
194 {
195 credits_screen_buttons("CRB_04", 571, 425, 588, 413, 4), // GR_640
196 credits_screen_buttons("2_CRB_04", 914, 681, 953, 668, 4) // GR_1024
197 }
198 //XSTR:ON
199 };
200
201 static char Credits_music_name[NAME_LENGTH];
202 static int Credits_music_handle = -1;
203 static int Credits_music_begin_timestamp;
204
205 static int Credits_frametime; // frametime of credits_do_frame() loop in ms
206 static int Credits_last_time; // timestamp used to calc frametime (in ms)
207 static float Credits_counter;
208
209 static int Credits_num_images;
210 static int Credits_artwork_index;
211 static SCP_vector<int> Credits_bmps;
212
213 // Positions for credits...
214 float Credit_start_pos, Credit_stop_pos, Credit_position = 0.0f;
215
216 static int Credits_music_delay = 2000;
217 static float Credits_scroll_rate = 15.0f;
218 static float Credits_artwork_display_time = 9.0f;
219 static float Credits_artwork_fade_time = 1.0f;
220
221 static SCP_vector<SCP_string> Credit_text_parts;
222
223 static bool Credits_parsed;
224
225 enum CreditsPosition
226 {
227 START,
228 END
229 };
230
231 static CreditsPosition SCP_credits_position = START;
232
credits_stop_music(bool fade)233 void credits_stop_music(bool fade)
234 {
235 if ( Credits_music_handle != -1 ) {
236 audiostream_close_file(Credits_music_handle, fade);
237 Credits_music_handle = -1;
238 }
239 }
240
credits_load_music(char * fname)241 void credits_load_music(char* fname)
242 {
243 if ( Credits_music_handle != -1 ){
244 return;
245 }
246
247 if ( fname && *fname ){
248 Credits_music_handle = audiostream_open( fname, ASF_MENUMUSIC );
249 }
250 }
251
credits_start_music()252 void credits_start_music()
253 {
254 if (Credits_music_handle != -1) {
255 if ( !audiostream_is_playing(Credits_music_handle) ){
256 audiostream_play(Credits_music_handle, Master_event_music_volume, 1);
257 }
258 } else {
259 nprintf(("Warning", "Cannot play credits music\n"));
260 }
261 }
262
credits_screen_button_pressed(int n)263 int credits_screen_button_pressed(int n)
264 {
265 switch (n) {
266 case TECH_DATABASE_BUTTON:
267 gamesnd_play_iface(InterfaceSounds::SWITCH_SCREENS);
268 gameseq_post_event(GS_EVENT_TECH_MENU);
269 return 1;
270
271 case SIMULATOR_BUTTON:
272 gamesnd_play_iface(InterfaceSounds::SWITCH_SCREENS);
273 gameseq_post_event(GS_EVENT_SIMULATOR_ROOM);
274 return 1;
275
276 case CUTSCENES_BUTTON:
277 gamesnd_play_iface(InterfaceSounds::SWITCH_SCREENS);
278 gameseq_post_event(GS_EVENT_GOTO_VIEW_CUTSCENES_SCREEN);
279 return 1;
280
281 case EXIT_BUTTON:
282 gamesnd_play_iface(InterfaceSounds::COMMIT_PRESSED);
283 gameseq_post_event(GS_EVENT_MAIN_MENU);
284 game_flush();
285 break;
286 }
287
288 return 0;
289 }
290
credits_parse_table(const char * filename)291 void credits_parse_table(const char* filename)
292 {
293 try
294 {
295 read_file_text(filename, CF_TYPE_TABLES);
296 reset_parse();
297
298 // any metadata?
299 if (optional_string("$Music:"))
300 {
301 stuff_string(Credits_music_name, F_NAME, NAME_LENGTH);
302 }
303 if (optional_string("$Number of Images:"))
304 {
305 int temp;
306 stuff_int(&temp);
307 if (temp > 0)
308 Credits_num_images = temp;
309 }
310 if (optional_string("$Start Image Index:"))
311 {
312 stuff_int(&Credits_artwork_index);
313
314 // bounds check
315 if (Credits_artwork_index < 0)
316 {
317 Credits_artwork_index = 0;
318 }
319 else if (Credits_artwork_index >= Credits_num_images)
320 {
321 Credits_artwork_index = Credits_num_images - 1;
322 }
323 }
324 if (optional_string("$Text scroll rate:"))
325 {
326 stuff_float(&Credits_scroll_rate);
327 if (Credits_scroll_rate < 0.01f)
328 Credits_scroll_rate = 0.01f;
329 }
330 if (optional_string("$Artworks display time:"))
331 {
332 stuff_float(&Credits_artwork_display_time);
333 if (Credits_artwork_display_time < 0.01f)
334 Credits_artwork_display_time = 0.01f;
335 }
336 if (optional_string("$Artworks fade time:"))
337 {
338 stuff_float(&Credits_artwork_fade_time);
339 if (Credits_artwork_fade_time < 0.01f)
340 Credits_artwork_fade_time = 0.01f;
341 }
342 if (optional_string("$SCP Credits position:"))
343 {
344 char mode[NAME_LENGTH];
345
346 stuff_string(mode, F_NAME, NAME_LENGTH);
347
348 if (!stricmp(mode, "Start"))
349 SCP_credits_position = START;
350 else if (!stricmp(mode, "End"))
351 SCP_credits_position = END;
352 else
353 Warning(LOCATION, "Unknown credits position mode \"%s\".", mode);
354 }
355
356 ignore_white_space();
357
358 SCP_string credits_text;
359 SCP_string line;
360
361 SCP_vector<int> charNum;
362 SCP_vector<const char*> lines;
363 int numLines = -1;
364
365 bool first_run = true;
366 while (!check_for_eof_raw() && !check_for_string_raw("#end"))
367 {
368 // Read in a line of text
369 stuff_string_line(line);
370
371 // This is a bit odd but it means if a total conversion uses different credits the
372 // Volition credit won't happen
373 // Also don't append the default credits anymore when there was already a parsed table
374 if (first_run && !Credits_parsed && line == mod_check)
375 {
376 credits_text.append(unmodified_credits);
377 }
378
379 first_run = false;
380
381 if (line.empty())
382 {
383 // If the line is empty then just append a newline, don't bother with splitting it first
384 credits_text.append("\n");
385 }
386 else
387 {
388 // split_str doesn't take care of this.
389 charNum.clear();
390
391 // Split the string into multiple lines if it's too long
392 numLines = split_str(line.c_str(), Credits_text_coords[gr_screen.res][2], charNum, lines);
393
394 // Make sure that we have valid data
395 Assertion(lines.size() == (size_t)numLines, "split_str reported %d lines but vector contains " SIZE_T_ARG " entries!", numLines, lines.size());
396
397 Assertion(lines.size() <= charNum.size(),
398 "Something has gone wrong while splitting strings. Got " SIZE_T_ARG " lines but only " SIZE_T_ARG " chacter lengths.",
399 lines.size(), charNum.size());
400
401 // Now add all splitted lines to the credit text and append a newline to the end
402 for (int i = 0; i < numLines; i++)
403 {
404 credits_text.append(SCP_string(lines[i], charNum[i]));
405 credits_text.append("\n");
406 }
407 }
408 }
409
410 Credit_text_parts.push_back(credits_text);
411
412 Credits_parsed = true;
413 }
414 catch (const parse::ParseException& e)
415 {
416 mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", filename, e.what()));
417 return;
418 }
419 }
420
credits_parse()421 void credits_parse()
422 {
423 // Parse main table
424 credits_parse_table("credits.tbl");
425
426 // Parse modular tables
427 parse_modular_table("*-crd.tbm", credits_parse_table);
428 }
429
credits_init()430 void credits_init()
431 {
432 int i;
433 credits_screen_buttons *b;
434
435 // pre-initialize
436 Credits_num_images = DEFAULT_NUM_IMAGES;
437 Credits_artwork_index = -1;
438
439 // this is moved up here so we can override it if desired
440 strcpy_s(Credits_music_name, "Cinema");
441
442 // parse credits early so as to set up any overrides (for music and such)
443 Credits_parsed = false;
444 credits_parse();
445
446 // we could conceivably have specified a number of images but not an index,
447 // so if that's the case, set the value here
448 if (Credits_artwork_index < 0) {
449 Credits_artwork_index = Random::next(Credits_num_images);
450 }
451
452 int credits_spooled_music_index = event_music_get_spooled_music_index(Credits_music_name);
453 if(credits_spooled_music_index != -1){
454 char *credits_wavfile_name = Spooled_music[credits_spooled_music_index].filename;
455 if(credits_wavfile_name != NULL){
456 credits_load_music(credits_wavfile_name);
457 }
458 }
459
460 // Use this id to trigger the start of music playing on the briefing screen
461 Credits_music_begin_timestamp = timestamp(Credits_music_delay);
462
463 Credits_frametime = 0;
464 Credits_last_time = timer_get_milliseconds();
465
466 if (!Credits_parsed)
467 {
468 Credit_text_parts.push_back(SCP_string("No credits available.\n"));
469 }
470 else
471 {
472 switch (SCP_credits_position)
473 {
474 case START:
475 Credit_text_parts.insert(Credit_text_parts.begin(), fs2_open_credit_text);
476 break;
477
478 case END:
479 Credit_text_parts.push_back(fs2_open_credit_text);
480 break;
481
482 default:
483 Error(LOCATION, "Unimplemented credits position %d. Get a coder!", (int) SCP_credits_position);
484 break;
485 }
486 }
487
488 int ch;
489 SCP_vector<SCP_string>::iterator iter;
490
491 for (iter = Credit_text_parts.begin(); iter != Credit_text_parts.end(); ++iter)
492 {
493 for (SCP_string::iterator ii = iter->begin(); ii != iter->end(); ++ii)
494 {
495 ch = *ii;
496 switch (ch)
497 {
498 case -4:
499 ch = 129;
500 break;
501
502 case -28:
503 ch = 132;
504 break;
505
506 case -10:
507 ch = 148;
508 break;
509
510 case -23:
511 ch = 130;
512 break;
513
514 case -30:
515 ch = 131;
516 break;
517
518 case -25:
519 ch = 135;
520 break;
521
522 case -21:
523 ch = 137;
524 break;
525
526 case -24:
527 ch = 138;
528 break;
529
530 case -17:
531 ch = 139;
532 break;
533
534 case -18:
535 ch = 140;
536 break;
537
538 case -60:
539 ch = 142;
540 break;
541
542 case -55:
543 ch = 144;
544 break;
545
546 case -12:
547 ch = 147;
548 break;
549
550 case -14:
551 ch = 149;
552 break;
553
554 case -5:
555 ch = 150;
556 break;
557
558 case -7:
559 ch = 151;
560 break;
561
562 case -42:
563 ch = 153;
564 break;
565
566 case -36:
567 ch = 154;
568 break;
569
570 case -31:
571 ch = 160;
572 break;
573
574 case -19:
575 ch = 161;
576 break;
577
578 case -13:
579 ch = 162;
580 break;
581
582 case -6:
583 ch = 163;
584 break;
585
586 case -32:
587 ch = 133;
588 break;
589
590 case -22:
591 ch = 136;
592 break;
593
594 case -20:
595 ch = 141;
596 break;
597 }
598
599 *ii = (char) ch;
600 }
601 }
602
603 int temp_h;
604 int h = 0;
605
606 for (iter = Credit_text_parts.begin(); iter != Credit_text_parts.end(); ++iter)
607 {
608 gr_get_string_size(NULL, &temp_h, iter->c_str(), (int)iter->length());
609
610 h = h + temp_h;
611 }
612
613 Credit_start_pos = i2fl(Credits_text_coords[gr_screen.res][CREDITS_H_COORD]);
614 Credit_stop_pos = -i2fl(h);
615 Credit_position = Credit_start_pos;
616
617 Ui_window.create(0, 0, gr_screen.max_w_unscaled, gr_screen.max_h_unscaled, 0);
618 Ui_window.set_mask_bmap(Credits_bitmap_mask_fname[gr_screen.res]);
619 common_set_interface_palette("InterfacePalette"); // set the interface palette
620
621 for (i=0; i<NUM_BUTTONS; i++) {
622 b = &Buttons[i][gr_screen.res];
623
624 b->button.create(&Ui_window, "", b->x, b->y, 60, 30, (i < 2), 1);
625 // set up callback for when a mouse first goes over a button
626 b->button.set_highlight_action(common_play_highlight_sound);
627 b->button.set_bmaps(b->filename);
628 b->button.link_hotspot(b->hotspot);
629 }
630
631 // add some text
632 Ui_window.add_XSTR("Technical Database", 1055, Buttons[TECH_DATABASE_BUTTON][gr_screen.res].xt, Buttons[TECH_DATABASE_BUTTON][gr_screen.res].yt, &Buttons[TECH_DATABASE_BUTTON][gr_screen.res].button, UI_XSTR_COLOR_GREEN);
633 Ui_window.add_XSTR("Mission Simulator", 1056, Buttons[SIMULATOR_BUTTON][gr_screen.res].xt, Buttons[SIMULATOR_BUTTON][gr_screen.res].yt, &Buttons[SIMULATOR_BUTTON][gr_screen.res].button, UI_XSTR_COLOR_GREEN);
634 Ui_window.add_XSTR("Cutscenes", 1057, Buttons[CUTSCENES_BUTTON][gr_screen.res].xt, Buttons[CUTSCENES_BUTTON][gr_screen.res].yt, &Buttons[CUTSCENES_BUTTON][gr_screen.res].button, UI_XSTR_COLOR_GREEN);
635 Ui_window.add_XSTR("Credits", 1058, Buttons[CREDITS_BUTTON][gr_screen.res].xt, Buttons[CREDITS_BUTTON][gr_screen.res].yt, &Buttons[CREDITS_BUTTON][gr_screen.res].button, UI_XSTR_COLOR_GREEN);
636 Ui_window.add_XSTR("Exit", 1420, Buttons[EXIT_BUTTON][gr_screen.res].xt, Buttons[EXIT_BUTTON][gr_screen.res].yt, &Buttons[EXIT_BUTTON][gr_screen.res].button, UI_XSTR_COLOR_PINK);
637
638 if (Player->flags & PLAYER_FLAGS_IS_MULTI) {
639 Buttons[SIMULATOR_BUTTON][gr_screen.res].button.disable();
640 Buttons[CUTSCENES_BUTTON][gr_screen.res].button.disable();
641 }
642
643 Buttons[EXIT_BUTTON][gr_screen.res].button.set_hotkey(KEY_CTRLED | KEY_ENTER);
644
645 Background_bitmap = bm_load(Credits_bitmap_fname[gr_screen.res]);
646
647 Credits_bmps.resize(Credits_num_images);
648 for (i=0; i<Credits_num_images; i++) {
649 Credits_bmps[i] = -1;
650 }
651 }
652
credits_close()653 void credits_close()
654 {
655 int i;
656
657 for (i=0; i<Credits_num_images; i++) {
658 if (Credits_bmps[i] >= 0){
659 bm_release(Credits_bmps[i]);
660 }
661 }
662 Credits_bmps.clear();
663
664 credits_stop_music(true);
665
666 Credit_text_parts.clear();
667
668 if (Background_bitmap){
669 bm_release(Background_bitmap);
670 }
671
672 Ui_window.destroy();
673 common_free_interface_palette(); // restore game palette
674 }
675
credits_do_frame(float)676 void credits_do_frame(float /*frametime*/)
677 {
678 GR_DEBUG_SCOPE("Credits do frame");
679
680 int i, k, next, percent, bm1, bm2;
681 int bx1, by1, bw1, bh1;
682 int bx2, by2, bw2, bh2;
683
684 // Use this id to trigger the start of music playing on the credits screen
685 if ( timestamp_elapsed(Credits_music_begin_timestamp) ) {
686 Credits_music_begin_timestamp = 0;
687 credits_start_music();
688 }
689
690 k = Ui_window.process();
691 switch (k) {
692 case KEY_ESC:
693 gameseq_post_event(GS_EVENT_MAIN_MENU);
694 key_flush();
695 break;
696
697 case KEY_CTRLED | KEY_UP:
698 case KEY_SHIFTED | KEY_TAB:
699 if ( !(Player->flags & PLAYER_FLAGS_IS_MULTI) ) {
700 credits_screen_button_pressed(CUTSCENES_BUTTON);
701 break;
702 }
703 // else, react like tab key.
704 FALLTHROUGH;
705
706 case KEY_CTRLED | KEY_DOWN:
707 case KEY_TAB:
708 credits_screen_button_pressed(TECH_DATABASE_BUTTON);
709 break;
710
711 default:
712 break;
713 } // end switch
714
715 for (i=0; i<NUM_BUTTONS; i++){
716 if (Buttons[i][gr_screen.res].button.pressed()){
717 if (credits_screen_button_pressed(i)){
718 return;
719 }
720 }
721 }
722
723 gr_reset_clip();
724 GR_MAYBE_CLEAR_RES(Background_bitmap);
725 if (Background_bitmap >= 0) {
726 gr_set_bitmap(Background_bitmap);
727 gr_bitmap(0, 0, GR_RESIZE_MENU);
728 }
729
730 percent = (int) (100.0f - (Credits_artwork_display_time - Credits_counter) * 100.0f / Credits_artwork_fade_time);
731 if (percent < 0){
732 percent = 0;
733 }
734
735 next = Credits_artwork_index + 1;
736 if (next >= Credits_num_images){
737 next = 0;
738 }
739
740 if (Credits_bmps[Credits_artwork_index] < 0) {
741 char buf[40];
742
743 if (gr_screen.res == GR_1024) {
744 sprintf(buf, NOX("2_CrIm%.2d"), Credits_artwork_index);
745 } else {
746 sprintf(buf, NOX("CrIm%.2d"), Credits_artwork_index);
747 }
748 Credits_bmps[Credits_artwork_index] = bm_load(buf);
749 }
750
751 if (Credits_bmps[next] < 0) {
752 char buf[40];
753
754 if (gr_screen.res == GR_1024) {
755 sprintf(buf, NOX("2_CrIm%.2d"), next);
756 } else {
757 sprintf(buf, NOX("CrIm%.2d"), next);
758 }
759 Credits_bmps[next] = bm_load(buf);
760 }
761
762 bm1 = Credits_bmps[Credits_artwork_index];
763 bm2 = Credits_bmps[next];
764
765 if((bm1 != -1) && (bm2 != -1)){
766 GR_DEBUG_SCOPE("Render credits bitmap");
767
768 Assert(percent >= 0 && percent <= 100);
769
770 // get width and height
771 bm_get_info(bm1, &bw1, &bh1, NULL, NULL, NULL);
772 bm_get_info(bm2, &bw2, &bh2, NULL, NULL, NULL);
773
774 // determine where to draw the coords
775 bx1 = Credits_image_coords[gr_screen.res][CREDITS_X_COORD] + ((Credits_image_coords[gr_screen.res][CREDITS_W_COORD] - bw1)/2);
776 by1 = Credits_image_coords[gr_screen.res][CREDITS_Y_COORD] + ((Credits_image_coords[gr_screen.res][CREDITS_H_COORD] - bh1)/2);
777 bx2 = Credits_image_coords[gr_screen.res][CREDITS_X_COORD] + ((Credits_image_coords[gr_screen.res][CREDITS_W_COORD] - bw2)/2);
778 by2 = Credits_image_coords[gr_screen.res][CREDITS_Y_COORD] + ((Credits_image_coords[gr_screen.res][CREDITS_H_COORD] - bh2)/2);
779
780 auto alpha = (float)percent / 100.0f;
781
782 gr_set_bitmap(bm1, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, 1.0f - alpha);
783 gr_bitmap(bx1, by1, GR_RESIZE_MENU);
784
785 gr_set_bitmap(bm2, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, alpha);
786 gr_bitmap(bx2, by2, GR_RESIZE_MENU);
787 }
788
789 Ui_window.draw();
790
791 for (i=TECH_DATABASE_BUTTON; i<=CREDITS_BUTTON; i++){
792 if (Buttons[i][gr_screen.res].button.button_down()){
793 break;
794 }
795 }
796
797 if (i > CREDITS_BUTTON){
798 Buttons[CREDITS_BUTTON][gr_screen.res].button.draw_forced(2);
799 }
800
801 gr_set_clip(Credits_text_coords[gr_screen.res][CREDITS_X_COORD], Credits_text_coords[gr_screen.res][CREDITS_Y_COORD], Credits_text_coords[gr_screen.res][CREDITS_W_COORD], Credits_text_coords[gr_screen.res][CREDITS_H_COORD], GR_RESIZE_MENU);
802 font::set_font(font::FONT1);
803 gr_set_color_fast(&Color_normal);
804
805 int y_offset = 0;
806 for (SCP_vector<SCP_string>::iterator iter = Credit_text_parts.begin(); iter != Credit_text_parts.end(); ++iter)
807 {
808 size_t currentPos = 0;
809 size_t lineEnd;
810 do
811 {
812 int height;
813 int width;
814 lineEnd = iter->find('\n', currentPos);
815
816 auto length = lineEnd - currentPos;
817 if (lineEnd == SCP_string::npos)
818 {
819 length = std::numeric_limits<size_t>::max();
820 }
821
822 gr_get_string_size(&width, &height, iter->c_str() + currentPos, static_cast<int>(length));
823 // Check if the text part is actually visible
824 if (Credit_position + y_offset + height > 0.0f)
825 {
826 float x = static_cast<float>((gr_screen.clip_width_unscaled - width) / 2);
827 gr_string(x, Credit_position + y_offset, iter->c_str() + currentPos, GR_RESIZE_MENU, static_cast<int>(length));
828 }
829
830 y_offset += height;
831 currentPos = lineEnd + 1;
832 } while (lineEnd < iter->length() && lineEnd != SCP_string::npos);
833 }
834
835 int temp_time;
836 temp_time = timer_get_milliseconds();
837
838 Credits_frametime = temp_time - Credits_last_time;
839 Credits_last_time = temp_time;
840 timestamp_inc(i2f(Credits_frametime) / TIMESTAMP_FREQUENCY);
841
842 float fl_frametime = i2fl(Credits_frametime) / 1000.f;
843 if (keyd_pressed[KEY_LSHIFT]) {
844 Credit_position -= fl_frametime * Credits_scroll_rate * 4.0f;
845 } else {
846 Credit_position -= fl_frametime * Credits_scroll_rate;
847 }
848
849 if (Credit_position < Credit_stop_pos){
850 Credit_position = Credit_start_pos;
851 }
852
853 Credits_counter += fl_frametime;
854 while (Credits_counter >= Credits_artwork_display_time) {
855 Credits_counter -= Credits_artwork_display_time;
856 Credits_artwork_index = next;
857 }
858
859 gr_flip();
860 }
861