xash3d-fwgs/engine/client/cl_events.c

496 lines
9.4 KiB
C

/*
cl_events.c - client-side event system implementation
Copyright (C) 2011 Uncle Mike
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 3 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.
*/
#include "common.h"
#include "client.h"
#include "event_flags.h"
#include "net_encode.h"
#include "con_nprint.h"
/*
===============
CL_ResetEvent
===============
*/
void CL_ResetEvent( event_info_t *ei )
{
ei->index = 0;
memset( &ei->args, 0, sizeof( ei->args ));
ei->fire_time = 0.0;
ei->flags = 0;
}
/*
=============
CL_CalcPlayerVelocity
compute velocity for a given client
=============
*/
void CL_CalcPlayerVelocity( int idx, vec3_t velocity )
{
clientdata_t *pcd;
vec3_t delta;
double dt;
VectorClear( velocity );
if( idx <= 0 || idx > cl.maxclients )
return;
if( idx == cl.playernum + 1 )
{
pcd = &cl.frames[cl.parsecountmod].clientdata;
VectorCopy( pcd->velocity, velocity );
}
else
{
dt = clgame.entities[idx].curstate.animtime - clgame.entities[idx].prevstate.animtime;
if( dt != 0.0 )
{
VectorSubtract( clgame.entities[idx].curstate.velocity, clgame.entities[idx].prevstate.velocity, delta );
VectorScale( delta, 1.0f / dt, velocity );
}
else
{
VectorCopy( clgame.entities[idx].curstate.velocity, velocity );
}
}
}
/*
=============
CL_DescribeEvent
=============
*/
void CL_DescribeEvent( int slot, int flags, const char *eventname )
{
int idx = (slot & 31);
con_nprint_t info;
if( !eventname || !cl_showevents->value )
return;
// mark reliable as green and unreliable as red
if( FBitSet( flags, FEV_RELIABLE ))
VectorSet( info.color, 0.0f, 1.0f, 0.0f );
else VectorSet( info.color, 1.0f, 0.0f, 0.0f );
info.time_to_live = 0.5f;
info.index = idx;
Con_NXPrintf( &info, "%i %f %s", slot, cl.time, eventname );
}
/*
=============
CL_SetEventIndex
=============
*/
void CL_SetEventIndex( const char *szEvName, int ev_index )
{
cl_user_event_t *ev;
int i;
if( !szEvName || !*szEvName )
return; // ignore blank names
// search event by name to link with
for( i = 0; i < MAX_EVENTS; i++ )
{
ev = clgame.events[i];
if( !ev ) break;
if( !Q_stricmp( ev->name, szEvName ))
{
ev->index = ev_index;
return;
}
}
}
/*
=============
CL_EventIndex
=============
*/
word CL_EventIndex( const char *name )
{
int i;
if( !COM_CheckString( name ))
return 0;
for( i = 1; i < MAX_EVENTS && cl.event_precache[i][0]; i++ )
{
if( !Q_stricmp( cl.event_precache[i], name ))
return i;
}
return 0;
}
/*
=============
CL_RegisterEvent
=============
*/
void CL_RegisterEvent( int lastnum, const char *szEvName, pfnEventHook func )
{
cl_user_event_t *ev;
if( lastnum == MAX_EVENTS )
return;
// clear existing or allocate new one
if( !clgame.events[lastnum] )
clgame.events[lastnum] = Mem_Calloc( cls.mempool, sizeof( cl_user_event_t ));
else memset( clgame.events[lastnum], 0, sizeof( cl_user_event_t ));
ev = clgame.events[lastnum];
// NOTE: ev->index will be set later
Q_strncpy( ev->name, szEvName, MAX_QPATH );
ev->func = func;
}
/*
=============
CL_FireEvent
=============
*/
qboolean CL_FireEvent( event_info_t *ei, int slot )
{
cl_user_event_t *ev;
const char *name;
int i, idx;
if( !ei || !ei->index )
return false;
// get the func pointer
for( i = 0; i < MAX_EVENTS; i++ )
{
ev = clgame.events[i];
if( !ev )
{
idx = bound( 1, ei->index, ( MAX_EVENTS - 1 ));
Con_Reportf( S_ERROR "CL_FireEvent: %s not precached\n", cl.event_precache[idx] );
break;
}
if( ev->index == ei->index )
{
if( ev->func )
{
CL_DescribeEvent( slot, ei->flags, cl.event_precache[ei->index] );
ev->func( &ei->args );
return true;
}
name = cl.event_precache[ei->index];
Con_Reportf( S_ERROR "CL_FireEvent: %s not hooked\n", name );
break;
}
}
return false;
}
/*
=============
CL_FireEvents
called right before draw frame
=============
*/
void CL_FireEvents( void )
{
event_state_t *es;
event_info_t *ei;
int i;
es = &cl.events;
for( i = 0; i < MAX_EVENT_QUEUE; i++ )
{
ei = &es->ei[i];
if( ei->index == 0 )
continue;
// delayed event!
if( ei->fire_time && ( ei->fire_time > cl.time ))
continue;
CL_FireEvent( ei, i );
// zero out the remaining fields
CL_ResetEvent( ei );
}
}
/*
=============
CL_FindEvent
find first empty event
=============
*/
event_info_t *CL_FindEmptyEvent( void )
{
int i;
event_state_t *es;
event_info_t *ei;
es = &cl.events;
// look for first slot where index is != 0
for( i = 0; i < MAX_EVENT_QUEUE; i++ )
{
ei = &es->ei[i];
if( ei->index != 0 )
continue;
return ei;
}
// no slots available
return NULL;
}
/*
=============
CL_FindEvent
replace only unreliable events
=============
*/
event_info_t *CL_FindUnreliableEvent( void )
{
event_state_t *es;
event_info_t *ei;
int i;
es = &cl.events;
for ( i = 0; i < MAX_EVENT_QUEUE; i++ )
{
ei = &es->ei[i];
if( ei->index != 0 )
{
// it's reliable, so skip it
if( FBitSet( ei->flags, FEV_RELIABLE ))
continue;
}
return ei;
}
// this should never happen
return NULL;
}
/*
=============
CL_QueueEvent
=============
*/
void CL_QueueEvent( int flags, int index, float delay, event_args_t *args )
{
event_info_t *ei;
// find a normal slot
ei = CL_FindEmptyEvent();
if( !ei )
{
if( FBitSet( flags, FEV_RELIABLE ))
{
ei = CL_FindUnreliableEvent();
}
if( !ei ) return;
}
ei->index = index;
ei->packet_index = 0;
ei->fire_time = delay ? (cl.time + delay) : 0.0f;
ei->flags = flags;
ei->args = *args;
}
/*
=============
CL_ParseReliableEvent
=============
*/
void CL_ParseReliableEvent( sizebuf_t *msg )
{
int event_index;
event_args_t nullargs, args;
float delay = 0.0f;
memset( &nullargs, 0, sizeof( nullargs ));
event_index = MSG_ReadUBitLong( msg, MAX_EVENT_BITS );
if( MSG_ReadOneBit( msg ))
delay = (float)MSG_ReadWord( msg ) * (1.0f / 100.0f);
// reliable events not use delta-compression just null-compression
MSG_ReadDeltaEvent( msg, &nullargs, &args );
if( args.entindex > 0 && args.entindex <= cl.maxclients )
args.angles[PITCH] *= -3.0f;
CL_QueueEvent( FEV_RELIABLE|FEV_SERVER, event_index, delay, &args );
}
/*
=============
CL_ParseEvent
=============
*/
void CL_ParseEvent( sizebuf_t *msg )
{
int event_index;
int i, num_events;
int packet_index;
event_args_t nullargs, args;
entity_state_t *state;
float delay;
memset( &nullargs, 0, sizeof( nullargs ));
memset( &args, 0, sizeof( args ));
num_events = MSG_ReadUBitLong( msg, 5 );
// parse events queue
for( i = 0 ; i < num_events; i++ )
{
event_index = MSG_ReadUBitLong( msg, MAX_EVENT_BITS );
if( MSG_ReadOneBit( msg ))
packet_index = MSG_ReadUBitLong( msg, cls.legacymode ? MAX_LEGACY_ENTITY_BITS : MAX_ENTITY_BITS );
else packet_index = -1;
if( MSG_ReadOneBit( msg ))
{
MSG_ReadDeltaEvent( msg, &nullargs, &args );
}
if( MSG_ReadOneBit( msg ))
delay = (float)MSG_ReadWord( msg ) * (1.0f / 100.0f);
else delay = 0.0f;
if( packet_index != -1 )
{
frame_t *frame = &cl.frames[cl.parsecountmod];
if( packet_index < frame->num_entities )
{
state = &cls.packet_entities[(frame->first_entity+packet_index)%cls.num_client_entities];
args.entindex = state->number;
if( VectorIsNull( args.origin ))
VectorCopy( state->origin, args.origin );
if( VectorIsNull( args.angles ))
VectorCopy( state->angles, args.angles );
COM_NormalizeAngles( args.angles );
if( state->number > 0 && state->number <= cl.maxclients )
{
args.angles[PITCH] *= -3.0f;
CL_CalcPlayerVelocity( state->number, args.velocity );
args.ducking = ( state->usehull == 1 );
}
}
else
{
if( args.entindex != 0 )
{
if( args.entindex > 0 && args.entindex <= cl.maxclients )
args.angles[PITCH] /= -3.0f;
}
}
// Place event on queue
CL_QueueEvent( FEV_SERVER, event_index, delay, &args );
}
}
}
/*
=============
CL_PlaybackEvent
=============
*/
void GAME_EXPORT CL_PlaybackEvent( int flags, const edict_t *pInvoker, word eventindex, float delay, float *origin,
float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 )
{
event_args_t args;
if( FBitSet( flags, FEV_SERVER ))
return;
// first check event for out of bounds
if( eventindex < 1 || eventindex > MAX_EVENTS )
{
Con_DPrintf( S_ERROR "CL_PlaybackEvent: invalid eventindex %i\n", eventindex );
return;
}
// check event for precached
if( !CL_EventIndex( cl.event_precache[eventindex] ))
{
Con_DPrintf( S_ERROR "CL_PlaybackEvent: event %i was not precached\n", eventindex );
return;
}
SetBits( flags, FEV_CLIENT ); // it's a client event
ClearBits( flags, FEV_NOTHOST|FEV_HOSTONLY|FEV_GLOBAL );
if( delay < 0.0f ) delay = 0.0f; // fixup negative delays
memset( &args, 0, sizeof( args ));
VectorCopy( origin, args.origin );
VectorCopy( angles, args.angles );
VectorCopy( cl.simvel, args.velocity );
args.entindex = cl.playernum + 1;
args.ducking = ( cl.local.usehull == 1 );
args.fparam1 = fparam1;
args.fparam2 = fparam2;
args.iparam1 = iparam1;
args.iparam2 = iparam2;
args.bparam1 = bparam1;
args.bparam2 = bparam2;
CL_QueueEvent( flags, eventindex, delay, &args );
}