/* Copyright (C) 1997-2001 Id Software, Inc. This program 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. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // sv_main.c -- server main program #include "server.h" // shared message buffer to be used for occasional messages msg_t tmpMessage; qbyte tmpMessageData[MAX_MSGLEN]; /* ============================================================================= Com_Printf redirection ============================================================================= */ char sv_outputbuf[SV_OUTPUTBUF_LENGTH]; void SV_FlushRedirect( int sv_redirected, char *outputbuf ) { if( sv_client->edict && (sv_client->edict->r.svflags & SVF_FAKECLIENT) ) return; if( sv_redirected == RD_PACKET ) { Netchan_OutOfBandPrint( NS_SERVER, net_from, "print\n%s", outputbuf ); } else if( sv_redirected == RD_CLIENT ) { SV_ClientPrintf( sv_client, outputbuf ); } } /* ============================================================================= 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; if( !client ) return; if( client->edict && (client->edict->r.svflags & SVF_FAKECLIENT) ) return; if( !cmd || !cmd[0] || !strlen(cmd) ) return; // 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, DROP_TYPE_GENERAL, "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 SV_SendServerCommand( client_t *cl, const char *fmt, ... ) { va_list argptr; char message[MAX_MSGLEN]; client_t *client; int j; va_start( argptr,fmt ); vsnprintf( message, sizeof(message), fmt, argptr ); va_end( argptr ); if ( cl != NULL ) { if( cl->state < CS_CONNECTED ) return; SV_AddServerCommand( cl, message ); return; } // hack to echo broadcast prints to console if( dedicated->integer ) { if( !strncmp( message, "pr", 2 ) ) Com_DPrintf( "broadcast: %s\n", SV_ExpandNewlines(message) ); } // send the data to all relevant clients for( j = 0, client = svs.clients; j < sv_maxclients->integer; j++, client++ ) { if( client->state < CS_SPAWNED ) { // jal continue; } SV_AddServerCommand( client, message ); } } /* ================== SV_AddReliableCommandsToMessage (re)send all server commands the client hasn't acknowledged yet ================== */ void SV_AddReliableCommandsToMessage( client_t *client, msg_t *msg ) { int i; if( client->edict && (client->edict->r.svflags & SVF_FAKECLIENT) ) return; if( sv_debug_serverCmd->integer ) Com_Printf( "sv_cl->reliableAcknowledge: %i sv_cl->reliableSequence:%i\n", client->reliableAcknowledge, client->reliableSequence ); // write any unacknowledged serverCommands for( i = client->reliableAcknowledge + 1 ; i <= client->reliableSequence ; i++ ) { if( !strlen( client->reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] ) ) continue; MSG_WriteByte( msg, svc_servercmd ); MSG_WriteLong( msg, i ); MSG_WriteString( msg, client->reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] ); if( sv_debug_serverCmd->integer ) Com_Printf( "SV_AddServerCommandsToMessage(%i):%s\n", i, client->reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] ); } client->reliableSent = client->reliableSequence; } #ifdef BATTLEYE void SV_AddBattleyeCommandToMessage( client_t *client, msg_t *msg ) { qbyte *bepacket = NULL; int bepacketlen = 0; if( !client || !client->battleye ) return; if( sv_battleye->integer ) { // the client is not acknowledging packets if( client->BE.acknowledgedPacket + BE_UPDATE_BACKUP <= client->BE.headPacket ) { SV_DropClient( client, DROP_TYPE_GENERAL, "Too many unacknowledged BattlEye packets" ); return; } if( msg ) { if( client->BE.lastSentTime > svs.realtime ) // clamp client->BE.lastSentTime = svs.realtime; if( client->BE.lastSentTime + BE_MIN_RESEND < svs.realtime ) { // send the next buffered unacknowledged packet (if any) if( client->BE.headPacket > client->BE.acknowledgedPacket ) { bepacketlen = client->BE.packetLens[client->BE.acknowledgedPacket & BE_UPDATE_MASK]; if( bepacketlen ) { bepacket = client->BE.packets[client->BE.acknowledgedPacket & BE_UPDATE_MASK]; } } } MSG_WriteByte( msg, svc_battleye ); //write clc_battleye acknowledge MSG_WriteLong( msg, client->BE.commandReceived ); if( bepacket ) { MSG_WriteShort( msg, bepacketlen ); MSG_Write( msg, bepacket, bepacketlen ); MSG_WriteLong( msg, client->BE.acknowledgedPacket+1 ); // id of this BE packet client->BE.lastSentTime = svs.realtime; } else { MSG_WriteShort( msg, 0 ); // no data, just acknowledge of clc_battleye } } } } #endif /* ============================================================================= EVENT MESSAGES ============================================================================= */ /* ================= SV_ClientChatf Sends text across to be displayed in chat window ================= */ void SV_ClientChatf( client_t *cl, char *fmt, ... ) { va_list argptr; char string[1024], *p; client_t *client; int i; va_start( argptr, fmt ); vsnprintf( string, sizeof(string), fmt, argptr ); va_end( argptr ); // double quotes are bad while( (p = strchr(string, '\"')) != NULL ) *p = '\''; if( cl != NULL ) { if( cl->state == CS_SPAWNED ) SV_SendServerCommand( cl, "ch \"%s\"", string ); return; } for( i = 0, client = svs.clients; i < sv_maxclients->integer; i++, client++ ) { if( client->state == CS_SPAWNED ) SV_SendServerCommand( client, "ch \"%s\"", string ); } // echo to console if( dedicated->integer ) { char copy[MAX_PRINTMSG]; int i; // mask off high bits and colored strings for( i = 0 ; i < sizeof(copy)-1 && string[i] ; i++ ) copy[i] = string[i]&127; copy[i] = 0; Com_Printf( "%s", copy ); } } /* ================= SV_ClientPrintf Sends text across to be displayed as print ================= */ void SV_ClientPrintf( client_t *cl, char *fmt, ... ) { va_list argptr; char string[1024], *p; client_t *client; int i; va_start( argptr, fmt ); vsnprintf( string, sizeof(string), fmt, argptr ); va_end( argptr ); // double quotes are bad while( (p = strchr(string, '\"')) != NULL ) *p = '\''; if( cl != NULL ) { SV_SendServerCommand( cl, "pr \"%s\"", string ); return; } for( i = 0, client = svs.clients; i < sv_maxclients->integer; i++, client++ ) { if( client->state < CS_SPAWNED ) continue; SV_SendServerCommand( client, "pr \"%s\"", string ); } // echo to console if( dedicated->integer ) { char copy[MAX_PRINTMSG]; int i; // mask off high bits and colored strings for( i = 0 ; i < sizeof(copy)-1 && string[i] ; i++ ) copy[i] = string[i]&127; copy[i] = 0; Com_Printf( "%s", copy ); } } /* ================= SV_BroadcastCommand Sends a command to all connected clients. Ignores client->state < CS_SPAWNED check ================= */ void SV_BroadcastCommand( char *fmt, ... ) { client_t *client; int i; va_list argptr; char string[1024]; if( !sv.state ) return; va_start( argptr, fmt ); vsnprintf( string, sizeof(string), fmt, argptr ); va_end( argptr ); for( i = 0, client = svs.clients; i < sv_maxclients->integer; i++, client++ ) { if( client->state < CS_CONNECTED ) continue; SV_SendServerCommand( client, string ); } } /* ================= SV_Multicast Sends the contents of sv.multicast to a subset of the clients, then clears sv.multicast. MULTICAST_ALL same as broadcast (origin can be NULL) MULTICAST_PVS send to clients potentially visible from org MULTICAST_PHS send to clients potentially hearable from org ================= */ void SV_MulticastMessage( qbyte *messageData, int length, vec3_t origin, multicast_t to ) { client_t *client; qbyte *mask; int leafnum, cluster; int j; int area1, area2; // check we have data to send before adding it if( !length || !messageData ) return; if( to != MULTICAST_ALL ) { assert( origin != NULL ); leafnum = CM_PointLeafnum( origin ); area1 = CM_LeafArea( leafnum ); } else { leafnum = 0; // just to avoid compiler warnings area1 = 0; } #ifdef SERVERSIDE_DEMOS // if doing a serverrecord, store everything if (svs.demofile) MSG_Write (&svs.demo_multicast, messageData, length); #endif switch( to ) { case MULTICAST_ALL: leafnum = 0; mask = NULL; break; case MULTICAST_PHS: leafnum = CM_PointLeafnum( origin ); cluster = CM_LeafCluster( leafnum ); mask = CM_ClusterPHS( cluster ); break; case MULTICAST_PVS: leafnum = CM_PointLeafnum( origin ); cluster = CM_LeafCluster( leafnum ); mask = CM_ClusterPVS( cluster ); break; default: { mask = NULL; // wsw : jal : no need to crash here Com_DPrintf( "ERROR: SV_MulticastMessage: bad to : %i", to ); return; } } // send the data to all relevant clients for( j = 0, client = svs.clients; j < sv_maxclients->integer; j++, client++ ) { if( client->state < CS_SPAWNED ) continue; if( client->edict && (client->edict->r.svflags & SVF_FAKECLIENT) ) continue; if(mask) { leafnum = CM_PointLeafnum( client->edict->s.origin ); cluster = CM_LeafCluster( leafnum ); if( mask && ( !(mask[cluster>>3] & (1<<(cluster&7)) ) ) ) continue; area2 = CM_LeafArea( leafnum ); if( !CM_AreasConnected(area1, area2) ) continue; } if( messageData[0] != svc_sound ) { Com_Error( ERR_DROP, "SV_MulticasMessage: Message was not a sound\n" ); } else { // wsw : jal : multicast only for sounds and is not for reliable MSG_Write( &client->soundsmsg, messageData, length ); } } } /* ================== SV_StartSound Each entity can have eight independant sound sources, like voice, weapon, feet, etc. If cahnnel & 8, the sound will be sent to everyone, not just things in the PHS. FIXME: if entity isn't in PHS, they must be forced to be sent or have the origin explicitly sent. Channel 0 is an auto-allocate channel, the others override anything already running on that entity/channel pair. An attenuation of 0 will play full volume everywhere in the level. Larger attenuations will drop off. (max 4 attenuation) Timeofs can range from 0.0 to 0.1 to cause sounds to be started later in the frame than they normally would. If origin is NULL, the origin is determined from the entity origin or the midpoint of the entity box for bmodels. ================== */ void SV_StartSound( vec3_t origin, edict_t *entity, int channel, int soundindex, float volume, float attenuation ) { int sendchan; int flags; int i, v; int ent; vec3_t origin_v; qboolean use_phs; msg_t message; qbyte messageData[MAX_MSGLEN]; MSG_Init( &message, messageData, sizeof(messageData) ); MSG_Clear( &message ); if( soundindex < 0 || soundindex >= MAX_SOUNDS ) Com_Error( ERR_FATAL, "SV_StartSound: soundindex = %i", soundindex ); if( volume < 0 || volume > 1.0 ) Com_Error( ERR_FATAL, "SV_StartSound: volume = %f", volume); if( attenuation < 0 || attenuation > 4 ) Com_Error( ERR_FATAL, "SV_StartSound: attenuation = %f", attenuation ); // if( channel < 0 || channel > 15 ) // Com_Error( ERR_FATAL, "SV_StartSound: channel = %i", channel ); ent = NUM_FOR_EDICT( entity ); if( channel & CHAN_NO_PHS_ADD ) // no PHS flag { use_phs = qfalse; channel &= 7; } else use_phs = qtrue; sendchan = (ent<<3) | (channel&7); flags = 0; if( volume != DEFAULT_SOUND_PACKET_VOLUME ) flags |= SND_VOLUME; if( attenuation != DEFAULT_SOUND_PACKET_ATTENUATION ) flags |= SND_ATTENUATION; // use the entity origin unless it is a bmodel or explicitly specified if( !origin ) { origin = origin_v; // the client doesn't know that bmodels have weird origins // the origin can also be explicitly set if( entity->r.solid == SOLID_BSP ) { for( i=0 ; i<3 ; i++ ) origin_v[i] = entity->s.origin[i]+0.5 * (entity->r.mins[i]+entity->r.maxs[i]); } else { VectorCopy( entity->s.origin, origin_v ); } } v = Q_rint( origin[0] ); flags |= ( SND_POS0_8|SND_POS0_16 ); if( v+256/2 >= 0 && v+256/2 < 256 ) flags &= ~SND_POS0_16; else if( v+65536/2 >= 0 && v+65536/2 < 65536 ) flags &= ~SND_POS0_8; v = Q_rint( origin[1] ); flags |= ( SND_POS1_8|SND_POS1_16 ); if( v+256/2 >= 0 && v+256/2 < 256 ) flags &= ~SND_POS1_16; else if( v+65536/2 >= 0 && v+65536/2 < 65536 ) flags &= ~SND_POS1_8; v = Q_rint( origin[2] ); flags |= ( SND_POS2_8|SND_POS2_16 ); if( v+256/2 >= 0 && v+256/2 < 256 ) flags &= ~SND_POS2_16; else if( v+65536/2 >= 0 && v+65536/2 < 65536 ) flags &= ~SND_POS2_8; MSG_WriteByte( &message, svc_sound ); MSG_WriteByte( &message, flags ); MSG_WriteByte( &message, soundindex ); // always send the entity number for channel overrides MSG_WriteShort( &message, sendchan ); if( (flags & (SND_POS0_8|SND_POS0_16)) == SND_POS0_8 ) MSG_WriteChar( &message, Q_rint(origin[0]) ); else if( (flags & (SND_POS0_8|SND_POS0_16)) == SND_POS0_16 ) MSG_WriteShort( &message, Q_rint(origin[0]) ); else MSG_WriteInt3( &message, Q_rint(origin[0]) ); if( (flags & (SND_POS1_8|SND_POS1_16)) == SND_POS1_8 ) MSG_WriteChar( &message, Q_rint(origin[1]) ); else if( (flags & (SND_POS1_8|SND_POS1_16)) == SND_POS1_16 ) MSG_WriteShort( &message, Q_rint(origin[1]) ); else MSG_WriteInt3( &message, Q_rint(origin[1]) ); if( (flags & (SND_POS2_8|SND_POS2_16)) == SND_POS2_8 ) MSG_WriteChar( &message, Q_rint(origin[2]) ); else if( (flags & (SND_POS2_8|SND_POS2_16)) == SND_POS2_16 ) MSG_WriteShort( &message, Q_rint(origin[2]) ); else MSG_WriteInt3( &message, Q_rint(origin[2]) ); if( flags & SND_VOLUME ) MSG_WriteByte( &message, volume*255 ); if( flags & SND_ATTENUATION ) MSG_WriteByte( &message, attenuation*64 ); // if the sound doesn't attenuate,send it to everyone // (global radio chatter, voiceovers, etc) if( attenuation == ATTN_NONE ) use_phs = qfalse; // wsw : jal : I never send sounds are reliable (at least by now) SV_MulticastMessage( messageData, message.cursize, origin, use_phs ? MULTICAST_PHS : MULTICAST_ALL ); /*if( channel & CHAN_RELIABLE ) { if( use_phs ) SV_Multicast( origin, MULTICAST_PHS_R ); else SV_Multicast( origin, MULTICAST_ALL_R ); } else { if( use_phs ) SV_Multicast( origin, MULTICAST_PHS ); else SV_Multicast( origin, MULTICAST_ALL ); }*/ } /* =============================================================================== FRAME UPDATES =============================================================================== */ /* ======================= SV_RateDrop Returns true if the client is over its current bandwidth estimation and should not be sent another packet ======================= */ qboolean SV_RateDrop( client_t *c ) { return qfalse; } /* ======================= SV_SendClientsFragments ======================= */ qboolean SV_SendClientsFragments( void ) { int i; client_t *client; qboolean remaining = qfalse; // send a message to each connected client for( i=0, client = svs.clients ; iinteger; i++, client++ ) { if( !client->state ) continue; if( client->edict && (client->edict->r.svflags & SVF_FAKECLIENT) ) continue; if( client->netchan.unsentFragments ) Netchan_TransmitNextFragment( &client->netchan ); if( client->netchan.unsentFragments ) remaining = qtrue; } return remaining; } //================== //SV_Netchan_Transmit //================== void SV_Netchan_Transmit( client_t *client, msg_t *msg ) { size_t length = 0; int zerror; // if we got here with unsent fragments, fire them all now Netchan_PushAllFragments( &client->netchan ); #ifdef BATTLEYE SV_AddBattleyeCommandToMessage( client, msg ); #endif zerror = Netchan_CompressMessage( msg ); if( zerror < 0 ) { // it's compression error, just send uncompressed Com_DPrintf( "SV_Netchan_Transmit (ignoring compression): Compression error %i\n", zerror ); } //jalfixme: not as precise as it could be length = msg->cursize + PACKET_HEADER; Netchan_Transmit( &client->netchan, msg ); client->lastPacketSentTime = svs.realtime; } /* ======================= SV_SendMessageToClient ======================= */ void SV_SendMessageToClient( client_t *client, msg_t *msg ) { static msg_t message; static qbyte messageData[MAX_MSGLEN]; if( !client ) return; if( client->edict && (client->edict->r.svflags & SVF_FAKECLIENT) ) return; MSG_Init( &message, messageData, sizeof(messageData) ); MSG_Clear( &message ); // write the last client-command we received so it's acknowledged MSG_WriteByte( &message, svc_clcack ); MSG_WriteLong( &message, (unsigned long)client->clientCommandExecuted ); // write the message data if( msg != NULL && msg->cursize ) { MSG_Write( &message, msg->data, msg->cursize ); } SV_Netchan_Transmit( client, &message ); } //======================= //SV_ResetClientFrameCounters // This is used for a temporary sanity check I'm doing. //======================= void SV_ResetClientFrameCounters( void ) { int i; client_t *client; for( i=0, client = svs.clients ; iinteger; i++, client++ ) { if( !client->state ) continue; if( client->edict && (client->edict->r.svflags & SVF_FAKECLIENT) ) continue; client->lastSentFrameNum = 0; } } /* ======================= SV_SendClientDatagram ======================= */ void SV_SendClientDatagram( client_t *client ) { qbyte msg_buf[MAX_MSGLEN]; msg_t msg; if( client->edict && (client->edict->r.svflags & SVF_FAKECLIENT) ) return; // temp warning for me if( client->lastSentFrameNum >= sv.framenum ) { Com_Printf( "WARNING: Attempting to send a frame snap twice (tell this to jal: lastsent:%i current:%i)\n", client->lastSentFrameNum, sv.framenum ); //return; } MSG_Init( &msg, msg_buf, sizeof(msg_buf) ); MSG_Clear( &msg ); msg.allowoverflow = qtrue; SV_AddReliableCommandsToMessage( client, &msg ); // send over all the relevant entity_state_t // and the player_state_t SV_BuildClientFrameSnap( client ); SV_WriteFrameSnapToClient( client, &msg ); SV_SendMessageToClient( client, &msg ); return; } /* ======================= SV_SendClientMessages ======================= */ void SV_SendClientMessages( void ) { int i; client_t *client; // send a message to each connected client for( i=0, client = svs.clients ; iinteger; i++, client++ ) { if( !client->state ) continue; if( client->edict && (client->edict->r.svflags & SVF_FAKECLIENT) ) continue; if( client->state == CS_SPAWNED ) { SV_SendClientDatagram( client ); } else { // send pending reliable commands, or send heartbeats for not timing out if( client->reliableSequence > client->reliableAcknowledge || svs.realtime - client->lastPacketSentTime > 1000 ) { MSG_Clear( &tmpMessage ); SV_AddReliableCommandsToMessage( client, &tmpMessage ); SV_SendMessageToClient( client, &tmpMessage ); } } } }