mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-08 07:28:22 -10:00
* use std::move() for local variables that are copied but not reused Addresses ~140 Coverity "Variable copied when it could be moved" defects across 74 files. Every std::move() targets a local variable whose value is consumed by push_back, emplace, assignment, lambda capture, or a function call and is never read again afterward. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * clang: use std::make_shared * clang: use std::make_unique * address feedback --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
911 lines
22 KiB
C++
911 lines
22 KiB
C++
/*
|
|
* 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 <cstdlib>
|
|
|
|
#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 <lua.h>
|
|
|
|
|
|
// 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"
|
|
"Zoe \"EatThePath\" Gibson\n"
|
|
"Mike \"MjnMixael\" Nelson\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"
|
|
"Kiloku, Lafiel, m!m\n"
|
|
"MageKing17, mrduckman, naomimyselfandi\n"
|
|
"niffiwan, penguin, Phantom Hoover\n"
|
|
"portej05, qazwsxal, RandomTiger\n"
|
|
"Righteous1, Sesquipedalian, Shade\n"
|
|
"Sticks, Sushi, Swifty\n"
|
|
"UnknownPlayer, Valathil, Wanderer\n"
|
|
"WMCoolmon, wookieejedi, Yarn\n"
|
|
"z64555, zookeeper\n"
|
|
"\n"
|
|
"\n"
|
|
"Web Support:\n"
|
|
"\n"
|
|
"https://www.hard-light.net/\n"
|
|
"https://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"
|
|
"Dear ImGui by Omar Cornut and contributors\n"
|
|
"dkjson.lua - Copyright (C) 2010-2014 David Heiko Kolf"
|
|
"\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
|
|
};
|
|
|
|
char Credits_music_name[NAME_LENGTH] = "Cinema";
|
|
char Credits_substitute_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;
|
|
|
|
int Credits_num_images = DEFAULT_NUM_IMAGES;
|
|
int Credits_artwork_index = -1;
|
|
static SCP_vector<int> Credits_bmps;
|
|
|
|
// Positions for credits...
|
|
float Credit_start_pos, Credit_stop_pos, Credit_position = 0.0f;
|
|
|
|
static int Credits_music_delay = 2000;
|
|
float Credits_scroll_rate = 15.0f;
|
|
float Credits_artwork_display_time = 9.0f;
|
|
float Credits_artwork_fade_time = 1.0f;
|
|
|
|
SCP_string credits_complete;
|
|
|
|
SCP_vector<SCP_string> Credit_text_parts;
|
|
|
|
static bool Credits_parsed;
|
|
|
|
static bool Split_credits_lines = true;
|
|
|
|
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(const 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"));
|
|
}
|
|
}
|
|
|
|
const char* credits_get_music_filename(const char* music)
|
|
{
|
|
int credits_spooled_music_index = event_music_get_spooled_music_index(music);
|
|
if (credits_spooled_music_index != -1) {
|
|
return Spooled_music[credits_spooled_music_index].filename;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
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("$Substitute Music:"))
|
|
{
|
|
stuff_string(Credits_substitute_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<int> charNum;
|
|
SCP_vector<const char*> 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
|
|
{
|
|
// optionally split lines. This is default behavior but SCPUI doesn't use it
|
|
if (Split_credits_lines) {
|
|
|
|
// 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());
|
|
} else {
|
|
lines.push_back(line.c_str());
|
|
charNum.push_back((int)line.length());
|
|
numLines = 1;
|
|
}
|
|
|
|
// 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");
|
|
}
|
|
|
|
// clear vectors for the next round
|
|
lines.clear();
|
|
charNum.clear();
|
|
}
|
|
}
|
|
|
|
Credit_text_parts.push_back(std::move(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_scp_position()
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
void credits_parse(bool split_lines)
|
|
{
|
|
|
|
Split_credits_lines = split_lines;
|
|
|
|
// 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;
|
|
|
|
// 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);
|
|
}
|
|
|
|
const char *credits_wavfile_name = nullptr;
|
|
|
|
// try substitute music first
|
|
if (*Credits_substitute_music_name)
|
|
credits_wavfile_name = credits_get_music_filename(Credits_substitute_music_name);
|
|
|
|
// fall back to regular music
|
|
if (!credits_wavfile_name)
|
|
credits_wavfile_name = credits_get_music_filename(Credits_music_name);
|
|
|
|
// if we have something, play it
|
|
if (credits_wavfile_name)
|
|
credits_load_music(credits_wavfile_name);
|
|
|
|
// Use this id to trigger the start of music playing on the credits 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
|
|
{
|
|
credits_scp_position();
|
|
}
|
|
|
|
int ch;
|
|
SCP_vector<SCP_string>::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(nullptr, &temp_h, iter->c_str(), 1.0f, 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; i<NUM_BUTTONS; i++) {
|
|
b = &Buttons[i][gr_screen.res];
|
|
|
|
b->button.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<Credits_num_images; i++) {
|
|
Credits_bmps[i] = -1;
|
|
}
|
|
}
|
|
|
|
void credits_close()
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i<Credits_num_images; i++) {
|
|
if (Credits_bmps[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<NUM_BUTTONS; i++){
|
|
if (Buttons[i][gr_screen.res].button.pressed()){
|
|
if (credits_screen_button_pressed(i)){
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
gr_reset_clip();
|
|
GR_MAYBE_CLEAR_RES(Background_bitmap);
|
|
if (Background_bitmap >= 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<SCP_string>::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<size_t>::max();
|
|
}
|
|
|
|
gr_get_string_size(&width, &height, iter->c_str() + currentPos, 1.0f, length);
|
|
// Check if the text part is actually visible
|
|
if (Credit_position + y_offset + height > 0.0f)
|
|
{
|
|
int x = (gr_screen.clip_width_unscaled - width) / 2;
|
|
gr_string(i2fl(x), Credit_position + y_offset, iter->c_str() + currentPos, GR_RESIZE_MENU, 1.0f, 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 (key_is_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();
|
|
}
|