The Quake III Arena sources as originally released under the GPL license on August 20, 2005.
This commit is contained in:
commit
dbe4ddb103
1409 changed files with 806066 additions and 0 deletions
404
code/server/server.h
Normal file
404
code/server/server.h
Normal file
|
@ -0,0 +1,404 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code is distributed in the hope that it will be
|
||||
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Foobar; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
// server.h
|
||||
|
||||
#include "../game/q_shared.h"
|
||||
#include "../qcommon/qcommon.h"
|
||||
#include "../game/g_public.h"
|
||||
#include "../game/bg_public.h"
|
||||
|
||||
//=============================================================================
|
||||
|
||||
#define PERS_SCORE 0 // !!! MUST NOT CHANGE, SERVER AND
|
||||
// GAME BOTH REFERENCE !!!
|
||||
|
||||
#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
|
||||
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
|
||||
} 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;
|
||||
qboolean restarting; // if true, send configstring changes during SS_LOADING
|
||||
int serverId; // changes each server start
|
||||
int restartedServerId; // serverId before a map_restart
|
||||
int checksumFeed; // the feed key that we use to compute the pure checksum strings
|
||||
// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475
|
||||
// the serverId associated with the current checksumFeed (always <= serverId)
|
||||
int checksumFeedServerId;
|
||||
int snapshotCounter; // incremented for each snapshot built
|
||||
int timeResidual; // <= 1000 / sv_frame->value
|
||||
int nextFrameTime; // when time > nextFrameTime, process world
|
||||
struct cmodel_s *models[MAX_MODELS];
|
||||
char *configstrings[MAX_CONFIGSTRINGS];
|
||||
svEntity_t svEntities[MAX_GENTITIES];
|
||||
|
||||
char *entityParsePoint; // used during game VM init
|
||||
|
||||
// the game virtual machine will update these on init and changes
|
||||
sharedEntity_t *gentities;
|
||||
int gentitySize;
|
||||
int num_entities; // current number, <= MAX_GENTITIES
|
||||
|
||||
playerState_t *gameClients;
|
||||
int gameClientSize; // will be > sizeof(playerState_t) due to game private data
|
||||
|
||||
int restartTime;
|
||||
} 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 netchan_buffer_s {
|
||||
msg_t msg;
|
||||
byte msgBuffer[MAX_MSGLEN];
|
||||
struct netchan_buffer_s *next;
|
||||
} netchan_buffer_t;
|
||||
|
||||
typedef struct client_s {
|
||||
clientState_t state;
|
||||
char userinfo[MAX_INFO_STRING]; // name, etc
|
||||
|
||||
char reliableCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS];
|
||||
int reliableSequence; // last added reliable message, not necesarily sent or acknowledged yet
|
||||
int reliableAcknowledge; // last acknowledged reliable message
|
||||
int reliableSent; // last sent reliable message, not necesarily acknowledged yet
|
||||
int messageAcknowledge;
|
||||
|
||||
int gamestateMessageNum; // netchan->outgoingSequence of gamestate
|
||||
int challenge;
|
||||
|
||||
usercmd_t lastUsercmd;
|
||||
int lastMessageNum; // for delta compression
|
||||
int lastClientCommand; // reliable client message sequence
|
||||
char lastClientCommandString[MAX_STRING_CHARS];
|
||||
sharedEntity_t *gentity; // SV_GentityNum(clientnum)
|
||||
char name[MAX_NAME_LENGTH]; // extracted from userinfo, high bits masked
|
||||
|
||||
// downloading
|
||||
char downloadName[MAX_QPATH]; // if not empty string, we are downloading
|
||||
fileHandle_t download; // file being downloaded
|
||||
int downloadSize; // total bytes (can't use EOF because of paks)
|
||||
int downloadCount; // bytes sent
|
||||
int downloadClientBlock; // last block we sent to the client, awaiting ack
|
||||
int downloadCurrentBlock; // current block number
|
||||
int downloadXmitBlock; // last block we xmited
|
||||
unsigned char *downloadBlocks[MAX_DOWNLOAD_WINDOW]; // the buffers for the download blocks
|
||||
int downloadBlockSize[MAX_DOWNLOAD_WINDOW];
|
||||
qboolean downloadEOF; // We have sent the EOF block
|
||||
int downloadSendTime; // time we last got an ack from the client
|
||||
|
||||
int deltaMessage; // frame last client usercmd message
|
||||
int nextReliableTime; // svs.time when another reliable command will be allowed
|
||||
int lastPacketTime; // svs.time when packet was last received
|
||||
int lastConnectTime; // svs.time when connection started
|
||||
int nextSnapshotTime; // send another snapshot when svs.time >= nextSnapshotTime
|
||||
qboolean rateDelayed; // true if nextSnapshotTime was set based on rate instead of snapshotMsec
|
||||
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
|
||||
int pureAuthentic;
|
||||
qboolean gotCP; // TTimo - additional flag to distinguish between a bad pure checksum, and no cp command at all
|
||||
netchan_t netchan;
|
||||
// TTimo
|
||||
// queuing outgoing fragmented messages to send them properly, without udp packet bursts
|
||||
// in case large fragmented messages are stacking up
|
||||
// buffer them into this queue, and hand them out to netchan as needed
|
||||
netchan_buffer_t *netchan_start_queue;
|
||||
netchan_buffer_t **netchan_end_queue;
|
||||
} client_t;
|
||||
|
||||
//=============================================================================
|
||||
|
||||
|
||||
// MAX_CHALLENGES is made large to prevent a denial
|
||||
// of service attack that could cycle all of them
|
||||
// out before legitimate users connected
|
||||
#define MAX_CHALLENGES 1024
|
||||
|
||||
#define AUTHORIZE_TIMEOUT 5000
|
||||
|
||||
typedef struct {
|
||||
netadr_t adr;
|
||||
int challenge;
|
||||
int time; // time the last packet was sent to the autherize server
|
||||
int pingTime; // time the challenge response was sent to client
|
||||
int firstTime; // time the adr was first used, for authorize timeout checks
|
||||
qboolean connected;
|
||||
} challenge_t;
|
||||
|
||||
|
||||
#define MAX_MASTERS 8 // max recipients for heartbeat packets
|
||||
|
||||
|
||||
// this structure will be cleared only when the game dll changes
|
||||
typedef struct {
|
||||
qboolean initialized; // sv_init has completed
|
||||
|
||||
int time; // will be strictly increasing across level changes
|
||||
|
||||
int snapFlagServerBit; // ^= SNAPFLAG_SERVERCOUNT every SV_SpawnServer()
|
||||
|
||||
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;
|
||||
challenge_t challenges[MAX_CHALLENGES]; // to prevent invalid IPs from connecting
|
||||
netadr_t redirectAddress; // for rcon return messages
|
||||
|
||||
netadr_t authorizeAddress; // for rcon return messages
|
||||
} serverStatic_t;
|
||||
|
||||
//=============================================================================
|
||||
|
||||
extern serverStatic_t svs; // persistant server info across maps
|
||||
extern server_t sv; // cleared each map
|
||||
extern vm_t *gvm; // game virtual machine
|
||||
|
||||
#define MAX_MASTER_SERVERS 5
|
||||
|
||||
extern cvar_t *sv_fps;
|
||||
extern cvar_t *sv_timeout;
|
||||
extern cvar_t *sv_zombietime;
|
||||
extern cvar_t *sv_rconPassword;
|
||||
extern cvar_t *sv_privatePassword;
|
||||
extern cvar_t *sv_allowDownload;
|
||||
extern cvar_t *sv_maxclients;
|
||||
|
||||
extern cvar_t *sv_privateClients;
|
||||
extern cvar_t *sv_hostname;
|
||||
extern cvar_t *sv_master[MAX_MASTER_SERVERS];
|
||||
extern cvar_t *sv_reconnectlimit;
|
||||
extern cvar_t *sv_showloss;
|
||||
extern cvar_t *sv_padPackets;
|
||||
extern cvar_t *sv_killserver;
|
||||
extern cvar_t *sv_mapname;
|
||||
extern cvar_t *sv_mapChecksum;
|
||||
extern cvar_t *sv_serverid;
|
||||
extern cvar_t *sv_maxRate;
|
||||
extern cvar_t *sv_minPing;
|
||||
extern cvar_t *sv_maxPing;
|
||||
extern cvar_t *sv_gametype;
|
||||
extern cvar_t *sv_pure;
|
||||
extern cvar_t *sv_floodProtect;
|
||||
extern cvar_t *sv_lanForceRate;
|
||||
extern cvar_t *sv_strictAuth;
|
||||
|
||||
//===========================================================
|
||||
|
||||
//
|
||||
// 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);
|
||||
|
||||
|
||||
void SV_MasterHeartbeat (void);
|
||||
void SV_MasterShutdown (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_ChangeMaxClients( void );
|
||||
void SV_SpawnServer( char *server, qboolean killBots );
|
||||
|
||||
|
||||
|
||||
//
|
||||
// sv_client.c
|
||||
//
|
||||
void SV_GetChallenge( netadr_t from );
|
||||
|
||||
void SV_DirectConnect( netadr_t from );
|
||||
|
||||
void SV_AuthorizeIpPacket( 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 );
|
||||
void SV_DropClient( client_t *drop, const char *reason );
|
||||
|
||||
void SV_ExecuteClientCommand( client_t *cl, const char *s, qboolean clientOK );
|
||||
void SV_ClientThink (client_t *cl, usercmd_t *cmd);
|
||||
|
||||
void SV_WriteDownloadToClient( client_t *cl , msg_t *msg );
|
||||
|
||||
//
|
||||
// sv_ccmds.c
|
||||
//
|
||||
void SV_Heartbeat_f( void );
|
||||
|
||||
//
|
||||
// sv_snapshot.c
|
||||
//
|
||||
void SV_AddServerCommand( client_t *client, const char *cmd );
|
||||
void SV_UpdateServerCommandsToClient( client_t *client, msg_t *msg );
|
||||
void SV_WriteFrameToClient (client_t *client, msg_t *msg);
|
||||
void SV_SendMessageToClient( msg_t *msg, client_t *client );
|
||||
void SV_SendClientMessages( void );
|
||||
void SV_SendClientSnapshot( client_t *client );
|
||||
|
||||
//
|
||||
// sv_game.c
|
||||
//
|
||||
int SV_NumForGentity( sharedEntity_t *ent );
|
||||
sharedEntity_t *SV_GentityNum( int num );
|
||||
playerState_t *SV_GameClientNum( int num );
|
||||
svEntity_t *SV_SvEntityForGentity( sharedEntity_t *gEnt );
|
||||
sharedEntity_t *SV_GEntityForSvEntity( svEntity_t *svEnt );
|
||||
void SV_InitGameProgs ( void );
|
||||
void SV_ShutdownGameProgs ( void );
|
||||
void SV_RestartGameProgs( void );
|
||||
qboolean SV_inPVS (const vec3_t p1, const vec3_t p2);
|
||||
|
||||
//
|
||||
// sv_bot.c
|
||||
//
|
||||
void SV_BotFrame( int time );
|
||||
int SV_BotAllocateClient(void);
|
||||
void SV_BotFreeClient( int clientNum );
|
||||
|
||||
void SV_BotInitCvars(void);
|
||||
int SV_BotLibSetup( void );
|
||||
int SV_BotLibShutdown( void );
|
||||
int SV_BotGetSnapshotEntity( int client, int ent );
|
||||
int SV_BotGetConsoleMessage( int client, char *buf, int size );
|
||||
|
||||
int BotImport_DebugPolygonCreate(int color, int numPoints, vec3_t *points);
|
||||
void BotImport_DebugPolygonDelete(int id);
|
||||
|
||||
//============================================================
|
||||
//
|
||||
// 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( sharedEntity_t *ent );
|
||||
// call before removing an entity, and before trying to move one,
|
||||
// so it doesn't clip against itself
|
||||
|
||||
void SV_LinkEntity( sharedEntity_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 sharedEntity_t *ent );
|
||||
|
||||
|
||||
void SV_SectorList_f( void );
|
||||
|
||||
|
||||
int SV_AreaEntities( const vec3_t mins, const vec3_t maxs, int *entityList, int maxcount );
|
||||
// fills in a table of entity numbers 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.
|
||||
|
||||
|
||||
void SV_Trace( trace_t *results, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask, int capsule );
|
||||
// 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)
|
||||
|
||||
|
||||
void SV_ClipToEntity( trace_t *trace, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int entityNum, int contentmask, int capsule );
|
||||
// clip to a specific entity
|
||||
|
||||
//
|
||||
// sv_net_chan.c
|
||||
//
|
||||
void SV_Netchan_Transmit( client_t *client, msg_t *msg);
|
||||
void SV_Netchan_TransmitNextFragment( client_t *client );
|
||||
qboolean SV_Netchan_Process( client_t *client, msg_t *msg );
|
||||
|
635
code/server/sv_bot.c
Normal file
635
code/server/sv_bot.c
Normal file
|
@ -0,0 +1,635 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code is distributed in the hope that it will be
|
||||
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Foobar; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
// sv_bot.c
|
||||
|
||||
#include "server.h"
|
||||
#include "../game/botlib.h"
|
||||
|
||||
typedef struct bot_debugpoly_s
|
||||
{
|
||||
int inuse;
|
||||
int color;
|
||||
int numPoints;
|
||||
vec3_t points[128];
|
||||
} bot_debugpoly_t;
|
||||
|
||||
static bot_debugpoly_t *debugpolygons;
|
||||
int bot_maxdebugpolys;
|
||||
|
||||
extern botlib_export_t *botlib_export;
|
||||
int bot_enable;
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_BotAllocateClient
|
||||
==================
|
||||
*/
|
||||
int SV_BotAllocateClient(void) {
|
||||
int i;
|
||||
client_t *cl;
|
||||
|
||||
// find a client slot
|
||||
for ( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) {
|
||||
if ( cl->state == CS_FREE ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( i == sv_maxclients->integer ) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cl->gentity = SV_GentityNum( i );
|
||||
cl->gentity->s.number = i;
|
||||
cl->state = CS_ACTIVE;
|
||||
cl->lastPacketTime = svs.time;
|
||||
cl->netchan.remoteAddress.type = NA_BOT;
|
||||
cl->rate = 16384;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_BotFreeClient
|
||||
==================
|
||||
*/
|
||||
void SV_BotFreeClient( int clientNum ) {
|
||||
client_t *cl;
|
||||
|
||||
if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) {
|
||||
Com_Error( ERR_DROP, "SV_BotFreeClient: bad clientNum: %i", clientNum );
|
||||
}
|
||||
cl = &svs.clients[clientNum];
|
||||
cl->state = CS_FREE;
|
||||
cl->name[0] = 0;
|
||||
if ( cl->gentity ) {
|
||||
cl->gentity->r.svFlags &= ~SVF_BOT;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
BotDrawDebugPolygons
|
||||
==================
|
||||
*/
|
||||
void BotDrawDebugPolygons(void (*drawPoly)(int color, int numPoints, float *points), int value) {
|
||||
static cvar_t *bot_debug, *bot_groundonly, *bot_reachability, *bot_highlightarea;
|
||||
bot_debugpoly_t *poly;
|
||||
int i, parm0;
|
||||
|
||||
if (!debugpolygons)
|
||||
return;
|
||||
//bot debugging
|
||||
if (!bot_debug) bot_debug = Cvar_Get("bot_debug", "0", 0);
|
||||
//
|
||||
if (bot_enable && bot_debug->integer) {
|
||||
//show reachabilities
|
||||
if (!bot_reachability) bot_reachability = Cvar_Get("bot_reachability", "0", 0);
|
||||
//show ground faces only
|
||||
if (!bot_groundonly) bot_groundonly = Cvar_Get("bot_groundonly", "1", 0);
|
||||
//get the hightlight area
|
||||
if (!bot_highlightarea) bot_highlightarea = Cvar_Get("bot_highlightarea", "0", 0);
|
||||
//
|
||||
parm0 = 0;
|
||||
if (svs.clients[0].lastUsercmd.buttons & BUTTON_ATTACK) parm0 |= 1;
|
||||
if (bot_reachability->integer) parm0 |= 2;
|
||||
if (bot_groundonly->integer) parm0 |= 4;
|
||||
botlib_export->BotLibVarSet("bot_highlightarea", bot_highlightarea->string);
|
||||
botlib_export->Test(parm0, NULL, svs.clients[0].gentity->r.currentOrigin,
|
||||
svs.clients[0].gentity->r.currentAngles);
|
||||
} //end if
|
||||
//draw all debug polys
|
||||
for (i = 0; i < bot_maxdebugpolys; i++) {
|
||||
poly = &debugpolygons[i];
|
||||
if (!poly->inuse) continue;
|
||||
drawPoly(poly->color, poly->numPoints, (float *) poly->points);
|
||||
//Com_Printf("poly %i, numpoints = %d\n", i, poly->numPoints);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
BotImport_Print
|
||||
==================
|
||||
*/
|
||||
void QDECL BotImport_Print(int type, char *fmt, ...)
|
||||
{
|
||||
char str[2048];
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vsprintf(str, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
switch(type) {
|
||||
case PRT_MESSAGE: {
|
||||
Com_Printf("%s", str);
|
||||
break;
|
||||
}
|
||||
case PRT_WARNING: {
|
||||
Com_Printf(S_COLOR_YELLOW "Warning: %s", str);
|
||||
break;
|
||||
}
|
||||
case PRT_ERROR: {
|
||||
Com_Printf(S_COLOR_RED "Error: %s", str);
|
||||
break;
|
||||
}
|
||||
case PRT_FATAL: {
|
||||
Com_Printf(S_COLOR_RED "Fatal: %s", str);
|
||||
break;
|
||||
}
|
||||
case PRT_EXIT: {
|
||||
Com_Error(ERR_DROP, S_COLOR_RED "Exit: %s", str);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Com_Printf("unknown print type\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
BotImport_Trace
|
||||
==================
|
||||
*/
|
||||
void BotImport_Trace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask) {
|
||||
trace_t trace;
|
||||
|
||||
SV_Trace(&trace, start, mins, maxs, end, passent, contentmask, qfalse);
|
||||
//copy the trace information
|
||||
bsptrace->allsolid = trace.allsolid;
|
||||
bsptrace->startsolid = trace.startsolid;
|
||||
bsptrace->fraction = trace.fraction;
|
||||
VectorCopy(trace.endpos, bsptrace->endpos);
|
||||
bsptrace->plane.dist = trace.plane.dist;
|
||||
VectorCopy(trace.plane.normal, bsptrace->plane.normal);
|
||||
bsptrace->plane.signbits = trace.plane.signbits;
|
||||
bsptrace->plane.type = trace.plane.type;
|
||||
bsptrace->surface.value = trace.surfaceFlags;
|
||||
bsptrace->ent = trace.entityNum;
|
||||
bsptrace->exp_dist = 0;
|
||||
bsptrace->sidenum = 0;
|
||||
bsptrace->contents = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
BotImport_EntityTrace
|
||||
==================
|
||||
*/
|
||||
void BotImport_EntityTrace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int entnum, int contentmask) {
|
||||
trace_t trace;
|
||||
|
||||
SV_ClipToEntity(&trace, start, mins, maxs, end, entnum, contentmask, qfalse);
|
||||
//copy the trace information
|
||||
bsptrace->allsolid = trace.allsolid;
|
||||
bsptrace->startsolid = trace.startsolid;
|
||||
bsptrace->fraction = trace.fraction;
|
||||
VectorCopy(trace.endpos, bsptrace->endpos);
|
||||
bsptrace->plane.dist = trace.plane.dist;
|
||||
VectorCopy(trace.plane.normal, bsptrace->plane.normal);
|
||||
bsptrace->plane.signbits = trace.plane.signbits;
|
||||
bsptrace->plane.type = trace.plane.type;
|
||||
bsptrace->surface.value = trace.surfaceFlags;
|
||||
bsptrace->ent = trace.entityNum;
|
||||
bsptrace->exp_dist = 0;
|
||||
bsptrace->sidenum = 0;
|
||||
bsptrace->contents = 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
BotImport_PointContents
|
||||
==================
|
||||
*/
|
||||
int BotImport_PointContents(vec3_t point) {
|
||||
return SV_PointContents(point, -1);
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
BotImport_inPVS
|
||||
==================
|
||||
*/
|
||||
int BotImport_inPVS(vec3_t p1, vec3_t p2) {
|
||||
return SV_inPVS (p1, p2);
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
BotImport_BSPEntityData
|
||||
==================
|
||||
*/
|
||||
char *BotImport_BSPEntityData(void) {
|
||||
return CM_EntityString();
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
BotImport_BSPModelMinsMaxsOrigin
|
||||
==================
|
||||
*/
|
||||
void BotImport_BSPModelMinsMaxsOrigin(int modelnum, vec3_t angles, vec3_t outmins, vec3_t outmaxs, vec3_t origin) {
|
||||
clipHandle_t h;
|
||||
vec3_t mins, maxs;
|
||||
float max;
|
||||
int i;
|
||||
|
||||
h = CM_InlineModel(modelnum);
|
||||
CM_ModelBounds(h, mins, maxs);
|
||||
//if the model is rotated
|
||||
if ((angles[0] || angles[1] || angles[2])) {
|
||||
// expand for rotation
|
||||
|
||||
max = RadiusFromBounds(mins, maxs);
|
||||
for (i = 0; i < 3; i++) {
|
||||
mins[i] = -max;
|
||||
maxs[i] = max;
|
||||
}
|
||||
}
|
||||
if (outmins) VectorCopy(mins, outmins);
|
||||
if (outmaxs) VectorCopy(maxs, outmaxs);
|
||||
if (origin) VectorClear(origin);
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
BotImport_GetMemory
|
||||
==================
|
||||
*/
|
||||
void *BotImport_GetMemory(int size) {
|
||||
void *ptr;
|
||||
|
||||
ptr = Z_TagMalloc( size, TAG_BOTLIB );
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
BotImport_FreeMemory
|
||||
==================
|
||||
*/
|
||||
void BotImport_FreeMemory(void *ptr) {
|
||||
Z_Free(ptr);
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
BotImport_HunkAlloc
|
||||
=================
|
||||
*/
|
||||
void *BotImport_HunkAlloc( int size ) {
|
||||
if( Hunk_CheckMark() ) {
|
||||
Com_Error( ERR_DROP, "SV_Bot_HunkAlloc: Alloc with marks already set\n" );
|
||||
}
|
||||
return Hunk_Alloc( size, h_high );
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
BotImport_DebugPolygonCreate
|
||||
==================
|
||||
*/
|
||||
int BotImport_DebugPolygonCreate(int color, int numPoints, vec3_t *points) {
|
||||
bot_debugpoly_t *poly;
|
||||
int i;
|
||||
|
||||
if (!debugpolygons)
|
||||
return 0;
|
||||
|
||||
for (i = 1; i < bot_maxdebugpolys; i++) {
|
||||
if (!debugpolygons[i].inuse)
|
||||
break;
|
||||
}
|
||||
if (i >= bot_maxdebugpolys)
|
||||
return 0;
|
||||
poly = &debugpolygons[i];
|
||||
poly->inuse = qtrue;
|
||||
poly->color = color;
|
||||
poly->numPoints = numPoints;
|
||||
Com_Memcpy(poly->points, points, numPoints * sizeof(vec3_t));
|
||||
//
|
||||
return i;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
BotImport_DebugPolygonShow
|
||||
==================
|
||||
*/
|
||||
void BotImport_DebugPolygonShow(int id, int color, int numPoints, vec3_t *points) {
|
||||
bot_debugpoly_t *poly;
|
||||
|
||||
if (!debugpolygons) return;
|
||||
poly = &debugpolygons[id];
|
||||
poly->inuse = qtrue;
|
||||
poly->color = color;
|
||||
poly->numPoints = numPoints;
|
||||
Com_Memcpy(poly->points, points, numPoints * sizeof(vec3_t));
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
BotImport_DebugPolygonDelete
|
||||
==================
|
||||
*/
|
||||
void BotImport_DebugPolygonDelete(int id)
|
||||
{
|
||||
if (!debugpolygons) return;
|
||||
debugpolygons[id].inuse = qfalse;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
BotImport_DebugLineCreate
|
||||
==================
|
||||
*/
|
||||
int BotImport_DebugLineCreate(void) {
|
||||
vec3_t points[1];
|
||||
return BotImport_DebugPolygonCreate(0, 0, points);
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
BotImport_DebugLineDelete
|
||||
==================
|
||||
*/
|
||||
void BotImport_DebugLineDelete(int line) {
|
||||
BotImport_DebugPolygonDelete(line);
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
BotImport_DebugLineShow
|
||||
==================
|
||||
*/
|
||||
void BotImport_DebugLineShow(int line, vec3_t start, vec3_t end, int color) {
|
||||
vec3_t points[4], dir, cross, up = {0, 0, 1};
|
||||
float dot;
|
||||
|
||||
VectorCopy(start, points[0]);
|
||||
VectorCopy(start, points[1]);
|
||||
//points[1][2] -= 2;
|
||||
VectorCopy(end, points[2]);
|
||||
//points[2][2] -= 2;
|
||||
VectorCopy(end, points[3]);
|
||||
|
||||
|
||||
VectorSubtract(end, start, dir);
|
||||
VectorNormalize(dir);
|
||||
dot = DotProduct(dir, up);
|
||||
if (dot > 0.99 || dot < -0.99) VectorSet(cross, 1, 0, 0);
|
||||
else CrossProduct(dir, up, cross);
|
||||
|
||||
VectorNormalize(cross);
|
||||
|
||||
VectorMA(points[0], 2, cross, points[0]);
|
||||
VectorMA(points[1], -2, cross, points[1]);
|
||||
VectorMA(points[2], -2, cross, points[2]);
|
||||
VectorMA(points[3], 2, cross, points[3]);
|
||||
|
||||
BotImport_DebugPolygonShow(line, color, 4, points);
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_BotClientCommand
|
||||
==================
|
||||
*/
|
||||
void BotClientCommand( int client, char *command ) {
|
||||
SV_ExecuteClientCommand( &svs.clients[client], command, qtrue );
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_BotFrame
|
||||
==================
|
||||
*/
|
||||
void SV_BotFrame( int time ) {
|
||||
if (!bot_enable) return;
|
||||
//NOTE: maybe the game is already shutdown
|
||||
if (!gvm) return;
|
||||
VM_Call( gvm, BOTAI_START_FRAME, time );
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_BotLibSetup
|
||||
===============
|
||||
*/
|
||||
int SV_BotLibSetup( void ) {
|
||||
if (!bot_enable) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( !botlib_export ) {
|
||||
Com_Printf( S_COLOR_RED "Error: SV_BotLibSetup without SV_BotInitBotLib\n" );
|
||||
return -1;
|
||||
}
|
||||
|
||||
return botlib_export->BotLibSetup();
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_ShutdownBotLib
|
||||
|
||||
Called when either the entire server is being killed, or
|
||||
it is changing to a different game directory.
|
||||
===============
|
||||
*/
|
||||
int SV_BotLibShutdown( void ) {
|
||||
|
||||
if ( !botlib_export ) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return botlib_export->BotLibShutdown();
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_BotInitCvars
|
||||
==================
|
||||
*/
|
||||
void SV_BotInitCvars(void) {
|
||||
|
||||
Cvar_Get("bot_enable", "1", 0); //enable the bot
|
||||
Cvar_Get("bot_developer", "0", CVAR_CHEAT); //bot developer mode
|
||||
Cvar_Get("bot_debug", "0", CVAR_CHEAT); //enable bot debugging
|
||||
Cvar_Get("bot_maxdebugpolys", "2", 0); //maximum number of debug polys
|
||||
Cvar_Get("bot_groundonly", "1", 0); //only show ground faces of areas
|
||||
Cvar_Get("bot_reachability", "0", 0); //show all reachabilities to other areas
|
||||
Cvar_Get("bot_visualizejumppads", "0", CVAR_CHEAT); //show jumppads
|
||||
Cvar_Get("bot_forceclustering", "0", 0); //force cluster calculations
|
||||
Cvar_Get("bot_forcereachability", "0", 0); //force reachability calculations
|
||||
Cvar_Get("bot_forcewrite", "0", 0); //force writing aas file
|
||||
Cvar_Get("bot_aasoptimize", "0", 0); //no aas file optimisation
|
||||
Cvar_Get("bot_saveroutingcache", "0", 0); //save routing cache
|
||||
Cvar_Get("bot_thinktime", "100", CVAR_CHEAT); //msec the bots thinks
|
||||
Cvar_Get("bot_reloadcharacters", "0", 0); //reload the bot characters each time
|
||||
Cvar_Get("bot_testichat", "0", 0); //test ichats
|
||||
Cvar_Get("bot_testrchat", "0", 0); //test rchats
|
||||
Cvar_Get("bot_testsolid", "0", CVAR_CHEAT); //test for solid areas
|
||||
Cvar_Get("bot_testclusters", "0", CVAR_CHEAT); //test the AAS clusters
|
||||
Cvar_Get("bot_fastchat", "0", 0); //fast chatting bots
|
||||
Cvar_Get("bot_nochat", "0", 0); //disable chats
|
||||
Cvar_Get("bot_pause", "0", CVAR_CHEAT); //pause the bots thinking
|
||||
Cvar_Get("bot_report", "0", CVAR_CHEAT); //get a full report in ctf
|
||||
Cvar_Get("bot_grapple", "0", 0); //enable grapple
|
||||
Cvar_Get("bot_rocketjump", "1", 0); //enable rocket jumping
|
||||
Cvar_Get("bot_challenge", "0", 0); //challenging bot
|
||||
Cvar_Get("bot_minplayers", "0", 0); //minimum players in a team or the game
|
||||
Cvar_Get("bot_interbreedchar", "", CVAR_CHEAT); //bot character used for interbreeding
|
||||
Cvar_Get("bot_interbreedbots", "10", CVAR_CHEAT); //number of bots used for interbreeding
|
||||
Cvar_Get("bot_interbreedcycle", "20", CVAR_CHEAT); //bot interbreeding cycle
|
||||
Cvar_Get("bot_interbreedwrite", "", CVAR_CHEAT); //write interbreeded bots to this file
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_BotInitBotLib
|
||||
==================
|
||||
*/
|
||||
void SV_BotInitBotLib(void) {
|
||||
botlib_import_t botlib_import;
|
||||
|
||||
if ( !Cvar_VariableValue("fs_restrict") && !Sys_CheckCD() ) {
|
||||
Com_Error( ERR_NEED_CD, "Game CD not in drive" );
|
||||
}
|
||||
|
||||
if (debugpolygons) Z_Free(debugpolygons);
|
||||
bot_maxdebugpolys = Cvar_VariableIntegerValue("bot_maxdebugpolys");
|
||||
debugpolygons = Z_Malloc(sizeof(bot_debugpoly_t) * bot_maxdebugpolys);
|
||||
|
||||
botlib_import.Print = BotImport_Print;
|
||||
botlib_import.Trace = BotImport_Trace;
|
||||
botlib_import.EntityTrace = BotImport_EntityTrace;
|
||||
botlib_import.PointContents = BotImport_PointContents;
|
||||
botlib_import.inPVS = BotImport_inPVS;
|
||||
botlib_import.BSPEntityData = BotImport_BSPEntityData;
|
||||
botlib_import.BSPModelMinsMaxsOrigin = BotImport_BSPModelMinsMaxsOrigin;
|
||||
botlib_import.BotClientCommand = BotClientCommand;
|
||||
|
||||
//memory management
|
||||
botlib_import.GetMemory = BotImport_GetMemory;
|
||||
botlib_import.FreeMemory = BotImport_FreeMemory;
|
||||
botlib_import.AvailableMemory = Z_AvailableMemory;
|
||||
botlib_import.HunkAlloc = BotImport_HunkAlloc;
|
||||
|
||||
// file system access
|
||||
botlib_import.FS_FOpenFile = FS_FOpenFileByMode;
|
||||
botlib_import.FS_Read = FS_Read2;
|
||||
botlib_import.FS_Write = FS_Write;
|
||||
botlib_import.FS_FCloseFile = FS_FCloseFile;
|
||||
botlib_import.FS_Seek = FS_Seek;
|
||||
|
||||
//debug lines
|
||||
botlib_import.DebugLineCreate = BotImport_DebugLineCreate;
|
||||
botlib_import.DebugLineDelete = BotImport_DebugLineDelete;
|
||||
botlib_import.DebugLineShow = BotImport_DebugLineShow;
|
||||
|
||||
//debug polygons
|
||||
botlib_import.DebugPolygonCreate = BotImport_DebugPolygonCreate;
|
||||
botlib_import.DebugPolygonDelete = BotImport_DebugPolygonDelete;
|
||||
|
||||
botlib_export = (botlib_export_t *)GetBotLibAPI( BOTLIB_API_VERSION, &botlib_import );
|
||||
assert(botlib_export); // bk001129 - somehow we end up with a zero import.
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// * * * BOT AI CODE IS BELOW THIS POINT * * *
|
||||
//
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_BotGetConsoleMessage
|
||||
==================
|
||||
*/
|
||||
int SV_BotGetConsoleMessage( int client, char *buf, int size )
|
||||
{
|
||||
client_t *cl;
|
||||
int index;
|
||||
|
||||
cl = &svs.clients[client];
|
||||
cl->lastPacketTime = svs.time;
|
||||
|
||||
if ( cl->reliableAcknowledge == cl->reliableSequence ) {
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
cl->reliableAcknowledge++;
|
||||
index = cl->reliableAcknowledge & ( MAX_RELIABLE_COMMANDS - 1 );
|
||||
|
||||
if ( !cl->reliableCommands[index][0] ) {
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
Q_strncpyz( buf, cl->reliableCommands[index], size );
|
||||
return qtrue;
|
||||
}
|
||||
|
||||
#if 0
|
||||
/*
|
||||
==================
|
||||
EntityInPVS
|
||||
==================
|
||||
*/
|
||||
int EntityInPVS( int client, int entityNum ) {
|
||||
client_t *cl;
|
||||
clientSnapshot_t *frame;
|
||||
int i;
|
||||
|
||||
cl = &svs.clients[client];
|
||||
frame = &cl->frames[cl->netchan.outgoingSequence & PACKET_MASK];
|
||||
for ( i = 0; i < frame->num_entities; i++ ) {
|
||||
if ( svs.snapshotEntities[(frame->first_entity + i) % svs.numSnapshotEntities].number == entityNum ) {
|
||||
return qtrue;
|
||||
}
|
||||
}
|
||||
return qfalse;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_BotGetSnapshotEntity
|
||||
==================
|
||||
*/
|
||||
int SV_BotGetSnapshotEntity( int client, int sequence ) {
|
||||
client_t *cl;
|
||||
clientSnapshot_t *frame;
|
||||
|
||||
cl = &svs.clients[client];
|
||||
frame = &cl->frames[cl->netchan.outgoingSequence & PACKET_MASK];
|
||||
if (sequence < 0 || sequence >= frame->num_entities) {
|
||||
return -1;
|
||||
}
|
||||
return svs.snapshotEntities[(frame->first_entity + sequence) % svs.numSnapshotEntities].number;
|
||||
}
|
||||
|
762
code/server/sv_ccmds.c
Normal file
762
code/server/sv_ccmds.c
Normal file
|
@ -0,0 +1,762 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code is distributed in the hope that it will be
|
||||
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Foobar; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
|
||||
/*
|
||||
===============================================================================
|
||||
|
||||
OPERATOR CONSOLE ONLY COMMANDS
|
||||
|
||||
These commands can only be entered from stdin or by a remote operator datagram
|
||||
===============================================================================
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_GetPlayerByName
|
||||
|
||||
Returns the player with name from Cmd_Argv(1)
|
||||
==================
|
||||
*/
|
||||
static client_t *SV_GetPlayerByName( void ) {
|
||||
client_t *cl;
|
||||
int i;
|
||||
char *s;
|
||||
char cleanName[64];
|
||||
|
||||
// make sure server is running
|
||||
if ( !com_sv_running->integer ) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ( Cmd_Argc() < 2 ) {
|
||||
Com_Printf( "No player specified.\n" );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s = Cmd_Argv(1);
|
||||
|
||||
// check for a name match
|
||||
for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) {
|
||||
if ( !cl->state ) {
|
||||
continue;
|
||||
}
|
||||
if ( !Q_stricmp( cl->name, s ) ) {
|
||||
return cl;
|
||||
}
|
||||
|
||||
Q_strncpyz( cleanName, cl->name, sizeof(cleanName) );
|
||||
Q_CleanStr( cleanName );
|
||||
if ( !Q_stricmp( cleanName, s ) ) {
|
||||
return cl;
|
||||
}
|
||||
}
|
||||
|
||||
Com_Printf( "Player %s is not on the server\n", s );
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_GetPlayerByNum
|
||||
|
||||
Returns the player with idnum from Cmd_Argv(1)
|
||||
==================
|
||||
*/
|
||||
static client_t *SV_GetPlayerByNum( void ) {
|
||||
client_t *cl;
|
||||
int i;
|
||||
int idnum;
|
||||
char *s;
|
||||
|
||||
// make sure server is running
|
||||
if ( !com_sv_running->integer ) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ( Cmd_Argc() < 2 ) {
|
||||
Com_Printf( "No player specified.\n" );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s = Cmd_Argv(1);
|
||||
|
||||
for (i = 0; s[i]; i++) {
|
||||
if (s[i] < '0' || s[i] > '9') {
|
||||
Com_Printf( "Bad slot number: %s\n", s);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
idnum = atoi( s );
|
||||
if ( idnum < 0 || idnum >= sv_maxclients->integer ) {
|
||||
Com_Printf( "Bad client slot: %i\n", idnum );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cl = &svs.clients[idnum];
|
||||
if ( !cl->state ) {
|
||||
Com_Printf( "Client %i is not active\n", idnum );
|
||||
return NULL;
|
||||
}
|
||||
return cl;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//=========================================================
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_Map_f
|
||||
|
||||
Restart the server on a different map
|
||||
==================
|
||||
*/
|
||||
static void SV_Map_f( void ) {
|
||||
char *cmd;
|
||||
char *map;
|
||||
qboolean killBots, cheat;
|
||||
char expanded[MAX_QPATH];
|
||||
char mapname[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
|
||||
Com_sprintf (expanded, sizeof(expanded), "maps/%s.bsp", map);
|
||||
if ( FS_ReadFile (expanded, NULL) == -1 ) {
|
||||
Com_Printf ("Can't find map %s\n", expanded);
|
||||
return;
|
||||
}
|
||||
|
||||
// force latched values to get set
|
||||
Cvar_Get ("g_gametype", "0", CVAR_SERVERINFO | CVAR_USERINFO | CVAR_LATCH );
|
||||
|
||||
cmd = Cmd_Argv(0);
|
||||
if( Q_stricmpn( cmd, "sp", 2 ) == 0 ) {
|
||||
Cvar_SetValue( "g_gametype", GT_SINGLE_PLAYER );
|
||||
Cvar_SetValue( "g_doWarmup", 0 );
|
||||
// may not set sv_maxclients directly, always set latched
|
||||
Cvar_SetLatched( "sv_maxclients", "8" );
|
||||
cmd += 2;
|
||||
cheat = qfalse;
|
||||
killBots = qtrue;
|
||||
}
|
||||
else {
|
||||
if ( !Q_stricmp( cmd, "devmap" ) || !Q_stricmp( cmd, "spdevmap" ) ) {
|
||||
cheat = qtrue;
|
||||
killBots = qtrue;
|
||||
} else {
|
||||
cheat = qfalse;
|
||||
killBots = qfalse;
|
||||
}
|
||||
if( sv_gametype->integer == GT_SINGLE_PLAYER ) {
|
||||
Cvar_SetValue( "g_gametype", GT_FFA );
|
||||
}
|
||||
}
|
||||
|
||||
// save the map name here cause on a map restart we reload the q3config.cfg
|
||||
// and thus nuke the arguments of the map command
|
||||
Q_strncpyz(mapname, map, sizeof(mapname));
|
||||
|
||||
// start up the map
|
||||
SV_SpawnServer( mapname, killBots );
|
||||
|
||||
// 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 ( cheat ) {
|
||||
Cvar_Set( "sv_cheats", "1" );
|
||||
} else {
|
||||
Cvar_Set( "sv_cheats", "0" );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
SV_MapRestart_f
|
||||
|
||||
Completely restarts a level, but doesn't send a new gamestate to the clients.
|
||||
This allows fair starts with variable load times.
|
||||
================
|
||||
*/
|
||||
static void SV_MapRestart_f( void ) {
|
||||
int i;
|
||||
client_t *client;
|
||||
char *denied;
|
||||
qboolean isBot;
|
||||
int delay;
|
||||
|
||||
// make sure we aren't restarting twice in the same frame
|
||||
if ( com_frameTime == sv.serverId ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure server is running
|
||||
if ( !com_sv_running->integer ) {
|
||||
Com_Printf( "Server is not running.\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( sv.restartTime ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Cmd_Argc() > 1 ) {
|
||||
delay = atoi( Cmd_Argv(1) );
|
||||
}
|
||||
else {
|
||||
delay = 5;
|
||||
}
|
||||
if( delay && !Cvar_VariableValue("g_doWarmup") ) {
|
||||
sv.restartTime = svs.time + delay * 1000;
|
||||
SV_SetConfigstring( CS_WARMUP, va("%i", sv.restartTime) );
|
||||
return;
|
||||
}
|
||||
|
||||
// check for changes in variables that can't just be restarted
|
||||
// check for maxclients change
|
||||
if ( sv_maxclients->modified || sv_gametype->modified ) {
|
||||
char mapname[MAX_QPATH];
|
||||
|
||||
Com_Printf( "variable change -- restarting.\n" );
|
||||
// restart the map the slow way
|
||||
Q_strncpyz( mapname, Cvar_VariableString( "mapname" ), sizeof( mapname ) );
|
||||
|
||||
SV_SpawnServer( mapname, qfalse );
|
||||
return;
|
||||
}
|
||||
|
||||
// toggle the server bit so clients can detect that a
|
||||
// map_restart has happened
|
||||
svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT;
|
||||
|
||||
// generate a new serverid
|
||||
// TTimo - don't update restartedserverId there, otherwise we won't deal correctly with multiple map_restart
|
||||
sv.serverId = com_frameTime;
|
||||
Cvar_Set( "sv_serverid", va("%i", sv.serverId ) );
|
||||
|
||||
// reset all the vm data in place without changing memory allocation
|
||||
// note that we do NOT set sv.state = SS_LOADING, so configstrings that
|
||||
// had been changed from their default values will generate broadcast updates
|
||||
sv.state = SS_LOADING;
|
||||
sv.restarting = qtrue;
|
||||
|
||||
SV_RestartGameProgs();
|
||||
|
||||
// run a few frames to allow everything to settle
|
||||
for ( i = 0 ;i < 3 ; i++ ) {
|
||||
VM_Call( gvm, GAME_RUN_FRAME, svs.time );
|
||||
svs.time += 100;
|
||||
}
|
||||
|
||||
sv.state = SS_GAME;
|
||||
sv.restarting = qfalse;
|
||||
|
||||
// connect and begin all the clients
|
||||
for (i=0 ; i<sv_maxclients->integer ; i++) {
|
||||
client = &svs.clients[i];
|
||||
|
||||
// send the new gamestate to all connected clients
|
||||
if ( client->state < CS_CONNECTED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( client->netchan.remoteAddress.type == NA_BOT ) {
|
||||
isBot = qtrue;
|
||||
} else {
|
||||
isBot = qfalse;
|
||||
}
|
||||
|
||||
// add the map_restart command
|
||||
SV_AddServerCommand( client, "map_restart\n" );
|
||||
|
||||
// connect the client again, without the firstTime flag
|
||||
denied = VM_ExplicitArgPtr( gvm, VM_Call( gvm, GAME_CLIENT_CONNECT, i, qfalse, isBot ) );
|
||||
if ( denied ) {
|
||||
// this generally shouldn't happen, because the client
|
||||
// was connected before the level change
|
||||
SV_DropClient( client, denied );
|
||||
Com_Printf( "SV_MapRestart_f(%d): dropped client %i - denied!\n", delay, i ); // bk010125
|
||||
continue;
|
||||
}
|
||||
|
||||
client->state = CS_ACTIVE;
|
||||
|
||||
SV_ClientEnterWorld( client, &client->lastUsercmd );
|
||||
}
|
||||
|
||||
// run another frame to allow things to look at all the players
|
||||
VM_Call( gvm, GAME_RUN_FRAME, svs.time );
|
||||
svs.time += 100;
|
||||
}
|
||||
|
||||
//===============================================================
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_Kick_f
|
||||
|
||||
Kick a user off of the server FIXME: move to game
|
||||
==================
|
||||
*/
|
||||
static void SV_Kick_f( void ) {
|
||||
client_t *cl;
|
||||
int i;
|
||||
|
||||
// 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: kick <player name>\nkick all = kick everyone\nkick allbots = kick all bots\n");
|
||||
return;
|
||||
}
|
||||
|
||||
cl = SV_GetPlayerByName();
|
||||
if ( !cl ) {
|
||||
if ( !Q_stricmp(Cmd_Argv(1), "all") ) {
|
||||
for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) {
|
||||
if ( !cl->state ) {
|
||||
continue;
|
||||
}
|
||||
if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
|
||||
continue;
|
||||
}
|
||||
SV_DropClient( cl, "was kicked" );
|
||||
cl->lastPacketTime = svs.time; // in case there is a funny zombie
|
||||
}
|
||||
}
|
||||
else if ( !Q_stricmp(Cmd_Argv(1), "allbots") ) {
|
||||
for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) {
|
||||
if ( !cl->state ) {
|
||||
continue;
|
||||
}
|
||||
if( cl->netchan.remoteAddress.type != NA_BOT ) {
|
||||
continue;
|
||||
}
|
||||
SV_DropClient( cl, "was kicked" );
|
||||
cl->lastPacketTime = svs.time; // in case there is a funny zombie
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
|
||||
SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n");
|
||||
return;
|
||||
}
|
||||
|
||||
SV_DropClient( cl, "was kicked" );
|
||||
cl->lastPacketTime = svs.time; // in case there is a funny zombie
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_Ban_f
|
||||
|
||||
Ban a user from being able to play on this server through the auth
|
||||
server
|
||||
==================
|
||||
*/
|
||||
static void SV_Ban_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: banUser <player name>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
cl = SV_GetPlayerByName();
|
||||
|
||||
if (!cl) {
|
||||
return;
|
||||
}
|
||||
|
||||
if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
|
||||
SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// look up the authorize server's IP
|
||||
if ( !svs.authorizeAddress.ip[0] && svs.authorizeAddress.type != NA_BAD ) {
|
||||
Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME );
|
||||
if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &svs.authorizeAddress ) ) {
|
||||
Com_Printf( "Couldn't resolve address\n" );
|
||||
return;
|
||||
}
|
||||
svs.authorizeAddress.port = BigShort( PORT_AUTHORIZE );
|
||||
Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME,
|
||||
svs.authorizeAddress.ip[0], svs.authorizeAddress.ip[1],
|
||||
svs.authorizeAddress.ip[2], svs.authorizeAddress.ip[3],
|
||||
BigShort( svs.authorizeAddress.port ) );
|
||||
}
|
||||
|
||||
// otherwise send their ip to the authorize server
|
||||
if ( svs.authorizeAddress.type != NA_BAD ) {
|
||||
NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress,
|
||||
"banUser %i.%i.%i.%i", cl->netchan.remoteAddress.ip[0], cl->netchan.remoteAddress.ip[1],
|
||||
cl->netchan.remoteAddress.ip[2], cl->netchan.remoteAddress.ip[3] );
|
||||
Com_Printf("%s was banned from coming back\n", cl->name);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_BanNum_f
|
||||
|
||||
Ban a user from being able to play on this server through the auth
|
||||
server
|
||||
==================
|
||||
*/
|
||||
static void SV_BanNum_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: banClient <client number>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
cl = SV_GetPlayerByNum();
|
||||
if ( !cl ) {
|
||||
return;
|
||||
}
|
||||
if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
|
||||
SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// look up the authorize server's IP
|
||||
if ( !svs.authorizeAddress.ip[0] && svs.authorizeAddress.type != NA_BAD ) {
|
||||
Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME );
|
||||
if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &svs.authorizeAddress ) ) {
|
||||
Com_Printf( "Couldn't resolve address\n" );
|
||||
return;
|
||||
}
|
||||
svs.authorizeAddress.port = BigShort( PORT_AUTHORIZE );
|
||||
Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME,
|
||||
svs.authorizeAddress.ip[0], svs.authorizeAddress.ip[1],
|
||||
svs.authorizeAddress.ip[2], svs.authorizeAddress.ip[3],
|
||||
BigShort( svs.authorizeAddress.port ) );
|
||||
}
|
||||
|
||||
// otherwise send their ip to the authorize server
|
||||
if ( svs.authorizeAddress.type != NA_BAD ) {
|
||||
NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress,
|
||||
"banUser %i.%i.%i.%i", cl->netchan.remoteAddress.ip[0], cl->netchan.remoteAddress.ip[1],
|
||||
cl->netchan.remoteAddress.ip[2], cl->netchan.remoteAddress.ip[3] );
|
||||
Com_Printf("%s was banned from coming back\n", cl->name);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_KickNum_f
|
||||
|
||||
Kick a user off of the server FIXME: move to game
|
||||
==================
|
||||
*/
|
||||
static void SV_KickNum_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: kicknum <client number>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
cl = SV_GetPlayerByNum();
|
||||
if ( !cl ) {
|
||||
return;
|
||||
}
|
||||
if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
|
||||
SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n");
|
||||
return;
|
||||
}
|
||||
|
||||
SV_DropClient( cl, "was kicked" );
|
||||
cl->lastPacketTime = svs.time; // in case there is a funny zombie
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
SV_Status_f
|
||||
================
|
||||
*/
|
||||
static void SV_Status_f( void ) {
|
||||
int i, j, l;
|
||||
client_t *cl;
|
||||
playerState_t *ps;
|
||||
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 < sv_maxclients->integer ; i++,cl++)
|
||||
{
|
||||
if (!cl->state)
|
||||
continue;
|
||||
Com_Printf ("%3i ", i);
|
||||
ps = SV_GameClientNum( i );
|
||||
Com_Printf ("%5i ", ps->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);
|
||||
// TTimo adding a ^7 to reset the color
|
||||
// NOTE: colored names in status breaks the padding (WONTFIX)
|
||||
Com_Printf ("^7");
|
||||
l = 16 - strlen(cl->name);
|
||||
for (j=0 ; j<l ; j++)
|
||||
Com_Printf (" ");
|
||||
|
||||
Com_Printf ("%7i ", svs.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_ConSay_f
|
||||
==================
|
||||
*/
|
||||
static void SV_ConSay_f(void) {
|
||||
char *p;
|
||||
char text[1024];
|
||||
|
||||
// make sure server is running
|
||||
if ( !com_sv_running->integer ) {
|
||||
Com_Printf( "Server is not running.\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( Cmd_Argc () < 2 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
strcpy (text, "console: ");
|
||||
p = Cmd_Args();
|
||||
|
||||
if ( *p == '"' ) {
|
||||
p++;
|
||||
p[strlen(p)-1] = 0;
|
||||
}
|
||||
|
||||
strcat(text, p);
|
||||
|
||||
SV_SendServerCommand(NULL, "chat \"%s\n\"", text);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_Heartbeat_f
|
||||
|
||||
Also called by SV_DropClient, SV_DirectConnect, and SV_SpawnServer
|
||||
==================
|
||||
*/
|
||||
void SV_Heartbeat_f( void ) {
|
||||
svs.nextHeartbeatTime = -9999999;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===========
|
||||
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_GetPlayerByName();
|
||||
if ( !cl ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Com_Printf( "userinfo\n" );
|
||||
Com_Printf( "--------\n" );
|
||||
Info_Print( cl->userinfo );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
SV_KillServer
|
||||
=================
|
||||
*/
|
||||
static void SV_KillServer_f( void ) {
|
||||
SV_Shutdown( "killserver" );
|
||||
}
|
||||
|
||||
//===========================================================
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_AddOperatorCommands
|
||||
==================
|
||||
*/
|
||||
void SV_AddOperatorCommands( void ) {
|
||||
static qboolean initialized;
|
||||
|
||||
if ( initialized ) {
|
||||
return;
|
||||
}
|
||||
initialized = qtrue;
|
||||
|
||||
Cmd_AddCommand ("heartbeat", SV_Heartbeat_f);
|
||||
Cmd_AddCommand ("kick", SV_Kick_f);
|
||||
Cmd_AddCommand ("banUser", SV_Ban_f);
|
||||
Cmd_AddCommand ("banClient", SV_BanNum_f);
|
||||
Cmd_AddCommand ("clientkick", SV_KickNum_f);
|
||||
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 ("map_restart", SV_MapRestart_f);
|
||||
Cmd_AddCommand ("sectorlist", SV_SectorList_f);
|
||||
Cmd_AddCommand ("map", SV_Map_f);
|
||||
#ifndef PRE_RELEASE_DEMO
|
||||
Cmd_AddCommand ("devmap", SV_Map_f);
|
||||
Cmd_AddCommand ("spmap", SV_Map_f);
|
||||
Cmd_AddCommand ("spdevmap", SV_Map_f);
|
||||
#endif
|
||||
Cmd_AddCommand ("killserver", SV_KillServer_f);
|
||||
if( com_dedicated->integer ) {
|
||||
Cmd_AddCommand ("say", SV_ConSay_f);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_RemoveOperatorCommands
|
||||
==================
|
||||
*/
|
||||
void SV_RemoveOperatorCommands( void ) {
|
||||
#if 0
|
||||
// removing these won't let the server start again
|
||||
Cmd_RemoveCommand ("heartbeat");
|
||||
Cmd_RemoveCommand ("kick");
|
||||
Cmd_RemoveCommand ("banUser");
|
||||
Cmd_RemoveCommand ("banClient");
|
||||
Cmd_RemoveCommand ("status");
|
||||
Cmd_RemoveCommand ("serverinfo");
|
||||
Cmd_RemoveCommand ("systeminfo");
|
||||
Cmd_RemoveCommand ("dumpuser");
|
||||
Cmd_RemoveCommand ("map_restart");
|
||||
Cmd_RemoveCommand ("sectorlist");
|
||||
Cmd_RemoveCommand ("say");
|
||||
#endif
|
||||
}
|
||||
|
1531
code/server/sv_client.c
Normal file
1531
code/server/sv_client.c
Normal file
File diff suppressed because it is too large
Load diff
981
code/server/sv_game.c
Normal file
981
code/server/sv_game.c
Normal file
|
@ -0,0 +1,981 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code is distributed in the hope that it will be
|
||||
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Foobar; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
// sv_game.c -- interface to the game dll
|
||||
|
||||
#include "server.h"
|
||||
|
||||
#include "../game/botlib.h"
|
||||
|
||||
botlib_export_t *botlib_export;
|
||||
|
||||
void SV_GameError( const char *string ) {
|
||||
Com_Error( ERR_DROP, "%s", string );
|
||||
}
|
||||
|
||||
void SV_GamePrint( const char *string ) {
|
||||
Com_Printf( "%s", string );
|
||||
}
|
||||
|
||||
// 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( sharedEntity_t *ent ) {
|
||||
int num;
|
||||
|
||||
num = ( (byte *)ent - (byte *)sv.gentities ) / sv.gentitySize;
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
sharedEntity_t *SV_GentityNum( int num ) {
|
||||
sharedEntity_t *ent;
|
||||
|
||||
ent = (sharedEntity_t *)((byte *)sv.gentities + sv.gentitySize*(num));
|
||||
|
||||
return ent;
|
||||
}
|
||||
|
||||
playerState_t *SV_GameClientNum( int num ) {
|
||||
playerState_t *ps;
|
||||
|
||||
ps = (playerState_t *)((byte *)sv.gameClients + sv.gameClientSize*(num));
|
||||
|
||||
return ps;
|
||||
}
|
||||
|
||||
svEntity_t *SV_SvEntityForGentity( sharedEntity_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 ];
|
||||
}
|
||||
|
||||
sharedEntity_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 *text ) {
|
||||
if ( clientNum == -1 ) {
|
||||
SV_SendServerCommand( NULL, "%s", text );
|
||||
} else {
|
||||
if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) {
|
||||
return;
|
||||
}
|
||||
SV_SendServerCommand( svs.clients + clientNum, "%s", text );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_GameDropClient
|
||||
|
||||
Disconnects the client with a message
|
||||
===============
|
||||
*/
|
||||
void SV_GameDropClient( int clientNum, const char *reason ) {
|
||||
if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) {
|
||||
return;
|
||||
}
|
||||
SV_DropClient( svs.clients + clientNum, reason );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
SV_SetBrushModel
|
||||
|
||||
sets mins and maxs for inline bmodels
|
||||
=================
|
||||
*/
|
||||
void SV_SetBrushModel( sharedEntity_t *ent, const char *name ) {
|
||||
clipHandle_t h;
|
||||
vec3_t mins, maxs;
|
||||
|
||||
if (!name) {
|
||||
Com_Error( ERR_DROP, "SV_SetBrushModel: NULL" );
|
||||
}
|
||||
|
||||
if (name[0] != '*') {
|
||||
Com_Error( ERR_DROP, "SV_SetBrushModel: %s isn't a brush model", name );
|
||||
}
|
||||
|
||||
|
||||
ent->s.modelindex = atoi( name + 1 );
|
||||
|
||||
h = CM_InlineModel( ent->s.modelindex );
|
||||
CM_ModelBounds( h, mins, maxs );
|
||||
VectorCopy (mins, ent->r.mins);
|
||||
VectorCopy (maxs, ent->r.maxs);
|
||||
ent->r.bmodel = qtrue;
|
||||
|
||||
ent->r.contents = -1; // we don't know exactly what is in the brushes
|
||||
|
||||
SV_LinkEntity( ent ); // FIXME: remove
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
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;
|
||||
byte *mask;
|
||||
|
||||
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)) ) ) )
|
||||
return qfalse;
|
||||
if (!CM_AreasConnected (area1, area2))
|
||||
return qfalse; // a door blocks sight
|
||||
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;
|
||||
byte *mask;
|
||||
|
||||
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)) ) ) )
|
||||
return qfalse;
|
||||
|
||||
return qtrue;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
========================
|
||||
SV_AdjustAreaPortalState
|
||||
========================
|
||||
*/
|
||||
void SV_AdjustAreaPortalState( sharedEntity_t *ent, qboolean open ) {
|
||||
svEntity_t *svEnt;
|
||||
|
||||
svEnt = SV_SvEntityForGentity( ent );
|
||||
if ( svEnt->areanum2 == -1 ) {
|
||||
return;
|
||||
}
|
||||
CM_AdjustAreaPortalState( svEnt->areanum, svEnt->areanum2, open );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_GameAreaEntities
|
||||
==================
|
||||
*/
|
||||
qboolean SV_EntityContact( vec3_t mins, vec3_t maxs, const sharedEntity_t *gEnt, int capsule ) {
|
||||
const float *origin, *angles;
|
||||
clipHandle_t ch;
|
||||
trace_t trace;
|
||||
|
||||
// check for exact collision
|
||||
origin = gEnt->r.currentOrigin;
|
||||
angles = gEnt->r.currentAngles;
|
||||
|
||||
ch = SV_ClipHandleForEntity( gEnt );
|
||||
CM_TransformedBoxTrace ( &trace, vec3_origin, vec3_origin, mins, maxs,
|
||||
ch, -1, origin, angles, capsule );
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_LocateGameData
|
||||
|
||||
===============
|
||||
*/
|
||||
void SV_LocateGameData( sharedEntity_t *gEnts, int numGEntities, int sizeofGEntity_t,
|
||||
playerState_t *clients, int sizeofGameClient ) {
|
||||
sv.gentities = gEnts;
|
||||
sv.gentitySize = sizeofGEntity_t;
|
||||
sv.num_entities = numGEntities;
|
||||
|
||||
sv.gameClients = clients;
|
||||
sv.gameClientSize = sizeofGameClient;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_GetUsercmd
|
||||
|
||||
===============
|
||||
*/
|
||||
void SV_GetUsercmd( int clientNum, usercmd_t *cmd ) {
|
||||
if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) {
|
||||
Com_Error( ERR_DROP, "SV_GetUsercmd: bad clientNum:%i", clientNum );
|
||||
}
|
||||
*cmd = svs.clients[clientNum].lastUsercmd;
|
||||
}
|
||||
|
||||
//==============================================
|
||||
|
||||
static int FloatAsInt( float f ) {
|
||||
union
|
||||
{
|
||||
int i;
|
||||
float f;
|
||||
} temp;
|
||||
|
||||
temp.f = f;
|
||||
return temp.i;
|
||||
}
|
||||
|
||||
/*
|
||||
====================
|
||||
SV_GameSystemCalls
|
||||
|
||||
The module is making a system call
|
||||
====================
|
||||
*/
|
||||
//rcg010207 - see my comments in VM_DllSyscall(), in qcommon/vm.c ...
|
||||
#if ((defined __linux__) && (defined __powerpc__))
|
||||
#define VMA(x) ((void *) args[x])
|
||||
#else
|
||||
#define VMA(x) VM_ArgPtr(args[x])
|
||||
#endif
|
||||
|
||||
#define VMF(x) ((float *)args)[x]
|
||||
|
||||
int SV_GameSystemCalls( int *args ) {
|
||||
switch( args[0] ) {
|
||||
case G_PRINT:
|
||||
Com_Printf( "%s", VMA(1) );
|
||||
return 0;
|
||||
case G_ERROR:
|
||||
Com_Error( ERR_DROP, "%s", VMA(1) );
|
||||
return 0;
|
||||
case G_MILLISECONDS:
|
||||
return Sys_Milliseconds();
|
||||
case G_CVAR_REGISTER:
|
||||
Cvar_Register( VMA(1), VMA(2), VMA(3), args[4] );
|
||||
return 0;
|
||||
case G_CVAR_UPDATE:
|
||||
Cvar_Update( VMA(1) );
|
||||
return 0;
|
||||
case G_CVAR_SET:
|
||||
Cvar_Set( (const char *)VMA(1), (const char *)VMA(2) );
|
||||
return 0;
|
||||
case G_CVAR_VARIABLE_INTEGER_VALUE:
|
||||
return Cvar_VariableIntegerValue( (const char *)VMA(1) );
|
||||
case G_CVAR_VARIABLE_STRING_BUFFER:
|
||||
Cvar_VariableStringBuffer( VMA(1), VMA(2), args[3] );
|
||||
return 0;
|
||||
case G_ARGC:
|
||||
return Cmd_Argc();
|
||||
case G_ARGV:
|
||||
Cmd_ArgvBuffer( args[1], VMA(2), args[3] );
|
||||
return 0;
|
||||
case G_SEND_CONSOLE_COMMAND:
|
||||
Cbuf_ExecuteText( args[1], VMA(2) );
|
||||
return 0;
|
||||
|
||||
case G_FS_FOPEN_FILE:
|
||||
return FS_FOpenFileByMode( VMA(1), VMA(2), args[3] );
|
||||
case G_FS_READ:
|
||||
FS_Read2( VMA(1), args[2], args[3] );
|
||||
return 0;
|
||||
case G_FS_WRITE:
|
||||
FS_Write( VMA(1), args[2], args[3] );
|
||||
return 0;
|
||||
case G_FS_FCLOSE_FILE:
|
||||
FS_FCloseFile( args[1] );
|
||||
return 0;
|
||||
case G_FS_GETFILELIST:
|
||||
return FS_GetFileList( VMA(1), VMA(2), VMA(3), args[4] );
|
||||
case G_FS_SEEK:
|
||||
return FS_Seek( args[1], args[2], args[3] );
|
||||
|
||||
case G_LOCATE_GAME_DATA:
|
||||
SV_LocateGameData( VMA(1), args[2], args[3], VMA(4), args[5] );
|
||||
return 0;
|
||||
case G_DROP_CLIENT:
|
||||
SV_GameDropClient( args[1], VMA(2) );
|
||||
return 0;
|
||||
case G_SEND_SERVER_COMMAND:
|
||||
SV_GameSendServerCommand( args[1], VMA(2) );
|
||||
return 0;
|
||||
case G_LINKENTITY:
|
||||
SV_LinkEntity( VMA(1) );
|
||||
return 0;
|
||||
case G_UNLINKENTITY:
|
||||
SV_UnlinkEntity( VMA(1) );
|
||||
return 0;
|
||||
case G_ENTITIES_IN_BOX:
|
||||
return SV_AreaEntities( VMA(1), VMA(2), VMA(3), args[4] );
|
||||
case G_ENTITY_CONTACT:
|
||||
return SV_EntityContact( VMA(1), VMA(2), VMA(3), /*int capsule*/ qfalse );
|
||||
case G_ENTITY_CONTACTCAPSULE:
|
||||
return SV_EntityContact( VMA(1), VMA(2), VMA(3), /*int capsule*/ qtrue );
|
||||
case G_TRACE:
|
||||
SV_Trace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], /*int capsule*/ qfalse );
|
||||
return 0;
|
||||
case G_TRACECAPSULE:
|
||||
SV_Trace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], /*int capsule*/ qtrue );
|
||||
return 0;
|
||||
case G_POINT_CONTENTS:
|
||||
return SV_PointContents( VMA(1), args[2] );
|
||||
case G_SET_BRUSH_MODEL:
|
||||
SV_SetBrushModel( VMA(1), VMA(2) );
|
||||
return 0;
|
||||
case G_IN_PVS:
|
||||
return SV_inPVS( VMA(1), VMA(2) );
|
||||
case G_IN_PVS_IGNORE_PORTALS:
|
||||
return SV_inPVSIgnorePortals( VMA(1), VMA(2) );
|
||||
|
||||
case G_SET_CONFIGSTRING:
|
||||
SV_SetConfigstring( args[1], VMA(2) );
|
||||
return 0;
|
||||
case G_GET_CONFIGSTRING:
|
||||
SV_GetConfigstring( args[1], VMA(2), args[3] );
|
||||
return 0;
|
||||
case G_SET_USERINFO:
|
||||
SV_SetUserinfo( args[1], VMA(2) );
|
||||
return 0;
|
||||
case G_GET_USERINFO:
|
||||
SV_GetUserinfo( args[1], VMA(2), args[3] );
|
||||
return 0;
|
||||
case G_GET_SERVERINFO:
|
||||
SV_GetServerinfo( VMA(1), args[2] );
|
||||
return 0;
|
||||
case G_ADJUST_AREA_PORTAL_STATE:
|
||||
SV_AdjustAreaPortalState( VMA(1), args[2] );
|
||||
return 0;
|
||||
case G_AREAS_CONNECTED:
|
||||
return CM_AreasConnected( args[1], args[2] );
|
||||
|
||||
case G_BOT_ALLOCATE_CLIENT:
|
||||
return SV_BotAllocateClient();
|
||||
case G_BOT_FREE_CLIENT:
|
||||
SV_BotFreeClient( args[1] );
|
||||
return 0;
|
||||
|
||||
case G_GET_USERCMD:
|
||||
SV_GetUsercmd( args[1], VMA(2) );
|
||||
return 0;
|
||||
case G_GET_ENTITY_TOKEN:
|
||||
{
|
||||
const char *s;
|
||||
|
||||
s = COM_Parse( &sv.entityParsePoint );
|
||||
Q_strncpyz( VMA(1), s, args[2] );
|
||||
if ( !sv.entityParsePoint && !s[0] ) {
|
||||
return qfalse;
|
||||
} else {
|
||||
return qtrue;
|
||||
}
|
||||
}
|
||||
|
||||
case G_DEBUG_POLYGON_CREATE:
|
||||
return BotImport_DebugPolygonCreate( args[1], args[2], VMA(3) );
|
||||
case G_DEBUG_POLYGON_DELETE:
|
||||
BotImport_DebugPolygonDelete( args[1] );
|
||||
return 0;
|
||||
case G_REAL_TIME:
|
||||
return Com_RealTime( VMA(1) );
|
||||
case G_SNAPVECTOR:
|
||||
Sys_SnapVector( VMA(1) );
|
||||
return 0;
|
||||
|
||||
//====================================
|
||||
|
||||
case BOTLIB_SETUP:
|
||||
return SV_BotLibSetup();
|
||||
case BOTLIB_SHUTDOWN:
|
||||
return SV_BotLibShutdown();
|
||||
case BOTLIB_LIBVAR_SET:
|
||||
return botlib_export->BotLibVarSet( VMA(1), VMA(2) );
|
||||
case BOTLIB_LIBVAR_GET:
|
||||
return botlib_export->BotLibVarGet( VMA(1), VMA(2), args[3] );
|
||||
|
||||
case BOTLIB_PC_ADD_GLOBAL_DEFINE:
|
||||
return botlib_export->PC_AddGlobalDefine( VMA(1) );
|
||||
case BOTLIB_PC_LOAD_SOURCE:
|
||||
return botlib_export->PC_LoadSourceHandle( VMA(1) );
|
||||
case BOTLIB_PC_FREE_SOURCE:
|
||||
return botlib_export->PC_FreeSourceHandle( args[1] );
|
||||
case BOTLIB_PC_READ_TOKEN:
|
||||
return botlib_export->PC_ReadTokenHandle( args[1], VMA(2) );
|
||||
case BOTLIB_PC_SOURCE_FILE_AND_LINE:
|
||||
return botlib_export->PC_SourceFileAndLine( args[1], VMA(2), VMA(3) );
|
||||
|
||||
case BOTLIB_START_FRAME:
|
||||
return botlib_export->BotLibStartFrame( VMF(1) );
|
||||
case BOTLIB_LOAD_MAP:
|
||||
return botlib_export->BotLibLoadMap( VMA(1) );
|
||||
case BOTLIB_UPDATENTITY:
|
||||
return botlib_export->BotLibUpdateEntity( args[1], VMA(2) );
|
||||
case BOTLIB_TEST:
|
||||
return botlib_export->Test( args[1], VMA(2), VMA(3), VMA(4) );
|
||||
|
||||
case BOTLIB_GET_SNAPSHOT_ENTITY:
|
||||
return SV_BotGetSnapshotEntity( args[1], args[2] );
|
||||
case BOTLIB_GET_CONSOLE_MESSAGE:
|
||||
return SV_BotGetConsoleMessage( args[1], VMA(2), args[3] );
|
||||
case BOTLIB_USER_COMMAND:
|
||||
SV_ClientThink( &svs.clients[args[1]], VMA(2) );
|
||||
return 0;
|
||||
|
||||
case BOTLIB_AAS_BBOX_AREAS:
|
||||
return botlib_export->aas.AAS_BBoxAreas( VMA(1), VMA(2), VMA(3), args[4] );
|
||||
case BOTLIB_AAS_AREA_INFO:
|
||||
return botlib_export->aas.AAS_AreaInfo( args[1], VMA(2) );
|
||||
case BOTLIB_AAS_ALTERNATIVE_ROUTE_GOAL:
|
||||
return botlib_export->aas.AAS_AlternativeRouteGoals( VMA(1), args[2], VMA(3), args[4], args[5], VMA(6), args[7], args[8] );
|
||||
case BOTLIB_AAS_ENTITY_INFO:
|
||||
botlib_export->aas.AAS_EntityInfo( args[1], VMA(2) );
|
||||
return 0;
|
||||
|
||||
case BOTLIB_AAS_INITIALIZED:
|
||||
return botlib_export->aas.AAS_Initialized();
|
||||
case BOTLIB_AAS_PRESENCE_TYPE_BOUNDING_BOX:
|
||||
botlib_export->aas.AAS_PresenceTypeBoundingBox( args[1], VMA(2), VMA(3) );
|
||||
return 0;
|
||||
case BOTLIB_AAS_TIME:
|
||||
return FloatAsInt( botlib_export->aas.AAS_Time() );
|
||||
|
||||
case BOTLIB_AAS_POINT_AREA_NUM:
|
||||
return botlib_export->aas.AAS_PointAreaNum( VMA(1) );
|
||||
case BOTLIB_AAS_POINT_REACHABILITY_AREA_INDEX:
|
||||
return botlib_export->aas.AAS_PointReachabilityAreaIndex( VMA(1) );
|
||||
case BOTLIB_AAS_TRACE_AREAS:
|
||||
return botlib_export->aas.AAS_TraceAreas( VMA(1), VMA(2), VMA(3), VMA(4), args[5] );
|
||||
|
||||
case BOTLIB_AAS_POINT_CONTENTS:
|
||||
return botlib_export->aas.AAS_PointContents( VMA(1) );
|
||||
case BOTLIB_AAS_NEXT_BSP_ENTITY:
|
||||
return botlib_export->aas.AAS_NextBSPEntity( args[1] );
|
||||
case BOTLIB_AAS_VALUE_FOR_BSP_EPAIR_KEY:
|
||||
return botlib_export->aas.AAS_ValueForBSPEpairKey( args[1], VMA(2), VMA(3), args[4] );
|
||||
case BOTLIB_AAS_VECTOR_FOR_BSP_EPAIR_KEY:
|
||||
return botlib_export->aas.AAS_VectorForBSPEpairKey( args[1], VMA(2), VMA(3) );
|
||||
case BOTLIB_AAS_FLOAT_FOR_BSP_EPAIR_KEY:
|
||||
return botlib_export->aas.AAS_FloatForBSPEpairKey( args[1], VMA(2), VMA(3) );
|
||||
case BOTLIB_AAS_INT_FOR_BSP_EPAIR_KEY:
|
||||
return botlib_export->aas.AAS_IntForBSPEpairKey( args[1], VMA(2), VMA(3) );
|
||||
|
||||
case BOTLIB_AAS_AREA_REACHABILITY:
|
||||
return botlib_export->aas.AAS_AreaReachability( args[1] );
|
||||
|
||||
case BOTLIB_AAS_AREA_TRAVEL_TIME_TO_GOAL_AREA:
|
||||
return botlib_export->aas.AAS_AreaTravelTimeToGoalArea( args[1], VMA(2), args[3], args[4] );
|
||||
case BOTLIB_AAS_ENABLE_ROUTING_AREA:
|
||||
return botlib_export->aas.AAS_EnableRoutingArea( args[1], args[2] );
|
||||
case BOTLIB_AAS_PREDICT_ROUTE:
|
||||
return botlib_export->aas.AAS_PredictRoute( VMA(1), args[2], VMA(3), args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11] );
|
||||
|
||||
case BOTLIB_AAS_SWIMMING:
|
||||
return botlib_export->aas.AAS_Swimming( VMA(1) );
|
||||
case BOTLIB_AAS_PREDICT_CLIENT_MOVEMENT:
|
||||
return botlib_export->aas.AAS_PredictClientMovement( VMA(1), args[2], VMA(3), args[4], args[5],
|
||||
VMA(6), VMA(7), args[8], args[9], VMF(10), args[11], args[12], args[13] );
|
||||
|
||||
case BOTLIB_EA_SAY:
|
||||
botlib_export->ea.EA_Say( args[1], VMA(2) );
|
||||
return 0;
|
||||
case BOTLIB_EA_SAY_TEAM:
|
||||
botlib_export->ea.EA_SayTeam( args[1], VMA(2) );
|
||||
return 0;
|
||||
case BOTLIB_EA_COMMAND:
|
||||
botlib_export->ea.EA_Command( args[1], VMA(2) );
|
||||
return 0;
|
||||
|
||||
case BOTLIB_EA_ACTION:
|
||||
botlib_export->ea.EA_Action( args[1], args[2] );
|
||||
break;
|
||||
case BOTLIB_EA_GESTURE:
|
||||
botlib_export->ea.EA_Gesture( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_EA_TALK:
|
||||
botlib_export->ea.EA_Talk( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_EA_ATTACK:
|
||||
botlib_export->ea.EA_Attack( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_EA_USE:
|
||||
botlib_export->ea.EA_Use( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_EA_RESPAWN:
|
||||
botlib_export->ea.EA_Respawn( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_EA_CROUCH:
|
||||
botlib_export->ea.EA_Crouch( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_EA_MOVE_UP:
|
||||
botlib_export->ea.EA_MoveUp( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_EA_MOVE_DOWN:
|
||||
botlib_export->ea.EA_MoveDown( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_EA_MOVE_FORWARD:
|
||||
botlib_export->ea.EA_MoveForward( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_EA_MOVE_BACK:
|
||||
botlib_export->ea.EA_MoveBack( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_EA_MOVE_LEFT:
|
||||
botlib_export->ea.EA_MoveLeft( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_EA_MOVE_RIGHT:
|
||||
botlib_export->ea.EA_MoveRight( args[1] );
|
||||
return 0;
|
||||
|
||||
case BOTLIB_EA_SELECT_WEAPON:
|
||||
botlib_export->ea.EA_SelectWeapon( args[1], args[2] );
|
||||
return 0;
|
||||
case BOTLIB_EA_JUMP:
|
||||
botlib_export->ea.EA_Jump( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_EA_DELAYED_JUMP:
|
||||
botlib_export->ea.EA_DelayedJump( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_EA_MOVE:
|
||||
botlib_export->ea.EA_Move( args[1], VMA(2), VMF(3) );
|
||||
return 0;
|
||||
case BOTLIB_EA_VIEW:
|
||||
botlib_export->ea.EA_View( args[1], VMA(2) );
|
||||
return 0;
|
||||
|
||||
case BOTLIB_EA_END_REGULAR:
|
||||
botlib_export->ea.EA_EndRegular( args[1], VMF(2) );
|
||||
return 0;
|
||||
case BOTLIB_EA_GET_INPUT:
|
||||
botlib_export->ea.EA_GetInput( args[1], VMF(2), VMA(3) );
|
||||
return 0;
|
||||
case BOTLIB_EA_RESET_INPUT:
|
||||
botlib_export->ea.EA_ResetInput( args[1] );
|
||||
return 0;
|
||||
|
||||
case BOTLIB_AI_LOAD_CHARACTER:
|
||||
return botlib_export->ai.BotLoadCharacter( VMA(1), VMF(2) );
|
||||
case BOTLIB_AI_FREE_CHARACTER:
|
||||
botlib_export->ai.BotFreeCharacter( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_AI_CHARACTERISTIC_FLOAT:
|
||||
return FloatAsInt( botlib_export->ai.Characteristic_Float( args[1], args[2] ) );
|
||||
case BOTLIB_AI_CHARACTERISTIC_BFLOAT:
|
||||
return FloatAsInt( botlib_export->ai.Characteristic_BFloat( args[1], args[2], VMF(3), VMF(4) ) );
|
||||
case BOTLIB_AI_CHARACTERISTIC_INTEGER:
|
||||
return botlib_export->ai.Characteristic_Integer( args[1], args[2] );
|
||||
case BOTLIB_AI_CHARACTERISTIC_BINTEGER:
|
||||
return botlib_export->ai.Characteristic_BInteger( args[1], args[2], args[3], args[4] );
|
||||
case BOTLIB_AI_CHARACTERISTIC_STRING:
|
||||
botlib_export->ai.Characteristic_String( args[1], args[2], VMA(3), args[4] );
|
||||
return 0;
|
||||
|
||||
case BOTLIB_AI_ALLOC_CHAT_STATE:
|
||||
return botlib_export->ai.BotAllocChatState();
|
||||
case BOTLIB_AI_FREE_CHAT_STATE:
|
||||
botlib_export->ai.BotFreeChatState( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_AI_QUEUE_CONSOLE_MESSAGE:
|
||||
botlib_export->ai.BotQueueConsoleMessage( args[1], args[2], VMA(3) );
|
||||
return 0;
|
||||
case BOTLIB_AI_REMOVE_CONSOLE_MESSAGE:
|
||||
botlib_export->ai.BotRemoveConsoleMessage( args[1], args[2] );
|
||||
return 0;
|
||||
case BOTLIB_AI_NEXT_CONSOLE_MESSAGE:
|
||||
return botlib_export->ai.BotNextConsoleMessage( args[1], VMA(2) );
|
||||
case BOTLIB_AI_NUM_CONSOLE_MESSAGE:
|
||||
return botlib_export->ai.BotNumConsoleMessages( args[1] );
|
||||
case BOTLIB_AI_INITIAL_CHAT:
|
||||
botlib_export->ai.BotInitialChat( args[1], VMA(2), args[3], VMA(4), VMA(5), VMA(6), VMA(7), VMA(8), VMA(9), VMA(10), VMA(11) );
|
||||
return 0;
|
||||
case BOTLIB_AI_NUM_INITIAL_CHATS:
|
||||
return botlib_export->ai.BotNumInitialChats( args[1], VMA(2) );
|
||||
case BOTLIB_AI_REPLY_CHAT:
|
||||
return botlib_export->ai.BotReplyChat( args[1], VMA(2), args[3], args[4], VMA(5), VMA(6), VMA(7), VMA(8), VMA(9), VMA(10), VMA(11), VMA(12) );
|
||||
case BOTLIB_AI_CHAT_LENGTH:
|
||||
return botlib_export->ai.BotChatLength( args[1] );
|
||||
case BOTLIB_AI_ENTER_CHAT:
|
||||
botlib_export->ai.BotEnterChat( args[1], args[2], args[3] );
|
||||
return 0;
|
||||
case BOTLIB_AI_GET_CHAT_MESSAGE:
|
||||
botlib_export->ai.BotGetChatMessage( args[1], VMA(2), args[3] );
|
||||
return 0;
|
||||
case BOTLIB_AI_STRING_CONTAINS:
|
||||
return botlib_export->ai.StringContains( VMA(1), VMA(2), args[3] );
|
||||
case BOTLIB_AI_FIND_MATCH:
|
||||
return botlib_export->ai.BotFindMatch( VMA(1), VMA(2), args[3] );
|
||||
case BOTLIB_AI_MATCH_VARIABLE:
|
||||
botlib_export->ai.BotMatchVariable( VMA(1), args[2], VMA(3), args[4] );
|
||||
return 0;
|
||||
case BOTLIB_AI_UNIFY_WHITE_SPACES:
|
||||
botlib_export->ai.UnifyWhiteSpaces( VMA(1) );
|
||||
return 0;
|
||||
case BOTLIB_AI_REPLACE_SYNONYMS:
|
||||
botlib_export->ai.BotReplaceSynonyms( VMA(1), args[2] );
|
||||
return 0;
|
||||
case BOTLIB_AI_LOAD_CHAT_FILE:
|
||||
return botlib_export->ai.BotLoadChatFile( args[1], VMA(2), VMA(3) );
|
||||
case BOTLIB_AI_SET_CHAT_GENDER:
|
||||
botlib_export->ai.BotSetChatGender( args[1], args[2] );
|
||||
return 0;
|
||||
case BOTLIB_AI_SET_CHAT_NAME:
|
||||
botlib_export->ai.BotSetChatName( args[1], VMA(2), args[3] );
|
||||
return 0;
|
||||
|
||||
case BOTLIB_AI_RESET_GOAL_STATE:
|
||||
botlib_export->ai.BotResetGoalState( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_AI_RESET_AVOID_GOALS:
|
||||
botlib_export->ai.BotResetAvoidGoals( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_AI_REMOVE_FROM_AVOID_GOALS:
|
||||
botlib_export->ai.BotRemoveFromAvoidGoals( args[1], args[2] );
|
||||
return 0;
|
||||
case BOTLIB_AI_PUSH_GOAL:
|
||||
botlib_export->ai.BotPushGoal( args[1], VMA(2) );
|
||||
return 0;
|
||||
case BOTLIB_AI_POP_GOAL:
|
||||
botlib_export->ai.BotPopGoal( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_AI_EMPTY_GOAL_STACK:
|
||||
botlib_export->ai.BotEmptyGoalStack( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_AI_DUMP_AVOID_GOALS:
|
||||
botlib_export->ai.BotDumpAvoidGoals( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_AI_DUMP_GOAL_STACK:
|
||||
botlib_export->ai.BotDumpGoalStack( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_AI_GOAL_NAME:
|
||||
botlib_export->ai.BotGoalName( args[1], VMA(2), args[3] );
|
||||
return 0;
|
||||
case BOTLIB_AI_GET_TOP_GOAL:
|
||||
return botlib_export->ai.BotGetTopGoal( args[1], VMA(2) );
|
||||
case BOTLIB_AI_GET_SECOND_GOAL:
|
||||
return botlib_export->ai.BotGetSecondGoal( args[1], VMA(2) );
|
||||
case BOTLIB_AI_CHOOSE_LTG_ITEM:
|
||||
return botlib_export->ai.BotChooseLTGItem( args[1], VMA(2), VMA(3), args[4] );
|
||||
case BOTLIB_AI_CHOOSE_NBG_ITEM:
|
||||
return botlib_export->ai.BotChooseNBGItem( args[1], VMA(2), VMA(3), args[4], VMA(5), VMF(6) );
|
||||
case BOTLIB_AI_TOUCHING_GOAL:
|
||||
return botlib_export->ai.BotTouchingGoal( VMA(1), VMA(2) );
|
||||
case BOTLIB_AI_ITEM_GOAL_IN_VIS_BUT_NOT_VISIBLE:
|
||||
return botlib_export->ai.BotItemGoalInVisButNotVisible( args[1], VMA(2), VMA(3), VMA(4) );
|
||||
case BOTLIB_AI_GET_LEVEL_ITEM_GOAL:
|
||||
return botlib_export->ai.BotGetLevelItemGoal( args[1], VMA(2), VMA(3) );
|
||||
case BOTLIB_AI_GET_NEXT_CAMP_SPOT_GOAL:
|
||||
return botlib_export->ai.BotGetNextCampSpotGoal( args[1], VMA(2) );
|
||||
case BOTLIB_AI_GET_MAP_LOCATION_GOAL:
|
||||
return botlib_export->ai.BotGetMapLocationGoal( VMA(1), VMA(2) );
|
||||
case BOTLIB_AI_AVOID_GOAL_TIME:
|
||||
return FloatAsInt( botlib_export->ai.BotAvoidGoalTime( args[1], args[2] ) );
|
||||
case BOTLIB_AI_SET_AVOID_GOAL_TIME:
|
||||
botlib_export->ai.BotSetAvoidGoalTime( args[1], args[2], VMF(3));
|
||||
return 0;
|
||||
case BOTLIB_AI_INIT_LEVEL_ITEMS:
|
||||
botlib_export->ai.BotInitLevelItems();
|
||||
return 0;
|
||||
case BOTLIB_AI_UPDATE_ENTITY_ITEMS:
|
||||
botlib_export->ai.BotUpdateEntityItems();
|
||||
return 0;
|
||||
case BOTLIB_AI_LOAD_ITEM_WEIGHTS:
|
||||
return botlib_export->ai.BotLoadItemWeights( args[1], VMA(2) );
|
||||
case BOTLIB_AI_FREE_ITEM_WEIGHTS:
|
||||
botlib_export->ai.BotFreeItemWeights( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_AI_INTERBREED_GOAL_FUZZY_LOGIC:
|
||||
botlib_export->ai.BotInterbreedGoalFuzzyLogic( args[1], args[2], args[3] );
|
||||
return 0;
|
||||
case BOTLIB_AI_SAVE_GOAL_FUZZY_LOGIC:
|
||||
botlib_export->ai.BotSaveGoalFuzzyLogic( args[1], VMA(2) );
|
||||
return 0;
|
||||
case BOTLIB_AI_MUTATE_GOAL_FUZZY_LOGIC:
|
||||
botlib_export->ai.BotMutateGoalFuzzyLogic( args[1], VMF(2) );
|
||||
return 0;
|
||||
case BOTLIB_AI_ALLOC_GOAL_STATE:
|
||||
return botlib_export->ai.BotAllocGoalState( args[1] );
|
||||
case BOTLIB_AI_FREE_GOAL_STATE:
|
||||
botlib_export->ai.BotFreeGoalState( args[1] );
|
||||
return 0;
|
||||
|
||||
case BOTLIB_AI_RESET_MOVE_STATE:
|
||||
botlib_export->ai.BotResetMoveState( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_AI_ADD_AVOID_SPOT:
|
||||
botlib_export->ai.BotAddAvoidSpot( args[1], VMA(2), VMF(3), args[4] );
|
||||
return 0;
|
||||
case BOTLIB_AI_MOVE_TO_GOAL:
|
||||
botlib_export->ai.BotMoveToGoal( VMA(1), args[2], VMA(3), args[4] );
|
||||
return 0;
|
||||
case BOTLIB_AI_MOVE_IN_DIRECTION:
|
||||
return botlib_export->ai.BotMoveInDirection( args[1], VMA(2), VMF(3), args[4] );
|
||||
case BOTLIB_AI_RESET_AVOID_REACH:
|
||||
botlib_export->ai.BotResetAvoidReach( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_AI_RESET_LAST_AVOID_REACH:
|
||||
botlib_export->ai.BotResetLastAvoidReach( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_AI_REACHABILITY_AREA:
|
||||
return botlib_export->ai.BotReachabilityArea( VMA(1), args[2] );
|
||||
case BOTLIB_AI_MOVEMENT_VIEW_TARGET:
|
||||
return botlib_export->ai.BotMovementViewTarget( args[1], VMA(2), args[3], VMF(4), VMA(5) );
|
||||
case BOTLIB_AI_PREDICT_VISIBLE_POSITION:
|
||||
return botlib_export->ai.BotPredictVisiblePosition( VMA(1), args[2], VMA(3), args[4], VMA(5) );
|
||||
case BOTLIB_AI_ALLOC_MOVE_STATE:
|
||||
return botlib_export->ai.BotAllocMoveState();
|
||||
case BOTLIB_AI_FREE_MOVE_STATE:
|
||||
botlib_export->ai.BotFreeMoveState( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_AI_INIT_MOVE_STATE:
|
||||
botlib_export->ai.BotInitMoveState( args[1], VMA(2) );
|
||||
return 0;
|
||||
|
||||
case BOTLIB_AI_CHOOSE_BEST_FIGHT_WEAPON:
|
||||
return botlib_export->ai.BotChooseBestFightWeapon( args[1], VMA(2) );
|
||||
case BOTLIB_AI_GET_WEAPON_INFO:
|
||||
botlib_export->ai.BotGetWeaponInfo( args[1], args[2], VMA(3) );
|
||||
return 0;
|
||||
case BOTLIB_AI_LOAD_WEAPON_WEIGHTS:
|
||||
return botlib_export->ai.BotLoadWeaponWeights( args[1], VMA(2) );
|
||||
case BOTLIB_AI_ALLOC_WEAPON_STATE:
|
||||
return botlib_export->ai.BotAllocWeaponState();
|
||||
case BOTLIB_AI_FREE_WEAPON_STATE:
|
||||
botlib_export->ai.BotFreeWeaponState( args[1] );
|
||||
return 0;
|
||||
case BOTLIB_AI_RESET_WEAPON_STATE:
|
||||
botlib_export->ai.BotResetWeaponState( args[1] );
|
||||
return 0;
|
||||
|
||||
case BOTLIB_AI_GENETIC_PARENTS_AND_CHILD_SELECTION:
|
||||
return botlib_export->ai.GeneticParentsAndChildSelection(args[1], VMA(2), VMA(3), VMA(4), VMA(5));
|
||||
|
||||
case TRAP_MEMSET:
|
||||
Com_Memset( VMA(1), args[2], args[3] );
|
||||
return 0;
|
||||
|
||||
case TRAP_MEMCPY:
|
||||
Com_Memcpy( VMA(1), VMA(2), args[3] );
|
||||
return 0;
|
||||
|
||||
case TRAP_STRNCPY:
|
||||
return (int)strncpy( VMA(1), VMA(2), args[3] );
|
||||
|
||||
case TRAP_SIN:
|
||||
return FloatAsInt( sin( VMF(1) ) );
|
||||
|
||||
case TRAP_COS:
|
||||
return FloatAsInt( cos( VMF(1) ) );
|
||||
|
||||
case TRAP_ATAN2:
|
||||
return FloatAsInt( atan2( VMF(1), VMF(2) ) );
|
||||
|
||||
case TRAP_SQRT:
|
||||
return FloatAsInt( sqrt( VMF(1) ) );
|
||||
|
||||
case TRAP_MATRIXMULTIPLY:
|
||||
MatrixMultiply( VMA(1), VMA(2), VMA(3) );
|
||||
return 0;
|
||||
|
||||
case TRAP_ANGLEVECTORS:
|
||||
AngleVectors( VMA(1), VMA(2), VMA(3), VMA(4) );
|
||||
return 0;
|
||||
|
||||
case TRAP_PERPENDICULARVECTOR:
|
||||
PerpendicularVector( VMA(1), VMA(2) );
|
||||
return 0;
|
||||
|
||||
case TRAP_FLOOR:
|
||||
return FloatAsInt( floor( VMF(1) ) );
|
||||
|
||||
case TRAP_CEIL:
|
||||
return FloatAsInt( ceil( VMF(1) ) );
|
||||
|
||||
|
||||
default:
|
||||
Com_Error( ERR_DROP, "Bad game system trap: %i", args[0] );
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_ShutdownGameProgs
|
||||
|
||||
Called every time a map changes
|
||||
===============
|
||||
*/
|
||||
void SV_ShutdownGameProgs( void ) {
|
||||
if ( !gvm ) {
|
||||
return;
|
||||
}
|
||||
VM_Call( gvm, GAME_SHUTDOWN, qfalse );
|
||||
VM_Free( gvm );
|
||||
gvm = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_InitGameVM
|
||||
|
||||
Called for both a full init and a restart
|
||||
==================
|
||||
*/
|
||||
static void SV_InitGameVM( qboolean restart ) {
|
||||
int i;
|
||||
|
||||
// start the entity parsing at the beginning
|
||||
sv.entityParsePoint = CM_EntityString();
|
||||
|
||||
// clear all gentity pointers that might still be set from
|
||||
// a previous level
|
||||
// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=522
|
||||
// now done before GAME_INIT call
|
||||
for ( i = 0 ; i < sv_maxclients->integer ; i++ ) {
|
||||
svs.clients[i].gentity = NULL;
|
||||
}
|
||||
|
||||
// use the current msec count for a random seed
|
||||
// init for this gamestate
|
||||
VM_Call( gvm, GAME_INIT, svs.time, Com_Milliseconds(), restart );
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
===================
|
||||
SV_RestartGameProgs
|
||||
|
||||
Called on a map_restart, but not on a normal map change
|
||||
===================
|
||||
*/
|
||||
void SV_RestartGameProgs( void ) {
|
||||
if ( !gvm ) {
|
||||
return;
|
||||
}
|
||||
VM_Call( gvm, GAME_SHUTDOWN, qtrue );
|
||||
|
||||
// do a restart instead of a free
|
||||
gvm = VM_Restart( gvm );
|
||||
if ( !gvm ) { // bk001212 - as done below
|
||||
Com_Error( ERR_FATAL, "VM_Restart on game failed" );
|
||||
}
|
||||
|
||||
SV_InitGameVM( qtrue );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_InitGameProgs
|
||||
|
||||
Called on a normal map change, not on a map_restart
|
||||
===============
|
||||
*/
|
||||
void SV_InitGameProgs( void ) {
|
||||
cvar_t *var;
|
||||
//FIXME these are temp while I make bots run in vm
|
||||
extern int bot_enable;
|
||||
|
||||
var = Cvar_Get( "bot_enable", "1", CVAR_LATCH );
|
||||
if ( var ) {
|
||||
bot_enable = var->integer;
|
||||
}
|
||||
else {
|
||||
bot_enable = 0;
|
||||
}
|
||||
|
||||
// load the dll or bytecode
|
||||
gvm = VM_Create( "qagame", SV_GameSystemCalls, Cvar_VariableValue( "vm_game" ) );
|
||||
if ( !gvm ) {
|
||||
Com_Error( ERR_FATAL, "VM_Create on game failed" );
|
||||
}
|
||||
|
||||
SV_InitGameVM( qfalse );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
====================
|
||||
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 VM_Call( gvm, GAME_CONSOLE_COMMAND );
|
||||
}
|
||||
|
695
code/server/sv_init.c
Normal file
695
code/server/sv_init.c
Normal file
|
@ -0,0 +1,695 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code is distributed in the hope that it will be
|
||||
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Foobar; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_SetConfigstring
|
||||
|
||||
===============
|
||||
*/
|
||||
void SV_SetConfigstring (int index, const char *val) {
|
||||
int len, i;
|
||||
int maxChunkSize = MAX_STRING_CHARS - 24;
|
||||
client_t *client;
|
||||
|
||||
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.restarting ) {
|
||||
|
||||
// send the data to all relevent clients
|
||||
for (i = 0, client = svs.clients; i < sv_maxclients->integer ; i++, client++) {
|
||||
if ( client->state < CS_PRIMED ) {
|
||||
continue;
|
||||
}
|
||||
// do not always send server info to all clients
|
||||
if ( index == CS_SERVERINFO && client->gentity && (client->gentity->r.svFlags & SVF_NOSERVERINFO) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
len = strlen( val );
|
||||
if( len >= maxChunkSize ) {
|
||||
int sent = 0;
|
||||
int remaining = len;
|
||||
char *cmd;
|
||||
char buf[MAX_STRING_CHARS];
|
||||
|
||||
while (remaining > 0 ) {
|
||||
if ( sent == 0 ) {
|
||||
cmd = "bcs0";
|
||||
}
|
||||
else if( remaining < maxChunkSize ) {
|
||||
cmd = "bcs2";
|
||||
}
|
||||
else {
|
||||
cmd = "bcs1";
|
||||
}
|
||||
Q_strncpyz( buf, &val[sent], maxChunkSize );
|
||||
|
||||
SV_SendServerCommand( client, "%s %i \"%s\"\n", cmd, index, buf );
|
||||
|
||||
sent += (maxChunkSize - 1);
|
||||
remaining -= (maxChunkSize - 1);
|
||||
}
|
||||
} else {
|
||||
// standard cs, just send it
|
||||
SV_SendServerCommand( client, "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 >= sv_maxclients->integer ) {
|
||||
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 ) );
|
||||
Q_strncpyz( svs.clients[index].name, Info_ValueForKey( val, "name" ), sizeof(svs.clients[index].name) );
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
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 >= sv_maxclients->integer ) {
|
||||
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 ) {
|
||||
sharedEntity_t *svent;
|
||||
int entnum;
|
||||
|
||||
for ( entnum = 1; entnum < sv.num_entities ; entnum++ ) {
|
||||
svent = SV_GentityNum(entnum);
|
||||
if (!svent->r.linked) {
|
||||
continue;
|
||||
}
|
||||
svent->s.number = entnum;
|
||||
|
||||
//
|
||||
// take current state as baseline
|
||||
//
|
||||
sv.svEntities[entnum].baseline = svent->s;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_BoundMaxClients
|
||||
|
||||
===============
|
||||
*/
|
||||
void SV_BoundMaxClients( int minimum ) {
|
||||
// get the current maxclients value
|
||||
Cvar_Get( "sv_maxclients", "8", 0 );
|
||||
|
||||
sv_maxclients->modified = qfalse;
|
||||
|
||||
if ( sv_maxclients->integer < minimum ) {
|
||||
Cvar_Set( "sv_maxclients", va("%i", minimum) );
|
||||
} else if ( sv_maxclients->integer > MAX_CLIENTS ) {
|
||||
Cvar_Set( "sv_maxclients", va("%i", MAX_CLIENTS) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_Startup
|
||||
|
||||
Called when a host starts a map when it wasn't running
|
||||
one before. Successive map or map_restart commands will
|
||||
NOT cause this to be called, unless the game is exited to
|
||||
the menu system first.
|
||||
===============
|
||||
*/
|
||||
void SV_Startup( void ) {
|
||||
if ( svs.initialized ) {
|
||||
Com_Error( ERR_FATAL, "SV_Startup: svs.initialized" );
|
||||
}
|
||||
SV_BoundMaxClients( 1 );
|
||||
|
||||
svs.clients = Z_Malloc (sizeof(client_t) * sv_maxclients->integer );
|
||||
if ( com_dedicated->integer ) {
|
||||
svs.numSnapshotEntities = sv_maxclients->integer * PACKET_BACKUP * 64;
|
||||
} else {
|
||||
// we don't need nearly as many when playing locally
|
||||
svs.numSnapshotEntities = sv_maxclients->integer * 4 * 64;
|
||||
}
|
||||
svs.initialized = qtrue;
|
||||
|
||||
Cvar_Set( "sv_running", "1" );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_ChangeMaxClients
|
||||
==================
|
||||
*/
|
||||
void SV_ChangeMaxClients( void ) {
|
||||
int oldMaxClients;
|
||||
int i;
|
||||
client_t *oldClients;
|
||||
int count;
|
||||
|
||||
// get the highest client number in use
|
||||
count = 0;
|
||||
for ( i = 0 ; i < sv_maxclients->integer ; i++ ) {
|
||||
if ( svs.clients[i].state >= CS_CONNECTED ) {
|
||||
if (i > count)
|
||||
count = i;
|
||||
}
|
||||
}
|
||||
count++;
|
||||
|
||||
oldMaxClients = sv_maxclients->integer;
|
||||
// never go below the highest client number in use
|
||||
SV_BoundMaxClients( count );
|
||||
// if still the same
|
||||
if ( sv_maxclients->integer == oldMaxClients ) {
|
||||
return;
|
||||
}
|
||||
|
||||
oldClients = Hunk_AllocateTempMemory( count * sizeof(client_t) );
|
||||
// copy the clients to hunk memory
|
||||
for ( i = 0 ; i < count ; i++ ) {
|
||||
if ( svs.clients[i].state >= CS_CONNECTED ) {
|
||||
oldClients[i] = svs.clients[i];
|
||||
}
|
||||
else {
|
||||
Com_Memset(&oldClients[i], 0, sizeof(client_t));
|
||||
}
|
||||
}
|
||||
|
||||
// free old clients arrays
|
||||
Z_Free( svs.clients );
|
||||
|
||||
// allocate new clients
|
||||
svs.clients = Z_Malloc ( sv_maxclients->integer * sizeof(client_t) );
|
||||
Com_Memset( svs.clients, 0, sv_maxclients->integer * sizeof(client_t) );
|
||||
|
||||
// copy the clients over
|
||||
for ( i = 0 ; i < count ; i++ ) {
|
||||
if ( oldClients[i].state >= CS_CONNECTED ) {
|
||||
svs.clients[i] = oldClients[i];
|
||||
}
|
||||
}
|
||||
|
||||
// free the old clients on the hunk
|
||||
Hunk_FreeTempMemory( oldClients );
|
||||
|
||||
// allocate new snapshot entities
|
||||
if ( com_dedicated->integer ) {
|
||||
svs.numSnapshotEntities = sv_maxclients->integer * PACKET_BACKUP * 64;
|
||||
} else {
|
||||
// we don't need nearly as many when playing locally
|
||||
svs.numSnapshotEntities = sv_maxclients->integer * 4 * 64;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
SV_ClearServer
|
||||
================
|
||||
*/
|
||||
void SV_ClearServer(void) {
|
||||
int i;
|
||||
|
||||
for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) {
|
||||
if ( sv.configstrings[i] ) {
|
||||
Z_Free( sv.configstrings[i] );
|
||||
}
|
||||
}
|
||||
Com_Memset (&sv, 0, sizeof(sv));
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
SV_TouchCGame
|
||||
|
||||
touch the cgame.vm so that a pure client can load it if it's in a seperate pk3
|
||||
================
|
||||
*/
|
||||
void SV_TouchCGame(void) {
|
||||
fileHandle_t f;
|
||||
char filename[MAX_QPATH];
|
||||
|
||||
Com_sprintf( filename, sizeof(filename), "vm/%s.qvm", "cgame" );
|
||||
FS_FOpenFileRead( filename, &f, qfalse );
|
||||
if ( f ) {
|
||||
FS_FCloseFile( f );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
SV_SpawnServer
|
||||
|
||||
Change the server to a new map, taking all connected
|
||||
clients along with it.
|
||||
This is NOT called for map_restart
|
||||
================
|
||||
*/
|
||||
void SV_SpawnServer( char *server, qboolean killBots ) {
|
||||
int i;
|
||||
int checksum;
|
||||
qboolean isBot;
|
||||
char systemInfo[16384];
|
||||
const char *p;
|
||||
|
||||
// shut down the existing game if it is running
|
||||
SV_ShutdownGameProgs();
|
||||
|
||||
Com_Printf ("------ Server Initialization ------\n");
|
||||
Com_Printf ("Server: %s\n",server);
|
||||
|
||||
// if not running a dedicated server CL_MapLoading will connect the client to the server
|
||||
// also print some status stuff
|
||||
CL_MapLoading();
|
||||
|
||||
// make sure all the client stuff is unloaded
|
||||
CL_ShutdownAll();
|
||||
|
||||
// clear the whole hunk because we're (re)loading the server
|
||||
Hunk_Clear();
|
||||
|
||||
// clear collision map data
|
||||
CM_ClearMap();
|
||||
|
||||
// init client structures and svs.numSnapshotEntities
|
||||
if ( !Cvar_VariableValue("sv_running") ) {
|
||||
SV_Startup();
|
||||
} else {
|
||||
// check for maxclients change
|
||||
if ( sv_maxclients->modified ) {
|
||||
SV_ChangeMaxClients();
|
||||
}
|
||||
}
|
||||
|
||||
// clear pak references
|
||||
FS_ClearPakReferences(0);
|
||||
|
||||
// allocate the snapshot entities on the hunk
|
||||
svs.snapshotEntities = Hunk_Alloc( sizeof(entityState_t)*svs.numSnapshotEntities, h_high );
|
||||
svs.nextSnapshotEntities = 0;
|
||||
|
||||
// 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", "map_restart 0");
|
||||
// Cvar_Set( "nextmap", va("map %s", server) );
|
||||
|
||||
// wipe the entire per-level structure
|
||||
SV_ClearServer();
|
||||
for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) {
|
||||
sv.configstrings[i] = CopyString("");
|
||||
}
|
||||
|
||||
// make sure we are not paused
|
||||
Cvar_Set("cl_paused", "0");
|
||||
|
||||
// get a new checksum feed and restart the file system
|
||||
srand(Com_Milliseconds());
|
||||
sv.checksumFeed = ( ((int) rand() << 16) ^ rand() ) ^ Com_Milliseconds();
|
||||
FS_Restart( sv.checksumFeed );
|
||||
|
||||
CM_LoadMap( va("maps/%s.bsp", server), qfalse, &checksum );
|
||||
|
||||
// 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;
|
||||
sv.restartedServerId = sv.serverId; // I suppose the init here is just to be safe
|
||||
sv.checksumFeedServerId = sv.serverId;
|
||||
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();
|
||||
|
||||
// don't allow a map_restart if game is modified
|
||||
sv_gametype->modified = qfalse;
|
||||
|
||||
// run a few frames to allow everything to settle
|
||||
for ( i = 0 ;i < 3 ; i++ ) {
|
||||
VM_Call( gvm, GAME_RUN_FRAME, svs.time );
|
||||
SV_BotFrame( svs.time );
|
||||
svs.time += 100;
|
||||
}
|
||||
|
||||
// create a baseline for more efficient communications
|
||||
SV_CreateBaseline ();
|
||||
|
||||
for (i=0 ; i<sv_maxclients->integer ; i++) {
|
||||
// send the new gamestate to all connected clients
|
||||
if (svs.clients[i].state >= CS_CONNECTED) {
|
||||
char *denied;
|
||||
|
||||
if ( svs.clients[i].netchan.remoteAddress.type == NA_BOT ) {
|
||||
if ( killBots ) {
|
||||
SV_DropClient( &svs.clients[i], "" );
|
||||
continue;
|
||||
}
|
||||
isBot = qtrue;
|
||||
}
|
||||
else {
|
||||
isBot = qfalse;
|
||||
}
|
||||
|
||||
// connect the client again
|
||||
denied = VM_ExplicitArgPtr( gvm, VM_Call( gvm, GAME_CLIENT_CONNECT, i, qfalse, isBot ) ); // firstTime = qfalse
|
||||
if ( denied ) {
|
||||
// this generally shouldn't happen, because the client
|
||||
// was connected before the level change
|
||||
SV_DropClient( &svs.clients[i], denied );
|
||||
} else {
|
||||
if( !isBot ) {
|
||||
// when we get the next packet from a connected client,
|
||||
// the new gamestate will be sent
|
||||
svs.clients[i].state = CS_CONNECTED;
|
||||
}
|
||||
else {
|
||||
client_t *client;
|
||||
sharedEntity_t *ent;
|
||||
|
||||
client = &svs.clients[i];
|
||||
client->state = CS_ACTIVE;
|
||||
ent = SV_GentityNum( i );
|
||||
ent->s.number = i;
|
||||
client->gentity = ent;
|
||||
|
||||
client->deltaMessage = -1;
|
||||
client->nextSnapshotTime = svs.time; // generate a snapshot immediately
|
||||
|
||||
VM_Call( gvm, GAME_CLIENT_BEGIN, i );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// run another frame to allow things to look at all the players
|
||||
VM_Call( gvm, GAME_RUN_FRAME, svs.time );
|
||||
SV_BotFrame( svs.time );
|
||||
svs.time += 100;
|
||||
|
||||
if ( sv_pure->integer ) {
|
||||
// the server sends these to the clients so they will only
|
||||
// load pk3s also loaded at the server
|
||||
p = FS_LoadedPakChecksums();
|
||||
Cvar_Set( "sv_paks", p );
|
||||
if (strlen(p) == 0) {
|
||||
Com_Printf( "WARNING: sv_pure set but no PK3 files loaded\n" );
|
||||
}
|
||||
p = FS_LoadedPakNames();
|
||||
Cvar_Set( "sv_pakNames", p );
|
||||
|
||||
// if a dedicated pure server we need to touch the cgame because it could be in a
|
||||
// seperate pk3 file and the client will need to load the latest cgame.qvm
|
||||
if ( com_dedicated->integer ) {
|
||||
SV_TouchCGame();
|
||||
}
|
||||
}
|
||||
else {
|
||||
Cvar_Set( "sv_paks", "" );
|
||||
Cvar_Set( "sv_pakNames", "" );
|
||||
}
|
||||
// the server sends these to the clients so they can figure
|
||||
// out which pk3s should be auto-downloaded
|
||||
p = FS_ReferencedPakChecksums();
|
||||
Cvar_Set( "sv_referencedPaks", p );
|
||||
p = FS_ReferencedPakNames();
|
||||
Cvar_Set( "sv_referencedPakNames", p );
|
||||
|
||||
// save systeminfo and serverinfo strings
|
||||
Q_strncpyz( systemInfo, Cvar_InfoString_Big( CVAR_SYSTEMINFO ), sizeof( systemInfo ) );
|
||||
cvar_modifiedFlags &= ~CVAR_SYSTEMINFO;
|
||||
SV_SetConfigstring( CS_SYSTEMINFO, 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
|
||||
SV_Heartbeat_f();
|
||||
|
||||
Hunk_SetMark();
|
||||
|
||||
Com_Printf ("-----------------------------------\n");
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_Init
|
||||
|
||||
Only called at main exe startup, not for each game
|
||||
===============
|
||||
*/
|
||||
void SV_BotInitBotLib(void);
|
||||
|
||||
void SV_Init (void) {
|
||||
SV_AddOperatorCommands ();
|
||||
|
||||
// serverinfo vars
|
||||
Cvar_Get ("dmflags", "0", CVAR_SERVERINFO);
|
||||
Cvar_Get ("fraglimit", "20", CVAR_SERVERINFO);
|
||||
Cvar_Get ("timelimit", "0", CVAR_SERVERINFO);
|
||||
sv_gametype = Cvar_Get ("g_gametype", "0", CVAR_SERVERINFO | CVAR_LATCH );
|
||||
Cvar_Get ("sv_keywords", "", CVAR_SERVERINFO);
|
||||
Cvar_Get ("protocol", va("%i", PROTOCOL_VERSION), CVAR_SERVERINFO | CVAR_ROM);
|
||||
sv_mapname = Cvar_Get ("mapname", "nomap", CVAR_SERVERINFO | CVAR_ROM);
|
||||
sv_privateClients = Cvar_Get ("sv_privateClients", "0", CVAR_SERVERINFO);
|
||||
sv_hostname = Cvar_Get ("sv_hostname", "noname", CVAR_SERVERINFO | CVAR_ARCHIVE );
|
||||
sv_maxclients = Cvar_Get ("sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH);
|
||||
|
||||
sv_maxRate = Cvar_Get ("sv_maxRate", "0", CVAR_ARCHIVE | CVAR_SERVERINFO );
|
||||
sv_minPing = Cvar_Get ("sv_minPing", "0", CVAR_ARCHIVE | CVAR_SERVERINFO );
|
||||
sv_maxPing = Cvar_Get ("sv_maxPing", "0", CVAR_ARCHIVE | CVAR_SERVERINFO );
|
||||
sv_floodProtect = Cvar_Get ("sv_floodProtect", "1", CVAR_ARCHIVE | CVAR_SERVERINFO );
|
||||
|
||||
// systeminfo
|
||||
Cvar_Get ("sv_cheats", "1", CVAR_SYSTEMINFO | CVAR_ROM );
|
||||
sv_serverid = Cvar_Get ("sv_serverid", "0", CVAR_SYSTEMINFO | CVAR_ROM );
|
||||
#ifndef DLL_ONLY // bk010216 - for DLL-only servers
|
||||
sv_pure = Cvar_Get ("sv_pure", "1", CVAR_SYSTEMINFO );
|
||||
#else
|
||||
sv_pure = Cvar_Get ("sv_pure", "0", CVAR_SYSTEMINFO | CVAR_INIT | CVAR_ROM );
|
||||
#endif
|
||||
Cvar_Get ("sv_paks", "", CVAR_SYSTEMINFO | CVAR_ROM );
|
||||
Cvar_Get ("sv_pakNames", "", CVAR_SYSTEMINFO | CVAR_ROM );
|
||||
Cvar_Get ("sv_referencedPaks", "", CVAR_SYSTEMINFO | CVAR_ROM );
|
||||
Cvar_Get ("sv_referencedPakNames", "", CVAR_SYSTEMINFO | CVAR_ROM );
|
||||
|
||||
// server vars
|
||||
sv_rconPassword = Cvar_Get ("rconPassword", "", CVAR_TEMP );
|
||||
sv_privatePassword = Cvar_Get ("sv_privatePassword", "", CVAR_TEMP );
|
||||
sv_fps = Cvar_Get ("sv_fps", "20", CVAR_TEMP );
|
||||
sv_timeout = Cvar_Get ("sv_timeout", "200", CVAR_TEMP );
|
||||
sv_zombietime = Cvar_Get ("sv_zombietime", "2", CVAR_TEMP );
|
||||
Cvar_Get ("nextmap", "", CVAR_TEMP );
|
||||
|
||||
sv_allowDownload = Cvar_Get ("sv_allowDownload", "0", CVAR_SERVERINFO);
|
||||
sv_master[0] = Cvar_Get ("sv_master1", MASTER_SERVER_NAME, 0 );
|
||||
sv_master[1] = Cvar_Get ("sv_master2", "", CVAR_ARCHIVE );
|
||||
sv_master[2] = Cvar_Get ("sv_master3", "", CVAR_ARCHIVE );
|
||||
sv_master[3] = Cvar_Get ("sv_master4", "", CVAR_ARCHIVE );
|
||||
sv_master[4] = Cvar_Get ("sv_master5", "", CVAR_ARCHIVE );
|
||||
sv_reconnectlimit = Cvar_Get ("sv_reconnectlimit", "3", 0);
|
||||
sv_showloss = Cvar_Get ("sv_showloss", "0", 0);
|
||||
sv_padPackets = Cvar_Get ("sv_padPackets", "0", 0);
|
||||
sv_killserver = Cvar_Get ("sv_killserver", "0", 0);
|
||||
sv_mapChecksum = Cvar_Get ("sv_mapChecksum", "", CVAR_ROM);
|
||||
sv_lanForceRate = Cvar_Get ("sv_lanForceRate", "1", CVAR_ARCHIVE );
|
||||
sv_strictAuth = Cvar_Get ("sv_strictAuth", "1", CVAR_ARCHIVE );
|
||||
|
||||
// initialize bot cvars so they are listed and can be set before loading the botlib
|
||||
SV_BotInitCvars();
|
||||
|
||||
// init the botlib here because we need the pre-compiler in the UI
|
||||
SV_BotInitBotLib();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
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;
|
||||
|
||||
// send it twice, ignoring rate
|
||||
for ( j = 0 ; j < 2 ; j++ ) {
|
||||
for (i=0, cl = svs.clients ; i < sv_maxclients->integer ; i++, cl++) {
|
||||
if (cl->state >= CS_CONNECTED) {
|
||||
// don't send a disconnect to a local client
|
||||
if ( cl->netchan.remoteAddress.type != NA_LOOPBACK ) {
|
||||
SV_SendServerCommand( cl, "print \"%s\"", message );
|
||||
SV_SendServerCommand( cl, "disconnect" );
|
||||
}
|
||||
// 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 ) {
|
||||
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_MasterShutdown();
|
||||
SV_ShutdownGameProgs();
|
||||
|
||||
// free current level
|
||||
SV_ClearServer();
|
||||
|
||||
// free server static data
|
||||
if ( svs.clients ) {
|
||||
Z_Free( svs.clients );
|
||||
}
|
||||
Com_Memset( &svs, 0, sizeof( svs ) );
|
||||
|
||||
Cvar_Set( "sv_running", "0" );
|
||||
Cvar_Set("ui_singlePlayerActive", "0");
|
||||
|
||||
Com_Printf( "---------------------------\n" );
|
||||
|
||||
// disconnect any local clients
|
||||
CL_Disconnect( qfalse );
|
||||
}
|
||||
|
855
code/server/sv_main.c
Normal file
855
code/server/sv_main.c
Normal file
|
@ -0,0 +1,855 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code is distributed in the hope that it will be
|
||||
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Foobar; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
|
||||
serverStatic_t svs; // persistant server info
|
||||
server_t sv; // local server
|
||||
vm_t *gvm = NULL; // game virtual machine // bk001212 init
|
||||
|
||||
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_rconPassword; // password for remote server commands
|
||||
cvar_t *sv_privatePassword; // password for the privateClient slots
|
||||
cvar_t *sv_allowDownload;
|
||||
cvar_t *sv_maxclients;
|
||||
|
||||
cvar_t *sv_privateClients; // number of clients reserved for password
|
||||
cvar_t *sv_hostname;
|
||||
cvar_t *sv_master[MAX_MASTER_SERVERS]; // master server ip address
|
||||
cvar_t *sv_reconnectlimit; // minimum seconds between connect messages
|
||||
cvar_t *sv_showloss; // report when usercmds are lost
|
||||
cvar_t *sv_padPackets; // add nop bytes to messages
|
||||
cvar_t *sv_killserver; // menu system can set to 1 to shut server down
|
||||
cvar_t *sv_mapname;
|
||||
cvar_t *sv_mapChecksum;
|
||||
cvar_t *sv_serverid;
|
||||
cvar_t *sv_maxRate;
|
||||
cvar_t *sv_minPing;
|
||||
cvar_t *sv_maxPing;
|
||||
cvar_t *sv_gametype;
|
||||
cvar_t *sv_pure;
|
||||
cvar_t *sv_floodProtect;
|
||||
cvar_t *sv_lanForceRate; // dedicated 1 (LAN) server forces local client rates to 99999 (bug #491)
|
||||
cvar_t *sv_strictAuth;
|
||||
|
||||
/*
|
||||
=============================================================================
|
||||
|
||||
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_ReplacePendingServerCommands
|
||||
|
||||
This is ugly
|
||||
======================
|
||||
*/
|
||||
int SV_ReplacePendingServerCommands( client_t *client, const char *cmd ) {
|
||||
int i, index, csnum1, csnum2;
|
||||
|
||||
for ( i = client->reliableSent+1; i <= client->reliableSequence; i++ ) {
|
||||
index = i & ( MAX_RELIABLE_COMMANDS - 1 );
|
||||
//
|
||||
if ( !Q_strncmp(cmd, client->reliableCommands[ index ], strlen("cs")) ) {
|
||||
sscanf(cmd, "cs %i", &csnum1);
|
||||
sscanf(client->reliableCommands[ index ], "cs %i", &csnum2);
|
||||
if ( csnum1 == csnum2 ) {
|
||||
Q_strncpyz( client->reliableCommands[ index ], cmd, sizeof( client->reliableCommands[ index ] ) );
|
||||
/*
|
||||
if ( client->netchan.remoteAddress.type != NA_BOT ) {
|
||||
Com_Printf( "WARNING: client %i removed double pending config string %i: %s\n", client-svs.clients, csnum1, cmd );
|
||||
}
|
||||
*/
|
||||
return qtrue;
|
||||
}
|
||||
}
|
||||
}
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
/*
|
||||
======================
|
||||
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, i;
|
||||
|
||||
// this is very ugly but it's also a waste to for instance send multiple config string updates
|
||||
// for the same config string index in one snapshot
|
||||
// if ( SV_ReplacePendingServerCommands( client, cmd ) ) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
client->reliableSequence++;
|
||||
// if we would be losing an old command that hasn't been acknowledged,
|
||||
// we must drop the connection
|
||||
// we check == instead of >= so a broadcast print added by SV_DropClient()
|
||||
// doesn't cause a recursive drop client
|
||||
if ( client->reliableSequence - client->reliableAcknowledge == MAX_RELIABLE_COMMANDS + 1 ) {
|
||||
Com_Printf( "===== pending server commands =====\n" );
|
||||
for ( i = client->reliableAcknowledge + 1 ; i <= client->reliableSequence ; i++ ) {
|
||||
Com_Printf( "cmd %5d: %s\n", i, client->reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] );
|
||||
}
|
||||
Com_Printf( "cmd %5d: %s\n", i, cmd );
|
||||
SV_DropClient( client, "Server command overflow" );
|
||||
return;
|
||||
}
|
||||
index = client->reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 );
|
||||
Q_strncpyz( client->reliableCommands[ index ], cmd, sizeof( client->reliableCommands[ index ] ) );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
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 QDECL SV_SendServerCommand(client_t *cl, const char *fmt, ...) {
|
||||
va_list argptr;
|
||||
byte message[MAX_MSGLEN];
|
||||
client_t *client;
|
||||
int j;
|
||||
|
||||
va_start (argptr,fmt);
|
||||
Q_vsnprintf ((char *)message, sizeof(message), fmt,argptr);
|
||||
va_end (argptr);
|
||||
|
||||
if ( cl != NULL ) {
|
||||
SV_AddServerCommand( cl, (char *)message );
|
||||
return;
|
||||
}
|
||||
|
||||
// hack to echo broadcast prints to console
|
||||
if ( com_dedicated->integer && !strncmp( (char *)message, "print", 5) ) {
|
||||
Com_Printf ("broadcast: %s\n", SV_ExpandNewlines((char *)message) );
|
||||
}
|
||||
|
||||
// send the data to all relevent clients
|
||||
for (j = 0, client = svs.clients; j < sv_maxclients->integer ; j++, client++) {
|
||||
if ( client->state < CS_PRIMED ) {
|
||||
continue;
|
||||
}
|
||||
SV_AddServerCommand( client, (char *)message );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
MASTER SERVER FUNCTIONS
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
================
|
||||
SV_MasterHeartbeat
|
||||
|
||||
Send a message to the masters every few minutes to
|
||||
let it know we are alive, and log information.
|
||||
We will also have a heartbeat sent when a server
|
||||
changes from empty to non-empty, and full to non-full,
|
||||
but not on every player enter or exit.
|
||||
================
|
||||
*/
|
||||
#define HEARTBEAT_MSEC 300*1000
|
||||
#define HEARTBEAT_GAME "QuakeArena-1"
|
||||
void SV_MasterHeartbeat( void ) {
|
||||
static netadr_t adr[MAX_MASTER_SERVERS];
|
||||
int i;
|
||||
|
||||
// "dedicated 1" is for lan play, "dedicated 2" is for inet public play
|
||||
if ( !com_dedicated || com_dedicated->integer != 2 ) {
|
||||
return; // only dedicated servers send heartbeats
|
||||
}
|
||||
|
||||
// if not time yet, don't send anything
|
||||
if ( svs.time < svs.nextHeartbeatTime ) {
|
||||
return;
|
||||
}
|
||||
svs.nextHeartbeatTime = svs.time + HEARTBEAT_MSEC;
|
||||
|
||||
|
||||
// send to group masters
|
||||
for ( i = 0 ; i < MAX_MASTER_SERVERS ; i++ ) {
|
||||
if ( !sv_master[i]->string[0] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// see if we haven't already resolved the name
|
||||
// resolving usually causes hitches on win95, so only
|
||||
// do it when needed
|
||||
if ( sv_master[i]->modified ) {
|
||||
sv_master[i]->modified = qfalse;
|
||||
|
||||
Com_Printf( "Resolving %s\n", sv_master[i]->string );
|
||||
if ( !NET_StringToAdr( sv_master[i]->string, &adr[i] ) ) {
|
||||
// if the address failed to resolve, clear it
|
||||
// so we don't take repeated dns hits
|
||||
Com_Printf( "Couldn't resolve address: %s\n", sv_master[i]->string );
|
||||
Cvar_Set( sv_master[i]->name, "" );
|
||||
sv_master[i]->modified = qfalse;
|
||||
continue;
|
||||
}
|
||||
if ( !strstr( ":", sv_master[i]->string ) ) {
|
||||
adr[i].port = BigShort( PORT_MASTER );
|
||||
}
|
||||
Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", sv_master[i]->string,
|
||||
adr[i].ip[0], adr[i].ip[1], adr[i].ip[2], adr[i].ip[3],
|
||||
BigShort( adr[i].port ) );
|
||||
}
|
||||
|
||||
|
||||
Com_Printf ("Sending heartbeat to %s\n", sv_master[i]->string );
|
||||
// this command should be changed if the server info / status format
|
||||
// ever incompatably changes
|
||||
NET_OutOfBandPrint( NS_SERVER, adr[i], "heartbeat %s\n", HEARTBEAT_GAME );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
SV_MasterShutdown
|
||||
|
||||
Informs all masters that this server is going down
|
||||
=================
|
||||
*/
|
||||
void SV_MasterShutdown( void ) {
|
||||
// send a hearbeat right now
|
||||
svs.nextHeartbeatTime = -9999;
|
||||
SV_MasterHeartbeat();
|
||||
|
||||
// send it again to minimize chance of drops
|
||||
svs.nextHeartbeatTime = -9999;
|
||||
SV_MasterHeartbeat();
|
||||
|
||||
// when the master tries to poll the server, it won't respond, so
|
||||
// it will be removed from the list
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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;
|
||||
playerState_t *ps;
|
||||
int statusLength;
|
||||
int playerLength;
|
||||
char infostring[MAX_INFO_STRING];
|
||||
|
||||
// ignore if we are in single player
|
||||
if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER ) {
|
||||
return;
|
||||
}
|
||||
|
||||
strcpy( infostring, Cvar_InfoString( CVAR_SERVERINFO ) );
|
||||
|
||||
// echo back the parameter to status. so master servers can use it as a challenge
|
||||
// to prevent timed spoofed reply packets that add ghost servers
|
||||
Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) );
|
||||
|
||||
// add "demo" to the sv_keywords if restricted
|
||||
if ( Cvar_VariableValue( "fs_restrict" ) ) {
|
||||
char keywords[MAX_INFO_STRING];
|
||||
|
||||
Com_sprintf( keywords, sizeof( keywords ), "demo %s",
|
||||
Info_ValueForKey( infostring, "sv_keywords" ) );
|
||||
Info_SetValueForKey( infostring, "sv_keywords", keywords );
|
||||
}
|
||||
|
||||
status[0] = 0;
|
||||
statusLength = 0;
|
||||
|
||||
for (i=0 ; i < sv_maxclients->integer ; i++) {
|
||||
cl = &svs.clients[i];
|
||||
if ( cl->state >= CS_CONNECTED ) {
|
||||
ps = SV_GameClientNum( i );
|
||||
Com_sprintf (player, sizeof(player), "%i %i \"%s\"\n",
|
||||
ps->persistant[PERS_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
|
||||
================
|
||||
*/
|
||||
void SVC_Info( netadr_t from ) {
|
||||
int i, count;
|
||||
char *gamedir;
|
||||
char infostring[MAX_INFO_STRING];
|
||||
|
||||
// ignore if we are in single player
|
||||
if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// don't count privateclients
|
||||
count = 0;
|
||||
for ( i = sv_privateClients->integer ; i < sv_maxclients->integer ; 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", sv_maxclients->integer - sv_privateClients->integer ) );
|
||||
Info_SetValueForKey( infostring, "gametype", va("%i", sv_gametype->integer ) );
|
||||
Info_SetValueForKey( infostring, "pure", va("%i", sv_pure->integer ) );
|
||||
|
||||
if( sv_minPing->integer ) {
|
||||
Info_SetValueForKey( infostring, "minPing", va("%i", sv_minPing->integer) );
|
||||
}
|
||||
if( sv_maxPing->integer ) {
|
||||
Info_SetValueForKey( infostring, "maxPing", va("%i", sv_maxPing->integer) );
|
||||
}
|
||||
gamedir = Cvar_VariableString( "fs_game" );
|
||||
if( *gamedir ) {
|
||||
Info_SetValueForKey( infostring, "game", gamedir );
|
||||
}
|
||||
|
||||
NET_OutOfBandPrint( NS_SERVER, from, "infoResponse\n%s", infostring );
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
SVC_FlushRedirect
|
||||
|
||||
================
|
||||
*/
|
||||
void SV_FlushRedirect( char *outputbuf ) {
|
||||
NET_OutOfBandPrint( NS_SERVER, svs.redirectAddress, "print\n%s", outputbuf );
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
SVC_RemoteCommand
|
||||
|
||||
An rcon packet arrived from the network.
|
||||
Shift down the remaining args
|
||||
Redirect all printfs
|
||||
===============
|
||||
*/
|
||||
void SVC_RemoteCommand( netadr_t from, msg_t *msg ) {
|
||||
qboolean valid;
|
||||
unsigned int time;
|
||||
char remaining[1024];
|
||||
// TTimo - scaled down to accumulate, but not overflow anything network wise, print wise etc.
|
||||
// (OOB messages are the bottleneck here)
|
||||
#define SV_OUTPUTBUF_LENGTH (1024 - 16)
|
||||
char sv_outputbuf[SV_OUTPUTBUF_LENGTH];
|
||||
static unsigned int lasttime = 0;
|
||||
char *cmd_aux;
|
||||
|
||||
// TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=534
|
||||
time = Com_Milliseconds();
|
||||
if (time<(lasttime+500)) {
|
||||
return;
|
||||
}
|
||||
lasttime = time;
|
||||
|
||||
if ( !strlen( sv_rconPassword->string ) ||
|
||||
strcmp (Cmd_Argv(1), sv_rconPassword->string) ) {
|
||||
valid = qfalse;
|
||||
Com_Printf ("Bad rcon from %s:\n%s\n", NET_AdrToString (from), Cmd_Argv(2) );
|
||||
} else {
|
||||
valid = qtrue;
|
||||
Com_Printf ("Rcon from %s:\n%s\n", NET_AdrToString (from), Cmd_Argv(2) );
|
||||
}
|
||||
|
||||
// start redirecting all print outputs to the packet
|
||||
svs.redirectAddress = from;
|
||||
Com_BeginRedirect (sv_outputbuf, SV_OUTPUTBUF_LENGTH, SV_FlushRedirect);
|
||||
|
||||
if ( !strlen( sv_rconPassword->string ) ) {
|
||||
Com_Printf ("No rconpassword set on the server.\n");
|
||||
} else if ( !valid ) {
|
||||
Com_Printf ("Bad rconpassword.\n");
|
||||
} else {
|
||||
remaining[0] = 0;
|
||||
|
||||
// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543
|
||||
// get the command directly, "rcon <pass> <command>" to avoid quoting issues
|
||||
// extract the command by walking
|
||||
// since the cmd formatting can fuckup (amount of spaces), using a dumb step by step parsing
|
||||
cmd_aux = Cmd_Cmd();
|
||||
cmd_aux+=4;
|
||||
while(cmd_aux[0]==' ')
|
||||
cmd_aux++;
|
||||
while(cmd_aux[0] && cmd_aux[0]!=' ') // password
|
||||
cmd_aux++;
|
||||
while(cmd_aux[0]==' ')
|
||||
cmd_aux++;
|
||||
|
||||
Q_strcat( remaining, sizeof(remaining), cmd_aux);
|
||||
|
||||
Cmd_ExecuteString (remaining);
|
||||
|
||||
}
|
||||
|
||||
Com_EndRedirect ();
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
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.
|
||||
=================
|
||||
*/
|
||||
void SV_ConnectionlessPacket( netadr_t from, msg_t *msg ) {
|
||||
char *s;
|
||||
char *c;
|
||||
|
||||
MSG_BeginReadingOOB( msg );
|
||||
MSG_ReadLong( msg ); // skip the -1 marker
|
||||
|
||||
if (!Q_strncmp("connect", &msg->data[4], 7)) {
|
||||
Huff_Decompress(msg, 12);
|
||||
}
|
||||
|
||||
s = MSG_ReadStringLine( msg );
|
||||
Cmd_TokenizeString( s );
|
||||
|
||||
c = Cmd_Argv(0);
|
||||
Com_DPrintf ("SV packet %s : %s\n", NET_AdrToString(from), c);
|
||||
|
||||
if (!Q_stricmp(c, "getstatus")) {
|
||||
SVC_Status( from );
|
||||
} else if (!Q_stricmp(c, "getinfo")) {
|
||||
SVC_Info( from );
|
||||
} else if (!Q_stricmp(c, "getchallenge")) {
|
||||
SV_GetChallenge( from );
|
||||
} else if (!Q_stricmp(c, "connect")) {
|
||||
SV_DirectConnect( from );
|
||||
} else if (!Q_stricmp(c, "ipAuthorize")) {
|
||||
SV_AuthorizeIpPacket( from );
|
||||
} else if (!Q_stricmp(c, "rcon")) {
|
||||
SVC_RemoteCommand( from, msg );
|
||||
} else if (!Q_stricmp(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_BeginReadingOOB( msg );
|
||||
MSG_ReadLong( msg ); // sequence number
|
||||
qport = MSG_ReadShort( msg ) & 0xffff;
|
||||
|
||||
// find which client the message is from
|
||||
for (i=0, cl=svs.clients ; i < sv_maxclients->integer ; 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_PacketEvent: fixing up a translated port\n" );
|
||||
cl->netchan.remoteAddress.port = from.port;
|
||||
}
|
||||
|
||||
// make sure it is a valid, in sequence packet
|
||||
if (SV_Netchan_Process(cl, msg)) {
|
||||
// zombie clients still need 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 = svs.time; // don't timeout
|
||||
SV_ExecuteClientMessage( cl, msg );
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// if we received a sequenced packet from an address we don't recognize,
|
||||
// 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;
|
||||
playerState_t *ps;
|
||||
|
||||
for (i=0 ; i < sv_maxclients->integer ; i++) {
|
||||
cl = &svs.clients[i];
|
||||
if ( cl->state != CS_ACTIVE ) {
|
||||
cl->ping = 999;
|
||||
continue;
|
||||
}
|
||||
if ( !cl->gentity ) {
|
||||
cl->ping = 999;
|
||||
continue;
|
||||
}
|
||||
if ( cl->gentity->r.svFlags & SVF_BOT ) {
|
||||
cl->ping = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
total = 0;
|
||||
count = 0;
|
||||
for ( j = 0 ; j < PACKET_BACKUP ; j++ ) {
|
||||
if ( cl->frames[j].messageAcked <= 0 ) {
|
||||
continue;
|
||||
}
|
||||
delta = cl->frames[j].messageAcked - cl->frames[j].messageSent;
|
||||
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
|
||||
ps = SV_GameClientNum( i );
|
||||
ps->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 = svs.time - 1000 * sv_timeout->integer;
|
||||
zombiepoint = svs.time - 1000 * sv_zombietime->integer;
|
||||
|
||||
for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) {
|
||||
// message times may be wrong across a changelevel
|
||||
if (cl->lastPacketTime > svs.time) {
|
||||
cl->lastPacketTime = svs.time;
|
||||
}
|
||||
|
||||
if (cl->state == CS_ZOMBIE
|
||||
&& cl->lastPacketTime < zombiepoint) {
|
||||
// using the client id cause the cl->name is empty at this point
|
||||
Com_DPrintf( "Going from CS_ZOMBIE to CS_FREE for client %d\n", i );
|
||||
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 ) {
|
||||
int count;
|
||||
client_t *cl;
|
||||
int i;
|
||||
|
||||
if ( !cl_paused->integer ) {
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
// only pause if there is just a single client connected
|
||||
count = 0;
|
||||
for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) {
|
||||
if ( cl->state >= CS_CONNECTED && cl->netchan.remoteAddress.type != NA_BOT ) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if ( count > 1 ) {
|
||||
// don't pause
|
||||
if (sv_paused->integer)
|
||||
Cvar_Set("sv_paused", "0");
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
if (!sv_paused->integer)
|
||||
Cvar_Set("sv_paused", "1");
|
||||
return qtrue;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_Frame
|
||||
|
||||
Player movement occurs as a result of packet events, which
|
||||
happen before SV_Frame is called
|
||||
==================
|
||||
*/
|
||||
void SV_Frame( int msec ) {
|
||||
int frameMsec;
|
||||
int startTime;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// allow pause if only the local client is connected
|
||||
if ( SV_CheckPaused() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
if (!com_dedicated->integer) SV_BotFrame( svs.time + sv.timeResidual );
|
||||
|
||||
if ( com_dedicated->integer && sv.timeResidual < frameMsec ) {
|
||||
// NET_Sleep will give the OS time slices until either get a packet
|
||||
// or time enough for a server frame has gone by
|
||||
NET_Sleep(frameMsec - sv.timeResidual);
|
||||
return;
|
||||
}
|
||||
|
||||
// if time is about to hit the 32nd bit, kick all clients
|
||||
// and clear sv.time, rather
|
||||
// than checking for negative time wraparound everywhere.
|
||||
// 2giga-milliseconds = 23 days, so it won't be too often
|
||||
if ( svs.time > 0x70000000 ) {
|
||||
SV_Shutdown( "Restarting server due to time wrapping" );
|
||||
Cbuf_AddText( "vstr nextmap\n" );
|
||||
return;
|
||||
}
|
||||
// this can happen considerably earlier when lots of clients play and the map doesn't change
|
||||
if ( svs.nextSnapshotEntities >= 0x7FFFFFFE - svs.numSnapshotEntities ) {
|
||||
SV_Shutdown( "Restarting server due to numSnapshotEntities wrapping" );
|
||||
Cbuf_AddText( "vstr nextmap\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
if( sv.restartTime && svs.time >= sv.restartTime ) {
|
||||
sv.restartTime = 0;
|
||||
Cbuf_AddText( "map_restart 0\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_Big( CVAR_SYSTEMINFO ) );
|
||||
cvar_modifiedFlags &= ~CVAR_SYSTEMINFO;
|
||||
}
|
||||
|
||||
if ( com_speeds->integer ) {
|
||||
startTime = Sys_Milliseconds ();
|
||||
} else {
|
||||
startTime = 0; // quite a compiler warning
|
||||
}
|
||||
|
||||
// update ping based on the all received frames
|
||||
SV_CalcPings();
|
||||
|
||||
if (com_dedicated->integer) SV_BotFrame( svs.time );
|
||||
|
||||
// run the game simulation in chunks
|
||||
while ( sv.timeResidual >= frameMsec ) {
|
||||
sv.timeResidual -= frameMsec;
|
||||
svs.time += frameMsec;
|
||||
|
||||
// let everything in the world think and move
|
||||
VM_Call( gvm, GAME_RUN_FRAME, svs.time );
|
||||
}
|
||||
|
||||
if ( com_speeds->integer ) {
|
||||
time_game = Sys_Milliseconds () - startTime;
|
||||
}
|
||||
|
||||
// check timeouts
|
||||
SV_CheckTimeouts();
|
||||
|
||||
// send messages back to the clients
|
||||
SV_SendClientMessages();
|
||||
|
||||
// send a heartbeat to the master if needed
|
||||
SV_MasterHeartbeat();
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
|
207
code/server/sv_net_chan.c
Normal file
207
code/server/sv_net_chan.c
Normal file
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code is distributed in the hope that it will be
|
||||
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Foobar; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
#include "../game/q_shared.h"
|
||||
#include "../qcommon/qcommon.h"
|
||||
#include "server.h"
|
||||
|
||||
/*
|
||||
==============
|
||||
SV_Netchan_Encode
|
||||
|
||||
// first four bytes of the data are always:
|
||||
long reliableAcknowledge;
|
||||
|
||||
==============
|
||||
*/
|
||||
static void SV_Netchan_Encode( client_t *client, msg_t *msg ) {
|
||||
long reliableAcknowledge, i, index;
|
||||
byte key, *string;
|
||||
int srdc, sbit, soob;
|
||||
|
||||
if ( msg->cursize < SV_ENCODE_START ) {
|
||||
return;
|
||||
}
|
||||
|
||||
srdc = msg->readcount;
|
||||
sbit = msg->bit;
|
||||
soob = msg->oob;
|
||||
|
||||
msg->bit = 0;
|
||||
msg->readcount = 0;
|
||||
msg->oob = 0;
|
||||
|
||||
reliableAcknowledge = MSG_ReadLong(msg);
|
||||
|
||||
msg->oob = soob;
|
||||
msg->bit = sbit;
|
||||
msg->readcount = srdc;
|
||||
|
||||
string = (byte *)client->lastClientCommandString;
|
||||
index = 0;
|
||||
// xor the client challenge with the netchan sequence number
|
||||
key = client->challenge ^ client->netchan.outgoingSequence;
|
||||
for (i = SV_ENCODE_START; i < msg->cursize; i++) {
|
||||
// modify the key with the last received and with this message acknowledged client command
|
||||
if (!string[index])
|
||||
index = 0;
|
||||
if (string[index] > 127 || string[index] == '%') {
|
||||
key ^= '.' << (i & 1);
|
||||
}
|
||||
else {
|
||||
key ^= string[index] << (i & 1);
|
||||
}
|
||||
index++;
|
||||
// encode the data with this key
|
||||
*(msg->data + i) = *(msg->data + i) ^ key;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==============
|
||||
SV_Netchan_Decode
|
||||
|
||||
// first 12 bytes of the data are always:
|
||||
long serverId;
|
||||
long messageAcknowledge;
|
||||
long reliableAcknowledge;
|
||||
|
||||
==============
|
||||
*/
|
||||
static void SV_Netchan_Decode( client_t *client, msg_t *msg ) {
|
||||
int serverId, messageAcknowledge, reliableAcknowledge;
|
||||
int i, index, srdc, sbit, soob;
|
||||
byte key, *string;
|
||||
|
||||
srdc = msg->readcount;
|
||||
sbit = msg->bit;
|
||||
soob = msg->oob;
|
||||
|
||||
msg->oob = 0;
|
||||
|
||||
serverId = MSG_ReadLong(msg);
|
||||
messageAcknowledge = MSG_ReadLong(msg);
|
||||
reliableAcknowledge = MSG_ReadLong(msg);
|
||||
|
||||
msg->oob = soob;
|
||||
msg->bit = sbit;
|
||||
msg->readcount = srdc;
|
||||
|
||||
string = (byte *)client->reliableCommands[ reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ];
|
||||
index = 0;
|
||||
//
|
||||
key = client->challenge ^ serverId ^ messageAcknowledge;
|
||||
for (i = msg->readcount + SV_DECODE_START; i < msg->cursize; i++) {
|
||||
// modify the key with the last sent and acknowledged server command
|
||||
if (!string[index])
|
||||
index = 0;
|
||||
if (string[index] > 127 || string[index] == '%') {
|
||||
key ^= '.' << (i & 1);
|
||||
}
|
||||
else {
|
||||
key ^= string[index] << (i & 1);
|
||||
}
|
||||
index++;
|
||||
// decode the data with this key
|
||||
*(msg->data + i) = *(msg->data + i) ^ key;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
SV_Netchan_TransmitNextFragment
|
||||
=================
|
||||
*/
|
||||
void SV_Netchan_TransmitNextFragment( client_t *client ) {
|
||||
Netchan_TransmitNextFragment( &client->netchan );
|
||||
if (!client->netchan.unsentFragments)
|
||||
{
|
||||
// make sure the netchan queue has been properly initialized (you never know)
|
||||
if (!client->netchan_end_queue) {
|
||||
Com_Error(ERR_DROP, "netchan queue is not properly initialized in SV_Netchan_TransmitNextFragment\n");
|
||||
}
|
||||
// the last fragment was transmitted, check wether we have queued messages
|
||||
if (client->netchan_start_queue) {
|
||||
netchan_buffer_t *netbuf;
|
||||
Com_DPrintf("#462 Netchan_TransmitNextFragment: popping a queued message for transmit\n");
|
||||
netbuf = client->netchan_start_queue;
|
||||
SV_Netchan_Encode( client, &netbuf->msg );
|
||||
Netchan_Transmit( &client->netchan, netbuf->msg.cursize, netbuf->msg.data );
|
||||
// pop from queue
|
||||
client->netchan_start_queue = netbuf->next;
|
||||
if (!client->netchan_start_queue) {
|
||||
Com_DPrintf("#462 Netchan_TransmitNextFragment: emptied queue\n");
|
||||
client->netchan_end_queue = &client->netchan_start_queue;
|
||||
}
|
||||
else
|
||||
Com_DPrintf("#462 Netchan_TransmitNextFragment: remaining queued message\n");
|
||||
Z_Free(netbuf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_Netchan_Transmit
|
||||
TTimo
|
||||
https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=462
|
||||
if there are some unsent fragments (which may happen if the snapshots
|
||||
and the gamestate are fragmenting, and collide on send for instance)
|
||||
then buffer them and make sure they get sent in correct order
|
||||
================
|
||||
*/
|
||||
|
||||
void SV_Netchan_Transmit( client_t *client, msg_t *msg) { //int length, const byte *data ) {
|
||||
MSG_WriteByte( msg, svc_EOF );
|
||||
if (client->netchan.unsentFragments) {
|
||||
netchan_buffer_t *netbuf;
|
||||
Com_DPrintf("#462 SV_Netchan_Transmit: unsent fragments, stacked\n");
|
||||
netbuf = (netchan_buffer_t *)Z_Malloc(sizeof(netchan_buffer_t));
|
||||
// store the msg, we can't store it encoded, as the encoding depends on stuff we still have to finish sending
|
||||
MSG_Copy(&netbuf->msg, netbuf->msgBuffer, sizeof( netbuf->msgBuffer ), msg);
|
||||
netbuf->next = NULL;
|
||||
// insert it in the queue, the message will be encoded and sent later
|
||||
*client->netchan_end_queue = netbuf;
|
||||
client->netchan_end_queue = &(*client->netchan_end_queue)->next;
|
||||
// emit the next fragment of the current message for now
|
||||
Netchan_TransmitNextFragment(&client->netchan);
|
||||
} else {
|
||||
SV_Netchan_Encode( client, msg );
|
||||
Netchan_Transmit( &client->netchan, msg->cursize, msg->data );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
Netchan_SV_Process
|
||||
=================
|
||||
*/
|
||||
qboolean SV_Netchan_Process( client_t *client, msg_t *msg ) {
|
||||
int ret;
|
||||
ret = Netchan_Process( &client->netchan, msg );
|
||||
if (!ret)
|
||||
return qfalse;
|
||||
SV_Netchan_Decode( client, msg );
|
||||
return qtrue;
|
||||
}
|
||||
|
1537
code/server/sv_rankings.c
Normal file
1537
code/server/sv_rankings.c
Normal file
File diff suppressed because it is too large
Load diff
682
code/server/sv_snapshot.c
Normal file
682
code/server/sv_snapshot.c
Normal file
|
@ -0,0 +1,682 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code is distributed in the hope that it will be
|
||||
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Foobar; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
#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;
|
||||
while ( newindex < to->num_entities || oldindex < from_num_entities ) {
|
||||
if ( newindex >= to->num_entities ) {
|
||||
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_WriteDeltaEntity (msg, oldent, newent, qfalse );
|
||||
oldindex++;
|
||||
newindex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( newnum < oldnum ) {
|
||||
// this is a new entity, send it from the baseline
|
||||
MSG_WriteDeltaEntity (msg, &sv.svEntities[newnum].baseline, newent, qtrue );
|
||||
newindex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( newnum > oldnum ) {
|
||||
// the old entity isn't present in the new message
|
||||
MSG_WriteDeltaEntity (msg, oldent, NULL, qtrue );
|
||||
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 i;
|
||||
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);
|
||||
|
||||
// NOTE, MRE: now sent at the start of every message from server to client
|
||||
// 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, svs.time);
|
||||
|
||||
// what we are delta'ing from
|
||||
MSG_WriteByte (msg, lastframe);
|
||||
|
||||
snapFlags = svs.snapFlagServerBit;
|
||||
if ( client->rateDelayed ) {
|
||||
snapFlags |= SNAPFLAG_RATE_DELAYED;
|
||||
}
|
||||
if ( client->state != CS_ACTIVE ) {
|
||||
snapFlags |= SNAPFLAG_NOT_ACTIVE;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// padding for rate debugging
|
||||
if ( sv_padPackets->integer ) {
|
||||
for ( i = 0 ; i < sv_padPackets->integer ; i++ ) {
|
||||
MSG_WriteByte (msg, svc_nop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_UpdateServerCommandsToClient
|
||||
|
||||
(re)send all server commands the client hasn't acknowledged yet
|
||||
==================
|
||||
*/
|
||||
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) ] );
|
||||
}
|
||||
client->reliableSent = client->reliableSequence;
|
||||
}
|
||||
|
||||
/*
|
||||
=============================================================================
|
||||
|
||||
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 QDECL 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, sharedEntity_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;
|
||||
}
|
||||
|
||||
eNums->snapshotEntities[ eNums->numSnapshotEntities ] = gEnt->s.number;
|
||||
eNums->numSnapshotEntities++;
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_AddEntitiesVisibleFromPoint
|
||||
===============
|
||||
*/
|
||||
static void SV_AddEntitiesVisibleFromPoint( vec3_t origin, clientSnapshot_t *frame,
|
||||
snapshotEntityNumbers_t *eNums, qboolean portal ) {
|
||||
int e, i;
|
||||
sharedEntity_t *ent;
|
||||
svEntity_t *svEnt;
|
||||
int l;
|
||||
int clientarea, clientcluster;
|
||||
int leafnum;
|
||||
int c_fullsend;
|
||||
byte *clientpvs;
|
||||
byte *bitvector;
|
||||
|
||||
// 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;
|
||||
|
||||
for ( e = 0 ; e < sv.num_entities ; e++ ) {
|
||||
ent = SV_GentityNum(e);
|
||||
|
||||
// never send entities that aren't linked in
|
||||
if ( !ent->r.linked ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ent->s.number != e) {
|
||||
Com_DPrintf ("FIXING ENT->S.NUMBER!!!\n");
|
||||
ent->s.number = e;
|
||||
}
|
||||
|
||||
// entities can be flagged to explicitly not be sent to the client
|
||||
if ( ent->r.svFlags & SVF_NOCLIENT ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// entities can be flagged to be sent to only one client
|
||||
if ( ent->r.svFlags & SVF_SINGLECLIENT ) {
|
||||
if ( ent->r.singleClient != frame->ps.clientNum ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// entities can be flagged to be sent to everyone but one client
|
||||
if ( ent->r.svFlags & SVF_NOTSINGLECLIENT ) {
|
||||
if ( ent->r.singleClient == frame->ps.clientNum ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// entities can be flagged to be sent to a given mask of clients
|
||||
if ( ent->r.svFlags & SVF_CLIENTMASK ) {
|
||||
if (frame->ps.clientNum >= 32)
|
||||
Com_Error( ERR_DROP, "SVF_CLIENTMASK: cientNum > 32\n" );
|
||||
if (~ent->r.singleClient & (1 << frame->ps.clientNum))
|
||||
continue;
|
||||
}
|
||||
|
||||
svEnt = SV_SvEntityForGentity( ent );
|
||||
|
||||
// don't double add an entity through portals
|
||||
if ( svEnt->snapshotCounter == sv.snapshotCounter ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// broadcast entities are always sent
|
||||
if ( ent->r.svFlags & SVF_BROADCAST ) {
|
||||
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;
|
||||
for ( i=0 ; i < svEnt->numClusters ; i++ ) {
|
||||
l = svEnt->clusternums[i];
|
||||
if ( bitvector[l >> 3] & (1 << (l&7) ) ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if we haven't found it to be visible,
|
||||
// check overflow clusters that coudln't be stored
|
||||
if ( i == svEnt->numClusters ) {
|
||||
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->r.svFlags & SVF_PORTAL ) {
|
||||
if ( ent->s.generic1 ) {
|
||||
vec3_t dir;
|
||||
VectorSubtract(ent->s.origin, origin, dir);
|
||||
if ( VectorLengthSquared(dir) > (float) ent->s.generic1 * ent->s.generic1 ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
SV_AddEntitiesVisibleFromPoint( ent->s.origin2, frame, eNums, qtrue );
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=============
|
||||
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 void SV_BuildClientSnapshot( client_t *client ) {
|
||||
vec3_t org;
|
||||
clientSnapshot_t *frame;
|
||||
snapshotEntityNumbers_t entityNumbers;
|
||||
int i;
|
||||
sharedEntity_t *ent;
|
||||
entityState_t *state;
|
||||
svEntity_t *svEnt;
|
||||
sharedEntity_t *clent;
|
||||
int clientNum;
|
||||
playerState_t *ps;
|
||||
|
||||
// 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;
|
||||
Com_Memset( frame->areabits, 0, sizeof( frame->areabits ) );
|
||||
|
||||
// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=62
|
||||
frame->num_entities = 0;
|
||||
|
||||
clent = client->gentity;
|
||||
if ( !clent || client->state == CS_ZOMBIE ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// grab the current playerState_t
|
||||
ps = SV_GameClientNum( client - svs.clients );
|
||||
frame->ps = *ps;
|
||||
|
||||
// never send client's own entity, because it can
|
||||
// be regenerated from the playerstate
|
||||
clientNum = frame->ps.clientNum;
|
||||
if ( clientNum < 0 || clientNum >= MAX_GENTITIES ) {
|
||||
Com_Error( ERR_DROP, "SV_SvEntityForGentity: bad gEnt" );
|
||||
}
|
||||
svEnt = &sv.svEntities[ clientNum ];
|
||||
|
||||
svEnt->snapshotCounter = sv.snapshotCounter;
|
||||
|
||||
// find the client's viewpoint
|
||||
VectorCopy( ps->origin, org );
|
||||
org[2] += ps->viewheight;
|
||||
|
||||
// add all the entities directly visible to the eye, which
|
||||
// may include portal entities that merge other viewpoints
|
||||
SV_AddEntitiesVisibleFromPoint( org, frame, &entityNumbers, qfalse );
|
||||
|
||||
// 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++;
|
||||
// this should never hit, map should always be restarted first in SV_Frame
|
||||
if ( svs.nextSnapshotEntities >= 0x7FFFFFFE ) {
|
||||
Com_Error(ERR_FATAL, "svs.nextSnapshotEntities wrapped");
|
||||
}
|
||||
frame->num_entities++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
====================
|
||||
SV_RateMsec
|
||||
|
||||
Return the number of msec a given size message is supposed
|
||||
to take to clear, based on the current rate
|
||||
====================
|
||||
*/
|
||||
#define HEADER_RATE_BYTES 48 // include our header, IP header, and some overhead
|
||||
static int SV_RateMsec( client_t *client, int messageSize ) {
|
||||
int rate;
|
||||
int rateMsec;
|
||||
|
||||
// individual messages will never be larger than fragment size
|
||||
if ( messageSize > 1500 ) {
|
||||
messageSize = 1500;
|
||||
}
|
||||
rate = client->rate;
|
||||
if ( sv_maxRate->integer ) {
|
||||
if ( sv_maxRate->integer < 1000 ) {
|
||||
Cvar_Set( "sv_MaxRate", "1000" );
|
||||
}
|
||||
if ( sv_maxRate->integer < rate ) {
|
||||
rate = sv_maxRate->integer;
|
||||
}
|
||||
}
|
||||
rateMsec = ( messageSize + HEADER_RATE_BYTES ) * 1000 / rate;
|
||||
|
||||
return rateMsec;
|
||||
}
|
||||
|
||||
/*
|
||||
=======================
|
||||
SV_SendMessageToClient
|
||||
|
||||
Called by SV_SendClientSnapshot and SV_SendClientGameState
|
||||
=======================
|
||||
*/
|
||||
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 = svs.time;
|
||||
client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageAcked = -1;
|
||||
|
||||
// send the datagram
|
||||
SV_Netchan_Transmit( client, msg ); //msg->cursize, msg->data );
|
||||
|
||||
// set nextSnapshotTime based on rate and requested number of updates
|
||||
|
||||
// local clients get snapshots every frame
|
||||
// TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=491
|
||||
// added sv_lanForceRate check
|
||||
if ( client->netchan.remoteAddress.type == NA_LOOPBACK || (sv_lanForceRate->integer && Sys_IsLANAddress (client->netchan.remoteAddress)) ) {
|
||||
client->nextSnapshotTime = svs.time - 1;
|
||||
return;
|
||||
}
|
||||
|
||||
// normal rate / snapshotMsec calculation
|
||||
rateMsec = SV_RateMsec( client, msg->cursize );
|
||||
|
||||
if ( rateMsec < client->snapshotMsec ) {
|
||||
// never send more packets than this, no matter what the rate is at
|
||||
rateMsec = client->snapshotMsec;
|
||||
client->rateDelayed = qfalse;
|
||||
} else {
|
||||
client->rateDelayed = qtrue;
|
||||
}
|
||||
|
||||
client->nextSnapshotTime = svs.time + rateMsec;
|
||||
|
||||
// don't pile up empty snapshots while connecting
|
||||
if ( client->state != CS_ACTIVE ) {
|
||||
// a gigantic connection message may have already put the nextSnapshotTime
|
||||
// more than a second away, so don't shorten it
|
||||
// do shorten if client is downloading
|
||||
if ( !*client->downloadName && client->nextSnapshotTime < svs.time + 1000 ) {
|
||||
client->nextSnapshotTime = svs.time + 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=======================
|
||||
SV_SendClientSnapshot
|
||||
|
||||
Also called by SV_FinalMessage
|
||||
|
||||
=======================
|
||||
*/
|
||||
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->r.svFlags & SVF_BOT ) {
|
||||
return;
|
||||
}
|
||||
|
||||
MSG_Init (&msg, msg_buf, sizeof(msg_buf));
|
||||
msg.allowoverflow = qtrue;
|
||||
|
||||
// NOTE, MRE: all server->client messages now acknowledge
|
||||
// let the client know which reliable clientCommands we have received
|
||||
MSG_WriteLong( &msg, client->lastClientCommand );
|
||||
|
||||
// (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 );
|
||||
|
||||
// Add any download data if the client is downloading
|
||||
SV_WriteDownloadToClient( 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 < sv_maxclients->integer ; i++, c++) {
|
||||
if (!c->state) {
|
||||
continue; // not connected
|
||||
}
|
||||
|
||||
if ( svs.time < c->nextSnapshotTime ) {
|
||||
continue; // not time yet
|
||||
}
|
||||
|
||||
// send additional message fragments if the last message
|
||||
// was too large to send at once
|
||||
if ( c->netchan.unsentFragments ) {
|
||||
c->nextSnapshotTime = svs.time +
|
||||
SV_RateMsec( c, c->netchan.unsentLength - c->netchan.unsentFragmentStart );
|
||||
SV_Netchan_TransmitNextFragment( c );
|
||||
continue;
|
||||
}
|
||||
|
||||
// generate and send a new message
|
||||
SV_SendClientSnapshot( c );
|
||||
}
|
||||
}
|
||||
|
691
code/server/sv_world.c
Normal file
691
code/server/sv_world.c
Normal file
|
@ -0,0 +1,691 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code is distributed in the hope that it will be
|
||||
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Foobar; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
// world.c -- world query functions
|
||||
|
||||
#include "server.h"
|
||||
|
||||
/*
|
||||
================
|
||||
SV_ClipHandleForEntity
|
||||
|
||||
Returns a headnode that can be used for testing or clipping to a
|
||||
given entity. If the entity is a bsp model, the headnode will
|
||||
be returned, otherwise a custom box tree will be constructed.
|
||||
================
|
||||
*/
|
||||
clipHandle_t SV_ClipHandleForEntity( const sharedEntity_t *ent ) {
|
||||
if ( ent->r.bmodel ) {
|
||||
// explicit hulls in the BSP model
|
||||
return CM_InlineModel( ent->s.modelindex );
|
||||
}
|
||||
if ( ent->r.svFlags & SVF_CAPSULE ) {
|
||||
// create a temp capsule from bounding box sizes
|
||||
return CM_TempBoxModel( ent->r.mins, ent->r.maxs, qtrue );
|
||||
}
|
||||
|
||||
// create a temp tree from bounding box sizes
|
||||
return CM_TempBoxModel( ent->r.mins, ent->r.maxs, qfalse );
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
===============================================================================
|
||||
|
||||
ENTITY CHECKING
|
||||
|
||||
To avoid linearly searching through lists of entities during environment testing,
|
||||
the world is carved up with an evenly spaced, axially aligned bsp tree. Entities
|
||||
are kept in chains either at the final leafs, or at the first node that splits
|
||||
them, which prevents having to deal with multiple fragments of a single entity.
|
||||
|
||||
===============================================================================
|
||||
*/
|
||||
|
||||
typedef struct worldSector_s {
|
||||
int axis; // -1 = leaf node
|
||||
float dist;
|
||||
struct worldSector_s *children[2];
|
||||
svEntity_t *entities;
|
||||
} worldSector_t;
|
||||
|
||||
#define AREA_DEPTH 4
|
||||
#define AREA_NODES 64
|
||||
|
||||
worldSector_t sv_worldSectors[AREA_NODES];
|
||||
int sv_numworldSectors;
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_SectorList_f
|
||||
===============
|
||||
*/
|
||||
void SV_SectorList_f( void ) {
|
||||
int i, c;
|
||||
worldSector_t *sec;
|
||||
svEntity_t *ent;
|
||||
|
||||
for ( i = 0 ; i < AREA_NODES ; i++ ) {
|
||||
sec = &sv_worldSectors[i];
|
||||
|
||||
c = 0;
|
||||
for ( ent = sec->entities ; ent ; ent = ent->nextEntityInWorldSector ) {
|
||||
c++;
|
||||
}
|
||||
Com_Printf( "sector %i: %i entities\n", i, c );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_CreateworldSector
|
||||
|
||||
Builds a uniformly subdivided tree for the given world size
|
||||
===============
|
||||
*/
|
||||
worldSector_t *SV_CreateworldSector( int depth, vec3_t mins, vec3_t maxs ) {
|
||||
worldSector_t *anode;
|
||||
vec3_t size;
|
||||
vec3_t mins1, maxs1, mins2, maxs2;
|
||||
|
||||
anode = &sv_worldSectors[sv_numworldSectors];
|
||||
sv_numworldSectors++;
|
||||
|
||||
if (depth == AREA_DEPTH) {
|
||||
anode->axis = -1;
|
||||
anode->children[0] = anode->children[1] = NULL;
|
||||
return anode;
|
||||
}
|
||||
|
||||
VectorSubtract (maxs, mins, size);
|
||||
if (size[0] > size[1]) {
|
||||
anode->axis = 0;
|
||||
} else {
|
||||
anode->axis = 1;
|
||||
}
|
||||
|
||||
anode->dist = 0.5 * (maxs[anode->axis] + mins[anode->axis]);
|
||||
VectorCopy (mins, mins1);
|
||||
VectorCopy (mins, mins2);
|
||||
VectorCopy (maxs, maxs1);
|
||||
VectorCopy (maxs, maxs2);
|
||||
|
||||
maxs1[anode->axis] = mins2[anode->axis] = anode->dist;
|
||||
|
||||
anode->children[0] = SV_CreateworldSector (depth+1, mins2, maxs2);
|
||||
anode->children[1] = SV_CreateworldSector (depth+1, mins1, maxs1);
|
||||
|
||||
return anode;
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_ClearWorld
|
||||
|
||||
===============
|
||||
*/
|
||||
void SV_ClearWorld( void ) {
|
||||
clipHandle_t h;
|
||||
vec3_t mins, maxs;
|
||||
|
||||
Com_Memset( sv_worldSectors, 0, sizeof(sv_worldSectors) );
|
||||
sv_numworldSectors = 0;
|
||||
|
||||
// get world map bounds
|
||||
h = CM_InlineModel( 0 );
|
||||
CM_ModelBounds( h, mins, maxs );
|
||||
SV_CreateworldSector( 0, mins, maxs );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_UnlinkEntity
|
||||
|
||||
===============
|
||||
*/
|
||||
void SV_UnlinkEntity( sharedEntity_t *gEnt ) {
|
||||
svEntity_t *ent;
|
||||
svEntity_t *scan;
|
||||
worldSector_t *ws;
|
||||
|
||||
ent = SV_SvEntityForGentity( gEnt );
|
||||
|
||||
gEnt->r.linked = qfalse;
|
||||
|
||||
ws = ent->worldSector;
|
||||
if ( !ws ) {
|
||||
return; // not linked in anywhere
|
||||
}
|
||||
ent->worldSector = NULL;
|
||||
|
||||
if ( ws->entities == ent ) {
|
||||
ws->entities = ent->nextEntityInWorldSector;
|
||||
return;
|
||||
}
|
||||
|
||||
for ( scan = ws->entities ; scan ; scan = scan->nextEntityInWorldSector ) {
|
||||
if ( scan->nextEntityInWorldSector == ent ) {
|
||||
scan->nextEntityInWorldSector = ent->nextEntityInWorldSector;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Com_Printf( "WARNING: SV_UnlinkEntity: not found in worldSector\n" );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_LinkEntity
|
||||
|
||||
===============
|
||||
*/
|
||||
#define MAX_TOTAL_ENT_LEAFS 128
|
||||
void SV_LinkEntity( sharedEntity_t *gEnt ) {
|
||||
worldSector_t *node;
|
||||
int leafs[MAX_TOTAL_ENT_LEAFS];
|
||||
int cluster;
|
||||
int num_leafs;
|
||||
int i, j, k;
|
||||
int area;
|
||||
int lastLeaf;
|
||||
float *origin, *angles;
|
||||
svEntity_t *ent;
|
||||
|
||||
ent = SV_SvEntityForGentity( gEnt );
|
||||
|
||||
if ( ent->worldSector ) {
|
||||
SV_UnlinkEntity( gEnt ); // unlink from old position
|
||||
}
|
||||
|
||||
// encode the size into the entityState_t for client prediction
|
||||
if ( gEnt->r.bmodel ) {
|
||||
gEnt->s.solid = SOLID_BMODEL; // a solid_box will never create this value
|
||||
} else if ( gEnt->r.contents & ( CONTENTS_SOLID | CONTENTS_BODY ) ) {
|
||||
// assume that x/y are equal and symetric
|
||||
i = gEnt->r.maxs[0];
|
||||
if (i<1)
|
||||
i = 1;
|
||||
if (i>255)
|
||||
i = 255;
|
||||
|
||||
// z is not symetric
|
||||
j = (-gEnt->r.mins[2]);
|
||||
if (j<1)
|
||||
j = 1;
|
||||
if (j>255)
|
||||
j = 255;
|
||||
|
||||
// and z maxs can be negative...
|
||||
k = (gEnt->r.maxs[2]+32);
|
||||
if (k<1)
|
||||
k = 1;
|
||||
if (k>255)
|
||||
k = 255;
|
||||
|
||||
gEnt->s.solid = (k<<16) | (j<<8) | i;
|
||||
} else {
|
||||
gEnt->s.solid = 0;
|
||||
}
|
||||
|
||||
// get the position
|
||||
origin = gEnt->r.currentOrigin;
|
||||
angles = gEnt->r.currentAngles;
|
||||
|
||||
// set the abs box
|
||||
if ( gEnt->r.bmodel && (angles[0] || angles[1] || angles[2]) ) {
|
||||
// expand for rotation
|
||||
float max;
|
||||
int i;
|
||||
|
||||
max = RadiusFromBounds( gEnt->r.mins, gEnt->r.maxs );
|
||||
for (i=0 ; i<3 ; i++) {
|
||||
gEnt->r.absmin[i] = origin[i] - max;
|
||||
gEnt->r.absmax[i] = origin[i] + max;
|
||||
}
|
||||
} else {
|
||||
// normal
|
||||
VectorAdd (origin, gEnt->r.mins, gEnt->r.absmin);
|
||||
VectorAdd (origin, gEnt->r.maxs, gEnt->r.absmax);
|
||||
}
|
||||
|
||||
// because movement is clipped an epsilon away from an actual edge,
|
||||
// we must fully check even when bounding boxes don't quite touch
|
||||
gEnt->r.absmin[0] -= 1;
|
||||
gEnt->r.absmin[1] -= 1;
|
||||
gEnt->r.absmin[2] -= 1;
|
||||
gEnt->r.absmax[0] += 1;
|
||||
gEnt->r.absmax[1] += 1;
|
||||
gEnt->r.absmax[2] += 1;
|
||||
|
||||
// link to PVS leafs
|
||||
ent->numClusters = 0;
|
||||
ent->lastCluster = 0;
|
||||
ent->areanum = -1;
|
||||
ent->areanum2 = -1;
|
||||
|
||||
//get all leafs, including solids
|
||||
num_leafs = CM_BoxLeafnums( gEnt->r.absmin, gEnt->r.absmax,
|
||||
leafs, MAX_TOTAL_ENT_LEAFS, &lastLeaf );
|
||||
|
||||
// if none of the leafs were inside the map, the
|
||||
// entity is outside the world and can be considered unlinked
|
||||
if ( !num_leafs ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// set areas, even from clusters that don't fit in the entity array
|
||||
for (i=0 ; i<num_leafs ; i++) {
|
||||
area = CM_LeafArea (leafs[i]);
|
||||
if (area != -1) {
|
||||
// doors may legally straggle two areas,
|
||||
// but nothing should evern need more than that
|
||||
if (ent->areanum != -1 && ent->areanum != area) {
|
||||
if (ent->areanum2 != -1 && ent->areanum2 != area && sv.state == SS_LOADING) {
|
||||
Com_DPrintf ("Object %i touching 3 areas at %f %f %f\n",
|
||||
gEnt->s.number,
|
||||
gEnt->r.absmin[0], gEnt->r.absmin[1], gEnt->r.absmin[2]);
|
||||
}
|
||||
ent->areanum2 = area;
|
||||
} else {
|
||||
ent->areanum = area;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// store as many explicit clusters as we can
|
||||
ent->numClusters = 0;
|
||||
for (i=0 ; i < num_leafs ; i++) {
|
||||
cluster = CM_LeafCluster( leafs[i] );
|
||||
if ( cluster != -1 ) {
|
||||
ent->clusternums[ent->numClusters++] = cluster;
|
||||
if ( ent->numClusters == MAX_ENT_CLUSTERS ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// store off a last cluster if we need to
|
||||
if ( i != num_leafs ) {
|
||||
ent->lastCluster = CM_LeafCluster( lastLeaf );
|
||||
}
|
||||
|
||||
gEnt->r.linkcount++;
|
||||
|
||||
// find the first world sector node that the ent's box crosses
|
||||
node = sv_worldSectors;
|
||||
while (1)
|
||||
{
|
||||
if (node->axis == -1)
|
||||
break;
|
||||
if ( gEnt->r.absmin[node->axis] > node->dist)
|
||||
node = node->children[0];
|
||||
else if ( gEnt->r.absmax[node->axis] < node->dist)
|
||||
node = node->children[1];
|
||||
else
|
||||
break; // crosses the node
|
||||
}
|
||||
|
||||
// link it in
|
||||
ent->worldSector = node;
|
||||
ent->nextEntityInWorldSector = node->entities;
|
||||
node->entities = ent;
|
||||
|
||||
gEnt->r.linked = qtrue;
|
||||
}
|
||||
|
||||
/*
|
||||
============================================================================
|
||||
|
||||
AREA QUERY
|
||||
|
||||
Fills in a list of all entities who's absmin / absmax intersects the given
|
||||
bounds. This does NOT mean that they actually touch in the case of bmodels.
|
||||
============================================================================
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
const float *mins;
|
||||
const float *maxs;
|
||||
int *list;
|
||||
int count, maxcount;
|
||||
} areaParms_t;
|
||||
|
||||
|
||||
/*
|
||||
====================
|
||||
SV_AreaEntities_r
|
||||
|
||||
====================
|
||||
*/
|
||||
void SV_AreaEntities_r( worldSector_t *node, areaParms_t *ap ) {
|
||||
svEntity_t *check, *next;
|
||||
sharedEntity_t *gcheck;
|
||||
int count;
|
||||
|
||||
count = 0;
|
||||
|
||||
for ( check = node->entities ; check ; check = next ) {
|
||||
next = check->nextEntityInWorldSector;
|
||||
|
||||
gcheck = SV_GEntityForSvEntity( check );
|
||||
|
||||
if ( gcheck->r.absmin[0] > ap->maxs[0]
|
||||
|| gcheck->r.absmin[1] > ap->maxs[1]
|
||||
|| gcheck->r.absmin[2] > ap->maxs[2]
|
||||
|| gcheck->r.absmax[0] < ap->mins[0]
|
||||
|| gcheck->r.absmax[1] < ap->mins[1]
|
||||
|| gcheck->r.absmax[2] < ap->mins[2]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ap->count == ap->maxcount ) {
|
||||
Com_Printf ("SV_AreaEntities: MAXCOUNT\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ap->list[ap->count] = check - sv.svEntities;
|
||||
ap->count++;
|
||||
}
|
||||
|
||||
if (node->axis == -1) {
|
||||
return; // terminal node
|
||||
}
|
||||
|
||||
// recurse down both sides
|
||||
if ( ap->maxs[node->axis] > node->dist ) {
|
||||
SV_AreaEntities_r ( node->children[0], ap );
|
||||
}
|
||||
if ( ap->mins[node->axis] < node->dist ) {
|
||||
SV_AreaEntities_r ( node->children[1], ap );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
SV_AreaEntities
|
||||
================
|
||||
*/
|
||||
int SV_AreaEntities( const vec3_t mins, const vec3_t maxs, int *entityList, int maxcount ) {
|
||||
areaParms_t ap;
|
||||
|
||||
ap.mins = mins;
|
||||
ap.maxs = maxs;
|
||||
ap.list = entityList;
|
||||
ap.count = 0;
|
||||
ap.maxcount = maxcount;
|
||||
|
||||
SV_AreaEntities_r( sv_worldSectors, &ap );
|
||||
|
||||
return ap.count;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//===========================================================================
|
||||
|
||||
|
||||
typedef struct {
|
||||
vec3_t boxmins, boxmaxs;// enclose the test object along entire move
|
||||
const float *mins;
|
||||
const float *maxs; // size of the moving object
|
||||
const float *start;
|
||||
vec3_t end;
|
||||
trace_t trace;
|
||||
int passEntityNum;
|
||||
int contentmask;
|
||||
int capsule;
|
||||
} moveclip_t;
|
||||
|
||||
|
||||
/*
|
||||
====================
|
||||
SV_ClipToEntity
|
||||
|
||||
====================
|
||||
*/
|
||||
void SV_ClipToEntity( trace_t *trace, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int entityNum, int contentmask, int capsule ) {
|
||||
sharedEntity_t *touch;
|
||||
clipHandle_t clipHandle;
|
||||
float *origin, *angles;
|
||||
|
||||
touch = SV_GentityNum( entityNum );
|
||||
|
||||
Com_Memset(trace, 0, sizeof(trace_t));
|
||||
|
||||
// if it doesn't have any brushes of a type we
|
||||
// are looking for, ignore it
|
||||
if ( ! ( contentmask & touch->r.contents ) ) {
|
||||
trace->fraction = 1.0;
|
||||
return;
|
||||
}
|
||||
|
||||
// might intersect, so do an exact clip
|
||||
clipHandle = SV_ClipHandleForEntity (touch);
|
||||
|
||||
origin = touch->r.currentOrigin;
|
||||
angles = touch->r.currentAngles;
|
||||
|
||||
if ( !touch->r.bmodel ) {
|
||||
angles = vec3_origin; // boxes don't rotate
|
||||
}
|
||||
|
||||
CM_TransformedBoxTrace ( trace, (float *)start, (float *)end,
|
||||
(float *)mins, (float *)maxs, clipHandle, contentmask,
|
||||
origin, angles, capsule);
|
||||
|
||||
if ( trace->fraction < 1 ) {
|
||||
trace->entityNum = touch->s.number;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
====================
|
||||
SV_ClipMoveToEntities
|
||||
|
||||
====================
|
||||
*/
|
||||
void SV_ClipMoveToEntities( moveclip_t *clip ) {
|
||||
int i, num;
|
||||
int touchlist[MAX_GENTITIES];
|
||||
sharedEntity_t *touch;
|
||||
int passOwnerNum;
|
||||
trace_t trace;
|
||||
clipHandle_t clipHandle;
|
||||
float *origin, *angles;
|
||||
|
||||
num = SV_AreaEntities( clip->boxmins, clip->boxmaxs, touchlist, MAX_GENTITIES);
|
||||
|
||||
if ( clip->passEntityNum != ENTITYNUM_NONE ) {
|
||||
passOwnerNum = ( SV_GentityNum( clip->passEntityNum ) )->r.ownerNum;
|
||||
if ( passOwnerNum == ENTITYNUM_NONE ) {
|
||||
passOwnerNum = -1;
|
||||
}
|
||||
} else {
|
||||
passOwnerNum = -1;
|
||||
}
|
||||
|
||||
for ( i=0 ; i<num ; i++ ) {
|
||||
if ( clip->trace.allsolid ) {
|
||||
return;
|
||||
}
|
||||
touch = SV_GentityNum( touchlist[i] );
|
||||
|
||||
// see if we should ignore this entity
|
||||
if ( clip->passEntityNum != ENTITYNUM_NONE ) {
|
||||
if ( touchlist[i] == clip->passEntityNum ) {
|
||||
continue; // don't clip against the pass entity
|
||||
}
|
||||
if ( touch->r.ownerNum == clip->passEntityNum ) {
|
||||
continue; // don't clip against own missiles
|
||||
}
|
||||
if ( touch->r.ownerNum == passOwnerNum ) {
|
||||
continue; // don't clip against other missiles from our owner
|
||||
}
|
||||
}
|
||||
|
||||
// if it doesn't have any brushes of a type we
|
||||
// are looking for, ignore it
|
||||
if ( ! ( clip->contentmask & touch->r.contents ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// might intersect, so do an exact clip
|
||||
clipHandle = SV_ClipHandleForEntity (touch);
|
||||
|
||||
origin = touch->r.currentOrigin;
|
||||
angles = touch->r.currentAngles;
|
||||
|
||||
|
||||
if ( !touch->r.bmodel ) {
|
||||
angles = vec3_origin; // boxes don't rotate
|
||||
}
|
||||
|
||||
CM_TransformedBoxTrace ( &trace, (float *)clip->start, (float *)clip->end,
|
||||
(float *)clip->mins, (float *)clip->maxs, clipHandle, clip->contentmask,
|
||||
origin, angles, clip->capsule);
|
||||
|
||||
if ( trace.allsolid ) {
|
||||
clip->trace.allsolid = qtrue;
|
||||
trace.entityNum = touch->s.number;
|
||||
} else if ( trace.startsolid ) {
|
||||
clip->trace.startsolid = qtrue;
|
||||
trace.entityNum = touch->s.number;
|
||||
}
|
||||
|
||||
if ( trace.fraction < clip->trace.fraction ) {
|
||||
qboolean oldStart;
|
||||
|
||||
// make sure we keep a startsolid from a previous trace
|
||||
oldStart = clip->trace.startsolid;
|
||||
|
||||
trace.entityNum = touch->s.number;
|
||||
clip->trace = trace;
|
||||
clip->trace.startsolid |= oldStart;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_Trace
|
||||
|
||||
Moves the given mins/maxs volume through the world from start to end.
|
||||
passEntityNum and entities owned by passEntityNum are explicitly not checked.
|
||||
==================
|
||||
*/
|
||||
void SV_Trace( trace_t *results, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask, int capsule ) {
|
||||
moveclip_t clip;
|
||||
int i;
|
||||
|
||||
if ( !mins ) {
|
||||
mins = vec3_origin;
|
||||
}
|
||||
if ( !maxs ) {
|
||||
maxs = vec3_origin;
|
||||
}
|
||||
|
||||
Com_Memset ( &clip, 0, sizeof ( moveclip_t ) );
|
||||
|
||||
// clip to world
|
||||
CM_BoxTrace( &clip.trace, start, end, mins, maxs, 0, contentmask, capsule );
|
||||
clip.trace.entityNum = clip.trace.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE;
|
||||
if ( clip.trace.fraction == 0 ) {
|
||||
*results = clip.trace;
|
||||
return; // blocked immediately by the world
|
||||
}
|
||||
|
||||
clip.contentmask = contentmask;
|
||||
clip.start = start;
|
||||
// VectorCopy( clip.trace.endpos, clip.end );
|
||||
VectorCopy( end, clip.end );
|
||||
clip.mins = mins;
|
||||
clip.maxs = maxs;
|
||||
clip.passEntityNum = passEntityNum;
|
||||
clip.capsule = capsule;
|
||||
|
||||
// create the bounding box of the entire move
|
||||
// we can limit it to the part of the move not
|
||||
// already clipped off by the world, which can be
|
||||
// a significant savings for line of sight and shot traces
|
||||
for ( i=0 ; i<3 ; i++ ) {
|
||||
if ( end[i] > start[i] ) {
|
||||
clip.boxmins[i] = clip.start[i] + clip.mins[i] - 1;
|
||||
clip.boxmaxs[i] = clip.end[i] + clip.maxs[i] + 1;
|
||||
} else {
|
||||
clip.boxmins[i] = clip.end[i] + clip.mins[i] - 1;
|
||||
clip.boxmaxs[i] = clip.start[i] + clip.maxs[i] + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// clip to other solid entities
|
||||
SV_ClipMoveToEntities ( &clip );
|
||||
|
||||
*results = clip.trace;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
=============
|
||||
SV_PointContents
|
||||
=============
|
||||
*/
|
||||
int SV_PointContents( const vec3_t p, int passEntityNum ) {
|
||||
int touch[MAX_GENTITIES];
|
||||
sharedEntity_t *hit;
|
||||
int i, num;
|
||||
int contents, c2;
|
||||
clipHandle_t clipHandle;
|
||||
float *angles;
|
||||
|
||||
// get base contents from world
|
||||
contents = CM_PointContents( p, 0 );
|
||||
|
||||
// or in contents from all the other entities
|
||||
num = SV_AreaEntities( p, p, touch, MAX_GENTITIES );
|
||||
|
||||
for ( i=0 ; i<num ; i++ ) {
|
||||
if ( touch[i] == passEntityNum ) {
|
||||
continue;
|
||||
}
|
||||
hit = SV_GentityNum( touch[i] );
|
||||
// might intersect, so do an exact clip
|
||||
clipHandle = SV_ClipHandleForEntity( hit );
|
||||
angles = hit->s.angles;
|
||||
if ( !hit->r.bmodel ) {
|
||||
angles = vec3_origin; // boxes don't rotate
|
||||
}
|
||||
|
||||
c2 = CM_TransformedPointContents (p, clipHandle, hit->s.origin, hit->s.angles);
|
||||
|
||||
contents |= c2;
|
||||
}
|
||||
|
||||
return contents;
|
||||
}
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue