/* * Copyright (C) Volition, Inc. 1999. All rights reserved. * * All source code herein is the property of Volition, Inc. You may not sell * or otherwise commercially exploit the source or things you created based on the * source. * */ #include #include "cfile/cfile.h" #include "freespace.h" #include "gamesequence/gamesequence.h" #include "gamesnd/eventmusic.h" /* for Master_event_music_volume */ #include "gamesnd/gamesnd.h" #include "globalincs/alphacolors.h" #include "graphics/font.h" #include "io/key.h" #include "io/timer.h" #include "localization/localize.h" #include "menuui/credits.h" #include "missionui/missionscreencommon.h" #include "parse/parselo.h" #include "playerman/player.h" #include "sound/audiostr.h" #include "ui/ui.h" #include // This is the fs2_open credit list, please only add yourself if you have actually contributed code // Rules! const char *fs2_open_credit_text = "SOURCE CODE PROJECT STAFF:\n" "\n" "\n" "Project Leader:\n" "\n" "Tyler \"DahBlount\" Blount\n" "\n" "Senior Advisors:\n" "\n" "Ian \"Goober5000\" Warfield\n" "Fabian \"The E\" Woltermann\n" "Cliff \"chief1983\" Gordon\n" "Taylor Richards\n" "Michael \"Zacam\" LaFleur\n" "Edward \"Inquisitor\" Gardner\n" "\n" "Programmers:\n" "\n" "Mike \"Bobboau\" Abegg\n" "Hassan \"Karajorma\" Kazmi\n" "Derek \"Kazan\" Meek\n" "Nick \"phreak\" Iannetta\n" "argv[-1], Backslash, Baezon\n" "CommanderDJ, Cyborg, DTP\n" "Echelon9, EdrickV, Eternal1\n" "Flaming_Sword, Fry_Day, FUBAR\n" "Hery, Iss Mneur, jg18\n" "m!m, MageKing17, mrduckman\n" "niffiwan, penguin, portej05\n" "RandomTiger, Righteous1, Sesquipedalian\n" "Shade, Sticks, Sushi\n" "Swifty, UnknownPlayer, Valathil\n" "Wanderer, WMCoolmon, wookieejedi\n" "Yarn, z64555, zookeeper\n" "\n" "\n" "Web Support:\n" "\n" "http://www.hard-light.net/\n" "http://scp.indiegames.us/\n" "\n" "\n" "Special thanks to:\n" "\n" "Fury\n" "kasperl\n" "QuantumDelta\n" "redmenace\n" "\n" "\n" "Linux and OSX version with the support of\n" "the icculus.org FreeSpace2 project:\n" "Steven \"relnev\" Fuller\n" "Ryan Gordon\n" "Charles Mason\n" "Dan Olson\n" "Taylor Richards\n" "\"tigital\"\n" "\n" "\n" "Very special thanks to:\n" "\n" "Volition for making FS2 such a great game\n" "Dave Baranec for giving us the code and keeping us sanity checked\n" "\n" "\n" "FS2_Open makes use of the following technologies:\n" "\n" "Ogg Vorbis - (C) 2005, Xiph.Org Foundation\n" "JPEG - Independent JPEG Group, (C) 1991-1998, Thomas G. Lane\n" "libpng - Copyright (C) 1998-2010 Glenn Randers-Pehrson\n" "liblua (" LUA_RELEASE ") - " LUA_COPYRIGHT "\n" "zlib - Copyright (C) 1995-2005 Jean-loup Gailly and Mark Adler\n" "FXAA - Copyright (c) 2010 NVIDIA Corporation. All rights reserved.\n" "libpcp - Copyright (c) 2013 by Cisco Systems, Inc.\n" "This software uses libraries from the FFmpeg project under the LGPLv2.1\n" "\n" "\n" "\n"; const char *unmodified_credits = "ORIGINAL VOLITION STAFF:\n\n\n"; const char *mod_check = "Design:"; #define NUM_BUTTONS 5 #define DEFAULT_NUM_IMAGES 46 #define TECH_DATABASE_BUTTON 0 #define SIMULATOR_BUTTON 1 #define CUTSCENES_BUTTON 2 #define CREDITS_BUTTON 3 #define EXIT_BUTTON 4 // inidicies for coordinates #define CREDITS_X_COORD 0 #define CREDITS_Y_COORD 1 #define CREDITS_W_COORD 2 #define CREDITS_H_COORD 3 static const char* Credits_bitmap_fname[GR_NUM_RESOLUTIONS] = { "Credits", // GR_640 "2_Credits" }; static const char* Credits_bitmap_mask_fname[GR_NUM_RESOLUTIONS] = { "Credits-M", // GR_640 "2_Credits-M" }; int Credits_image_coords[GR_NUM_RESOLUTIONS][4] = { { 219, 15, 394, 286 // GR_640 }, { 351, 25, 629, 455 // GR_1024 } }; // x, y, w, h int Credits_text_coords[GR_NUM_RESOLUTIONS][4] = { { 26, 316, 482, 157 // GR_640 }, { 144, 507, 568, 249 // GR_640 } }; struct credits_screen_buttons { const char *filename; int x, y, xt, yt; int hotspot; UI_BUTTON button; // because we have a class inside this struct, we need the constructor below.. 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) {} }; static int Background_bitmap; static UI_WINDOW Ui_window; static credits_screen_buttons Buttons[NUM_BUTTONS][GR_NUM_RESOLUTIONS] = { //XSTR:OFF { credits_screen_buttons("TDB_00", 7, 3, 37, 7, 0), // GR_640 credits_screen_buttons("2_TDB_00", 12, 5, 59, 12, 0) // GR_1024 }, { credits_screen_buttons("TDB_01", 7, 18, 37, 23, 1), // GR_640 credits_screen_buttons("2_TDB_01", 12, 31, 59, 37, 1) // GR_1024 }, { credits_screen_buttons("TDB_02", 7, 34, 37, 38, 2), // GR_640 credits_screen_buttons("2_TDB_02", 12, 56, 59, 62, 2) // GR_1024 }, { credits_screen_buttons("TDB_03", 7, 49, 37, 54, 3), // GR_640 credits_screen_buttons("2_TDB_03", 12, 81, 59, 88, 3) // GR_1024 }, { credits_screen_buttons("CRB_04", 571, 425, 588, 413, 4), // GR_640 credits_screen_buttons("2_CRB_04", 914, 681, 953, 668, 4) // GR_1024 } //XSTR:ON }; static char Credits_music_name[NAME_LENGTH]; static int Credits_music_handle = -1; static UI_TIMESTAMP Credits_music_begin_timestamp; static int Credits_frametime; // frametime of credits_do_frame() loop in ms static int Credits_last_time; // timestamp used to calc frametime (in ms) static float Credits_counter; static int Credits_num_images; static int Credits_artwork_index; static SCP_vector Credits_bmps; // Positions for credits... float Credit_start_pos, Credit_stop_pos, Credit_position = 0.0f; static int Credits_music_delay = 2000; static float Credits_scroll_rate = 15.0f; static float Credits_artwork_display_time = 9.0f; static float Credits_artwork_fade_time = 1.0f; static SCP_vector Credit_text_parts; static bool Credits_parsed; enum CreditsPosition { START, END }; static CreditsPosition SCP_credits_position = START; void credits_stop_music(bool fade) { if ( Credits_music_handle != -1 ) { audiostream_close_file(Credits_music_handle, fade); Credits_music_handle = -1; } } void credits_load_music(char* fname) { if ( Credits_music_handle != -1 ){ return; } if ( fname && *fname ){ Credits_music_handle = audiostream_open( fname, ASF_MENUMUSIC ); } } void credits_start_music() { if (Credits_music_handle != -1) { if ( !audiostream_is_playing(Credits_music_handle) ){ audiostream_play(Credits_music_handle, Master_event_music_volume, 1); } } else { nprintf(("Warning", "Cannot play credits music\n")); } } int credits_screen_button_pressed(int n) { switch (n) { case TECH_DATABASE_BUTTON: gamesnd_play_iface(InterfaceSounds::SWITCH_SCREENS); gameseq_post_event(GS_EVENT_TECH_MENU); return 1; case SIMULATOR_BUTTON: gamesnd_play_iface(InterfaceSounds::SWITCH_SCREENS); gameseq_post_event(GS_EVENT_SIMULATOR_ROOM); return 1; case CUTSCENES_BUTTON: gamesnd_play_iface(InterfaceSounds::SWITCH_SCREENS); gameseq_post_event(GS_EVENT_GOTO_VIEW_CUTSCENES_SCREEN); return 1; case EXIT_BUTTON: gamesnd_play_iface(InterfaceSounds::COMMIT_PRESSED); gameseq_post_event(GS_EVENT_MAIN_MENU); game_flush(); break; } return 0; } void credits_parse_table(const char* filename) { try { read_file_text(filename, CF_TYPE_TABLES); reset_parse(); // any metadata? if (optional_string("$Music:")) { stuff_string(Credits_music_name, F_NAME, NAME_LENGTH); } if (optional_string("$Number of Images:")) { int temp; stuff_int(&temp); if (temp > 0) Credits_num_images = temp; } if (optional_string("$Start Image Index:")) { stuff_int(&Credits_artwork_index); // bounds check if (Credits_artwork_index < 0) { Credits_artwork_index = 0; } else if (Credits_artwork_index >= Credits_num_images) { Credits_artwork_index = Credits_num_images - 1; } } if (optional_string("$Text scroll rate:")) { stuff_float(&Credits_scroll_rate); if (Credits_scroll_rate < 0.01f) Credits_scroll_rate = 0.01f; } if (optional_string("$Artworks display time:")) { stuff_float(&Credits_artwork_display_time); if (Credits_artwork_display_time < 0.01f) Credits_artwork_display_time = 0.01f; } if (optional_string("$Artworks fade time:")) { stuff_float(&Credits_artwork_fade_time); if (Credits_artwork_fade_time < 0.01f) Credits_artwork_fade_time = 0.01f; } if (optional_string("$SCP Credits position:")) { char mode[NAME_LENGTH]; stuff_string(mode, F_NAME, NAME_LENGTH); if (!stricmp(mode, "Start")) SCP_credits_position = START; else if (!stricmp(mode, "End")) SCP_credits_position = END; else Warning(LOCATION, "Unknown credits position mode \"%s\".", mode); } ignore_white_space(); SCP_string credits_text; SCP_string line; SCP_vector charNum; SCP_vector lines; int numLines = -1; bool first_run = true; while (!check_for_eof_raw() && !check_for_string_raw("#end")) { // Read in a line of text stuff_string_line(line); // This is a bit odd but it means if a total conversion uses different credits the // Volition credit won't happen // Also don't append the default credits anymore when there was already a parsed table if (first_run && !Credits_parsed && line == mod_check) { credits_text.append(unmodified_credits); } first_run = false; if (line.empty()) { // If the line is empty then just append a newline, don't bother with splitting it first credits_text.append("\n"); } else { // split_str doesn't take care of this. charNum.clear(); // Split the string into multiple lines if it's too long numLines = split_str(line.c_str(), Credits_text_coords[gr_screen.res][2], charNum, lines); // Make sure that we have valid data Assertion(lines.size() == (size_t)numLines, "split_str reported %d lines but vector contains " SIZE_T_ARG " entries!", numLines, lines.size()); Assertion(lines.size() <= charNum.size(), "Something has gone wrong while splitting strings. Got " SIZE_T_ARG " lines but only " SIZE_T_ARG " chacter lengths.", lines.size(), charNum.size()); // Now add all splitted lines to the credit text and append a newline to the end for (int i = 0; i < numLines; i++) { credits_text.append(SCP_string(lines[i], charNum[i])); credits_text.append("\n"); } } } Credit_text_parts.push_back(credits_text); Credits_parsed = true; } catch (const parse::ParseException& e) { mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", filename, e.what())); return; } } void credits_parse() { // Parse main table credits_parse_table("credits.tbl"); // Parse modular tables parse_modular_table("*-crd.tbm", credits_parse_table); } void credits_init() { int i; credits_screen_buttons *b; // pre-initialize Credits_num_images = DEFAULT_NUM_IMAGES; Credits_artwork_index = -1; // this is moved up here so we can override it if desired strcpy_s(Credits_music_name, "Cinema"); // parse credits early so as to set up any overrides (for music and such) Credits_parsed = false; credits_parse(); // we could conceivably have specified a number of images but not an index, // so if that's the case, set the value here if (Credits_artwork_index < 0) { Credits_artwork_index = Random::next(Credits_num_images); } int credits_spooled_music_index = event_music_get_spooled_music_index(Credits_music_name); if(credits_spooled_music_index != -1){ char *credits_wavfile_name = Spooled_music[credits_spooled_music_index].filename; if(credits_wavfile_name != NULL){ credits_load_music(credits_wavfile_name); } } // Use this id to trigger the start of music playing on the briefing screen Credits_music_begin_timestamp = ui_timestamp(Credits_music_delay); Credits_frametime = 0; Credits_last_time = timer_get_milliseconds(); if (!Credits_parsed) { Credit_text_parts.push_back(SCP_string("No credits available.\n")); } else { switch (SCP_credits_position) { case START: Credit_text_parts.insert(Credit_text_parts.begin(), fs2_open_credit_text); break; case END: Credit_text_parts.push_back(fs2_open_credit_text); break; default: Error(LOCATION, "Unimplemented credits position %d. Get a coder!", (int) SCP_credits_position); break; } } int ch; SCP_vector::iterator iter; for (iter = Credit_text_parts.begin(); iter != Credit_text_parts.end(); ++iter) { for (SCP_string::iterator ii = iter->begin(); ii != iter->end(); ++ii) { ch = *ii; switch (ch) { case -4: ch = 129; break; case -28: ch = 132; break; case -10: ch = 148; break; case -23: ch = 130; break; case -30: ch = 131; break; case -25: ch = 135; break; case -21: ch = 137; break; case -24: ch = 138; break; case -17: ch = 139; break; case -18: ch = 140; break; case -60: ch = 142; break; case -55: ch = 144; break; case -12: ch = 147; break; case -14: ch = 149; break; case -5: ch = 150; break; case -7: ch = 151; break; case -42: ch = 153; break; case -36: ch = 154; break; case -31: ch = 160; break; case -19: ch = 161; break; case -13: ch = 162; break; case -6: ch = 163; break; case -32: ch = 133; break; case -22: ch = 136; break; case -20: ch = 141; break; } *ii = (char) ch; } } int temp_h; int h = 0; for (iter = Credit_text_parts.begin(); iter != Credit_text_parts.end(); ++iter) { gr_get_string_size(NULL, &temp_h, iter->c_str(), (int)iter->length()); h = h + temp_h; } Credit_start_pos = i2fl(Credits_text_coords[gr_screen.res][CREDITS_H_COORD]); Credit_stop_pos = -i2fl(h); Credit_position = Credit_start_pos; Ui_window.create(0, 0, gr_screen.max_w_unscaled, gr_screen.max_h_unscaled, 0); Ui_window.set_mask_bmap(Credits_bitmap_mask_fname[gr_screen.res]); common_set_interface_palette("InterfacePalette"); // set the interface palette for (i=0; ibutton.create(&Ui_window, "", b->x, b->y, 60, 30, (i < 2), 1); // set up callback for when a mouse first goes over a button b->button.set_highlight_action(common_play_highlight_sound); b->button.set_bmaps(b->filename); b->button.link_hotspot(b->hotspot); } // add some text 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); 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); 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); 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); 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); if (Player->flags & PLAYER_FLAGS_IS_MULTI) { Buttons[SIMULATOR_BUTTON][gr_screen.res].button.disable(); Buttons[CUTSCENES_BUTTON][gr_screen.res].button.disable(); } Buttons[EXIT_BUTTON][gr_screen.res].button.set_hotkey(KEY_CTRLED | KEY_ENTER); Background_bitmap = bm_load(Credits_bitmap_fname[gr_screen.res]); Credits_bmps.resize(Credits_num_images); for (i=0; i= 0){ bm_release(Credits_bmps[i]); } } Credits_bmps.clear(); Credit_text_parts.clear(); if (Background_bitmap){ bm_release(Background_bitmap); } Ui_window.destroy(); common_free_interface_palette(); // restore game palette // non-UI stuff credits_stop_music(true); } void credits_do_frame(float /*frametime*/) { GR_DEBUG_SCOPE("Credits do frame"); int i, k, next, percent, bm1, bm2; int bx1, by1, bw1, bh1; int bx2, by2, bw2, bh2; // Use this id to trigger the start of music playing on the credits screen if ( ui_timestamp_elapsed(Credits_music_begin_timestamp) ) { Credits_music_begin_timestamp = UI_TIMESTAMP::invalid(); credits_start_music(); } k = Ui_window.process(); switch (k) { case KEY_ESC: gameseq_post_event(GS_EVENT_MAIN_MENU); key_flush(); break; case KEY_CTRLED | KEY_UP: case KEY_SHIFTED | KEY_TAB: if ( !(Player->flags & PLAYER_FLAGS_IS_MULTI) ) { credits_screen_button_pressed(CUTSCENES_BUTTON); break; } // else, react like tab key. FALLTHROUGH; case KEY_CTRLED | KEY_DOWN: case KEY_TAB: credits_screen_button_pressed(TECH_DATABASE_BUTTON); break; default: break; } // end switch for (i=0; i= 0) { gr_set_bitmap(Background_bitmap); gr_bitmap(0, 0, GR_RESIZE_MENU); } percent = (int) (100.0f - (Credits_artwork_display_time - Credits_counter) * 100.0f / Credits_artwork_fade_time); if (percent < 0){ percent = 0; } next = Credits_artwork_index + 1; if (next >= Credits_num_images){ next = 0; } if (Credits_bmps[Credits_artwork_index] < 0) { char buf[40]; if (gr_screen.res == GR_1024) { sprintf(buf, NOX("2_CrIm%.2d"), Credits_artwork_index); } else { sprintf(buf, NOX("CrIm%.2d"), Credits_artwork_index); } Credits_bmps[Credits_artwork_index] = bm_load(buf); } if (Credits_bmps[next] < 0) { char buf[40]; if (gr_screen.res == GR_1024) { sprintf(buf, NOX("2_CrIm%.2d"), next); } else { sprintf(buf, NOX("CrIm%.2d"), next); } Credits_bmps[next] = bm_load(buf); } bm1 = Credits_bmps[Credits_artwork_index]; bm2 = Credits_bmps[next]; if((bm1 != -1) && (bm2 != -1)){ GR_DEBUG_SCOPE("Render credits bitmap"); Assert(percent >= 0 && percent <= 100); // get width and height bm_get_info(bm1, &bw1, &bh1, NULL, NULL, NULL); bm_get_info(bm2, &bw2, &bh2, NULL, NULL, NULL); // determine where to draw the coords bx1 = Credits_image_coords[gr_screen.res][CREDITS_X_COORD] + ((Credits_image_coords[gr_screen.res][CREDITS_W_COORD] - bw1)/2); by1 = Credits_image_coords[gr_screen.res][CREDITS_Y_COORD] + ((Credits_image_coords[gr_screen.res][CREDITS_H_COORD] - bh1)/2); bx2 = Credits_image_coords[gr_screen.res][CREDITS_X_COORD] + ((Credits_image_coords[gr_screen.res][CREDITS_W_COORD] - bw2)/2); by2 = Credits_image_coords[gr_screen.res][CREDITS_Y_COORD] + ((Credits_image_coords[gr_screen.res][CREDITS_H_COORD] - bh2)/2); auto alpha = (float)percent / 100.0f; gr_set_bitmap(bm1, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, 1.0f - alpha); gr_bitmap(bx1, by1, GR_RESIZE_MENU); gr_set_bitmap(bm2, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, alpha); gr_bitmap(bx2, by2, GR_RESIZE_MENU); } Ui_window.draw(); for (i=TECH_DATABASE_BUTTON; i<=CREDITS_BUTTON; i++){ if (Buttons[i][gr_screen.res].button.button_down()){ break; } } if (i > CREDITS_BUTTON){ Buttons[CREDITS_BUTTON][gr_screen.res].button.draw_forced(2); } 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); font::set_font(font::FONT1); gr_set_color_fast(&Color_normal); int y_offset = 0; for (SCP_vector::iterator iter = Credit_text_parts.begin(); iter != Credit_text_parts.end(); ++iter) { size_t currentPos = 0; size_t lineEnd; do { int height; int width; lineEnd = iter->find('\n', currentPos); auto length = lineEnd - currentPos; if (lineEnd == SCP_string::npos) { length = std::numeric_limits::max(); } gr_get_string_size(&width, &height, iter->c_str() + currentPos, static_cast(length)); // Check if the text part is actually visible if (Credit_position + y_offset + height > 0.0f) { float x = static_cast((gr_screen.clip_width_unscaled - width) / 2); gr_string(x, Credit_position + y_offset, iter->c_str() + currentPos, GR_RESIZE_MENU, static_cast(length)); } y_offset += height; currentPos = lineEnd + 1; } while (lineEnd < iter->length() && lineEnd != SCP_string::npos); } int temp_time; temp_time = timer_get_milliseconds(); Credits_frametime = temp_time - Credits_last_time; Credits_last_time = temp_time; float fl_frametime = i2fl(Credits_frametime) / 1000.f; if (keyd_pressed[KEY_LSHIFT]) { Credit_position -= fl_frametime * Credits_scroll_rate * 4.0f; } else { Credit_position -= fl_frametime * Credits_scroll_rate; } if (Credit_position < Credit_stop_pos){ Credit_position = Credit_start_pos; } Credits_counter += fl_frametime; while (Credits_counter >= Credits_artwork_display_time) { Credits_counter -= Credits_artwork_display_time; Credits_artwork_index = next; } gr_flip(); }