mirror of
https://github.com/yquake2/yquake2remaster.git
synced 2024-12-15 06:50:50 +00:00
607 lines
11 KiB
C
607 lines
11 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.
|
|
*
|
|
* =======================================================================
|
|
*
|
|
* This file implements the .cin video codec and the corresponding .pcx
|
|
* bitmap decoder. .cin files are just a bunch of .pcx images.
|
|
*
|
|
* =======================================================================
|
|
*/
|
|
|
|
#include "header/client.h"
|
|
|
|
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;
|
|
|
|
/* 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;
|
|
|
|
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;
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
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)
|
|
{
|
|
re.CinematicSetPalette(NULL);
|
|
cl.cinematicpalette_active = false;
|
|
}
|
|
|
|
if (cl.cinematic_file)
|
|
{
|
|
FS_FCloseFile((size_t)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();
|
|
}
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* 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), (size_t)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 counts */
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
cblock_t
|
|
Huff1Decompress(cblock_t in)
|
|
{
|
|
byte *input;
|
|
byte *out_p;
|
|
int nodenum;
|
|
int count;
|
|
cblock_t out;
|
|
int inbyte;
|
|
int *hnodes, *hnodesbase;
|
|
|
|
/* 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++;
|
|
|
|
int i = 0;
|
|
|
|
for (i = 0; i < 8; i++)
|
|
{
|
|
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;
|
|
}
|
|
|
|
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, (size_t)cl.cinematic_file);
|
|
|
|
if (r == 0)
|
|
{
|
|
/* we'll give it one more chance */
|
|
r = FS_FRead(&command, 4, 1, (size_t)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),
|
|
(size_t)cl.cinematic_file);
|
|
cl.cinematicpalette_active = 0;
|
|
}
|
|
|
|
/* decompress the next frame */
|
|
FS_Read(&size, 4, (size_t)cl.cinematic_file);
|
|
size = LittleLong(size);
|
|
|
|
if (((unsigned long)size > sizeof(compressed)) || (size < 1))
|
|
{
|
|
Com_Error(ERR_DROP, "Bad compressed frame size");
|
|
}
|
|
|
|
FS_Read(compressed, size, (size_t)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,
|
|
(size_t)cl.cinematic_file);
|
|
|
|
if (cin.s_width == 2)
|
|
{
|
|
for (r = 0; r < count * cin.s_channels; r++)
|
|
{
|
|
((short *)samples)[r] = LittleShort(((short *)samples)[r]);
|
|
}
|
|
}
|
|
|
|
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
|
|
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; /* the black screen behind loading */
|
|
SCR_BeginLoadingPlaque();
|
|
cl.cinematictime = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Returns true if a cinematic is active, meaning the
|
|
* view rendering should be skipped
|
|
*/
|
|
qboolean
|
|
SCR_DrawCinematic(void)
|
|
{
|
|
if (cl.cinematictime <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/* blank screen and pause if menu is up */
|
|
if (cls.key_dest == key_menu)
|
|
{
|
|
re.CinematicSetPalette(NULL);
|
|
cl.cinematicpalette_active = false;
|
|
return true;
|
|
}
|
|
|
|
if (!cl.cinematicpalette_active)
|
|
{
|
|
re.CinematicSetPalette(cl.cinematicpalette);
|
|
cl.cinematicpalette_active = true;
|
|
}
|
|
|
|
if (!cin.pic)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
re.DrawStretchRaw(0, 0, viddef.width, viddef.height,
|
|
cin.width, cin.height, cin.pic);
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
SCR_PlayCinematic(char *arg)
|
|
{
|
|
int width, height;
|
|
byte *palette;
|
|
char name[MAX_OSPATH], *dot;
|
|
|
|
/* make sure background music is not playing */
|
|
#ifdef CDA
|
|
CDAudio_Stop();
|
|
#endif
|
|
#ifdef OGG
|
|
OGG_Stop();
|
|
#endif
|
|
|
|
cl.cinematicframe = 0;
|
|
dot = strstr(arg, ".");
|
|
|
|
/* static pcx image */
|
|
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;
|
|
|
|
if (!cin.pic)
|
|
{
|
|
Com_Printf("%s not found.\n", name);
|
|
cl.cinematictime = 0;
|
|
}
|
|
else
|
|
{
|
|
memcpy(cl.cinematicpalette, palette, sizeof(cl.cinematicpalette));
|
|
Z_Free(palette);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
Com_sprintf(name, sizeof(name), "video/%s", arg);
|
|
FS_FOpenFile(name, (fileHandle_t *)&cl.cinematic_file, FS_READ);
|
|
|
|
if (!cl.cinematic_file)
|
|
{
|
|
SCR_FinishCinematic();
|
|
cl.cinematictime = 0; /* done */
|
|
return;
|
|
}
|
|
|
|
SCR_EndLoadingPlaque();
|
|
|
|
cls.state = ca_active;
|
|
|
|
FS_Read(&width, 4, (size_t)cl.cinematic_file);
|
|
FS_Read(&height, 4, (size_t)cl.cinematic_file);
|
|
cin.width = LittleLong(width);
|
|
cin.height = LittleLong(height);
|
|
|
|
FS_Read(&cin.s_rate, 4, (size_t)cl.cinematic_file);
|
|
cin.s_rate = LittleLong(cin.s_rate);
|
|
FS_Read(&cin.s_width, 4, (size_t)cl.cinematic_file);
|
|
cin.s_width = LittleLong(cin.s_width);
|
|
FS_Read(&cin.s_channels, 4, (size_t)cl.cinematic_file);
|
|
cin.s_channels = LittleLong(cin.s_channels);
|
|
|
|
Huff1TableInit();
|
|
|
|
cl.cinematicframe = 0;
|
|
cin.pic = SCR_ReadNextFrame();
|
|
cl.cinematictime = Sys_Milliseconds();
|
|
}
|
|
|