📄 sv_client.c
字号:
newcl = &svs.clients[sv_maxclients->integer - 1];
}
else {
Com_Error( ERR_FATAL, "server is full on local connect\n" );
return;
}
}
else {
NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is full.\n" );
Com_DPrintf ("Rejected a connection.\n");
return;
}
}
// we got a newcl, so reset the reliableSequence and reliableAcknowledge
cl->reliableAcknowledge = 0;
cl->reliableSequence = 0;
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 challenge
newcl->challenge = challenge;
// save the address
Netchan_Setup (NS_SERVER, &newcl->netchan , from, qport);
// init the netchan queue
newcl->netchan_end_queue = &newcl->netchan_start_queue;
// 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 = (char *)VM_Call( gvm, GAME_CLIENT_CONNECT, clientNum, qtrue, qfalse ); // firstTime = qtrue
if ( denied ) {
// we can't just use VM_ArgPtr, because that is only valid inside a VM_Call
denied = VM_ExplicitArgPtr( gvm, (int)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" );
Com_DPrintf( "Going from CS_FREE to CS_CONNECTED for %s\n", newcl->name );
newcl->state = CS_CONNECTED;
newcl->nextSnapshotTime = svs.time;
newcl->lastPacketTime = svs.time;
newcl->lastConnectTime = svs.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;
// if this was the first client on the server, or the last client
// the server can hold, send a heartbeat to the master.
count = 0;
for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) {
if ( svs.clients[i].state >= CS_CONNECTED ) {
count++;
}
}
if ( count == 1 || count == sv_maxclients->integer ) {
SV_Heartbeat_f();
}
}
/*
=====================
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 ) {
int i;
challenge_t *challenge;
if ( drop->state == CS_ZOMBIE ) {
return; // already dropped
}
if ( !drop->gentity || !(drop->gentity->r.svFlags & SVF_BOT) ) {
// see if we already have a challenge for this ip
challenge = &svs.challenges[0];
for (i = 0 ; i < MAX_CHALLENGES ; i++, challenge++) {
if ( NET_CompareAdr( drop->netchan.remoteAddress, challenge->adr ) ) {
challenge->connected = qfalse;
break;
}
}
}
// Kill any download
SV_CloseDownload( drop );
// tell everyone why they got dropped
SV_SendServerCommand( NULL, "print \"%s" S_COLOR_WHITE " %s\n\"", drop->name, reason );
Com_DPrintf( "Going to CS_ZOMBIE for %s\n", drop->name );
drop->state = CS_ZOMBIE; // become free in a few seconds
if (drop->download) {
FS_FCloseFile( drop->download );
drop->download = 0;
}
// call the prog function for removing a client
// this will remove the body, among other things
VM_Call( gvm, GAME_CLIENT_DISCONNECT, drop - svs.clients );
// add the disconnect command
SV_SendServerCommand( drop, "disconnect \"%s\"", reason);
if ( drop->netchan.remoteAddress.type == NA_BOT ) {
SV_BotFreeClient( drop - svs.clients );
}
// nuke user info
SV_SetUserinfo( drop - svs.clients, "" );
// if this was the last client on the server, send a heartbeat
// to the master so it is known the server is empty
// send a heartbeat now so the master will get up to date info
// if there is already a slot for this ip, reuse it
for (i=0 ; i < sv_maxclients->integer ; i++ ) {
if ( svs.clients[i].state >= CS_CONNECTED ) {
break;
}
}
if ( i == sv_maxclients->integer ) {
SV_Heartbeat_f();
}
}
/*
================
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;
entityState_t *base, nullstate;
msg_t msg;
byte msgBuffer[MAX_MSGLEN];
Com_DPrintf ("SV_SendClientGameState() for %s\n", client->name);
Com_DPrintf( "Going from CS_CONNECTED to CS_PRIMED for %s\n", client->name );
client->state = CS_PRIMED;
client->pureAuthentic = 0;
client->gotCP = qfalse;
// 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;
MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) );
// NOTE, MRE: all server->client messages now acknowledge
// let the client know which reliable clientCommands we have received
MSG_WriteLong( &msg, client->lastClientCommand );
// send any server commands waiting to be sent first.
// we have to do this cause we send the client->reliableSequence
// with a gamestate and it sets the clc.serverCommandSequence at
// the client side
SV_UpdateServerCommandsToClient( client, &msg );
// 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_WriteBigString( &msg, sv.configstrings[start] );
}
}
// write the baselines
Com_Memset( &nullstate, 0, sizeof( nullstate ) );
for ( start = 0 ; start < MAX_GENTITIES; start++ ) {
base = &sv.svEntities[start].baseline;
if ( !base->number ) {
continue;
}
MSG_WriteByte( &msg, svc_baseline );
MSG_WriteDeltaEntity( &msg, &nullstate, base, qtrue );
}
MSG_WriteByte( &msg, svc_EOF );
MSG_WriteLong( &msg, client - svs.clients);
// write the checksum feed
MSG_WriteLong( &msg, sv.checksumFeed);
// deliver this to the client
SV_SendMessageToClient( &msg, client );
}
/*
==================
SV_ClientEnterWorld
==================
*/
void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd ) {
int clientNum;
sharedEntity_t *ent;
Com_DPrintf( "Going from CS_PRIMED to CS_ACTIVE for %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;
client->deltaMessage = -1;
client->nextSnapshotTime = svs.time; // generate a snapshot immediately
client->lastUsercmd = *cmd;
// call the game begin function
VM_Call( gvm, GAME_CLIENT_BEGIN, client - svs.clients );
}
/*
============================================================
CLIENT COMMAND EXECUTION
============================================================
*/
/*
==================
SV_CloseDownload
clear/free any download vars
==================
*/
static void SV_CloseDownload( client_t *cl ) {
int i;
// EOF
if (cl->download) {
FS_FCloseFile( cl->download );
}
cl->download = 0;
*cl->downloadName = 0;
// Free the temporary buffer space
for (i = 0; i < MAX_DOWNLOAD_WINDOW; i++) {
if (cl->downloadBlocks[i]) {
Z_Free( cl->downloadBlocks[i] );
cl->downloadBlocks[i] = NULL;
}
}
}
/*
==================
SV_StopDownload_f
Abort a download if in progress
==================
*/
void SV_StopDownload_f( client_t *cl ) {
if (*cl->downloadName)
Com_DPrintf( "clientDownload: %d : file \"%s\" aborted\n", cl - svs.clients, cl->downloadName );
SV_CloseDownload( cl );
}
/*
==================
SV_DoneDownload_f
Downloads are finished
==================
*/
void SV_DoneDownload_f( client_t *cl ) {
Com_DPrintf( "clientDownload: %s Done\n", cl->name);
// resend the game state to update any clients that entered during the download
SV_SendClientGameState(cl);
}
/*
==================
SV_NextDownload_f
The argument will be the last acknowledged block from the client, it should be
the same as cl->downloadClientBlock
==================
*/
void SV_NextDownload_f( client_t *cl )
{
int block = atoi( Cmd_Argv(1) );
if (block == cl->downloadClientBlock) {
Com_DPrintf( "clientDownload: %d : client acknowledge of block %d\n", cl - svs.clients, block );
// Find out if we are done. A zero-length block indicates EOF
if (cl->downloadBlockSize[cl->downloadClientBlock % MAX_DOWNLOAD_WINDOW] == 0) {
Com_Printf( "clientDownload: %d : file \"%s\" completed\n", cl - svs.clients, cl->downloadName );
SV_CloseDownload( cl );
return;
}
cl->downloadSendTime = svs.time;
cl->downloadClientBlock++;
return;
}
// We aren't getting an acknowledge for the correct block, drop the client
// FIXME: this is bad... the client will never parse the disconnect message
// because the cgame isn't loaded yet
SV_DropClient( cl, "broken download" );
}
/*
==================
SV_BeginDownload_f
==================
*/
void SV_BeginDownload_f( client_t *cl ) {
// Kill any existing download
SV_CloseDownload( cl );
// cl->downloadName is non-zero now, SV_WriteDownloadToClient will see this and open
// the file itself
Q_strncpyz( cl->downloadName, Cmd_Argv(1), sizeof(cl->downloadName) );
}
/*
==================
SV_WriteDownloadToClient
Check to see if the client wants a file, open it if needed and start pumping the client
Fill up msg with data
==================
*/
void SV_WriteDownloadToClient( client_t *cl , msg_t *msg )
{
int curindex;
int rate;
int blockspersnap;
int idPack, missionPack;
char errorMessage[1024];
if (!*cl->downloadName)
return; // Nothing being downloaded
if (!cl->download) {
// We open the file here
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -