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