/* * 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" 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; /* * ================================================================= * * 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) { re.CinematicSetPalette(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) /* was 1 */ 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); 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 ================== */ qboolean SCR_DrawCinematic(void) { if (cl.cinematictime <= 0) { return false; } if (cls.key_dest == key_menu) { /* blank screen and pause if menu is up */ 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; } /* * ================== 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(); 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); } 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); */ 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) { 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(); }