1 /*
2 Copyright (C) 1997-2001 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
21 #include "server.h"
22
23 #ifdef _WIN32
24 #include <process.h>
25 #endif
26
27 extern time_t server_start_time;
28
29 server_static_t svs; // persistant server info
30 server_t sv; // local server
31
32 /*
33 ================
34 SV_FindIndex
35
36 ================
37 */
SV_FindIndex(const char * name,int start,int maxIndex,qboolean create)38 int SV_FindIndex (const char *name, int start, int maxIndex, qboolean create)
39 {
40 int i;
41
42 if (!name || !name[0])
43 {
44 if (sv_gamedebug->intvalue)
45 Com_Printf ("GAME WARNING: SV_FindIndex: NULL or empty name, ignored\n", LOG_SERVER|LOG_WARNING|LOG_GAMEDEBUG);
46
47 if (sv_gamedebug->intvalue >= 3)
48 Sys_DebugBreak ();
49 return 0;
50 }
51
52 for (i=1 ; i<maxIndex && sv.configstrings[start+i][0] ; i++)
53 {
54 if (!strcmp(sv.configstrings[start+i], name)) {
55 return i;
56 }
57 }
58
59 if (!create)
60 return 0;
61
62 if (i == maxIndex) {
63 Com_Printf ("ERROR: Ran out of configstrings while attempting to add '%s' (%d,%d)\n", LOG_SERVER|LOG_ERROR, name, start, maxIndex);
64 Com_Printf ("Dumping configstrings in use to 'configstrings.txt'...", LOG_SERVER|LOG_ERROR);
65 {
66 FILE *cs;
67 cs = fopen ("configstrings.txt", "wb");
68 if (!cs) {
69 Com_Printf ("failed.\n", LOG_SERVER|LOG_ERROR);
70 } else {
71 fprintf (cs, "configstring dump:\n\nCS_SOUNDS:\n");
72 for (i = CS_SOUNDS; i < CS_SOUNDS+MAX_SOUNDS; i++)
73 fprintf (cs, "%i: %s\n", i, sv.configstrings[i]);
74
75 fprintf (cs, "\nCS_MODELS:\n");
76 for (i = CS_MODELS; i < CS_MODELS+MAX_MODELS; i++)
77 fprintf (cs, "%i: %s\n", i, sv.configstrings[i]);
78
79 fprintf (cs, "\nCS_ITEMS:\n");
80 for (i = CS_MODELS; i < CS_ITEMS+MAX_MODELS; i++)
81 fprintf (cs, "%i: %s\n", i, sv.configstrings[i]);
82
83 fprintf (cs, "\nCS_IMAGES:\n");
84 for (i = CS_IMAGES; i < CS_IMAGES+MAX_IMAGES; i++)
85 fprintf (cs, "%i: %s\n", i, sv.configstrings[i]);
86
87 fprintf (cs, "\nCS_LIGHTS:\n");
88 for (i = CS_LIGHTS; i < CS_LIGHTS+MAX_LIGHTSTYLES; i++)
89 fprintf (cs, "%i: %s\n", i, sv.configstrings[i]);
90
91 fprintf (cs, "\nCS_GENERAL:\n");
92 for (i = CS_GENERAL; i < CS_GENERAL+MAX_GENERAL; i++)
93 fprintf (cs, "%i: %s\n", i, sv.configstrings[i]);
94
95 fclose (cs);
96 Com_Printf ("done.\n", LOG_SERVER|LOG_ERROR);
97 }
98 }
99 Com_Error (ERR_GAME, "SV_FindIndex: overflow finding index for %s", name);
100 }
101
102 strncpy (sv.configstrings[start+i], name, sizeof(sv.configstrings[i])-1);
103
104 if (sv.state != ss_loading)
105 { // send the update to everyone
106
107 //r1: why clear?
108 //SZ_Clear (&sv.multicast);
109
110 MSG_BeginWriting (svc_configstring);
111 MSG_WriteShort (start+i);
112 MSG_WriteString (sv.configstrings[start+i]);
113 SV_Multicast (NULL, MULTICAST_ALL_R);
114 return i;
115 }
116
117 return i;
118 }
119
120
SV_ModelIndex(const char * name)121 int EXPORT SV_ModelIndex (const char *name)
122 {
123 return SV_FindIndex (name, CS_MODELS, MAX_MODELS, true);
124 }
125
SV_SoundIndex(const char * name)126 int EXPORT SV_SoundIndex (const char *name)
127 {
128 return SV_FindIndex (name, CS_SOUNDS, MAX_SOUNDS, true);
129 }
130
SV_ImageIndex(const char * name)131 int EXPORT SV_ImageIndex (const char *name)
132 {
133 return SV_FindIndex (name, CS_IMAGES, MAX_IMAGES, true);
134 }
135
136 /*
137 =================
138 SV_CheckForSavegame
139 =================
140 */
SV_CheckForSavegame(void)141 static void SV_CheckForSavegame (void)
142 {
143 char name[MAX_OSPATH];
144 FILE *f;
145 int i;
146
147 if (sv_noreload->intvalue)
148 return;
149
150 if (Cvar_IntValue ("deathmatch"))
151 return;
152
153 Com_sprintf (name, sizeof(name), "%s/save/current/%s.sav", FS_Gamedir(), sv.name);
154 f = fopen (name, "rb");
155 if (!f)
156 return; // no savegame
157
158 fclose (f);
159
160 SV_ClearWorld ();
161
162 // get configstrings and areaportals
163 SV_ReadLevelFile ();
164
165 if (!sv.loadgame)
166 { // coming back to a level after being in a different
167 // level, so run it for ten seconds
168
169 // rlava2 was sending too many lightstyles, and overflowing the
170 // reliable data. temporarily changing the server state to loading
171 // prevents these from being passed down.
172 server_state_t previousState; // PGM
173
174 previousState = sv.state; // PGM
175 sv.state = ss_loading; // PGM
176 for (i=0 ; i<100 ; i++)
177 ge->RunFrame ();
178
179 sv.state = previousState; // PGM
180 }
181 }
182
183
184 /*
185 ================
186 SV_SpawnServer
187
188 Change the server to a new map, taking all connected
189 clients along with it.
190
191 ================
192 */
SV_SpawnServer(const char * server,const char * spawnpoint,server_state_t serverstate,qboolean attractloop,qboolean loadgame)193 static void SV_SpawnServer (const char *server, const char *spawnpoint, server_state_t serverstate, qboolean attractloop, qboolean loadgame)
194 {
195 int i;
196 uint32 checksum;
197 char *cmd;
198
199 //r1: get latched vars
200 if (Cvar_GetNumLatchedVars() || sv_recycle->intvalue)
201 {
202 Com_Printf ("SV_SpawnServer: Reloading Game DLL.\n", LOG_SERVER);
203 SV_InitGame();
204
205 if (sv_recycle->intvalue != 2)
206 Cvar_ForceSet ("sv_recycle", "0");
207 }
208
209 Cvar_ForceSet ("$mapname", server);
210
211 #ifndef DEDICATED_ONLY
212 if (dedicated->intvalue)
213 {
214 #endif
215 cmd = Cmd_MacroExpandString("$sv_beginmapcmd");
216 if (cmd)
217 {
218 Cbuf_AddText (cmd);
219 Cbuf_AddText ("\n");
220 Cbuf_Execute ();
221 }
222 else
223 Com_Printf ("WARNING: Error expanding $sv_beginmapcmd, ignored.\n", LOG_SERVER|LOG_WARNING);
224 #ifndef DEDICATED_ONLY
225 }
226 #endif
227
228 Z_Verify("SV_SpawnServer:START");
229
230 if (attractloop)
231 Cvar_Set ("paused", "0");
232
233 Com_Printf ("------- Server Initialization -------\n", LOG_SERVER);
234
235 Com_DPrintf ("SpawnServer: %s\n", server);
236 if (sv.demofile)
237 fclose (sv.demofile);
238
239 svs.spawncount++; // any partially connected client will be
240 // restarted
241
242 //lookup any possible new IP
243 if (dedicated->intvalue && (svs.spawncount % 10) == 0)
244 {
245 if (sv_global_master->intvalue)
246 NET_StringToAdr ("master.q2servers.com:27900", &master_adr[0]);
247
248 if (sv_cheaternet->intvalue)
249 NET_StringToAdr ("query.anticheat.r1ch.net:27930", &cheaternet_adr);
250 }
251
252 sv.state = ss_dead;
253 Com_SetServerState (sv.state);
254
255 // wipe the entire per-level structure
256 memset (&sv, 0, sizeof(sv));
257
258 //r1: randomize serverframe to thwart some map timers
259 if (sv_randomframe->intvalue)
260 sv.randomframe = (int)(random() * 0xFFFF);
261
262 svs.realtime = 0;
263 sv.loadgame = loadgame;
264 sv.attractloop = attractloop;
265
266 // save name for levels that don't set message
267 strncpy (sv.configstrings[CS_NAME], server, sizeof(sv.configstrings[CS_NAME])-1);
268
269 if (Cvar_IntValue ("deathmatch"))
270 {
271 Com_sprintf(sv.configstrings[CS_AIRACCEL], sizeof(sv.configstrings[CS_AIRACCEL]), "%d", sv_airaccelerate->intvalue);
272 pm_airaccelerate = (qboolean)sv_airaccelerate->intvalue;
273 }
274 else
275 {
276 strcpy(sv.configstrings[CS_AIRACCEL], "0");
277 pm_airaccelerate = false;
278 }
279
280 //SZ_Init (&sv.multicast, sv.multicast_buf, sizeof(sv.multicast_buf));
281
282 strcpy (sv.name, server);
283
284 // leave slots at start for clients only
285 for (i=0 ; i<maxclients->intvalue ; i++)
286 {
287 // needs to reconnect
288 if (svs.clients[i].state == cs_spawned)
289 svs.clients[i].state = cs_connected;
290 svs.clients[i].lastframe = -1;
291 svs.clients[i].packetCount = 0;
292 }
293
294 sv.time = 1000;
295
296 //strcpy (sv.name, server);
297 //strcpy (sv.configstrings[CS_NAME], server);
298
299 if (serverstate != ss_game)
300 {
301 sv.models[1] = CM_LoadMap ("", false, &checksum); // no real map
302 }
303 else
304 {
305 char *p;
306 Com_sprintf (sv.configstrings[CS_MODELS+1],sizeof(sv.configstrings[CS_MODELS+1]),
307 "maps/%s.bsp", server);
308 sv.models[1] = CM_LoadMap (sv.configstrings[CS_MODELS+1], false, &checksum);
309
310 //FUCKING HUGE AND UGLY hack to allow map overriding
311 strcpy (sv.configstrings[CS_MODELS+1], CM_MapName());
312 strcpy (sv.name, CM_MapName() + 5);
313 p = strrchr(sv.name, '.');
314 if (!p)
315 Com_Error (ERR_DROP, "Aiee, sv.name is missing its period: %s", sv.name);
316 p[0] = 0;
317 }
318
319 Com_sprintf (sv.configstrings[CS_MAPCHECKSUM],sizeof(sv.configstrings[CS_MAPCHECKSUM]),
320 "%i", checksum);
321
322 //
323 // clear physics interaction links
324 //
325 SV_ClearWorld ();
326
327 for (i=1 ; i< CM_NumInlineModels ; i++)
328 {
329 Com_sprintf (sv.configstrings[CS_MODELS+1+i], sizeof(sv.configstrings[CS_MODELS+1+i]),
330 "*%i", i);
331 sv.models[i+1] = CM_InlineModel (sv.configstrings[CS_MODELS+1+i]);
332 }
333
334 //
335 // spawn the rest of the entities on the map
336 //
337
338 // precache and static commands can be issued during
339 // map initialization
340 sv.state = ss_loading;
341 Com_SetServerState (sv.state);
342
343 // load and spawn all other entities
344 if (sv.attractloop)
345 {
346 strcpy (sv.configstrings[CS_MAXCLIENTS], "1");
347 }
348 else
349 {
350 ge->SpawnEntities ( sv.name, CM_EntityString(), spawnpoint );
351
352 //r1ch: override what the game dll may or may not have set for this with the true value
353 Com_sprintf (sv.configstrings[CS_MAXCLIENTS], sizeof(sv.configstrings[CS_MAXCLIENTS]), "%d", maxclients->intvalue);
354
355 // run two frames to allow everything to settle
356 ge->RunFrame ();
357 ge->RunFrame ();
358 }
359
360 //verify game didn't clobber important stuff
361 if ((int)checksum != atoi(sv.configstrings[CS_MAPCHECKSUM]))
362 Com_Error (ERR_DROP, "Game DLL corrupted server configstrings");
363
364 // all precaches are complete
365 sv.state = serverstate;
366 Com_SetServerState (sv.state);
367
368 // create a baseline for more efficient communications
369
370 //r1: baslines are now allocated on a per client basis
371 //SV_CreateBaseline ();
372
373 // check for a savegame
374 SV_CheckForSavegame ();
375
376 // set serverinfo variable
377 Cvar_FullSet ("mapname", sv.name, CVAR_SERVERINFO | CVAR_NOSET);
378
379 #ifndef DEDICATED_ONLY
380 if (dedicated->intvalue)
381 {
382 #endif
383 cmd = Cmd_MacroExpandString("$sv_postbeginmapcmd");
384 if (cmd)
385 {
386 Cbuf_AddText (cmd);
387 Cbuf_AddText ("\n");
388 Cbuf_Execute ();
389 }
390 else
391 Com_Printf ("WARNING: Error expanding $sv_postbeginmapcmd, ignored.\n", LOG_SERVER|LOG_WARNING);
392 #ifndef DEDICATED_ONLY
393 }
394 #endif
395
396 Com_Printf ("-------------------------------------\n", LOG_SERVER);
397 Z_Verify("SV_SpawnServer:END");
398 }
399
400 /*
401 ==============
402 SV_InitGame
403
404 A brand new game has been started
405 ==============
406 */
SV_InitGame(void)407 void SV_InitGame (void)
408 {
409 int i;
410 Z_Verify("SV_InitGame:START");
411
412 if (svs.initialized)
413 {
414 // cause any connected clients to reconnect
415 SV_Shutdown ("Server restarted\n", true, false);
416 }
417 #ifndef DEDICATED_ONLY
418 else
419 {
420 // make sure the client is down
421 CL_Drop (false, true);
422 SCR_BeginLoadingPlaque ();
423 }
424 #endif
425
426 svs.initialized = true;
427 server_start_time = time(NULL);
428
429 // get any latched variable changes (maxclients, etc)
430 Cvar_GetLatchedVars ();
431
432 if (Cvar_IntValue ("coop") && Cvar_IntValue ("deathmatch"))
433 {
434 Com_Printf("Deathmatch and Coop both set, disabling Coop\n", LOG_SERVER|LOG_NOTICE);
435 Cvar_FullSet ("coop", "0", CVAR_SERVERINFO | CVAR_LATCH);
436 }
437
438 // dedicated servers are can't be single player and are usually DM
439 // so unless they explicity set coop, force it to deathmatch
440 if (dedicated->intvalue)
441 {
442 if (!Cvar_IntValue ("coop"))
443 Cvar_FullSet ("deathmatch", "1", CVAR_SERVERINFO | CVAR_LATCH);
444 }
445
446 // init clients
447 if (Cvar_IntValue ("deathmatch"))
448 {
449 if (maxclients->intvalue <= 1)
450 Cvar_FullSet ("maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH);
451 else if (maxclients->intvalue > MAX_CLIENTS)
452 Cvar_FullSet ("maxclients", va("%i", MAX_CLIENTS), CVAR_SERVERINFO | CVAR_LATCH);
453 }
454 else if (Cvar_IntValue ("coop"))
455 {
456 if (maxclients->intvalue <= 1 || maxclients->intvalue > MAX_CLIENTS)
457 Cvar_FullSet ("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH);
458 }
459 else // non-deathmatch, non-coop is one player
460 {
461 Cvar_FullSet ("maxclients", "1", CVAR_SERVERINFO | CVAR_LATCH);
462 }
463
464 svs.ratelimit_badrcon.period = 1000;
465 svs.ratelimit_status.period = 1000;
466
467 svs.spawncount = randomMT()&0x7FFFFFFF;
468
469 svs.clients = Z_TagMalloc (sizeof(client_t)*maxclients->intvalue, TAGMALLOC_CLIENTS);
470
471 svs.num_client_entities = maxclients->intvalue*UPDATE_BACKUP*64;
472 svs.client_entities = Z_TagMalloc (sizeof(entity_state_t)*svs.num_client_entities, TAGMALLOC_CL_ENTS);
473
474 memset (svs.clients, 0, sizeof(client_t)*maxclients->intvalue);
475 memset (svs.client_entities, 0, sizeof(entity_state_t)*svs.num_client_entities);
476
477 // r1: spam warning for those stupid servers that run 250 maxclients and 32 player slots
478 if (maxclients->intvalue > 64)
479 Com_Printf ("WARNING: Setting maxclients higher than the maximum number of players you intend to have playing can negatively affect server performance and bandwidth use.\n", LOG_SERVER|LOG_WARNING);
480
481 // init network stuff
482 if (maxclients->intvalue > 1)
483 NET_Config (NET_SERVER);
484
485 // init game
486 SV_InitGameProgs ();
487
488 // heartbeats will always be sent to the id master
489 svs.last_heartbeat = -295000; // send immediately (r1: give few secs for configs to run)
490
491 if (dedicated->intvalue)
492 {
493 if (sv_cheaternet->intvalue)
494 NET_StringToAdr ("query.anticheat.r1ch.net:27930", &cheaternet_adr);
495
496 if (sv_global_master->intvalue)
497 NET_StringToAdr ("master.q2servers.com:27900", &master_adr[0]);
498 }
499
500 //r1: ping masters now that the network is up
501 if (Cvar_IntValue ("public") && dedicated->intvalue)
502 {
503 for (i=0 ; i<MAX_MASTERS ; i++)
504 {
505 if (master_adr[i].port)
506 {
507 Com_Printf ("Pinging master server %s\n", LOG_SERVER|LOG_NOTICE, NET_AdrToString (&master_adr[i]));
508 Netchan_OutOfBandPrint (NS_SERVER, &master_adr[i], "ping");
509 }
510 }
511 }
512
513 #ifdef ANTICHEAT
514 if (sv_require_anticheat->intvalue)
515 {
516 SV_AntiCheat_Connect ();
517 SV_AntiCheat_WaitForInitialConnect ();
518 }
519 #endif
520
521 Z_Verify("SV_InitGame:END");
522 }
523
524
525 /*
526 ======================
527 SV_Map
528
529 the full syntax is:
530
531 map [*]<map>$<startspot>+<nextserver>
532
533 command from the console or progs.
534 Map can also be a.cin, .pcx, or .dm2 file
535 Nextserver is used to allow a cinematic to play, then proceed to
536 another level:
537
538 map tram.cin+jail_e3
539 ======================
540 */
SV_Map(qboolean attractloop,const char * levelstring,qboolean loadgame)541 void SV_Map (qboolean attractloop, const char *levelstring, qboolean loadgame)
542 {
543 char *cmd;
544 char level[MAX_QPATH-9]; //save space for maps/*.bsp
545 char *ch;
546 char spawnpoint[MAX_QPATH];
547 size_t l;
548
549 Z_Verify("SV_Map:START");
550
551 sv.loadgame = loadgame;
552 sv.attractloop = attractloop;
553
554 //r1: yet another buffer overflow was here.
555 Q_strncpy (level, levelstring, sizeof(level)-1);
556
557 //warning: macro expansion will overwrite cmd_argv == levelstring.
558 if (sv.state != ss_dead)
559 {
560 cmd = Cmd_MacroExpandString("$sv_endmapcmd");
561 if (cmd)
562 {
563 Cbuf_AddText (cmd);
564 Cbuf_AddText ("\n");
565 Cbuf_Execute ();
566 }
567 else
568 Com_Printf ("WARNING: Error expanding $sv_endmapcmd, ignored.\n", LOG_SERVER|LOG_WARNING);
569 }
570
571 if (sv.state == ss_dead && !sv.loadgame)
572 {
573 // the game is just starting
574 SV_InitGame ();
575 }
576
577 // if there is a + in the map, set nextserver to the remainder
578 ch = strchr(level, '+');
579 if (ch)
580 {
581 *ch = 0;
582 Cvar_Set ("nextserver", va("gamemap \"%s\"", ch+1));
583 }
584 else
585 Cvar_Set ("nextserver", "");
586
587 //ZOID special hack for end game screen in coop mode
588 if (Cvar_IntValue ("coop") && !Q_stricmp(level, "victory.pcx"))
589 Cvar_Set ("nextserver", "gamemap \"*base1\"");
590
591 // if there is a $, use the remainder as a spawnpoint
592 ch = strchr(level, '$');
593 if (ch)
594 {
595 *ch = 0;
596 strcpy (spawnpoint, ch+1);
597 }
598 else
599 spawnpoint[0] = 0;
600
601 // skip the end-of-unit flag if necessary
602 //r1: should be using memmove for this, overlapping strcpy = unreliable
603 if (level[0] == '*')
604 //strcpy (level, level+1);
605 memmove (level, level+1, strlen(level)+1);
606
607 l = strlen(level);
608 if (l > 4 && !strcmp (level+l-4, ".cin") )
609 {
610 if (attractloop)
611 Com_Error (ERR_HARD, "Demomap may only be used to replay demos (*.dm2)");
612 #ifndef DEDICATED_ONLY
613 SCR_BeginLoadingPlaque (); // for local system
614 #endif
615 SV_BroadcastCommand ("changing\n");
616 SV_SpawnServer (level, spawnpoint, ss_cinematic, attractloop, loadgame);
617 }
618 else if (l > 4 && !strcmp (level+l-4, ".dm2") )
619 {
620 if (!attractloop)
621 Com_Printf ("WARNING: Loading a Game DLL while playing back a demo. Use 'demomap' if you encounter problems.\n", LOG_GENERAL);
622 //Com_Error (ERR_HARD, "Demos should be replayed using the 'demomap' command");
623 #ifndef DEDICATED_ONLY
624 SCR_BeginLoadingPlaque (); // for local system
625 #endif
626 SV_BroadcastCommand ("changing\n");
627 sv.attractloop = attractloop = 1;
628 SV_SpawnServer (level, spawnpoint, ss_demo, attractloop, loadgame);
629 }
630 else if (l > 4 && !strcmp (level+l-4, ".pcx") )
631 {
632 if (attractloop)
633 Com_Error (ERR_HARD, "Demomap may only be used to replay demos (*.dm2)");
634 #ifndef DEDICATED_ONLY
635 SCR_BeginLoadingPlaque (); // for local system
636 #endif
637 SV_BroadcastCommand ("changing\n");
638 SV_SpawnServer (level, spawnpoint, ss_pic, attractloop, loadgame);
639 }
640 else
641 {
642 if (attractloop)
643 Com_Error (ERR_HARD, "Demomap may only be used to replay demos (*.dm2)");
644 #ifndef DEDICATED_ONLY
645 SCR_BeginLoadingPlaque (); // for local system
646 #endif
647 SV_BroadcastCommand ("changing\n");
648 SV_SendClientMessages ();
649 SV_SpawnServer (level, spawnpoint, ss_game, attractloop, loadgame);
650
651 //r1: do we really need this?
652 //Cbuf_CopyToDefer ();
653 }
654
655 #ifdef ANTICHEAT
656 //FIXME: see how often this becomes a problem and rework acserver someday to handle it better...
657 if (SV_AntiCheat_IsConnected ())
658 {
659 client_t *cl;
660 int i;
661 for (i = 0; i < maxclients->intvalue; i++)
662 {
663 cl = &svs.clients[i];
664 if (cl->state >= cs_connected && !cl->anticheat_valid)
665 {
666 if ((sv_require_anticheat->intvalue == 2 || cl->anticheat_required == ANTICHEAT_REQUIRED)
667 && (cl->anticheat_required != ANTICHEAT_EXEMPT))
668 {
669 SV_ClientPrintf (cl, PRINT_HIGH, ANTICHEATMESSAGE " Due to a server connection problem, you must reconnect to re-enable anticheat.\n");
670 SV_DropClient (cl, true);
671 }
672 }
673 }
674 }
675 #endif
676
677 //check the server is running proper Q2 physics model
678 //if (!Sys_CheckFPUStatus ())
679 // Com_Error (ERR_FATAL, "FPU control word is not set as expected, Quake II physics model will break.");
680
681 SV_BroadcastCommand ("reconnect\n");
682 Z_Verify("SV_Map:END");
683 }
684