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