Initial commit.
This commit is contained in:
605
code/server/sv_client.cpp
Normal file
605
code/server/sv_client.cpp
Normal file
@@ -0,0 +1,605 @@
|
||||
// sv_client.c -- server code for dealing with clients
|
||||
|
||||
// leave this as first line for PCH reasons...
|
||||
//
|
||||
#include "../server/exe_headers.h"
|
||||
|
||||
|
||||
|
||||
|
||||
#include "server.h"
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_DirectConnect
|
||||
|
||||
A "connect" OOB command has been received
|
||||
==================
|
||||
*/
|
||||
void SV_DirectConnect( netadr_t from ) {
|
||||
char userinfo[MAX_INFO_STRING];
|
||||
int i;
|
||||
client_t *cl, *newcl;
|
||||
MAC_STATIC client_t temp;
|
||||
gentity_t *ent;
|
||||
int clientNum;
|
||||
int version;
|
||||
int qport;
|
||||
int challenge;
|
||||
char *denied;
|
||||
|
||||
Com_DPrintf ("SVC_DirectConnect ()\n");
|
||||
|
||||
Q_strncpyz( userinfo, Cmd_Argv(1), sizeof(userinfo) );
|
||||
|
||||
version = atoi( Info_ValueForKey( userinfo, "protocol" ) );
|
||||
if ( version != PROTOCOL_VERSION ) {
|
||||
NET_OutOfBandPrint( NS_SERVER, from, "print\nServer uses protocol version %i.\n", PROTOCOL_VERSION );
|
||||
Com_DPrintf (" rejected connect from version %i\n", version);
|
||||
return;
|
||||
}
|
||||
|
||||
qport = atoi( Info_ValueForKey( userinfo, "qport" ) );
|
||||
|
||||
challenge = atoi( Info_ValueForKey( userinfo, "challenge" ) );
|
||||
|
||||
// see if the challenge is valid (local clients don't need to challenge)
|
||||
if ( !NET_IsLocalAddress (from) ) {
|
||||
NET_OutOfBandPrint( NS_SERVER, from, "print\nNo challenge for address.\n" );
|
||||
return;
|
||||
} else {
|
||||
// force the "ip" info key to "localhost"
|
||||
Info_SetValueForKey( userinfo, "ip", "localhost" );
|
||||
}
|
||||
|
||||
newcl = &temp;
|
||||
memset (newcl, 0, sizeof(client_t));
|
||||
|
||||
// if there is already a slot for this ip, reuse it
|
||||
for (i=0,cl=svs.clients ; i < 1 ; i++,cl++)
|
||||
{
|
||||
if ( cl->state == CS_FREE ) {
|
||||
continue;
|
||||
}
|
||||
if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress )
|
||||
&& ( cl->netchan.qport == qport
|
||||
|| from.port == cl->netchan.remoteAddress.port ) )
|
||||
{
|
||||
if (( sv.time - cl->lastConnectTime)
|
||||
< (sv_reconnectlimit->integer * 1000))
|
||||
{
|
||||
Com_DPrintf ("%s:reconnect rejected : too soon\n", NET_AdrToString (from));
|
||||
return;
|
||||
}
|
||||
Com_Printf ("%s:reconnect\n", NET_AdrToString (from));
|
||||
newcl = cl;
|
||||
goto gotnewcl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
newcl = NULL;
|
||||
for ( i = 0; i < 1 ; i++ ) {
|
||||
cl = &svs.clients[i];
|
||||
if (cl->state == CS_FREE) {
|
||||
newcl = cl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !newcl ) {
|
||||
NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is full.\n" );
|
||||
Com_DPrintf ("Rejected a connection.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
gotnewcl:
|
||||
// build a new connection
|
||||
// accept the new client
|
||||
// this is the only place a client_t is ever initialized
|
||||
*newcl = temp;
|
||||
clientNum = newcl - svs.clients;
|
||||
ent = SV_GentityNum( clientNum );
|
||||
newcl->gentity = ent;
|
||||
|
||||
// save the address
|
||||
Netchan_Setup (NS_SERVER, &newcl->netchan , from, qport);
|
||||
|
||||
// save the userinfo
|
||||
Q_strncpyz( newcl->userinfo, userinfo, sizeof(newcl->userinfo) );
|
||||
|
||||
// get the game a chance to reject this connection or modify the userinfo
|
||||
denied = ge->ClientConnect( clientNum, qtrue, eSavedGameJustLoaded ); // firstTime = qtrue
|
||||
if ( denied ) {
|
||||
NET_OutOfBandPrint( NS_SERVER, from, "print\n%s\n", denied );
|
||||
Com_DPrintf ("Game rejected a connection: %s.\n", denied);
|
||||
return;
|
||||
}
|
||||
|
||||
SV_UserinfoChanged( newcl );
|
||||
|
||||
// send the connect packet to the client
|
||||
NET_OutOfBandPrint( NS_SERVER, from, "connectResponse" );
|
||||
|
||||
newcl->state = CS_CONNECTED;
|
||||
newcl->nextSnapshotTime = sv.time;
|
||||
newcl->lastPacketTime = sv.time;
|
||||
newcl->lastConnectTime = sv.time;
|
||||
|
||||
// when we receive the first packet from the client, we will
|
||||
// notice that it is from a different serverid and that the
|
||||
// gamestate message was not just sent, forcing a retransmit
|
||||
newcl->gamestateMessageNum = -1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=====================
|
||||
SV_DropClient
|
||||
|
||||
Called when the player is totally leaving the server, either willingly
|
||||
or unwillingly. This is NOT called if the entire server is quiting
|
||||
or crashing -- SV_FinalMessage() will handle that
|
||||
=====================
|
||||
*/
|
||||
void SV_DropClient( client_t *drop, const char *reason ) {
|
||||
if ( drop->state == CS_ZOMBIE ) {
|
||||
return; // already dropped
|
||||
}
|
||||
drop->state = CS_ZOMBIE; // become free in a few seconds
|
||||
|
||||
if (drop->download) {
|
||||
FS_FreeFile (drop->download);
|
||||
drop->download = NULL;
|
||||
}
|
||||
|
||||
// call the prog function for removing a client
|
||||
// this will remove the body, among other things
|
||||
ge->ClientDisconnect( drop - svs.clients );
|
||||
|
||||
// tell everyone why they got dropped
|
||||
SV_SendServerCommand( NULL, "print \"%s %s\n\"", drop->name, reason );
|
||||
|
||||
// add the disconnect command
|
||||
SV_SendServerCommand( drop, "disconnect" );
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
SV_SendClientGameState
|
||||
|
||||
Sends the first message from the server to a connected client.
|
||||
This will be sent on the initial connection and upon each new map load.
|
||||
|
||||
It will be resent if the client acknowledges a later message but has
|
||||
the wrong gamestate.
|
||||
================
|
||||
*/
|
||||
void SV_SendClientGameState( client_t *client ) {
|
||||
int start;
|
||||
msg_t msg;
|
||||
byte msgBuffer[MAX_MSGLEN];
|
||||
|
||||
Com_DPrintf ("SV_SendGameState() for %s\n", client->name);
|
||||
client->state = CS_PRIMED;
|
||||
|
||||
// when we receive the first packet from the client, we will
|
||||
// notice that it is from a different serverid and that the
|
||||
// gamestate message was not just sent, forcing a retransmit
|
||||
client->gamestateMessageNum = client->netchan.outgoingSequence;
|
||||
|
||||
// clear the reliable message list for this client
|
||||
client->reliableSequence = 0;
|
||||
client->reliableAcknowledge = 0;
|
||||
|
||||
MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) );
|
||||
|
||||
// send the gamestate
|
||||
MSG_WriteByte( &msg, svc_gamestate );
|
||||
MSG_WriteLong( &msg, client->reliableSequence );
|
||||
|
||||
// write the configstrings
|
||||
for ( start = 0 ; start < MAX_CONFIGSTRINGS ; start++ ) {
|
||||
if (sv.configstrings[start][0]) {
|
||||
MSG_WriteByte( &msg, svc_configstring );
|
||||
MSG_WriteShort( &msg, start );
|
||||
MSG_WriteString( &msg, sv.configstrings[start] );
|
||||
}
|
||||
}
|
||||
|
||||
MSG_WriteByte( &msg, 0 );
|
||||
|
||||
// check for overflow
|
||||
if ( msg.overflowed ) {
|
||||
Com_Printf ("WARNING: GameState overflowed for %s\n", client->name);
|
||||
}
|
||||
|
||||
// deliver this to the client
|
||||
SV_SendMessageToClient( &msg, client );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_ClientEnterWorld
|
||||
==================
|
||||
*/
|
||||
void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd, SavedGameJustLoaded_e eSavedGameJustLoaded ) {
|
||||
int clientNum;
|
||||
gentity_t *ent;
|
||||
|
||||
Com_DPrintf ("SV_ClientEnterWorld() from %s\n", client->name);
|
||||
client->state = CS_ACTIVE;
|
||||
|
||||
// set up the entity for the client
|
||||
clientNum = client - svs.clients;
|
||||
ent = SV_GentityNum( clientNum );
|
||||
ent->s.number = clientNum;
|
||||
client->gentity = ent;
|
||||
|
||||
// normally I check 'qbFromSavedGame' to avoid overwriting loaded client data, but this stuff I want
|
||||
// to be reset so that client packet delta-ing bgins afresh, rather than based on your previous frame
|
||||
// (which didn't in fact happen now if we've just loaded from a saved game...)
|
||||
//
|
||||
client->deltaMessage = -1;
|
||||
client->cmdNum = 0;
|
||||
client->nextSnapshotTime = sv.time; // generate a snapshot immediately
|
||||
|
||||
// call the game begin function
|
||||
ge->ClientBegin( client - svs.clients, cmd, eSavedGameJustLoaded );
|
||||
}
|
||||
|
||||
/*
|
||||
============================================================
|
||||
|
||||
CLIENT COMMAND EXECUTION
|
||||
|
||||
============================================================
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
SV_Disconnect_f
|
||||
|
||||
The client is going to disconnect, so remove the connection immediately FIXME: move to game?
|
||||
=================
|
||||
*/
|
||||
static void SV_Disconnect_f( client_t *cl ) {
|
||||
SV_DropClient( cl, "disconnected" );
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
SV_UserinfoChanged
|
||||
|
||||
Pull specific info from a newly changed userinfo string
|
||||
into a more C friendly form.
|
||||
=================
|
||||
*/
|
||||
void SV_UserinfoChanged( client_t *cl ) {
|
||||
char *val;
|
||||
int i;
|
||||
|
||||
// name for C code
|
||||
Q_strncpyz( cl->name, Info_ValueForKey (cl->userinfo, "name"), sizeof(cl->name) );
|
||||
|
||||
// rate command
|
||||
|
||||
// if the client is on the same subnet as the server and we aren't running an
|
||||
// internet public server, assume they don't need a rate choke
|
||||
cl->rate = 99999; // lans should not rate limit
|
||||
|
||||
// snaps command
|
||||
val = Info_ValueForKey (cl->userinfo, "snaps");
|
||||
if (strlen(val)) {
|
||||
i = atoi(val);
|
||||
if ( i < 1 ) {
|
||||
i = 1;
|
||||
} else if ( i > 30 ) {
|
||||
i = 30;
|
||||
}
|
||||
cl->snapshotMsec = 1000/i;
|
||||
} else {
|
||||
cl->snapshotMsec = 50;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_UpdateUserinfo_f
|
||||
==================
|
||||
*/
|
||||
static void SV_UpdateUserinfo_f( client_t *cl ) {
|
||||
Q_strncpyz( cl->userinfo, Cmd_Argv(1), sizeof(cl->userinfo) );
|
||||
|
||||
// call prog code to allow overrides
|
||||
ge->ClientUserinfoChanged( cl - svs.clients );
|
||||
|
||||
SV_UserinfoChanged( cl );
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
void (*func)( client_t *cl );
|
||||
} ucmd_t;
|
||||
|
||||
static ucmd_t ucmds[] = {
|
||||
{"userinfo", SV_UpdateUserinfo_f},
|
||||
{"disconnect", SV_Disconnect_f},
|
||||
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_ExecuteClientCommand
|
||||
==================
|
||||
*/
|
||||
void SV_ExecuteClientCommand( client_t *cl, const char *s ) {
|
||||
ucmd_t *u;
|
||||
|
||||
Cmd_TokenizeString( s );
|
||||
|
||||
// see if it is a server level command
|
||||
for (u=ucmds ; u->name ; u++) {
|
||||
if (!strcmp (Cmd_Argv(0), u->name) ) {
|
||||
u->func( cl );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// pass unknown strings to the game
|
||||
if (!u->name && sv.state == SS_GAME) {
|
||||
ge->ClientCommand( cl - svs.clients );
|
||||
}
|
||||
}
|
||||
|
||||
#define MAX_STRINGCMDS 8
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_ClientCommand
|
||||
===============
|
||||
*/
|
||||
static void SV_ClientCommand( client_t *cl, msg_t *msg ) {
|
||||
int seq;
|
||||
const char *s;
|
||||
|
||||
seq = MSG_ReadLong( msg );
|
||||
s = MSG_ReadString( msg );
|
||||
|
||||
// see if we have already executed it
|
||||
if ( cl->lastClientCommand >= seq ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Com_DPrintf( "clientCommand: %s : %i : %s\n", cl->name, seq, s );
|
||||
|
||||
// drop the connection if we have somehow lost commands
|
||||
if ( seq > cl->lastClientCommand + 1 ) {
|
||||
Com_Printf( "Client %s lost %i clientCommands\n", cl->name,
|
||||
seq - cl->lastClientCommand + 1 );
|
||||
}
|
||||
|
||||
SV_ExecuteClientCommand( cl, s );
|
||||
|
||||
cl->lastClientCommand = seq;
|
||||
}
|
||||
|
||||
|
||||
//==================================================================================
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_ClientThink
|
||||
==================
|
||||
*/
|
||||
void SV_ClientThink (client_t *cl, usercmd_t *cmd) {
|
||||
cl->lastUsercmd = *cmd;
|
||||
|
||||
if ( cl->state != CS_ACTIVE ) {
|
||||
return; // may have been kicked during the last usercmd
|
||||
}
|
||||
|
||||
ge->ClientThink( cl - svs.clients, cmd );
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_UserMove
|
||||
|
||||
The message usually contains all the movement commands
|
||||
that were in the last three packets, so that the information
|
||||
in dropped packets can be recovered.
|
||||
|
||||
On very fast clients, there may be multiple usercmd packed into
|
||||
each of the backup packets.
|
||||
==================
|
||||
*/
|
||||
static void SV_UserMove( client_t *cl, msg_t *msg ) {
|
||||
int i, start;
|
||||
int cmdNum;
|
||||
int firstNum;
|
||||
int cmdCount;
|
||||
usercmd_t nullcmd;
|
||||
usercmd_t cmds[MAX_PACKET_USERCMDS];
|
||||
usercmd_t *cmd, *oldcmd;
|
||||
int clientTime;
|
||||
int serverId;
|
||||
|
||||
cl->reliableAcknowledge = MSG_ReadLong( msg );
|
||||
serverId = MSG_ReadLong( msg );
|
||||
clientTime = MSG_ReadLong( msg );
|
||||
cl->deltaMessage = MSG_ReadLong( msg );
|
||||
|
||||
// cmdNum is the command number of the most recent included usercmd
|
||||
cmdNum = MSG_ReadLong( msg );
|
||||
cmdCount = MSG_ReadByte( msg );
|
||||
|
||||
if ( cmdCount < 1 ) {
|
||||
Com_Printf( "cmdCount < 1\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( cmdCount > MAX_PACKET_USERCMDS ) {
|
||||
Com_Printf( "cmdCount > MAX_PACKET_USERCMDS\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
memset( &nullcmd, 0, sizeof(nullcmd) );
|
||||
oldcmd = &nullcmd;
|
||||
for ( i = 0 ; i < cmdCount ; i++ ) {
|
||||
cmd = &cmds[i];
|
||||
MSG_ReadDeltaUsercmd( msg, oldcmd, cmd );
|
||||
oldcmd = cmd;
|
||||
}
|
||||
|
||||
// if this is a usercmd from a previous gamestate,
|
||||
// ignore it or retransmit the current gamestate
|
||||
if ( serverId != sv.serverId ) {
|
||||
// if we can tell that the client has dropped the last
|
||||
// gamestate we sent them, resend it
|
||||
if ( cl->netchan.incomingAcknowledged > cl->gamestateMessageNum ) {
|
||||
Com_DPrintf( "%s : dropped gamestate, resending\n", cl->name );
|
||||
SV_SendClientGameState( cl );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// if this is the first usercmd we have received
|
||||
// this gamestate, put the client into the world
|
||||
if ( cl->state == CS_PRIMED ) {
|
||||
|
||||
SV_ClientEnterWorld( cl, &cmds[0], eSavedGameJustLoaded );
|
||||
#ifndef _XBOX // No auto-saving for now?
|
||||
if ( sv_mapname->string[0]!='_' )
|
||||
{
|
||||
char savename[MAX_QPATH];
|
||||
if ( eSavedGameJustLoaded == eNO )
|
||||
{
|
||||
SG_WriteSavegame("auto",qtrue);
|
||||
if ( strnicmp(sv_mapname->string, "academy", 7) != 0)
|
||||
{
|
||||
Com_sprintf (savename, sizeof(savename), "auto_%s",sv_mapname->string);
|
||||
SG_WriteSavegame(savename,qtrue);//can't use va becuase it's nested
|
||||
}
|
||||
}
|
||||
else if ( qbLoadTransition == qtrue )
|
||||
{
|
||||
Com_sprintf (savename, sizeof(savename), "hub/%s", sv_mapname->string );
|
||||
SG_WriteSavegame( savename, qfalse );//save a full one
|
||||
SG_WriteSavegame( "auto", qfalse );//need a copy for auto, too
|
||||
}
|
||||
}
|
||||
#endif
|
||||
eSavedGameJustLoaded = eNO;
|
||||
// the moves can be processed normaly
|
||||
}
|
||||
|
||||
if ( cl->state != CS_ACTIVE ) {
|
||||
cl->deltaMessage = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// if there is a time gap from the last packet to this packet,
|
||||
// fill in with the first command in the packet
|
||||
|
||||
// with a packetdup of 0, firstNum == cmdNum
|
||||
firstNum = cmdNum - ( cmdCount - 1 );
|
||||
if ( cl->cmdNum < firstNum - 1 ) {
|
||||
cl->droppedCommands = qtrue;
|
||||
if ( sv_showloss->integer ) {
|
||||
Com_Printf("Lost %i usercmds from %s\n", firstNum - 1 - cl->cmdNum,
|
||||
cl->name);
|
||||
}
|
||||
if ( cl->cmdNum < firstNum - 6 ) {
|
||||
cl->cmdNum = firstNum - 6; // don't generate too many
|
||||
}
|
||||
while ( cl->cmdNum < firstNum - 1 ) {
|
||||
cl->cmdNum++;
|
||||
SV_ClientThink( cl, &cmds[0] );
|
||||
}
|
||||
}
|
||||
// skip over any usercmd_t we have already executed
|
||||
start = cl->cmdNum - ( firstNum - 1 );
|
||||
for ( i = start ; i < cmdCount ; i++ ) {
|
||||
SV_ClientThink (cl, &cmds[ i ]);
|
||||
}
|
||||
cl->cmdNum = cmdNum;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===========================================================================
|
||||
|
||||
USER CMD EXECUTION
|
||||
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
===================
|
||||
SV_ExecuteClientMessage
|
||||
|
||||
Parse a client packet
|
||||
===================
|
||||
*/
|
||||
void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) {
|
||||
int c;
|
||||
|
||||
while( 1 ) {
|
||||
if ( msg->readcount > msg->cursize ) {
|
||||
SV_DropClient (cl, "had a badread");
|
||||
return;
|
||||
}
|
||||
|
||||
c = MSG_ReadByte( msg );
|
||||
if ( c == -1 ) {
|
||||
break;
|
||||
}
|
||||
|
||||
switch( c ) {
|
||||
default:
|
||||
SV_DropClient( cl,"had an unknown command char" );
|
||||
return;
|
||||
|
||||
case clc_nop:
|
||||
break;
|
||||
|
||||
case clc_move:
|
||||
SV_UserMove( cl, msg );
|
||||
break;
|
||||
|
||||
case clc_clientCommand:
|
||||
SV_ClientCommand( cl, msg );
|
||||
if (cl->state == CS_ZOMBIE) {
|
||||
return; // disconnect command
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SV_FreeClient(client_t *client)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!client) return;
|
||||
|
||||
for(i=0; i<MAX_RELIABLE_COMMANDS; i++) {
|
||||
if ( client->reliableCommands[ i] ) {
|
||||
Z_Free( client->reliableCommands[ i] );
|
||||
client->reliableCommands[i] = NULL;
|
||||
client->reliableSequence = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user