yquake2remaster/src/client/cl_cin.c

702 lines
12 KiB
C
Raw Normal View History

/*
* Copyright (C) 1997-2001 Id Software, Inc.
*
* 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.
*
* 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.
*
* =======================================================================
*
* This file implements the .cin video codec and the corresponding .pcx
* bitmap decoder. .cin files are just a bunch of .pcx images.
*
* =======================================================================
*/
#include <limits.h>
2009-03-03 13:43:32 +00:00
#include "header/client.h"
#include "input/header/input.h"
extern cvar_t *vid_renderer;
cvar_t *cin_force43;
Another try to fix the problem of spurious aborts of cinematics. Until this commit a cinematic was aborted as soon as any key were marked down when finishing the user command and sending it to the server. The whole logic to detect if a key is down is broken, for example `vid_restart` may leave keys marked down that are in fact up. And there's the possibility to inject fake key events from nearly everywhere. I'm not really sure but I suspect that even the server may be able to inject key events. Therefore untangle the cinematic abort code from the user command processing, it should depend only on real key strokes: 1. Introduce a new global variable `abort_cinamatic` and set it to `cls.realtime` as soon as a key down event is detected. The only exceptions are Escape and Shift, because opening the menu and toggeling the console should never abort a cinematic. 2. When starting a cinematic `abort_cinamatic` is set to INT_MAX, because it needs to be higher than the current `cls.realtime`. 3. When a cinematic is running, `cls.key_dest` is set to `key_game` (`key_menu` and `key_console` are ignored, keys send to the menu or the console should never abort a cinematic; `key_message` can / should never happen while a cinematic is running) and `abort_cinamatic` is less than `cls.realtime` the cinematic is aborted. `abort_cinamatic` less than `cls.realtime` is necessary because the client needs one frame to pop up the menu or toggle the console and set the `cls.key_dest` accordingly. `abort_cinamatic == cls.realtime - 1` is not possible because not every frame finishes a user command. This closes #502.
2020-02-17 16:56:54 +00:00
int abort_cinematic;
2012-07-22 13:34:45 +00:00
typedef struct
{
byte *data;
int count;
} cblock_t;
2012-07-22 13:34:45 +00:00
typedef struct
{
qboolean restart_sound;
int s_rate;
int s_width;
int s_channels;
2012-07-22 13:34:45 +00:00
int width;
int height;
byte *pic;
byte *pic_pending;
/* order 1 huffman stuff */
2012-07-22 13:34:45 +00:00
int *hnodes1;
/* [256][256][2]; */
2012-07-22 13:34:45 +00:00
int numhnodes1[256];
2012-07-22 13:34:45 +00:00
int h_used[512];
int h_count[512];
} cinematics_t;
2012-07-22 13:34:45 +00:00
cinematics_t cin;
void
2012-07-22 13:34:45 +00:00
SCR_LoadPCX(char *filename, byte **pic, byte **palette, int *width, int *height)
{
byte *raw;
pcx_t *pcx;
int x, y;
int len, full_size;
2012-07-22 13:34:45 +00:00
int dataByte, runLength;
byte *out, *pix;
*pic = NULL;
/* load the file */
len = FS_LoadFile(filename, (void **)&raw);
if (!raw || len < sizeof(pcx_t))
2012-07-22 13:34:45 +00:00
{
return;
2012-07-22 13:34:45 +00:00
}
/* parse the PCX file */
2012-07-22 13:34:45 +00:00
pcx = (pcx_t *)raw;
raw = &pcx->data;
2012-07-22 13:34:45 +00:00
if ((pcx->manufacturer != 0x0a) ||
(pcx->version != 5) ||
(pcx->encoding != 1) ||
(pcx->bits_per_pixel != 8) ||
(pcx->xmax >= 640) ||
(pcx->ymax >= 480))
{
Com_Printf("Bad pcx file %s\n", filename);
return;
}
full_size = (pcx->ymax + 1) * (pcx->xmax + 1);
out = Z_Malloc(full_size);
*pic = out;
pix = out;
2012-07-22 13:34:45 +00:00
if (palette)
{
*palette = Z_Malloc(768);
2012-07-22 13:34:45 +00:00
memcpy(*palette, (byte *)pcx + len - 768, 768);
}
if (width)
2012-07-22 13:34:45 +00:00
{
*width = pcx->xmax + 1;
2012-07-22 13:34:45 +00:00
}
if (height)
2012-07-22 13:34:45 +00:00
{
*height = pcx->ymax + 1;
2012-07-22 13:34:45 +00:00
}
2012-07-22 13:34:45 +00:00
for (y = 0; y <= pcx->ymax; y++, pix += pcx->xmax + 1)
{
for (x = 0; x <= pcx->xmax; )
{
dataByte = *raw++;
2012-07-22 13:34:45 +00:00
if ((dataByte & 0xC0) == 0xC0)
{
runLength = dataByte & 0x3F;
dataByte = *raw++;
2012-07-22 13:34:45 +00:00
}
else
{
runLength = 1;
2012-07-22 13:34:45 +00:00
}
while (runLength-- > 0)
2012-07-22 13:34:45 +00:00
{
if ((*pic + full_size) <= (pix + x))
{
x += runLength;
runLength = 0;
}
else
{
pix[x++] = dataByte;
}
2012-07-22 13:34:45 +00:00
}
}
}
2012-07-22 13:34:45 +00:00
if (raw - (byte *)pcx > len)
{
Com_Printf("PCX file %s was malformed", filename);
Z_Free(*pic);
*pic = NULL;
}
FS_FreeFile(pcx);
}
void
2012-07-22 13:34:45 +00:00
SCR_StopCinematic(void)
{
cl.cinematictime = 0; /* done */
2012-07-22 13:34:45 +00:00
if (cin.pic)
{
Z_Free(cin.pic);
cin.pic = NULL;
}
2012-07-22 13:34:45 +00:00
if (cin.pic_pending)
{
Z_Free(cin.pic_pending);
cin.pic_pending = NULL;
}
2012-07-22 13:34:45 +00:00
if (cl.cinematicpalette_active)
{
R_SetPalette(NULL);
cl.cinematicpalette_active = false;
}
2012-07-22 13:34:45 +00:00
if (cl.cinematic_file)
{
FS_FCloseFile(cl.cinematic_file);
cl.cinematic_file = 0;
}
2012-07-22 13:34:45 +00:00
if (cin.hnodes1)
{
Z_Free(cin.hnodes1);
cin.hnodes1 = NULL;
}
/* switch back down to 11 khz sound if necessary */
2012-07-22 13:34:45 +00:00
if (cin.restart_sound)
{
cin.restart_sound = false;
CL_Snd_Restart_f();
}
}
void
2012-07-22 13:34:45 +00:00
SCR_FinishCinematic(void)
{
/* tell the server to advance to the next map / cinematic */
MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
SZ_Print(&cls.netchan.message, va("nextserver %i\n", cl.servercount));
}
int
2012-07-22 13:34:45 +00:00
SmallestNode1(int numhnodes)
{
int i;
int best, bestnode;
best = 99999999;
bestnode = -1;
2012-07-22 13:34:45 +00:00
for (i = 0; i < numhnodes; i++)
{
if (cin.h_used[i])
2012-07-22 13:34:45 +00:00
{
continue;
2012-07-22 13:34:45 +00:00
}
if (!cin.h_count[i])
2012-07-22 13:34:45 +00:00
{
continue;
2012-07-22 13:34:45 +00:00
}
2012-07-22 13:34:45 +00:00
if (cin.h_count[i] < best)
{
best = cin.h_count[i];
bestnode = i;
}
}
if (bestnode == -1)
2012-07-22 13:34:45 +00:00
{
return -1;
2012-07-22 13:34:45 +00:00
}
cin.h_used[bestnode] = true;
return bestnode;
}
2010-06-18 18:07:56 +00:00
/*
* Reads the 64k counts table and initializes the node trees
*/
void
2012-07-22 13:34:45 +00:00
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);
2012-07-22 13:34:45 +00:00
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), cl.cinematic_file);
for (j = 0; j < 256; j++)
2012-07-22 13:34:45 +00:00
{
cin.h_count[j] = counts[j];
2012-07-22 13:34:45 +00:00
}
/* build the nodes */
numhnodes = 256;
nodebase = cin.hnodes1 + prev * 256 * 2;
2012-07-22 13:34:45 +00:00
while (numhnodes != 511)
{
node = nodebase + (numhnodes - 256) * 2;
/* pick two lowest counts */
node[0] = SmallestNode1(numhnodes);
if (node[0] == -1)
2012-07-22 13:34:45 +00:00
{
break; /* no more counts */
}
node[1] = SmallestNode1(numhnodes);
if (node[1] == -1)
2012-07-22 13:34:45 +00:00
{
break;
2012-07-22 13:34:45 +00:00
}
2012-07-22 13:34:45 +00:00
cin.h_count[numhnodes] = cin.h_count[node[0]] +
cin.h_count[node[1]];
numhnodes++;
}
cin.numhnodes1[prev] = numhnodes - 1;
}
}
cblock_t
2012-07-22 13:34:45 +00:00
Huff1Decompress(cblock_t in)
{
byte *input;
byte *out_p;
int nodenum;
int count;
cblock_t out;
int inbyte;
int *hnodes, *hnodesbase;
/* get decompressed count */
Another try to fix the problem of spurious aborts of cinematics. Until this commit a cinematic was aborted as soon as any key were marked down when finishing the user command and sending it to the server. The whole logic to detect if a key is down is broken, for example `vid_restart` may leave keys marked down that are in fact up. And there's the possibility to inject fake key events from nearly everywhere. I'm not really sure but I suspect that even the server may be able to inject key events. Therefore untangle the cinematic abort code from the user command processing, it should depend only on real key strokes: 1. Introduce a new global variable `abort_cinamatic` and set it to `cls.realtime` as soon as a key down event is detected. The only exceptions are Escape and Shift, because opening the menu and toggeling the console should never abort a cinematic. 2. When starting a cinematic `abort_cinamatic` is set to INT_MAX, because it needs to be higher than the current `cls.realtime`. 3. When a cinematic is running, `cls.key_dest` is set to `key_game` (`key_menu` and `key_console` are ignored, keys send to the menu or the console should never abort a cinematic; `key_message` can / should never happen while a cinematic is running) and `abort_cinamatic` is less than `cls.realtime` the cinematic is aborted. `abort_cinamatic` less than `cls.realtime` is necessary because the client needs one frame to pop up the menu or toggle the console and set the `cls.key_dest` accordingly. `abort_cinamatic == cls.realtime - 1` is not possible because not every frame finishes a user command. This closes #502.
2020-02-17 16:56:54 +00:00
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);
/* read bits */
2012-07-22 13:34:45 +00:00
hnodesbase = cin.hnodes1 - 256 * 2; /* nodes 0-255 aren't stored */
hnodes = hnodesbase;
nodenum = cin.numhnodes1[0];
2012-07-22 13:34:45 +00:00
while (count)
{
inbyte = *input++;
int i = 0;
2012-07-22 13:34:45 +00:00
for (i = 0; i < 8; i++)
{
if (nodenum < 256)
{
hnodes = hnodesbase + (nodenum << 9);
*out_p++ = nodenum;
if (!--count)
2012-07-22 13:34:45 +00:00
{
break;
2012-07-22 13:34:45 +00:00
}
nodenum = cin.numhnodes1[nodenum];
}
nodenum = hnodes[nodenum * 2 + (inbyte & 1)];
inbyte >>= 1;
}
}
2012-07-22 13:34:45 +00:00
if ((input - in.data != in.count) && (input - in.data != in.count + 1))
{
Com_Printf("Decompression overread by %i", (int)(input - in.data) - in.count);
}
out.count = out_p - out.data;
return out;
}
byte *
2012-07-22 13:34:45 +00:00
SCR_ReadNextFrame(void)
{
int r;
int command;
// the samples array is used as bytes or shorts, depending on bitrate (cin.s_width)
// so we need to make sure to align it correctly
YQ2_ALIGNAS_TYPE(short) byte samples[22050 / 14 * 4];
2012-07-22 13:34:45 +00:00
byte compressed[0x20000];
int size;
byte *pic;
cblock_t in, huf1;
int start, end, count;
/* read the next frame */
r = FS_FRead(&command, 4, 1, cl.cinematic_file);
if (r == 0)
2012-07-22 13:34:45 +00:00
{
/* we'll give it one more chance */
r = FS_FRead(&command, 4, 1, cl.cinematic_file);
2012-07-22 13:34:45 +00:00
}
if (r != 4)
2012-07-22 13:34:45 +00:00
{
return NULL;
2012-07-22 13:34:45 +00:00
}
command = LittleLong(command);
if (command == 2)
2012-07-22 13:34:45 +00:00
{
return NULL; /* last frame marker */
2012-07-22 13:34:45 +00:00
}
2012-07-22 13:34:45 +00:00
if (command == 1)
{
/* read palette */
2012-07-22 13:34:45 +00:00
FS_Read(cl.cinematicpalette, sizeof(cl.cinematicpalette),
cl.cinematic_file);
cl.cinematicpalette_active = 0;
}
/* decompress the next frame */
FS_Read(&size, 4, cl.cinematic_file);
size = LittleLong(size);
if (((size_t)size > sizeof(compressed)) || (size < 1))
2012-07-22 13:34:45 +00:00
{
Com_Error(ERR_DROP, "Bad compressed frame size");
2012-07-22 13:34:45 +00:00
}
FS_Read(compressed, size, cl.cinematic_file);
/* read sound */
start = cl.cinematicframe * cin.s_rate / 14;
end = (cl.cinematicframe + 1) * cin.s_rate / 14;
count = end - start;
2012-07-22 13:34:45 +00:00
FS_Read(samples, count * cin.s_width * cin.s_channels,
cl.cinematic_file);
2012-07-22 13:34:45 +00:00
if (cin.s_width == 2)
{
for (r = 0; r < count * cin.s_channels; r++)
2012-07-22 13:34:45 +00:00
{
((short *)samples)[r] = LittleShort(((short *)samples)[r]);
}
}
2012-07-22 13:34:45 +00:00
S_RawSamples(count, cin.s_rate, cin.s_width, cin.s_channels,
samples, Cvar_VariableValue("s_volume"));
in.data = compressed;
in.count = size;
huf1 = Huff1Decompress(in);
pic = huf1.data;
cl.cinematicframe++;
return pic;
}
void
2012-07-22 13:34:45 +00:00
SCR_RunCinematic(void)
{
int frame;
2012-07-22 13:34:45 +00:00
if (cl.cinematictime <= 0)
{
SCR_StopCinematic();
return;
}
if (cl.cinematicframe == -1)
2012-07-22 13:34:45 +00:00
{
return; /* static image */
2012-07-22 13:34:45 +00:00
}
2012-07-22 13:34:45 +00:00
if (cls.key_dest != key_game)
{
/* pause if menu or console is up */
cl.cinematictime = cls.realtime - cl.cinematicframe * 1000 / 14;
return;
}
frame = (cls.realtime - cl.cinematictime) * 14.0 / 1000;
if (frame <= cl.cinematicframe)
2012-07-22 13:34:45 +00:00
{
return;
2012-07-22 13:34:45 +00:00
}
2012-07-22 13:34:45 +00:00
if (frame > cl.cinematicframe + 1)
{
Com_Printf("Dropped frame: %i > %i\n", frame, cl.cinematicframe + 1);
cl.cinematictime = cls.realtime - cl.cinematicframe * 1000 / 14;
}
if (cin.pic)
2012-07-22 13:34:45 +00:00
{
Z_Free(cin.pic);
2012-07-22 13:34:45 +00:00
}
cin.pic = cin.pic_pending;
cin.pic_pending = NULL;
cin.pic_pending = SCR_ReadNextFrame();
2012-07-22 13:34:45 +00:00
if (!cin.pic_pending)
{
SCR_StopCinematic();
SCR_FinishCinematic();
cl.cinematictime = 1; /* the black screen behind loading */
SCR_BeginLoadingPlaque();
cl.cinematictime = 0;
return;
}
}
static int
SCR_MinimalColor(void)
{
int i, min_color, min_index;
min_color = 255 * 3;
min_index = 0;
for(i=0; i<255; i++)
{
int current_color = (cl.cinematicpalette[i*3+0] +
cl.cinematicpalette[i*3+1] +
cl.cinematicpalette[i*3+2]);
if (min_color > current_color)
{
min_color = current_color;
min_index = i;
}
}
return min_index;
}
2010-06-18 18:07:56 +00:00
/*
2012-04-29 13:57:33 +00:00
* Returns true if a cinematic is active, meaning the
2010-06-18 18:07:56 +00:00
* view rendering should be skipped
*/
qboolean
2012-07-22 13:34:45 +00:00
SCR_DrawCinematic(void)
{
int x, y, w, h, color;
2012-07-22 13:34:45 +00:00
if (cl.cinematictime <= 0)
{
return false;
}
/* blank screen and pause if menu is up */
2012-07-22 13:34:45 +00:00
if (cls.key_dest == key_menu)
{
R_SetPalette(NULL);
cl.cinematicpalette_active = false;
return true;
}
2012-07-22 13:34:45 +00:00
if (!cl.cinematicpalette_active)
{
R_SetPalette(cl.cinematicpalette);
cl.cinematicpalette_active = true;
}
if (!cin.pic)
2012-07-22 13:34:45 +00:00
{
return true;
2012-07-22 13:34:45 +00:00
}
if (cin_force43->value)
{
w = viddef.height * 4 / 3;
if (w > viddef.width)
{
w = viddef.width;
}
w &= ~3;
h = w * 3 / 4;
x = (viddef.width - w) / 2;
y = (viddef.height - h) / 2;
}
else
{
x = y = 0;
w = viddef.width;
h = viddef.height;
}
if (!vid_renderer)
{
vid_renderer = Cvar_Get("vid_renderer", "gl1", CVAR_ARCHIVE);
}
if (Q_stricmp(vid_renderer->string, "soft") == 0)
{
color = SCR_MinimalColor();
}
else
{
color = 0;
}
if (x > 0)
{
Draw_Fill(0, 0, x, viddef.height, color);
}
if (x + w < viddef.width)
{
Draw_Fill(x + w, 0, viddef.width - (x + w), viddef.height, color);
}
if (y > 0)
{
Draw_Fill(x, 0, w, y, color);
}
if (y + h < viddef.height)
{
Draw_Fill(x, y + h, w, viddef.height - (y + h), color);
}
Draw_StretchRaw(x, y, w, h, cin.width, cin.height, cin.pic);
return true;
}
void
2012-07-22 13:34:45 +00:00
SCR_PlayCinematic(char *arg)
{
int width, height;
byte *palette;
char name[MAX_OSPATH], *dot;
2017-04-22 08:24:12 +00:00
In_FlushQueue();
Another try to fix the problem of spurious aborts of cinematics. Until this commit a cinematic was aborted as soon as any key were marked down when finishing the user command and sending it to the server. The whole logic to detect if a key is down is broken, for example `vid_restart` may leave keys marked down that are in fact up. And there's the possibility to inject fake key events from nearly everywhere. I'm not really sure but I suspect that even the server may be able to inject key events. Therefore untangle the cinematic abort code from the user command processing, it should depend only on real key strokes: 1. Introduce a new global variable `abort_cinamatic` and set it to `cls.realtime` as soon as a key down event is detected. The only exceptions are Escape and Shift, because opening the menu and toggeling the console should never abort a cinematic. 2. When starting a cinematic `abort_cinamatic` is set to INT_MAX, because it needs to be higher than the current `cls.realtime`. 3. When a cinematic is running, `cls.key_dest` is set to `key_game` (`key_menu` and `key_console` are ignored, keys send to the menu or the console should never abort a cinematic; `key_message` can / should never happen while a cinematic is running) and `abort_cinamatic` is less than `cls.realtime` the cinematic is aborted. `abort_cinamatic` less than `cls.realtime` is necessary because the client needs one frame to pop up the menu or toggle the console and set the `cls.key_dest` accordingly. `abort_cinamatic == cls.realtime - 1` is not possible because not every frame finishes a user command. This closes #502.
2020-02-17 16:56:54 +00:00
abort_cinematic = INT_MAX;
2012-04-25 08:53:25 +00:00
/* make sure background music is not playing */
OGG_Stop();
cl.cinematicframe = 0;
dot = strstr(arg, ".");
/* static pcx image */
2012-07-22 13:34:45 +00:00
if (dot && !strcmp(dot, ".pcx"))
{
Com_sprintf(name, sizeof(name), "pics/%s", arg);
SCR_LoadPCX(name, &cin.pic, &palette, &cin.width, &cin.height);
cl.cinematicframe = -1;
cl.cinematictime = 1;
SCR_EndLoadingPlaque();
cls.state = ca_active;
2012-07-22 13:34:45 +00:00
if (!cin.pic)
{
Com_Printf("%s not found.\n", name);
cl.cinematictime = 0;
2012-07-22 13:34:45 +00:00
}
else
{
memcpy(cl.cinematicpalette, palette, sizeof(cl.cinematicpalette));
Z_Free(palette);
}
return;
}
Com_sprintf(name, sizeof(name), "video/%s", arg);
FS_FOpenFile(name, &cl.cinematic_file, false);
2012-07-22 13:34:45 +00:00
if (!cl.cinematic_file)
{
SCR_FinishCinematic();
cl.cinematictime = 0; /* done */
return;
}
SCR_EndLoadingPlaque();
cls.state = ca_active;
FS_Read(&width, 4, cl.cinematic_file);
FS_Read(&height, 4, cl.cinematic_file);
cin.width = LittleLong(width);
cin.height = LittleLong(height);
FS_Read(&cin.s_rate, 4, cl.cinematic_file);
cin.s_rate = LittleLong(cin.s_rate);
FS_Read(&cin.s_width, 4, cl.cinematic_file);
cin.s_width = LittleLong(cin.s_width);
FS_Read(&cin.s_channels, 4, cl.cinematic_file);
cin.s_channels = LittleLong(cin.s_channels);
Huff1TableInit();
cl.cinematicframe = 0;
cin.pic = SCR_ReadNextFrame();
cl.cinematictime = Sys_Milliseconds();
}
2012-07-22 13:34:45 +00:00