mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 16:18:19 -10:00
347 lines
10 KiB
C++
347 lines
10 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.
|
|
*
|
|
*/
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#include "globalincs/pstypes.h"
|
|
#include "freespace.h"
|
|
#include "globalincs/alphacolors.h"
|
|
#include "globalincs/systemvars.h"
|
|
#include "io/cursor.h"
|
|
#include "graphics/2d.h"
|
|
#include "cfile/cfile.h"
|
|
#include "cmdline/cmdline.h"
|
|
#include "cutscene/cutscenes.h" // cutscene_mark_viewable()
|
|
#include "cutscene/player.h" // cutscene_mark_viewable()
|
|
#include "tracing/categories.h"
|
|
#include "tracing/tracing.h"
|
|
#include "io/timer.h"
|
|
#include "io/key.h"
|
|
#include "mod_table/mod_table.h"
|
|
#include "network/multi.h"
|
|
#include "scripting/global_hooks.h"
|
|
|
|
extern int Game_mode;
|
|
extern int Is_standalone;
|
|
|
|
namespace {
|
|
|
|
using namespace cutscene;
|
|
|
|
struct PlaybackState {
|
|
bool playing = true;
|
|
|
|
vec2d posTopLeft;
|
|
vec2d posBottomRight;
|
|
|
|
int subtitle_font = -1;
|
|
color subtitle_color = Color_bright_white;
|
|
};
|
|
|
|
void processEvents()
|
|
{
|
|
io::mouse::CursorManager::get()->showCursor(false);
|
|
|
|
os_remove_deferred_cutscene_key_events();
|
|
os_poll();
|
|
}
|
|
|
|
template<typename... Args>
|
|
float print_string(float x, float y, const char* fmt, Args... params) {
|
|
SCP_string text;
|
|
sprintf(text, fmt, params...);
|
|
|
|
gr_string(x, y, text.c_str(), GR_RESIZE_NONE);
|
|
|
|
return y + font::get_current_font()->getHeight();
|
|
}
|
|
|
|
void showVideoInfo(const PlayerState& state) {
|
|
gr_set_color_fast(&Color_white);
|
|
font::set_font(font::FONT1);
|
|
|
|
float y = 200.f;
|
|
float x = 100.f;
|
|
y = print_string(x, y, "Movie FPS: %f", state.props.fps);
|
|
y = print_string(x, y, "Size: %dx%d", state.props.size.width, state.props.size.height);
|
|
|
|
y = gr_screen.max_h - 200.f;
|
|
|
|
size_t audio_queue_size = state.decoder->getAudioQueueSize();
|
|
if (state.hasAudio) {
|
|
ALint queued;
|
|
OpenAL_ErrorPrint(alGetSourcei(state.audioSid, AL_BUFFERS_QUEUED, &queued));
|
|
audio_queue_size += queued;
|
|
}
|
|
|
|
y = print_string(x, y, "Audio Queue size: " SIZE_T_ARG, audio_queue_size);
|
|
y = print_string(x, y, "Video Queue size: " SIZE_T_ARG, state.decoder->getVideoQueueSize());
|
|
y += font::get_current_font()->getHeight();
|
|
// Estimate the size of the video buffer
|
|
// We use YUV420p frames so one pixel uses 1.5 bytes of storage
|
|
size_t single_frame_size = (size_t) (state.props.size.width * state.props.size.height * 1.5);
|
|
size_t total_size = single_frame_size * state.decoder->getVideoQueueSize();
|
|
print_string(x, y, "Video buffer size: " SIZE_T_ARG "B", total_size);
|
|
}
|
|
|
|
void displayVideo(Player* player, PlaybackState* state) {
|
|
TRACE_SCOPE(tracing::CutsceneDrawVideoFrame);
|
|
GR_DEBUG_SCOPE("Display Video");
|
|
|
|
gr_clear();
|
|
player->draw(state->posTopLeft.x, state->posTopLeft.y, state->posBottomRight.x, state->posBottomRight.y);
|
|
|
|
auto subtitle = player->getCurrentSubtitle();
|
|
if (!subtitle.empty() && state->subtitle_font >= 0) {
|
|
gr_set_color_fast(&state->subtitle_color);
|
|
font::set_font(state->subtitle_font);
|
|
|
|
int width;
|
|
int height;
|
|
gr_get_string_size(&width, &height, subtitle.c_str());
|
|
|
|
// Offset the subtitle by 20% of the viewport size from the bottom
|
|
auto y_offset = static_cast<int>((state->posBottomRight.y - state->posTopLeft.y) * 0.1);
|
|
|
|
auto center_x = (state->posBottomRight.x - state->posTopLeft.x) / 2;
|
|
|
|
auto text_x = center_x - width / 2;
|
|
auto text_y = state->posBottomRight.y - y_offset - height;
|
|
|
|
gr_string(text_x, text_y, subtitle.c_str(), GR_RESIZE_NONE);
|
|
}
|
|
|
|
if (Cmdline_show_video_info) {
|
|
showVideoInfo(player->getInternalState());
|
|
}
|
|
|
|
gr_flip();
|
|
}
|
|
void determine_display_positions(Player* player, PlaybackState* state) {
|
|
auto& props = player->getMovieProperties();
|
|
|
|
float screen_ratio = (float) gr_screen.center_w / (float) gr_screen.center_h;
|
|
float movie_ratio = (float) props.size.width / (float) props.size.height;
|
|
|
|
float scale_by;
|
|
if (screen_ratio > movie_ratio) {
|
|
scale_by = (float) gr_screen.center_h / (float) props.size.height;
|
|
} else {
|
|
scale_by = (float) gr_screen.center_w / (float) props.size.width;
|
|
}
|
|
|
|
float screenX;
|
|
float screenY;
|
|
|
|
if (!Cmdline_noscalevid && (scale_by != 1.0f)) {
|
|
screenX = ((gr_screen.center_w / 2.0f + gr_screen.center_offset_x) / scale_by) - (static_cast<int>(props.size.width) / 2.0f) + 0.5f;
|
|
screenY = ((gr_screen.center_h / 2.0f + gr_screen.center_offset_y) / scale_by) - (static_cast<int>(props.size.height) / 2.0f) + 0.5f;
|
|
} else {
|
|
// centers on 1024x768, fills on 640x480
|
|
screenX = i2fl(((gr_screen.center_w - static_cast<int>(props.size.width)) / 2) + gr_screen.center_offset_x);
|
|
screenY = i2fl(((gr_screen.center_h - static_cast<int>(props.size.height)) / 2) + gr_screen.center_offset_y);
|
|
}
|
|
|
|
// set additional values for screen width/height and UV coords
|
|
float screenXW = screenX + static_cast<int>(props.size.width);
|
|
float screenYH = screenY + static_cast<int>(props.size.height);
|
|
|
|
if (!Cmdline_noscalevid && (scale_by != 1.0f)) {
|
|
// Apply scaling
|
|
screenX *= scale_by;
|
|
screenY *= scale_by;
|
|
|
|
screenXW *= scale_by;
|
|
screenYH *= scale_by;
|
|
}
|
|
|
|
state->posTopLeft.x = screenX;
|
|
state->posTopLeft.y = screenY;
|
|
|
|
state->posBottomRight.x = screenXW;
|
|
state->posBottomRight.y = screenYH;
|
|
}
|
|
|
|
void initialize_player_state(Player* player, PlaybackState* state) {
|
|
determine_display_positions(player, state);
|
|
|
|
if (!Movie_subtitle_font.empty()) {
|
|
state->subtitle_font = font::FontManager::getFontIndex(Movie_subtitle_font);
|
|
|
|
if (state->subtitle_font < 0) {
|
|
Warning(LOCATION, "Failed to load subtitle font '%s'! Subtitles will be disabled.", Movie_subtitle_font.c_str());
|
|
}
|
|
|
|
if ((Movie_subtitle_rgba[0] >= 0 && Movie_subtitle_rgba[0] <= 255) &&
|
|
(Movie_subtitle_rgba[1] >= 0 && Movie_subtitle_rgba[1] <= 255) &&
|
|
(Movie_subtitle_rgba[2] >= 0 && Movie_subtitle_rgba[2] <= 255) &&
|
|
(Movie_subtitle_rgba[3] >= 0 && Movie_subtitle_rgba[3] <= 255))
|
|
{
|
|
gr_init_alphacolor(&state->subtitle_color,
|
|
Movie_subtitle_rgba[0],
|
|
Movie_subtitle_rgba[1],
|
|
Movie_subtitle_rgba[2],
|
|
Movie_subtitle_rgba[3]);
|
|
}
|
|
|
|
}
|
|
else if (font::FontManager::numberOfFonts() > 0) {
|
|
state->subtitle_font = 0; // if not explicitly specified, default to the first available font
|
|
}
|
|
}
|
|
|
|
void movie_display_loop(Player* player, PlaybackState* state) {
|
|
// Compute the maximum time we will sleep to make sure we can still maintain the movie FPS
|
|
// and not waste too much CPU time
|
|
// We will sleep at most half the time a frame would be displayed
|
|
auto sleepTime = static_cast<std::uint64_t>((1. / (4. * player->getMovieProperties().fps)) * 1000.);
|
|
|
|
auto listener = [state](const SDL_Event& event) {
|
|
switch (event.key.key) {
|
|
case SDLK_ESCAPE:
|
|
case SDLK_KP_ENTER:
|
|
case SDLK_RETURN:
|
|
case SDLK_SPACE:
|
|
state->playing = false;
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
};
|
|
// Use a dedicated listener here since we want to listen for the KEYUP event
|
|
auto key_handle = os::events::addEventListener(SDL_EVENT_KEY_UP, os::events::DEFAULT_LISTENER_WEIGHT - 10, listener);
|
|
|
|
// slight hack to make sure that game_set_frametime() doesn't also try to cap the framerate
|
|
auto fpsCapSave = Cmdline_NoFPSCap;
|
|
Cmdline_NoFPSCap = 1;
|
|
|
|
auto lastDisplayTime = timer_get_microseconds();
|
|
while (state->playing) {
|
|
TRACE_SCOPE(tracing::CutsceneStep);
|
|
|
|
auto now = timer_get_microseconds();
|
|
|
|
auto passed = now - lastDisplayTime;
|
|
lastDisplayTime = now;
|
|
|
|
if (player->isPlaybackReady()) {
|
|
// Play as long as the player reports that there is more to display
|
|
state->playing = player->update(passed);
|
|
|
|
displayVideo(player, state);
|
|
}
|
|
|
|
processEvents();
|
|
|
|
// NOTE: This does not update mission time! If movies get enabled in places
|
|
// other than through cutscenes then some refactoring should be done
|
|
// to account for normal time progression rather than just timestamps
|
|
game_set_frametime(-1);
|
|
|
|
// maybe do multi processing
|
|
if ( (Game_mode & GM_MULTIPLAYER) && Net_player && (Net_player->flags & NETINFO_FLAG_DO_NETWORKING) ) {
|
|
multi_do_frame();
|
|
}
|
|
|
|
if (passed < sleepTime) {
|
|
auto sleep = sleepTime - passed;
|
|
|
|
os_sleep(static_cast<uint>(sleep));
|
|
}
|
|
}
|
|
|
|
Cmdline_NoFPSCap = fpsCapSave;
|
|
|
|
os::events::removeEventListener(key_handle);
|
|
|
|
// flush keys so we don't pass anything to the current state
|
|
key_flush();
|
|
}
|
|
|
|
}
|
|
|
|
namespace movie {
|
|
// Play one movie
|
|
bool play(const char* filename, bool via_tech_room = false)
|
|
{
|
|
if (scripting::hooks::OnMovieAboutToPlay->isActive())
|
|
{
|
|
auto paramList = scripting::hook_param_list(scripting::hook_param("Filename", 's', filename), scripting::hook_param("ViaTechRoom", 'b', via_tech_room));
|
|
bool skip = scripting::hooks::OnMovieAboutToPlay->isOverride(paramList);
|
|
scripting::hooks::OnMovieAboutToPlay->run(std::move(paramList));
|
|
if (skip)
|
|
return false;
|
|
}
|
|
|
|
// mark the movie as viewable to the player when in a campaign
|
|
// do this before anything else so that we're sure the movie is available
|
|
// to the player even if it's not going to play right now
|
|
if (Game_mode & GM_CAMPAIGN_MODE) {
|
|
cutscene_mark_viewable(filename);
|
|
}
|
|
|
|
if (Cmdline_nomovies || Is_standalone) {
|
|
return false;
|
|
}
|
|
|
|
// for multiplayer, change the state in my netplayer structure
|
|
if ( (Game_mode & GM_MULTIPLAYER) && Net_player ) {
|
|
Net_player->state = NETPLAYER_STATE_CUTSCENE;
|
|
}
|
|
|
|
// clear the screen and hide the mouse cursor
|
|
io::mouse::CursorManager::get()->pushStatus();
|
|
io::mouse::CursorManager::get()->showCursor(false);
|
|
Movie_active = true;
|
|
gr_reset_clip();
|
|
gr_set_color(255, 255, 255);
|
|
gr_set_clear_color(0, 0, 0);
|
|
gr_zbuffer_clear(0);
|
|
// clear first buffer
|
|
gr_clear();
|
|
gr_flip();
|
|
// clear second buffer (may not be one, but that's ok)
|
|
gr_clear();
|
|
gr_flip();
|
|
// clear third buffer (may not be one, but that's ok)
|
|
gr_clear();
|
|
|
|
auto player = cutscene::Player::newPlayer(filename);
|
|
if (player) {
|
|
PlaybackState state;
|
|
initialize_player_state(player.get(), &state);
|
|
|
|
movie_display_loop(player.get(), &state);
|
|
|
|
player->stopPlayback();
|
|
} else {
|
|
// uh-oh, movie is invalid... Abory, Retry, Fail?
|
|
mprintf(("MOVIE ERROR: Found invalid movie! (%s)\n", filename));
|
|
}
|
|
|
|
Movie_active = false;
|
|
|
|
// show the mouse cursor again
|
|
io::mouse::CursorManager::get()->popStatus();
|
|
|
|
return true;
|
|
}
|
|
|
|
void play_two(const char* filename1, const char* filename2) {
|
|
if (play(filename1)) {
|
|
play(filename2);
|
|
}
|
|
}
|
|
}
|