]> icculus.org git repositories - divverent/darkplaces.git/blob - host.c
forgot to remove these
[divverent/darkplaces.git] / host.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20 // host.c -- coordinates spawning and killing of local servers
21
22 #include "quakedef.h"
23
24 /*
25
26 A server can always be started, even if the system started out as a client
27 to a remote system.
28
29 A client can NOT be started if the system started as a dedicated server.
30
31 Memory is cleared / released when a server or client begins, not when they end.
32
33 */
34
35 quakeparms_t host_parms;
36
37 qboolean        host_initialized;               // true if into command execution
38 qboolean        host_loopactive = false;        // LordHavoc: used to turn Host_Error into Sys_Error if starting up or shutting down
39 qboolean        host_shuttingdown = false;      // LordHavoc: set when quit is executed
40
41 double          host_frametime;
42 double          host_realframetime;             // LordHavoc: the real frametime, before slowmo and clamping are applied (used for console scrolling)
43 double          realtime;                               // without any filtering or bounding
44 double          oldrealtime;                    // last frame run
45 int                     host_framecount;
46
47 double          sv_frametime;
48
49 int                     minimum_memory;
50
51 client_t        *host_client;                   // current client
52
53 jmp_buf         host_abortserver;
54
55 cvar_t  host_framerate = {0, "host_framerate","0"};     // set for slow motion
56 cvar_t  host_speeds = {0, "host_speeds","0"};                   // set for running times
57 cvar_t  slowmo = {0, "slowmo", "1.0"};                                  // LordHavoc: framerate independent slowmo
58 cvar_t  host_minfps = {CVAR_SAVE, "host_minfps", "10"};         // LordHavoc: game logic lower cap on framerate (if framerate is below this is, it pretends it is this, so game logic will run normally)
59 cvar_t  host_maxfps = {CVAR_SAVE, "host_maxfps", "1000"};               // LordHavoc: framerate upper cap
60
61 cvar_t  sys_ticrate = {CVAR_SAVE, "sys_ticrate","0.05"};
62 cvar_t  serverprofile = {0, "serverprofile","0"};
63
64 cvar_t  fraglimit = {CVAR_NOTIFY, "fraglimit","0"};
65 cvar_t  timelimit = {CVAR_NOTIFY, "timelimit","0"};
66 cvar_t  teamplay = {CVAR_NOTIFY, "teamplay","0"};
67
68 cvar_t  samelevel = {0, "samelevel","0"};
69 cvar_t  noexit = {CVAR_NOTIFY, "noexit","0"};
70
71 cvar_t  developer = {0, "developer","0"};
72
73 cvar_t  skill = {0, "skill","1"};                                               // 0 - 3
74 cvar_t  deathmatch = {0, "deathmatch","0"};                     // 0, 1, or 2
75 cvar_t  coop = {0, "coop","0"};                 // 0 or 1
76
77 cvar_t  pausable = {0, "pausable","1"};
78
79 cvar_t  temp1 = {0, "temp1","0"};
80
81 cvar_t  timestamps = {CVAR_SAVE, "timestamps", "0"};
82 cvar_t  timeformat = {CVAR_SAVE, "timeformat", "[%b %e %X] "};
83
84 /*
85 ================
86 Host_EndGame
87 ================
88 */
89 void Host_EndGame (char *message, ...)
90 {
91         va_list         argptr;
92         char            string[1024];
93
94         va_start (argptr,message);
95         vsprintf (string,message,argptr);
96         va_end (argptr);
97         Con_DPrintf ("Host_EndGame: %s\n",string);
98
99         if (sv.active)
100                 Host_ShutdownServer (false);
101
102         if (cls.state == ca_dedicated)
103                 Sys_Error ("Host_EndGame: %s\n",string);        // dedicated servers exit
104
105         if (cls.demonum != -1)
106                 CL_NextDemo ();
107         else
108                 CL_Disconnect ();
109
110         longjmp (host_abortserver, 1);
111 }
112
113 /*
114 ================
115 Host_Error
116
117 This shuts down both the client and server
118 ================
119 */
120 char hosterrorstring[4096];
121 void Host_Error (char *error, ...)
122 {
123         va_list         argptr;
124         static  qboolean inerror = false;
125
126         // LordHavoc: if first frame has not been shown, or currently shutting
127         // down, do Sys_Error instead
128         if (!host_loopactive || host_shuttingdown)
129         {
130                 char string[4096];
131                 va_start (argptr,error);
132                 vsprintf (string,error,argptr);
133                 va_end (argptr);
134                 Sys_Error ("%s", string);
135         }
136
137         if (inerror)
138         {
139                 char string[4096];
140                 va_start (argptr,error);
141                 vsprintf (string,error,argptr);
142                 va_end (argptr);
143                 Sys_Error ("Host_Error: recursively entered (original error was: %s    new error is: %s)", hosterrorstring, string);
144         }
145         inerror = true;
146         
147 //      SCR_EndLoadingPlaque ();                // reenable screen updates
148
149         va_start (argptr,error);
150         vsprintf (hosterrorstring,error,argptr);
151         va_end (argptr);
152         Con_Printf ("Host_Error: %s\n",hosterrorstring);
153         
154         if (sv.active)
155                 Host_ShutdownServer (false);
156
157         if (cls.state == ca_dedicated)
158                 Sys_Error ("Host_Error: %s\n",hosterrorstring); // dedicated servers exit
159
160         CL_Disconnect ();
161         cls.demonum = -1;
162
163         inerror = false;
164
165         longjmp (host_abortserver, 1);
166 }
167
168 static mempool_t *clients_mempool;
169
170 /*
171 ================
172 Host_FindMaxClients
173 ================
174 */
175 void    Host_FindMaxClients (void)
176 {
177         int             i;
178
179         svs.maxclients = 1;
180
181         i = COM_CheckParm ("-dedicated");
182         if (i)
183         {
184                 cls.state = ca_dedicated;
185                 if (i != (com_argc - 1))
186                 {
187                         svs.maxclients = atoi (com_argv[i+1]);
188                 }
189                 else
190                         svs.maxclients = 8;
191         }
192         else
193                 cls.state = ca_disconnected;
194
195         i = COM_CheckParm ("-listen");
196         if (i)
197         {
198                 if (cls.state == ca_dedicated)
199                         Sys_Error ("Only one of -dedicated or -listen can be specified");
200                 if (i != (com_argc - 1))
201                         svs.maxclients = atoi (com_argv[i+1]);
202                 else
203                         svs.maxclients = 8;
204         }
205         if (svs.maxclients < 1)
206                 svs.maxclients = 8;
207         else if (svs.maxclients > MAX_SCOREBOARD)
208                 svs.maxclients = MAX_SCOREBOARD;
209
210         svs.maxclientslimit = svs.maxclients;
211         if (svs.maxclientslimit < MAX_SCOREBOARD) // LordHavoc: upped listen mode limit from 4 to MAX_SCOREBOARD
212                 svs.maxclientslimit = MAX_SCOREBOARD;
213         if (!clients_mempool)
214                 clients_mempool = Mem_AllocPool("clients");
215         if (svs.clients)
216                 Mem_Free(svs.clients);
217         svs.clients = Mem_Alloc(clients_mempool, svs.maxclientslimit*sizeof(client_t));
218
219         if (svs.maxclients > 1)
220                 Cvar_SetValue ("deathmatch", 1.0);
221         else
222                 Cvar_SetValue ("deathmatch", 0.0);
223 }
224
225
226 /*
227 =======================
228 Host_InitLocal
229 ======================
230 */
231 void Host_InitLocal (void)
232 {
233         Host_InitCommands ();
234
235         Cvar_RegisterVariable (&host_framerate);
236         Cvar_RegisterVariable (&host_speeds);
237         Cvar_RegisterVariable (&slowmo);
238         Cvar_RegisterVariable (&host_minfps);
239         Cvar_RegisterVariable (&host_maxfps);
240
241         Cvar_RegisterVariable (&sys_ticrate);
242         Cvar_RegisterVariable (&serverprofile);
243
244         Cvar_RegisterVariable (&fraglimit);
245         Cvar_RegisterVariable (&timelimit);
246         Cvar_RegisterVariable (&teamplay);
247         Cvar_RegisterVariable (&samelevel);
248         Cvar_RegisterVariable (&noexit);
249         Cvar_RegisterVariable (&skill);
250         Cvar_RegisterVariable (&developer);
251         Cvar_RegisterVariable (&deathmatch);
252         Cvar_RegisterVariable (&coop);
253
254         Cvar_RegisterVariable (&pausable);
255
256         Cvar_RegisterVariable (&temp1);
257
258         Cvar_RegisterVariable (&timestamps);
259         Cvar_RegisterVariable (&timeformat);
260
261         Host_FindMaxClients ();
262 }
263
264
265 /*
266 ===============
267 Host_WriteConfiguration
268
269 Writes key bindings and archived cvars to config.cfg
270 ===============
271 */
272 void Host_WriteConfiguration (void)
273 {
274         QFile   *f;
275
276 // dedicated servers initialize the host but don't parse and set the
277 // config.cfg cvars
278         if (host_initialized && cls.state != ca_dedicated)
279         {
280                 f = Qopen (va("%s/config.cfg",com_gamedir), "w");
281                 if (!f)
282                 {
283                         Con_Printf ("Couldn't write config.cfg.\n");
284                         return;
285                 }
286                 
287                 Key_WriteBindings (f);
288                 Cvar_WriteVariables (f);
289
290                 Qclose (f);
291         }
292 }
293
294
295 /*
296 =================
297 SV_ClientPrintf
298
299 Sends text across to be displayed 
300 FIXME: make this just a stuffed echo?
301 =================
302 */
303 void SV_ClientPrintf (char *fmt, ...)
304 {
305         va_list         argptr;
306         char            string[1024];
307         
308         va_start (argptr,fmt);
309         vsprintf (string, fmt,argptr);
310         va_end (argptr);
311         
312         MSG_WriteByte (&host_client->message, svc_print);
313         MSG_WriteString (&host_client->message, string);
314 }
315
316 /*
317 =================
318 SV_BroadcastPrintf
319
320 Sends text to all active clients
321 =================
322 */
323 void SV_BroadcastPrintf (char *fmt, ...)
324 {
325         va_list         argptr;
326         char            string[1024];
327         int                     i;
328         
329         va_start (argptr,fmt);
330         vsprintf (string, fmt,argptr);
331         va_end (argptr);
332         
333         for (i=0 ; i<svs.maxclients ; i++)
334                 if (svs.clients[i].active && svs.clients[i].spawned)
335                 {
336                         MSG_WriteByte (&svs.clients[i].message, svc_print);
337                         MSG_WriteString (&svs.clients[i].message, string);
338                 }
339 }
340
341 /*
342 =================
343 Host_ClientCommands
344
345 Send text over to the client to be executed
346 =================
347 */
348 void Host_ClientCommands (char *fmt, ...)
349 {
350         va_list         argptr;
351         char            string[1024];
352         
353         va_start (argptr,fmt);
354         vsprintf (string, fmt,argptr);
355         va_end (argptr);
356         
357         MSG_WriteByte (&host_client->message, svc_stufftext);
358         MSG_WriteString (&host_client->message, string);
359 }
360
361 /*
362 =====================
363 SV_DropClient
364
365 Called when the player is getting totally kicked off the host
366 if (crash = true), don't bother sending signofs
367 =====================
368 */
369 void SV_DropClient (qboolean crash)
370 {
371         int             saveSelf;
372         int             i;
373         client_t *client;
374
375         if (!crash)
376         {
377                 // send any final messages (don't check for errors)
378                 if (NET_CanSendMessage (host_client->netconnection))
379                 {
380                         MSG_WriteByte (&host_client->message, svc_disconnect);
381                         NET_SendMessage (host_client->netconnection, &host_client->message);
382                 }
383         
384                 if (sv.active && host_client->edict && host_client->spawned) // LordHavoc: don't call QC if server is dead (avoids recursive Host_Error in some mods when they run out of edicts)
385                 {
386                 // call the prog function for removing a client
387                 // this will set the body to a dead frame, among other things
388                         saveSelf = pr_global_struct->self;
389                         pr_global_struct->self = EDICT_TO_PROG(host_client->edict);
390                         PR_ExecuteProgram (pr_global_struct->ClientDisconnect, "QC function ClientDisconnect is missing");
391                         pr_global_struct->self = saveSelf;
392                 }
393
394                 Sys_Printf ("Client %s removed\n",host_client->name);
395         }
396
397 // break the net connection
398         NET_Close (host_client->netconnection);
399         host_client->netconnection = NULL;
400
401 // free the client (the body stays around)
402         host_client->active = false;
403         host_client->name[0] = 0;
404         host_client->old_frags = -999999;
405         net_activeconnections--;
406
407 // send notification to all clients
408         for (i=0, client = svs.clients ; i<svs.maxclients ; i++, client++)
409         {
410                 if (!client->active)
411                         continue;
412                 MSG_WriteByte (&client->message, svc_updatename);
413                 MSG_WriteByte (&client->message, host_client - svs.clients);
414                 MSG_WriteString (&client->message, "");
415                 MSG_WriteByte (&client->message, svc_updatefrags);
416                 MSG_WriteByte (&client->message, host_client - svs.clients);
417                 MSG_WriteShort (&client->message, 0);
418                 MSG_WriteByte (&client->message, svc_updatecolors);
419                 MSG_WriteByte (&client->message, host_client - svs.clients);
420                 MSG_WriteByte (&client->message, 0);
421         }
422 }
423
424 /*
425 ==================
426 Host_ShutdownServer
427
428 This only happens at the end of a game, not between levels
429 ==================
430 */
431 void Host_ShutdownServer(qboolean crash)
432 {
433         int             i;
434         int             count;
435         sizebuf_t       buf;
436         char            message[4];
437         double  start;
438
439         if (!sv.active)
440                 return;
441
442         sv.active = false;
443
444 // stop all client sounds immediately
445         if (cls.state == ca_connected)
446                 CL_Disconnect ();
447
448 // flush any pending messages - like the score!!!
449         start = Sys_DoubleTime();
450         do
451         {
452                 count = 0;
453                 for (i=0, host_client = svs.clients ; i<svs.maxclients ; i++, host_client++)
454                 {
455                         if (host_client->active && host_client->message.cursize)
456                         {
457                                 if (NET_CanSendMessage (host_client->netconnection))
458                                 {
459                                         NET_SendMessage(host_client->netconnection, &host_client->message);
460                                         SZ_Clear (&host_client->message);
461                                 }
462                                 else
463                                 {
464                                         NET_GetMessage(host_client->netconnection);
465                                         count++;
466                                 }
467                         }
468                 }
469                 if ((Sys_DoubleTime() - start) > 3.0)
470                         break;
471         }
472         while (count);
473
474 // make sure all the clients know we're disconnecting
475         buf.data = message;
476         buf.maxsize = 4;
477         buf.cursize = 0;
478         MSG_WriteByte(&buf, svc_disconnect);
479         count = NET_SendToAll(&buf, 5);
480         if (count)
481                 Con_Printf("Host_ShutdownServer: NET_SendToAll failed for %u clients\n", count);
482
483         for (i=0, host_client = svs.clients ; i<svs.maxclients ; i++, host_client++)
484                 if (host_client->active)
485                         SV_DropClient(crash);
486
487 //
488 // clear structures
489 //
490         memset (&sv, 0, sizeof(sv));
491         memset (svs.clients, 0, svs.maxclientslimit*sizeof(client_t));
492 }
493
494
495 /*
496 ================
497 Host_ClearMemory
498
499 This clears all the memory used by both the client and server, but does
500 not reinitialize anything.
501 ================
502 */
503 void Host_ClearMemory (void)
504 {
505         Con_DPrintf ("Clearing memory\n");
506         Mod_ClearAll ();
507
508         cls.signon = 0;
509         memset (&sv, 0, sizeof(sv));
510         memset (&cl, 0, sizeof(cl));
511 }
512
513
514 //============================================================================
515
516 /*
517 ===================
518 Host_FilterTime
519
520 Returns false if the time is too short to run a frame
521 ===================
522 */
523 qboolean Host_FilterTime (double time)
524 {
525         double timecap;
526         realtime += time;
527
528         if (slowmo.value < 0.0f)
529                 Cvar_SetValue("slowmo", 0.0f);
530         if (host_minfps.value < 10.0f)
531                 Cvar_SetValue("host_minfps", 10.0f);
532         if (host_maxfps.value < host_minfps.value)
533                 Cvar_SetValue("host_maxfps", host_minfps.value);
534
535          // check if framerate is too high
536         if (!cls.timedemo)
537         {
538                 timecap = sys_ticrate.value;
539                 if (cls.state == ca_connected)
540                         timecap = 1.0 / host_maxfps.value;
541
542                 if ((realtime - oldrealtime) < timecap)
543                         return false;
544         }
545
546         // LordHavoc: copy into host_realframetime as well
547         host_realframetime = host_frametime = realtime - oldrealtime;
548         oldrealtime = realtime;
549
550         if (cls.timedemo)
551         {
552                 // disable time effects
553                 cl.frametime = host_frametime;
554                 return true;
555         }
556
557         if (host_framerate.value > 0)
558                 host_frametime = host_framerate.value;
559         else
560         {
561                 // don't allow really short frames
562                 if (host_frametime > (1.0 / host_minfps.value))
563                         host_frametime = (1.0 / host_minfps.value);
564         }
565
566         cl.frametime = host_frametime = bound(0, host_frametime * slowmo.value, 0.1f); // LordHavoc: the QC code relies on no less than 10fps
567         
568         return true;
569 }
570
571
572 /*
573 ===================
574 Host_GetConsoleCommands
575
576 Add them exactly as if they had been typed at the console
577 ===================
578 */
579 void Host_GetConsoleCommands (void)
580 {
581         char    *cmd;
582
583         while (1)
584         {
585                 cmd = Sys_ConsoleInput ();
586                 if (!cmd)
587                         break;
588                 Cbuf_AddText (cmd);
589         }
590 }
591
592
593 /*
594 ==================
595 Host_ServerFrame
596
597 ==================
598 */
599 void Host_ServerFrame (void)
600 {
601         static double frametimetotal = 0, lastservertime = 0;
602         frametimetotal += host_frametime;
603         // LordHavoc: cap server at sys_ticrate in listen games
604         if (cls.state != ca_dedicated && svs.maxclients > 1 && ((realtime - lastservertime) < sys_ticrate.value))
605                 return;
606 // run the world state
607         sv.frametime = pr_global_struct->frametime = frametimetotal;
608         frametimetotal = 0;
609         lastservertime = realtime;
610 //      pr_global_struct->frametime = host_frametime;
611
612 // set the time and clear the general datagram
613         SV_ClearDatagram ();
614         
615 // check for new clients
616         SV_CheckForNewClients ();
617
618 // read client messages
619         SV_RunClients ();
620         
621 // move things around and think
622 // always pause in single player if in console or menus
623         if (!sv.paused && (svs.maxclients > 1 || key_dest == key_game) )
624                 SV_Physics ();
625
626 // send all messages to the clients
627         SV_SendClientMessages ();
628 }
629
630
631 /*
632 ==================
633 Host_Frame
634
635 Runs all active servers
636 ==================
637 */
638 void _Host_Frame (float time)
639 {
640         static double           time1 = 0;
641         static double           time2 = 0;
642         static double           time3 = 0;
643         int                     pass1, pass2, pass3;
644
645         if (setjmp (host_abortserver) )
646                 return;                 // something bad happened, or the server disconnected
647
648 // keep the random time dependent
649         rand ();
650
651 // decide the simulation time
652         if (!Host_FilterTime (time))
653         {
654                 // if time was rejected, don't totally hog the CPU
655                 Sys_Sleep();
656                 return;
657         }
658
659 // get new key events
660         Sys_SendKeyEvents ();
661
662 // allow mice or other external controllers to add commands
663         IN_Commands ();
664
665 // process console commands
666         Cbuf_Execute ();
667
668         NET_Poll();
669
670 // if running the server locally, make intentions now
671         if (sv.active)
672                 CL_SendCmd ();
673
674 //-------------------
675 //
676 // server operations
677 //
678 //-------------------
679
680 // check for commands typed to the host
681         Host_GetConsoleCommands ();
682
683         if (sv.active)
684                 Host_ServerFrame ();
685
686 //-------------------
687 //
688 // client operations
689 //
690 //-------------------
691
692 // if running the server remotely, send intentions now after
693 // the incoming messages have been read
694         if (!sv.active)
695                 CL_SendCmd ();
696
697 // fetch results from server
698         if (cls.state == ca_connected)
699                 CL_ReadFromServer ();
700
701         ui_update();
702
703 // update video
704         if (host_speeds.integer)
705                 time1 = Sys_DoubleTime ();
706
707         SCR_UpdateScreen ();
708
709         if (host_speeds.integer)
710                 time2 = Sys_DoubleTime ();
711
712 // update audio
713         if (cls.signon == SIGNONS)
714         {
715                 S_Update (r_origin, vpn, vright, vup);
716                 CL_DecayLights ();
717         }
718         else
719                 S_Update (vec3_origin, vec3_origin, vec3_origin, vec3_origin);
720
721         CDAudio_Update();
722
723         if (host_speeds.integer)
724         {
725                 pass1 = (time1 - time3)*1000000;
726                 time3 = Sys_DoubleTime ();
727                 pass2 = (time2 - time1)*1000000;
728                 pass3 = (time3 - time2)*1000000;
729                 Con_Printf ("%6ius total %6ius server %6ius gfx %6ius snd\n",
730                                         pass1+pass2+pass3, pass1, pass2, pass3);
731         }
732
733         host_framecount++;
734         host_loopactive = true;
735 }
736
737 void Host_Frame (float time)
738 {
739         double  time1, time2;
740         static double   timetotal;
741         static int              timecount;
742         int             i, c, m;
743
744         if (!serverprofile.integer)
745         {
746                 _Host_Frame (time);
747                 return;
748         }
749
750         time1 = Sys_DoubleTime ();
751         _Host_Frame (time);
752         time2 = Sys_DoubleTime ();      
753         
754         timetotal += time2 - time1;
755         timecount++;
756         
757         if (timecount < 1000)
758                 return;
759
760         m = timetotal*1000/timecount;
761         timecount = 0;
762         timetotal = 0;
763         c = 0;
764         for (i=0 ; i<svs.maxclients ; i++)
765         {
766                 if (svs.clients[i].active)
767                         c++;
768         }
769
770         Con_Printf ("serverprofile: %2i clients %2i msec\n",  c,  m);
771 }
772
773 //============================================================================
774
775 void Render_Init(void);
776 void QuakeIO_Init(void);
777
778 /*
779 ====================
780 Host_Init
781 ====================
782 */
783 void Host_Init (void)
784 {
785         com_argc = host_parms.argc;
786         com_argv = host_parms.argv;
787
788         Memory_Init ();
789         Cmd_Init ();
790         Memory_Init_Commands();
791         R_Modules_Init();
792         Cbuf_Init ();
793         QuakeIO_Init ();
794         V_Init ();
795         COM_Init ();
796         Host_InitLocal ();
797         W_LoadWadFile ("gfx.wad");
798         Key_Init ();
799         Con_Init ();
800         Chase_Init ();
801         M_Init ();
802         PR_Init ();
803         Mod_Init ();
804         NET_Init ();
805         SV_Init ();
806
807         Con_Printf ("Exe: "__TIME__" "__DATE__"\n");
808
809         if (cls.state != ca_dedicated)
810         {
811                 VID_InitCvars();
812
813                 Gamma_Init();
814
815                 Palette_Init();
816
817 #ifndef _WIN32 // on non win32, mouse comes before video for security reasons
818                 IN_Init ();
819 #endif
820                 VID_Init ();
821
822                 Render_Init();
823                 S_Init ();
824                 CDAudio_Init ();
825                 Sbar_Init ();
826                 CL_Init ();
827 #ifdef _WIN32 // on non win32, mouse comes before video for security reasons
828                 IN_Init ();
829 #endif
830         }
831
832         Cbuf_InsertText ("exec quake.rc\n");
833
834         host_initialized = true;
835         
836         Sys_Printf ("========Quake Initialized=========\n");    
837 }
838
839
840 /*
841 ===============
842 Host_Shutdown
843
844 FIXME: this is a callback from Sys_Quit and Sys_Error.  It would be better
845 to run quit through here before the final handoff to the sys code.
846 ===============
847 */
848 void Host_Shutdown(void)
849 {
850         static qboolean isdown = false;
851         
852         if (isdown)
853         {
854                 printf ("recursive shutdown\n");
855                 return;
856         }
857         isdown = true;
858
859 // keep Con_Printf from trying to update the screen
860         scr_disabled_for_loading = true;
861
862         Host_WriteConfiguration (); 
863
864         CDAudio_Shutdown ();
865         NET_Shutdown ();
866         S_Shutdown();
867         IN_Shutdown ();
868
869         if (cls.state != ca_dedicated)
870         {
871                 R_Modules_Shutdown();
872                 VID_Shutdown();
873         }
874 }
875