//======================================================================= // Copyright XashXT Group 2008 © // sv_frame.c - server world snapshot //======================================================================= #include "common.h" #include "server.h" #include "const.h" #include "net_encode.h" #include "entity_types.h" #define MAX_VISIBLE_PACKET 512 typedef struct { int num_entities; entity_state_t entities[MAX_VISIBLE_PACKET]; } sv_ents_t; static byte *clientpvs; // FatPVS static byte *clientphs; // FatPHS int c_fullsend; // just a debug counter /* ======================= SV_EntityNumbers ======================= */ static int SV_EntityNumbers( const void *a, const void *b ) { int ent1, ent2; ent1 = ((entity_state_t *)a)->number; ent2 = ((entity_state_t *)b)->number; if( ent1 == ent2 ) Host_Error( "SV_SortEntities: duplicated entity\n" ); if( ent1 < ent2 ) return -1; return 1; } /* ======================= SV_ClearPacketEntities ======================= */ void SV_ClearPacketEntities( client_frame_t *frame ) { packet_entities_t *packet; ASSERT( frame != NULL ); packet = &frame->entities; if( packet ) { if( packet->entities != NULL ) Mem_Free( packet->entities ); packet->num_entities = 0; packet->max_entities = 0; packet->entities = NULL; } } /* ======================= SV_AllocPacketEntities ======================= */ void SV_AllocPacketEntities( client_frame_t *frame, int count ) { packet_entities_t *packet; ASSERT( frame != NULL ); packet = &frame->entities; if( count < 1 ) count = 1; // check if new frame has more entities than previous if( packet->max_entities < count ) { SV_ClearPacketEntities( frame ); packet->entities = Mem_Alloc( svgame.mempool, sizeof( entity_state_t ) * count ); packet->max_entities = count; } packet->num_entities = count; } /* ================ SV_ClearFrames free client frames memory ================ */ void SV_ClearFrames( client_frame_t **frames ) { client_frame_t *frame; int i; if( *frames == NULL ) return; for( i = 0, frame = *frames; i < SV_UPDATE_BACKUP; i++, frame++ ) { SV_ClearPacketEntities( frame ); } Mem_Free( *frames ); *frames = NULL; } /* ============= SV_AddEntitiesToPacket ============= */ static void SV_AddEntitiesToPacket( edict_t *pViewEnt, edict_t *pClient, client_frame_t *frame, sv_ents_t *ents ) { edict_t *ent; byte *pset; bool fullvis = false; sv_client_t *cl, *netclient; entity_state_t *state; int e, player; // 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; if( pClient && !( sv.hostflags & SVF_PORTALPASS )) { // portals can't change hostflags sv.hostflags &= ~SVF_SKIPLOCALHOST; cl = SV_ClientFromEdict( pClient, true ); ASSERT( cl ); // setup hostflags if( cl->local_weapons ) { sv.hostflags |= SVF_SKIPLOCALHOST; } } svgame.dllFuncs.pfnSetupVisibility( pViewEnt, pClient, &clientpvs, &clientphs ); if( !clientpvs ) fullvis = true; for( e = 1; e < svgame.numEntities; e++ ) { ent = EDICT_NUM( e ); if( ent->free ) continue; if( ent->serialnumber != e ) { // this should never happens MsgDev( D_NOTE, "fixing ent->serialnumber from %i to %i\n", ent->serialnumber, e ); ent->serialnumber = e; } // don't double add an entity through portals (already added) if( ent->framenum == sv.net_framenum ) continue; if( ent->v.flags & FL_CHECK_PHS ) pset = clientphs; else pset = clientpvs; state = &ents->entities[ents->num_entities]; netclient = SV_ClientFromEdict( ent, true ); player = ( netclient != NULL ); // add entity to the net packet if( svgame.dllFuncs.pfnAddToFullPack( state, e, ent, pClient, sv.hostflags, player, pset )) { // to prevent adds it twice through portals ent->framenum = sv.net_framenum; if( netclient && netclient->modelindex ) // apply custom model if present state->modelindex = netclient->modelindex; // if we are full, silently discard entities if( ents->num_entities < MAX_VISIBLE_PACKET ) { ents->num_entities++; // entity accepted c_fullsend++; // debug counter } else { // visibility list is full MsgDev( D_ERROR, "too many entities in visible packet list\n" ); break; } } if( fullvis ) continue; // portal ents will be added anyway, ignore recursion // if its a portal entity, add everything visible from its camera position if( !( sv.hostflags & SVF_PORTALPASS ) && ent->v.effects & EF_MERGE_VISIBILITY ) { sv.hostflags |= SVF_PORTALPASS; SV_AddEntitiesToPacket( ent, pClient, frame, ents ); sv.hostflags &= ~SVF_PORTALPASS; } } } /* ============================================================================= Encode a client frame onto the network channel ============================================================================= */ /* ============= SV_EmitPacketEntities Writes a delta update of an entity_state_t list to the message-> ============= */ void SV_EmitPacketEntities( sv_client_t *cl, packet_entities_t *to, sizebuf_t *msg ) { packet_entities_t *from; client_frame_t *oldframe; int oldindex, newindex; int oldnum, newnum; int oldmax; // this is the frame that we are going to delta update from if( cl->delta_sequence != -1 ) { oldframe = &cl->frames[cl->delta_sequence & SV_UPDATE_MASK]; from = &oldframe->entities; oldmax = from->num_entities; BF_WriteByte( msg, svc_deltapacketentities ); BF_WriteWord( msg, to->num_entities ); BF_WriteByte( msg, cl->delta_sequence ); } else { oldmax = 0; // no delta update from = NULL; BF_WriteByte( msg, svc_packetentities ); BF_WriteWord( msg, to->num_entities ); } newindex = 0; oldindex = 0; while( newindex < to->num_entities || oldindex < oldmax ) { newnum = newindex >= to->num_entities ? MAX_ENTNUMBER : to->entities[newindex].number; oldnum = oldindex >= oldmax ? MAX_ENTNUMBER : from->entities[oldindex].number; if( newnum == oldnum ) { // delta update from old position // because the force parm is false, this will not result // in any bytes being emited if the entity has not changed at all MSG_WriteDeltaEntity( &from->entities[oldindex], &to->entities[newindex], msg, false, sv_time( )); oldindex++; newindex++; continue; } if( newnum < oldnum ) { // this is a new entity, send it from the baseline MSG_WriteDeltaEntity( &svs.baselines[newnum], &to->entities[newindex], msg, true, sv_time( )); newindex++; continue; } if( newnum > oldnum ) { bool force; if( EDICT_NUM( from->entities[oldindex].number )->free ) force = true; // entity completely removed from server else force = false; // just removed from delta-message // remove from message MSG_WriteDeltaEntity( &from->entities[oldindex], NULL, msg, force, sv_time( )); oldindex++; continue; } } BF_WriteWord( msg, 0 ); // end of packetentities } /* ============= SV_EmitEvents ============= */ static void SV_EmitEvents( sv_client_t *cl, packet_entities_t *to, sizebuf_t *msg ) { int i, ev; event_state_t *es; event_info_t *info; int ev_count = 0; int c; es = &cl->events; // count events for( ev = 0; ev < MAX_EVENT_QUEUE; ev++ ) { info = &es->ei[ev]; if( info->index == 0 ) continue; ev_count++; } // nothing to send if( !ev_count ) return; if( ev_count >= MAX_EVENT_QUEUE ) ev_count = MAX_EVENT_QUEUE - 1; BF_WriteByte( msg, svc_event ); // create message BF_WriteByte( msg, ev_count ); // Up to MAX_EVENT_QUEUE events for( i = c = 0 ; i < MAX_EVENT_QUEUE; i++ ) { info = &es->ei[i]; if( info->index == 0 ) continue; // only send if there's room if ( c < ev_count ) SV_PlaybackEvent( msg, info ); info->index = 0; c++; } } /* ============= SV_EmitPings ============= */ void SV_EmitPings( sizebuf_t *msg ) { sv_client_t *cl; int packet_loss; int i, ping; BF_WriteByte( msg, svc_updatepings ); for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if( cl->state != cs_spawned ) continue; SV_GetPlayerStats( cl, &ping, &packet_loss ); // there are 25 bits for each client BF_WriteOneBit( msg, 1 ); BF_WriteUBitLong( msg, i, MAX_CLIENT_BITS ); BF_WriteUBitLong( msg, ping, 12 ); BF_WriteUBitLong( msg, packet_loss, 7 ); } // end marker BF_WriteOneBit( msg, 0 ); } /* ================== SV_WriteClientdataToMessage ================== */ void SV_WriteClientdataToMessage( sv_client_t *cl, sizebuf_t *msg ) { clientdata_t nullcd; clientdata_t *from_cd, *to_cd; weapon_data_t nullwd; weapon_data_t *from_wd, *to_wd; client_frame_t *frame; edict_t *clent; int i; Mem_Set( &nullcd, 0, sizeof( nullcd )); clent = cl->edict; frame = &( cl->frames[cl->netchan.outgoing_sequence & SV_UPDATE_MASK] ); frame->senttime = host.realtime; frame->raw_ping = -1.0f; frame->latency = -1.0f; if( cl->chokecount != 0 ) { BF_WriteByte( msg, svc_chokecount ); cl->chokecount = 0; } // update client fixangle switch( clent->v.fixangle ) { case 1: BF_WriteByte( msg, svc_setangle ); BF_WriteBitAngle( msg, clent->v.angles[0], 16 ); BF_WriteBitAngle( msg, clent->v.angles[1], 16 ); clent->v.effects |= EF_NOINTERP; break; case 2: BF_WriteByte( msg, svc_addangle ); BF_WriteBitAngle( msg, cl->addangle, 16 ); cl->addangle = 0; break; } clent->v.fixangle = 0; // reset fixangle Mem_Set( &frame->clientdata, 0, sizeof( frame->clientdata )); // update clientdata_t svgame.dllFuncs.pfnUpdateClientData( clent, cl->local_weapons, &frame->clientdata ); BF_WriteByte( msg, svc_clientdata ); if( cl->hltv_proxy ) return; // don't send more nothing if( cl->delta_sequence == -1 ) from_cd = &nullcd; else from_cd = &cl->frames[cl->delta_sequence & SV_UPDATE_MASK].clientdata; to_cd = &frame->clientdata; if( cl->delta_sequence == -1 ) { BF_WriteOneBit( msg, 0 ); // no delta-compression } else { BF_WriteOneBit( msg, 1 ); // we are delta-ing from BF_WriteByte( msg, cl->delta_sequence ); } // write clientdata_t MSG_WriteClientData( msg, from_cd, to_cd, sv.time ); if( cl->local_weapons && svgame.dllFuncs.pfnGetWeaponData( clent, frame->weapondata )) { Mem_Set( &nullwd, 0, sizeof( nullwd )); for( i = 0; i < 32; i++ ) { if( cl->delta_sequence == -1 ) from_wd = &nullwd; else from_wd = &cl->frames[cl->delta_sequence & SV_UPDATE_MASK].weapondata[i]; to_wd = &frame->weapondata[i]; MSG_WriteWeaponData( msg, from_wd, to_wd, sv.time, i ); } } // end marker BF_WriteOneBit( msg, 0 ); } /* ================== SV_WriteEntitiesToClient ================== */ void SV_WriteEntitiesToClient( sv_client_t *cl, sizebuf_t *msg ) { edict_t *clent; edict_t *viewent; // may be NULL client_frame_t *frame; packet_entities_t *packet; static sv_ents_t frame_ents; bool send_pings; clent = cl->edict; viewent = cl->pViewEntity; // himself or trigger_camera frame = &cl->frames[cl->netchan.outgoing_sequence & SV_UPDATE_MASK]; packet = &frame->entities; send_pings = SV_ShouldUpdatePing( cl ); sv.net_framenum++; // now all portal-through entities are invalidate sv.hostflags &= ~SVF_PORTALPASS; // clear everything in this snapshot frame_ents.num_entities = c_fullsend = 0; // add all the entities directly visible to the eye, which // may include portal entities that merge other viewpoints SV_AddEntitiesToPacket( viewent, clent, frame, &frame_ents ); // 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( frame_ents.entities, frame_ents.num_entities, sizeof( frame_ents.entities[0] ), SV_EntityNumbers ); // copy the entity states to client frame SV_AllocPacketEntities( frame, frame_ents.num_entities ); Mem_Copy( packet->entities, frame_ents.entities, sizeof( entity_state_t ) * frame_ents.num_entities ); SV_EmitPacketEntities( cl, packet, msg ); SV_EmitEvents( cl, packet, msg ); if( send_pings ) SV_EmitPings( msg ); } /* =============================================================================== FRAME UPDATES =============================================================================== */ /* ======================= SV_SendClientDatagram ======================= */ void SV_SendClientDatagram( sv_client_t *cl ) { byte msg_buf[NET_MAX_PAYLOAD]; sizebuf_t msg; svs.currentPlayer = cl; BF_Init( &msg, "Datagram", msg_buf, sizeof( msg_buf )); // always send servertime at new frame BF_WriteByte( &msg, svc_time ); BF_WriteFloat( &msg, sv.time ); SV_WriteClientdataToMessage( cl, &msg ); SV_WriteEntitiesToClient( cl, &msg ); // copy the accumulated multicast datagram // for this client out to the message if( BF_CheckOverflow( &cl->datagram )) MsgDev( D_WARN, "datagram overflowed for %s\n", cl->name ); else BF_WriteBits( &msg, BF_GetData( &cl->datagram ), BF_GetNumBitsWritten( &cl->datagram )); BF_Clear( &cl->datagram ); if( BF_CheckOverflow( &msg )) { // must have room left for the packet header MsgDev( D_WARN, "msg overflowed for %s\n", cl->name ); BF_Clear( &msg ); } // send the datagram Netchan_TransmitBits( &cl->netchan, BF_GetNumBitsWritten( &msg ), BF_GetData( &msg )); } /* ======================= SV_UpdateToReliableMessages ======================= */ void SV_UpdateToReliableMessages( void ) { int i; sv_client_t *cl; // check for changes to be sent over the reliable streams to all clients for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if( !cl->edict ) continue; // not in game yet if( cl->state != cs_spawned ) continue; if( cl->sendinfo ) { cl->sendinfo = false; SV_FullClientUpdate( cl, &sv.reliable_datagram ); } if( cl->sendmovevars ) { cl->sendmovevars = false; SV_FullUpdateMovevars( cl, &cl->netchan.message ); } } // clear the server datagram if it overflowed. if( BF_CheckOverflow( &sv.datagram )) { MsgDev( D_ERROR, "sv.datagram overflowed!\n" ); BF_Clear( &sv.datagram ); } // now send the reliable and server datagrams to all clients. for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if( cl->state < cs_connected || cl->fakeclient ) continue; // reliables go to all connected or spawned BF_WriteBits( &cl->netchan.message, BF_GetData( &sv.reliable_datagram ), BF_GetNumBitsWritten( &sv.reliable_datagram )); BF_WriteBits( &cl->datagram, BF_GetData( &sv.datagram ), BF_GetNumBitsWritten( &sv.datagram )); } // now clear the reliable and datagram buffers. BF_Clear( &sv.reliable_datagram ); BF_Clear( &sv.datagram ); } /* ======================= SV_SendClientMessages ======================= */ void SV_SendClientMessages( void ) { sv_client_t *cl; int i; svs.currentPlayer = NULL; if( sv.state == ss_dead ) return; SV_UpdateToReliableMessages (); // send a message to each connected client for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if( !cl->state || cl->fakeclient ) continue; if( cl->skip_message ) { cl->skip_message = false; continue; } if( !host_limitlocal->integer && NET_IsLocalAddress( cl->netchan.remote_address )) { cl->send_message = true; } if( cl->state == cs_spawned ) { // Try to send a message as soon as we can. // If the target time for sending is within the next frame interval ( based on last frame ), // trigger the send now. Note that in single player, // send_message is also set to true any time a packet arrives from the client. float time_unti_next_message = cl->next_messagetime - (host.realtime + host.frametime); if( time_unti_next_message <= 0.0f ) { cl->send_message = true; } // something got hosed if( time_unti_next_message > 2.0f ) { cl->send_message = true; } } // if the reliable message overflowed, drop the client if( BF_CheckOverflow( &cl->netchan.message )) { BF_Clear( &cl->netchan.message ); BF_Clear( &cl->datagram ); SV_BroadcastPrintf( PRINT_HIGH, "%s overflowed\n", cl->name ); MsgDev( D_WARN, "reliable overflow for %s\n", cl->name ); SV_DropClient( cl ); cl->send_message = true; cl->netchan.cleartime = 0; // don't choke this message } else if( cl->send_message ) { // If we haven't gotten a message in sv_failuretime seconds, then stop sending messages to this client // until we get another packet in from the client. This prevents crash/drop and reconnect where they are // being hosed with "sequenced packet without connection" packets. if(( host.realtime - cl->netchan.last_received ) > sv_failuretime->value ) { cl->send_message = false; } } // only send messages if the client has sent one // and the bandwidth is not choked if( !cl->send_message ) continue; // Bandwidth choke active? if( !Netchan_CanPacket( &cl->netchan )) { cl->chokecount++; continue; } cl->send_message = false; // Now that we were able to send, reset timer to point to next possible send time. cl->next_messagetime = host.realtime + host.frametime + cl->cl_updaterate; if( cl->state == cs_spawned ) { SV_SendClientDatagram( cl ); } else { Netchan_Transmit( &cl->netchan, 0, NULL ); } } // reset current client svs.currentPlayer = NULL; } /* ======================= SV_SendMessagesToAll e.g. before changing level ======================= */ void SV_SendMessagesToAll( void ) { int i; sv_client_t *cl; for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if( cl->state >= cs_connected ) cl->send_message = true; } SV_SendClientMessages(); } /* ======================= SV_InactivateClients Purpose: Prepare for level transition, etc. ======================= */ void SV_InactivateClients( void ) { int i; sv_client_t *cl; if( sv.state == ss_dead ) return; // send a message to each connected client for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if( !cl->state || !cl->edict ) continue; if( !cl->edict || (cl->edict->v.flags & ( FL_FAKECLIENT|FL_SPECTATOR ))) continue; if( svs.clients[i].state > cs_connected ) svs.clients[i].state = cs_connected; // clear netchan message (but keep other buffers) BF_Clear( &cl->netchan.message ); } }