thirtyflightsofloving/client/cl_cin.c
2020-06-12 12:21:21 -07:00

673 lines
13 KiB
C

/*
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.
*/
#include "client.h"
#ifndef ROQ_SUPPORT
typedef struct
{
byte *data;
int count;
} cblock_t;
typedef struct
{
qboolean restart_sound;
int s_rate;
int s_width;
int s_channels;
int width;
int height;
byte *pic;
byte *pic_pending;
qboolean isStaticPic; // Knightmare added
char picName[MAX_OSPATH];// Knightmare added
// order 1 huffman stuff
int *hnodes1; // [256][256][2];
int numhnodes1[256];
int h_used[512];
int h_count[512];
} cinematics_t;
cinematics_t cin;
/*
=================================================================
PCX LOADING
=================================================================
*/
/*
==============
SCR_LoadPCX
==============
*/
void SCR_LoadPCX (char *filename, byte **pic, byte **palette, int *width, int *height)
{
byte *raw;
pcx_t *pcx;
int x, y;
int len;
int dataByte, runLength;
byte *out, *pix;
*pic = NULL;
//
// load the file
//
len = FS_LoadFile (filename, (void **)&raw);
if (!raw)
return; // Com_Printf ("Bad pcx file %s\n", filename);
//
// parse the PCX file
//
pcx = (pcx_t *)raw;
raw = &pcx->data;
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;
}
out = Z_Malloc ( (pcx->ymax+1) * (pcx->xmax+1) );
*pic = out;
pix = out;
if (palette)
{
*palette = Z_Malloc(768);
memcpy (*palette, (byte *)pcx + len - 768, 768);
}
if (width)
*width = pcx->xmax+1;
if (height)
*height = pcx->ymax+1;
for (y=0 ; y<=pcx->ymax ; y++, pix += pcx->xmax+1)
{
for (x=0 ; x<=pcx->xmax ; )
{
dataByte = *raw++;
if((dataByte & 0xC0) == 0xC0)
{
runLength = dataByte & 0x3F;
dataByte = *raw++;
}
else
runLength = 1;
while(runLength-- > 0)
pix[x++] = dataByte;
}
}
if ( raw - (byte *)pcx > len)
{
Com_Printf ("PCX file %s was malformed", filename);
Z_Free (*pic);
*pic = NULL;
}
FS_FreeFile (pcx);
}
//=============================================================
/*
==================
SCR_StopCinematic
==================
*/
void SCR_StopCinematic (void)
{
cl.cinematictime = 0; // done
if (cin.pic)
{
Z_Free (cin.pic);
cin.pic = NULL;
}
if (cin.pic_pending)
{
Z_Free (cin.pic_pending);
cin.pic_pending = NULL;
}
if (cl.cinematicpalette_active)
{
R_SetPalette(NULL);
cl.cinematicpalette_active = false;
}
if (cl.cinematic_file)
{
FS_FCloseFile(cl.cinematic_file);
cl.cinematic_file = 0;
}
if (cin.hnodes1)
{
Z_Free (cin.hnodes1);
cin.hnodes1 = NULL;
}
// switch back down to 11 khz sound if necessary
if (cin.restart_sound)
{
cin.restart_sound = false;
CL_Snd_Restart_f ();
}
}
/*
====================
SCR_FinishCinematic
Called when either the cinematic completes, or it is aborted
====================
*/
void 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));
}
//==========================================================================
/*
==================
SmallestNode1
==================
*/
int SmallestNode1 (int numhnodes)
{
int i;
int best, bestnode;
best = 99999999;
bestnode = -1;
for (i=0 ; i<numhnodes ; i++)
{
if (cin.h_used[i])
continue;
if (!cin.h_count[i])
continue;
if (cin.h_count[i] < best)
{
best = cin.h_count[i];
bestnode = i;
}
}
if (bestnode == -1)
return -1;
cin.h_used[bestnode] = true;
return bestnode;
}
/*
==================
Huff1TableInit
Reads the 64k counts table and initializes the node trees
==================
*/
void 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 (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++)
cin.h_count[j] = counts[j];
// build the nodes
numhnodes = 256;
nodebase = cin.hnodes1 + prev*256*2;
while (numhnodes != 511)
{
node = nodebase + (numhnodes-256)*2;
// pick two lowest counts
node[0] = SmallestNode1 (numhnodes);
if (node[0] == -1)
break; // no more
node[1] = SmallestNode1 (numhnodes);
if (node[1] == -1)
break;
cin.h_count[numhnodes] = cin.h_count[node[0]] + cin.h_count[node[1]];
numhnodes++;
}
cin.numhnodes1[prev] = numhnodes-1;
}
}
/*
==================
Huff1Decompress
==================
*/
cblock_t Huff1Decompress (cblock_t in)
{
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);
// read bits
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];
}
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 %i", (input - in.data) - in.count);
}
out.count = out_p - out.data;
return out;
}
/*
==================
SCR_ReadNextFrame
==================
*/
byte *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;
// read the next frame
r = FS_FRead (&command, 4, 1, cl.cinematic_file);
if (r == 0) // we'll give it one more chance
r = FS_FRead (&command, 4, 1, cl.cinematic_file);
if (r != 4)
return NULL;
command = LittleLong(command);
if (command == 2)
return NULL; // last frame marker
if (command == 1)
{ // read palette
FS_Read (cl.cinematicpalette, sizeof(cl.cinematicpalette), cl.cinematic_file);
cl.cinematicpalette_active = 0; // dubious.... exposes an edge case
}
// decompress the next frame
FS_Read (&size, 4, cl.cinematic_file);
size = LittleLong(size);
if (size > sizeof(compressed) || size < 1)
Com_Error (ERR_DROP, "Bad compressed frame size");
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;
FS_Read (samples, count*cin.s_width*cin.s_channels, cl.cinematic_file);
S_RawSamples (count, cin.s_rate, cin.s_width, cin.s_channels, samples, false);
in.data = compressed;
in.count = size;
huf1 = Huff1Decompress (in);
pic = huf1.data;
cl.cinematicframe++;
return pic;
}
/*
==================
SCR_RunCinematic
==================
*/
void SCR_RunCinematic (void)
{
int frame;
if (cl.cinematictime <= 0)
{
SCR_StopCinematic ();
return;
}
if (cl.cinematicframe == -1)
return; // static image
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)
return;
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)
Z_Free (cin.pic);
cin.pic = cin.pic_pending;
cin.pic_pending = NULL;
cin.pic_pending = SCR_ReadNextFrame ();
if (!cin.pic_pending)
{
SCR_StopCinematic ();
SCR_FinishCinematic ();
cl.cinematictime = 1; // hack to get the black screen behind loading
SCR_BeginLoadingPlaque ();
cl.cinematictime = 0;
return;
}
}
/*
==================
SCR_DrawCinematic
Returns true if a cinematic is active, meaning the view rendering
should be skipped
==================
*/
extern cvar_t *vid_ref;
qboolean SCR_DrawCinematic (void)
{
if (cl.cinematictime <= 0)
return false;
if (cin.isStaticPic) // Knightmare- HACK to show JPG endscreens
{
R_DrawStretchPic (0, 0, viddef.width, viddef.height, cin.picName, 1.0);
return true;
}
// Knightmare- pause if reloading video
if (cls.key_dest == key_menu || vid_ref->modified)
{ // blank screen and pause if menu is up
R_SetPalette(NULL);
cl.cinematicpalette_active = false;
return true;
}
if (!cl.cinematicpalette_active)
{
R_SetPalette(cl.cinematicpalette);
cl.cinematicpalette_active = true;
}
if (!cin.pic)
return true;
// fix odd-colored lines at top and bottom of screen
//R_DrawStretchRaw (0, 0, viddef.width, viddef.height,
// cin.width, cin.height, cin.pic);
R_DrawStretchRaw (-2, -2, viddef.width +4, viddef.height +4,
cin.width, cin.height, cin.pic);
return true;
}
/*
==================
SCR_PlayCinematic
==================
*/
void SCR_PlayCinematic (char *arg)
{
int width, height;
byte *palette;
char name[MAX_OSPATH], *dot;
int old_khz;
// make sure CD isn't playing music
CDAudio_Stop();
cin.isStaticPic = false; // Knightmare added
cl.cinematicframe = 0;
dot = strstr (arg, ".");
if (dot && !strcmp (dot, ".pcx"))
{ // static pcx image
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;
if (!cin.pic)
{
Com_Printf ("%s not found.\n", name);
cl.cinematictime = 0;
}
else
{
memcpy (cl.cinematicpalette, palette, sizeof(cl.cinematicpalette));
Z_Free (palette);
// Knightmare- HACK to show JPG endscreens
cin.isStaticPic = true;
Com_sprintf (cin.picName, sizeof(cin.picName), "/pics/%s", arg);
}
return;
}
Com_sprintf (name, sizeof(name), "video/%s", arg);
FS_FOpenFile (name, &cl.cinematic_file, FS_READ);
if (!cl.cinematic_file)
{
//Com_Error (ERR_DROP, "Cinematic %s not found.\n", name);
//Com_Printf (S_COLOR_YELLOW"Cinematic %s not found.\n", name);
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 ();
// switch up to 22 khz sound if necessary
old_khz = Cvar_VariableValue ("s_khz");
if (old_khz < cin.s_rate/1000) // Knighmare- changed != to <
{
cin.restart_sound = true;
Cvar_SetValue ("s_khz", cin.s_rate/1000);
CL_Snd_Restart_f ();
Cvar_SetValue ("s_khz", old_khz);
}
cl.cinematicframe = 0;
cin.pic = SCR_ReadNextFrame ();
cl.cinematictime = Sys_Milliseconds ();
}
#endif //!ROQ_SUPPORT