/* 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. */ #include "server.h" /* ============================================================================= 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( client_frame_t *from, client_frame_t *to, msg_t *msg ) { entity_state_t *oldent, *newent; int oldindex, newindex; int oldnum, newnum; int from_num_entities; int bits; MSG_WriteByte( msg, svc_packetentities ); if( !from ) from_num_entities = 0; else from_num_entities = from->num_entities; newindex = 0; oldindex = 0; while( newindex < to->num_entities || oldindex < from_num_entities ) { if( newindex >= to->num_entities ) { newent = NULL; newnum = 9999; } else { newent = &svs.client_entities[(to->first_entity+newindex)%svs.num_client_entities]; newnum = newent->number; } if( oldindex >= from_num_entities ) { oldent = NULL; oldnum = 9999; } else { oldent = &svs.client_entities[(from->first_entity+oldindex)%svs.num_client_entities]; oldnum = oldent->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 // note that players are always 'newentities', this updates their oldorigin always // and prevents warping MSG_WriteDeltaEntity ( oldent, newent, msg, qfalse, newent->number <= sv_maxclients->integer || ( (EDICT_NUM(newent->number))->r.svflags & SVF_FORCEOLDORIGIN ) ); oldindex++; newindex++; continue; } if( newnum < oldnum ) { // this is a new entity, send it from the baseline MSG_WriteDeltaEntity (&sv.baselines[newnum], newent, msg, qtrue, !((EDICT_NUM(newent->number))->r.svflags & SVF_NOOLDORIGIN)); newindex++; continue; } if( newnum > oldnum ) { // the old entity isn't present in the new message bits = U_REMOVE; if( oldnum >= 256 ) bits |= U_NUMBER16 | U_MOREBITS1; MSG_WriteByte( msg, bits&255 ); if (bits & 0x0000ff00) MSG_WriteByte( msg, (bits>>8)&255 ); if (bits & U_NUMBER16) MSG_WriteShort( msg, oldnum ); else MSG_WriteByte( msg, oldnum ); oldindex++; continue; } } MSG_WriteShort( msg, 0 ); // end of packetentities } /* ============= SV_WriteDeltaMatchStateToClient ============= */ void SV_WriteDeltaMatchStateToClient( client_frame_t *from, client_frame_t *to, msg_t *msg ) { match_state_t *ms, *oms; match_state_t dummy; int bitmask; ms = &to->matchstate; if( !from ) { memset( &dummy, 0, sizeof(dummy) ); oms = &dummy; } else oms = &from->matchstate; bitmask = 0; if( oms->state != ms->state ) bitmask |= MATCHSTATE_FLAG_STATE; if( oms->timelimit != ms->timelimit || oms->extendedtime != ms->extendedtime ) bitmask |= MATCHSTATE_FLAG_TIMELIMIT; if( oms->clock_msecs != ms->clock_msecs ) bitmask |= MATCHSTATE_FLAG_CLOCK_MSECS; if( oms->clock_secs != ms->clock_secs ) bitmask |= MATCHSTATE_FLAG_CLOCK_SECS; if( oms->clock_mins != ms->clock_mins ) bitmask |= MATCHSTATE_FLAG_CLOCK_MINUTES; // write it MSG_WriteByte( msg, svc_match ); MSG_WriteByte( msg, bitmask ); if( bitmask & MATCHSTATE_FLAG_STATE ) MSG_WriteByte( msg, (qbyte)ms->state ); if( bitmask & MATCHSTATE_FLAG_TIMELIMIT ) { int timelimitmask = ms->timelimit; if( ms->extendedtime ) timelimitmask |= MATCHSTATE_EXTENDEDTIME_BIT; MSG_WriteLong( msg, timelimitmask ); } if( bitmask & MATCHSTATE_FLAG_CLOCK_MSECS ) MSG_WriteByte( msg, (qbyte)(ms->clock_msecs * 0.1) ); if( bitmask & MATCHSTATE_FLAG_CLOCK_SECS ) MSG_WriteByte( msg, ms->clock_secs ); if( bitmask & MATCHSTATE_FLAG_CLOCK_MINUTES ) MSG_WriteShort( msg, ms->clock_mins ); } /* ============= SV_WritePlayerstateToClient ============= */ void SV_WritePlayerstateToClient( client_frame_t *from, client_frame_t *to, msg_t *msg ) { int i; int pflags; player_state_t *ps, *ops; player_state_t dummy; int statbits; ps = &to->ps; if( !from ) { memset( &dummy, 0, sizeof(dummy) ); ops = &dummy; } else ops = &from->ps; // // determine what needs to be sent // pflags = 0; if( ps->pmove.pm_type != ops->pmove.pm_type ) pflags |= PS_M_TYPE; if( ps->pmove.origin[0] != ops->pmove.origin[0] ) pflags |= PS_M_ORIGIN0; if( ps->pmove.origin[1] != ops->pmove.origin[1] ) pflags |= PS_M_ORIGIN1; if( ps->pmove.origin[2] != ops->pmove.origin[2] ) pflags |= PS_M_ORIGIN2; if( ps->pmove.velocity[0] != ops->pmove.velocity[0] ) pflags |= PS_M_VELOCITY0; if( ps->pmove.velocity[1] != ops->pmove.velocity[1] ) pflags |= PS_M_VELOCITY1; if( ps->pmove.velocity[2] != ops->pmove.velocity[2] ) pflags |= PS_M_VELOCITY2; if( ps->pmove.pm_time != ops->pmove.pm_time ) pflags |= PS_M_TIME; if( ps->pmove.pm_flags != ops->pmove.pm_flags ) pflags |= PS_M_FLAGS; if( ps->pmove.delta_angles[0] != ops->pmove.delta_angles[0] ) pflags |= PS_M_DELTA_ANGLES0; if( ps->pmove.delta_angles[1] != ops->pmove.delta_angles[1] ) pflags |= PS_M_DELTA_ANGLES1; if( ps->pmove.delta_angles[2] != ops->pmove.delta_angles[2] ) pflags |= PS_M_DELTA_ANGLES2; if( ps->event != ops->event ) pflags |= PS_EVENT; if( ps->viewangles[0] != ops->viewangles[0] || ps->viewangles[1] != ops->viewangles[1] || ps->viewangles[2] != ops->viewangles[2] ) pflags |= PS_VIEWANGLES; if( ps->pmove.gravity != ops->pmove.gravity ) pflags |= PS_M_GRAVITY; if( ps->fov != ops->fov ) pflags |= PS_FOV; if( ps->POVnum != ops->POVnum ) pflags |= PS_POVNUM; if( ps->viewheight != ops->viewheight ) pflags |= PS_VIEWHEIGHT; for ( i = 0 ; i < PM_STAT_SIZE ; i++ ) if( ps->pmove.stats[i] != ops->pmove.stats[i] ) pflags |= PS_PMOVESTATS; for( i = 0; i < MAX_WEAPLIST_STATS; i++ ) { if( ps->weaponlist[i][0] != ops->weaponlist[i][0] || ps->weaponlist[i][1] != ops->weaponlist[i][1] || ps->weaponlist[i][2] != ops->weaponlist[i][2] ) { pflags |= PS_WEAPONLIST; break; } } // // write it // MSG_WriteByte( msg, svc_playerinfo ); if( pflags & 0xff000000 ) pflags |= PS_MOREBITS3 | PS_MOREBITS2 | PS_MOREBITS1; else if( pflags & 0x00ff0000 ) pflags |= PS_MOREBITS2 | PS_MOREBITS1; else if( pflags & 0x0000ff00 ) pflags |= PS_MOREBITS1; MSG_WriteByte( msg, pflags&255 ); if( pflags & 0xff000000 ) { MSG_WriteByte( msg, (pflags>>8 )&255 ); MSG_WriteByte( msg, (pflags>>16)&255 ); MSG_WriteByte( msg, (pflags>>24)&255 ); } else if( pflags & 0x00ff0000 ) { MSG_WriteByte( msg, (pflags>>8 )&255 ); MSG_WriteByte( msg, (pflags>>16)&255 ); } else if( pflags & 0x0000ff00 ) { MSG_WriteByte( msg, (pflags>>8 )&255 ); } // // write the pmove_state_t // if( pflags & PS_M_TYPE ) MSG_WriteByte( msg, ps->pmove.pm_type ); if( pflags & PS_M_ORIGIN0 ) MSG_WriteInt3( msg, ps->pmove.origin[0] ); if( pflags & PS_M_ORIGIN1 ) MSG_WriteInt3( msg, ps->pmove.origin[1] ); if( pflags & PS_M_ORIGIN2 ) MSG_WriteInt3( msg, ps->pmove.origin[2] ); if( pflags & PS_M_VELOCITY0 ) MSG_WriteInt3( msg, ps->pmove.velocity[0] ); if( pflags & PS_M_VELOCITY1 ) MSG_WriteInt3( msg, ps->pmove.velocity[1] ); if( pflags & PS_M_VELOCITY2 ) MSG_WriteInt3( msg, ps->pmove.velocity[2] ); if( pflags & PS_M_TIME ) MSG_WriteByte( msg, ps->pmove.pm_time ); if( pflags & PS_M_FLAGS ) MSG_WriteShort( msg, ps->pmove.pm_flags ); if( pflags & PS_M_DELTA_ANGLES0 ) MSG_WriteShort( msg, ps->pmove.delta_angles[0] ); if( pflags & PS_M_DELTA_ANGLES1 ) MSG_WriteShort( msg, ps->pmove.delta_angles[1] ); if( pflags & PS_M_DELTA_ANGLES2 ) MSG_WriteShort( msg, ps->pmove.delta_angles[2] ); if( pflags & PS_EVENT ) MSG_WriteShort( msg, (int)ps->event ); if( pflags & PS_VIEWANGLES ) { MSG_WriteAngle16( msg, ps->viewangles[0] ); MSG_WriteAngle16( msg, ps->viewangles[1] ); MSG_WriteAngle16( msg, ps->viewangles[2] ); } if( pflags & PS_M_GRAVITY ) MSG_WriteShort( msg, ps->pmove.gravity ); if( pflags & PS_FOV ) MSG_WriteByte( msg, (qbyte)ps->fov ); if( pflags & PS_POVNUM ) MSG_WriteByte( msg, (qbyte)ps->POVnum ); if( pflags & PS_VIEWHEIGHT ) MSG_WriteChar( msg, (char)ps->viewheight ); if( pflags & PS_PMOVESTATS ) { for( i = 0 ; i < PM_STAT_SIZE ; i++ ) { MSG_WriteShort( msg, ps->pmove.stats[i] ); } } if( pflags & PS_WEAPONLIST ) { // send weaponlist stats statbits = 0; for( i = 0; i < MAX_WEAPLIST_STATS; i++ ) { if( ps->weaponlist[i][0] != ops->weaponlist[i][0] || ps->weaponlist[i][1] != ops->weaponlist[i][1] || ps->weaponlist[i][2] != ops->weaponlist[i][2] ) statbits |= (1<weaponlist[i][0] ); MSG_WriteByte( msg, (qbyte)ps->weaponlist[i][1] ); MSG_WriteByte( msg, (qbyte)ps->weaponlist[i][2] ); } } } // send stats statbits = 0; for( i = 0; i < PS_MAX_STATS; i++ ) { if( ps->stats[i] != ops->stats[i] ) statbits |= 1<stats[i] ); } } /* ======================= SV_WriteClientSoundsDatagram Write the accumulated sound commands buffer into the snap message Note: Any command could go inside this message buffer, but I want it restricted to sound commands, so, please, keep it like that. Note2: The sounds datagram is not delta-compressed to older frame. ======================= */ void SV_WriteClientSoundsDatagram( client_t *client, msg_t *msg, msg_t *soundsMsg ) { if( !msg || !soundsMsg || !msg->data || !soundsMsg->data ) return; if( !soundsMsg->cursize ) return; if( soundsMsg->overflowed ) { Com_Printf( "WARNING: dropping sounds datagram for %s. SoundsMsg overflowed\n", client->name ); MSG_Clear( soundsMsg ); return; } // packet is already full if( msg->overflowed || msg->cursize >= msg->maxsize ) { Com_Printf( "WARNING: dropping sounds datagram for %s. Msg overflowed\n", client->name ); MSG_Clear( soundsMsg ); return; } // see if there's room for it if( (msg->maxsize - msg->cursize) < (soundsMsg->cursize + PACKET_HEADER) ) { Com_Printf( "WARNING: dropping sounds datagram for %s. Not enough space in msg\n", client->name ); MSG_Clear( soundsMsg ); return; } // write it MSG_Write( msg, soundsMsg->data, soundsMsg->cursize ); MSG_Clear( soundsMsg ); } /* ================== SV_WriteFrameSnapToClient ================== */ void SV_WriteFrameSnapToClient( client_t *client, msg_t *msg ) { client_frame_t *frame, *oldframe; int lastframe; // this is the frame we are creating frame = &client->frames[sv.framenum & UPDATE_MASK]; if( client->lastframe <= 0 || client->lastframe > (int)sv.framenum ) { // client is asking for a not compressed retransmit oldframe = NULL; lastframe = -1; } else if( sv.framenum - client->lastframe >= (UPDATE_BACKUP - 3) ) { // client hasn't gotten a good message through in a long time oldframe = NULL; lastframe = -1; } else { // we have a valid message to delta from oldframe = &client->frames[client->lastframe & UPDATE_MASK]; lastframe = client->lastframe; } MSG_WriteByte( msg, svc_frame ); MSG_WriteLong( msg, svs.realtime ); // server timeStamp MSG_WriteLong( msg, sv.framenum ); MSG_WriteLong( msg, lastframe ); // what we are delta'ing from #ifdef RATEKILLED MSG_WriteByte( msg, 0 ); #else MSG_WriteByte( msg, client->suppressCount ); // rate dropped packets client->suppressCount = 0; #endif // send over the areabits MSG_WriteByte( msg, frame->areabytes ); MSG_Write( msg, frame->areabits, frame->areabytes ); SV_WriteDeltaMatchStateToClient( oldframe, frame, msg ); // delta encode the playerstate SV_WritePlayerstateToClient( oldframe, frame, msg ); // delta encode the entities SV_EmitPacketEntities( oldframe, frame, msg ); // add the sound commands generated this frame SV_WriteClientSoundsDatagram( client, msg, &client->soundsmsg ); client->lastSentFrameNum = sv.framenum; } /* ============================================================================= Build a client frame structure ============================================================================= */ qbyte fatpvs[MAX_MAP_LEAFS/8]; qbyte fatphs[MAX_MAP_LEAFS/8]; /* ============ SV_FatPVS The client will interpolate the view position, so we can't use a single PVS point =========== */ void SV_FatPVS( vec3_t org ) { int leafs[128]; int i, j, count; int longs; qbyte *src; vec3_t mins, maxs; for (i=0 ; i<3 ; i++) { mins[i] = org[i] - 8; maxs[i] = org[i] + 8; } count = CM_BoxLeafnums (mins, maxs, leafs, 128, NULL); if (count < 1) Com_Error (ERR_FATAL, "SV_FatPVS: count < 1"); longs = CM_ClusterSize()>>2; // convert leafs to clusters for (i=0 ; i>2; // convert leafs to clusters for( i = 0; i < count; i++ ) leafs[i] = CM_LeafCluster( leafs[i] ); // or in all the other leaf bits for( i = 0; i < count; i++ ) { for( j = 0; j < i; j++ ) if( leafs[i] == leafs[j] ) break; if( j != i ) continue; // already have the cluster we want src = CM_ClusterPVS( leafs[i] ); for( j = 0; j < longs; j++ ) ((long *)fatpvs)[j] |= ((long *)src)[j]; } } /* ============ SV_MergePHS =========== */ void SV_MergePHS( int cluster ) { int i, longs; qbyte *src; longs = CM_ClusterSize()>>2; // or in all the other leaf bits src = CM_ClusterPHS (cluster); for (i=0 ; ir.svflags & SVF_BROADCAST ) return qfalse; if (ent->r.num_clusters == -1) { // too many leafs for individual check, go by headnode if( !CM_HeadnodeVisible (ent->r.headnode, fatpvs) ) return qtrue; return qfalse; } // check individual leafs for( i = 0; i < ent->r.num_clusters; i++ ) { l = ent->r.clusternums[i]; if( fatpvs[l >> 3] & (1 << (l&7) ) ) { return qfalse; } } return qtrue; // not visible } /* ============= SV_BuildClientFrameSnap Decides which entities are going to be visible to the client, and copies off the playerstat and areabits. ============= */ void SV_BuildClientFrameSnap( client_t *client ) { int e, l; vec3_t org; edict_t *ent, *clent; edict_t *pedicts[MAX_EDICTS]; client_frame_t *frame; entity_state_t *state; int clientarea, portalarea; int leafnum, clusternum; int numedicts; qboolean portalview; clent = client->edict; if( !clent->r.client ) return; // not in game yet // this is the frame we are creating frame = &client->frames[sv.framenum & UPDATE_MASK]; frame->sentTimeStamp = svs.realtime; // find the client's PVS VectorSet( org, clent->s.origin[0], clent->s.origin[1], clent->s.origin[2] + clent->r.client->ps.viewheight ); leafnum = CM_PointLeafnum( org ); clusternum = CM_LeafCluster( leafnum ); clientarea = CM_LeafArea( leafnum ); // calculate the visible areas frame->areabytes = CM_WriteAreaBits( frame->areabits, clientarea ); // grab the current player_state_t frame->ps = clent->r.client->ps; // grab current match state information ge->GetMatchState( &frame->matchstate ); // build up the list of visible entities frame->num_entities = 0; frame->first_entity = svs.next_client_entities; // the client is outside the world if( clusternum == -1 ) { for( e = 1; e < sv.num_edicts; e++ ) { ent = EDICT_NUM( e ); // ignore ents without visible models if( ent->r.svflags & SVF_NOCLIENT ) continue; if( ent->r.visclent && (ent->r.visclent != clent) ) continue; // ignore ents without visible models unless they have an effect if( !ent->s.modelindex && !ent->s.effects && !ent->s.sound && !ent->s.events[0] && !ent->s.light ) continue; // wsw : jal : transmit to same team only if( ent->r.svflags & SVF_ONLYTEAM && ent->s.team != clent->s.team ) continue; if( !(ent->r.svflags & SVF_BROADCAST) || ent != clent ) continue; // fix number if broken if( ent->s.number != e ) { Com_DPrintf( "FIXING ENT->S.NUMBER!!!\n" ); ent->s.number = e; } // add it to the circular client_entities array state = &svs.client_entities[svs.next_client_entities%svs.num_client_entities]; *state = ent->s; svs.next_client_entities++; frame->num_entities++; } return; } // save client's PVS so it can be later compared with fatpvs SV_FatPVS( org ); SV_FatPHS( clusternum ); portalview = qfalse; // portal entities are the first to be checked so we can merge PV sets for( e = 1, numedicts = 0; e < sv.num_edicts; e++ ) { ent = EDICT_NUM( e ); // ignore ents without visible models if( ent->r.svflags & SVF_NOCLIENT ) continue; if( ent->r.visclent && (ent->r.visclent != clent) ) continue; // ignore ents without visible models unless they have an effect if( !ent->s.modelindex && !ent->s.effects && !ent->s.sound && !ent->s.events[0] && !ent->s.light ) continue; // wsw : jal : transmit to same team only if( ent->r.svflags & SVF_ONLYTEAM && ent->s.team != clent->s.team ) continue; // fix number if broken if( ent->s.number != e ) { Com_DPrintf( "FIXING ENT->S.NUMBER!!!\n" ); ent->s.number = e; } // ignore if not touching a PV leaf //if( ent != clent || !(ent->r.svflags & SVF_BROADCAST) ) { if( ent != clent && !(ent->r.svflags & SVF_BROADCAST) ) { // wsw : jal : never filter broadcast entities if( !(ent->r.svflags & SVF_PORTAL) ) { if( !ent->s.modelindex && !ent->s.events[0] && !ent->s.light && !ent->s.effects ) { // don't send sounds if they will be attenuated away vec3_t delta; float len; VectorSubtract( org, ent->s.origin, delta ); len = VectorLength( delta ); if( len > 400 ) continue; } pedicts[numedicts++] = ent; continue; } // check area if( !CM_AreasConnected( clientarea, ent->r.areanum ) && !(ent->r.svflags & SVF_BROADCAST) ) continue; if( SV_CullEntity( ent ) ) continue; // merge PV sets if portal if( !VectorCompare( ent->s.origin, ent->s.origin2 ) ) { SV_MergePVS( ent->s.old_origin ); portalarea = CM_PointLeafnum( ent->s.origin2 ); SV_MergePHS( CM_LeafCluster( portalarea ) ); portalarea = CM_LeafArea( portalarea ); CM_MergeAreaBits( frame->areabits, portalarea ); portalview = qtrue; } } // add it to the circular client_entities array state = &svs.client_entities[svs.next_client_entities%svs.num_client_entities]; *state = ent->s; // don't mark players missiles as solid if( ent->r.owner == client->edict ) state->solid = 0; svs.next_client_entities++; frame->num_entities++; } for( e = 0; e < numedicts; e++ ) { ent = pedicts[e]; // check area if( ! (frame->areabits[ent->r.areanum>>3] & (1<<(ent->r.areanum&7)) ) ) { // doors can legally straddle two areas, so // we may need to check another one if( !ent->r.areanum2 || !(frame->areabits[ent->r.areanum2>>3] & (1<<(ent->r.areanum2&7)) ) ) continue; // blocked by a door } // just check one point for PHS if( ent->r.svflags & SVF_FORCEOLDORIGIN ) { if( ent->r.num_clusters == -1 ) { if( !CM_HeadnodeVisible( ent->r.headnode, fatphs ) && !(ent->r.svflags & SVF_BROADCAST) ) continue; } else { l = ent->r.clusternums[0]; if( !(fatphs[l >> 3] & (1 << (l&7) )) ) continue; } } else if( SV_CullEntity( ent ) ) { continue; } // add it to the circular client_entities array state = &svs.client_entities[svs.next_client_entities%svs.num_client_entities]; *state = ent->s; // don't mark players missiles as solid if( ent->r.owner == client->edict ) state->solid = 0; svs.next_client_entities++; frame->num_entities++; } }