Initial commit.

This commit is contained in:
Jim Gray
2013-04-04 14:32:05 -07:00
parent ba5c81da32
commit d71d53e8ec
2180 changed files with 1393544 additions and 1 deletions

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

749
code/server/sv_snapshot.cpp Normal file
View 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

File diff suppressed because it is too large Load Diff