mirror of
https://github.com/w23/xash3d-fwgs
synced 2024-12-16 06:00:33 +01:00
5e0a0765ce
The `.editorconfig` file in this repo is configured to trim all trailing whitespace regardless of whether the line is modified. Trims all trailing whitespace in the repository to make the codebase easier to work with in editors that respect `.editorconfig`. `git blame` becomes less useful on these lines but it already isn't very useful. Commands: ``` find . -type f -name '*.h' -exec sed --in-place 's/[[:space:]]\+$//' {} \+ find . -type f -name '*.c' -exec sed --in-place 's/[[:space:]]\+$//' {} \+ ```
1065 lines
24 KiB
C
1065 lines
24 KiB
C
/*
|
|
sv_init.c - server initialize operations
|
|
Copyright (C) 2009 Uncle Mike
|
|
|
|
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 3 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.
|
|
*/
|
|
|
|
#include "common.h"
|
|
#include "server.h"
|
|
#include "net_encode.h"
|
|
#include "library.h"
|
|
#if XASH_LOW_MEMORY != 2
|
|
int SV_UPDATE_BACKUP = SINGLEPLAYER_BACKUP;
|
|
#endif
|
|
server_t sv; // local server
|
|
server_static_t svs; // persistant server info
|
|
svgame_static_t svgame; // persistant game info
|
|
|
|
/*
|
|
================
|
|
SV_AddResource
|
|
|
|
generic method to put the resources into array
|
|
================
|
|
*/
|
|
static void SV_AddResource( resourcetype_t type, const char *name, int size, byte flags, int index )
|
|
{
|
|
resource_t *pResource = &sv.resources[sv.num_resources];
|
|
|
|
if( sv.num_resources >= MAX_RESOURCES )
|
|
Host_Error( "MAX_RESOURCES limit exceeded (%d)\n", MAX_RESOURCES );
|
|
sv.num_resources++;
|
|
|
|
Q_strncpy( pResource->szFileName, name, sizeof( pResource->szFileName ));
|
|
pResource->nDownloadSize = size;
|
|
pResource->ucFlags = flags;
|
|
pResource->nIndex = index;
|
|
pResource->type = type;
|
|
}
|
|
|
|
/*
|
|
================
|
|
SV_SendSingleResource
|
|
|
|
hot precache on a flying
|
|
================
|
|
*/
|
|
void SV_SendSingleResource( const char *name, resourcetype_t type, int index, byte flags )
|
|
{
|
|
resource_t *pResource = &sv.resources[sv.num_resources];
|
|
int nSize = 0;
|
|
|
|
if( !COM_CheckString( name ))
|
|
return;
|
|
|
|
switch( type )
|
|
{
|
|
case t_model:
|
|
nSize = ( name[0] != '*' ) ? FS_FileSize( name, false ) : 0;
|
|
break;
|
|
case t_sound:
|
|
nSize = FS_FileSize( va( DEFAULT_SOUNDPATH "%s", name ), false );
|
|
break;
|
|
default:
|
|
nSize = FS_FileSize( name, false );
|
|
break;
|
|
}
|
|
|
|
SV_AddResource( type, name, nSize, flags, index );
|
|
MSG_BeginServerCmd( &sv.reliable_datagram, svc_resource );
|
|
SV_SendResource( pResource, &sv.reliable_datagram );
|
|
}
|
|
|
|
/*
|
|
================
|
|
SV_ModelIndex
|
|
|
|
register unique model for a server and client
|
|
================
|
|
*/
|
|
int SV_ModelIndex( const char *filename )
|
|
{
|
|
char name[MAX_QPATH];
|
|
int i;
|
|
|
|
if( !COM_CheckString( filename ))
|
|
return 0;
|
|
|
|
if( *filename == '\\' || *filename == '/' )
|
|
filename++;
|
|
Q_strncpy( name, filename, sizeof( name ));
|
|
COM_FixSlashes( name );
|
|
|
|
for( i = 1; i < MAX_MODELS && sv.model_precache[i][0]; i++ )
|
|
{
|
|
if( !Q_stricmp( sv.model_precache[i], name ))
|
|
return i;
|
|
}
|
|
|
|
if( i == MAX_MODELS )
|
|
{
|
|
Host_Error( "MAX_MODELS limit exceeded (%d)\n", MAX_MODELS );
|
|
return 0;
|
|
}
|
|
|
|
// register new model
|
|
Q_strncpy( sv.model_precache[i], name, sizeof( sv.model_precache[i] ));
|
|
|
|
if( sv.state != ss_loading )
|
|
{
|
|
// send the update to everyone
|
|
SV_SendSingleResource( name, t_model, i, sv.model_precache_flags[i] );
|
|
Con_Printf( S_WARN "late precache of %s\n", name );
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
================
|
|
SV_SoundIndex
|
|
|
|
register unique sound for client
|
|
================
|
|
*/
|
|
int GAME_EXPORT SV_SoundIndex( const char *filename )
|
|
{
|
|
char name[MAX_QPATH];
|
|
int i;
|
|
|
|
if( !COM_CheckString( filename ))
|
|
return 0;
|
|
|
|
if( filename[0] == '!' )
|
|
{
|
|
Con_Printf( S_WARN "'%s' do not precache sentence names!\n", filename );
|
|
return 0;
|
|
}
|
|
|
|
if( *filename == '\\' || *filename == '/' )
|
|
filename++;
|
|
Q_strncpy( name, filename, sizeof( name ));
|
|
COM_FixSlashes( name );
|
|
|
|
for( i = 1; i < MAX_SOUNDS && sv.sound_precache[i][0]; i++ )
|
|
{
|
|
if( !Q_stricmp( sv.sound_precache[i], name ))
|
|
return i;
|
|
}
|
|
|
|
if( i == MAX_SOUNDS )
|
|
{
|
|
Host_Error( "MAX_SOUNDS limit exceeded (%d)\n", MAX_SOUNDS );
|
|
return 0;
|
|
}
|
|
|
|
// register new sound
|
|
Q_strncpy( sv.sound_precache[i], name, sizeof( sv.sound_precache[i] ));
|
|
|
|
if( sv.state != ss_loading )
|
|
{
|
|
// send the update to everyone
|
|
SV_SendSingleResource( name, t_sound, i, 0 );
|
|
Con_Printf( S_WARN "late precache of %s\n", name );
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
================
|
|
SV_EventIndex
|
|
|
|
register network event for a server and client
|
|
================
|
|
*/
|
|
int SV_EventIndex( const char *filename )
|
|
{
|
|
char name[MAX_QPATH];
|
|
int i;
|
|
|
|
if( !COM_CheckString( filename ))
|
|
return 0;
|
|
|
|
Q_strncpy( name, filename, sizeof( name ));
|
|
COM_FixSlashes( name );
|
|
|
|
for( i = 1; i < MAX_EVENTS && sv.event_precache[i][0]; i++ )
|
|
{
|
|
if( !Q_stricmp( sv.event_precache[i], name ))
|
|
return i;
|
|
}
|
|
|
|
if( i == MAX_EVENTS )
|
|
{
|
|
Host_Error( "MAX_EVENTS limit exceeded (%d)\n", MAX_EVENTS );
|
|
return 0;
|
|
}
|
|
|
|
// register new event
|
|
Q_strncpy( sv.event_precache[i], name, sizeof( sv.event_precache[i] ));
|
|
|
|
if( sv.state != ss_loading )
|
|
{
|
|
// send the update to everyone
|
|
SV_SendSingleResource( name, t_eventscript, i, RES_FATALIFMISSING );
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
================
|
|
SV_GenericIndex
|
|
|
|
register generic resourse for a server and client
|
|
================
|
|
*/
|
|
int GAME_EXPORT SV_GenericIndex( const char *filename )
|
|
{
|
|
char name[MAX_QPATH];
|
|
int i;
|
|
|
|
if( !COM_CheckString( filename ))
|
|
return 0;
|
|
|
|
Q_strncpy( name, filename, sizeof( name ));
|
|
COM_FixSlashes( name );
|
|
|
|
for( i = 1; i < MAX_CUSTOM && sv.files_precache[i][0]; i++ )
|
|
{
|
|
if( !Q_stricmp( sv.files_precache[i], name ))
|
|
return i;
|
|
}
|
|
|
|
if( i == MAX_CUSTOM )
|
|
{
|
|
Host_Error( "MAX_CUSTOM limit exceeded (%d)\n", MAX_CUSTOM );
|
|
return 0;
|
|
}
|
|
|
|
// register new generic resource
|
|
Q_strncpy( sv.files_precache[i], name, sizeof( sv.files_precache[i] ));
|
|
|
|
if( sv.state != ss_loading )
|
|
{
|
|
// send the update to everyone
|
|
SV_SendSingleResource( name, t_generic, i, RES_FATALIFMISSING );
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
================
|
|
SV_ModelHandle
|
|
|
|
get model by handle
|
|
================
|
|
*/
|
|
model_t *SV_ModelHandle( int modelindex )
|
|
{
|
|
if( modelindex < 0 || modelindex >= MAX_MODELS )
|
|
return NULL;
|
|
return sv.models[modelindex];
|
|
}
|
|
|
|
void SV_ReadResourceList( const char *filename )
|
|
{
|
|
string token;
|
|
byte *afile;
|
|
char *pfile;
|
|
|
|
afile = FS_LoadFile( filename, NULL, false );
|
|
if( !afile ) return;
|
|
|
|
pfile = (char *)afile;
|
|
|
|
Con_DPrintf( "Precaching from %s\n", filename );
|
|
Con_DPrintf( "----------------------------------\n" );
|
|
|
|
while(( pfile = COM_ParseFile( pfile, token )) != NULL )
|
|
{
|
|
if( !COM_IsSafeFileToDownload( token ))
|
|
continue;
|
|
|
|
Con_DPrintf( " %s\n", token );
|
|
SV_GenericIndex( token );
|
|
}
|
|
|
|
Con_DPrintf( "----------------------------------\n" );
|
|
Mem_Free( afile );
|
|
}
|
|
|
|
/*
|
|
================
|
|
SV_CreateGenericResources
|
|
|
|
loads external resource list
|
|
================
|
|
*/
|
|
void SV_CreateGenericResources( void )
|
|
{
|
|
string filename;
|
|
|
|
Q_strncpy( filename, sv.model_precache[1], sizeof( filename ));
|
|
COM_ReplaceExtension( filename, ".res" );
|
|
COM_FixSlashes( filename );
|
|
|
|
SV_ReadResourceList( filename );
|
|
SV_ReadResourceList( "reslist.txt" );
|
|
}
|
|
|
|
/*
|
|
================
|
|
SV_CreateResourceList
|
|
|
|
add resources to common list
|
|
================
|
|
*/
|
|
void SV_CreateResourceList( void )
|
|
{
|
|
qboolean ffirstsent = false;
|
|
int i, nSize;
|
|
char *s;
|
|
|
|
sv.num_resources = 0;
|
|
|
|
for( i = 1; i < MAX_CUSTOM; i++ )
|
|
{
|
|
s = sv.files_precache[i];
|
|
if( !COM_CheckString( s )) break; // end of list
|
|
nSize = FS_FileSize( s, false );
|
|
SV_AddResource( t_generic, s, nSize, RES_FATALIFMISSING, i );
|
|
}
|
|
|
|
for( i = 1; i < MAX_SOUNDS; i++ )
|
|
{
|
|
s = sv.sound_precache[i];
|
|
if( !COM_CheckString( s ))
|
|
break; // end of list
|
|
|
|
if( s[0] == '!' )
|
|
{
|
|
if( !ffirstsent )
|
|
{
|
|
SV_AddResource( t_sound, "!", 0, RES_FATALIFMISSING, i );
|
|
ffirstsent = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nSize = FS_FileSize( va( DEFAULT_SOUNDPATH "%s", s ), false );
|
|
SV_AddResource( t_sound, s, nSize, 0, i );
|
|
}
|
|
}
|
|
|
|
for( i = 1; i < MAX_MODELS; i++ )
|
|
{
|
|
s = sv.model_precache[i];
|
|
if( !COM_CheckString( s )) break; // end of list
|
|
nSize = ( s[0] != '*' ) ? FS_FileSize( s, false ) : 0;
|
|
SV_AddResource( t_model, s, nSize, sv.model_precache_flags[i], i );
|
|
}
|
|
|
|
// just send names
|
|
for( i = 0; i < MAX_DECALS && host.draw_decals[i][0]; i++ )
|
|
{
|
|
SV_AddResource( t_decal, host.draw_decals[i], 0, 0, i );
|
|
}
|
|
|
|
for( i = 1; i < MAX_EVENTS; i++ )
|
|
{
|
|
s = sv.event_precache[i];
|
|
if( !COM_CheckString( s )) break; // end of list
|
|
nSize = FS_FileSize( s, false );
|
|
SV_AddResource( t_eventscript, s, nSize, RES_FATALIFMISSING, i );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
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
|
|
|
|
INTERNAL RESOURCE
|
|
================
|
|
*/
|
|
void SV_CreateBaseline( void )
|
|
{
|
|
entity_state_t nullstate, *base;
|
|
int playermodel;
|
|
int delta_type;
|
|
int entnum;
|
|
|
|
if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ))
|
|
playermodel = SV_ModelIndex( DEFAULT_PLAYER_PATH_QUAKE );
|
|
else playermodel = SV_ModelIndex( DEFAULT_PLAYER_PATH_HALFLIFE );
|
|
|
|
memset( &nullstate, 0, sizeof( nullstate ));
|
|
|
|
for( entnum = 0; entnum < svgame.numEntities; entnum++ )
|
|
{
|
|
edict_t *pEdict = EDICT_NUM( entnum );
|
|
|
|
if( !SV_IsValidEdict( pEdict ))
|
|
continue;
|
|
|
|
if( entnum != 0 && entnum <= svs.maxclients )
|
|
{
|
|
delta_type = DELTA_PLAYER;
|
|
}
|
|
else
|
|
{
|
|
if( !pEdict->v.modelindex )
|
|
continue; // invisible
|
|
delta_type = DELTA_ENTITY;
|
|
}
|
|
|
|
// take current state as baseline
|
|
base = &svs.baselines[entnum];
|
|
|
|
base->number = entnum;
|
|
|
|
// set entity type
|
|
if( FBitSet( pEdict->v.flags, FL_CUSTOMENTITY ))
|
|
base->entityType = ENTITY_BEAM;
|
|
else base->entityType = ENTITY_NORMAL;
|
|
|
|
svgame.dllFuncs.pfnCreateBaseline( delta_type, entnum, base, pEdict, playermodel, host.player_mins[0], host.player_maxs[0] );
|
|
sv.last_valid_baseline = entnum;
|
|
}
|
|
|
|
// create the instanced baselines
|
|
svgame.dllFuncs.pfnCreateInstancedBaselines();
|
|
|
|
// now put the baseline into the signon message.
|
|
MSG_BeginServerCmd( &sv.signon, svc_spawnbaseline );
|
|
|
|
for( entnum = 0; entnum < svgame.numEntities; entnum++ )
|
|
{
|
|
edict_t *pEdict = EDICT_NUM( entnum );
|
|
|
|
if( !SV_IsValidEdict( pEdict ))
|
|
continue;
|
|
|
|
if( entnum != 0 && entnum <= svs.maxclients )
|
|
{
|
|
delta_type = DELTA_PLAYER;
|
|
}
|
|
else
|
|
{
|
|
if( !pEdict->v.modelindex )
|
|
continue; // invisible
|
|
delta_type = DELTA_ENTITY;
|
|
}
|
|
|
|
// take current state as baseline
|
|
base = &svs.baselines[entnum];
|
|
|
|
MSG_WriteDeltaEntity( &nullstate, base, &sv.signon, true, delta_type, 1.0f, 0 );
|
|
}
|
|
|
|
MSG_WriteUBitLong( &sv.signon, LAST_EDICT, MAX_ENTITY_BITS ); // end of baselines
|
|
MSG_WriteUBitLong( &sv.signon, sv.num_instanced, 6 );
|
|
|
|
for( entnum = 0; entnum < sv.num_instanced; entnum++ )
|
|
{
|
|
base = &sv.instanced[entnum].baseline;
|
|
MSG_WriteDeltaEntity( &nullstate, base, &sv.signon, true, DELTA_ENTITY, 1.0f, 0 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
SV_FreeOldEntities
|
|
|
|
remove immediate entities
|
|
================
|
|
*/
|
|
void SV_FreeOldEntities( void )
|
|
{
|
|
edict_t *ent;
|
|
int i;
|
|
|
|
// at end of frame kill all entities which supposed to it
|
|
for( i = svs.maxclients + 1; i < svgame.numEntities; i++ )
|
|
{
|
|
ent = EDICT_NUM( i );
|
|
|
|
if( !ent->free && FBitSet( ent->v.flags, FL_KILLME ))
|
|
SV_FreeEdict( ent );
|
|
}
|
|
|
|
// decrement svgame.numEntities if the highest number entities died
|
|
for( ; EDICT_NUM( svgame.numEntities - 1 )->free; svgame.numEntities-- );
|
|
}
|
|
|
|
/*
|
|
================
|
|
SV_ActivateServer
|
|
|
|
activate server on changed map, run physics
|
|
================
|
|
*/
|
|
void SV_ActivateServer( int runPhysics )
|
|
{
|
|
int i, numFrames;
|
|
byte msg_buf[MAX_INIT_MSG];
|
|
sizebuf_t msg;
|
|
sv_client_t *cl;
|
|
|
|
if( !svs.initialized )
|
|
return;
|
|
|
|
MSG_Init( &msg, "ActivateServer", msg_buf, sizeof( msg_buf ));
|
|
|
|
// always clearing newunit variable
|
|
Cvar_SetValue( "sv_newunit", 0 );
|
|
|
|
// relese all intermediate entities
|
|
SV_FreeOldEntities ();
|
|
|
|
// Activate the DLL server code
|
|
svgame.globals->time = sv.time;
|
|
svgame.dllFuncs.pfnServerActivate( svgame.edicts, svgame.numEntities, svs.maxclients );
|
|
|
|
SV_SetStringArrayMode( true );
|
|
|
|
// parse user-specified resources
|
|
SV_CreateGenericResources();
|
|
|
|
if( runPhysics )
|
|
{
|
|
numFrames = (svs.maxclients <= 1) ? 2 : 8;
|
|
sv.frametime = SV_SPAWN_TIME;
|
|
}
|
|
else
|
|
{
|
|
sv.frametime = 0.001;
|
|
numFrames = 1;
|
|
}
|
|
|
|
// run some frames to allow everything to settle
|
|
for( i = 0; i < numFrames; i++ )
|
|
SV_Physics();
|
|
|
|
// create a baseline for more efficient communications
|
|
SV_CreateBaseline();
|
|
|
|
// collect all info from precached resources
|
|
SV_CreateResourceList();
|
|
|
|
// check and count all files that marked by user as unmodified (typically is a player models etc)
|
|
SV_TransferConsistencyInfo();
|
|
|
|
// send serverinfo to all connected clients
|
|
for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )
|
|
{
|
|
if( cl->state < cs_connected )
|
|
continue;
|
|
|
|
Netchan_Clear( &cl->netchan );
|
|
cl->delta_sequence = -1;
|
|
}
|
|
|
|
// invoke to refresh all movevars
|
|
memset( &svgame.oldmovevars, 0, sizeof( movevars_t ));
|
|
svgame.globals->changelevel = false;
|
|
|
|
// setup hostflags
|
|
sv.hostflags = 0;
|
|
|
|
HPAK_FlushHostQueue();
|
|
|
|
// tell what kind of server has been started.
|
|
if( svs.maxclients > 1 )
|
|
Con_Printf( "%i player server started\n", svs.maxclients );
|
|
else Con_Printf( "Game started\n" );
|
|
|
|
Log_Printf( "Started map \"%s\" (CRC \"%u\")\n", sv.name, sv.worldmapCRC );
|
|
|
|
// dedicated server purge unused resources here
|
|
if( Host_IsDedicated() )
|
|
Mod_FreeUnused ();
|
|
|
|
host.movevars_changed = true;
|
|
Host_SetServerState( ss_active );
|
|
|
|
Con_DPrintf( "level loaded at %.2f sec\n", Sys_DoubleTime() - svs.timestart );
|
|
|
|
if( sv.ignored_static_ents )
|
|
Con_Printf( S_WARN "%i static entities was rejected due buffer overflow\n", sv.ignored_static_ents );
|
|
|
|
if( sv.ignored_world_decals )
|
|
Con_Printf( S_WARN "%i static decals was rejected due buffer overflow\n", sv.ignored_world_decals );
|
|
|
|
if( svs.maxclients > 1 )
|
|
{
|
|
const char *cycle = Cvar_VariableString( "mapchangecfgfile" );
|
|
|
|
if( COM_CheckString( cycle ))
|
|
Cbuf_AddText( va( "exec %s\n", cycle ));
|
|
|
|
if( public_server->value )
|
|
Master_Add( );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
SV_DeactivateServer
|
|
|
|
deactivate server, free edicts, strings etc
|
|
================
|
|
*/
|
|
void SV_DeactivateServer( void )
|
|
{
|
|
int i;
|
|
|
|
if( !svs.initialized || sv.state == ss_dead )
|
|
return;
|
|
|
|
svgame.globals->time = sv.time;
|
|
svgame.dllFuncs.pfnServerDeactivate();
|
|
Host_SetServerState( ss_dead );
|
|
|
|
SV_FreeEdicts ();
|
|
|
|
SV_ClearPhysEnts ();
|
|
|
|
SV_EmptyStringPool();
|
|
|
|
for( i = 0; i < svs.maxclients; i++ )
|
|
{
|
|
// release client frames
|
|
if( svs.clients[i].frames )
|
|
Mem_Free( svs.clients[i].frames );
|
|
svs.clients[i].frames = NULL;
|
|
}
|
|
|
|
svgame.globals->maxEntities = GI->max_edicts;
|
|
svgame.globals->maxClients = svs.maxclients;
|
|
svgame.numEntities = svs.maxclients + 1; // clients + world
|
|
svgame.globals->startspot = 0;
|
|
svgame.globals->mapname = 0;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
SV_InitGame
|
|
|
|
A brand new game has been started
|
|
==============
|
|
*/
|
|
qboolean SV_InitGame( void )
|
|
{
|
|
string dllpath;
|
|
|
|
if( svs.initialized )
|
|
return true; // already initialized ?
|
|
|
|
// first initialize?
|
|
COM_ResetLibraryError();
|
|
|
|
COM_GetCommonLibraryPath( LIBRARY_SERVER, dllpath, sizeof( dllpath ));
|
|
|
|
if( !SV_LoadProgs( dllpath ))
|
|
{
|
|
Con_Printf( S_ERROR "can't initialize %s: %s\n", dllpath, COM_GetLibraryError() );
|
|
return false; // failed to loading server.dll
|
|
}
|
|
|
|
// client frames will be allocated in SV_ClientConnect
|
|
svs.initialized = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
SV_ShutdownGame
|
|
|
|
prepare to close server
|
|
==============
|
|
*/
|
|
void SV_ShutdownGame( void )
|
|
{
|
|
if( !GameState->loadGame )
|
|
SV_ClearGameState();
|
|
|
|
SV_FinalMessage( "", true );
|
|
S_StopBackgroundTrack();
|
|
CL_StopPlayback(); // stop demo too
|
|
|
|
if( GameState->newGame )
|
|
{
|
|
Host_EndGame( false, DEFAULT_ENDGAME_MESSAGE );
|
|
}
|
|
else
|
|
{
|
|
S_StopAllSounds( true );
|
|
SV_DeactivateServer();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
SV_SetupClients
|
|
|
|
determine the game type and prepare clients
|
|
================
|
|
*/
|
|
void SV_SetupClients( void )
|
|
{
|
|
qboolean changed_maxclients = false;
|
|
|
|
// check if clients count was really changed
|
|
if( svs.maxclients != (int)sv_maxclients->value )
|
|
changed_maxclients = true;
|
|
|
|
if( !changed_maxclients ) return; // nothing to change
|
|
|
|
// if clients count was changed we need to run full shutdown procedure
|
|
if( svs.maxclients ) Host_ShutdownServer();
|
|
|
|
// copy the actual value from cvar
|
|
svs.maxclients = (int)sv_maxclients->value;
|
|
|
|
// dedicated servers are can't be single player and are usually DM
|
|
if( Host_IsDedicated() )
|
|
svs.maxclients = bound( 4, svs.maxclients, MAX_CLIENTS );
|
|
else svs.maxclients = bound( 1, svs.maxclients, MAX_CLIENTS );
|
|
|
|
if( svs.maxclients == 1 )
|
|
Cvar_SetValue( "deathmatch", 0.0f );
|
|
else Cvar_SetValue( "deathmatch", 1.0f );
|
|
|
|
// make cvars consistant
|
|
if( coop.value ) Cvar_SetValue( "deathmatch", 0.0f );
|
|
|
|
// feedback for cvar
|
|
Cvar_FullSet( "maxplayers", va( "%d", svs.maxclients ), FCVAR_LATCH );
|
|
#if XASH_LOW_MEMORY != 2
|
|
SV_UPDATE_BACKUP = ( svs.maxclients == 1 ) ? SINGLEPLAYER_BACKUP : MULTIPLAYER_BACKUP;
|
|
#endif
|
|
|
|
svs.clients = Z_Realloc( svs.clients, sizeof( sv_client_t ) * svs.maxclients );
|
|
svs.num_client_entities = svs.maxclients * SV_UPDATE_BACKUP * NUM_PACKET_ENTITIES;
|
|
svs.packet_entities = Z_Realloc( svs.packet_entities, sizeof( entity_state_t ) * svs.num_client_entities );
|
|
Con_Reportf( "%s alloced by server packet entities\n", Q_memprint( sizeof( entity_state_t ) * svs.num_client_entities ));
|
|
|
|
// init network stuff
|
|
NET_Config(( svs.maxclients > 1 ));
|
|
svgame.numEntities = svs.maxclients + 1; // clients + world
|
|
ClearBits( sv_maxclients->flags, FCVAR_CHANGED );
|
|
}
|
|
|
|
static qboolean CRC32_MapFile( dword *crcvalue, const char *filename, qboolean multiplayer )
|
|
{
|
|
char headbuf[1024], buffer[1024];
|
|
int i, num_bytes, lumplen;
|
|
int version, hdr_size;
|
|
dheader_t *header;
|
|
file_t *f;
|
|
|
|
if( !crcvalue ) return false;
|
|
|
|
// always calc same checksum for singleplayer
|
|
if( multiplayer == false )
|
|
{
|
|
*crcvalue = (('H'<<24)+('S'<<16)+('A'<<8)+'X');
|
|
return true;
|
|
}
|
|
|
|
f = FS_Open( filename, "rb", false );
|
|
if( !f ) return false;
|
|
|
|
// read version number
|
|
FS_Read( f, &version, sizeof( int ));
|
|
FS_Seek( f, 0, SEEK_SET );
|
|
|
|
hdr_size = sizeof( int ) + sizeof( dlump_t ) * HEADER_LUMPS;
|
|
num_bytes = FS_Read( f, headbuf, hdr_size );
|
|
|
|
// corrupted map ?
|
|
if( num_bytes != hdr_size )
|
|
{
|
|
FS_Close( f );
|
|
return false;
|
|
}
|
|
|
|
header = (dheader_t *)headbuf;
|
|
|
|
// invalid version ?
|
|
switch( header->version )
|
|
{
|
|
case Q1BSP_VERSION:
|
|
case HLBSP_VERSION:
|
|
case QBSP2_VERSION:
|
|
break;
|
|
default:
|
|
FS_Close( f );
|
|
return false;
|
|
}
|
|
|
|
CRC32_Init( crcvalue );
|
|
|
|
for( i = LUMP_PLANES; i < HEADER_LUMPS; i++ )
|
|
{
|
|
lumplen = header->lumps[i].filelen;
|
|
FS_Seek( f, header->lumps[i].fileofs, SEEK_SET );
|
|
|
|
while( lumplen > 0 )
|
|
{
|
|
if( lumplen >= sizeof( buffer ))
|
|
num_bytes = FS_Read( f, buffer, sizeof( buffer ));
|
|
else num_bytes = FS_Read( f, buffer, lumplen );
|
|
|
|
if( num_bytes > 0 )
|
|
{
|
|
lumplen -= num_bytes;
|
|
CRC32_ProcessBuffer( crcvalue, buffer, num_bytes );
|
|
}
|
|
|
|
// file unexpected end ?
|
|
if( FS_Eof( f )) break;
|
|
}
|
|
}
|
|
|
|
FS_Close( f );
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
================
|
|
SV_SpawnServer
|
|
|
|
Change the server to a new map, taking all connected
|
|
clients along with it.
|
|
================
|
|
*/
|
|
qboolean SV_SpawnServer( const char *mapname, const char *startspot, qboolean background )
|
|
{
|
|
int i, current_skill;
|
|
edict_t *ent;
|
|
|
|
SV_SetupClients();
|
|
|
|
if( !SV_InitGame( ))
|
|
return false;
|
|
|
|
Log_Open();
|
|
Log_Printf( "Loading map \"%s\"\n", mapname );
|
|
Log_PrintServerVars();
|
|
|
|
svs.timestart = Sys_DoubleTime();
|
|
svs.spawncount++; // any partially connected client will be restarted
|
|
|
|
// let's not have any servers with no name
|
|
if( !COM_CheckString( hostname.string ))
|
|
Cvar_Set( "hostname", svgame.dllFuncs.pfnGetGameDescription ? svgame.dllFuncs.pfnGetGameDescription() : FS_Title( ));
|
|
|
|
if( startspot )
|
|
{
|
|
Con_Printf( "Spawn Server: %s [%s]\n", mapname, startspot );
|
|
}
|
|
else
|
|
{
|
|
Con_DPrintf( "Spawn Server: %s\n", mapname );
|
|
}
|
|
|
|
memset( &sv, 0, sizeof( sv )); // wipe the entire per-level structure
|
|
sv.time = svgame.globals->time = 1.0f; // server spawn time it's always 1.0 second
|
|
sv.background = background;
|
|
|
|
// initialize buffers
|
|
MSG_Init( &sv.signon, "Signon", sv.signon_buf, sizeof( sv.signon_buf ));
|
|
MSG_Init( &sv.multicast, "Multicast", sv.multicast_buf, sizeof( sv.multicast_buf ));
|
|
MSG_Init( &sv.datagram, "Datagram", sv.datagram_buf, sizeof( sv.datagram_buf ));
|
|
MSG_Init( &sv.reliable_datagram, "Reliable Datagram", sv.reliable_datagram_buf, sizeof( sv.reliable_datagram_buf ));
|
|
MSG_Init( &sv.spec_datagram, "Spectator Datagram", sv.spectator_buf, sizeof( sv.spectator_buf ));
|
|
|
|
// clearing all the baselines
|
|
memset( svs.static_entities, 0, sizeof( entity_state_t ) * MAX_STATIC_ENTITIES );
|
|
memset( svs.baselines, 0, sizeof( entity_state_t ) * GI->max_edicts );
|
|
|
|
// make cvars consistant
|
|
if( coop.value ) Cvar_SetValue( "deathmatch", 0 );
|
|
current_skill = Q_rint( skill.value );
|
|
current_skill = bound( 0, current_skill, 3 );
|
|
Cvar_SetValue( "skill", (float)current_skill );
|
|
|
|
// force normal player collisions for single player
|
|
if( svs.maxclients == 1 )
|
|
Cvar_SetValue( "sv_clienttrace", 1 );
|
|
|
|
// copy gamemode into svgame.globals
|
|
svgame.globals->deathmatch = deathmatch.value;
|
|
svgame.globals->coop = coop.value;
|
|
svgame.globals->maxClients = svs.maxclients;
|
|
|
|
if( sv.background )
|
|
{
|
|
// tell the game parts about background state
|
|
Cvar_FullSet( "sv_background", "1", FCVAR_READ_ONLY );
|
|
Cvar_FullSet( "cl_background", "1", FCVAR_READ_ONLY );
|
|
}
|
|
else
|
|
{
|
|
Cvar_FullSet( "sv_background", "0", FCVAR_READ_ONLY );
|
|
Cvar_FullSet( "cl_background", "0", FCVAR_READ_ONLY );
|
|
}
|
|
|
|
// force normal player collisions for single player
|
|
if( svs.maxclients == 1 ) Cvar_SetValue( "sv_clienttrace", 1 );
|
|
|
|
// make sure what server name doesn't contain path and extension
|
|
COM_FileBase( mapname, sv.name );
|
|
|
|
// precache and static commands can be issued during map initialization
|
|
Host_SetServerState( ss_loading );
|
|
|
|
if( startspot )
|
|
Q_strncpy( sv.startspot, startspot, sizeof( sv.startspot ));
|
|
else sv.startspot[0] = '\0';
|
|
|
|
Q_snprintf( sv.model_precache[WORLD_INDEX], sizeof( sv.model_precache[0] ), "maps/%s.bsp", sv.name );
|
|
SetBits( sv.model_precache_flags[WORLD_INDEX], RES_FATALIFMISSING );
|
|
sv.worldmodel = sv.models[WORLD_INDEX] = Mod_LoadWorld( sv.model_precache[WORLD_INDEX], true );
|
|
CRC32_MapFile( &sv.worldmapCRC, sv.model_precache[WORLD_INDEX], svs.maxclients > 1 );
|
|
|
|
if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ) && FS_FileExists( "progs.dat", false ))
|
|
{
|
|
file_t *f = FS_Open( "progs.dat", "rb", false );
|
|
FS_Seek( f, sizeof( int ), SEEK_SET );
|
|
FS_Read( f, &sv.progsCRC, sizeof( int ));
|
|
FS_Close( f );
|
|
}
|
|
|
|
for( i = WORLD_INDEX; i < sv.worldmodel->numsubmodels; i++ )
|
|
{
|
|
Q_sprintf( sv.model_precache[i+1], "*%i", i );
|
|
sv.models[i+1] = Mod_ForName( sv.model_precache[i+1], false, false );
|
|
SetBits( sv.model_precache_flags[i+1], RES_FATALIFMISSING );
|
|
}
|
|
|
|
// leave slots at start for clients only
|
|
for( i = 0; i < svs.maxclients; i++ )
|
|
{
|
|
// needs to reconnect
|
|
if( svs.clients[i].state > cs_connected )
|
|
svs.clients[i].state = cs_connected;
|
|
|
|
ent = EDICT_NUM( i + 1 );
|
|
svs.clients[i].pViewEntity = NULL;
|
|
svs.clients[i].edict = ent;
|
|
SV_InitEdict( ent );
|
|
}
|
|
|
|
// heartbeats will always be sent to the id master
|
|
svs.last_heartbeat = MAX_HEARTBEAT; // send immediately
|
|
|
|
// get actual movevars
|
|
SV_UpdateMovevars( true );
|
|
|
|
// clear physics interaction links
|
|
SV_ClearWorld();
|
|
|
|
return true;
|
|
}
|
|
|
|
qboolean SV_Active( void )
|
|
{
|
|
return (sv.state != ss_dead);
|
|
}
|
|
|
|
qboolean SV_Initialized( void )
|
|
{
|
|
return svs.initialized;
|
|
}
|
|
|
|
int SV_GetMaxClients( void )
|
|
{
|
|
return svs.maxclients;
|
|
}
|
|
|
|
void SV_InitGameProgs( void )
|
|
{
|
|
string dllpath;
|
|
|
|
if( svgame.hInstance ) return; // already loaded
|
|
|
|
COM_GetCommonLibraryPath( LIBRARY_SERVER, dllpath, sizeof( dllpath ));
|
|
|
|
// just try to initialize
|
|
SV_LoadProgs( dllpath );
|
|
}
|
|
|
|
void SV_FreeGameProgs( void )
|
|
{
|
|
if( svs.initialized ) return; // server is active
|
|
|
|
// unload progs (free cvars and commands)
|
|
SV_UnloadProgs();
|
|
}
|
|
|
|
/*
|
|
================
|
|
SV_ExecLoadLevel
|
|
|
|
State machine exec new map
|
|
================
|
|
*/
|
|
void SV_ExecLoadLevel( void )
|
|
{
|
|
SV_SetStringArrayMode( false );
|
|
if( SV_SpawnServer( GameState->levelName, NULL, GameState->backgroundMap ))
|
|
{
|
|
SV_SpawnEntities( GameState->levelName );
|
|
SV_ActivateServer( true );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
SV_ExecLoadGame
|
|
|
|
State machine exec load saved game
|
|
================
|
|
*/
|
|
void SV_ExecLoadGame( void )
|
|
{
|
|
if( SV_SpawnServer( GameState->levelName, NULL, false ))
|
|
{
|
|
if( !SV_LoadGameState( GameState->levelName ))
|
|
SV_SpawnEntities( GameState->levelName );
|
|
SV_ActivateServer( false );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
SV_ExecChangeLevel
|
|
|
|
State machine exec changelevel path
|
|
================
|
|
*/
|
|
void SV_ExecChangeLevel( void )
|
|
{
|
|
SV_ChangeLevel( GameState->loadGame, GameState->levelName, GameState->landmarkName, GameState->backgroundMap );
|
|
}
|