/* Copyright (C) 1997-2001 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "server.h" server_constant_t svc; // constant server info (trully persistant since sv_init) server_static_t svs; // persistant server info server_t sv; // local server /* We cache pak checksums at map start so we don't need to calculate them while game is going on */ typedef struct cached_checksum_s cached_checksum_t; struct cached_checksum_s { char pakname[MAX_OSPATH]; unsigned checksum; cached_checksum_t *next; }; static cached_checksum_t *cached_checksums = NULL; /* ================ SV_FindIndex ================ */ int SV_FindIndex( char *name, int start, int max, qboolean create ) { int i; if( !name || !name[0] ) return 0; for( i = 1; i < max && sv.configstrings[start+i][0]; i++ ) if( !strncmp( sv.configstrings[start+i], name, sizeof(sv.configstrings[start+i]) ) ) return i; if( !create ) return 0; if( i == max ) Com_Error( ERR_DROP, "*Index: overflow" ); Q_strncpyz( sv.configstrings[start+i], name, sizeof(sv.configstrings[i]) ); if( sv.state != ss_loading ) { // send the update to everyone SV_SendServerCommand( NULL, "cs %i \"%s\"", start+i, name ); } return i; } int SV_ModelIndex( char *name ) { return SV_FindIndex( name, CS_MODELS, MAX_MODELS, qtrue ); } int SV_SoundIndex( char *name ) { return SV_FindIndex( name, CS_SOUNDS, MAX_SOUNDS, qtrue ); } int SV_ImageIndex( char *name ) { return SV_FindIndex( name, CS_IMAGES, MAX_IMAGES, qtrue ); } int SV_SkinIndex( char *name ) { return SV_FindIndex( name, CS_SKINFILES, MAX_SKINFILES, qtrue ); } /* ================ SV_CreateBaseline Entity baselines are used to compress the update messages to the clients -- only the fields that differ from the baseline will be transmitted ================ */ void SV_CreateBaseline (void) { edict_t *svent; int entnum; for (entnum = 1; entnum < sv.num_edicts ; entnum++) { svent = EDICT_NUM(entnum); if (!svent->r.inuse) continue; if (!svent->s.modelindex && !svent->s.sound && !svent->s.effects) continue; svent->s.number = entnum; // // take current state as baseline // VectorCopy (svent->s.origin, svent->s.old_origin); sv.baselines[entnum] = svent->s; } } /* ================= SV_CheckForSavegame ================= */ void SV_CheckForSavegame (void) { char name[MAX_OSPATH]; int i; if (sv_noreload->integer) return; // wsw : Medar : disabled for now since we only have DM // we should most likely allow game module to decide whether to save return; Q_snprintfz (name, sizeof(name), "save/current/%s.sav", sv.name); if( FS_FOpenFile( name, NULL, FS_READ ) == -1 ) return; // no savegame SV_ClearWorld (); // get configstrings and areaportals SV_ReadLevelFile (); if (!sv.loadgame) { // coming back to a level after being in a different // level, so run it for ten seconds // rlava2 was sending too many lightstyles, and overflowing the // reliable data. temporarily changing the server state to loading // prevents these from being passed down. server_state_t previousState; previousState = sv.state; sv.state = ss_loading; Com_Printf("******RUNNING MAP FOR 10secs********\n"); Com_Printf("******If you see this message*******\n"); Com_Printf("******please, warn the warsow*******\n"); Com_Printf("******team (www.warsow.net)*******\n"); for (i=0 ; i<10000 ; i+=svc.frametime) ge->RunFrame ( svc.frametime ); sv.state = previousState; } } void SV_FreeCachedChecksums (void) { cached_checksum_t *cc, *next; cc = cached_checksums; while( cc != NULL ) { next = cc->next; Mem_Free( cc ); cc = next; } cached_checksums = NULL; } /* ================= SV_GetCachedPakChecksum ================= */ unsigned SV_GetCachedPakChecksum (const char *pakname) { cached_checksum_t *cc; cc = cached_checksums; while( cc != NULL ) { if( !Q_stricmp( cc->pakname, pakname ) ) return cc->checksum; cc = cc->next; } return 0; } /* ================= SV_CachePakChecksum ================= */ static void SV_CachePakChecksum (const char *filename) { const char *pakname; cached_checksum_t *new; int length; qbyte *buffer; unsigned checksum; if( strstr (filename, "..") || *filename == '.' || *filename == '/' || strchr (filename, '\"') ) return; // invalid filename for uploading pakname = FS_PakNameForFile(filename); if( !pakname ) return; if( SV_GetCachedPakChecksum(pakname) ) return; // already cached Com_DPrintf("Caching checksum for pak: %s (%s)\n", pakname, filename); length = FS_LoadAbsoluteFile( pakname, (void **)&buffer, NULL, 0 ); if( !buffer ) { Com_Printf( "SV_CachePakChecksum: Error loading %s\n", pakname ); checksum = 0; } else { checksum = Com_BlockChecksum( buffer, length ); FS_FreeFile( buffer ); } new = Mem_Alloc( sv_mempool, sizeof(cached_checksum_t) ); Q_strncpyz( new->pakname, pakname, MAX_OSPATH ); new->checksum = checksum; new->next = NULL; if( cached_checksums == NULL ) { cached_checksums = new; } else { cached_checksum_t *cc = cached_checksums; while( cc->next != NULL ) cc = cc->next; cc->next = new; } } /* ================= SV_CacheChecksums ================= */ void SV_CacheChecksums (void) { int i; char tempname[MAX_QPATH]; for( i = CS_MODELS; i < CS_IMAGES+MAX_IMAGES; i++ ) { if( strlen(sv.configstrings[i]) == 0 ) continue; if( i == CS_MODELS || i == CS_SOUNDS || i == CS_IMAGES ) // empty fields continue; if( i == CS_MODELS+1 ) // map { SV_CachePakChecksum( sv.configstrings[i] ); continue; } else if( i < CS_MODELS+MAX_MODELS ) { if (sv.configstrings[i][0] == '*' || sv.configstrings[i][0] == '$' || // disable playermodel downloading for now sv.configstrings[i][0] == '#') { continue; } SV_CachePakChecksum( sv.configstrings[i] ); continue; } else if( i < CS_SOUNDS+MAX_SOUNDS ) { if( sv.configstrings[i][0] == '*' ) // sexed sounds continue; Q_strncpyz( tempname, sv.configstrings[i], sizeof(tempname) ); COM_DefaultExtension( tempname, ".wav", sizeof(tempname) ); SV_CachePakChecksum( tempname ); continue; } else if( i < CS_IMAGES+MAX_IMAGES ) { // disabled for now continue; } } } /* ================ SV_SpawnServer Change the server to a new map, taking all connected clients along with it. ================ */ void SV_SpawnServer( char *server, char *spawnpoint, server_state_t serverstate, qboolean loadgame, qboolean devmap ) { unsigned checksum; if( devmap ) Cvar_ForceSet( "sv_cheats", "1" ); Cvar_FixCheatVars(); Com_Printf( "------- Server Initialization -------\n" ); Com_DPrintf( "SpawnServer: %s\n",server ); svs.spawncount++; // any partially connected client will be // restarted sv.state = ss_dead; Com_SetServerState( sv.state ); // wipe the entire per-level structure memset( &sv, 0, sizeof(sv) ); SV_ResetClientFrameCounters(); svs.realtime = 0; sv.loadgame = loadgame; Q_strncpyz( sv.name, server, sizeof(sv.name) ); sv.time = 1000; if( serverstate != ss_game ) CM_LoadMap( "", qfalse, &checksum ); // no real map else { Q_snprintfz( sv.configstrings[CS_MODELS+1],sizeof(sv.configstrings[CS_MODELS+1]), "maps/%s.bsp", server ); CM_LoadMap( sv.configstrings[CS_MODELS+1], qfalse, &checksum ); // // clear physics interaction links // SV_ClearWorld(); } Q_snprintfz( sv.configstrings[CS_MAPCHECKSUM], sizeof(sv.configstrings[CS_MAPCHECKSUM]), "%i", checksum ); // // spawn the rest of the entities on the map // // precache and static commands can be issued during // map initialization sv.state = ss_loading; Com_SetServerState( sv.state ); // load and spawn all other entities ge->SpawnEntities( sv.name, CM_EntityString(), CM_EntityStringLen(), spawnpoint ); // run two frames to allow everything to settle ge->RunFrame( svc.frametime ); ge->RunFrame( svc.frametime ); // all precaches are complete sv.state = serverstate; Com_SetServerState( sv.state ); // create a baseline for more efficient communications SV_CreateBaseline(); // check for a savegame SV_CheckForSavegame(); // cache checksums SV_CacheChecksums(); // set serverinfo variable Cvar_FullSet( "mapname", sv.name, CVAR_SERVERINFO | CVAR_NOSET, qtrue ); Com_Printf( "-------------------------------------\n" ); } /* ============== SV_InitGame A brand new game has been started ============== */ void SV_InitGame( void ) { int i; edict_t *ent; if( svs.initialized ) { // cause any connected clients to reconnect SV_Shutdown ("Server restarted\n", qtrue); // SV_Shutdown will also call Cvar_GetLatchedVars } else { // make sure the client is down CL_Disconnect( NULL ); SCR_BeginLoadingPlaque(); // get any latched variable changes (sv_maxclients, etc) Cvar_GetLatchedVars( CVAR_LATCH ); } svs.initialized = qtrue; // 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.num_client_entities = sv_maxclients->integer * UPDATE_BACKUP * 64; svs.client_entities = Mem_Alloc( sv_mempool, sizeof(entity_state_t) * svs.num_client_entities ); // init network stuff NET_Config( (sv_maxclients->integer > 1) ); // 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; memset( &svs.clients[i].lastcmd, 0, sizeof(svs.clients[i].lastcmd) ); } } /* ====================== SV_Map the full syntax is: map [*]$+ command from the console or progs. Map can also be a .roq, or .dqf file Nextserver is used to allow a cinematic to play, then proceed to another level: map intro.roq+q3dm0 ====================== */ void SV_Map( char *levelstring, qboolean loadgame, qboolean devmap ) { char level[MAX_QPATH]; char *ch; char spawnpoint[MAX_QPATH]; int i; sv.loadgame = loadgame; if( sv.state == ss_dead && !sv.loadgame ) SV_InitGame(); // the game is just starting Q_strncpyz( level, levelstring, sizeof(level) ); // 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", "" ); // if there is a $, use the remainder as a spawnpoint ch = strstr( level, "$" ); if( ch ) { *ch = 0; Q_strncpyz( spawnpoint, ch+1, sizeof(spawnpoint) ); } else spawnpoint[0] = 0; // skip the end-of-unit flag if necessary if( level[0] == '*' ) Q_strncpyz( level, level+1, sizeof(level) ); // wsw : Medar : this used to be at SV_SpawnServer, but we need to do it before sending changing // so we don't send frames after sending changing command // leave slots at start for clients only for( i = 0; i < sv_maxclients->integer; i++ ) { // needs to reconnect if( svs.clients[i].state > CS_CONNECTED ) svs.clients[i].state = CS_CONNECTED; svs.clients[i].lastframe = -1; } SCR_BeginLoadingPlaque(); // for local system SV_BroadcastCommand( "changing\n" ); SV_SendClientMessages(); SV_SpawnServer( level, spawnpoint, ss_game, loadgame, devmap ); Cbuf_CopyToDefer(); SV_BroadcastCommand( "reconnect\n" ); }