From db29d758952bfad776ca6a0df7c2485c5b13ae53 Mon Sep 17 00:00:00 2001 From: Andrey Nazarov Date: Fri, 7 Oct 2022 01:16:54 +0300 Subject: [PATCH] Add support for cinematics. --- doc/server.md | 4 + inc/client/sound/sound.h | 3 +- inc/common/protocol.h | 3 +- inc/refresh/refresh.h | 2 + inc/server/server.h | 3 +- src/client/cin.c | 618 +++++++++++++++------------------------ src/client/client.h | 11 +- src/client/keys.c | 10 +- src/client/main.c | 7 +- src/client/parse.c | 2 +- src/client/refresh.c | 2 + src/client/screen.c | 21 +- src/client/sound/main.c | 18 +- src/refresh/gl/draw.c | 11 + src/refresh/gl/gl.h | 5 +- src/refresh/gl/main.c | 2 + src/refresh/gl/texture.c | 7 + src/refresh/vkpt/draw.c | 19 ++ src/refresh/vkpt/main.c | 2 + src/refresh/vkpt/vkpt.h | 4 + src/server/commands.c | 4 +- src/server/init.c | 104 +++++-- src/server/main.c | 2 + src/server/server.h | 1 + src/server/user.c | 38 ++- 25 files changed, 419 insertions(+), 484 deletions(-) diff --git a/doc/server.md b/doc/server.md index 9da838b38..5bffcc533 100644 --- a/doc/server.md +++ b/doc/server.md @@ -127,6 +127,10 @@ The latter will prevent MVD/GTV features from working. *NOTE*: If `sv_password` is set, then game mod's `password` variable must be empty. Otherwise clients will be unable to connect. +#### `sv_cinematics` +If set to 0, server will skip cinematics even if they exist. Default value +is 1. + #### `sv_reserved_slots` Number of client slots reserved for clients who know `sv_reserved_password` or `sv_password`. Must be less than `maxclients` value. Default value is 0 diff --git a/inc/client/sound/sound.h b/inc/client/sound/sound.h index c93294286..6d651cc83 100644 --- a/inc/client/sound/sound.h +++ b/inc/client/sound/sound.h @@ -48,8 +48,7 @@ void OGG_Shutdown(void); void OGG_RecoverState(void); void OGG_SaveState(void); -bool S_RawSamples(int samples, int rate, int width, - int channels, byte *data, float volume); +void S_RawSamples(int samples, int rate, int width, int channels, const byte *data); void S_UnqueueRawSamples(void); diff --git a/inc/common/protocol.h b/inc/common/protocol.h index 9ecaf3ded..1dba310d6 100644 --- a/inc/common/protocol.h +++ b/inc/common/protocol.h @@ -48,7 +48,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #define PROTOCOL_VERSION_Q2PRO_EXTENDED_LAYOUT 1020 // r1354 #define PROTOCOL_VERSION_Q2PRO_ZLIB_DOWNLOADS 1021 // r1358 #define PROTOCOL_VERSION_Q2PRO_CLIENTNUM_SHORT 1022 // r2161 -#define PROTOCOL_VERSION_Q2PRO_CURRENT 1022 // r2161 +#define PROTOCOL_VERSION_Q2PRO_CINEMATICS 1023 // r2263 +#define PROTOCOL_VERSION_Q2PRO_CURRENT 1023 // r2263 #define PROTOCOL_VERSION_MVD_MINIMUM 2009 // r168 #define PROTOCOL_VERSION_MVD_CURRENT 2010 // r177 diff --git a/inc/refresh/refresh.h b/inc/refresh/refresh.h index 8c83576ff..745a4e858 100644 --- a/inc/refresh/refresh.h +++ b/inc/refresh/refresh.h @@ -324,9 +324,11 @@ extern int (*R_DrawString)(int x, int y, int flags, size_t maxChars, bool R_GetPicSize(int *w, int *h, qhandle_t pic); // returns transparency bit extern void (*R_DrawPic)(int x, int y, qhandle_t pic); extern void (*R_DrawStretchPic)(int x, int y, int w, int h, qhandle_t pic); +extern void (*R_DrawStretchRaw)(int x, int y, int w, int h); extern void (*R_TileClear)(int x, int y, int w, int h, qhandle_t pic); extern void (*R_DrawFill8)(int x, int y, int w, int h, int c); extern void (*R_DrawFill32)(int x, int y, int w, int h, uint32_t color); +extern void (*R_UpdateRawPic)(int pic_w, int pic_h, const uint32_t *pic); // video mode and refresh state management entry points extern void (*R_BeginFrame)(void); diff --git a/inc/server/server.h b/inc/server/server.h index 82c779aab..8de6900a6 100644 --- a/inc/server/server.h +++ b/inc/server/server.h @@ -21,13 +21,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "common/net/net.h" +// if this is changed, Q2PRO protocol version must be changed too! typedef enum { ss_dead, // no map loaded ss_loading, // spawning level edicts ss_game, // actively running ss_pic, // showing static picture ss_broadcast, // running MVD client - ss_cinematic, + ss_cinematic, // playing a cinematic } server_state_t; #if USE_ICMP diff --git a/src/client/cin.c b/src/client/cin.c index 1fe665395..437cceca3 100644 --- a/src/client/cin.c +++ b/src/client/cin.c @@ -1,62 +1,57 @@ /* Copyright (C) 1997-2001 Id Software, Inc. -Copyright (C) 2019, NVIDIA CORPORATION. All rights reserved. -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. -See the GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "client.h" -#include "client/sound/sound.h" -#include "common/files.h" -#include "refresh/images.h" - -typedef struct -{ - byte *data; - int count; -} cblock_t; -typedef struct -{ - int s_khz_original; - int s_rate; - int s_width; - int s_channels; +typedef struct { + uint32_t width; + uint32_t height; + uint32_t s_rate; + uint32_t s_width; + uint32_t s_channels; +} cheader_t; - int width; - int height; +typedef struct { + int16_t children[2]; +} hnode_t; - // order 1 huffman stuff - int *hnodes1; // [256][256][2]; - int numhnodes1[256]; +typedef struct { + int width; + int height; + int s_rate; + int s_width; + int s_channels; - int h_used[512]; - int h_count[512]; + uint32_t *pic; + uint32_t palette[256]; - byte palette[768]; - bool palette_active; + hnode_t hnodes[256][256]; + int numhnodes[256]; - char file_name[MAX_QPATH]; - qhandle_t file; + int h_count[512]; + bool h_used[512]; - int start_time; // cls.realtime for first cinematic frame - int frame_index; -} cinematics_t; + qhandle_t file; + unsigned frame; + unsigned time; +} cinematic_t; -static cinematics_t cin = { 0 }; +static cinematic_t cin; /* ================== @@ -65,33 +60,11 @@ SCR_StopCinematic */ void SCR_StopCinematic(void) { - cin.start_time = 0; // done - - S_UnqueueRawSamples(); - - if (cl.image_precache[0]) - { - R_UnregisterImage(cl.image_precache[0]); - cl.image_precache[0] = 0; - } - + if (cin.pic) + Z_Free(cin.pic); if (cin.file) - { FS_CloseFile(cin.file); - cin.file = 0; - } - if (cin.hnodes1) - { - Z_Free(cin.hnodes1); - cin.hnodes1 = NULL; - } - - // switch the sample rate back to its original value if necessary - if (cin.s_khz_original != 0) - { - Cvar_Set("s_khz", va("%d", cin.s_khz_original)); - cin.s_khz_original = 0; - } + memset(&cin, 0, sizeof(cin)); } /* @@ -109,28 +82,24 @@ void SCR_FinishCinematic(void) CL_ClientCommand(va("nextserver %i\n", cl.servercount)); } -//========================================================================== - /* ================== SmallestNode1 ================== */ -int SmallestNode1(int numhnodes) +static int SmallestNode1(int numhnodes) { - int i; - int best, bestnode; + int i; + int best, bestnode; best = 99999999; bestnode = -1; - for (i = 0; i < numhnodes; i++) - { + for (i = 0; i < numhnodes; i++) { if (cin.h_used[i]) continue; if (!cin.h_count[i]) continue; - if (cin.h_count[i] < best) - { + if (cin.h_count[i] < best) { best = cin.h_count[i]; bestnode = i; } @@ -143,7 +112,6 @@ int SmallestNode1(int numhnodes) return bestnode; } - /* ================== Huff1TableInit @@ -151,50 +119,45 @@ Huff1TableInit Reads the 64k counts table and initializes the node trees ================== */ -void Huff1TableInit(void) +static bool Huff1TableInit(void) { - int prev; - int j; - int *node, *nodebase; - byte counts[256]; - int numhnodes; - - cin.hnodes1 = Z_Malloc(256 * 256 * 2 * 4); - memset(cin.hnodes1, 0, 256 * 256 * 2 * 4); + for (int prev = 0; prev < 256; prev++) { + hnode_t *hnodes = cin.hnodes[prev]; + byte counts[256]; + int numhnodes; - for (prev = 0; prev < 256; prev++) - { memset(cin.h_count, 0, sizeof(cin.h_count)); memset(cin.h_used, 0, sizeof(cin.h_used)); // read a row of counts - FS_Read(counts, sizeof(counts), cin.file); - for (j = 0; j < 256; j++) - cin.h_count[j] = counts[j]; + if (FS_Read(counts, sizeof(counts), cin.file) != sizeof(counts)) + return false; - // build the nodes - numhnodes = 256; - nodebase = cin.hnodes1 + prev * 256 * 2; + for (int i = 0; i < 256; i++) + cin.h_count[i] = counts[i]; - while (numhnodes != 511) - { - node = nodebase + (numhnodes - 256) * 2; + // build the nodes + for (numhnodes = 256; numhnodes < 512; numhnodes++) { + hnode_t *node = &hnodes[numhnodes - 256]; // pick two lowest counts - node[0] = SmallestNode1(numhnodes); - if (node[0] == -1) - break; // no more + node->children[0] = SmallestNode1(numhnodes); + if (node->children[0] == -1) + break; // no more - node[1] = SmallestNode1(numhnodes); - if (node[1] == -1) + node->children[1] = SmallestNode1(numhnodes); + if (node->children[1] == -1) break; - cin.h_count[numhnodes] = cin.h_count[node[0]] + cin.h_count[node[1]]; - numhnodes++; + cin.h_count[numhnodes] = + cin.h_count[node->children[0]] + + cin.h_count[node->children[1]]; } - cin.numhnodes1[prev] = numhnodes - 1; + cin.numhnodes[prev] = numhnodes - 1; } + + return true; } /* @@ -202,360 +165,247 @@ void Huff1TableInit(void) Huff1Decompress ================== */ -cblock_t Huff1Decompress(cblock_t in) +static bool Huff1Decompress(const byte *data, int size) { - byte *input; - byte *out_p; - int nodenum; - int count; - cblock_t out; - int inbyte; - int *hnodes, *hnodesbase; - //int i; - - // get decompressed count - count = in.data[0] + (in.data[1] << 8) + (in.data[2] << 16) + (in.data[3] << 24); - input = in.data + 4; - out_p = out.data = Z_Malloc(count); + const byte *in, *in_end; + uint32_t *out; + int prev, bitpos, inbyte, count; - // read bits + in = data + 4; + in_end = data + size; - hnodesbase = cin.hnodes1 - 256 * 2; // nodes 0-255 aren't stored - - hnodes = hnodesbase; - nodenum = cin.numhnodes1[0]; - while (count) - { - inbyte = *input++; - //----------- - if (nodenum < 256) - { - hnodes = hnodesbase + (nodenum << 9); - *out_p++ = nodenum; - if (!--count) - break; - nodenum = cin.numhnodes1[nodenum]; - } - nodenum = hnodes[nodenum * 2 + (inbyte & 1)]; - inbyte >>= 1; - //----------- - if (nodenum < 256) - { - hnodes = hnodesbase + (nodenum << 9); - *out_p++ = nodenum; - if (!--count) - break; - nodenum = cin.numhnodes1[nodenum]; - } - nodenum = hnodes[nodenum * 2 + (inbyte & 1)]; - inbyte >>= 1; - //----------- - if (nodenum < 256) - { - hnodes = hnodesbase + (nodenum << 9); - *out_p++ = nodenum; - if (!--count) - break; - nodenum = cin.numhnodes1[nodenum]; - } - nodenum = hnodes[nodenum * 2 + (inbyte & 1)]; - inbyte >>= 1; - //----------- - if (nodenum < 256) - { - hnodes = hnodesbase + (nodenum << 9); - *out_p++ = nodenum; - if (!--count) - break; - nodenum = cin.numhnodes1[nodenum]; - } - nodenum = hnodes[nodenum * 2 + (inbyte & 1)]; - inbyte >>= 1; - //----------- - if (nodenum < 256) - { - hnodes = hnodesbase + (nodenum << 9); - *out_p++ = nodenum; - if (!--count) - break; - nodenum = cin.numhnodes1[nodenum]; - } - nodenum = hnodes[nodenum * 2 + (inbyte & 1)]; - inbyte >>= 1; - //----------- - if (nodenum < 256) - { - hnodes = hnodesbase + (nodenum << 9); - *out_p++ = nodenum; - if (!--count) - break; - nodenum = cin.numhnodes1[nodenum]; - } - nodenum = hnodes[nodenum * 2 + (inbyte & 1)]; - inbyte >>= 1; - //----------- - if (nodenum < 256) - { - hnodes = hnodesbase + (nodenum << 9); - *out_p++ = nodenum; - if (!--count) - break; - nodenum = cin.numhnodes1[nodenum]; - } - nodenum = hnodes[nodenum * 2 + (inbyte & 1)]; - inbyte >>= 1; - //----------- - if (nodenum < 256) - { - hnodes = hnodesbase + (nodenum << 9); - *out_p++ = nodenum; - if (!--count) - break; - nodenum = cin.numhnodes1[nodenum]; + out = cin.pic; + count = cin.width * cin.height; + + // read bits + prev = bitpos = inbyte = 0; + for (int i = 0; i < count; i++) { + int nodenum = cin.numhnodes[prev]; + hnode_t *hnodes = cin.hnodes[prev]; + + while (nodenum >= 256) { + if (bitpos == 0) { + if (in >= in_end) + return false; + inbyte = *in++; + bitpos = 8; + } + nodenum = hnodes[nodenum - 256].children[inbyte & 1]; + inbyte >>= 1; + bitpos--; } - nodenum = hnodes[nodenum * 2 + (inbyte & 1)]; - inbyte >>= 1; - } - if (input - in.data != in.count && input - in.data != in.count + 1) - { - Com_Printf("Decompression overread by %li", (input - in.data) - in.count); + *out++ = cin.palette[nodenum]; + prev = nodenum; } - out.count = out_p - out.data; - return out; + return true; } -extern uint32_t d_8to24table[256]; - /* ================== SCR_ReadNextFrame ================== */ -qhandle_t SCR_ReadNextFrame(void) +static bool SCR_ReadNextFrame(void) { - int r; - int command; - byte samples[22050 / 14 * 4]; - byte compressed[0x20000]; - int size; - byte *pic; - cblock_t in, huf1; - int start, end, count; + uint32_t command, size; + byte compressed[0x20000]; // read the next frame - r = FS_Read(&command, 4, cin.file); - if (r == 0) // we'll give it one more chance - r = FS_Read(&command, 4, cin.file); - - if (r != 4) - return 0; + if (FS_Read(&command, 4, cin.file) != 4) + return false; command = LittleLong(command); - if (command == 2) - return 0; // last frame marker - - if (command == 1) - { // read palette - FS_Read(cin.palette, sizeof(cin.palette), cin.file); - cin.palette_active = true; + if (command >= 2) + return false; // last frame marker + if (command == 1) { + // read palette + byte palette[768], *p; + int i; + + if (FS_Read(palette, sizeof(palette), cin.file) != sizeof(palette)) + return false; + + for (i = 0, p = palette; i < 256; i++, p += 3) + cin.palette[i] = MakeColor(p[0], p[1], p[2], 255); } // decompress the next frame - FS_Read(&size, 4, cin.file); + if (FS_Read(&size, 4, cin.file) != 4) + return false; size = LittleLong(size); - if (size > sizeof(compressed) || size < 1) - Com_Error(ERR_DROP, "Bad compressed frame size"); - FS_Read(compressed, size, cin.file); + if (size < 4 || size > sizeof(compressed)) { + Com_EPrintf("Bad compressed frame size\n"); + return false; + } + if (FS_Read(compressed, size, cin.file) != size) + return false; + if (!Huff1Decompress(compressed, size)) { + Com_EPrintf("Decompression overread\n"); + return false; + } // read sound - start = cin.frame_index*cin.s_rate / 14; - end = (cin.frame_index + 1)*cin.s_rate / 14; - count = end - start; + if (cin.s_rate) { + int start = cin.frame * cin.s_rate / 14; + int end = (cin.frame + 1) * cin.s_rate / 14; + int s_size = (end - start) * cin.s_width * cin.s_channels; + byte samples[22050 / 14 * 4]; - FS_Read(samples, count*cin.s_width*cin.s_channels, cin.file); + if (FS_Read(samples, s_size, cin.file) != s_size) + return false; -#if USE_BIG_ENDIAN - if (cin.s_width == 2) { - uint16_t *data = (uint16_t *)samples; - for (int i = 0; i < s_size >> 1; i++) - data[i] = LittleShort(data[i]); + S_RawSamples(end - start, cin.s_rate, cin.s_width, cin.s_channels, samples); } -#endif - - S_RawSamples(count, cin.s_rate, cin.s_width, cin.s_channels, samples, 1.0f); - - in.data = compressed; - in.count = size; - - huf1 = Huff1Decompress(in); - pic = huf1.data; - - uint32_t* rgba = Z_Malloc(cin.width * cin.height * 4); - uint32_t* wptr = rgba; - - for (int y = 0; y < cin.height; y++) - { - if (cin.palette_active) - { - for (int x = 0; x < cin.width; x++) - { - byte* src = cin.palette + (*pic) * 3; - *wptr = MakeColor(src[0], src[1], src[2], 255); - pic++; - wptr++; - } - } - else - { - for (int x = 0; x < cin.width; x++) - { - *wptr = d_8to24table[*pic]; - pic++; - wptr++; - } - } - } - - Z_Free(huf1.data); - - cin.frame_index++; - - const char* image_name = va("%s[%d]", cin.file_name, cin.frame_index); - return R_RegisterRawImage(image_name, cin.width, cin.height, (byte*)rgba, IT_SPRITE, IF_SRGB); + R_UpdateRawPic(cin.width, cin.height, cin.pic); + cin.frame++; + return true; } - /* ================== SCR_RunCinematic - ================== */ void SCR_RunCinematic(void) { - int frame; + unsigned frame; - if (cin.start_time <= 0) + if (cls.state != ca_cinematic) return; - if (cin.frame_index == -1) - return; // static image + if (!cin.file) + return; // static image - if (cls.key_dest != KEY_GAME) - { + if (cls.key_dest != KEY_GAME) { // pause if menu or console is up - cin.start_time = cls.realtime - cin.frame_index * 1000 / 14; - - S_UnqueueRawSamples(); - + cin.time = cls.realtime - cin.frame * 1000 / 14; return; } - frame = (cls.realtime - cin.start_time)*14.0 / 1000; - if (frame <= cin.frame_index) + frame = (cls.realtime - cin.time) * 14 / 1000; + if (frame <= cin.frame) return; - if (frame > cin.frame_index + 1) - { - // Com_Printf("Dropped frame: %i > %i\n", frame, cin.frame_index + 1); - cin.start_time = cls.realtime - cin.frame_index * 1000 / 14; - } - R_UnregisterImage(cl.image_precache[0]); - cl.image_precache[0] = SCR_ReadNextFrame(); + if (frame > cin.frame + 1) { + Com_DPrintf("Dropped frame: %u > %u\n", frame, cin.frame + 1); + cin.time = cls.realtime - cin.frame * 1000 / 14; + } - if (!cl.image_precache[0]) - { + if (!SCR_ReadNextFrame()) { SCR_FinishCinematic(); - cin.start_time = 1; // hack to get the black screen behind loading - SCR_BeginLoadingPlaque(); - cin.start_time = 0; return; } } /* ================== -SCR_PlayCinematic - +SCR_DrawCinematic ================== */ -void SCR_PlayCinematic(const char *name) +void SCR_DrawCinematic(void) { - int width, height; - int old_khz; + if (cin.pic) { + R_DrawStretchRaw(0, 0, r_config.width, r_config.height); + return; + } - // make sure CD isn't playing music - OGG_Stop(); + qhandle_t pic = cl.image_precache[0]; - cin.s_khz_original = 0; + if (!pic || R_GetPicSize(NULL, NULL, pic)) + R_DrawFill8(0, 0, r_config.width, r_config.height, 0); - cin.frame_index = 0; - cin.start_time = 0; + if (pic) + R_DrawStretchPic(0, 0, r_config.width, r_config.height, pic); +} - if (!COM_CompareExtension(name, ".pcx")) - { - cl.image_precache[0] = R_RegisterPic2(name); - if (!cl.image_precache[0]) { - SCR_FinishCinematic(); - return; - } +/* +================== +SCR_StartCinematic +================== +*/ +static bool SCR_StartCinematic(const char *name) +{ + cheader_t header; + char fullname[MAX_QPATH]; + int ret; + + if (Q_snprintf(fullname, sizeof(fullname), "video/%s", name) >= sizeof(fullname)) { + Com_EPrintf("Oversize cinematic name\n"); + return false; } - else if (!COM_CompareExtension(name, ".cin")) - { - if (!Cvar_VariableValue("cl_cinematics")) - { - SCR_FinishCinematic(); - return; - } - Q_snprintf(cin.file_name, sizeof(cin.file_name), "video/%s", name); + ret = FS_OpenFile(fullname, &cin.file, FS_MODE_READ); + if (!cin.file) { + Com_EPrintf("Couldn't load %s: %s\n", fullname, Q_ErrorString(ret)); + return false; + } - FS_OpenFile(cin.file_name, &cin.file, FS_MODE_READ); - if (!cin.file) - { - Com_WPrintf("Cinematic \"%s\" not found. Skipping.\n", name); - SCR_FinishCinematic(); - return; - } + if (FS_Read(&header, sizeof(header), cin.file) != sizeof(header)) { + Com_EPrintf("Error reading cinematic header\n"); + return false; + } - FS_Read(&width, 4, cin.file); - FS_Read(&height, 4, cin.file); - cin.width = LittleLong(width); - cin.height = LittleLong(height); + cin.width = LittleLong(header.width); + cin.height = LittleLong(header.height); + cin.s_rate = LittleLong(header.s_rate); + cin.s_width = LittleLong(header.s_width); + cin.s_channels = LittleLong(header.s_channels); - FS_Read(&cin.s_rate, 4, cin.file); - cin.s_rate = LittleLong(cin.s_rate); - FS_Read(&cin.s_width, 4, cin.file); - cin.s_width = LittleLong(cin.s_width); - FS_Read(&cin.s_channels, 4, cin.file); - cin.s_channels = LittleLong(cin.s_channels); + if (cin.width < 1 || cin.width > 640 || cin.height < 1 || cin.height > 480) { + Com_EPrintf("Bad cinematic video dimensions\n"); + return false; + } + if (cin.s_rate && (cin.s_rate < 8000 || cin.s_rate > 22050 || + cin.s_width < 1 || cin.s_width > 2 || + cin.s_channels < 1 || cin.s_channels > 2)) { + Com_EPrintf("Bad cinematic audio parameters\n"); + return false; + } - Huff1TableInit(); + if (!Huff1TableInit()) { + Com_EPrintf("Error reading huffman table\n"); + return false; + } - cin.palette_active = false; + cin.frame = 0; + cin.time = cls.realtime; + cin.pic = Z_Malloc(cin.width * cin.height * 4); - // switch to 22 khz sound if necessary - old_khz = Cvar_VariableValue("s_khz"); - if (old_khz != cin.s_rate / 1000 && s_started == SS_DMA) - { - cin.s_khz_original = old_khz; - Cvar_Set("s_khz", va("%d", cin.s_rate / 1000)); - } + return SCR_ReadNextFrame(); +} - cin.frame_index = 0; - cl.image_precache[0] = SCR_ReadNextFrame(); - cin.start_time = cls.realtime; - } - else - { - SCR_FinishCinematic(); - return; +/* +================== +SCR_PlayCinematic +================== +*/ +void SCR_PlayCinematic(const char *name) +{ + // make sure CD isn't playing music + OGG_Stop(); + + if (!COM_CompareExtension(name, ".pcx")) { + cl.image_precache[0] = R_RegisterPic2(name); + if (!cl.image_precache[0]) + goto finish; + } else if (!COM_CompareExtension(name, ".cin")) { + if (!SCR_StartCinematic(name)) + goto finish; + } else { + goto finish; } + // save picture name for reloading + Q_strlcpy(cl.mapname, name, sizeof(cl.mapname)); + cls.state = ca_cinematic; SCR_EndLoadingPlaque(); // get rid of loading plaque - Con_Close(false); // get rid of connection screen + Con_Close(false); // get rid of connection screen + return; + +finish: + SCR_FinishCinematic(); } diff --git a/src/client/client.h b/src/client/client.h index f6f87aeb4..4ba39853c 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -945,9 +945,6 @@ void SCR_UpdateScreen(void); void SCR_SizeUp(void); void SCR_SizeDown(void); void SCR_CenterPrint(const char *str); -void SCR_FinishCinematic(void); -void SCR_PlayCinematic(const char *name); -void SCR_RunCinematic(void); void SCR_BeginLoadingPlaque(void); void SCR_EndLoadingPlaque(void); void SCR_TouchPics(void); @@ -966,6 +963,14 @@ void SCR_DrawStringMulti(int x, int y, int flags, size_t maxlen, const char * void SCR_ClearChatHUD_f(void); void SCR_AddToChatHUD(const char *text); +// +// cin.c +// +void SCR_StopCinematic(void); +void SCR_FinishCinematic(void); +void SCR_RunCinematic(void); +void SCR_DrawCinematic(void); +void SCR_PlayCinematic(const char *name); // // ascii.c diff --git a/src/client/keys.c b/src/client/keys.c index 2ce19ff32..6071f5271 100644 --- a/src/client/keys.c +++ b/src/client/keys.c @@ -713,11 +713,6 @@ void Key_Event(unsigned key, bool down, unsigned time) IN_Activate(); } - // skip the rest of the cinematic - if (cls.key_dest == KEY_GAME && cls.state == ca_cinematic && down) { - SCR_FinishCinematic(); - } - if (cls.key_dest == KEY_GAME) { if(R_InterceptKey(key, down)) @@ -763,6 +758,11 @@ void Key_Event(unsigned key, bool down, unsigned time) // button commands add keynum and time as a parm Q_snprintf(cmd, sizeof(cmd), "%s %i %i\n", kb, key, time); Cbuf_AddText(&cmd_buffer, cmd); + + // skip the rest of the cinematic + if (cls.state == ca_cinematic) { + SCR_FinishCinematic(); + } } else { Cbuf_AddText(&cmd_buffer, kb); Cbuf_AddText(&cmd_buffer, "\n"); diff --git a/src/client/main.c b/src/client/main.c index 8c8fb8ba5..9de7f8010 100644 --- a/src/client/main.c +++ b/src/client/main.c @@ -703,6 +703,7 @@ void CL_ClearState(void) { S_StopAllSounds(); OGG_Stop(); + SCR_StopCinematic(); CL_ClearEffects(); CL_ClearTEnts(); LOC_FreeLocations(); @@ -2387,7 +2388,7 @@ void CL_RestartFilesystem(bool total) CL_LoadState(LOAD_SOUNDS); CL_RegisterSounds(); CL_LoadState(LOAD_NONE); - } else if (cls_state == ca_cinematic) { + } else if (cls_state == ca_cinematic && !COM_CompareExtension(cl.mapname, ".pcx")) { cl.image_precache[0] = R_RegisterPic2(cl.mapname); } @@ -2442,7 +2443,7 @@ void CL_RestartRefresh(bool total) CL_LoadState(LOAD_MAP); CL_PrepRefresh(); CL_LoadState(LOAD_NONE); - } else if (cls_state == ca_cinematic) { + } else if (cls_state == ca_cinematic && !COM_CompareExtension(cl.mapname, ".pcx")) { cl.image_precache[0] = R_RegisterPic2(cl.mapname); } @@ -3265,6 +3266,8 @@ unsigned CL_Frame(unsigned msec) Con_RunConsole(); + SCR_RunCinematic(); + UI_Frame(main_extra); if (ref_frame) { diff --git a/src/client/parse.c b/src/client/parse.c index c37be4b96..c50b9f365 100644 --- a/src/client/parse.c +++ b/src/client/parse.c @@ -596,7 +596,7 @@ static void CL_ParseServerData(void) if (cls.protocolVersion >= PROTOCOL_VERSION_Q2PRO_SERVER_STATE) { Com_DPrintf("Q2PRO server state %d\n", i); cl.serverstate = i; - cinematic = i == ss_pic; + cinematic = i == ss_pic || i == ss_cinematic; } i = MSG_ReadByte(); if (i) { diff --git a/src/client/refresh.c b/src/client/refresh.c index 506c0da7d..98a0cdbeb 100644 --- a/src/client/refresh.c +++ b/src/client/refresh.c @@ -423,9 +423,11 @@ int(*R_DrawString)(int x, int y, int flags, size_t maxChars, const char *string, qhandle_t font) = NULL; void(*R_DrawPic)(int x, int y, qhandle_t pic) = NULL; void(*R_DrawStretchPic)(int x, int y, int w, int h, qhandle_t pic) = NULL; +void(*R_DrawStretchRaw)(int x, int y, int w, int h) = NULL; void(*R_TileClear)(int x, int y, int w, int h, qhandle_t pic) = NULL; void(*R_DrawFill8)(int x, int y, int w, int h, int c) = NULL; void(*R_DrawFill32)(int x, int y, int w, int h, uint32_t color) = NULL; +void(*R_UpdateRawPic)(int pic_w, int pic_h, const uint32_t *pic) = NULL; void(*R_BeginFrame)(void) = NULL; void(*R_EndFrame)(void) = NULL; void(*R_ModeChanged)(int width, int height, int flags, int rowbytes, void *pixels) = NULL; diff --git a/src/client/screen.c b/src/client/screen.c index ad1cc1f0a..ebf8f3723 100644 --- a/src/client/screen.c +++ b/src/client/screen.c @@ -1298,6 +1298,8 @@ void SCR_Shutdown(void) scr.initialized = false; } +//============================================================================= + /* ================ SCR_BeginLoadingPlaque @@ -1974,24 +1976,7 @@ static void SCR_DrawActive(void) } if (cls.state == ca_cinematic) { - if (cl.image_precache[0]) - { - // scale the image to touch the screen from inside, keeping the aspect ratio - - image_t* image = IMG_ForHandle(cl.image_precache[0]); - - float zoomx = (float)r_config.width / (float)image->width; - float zoomy = (float)r_config.height / (float)image->height; - float zoom = min(zoomx, zoomy); - - int w = (int)(image->width * zoom); - int h = (int)(image->height * zoom); - int x = (r_config.width - w) / 2; - int y = (r_config.height - h) / 2; - - R_DrawFill8(0, 0, r_config.width, r_config.height, 0); - R_DrawStretchPic(x, y, w, h, cl.image_precache[0]); - } + SCR_DrawCinematic(); return; } diff --git a/src/client/sound/main.c b/src/client/sound/main.c index 6ed34d8ba..690b9f426 100644 --- a/src/client/sound/main.c +++ b/src/client/sound/main.c @@ -786,6 +786,12 @@ void S_StopAllSounds(void) memset(s_channels, 0, sizeof(s_channels)); } +void S_RawSamples(int samples, int rate, int width, int channels, const byte *data) +{ + if (s_started) + s_api.raw_samples(samples, rate, width, channels, data, 1.0f); +} + // ======================================================================= // Update sound buffer // ======================================================================= @@ -866,18 +872,6 @@ float S_GetLinearVolume(float perceptual) return volume; } -/* - * Cinematic streaming and voice over network. - * This could be used for chat over network, but - * that would be terrible slow. - */ -bool -S_RawSamples(int samples, int rate, int width, - int channels, byte *data, float volume) -{ - return s_api.raw_samples(samples, rate, width, channels, data, volume); -} - void S_UnqueueRawSamples() { s_api.drop_raw_samples(); diff --git a/src/refresh/gl/draw.c b/src/refresh/gl/draw.c index 535c52123..3ab77adfa 100644 --- a/src/refresh/gl/draw.c +++ b/src/refresh/gl/draw.c @@ -184,6 +184,17 @@ void R_DrawPic_GL(int x, int y, qhandle_t pic) image->sl, image->tl, image->sh, image->th, draw.colors[0].u32, image); } +void R_DrawStretchRaw_GL(int x, int y, int w, int h) +{ + _GL_StretchPic(x, y, w, h, 0, 0, 1, 1, U32_WHITE, TEXNUM_RAW, 0); +} + +void R_UpdateRawPic_GL(int pic_w, int pic_h, const uint32_t *pic) +{ + GL_ForceTexture(0, TEXNUM_RAW); + qglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pic_w, pic_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pic); +} + #define DIV64 (1.0f / 64.0f) void R_TileClear_GL(int x, int y, int w, int h, qhandle_t pic) diff --git a/src/refresh/gl/gl.h b/src/refresh/gl/gl.h index f7be2cb24..fc2503085 100644 --- a/src/refresh/gl/gl.h +++ b/src/refresh/gl/gl.h @@ -51,7 +51,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define TAB_COS(x) gl_static.sintab[((x) + 64) & 255] #define MAX_PROGRAMS 64 -#define NUM_TEXNUMS 6 +#define NUM_TEXNUMS 7 typedef struct { const char *name; @@ -472,6 +472,8 @@ float R_ClampScaleGL(cvar_t *var); void R_SetScale_GL(float scale); void R_DrawStretchPic_GL(int x, int y, int w, int h, qhandle_t pic); void R_DrawPic_GL(int x, int y, qhandle_t pic); +void R_DrawStretchRaw_GL(int x, int y, int w, int h); +void R_UpdateRawPic_GL(int pic_w, int pic_h, const uint32_t *pic); void R_TileClear_GL(int x, int y, int w, int h, qhandle_t pic); void R_DrawFill8_GL(int x, int y, int w, int h, int c); void R_DrawFill32_GL(int x, int y, int w, int h, uint32_t color); @@ -490,6 +492,7 @@ int R_DrawString_GL(int x, int y, int flags, size_t maxlen, const char *s, qhand #define TEXNUM_BEAM gl_static.texnums[3] #define TEXNUM_WHITE gl_static.texnums[4] #define TEXNUM_BLACK gl_static.texnums[5] +#define TEXNUM_RAW gl_static.texnums[6] void Scrap_Upload(void); diff --git a/src/refresh/gl/main.c b/src/refresh/gl/main.c index 1a9f856a7..55866d675 100644 --- a/src/refresh/gl/main.c +++ b/src/refresh/gl/main.c @@ -964,6 +964,8 @@ void R_RegisterFunctionsGL() R_DrawString = R_DrawString_GL; R_DrawPic = R_DrawPic_GL; R_DrawStretchPic = R_DrawStretchPic_GL; + R_DrawStretchRaw = R_DrawStretchRaw_GL; + R_UpdateRawPic = R_UpdateRawPic_GL; R_TileClear = R_TileClear_GL; R_DrawFill8 = R_DrawFill8_GL; R_DrawFill32 = R_DrawFill32_GL; diff --git a/src/refresh/gl/texture.c b/src/refresh/gl/texture.c index ae843adec..bc3242c04 100644 --- a/src/refresh/gl/texture.c +++ b/src/refresh/gl/texture.c @@ -893,6 +893,12 @@ static void GL_InitBeamTexture(void) GL_SetFilterAndRepeat(IT_SPRITE, IF_NONE); } +static void GL_InitRawTexture(void) +{ + GL_ForceTexture(0, TEXNUM_RAW); + GL_SetFilterAndRepeat(IT_PIC, IF_NONE); +} + static void gl_partshape_changed(cvar_t *self) { GL_InitParticleTexture(); @@ -985,6 +991,7 @@ void GL_InitImages(void) GL_InitParticleTexture(); GL_InitWhiteImage(); GL_InitBeamTexture(); + GL_InitRawTexture(); GL_ShowErrors(__func__); } diff --git a/src/refresh/vkpt/draw.c b/src/refresh/vkpt/draw.c index bf378c7d5..9e6b0ae5c 100644 --- a/src/refresh/vkpt/draw.c +++ b/src/refresh/vkpt/draw.c @@ -816,6 +816,25 @@ R_DrawPic_RTX(int x, int y, qhandle_t pic) R_DrawStretchPic(x, y, image->width, image->height, pic); } +void +R_DrawStretchRaw_RTX(int x, int y, int w, int h) +{ + R_DrawStretchPic(x, y, w, h, qvk.raw_image - r_images); +} + +void +R_UpdateRawPic_RTX(int pic_w, int pic_h, const uint32_t *pic) +{ + if(qvk.raw_image) + R_UnregisterImage(qvk.raw_image - r_images); + + size_t raw_size = pic_w * pic_h * 4; + byte *raw_data = Z_Malloc(raw_size); + memcpy(raw_data, pic, raw_size); + static int raw_id; + qvk.raw_image = r_images + R_RegisterRawImage(va("**raw[%d]**", raw_id++), pic_w, pic_h, raw_data, IT_SPRITE, IF_SRGB); +} + #define DIV64 (1.0f / 64.0f) void diff --git a/src/refresh/vkpt/main.c b/src/refresh/vkpt/main.c index 6327167c1..fd3c086ab 100644 --- a/src/refresh/vkpt/main.c +++ b/src/refresh/vkpt/main.c @@ -4413,6 +4413,8 @@ void R_RegisterFunctionsRTX() R_DrawString = R_DrawString_RTX; R_DrawPic = R_DrawPic_RTX; R_DrawStretchPic = R_DrawStretchPic_RTX; + R_DrawStretchRaw = R_DrawStretchRaw_RTX; + R_UpdateRawPic = R_UpdateRawPic_RTX; R_TileClear = R_TileClear_RTX; R_DrawFill8 = R_DrawFill8_RTX; R_DrawFill32 = R_DrawFill32_RTX; diff --git a/src/refresh/vkpt/vkpt.h b/src/refresh/vkpt/vkpt.h index 24ca8b1ee..d262ed79f 100644 --- a/src/refresh/vkpt/vkpt.h +++ b/src/refresh/vkpt/vkpt.h @@ -286,6 +286,8 @@ typedef struct QVK_s { VkDeviceMemory screenshot_image_memory; VkDeviceSize screenshot_image_memory_size; + image_t *raw_image; // "raw" image, for cinematics + #ifdef VKPT_IMAGE_DUMPS // host-visible image for dumping FB data through VkImage dump_image; @@ -840,6 +842,8 @@ void R_LightPoint_RTX(const vec3_t origin, vec3_t light); void R_SetScale_RTX(float scale); void R_DrawStretchPic_RTX(int x, int y, int w, int h, qhandle_t pic); void R_DrawPic_RTX(int x, int y, qhandle_t pic); +void R_DrawStretchRaw_RTX(int x, int y, int w, int h); +void R_UpdateRawPic_RTX(int pic_w, int pic_h, const uint32_t *pic); void R_TileClear_RTX(int x, int y, int w, int h, qhandle_t pic); void R_DrawFill8_RTX(int x, int y, int w, int h, int c); void R_DrawFill32_RTX(int x, int y, int w, int h, uint32_t color); diff --git a/src/server/commands.c b/src/server/commands.c index 9ec4fd597..9addb3a2b 100644 --- a/src/server/commands.c +++ b/src/server/commands.c @@ -272,7 +272,7 @@ static void SV_Map(bool restart) SV_AutoSaveBegin(&cmd); // any error will drop from this point - if ((sv.state != ss_game && sv.state != ss_pic && sv.state != ss_cinematic) || restart) + if (sv.state < ss_game || sv.state == ss_broadcast || restart) SV_InitGame(MVD_SPAWN_DISABLED); // the game is just starting // clear pending CM @@ -356,7 +356,7 @@ static int should_really_restart(void) { static bool warned; - if (sv.state != ss_game && sv.state != ss_pic && sv.state != ss_cinematic) + if (sv.state < ss_game || sv.state == ss_broadcast) return 1; // the game is just starting #if !USE_CLIENT diff --git a/src/server/init.c b/src/server/init.c index 9a2d4bab5..e4fe95859 100644 --- a/src/server/init.c +++ b/src/server/init.c @@ -269,40 +269,14 @@ void SV_SpawnServer(mapcmd_t *cmd) Com_Printf("-------------------------------------\n"); } -/* -============== -SV_ParseMapCmd - -Parses mapcmd into more C friendly form. -Loads and fully validates the map to make sure server doesn't get killed. -============== -*/ -bool SV_ParseMapCmd(mapcmd_t *cmd) +static bool check_server(mapcmd_t *cmd, const char *server, bool nextserver) { char expanded[MAX_QPATH]; char *s, *ch; int ret = Q_ERR(ENAMETOOLONG); - s = cmd->buffer; - - // skip the end-of-unit flag if necessary - if (*s == '*') { - s++; - cmd->endofunit = true; - } - - // if there is a + in the map, set nextserver to the remainder. - ch = strchr(s, '+'); - if (ch) - { - *ch = 0; - Cvar_Set("nextserver", va("gamemap \"%s\"", ch + 1)); - } - else - Cvar_Set("nextserver", ""); - // copy it off to keep original mapcmd intact - Q_strlcpy(cmd->server, s, sizeof(cmd->server)); + Q_strlcpy(cmd->server, server, sizeof(cmd->server)); s = cmd->server; // if there is a $, use the remainder as a spawnpoint @@ -317,12 +291,16 @@ bool SV_ParseMapCmd(mapcmd_t *cmd) // now expand and try to load the map if (!COM_CompareExtension(s, ".pcx")) { if (Q_concat(expanded, sizeof(expanded), "pics/", s) < sizeof(expanded)) { - ret = FS_LoadFile(expanded, NULL); + ret = COM_DEDICATED ? Q_ERR_SUCCESS : FS_LoadFile(expanded, NULL); } cmd->state = ss_pic; - } - else if (!COM_CompareExtension(s, ".cin")) { - ret = Q_ERR_SUCCESS; + } else if (!COM_CompareExtension(s, ".cin")) { + if (!sv_cinematics->integer && nextserver) + return false; // skip it + if (Q_concat(expanded, sizeof(expanded), "video/", s) < sizeof(expanded)) { + if (COM_DEDICATED || (ret = FS_LoadFile(expanded, NULL)) == Q_ERR(EFBIG)) + ret = Q_ERR_SUCCESS; + } cmd->state = ss_cinematic; } else { @@ -340,6 +318,68 @@ bool SV_ParseMapCmd(mapcmd_t *cmd) return true; } +/* +============== +SV_ParseMapCmd + +Parses mapcmd into more C friendly form. +Loads and fully validates the map to make sure server doesn't get killed. +============== +*/ +bool SV_ParseMapCmd(mapcmd_t *cmd) +{ + char *s, *ch; + char copy[MAX_QPATH]; + bool killserver = false; + + // copy it off to keep original mapcmd intact + Q_strlcpy(copy, cmd->buffer, sizeof(copy)); + s = copy; + + while (1) { + // hack for nextserver: kill server if map doesn't exist + if (*s == '!') { + s++; + killserver = true; + } + + // skip the end-of-unit flag if necessary + if (*s == '*') { + s++; + cmd->endofunit = true; + } + + // if there is a + in the map, set nextserver to the remainder. + ch = strchr(s, '+'); + if (ch) + *ch = 0; + + // see if map exists and can be loaded + if (check_server(cmd, s, ch)) { + if (ch) + Cvar_Set("nextserver", va("gamemap \"!%s\"", ch + 1)); + else + Cvar_Set("nextserver", ""); + + // special hack for end game screen in coop mode + if (Cvar_VariableInteger("coop") && !Q_stricmp(s, "victory.pcx")) + Cvar_Set("nextserver", "gamemap \"!*base1\""); + return true; + } + + // skip to nextserver if cinematic doesn't exist + if (!ch) + break; + + s = ch + 1; + } + + if (killserver) + Cbuf_AddText(&cmd_buffer, "killserver\n"); + + return false; +} + /* ============== SV_InitGame diff --git a/src/server/main.c b/src/server/main.c index e81fa1c70..fb0a406dd 100644 --- a/src/server/main.c +++ b/src/server/main.c @@ -88,6 +88,7 @@ cvar_t *sv_waterjump_hack; cvar_t *sv_packetdup_hack; #endif cvar_t *sv_allow_map; +cvar_t *sv_cinematics; #if !USE_CLIENT cvar_t *sv_recycle; #endif @@ -2226,6 +2227,7 @@ void SV_Init(void) #endif sv_allow_map = Cvar_Get("sv_allow_map", "0", 0); + sv_cinematics = Cvar_Get("sv_cinematics", "1", 0); #if !USE_CLIENT sv_recycle = Cvar_Get("sv_recycle", "0", 0); diff --git a/src/server/server.h b/src/server/server.h index b5f090094..d7db606ac 100644 --- a/src/server/server.h +++ b/src/server/server.h @@ -536,6 +536,7 @@ extern cvar_t *sv_strafejump_hack; extern cvar_t *sv_packetdup_hack; #endif extern cvar_t *sv_allow_map; +extern cvar_t *sv_cinematics; #if !USE_CLIENT extern cvar_t *sv_recycle; #endif diff --git a/src/server/user.c b/src/server/user.c index 308d5a7a1..5120ee0d9 100644 --- a/src/server/user.c +++ b/src/server/user.c @@ -330,7 +330,10 @@ void SV_New_f(void) break; case PROTOCOL_VERSION_Q2PRO: MSG_WriteShort(sv_client->version); - MSG_WriteByte(sv.state); + if (sv.state == ss_cinematic && sv_client->version < PROTOCOL_VERSION_Q2PRO_CINEMATICS) + MSG_WriteByte(ss_pic); + else + MSG_WriteByte(sv.state); MSG_WriteByte(sv_client->pmp.strafehack); MSG_WriteByte(sv_client->pmp.qwmode); if (sv_client->version >= PROTOCOL_VERSION_Q2PRO_WATERJUMP_HACK) { @@ -667,34 +670,29 @@ static void SV_StopDownload_f(void) //============================================================================ -// special hack for end game screen in coop mode +// a cinematic has completed or been aborted by a client, so move to the next server static void SV_NextServer_f(void) { - char nextserver[MAX_QPATH]; - char* v = Cvar_VariableString("nextserver"); - Q_strlcpy(nextserver, v, sizeof(nextserver)); - Cvar_Set("nextserver", ""); - if (sv.state != ss_pic && sv.state != ss_cinematic) return; // can't nextserver while playing a normal game - if (Cvar_VariableInteger("deathmatch")) - return; + if (sv.state == ss_pic && !Cvar_VariableInteger("coop")) + return; // ss_pic can be nextserver'd in coop mode - sv.name[0] = 0; // make sure another doesn't sneak in + if (atoi(Cmd_Argv(1)) != sv.spawncount) + return; // leftover from last server - if (!nextserver[0]) - { - if (Cvar_VariableInteger("coop")) - Cbuf_AddText(&cmd_buffer, "gamemap \"*base1\"\n"); - else - Cbuf_AddText(&cmd_buffer, "killserver\n"); - } - else - { - Cbuf_AddText(&cmd_buffer, nextserver); + sv.spawncount ^= 1; // make sure another doesn't sneak in + + const char *v = Cvar_VariableString("nextserver"); + if (*v) { + Cbuf_AddText(&cmd_buffer, v); Cbuf_AddText(&cmd_buffer, "\n"); + } else { + Cbuf_AddText(&cmd_buffer, "killserver\n"); } + + Cvar_Set("nextserver", ""); } // the client is going to disconnect, so remove the connection immediately