make dedicated work again
[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         CL_Disconnect ();
446
447 // flush any pending messages - like the score!!!
448         start = Sys_DoubleTime();
449         do
450         {
451                 count = 0;
452                 for (i=0, host_client = svs.clients ; i<svs.maxclients ; i++, host_client++)
453                 {
454                         if (host_client->active && host_client->message.cursize)
455                         {
456                                 if (NET_CanSendMessage (host_client->netconnection))
457                                 {
458                                         NET_SendMessage(host_client->netconnection, &host_client->message);
459                                         SZ_Clear (&host_client->message);
460                                 }
461                                 else
462                                 {
463                                         NET_GetMessage(host_client->netconnection);
464                                         count++;
465                                 }
466                         }
467                 }
468                 if ((Sys_DoubleTime() - start) > 3.0)
469                         break;
470         }
471         while (count);
472
473 // make sure all the clients know we're disconnecting
474         buf.data = message;
475         buf.maxsize = 4;
476         buf.cursize = 0;
477         MSG_WriteByte(&buf, svc_disconnect);
478         count = NET_SendToAll(&buf, 5);
479         if (count)
480                 Con_Printf("Host_ShutdownServer: NET_SendToAll failed for %u clients\n", count);
481
482         for (i=0, host_client = svs.clients ; i<svs.maxclients ; i++, host_client++)
483                 if (host_client->active)
484                         SV_DropClient(crash);
485
486 //
487 // clear structures
488 //
489         memset (&sv, 0, sizeof(sv));
490         memset (svs.clients, 0, svs.maxclientslimit*sizeof(client_t));
491 }
492
493
494 /*
495 ================
496 Host_ClearMemory
497
498 This clears all the memory used by both the client and server, but does
499 not reinitialize anything.
500 ================
501 */
502 void Host_ClearMemory (void)
503 {
504         Con_DPrintf ("Clearing memory\n");
505         Mod_ClearAll ();
506
507         cls.signon = 0;
508         memset (&sv, 0, sizeof(sv));
509         memset (&cl, 0, sizeof(cl));
510 }
511
512
513 //============================================================================
514
515 /*
516 ===================
517 Host_FilterTime
518
519 Returns false if the time is too short to run a frame
520 ===================
521 */
522 qboolean Host_FilterTime (double time)
523 {
524         double timecap;
525         realtime += time;
526
527         if (slowmo.value < 0.0f)
528                 Cvar_SetValue("slowmo", 0.0f);
529         if (host_minfps.value < 10.0f)
530                 Cvar_SetValue("host_minfps", 10.0f);
531         if (host_maxfps.value < host_minfps.value)
532                 Cvar_SetValue("host_maxfps", host_minfps.value);
533
534          // check if framerate is too high
535         if (!cls.timedemo)
536         {
537                 timecap = sys_ticrate.value;
538                 if (cls.state == ca_connected)
539                         timecap = 1.0 / host_maxfps.value;
540
541                 if ((realtime - oldrealtime) < timecap)
542                         return false;
543         }
544
545         // LordHavoc: copy into host_realframetime as well
546         host_realframetime = host_frametime = realtime - oldrealtime;
547         oldrealtime = realtime;
548
549         if (cls.timedemo)
550         {
551                 // disable time effects
552                 cl.frametime = host_frametime;
553                 return true;
554         }
555
556         if (host_framerate.value > 0)
557                 host_frametime = host_framerate.value;
558         else
559         {
560                 // don't allow really short frames
561                 if (host_frametime > (1.0 / host_minfps.value))
562                         host_frametime = (1.0 / host_minfps.value);
563         }
564
565         cl.frametime = host_frametime = bound(0, host_frametime * slowmo.value, 0.1f); // LordHavoc: the QC code relies on no less than 10fps
566         
567         return true;
568 }
569
570
571 /*
572 ===================
573 Host_GetConsoleCommands
574
575 Add them exactly as if they had been typed at the console
576 ===================
577 */
578 void Host_GetConsoleCommands (void)
579 {
580         char    *cmd;
581
582         while (1)
583         {
584                 cmd = Sys_ConsoleInput ();
585                 if (!cmd)
586                         break;
587                 Cbuf_AddText (cmd);
588         }
589 }
590
591
592 /*
593 ==================
594 Host_ServerFrame
595
596 ==================
597 */
598 void Host_ServerFrame (void)
599 {
600         static double frametimetotal = 0, lastservertime = 0;
601         frametimetotal += host_frametime;
602         // LordHavoc: cap server at sys_ticrate in listen games
603         if (cls.state != ca_dedicated && svs.maxclients > 1 && ((realtime - lastservertime) < sys_ticrate.value))
604                 return;
605 // run the world state
606         sv.frametime = pr_global_struct->frametime = frametimetotal;
607         frametimetotal = 0;
608         lastservertime = realtime;
609 //      pr_global_struct->frametime = host_frametime;
610
611 // set the time and clear the general datagram
612         SV_ClearDatagram ();
613         
614 // check for new clients
615         SV_CheckForNewClients ();
616
617 // read client messages
618         SV_RunClients ();
619         
620 // move things around and think
621 // always pause in single player if in console or menus
622         if (!sv.paused && (svs.maxclients > 1 || key_dest == key_game) )
623                 SV_Physics ();
624
625 // send all messages to the clients
626         SV_SendClientMessages ();
627 }
628
629
630 /*
631 ==================
632 Host_Frame
633
634 Runs all active servers
635 ==================
636 */
637 void _Host_Frame (float time)
638 {
639         static double           time1 = 0;
640         static double           time2 = 0;
641         static double           time3 = 0;
642         int                     pass1, pass2, pass3;
643
644         if (setjmp (host_abortserver) )
645                 return;                 // something bad happened, or the server disconnected
646
647 // keep the random time dependent
648         rand ();
649
650 // decide the simulation time
651         if (!Host_FilterTime (time))
652         {
653                 // if time was rejected, don't totally hog the CPU
654                 Sys_Sleep();
655                 return;
656         }
657
658 // get new key events
659         Sys_SendKeyEvents ();
660
661 // allow mice or other external controllers to add commands
662         IN_Commands ();
663
664 // process console commands
665         Cbuf_Execute ();
666
667         NET_Poll();
668
669 // if running the server locally, make intentions now
670         if (sv.active)
671                 CL_SendCmd ();
672
673 //-------------------
674 //
675 // server operations
676 //
677 //-------------------
678
679 // check for commands typed to the host
680         Host_GetConsoleCommands ();
681
682         if (sv.active)
683                 Host_ServerFrame ();
684
685 //-------------------
686 //
687 // client operations
688 //
689 //-------------------
690
691 // if running the server remotely, send intentions now after
692 // the incoming messages have been read
693         if (!sv.active)
694                 CL_SendCmd ();
695
696 // fetch results from server
697         if (cls.state == ca_connected)
698                 CL_ReadFromServer ();
699
700         ui_update();
701
702 // update video
703         if (host_speeds.integer)
704                 time1 = Sys_DoubleTime ();
705
706         SCR_UpdateScreen ();
707
708         if (host_speeds.integer)
709                 time2 = Sys_DoubleTime ();
710
711 // update audio
712         if (cls.signon == SIGNONS)
713         {
714                 S_Update (r_origin, vpn, vright, vup);
715                 CL_DecayLights ();
716         }
717         else
718                 S_Update (vec3_origin, vec3_origin, vec3_origin, vec3_origin);
719
720         CDAudio_Update();
721
722         if (host_speeds.integer)
723         {
724                 pass1 = (time1 - time3)*1000000;
725                 time3 = Sys_DoubleTime ();
726                 pass2 = (time2 - time1)*1000000;
727                 pass3 = (time3 - time2)*1000000;
728                 Con_Printf ("%6ius total %6ius server %6ius gfx %6ius snd\n",
729                                         pass1+pass2+pass3, pass1, pass2, pass3);
730         }
731
732         host_framecount++;
733         host_loopactive = true;
734 }
735
736 void Host_Frame (float time)
737 {
738         double  time1, time2;
739         static double   timetotal;
740         static int              timecount;
741         int             i, c, m;
742
743         if (!serverprofile.integer)
744         {
745                 _Host_Frame (time);
746                 return;
747         }
748
749         time1 = Sys_DoubleTime ();
750         _Host_Frame (time);
751         time2 = Sys_DoubleTime ();      
752         
753         timetotal += time2 - time1;
754         timecount++;
755         
756         if (timecount < 1000)
757                 return;
758
759         m = timetotal*1000/timecount;
760         timecount = 0;
761         timetotal = 0;
762         c = 0;
763         for (i=0 ; i<svs.maxclients ; i++)
764         {
765                 if (svs.clients[i].active)
766                         c++;
767         }
768
769         Con_Printf ("serverprofile: %2i clients %2i msec\n",  c,  m);
770 }
771
772 //============================================================================
773
774 void Render_Init(void);
775 void QuakeIO_Init(void);
776
777 /*
778 ====================
779 Host_Init
780 ====================
781 */
782 void Host_Init (void)
783 {
784         com_argc = host_parms.argc;
785         com_argv = host_parms.argv;
786
787         Memory_Init ();
788         Cmd_Init ();
789         Memory_Init_Commands();
790         R_Modules_Init();
791         Cbuf_Init ();
792         QuakeIO_Init ();
793         V_Init ();
794         COM_Init ();
795         Host_InitLocal ();
796         W_LoadWadFile ("gfx.wad");
797         Key_Init ();
798         Con_Init ();
799         Chase_Init ();
800         M_Init ();
801         PR_Init ();
802         Mod_Init ();
803         NET_Init ();
804         SV_Init ();
805
806         Con_Printf ("Exe: "__TIME__" "__DATE__"\n");
807
808         if (cls.state != ca_dedicated)
809         {
810                 VID_InitCvars();
811
812                 Gamma_Init();
813
814                 Palette_Init();
815
816 #ifndef _WIN32 // on non win32, mouse comes before video for security reasons
817                 IN_Init ();
818 #endif
819                 VID_Init ();
820
821                 Render_Init();
822                 S_Init ();
823                 CDAudio_Init ();
824                 Sbar_Init ();
825                 CL_Init ();
826 #ifdef _WIN32 // on non win32, mouse comes before video for security reasons
827                 IN_Init ();
828 #endif
829         }
830
831         Cbuf_InsertText ("exec quake.rc\n");
832
833         host_initialized = true;
834         
835         Sys_Printf ("========Quake Initialized=========\n");    
836 }
837
838
839 /*
840 ===============
841 Host_Shutdown
842
843 FIXME: this is a callback from Sys_Quit and Sys_Error.  It would be better
844 to run quit through here before the final handoff to the sys code.
845 ===============
846 */
847 void Host_Shutdown(void)
848 {
849         static qboolean isdown = false;
850         
851         if (isdown)
852         {
853                 printf ("recursive shutdown\n");
854                 return;
855         }
856         isdown = true;
857
858 // keep Con_Printf from trying to update the screen
859         scr_disabled_for_loading = true;
860
861         Host_WriteConfiguration (); 
862
863         CDAudio_Shutdown ();
864         NET_Shutdown ();
865         S_Shutdown();
866         IN_Shutdown ();
867
868         if (cls.state != ca_dedicated)
869         {
870                 R_Modules_Shutdown();
871                 VID_Shutdown();
872         }
873 }
874