yquake2remaster/src/server/sv_entities.c
2012-04-30 08:25:59 +02:00

711 lines
15 KiB
C

/*
* 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.
*
* =======================================================================
*
* Server entity handling. Just encodes the entties of a client side
* frame into network / local communication packages and sends them to
* the appropriate clients.
*
* =======================================================================
*/
#include "header/server.h"
byte fatpvs [ 65536 / 8 ];
/*
* Writes a delta update of an entity_state_t list to the message.
*/
void
SV_EmitPacketEntities ( client_frame_t *from, client_frame_t *to, sizebuf_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;
newent = NULL;
oldent = NULL;
while ( newindex < to->num_entities || oldindex < from_num_entities )
{
if ( msg->cursize > MAX_MSGLEN - 150 )
{
break;
}
if ( newindex >= to->num_entities )
{
newnum = 9999;
}
else
{
newent = &svs.client_entities [ ( to->first_entity + newindex ) % svs.num_client_entities ];
newnum = newent->number;
}
if ( oldindex >= from_num_entities )
{
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, false, newent->number <= maxclients->value );
oldindex++;
newindex++;
continue;
}
if ( newnum < oldnum )
{
/* this is a new entity, send it from the baseline */
MSG_WriteDeltaEntity( &sv.baselines [ newnum ], newent, msg, true, true );
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 );
}
void
SV_WritePlayerstateToClient ( client_frame_t *from, client_frame_t *to, sizebuf_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 ] ) ||
( ps->pmove.origin [ 1 ] != ops->pmove.origin [ 1 ] ) ||
( ps->pmove.origin [ 2 ] != ops->pmove.origin [ 2 ] ) )
{
pflags |= PS_M_ORIGIN;
}
if ( ( ps->pmove.velocity [ 0 ] != ops->pmove.velocity [ 0 ] ) ||
( ps->pmove.velocity [ 1 ] != ops->pmove.velocity [ 1 ] ) ||
( ps->pmove.velocity [ 2 ] != ops->pmove.velocity [ 2 ] ) )
{
pflags |= PS_M_VELOCITY;
}
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.gravity != ops->pmove.gravity )
{
pflags |= PS_M_GRAVITY;
}
if ( ( ps->pmove.delta_angles [ 0 ] != ops->pmove.delta_angles [ 0 ] ) ||
( ps->pmove.delta_angles [ 1 ] != ops->pmove.delta_angles [ 1 ] ) ||
( ps->pmove.delta_angles [ 2 ] != ops->pmove.delta_angles [ 2 ] ) )
{
pflags |= PS_M_DELTA_ANGLES;
}
if ( ( ps->viewoffset [ 0 ] != ops->viewoffset [ 0 ] ) ||
( ps->viewoffset [ 1 ] != ops->viewoffset [ 1 ] ) ||
( ps->viewoffset [ 2 ] != ops->viewoffset [ 2 ] ) )
{
pflags |= PS_VIEWOFFSET;
}
if ( ( ps->viewangles [ 0 ] != ops->viewangles [ 0 ] ) ||
( ps->viewangles [ 1 ] != ops->viewangles [ 1 ] ) ||
( ps->viewangles [ 2 ] != ops->viewangles [ 2 ] ) )
{
pflags |= PS_VIEWANGLES;
}
if ( ( ps->kick_angles [ 0 ] != ops->kick_angles [ 0 ] ) ||
( ps->kick_angles [ 1 ] != ops->kick_angles [ 1 ] ) ||
( ps->kick_angles [ 2 ] != ops->kick_angles [ 2 ] ) )
{
pflags |= PS_KICKANGLES;
}
if ( ( ps->blend [ 0 ] != ops->blend [ 0 ] ) ||
( ps->blend [ 1 ] != ops->blend [ 1 ] ) ||
( ps->blend [ 2 ] != ops->blend [ 2 ] ) ||
( ps->blend [ 3 ] != ops->blend [ 3 ] ) )
{
pflags |= PS_BLEND;
}
if ( ps->fov != ops->fov )
{
pflags |= PS_FOV;
}
if ( ps->rdflags != ops->rdflags )
{
pflags |= PS_RDFLAGS;
}
if ( ps->gunframe != ops->gunframe )
{
pflags |= PS_WEAPONFRAME;
}
pflags |= PS_WEAPONINDEX;
/* write it */
MSG_WriteByte( msg, svc_playerinfo );
MSG_WriteShort( msg, pflags );
/* write the pmove_state_t */
if ( pflags & PS_M_TYPE )
{
MSG_WriteByte( msg, ps->pmove.pm_type );
}
if ( pflags & PS_M_ORIGIN )
{
MSG_WriteShort( msg, ps->pmove.origin [ 0 ] );
MSG_WriteShort( msg, ps->pmove.origin [ 1 ] );
MSG_WriteShort( msg, ps->pmove.origin [ 2 ] );
}
if ( pflags & PS_M_VELOCITY )
{
MSG_WriteShort( msg, ps->pmove.velocity [ 0 ] );
MSG_WriteShort( msg, ps->pmove.velocity [ 1 ] );
MSG_WriteShort( msg, ps->pmove.velocity [ 2 ] );
}
if ( pflags & PS_M_TIME )
{
MSG_WriteByte( msg, ps->pmove.pm_time );
}
if ( pflags & PS_M_FLAGS )
{
MSG_WriteByte( msg, ps->pmove.pm_flags );
}
if ( pflags & PS_M_GRAVITY )
{
MSG_WriteShort( msg, ps->pmove.gravity );
}
if ( pflags & PS_M_DELTA_ANGLES )
{
MSG_WriteShort( msg, ps->pmove.delta_angles [ 0 ] );
MSG_WriteShort( msg, ps->pmove.delta_angles [ 1 ] );
MSG_WriteShort( msg, ps->pmove.delta_angles [ 2 ] );
}
/* write the rest of the player_state_t */
if ( pflags & PS_VIEWOFFSET )
{
MSG_WriteChar( msg, ps->viewoffset [ 0 ] * 4 );
MSG_WriteChar( msg, ps->viewoffset [ 1 ] * 4 );
MSG_WriteChar( msg, ps->viewoffset [ 2 ] * 4 );
}
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_KICKANGLES )
{
MSG_WriteChar( msg, ps->kick_angles [ 0 ] * 4 );
MSG_WriteChar( msg, ps->kick_angles [ 1 ] * 4 );
MSG_WriteChar( msg, ps->kick_angles [ 2 ] * 4 );
}
if ( pflags & PS_WEAPONINDEX )
{
MSG_WriteByte( msg, ps->gunindex );
}
if ( pflags & PS_WEAPONFRAME )
{
MSG_WriteByte( msg, ps->gunframe );
MSG_WriteChar( msg, ps->gunoffset [ 0 ] * 4 );
MSG_WriteChar( msg, ps->gunoffset [ 1 ] * 4 );
MSG_WriteChar( msg, ps->gunoffset [ 2 ] * 4 );
MSG_WriteChar( msg, ps->gunangles [ 0 ] * 4 );
MSG_WriteChar( msg, ps->gunangles [ 1 ] * 4 );
MSG_WriteChar( msg, ps->gunangles [ 2 ] * 4 );
}
if ( pflags & PS_BLEND )
{
MSG_WriteByte( msg, ps->blend [ 0 ] * 255 );
MSG_WriteByte( msg, ps->blend [ 1 ] * 255 );
MSG_WriteByte( msg, ps->blend [ 2 ] * 255 );
MSG_WriteByte( msg, ps->blend [ 3 ] * 255 );
}
if ( pflags & PS_FOV )
{
MSG_WriteByte( msg, ps->fov );
}
if ( pflags & PS_RDFLAGS )
{
MSG_WriteByte( msg, ps->rdflags );
}
/* send stats */
statbits = 0;
for ( i = 0; i < MAX_STATS; i++ )
{
if ( ps->stats [ i ] != ops->stats [ i ] )
{
statbits |= 1 << i;
}
}
MSG_WriteLong( msg, statbits );
for ( i = 0; i < MAX_STATS; i++ )
{
if ( statbits & ( 1 << i ) )
{
MSG_WriteShort( msg, ps->stats [ i ] );
}
}
}
void
SV_WriteFrameToClient ( client_t *client, sizebuf_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 is asking for a 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, sv.framenum );
MSG_WriteLong( msg, lastframe ); /* what we are delta'ing from */
MSG_WriteByte( msg, client->surpressCount ); /* rate dropped packets */
client->surpressCount = 0;
/* send over the areabits */
MSG_WriteByte( msg, frame->areabytes );
SZ_Write( msg, frame->areabits, frame->areabytes );
/* delta encode the playerstate */
SV_WritePlayerstateToClient( oldframe, frame, msg );
/* delta encode the entities */
SV_EmitPacketEntities( oldframe, frame, msg );
}
/*
* The client will interpolate the view position,
* so we can't use a single PVS point
*/
void
SV_FatPVS ( vec3_t org )
{
int leafs [ 64 ];
int i, j, count;
int longs;
byte *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, 64, NULL );
if ( count < 1 )
{
Com_Error( ERR_FATAL, "SV_FatPVS: count < 1" );
}
longs = ( CM_NumClusters() + 31 ) >> 5;
/* convert leafs to clusters */
for ( i = 0; i < count; i++ )
{
leafs [ i ] = CM_LeafCluster( leafs [ i ] );
}
memcpy( fatpvs, CM_ClusterPVS( leafs [ 0 ] ), longs << 2 );
/* or in all the other leaf bits */
for ( i = 1; 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 ];
}
}
}
/*
* Decides which entities are going to be visible to the client, and
* copies off the playerstat and areabits.
*/
void
SV_BuildClientFrame ( client_t *client )
{
int e, i;
vec3_t org;
edict_t *ent;
edict_t *clent;
client_frame_t *frame;
entity_state_t *state;
int l;
int clientarea, clientcluster;
int leafnum;
int c_fullsend;
byte *clientphs;
byte *bitvector;
clent = client->edict;
if ( !clent->client )
{
return; /* not in game yet */
}
/* this is the frame we are creating */
frame = &client->frames [ sv.framenum & UPDATE_MASK ];
frame->senttime = svs.realtime; /* save it for ping calc later */
/* find the client's PVS */
for ( i = 0; i < 3; i++ )
{
org [ i ] = clent->client->ps.pmove.origin [ i ] * 0.125 + clent->client->ps.viewoffset [ i ];
}
leafnum = CM_PointLeafnum( org );
clientarea = CM_LeafArea( leafnum );
clientcluster = CM_LeafCluster( leafnum );
/* calculate the visible areas */
frame->areabytes = CM_WriteAreaBits( frame->areabits, clientarea );
/* grab the current player_state_t */
frame->ps = clent->client->ps;
SV_FatPVS( org );
clientphs = CM_ClusterPHS( clientcluster );
/* build up the list of visible entities */
frame->num_entities = 0;
frame->first_entity = svs.next_client_entities;
c_fullsend = 0;
for ( e = 1; e < ge->num_edicts; e++ )
{
ent = EDICT_NUM( e );
/* ignore ents without visible models */
if ( ent->svflags & SVF_NOCLIENT )
{
continue;
}
/* ignore ents without visible models unless they have an effect */
if ( !ent->s.modelindex && !ent->s.effects && !ent->s.sound &&
!ent->s.event )
{
continue;
}
/* ignore if not touching a PV leaf */
if ( ent != clent )
{
/* check area */
if ( !CM_AreasConnected( clientarea, ent->areanum ) )
{
/* doors can legally straddle two areas, so
we may need to check another one */
if ( !ent->areanum2 ||
!CM_AreasConnected( clientarea, ent->areanum2 ) )
{
continue; /* blocked by a door */
}
}
/* beams just check one point for PHS */
if ( ent->s.renderfx & RF_BEAM )
{
l = ent->clusternums [ 0 ];
if ( !( clientphs [ l >> 3 ] & ( 1 << ( l & 7 ) ) ) )
{
continue;
}
}
else
{
bitvector = fatpvs;
if ( ent->num_clusters == -1 )
{
/* too many leafs for individual check, go by headnode */
if ( !CM_HeadnodeVisible( ent->headnode, bitvector ) )
{
continue;
}
c_fullsend++;
}
else
{
/* check individual leafs */
for ( i = 0; i < ent->num_clusters; i++ )
{
l = ent->clusternums [ i ];
if ( bitvector [ l >> 3 ] & ( 1 << ( l & 7 ) ) )
{
break;
}
}
if ( i == ent->num_clusters )
{
continue; /* not visible */
}
}
if ( !ent->s.modelindex )
{
/* 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;
}
}
}
}
/* add it to the circular client_entities array */
state = &svs.client_entities [ svs.next_client_entities % svs.num_client_entities ];
if ( ent->s.number != e )
{
Com_DPrintf( "FIXING ENT->S.NUMBER!!!\n" );
ent->s.number = e;
}
*state = ent->s;
/* don't mark players missiles as solid */
if ( ent->owner == client->edict )
{
state->solid = 0;
}
svs.next_client_entities++;
frame->num_entities++;
}
}
/*
* Save everything in the world out without deltas.
* Used for recording footage for merged or assembled demos
*/
void
SV_RecordDemoMessage ( void )
{
int e;
edict_t *ent;
entity_state_t nostate;
sizebuf_t buf;
byte buf_data [ 32768 ];
int len;
if ( !svs.demofile )
{
return;
}
memset( &nostate, 0, sizeof ( nostate ) );
SZ_Init( &buf, buf_data, sizeof ( buf_data ) );
/* write a frame message that doesn't contain a player_state_t */
MSG_WriteByte( &buf, svc_frame );
MSG_WriteLong( &buf, sv.framenum );
MSG_WriteByte( &buf, svc_packetentities );
e = 1;
ent = EDICT_NUM( e );
while ( e < ge->num_edicts )
{
/* ignore ents without visible models unless they have an effect */
if ( ent->inuse &&
ent->s.number &&
( ent->s.modelindex || ent->s.effects || ent->s.sound || ent->s.event ) &&
!( ent->svflags & SVF_NOCLIENT ) )
{
MSG_WriteDeltaEntity( &nostate, &ent->s, &buf, false, true );
}
e++;
ent = EDICT_NUM( e );
}
MSG_WriteShort( &buf, 0 ); /* end of packetentities */
/* now add the accumulated multicast information */
SZ_Write( &buf, svs.demo_multicast.data, svs.demo_multicast.cursize );
SZ_Clear( &svs.demo_multicast );
/* now write the entire message to the file, prefixed by the length */
len = LittleLong( buf.cursize );
fwrite( &len, 4, 1, svs.demofile );
fwrite( buf.data, buf.cursize, 1, svs.demofile );
}