/* ===================== CL_NextDemo Called to play the next demo in the demo loop ===================== */ void CL_NextDemo(void) { char str[1024]; if (cls.demonum == -1) return; // don't play demos SCR_BeginLoadingPlaque(); if (!cls.demos[cls.demonum][0] || cls.demonum == MAX_DEMOS) { cls.demonum = 0; if (!cls.demos[cls.demonum][0]) { Con_Printf("No demos listed with startdemos\n"); cls.demonum = -1; return; } } sprintf(str, "playdemo %s\n", cls.demos[cls.demonum]); Cbuf_InsertText(str); cls.demonum++; }
/* ====================== SV_Map the full syntax is: map [*]<map>$<startspot>+<nextserver> command from the console or progs. Map can also be a.cin, .pcx, or .dm2 file Nextserver is used to allow a cinematic to play, then proceed to another level: map tram.cin+jail_e3 ====================== */ void SV_Map (qboolean attractloop, char *levelstring, qboolean loadgame) { char level[MAX_QPATH]; char *ch; int l; char spawnpoint[MAX_QPATH]; sv.loadgame = loadgame; sv.attractloop = attractloop; if (sv.state == ss_dead && !sv.loadgame) SV_InitGame (); // the game is just starting strcpy (level, levelstring); // if there is a + in the map, set nextserver to the remainder ch = strstr(level, "+"); if (ch) { *ch = 0; Cvar_Set ("nextserver", va("gamemap \"%s\"", ch+1)); } else Cvar_Set ("nextserver", ""); //ZOID special hack for end game screen in coop mode if (Cvar_VariableValue ("coop") && !Q_stricmp(level, "victory.pcx")) Cvar_Set ("nextserver", "gamemap \"*base1\""); // if there is a $, use the remainder as a spawnpoint ch = strstr(level, "$"); if (ch) { *ch = 0; strcpy (spawnpoint, ch+1); } else spawnpoint[0] = 0; // skip the end-of-unit flag if necessary if (level[0] == '*') strcpy (level, level+1); l = strlen(level); if (l > 4 && !strcmp (level+l-4, ".cin") ) { SCR_BeginLoadingPlaque (); // for local system SV_BroadcastCommand ("changing\n"); SV_SpawnServer (level, spawnpoint, ss_cinematic, attractloop, loadgame); } else if (l > 4 && !strcmp (level+l-4, ".dm2") ) { SCR_BeginLoadingPlaque (); // for local system SV_BroadcastCommand ("changing\n"); SV_SpawnServer (level, spawnpoint, ss_demo, attractloop, loadgame); } else if (l > 4 && !strcmp (level+l-4, ".pcx") ) { SCR_BeginLoadingPlaque (); // for local system SV_BroadcastCommand ("changing\n"); SV_SpawnServer (level, spawnpoint, ss_pic, attractloop, loadgame); } else { SCR_BeginLoadingPlaque (); // for local system SV_BroadcastCommand ("changing\n"); SV_SendClientMessages (); SV_SpawnServer (level, spawnpoint, ss_game, attractloop, loadgame); Cbuf_CopyToDefer (); } SV_BroadcastCommand ("reconnect\n"); }
/* ============== SV_InitGame A brand new game has been started ============== */ void SV_InitGame (void) { int i; edict_t *ent; char idmaster[32]; if (svs.initialized) { // cause any connected clients to reconnect SV_Shutdown ("Server restarted\n", true); } else { // make sure the client is down CL_Drop (); SCR_BeginLoadingPlaque (); } // get any latched variable changes (maxclients, etc) Cvar_GetLatchedVars (); svs.initialized = true; if (Cvar_VariableValue ("coop") && Cvar_VariableValue ("deathmatch")) { Com_Printf("Deathmatch and Coop both set, disabling Coop\n"); Cvar_FullSet ("coop", "0", CVAR_SERVERINFO | CVAR_LATCH); } // dedicated servers are can't be single player and are usually DM // so unless they explicity set coop, force it to deathmatch if (dedicated->value) { if (!Cvar_VariableValue ("coop")) Cvar_FullSet ("deathmatch", "1", CVAR_SERVERINFO | CVAR_LATCH); } // init clients if (Cvar_VariableValue ("deathmatch")) { if (maxclients->value <= 1) Cvar_FullSet ("maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH); else if (maxclients->value > MAX_CLIENTS) Cvar_FullSet ("maxclients", va("%i", MAX_CLIENTS), CVAR_SERVERINFO | CVAR_LATCH); } else if (Cvar_VariableValue ("coop")) { if (maxclients->value <= 1 || maxclients->value > 4) Cvar_FullSet ("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH); #ifdef COPYPROTECT if (!sv.attractloop && !dedicated->value) Sys_CopyProtect (); #endif } else // non-deathmatch, non-coop is one player { Cvar_FullSet ("maxclients", "1", CVAR_SERVERINFO | CVAR_LATCH); #ifdef COPYPROTECT if (!sv.attractloop) Sys_CopyProtect (); #endif } svs.spawncount = rand(); svs.clients = Z_Malloc (sizeof(client_t)*maxclients->value); svs.num_client_entities = maxclients->value*UPDATE_BACKUP*64; svs.client_entities = Z_Malloc (sizeof(entity_state_t)*svs.num_client_entities); // init network stuff NET_Config ( (maxclients->value > 1) ); // heartbeats will always be sent to the id master svs.last_heartbeat = -99999; // send immediately Com_sprintf(idmaster, sizeof(idmaster), "192.246.40.37:%i", PORT_MASTER); NET_StringToAdr (idmaster, &master_adr[0]); // init game SV_InitGameProgs (); for (i=0 ; i<maxclients->value ; i++) { ent = EDICT_NUM(i+1); ent->s.number = i+1; svs.clients[i].edict = ent; memset (&svs.clients[i].lastcmd, 0, sizeof(svs.clients[i].lastcmd)); } }
/* ================== Host_Reconnect_f This command causes the client to wait for the signon messages again. This is sent just before a server changes levels ================== */ void Host_Reconnect_f (void) { SCR_BeginLoadingPlaque (); cls.signon = 0; // need new connection messages }
/* * SV_InitGame * A brand new game has been started */ void SV_InitGame( void ) { int i; edict_t *ent; netadr_t address, ipv6_address; // make sure the client is down CL_Disconnect( NULL ); SCR_BeginLoadingPlaque(); if( svs.initialized ) { // cause any connected clients to reconnect SV_ShutdownGame( "Server restarted", qtrue ); // SV_ShutdownGame will also call Cvar_GetLatchedVars } else { // get any latched variable changes (sv_maxclients, etc) Cvar_GetLatchedVars( CVAR_LATCH ); } svs.initialized = qtrue; if( sv_skilllevel->integer > 2 ) Cvar_ForceSet( "sv_skilllevel", "2" ); if( sv_skilllevel->integer < 0 ) Cvar_ForceSet( "sv_skilllevel", "0" ); // init clients if( sv_maxclients->integer < 1 ) Cvar_FullSet( "sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH, qtrue ); else if( sv_maxclients->integer > MAX_CLIENTS ) Cvar_FullSet( "sv_maxclients", va( "%i", MAX_CLIENTS ), CVAR_SERVERINFO | CVAR_LATCH, qtrue ); svs.spawncount = rand(); svs.clients = Mem_Alloc( sv_mempool, sizeof( client_t )*sv_maxclients->integer ); svs.client_entities.num_entities = sv_maxclients->integer * UPDATE_BACKUP * MAX_SNAP_ENTITIES; svs.client_entities.entities = Mem_Alloc( sv_mempool, sizeof( entity_state_t ) * svs.client_entities.num_entities ); // init network stuff address.type = NA_NOTRANSMIT; ipv6_address.type = NA_NOTRANSMIT; if( !dedicated->integer ) { NET_InitAddress( &address, NA_LOOPBACK ); if( !NET_OpenSocket( &svs.socket_loopback, SOCKET_LOOPBACK, &address, qtrue ) ) Com_Error( ERR_FATAL, "Couldn't open loopback socket: %s\n", NET_ErrorString() ); } if( dedicated->integer || sv_maxclients->integer > 1 ) { qboolean socket_opened = qfalse; // IPv4 NET_StringToAddress( sv_ip->string, &address ); NET_SetAddressPort( &address, sv_port->integer ); if( !NET_OpenSocket( &svs.socket_udp, SOCKET_UDP, &address, qtrue ) ) Com_Printf( "Error: Couldn't open UDP socket: %s\n", NET_ErrorString() ); else socket_opened = qtrue; // IPv6 NET_StringToAddress( sv_ip6->string, &ipv6_address ); if( ipv6_address.type == NA_IP6 ) { NET_SetAddressPort( &ipv6_address, sv_port6->integer ); if( !NET_OpenSocket( &svs.socket_udp6, SOCKET_UDP, &ipv6_address, qtrue ) ) Com_Printf( "Error: Couldn't open UDP6 socket: %s\n", NET_ErrorString() ); else socket_opened = qtrue; } else Com_Printf( "Error: invalid IPv6 address: %s\n", sv_ip6->string ); if( dedicated->integer && !socket_opened ) Com_Error( ERR_FATAL, "Couldn't open any socket\n" ); } #ifdef TCP_ALLOW_CONNECT if( sv_tcp->integer && ( dedicated->integer || sv_maxclients->integer > 1 ) ) { if( !NET_OpenSocket( &svs.socket_tcp, SOCKET_TCP, &address, qtrue ) ) { Com_Printf( "Error: Couldn't open TCP socket: %s", NET_ErrorString() ); Cvar_ForceSet( "sv_tcp", "0" ); } else { if( !NET_Listen( &svs.socket_tcp ) ) { Com_Printf( "Error: Couldn't listen to TCP socket: %s", NET_ErrorString() ); NET_CloseSocket( &svs.socket_tcp ); Cvar_ForceSet( "sv_tcp", "0" ); } } } #endif // init mm // SV_MM_Init(); // init game SV_InitGameProgs(); for( i = 0; i < sv_maxclients->integer; i++ ) { ent = EDICT_NUM( i+1 ); ent->s.number = i+1; svs.clients[i].edict = ent; } // load the map assert( !svs.cms ); svs.cms = CM_New( NULL ); }
/* ==================== Host_Init ==================== */ static void Host_Init (void) { int i; const char* os; char vabuf[1024]; if (COM_CheckParm("-profilegameonly")) Sys_AllowProfiling(false); // LordHavoc: quake never seeded the random number generator before... heh if (COM_CheckParm("-benchmark")) srand(0); // predictable random sequence for -benchmark else srand((unsigned int)time(NULL)); // FIXME: this is evil, but possibly temporary // LordHavoc: doesn't seem very temporary... // LordHavoc: made this a saved cvar // COMMANDLINEOPTION: Console: -developer enables warnings and other notices (RECOMMENDED for mod developers) if (COM_CheckParm("-developer")) { developer.value = developer.integer = 1; developer.string = "1"; } if (COM_CheckParm("-developer2") || COM_CheckParm("-developer3")) { developer.value = developer.integer = 1; developer.string = "1"; developer_extra.value = developer_extra.integer = 1; developer_extra.string = "1"; developer_insane.value = developer_insane.integer = 1; developer_insane.string = "1"; developer_memory.value = developer_memory.integer = 1; developer_memory.string = "1"; developer_memorydebug.value = developer_memorydebug.integer = 1; developer_memorydebug.string = "1"; } if (COM_CheckParm("-developer3")) { gl_paranoid.integer = 1;gl_paranoid.string = "1"; gl_printcheckerror.integer = 1;gl_printcheckerror.string = "1"; } // COMMANDLINEOPTION: Console: -nostdout disables text output to the terminal the game was launched from if (COM_CheckParm("-nostdout")) sys_nostdout = 1; // used by everything Memory_Init(); // initialize console command/cvar/alias/command execution systems Cmd_Init(); // initialize memory subsystem cvars/commands Memory_Init_Commands(); // initialize console and logging and its cvars/commands Con_Init(); // initialize various cvars that could not be initialized earlier u8_Init(); Curl_Init_Commands(); Cmd_Init_Commands(); Sys_Init_Commands(); COM_Init_Commands(); FS_Init_Commands(); // initialize console window (only used by sys_win.c) Sys_InitConsole(); // initialize the self-pack (must be before COM_InitGameType as it may add command line options) FS_Init_SelfPack(); // detect gamemode from commandline options or executable name COM_InitGameType(); // construct a version string for the corner of the console os = DP_OS_NAME; dpsnprintf (engineversion, sizeof (engineversion), "%s %s %s", gamename, os, buildstring); Con_Printf("%s\n", engineversion); // initialize process nice level Sys_InitProcessNice(); // initialize ixtable Mathlib_Init(); // initialize filesystem (including fs_basedir, fs_gamedir, -game, scr_screenshot_name) FS_Init(); // register the cvars for session locking Host_InitSession(); // must be after FS_Init Crypto_Init(); Crypto_Init_Commands(); NetConn_Init(); Curl_Init(); //PR_Init(); //PR_Cmd_Init(); PRVM_Init(); Mod_Init(); World_Init(); SV_Init(); V_Init(); // some cvars needed by server player physics (cl_rollangle etc) Host_InitCommands(); Host_InitLocal(); Host_ServerOptions(); Thread_Init(); if (cls.state == ca_dedicated) Cmd_AddCommand ("disconnect", CL_Disconnect_f, "disconnect from server (or disconnect all clients if running a server)"); else { Con_DPrintf("Initializing client\n"); R_Modules_Init(); Palette_Init(); #ifdef CONFIG_MENU MR_Init_Commands(); #endif VID_Shared_Init(); VID_Init(); Render_Init(); S_Init(); CDAudio_Init(); Key_Init(); CL_Init(); } // save off current state of aliases, commands and cvars for later restore if FS_GameDir_f is called // NOTE: menu commands are freed by Cmd_RestoreInitState Cmd_SaveInitState(); // FIXME: put this into some neat design, but the menu should be allowed to crash // without crashing the whole game, so this should just be a short-time solution // here comes the not so critical stuff if (setjmp(host_abortframe)) { return; } Host_AddConfigText(); Cbuf_Execute(); // if stuffcmds wasn't run, then quake.rc is probably missing, use default if (!host_stuffcmdsrun) { Cbuf_AddText("exec default.cfg\nexec " CONFIGFILENAME "\nexec autoexec.cfg\nstuffcmds\n"); Cbuf_Execute(); } // put up the loading image so the user doesn't stare at a black screen... SCR_BeginLoadingPlaque(true); #ifdef CONFIG_MENU if (cls.state != ca_dedicated) { MR_Init(); } #endif // check for special benchmark mode // COMMANDLINEOPTION: Client: -benchmark <demoname> runs a timedemo and quits, results of any timedemo can be found in gamedir/benchmark.log (for example id1/benchmark.log) i = COM_CheckParm("-benchmark"); if (i && i + 1 < com_argc) if (!sv.active && !cls.demoplayback && !cls.connect_trying) { Cbuf_AddText(va(vabuf, sizeof(vabuf), "timedemo %s\n", com_argv[i + 1])); Cbuf_Execute(); } // check for special demo mode // COMMANDLINEOPTION: Client: -demo <demoname> runs a playdemo and quits i = COM_CheckParm("-demo"); if (i && i + 1 < com_argc) if (!sv.active && !cls.demoplayback && !cls.connect_trying) { Cbuf_AddText(va(vabuf, sizeof(vabuf), "playdemo %s\n", com_argv[i + 1])); Cbuf_Execute(); } // COMMANDLINEOPTION: Client: -capturedemo <demoname> captures a playdemo and quits i = COM_CheckParm("-capturedemo"); if (i && i + 1 < com_argc) if (!sv.active && !cls.demoplayback && !cls.connect_trying) { Cbuf_AddText(va(vabuf, sizeof(vabuf), "playdemo %s\ncl_capturevideo 1\n", com_argv[i + 1])); Cbuf_Execute(); } if (cls.state == ca_dedicated || COM_CheckParm("-listen")) if (!sv.active && !cls.demoplayback && !cls.connect_trying) { Cbuf_AddText("startmap_dm\n"); Cbuf_Execute(); } if (!sv.active && !cls.demoplayback && !cls.connect_trying) { #ifdef CONFIG_MENU Cbuf_AddText("togglemenu 1\n"); #endif Cbuf_Execute(); } Con_DPrint("========Initialized=========\n"); //Host_StartVideo(); if (cls.state != ca_dedicated) SV_StartThread(); }
void SV_ChangeLevel2_f( void ) { char *spawn_entity, *mapname; int flags, c = Cmd_Argc(); if( c < 2 ) { Msg( "Usage: changelevel2 <map> [landmark]\n" ); return; } if( host_xashds_hacks->value ) { Cbuf_InsertText(va("rcon changelevel2 %s %s\n",Cmd_Argv( 1 ), Cmd_Argv( 2 ))); return; } mapname = Cmd_Argv( 1 ); // determine spawn entity classname if( sv_maxclients->integer == 1 ) spawn_entity = GI->sp_entity; else spawn_entity = GI->mp_entity; flags = SV_MapIsValid( mapname, spawn_entity, Cmd_Argv( 2 )); if( flags & MAP_INVALID_VERSION ) { Msg( "SV_ChangeLevel: Map %s is invalid or not supported.\n", mapname ); return; } if(!( flags & MAP_IS_EXIST )) { Msg( "SV_ChangeLevel: Map %s doesn't exist.\n", mapname ); return; } if( c >= 3 && !Q_stricmp( sv.name, Cmd_Argv( 1 ))) { MsgDev( D_INFO, "SV_ChangeLevel: Can't changelevel with same map. Ignored.\n" ); return; } // bad changelevel position invoke enables in one-way transtion if( sv.net_framenum < 30 ) { if( sv_validate_changelevel->integer ) { MsgDev( D_INFO, "SV_ChangeLevel: An infinite changelevel detected.\n" ); MsgDev( D_INFO, "Changelevel will be disabled until the next save\\restore.\n" ); return; // lock with svs.spawncount here } } /*if( sv.state != ss_active ) { MsgDev( D_INFO, "Only the server may changelevel\n" ); return; }*/ SCR_BeginLoadingPlaque( false ); SV_ChangeLevel( true, Cmd_Argv( 1 ), Cmd_Argv( 2 )); }
/* * ====================== * SV_Map * * the full syntax is: * * map [*]<map>$<startspot>+<nextserver> * * command from the console or progs. * Map can also be a.cin, .pcx, or .dm2 file * Nextserver is used to allow a cinematic to play, then proceed to * another level: * * map tram.cin+jail_e3 * ====================== */ void SV_Map(qboolean attractloop, char *levelstring, qboolean loadgame) { char level[MAX_QPATH]; char *ch; int l; char spawnpoint[MAX_QPATH]; sv.loadgame = loadgame; sv.attractloop = attractloop; if ((sv.state == ss_dead) && !sv.loadgame) { SV_InitGame(); // the game is just starting } // r1ch fix: buffer overflow // strcpy (level, levelstring); strncpy(level, levelstring, sizeof(level) - 1); // if there is a + in the map, set nextserver to the remainder ch = strstr(level, "+"); if (ch) { *ch = 0; Cvar_Set("nextserver", va("gamemap \"%s\"", ch + 1)); } else { Cvar_Set("nextserver", ""); } //ZOID special hack for end game screen in coop mode if (Cvar_VariableValue("coop") && !Q_strcasecmp(level, "victory.pcx")) { Cvar_Set("nextserver", "gamemap \"*base1\""); } // if there is a $, use the remainder as a spawnpoint ch = strstr(level, "$"); if (ch) { *ch = 0; strcpy(spawnpoint, ch + 1); } else { spawnpoint[0] = 0; } // skip the end-of-unit flag if necessary if (level[0] == '*') { strcpy(level, level + 1); } l = strlen(level); #ifdef ROQ_SUPPORT if ((l > 4) && (!strcmp(level + l - 4, ".cin") || !strcmp(level + l - 4, ".roq"))) #else if ((l > 4) && !strcmp(level + l - 4, ".cin")) #endif // ROQ_SUPPORT { if (!dedicated->value) { #ifndef DEDICATED_ONLY SCR_BeginLoadingPlaque(); // for local system #endif } SV_BroadcastCommand("changing\n"); SV_SpawnServer(level, spawnpoint, ss_cinematic, attractloop, loadgame); } else if ((l > 4) && !strcmp(level + l - 4, ".dm2")) { if (!dedicated->value) { #ifndef DEDICATED_ONLY SCR_BeginLoadingPlaque(); // for local system #endif } SV_BroadcastCommand("changing\n"); SV_SpawnServer(level, spawnpoint, ss_demo, attractloop, loadgame); } else if ((l > 4) && !strcmp(level + l - 4, ".pcx")) { if (!dedicated->value) { #ifndef DEDICATED_ONLY SCR_BeginLoadingPlaque(); // for local system #endif } SV_BroadcastCommand("changing\n"); SV_SpawnServer(level, spawnpoint, ss_pic, attractloop, loadgame); } else { if (!dedicated->value) { #ifndef DEDICATED_ONLY SCR_BeginLoadingPlaque(); // for local system #endif } SV_BroadcastCommand("changing\n"); SV_SendClientMessages(); SV_SpawnServer(level, spawnpoint, ss_game, attractloop, loadgame); Cbuf_CopyToDefer(); } SV_BroadcastCommand("reconnect\n"); }
/* ================== SV_Map_f Goes directly to a given map without any savegame archiving. For development work ================== */ void SV_Map_f( void ) { char *spawn_entity; string mapname; int flags; if( Cmd_Argc() != 2 ) { Msg( "Usage: map <mapname>\n" ); return; } if( host_xashds_hacks->value ) { CL_Disconnect(); Cbuf_InsertText(va("wait;rcon map %s\n",Cmd_Argv( 1 ))); Cbuf_AddText("wait;connect 127.0.0.1\n"); return; } // hold mapname to other place Q_strncpy( mapname, Cmd_Argv( 1 ), sizeof( mapname )); // determine spawn entity classname if( sv_maxclients->integer == 1 ) spawn_entity = GI->sp_entity; else spawn_entity = GI->mp_entity; flags = SV_MapIsValid( mapname, spawn_entity, NULL ); if( flags & MAP_INVALID_VERSION ) { Msg( "SV_NewMap: map %s is invalid or not supported\n", mapname ); return; } if(!( flags & MAP_IS_EXIST )) { Msg( "SV_NewMap: map %s doesn't exist\n", mapname ); return; } if(!( flags & MAP_HAS_SPAWNPOINT )) { Msg( "SV_NewMap: map %s doesn't have a valid spawnpoint\n", mapname ); return; } // init network stuff NET_Config(( sv_maxclients->integer > 1 )); // changing singleplayer to multiplayer or back. refresh the player count if(( sv_maxclients->modified ) || ( deathmatch->modified ) || ( coop->modified ) || ( teamplay->modified )) Host_ShutdownServer(); SCR_BeginLoadingPlaque( false ); sv.changelevel = false; sv.background = false; sv.loadgame = false; // set right state SV_ClearSaveDir (); // delete all temporary *.hl files SV_DeactivateServer(); SV_SpawnServer( mapname, NULL ); SV_LevelInit( mapname, NULL, NULL, false ); SV_ActivateServer (); }
/* ===================== CL_ParseServerMessage ===================== */ void CL_ParseServerMessage( sizebuf_t *msg ) { char *s; int i, j, cmd; int param1, param2; int bufStart; cls_message_debug.parsing = true; // begin parsing starting_count = BF_GetNumBytesRead( msg ); // updates each frame // parse the message while( 1 ) { if( BF_CheckOverflow( msg )) { Host_Error( "CL_ParseServerMessage: overflow!\n" ); return; } // mark start position bufStart = BF_GetNumBytesRead( msg ); // end of message if( BF_GetNumBitsLeft( msg ) < 8 ) break; cmd = BF_ReadByte( msg ); // record command for debugging spew on parse problem CL_Parse_RecordCommand( cmd, bufStart ); // other commands switch( cmd ) { case svc_bad: Host_Error( "svc_bad\n" ); break; case svc_nop: // this does nothing break; case svc_disconnect: MsgDev( D_INFO, "Disconnected from server\n" ); CL_Drop (); Host_AbortCurrentFrame (); break; case svc_changing: if( BF_ReadOneBit( msg )) { cls.changelevel = true; S_StopAllSounds(); if( cls.demoplayback ) { SCR_BeginLoadingPlaque( cl.background ); cls.changedemo = true; } } else MsgDev( D_INFO, "Server disconnected, reconnecting\n" ); CL_ClearState (); CL_InitEdicts (); // re-arrange edicts if( cls.demoplayback ) { cl.background = (cls.demonum != -1) ? true : false; cls.state = ca_connected; } else cls.state = ca_connecting; cls.connect_time = MAX_HEARTBEAT; // CL_CheckForResend() will fire immediately break; case svc_setview: cl.refdef.viewentity = BF_ReadWord( msg ); break; case svc_sound: CL_ParseSoundPacket( msg, false ); break; case svc_time: // shuffle timestamps cl.mtime[1] = cl.mtime[0]; cl.mtime[0] = BF_ReadFloat( msg ); break; case svc_print: i = BF_ReadByte( msg ); MsgDev( D_INFO, "^6%s", BF_ReadString( msg )); if( i == PRINT_CHAT ) S_StartLocalSound( "common/menu2.wav", VOL_NORM, false ); break; case svc_stufftext: CL_ParseStuffText( msg ); break; case svc_lightstyle: CL_ParseLightStyle( msg ); break; case svc_setangle: CL_ParseSetAngle( msg ); break; case svc_serverdata: Cbuf_Execute(); // make sure any stuffed commands are done CL_ParseServerData( msg ); break; case svc_addangle: CL_ParseAddAngle( msg ); break; case svc_clientdata: CL_ParseClientData( msg ); break; case svc_packetentities: CL_ParsePacketEntities( msg, false ); break; case svc_deltapacketentities: CL_ParsePacketEntities( msg, true ); break; case svc_updatepings: CL_UpdateUserPings( msg ); break; case svc_usermessage: CL_RegisterUserMessage( msg ); break; case svc_particle: CL_ParseParticles( msg ); break; case svc_restoresound: CL_ParseRestoreSoundPacket( msg ); break; case svc_spawnstatic: CL_ParseStaticEntity( msg ); break; case svc_ambientsound: CL_ParseSoundPacket( msg, true ); break; case svc_crosshairangle: CL_ParseCrosshairAngle( msg ); break; case svc_spawnbaseline: CL_ParseBaseline( msg ); break; case svc_temp_entity: CL_ParseTempEntity( msg ); break; case svc_setpause: cl.refdef.paused = ( BF_ReadOneBit( msg ) != 0 ); break; case svc_deltamovevars: CL_ParseMovevars( msg ); break; case svc_customization: CL_ParseCustomization( msg ); break; case svc_centerprint: CL_CenterPrint( BF_ReadString( msg ), 0.25f ); break; case svc_event: CL_ParseEvent( msg ); break; case svc_event_reliable: CL_ParseReliableEvent( msg ); break; case svc_updateuserinfo: CL_UpdateUserinfo( msg ); break; case svc_intermission: cl.refdef.intermission = true; break; case svc_modelindex: CL_PrecacheModel( msg ); break; case svc_soundindex: CL_PrecacheSound( msg ); break; case svc_soundfade: CL_ParseSoundFade( msg ); break; case svc_cdtrack: param1 = BF_ReadByte( msg ); param1 = bound( 1, param1, MAX_CDTRACKS ); // tracknum param2 = BF_ReadByte( msg ); param2 = bound( 1, param2, MAX_CDTRACKS ); // loopnum S_StartBackgroundTrack( clgame.cdtracks[param1-1], clgame.cdtracks[param2-1], 0 ); break; case svc_serverinfo: CL_ServerInfo( msg ); break; case svc_eventindex: CL_PrecacheEvent( msg ); break; case svc_deltatable: Delta_ParseTableField( msg ); break; case svc_weaponanim: param1 = BF_ReadByte( msg ); // iAnim param2 = BF_ReadByte( msg ); // body CL_WeaponAnim( param1, param2 ); break; case svc_bspdecal: CL_ParseStaticDecal( msg ); break; case svc_roomtype: param1 = BF_ReadShort( msg ); Cvar_SetFloat( "room_type", param1 ); break; case svc_chokecount: i = BF_ReadByte( msg ); j = cls.netchan.incoming_acknowledged - 1; for( ; i > 0 && j > cls.netchan.outgoing_sequence - CL_UPDATE_BACKUP; j-- ) { if( cl.frames[j & CL_UPDATE_MASK].receivedtime != -3.0 ) { cl.frames[j & CL_UPDATE_MASK].receivedtime = -2.0; i--; } } break; case svc_resourcelist: CL_ParseResourceList( msg ); break; case svc_director: CL_ParseDirector( msg ); break; case svc_studiodecal: CL_ParseStudioDecal( msg ); break; case svc_querycvarvalue: CL_ParseCvarValue( msg ); break; case svc_querycvarvalue2: CL_ParseCvarValue2( msg ); break; default: CL_ParseUserMessage( msg, cmd ); break; } } cls_message_debug.parsing = false; // done // we don't know if it is ok to save a demo message until // after we have parsed the frame if( !cls.demoplayback ) { if( cls.demorecording && !cls.demowaiting ) { CL_WriteDemoMessage( false, starting_count, msg ); } else if( cls.state != ca_active ) { CL_WriteDemoMessage( true, starting_count, msg ); } } }
/* ====================== Host_Map_f handle a map <servername> command from the console. Active clients are kicked off. ====================== */ static void Host_Map_f (void) { int i; char name[MAX_QPATH]; if (Cmd_Argc() < 2) //no map name given { Con_Printf ("map <levelname>: start a new server\n"); if (cls.state == ca_disconnected) return; if (cls.state == ca_connected) { Con_Printf ("Current level: %s [ %s ]\n", cl.levelname, cl.mapname); return; } // (cls.state == ca_dedicated) if (sv.active) { Con_Printf ("Current level: %s [ %s ]\n", SV_GetLevelname(), sv.name); } return; } if (cmd_source != src_command) return; cls.demonum = -1; // stop demo loop in case this fails CL_Disconnect (); Host_ShutdownServer(false); Key_SetDest (key_game); // remove console or menu SCR_BeginLoadingPlaque (); info_mask = 0; if (!coop.integer && deathmatch.integer) info_mask2 = 0x80000000; else info_mask2 = 0; svs.serverflags = 0; // haven't completed an episode yet q_strlcpy (name, Cmd_Argv(1), sizeof(name)); SV_SpawnServer (name, NULL); if (!sv.active) return; if (cls.state != ca_dedicated) { loading_stage = 2; memset (cls.spawnparms, 0, MAX_MAPSTRING); for (i = 2; i < Cmd_Argc(); i++) { q_strlcat (cls.spawnparms, Cmd_Argv(i), MAX_MAPSTRING); q_strlcat (cls.spawnparms, " ", MAX_MAPSTRING); } Cmd_ExecuteString ("connect local", src_command); } }
/* ============== SV_InitGame A brand new game has been started. If mvd_spawn is non-zero, load the built-in MVD game module. ============== */ void SV_InitGame( unsigned mvd_spawn ) { int i, entnum; edict_t *ent; client_t *client; if( svs.initialized ) { // cause any connected clients to reconnect SV_Shutdown( "Server restarted\n", ERR_RECONNECT | mvd_spawn ); } else { #if USE_CLIENT // make sure the client is down CL_Disconnect( ERR_RECONNECT ); SCR_BeginLoadingPlaque(); #endif CM_FreeMap( &sv.cm ); SV_FreeFile( sv.entitystring ); memset( &sv, 0, sizeof( sv ) ); #if USE_FPS // set up default frametime for main loop sv.frametime = BASE_FRAMETIME; #endif } // get any latched variable changes (maxclients, etc) Cvar_GetLatchedVars (); #if !USE_CLIENT Cvar_Reset( sv_recycle ); #endif if( mvd_spawn ) { Cvar_Set( "deathmatch", "1" ); Cvar_Set( "coop", "0" ); } else { if( Cvar_VariableInteger( "coop" ) && Cvar_VariableInteger( "deathmatch" ) ) { Com_Printf( "Deathmatch and Coop both set, disabling Coop\n" ); Cvar_Set( "coop", "0" ); } // dedicated servers can't be single player and are usually DM // so unless they explicity set coop, force it to deathmatch if( Com_IsDedicated() ) { if( !Cvar_VariableInteger( "coop" ) ) Cvar_Set( "deathmatch", "1" ); } } // init clients if( Cvar_VariableInteger( "deathmatch" ) ) { if( sv_maxclients->integer <= 1 ) { Cvar_SetInteger( sv_maxclients, 8, FROM_CODE ); } else if( sv_maxclients->integer > CLIENTNUM_RESERVED ) { Cvar_SetInteger( sv_maxclients, CLIENTNUM_RESERVED, FROM_CODE ); } } else if( Cvar_VariableInteger( "coop" ) ) { if( sv_maxclients->integer <= 1 || sv_maxclients->integer > 4 ) Cvar_Set( "maxclients", "4" ); } else { // non-deathmatch, non-coop is one player Cvar_FullSet( "maxclients", "1", CVAR_SERVERINFO|CVAR_LATCH, FROM_CODE ); } // enable networking if( sv_maxclients->integer > 1 ) { NET_Config( NET_SERVER ); } svs.client_pool = SV_Mallocz( sizeof( client_t ) * sv_maxclients->integer ); svs.num_entities = sv_maxclients->integer * UPDATE_BACKUP * MAX_PACKET_ENTITIES; svs.entities = SV_Mallocz( sizeof( entity_state_t ) * svs.num_entities ); #if USE_MVD_SERVER // initialize MVD server if( !mvd_spawn ) { SV_MvdInit(); } #endif Cvar_ClampInteger( sv_reserved_slots, 0, sv_maxclients->integer - 1 ); #if USE_ZLIB svs.z.zalloc = SV_Zalloc; svs.z.zfree = SV_Zfree; if( deflateInit2( &svs.z, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 9, Z_DEFAULT_STRATEGY ) != Z_OK ) { Com_Error( ERR_FATAL, "%s: deflateInit2() failed", __func__ ); } #endif // init game #if USE_MVD_CLIENT if( mvd_spawn ) { if( ge ) { SV_ShutdownGameProgs(); } ge = &mvd_ge; ge->Init(); } else #endif SV_InitGameProgs(); // send heartbeat very soon svs.last_heartbeat = -(HEARTBEAT_SECONDS-5)*1000; for( i = 0; i < sv_maxclients->integer; i++ ) { client = svs.client_pool + i; entnum = i + 1; ent = EDICT_NUM( entnum ); ent->s.number = entnum; client->edict = ent; client->number = i; } #if USE_AC_SERVER AC_Connect( mvd_spawn ); #endif svs.initialized = qtrue; }
/* ================ SV_SpawnServer Change the server to a new map, taking all connected clients along with it. ================ */ void SV_SpawnServer( cm_t *cm, const char *server, const char *spawnpoint ) { int i; client_t *client; #if USE_CLIENT SCR_BeginLoadingPlaque(); // for local system #endif Com_Printf( "------- Server Initialization -------\n" ); Com_Printf( "SpawnServer: %s\n", server ); // everyone needs to reconnect FOR_EACH_CLIENT( client ) { SV_ClientReset( client ); } SV_BroadcastCommand( "changing map=%s\n", server ); SV_SendClientMessages(); SV_SendAsyncPackets(); // free current level CM_FreeMap( &sv.cm ); SV_FreeFile( sv.entitystring ); // wipe the entire per-level structure memset( &sv, 0, sizeof( sv ) ); sv.spawncount = ( rand() | ( rand() << 16 ) ) ^ Sys_Milliseconds(); sv.spawncount &= 0x7FFFFFFF; // set legacy spawncounts FOR_EACH_CLIENT( client ) { client->spawncount = sv.spawncount; } // reset entity counter svs.next_entity = 0; #if USE_FPS // set framerate parameters set_frame_time(); #endif // save name for levels that don't set message Q_strlcpy( sv.configstrings[CS_NAME], server, MAX_QPATH ); Q_strlcpy( sv.name, server, sizeof( sv.name ) ); if( Cvar_VariableInteger( "deathmatch" ) ) { sprintf( sv.configstrings[CS_AIRACCEL], "%d", sv_airaccelerate->integer ); } else { strcpy( sv.configstrings[CS_AIRACCEL], "0" ); } #if !USE_CLIENT resolve_masters(); #endif override_entity_string( server ); sv.cm = *cm; sprintf( sv.configstrings[CS_MAPCHECKSUM], "%d", ( int )cm->cache->checksum ); // set inline model names Q_concat( sv.configstrings[CS_MODELS + 1], MAX_QPATH, "maps/", server, ".bsp", NULL ); for( i = 1; i < cm->cache->nummodels; i++ ) { sprintf( sv.configstrings[ CS_MODELS + 1 + i ], "*%d", i ); } // // clear physics interaction links // SV_ClearWorld(); // // spawn the rest of the entities on the map // // precache and static commands can be issued during // map initialization sv.state = ss_loading; X86_PUSH_FPCW; X86_SINGLE_FPCW; // load and spawn all other entities ge->SpawnEntities ( sv.name, sv.entitystring ? sv.entitystring : cm->cache->entitystring, spawnpoint ); // run two frames to allow everything to settle ge->RunFrame (); sv.framenum++; ge->RunFrame (); sv.framenum++; X86_POP_FPCW; // make sure maxclients string is correct sprintf( sv.configstrings[CS_MAXCLIENTS], "%d", sv_maxclients->integer ); // all precaches are complete sv.state = ss_game; #if USE_MVD_SERVER // respawn dummy MVD client, set base states, etc SV_MvdMapChanged(); #endif // set serverinfo variable SV_InfoSet( "mapname", sv.name ); SV_InfoSet( "port", net_port->string ); Cvar_SetInteger( sv_running, ss_game, FROM_CODE ); Cvar_Set( "sv_paused", "0" ); Cvar_Set( "timedemo", "0" ); EXEC_TRIGGER( sv_changemapcmd ); #if USE_SYSCON SV_SetConsoleTitle(); #endif SV_BroadcastCommand( "reconnect\n" ); Com_Printf ("-------------------------------------\n"); }
/* ==================== CL_PlayDemo_f playdemo <demoname> ==================== */ void CL_PlayDemo_f( void ) { string filename; string demoname; int i; if( Cmd_Argc() != 2 ) { Msg( "Usage: playdemo <demoname>\n" ); return; } if( cls.demoplayback ) { CL_StopPlayback(); } if( cls.demorecording ) { Msg( "Can't playback during demo record.\n"); return; } Q_strncpy( demoname, Cmd_Argv( 1 ), sizeof( demoname ) - 1 ); Q_snprintf( filename, sizeof( filename ), "demos/%s.dem", demoname ); if( !FS_FileExists( filename, true )) { MsgDev( D_ERROR, "couldn't open %s\n", filename ); cls.demonum = -1; // stop demo loop return; } cls.demofile = FS_Open( filename, "rb", true ); Q_strncpy( cls.demoname, demoname, sizeof( cls.demoname )); Q_strncpy( menu.globals->demoname, demoname, sizeof( menu.globals->demoname )); // read in the m_DemoHeader FS_Read( cls.demofile, &demo.header, sizeof( demoheader_t )); if( demo.header.id != IDEMOHEADER ) { MsgDev( D_ERROR, "%s is not a demo file\n", filename ); FS_Close( cls.demofile ); cls.demofile = NULL; cls.demonum = -1; // stop demo loop return; } if( demo.header.net_protocol != PROTOCOL_VERSION || demo.header.dem_protocol != DEMO_PROTOCOL ) { MsgDev( D_ERROR, "demo protocol outdated\n" "Demo file protocols Network(%i), Demo(%i)\n" "Server protocol is at Network(%i), Demo(%i)\n", demo.header.net_protocol, demo.header.dem_protocol, PROTOCOL_VERSION, DEMO_PROTOCOL ); FS_Close( cls.demofile ); cls.demofile = NULL; cls.demonum = -1; // stop demo loop return; } // now read in the directory structure. FS_Seek( cls.demofile, demo.header.directory_offset, SEEK_SET ); FS_Read( cls.demofile, &demo.directory.numentries, sizeof( int )); if( demo.directory.numentries < 1 || demo.directory.numentries > 1024 ) { MsgDev( D_ERROR, "demo had bogus # of directory entries: %i\n", demo.directory.numentries ); FS_Close( cls.demofile ); cls.demofile = NULL; cls.demonum = -1; // stop demo loop cls.changedemo = false; return; } if( cls.changedemo ) { S_StopAllSounds(); SCR_BeginLoadingPlaque( false ); CL_ClearState (); CL_InitEdicts (); // re-arrange edicts } else { // NOTE: at this point demo is still valid CL_Disconnect(); Host_ShutdownServer(); Con_Close(); UI_SetActiveMenu( false ); } // allocate demo entries demo.directory.entries = Mem_Alloc( cls.mempool, sizeof( demoentry_t ) * demo.directory.numentries ); for( i = 0; i < demo.directory.numentries; i++ ) { FS_Read( cls.demofile, &demo.directory.entries[i], sizeof( demoentry_t )); } demo.entryIndex = 0; demo.entry = &demo.directory.entries[demo.entryIndex]; FS_Seek( cls.demofile, demo.entry->offset, SEEK_SET ); cls.demoplayback = true; cls.state = ca_connected; cl.background = (cls.demonum != -1) ? true : false; demo.starttime = CL_GetDemoPlaybackClock(); // for determining whether to read another message Netchan_Setup( NS_CLIENT, &cls.netchan, net_from, net_qport->integer ); demo.framecount = 0; cls.lastoutgoingcommand = -1; cls.nextcmdtime = host.realtime; // g-cont. is this need? Q_strncpy( cls.servername, demoname, sizeof( cls.servername )); // begin a playback demo }
/** * @brief Change the server to a new map, taking all connected clients along with it. * @note the full syntax is: @code map [day|night] [+]<map> [<assembly>] @endcode * @sa SV_AssembleMap * @sa CM_LoadMap * @sa Com_SetServerState */ void SV_Map (qboolean day, const char *levelstring, const char *assembly) { int i; unsigned checksum = 0; char * map = SV_GetConfigString(CS_TILES); char * pos = SV_GetConfigString(CS_POSITIONS); mapInfo_t *randomMap = NULL; client_t *cl; /* any partially connected client will be restarted */ Com_SetServerState(ss_restart); /* the game is just starting */ SV_InitGame(); if (!svs.initialized) { Com_Printf("Could not spawn the server\n"); return; } assert(levelstring[0] != '\0'); Com_DPrintf(DEBUG_SERVER, "SpawnServer: %s\n", levelstring); /* save name for levels that don't set message */ SV_SetConfigString(CS_NAME, levelstring); SV_SetConfigString(CS_LIGHTMAP, day); Q_strncpyz(sv->name, levelstring, sizeof(sv->name)); /* set serverinfo variable */ sv_mapname = Cvar_FullSet("sv_mapname", sv->name, CVAR_SERVERINFO | CVAR_NOSET); /* notify the client in case of a listening server */ SCR_BeginLoadingPlaque(); if (assembly) Q_strncpyz(sv->assembly, assembly, sizeof(sv->assembly)); else sv->assembly[0] = '\0'; /* leave slots at start for clients only */ cl = NULL; while ((cl = SV_GetNextClient(cl)) != NULL) { /* needs to reconnect */ if (cl->state >= cs_spawning) SV_SetClientState(cl, cs_connected); } /* assemble and load the map */ if (levelstring[0] == '+') { randomMap = SV_AssembleMap(levelstring + 1, assembly, map, pos, 0); if (!randomMap) { Com_Printf("Could not load assembly for map '%s'\n", levelstring); return; } } else { SV_SetConfigString(CS_TILES, levelstring); SV_SetConfigString(CS_POSITIONS, assembly ? assembly : ""); } CM_LoadMap(map, day, pos, &sv->mapData, &sv->mapTiles); Com_Printf("checksum for the map '%s': %u\n", levelstring, sv->mapData.mapChecksum); SV_SetConfigString(CS_MAPCHECKSUM, sv->mapData.mapChecksum); checksum = Com_GetScriptChecksum(); Com_Printf("ufo script checksum %u\n", checksum); SV_SetConfigString(CS_UFOCHECKSUM, checksum); SV_SetConfigString(CS_OBJECTAMOUNT, csi.numODs); SV_SetConfigString(CS_VERSION, UFO_VERSION); SV_SetConfigString(CS_MAPTITLE, SV_GetMapTitle(randomMap, levelstring)); if (Q_strstart(SV_GetConfigString(CS_MAPTITLE), "b/")) { /* For base attack, CS_MAPTITLE contains too many chars */ SV_SetConfigString(CS_MAPTITLE, "Base attack"); SV_SetConfigString(CS_NAME, ".baseattack"); } /* clear random-map assembly data */ Mem_Free(randomMap); randomMap = NULL; /* clear physics interaction links */ SV_ClearWorld(); /* fix this! */ for (i = 1; i <= sv->mapData.numInline; i++) sv->models[i] = CM_InlineModel(&sv->mapTiles, va("*%i", i)); /* precache and static commands can be issued during map initialization */ Com_SetServerState(ss_loading); TH_MutexLock(svs.serverMutex); /* load and spawn all other entities */ svs.ge->SpawnEntities(sv->name, SV_GetConfigStringInteger(CS_LIGHTMAP), sv->mapData.mapEntityString); TH_MutexUnlock(svs.serverMutex); /* all precaches are complete */ Com_SetServerState(ss_game); Com_Printf("-------------------------------------\n"); Cbuf_CopyToDefer(); }
/* ================== SV_ChangeLevel_f Saves the state of the map just being exited and goes to a new map. ================== */ void SV_ChangeLevel_f( void ) { char *spawn_entity, *mapname; int flags, c = Cmd_Argc(); if( c < 2 ) { Msg( "Usage: changelevel <map> [landmark]\n" ); return; } if( host_xashds_hacks->value ) { Cbuf_InsertText(va("rcon changelevel %s %s\n",Cmd_Argv( 1 ), Cmd_Argv( 2 ))); return; } mapname = Cmd_Argv( 1 ); // determine spawn entity classname if( sv_maxclients->integer == 1 ) spawn_entity = GI->sp_entity; else spawn_entity = GI->mp_entity; flags = SV_MapIsValid( mapname, spawn_entity, Cmd_Argv( 2 )); if( flags & MAP_INVALID_VERSION ) { Msg( "SV_ChangeLevel: Map %s is invalid or not supported\n", mapname ); return; } if(!( flags & MAP_IS_EXIST )) { Msg( "SV_ChangeLevel: Map %s doesn't exist\n", mapname ); return; } if( c >= 3 && !( flags & MAP_HAS_LANDMARK )) { if( sv_validate_changelevel->integer ) { // NOTE: we find valid map but specified landmark it's doesn't exist // run simple changelevel like in q1, throw warning MsgDev( D_INFO, "SV_ChangeLevel: map %s exists but doesn't contain\n", mapname ); MsgDev( D_INFO, "landmark with name %s. Run classic Quake changelevel.\n", Cmd_Argv( 2 )); c = 2; // reduce args } } if( c >= 3 && !Q_stricmp( sv.name, Cmd_Argv( 1 ))) { MsgDev( D_INFO, "SV_ChangeLevel: Can't changelevel with same map. Ignored.\n" ); return; } if( c == 2 && !( flags & MAP_HAS_SPAWNPOINT )) { if( sv_validate_changelevel->integer ) { MsgDev( D_INFO, "SV_ChangeLevel: Map %s doesn't have a valid spawnpoint. Ignored.\n", mapname ); return; } } // bad changelevel position invoke enables in one-way transtion if( sv.net_framenum < 30 ) { if( sv_validate_changelevel->integer && host.type != HOST_DEDICATED ) { MsgDev( D_INFO, "SV_ChangeLevel: An infinite changelevel detected.\n" ); MsgDev( D_INFO, "Changelevel will be disabled until the next save\\restore.\n" ); return; // lock with svs.spawncount here } } if( sv.state != ss_active ) { MsgDev( D_INFO, "Only the server may changelevel\n" ); return; } SCR_BeginLoadingPlaque( false ); if( sv.background ) { // just load map Cbuf_AddText( va( "map %s\n", mapname )); return; } if( c == 2 ) SV_ChangeLevel( false, Cmd_Argv( 1 ), NULL ); else SV_ChangeLevel( true, Cmd_Argv( 1 ), Cmd_Argv( 2 )); }
void SCR_Loading_f(void) { SCR_BeginLoadingPlaque(); }
/* ====================== SV_Map the full syntax is: map [*]<map>$<startspot>+<nextserver> command from the console or progs. Map can also be a.cin, .pcx, or .dm2 file Nextserver is used to allow a cinematic to play, then proceed to another level: map tram.cin+jail_e3 ====================== */ void SV_Map (qboolean attractloop, const char *levelstring, qboolean loadgame) { char level[MAX_QPATH]; char *ch; int l; char spawnpoint[MAX_QPATH]; strcpy(level, levelstring); // jit - copy level string before it gets modified by other commands (since it's a command argument) sv.loadgame = loadgame; sv.attractloop = attractloop; if (sv.state == ss_dead && !sv.loadgame) SV_InitGame(); // the game is just starting // if there is a + in the map, set nextserver to the remainder ch = strstr(level, "+"); if (ch) { *ch = 0; Cvar_Set("nextserver", va("gamemap \"%s\"", ch + 1)); } else { Cvar_Set("nextserver", ""); } //ZOID special hack for end game screen in coop mode if (Cvar_VariableValue("coop") && Q_strcaseeq(level, "victory.pcx")) Cvar_Set("nextserver", "gamemap \"*base1\""); // if there is a $, use the remainder as a spawnpoint ch = strstr(level, "$"); if (ch) { *ch = 0; strcpy(spawnpoint, ch + 1); } else { spawnpoint[0] = 0; } // skip the end-of-unit flag if necessary if (level[0] == '*') strcpy (level, level+1); l = strlen(level); if (l > 4 && Q_streq(level + l - 4, ".cin")) { SCR_BeginLoadingPlaque(NULL); // for local system SV_BroadcastCommand("changing\n"); SV_SpawnServer(level, spawnpoint, ss_cinematic, attractloop, loadgame); } else if (l > 4 && Q_streq(level + l - 4, ".dm2")) { SCR_BeginLoadingPlaque(NULL); // for local system SV_BroadcastCommand("changing\n"); SV_SpawnServer(level, spawnpoint, ss_demo, attractloop, loadgame); } else if (l > 4 && Q_streq(level + l - 4, ".pcx")) { SCR_BeginLoadingPlaque(NULL); // for local system SV_BroadcastCommand("changing\n"); SV_SpawnServer(level, spawnpoint, ss_pic, attractloop, loadgame); } else { char changing_cmd[1024]; if (!dedicated->value) SCR_BeginLoadingPlaque(level); // for local system Com_sprintf(changing_cmd, sizeof(changing_cmd), "changing \"%s\"\n", level); SV_BroadcastCommand(changing_cmd); SV_SendClientMessages(); SV_SpawnServer(level, spawnpoint, ss_game, attractloop, loadgame); Cbuf_CopyToDefer(); } SV_BroadcastCommand("reconnect\n"); }