quakeforge/nq/source/host.c
Bill Currie 73e6e1684f FINALLY, the nq and qw renderers are merged. the view and particle subsystems
are not yet merged due to their dependence on the client, but that will come
soon.
2001-05-24 19:22:35 +00:00

1047 lines
22 KiB
C

/*
host.c
host init
Copyright (C) 1996-1997 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:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
$Id$
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "QF/cdaudio.h"
#include "QF/compat.h"
#include "QF/cmd.h"
#include "QF/console.h"
#include "QF/draw.h"
#include "QF/gib.h"
#include "QF/input.h"
#include "QF/keys.h"
#include "QF/msg.h"
#include "QF/plugin.h"
#include "QF/qargs.h"
#include "QF/screen.h"
#include "QF/sys.h"
#include "QF/va.h"
#include "QF/vid.h"
#include "chase.h"
#include "host.h"
#include "r_local.h"
#include "sbar.h"
#include "server.h"
#include "sv_progs.h"
#include "view.h"
extern void R_Particles_Init_Cvars (void);
extern void R_Init_Cvars (void);
extern void Host_Skin_Init (void);
extern void Host_Skin_Init_Cvars (void);
/*
A server can always be started, even if the system started out as a client
to a remote system.
A client can NOT be started if the system started as a dedicated server.
Memory is cleared / released when a server or client begins, not when they end.
*/
extern int fps_count;
qboolean msg_suppress_1 = 0;
quakeparms_t host_parms;
qboolean host_initialized; // true if into command execution
double host_frametime;
double host_time;
double realtime; // without any filtering or bounding
double oldrealtime; // last frame run
int host_framecount;
int host_hunklevel;
int minimum_memory;
client_t *host_client; // current client
jmp_buf host_abortserver;
byte *vid_basepal;
extern cvar_t *cl_writecfg;
cvar_t *fs_globalcfg;
cvar_t *fs_usercfg;
cvar_t *host_framerate;
cvar_t *host_speeds;
cvar_t *sys_ticrate;
cvar_t *serverprofile;
cvar_t *fraglimit;
cvar_t *timelimit;
cvar_t *teamplay;
cvar_t *samelevel;
cvar_t *noexit;
cvar_t *skill;
cvar_t *deathmatch;
cvar_t *coop;
cvar_t *pausable;
cvar_t *temp1;
void
Host_EndGame (char *message, ...)
{
va_list argptr;
char string[1024];
va_start (argptr, message);
vsnprintf (string, sizeof (string), message, argptr);
va_end (argptr);
Con_DPrintf ("Host_EndGame: %s\n", string);
if (sv.active)
Host_ShutdownServer (false);
if (cls.state == ca_dedicated)
Sys_Error ("Host_EndGame: %s\n", string); // dedicated servers exit
if (cls.demonum != -1)
CL_NextDemo ();
else
CL_Disconnect ();
longjmp (host_abortserver, 1);
}
/*
Host_Error
This shuts down both the client and server
*/
void
Host_Error (char *error, ...)
{
va_list argptr;
char string[1024];
static qboolean inerror = false;
if (inerror)
Sys_Error ("Host_Error: recursively entered");
inerror = true;
// SCR_EndLoadingPlaque (); // reenable screen updates
va_start (argptr, error);
vsnprintf (string, sizeof (string), error, argptr);
va_end (argptr);
Con_Printf ("Host_Error: %s\n", string);
if (sv.active)
Host_ShutdownServer (false);
if (cls.state == ca_dedicated)
Sys_Error ("Host_Error: %s\n", string); // dedicated servers exit
CL_Disconnect ();
cls.demonum = -1;
inerror = false;
longjmp (host_abortserver, 1);
}
void
Host_FindMaxClients (void)
{
int i;
svs.maxclients = 1;
i = COM_CheckParm ("-dedicated");
if (i) {
cls.state = ca_dedicated;
if (i != (com_argc - 1)) {
svs.maxclients = atoi (com_argv[i + 1]);
} else
svs.maxclients = 8;
} else
cls.state = ca_disconnected;
i = COM_CheckParm ("-listen");
if (i) {
if (cls.state == ca_dedicated)
Sys_Error ("Only one of -dedicated or -listen can be specified");
if (i != (com_argc - 1))
svs.maxclients = atoi (com_argv[i + 1]);
else
svs.maxclients = 8;
}
if (svs.maxclients < 1)
svs.maxclients = 8;
else if (svs.maxclients > MAX_SCOREBOARD)
svs.maxclients = MAX_SCOREBOARD;
svs.maxclientslimit = svs.maxclients;
if (svs.maxclientslimit < 4)
svs.maxclientslimit = 4;
svs.clients =
Hunk_AllocName (svs.maxclientslimit * sizeof (client_t), "clients");
if (svs.maxclients > 1)
Cvar_SetValue (deathmatch, 1.0);
else
Cvar_SetValue (deathmatch, 0.0);
}
void
Host_InitLocal (void)
{
Host_InitCommands ();
host_framerate =
Cvar_Get ("host_framerate", "0", CVAR_NONE, NULL,
"set for slow motion");
host_speeds =
Cvar_Get ("host_speeds", "0", CVAR_NONE, NULL,
"set for running times");
sys_ticrate = Cvar_Get ("sys_ticrate", "0.05", CVAR_NONE, NULL, "None");
serverprofile = Cvar_Get ("serverprofile", "0", CVAR_NONE, NULL, "None");
fraglimit = Cvar_Get ("fraglimit", "0", CVAR_SERVERINFO, Cvar_Info, "None");
timelimit = Cvar_Get ("timelimit", "0", CVAR_SERVERINFO, Cvar_Info, "None");
teamplay = Cvar_Get ("teamplay", "0", CVAR_SERVERINFO, Cvar_Info, "None");
samelevel = Cvar_Get ("samelevel", "0", CVAR_NONE, NULL, "None");
noexit = Cvar_Get ("noexit", "0", CVAR_SERVERINFO, Cvar_Info, "None");
skill = Cvar_Get ("skill", "1", CVAR_NONE, NULL, "0 - 3");
deathmatch = Cvar_Get ("deathmatch", "0", CVAR_NONE, NULL, "0, 1, or 2");
coop = Cvar_Get ("coop", "0", CVAR_NONE, NULL, "0 or 1");
pausable = Cvar_Get ("pausable", "1", CVAR_NONE, NULL, "None");
temp1 = Cvar_Get ("temp1", "0", CVAR_NONE, NULL, "None");
Host_FindMaxClients ();
host_time = 1.0; // so a think at time 0 won't get called
}
/*
Host_WriteConfiguration
Writes key bindings and archived cvars to config.cfg
*/
void
Host_WriteConfiguration (void)
{
QFile *f;
// dedicated servers initialize the host but don't parse and set the
// config.cfg cvars
if (cl_writecfg->int_val && (host_initialized & !isDedicated)) {
char *path = va ("%s/config.cfg", com_gamedir);
f = Qopen (path, "w");
if (!f) {
Con_Printf ("Couldn't write config.cfg.\n");
return;
}
Key_WriteBindings (f);
Cvar_WriteVariables (f);
Qclose (f);
}
}
/*
SV_ClientPrintf
Sends text across to be displayed
FIXME: make this just a stuffed echo
*/
void
SV_ClientPrintf (char *fmt, ...)
{
va_list argptr;
char string[1024];
va_start (argptr, fmt);
vsnprintf (string, sizeof (string), fmt, argptr);
va_end (argptr);
MSG_WriteByte (&host_client->message, svc_print);
MSG_WriteString (&host_client->message, string);
}
/*
SV_BroadcastPrintf
Sends text to all active clients
*/
void
SV_BroadcastPrintf (char *fmt, ...)
{
va_list argptr;
char string[1024];
int i;
va_start (argptr, fmt);
vsnprintf (string, sizeof (string), fmt, argptr);
va_end (argptr);
for (i = 0; i < svs.maxclients; i++)
if (svs.clients[i].active && svs.clients[i].spawned) {
MSG_WriteByte (&svs.clients[i].message, svc_print);
MSG_WriteString (&svs.clients[i].message, string);
}
}
/*
Host_ClientCommands
Send text over to the client to be executed
*/
void
Host_ClientCommands (char *fmt, ...)
{
va_list argptr;
char string[1024];
va_start (argptr, fmt);
vsnprintf (string, sizeof (string), fmt, argptr);
va_end (argptr);
MSG_WriteByte (&host_client->message, svc_stufftext);
MSG_WriteString (&host_client->message, string);
}
/*
SV_DropClient
Called when the player is getting totally kicked off the host
if (crash = true), don't bother sending signofs
*/
void
SV_DropClient (qboolean crash)
{
int saveSelf;
int i;
client_t *client;
if (!crash) {
// send any final messages (don't check for errors)
if (NET_CanSendMessage (host_client->netconnection)) {
MSG_WriteByte (&host_client->message, svc_disconnect);
NET_SendMessage (host_client->netconnection, &host_client->message);
}
if (host_client->edict && host_client->spawned) {
// call the prog function for removing a client
// this will set the body to a dead frame, among other things
saveSelf = *sv_globals.self;
*sv_globals.self =
EDICT_TO_PROG (&sv_pr_state, host_client->edict);
PR_ExecuteProgram (&sv_pr_state,
sv_funcs.ClientDisconnect);
*sv_globals.self = saveSelf;
}
Sys_Printf ("Client %s removed\n", host_client->name);
}
// break the net connection
NET_Close (host_client->netconnection);
host_client->netconnection = NULL;
// free the client (the body stays around)
host_client->active = false;
host_client->name[0] = 0;
host_client->old_frags = -999999;
net_activeconnections--;
// send notification to all clients
for (i = 0, client = svs.clients; i < svs.maxclients; i++, client++) {
if (!client->active)
continue;
MSG_WriteByte (&client->message, svc_updatename);
MSG_WriteByte (&client->message, host_client - svs.clients);
MSG_WriteString (&client->message, "");
MSG_WriteByte (&client->message, svc_updatefrags);
MSG_WriteByte (&client->message, host_client - svs.clients);
MSG_WriteShort (&client->message, 0);
MSG_WriteByte (&client->message, svc_updatecolors);
MSG_WriteByte (&client->message, host_client - svs.clients);
MSG_WriteByte (&client->message, 0);
}
}
/*
Host_ShutdownServer
This only happens at the end of a game, not between levels
*/
void
Host_ShutdownServer (qboolean crash)
{
int i;
int count;
sizebuf_t buf;
char message[4];
double start;
if (!sv.active)
return;
sv.active = false;
// stop all client sounds immediately
if (cls.state == ca_connected)
CL_Disconnect ();
// flush any pending messages - like the score!!!
start = Sys_DoubleTime ();
do {
count = 0;
for (i = 0, host_client = svs.clients; i < svs.maxclients;
i++, host_client++) {
if (host_client->active && host_client->message.cursize) {
if (NET_CanSendMessage (host_client->netconnection)) {
NET_SendMessage (host_client->netconnection,
&host_client->message);
SZ_Clear (&host_client->message);
} else {
NET_GetMessage (host_client->netconnection);
count++;
}
}
}
if ((Sys_DoubleTime () - start) > 3.0)
break;
}
while (count);
// make sure all the clients know we're disconnecting
buf.data = message;
buf.maxsize = 4;
buf.cursize = 0;
MSG_WriteByte (&buf, svc_disconnect);
count = NET_SendToAll (&buf, 5);
if (count)
Con_Printf
("Host_ShutdownServer: NET_SendToAll failed for %u clients\n",
count);
for (i = 0, host_client = svs.clients; i < svs.maxclients;
i++, host_client++)
if (host_client->active)
SV_DropClient (crash);
//
// clear structures
//
memset (&sv, 0, sizeof (sv));
memset (svs.clients, 0, svs.maxclientslimit * sizeof (client_t));
}
/*
Host_ClearMemory
This clears all the memory used by both the client and server, but does
not reinitialize anything.
*/
void
Host_ClearMemory (void)
{
Con_DPrintf ("Clearing memory\n");
D_FlushCaches ();
Mod_ClearAll ();
if (host_hunklevel)
Hunk_FreeToLowMark (host_hunklevel);
cls.signon = 0;
memset (&sv, 0, sizeof (sv));
memset (&cl, 0, sizeof (cl));
}
//============================================================================
/*
Host_FilterTime
Returns false if the time is too short to run a frame
*/
qboolean
Host_FilterTime (float time)
{
realtime += time;
if (!cls.timedemo && realtime - oldrealtime < 1.0 / 72.0)
return false; // framerate is too high
host_frametime = realtime - oldrealtime;
oldrealtime = realtime;
if (host_framerate->value > 0)
host_frametime = host_framerate->value;
else { // don't allow really long or short
// frames
if (host_frametime > 0.1)
host_frametime = 0.1;
if (host_frametime < 0.001)
host_frametime = 0.001;
}
return true;
}
/*
Host_GetConsoleCommands
Add them exactly as if they had been typed at the console
*/
void
Host_GetConsoleCommands (void)
{
char *cmd;
while (1) {
cmd = Sys_ConsoleInput ();
if (!cmd)
break;
Cbuf_AddText (cmd);
}
}
#ifdef FPS_20
void
_Host_ServerFrame (void)
{
// run the world state
*sv_globals.frametime = host_frametime;
// read client messages
SV_RunClients ();
// move things around and think
// always pause in single player if in console or menus
if (!sv.paused && (svs.maxclients > 1 || key_dest == key_game))
SV_Physics ();
}
void
Host_ServerFrame (void)
{
float save_host_frametime;
float temp_host_frametime;
// run the world state
*sv_globals.frametime = host_frametime;
// set the time and clear the general datagram
SV_ClearDatagram ();
// check for new clients
SV_CheckForNewClients ();
temp_host_frametime = save_host_frametime = host_frametime;
while (temp_host_frametime > (1.0 / 72.0)) {
if (temp_host_frametime > 0.05)
host_frametime = 0.05;
else
host_frametime = temp_host_frametime;
temp_host_frametime -= host_frametime;
_Host_ServerFrame ();
}
host_frametime = save_host_frametime;
// send all messages to the clients
SV_SendClientMessages ();
}
#else
void
Host_ServerFrame (void)
{
// run the world state
*sv_globals.frametime = host_frametime;
// set the time and clear the general datagram
SV_ClearDatagram ();
// check for new clients
SV_CheckForNewClients ();
// read client messages
SV_RunClients ();
// move things around and think
// always pause in single player if in console or menus
if (!sv.paused && (svs.maxclients > 1 || key_dest == key_game))
SV_Physics ();
// send all messages to the clients
SV_SendClientMessages ();
}
#endif
/*
Host_Frame
Runs all active servers
*/
void
_Host_Frame (float time)
{
static double time1 = 0;
static double time2 = 0;
static double time3 = 0;
int pass1, pass2, pass3;
if (setjmp (host_abortserver))
return; // something bad happened, or the
// server disconnected
// keep the random time dependent
rand ();
// decide the simulation time
if (!Host_FilterTime (time))
return; // don't run too fast, or packets
// will flood out
// get new key events
IN_SendKeyEvents ();
// allow mice or other external controllers to add commands
IN_Commands ();
// process console commands
Cbuf_Execute ();
NET_Poll ();
// if running the server locally, make intentions now
if (sv.active)
CL_SendCmd ();
//-------------------
//
// server operations
//
//-------------------
// check for commands typed to the host
Host_GetConsoleCommands ();
if (sv.active)
Host_ServerFrame ();
//-------------------
//
// client operations
//
//-------------------
// if running the server remotely, send intentions now after
// the incoming messages have been read
if (!sv.active)
CL_SendCmd ();
host_time += host_frametime;
// fetch results from server
if (cls.state == ca_connected) {
CL_ReadFromServer ();
}
// update video
if (host_speeds->int_val)
time1 = Sys_DoubleTime ();
r_inhibit_viewmodel = (chase_active->int_val
|| (cl.stats[STAT_ITEMS] & IT_INVISIBILITY)
|| cl.stats[STAT_HEALTH] <= 0);
r_force_fullscreen = cl.intermission;
r_paused = cl.paused;
r_active = cls.state == ca_active;
r_view_model = &cl.viewent;
r_frametime = host_frametime;
CL_UpdateScreen (cl.time);
if (host_speeds->int_val)
time2 = Sys_DoubleTime ();
// update audio
if (cls.signon == SIGNONS) {
S_Update (r_origin, vpn, vright, vup);
R_DecayLights (host_frametime);
} else
S_Update (vec3_origin, vec3_origin, vec3_origin, vec3_origin);
CDAudio_Update ();
if (host_speeds->int_val) {
pass1 = (time1 - time3) * 1000;
time3 = Sys_DoubleTime ();
pass2 = (time2 - time1) * 1000;
pass3 = (time3 - time2) * 1000;
Con_Printf ("%3i tot %3i server %3i gfx %3i snd\n",
pass1 + pass2 + pass3, pass1, pass2, pass3);
}
host_framecount++;
fps_count++;
}
void
Host_Frame (float time)
{
double time1, time2;
static double timetotal;
static int timecount;
int i, c, m;
if (!serverprofile->int_val) {
_Host_Frame (time);
return;
}
time1 = Sys_DoubleTime ();
_Host_Frame (time);
time2 = Sys_DoubleTime ();
timetotal += time2 - time1;
timecount++;
if (timecount < 1000)
return;
m = timetotal * 1000 / timecount;
timecount = 0;
timetotal = 0;
c = 0;
for (i = 0; i < svs.maxclients; i++) {
if (svs.clients[i].active)
c++;
}
Con_Printf ("serverprofile: %2i clients %2i msec\n", c, m);
}
//============================================================================
extern QFile *vcrFile;
#define VCR_SIGNATURE 0x56435231
// "VCR1"
void
Host_InitVCR (quakeparms_t *parms)
{
int i, len, n;
char *p;
if (COM_CheckParm ("-playback")) {
if (com_argc != 2)
Sys_Error ("No other parameters allowed with -playback\n");
vcrFile = Qopen ("quake.vcr", "rbz");
if (!vcrFile)
Sys_Error ("playback file not found\n");
Qread (vcrFile, &i, sizeof (int));
if (i != VCR_SIGNATURE)
Sys_Error ("Invalid signature in vcr file\n");
Qread (vcrFile, &com_argc, sizeof (int));
com_argv = malloc (com_argc * sizeof (char *));
com_argv[0] = parms->argv[0];
for (i = 0; i < com_argc; i++) {
Qread (vcrFile, &len, sizeof (int));
p = malloc (len);
Qread (vcrFile, p, len);
com_argv[i + 1] = p;
}
com_argc++; /* add one for arg[0] */
parms->argc = com_argc;
parms->argv = com_argv;
}
if ((n = COM_CheckParm ("-record")) != 0) {
vcrFile = Qopen ("quake.vcr", "wb");
i = VCR_SIGNATURE;
Qwrite (vcrFile, &i, sizeof (int));
i = com_argc - 1;
Qwrite (vcrFile, &i, sizeof (int));
for (i = 1; i < com_argc; i++) {
if (i == n) {
len = 10;
Qwrite (vcrFile, &len, sizeof (int));
Qwrite (vcrFile, "-playback", len);
continue;
}
len = strlen (com_argv[i]) + 1;
Qwrite (vcrFile, &len, sizeof (int));
Qwrite (vcrFile, com_argv[i], len);
}
}
}
static int
check_quakerc (void)
{
QFile *f;
char *l, *p;
int ret = 1;
COM_FOpenFile ("quake.rc", &f);
if (!f)
return 1;
while ((l = Qgetline (f))) {
if ((p = strstr (l, "stuffcmds"))) {
if (p == l) { // only known case so far
ret = 0;
break;
}
}
}
Qclose (f);
return ret;
}
void
Host_Init (quakeparms_t *parms)
{
if (standard_quake)
minimum_memory = MINIMUM_MEMORY;
else
minimum_memory = MINIMUM_MEMORY_LEVELPAK;
if (COM_CheckParm ("-minmemory"))
parms->memsize = minimum_memory;
host_parms = *parms;
if (parms->memsize < minimum_memory)
Sys_Error ("Only %4.1fMB of memory available, can't execute game",
parms->memsize / (float) 0x100000);
com_argc = parms->argc;
com_argv = parms->argv;
Cvar_Init_Hash ();
Cmd_Init_Hash ();
Memory_Init (parms->membase, parms->memsize);
Cvar_Init ();
Cbuf_Init ();
Cmd_Init ();
// execute +set as early as possible
Cmd_StuffCmds_f ();
Cbuf_Execute_Sets ();
// execute the global configuration file if it exists
// would have been nice if Cmd_Exec_f could have been used, but it
// only reads from within the quake file system, and changing that is
// probably Not A Good Thing (tm).
fs_globalcfg = Cvar_Get ("fs_globalcfg", FS_GLOBALCFG,
CVAR_ROM, NULL, "global configuration file");
Cmd_Exec_File (fs_globalcfg->string);
Cbuf_Execute_Sets ();
// execute +set again to override the config file
Cmd_StuffCmds_f ();
Cbuf_Execute_Sets ();
fs_usercfg = Cvar_Get ("fs_usercfg", FS_USERCFG, CVAR_ROM, NULL,
"user configuration file");
Cmd_Exec_File (fs_usercfg->string);
Cbuf_Execute_Sets ();
// execute +set again to override the config file
Cmd_StuffCmds_f ();
Cbuf_Execute_Sets ();
Chase_Init_Cvars ();
CL_InitCvars ();
IN_Init_Cvars ();
VID_Init_Cvars ();
S_Init_Cvars ();
Key_Init_Cvars ();
Con_Init_Cvars ();
PR_Init_Cvars ();
SV_Progs_Init_Cvars ();
R_Init_Cvars ();
R_Particles_Init_Cvars ();
Mod_Init_Cvars ();
Host_Skin_Init_Cvars ();
V_Init_Cvars ();
Cmd_StuffCmds_f ();
Cbuf_Execute_Sets ();
PI_Init ();
V_Init ();
COM_Init ();
GIB_Init ();
Game_Init ();
Host_InitVCR (parms);
Host_InitLocal ();
W_LoadWadFile ("gfx.wad");
Key_Init ();
Con_Init ();
// FIXME: MENUCODE
// M_Init ();
PR_Init ();
SV_Progs_Init ();
Mod_Init ();
NET_Init ();
SV_Init ();
Host_Skin_Init ();
Con_Printf ("Exe: " __TIME__ " " __DATE__ "\n");
Con_Printf ("%4.1f megabyte heap\n", parms->memsize / (1024 * 1024.0));
if (cls.state != ca_dedicated) {
vid_basepal = (byte *) COM_LoadHunkFile ("gfx/palette.lmp");
if (!vid_basepal)
Sys_Error ("Couldn't load gfx/palette.lmp");
// LordHavoc: force the transparent color to black
vid_basepal[765] = vid_basepal[766] = vid_basepal[767] = 0;
vid_colormap = (byte *) COM_LoadHunkFile ("gfx/colormap.lmp");
if (!vid_colormap)
Sys_Error ("Couldn't load gfx/colormap.lmp");
VID_Init (vid_basepal);
IN_Init ();
Draw_Init ();
SCR_Init ();
R_Init ();
S_Init ();
CDAudio_Init ();
Sbar_Init ();
CL_Init ();
}
Cbuf_InsertText ("exec quake.rc\n");
Cmd_Exec_File (fs_usercfg->string);
// reparse the command line for + commands other than set
// (sets still done, but it doesn't matter)
if (check_quakerc ()) {
Cmd_StuffCmds_f ();
}
Hunk_AllocName (0, "-HOST_HUNKLEVEL-");
host_hunklevel = Hunk_LowMark ();
host_initialized = true;
Sys_Printf ("========Quake Initialized=========\n");
CL_UpdateScreen (cl.time);
}
/*
Host_Shutdown
FIXME: this is a callback from Sys_Quit and Sys_Error. It would be
better to run quit through here before final handoff to the sys code.
*/
void
Host_Shutdown (void)
{
static qboolean isdown = false;
if (isdown) {
printf ("recursive shutdown\n");
return;
}
isdown = true;
// keep Con_Printf from trying to update the screen
scr_disabled_for_loading = true;
Host_WriteConfiguration ();
CDAudio_Shutdown ();
NET_Shutdown ();
S_Shutdown ();
IN_Shutdown ();
if (cls.state != ca_dedicated) {
VID_Shutdown ();
}
}