Initial commit.
This commit is contained in:
5
code/server/exe_headers.cpp
Normal file
5
code/server/exe_headers.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
// The file that generates the PCH for the whole executable...
|
||||
//
|
||||
|
||||
#include "../server/exe_headers.h"
|
||||
|
||||
13
code/server/exe_headers.h
Normal file
13
code/server/exe_headers.h
Normal file
@@ -0,0 +1,13 @@
|
||||
// stuff added for PCH files. I want to have a lot of stuff included here so the PCH is pretty rich,
|
||||
// but without exposing too many extra protos, so for now (while I experiment)...
|
||||
//
|
||||
|
||||
#include "../game/q_shared.h"
|
||||
#include "../qcommon/qcommon.h"
|
||||
|
||||
#include "../client/client.h"
|
||||
#include "../server/server.h"
|
||||
#include "../renderer/tr_local.h"
|
||||
|
||||
#pragma hdrstop
|
||||
|
||||
321
code/server/server.h
Normal file
321
code/server/server.h
Normal file
@@ -0,0 +1,321 @@
|
||||
// server.h
|
||||
|
||||
#include "../game/q_shared.h"
|
||||
#include "../qcommon/qcommon.h"
|
||||
#include "../game/g_public.h"
|
||||
#include "../game/bg_public.h"
|
||||
|
||||
#ifndef SERVER_H
|
||||
#define SERVER_H
|
||||
|
||||
|
||||
//=============================================================================
|
||||
|
||||
//#define PERS_SCORE 0 // !!! MUST NOT CHANGE, SERVER AND
|
||||
// GAME BOTH REFERENCE !!!
|
||||
//rww - this won't do.. I need to include bg_public.h in the exe elsewhere.
|
||||
//I'm including it here instead so we can have our PERS_SCORE value. And have
|
||||
//it be the proper enum value.
|
||||
|
||||
#define MAX_ENT_CLUSTERS 16
|
||||
|
||||
typedef struct svEntity_s {
|
||||
struct worldSector_s *worldSector;
|
||||
struct svEntity_s *nextEntityInWorldSector;
|
||||
|
||||
entityState_t baseline; // for delta compression of initial sighting
|
||||
#ifdef _XBOX
|
||||
signed char numClusters; // if -1, use headnode instead
|
||||
short clusternums[MAX_ENT_CLUSTERS];
|
||||
short lastCluster; // if all the clusters don't fit in clusternums
|
||||
short areanum, areanum2;
|
||||
char snapshotCounter; // used to prevent double adding from portal views
|
||||
#else
|
||||
int numClusters; // if -1, use headnode instead
|
||||
int clusternums[MAX_ENT_CLUSTERS];
|
||||
int lastCluster; // if all the clusters don't fit in clusternums
|
||||
int areanum, areanum2;
|
||||
int snapshotCounter; // used to prevent double adding from portal views
|
||||
#endif
|
||||
} svEntity_t;
|
||||
|
||||
typedef enum {
|
||||
SS_DEAD, // no map loaded
|
||||
SS_LOADING, // spawning level entities
|
||||
SS_GAME // actively running
|
||||
} serverState_t;
|
||||
|
||||
typedef struct {
|
||||
serverState_t state;
|
||||
int serverId; // changes each server start
|
||||
#ifdef _XBOX
|
||||
char snapshotCounter; // incremented for each snapshot built
|
||||
#else
|
||||
int snapshotCounter; // incremented for each snapshot built
|
||||
#endif
|
||||
int time; // all entities are correct for this time // These 2 saved out
|
||||
int timeResidual; // <= 1000 / sv_frame->value // during savegame.
|
||||
float timeResidualFraction; // fraction of a msec accumulated
|
||||
int nextFrameTime; // when time > nextFrameTime, process world // this doesn't get used anywhere! -Ste
|
||||
struct cmodel_s *models[MAX_MODELS];
|
||||
char *configstrings[MAX_CONFIGSTRINGS];
|
||||
//
|
||||
// be careful, Jake's code uses the 'svEntities' field as a marker to memset-this-far-only inside SV_InitSV()!!!!!
|
||||
//
|
||||
char *entityParsePoint; // used during game VM init
|
||||
|
||||
int mLocalSubBSPIndex;
|
||||
int mLocalSubBSPModelOffset;
|
||||
char *mLocalSubBSPEntityParsePoint;
|
||||
|
||||
svEntity_t svEntities[MAX_GENTITIES];
|
||||
} server_t;
|
||||
|
||||
|
||||
|
||||
typedef struct {
|
||||
int areabytes;
|
||||
byte areabits[MAX_MAP_AREA_BYTES]; // portalarea visibility bits
|
||||
playerState_t ps;
|
||||
int num_entities;
|
||||
int first_entity; // into the circular sv_packet_entities[]
|
||||
// the entities MUST be in increasing state number
|
||||
// order, otherwise the delta compression will fail
|
||||
int messageSent; // time the message was transmitted
|
||||
int messageAcked; // time the message was acked
|
||||
int messageSize; // used to rate drop packets
|
||||
} clientSnapshot_t;
|
||||
|
||||
typedef enum {
|
||||
CS_FREE, // can be reused for a new connection
|
||||
CS_ZOMBIE, // client has been disconnected, but don't reuse
|
||||
// connection for a couple seconds
|
||||
CS_CONNECTED, // has been assigned to a client_t, but no gamestate yet
|
||||
CS_PRIMED, // gamestate has been sent, but client hasn't sent a usercmd
|
||||
CS_ACTIVE // client is fully in game
|
||||
} clientState_t;
|
||||
|
||||
|
||||
typedef struct client_s {
|
||||
clientState_t state;
|
||||
char userinfo[MAX_INFO_STRING]; // name, etc
|
||||
|
||||
char *reliableCommands[MAX_RELIABLE_COMMANDS];
|
||||
int reliableSequence;
|
||||
int reliableAcknowledge;
|
||||
|
||||
int gamestateMessageNum; // netchan->outgoingSequence of gamestate
|
||||
|
||||
usercmd_t lastUsercmd;
|
||||
int lastMessageNum; // for delta compression
|
||||
int cmdNum; // command number last executed
|
||||
int lastClientCommand; // reliable client message sequence
|
||||
gentity_t *gentity; // SV_GentityNum(clientnum)
|
||||
char name[MAX_NAME_LENGTH]; // extracted from userinfo, high bits masked
|
||||
byte *download; // file being downloaded
|
||||
int downloadsize; // total bytes (can't use EOF because of paks)
|
||||
int downloadcount; // bytes sent
|
||||
int deltaMessage; // frame last client usercmd message
|
||||
int lastPacketTime; // sv.time when packet was last received
|
||||
int lastConnectTime; // sv.time when connection started
|
||||
int nextSnapshotTime; // send another snapshot when sv.time >= nextSnapshotTime
|
||||
qboolean rateDelayed; // true if nextSnapshotTime was set based on rate instead of snapshotMsec
|
||||
qboolean droppedCommands; // true if enough pakets to pass the cl_packetdup were dropped
|
||||
int timeoutCount; // must timeout a few frames in a row so debugging doesn't break
|
||||
clientSnapshot_t frames[PACKET_BACKUP]; // updates can be delta'd from here
|
||||
int ping;
|
||||
int rate; // bytes / second
|
||||
int snapshotMsec; // requests a snapshot every snapshotMsec unless rate choked
|
||||
netchan_t netchan;
|
||||
} client_t;
|
||||
|
||||
//=============================================================================
|
||||
|
||||
|
||||
typedef struct {
|
||||
netadr_t adr;
|
||||
int challenge;
|
||||
int time;
|
||||
} challenge_t;
|
||||
|
||||
// this structure will be cleared only when the game dll changes
|
||||
typedef struct {
|
||||
qboolean initialized; // sv_init has completed
|
||||
client_t *clients; // [sv_maxclients->integer];
|
||||
int numSnapshotEntities; // sv_maxclients->integer*PACKET_BACKUP*MAX_PACKET_ENTITIES
|
||||
int nextSnapshotEntities; // next snapshotEntities to use
|
||||
entityState_t *snapshotEntities; // [numSnapshotEntities]
|
||||
int nextHeartbeatTime;
|
||||
} serverStatic_t;
|
||||
|
||||
//=============================================================================
|
||||
|
||||
extern serverStatic_t svs; // persistant server info across maps
|
||||
extern server_t sv; // cleared each map
|
||||
|
||||
extern game_export_t *ge;
|
||||
|
||||
extern cvar_t *sv_fps;
|
||||
extern cvar_t *sv_timeout;
|
||||
extern cvar_t *sv_zombietime;
|
||||
extern cvar_t *sv_reconnectlimit;
|
||||
extern cvar_t *sv_showloss;
|
||||
extern cvar_t *sv_killserver;
|
||||
extern cvar_t *sv_mapname;
|
||||
extern cvar_t *sv_spawntarget;
|
||||
extern cvar_t *sv_mapChecksum;
|
||||
extern cvar_t *sv_serverid;
|
||||
extern cvar_t *sv_testsave;
|
||||
extern cvar_t *sv_compress_saved_games;
|
||||
|
||||
//===========================================================
|
||||
|
||||
//
|
||||
// sv_main.c
|
||||
//
|
||||
void SV_FinalMessage (char *message);
|
||||
void QDECL SV_SendServerCommand( client_t *cl, const char *fmt, ...);
|
||||
|
||||
|
||||
void SV_AddOperatorCommands (void);
|
||||
void SV_RemoveOperatorCommands (void);
|
||||
|
||||
|
||||
//
|
||||
// sv_init.c
|
||||
//
|
||||
void SV_SetConfigstring( int index, const char *val );
|
||||
void SV_GetConfigstring( int index, char *buffer, int bufferSize );
|
||||
|
||||
void SV_SetUserinfo( int index, const char *val );
|
||||
void SV_GetUserinfo( int index, char *buffer, int bufferSize );
|
||||
|
||||
void SV_SpawnServer( char *server, ForceReload_e eForceReload, qboolean bAllowScreenDissolve );
|
||||
|
||||
|
||||
//
|
||||
// sv_client.c
|
||||
//
|
||||
void SV_DirectConnect( netadr_t from );
|
||||
|
||||
void SV_ExecuteClientMessage( client_t *cl, msg_t *msg );
|
||||
void SV_UserinfoChanged( client_t *cl );
|
||||
|
||||
void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd, SavedGameJustLoaded_e eSavedGameJustLoaded );
|
||||
void SV_DropClient( client_t *drop, const char *reason );
|
||||
|
||||
void SV_ExecuteClientCommand( client_t *cl, const char *s );
|
||||
void SV_ClientThink (client_t *cl, usercmd_t *cmd);
|
||||
|
||||
|
||||
//
|
||||
// sv_snapshot.c
|
||||
//
|
||||
void SV_AddServerCommand( client_t *client, const char *cmd );
|
||||
void SV_SendMessageToClient( msg_t *msg, client_t *client );
|
||||
void SV_SendClientMessages( void );
|
||||
void SV_SendClientSnapshot( client_t *client );
|
||||
|
||||
|
||||
|
||||
//
|
||||
// sv_game.c
|
||||
//
|
||||
gentity_t *SV_GentityNum( int num );
|
||||
svEntity_t *SV_SvEntityForGentity( gentity_t *gEnt );
|
||||
gentity_t *SV_GEntityForSvEntity( svEntity_t *svEnt );
|
||||
void SV_InitGameProgs (void);
|
||||
void SV_ShutdownGameProgs (qboolean shutdownCin);
|
||||
qboolean SV_inPVS (const vec3_t p1, const vec3_t p2);
|
||||
|
||||
|
||||
//============================================================
|
||||
//
|
||||
// high level object sorting to reduce interaction tests
|
||||
//
|
||||
|
||||
void SV_ClearWorld (void);
|
||||
// called after the world model has been loaded, before linking any entities
|
||||
|
||||
void SV_UnlinkEntity( gentity_t *ent );
|
||||
// call before removing an entity, and before trying to move one,
|
||||
// so it doesn't clip against itself
|
||||
|
||||
void SV_LinkEntity( gentity_t *ent );
|
||||
// Needs to be called any time an entity changes origin, mins, maxs,
|
||||
// or solid. Automatically unlinks if needed.
|
||||
// sets ent->v.absmin and ent->v.absmax
|
||||
// sets ent->leafnums[] for pvs determination even if the entity
|
||||
// is not solid
|
||||
|
||||
|
||||
clipHandle_t SV_ClipHandleForEntity( const gentity_t *ent );
|
||||
|
||||
|
||||
void SV_SectorList_f( void );
|
||||
|
||||
|
||||
int SV_AreaEntities( const vec3_t mins, const vec3_t maxs, gentity_t **elist, int maxcount );
|
||||
// fills in a table of entity pointers with entities that have bounding boxes
|
||||
// that intersect the given area. It is possible for a non-axial bmodel
|
||||
// to be returned that doesn't actually intersect the area on an exact
|
||||
// test.
|
||||
// returns the number of pointers filled in
|
||||
// The world entity is never returned in this list.
|
||||
|
||||
|
||||
int SV_PointContents( const vec3_t p, int passEntityNum );
|
||||
// returns the CONTENTS_* value from the world and all entities at the given point.
|
||||
|
||||
/*
|
||||
Ghoul2 Insert Start
|
||||
*/
|
||||
void SV_Trace( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end,
|
||||
const int passEntityNum, const int contentmask, const EG2_Collision eG2TraceType = G2_NOCOLLIDE, const int useLod = 0);
|
||||
/*
|
||||
Ghoul2 Insert End
|
||||
*/
|
||||
// mins and maxs are relative
|
||||
|
||||
// if the entire move stays in a solid volume, trace.allsolid will be set,
|
||||
// trace.startsolid will be set, and trace.fraction will be 0
|
||||
|
||||
// if the starting point is in a solid, it will be allowed to move out
|
||||
// to an open area
|
||||
|
||||
// passEntityNum is explicitly excluded from clipping checks (normally ENTITYNUM_NONE)
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////
|
||||
//
|
||||
// sv_savegame.cpp
|
||||
//
|
||||
void SV_LoadGame_f(void);
|
||||
void SV_LoadTransition_f(void);
|
||||
void SV_SaveGame_f(void);
|
||||
void SV_WipeGame_f(void);
|
||||
qboolean SV_TryLoadTransition( const char *mapname );
|
||||
qboolean SG_WriteSavegame(const char *psPathlessBaseName, qboolean qbAutosave);
|
||||
qboolean SG_ReadSavegame(const char *psPathlessBaseName);
|
||||
void SG_WipeSavegame(const char *psPathlessBaseName);
|
||||
qboolean SG_Append(unsigned long chid, const void *data, int length);
|
||||
int SG_Read (unsigned long chid, void *pvAddress, int iLength, void **ppvAddressPtr = NULL);
|
||||
int SG_ReadOptional (unsigned long chid, void *pvAddress, int iLength, void **ppvAddressPtr = NULL);
|
||||
void SG_Shutdown();
|
||||
void SG_TestSave(void);
|
||||
|
||||
//
|
||||
// note that this version number does not mean that a savegame with the same version can necessarily be loaded,
|
||||
// since anyone can change any loadsave-affecting structure somewhere in a header and change a chunk size.
|
||||
// What it's used for is for things like mission pack etc if we need to distinguish "street-copy" savegames from
|
||||
// any new enhanced ones that need to ask for new chunks during loading.
|
||||
//
|
||||
#define iSAVEGAME_VERSION 1
|
||||
int SG_Version(void); // call this to know what version number a successfully-opened savegame file was
|
||||
//
|
||||
extern SavedGameJustLoaded_e eSavedGameJustLoaded;
|
||||
extern qboolean qbLoadTransition;
|
||||
//
|
||||
///////////////////////////////////////////////
|
||||
#endif // #ifndef SERVER_H
|
||||
484
code/server/sv_ccmds.cpp
Normal file
484
code/server/sv_ccmds.cpp
Normal file
@@ -0,0 +1,484 @@
|
||||
// leave this as first line for PCH reasons...
|
||||
//
|
||||
#include "../server/exe_headers.h"
|
||||
|
||||
|
||||
|
||||
#include "server.h"
|
||||
#include "..\game\weapons.h"
|
||||
#include "..\game\g_items.h"
|
||||
#include "..\game\statindex.h"
|
||||
|
||||
|
||||
/*
|
||||
===============================================================================
|
||||
|
||||
OPERATOR CONSOLE ONLY COMMANDS
|
||||
|
||||
These commands can only be entered from stdin or by a remote operator datagram
|
||||
===============================================================================
|
||||
*/
|
||||
|
||||
qboolean qbLoadTransition = qfalse;
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_SetPlayer
|
||||
|
||||
Returns the player
|
||||
==================
|
||||
*/
|
||||
static client_t *SV_SetPlayer( void ) {
|
||||
client_t *cl;
|
||||
|
||||
cl = &svs.clients[0];
|
||||
if ( !cl->state ) {
|
||||
Com_Printf( "Client is not active\n" );
|
||||
return NULL;
|
||||
}
|
||||
return cl;
|
||||
}
|
||||
|
||||
|
||||
//=========================================================
|
||||
// don't call this directly, it should only be called from SV_Map_f() or SV_MapTransition_f()
|
||||
//
|
||||
static void SV_Map_( ForceReload_e eForceReload )
|
||||
{
|
||||
char *map;
|
||||
// char expanded[MAX_QPATH];
|
||||
|
||||
map = Cmd_Argv(1);
|
||||
if ( !*map ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure the level exists before trying to change, so that
|
||||
// a typo at the server console won't end the game
|
||||
if (strchr (map, '\\') ) {
|
||||
Com_Printf ("Can't have mapnames with a \\\n");
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef _XBOX // Could check for maps/%s/brushes.mle or something...
|
||||
Com_sprintf (expanded, sizeof(expanded), "maps/%s.bsp", map);
|
||||
if ( FS_ReadFile (expanded, NULL) == -1 ) {
|
||||
Com_Printf ("Can't find map %s\n", expanded);
|
||||
extern cvar_t *com_buildScript;
|
||||
if (com_buildScript && com_buildScript->integer)
|
||||
{//yes, it's happened, someone deleted a map during my build...
|
||||
Com_Error( ERR_FATAL, "Can't find map %s\n", expanded );
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (map[0]!='_')
|
||||
{
|
||||
SG_WipeSavegame("Checkpoint");
|
||||
}
|
||||
|
||||
SV_SpawnServer( map, eForceReload, qtrue ); // start up the map
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Save out some player data for later restore if this is a spawn point with KEEP_PREV (spawnflags&1) set...
|
||||
//
|
||||
// (now also called by auto-save code to setup the cvars correctly
|
||||
void SV_Player_EndOfLevelSave(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
// I could just call GetClientState() but that's in sv_bot.cpp, and I'm not sure if that's going to be deleted for
|
||||
// the single player build, so here's the guts again...
|
||||
//
|
||||
client_t* cl = &svs.clients[0]; // 0 because only ever us as a player
|
||||
|
||||
if (cl
|
||||
&&
|
||||
cl->gentity && cl->gentity->client // crash fix for voy4->brig transition when you kill Foster.
|
||||
// Shouldn't happen, but does sometimes...
|
||||
)
|
||||
{
|
||||
Cvar_Set( sCVARNAME_PLAYERSAVE, ""); // default to blank
|
||||
|
||||
// clientSnapshot_t* pFrame = &cl->frames[cl->netchan.outgoingSequence & PACKET_MASK];
|
||||
playerState_t* pState = cl->gentity->client;
|
||||
const char *s2;
|
||||
// |general info |-force powers |-saber 1 |-saber 2 |-general saber
|
||||
const char *s = va("%i %i %i %i %i %i %i %f %f %f %i %i %i %i %i %s %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %s %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i",
|
||||
pState->stats[STAT_HEALTH],
|
||||
pState->stats[STAT_ARMOR],
|
||||
pState->stats[STAT_WEAPONS],
|
||||
pState->stats[STAT_ITEMS],
|
||||
pState->weapon,
|
||||
pState->weaponstate,
|
||||
pState->batteryCharge,
|
||||
pState->viewangles[0],
|
||||
pState->viewangles[1],
|
||||
pState->viewangles[2],
|
||||
//force power data
|
||||
pState->forcePowersKnown,
|
||||
pState->forcePower,
|
||||
pState->forcePowerMax,
|
||||
pState->forcePowerRegenRate,
|
||||
pState->forcePowerRegenAmount,
|
||||
//saber 1 data
|
||||
pState->saber[0].name,
|
||||
pState->saber[0].blade[0].active,
|
||||
pState->saber[0].blade[1].active,
|
||||
pState->saber[0].blade[2].active,
|
||||
pState->saber[0].blade[3].active,
|
||||
pState->saber[0].blade[4].active,
|
||||
pState->saber[0].blade[5].active,
|
||||
pState->saber[0].blade[6].active,
|
||||
pState->saber[0].blade[7].active,
|
||||
pState->saber[0].blade[0].color,
|
||||
pState->saber[0].blade[1].color,
|
||||
pState->saber[0].blade[2].color,
|
||||
pState->saber[0].blade[3].color,
|
||||
pState->saber[0].blade[4].color,
|
||||
pState->saber[0].blade[5].color,
|
||||
pState->saber[0].blade[6].color,
|
||||
pState->saber[0].blade[7].color,
|
||||
//saber 2 data
|
||||
pState->saber[1].name,
|
||||
pState->saber[1].blade[0].active,
|
||||
pState->saber[1].blade[1].active,
|
||||
pState->saber[1].blade[2].active,
|
||||
pState->saber[1].blade[3].active,
|
||||
pState->saber[1].blade[4].active,
|
||||
pState->saber[1].blade[5].active,
|
||||
pState->saber[1].blade[6].active,
|
||||
pState->saber[1].blade[7].active,
|
||||
pState->saber[1].blade[0].color,
|
||||
pState->saber[1].blade[1].color,
|
||||
pState->saber[1].blade[2].color,
|
||||
pState->saber[1].blade[3].color,
|
||||
pState->saber[1].blade[4].color,
|
||||
pState->saber[1].blade[5].color,
|
||||
pState->saber[1].blade[6].color,
|
||||
pState->saber[1].blade[7].color,
|
||||
//general saber data
|
||||
pState->saberStylesKnown,
|
||||
pState->saberAnimLevel,
|
||||
pState->saberLockEnemy,
|
||||
pState->saberLockTime
|
||||
);
|
||||
Cvar_Set( sCVARNAME_PLAYERSAVE, s );
|
||||
|
||||
//ammo
|
||||
s2 = "";
|
||||
for (i=0;i< AMMO_MAX; i++)
|
||||
{
|
||||
s2 = va("%s %i",s2, pState->ammo[i]);
|
||||
}
|
||||
Cvar_Set( "playerammo", s2 );
|
||||
|
||||
//inventory
|
||||
s2 = "";
|
||||
for (i=0;i< INV_MAX; i++)
|
||||
{
|
||||
s2 = va("%s %i",s2, pState->inventory[i]);
|
||||
}
|
||||
Cvar_Set( "playerinv", s2 );
|
||||
|
||||
// the new JK2 stuff - force powers, etc...
|
||||
//
|
||||
s2 = "";
|
||||
for (i=0;i< NUM_FORCE_POWERS; i++)
|
||||
{
|
||||
s2 = va("%s %i",s2, pState->forcePowerLevel[i]);
|
||||
}
|
||||
Cvar_Set( "playerfplvl", s2 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Restart the server on a different map
|
||||
//
|
||||
//extern void SCR_PrecacheScreenshot(); //scr_scrn.cpp
|
||||
static void SV_MapTransition_f(void)
|
||||
{
|
||||
char *spawntarget;
|
||||
|
||||
// SCR_PrecacheScreenshot();
|
||||
SV_Player_EndOfLevelSave();
|
||||
|
||||
spawntarget = Cmd_Argv(2);
|
||||
if ( *spawntarget != NULL )
|
||||
{
|
||||
Cvar_Set( "spawntarget", spawntarget );
|
||||
}
|
||||
else
|
||||
{
|
||||
Cvar_Set( "spawntarget", "" );
|
||||
}
|
||||
|
||||
SV_Map_( eForceReload_NOTHING );
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_Map_f
|
||||
|
||||
Restart the server on a different map, but clears a cvar so that typing "map blah" doesn't try and preserve
|
||||
player weapons/ammo/etc from the previous level that you haven't really exited (ie ignores KEEP_PREV on spawn points)
|
||||
==================
|
||||
*/
|
||||
//void SCR_UnprecacheScreenshot(); //scr_scrn.cpp
|
||||
static void SV_Map_f( void )
|
||||
{
|
||||
Cvar_Set( sCVARNAME_PLAYERSAVE, "");
|
||||
Cvar_Set( "spawntarget", "" );
|
||||
Cvar_Set("tier_storyinfo", "0");
|
||||
Cvar_Set("tiers_complete", "");
|
||||
// SCR_UnprecacheScreenshot();
|
||||
|
||||
ForceReload_e eForceReload = eForceReload_NOTHING; // default for normal load
|
||||
|
||||
if ( !Q_stricmp( Cmd_Argv(0), "devmapbsp") ) {
|
||||
eForceReload = eForceReload_BSP;
|
||||
}
|
||||
else
|
||||
if ( !Q_stricmp( Cmd_Argv(0), "devmapmdl") ) {
|
||||
eForceReload = eForceReload_MODELS;
|
||||
}
|
||||
else
|
||||
if ( !Q_stricmp( Cmd_Argv(0), "devmapall") ) {
|
||||
eForceReload = eForceReload_ALL;
|
||||
}
|
||||
|
||||
SV_Map_( eForceReload );
|
||||
|
||||
// set the cheat value
|
||||
// if the level was started with "map <levelname>", then
|
||||
// cheats will not be allowed. If started with "devmap <levelname>"
|
||||
// then cheats will be allowed
|
||||
if ( !Q_stricmpn( Cmd_Argv(0), "devmap", 6 ) ) {
|
||||
Cvar_Set( "helpUsObi", "1" );
|
||||
} else {
|
||||
#ifdef _XBOX
|
||||
Cvar_Set( "helpUsObi", "1" );
|
||||
#else
|
||||
Cvar_Set( "helpUsObi", "0" );
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_LoadTransition_f
|
||||
==================
|
||||
*/
|
||||
void SV_LoadTransition_f(void)
|
||||
{
|
||||
char *map;
|
||||
char *spawntarget;
|
||||
|
||||
map = Cmd_Argv(1);
|
||||
if ( !*map ) {
|
||||
return;
|
||||
}
|
||||
|
||||
qbLoadTransition = qtrue;
|
||||
|
||||
// SCR_PrecacheScreenshot();
|
||||
SV_Player_EndOfLevelSave();
|
||||
|
||||
//Save the full current state of the current map so we can return to it later
|
||||
SG_WriteSavegame( va("hub/%s", sv_mapname->string), qfalse );
|
||||
|
||||
//set the spawntarget if there is one
|
||||
spawntarget = Cmd_Argv(2);
|
||||
if ( *spawntarget != NULL )
|
||||
{
|
||||
Cvar_Set( "spawntarget", spawntarget );
|
||||
}
|
||||
else
|
||||
{
|
||||
Cvar_Set( "spawntarget", "" );
|
||||
}
|
||||
|
||||
if ( !SV_TryLoadTransition( map ) )
|
||||
{//couldn't load a savegame
|
||||
SV_Map_( eForceReload_NOTHING );
|
||||
}
|
||||
qbLoadTransition = qfalse;
|
||||
}
|
||||
//===============================================================
|
||||
|
||||
/*
|
||||
================
|
||||
SV_Status_f
|
||||
================
|
||||
*/
|
||||
static void SV_Status_f( void ) {
|
||||
int i, j, l;
|
||||
client_t *cl;
|
||||
const char *s;
|
||||
int ping;
|
||||
|
||||
// make sure server is running
|
||||
if ( !com_sv_running->integer ) {
|
||||
Com_Printf( "Server is not running.\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
Com_Printf ("map: %s\n", sv_mapname->string );
|
||||
|
||||
Com_Printf ("num score ping name lastmsg address qport rate\n");
|
||||
Com_Printf ("--- ----- ---- --------------- ------- --------------------- ----- -----\n");
|
||||
for (i=0,cl=svs.clients ; i < 1 ; i++,cl++)
|
||||
{
|
||||
if (!cl->state)
|
||||
continue;
|
||||
Com_Printf ("%3i ", i);
|
||||
Com_Printf ("%5i ", cl->gentity->client->persistant[PERS_SCORE]);
|
||||
|
||||
if (cl->state == CS_CONNECTED)
|
||||
Com_Printf ("CNCT ");
|
||||
else if (cl->state == CS_ZOMBIE)
|
||||
Com_Printf ("ZMBI ");
|
||||
else
|
||||
{
|
||||
ping = cl->ping < 9999 ? cl->ping : 9999;
|
||||
Com_Printf ("%4i ", ping);
|
||||
}
|
||||
|
||||
Com_Printf ("%s", cl->name);
|
||||
l = 16 - strlen(cl->name);
|
||||
for (j=0 ; j<l ; j++)
|
||||
Com_Printf (" ");
|
||||
|
||||
Com_Printf ("%7i ", sv.time - cl->lastPacketTime );
|
||||
|
||||
s = NET_AdrToString( cl->netchan.remoteAddress );
|
||||
Com_Printf ("%s", s);
|
||||
l = 22 - strlen(s);
|
||||
for (j=0 ; j<l ; j++)
|
||||
Com_Printf (" ");
|
||||
|
||||
Com_Printf ("%5i", cl->netchan.qport);
|
||||
|
||||
Com_Printf (" %5i", cl->rate);
|
||||
|
||||
Com_Printf ("\n");
|
||||
}
|
||||
Com_Printf ("\n");
|
||||
}
|
||||
|
||||
/*
|
||||
===========
|
||||
SV_Serverinfo_f
|
||||
|
||||
Examine the serverinfo string
|
||||
===========
|
||||
*/
|
||||
static void SV_Serverinfo_f( void ) {
|
||||
Com_Printf ("Server info settings:\n");
|
||||
Info_Print ( Cvar_InfoString( CVAR_SERVERINFO ) );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===========
|
||||
SV_Systeminfo_f
|
||||
|
||||
Examine or change the serverinfo string
|
||||
===========
|
||||
*/
|
||||
static void SV_Systeminfo_f( void ) {
|
||||
Com_Printf ("System info settings:\n");
|
||||
Info_Print ( Cvar_InfoString( CVAR_SYSTEMINFO ) );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===========
|
||||
SV_DumpUser_f
|
||||
|
||||
Examine all a users info strings FIXME: move to game
|
||||
===========
|
||||
*/
|
||||
static void SV_DumpUser_f( void ) {
|
||||
client_t *cl;
|
||||
|
||||
// make sure server is running
|
||||
if ( !com_sv_running->integer ) {
|
||||
Com_Printf( "Server is not running.\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( Cmd_Argc() != 2 ) {
|
||||
Com_Printf ("Usage: info <userid>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
cl = SV_SetPlayer();
|
||||
if ( !cl ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Com_Printf( "userinfo\n" );
|
||||
Com_Printf( "--------\n" );
|
||||
Info_Print( cl->userinfo );
|
||||
}
|
||||
|
||||
//===========================================================
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_AddOperatorCommands
|
||||
==================
|
||||
*/
|
||||
void SV_AddOperatorCommands( void ) {
|
||||
static qboolean initialized;
|
||||
|
||||
if ( initialized ) {
|
||||
return;
|
||||
}
|
||||
initialized = qtrue;
|
||||
|
||||
Cmd_AddCommand ("status", SV_Status_f);
|
||||
Cmd_AddCommand ("serverinfo", SV_Serverinfo_f);
|
||||
Cmd_AddCommand ("systeminfo", SV_Systeminfo_f);
|
||||
Cmd_AddCommand ("dumpuser", SV_DumpUser_f);
|
||||
Cmd_AddCommand ("sectorlist", SV_SectorList_f);
|
||||
Cmd_AddCommand ("map", SV_Map_f);
|
||||
Cmd_AddCommand ("devmap", SV_Map_f);
|
||||
Cmd_AddCommand ("devmapbsp", SV_Map_f);
|
||||
Cmd_AddCommand ("devmapmdl", SV_Map_f);
|
||||
Cmd_AddCommand ("devmapsnd", SV_Map_f);
|
||||
Cmd_AddCommand ("devmapall", SV_Map_f);
|
||||
Cmd_AddCommand ("maptransition", SV_MapTransition_f);
|
||||
Cmd_AddCommand ("load", SV_LoadGame_f);
|
||||
Cmd_AddCommand ("loadtransition", SV_LoadTransition_f);
|
||||
Cmd_AddCommand ("save", SV_SaveGame_f);
|
||||
Cmd_AddCommand ("wipe", SV_WipeGame_f);
|
||||
|
||||
//#ifdef _DEBUG
|
||||
// extern void UI_Dump_f(void);
|
||||
// Cmd_AddCommand ("ui_dump", UI_Dump_f);
|
||||
//#endif
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_RemoveOperatorCommands
|
||||
==================
|
||||
*/
|
||||
void SV_RemoveOperatorCommands( void ) {
|
||||
#if 0
|
||||
// removing these won't let the server start again
|
||||
Cmd_RemoveCommand ("status");
|
||||
Cmd_RemoveCommand ("serverinfo");
|
||||
Cmd_RemoveCommand ("systeminfo");
|
||||
Cmd_RemoveCommand ("dumpuser");
|
||||
Cmd_RemoveCommand ("serverrecord");
|
||||
Cmd_RemoveCommand ("serverstop");
|
||||
Cmd_RemoveCommand ("sectorlist");
|
||||
#endif
|
||||
}
|
||||
|
||||
605
code/server/sv_client.cpp
Normal file
605
code/server/sv_client.cpp
Normal file
@@ -0,0 +1,605 @@
|
||||
// sv_client.c -- server code for dealing with clients
|
||||
|
||||
// leave this as first line for PCH reasons...
|
||||
//
|
||||
#include "../server/exe_headers.h"
|
||||
|
||||
|
||||
|
||||
|
||||
#include "server.h"
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_DirectConnect
|
||||
|
||||
A "connect" OOB command has been received
|
||||
==================
|
||||
*/
|
||||
void SV_DirectConnect( netadr_t from ) {
|
||||
char userinfo[MAX_INFO_STRING];
|
||||
int i;
|
||||
client_t *cl, *newcl;
|
||||
MAC_STATIC client_t temp;
|
||||
gentity_t *ent;
|
||||
int clientNum;
|
||||
int version;
|
||||
int qport;
|
||||
int challenge;
|
||||
char *denied;
|
||||
|
||||
Com_DPrintf ("SVC_DirectConnect ()\n");
|
||||
|
||||
Q_strncpyz( userinfo, Cmd_Argv(1), sizeof(userinfo) );
|
||||
|
||||
version = atoi( Info_ValueForKey( userinfo, "protocol" ) );
|
||||
if ( version != PROTOCOL_VERSION ) {
|
||||
NET_OutOfBandPrint( NS_SERVER, from, "print\nServer uses protocol version %i.\n", PROTOCOL_VERSION );
|
||||
Com_DPrintf (" rejected connect from version %i\n", version);
|
||||
return;
|
||||
}
|
||||
|
||||
qport = atoi( Info_ValueForKey( userinfo, "qport" ) );
|
||||
|
||||
challenge = atoi( Info_ValueForKey( userinfo, "challenge" ) );
|
||||
|
||||
// see if the challenge is valid (local clients don't need to challenge)
|
||||
if ( !NET_IsLocalAddress (from) ) {
|
||||
NET_OutOfBandPrint( NS_SERVER, from, "print\nNo challenge for address.\n" );
|
||||
return;
|
||||
} else {
|
||||
// force the "ip" info key to "localhost"
|
||||
Info_SetValueForKey( userinfo, "ip", "localhost" );
|
||||
}
|
||||
|
||||
newcl = &temp;
|
||||
memset (newcl, 0, sizeof(client_t));
|
||||
|
||||
// if there is already a slot for this ip, reuse it
|
||||
for (i=0,cl=svs.clients ; i < 1 ; i++,cl++)
|
||||
{
|
||||
if ( cl->state == CS_FREE ) {
|
||||
continue;
|
||||
}
|
||||
if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress )
|
||||
&& ( cl->netchan.qport == qport
|
||||
|| from.port == cl->netchan.remoteAddress.port ) )
|
||||
{
|
||||
if (( sv.time - cl->lastConnectTime)
|
||||
< (sv_reconnectlimit->integer * 1000))
|
||||
{
|
||||
Com_DPrintf ("%s:reconnect rejected : too soon\n", NET_AdrToString (from));
|
||||
return;
|
||||
}
|
||||
Com_Printf ("%s:reconnect\n", NET_AdrToString (from));
|
||||
newcl = cl;
|
||||
goto gotnewcl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
newcl = NULL;
|
||||
for ( i = 0; i < 1 ; i++ ) {
|
||||
cl = &svs.clients[i];
|
||||
if (cl->state == CS_FREE) {
|
||||
newcl = cl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !newcl ) {
|
||||
NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is full.\n" );
|
||||
Com_DPrintf ("Rejected a connection.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
gotnewcl:
|
||||
// build a new connection
|
||||
// accept the new client
|
||||
// this is the only place a client_t is ever initialized
|
||||
*newcl = temp;
|
||||
clientNum = newcl - svs.clients;
|
||||
ent = SV_GentityNum( clientNum );
|
||||
newcl->gentity = ent;
|
||||
|
||||
// save the address
|
||||
Netchan_Setup (NS_SERVER, &newcl->netchan , from, qport);
|
||||
|
||||
// save the userinfo
|
||||
Q_strncpyz( newcl->userinfo, userinfo, sizeof(newcl->userinfo) );
|
||||
|
||||
// get the game a chance to reject this connection or modify the userinfo
|
||||
denied = ge->ClientConnect( clientNum, qtrue, eSavedGameJustLoaded ); // firstTime = qtrue
|
||||
if ( denied ) {
|
||||
NET_OutOfBandPrint( NS_SERVER, from, "print\n%s\n", denied );
|
||||
Com_DPrintf ("Game rejected a connection: %s.\n", denied);
|
||||
return;
|
||||
}
|
||||
|
||||
SV_UserinfoChanged( newcl );
|
||||
|
||||
// send the connect packet to the client
|
||||
NET_OutOfBandPrint( NS_SERVER, from, "connectResponse" );
|
||||
|
||||
newcl->state = CS_CONNECTED;
|
||||
newcl->nextSnapshotTime = sv.time;
|
||||
newcl->lastPacketTime = sv.time;
|
||||
newcl->lastConnectTime = sv.time;
|
||||
|
||||
// when we receive the first packet from the client, we will
|
||||
// notice that it is from a different serverid and that the
|
||||
// gamestate message was not just sent, forcing a retransmit
|
||||
newcl->gamestateMessageNum = -1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=====================
|
||||
SV_DropClient
|
||||
|
||||
Called when the player is totally leaving the server, either willingly
|
||||
or unwillingly. This is NOT called if the entire server is quiting
|
||||
or crashing -- SV_FinalMessage() will handle that
|
||||
=====================
|
||||
*/
|
||||
void SV_DropClient( client_t *drop, const char *reason ) {
|
||||
if ( drop->state == CS_ZOMBIE ) {
|
||||
return; // already dropped
|
||||
}
|
||||
drop->state = CS_ZOMBIE; // become free in a few seconds
|
||||
|
||||
if (drop->download) {
|
||||
FS_FreeFile (drop->download);
|
||||
drop->download = NULL;
|
||||
}
|
||||
|
||||
// call the prog function for removing a client
|
||||
// this will remove the body, among other things
|
||||
ge->ClientDisconnect( drop - svs.clients );
|
||||
|
||||
// tell everyone why they got dropped
|
||||
SV_SendServerCommand( NULL, "print \"%s %s\n\"", drop->name, reason );
|
||||
|
||||
// add the disconnect command
|
||||
SV_SendServerCommand( drop, "disconnect" );
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
SV_SendClientGameState
|
||||
|
||||
Sends the first message from the server to a connected client.
|
||||
This will be sent on the initial connection and upon each new map load.
|
||||
|
||||
It will be resent if the client acknowledges a later message but has
|
||||
the wrong gamestate.
|
||||
================
|
||||
*/
|
||||
void SV_SendClientGameState( client_t *client ) {
|
||||
int start;
|
||||
msg_t msg;
|
||||
byte msgBuffer[MAX_MSGLEN];
|
||||
|
||||
Com_DPrintf ("SV_SendGameState() for %s\n", client->name);
|
||||
client->state = CS_PRIMED;
|
||||
|
||||
// when we receive the first packet from the client, we will
|
||||
// notice that it is from a different serverid and that the
|
||||
// gamestate message was not just sent, forcing a retransmit
|
||||
client->gamestateMessageNum = client->netchan.outgoingSequence;
|
||||
|
||||
// clear the reliable message list for this client
|
||||
client->reliableSequence = 0;
|
||||
client->reliableAcknowledge = 0;
|
||||
|
||||
MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) );
|
||||
|
||||
// send the gamestate
|
||||
MSG_WriteByte( &msg, svc_gamestate );
|
||||
MSG_WriteLong( &msg, client->reliableSequence );
|
||||
|
||||
// write the configstrings
|
||||
for ( start = 0 ; start < MAX_CONFIGSTRINGS ; start++ ) {
|
||||
if (sv.configstrings[start][0]) {
|
||||
MSG_WriteByte( &msg, svc_configstring );
|
||||
MSG_WriteShort( &msg, start );
|
||||
MSG_WriteString( &msg, sv.configstrings[start] );
|
||||
}
|
||||
}
|
||||
|
||||
MSG_WriteByte( &msg, 0 );
|
||||
|
||||
// check for overflow
|
||||
if ( msg.overflowed ) {
|
||||
Com_Printf ("WARNING: GameState overflowed for %s\n", client->name);
|
||||
}
|
||||
|
||||
// deliver this to the client
|
||||
SV_SendMessageToClient( &msg, client );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_ClientEnterWorld
|
||||
==================
|
||||
*/
|
||||
void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd, SavedGameJustLoaded_e eSavedGameJustLoaded ) {
|
||||
int clientNum;
|
||||
gentity_t *ent;
|
||||
|
||||
Com_DPrintf ("SV_ClientEnterWorld() from %s\n", client->name);
|
||||
client->state = CS_ACTIVE;
|
||||
|
||||
// set up the entity for the client
|
||||
clientNum = client - svs.clients;
|
||||
ent = SV_GentityNum( clientNum );
|
||||
ent->s.number = clientNum;
|
||||
client->gentity = ent;
|
||||
|
||||
// normally I check 'qbFromSavedGame' to avoid overwriting loaded client data, but this stuff I want
|
||||
// to be reset so that client packet delta-ing bgins afresh, rather than based on your previous frame
|
||||
// (which didn't in fact happen now if we've just loaded from a saved game...)
|
||||
//
|
||||
client->deltaMessage = -1;
|
||||
client->cmdNum = 0;
|
||||
client->nextSnapshotTime = sv.time; // generate a snapshot immediately
|
||||
|
||||
// call the game begin function
|
||||
ge->ClientBegin( client - svs.clients, cmd, eSavedGameJustLoaded );
|
||||
}
|
||||
|
||||
/*
|
||||
============================================================
|
||||
|
||||
CLIENT COMMAND EXECUTION
|
||||
|
||||
============================================================
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
SV_Disconnect_f
|
||||
|
||||
The client is going to disconnect, so remove the connection immediately FIXME: move to game?
|
||||
=================
|
||||
*/
|
||||
static void SV_Disconnect_f( client_t *cl ) {
|
||||
SV_DropClient( cl, "disconnected" );
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
SV_UserinfoChanged
|
||||
|
||||
Pull specific info from a newly changed userinfo string
|
||||
into a more C friendly form.
|
||||
=================
|
||||
*/
|
||||
void SV_UserinfoChanged( client_t *cl ) {
|
||||
char *val;
|
||||
int i;
|
||||
|
||||
// name for C code
|
||||
Q_strncpyz( cl->name, Info_ValueForKey (cl->userinfo, "name"), sizeof(cl->name) );
|
||||
|
||||
// rate command
|
||||
|
||||
// if the client is on the same subnet as the server and we aren't running an
|
||||
// internet public server, assume they don't need a rate choke
|
||||
cl->rate = 99999; // lans should not rate limit
|
||||
|
||||
// snaps command
|
||||
val = Info_ValueForKey (cl->userinfo, "snaps");
|
||||
if (strlen(val)) {
|
||||
i = atoi(val);
|
||||
if ( i < 1 ) {
|
||||
i = 1;
|
||||
} else if ( i > 30 ) {
|
||||
i = 30;
|
||||
}
|
||||
cl->snapshotMsec = 1000/i;
|
||||
} else {
|
||||
cl->snapshotMsec = 50;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_UpdateUserinfo_f
|
||||
==================
|
||||
*/
|
||||
static void SV_UpdateUserinfo_f( client_t *cl ) {
|
||||
Q_strncpyz( cl->userinfo, Cmd_Argv(1), sizeof(cl->userinfo) );
|
||||
|
||||
// call prog code to allow overrides
|
||||
ge->ClientUserinfoChanged( cl - svs.clients );
|
||||
|
||||
SV_UserinfoChanged( cl );
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
void (*func)( client_t *cl );
|
||||
} ucmd_t;
|
||||
|
||||
static ucmd_t ucmds[] = {
|
||||
{"userinfo", SV_UpdateUserinfo_f},
|
||||
{"disconnect", SV_Disconnect_f},
|
||||
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_ExecuteClientCommand
|
||||
==================
|
||||
*/
|
||||
void SV_ExecuteClientCommand( client_t *cl, const char *s ) {
|
||||
ucmd_t *u;
|
||||
|
||||
Cmd_TokenizeString( s );
|
||||
|
||||
// see if it is a server level command
|
||||
for (u=ucmds ; u->name ; u++) {
|
||||
if (!strcmp (Cmd_Argv(0), u->name) ) {
|
||||
u->func( cl );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// pass unknown strings to the game
|
||||
if (!u->name && sv.state == SS_GAME) {
|
||||
ge->ClientCommand( cl - svs.clients );
|
||||
}
|
||||
}
|
||||
|
||||
#define MAX_STRINGCMDS 8
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_ClientCommand
|
||||
===============
|
||||
*/
|
||||
static void SV_ClientCommand( client_t *cl, msg_t *msg ) {
|
||||
int seq;
|
||||
const char *s;
|
||||
|
||||
seq = MSG_ReadLong( msg );
|
||||
s = MSG_ReadString( msg );
|
||||
|
||||
// see if we have already executed it
|
||||
if ( cl->lastClientCommand >= seq ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Com_DPrintf( "clientCommand: %s : %i : %s\n", cl->name, seq, s );
|
||||
|
||||
// drop the connection if we have somehow lost commands
|
||||
if ( seq > cl->lastClientCommand + 1 ) {
|
||||
Com_Printf( "Client %s lost %i clientCommands\n", cl->name,
|
||||
seq - cl->lastClientCommand + 1 );
|
||||
}
|
||||
|
||||
SV_ExecuteClientCommand( cl, s );
|
||||
|
||||
cl->lastClientCommand = seq;
|
||||
}
|
||||
|
||||
|
||||
//==================================================================================
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_ClientThink
|
||||
==================
|
||||
*/
|
||||
void SV_ClientThink (client_t *cl, usercmd_t *cmd) {
|
||||
cl->lastUsercmd = *cmd;
|
||||
|
||||
if ( cl->state != CS_ACTIVE ) {
|
||||
return; // may have been kicked during the last usercmd
|
||||
}
|
||||
|
||||
ge->ClientThink( cl - svs.clients, cmd );
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_UserMove
|
||||
|
||||
The message usually contains all the movement commands
|
||||
that were in the last three packets, so that the information
|
||||
in dropped packets can be recovered.
|
||||
|
||||
On very fast clients, there may be multiple usercmd packed into
|
||||
each of the backup packets.
|
||||
==================
|
||||
*/
|
||||
static void SV_UserMove( client_t *cl, msg_t *msg ) {
|
||||
int i, start;
|
||||
int cmdNum;
|
||||
int firstNum;
|
||||
int cmdCount;
|
||||
usercmd_t nullcmd;
|
||||
usercmd_t cmds[MAX_PACKET_USERCMDS];
|
||||
usercmd_t *cmd, *oldcmd;
|
||||
int clientTime;
|
||||
int serverId;
|
||||
|
||||
cl->reliableAcknowledge = MSG_ReadLong( msg );
|
||||
serverId = MSG_ReadLong( msg );
|
||||
clientTime = MSG_ReadLong( msg );
|
||||
cl->deltaMessage = MSG_ReadLong( msg );
|
||||
|
||||
// cmdNum is the command number of the most recent included usercmd
|
||||
cmdNum = MSG_ReadLong( msg );
|
||||
cmdCount = MSG_ReadByte( msg );
|
||||
|
||||
if ( cmdCount < 1 ) {
|
||||
Com_Printf( "cmdCount < 1\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( cmdCount > MAX_PACKET_USERCMDS ) {
|
||||
Com_Printf( "cmdCount > MAX_PACKET_USERCMDS\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
memset( &nullcmd, 0, sizeof(nullcmd) );
|
||||
oldcmd = &nullcmd;
|
||||
for ( i = 0 ; i < cmdCount ; i++ ) {
|
||||
cmd = &cmds[i];
|
||||
MSG_ReadDeltaUsercmd( msg, oldcmd, cmd );
|
||||
oldcmd = cmd;
|
||||
}
|
||||
|
||||
// if this is a usercmd from a previous gamestate,
|
||||
// ignore it or retransmit the current gamestate
|
||||
if ( serverId != sv.serverId ) {
|
||||
// if we can tell that the client has dropped the last
|
||||
// gamestate we sent them, resend it
|
||||
if ( cl->netchan.incomingAcknowledged > cl->gamestateMessageNum ) {
|
||||
Com_DPrintf( "%s : dropped gamestate, resending\n", cl->name );
|
||||
SV_SendClientGameState( cl );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// if this is the first usercmd we have received
|
||||
// this gamestate, put the client into the world
|
||||
if ( cl->state == CS_PRIMED ) {
|
||||
|
||||
SV_ClientEnterWorld( cl, &cmds[0], eSavedGameJustLoaded );
|
||||
#ifndef _XBOX // No auto-saving for now?
|
||||
if ( sv_mapname->string[0]!='_' )
|
||||
{
|
||||
char savename[MAX_QPATH];
|
||||
if ( eSavedGameJustLoaded == eNO )
|
||||
{
|
||||
SG_WriteSavegame("auto",qtrue);
|
||||
if ( strnicmp(sv_mapname->string, "academy", 7) != 0)
|
||||
{
|
||||
Com_sprintf (savename, sizeof(savename), "auto_%s",sv_mapname->string);
|
||||
SG_WriteSavegame(savename,qtrue);//can't use va becuase it's nested
|
||||
}
|
||||
}
|
||||
else if ( qbLoadTransition == qtrue )
|
||||
{
|
||||
Com_sprintf (savename, sizeof(savename), "hub/%s", sv_mapname->string );
|
||||
SG_WriteSavegame( savename, qfalse );//save a full one
|
||||
SG_WriteSavegame( "auto", qfalse );//need a copy for auto, too
|
||||
}
|
||||
}
|
||||
#endif
|
||||
eSavedGameJustLoaded = eNO;
|
||||
// the moves can be processed normaly
|
||||
}
|
||||
|
||||
if ( cl->state != CS_ACTIVE ) {
|
||||
cl->deltaMessage = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// if there is a time gap from the last packet to this packet,
|
||||
// fill in with the first command in the packet
|
||||
|
||||
// with a packetdup of 0, firstNum == cmdNum
|
||||
firstNum = cmdNum - ( cmdCount - 1 );
|
||||
if ( cl->cmdNum < firstNum - 1 ) {
|
||||
cl->droppedCommands = qtrue;
|
||||
if ( sv_showloss->integer ) {
|
||||
Com_Printf("Lost %i usercmds from %s\n", firstNum - 1 - cl->cmdNum,
|
||||
cl->name);
|
||||
}
|
||||
if ( cl->cmdNum < firstNum - 6 ) {
|
||||
cl->cmdNum = firstNum - 6; // don't generate too many
|
||||
}
|
||||
while ( cl->cmdNum < firstNum - 1 ) {
|
||||
cl->cmdNum++;
|
||||
SV_ClientThink( cl, &cmds[0] );
|
||||
}
|
||||
}
|
||||
// skip over any usercmd_t we have already executed
|
||||
start = cl->cmdNum - ( firstNum - 1 );
|
||||
for ( i = start ; i < cmdCount ; i++ ) {
|
||||
SV_ClientThink (cl, &cmds[ i ]);
|
||||
}
|
||||
cl->cmdNum = cmdNum;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===========================================================================
|
||||
|
||||
USER CMD EXECUTION
|
||||
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
===================
|
||||
SV_ExecuteClientMessage
|
||||
|
||||
Parse a client packet
|
||||
===================
|
||||
*/
|
||||
void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) {
|
||||
int c;
|
||||
|
||||
while( 1 ) {
|
||||
if ( msg->readcount > msg->cursize ) {
|
||||
SV_DropClient (cl, "had a badread");
|
||||
return;
|
||||
}
|
||||
|
||||
c = MSG_ReadByte( msg );
|
||||
if ( c == -1 ) {
|
||||
break;
|
||||
}
|
||||
|
||||
switch( c ) {
|
||||
default:
|
||||
SV_DropClient( cl,"had an unknown command char" );
|
||||
return;
|
||||
|
||||
case clc_nop:
|
||||
break;
|
||||
|
||||
case clc_move:
|
||||
SV_UserMove( cl, msg );
|
||||
break;
|
||||
|
||||
case clc_clientCommand:
|
||||
SV_ClientCommand( cl, msg );
|
||||
if (cl->state == CS_ZOMBIE) {
|
||||
return; // disconnect command
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SV_FreeClient(client_t *client)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!client) return;
|
||||
|
||||
for(i=0; i<MAX_RELIABLE_COMMANDS; i++) {
|
||||
if ( client->reliableCommands[ i] ) {
|
||||
Z_Free( client->reliableCommands[ i] );
|
||||
client->reliableCommands[i] = NULL;
|
||||
client->reliableSequence = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
724
code/server/sv_game.cpp
Normal file
724
code/server/sv_game.cpp
Normal file
@@ -0,0 +1,724 @@
|
||||
// sv_game.c -- interface to the game dll
|
||||
|
||||
// leave this as first line for PCH reasons...
|
||||
//
|
||||
#include "../server/exe_headers.h"
|
||||
|
||||
#include "../RMG/RM_Headers.h"
|
||||
|
||||
#include "../qcommon/cm_local.h"
|
||||
|
||||
#include "server.h"
|
||||
#include "..\client\vmachine.h"
|
||||
#include "..\client\client.h"
|
||||
#include "..\renderer\tr_local.h"
|
||||
#include "..\renderer\tr_WorldEffects.h"
|
||||
/*
|
||||
Ghoul2 Insert Start
|
||||
*/
|
||||
#if !defined(G2_H_INC)
|
||||
#include "..\ghoul2\G2.h"
|
||||
#endif
|
||||
|
||||
/*
|
||||
Ghoul2 Insert End
|
||||
*/
|
||||
|
||||
//prototypes
|
||||
extern void Sys_UnloadGame( void );
|
||||
extern void *Sys_GetGameAPI( void *parms);
|
||||
extern void Com_WriteCam ( const char *text );
|
||||
extern void Com_FlushCamFile();
|
||||
|
||||
#ifdef _XBOX
|
||||
extern int *s_entityWavVol;
|
||||
#else
|
||||
extern int s_entityWavVol[MAX_GENTITIES];
|
||||
#endif
|
||||
|
||||
|
||||
// these functions must be used instead of pointer arithmetic, because
|
||||
// the game allocates gentities with private information after the server shared part
|
||||
/*
|
||||
int SV_NumForGentity( gentity_t *ent ) {
|
||||
int num;
|
||||
|
||||
num = ( (byte *)ent - (byte *)ge->gentities ) / ge->gentitySize;
|
||||
|
||||
return num;
|
||||
}
|
||||
*/
|
||||
gentity_t *SV_GentityNum( int num ) {
|
||||
gentity_t *ent;
|
||||
|
||||
assert (num >=0);
|
||||
ent = (gentity_t *)((byte *)ge->gentities + ge->gentitySize*(num));
|
||||
|
||||
return ent;
|
||||
}
|
||||
|
||||
svEntity_t *SV_SvEntityForGentity( gentity_t *gEnt ) {
|
||||
if ( !gEnt || gEnt->s.number < 0 || gEnt->s.number >= MAX_GENTITIES ) {
|
||||
Com_Error( ERR_DROP, "SV_SvEntityForGentity: bad gEnt" );
|
||||
}
|
||||
return &sv.svEntities[ gEnt->s.number ];
|
||||
}
|
||||
|
||||
gentity_t *SV_GEntityForSvEntity( svEntity_t *svEnt ) {
|
||||
int num;
|
||||
|
||||
num = svEnt - sv.svEntities;
|
||||
return SV_GentityNum( num );
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_GameSendServerCommand
|
||||
|
||||
Sends a command string to a client
|
||||
===============
|
||||
*/
|
||||
void SV_GameSendServerCommand( int clientNum, const char *fmt, ... ) {
|
||||
char msg[8192];
|
||||
va_list argptr;
|
||||
|
||||
va_start (argptr,fmt);
|
||||
vsprintf (msg, fmt, argptr);
|
||||
va_end (argptr);
|
||||
|
||||
if ( clientNum == -1 ) {
|
||||
SV_SendServerCommand( NULL, "%s", msg );
|
||||
} else {
|
||||
if ( clientNum < 0 || clientNum >= 1 ) {
|
||||
return;
|
||||
}
|
||||
SV_SendServerCommand( svs.clients + clientNum, "%s", msg );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_GameDropClient
|
||||
|
||||
Disconnects the client with a message
|
||||
===============
|
||||
*/
|
||||
void SV_GameDropClient( int clientNum, const char *reason ) {
|
||||
if ( clientNum < 0 || clientNum >= 1 ) {
|
||||
return;
|
||||
}
|
||||
SV_DropClient( svs.clients + clientNum, reason );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
SV_SetBrushModel
|
||||
|
||||
sets mins and maxs for inline bmodels
|
||||
=================
|
||||
*/
|
||||
void SV_SetBrushModel( gentity_t *ent, const char *name ) {
|
||||
clipHandle_t h;
|
||||
vec3_t mins, maxs;
|
||||
|
||||
if (!name)
|
||||
{
|
||||
Com_Error( ERR_DROP, "SV_SetBrushModel: NULL model for ent number %d", ent->s.number );
|
||||
}
|
||||
|
||||
if (name[0] == '*')
|
||||
{
|
||||
ent->s.modelindex = atoi( name + 1 );
|
||||
|
||||
if (sv.mLocalSubBSPIndex != -1)
|
||||
{
|
||||
ent->s.modelindex += sv.mLocalSubBSPModelOffset;
|
||||
}
|
||||
|
||||
h = CM_InlineModel( ent->s.modelindex );
|
||||
|
||||
if (sv.mLocalSubBSPIndex != -1)
|
||||
{
|
||||
CM_ModelBounds( SubBSP[sv.mLocalSubBSPIndex], h, mins, maxs );
|
||||
}
|
||||
else
|
||||
{
|
||||
CM_ModelBounds( cmg, h, mins, maxs);
|
||||
}
|
||||
|
||||
//CM_ModelBounds( h, mins, maxs );
|
||||
|
||||
VectorCopy (mins, ent->mins);
|
||||
VectorCopy (maxs, ent->maxs);
|
||||
ent->bmodel = qtrue;
|
||||
|
||||
if (0) //com_RMG && com_RMG->integer //fixme: this test really should be do we have bsp instances
|
||||
{
|
||||
ent->contents = CM_ModelContents( h, sv.mLocalSubBSPIndex );
|
||||
}
|
||||
else
|
||||
{
|
||||
ent->contents = CM_ModelContents( h, -1 );
|
||||
}
|
||||
}
|
||||
else if (name[0] == '#')
|
||||
{
|
||||
ent->s.modelindex = CM_LoadSubBSP(va("maps/%s.bsp", name + 1), qfalse);
|
||||
CM_ModelBounds( SubBSP[CM_FindSubBSP(ent->s.modelindex)], ent->s.modelindex, mins, maxs );
|
||||
|
||||
VectorCopy (mins, ent->mins);
|
||||
VectorCopy (maxs, ent->maxs);
|
||||
ent->bmodel = qtrue;
|
||||
|
||||
//rwwNOTE: We don't ever want to set contents -1, it includes CONTENTS_LIGHTSABER.
|
||||
//Lots of stuff will explode if there's a brush with CONTENTS_LIGHTSABER that isn't attached to a client owner.
|
||||
//ent->contents = -1; // we don't know exactly what is in the brushes
|
||||
h = CM_InlineModel( ent->s.modelindex );
|
||||
ent->contents = CM_ModelContents( h, CM_FindSubBSP(ent->s.modelindex) );
|
||||
// ent->contents = CONTENTS_SOLID;
|
||||
}
|
||||
else
|
||||
{
|
||||
Com_Error( ERR_DROP, "SV_SetBrushModel: %s isn't a brush model (ent %d)", name, ent->s.number );
|
||||
}
|
||||
}
|
||||
|
||||
const char *SV_SetActiveSubBSP(int index)
|
||||
{
|
||||
if (index >= 0)
|
||||
{
|
||||
sv.mLocalSubBSPIndex = CM_FindSubBSP(index);
|
||||
sv.mLocalSubBSPModelOffset = index;
|
||||
sv.mLocalSubBSPEntityParsePoint = CM_SubBSPEntityString (sv.mLocalSubBSPIndex);
|
||||
return sv.mLocalSubBSPEntityParsePoint;
|
||||
}
|
||||
else
|
||||
{
|
||||
sv.mLocalSubBSPIndex = -1;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
SV_inPVS
|
||||
|
||||
Also checks portalareas so that doors block sight
|
||||
=================
|
||||
*/
|
||||
qboolean SV_inPVS (const vec3_t p1, const vec3_t p2)
|
||||
{
|
||||
int leafnum;
|
||||
int cluster;
|
||||
int area1, area2;
|
||||
#ifdef _XBOX
|
||||
const byte *mask;
|
||||
#else
|
||||
byte *mask;
|
||||
#endif
|
||||
int start=0;
|
||||
|
||||
if ( com_speeds->integer ) {
|
||||
start = Sys_Milliseconds ();
|
||||
}
|
||||
leafnum = CM_PointLeafnum (p1);
|
||||
cluster = CM_LeafCluster (leafnum);
|
||||
area1 = CM_LeafArea (leafnum);
|
||||
mask = CM_ClusterPVS (cluster);
|
||||
|
||||
leafnum = CM_PointLeafnum (p2);
|
||||
cluster = CM_LeafCluster (leafnum);
|
||||
area2 = CM_LeafArea (leafnum);
|
||||
if ( mask && (!(mask[cluster>>3] & (1<<(cluster&7)) ) ) )
|
||||
{
|
||||
if ( com_speeds->integer ) {
|
||||
timeInPVSCheck += Sys_Milliseconds () - start;
|
||||
}
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
if (!CM_AreasConnected (area1, area2))
|
||||
{
|
||||
timeInPVSCheck += Sys_Milliseconds() - start;
|
||||
return qfalse; // a door blocks sight
|
||||
}
|
||||
|
||||
if ( com_speeds->integer ) {
|
||||
timeInPVSCheck += Sys_Milliseconds() - start;
|
||||
}
|
||||
return qtrue;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
SV_inPVSIgnorePortals
|
||||
|
||||
Does NOT check portalareas
|
||||
=================
|
||||
*/
|
||||
qboolean SV_inPVSIgnorePortals( const vec3_t p1, const vec3_t p2)
|
||||
{
|
||||
int leafnum;
|
||||
int cluster;
|
||||
int area1, area2;
|
||||
#ifdef _XBOX
|
||||
const byte *mask;
|
||||
#else
|
||||
byte *mask;
|
||||
#endif
|
||||
int start=0;
|
||||
|
||||
if ( com_speeds->integer ) {
|
||||
start = Sys_Milliseconds ();
|
||||
}
|
||||
|
||||
leafnum = CM_PointLeafnum (p1);
|
||||
cluster = CM_LeafCluster (leafnum);
|
||||
area1 = CM_LeafArea (leafnum);
|
||||
mask = CM_ClusterPVS (cluster);
|
||||
|
||||
leafnum = CM_PointLeafnum (p2);
|
||||
cluster = CM_LeafCluster (leafnum);
|
||||
area2 = CM_LeafArea (leafnum);
|
||||
|
||||
if ( mask && (!(mask[cluster>>3] & (1<<(cluster&7)) ) ) )
|
||||
{
|
||||
if ( com_speeds->integer ) {
|
||||
timeInPVSCheck += Sys_Milliseconds() - start;
|
||||
}
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
if ( com_speeds->integer ) {
|
||||
timeInPVSCheck += Sys_Milliseconds() - start;
|
||||
}
|
||||
return qtrue;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
========================
|
||||
SV_AdjustAreaPortalState
|
||||
========================
|
||||
*/
|
||||
void SV_AdjustAreaPortalState( gentity_t *ent, qboolean open ) {
|
||||
if ( !(ent->contents&CONTENTS_OPAQUE) ) {
|
||||
#ifndef FINAL_BUILD
|
||||
// Com_Printf( "INFO: entity number %d not opaque: not affecting area portal!\n", ent->s.number );
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
svEntity_t *svEnt;
|
||||
|
||||
svEnt = SV_SvEntityForGentity( ent );
|
||||
if ( svEnt->areanum2 == -1 ) {
|
||||
return;
|
||||
}
|
||||
CM_AdjustAreaPortalState( svEnt->areanum, svEnt->areanum2, open );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_GameAreaEntities
|
||||
==================
|
||||
*/
|
||||
qboolean SV_EntityContact( const vec3_t mins, const vec3_t maxs, const gentity_t *gEnt ) {
|
||||
const float *origin, *angles;
|
||||
clipHandle_t ch;
|
||||
trace_t trace;
|
||||
|
||||
// check for exact collision
|
||||
origin = gEnt->currentOrigin;
|
||||
angles = gEnt->currentAngles;
|
||||
|
||||
ch = SV_ClipHandleForEntity( gEnt );
|
||||
CM_TransformedBoxTrace ( &trace, vec3_origin, vec3_origin, mins, maxs,
|
||||
ch, -1, origin, angles );
|
||||
|
||||
return trace.startsolid;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_GetServerinfo
|
||||
|
||||
===============
|
||||
*/
|
||||
void SV_GetServerinfo( char *buffer, int bufferSize ) {
|
||||
if ( bufferSize < 1 ) {
|
||||
Com_Error( ERR_DROP, "SV_GetServerinfo: bufferSize == %i", bufferSize );
|
||||
}
|
||||
Q_strncpyz( buffer, Cvar_InfoString( CVAR_SERVERINFO ), bufferSize );
|
||||
}
|
||||
|
||||
qboolean SV_GetEntityToken( char *buffer, int bufferSize )
|
||||
{
|
||||
char *s;
|
||||
|
||||
if (sv.mLocalSubBSPIndex == -1)
|
||||
{
|
||||
s = COM_Parse( (const char **)&sv.entityParsePoint );
|
||||
Q_strncpyz( buffer, s, bufferSize );
|
||||
if ( !sv.entityParsePoint && !s[0] )
|
||||
{
|
||||
return qfalse;
|
||||
}
|
||||
else
|
||||
{
|
||||
return qtrue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
s = COM_Parse( (const char **)&sv.mLocalSubBSPEntityParsePoint);
|
||||
Q_strncpyz( buffer, s, bufferSize );
|
||||
if ( !sv.mLocalSubBSPEntityParsePoint && !s[0] )
|
||||
{
|
||||
return qfalse;
|
||||
}
|
||||
else
|
||||
{
|
||||
return qtrue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_ShutdownGameProgs
|
||||
|
||||
Called when either the entire server is being killed, or
|
||||
it is changing to a different game directory.
|
||||
===============
|
||||
*/
|
||||
void SV_ShutdownGameProgs (qboolean shutdownCin) {
|
||||
if (!ge) {
|
||||
return;
|
||||
}
|
||||
ge->Shutdown ();
|
||||
|
||||
#ifdef _XBOX
|
||||
if(shutdownCin) {
|
||||
SCR_StopCinematic();
|
||||
}
|
||||
#else
|
||||
SCR_StopCinematic();
|
||||
#endif
|
||||
CL_ShutdownCGame(); //we have cgame burried in here.
|
||||
|
||||
Sys_UnloadGame (); //this kills cgame as well.
|
||||
|
||||
ge = NULL;
|
||||
cgvm.entryPoint = 0;
|
||||
}
|
||||
|
||||
// this is a compile-helper function since Z_Malloc can now become a macro with __LINE__ etc
|
||||
//
|
||||
static void *G_ZMalloc_Helper( int iSize, memtag_t eTag, qboolean bZeroit)
|
||||
{
|
||||
return Z_Malloc( iSize, eTag, bZeroit );
|
||||
}
|
||||
|
||||
//rww - RAGDOLL_BEGIN
|
||||
void G2API_SetRagDoll(CGhoul2Info_v &ghoul2,CRagDollParams *parms);
|
||||
void G2API_AnimateG2Models(CGhoul2Info_v &ghoul2, int AcurrentTime,CRagDollUpdateParams *params);
|
||||
|
||||
qboolean G2API_RagPCJConstraint(CGhoul2Info_v &ghoul2, const char *boneName, vec3_t min, vec3_t max);
|
||||
qboolean G2API_RagPCJGradientSpeed(CGhoul2Info_v &ghoul2, const char *boneName, const float speed);
|
||||
qboolean G2API_RagEffectorGoal(CGhoul2Info_v &ghoul2, const char *boneName, vec3_t pos);
|
||||
qboolean G2API_GetRagBonePos(CGhoul2Info_v &ghoul2, const char *boneName, vec3_t pos, vec3_t entAngles, vec3_t entPos, vec3_t entScale);
|
||||
qboolean G2API_RagEffectorKick(CGhoul2Info_v &ghoul2, const char *boneName, vec3_t velocity);
|
||||
qboolean G2API_RagForceSolve(CGhoul2Info_v &ghoul2, qboolean force);
|
||||
|
||||
qboolean G2API_SetBoneIKState(CGhoul2Info_v &ghoul2, int time, const char *boneName, int ikState, sharedSetBoneIKStateParams_t *params);
|
||||
qboolean G2API_IKMove(CGhoul2Info_v &ghoul2, int time, sharedIKMoveParams_t *params);
|
||||
//rww - RAGDOLL_END
|
||||
|
||||
//This is as good a place as any I guess.
|
||||
#ifndef _XBOX // Removing terrain from Xbox
|
||||
void RMG_Init(int terrainID)
|
||||
{
|
||||
if (!TheRandomMissionManager)
|
||||
{
|
||||
TheRandomMissionManager = new CRMManager;
|
||||
}
|
||||
TheRandomMissionManager->SetLandScape(cmg.landScape);
|
||||
if (TheRandomMissionManager->LoadMission(qtrue))
|
||||
{
|
||||
TheRandomMissionManager->SpawnMission(qtrue);
|
||||
}
|
||||
// cmg.landScapes[args[1]]->UpdatePatches();
|
||||
//sv.mRMGChecksum = cm.landScapes[terrainID]->get_rand_seed();
|
||||
}
|
||||
|
||||
CCMLandScape *CM_RegisterTerrain(const char *config, bool server);
|
||||
|
||||
int InterfaceCM_RegisterTerrain (const char *info)
|
||||
{
|
||||
return CM_RegisterTerrain(info, false)->GetTerrainId();
|
||||
}
|
||||
#endif // _XBOX
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_InitGameProgs
|
||||
|
||||
Init the game subsystem for a new map
|
||||
===============
|
||||
*/
|
||||
void SV_InitGameProgs (void) {
|
||||
game_import_t import;
|
||||
int i;
|
||||
|
||||
// unload anything we have now
|
||||
if ( ge ) {
|
||||
SV_ShutdownGameProgs (qtrue);
|
||||
}
|
||||
|
||||
#ifndef _XBOX
|
||||
if ( !Cvar_VariableIntegerValue("fs_restrict") && !Sys_CheckCD() )
|
||||
{
|
||||
Com_Error( ERR_NEED_CD, SE_GetString("CON_TEXT_NEED_CD") ); //"Game CD not in drive" );
|
||||
}
|
||||
#endif
|
||||
|
||||
// load a new game dll
|
||||
import.Printf = Com_Printf;
|
||||
import.WriteCam = Com_WriteCam;
|
||||
import.FlushCamFile = Com_FlushCamFile;
|
||||
import.Error = Com_Error;
|
||||
|
||||
import.Milliseconds = Sys_Milliseconds;
|
||||
|
||||
import.DropClient = SV_GameDropClient;
|
||||
|
||||
import.SendServerCommand = SV_GameSendServerCommand;
|
||||
|
||||
|
||||
import.linkentity = SV_LinkEntity;
|
||||
import.unlinkentity = SV_UnlinkEntity;
|
||||
import.EntitiesInBox = SV_AreaEntities;
|
||||
import.EntityContact = SV_EntityContact;
|
||||
import.trace = SV_Trace;
|
||||
import.pointcontents = SV_PointContents;
|
||||
import.totalMapContents = CM_TotalMapContents;
|
||||
import.SetBrushModel = SV_SetBrushModel;
|
||||
|
||||
import.inPVS = SV_inPVS;
|
||||
import.inPVSIgnorePortals = SV_inPVSIgnorePortals;
|
||||
|
||||
import.SetConfigstring = SV_SetConfigstring;
|
||||
import.GetConfigstring = SV_GetConfigstring;
|
||||
|
||||
import.SetUserinfo = SV_SetUserinfo;
|
||||
import.GetUserinfo = SV_GetUserinfo;
|
||||
|
||||
import.GetServerinfo = SV_GetServerinfo;
|
||||
|
||||
import.cvar = Cvar_Get;
|
||||
import.cvar_set = Cvar_Set;
|
||||
import.Cvar_VariableIntegerValue = Cvar_VariableIntegerValue;
|
||||
import.Cvar_VariableStringBuffer = Cvar_VariableStringBuffer;
|
||||
|
||||
import.argc = Cmd_Argc;
|
||||
import.argv = Cmd_Argv;
|
||||
import.SendConsoleCommand = Cbuf_AddText;
|
||||
|
||||
import.FS_FOpenFile = FS_FOpenFileByMode;
|
||||
import.FS_Read = FS_Read;
|
||||
import.FS_Write = FS_Write;
|
||||
import.FS_FCloseFile = FS_FCloseFile;
|
||||
import.FS_ReadFile = FS_ReadFile;
|
||||
import.FS_FreeFile = FS_FreeFile;
|
||||
import.FS_GetFileList = FS_GetFileList;
|
||||
|
||||
import.AppendToSaveGame = SG_Append;
|
||||
#ifndef _XBOX
|
||||
import.ReadFromSaveGame = SG_Read;
|
||||
import.ReadFromSaveGameOptional = SG_ReadOptional;
|
||||
#endif
|
||||
|
||||
import.AdjustAreaPortalState = SV_AdjustAreaPortalState;
|
||||
import.AreasConnected = CM_AreasConnected;
|
||||
|
||||
import.VoiceVolume = s_entityWavVol;
|
||||
|
||||
import.Malloc = G_ZMalloc_Helper;
|
||||
import.Free = Z_Free;
|
||||
import.bIsFromZone = Z_IsFromZone;
|
||||
/*
|
||||
Ghoul2 Insert Start
|
||||
*/
|
||||
|
||||
import.G2API_AddBolt = G2API_AddBolt;
|
||||
import.G2API_AttachEnt = G2API_AttachEnt;
|
||||
import.G2API_AttachG2Model = G2API_AttachG2Model;
|
||||
import.G2API_CollisionDetect = G2API_CollisionDetect;
|
||||
import.G2API_DetachEnt = G2API_DetachEnt;
|
||||
import.G2API_DetachG2Model = G2API_DetachG2Model;
|
||||
import.G2API_GetAnimFileName = G2API_GetAnimFileName;
|
||||
import.G2API_GetBoltMatrix = G2API_GetBoltMatrix;
|
||||
import.G2API_GetBoneAnim = G2API_GetBoneAnim;
|
||||
import.G2API_GetBoneAnimIndex = G2API_GetBoneAnimIndex;
|
||||
import.G2API_AddSurface = G2API_AddSurface;
|
||||
import.G2API_HaveWeGhoul2Models =G2API_HaveWeGhoul2Models;
|
||||
#ifndef _XBOX
|
||||
import.G2API_InitGhoul2Model = G2API_InitGhoul2Model;
|
||||
import.G2API_SetBoneAngles = G2API_SetBoneAngles;
|
||||
import.G2API_SetBoneAnglesMatrix = G2API_SetBoneAnglesMatrix;
|
||||
import.G2API_SetBoneAnim = G2API_SetBoneAnim;
|
||||
import.G2API_SetSkin = G2API_SetSkin;
|
||||
import.G2API_CopyGhoul2Instance = G2API_CopyGhoul2Instance;
|
||||
import.G2API_SetBoneAnglesIndex = G2API_SetBoneAnglesIndex;
|
||||
import.G2API_SetBoneAnimIndex = G2API_SetBoneAnimIndex;
|
||||
#endif
|
||||
import.G2API_IsPaused = G2API_IsPaused;
|
||||
import.G2API_ListBones = G2API_ListBones;
|
||||
import.G2API_ListSurfaces = G2API_ListSurfaces;
|
||||
import.G2API_PauseBoneAnim = G2API_PauseBoneAnim;
|
||||
import.G2API_PauseBoneAnimIndex = G2API_PauseBoneAnimIndex;
|
||||
import.G2API_PrecacheGhoul2Model = G2API_PrecacheGhoul2Model;
|
||||
import.G2API_RemoveBolt = G2API_RemoveBolt;
|
||||
import.G2API_RemoveBone = G2API_RemoveBone;
|
||||
import.G2API_RemoveGhoul2Model = G2API_RemoveGhoul2Model;
|
||||
import.G2API_SetLodBias = G2API_SetLodBias;
|
||||
import.G2API_SetRootSurface = G2API_SetRootSurface;
|
||||
import.G2API_SetShader = G2API_SetShader;
|
||||
import.G2API_SetSurfaceOnOff = G2API_SetSurfaceOnOff;
|
||||
import.G2API_StopBoneAngles = G2API_StopBoneAngles;
|
||||
import.G2API_StopBoneAnim = G2API_StopBoneAnim;
|
||||
import.G2API_SetGhoul2ModelFlags = G2API_SetGhoul2ModelFlags;
|
||||
import.G2API_AddBoltSurfNum = G2API_AddBoltSurfNum;
|
||||
import.G2API_RemoveSurface = G2API_RemoveSurface;
|
||||
import.G2API_GetAnimRange = G2API_GetAnimRange;
|
||||
import.G2API_GetAnimRangeIndex = G2API_GetAnimRangeIndex;
|
||||
import.G2API_GiveMeVectorFromMatrix = G2API_GiveMeVectorFromMatrix;
|
||||
import.G2API_GetGhoul2ModelFlags = G2API_GetGhoul2ModelFlags;
|
||||
import.G2API_CleanGhoul2Models = G2API_CleanGhoul2Models;
|
||||
import.TheGhoul2InfoArray = TheGhoul2InfoArray;
|
||||
import.G2API_GetParentSurface = G2API_GetParentSurface;
|
||||
import.G2API_GetSurfaceIndex = G2API_GetSurfaceIndex;
|
||||
import.G2API_GetSurfaceName = G2API_GetSurfaceName;
|
||||
import.G2API_GetGLAName = G2API_GetGLAName;
|
||||
import.G2API_SetNewOrigin = G2API_SetNewOrigin;
|
||||
import.G2API_GetBoneIndex = G2API_GetBoneIndex;
|
||||
import.G2API_StopBoneAnglesIndex = G2API_StopBoneAnglesIndex;
|
||||
import.G2API_StopBoneAnimIndex = G2API_StopBoneAnimIndex;
|
||||
import.G2API_SetBoneAnglesMatrixIndex = G2API_SetBoneAnglesMatrixIndex;
|
||||
import.G2API_SetAnimIndex = G2API_SetAnimIndex;
|
||||
import.G2API_GetAnimIndex = G2API_GetAnimIndex;
|
||||
|
||||
import.G2API_SaveGhoul2Models = G2API_SaveGhoul2Models;
|
||||
import.G2API_LoadGhoul2Models = G2API_LoadGhoul2Models;
|
||||
import.G2API_LoadSaveCodeDestructGhoul2Info = G2API_LoadSaveCodeDestructGhoul2Info;
|
||||
import.G2API_GetAnimFileNameIndex = G2API_GetAnimFileNameIndex;
|
||||
import.G2API_GetAnimFileInternalNameIndex = G2API_GetAnimFileInternalNameIndex;
|
||||
import.G2API_GetSurfaceRenderStatus = G2API_GetSurfaceRenderStatus;
|
||||
|
||||
//rww - RAGDOLL_BEGIN
|
||||
import.G2API_SetRagDoll = G2API_SetRagDoll;
|
||||
import.G2API_AnimateG2Models = G2API_AnimateG2Models;
|
||||
|
||||
import.G2API_RagPCJConstraint = G2API_RagPCJConstraint;
|
||||
import.G2API_RagPCJGradientSpeed = G2API_RagPCJGradientSpeed;
|
||||
import.G2API_RagEffectorGoal = G2API_RagEffectorGoal;
|
||||
import.G2API_GetRagBonePos = G2API_GetRagBonePos;
|
||||
import.G2API_RagEffectorKick = G2API_RagEffectorKick;
|
||||
import.G2API_RagForceSolve = G2API_RagForceSolve;
|
||||
|
||||
import.G2API_SetBoneIKState = G2API_SetBoneIKState;
|
||||
import.G2API_IKMove = G2API_IKMove;
|
||||
//rww - RAGDOLL_END
|
||||
|
||||
import.G2API_AddSkinGore = G2API_AddSkinGore;
|
||||
import.G2API_ClearSkinGore = G2API_ClearSkinGore;
|
||||
|
||||
#ifndef _XBOX
|
||||
import.RMG_Init = RMG_Init;
|
||||
import.CM_RegisterTerrain = InterfaceCM_RegisterTerrain;
|
||||
#endif
|
||||
import.SetActiveSubBSP = SV_SetActiveSubBSP;
|
||||
|
||||
import.RE_RegisterSkin = RE_RegisterSkin;
|
||||
import.RE_GetAnimationCFG = RE_GetAnimationCFG;
|
||||
|
||||
|
||||
import.WE_GetWindVector = R_GetWindVector;
|
||||
import.WE_GetWindGusting = R_GetWindGusting;
|
||||
import.WE_IsOutside = R_IsOutside;
|
||||
import.WE_IsOutsideCausingPain = R_IsOutsideCausingPain;
|
||||
import.WE_GetChanceOfSaberFizz = R_GetChanceOfSaberFizz;
|
||||
import.WE_IsShaking = R_IsShaking;
|
||||
import.WE_AddWeatherZone = R_AddWeatherZone;
|
||||
import.WE_SetTempGlobalFogColor = R_SetTempGlobalFogColor;
|
||||
|
||||
|
||||
/*
|
||||
Ghoul2 Insert End
|
||||
*/
|
||||
|
||||
ge = (game_export_t *)Sys_GetGameAPI (&import);
|
||||
|
||||
if (!ge)
|
||||
Com_Error (ERR_DROP, "failed to load game DLL");
|
||||
|
||||
//hook up the client while we're here
|
||||
#ifdef _XBOX
|
||||
VM_Create("cl");
|
||||
#else
|
||||
if (!VM_Create("cl"))
|
||||
Com_Error (ERR_DROP, "failed to attach to the client DLL");
|
||||
#endif
|
||||
|
||||
if (ge->apiversion != GAME_API_VERSION)
|
||||
Com_Error (ERR_DROP, "game is version %i, not %i", ge->apiversion,
|
||||
GAME_API_VERSION);
|
||||
|
||||
sv.entityParsePoint = CM_EntityString();
|
||||
|
||||
// use the current msec count for a random seed
|
||||
Z_TagFree(TAG_G_ALLOC);
|
||||
ge->Init( sv_mapname->string, sv_spawntarget->string, sv_mapChecksum->integer, CM_EntityString(), sv.time, com_frameTime, Com_Milliseconds(), eSavedGameJustLoaded, qbLoadTransition );
|
||||
|
||||
if(!Q_stricmp(sv_mapname->string, "t1_rail") )
|
||||
{
|
||||
Cvar_Set("in_shaking_rumble","0");
|
||||
}
|
||||
else
|
||||
{
|
||||
Cvar_Set("in_shaking_rumble","1");
|
||||
}
|
||||
|
||||
// clear all gentity pointers that might still be set from
|
||||
// a previous level
|
||||
for ( i = 0 ; i < 1 ; i++ ) {
|
||||
svs.clients[i].gentity = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
====================
|
||||
SV_GameCommand
|
||||
|
||||
See if the current console command is claimed by the game
|
||||
====================
|
||||
*/
|
||||
qboolean SV_GameCommand( void ) {
|
||||
if ( sv.state != SS_GAME ) {
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
return ge->ConsoleCommand();
|
||||
}
|
||||
|
||||
752
code/server/sv_init.cpp
Normal file
752
code/server/sv_init.cpp
Normal file
@@ -0,0 +1,752 @@
|
||||
|
||||
// leave this as first line for PCH reasons...
|
||||
//
|
||||
#include "../server/exe_headers.h"
|
||||
|
||||
#include "../client/snd_music.h" // didn't want to put this in snd_local because of rebuild times etc.
|
||||
#include "server.h"
|
||||
#include "../win32/xbox_texture_man.h"
|
||||
#include <xgraphics.h>
|
||||
|
||||
/*
|
||||
Ghoul2 Insert Start
|
||||
*/
|
||||
#if !defined(TR_LOCAL_H)
|
||||
#include "../renderer/tr_local.h"
|
||||
#endif
|
||||
|
||||
#if !defined (MINIHEAP_H_INC)
|
||||
#include "../qcommon/miniheap.h"
|
||||
#endif
|
||||
|
||||
void CM_CleanLeafCache(void);
|
||||
extern void SV_FreeClient(client_t*);
|
||||
|
||||
CMiniHeap *G2VertSpaceServer = NULL;
|
||||
/*
|
||||
Ghoul2 Insert End
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_SetConfigstring
|
||||
|
||||
===============
|
||||
*/
|
||||
void SV_SetConfigstring (int index, const char *val) {
|
||||
if ( index < 0 || index >= MAX_CONFIGSTRINGS ) {
|
||||
Com_Error (ERR_DROP, "SV_SetConfigstring: bad index %i\n", index);
|
||||
}
|
||||
|
||||
if ( !val ) {
|
||||
val = "";
|
||||
}
|
||||
|
||||
// don't bother broadcasting an update if no change
|
||||
if ( !strcmp( val, sv.configstrings[ index ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// change the string in sv
|
||||
Z_Free( sv.configstrings[index] );
|
||||
sv.configstrings[index] = CopyString( val );
|
||||
|
||||
// send it to all the clients if we aren't
|
||||
// spawning a new server
|
||||
if ( sv.state == SS_GAME ) {
|
||||
SV_SendServerCommand( NULL, "cs %i \"%s\"\n", index, val );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_GetConfigstring
|
||||
|
||||
===============
|
||||
*/
|
||||
void SV_GetConfigstring( int index, char *buffer, int bufferSize ) {
|
||||
if ( bufferSize < 1 ) {
|
||||
Com_Error( ERR_DROP, "SV_GetConfigstring: bufferSize == %i", bufferSize );
|
||||
}
|
||||
if ( index < 0 || index >= MAX_CONFIGSTRINGS ) {
|
||||
Com_Error (ERR_DROP, "SV_GetConfigstring: bad index %i\n", index);
|
||||
}
|
||||
if ( !sv.configstrings[index] ) {
|
||||
buffer[0] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
Q_strncpyz( buffer, sv.configstrings[index], bufferSize );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_SetUserinfo
|
||||
|
||||
===============
|
||||
*/
|
||||
void SV_SetUserinfo( int index, const char *val ) {
|
||||
if ( index < 0 || index >= 1 ) {
|
||||
Com_Error (ERR_DROP, "SV_SetUserinfo: bad index %i\n", index);
|
||||
}
|
||||
|
||||
if ( !val ) {
|
||||
val = "";
|
||||
}
|
||||
|
||||
Q_strncpyz( svs.clients[ index ].userinfo, val, sizeof( svs.clients[ index ].userinfo ) );
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_GetUserinfo
|
||||
|
||||
===============
|
||||
*/
|
||||
void SV_GetUserinfo( int index, char *buffer, int bufferSize ) {
|
||||
if ( bufferSize < 1 ) {
|
||||
Com_Error( ERR_DROP, "SV_GetUserinfo: bufferSize == %i", bufferSize );
|
||||
}
|
||||
if ( index < 0 || index >= 1 ) {
|
||||
Com_Error (ERR_DROP, "SV_GetUserinfo: bad index %i\n", index);
|
||||
}
|
||||
Q_strncpyz( buffer, svs.clients[ index ].userinfo, bufferSize );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
================
|
||||
SV_CreateBaseline
|
||||
|
||||
Entity baselines are used to compress non-delta messages
|
||||
to the clients -- only the fields that differ from the
|
||||
baseline will be transmitted
|
||||
================
|
||||
*/
|
||||
void SV_CreateBaseline( void ) {
|
||||
gentity_t *svent;
|
||||
int entnum;
|
||||
|
||||
for ( entnum = 0; entnum < ge->num_entities ; entnum++ ) {
|
||||
svent = SV_GentityNum(entnum);
|
||||
if (!svent->inuse) {
|
||||
continue;
|
||||
}
|
||||
if (!svent->linked) {
|
||||
continue;
|
||||
}
|
||||
svent->s.number = entnum;
|
||||
|
||||
//
|
||||
// take current state as baseline
|
||||
//
|
||||
sv.svEntities[entnum].baseline = svent->s;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_Startup
|
||||
|
||||
Called when a game is about to begin
|
||||
===============
|
||||
*/
|
||||
void SV_Startup( void ) {
|
||||
if ( svs.initialized ) {
|
||||
Com_Error( ERR_FATAL, "SV_Startup: svs.initialized" );
|
||||
}
|
||||
|
||||
svs.clients = (struct client_s *) Z_Malloc ( sizeof(client_t) * 1, TAG_CLIENTS, qtrue );
|
||||
svs.numSnapshotEntities = 2 * 4 * 64;
|
||||
svs.initialized = qtrue;
|
||||
|
||||
Cvar_Set( "sv_running", "1" );
|
||||
}
|
||||
|
||||
|
||||
#ifdef _XBOX
|
||||
//Xbox-only memory freeing.
|
||||
extern void R_ModelFree(void);
|
||||
extern void Sys_IORequestQueueClear(void);
|
||||
extern void Music_Free(void);
|
||||
extern void AS_FreePartial(void);
|
||||
extern void G_ASPreCacheFree(void);
|
||||
extern void Ghoul2InfoArray_Free(void);
|
||||
extern void Ghoul2InfoArray_Reset(void);
|
||||
extern void Menu_Reset(void);
|
||||
extern void G2_FreeRag(void);
|
||||
extern void ClearAllNavStructures(void);
|
||||
extern void ClearModelsAlreadyDone(void);
|
||||
extern void CL_FreeServerCommands(void);
|
||||
extern void CL_FreeReliableCommands(void);
|
||||
extern void CM_Free(void);
|
||||
extern void ShaderEntryPtrs_Clear(void);
|
||||
extern void G_FreeRoffs(void);
|
||||
extern void BG_ClearVehicles(void);
|
||||
extern void ClearHStringPool(void);
|
||||
extern void ClearTheBonePool(void);
|
||||
extern char cinematicSkipScript[64];
|
||||
extern HANDLE s_BCThread;
|
||||
extern void IN_HotSwap1Off(void);
|
||||
extern void IN_HotSwap2Off(void);
|
||||
extern void IN_HotSwap3Off(void);
|
||||
extern int cg_saberOnSoundTime[MAX_GENTITIES];
|
||||
extern char current_speeders;
|
||||
extern int zfFaceShaders[3];
|
||||
extern int tfTorsoShader;
|
||||
extern bool dontPillarPush;
|
||||
|
||||
void SV_ClearLastLevel(void)
|
||||
{
|
||||
Menu_Reset();
|
||||
Z_TagFree(TAG_G_ALLOC);
|
||||
Z_TagFree(TAG_UI_ALLOC);
|
||||
G_FreeRoffs();
|
||||
R_ModelFree();
|
||||
Music_Free();
|
||||
Sys_IORequestQueueClear();
|
||||
AS_FreePartial();
|
||||
G_ASPreCacheFree();
|
||||
Ghoul2InfoArray_Free();
|
||||
G2_FreeRag();
|
||||
ClearAllNavStructures();
|
||||
ClearModelsAlreadyDone();
|
||||
CL_FreeServerCommands();
|
||||
CL_FreeReliableCommands();
|
||||
CM_Free();
|
||||
ShaderEntryPtrs_Clear();
|
||||
ClearTheBonePool();
|
||||
BG_ClearVehicles();
|
||||
|
||||
cinematicSkipScript[0] = 0;
|
||||
|
||||
if (svs.clients)
|
||||
{
|
||||
SV_FreeClient( svs.clients );
|
||||
}
|
||||
|
||||
ClearHStringPool();
|
||||
|
||||
// The bink copier thread is so trivial as to not have any communication
|
||||
// Rather than polling constantly to clean it up, we just check here.
|
||||
// This code should only happen ONCE:
|
||||
if (s_BCThread != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
DWORD status;
|
||||
if (GetExitCodeThread( s_BCThread, &status ) && (status != STILL_ACTIVE))
|
||||
{
|
||||
// Thread is done. Clean up after ourselves:
|
||||
CloseHandle( s_BCThread );
|
||||
s_BCThread = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
IN_HotSwap1Off();
|
||||
IN_HotSwap2Off();
|
||||
IN_HotSwap3Off();
|
||||
|
||||
memset(&cg_saberOnSoundTime, 0, MAX_GENTITIES);
|
||||
memset(zfFaceShaders, -1, sizeof(zfFaceShaders));
|
||||
tfTorsoShader = -1;
|
||||
|
||||
current_speeders = 0;
|
||||
|
||||
dontPillarPush = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
qboolean CM_SameMap(char *server);
|
||||
qboolean CM_HasTerrain(void);
|
||||
void Cvar_Defrag(void);
|
||||
|
||||
// Load-time animation hackery:
|
||||
struct OVERLAYINFO
|
||||
{
|
||||
D3DTexture *texture;
|
||||
D3DSurface *surface;
|
||||
};
|
||||
|
||||
OVERLAYINFO Image;
|
||||
static int loadingX = 290;
|
||||
|
||||
void InitLoadingAnimation( void )
|
||||
{
|
||||
/*
|
||||
// Make our two textures:
|
||||
Image.texture = new IDirect3DTexture9;
|
||||
|
||||
// Fill in the texture headers:
|
||||
DWORD pixelSize =
|
||||
XGSetTextureHeader( 4,
|
||||
4,
|
||||
1,
|
||||
0,
|
||||
D3DFMT_YUY2,
|
||||
0,
|
||||
Image.texture,
|
||||
0,
|
||||
0 );
|
||||
|
||||
// Get pixel data, texNum is unused:
|
||||
byte *pixels = (byte *)gTextures.Allocate( pixelSize, 0 );
|
||||
|
||||
// texNum is unused:
|
||||
Image.texture->Register( pixels );
|
||||
|
||||
// Turn on overlays:
|
||||
glw_state->device->EnableOverlay( TRUE );
|
||||
|
||||
// Get surface pointers:
|
||||
Image.texture->GetSurfaceLevel( 0, &Image.surface );
|
||||
|
||||
D3DLOCKED_RECT lock;
|
||||
Image.surface->LockRect( &lock, NULL, D3DLOCK_TILED );
|
||||
|
||||
// Grey?
|
||||
memset( lock.pBits, 0x7f7f7f7f, lock.Pitch * 4 );
|
||||
|
||||
Image.surface->UnlockRect();
|
||||
|
||||
// Just to be safe:
|
||||
// loadingIndex = 0;
|
||||
*/
|
||||
}
|
||||
|
||||
void UpdateLoadingAnimation( void )
|
||||
{
|
||||
/*
|
||||
// Draw the image tiny, in the bottom of the screen:
|
||||
RECT dst_rect = { loadingX, 390, loadingX + 8, 398 };
|
||||
RECT src_rect = { 0, 0, 4, 4 };
|
||||
|
||||
// Update this bugger.
|
||||
glw_state->device->UpdateOverlay( Image.surface, &src_rect, &dst_rect, FALSE, 0 );
|
||||
loadingX += 4;
|
||||
if (loadingX > 342 )
|
||||
loadingX = 290;
|
||||
*/
|
||||
}
|
||||
|
||||
void StopLoadingAnimation( void )
|
||||
{
|
||||
/*
|
||||
// Release surfaces:
|
||||
Image.surface->Release();
|
||||
Image.surface = NULL;
|
||||
|
||||
// Clean up the textures we made for the overlay stuff:
|
||||
Image.texture->BlockUntilNotBusy();
|
||||
delete Image.texture;
|
||||
Image.texture = NULL;
|
||||
|
||||
// Turn overlays back off:
|
||||
glw_state->device->EnableOverlay( FALSE );
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
SV_SpawnServer
|
||||
|
||||
Change the server to a new map, taking all connected
|
||||
clients along with it.
|
||||
================
|
||||
*/
|
||||
void SV_SpawnServer( char *iServer, ForceReload_e eForceReload, qboolean bAllowScreenDissolve )
|
||||
{
|
||||
int i;
|
||||
int checksum;
|
||||
char server[64];
|
||||
|
||||
Q_strncpyz( server, iServer, sizeof(server), qtrue );
|
||||
|
||||
#ifdef XBOX_DEMO
|
||||
// Pause the timer if "someone is playing"
|
||||
extern void Demo_TimerPause( bool bPaused );
|
||||
Demo_TimerPause( true );
|
||||
#endif
|
||||
|
||||
// The following fixes for potential issues only work on Xbox
|
||||
#ifdef _XBOX
|
||||
extern qboolean stop_icarus;
|
||||
stop_icarus = qfalse;
|
||||
|
||||
//Broken scripts may leave the player locked. I think that's always bad.
|
||||
extern qboolean player_locked;
|
||||
player_locked = qfalse;
|
||||
|
||||
//If you quit while in Matrix Mode, this never gets cleared!
|
||||
extern qboolean MatrixMode;
|
||||
MatrixMode = qfalse;
|
||||
|
||||
// Failsafe to ensure that we don't have rumbling during level load
|
||||
extern void IN_KillRumbleScripts( void );
|
||||
IN_KillRumbleScripts();
|
||||
#endif
|
||||
|
||||
RE_RegisterMedia_LevelLoadBegin( server, eForceReload, bAllowScreenDissolve );
|
||||
|
||||
|
||||
Cvar_SetValue( "cl_paused", 0 );
|
||||
Cvar_Set( "timescale", "1" );//jic we were skipping
|
||||
|
||||
// shut down the existing game if it is running
|
||||
SV_ShutdownGameProgs(qtrue);
|
||||
|
||||
Com_Printf ("------ Server Initialization ------\n%s\n", com_version->string);
|
||||
Com_Printf ("Server: %s\n",server);
|
||||
Cvar_Set( "ui_mapname", server );
|
||||
|
||||
#ifndef FINAL_BUILD
|
||||
// extern unsigned long texturePointMax;
|
||||
// Com_Printf ("Texture pool highwater mark: %u\n", texturePointMax);
|
||||
#endif
|
||||
|
||||
#ifdef _XBOX
|
||||
// disable vsync during load for speed
|
||||
qglDisable(GL_VSYNC);
|
||||
#endif
|
||||
|
||||
// Hope this is correct - InitGame gets called later, which does this,
|
||||
// but UI_DrawConnect (in CL_MapLoading) needs it now, to properly
|
||||
// mimic CG_DrawInformation:
|
||||
extern SavedGameJustLoaded_e g_eSavedGameJustLoaded;
|
||||
g_eSavedGameJustLoaded = eSavedGameJustLoaded;
|
||||
|
||||
// don't let sound stutter and dump all stuff on the hunk
|
||||
CL_MapLoading();
|
||||
|
||||
if (!CM_SameMap(server))
|
||||
{ //rww - only clear if not loading the same map
|
||||
CM_ClearMap();
|
||||
}
|
||||
#ifndef _XBOX
|
||||
else if (CM_HasTerrain())
|
||||
{ //always clear when going between maps with terrain
|
||||
CM_ClearMap();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Miniheap never changes sizes, so I just put it really early in mem.
|
||||
G2VertSpaceServer->ResetHeap();
|
||||
|
||||
#ifdef _XBOX
|
||||
// Deletes all textures
|
||||
R_DeleteTextures();
|
||||
#endif
|
||||
Hunk_Clear();
|
||||
|
||||
// Moved up from below to help reduce fragmentation
|
||||
if (svs.snapshotEntities)
|
||||
{
|
||||
Z_Free(svs.snapshotEntities);
|
||||
svs.snapshotEntities = NULL;
|
||||
}
|
||||
|
||||
// wipe the entire per-level structure
|
||||
// Also moved up, trying to do all freeing before new allocs
|
||||
for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) {
|
||||
if ( sv.configstrings[i] ) {
|
||||
Z_Free( sv.configstrings[i] );
|
||||
sv.configstrings[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _XBOX
|
||||
SV_ClearLastLevel();
|
||||
#endif
|
||||
|
||||
// Collect all the small allocations done by the cvar system
|
||||
// This frees, then allocates. Make it the last thing before other
|
||||
// allocations begin!
|
||||
Cvar_Defrag();
|
||||
|
||||
/*
|
||||
This is useful for debugging memory fragmentation. Please don't
|
||||
remove it.
|
||||
*/
|
||||
#ifdef _XBOX
|
||||
// We've over-freed the info array above, this puts it back into a working state
|
||||
Ghoul2InfoArray_Reset();
|
||||
|
||||
extern void Z_DumpMemMap_f(void);
|
||||
extern void Z_Details_f(void);
|
||||
extern void Z_TagPointers(memtag_t);
|
||||
Z_DumpMemMap_f();
|
||||
// Z_TagPointers(TAG_ALL);
|
||||
Z_Details_f();
|
||||
#endif
|
||||
|
||||
InitLoadingAnimation();
|
||||
UpdateLoadingAnimation();
|
||||
|
||||
// init client structures and svs.numSnapshotEntities
|
||||
// This is moved down quite a bit, but should be safe. And keeps
|
||||
// svs.clients right at the beginning of memory
|
||||
if ( !Cvar_VariableIntegerValue("sv_running") ) {
|
||||
SV_Startup();
|
||||
}
|
||||
|
||||
// clear out those shaders, images and Models
|
||||
// R_InitImages();
|
||||
// R_InitShaders();
|
||||
// R_ModelInit();
|
||||
|
||||
// allocate the snapshot entities
|
||||
svs.snapshotEntities = (entityState_t *) Z_Malloc (sizeof(entityState_t)*svs.numSnapshotEntities, TAG_CLIENTS, qtrue );
|
||||
|
||||
Music_SetLevelName(server);
|
||||
|
||||
// toggle the server bit so clients can detect that a
|
||||
// server has changed
|
||||
//!@ svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT;
|
||||
|
||||
// set nextmap to the same map, but it may be overriden
|
||||
// by the game startup or another console command
|
||||
Cvar_Set( "nextmap", va("map %s", server) );
|
||||
|
||||
|
||||
memset (&sv, 0, sizeof(sv));
|
||||
|
||||
|
||||
for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) {
|
||||
sv.configstrings[i] = CopyString("");
|
||||
}
|
||||
|
||||
sv.time = 1000;
|
||||
G2API_SetTime(sv.time,G2T_SV_TIME);
|
||||
|
||||
#ifdef _XBOX
|
||||
UpdateLoadingAnimation();
|
||||
CL_StartHunkUsers();
|
||||
UpdateLoadingAnimation();
|
||||
CM_LoadMap( va("maps/%s.bsp", server), qfalse, &checksum );
|
||||
UpdateLoadingAnimation();
|
||||
RE_LoadWorldMap(va("maps/%s.bsp", server));
|
||||
UpdateLoadingAnimation();
|
||||
#else
|
||||
CM_LoadMap( va("maps/%s.bsp", server), qfalse, &checksum, qfalse );
|
||||
#endif
|
||||
|
||||
// set serverinfo visible name
|
||||
Cvar_Set( "mapname", server );
|
||||
|
||||
Cvar_Set( "sv_mapChecksum", va("%i",checksum) );
|
||||
|
||||
// serverid should be different each time
|
||||
sv.serverId = com_frameTime;
|
||||
Cvar_Set( "sv_serverid", va("%i", sv.serverId ) );
|
||||
|
||||
// clear physics interaction links
|
||||
SV_ClearWorld ();
|
||||
|
||||
// media configstring setting should be done during
|
||||
// the loading stage, so connected clients don't have
|
||||
// to load during actual gameplay
|
||||
sv.state = SS_LOADING;
|
||||
|
||||
// load and spawn all other entities
|
||||
SV_InitGameProgs();
|
||||
|
||||
// run a few frames to allow everything to settle
|
||||
for ( i = 0 ;i < 3 ; i++ ) {
|
||||
ge->RunFrame( sv.time );
|
||||
sv.time += 100;
|
||||
G2API_SetTime(sv.time,G2T_SV_TIME);
|
||||
}
|
||||
ge->ConnectNavs(sv_mapname->string, sv_mapChecksum->integer);
|
||||
|
||||
// create a baseline for more efficient communications
|
||||
SV_CreateBaseline ();
|
||||
|
||||
for (i=0 ; i<1 ; i++) {
|
||||
// clear all time counters, because we have reset sv.time
|
||||
svs.clients[i].lastPacketTime = 0;
|
||||
svs.clients[i].lastConnectTime = 0;
|
||||
svs.clients[i].nextSnapshotTime = 0;
|
||||
|
||||
// send the new gamestate to all connected clients
|
||||
if (svs.clients[i].state >= CS_CONNECTED) {
|
||||
char *denied;
|
||||
|
||||
// connect the client again
|
||||
denied = ge->ClientConnect( i, qfalse, eNO/*qfalse*/ ); // firstTime = qfalse, qbFromSavedGame
|
||||
if ( denied ) {
|
||||
// this generally shouldn't happen, because the client
|
||||
// was connected before the level change
|
||||
SV_DropClient( &svs.clients[i], denied );
|
||||
} else {
|
||||
svs.clients[i].state = CS_CONNECTED;
|
||||
// when we get the next packet from a connected client,
|
||||
// the new gamestate will be sent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// run another frame to allow things to look at all connected clients
|
||||
ge->RunFrame( sv.time );
|
||||
sv.time += 100;
|
||||
G2API_SetTime(sv.time,G2T_SV_TIME);
|
||||
|
||||
|
||||
// save systeminfo and serverinfo strings
|
||||
SV_SetConfigstring( CS_SYSTEMINFO, Cvar_InfoString( CVAR_SYSTEMINFO ) );
|
||||
cvar_modifiedFlags &= ~CVAR_SYSTEMINFO;
|
||||
|
||||
SV_SetConfigstring( CS_SERVERINFO, Cvar_InfoString( CVAR_SERVERINFO ) );
|
||||
cvar_modifiedFlags &= ~CVAR_SERVERINFO;
|
||||
|
||||
// any media configstring setting now should issue a warning
|
||||
// and any configstring changes should be reliably transmitted
|
||||
// to all clients
|
||||
sv.state = SS_GAME;
|
||||
|
||||
// send a heartbeat now so the master will get up to date info
|
||||
svs.nextHeartbeatTime = -9999999;
|
||||
|
||||
Hunk_SetMark();
|
||||
#ifndef _XBOX
|
||||
Z_Validate();
|
||||
Z_Validate();
|
||||
Z_Validate();
|
||||
#endif
|
||||
|
||||
StopLoadingAnimation();
|
||||
|
||||
Com_Printf ("-----------------------------------\n");
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_Init
|
||||
|
||||
Only called at main exe startup, not for each game
|
||||
===============
|
||||
*/
|
||||
void SV_Init (void) {
|
||||
SV_AddOperatorCommands ();
|
||||
|
||||
// serverinfo vars
|
||||
Cvar_Get ("protocol", va("%i", PROTOCOL_VERSION), CVAR_SERVERINFO | CVAR_ROM);
|
||||
sv_mapname = Cvar_Get ("mapname", "nomap", CVAR_SERVERINFO | CVAR_ROM);
|
||||
|
||||
// systeminfo
|
||||
Cvar_Get ("helpUsObi", "0", CVAR_SYSTEMINFO );
|
||||
sv_serverid = Cvar_Get ("sv_serverid", "0", CVAR_SYSTEMINFO | CVAR_ROM );
|
||||
|
||||
// server vars
|
||||
sv_fps = Cvar_Get ("sv_fps", "20", CVAR_TEMP );
|
||||
sv_timeout = Cvar_Get ("sv_timeout", "120", CVAR_TEMP );
|
||||
sv_zombietime = Cvar_Get ("sv_zombietime", "2", CVAR_TEMP );
|
||||
Cvar_Get ("nextmap", "", CVAR_TEMP );
|
||||
sv_spawntarget = Cvar_Get ("spawntarget", "", 0 );
|
||||
|
||||
sv_reconnectlimit = Cvar_Get ("sv_reconnectlimit", "3", 0);
|
||||
sv_showloss = Cvar_Get ("sv_showloss", "0", 0);
|
||||
sv_killserver = Cvar_Get ("sv_killserver", "0", 0);
|
||||
sv_mapChecksum = Cvar_Get ("sv_mapChecksum", "", CVAR_ROM);
|
||||
sv_testsave = Cvar_Get ("sv_testsave", "0", 0);
|
||||
sv_compress_saved_games = Cvar_Get ("sv_compress_saved_games", "1", 0);
|
||||
|
||||
// Only allocated once, no point in moving it around and fragmenting
|
||||
// create a heap for Ghoul2 to use for game side model vertex transforms used in collision detection
|
||||
{
|
||||
static CMiniHeap singleton(132096);
|
||||
G2VertSpaceServer = &singleton;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_FinalMessage
|
||||
|
||||
Used by SV_Shutdown to send a final message to all
|
||||
connected clients before the server goes down. The messages are sent immediately,
|
||||
not just stuck on the outgoing message list, because the server is going
|
||||
to totally exit after returning from this function.
|
||||
==================
|
||||
*/
|
||||
void SV_FinalMessage( char *message ) {
|
||||
int i, j;
|
||||
client_t *cl;
|
||||
|
||||
SV_SendServerCommand( NULL, "print \"%s\"", message );
|
||||
SV_SendServerCommand( NULL, "disconnect" );
|
||||
|
||||
// send it twice, ignoring rate
|
||||
for ( j = 0 ; j < 2 ; j++ ) {
|
||||
for (i=0, cl = svs.clients ; i < 1 ; i++, cl++) {
|
||||
if (cl->state >= CS_CONNECTED) {
|
||||
// force a snapshot to be sent
|
||||
cl->nextSnapshotTime = -1;
|
||||
SV_SendClientSnapshot( cl );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
================
|
||||
SV_Shutdown
|
||||
|
||||
Called when each game quits,
|
||||
before Sys_Quit or Sys_Error
|
||||
================
|
||||
*/
|
||||
void SV_Shutdown( char *finalmsg ) {
|
||||
int i;
|
||||
|
||||
if ( !com_sv_running || !com_sv_running->integer ) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Com_Printf( "----- Server Shutdown -----\n" );
|
||||
|
||||
if ( svs.clients && !com_errorEntered ) {
|
||||
SV_FinalMessage( finalmsg );
|
||||
}
|
||||
|
||||
SV_RemoveOperatorCommands();
|
||||
SV_ShutdownGameProgs(qfalse);
|
||||
|
||||
if (svs.snapshotEntities)
|
||||
{
|
||||
Z_Free(svs.snapshotEntities);
|
||||
svs.snapshotEntities = NULL;
|
||||
}
|
||||
|
||||
for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) {
|
||||
if ( sv.configstrings[i] ) {
|
||||
Z_Free( sv.configstrings[i] );
|
||||
}
|
||||
}
|
||||
|
||||
// free current level
|
||||
memset( &sv, 0, sizeof( sv ) );
|
||||
|
||||
// free server static data
|
||||
if ( svs.clients ) {
|
||||
SV_FreeClient(svs.clients);
|
||||
Z_Free( svs.clients );
|
||||
}
|
||||
memset( &svs, 0, sizeof( svs ) );
|
||||
|
||||
// Ensure we free any memory used by the leaf cache.
|
||||
CM_CleanLeafCache();
|
||||
|
||||
Cvar_Set( "sv_running", "0" );
|
||||
|
||||
//Com_Printf( "---------------------------\n" );
|
||||
}
|
||||
|
||||
572
code/server/sv_main.cpp
Normal file
572
code/server/sv_main.cpp
Normal file
@@ -0,0 +1,572 @@
|
||||
// leave this as first line for PCH reasons...
|
||||
//
|
||||
#include "../server/exe_headers.h"
|
||||
|
||||
|
||||
|
||||
#include "server.h"
|
||||
/*
|
||||
Ghoul2 Insert Start
|
||||
*/
|
||||
#if !defined (MINIHEAP_H_INC)
|
||||
#include "../qcommon/miniheap.h"
|
||||
#endif
|
||||
/*
|
||||
Ghoul2 Insert End
|
||||
*/
|
||||
|
||||
serverStatic_t svs; // persistant server info
|
||||
server_t sv; // local server
|
||||
game_export_t *ge;
|
||||
|
||||
cvar_t *sv_fps; // time rate for running non-clients
|
||||
cvar_t *sv_timeout; // seconds without any message
|
||||
cvar_t *sv_zombietime; // seconds to sink messages after disconnect
|
||||
cvar_t *sv_reconnectlimit; // minimum seconds between connect messages
|
||||
cvar_t *sv_showloss; // report when usercmds are lost
|
||||
cvar_t *sv_killserver; // menu system can set to 1 to shut server down
|
||||
cvar_t *sv_mapname;
|
||||
cvar_t *sv_spawntarget;
|
||||
cvar_t *sv_mapChecksum;
|
||||
cvar_t *sv_serverid;
|
||||
cvar_t *sv_testsave; // Run the savegame enumeration every game frame
|
||||
cvar_t *sv_compress_saved_games; // compress the saved games on the way out (only affect saver, loader can read both)
|
||||
|
||||
/*
|
||||
=============================================================================
|
||||
|
||||
EVENT MESSAGES
|
||||
|
||||
=============================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_ExpandNewlines
|
||||
|
||||
Converts newlines to "\n" so a line prints nicer
|
||||
===============
|
||||
*/
|
||||
char *SV_ExpandNewlines( char *in ) {
|
||||
static char string[1024];
|
||||
int l;
|
||||
|
||||
l = 0;
|
||||
while ( *in && l < sizeof(string) - 3 ) {
|
||||
if ( *in == '\n' ) {
|
||||
string[l++] = '\\';
|
||||
string[l++] = 'n';
|
||||
} else {
|
||||
string[l++] = *in;
|
||||
}
|
||||
in++;
|
||||
}
|
||||
string[l] = 0;
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
/*
|
||||
======================
|
||||
SV_AddServerCommand
|
||||
|
||||
The given command will be transmitted to the client, and is guaranteed to
|
||||
not have future snapshot_t executed before it is executed
|
||||
======================
|
||||
*/
|
||||
void SV_AddServerCommand( client_t *client, const char *cmd ) {
|
||||
int index;
|
||||
|
||||
// if we would be losing an old command that hasn't been acknowledged,
|
||||
// we must drop the connection
|
||||
if ( client->reliableSequence - client->reliableAcknowledge > MAX_RELIABLE_COMMANDS ) {
|
||||
SV_DropClient( client, "Server command overflow" );
|
||||
return;
|
||||
}
|
||||
client->reliableSequence++;
|
||||
index = client->reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 );
|
||||
if ( client->reliableCommands[ index ] ) {
|
||||
Z_Free( client->reliableCommands[ index ] );
|
||||
}
|
||||
client->reliableCommands[ index ] = CopyString( cmd );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
SV_SendServerCommand
|
||||
|
||||
Sends a reliable command string to be interpreted by
|
||||
the client game module: "cp", "print", "chat", etc
|
||||
A NULL client will broadcast to all clients
|
||||
=================
|
||||
*/
|
||||
void SV_SendServerCommand(client_t *cl, const char *fmt, ...) {
|
||||
va_list argptr;
|
||||
byte message[MAX_MSGLEN];
|
||||
int len;
|
||||
client_t *client;
|
||||
int j;
|
||||
|
||||
message[0] = svc_serverCommand;
|
||||
|
||||
va_start (argptr,fmt);
|
||||
vsprintf ((char *)message+1, fmt,argptr);
|
||||
va_end (argptr);
|
||||
len = strlen( (char *)message ) + 1;
|
||||
|
||||
if ( cl != NULL ) {
|
||||
SV_AddServerCommand( cl, (char *)message );
|
||||
return;
|
||||
}
|
||||
|
||||
// send the data to all relevent clients
|
||||
for (j = 0, client = svs.clients; j < 1 ; j++, client++) {
|
||||
if ( client->state < CS_PRIMED ) {
|
||||
continue;
|
||||
}
|
||||
SV_AddServerCommand( client, (char *)message );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
CONNECTIONLESS COMMANDS
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
================
|
||||
SVC_Status
|
||||
|
||||
Responds with all the info that qplug or qspy can see about the server
|
||||
and all connected players. Used for getting detailed information after
|
||||
the simple info query.
|
||||
================
|
||||
*/
|
||||
void SVC_Status( netadr_t from ) {
|
||||
char player[1024];
|
||||
char status[MAX_MSGLEN];
|
||||
int i;
|
||||
client_t *cl;
|
||||
int statusLength;
|
||||
int playerLength;
|
||||
int score;
|
||||
char infostring[MAX_INFO_STRING];
|
||||
|
||||
strcpy( infostring, Cvar_InfoString( CVAR_SERVERINFO ) );
|
||||
|
||||
// echo back the parameter to status. so servers can use it as a challenge
|
||||
// to prevent timed spoofed reply packets that add ghost servers
|
||||
Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) );
|
||||
|
||||
status[0] = 0;
|
||||
statusLength = 0;
|
||||
|
||||
for (i=0 ; i < 1 ; i++) {
|
||||
cl = &svs.clients[i];
|
||||
if ( cl->state >= CS_CONNECTED ) {
|
||||
if ( cl->gentity && cl->gentity->client ) {
|
||||
score = cl->gentity->client->persistant[PERS_SCORE];
|
||||
} else {
|
||||
score = 0;
|
||||
}
|
||||
Com_sprintf (player, sizeof(player), "%i %i \"%s\"\n",
|
||||
score, cl->ping, cl->name);
|
||||
playerLength = strlen(player);
|
||||
if (statusLength + playerLength >= sizeof(status) ) {
|
||||
break; // can't hold any more
|
||||
}
|
||||
strcpy (status + statusLength, player);
|
||||
statusLength += playerLength;
|
||||
}
|
||||
}
|
||||
|
||||
NET_OutOfBandPrint( NS_SERVER, from, "statusResponse\n%s\n%s", infostring, status );
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
SVC_Info
|
||||
|
||||
Responds with a short info message that should be enough to determine
|
||||
if a user is interested in a server to do a full status
|
||||
================
|
||||
*/
|
||||
static void SVC_Info( netadr_t from ) {
|
||||
int i, count;
|
||||
char infostring[MAX_INFO_STRING];
|
||||
|
||||
count = 0;
|
||||
for ( i = 0 ; i < 1 ; i++ ) {
|
||||
if ( svs.clients[i].state >= CS_CONNECTED ) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
infostring[0] = 0;
|
||||
|
||||
// echo back the parameter to status. so servers can use it as a challenge
|
||||
// to prevent timed spoofed reply packets that add ghost servers
|
||||
Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) );
|
||||
|
||||
Info_SetValueForKey( infostring, "protocol", va("%i", PROTOCOL_VERSION) );
|
||||
//Info_SetValueForKey( infostring, "hostname", sv_hostname->string );
|
||||
Info_SetValueForKey( infostring, "mapname", sv_mapname->string );
|
||||
Info_SetValueForKey( infostring, "clients", va("%i", count) );
|
||||
Info_SetValueForKey( infostring, "sv_maxclients", va("%i", 1) );
|
||||
|
||||
NET_OutOfBandPrint( NS_SERVER, from, "infoResponse\n%s", infostring );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
SV_ConnectionlessPacket
|
||||
|
||||
A connectionless packet has four leading 0xff
|
||||
characters to distinguish it from a game channel.
|
||||
Clients that are in the game can still send
|
||||
connectionless packets.
|
||||
=================
|
||||
*/
|
||||
static void SV_ConnectionlessPacket( netadr_t from, msg_t *msg ) {
|
||||
char *s;
|
||||
char *c;
|
||||
|
||||
MSG_BeginReading( msg );
|
||||
MSG_ReadLong( msg ); // skip the -1 marker
|
||||
|
||||
s = MSG_ReadStringLine( msg );
|
||||
|
||||
Cmd_TokenizeString( s );
|
||||
|
||||
c = Cmd_Argv(0);
|
||||
Com_DPrintf ("SV packet %s : %s\n", NET_AdrToString(from), c);
|
||||
|
||||
if (!strcmp(c,"getstatus")) {
|
||||
SVC_Status( from );
|
||||
} else if (!strcmp(c,"getinfo")) {
|
||||
SVC_Info( from );
|
||||
} else if (!strcmp(c,"connect")) {
|
||||
SV_DirectConnect( from );
|
||||
} else if (!strcmp(c,"disconnect")) {
|
||||
// if a client starts up a local server, we may see some spurious
|
||||
// server disconnect messages when their new server sees our final
|
||||
// sequenced messages to the old client
|
||||
} else {
|
||||
Com_DPrintf ("bad connectionless packet from %s:\n%s\n"
|
||||
, NET_AdrToString (from), s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//============================================================================
|
||||
|
||||
/*
|
||||
=================
|
||||
SV_ReadPackets
|
||||
=================
|
||||
*/
|
||||
void SV_PacketEvent( netadr_t from, msg_t *msg ) {
|
||||
int i;
|
||||
client_t *cl;
|
||||
int qport;
|
||||
|
||||
// check for connectionless packet (0xffffffff) first
|
||||
if ( msg->cursize >= 4 && *(int *)msg->data == -1) {
|
||||
SV_ConnectionlessPacket( from, msg );
|
||||
return;
|
||||
}
|
||||
|
||||
// read the qport out of the message so we can fix up
|
||||
// stupid address translating routers
|
||||
MSG_BeginReading( msg );
|
||||
MSG_ReadLong( msg ); // sequence number
|
||||
MSG_ReadLong( msg ); // sequence number
|
||||
qport = MSG_ReadShort( msg ) & 0xffff;
|
||||
|
||||
// find which client the message is from
|
||||
for (i=0, cl=svs.clients ; i < 1 ; i++,cl++) {
|
||||
if (cl->state == CS_FREE) {
|
||||
continue;
|
||||
}
|
||||
if ( !NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) ) {
|
||||
continue;
|
||||
}
|
||||
// it is possible to have multiple clients from a single IP
|
||||
// address, so they are differentiated by the qport variable
|
||||
if (cl->netchan.qport != qport) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// the IP port can't be used to differentiate them, because
|
||||
// some address translating routers periodically change UDP
|
||||
// port assignments
|
||||
if (cl->netchan.remoteAddress.port != from.port) {
|
||||
Com_Printf( "SV_ReadPackets: fixing up a translated port\n" );
|
||||
cl->netchan.remoteAddress.port = from.port;
|
||||
}
|
||||
|
||||
// make sure it is a valid, in sequence packet
|
||||
if (Netchan_Process(&cl->netchan, msg)) {
|
||||
// zombie clients stil neet to do the Netchan_Process
|
||||
// to make sure they don't need to retransmit the final
|
||||
// reliable message, but they don't do any other processing
|
||||
if (cl->state != CS_ZOMBIE) {
|
||||
cl->lastPacketTime = sv.time; // don't timeout
|
||||
cl->frames[ cl->netchan.incomingAcknowledged & PACKET_MASK ]
|
||||
.messageAcked = sv.time;
|
||||
SV_ExecuteClientMessage( cl, msg );
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// if we received a sequenced packet from an address we don't reckognize,
|
||||
// send an out of band disconnect packet to it
|
||||
NET_OutOfBandPrint( NS_SERVER, from, "disconnect" );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===================
|
||||
SV_CalcPings
|
||||
|
||||
Updates the cl->ping variables
|
||||
===================
|
||||
*/
|
||||
void SV_CalcPings (void) {
|
||||
int i, j;
|
||||
client_t *cl;
|
||||
int total, count;
|
||||
int delta;
|
||||
|
||||
for (i=0 ; i < 1 ; i++) {
|
||||
cl = &svs.clients[i];
|
||||
if ( cl->state != CS_ACTIVE ) {
|
||||
continue;
|
||||
}
|
||||
if ( cl->gentity->svFlags & SVF_BOT ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
total = 0;
|
||||
count = 0;
|
||||
for ( j = 0 ; j < PACKET_BACKUP ; j++ ) {
|
||||
delta = cl->frames[j].messageAcked - cl->frames[j].messageSent;
|
||||
if ( delta >= 0 ) {
|
||||
count++;
|
||||
total += delta;
|
||||
}
|
||||
}
|
||||
if (!count) {
|
||||
cl->ping = 999;
|
||||
} else {
|
||||
cl->ping = total/count;
|
||||
if ( cl->ping > 999 ) {
|
||||
cl->ping = 999;
|
||||
}
|
||||
}
|
||||
|
||||
// let the game dll know about the ping
|
||||
cl->gentity->client->ping = cl->ping;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_CheckTimeouts
|
||||
|
||||
If a packet has not been received from a client for timeout->integer
|
||||
seconds, drop the conneciton. Server time is used instead of
|
||||
realtime to avoid dropping the local client while debugging.
|
||||
|
||||
When a client is normally dropped, the client_t goes into a zombie state
|
||||
for a few seconds to make sure any final reliable message gets resent
|
||||
if necessary
|
||||
==================
|
||||
*/
|
||||
void SV_CheckTimeouts( void ) {
|
||||
int i;
|
||||
client_t *cl;
|
||||
int droppoint;
|
||||
int zombiepoint;
|
||||
|
||||
droppoint = sv.time - 1000 * sv_timeout->integer;
|
||||
zombiepoint = sv.time - 1000 * sv_zombietime->integer;
|
||||
|
||||
for (i=0,cl=svs.clients ; i < 1 ; i++,cl++) {
|
||||
// message times may be wrong across a changelevel
|
||||
if (cl->lastPacketTime > sv.time) {
|
||||
cl->lastPacketTime = sv.time;
|
||||
}
|
||||
|
||||
if (cl->state == CS_ZOMBIE
|
||||
&& cl->lastPacketTime < zombiepoint) {
|
||||
cl->state = CS_FREE; // can now be reused
|
||||
continue;
|
||||
}
|
||||
if ( cl->state >= CS_CONNECTED && cl->lastPacketTime < droppoint) {
|
||||
// wait several frames so a debugger session doesn't
|
||||
// cause a timeout
|
||||
if ( ++cl->timeoutCount > 5 ) {
|
||||
SV_DropClient (cl, "timed out");
|
||||
cl->state = CS_FREE; // don't bother with zombie state
|
||||
}
|
||||
} else {
|
||||
cl->timeoutCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_CheckPaused
|
||||
==================
|
||||
*/
|
||||
qboolean SV_CheckPaused( void ) {
|
||||
if ( !cl_paused->integer ) {
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
sv_paused->integer = 1;
|
||||
return qtrue;
|
||||
}
|
||||
|
||||
/*
|
||||
This wonderful hack is needed to avoid rendering frames until several camera related things
|
||||
have wended their way through the network. The problem is basically that the server asks the
|
||||
client where the camera is to decide what entities down to the client. However right after
|
||||
certain transitions the client tends to give a wrong answer. CGCam_Disable is one such time/
|
||||
When this happens we want to dump all rendered frame until these things have happened, in
|
||||
order:
|
||||
|
||||
0) (This state will mean that we are awaiting state 1)
|
||||
1) The server has run a frame and built a packet
|
||||
2) The client has computed a camera position
|
||||
3) The server has run a frame and built a packet
|
||||
4) The client has recieved a packet (This state also means the game is running normally).
|
||||
|
||||
We will keep track of this here:
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_Frame
|
||||
|
||||
Player movement occurs as a result of packet events, which
|
||||
happen before SV_Frame is called
|
||||
==================
|
||||
*/
|
||||
extern cvar_t *cl_newClock;
|
||||
void SV_Frame( int msec,float fractionMsec ) {
|
||||
int frameMsec;
|
||||
int startTime=0;
|
||||
|
||||
// the menu kills the server with this cvar
|
||||
if ( sv_killserver->integer ) {
|
||||
SV_Shutdown ("Server was killed.\n");
|
||||
Cvar_Set( "sv_killserver", "0" );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !com_sv_running->integer ) {
|
||||
return;
|
||||
}
|
||||
|
||||
extern void SE_CheckForLanguageUpdates(void);
|
||||
SE_CheckForLanguageUpdates(); // will fast-return else load different language if menu changed it
|
||||
|
||||
// allow pause if only the local client is connected
|
||||
if ( SV_CheckPaused() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// go ahead and let time slip if the server really hitched badly
|
||||
if ( msec > 1000 ) {
|
||||
Com_DPrintf( "SV_Frame: Truncating msec of %i to 1000\n", msec );
|
||||
msec = 1000;
|
||||
}
|
||||
|
||||
// if it isn't time for the next frame, do nothing
|
||||
if ( sv_fps->integer < 1 ) {
|
||||
Cvar_Set( "sv_fps", "10" );
|
||||
}
|
||||
frameMsec = 1000 / sv_fps->integer ;
|
||||
|
||||
sv.timeResidual += msec;
|
||||
sv.timeResidualFraction+=fractionMsec;
|
||||
if (sv.timeResidualFraction>=1.0f)
|
||||
{
|
||||
sv.timeResidualFraction-=1.0f;
|
||||
if (cl_newClock&&cl_newClock->integer)
|
||||
{
|
||||
sv.timeResidual++;
|
||||
}
|
||||
}
|
||||
if ( sv.timeResidual < frameMsec ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if time is about to hit the 32nd bit, restart the
|
||||
// level, which will force the time back to zero, rather
|
||||
// than checking for negative time wraparound everywhere.
|
||||
// 2giga-milliseconds = 23 days, so it won't be too often
|
||||
if ( sv.time > 0x70000000 ) {
|
||||
SV_Shutdown( "Restarting server due to time wrapping" );
|
||||
Com_Printf("You win. if you can play this long and not die, you deserve to win.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// update infostrings if anything has been changed
|
||||
if ( cvar_modifiedFlags & CVAR_SERVERINFO ) {
|
||||
SV_SetConfigstring( CS_SERVERINFO, Cvar_InfoString( CVAR_SERVERINFO ) );
|
||||
cvar_modifiedFlags &= ~CVAR_SERVERINFO;
|
||||
}
|
||||
if ( cvar_modifiedFlags & CVAR_SYSTEMINFO ) {
|
||||
SV_SetConfigstring( CS_SYSTEMINFO, Cvar_InfoString( CVAR_SYSTEMINFO ) );
|
||||
cvar_modifiedFlags &= ~CVAR_SYSTEMINFO;
|
||||
}
|
||||
|
||||
if ( com_speeds->integer ) {
|
||||
startTime = Sys_Milliseconds ();
|
||||
}
|
||||
|
||||
// SV_BotFrame( sv.time );
|
||||
|
||||
// run the game simulation in chunks
|
||||
while ( sv.timeResidual >= frameMsec ) {
|
||||
sv.timeResidual -= frameMsec;
|
||||
sv.time += frameMsec;
|
||||
G2API_SetTime(sv.time,G2T_SV_TIME);
|
||||
|
||||
// let everything in the world think and move
|
||||
ge->RunFrame( sv.time );
|
||||
}
|
||||
|
||||
if ( com_speeds->integer ) {
|
||||
time_game = Sys_Milliseconds () - startTime;
|
||||
}
|
||||
|
||||
SG_TestSave(); // returns immediately if not active, used for fake-save-every-cycle to test (mainly) Icarus disk code
|
||||
|
||||
// check timeouts
|
||||
SV_CheckTimeouts ();
|
||||
|
||||
// update ping based on the last known frame from all clients
|
||||
SV_CalcPings ();
|
||||
|
||||
// send messages back to the clients
|
||||
SV_SendClientMessages ();
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
|
||||
2998
code/server/sv_savegame.cpp
Normal file
2998
code/server/sv_savegame.cpp
Normal file
File diff suppressed because it is too large
Load Diff
749
code/server/sv_snapshot.cpp
Normal file
749
code/server/sv_snapshot.cpp
Normal file
@@ -0,0 +1,749 @@
|
||||
// leave this as first line for PCH reasons...
|
||||
//
|
||||
#include "../server/exe_headers.h"
|
||||
|
||||
|
||||
|
||||
#include "..\client\vmachine.h"
|
||||
#include "server.h"
|
||||
|
||||
|
||||
/*
|
||||
=============================================================================
|
||||
|
||||
Delta encode a client frame onto the network channel
|
||||
|
||||
A normal server packet will look like:
|
||||
|
||||
4 sequence number (high bit set if an oversize fragment)
|
||||
<optional reliable commands>
|
||||
1 svc_snapshot
|
||||
4 last client reliable command
|
||||
4 serverTime
|
||||
1 lastframe for delta compression
|
||||
1 snapFlags
|
||||
1 areaBytes
|
||||
<areabytes>
|
||||
<playerstate>
|
||||
<packetentities>
|
||||
|
||||
=============================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
=============
|
||||
SV_EmitPacketEntities
|
||||
|
||||
Writes a delta update of an entityState_t list to the message.
|
||||
=============
|
||||
*/
|
||||
static void SV_EmitPacketEntities( clientSnapshot_t *from, clientSnapshot_t *to, msg_t *msg ) {
|
||||
entityState_t *oldent, *newent;
|
||||
int oldindex, newindex;
|
||||
int oldnum, newnum;
|
||||
int from_num_entities;
|
||||
|
||||
// generate the delta update
|
||||
if ( !from ) {
|
||||
from_num_entities = 0;
|
||||
} else {
|
||||
from_num_entities = from->num_entities;
|
||||
}
|
||||
|
||||
newent = NULL;
|
||||
oldent = NULL;
|
||||
newindex = 0;
|
||||
oldindex = 0;
|
||||
const int num2Send = to->num_entities >= svs.numSnapshotEntities ? svs.numSnapshotEntities : to->num_entities;
|
||||
|
||||
while ( newindex < num2Send || oldindex < from_num_entities ) {
|
||||
if ( newindex >= num2Send ) {
|
||||
newnum = 9999;
|
||||
} else {
|
||||
newent = &svs.snapshotEntities[(to->first_entity+newindex) % svs.numSnapshotEntities];
|
||||
newnum = newent->number;
|
||||
}
|
||||
|
||||
if ( oldindex >= from_num_entities ) {
|
||||
oldnum = 9999;
|
||||
} else {
|
||||
oldent = &svs.snapshotEntities[(from->first_entity+oldindex) % svs.numSnapshotEntities];
|
||||
oldnum = oldent->number;
|
||||
}
|
||||
|
||||
if ( newnum == oldnum ) {
|
||||
// delta update from old position
|
||||
// because the force parm is qfalse, this will not result
|
||||
// in any bytes being emited if the entity has not changed at all
|
||||
MSG_WriteEntity(msg, newent, 0);
|
||||
oldindex++;
|
||||
newindex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( newnum < oldnum ) {
|
||||
// this is a new entity, send it from the baseline
|
||||
MSG_WriteEntity (msg, newent, 0);
|
||||
newindex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( newnum > oldnum ) {
|
||||
// the old entity isn't present in the new message
|
||||
if(oldent) {
|
||||
MSG_WriteEntity (msg, NULL, oldent->number);
|
||||
}
|
||||
oldindex++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
MSG_WriteBits( msg, (MAX_GENTITIES-1), GENTITYNUM_BITS ); // end of packetentities
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_WriteSnapshotToClient
|
||||
==================
|
||||
*/
|
||||
static void SV_WriteSnapshotToClient( client_t *client, msg_t *msg ) {
|
||||
clientSnapshot_t *frame, *oldframe;
|
||||
int lastframe;
|
||||
int snapFlags;
|
||||
|
||||
// this is the snapshot we are creating
|
||||
frame = &client->frames[ client->netchan.outgoingSequence & PACKET_MASK ];
|
||||
|
||||
// try to use a previous frame as the source for delta compressing the snapshot
|
||||
if ( client->deltaMessage <= 0 || client->state != CS_ACTIVE ) {
|
||||
// client is asking for a retransmit
|
||||
oldframe = NULL;
|
||||
lastframe = 0;
|
||||
} else if ( client->netchan.outgoingSequence - client->deltaMessage
|
||||
>= (PACKET_BACKUP - 3) ) {
|
||||
// client hasn't gotten a good message through in a long time
|
||||
Com_DPrintf ("%s: Delta request from out of date packet.\n", client->name);
|
||||
oldframe = NULL;
|
||||
lastframe = 0;
|
||||
} else {
|
||||
// we have a valid snapshot to delta from
|
||||
oldframe = &client->frames[ client->deltaMessage & PACKET_MASK ];
|
||||
lastframe = client->netchan.outgoingSequence - client->deltaMessage;
|
||||
|
||||
// the snapshot's entities may still have rolled off the buffer, though
|
||||
if ( oldframe->first_entity <= svs.nextSnapshotEntities - svs.numSnapshotEntities ) {
|
||||
Com_DPrintf ("%s: Delta request from out of date entities.\n", client->name);
|
||||
oldframe = NULL;
|
||||
lastframe = 0;
|
||||
}
|
||||
}
|
||||
|
||||
MSG_WriteByte (msg, svc_snapshot);
|
||||
|
||||
// let the client know which reliable clientCommands we have received
|
||||
MSG_WriteLong( msg, client->lastClientCommand );
|
||||
|
||||
// send over the current server time so the client can drift
|
||||
// its view of time to try to match
|
||||
MSG_WriteLong (msg, sv.time);
|
||||
|
||||
// we must write a message number, because recorded demos won't have
|
||||
// the same network message sequences
|
||||
MSG_WriteLong (msg, client->netchan.outgoingSequence );
|
||||
MSG_WriteByte (msg, lastframe); // what we are delta'ing from
|
||||
MSG_WriteLong (msg, client->cmdNum); // we have executed up to here
|
||||
|
||||
snapFlags = client->rateDelayed | ( client->droppedCommands << 1 );
|
||||
client->droppedCommands = 0;
|
||||
|
||||
MSG_WriteByte (msg, snapFlags);
|
||||
|
||||
// send over the areabits
|
||||
MSG_WriteByte (msg, frame->areabytes);
|
||||
MSG_WriteData (msg, frame->areabits, frame->areabytes);
|
||||
|
||||
// delta encode the playerstate
|
||||
if ( oldframe ) {
|
||||
MSG_WriteDeltaPlayerstate( msg, &oldframe->ps, &frame->ps );
|
||||
} else {
|
||||
MSG_WriteDeltaPlayerstate( msg, NULL, &frame->ps );
|
||||
}
|
||||
|
||||
// delta encode the entities
|
||||
SV_EmitPacketEntities (oldframe, frame, msg);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_UpdateServerCommandsToClient
|
||||
|
||||
(re)send all server commands the client hasn't acknowledged yet
|
||||
==================
|
||||
*/
|
||||
static void SV_UpdateServerCommandsToClient( client_t *client, msg_t *msg ) {
|
||||
int i;
|
||||
|
||||
// write any unacknowledged serverCommands
|
||||
for ( i = client->reliableAcknowledge + 1 ; i <= client->reliableSequence ; i++ ) {
|
||||
MSG_WriteByte( msg, svc_serverCommand );
|
||||
MSG_WriteLong( msg, i );
|
||||
MSG_WriteString( msg, client->reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=============================================================================
|
||||
|
||||
Build a client snapshot structure
|
||||
|
||||
=============================================================================
|
||||
*/
|
||||
|
||||
#define MAX_SNAPSHOT_ENTITIES 1024
|
||||
typedef struct {
|
||||
int numSnapshotEntities;
|
||||
int snapshotEntities[MAX_SNAPSHOT_ENTITIES];
|
||||
} snapshotEntityNumbers_t;
|
||||
|
||||
/*
|
||||
=======================
|
||||
SV_QsortEntityNumbers
|
||||
=======================
|
||||
*/
|
||||
static int SV_QsortEntityNumbers( const void *a, const void *b ) {
|
||||
int *ea, *eb;
|
||||
|
||||
ea = (int *)a;
|
||||
eb = (int *)b;
|
||||
|
||||
if ( *ea == *eb ) {
|
||||
Com_Error( ERR_DROP, "SV_QsortEntityStates: duplicated entity" );
|
||||
}
|
||||
|
||||
if ( *ea < *eb ) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_AddEntToSnapshot
|
||||
===============
|
||||
*/
|
||||
static void SV_AddEntToSnapshot( svEntity_t *svEnt, gentity_t *gEnt, snapshotEntityNumbers_t *eNums ) {
|
||||
// if we have already added this entity to this snapshot, don't add again
|
||||
if ( svEnt->snapshotCounter == sv.snapshotCounter ) {
|
||||
return;
|
||||
}
|
||||
svEnt->snapshotCounter = sv.snapshotCounter;
|
||||
|
||||
// if we are full, silently discard entities
|
||||
if ( eNums->numSnapshotEntities == MAX_SNAPSHOT_ENTITIES ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sv.snapshotCounter &1 && eNums->numSnapshotEntities == svs.numSnapshotEntities-1)
|
||||
{ //we're full, and about to wrap around and stomp ents, so half the time send the first set without stomping.
|
||||
return;
|
||||
}
|
||||
|
||||
eNums->snapshotEntities[ eNums->numSnapshotEntities ] = gEnt->s.number;
|
||||
eNums->numSnapshotEntities++;
|
||||
}
|
||||
|
||||
//rww - bg_public.h won't cooperate in here
|
||||
#define EF_PERMANENT 0x00080000
|
||||
|
||||
float sv_sightRangeForLevel[6] =
|
||||
{
|
||||
0,//FORCE_LEVEL_0
|
||||
1024.f, //FORCE_LEVEL_1
|
||||
2048.0f,//FORCE_LEVEL_2
|
||||
4096.0f,//FORCE_LEVEL_3
|
||||
4096.0f,//FORCE_LEVEL_4
|
||||
4096.0f//FORCE_LEVEL_5
|
||||
};
|
||||
|
||||
qboolean SV_PlayerCanSeeEnt( gentity_t *ent, int sightLevel )
|
||||
{//return true if this ent is in view
|
||||
//NOTE: this is similar to the func CG_PlayerCanSeeCent in cg_players
|
||||
vec3_t viewOrg, viewAngles, viewFwd, dir2Ent;
|
||||
if ( !ent )
|
||||
{
|
||||
return qfalse;
|
||||
}
|
||||
if ( VM_Call( CG_CAMERA_POS, viewOrg))
|
||||
{
|
||||
if ( VM_Call( CG_CAMERA_ANG, viewAngles))
|
||||
{
|
||||
float dot = 0.25f;//1.0f;
|
||||
float range = sv_sightRangeForLevel[sightLevel];
|
||||
|
||||
VectorSubtract( ent->currentOrigin, viewOrg, dir2Ent );
|
||||
float entDist = VectorNormalize( dir2Ent );
|
||||
|
||||
if ( (ent->s.eFlags&EF_FORCE_VISIBLE) )
|
||||
{//no dist check on them?
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( entDist < 128.0f )
|
||||
{//can always see them if they're really close
|
||||
return qtrue;
|
||||
}
|
||||
|
||||
if ( entDist > range )
|
||||
{//too far away to see them
|
||||
return qfalse;
|
||||
}
|
||||
}
|
||||
|
||||
dot += (0.99f-dot)*entDist/range;//the farther away they are, the more in front they have to be
|
||||
|
||||
AngleVectors( viewAngles, viewFwd, NULL, NULL );
|
||||
if ( DotProduct( viewFwd, dir2Ent ) < dot )
|
||||
{
|
||||
return qfalse;
|
||||
}
|
||||
return qtrue;
|
||||
}
|
||||
}
|
||||
return qfalse;
|
||||
}
|
||||
/*
|
||||
===============
|
||||
SV_AddEntitiesVisibleFromPoint
|
||||
===============
|
||||
*/
|
||||
static void SV_AddEntitiesVisibleFromPoint( vec3_t origin, clientSnapshot_t *frame,
|
||||
snapshotEntityNumbers_t *eNums, qboolean portal ) {
|
||||
int e, i;
|
||||
gentity_t *ent;
|
||||
svEntity_t *svEnt;
|
||||
int l;
|
||||
int clientarea, clientcluster;
|
||||
int leafnum;
|
||||
int c_fullsend;
|
||||
const byte *clientpvs;
|
||||
const byte *bitvector;
|
||||
qboolean sightOn = qfalse;
|
||||
|
||||
// during an error shutdown message we may need to transmit
|
||||
// the shutdown message after the server has shutdown, so
|
||||
// specfically check for it
|
||||
if ( !sv.state ) {
|
||||
return;
|
||||
}
|
||||
|
||||
leafnum = CM_PointLeafnum (origin);
|
||||
clientarea = CM_LeafArea (leafnum);
|
||||
clientcluster = CM_LeafCluster (leafnum);
|
||||
|
||||
// calculate the visible areas
|
||||
frame->areabytes = CM_WriteAreaBits( frame->areabits, clientarea );
|
||||
|
||||
clientpvs = CM_ClusterPVS (clientcluster);
|
||||
|
||||
c_fullsend = 0;
|
||||
|
||||
if ( !portal )
|
||||
{//not if this if through a portal...??? James said to do this...
|
||||
if ( (frame->ps.forcePowersActive&(1<<FP_SEE)) )
|
||||
{
|
||||
sightOn = qtrue;
|
||||
}
|
||||
}
|
||||
|
||||
for ( e = 0 ; e < ge->num_entities ; e++ ) {
|
||||
ent = SV_GentityNum(e);
|
||||
|
||||
if (!ent->inuse) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ent->s.eFlags & EF_PERMANENT)
|
||||
{ // he's permanent, so don't send him down!
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ent->s.number != e) {
|
||||
Com_DPrintf ("FIXING ENT->S.NUMBER!!!\n");
|
||||
ent->s.number = e;
|
||||
}
|
||||
|
||||
// never send entities that aren't linked in
|
||||
if ( !ent->linked ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// entities can be flagged to explicitly not be sent to the client
|
||||
if ( ent->svFlags & SVF_NOCLIENT ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
svEnt = SV_SvEntityForGentity( ent );
|
||||
|
||||
// don't double add an entity through portals
|
||||
if ( svEnt->snapshotCounter == sv.snapshotCounter ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// broadcast entities are always sent, and so is the main player so we don't see noclip weirdness
|
||||
if ( ent->svFlags & SVF_BROADCAST || !e) {
|
||||
SV_AddEntToSnapshot( svEnt, ent, eNums );
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ent->s.isPortalEnt)
|
||||
{ //rww - portal entities are always sent as well
|
||||
SV_AddEntToSnapshot( svEnt, ent, eNums );
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( sightOn )
|
||||
{//force sight is on, sees through portals, so draw them always if in radius
|
||||
if ( SV_PlayerCanSeeEnt( ent, frame->ps.forcePowerLevel[FP_SEE] ) )
|
||||
{//entity is visible
|
||||
SV_AddEntToSnapshot( svEnt, ent, eNums );
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// ignore if not touching a PV leaf
|
||||
// check area
|
||||
if ( !CM_AreasConnected( clientarea, svEnt->areanum ) ) {
|
||||
// doors can legally straddle two areas, so
|
||||
// we may need to check another one
|
||||
if ( !CM_AreasConnected( clientarea, svEnt->areanum2 ) ) {
|
||||
continue; // blocked by a door
|
||||
}
|
||||
}
|
||||
|
||||
bitvector = clientpvs;
|
||||
|
||||
// check individual leafs
|
||||
if ( !svEnt->numClusters ) {
|
||||
continue;
|
||||
}
|
||||
l = 0;
|
||||
#ifdef _XBOX
|
||||
if(bitvector) {
|
||||
#endif
|
||||
for ( i=0 ; i < svEnt->numClusters ; i++ ) {
|
||||
l = svEnt->clusternums[i];
|
||||
if ( bitvector[l >> 3] & (1 << (l&7) ) ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
#ifdef _XBOX
|
||||
}
|
||||
#endif
|
||||
|
||||
// if we haven't found it to be visible,
|
||||
// check overflow clusters that coudln't be stored
|
||||
#ifdef _XBOX
|
||||
if ( bitvector && i == svEnt->numClusters ) {
|
||||
#else
|
||||
if ( i == svEnt->numClusters ) {
|
||||
#endif
|
||||
if ( svEnt->lastCluster ) {
|
||||
for ( ; l <= svEnt->lastCluster ; l++ ) {
|
||||
if ( bitvector[l >> 3] & (1 << (l&7) ) ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( l == svEnt->lastCluster ) {
|
||||
continue; // not visible
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// add it
|
||||
SV_AddEntToSnapshot( svEnt, ent, eNums );
|
||||
|
||||
// if its a portal entity, add everything visible from its camera position
|
||||
if ( ent->svFlags & SVF_PORTAL ) {
|
||||
SV_AddEntitiesVisibleFromPoint( ent->s.origin2, frame, eNums, qtrue );
|
||||
#ifdef _XBOX
|
||||
//Must get clientpvs again since above call destroyed it.
|
||||
clientpvs = CM_ClusterPVS (clientcluster);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=============
|
||||
SV_BuildClientSnapshot
|
||||
|
||||
Decides which entities are going to be visible to the client, and
|
||||
copies off the playerstate and areabits.
|
||||
|
||||
This properly handles multiple recursive portals, but the render
|
||||
currently doesn't.
|
||||
|
||||
For viewing through other player's eyes, clent can be something other than client->gentity
|
||||
=============
|
||||
*/
|
||||
static clientSnapshot_t *SV_BuildClientSnapshot( client_t *client ) {
|
||||
vec3_t org;
|
||||
clientSnapshot_t *frame;
|
||||
snapshotEntityNumbers_t entityNumbers;
|
||||
int i;
|
||||
gentity_t *ent;
|
||||
entityState_t *state;
|
||||
gentity_t *clent;
|
||||
|
||||
// bump the counter used to prevent double adding
|
||||
sv.snapshotCounter++;
|
||||
|
||||
// this is the frame we are creating
|
||||
frame = &client->frames[ client->netchan.outgoingSequence & PACKET_MASK ];
|
||||
|
||||
// clear everything in this snapshot
|
||||
entityNumbers.numSnapshotEntities = 0;
|
||||
memset( frame->areabits, 0, sizeof( frame->areabits ) );
|
||||
|
||||
clent = client->gentity;
|
||||
if ( !clent ) {
|
||||
return frame;
|
||||
}
|
||||
|
||||
// grab the current playerState_t
|
||||
frame->ps = *clent->client;
|
||||
|
||||
// this stops the main client entity playerstate from being sent across, which has the effect of breaking
|
||||
// looping sounds for the main client. So I took it out.
|
||||
/* {
|
||||
int clientNum;
|
||||
svEntity_t *svEnt;
|
||||
clientNum = frame->ps.clientNum;
|
||||
if ( clientNum < 0 || clientNum >= MAX_GENTITIES ) {
|
||||
Com_Error( ERR_DROP, "SV_SvEntityForGentity: bad gEnt" );
|
||||
}
|
||||
svEnt = &sv.svEntities[ clientNum ];
|
||||
// never send client's own entity, because it can
|
||||
// be regenerated from the playerstate
|
||||
svEnt->snapshotCounter = sv.snapshotCounter;
|
||||
}
|
||||
*/
|
||||
// find the client's viewpoint
|
||||
|
||||
//if in camera mode use camera position instead
|
||||
if ( VM_Call( CG_CAMERA_POS, org))
|
||||
{
|
||||
//org[2] += clent->client->viewheight;
|
||||
}
|
||||
else
|
||||
{
|
||||
VectorCopy( clent->client->origin, org );
|
||||
org[2] += clent->client->viewheight;
|
||||
|
||||
//============
|
||||
// need to account for lean, or areaportal doors don't draw properly... -slc
|
||||
if (frame->ps.leanofs != 0)
|
||||
{
|
||||
vec3_t right;
|
||||
//add leaning offset
|
||||
vec3_t v3ViewAngles;
|
||||
VectorCopy(clent->client->viewangles, v3ViewAngles);
|
||||
v3ViewAngles[2] += (float)frame->ps.leanofs/2;
|
||||
AngleVectors(v3ViewAngles, NULL, right, NULL);
|
||||
VectorMA(org, (float)frame->ps.leanofs, right, org);
|
||||
}
|
||||
//============
|
||||
}
|
||||
VectorCopy( org, frame->ps.serverViewOrg );
|
||||
VectorCopy( org, clent->client->serverViewOrg );
|
||||
|
||||
// add all the entities directly visible to the eye, which
|
||||
// may include portal entities that merge other viewpoints
|
||||
SV_AddEntitiesVisibleFromPoint( org, frame, &entityNumbers, qfalse );
|
||||
/*
|
||||
//was in here for debugging- print list of all entities in snapshot when you go over the limit
|
||||
if ( entityNumbers.numSnapshotEntities >= 256 )
|
||||
{
|
||||
for ( int xxx = 0; xxx < entityNumbers.numSnapshotEntities; xxx++ )
|
||||
{
|
||||
Com_Printf("%d - ", xxx );
|
||||
ge->PrintEntClassname( entityNumbers.snapshotEntities[xxx] );
|
||||
}
|
||||
}
|
||||
else if ( entityNumbers.numSnapshotEntities >= 200 )
|
||||
{
|
||||
Com_Printf(S_COLOR_RED"%d snapshot entities!", entityNumbers.numSnapshotEntities );
|
||||
}
|
||||
else if ( entityNumbers.numSnapshotEntities >= 128 )
|
||||
{
|
||||
Com_Printf(S_COLOR_YELLOW"%d snapshot entities", entityNumbers.numSnapshotEntities );
|
||||
}
|
||||
*/
|
||||
|
||||
// if there were portals visible, there may be out of order entities
|
||||
// in the list which will need to be resorted for the delta compression
|
||||
// to work correctly. This also catches the error condition
|
||||
// of an entity being included twice.
|
||||
qsort( entityNumbers.snapshotEntities, entityNumbers.numSnapshotEntities,
|
||||
sizeof( entityNumbers.snapshotEntities[0] ), SV_QsortEntityNumbers );
|
||||
|
||||
// now that all viewpoint's areabits have been OR'd together, invert
|
||||
// all of them to make it a mask vector, which is what the renderer wants
|
||||
for ( i = 0 ; i < MAX_MAP_AREA_BYTES/4 ; i++ ) {
|
||||
((int *)frame->areabits)[i] = ((int *)frame->areabits)[i] ^ -1;
|
||||
}
|
||||
|
||||
// copy the entity states out
|
||||
frame->num_entities = 0;
|
||||
frame->first_entity = svs.nextSnapshotEntities;
|
||||
for ( i = 0 ; i < entityNumbers.numSnapshotEntities ; i++ ) {
|
||||
ent = SV_GentityNum(entityNumbers.snapshotEntities[i]);
|
||||
state = &svs.snapshotEntities[svs.nextSnapshotEntities % svs.numSnapshotEntities];
|
||||
*state = ent->s;
|
||||
svs.nextSnapshotEntities++;
|
||||
frame->num_entities++;
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=======================
|
||||
SV_SendMessageToClient
|
||||
|
||||
Called by SV_SendClientSnapshot and SV_SendClientGameState
|
||||
=======================
|
||||
*/
|
||||
#define HEADER_RATE_BYTES 48 // include our header, IP header, and some overhead
|
||||
void SV_SendMessageToClient( msg_t *msg, client_t *client ) {
|
||||
int rateMsec;
|
||||
|
||||
// record information about the message
|
||||
client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSize = msg->cursize;
|
||||
client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSent = sv.time;
|
||||
|
||||
// send the datagram
|
||||
Netchan_Transmit( &client->netchan, msg->cursize, msg->data );
|
||||
|
||||
// set nextSnapshotTime based on rate and requested number of updates
|
||||
|
||||
// local clients get snapshots every frame (FIXME: also treat LAN clients)
|
||||
if ( client->netchan.remoteAddress.type == NA_LOOPBACK ) {
|
||||
client->nextSnapshotTime = sv.time - 1;
|
||||
return;
|
||||
}
|
||||
|
||||
// normal rate / snapshotMsec calculation
|
||||
rateMsec = ( msg->cursize + HEADER_RATE_BYTES ) * 1000 / client->rate;
|
||||
if ( rateMsec < client->snapshotMsec ) {
|
||||
rateMsec = client->snapshotMsec;
|
||||
client->rateDelayed = qfalse;
|
||||
} else {
|
||||
client->rateDelayed = qtrue;
|
||||
}
|
||||
|
||||
client->nextSnapshotTime = sv.time + rateMsec;
|
||||
|
||||
// if we haven't gotten a message from the client in over a second, we will
|
||||
// drop to only sending one snapshot a second until they timeout
|
||||
if ( sv.time - client->lastPacketTime > 1000 || client->state != CS_ACTIVE ) {
|
||||
if ( client->nextSnapshotTime < sv.time + 1000 ) {
|
||||
client->nextSnapshotTime = sv.time + 1000;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
=======================
|
||||
SV_SendClientEmptyMessage
|
||||
|
||||
This is just an empty message so that we can tell if
|
||||
the client dropped the gamestate that went out before
|
||||
=======================
|
||||
*/
|
||||
void SV_SendClientEmptyMessage( client_t *client ) {
|
||||
msg_t msg;
|
||||
byte buffer[10];
|
||||
|
||||
MSG_Init( &msg, buffer, sizeof( buffer ) );
|
||||
SV_SendMessageToClient( &msg, client );
|
||||
}
|
||||
|
||||
/*
|
||||
=======================
|
||||
SV_SendClientSnapshot
|
||||
=======================
|
||||
*/
|
||||
void SV_SendClientSnapshot( client_t *client ) {
|
||||
byte msg_buf[MAX_MSGLEN];
|
||||
msg_t msg;
|
||||
|
||||
// build the snapshot
|
||||
SV_BuildClientSnapshot( client );
|
||||
|
||||
// bots need to have their snapshots build, but
|
||||
// the query them directly without needing to be sent
|
||||
if ( client->gentity && client->gentity->svFlags & SVF_BOT ) {
|
||||
return;
|
||||
}
|
||||
|
||||
MSG_Init (&msg, msg_buf, sizeof(msg_buf));
|
||||
msg.allowoverflow = qtrue;
|
||||
|
||||
// (re)send any reliable server commands
|
||||
SV_UpdateServerCommandsToClient( client, &msg );
|
||||
|
||||
// send over all the relevant entityState_t
|
||||
// and the playerState_t
|
||||
SV_WriteSnapshotToClient( client, &msg );
|
||||
|
||||
// check for overflow
|
||||
if ( msg.overflowed ) {
|
||||
Com_Printf ("WARNING: msg overflowed for %s\n", client->name);
|
||||
MSG_Clear (&msg);
|
||||
}
|
||||
|
||||
SV_SendMessageToClient( &msg, client );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=======================
|
||||
SV_SendClientMessages
|
||||
=======================
|
||||
*/
|
||||
void SV_SendClientMessages( void ) {
|
||||
int i;
|
||||
client_t *c;
|
||||
|
||||
// send a message to each connected client
|
||||
for (i=0, c = svs.clients ; i < 1 ; i++, c++) {
|
||||
if (!c->state) {
|
||||
continue; // not connected
|
||||
}
|
||||
|
||||
if ( sv.time < c->nextSnapshotTime ) {
|
||||
continue; // not time yet
|
||||
}
|
||||
|
||||
if ( c->state != CS_ACTIVE ) {
|
||||
if ( c->state != CS_ZOMBIE ) {
|
||||
SV_SendClientEmptyMessage( c );
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
SV_SendClientSnapshot( c );
|
||||
}
|
||||
}
|
||||
|
||||
1012
code/server/sv_world.cpp
Normal file
1012
code/server/sv_world.cpp
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user