2188 lines
45 KiB
2188 lines
45 KiB
|
|
|
|
#include "common.h" |
|
#include "client.h" |
|
#include "customentity.h" |
|
#include "r_efx.h" |
|
#include "cl_tent.h" |
|
#include "pm_local.h" |
|
#define PART_SIZE Q_max( 0.5f, cl_draw_particles->value ) |
|
|
|
/* |
|
============================================================== |
|
|
|
PARTICLES MANAGEMENT |
|
|
|
============================================================== |
|
*/ |
|
// particle ramps |
|
static int ramp1[8] = { 0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61 }; |
|
static int ramp2[8] = { 0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66 }; |
|
static int ramp3[6] = { 0x6d, 0x6b, 6, 5, 4, 3 }; |
|
static int gSparkRamp[9] = { 0xfe, 0xfd, 0xfc, 0x6f, 0x6e, 0x6d, 0x6c, 0x67, 0x60 }; |
|
|
|
convar_t *tracerspeed; |
|
convar_t *tracerlength; |
|
convar_t *traceroffset; |
|
|
|
particle_t *cl_active_particles; |
|
particle_t *cl_active_tracers; |
|
particle_t *cl_free_particles; |
|
particle_t *cl_particles = NULL; // particle pool |
|
static vec3_t cl_avelocities[NUMVERTEXNORMALS]; |
|
static float cl_lasttimewarn = 0.0f; |
|
|
|
/* |
|
================ |
|
R_LookupColor |
|
|
|
find nearest color in particle palette |
|
================ |
|
*/ |
|
short GAME_EXPORT R_LookupColor( byte r, byte g, byte b ) |
|
{ |
|
int i, best; |
|
float diff, bestdiff; |
|
float rf, gf, bf; |
|
|
|
bestdiff = 999999; |
|
best = -1; |
|
|
|
for( i = 0; i < 256; i++ ) |
|
{ |
|
rf = r - clgame.palette[i].r; |
|
gf = g - clgame.palette[i].g; |
|
bf = b - clgame.palette[i].b; |
|
|
|
// convert color to monochrome |
|
diff = rf * (rf * 0.2f) + gf * (gf * 0.5f) + bf * (bf * 0.3f); |
|
|
|
if ( diff < bestdiff ) |
|
{ |
|
bestdiff = diff; |
|
best = i; |
|
} |
|
} |
|
|
|
return best; |
|
} |
|
|
|
/* |
|
================ |
|
R_GetPackedColor |
|
|
|
in hardware mode does nothing |
|
================ |
|
*/ |
|
void GAME_EXPORT R_GetPackedColor( short *packed, short color ) |
|
{ |
|
if( packed ) *packed = 0; |
|
} |
|
|
|
/* |
|
================ |
|
CL_InitParticles |
|
|
|
================ |
|
*/ |
|
void CL_InitParticles( void ) |
|
{ |
|
int i; |
|
|
|
cl_particles = Mem_Calloc( cls.mempool, sizeof( particle_t ) * GI->max_particles ); |
|
CL_ClearParticles (); |
|
|
|
// this is used for EF_BRIGHTFIELD |
|
for( i = 0; i < NUMVERTEXNORMALS; i++ ) |
|
{ |
|
cl_avelocities[i][0] = COM_RandomFloat( 0.0f, 2.55f ); |
|
cl_avelocities[i][1] = COM_RandomFloat( 0.0f, 2.55f ); |
|
cl_avelocities[i][2] = COM_RandomFloat( 0.0f, 2.55f ); |
|
} |
|
|
|
tracerspeed = Cvar_Get( "tracerspeed", "6000", 0, "tracer speed" ); |
|
tracerlength = Cvar_Get( "tracerlength", "0.8", 0, "tracer length factor" ); |
|
traceroffset = Cvar_Get( "traceroffset", "30", 0, "tracer starting offset" ); |
|
} |
|
|
|
/* |
|
================ |
|
CL_ClearParticles |
|
|
|
================ |
|
*/ |
|
void CL_ClearParticles( void ) |
|
{ |
|
int i; |
|
|
|
if( !cl_particles ) return; |
|
|
|
cl_free_particles = cl_particles; |
|
cl_active_particles = NULL; |
|
cl_active_tracers = NULL; |
|
|
|
for( i = 0; i < GI->max_particles - 1; i++ ) |
|
cl_particles[i].next = &cl_particles[i+1]; |
|
|
|
cl_particles[GI->max_particles-1].next = NULL; |
|
} |
|
|
|
/* |
|
================ |
|
CL_FreeParticles |
|
|
|
================ |
|
*/ |
|
void CL_FreeParticles( void ) |
|
{ |
|
if( cl_particles ) |
|
Mem_Free( cl_particles ); |
|
cl_particles = NULL; |
|
} |
|
|
|
/* |
|
================ |
|
CL_FreeParticle |
|
|
|
move particle to freelist |
|
================ |
|
*/ |
|
void CL_FreeParticle( particle_t *p ) |
|
{ |
|
if( p->deathfunc ) |
|
{ |
|
// call right the deathfunc before die |
|
p->deathfunc( p ); |
|
p->deathfunc = NULL; |
|
} |
|
|
|
p->next = cl_free_particles; |
|
cl_free_particles = p; |
|
} |
|
|
|
/* |
|
================ |
|
CL_AllocParticleFast |
|
|
|
unconditionally give new particle pointer from cl_free_particles |
|
================ |
|
*/ |
|
particle_t *CL_AllocParticleFast( void ) |
|
{ |
|
particle_t *p = NULL; |
|
|
|
if( cl_free_particles ) |
|
{ |
|
p = cl_free_particles; |
|
cl_free_particles = p->next; |
|
} |
|
|
|
return p; |
|
} |
|
|
|
/* |
|
================ |
|
R_AllocParticle |
|
|
|
can return NULL if particles is out |
|
================ |
|
*/ |
|
particle_t * GAME_EXPORT R_AllocParticle( void (*callback)( particle_t*, float )) |
|
{ |
|
particle_t *p; |
|
|
|
if( !cl_draw_particles->value ) |
|
return NULL; |
|
|
|
// never alloc particles when we not in game |
|
// if( tr.frametime == 0.0 ) return NULL; |
|
|
|
if( !cl_free_particles ) |
|
{ |
|
if( cl_lasttimewarn < host.realtime ) |
|
{ |
|
// don't spam about overflow |
|
Con_DPrintf( S_ERROR "Overflow %d particles\n", GI->max_particles ); |
|
cl_lasttimewarn = host.realtime + 1.0f; |
|
} |
|
return NULL; |
|
} |
|
|
|
p = cl_free_particles; |
|
cl_free_particles = p->next; |
|
p->next = cl_active_particles; |
|
cl_active_particles = p; |
|
|
|
// clear old particle |
|
p->type = pt_static; |
|
VectorClear( p->vel ); |
|
VectorClear( p->org ); |
|
p->packedColor = 0; |
|
p->die = cl.time; |
|
p->color = 0; |
|
p->ramp = 0; |
|
|
|
if( callback ) |
|
{ |
|
p->type = pt_clientcustom; |
|
p->callback = callback; |
|
} |
|
|
|
return p; |
|
} |
|
|
|
/* |
|
================ |
|
R_AllocTracer |
|
|
|
can return NULL if particles is out |
|
================ |
|
*/ |
|
particle_t *R_AllocTracer( const vec3_t org, const vec3_t vel, float life ) |
|
{ |
|
particle_t *p; |
|
|
|
if( !cl_draw_tracers->value ) |
|
return NULL; |
|
|
|
// never alloc particles when we not in game |
|
//if( tr.frametime == 0.0 ) return NULL; |
|
|
|
if( !cl_free_particles ) |
|
{ |
|
if( cl_lasttimewarn < host.realtime ) |
|
{ |
|
// don't spam about overflow |
|
Con_DPrintf( S_ERROR "Overflow %d tracers\n", GI->max_particles ); |
|
cl_lasttimewarn = host.realtime + 1.0f; |
|
} |
|
return NULL; |
|
} |
|
|
|
p = cl_free_particles; |
|
cl_free_particles = p->next; |
|
p->next = cl_active_tracers; |
|
cl_active_tracers = p; |
|
|
|
// clear old particle |
|
p->type = pt_static; |
|
VectorCopy( org, p->org ); |
|
VectorCopy( vel, p->vel ); |
|
p->die = cl.time + life; |
|
p->ramp = tracerlength->value; |
|
p->color = 4; // select custom color |
|
p->packedColor = 255; // alpha |
|
|
|
return p; |
|
} |
|
/* |
|
============================================================== |
|
|
|
VIEWBEAMS MANAGEMENT |
|
|
|
============================================================== |
|
*/ |
|
BEAM *cl_active_beams; |
|
BEAM *cl_free_beams; |
|
BEAM *cl_viewbeams = NULL; // beams pool |
|
|
|
|
|
/* |
|
============================================================== |
|
|
|
BEAM ALLOCATE & PROCESSING |
|
|
|
============================================================== |
|
*/ |
|
|
|
|
|
/* |
|
============== |
|
R_BeamSetAttributes |
|
|
|
set beam attributes |
|
============== |
|
*/ |
|
static void R_BeamSetAttributes( BEAM *pbeam, float r, float g, float b, float framerate, int startFrame ) |
|
{ |
|
pbeam->frame = (float)startFrame; |
|
pbeam->frameRate = framerate; |
|
pbeam->r = r; |
|
pbeam->g = g; |
|
pbeam->b = b; |
|
} |
|
|
|
|
|
|
|
/* |
|
============== |
|
R_BeamAlloc |
|
|
|
============== |
|
*/ |
|
BEAM *R_BeamAlloc( void ) |
|
{ |
|
BEAM *pBeam; |
|
|
|
if( !cl_free_beams ) |
|
return NULL; |
|
|
|
pBeam = cl_free_beams; |
|
cl_free_beams = pBeam->next; |
|
memset( pBeam, 0, sizeof( *pBeam )); |
|
pBeam->next = cl_active_beams; |
|
cl_active_beams = pBeam; |
|
pBeam->die = cl.time; |
|
|
|
return pBeam; |
|
} |
|
|
|
/* |
|
============== |
|
R_BeamFree |
|
|
|
============== |
|
*/ |
|
void R_BeamFree( BEAM *pBeam ) |
|
{ |
|
// free particles that have died off. |
|
R_FreeDeadParticles( &pBeam->particles ); |
|
|
|
// now link into free list; |
|
pBeam->next = cl_free_beams; |
|
cl_free_beams = pBeam; |
|
} |
|
|
|
|
|
/* |
|
================ |
|
CL_InitViewBeams |
|
|
|
================ |
|
*/ |
|
void CL_InitViewBeams( void ) |
|
{ |
|
cl_viewbeams = Mem_Calloc( cls.mempool, sizeof( BEAM ) * GI->max_beams ); |
|
CL_ClearViewBeams(); |
|
} |
|
|
|
/* |
|
================ |
|
CL_ClearViewBeams |
|
|
|
================ |
|
*/ |
|
void CL_ClearViewBeams( void ) |
|
{ |
|
int i; |
|
|
|
if( !cl_viewbeams ) return; |
|
|
|
// clear beams |
|
cl_free_beams = cl_viewbeams; |
|
cl_active_beams = NULL; |
|
|
|
for( i = 0; i < GI->max_beams - 1; i++ ) |
|
cl_viewbeams[i].next = &cl_viewbeams[i+1]; |
|
cl_viewbeams[GI->max_beams - 1].next = NULL; |
|
} |
|
|
|
/* |
|
================ |
|
CL_FreeViewBeams |
|
|
|
================ |
|
*/ |
|
void CL_FreeViewBeams( void ) |
|
{ |
|
if( cl_viewbeams ) |
|
Mem_Free( cl_viewbeams ); |
|
cl_viewbeams = NULL; |
|
} |
|
|
|
/* |
|
============== |
|
R_BeamGetEntity |
|
|
|
extract entity number from index |
|
handle user entities |
|
============== |
|
*/ |
|
cl_entity_t *R_BeamGetEntity( int index ) |
|
{ |
|
if( index < 0 ) |
|
return clgame.dllFuncs.pfnGetUserEntity( BEAMENT_ENTITY( -index )); |
|
return CL_GetEntityByIndex( BEAMENT_ENTITY( index )); |
|
} |
|
|
|
/* |
|
============== |
|
CL_KillDeadBeams |
|
|
|
============== |
|
*/ |
|
void CL_KillDeadBeams( cl_entity_t *pDeadEntity ) |
|
{ |
|
BEAM *pbeam; |
|
BEAM *pnewlist; |
|
BEAM *pnext; |
|
particle_t *pHead; // build a new list to replace cl_active_beams. |
|
|
|
pbeam = cl_active_beams; // old list. |
|
pnewlist = NULL; // new list. |
|
|
|
while( pbeam ) |
|
{ |
|
cl_entity_t *beament; |
|
pnext = pbeam->next; |
|
|
|
// link into new list. |
|
if( R_BeamGetEntity( pbeam->startEntity ) != pDeadEntity ) |
|
{ |
|
pbeam->next = pnewlist; |
|
pnewlist = pbeam; |
|
|
|
pbeam = pnext; |
|
continue; |
|
} |
|
|
|
pbeam->flags &= ~(FBEAM_STARTENTITY | FBEAM_ENDENTITY); |
|
|
|
if( pbeam->type != TE_BEAMFOLLOW ) |
|
{ |
|
// remove beam |
|
pbeam->die = cl.time - 0.1f; |
|
|
|
// kill off particles |
|
pHead = pbeam->particles; |
|
while( pHead ) |
|
{ |
|
pHead->die = cl.time - 0.1f; |
|
pHead = pHead->next; |
|
} |
|
|
|
// free the beam |
|
R_BeamFree( pbeam ); |
|
} |
|
else |
|
{ |
|
// stay active |
|
pbeam->next = pnewlist; |
|
pnewlist = pbeam; |
|
} |
|
|
|
pbeam = pnext; |
|
} |
|
|
|
// We now have a new list with the bogus stuff released. |
|
cl_active_beams = pnewlist; |
|
} |
|
|
|
|
|
/* |
|
=============== |
|
CL_ReadLineFile_f |
|
|
|
Optimized version of pointfile - use beams instead of particles |
|
=============== |
|
*/ |
|
void CL_ReadLineFile_f( void ) |
|
{ |
|
byte *afile; |
|
char *pfile; |
|
vec3_t p1, p2; |
|
int count, modelIndex; |
|
char filename[MAX_QPATH]; |
|
model_t *model; |
|
string token; |
|
|
|
Q_snprintf( filename, sizeof( filename ), "maps/%s.lin", clgame.mapname ); |
|
afile = FS_LoadFile( filename, NULL, false ); |
|
|
|
if( !afile ) |
|
{ |
|
Con_Printf( S_ERROR "couldn't open %s\n", filename ); |
|
return; |
|
} |
|
|
|
Con_Printf( "Reading %s...\n", filename ); |
|
|
|
count = 0; |
|
pfile = (char *)afile; |
|
model = CL_LoadModel( DEFAULT_LASERBEAM_PATH, &modelIndex ); |
|
|
|
while( 1 ) |
|
{ |
|
pfile = COM_ParseFile( pfile, token, sizeof( token )); |
|
if( !pfile ) break; |
|
p1[0] = Q_atof( token ); |
|
|
|
pfile = COM_ParseFile( pfile, token, sizeof( token )); |
|
if( !pfile ) break; |
|
p1[1] = Q_atof( token ); |
|
|
|
pfile = COM_ParseFile( pfile, token, sizeof( token )); |
|
if( !pfile ) break; |
|
p1[2] = Q_atof( token ); |
|
|
|
pfile = COM_ParseFile( pfile, token, sizeof( token )); |
|
if( !pfile ) break; |
|
|
|
if( token[0] != '-' ) |
|
{ |
|
Con_Printf( S_ERROR "%s is corrupted\n", filename ); |
|
break; |
|
} |
|
|
|
pfile = COM_ParseFile( pfile, token, sizeof( token )); |
|
if( !pfile ) break; |
|
p2[0] = Q_atof( token ); |
|
|
|
pfile = COM_ParseFile( pfile, token, sizeof( token )); |
|
if( !pfile ) break; |
|
p2[1] = Q_atof( token ); |
|
|
|
pfile = COM_ParseFile( pfile, token, sizeof( token )); |
|
if( !pfile ) break; |
|
p2[2] = Q_atof( token ); |
|
|
|
count++; |
|
|
|
if( !R_BeamPoints( p1, p2, modelIndex, 0, 2, 0, 255, 0, 0, 0, 255.0f, 0.0f, 0.0f )) |
|
{ |
|
if( !model || model->type != mod_sprite ) |
|
Con_Printf( S_ERROR "failed to load \"%s\"!\n", DEFAULT_LASERBEAM_PATH ); |
|
else Con_Printf( S_ERROR "not enough free beams!\n" ); |
|
break; |
|
} |
|
} |
|
|
|
Mem_Free( afile ); |
|
|
|
if( count ) Con_Printf( "%i lines read\n", count ); |
|
else Con_Printf( "map %s has no leaks!\n", clgame.mapname ); |
|
} |
|
|
|
|
|
/* |
|
============== |
|
R_BeamSprite |
|
|
|
Create a beam with sprite at the end |
|
Valve legacy |
|
============== |
|
*/ |
|
static void CL_BeamSprite( vec3_t start, vec3_t end, int beamIndex, int spriteIndex ) |
|
{ |
|
R_BeamPoints( start, end, beamIndex, 0.01f, 0.4f, 0, COM_RandomFloat( 0.5f, 0.655f ), 5.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f ); |
|
R_TempSprite( end, vec3_origin, 0.1f, spriteIndex, kRenderTransAdd, kRenderFxNone, 0.35f, 0.01f, 0.0f ); |
|
} |
|
|
|
|
|
/* |
|
============== |
|
R_BeamSetup |
|
|
|
generic function. all beams must be |
|
passed through this |
|
============== |
|
*/ |
|
static void R_BeamSetup( BEAM *pbeam, vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed ) |
|
{ |
|
model_t *sprite = CL_ModelHandle( modelIndex ); |
|
|
|
if( !sprite ) return; |
|
|
|
pbeam->type = BEAM_POINTS; |
|
pbeam->modelIndex = modelIndex; |
|
pbeam->frame = 0; |
|
pbeam->frameRate = 0; |
|
pbeam->frameCount = sprite->numframes; |
|
|
|
VectorCopy( start, pbeam->source ); |
|
VectorCopy( end, pbeam->target ); |
|
VectorSubtract( end, start, pbeam->delta ); |
|
|
|
pbeam->freq = speed * cl.time; |
|
pbeam->die = life + cl.time; |
|
pbeam->amplitude = amplitude; |
|
pbeam->brightness = brightness; |
|
pbeam->width = width; |
|
pbeam->speed = speed; |
|
|
|
if( amplitude >= 0.50f ) |
|
pbeam->segments = VectorLength( pbeam->delta ) * 0.25f + 3.0f; // one per 4 pixels |
|
else pbeam->segments = VectorLength( pbeam->delta ) * 0.075f + 3.0f; // one per 16 pixels |
|
|
|
pbeam->pFollowModel = NULL; |
|
pbeam->flags = 0; |
|
} |
|
|
|
/* |
|
============== |
|
CL_BeamAttemptToDie |
|
|
|
Check for expired beams |
|
============== |
|
*/ |
|
qboolean CL_BeamAttemptToDie( BEAM *pBeam ) |
|
{ |
|
Assert( pBeam != NULL ); |
|
|
|
// premanent beams never die automatically |
|
if( FBitSet( pBeam->flags, FBEAM_FOREVER )) |
|
return false; |
|
|
|
if( pBeam->type == TE_BEAMFOLLOW && pBeam->particles ) |
|
{ |
|
// wait for all trails are dead |
|
return false; |
|
} |
|
|
|
// other beams |
|
if( pBeam->die > cl.time ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
/* |
|
============== |
|
R_BeamKill |
|
|
|
Remove beam attached to specified entity |
|
and all particle trails (if this is a beamfollow) |
|
============== |
|
*/ |
|
void GAME_EXPORT R_BeamKill( int deadEntity ) |
|
{ |
|
cl_entity_t *pDeadEntity; |
|
|
|
pDeadEntity = R_BeamGetEntity( deadEntity ); |
|
if( !pDeadEntity ) return; |
|
|
|
CL_KillDeadBeams( pDeadEntity ); |
|
} |
|
|
|
/* |
|
============== |
|
CL_ParseViewBeam |
|
|
|
handle beam messages |
|
============== |
|
*/ |
|
void CL_ParseViewBeam( sizebuf_t *msg, int beamType ) |
|
{ |
|
vec3_t start, end; |
|
int modelIndex, startFrame; |
|
float frameRate, life, width; |
|
int startEnt, endEnt; |
|
float noise, speed; |
|
float r, g, b, a; |
|
|
|
switch( beamType ) |
|
{ |
|
case TE_BEAMPOINTS: |
|
case TE_BEAMENTPOINT: |
|
case TE_BEAMENTS: |
|
if( beamType == TE_BEAMENTS ) |
|
{ |
|
startEnt = MSG_ReadShort( msg ); |
|
endEnt = MSG_ReadShort( msg ); |
|
} |
|
else |
|
{ |
|
if( beamType == TE_BEAMENTPOINT ) |
|
{ |
|
startEnt = MSG_ReadShort( msg ); |
|
} |
|
else |
|
{ |
|
start[0] = MSG_ReadCoord( msg ); |
|
start[1] = MSG_ReadCoord( msg ); |
|
start[2] = MSG_ReadCoord( msg ); |
|
} |
|
end[0] = MSG_ReadCoord( msg ); |
|
end[1] = MSG_ReadCoord( msg ); |
|
end[2] = MSG_ReadCoord( msg ); |
|
} |
|
modelIndex = MSG_ReadShort( msg ); |
|
startFrame = MSG_ReadByte( msg ); |
|
frameRate = (float)MSG_ReadByte( msg ) * 0.1f; |
|
life = (float)MSG_ReadByte( msg ) * 0.1f; |
|
width = (float)MSG_ReadByte( msg ) * 0.1f; |
|
noise = (float)MSG_ReadByte( msg ) * 0.01f; |
|
r = (float)MSG_ReadByte( msg ) / 255.0f; |
|
g = (float)MSG_ReadByte( msg ) / 255.0f; |
|
b = (float)MSG_ReadByte( msg ) / 255.0f; |
|
a = (float)MSG_ReadByte( msg ) / 255.0f; |
|
speed = (float)MSG_ReadByte( msg ) * 0.1f; |
|
if( beamType == TE_BEAMENTS ) |
|
R_BeamEnts( startEnt, endEnt, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); |
|
else if( beamType == TE_BEAMENTPOINT ) |
|
R_BeamEntPoint( startEnt, end, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); |
|
else |
|
R_BeamPoints( start, end, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); |
|
break; |
|
case TE_LIGHTNING: |
|
start[0] = MSG_ReadCoord( msg ); |
|
start[1] = MSG_ReadCoord( msg ); |
|
start[2] = MSG_ReadCoord( msg ); |
|
end[0] = MSG_ReadCoord( msg ); |
|
end[1] = MSG_ReadCoord( msg ); |
|
end[2] = MSG_ReadCoord( msg ); |
|
life = (float)MSG_ReadByte( msg ) * 0.1f; |
|
width = (float)MSG_ReadByte( msg ) * 0.1f; |
|
noise = (float)MSG_ReadByte( msg ) * 0.01f; |
|
modelIndex = MSG_ReadShort( msg ); |
|
R_BeamLightning( start, end, modelIndex, life, width, noise, 0.6f, 3.5f ); |
|
break; |
|
case TE_BEAM: |
|
break; |
|
case TE_BEAMSPRITE: |
|
start[0] = MSG_ReadCoord( msg ); |
|
start[1] = MSG_ReadCoord( msg ); |
|
start[2] = MSG_ReadCoord( msg ); |
|
end[0] = MSG_ReadCoord( msg ); |
|
end[1] = MSG_ReadCoord( msg ); |
|
end[2] = MSG_ReadCoord( msg ); |
|
modelIndex = MSG_ReadShort( msg ); // beam model |
|
startFrame = MSG_ReadShort( msg ); // sprite model |
|
CL_BeamSprite( start, end, modelIndex, startFrame ); |
|
break; |
|
case TE_BEAMTORUS: |
|
case TE_BEAMDISK: |
|
case TE_BEAMCYLINDER: |
|
start[0] = MSG_ReadCoord( msg ); |
|
start[1] = MSG_ReadCoord( msg ); |
|
start[2] = MSG_ReadCoord( msg ); |
|
end[0] = MSG_ReadCoord( msg ); |
|
end[1] = MSG_ReadCoord( msg ); |
|
end[2] = MSG_ReadCoord( msg ); |
|
modelIndex = MSG_ReadShort( msg ); |
|
startFrame = MSG_ReadByte( msg ); |
|
frameRate = (float)MSG_ReadByte( msg ) * 0.1f; |
|
life = (float)MSG_ReadByte( msg ) * 0.1f; |
|
width = (float)MSG_ReadByte( msg ); |
|
noise = (float)MSG_ReadByte( msg ) * 0.01f; |
|
r = (float)MSG_ReadByte( msg ) / 255.0f; |
|
g = (float)MSG_ReadByte( msg ) / 255.0f; |
|
b = (float)MSG_ReadByte( msg ) / 255.0f; |
|
a = (float)MSG_ReadByte( msg ) / 255.0f; |
|
speed = (float)MSG_ReadByte( msg ) * 0.1f; |
|
R_BeamCirclePoints( beamType, start, end, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); |
|
break; |
|
case TE_BEAMFOLLOW: |
|
startEnt = MSG_ReadShort( msg ); |
|
modelIndex = MSG_ReadShort( msg ); |
|
life = (float)MSG_ReadByte( msg ) * 0.1f; |
|
width = (float)MSG_ReadByte( msg ); |
|
r = (float)MSG_ReadByte( msg ) / 255.0f; |
|
g = (float)MSG_ReadByte( msg ) / 255.0f; |
|
b = (float)MSG_ReadByte( msg ) / 255.0f; |
|
a = (float)MSG_ReadByte( msg ) / 255.0f; |
|
R_BeamFollow( startEnt, modelIndex, life, width, r, g, b, a ); |
|
break; |
|
case TE_BEAMRING: |
|
startEnt = MSG_ReadShort( msg ); |
|
endEnt = MSG_ReadShort( msg ); |
|
modelIndex = MSG_ReadShort( msg ); |
|
startFrame = MSG_ReadByte( msg ); |
|
frameRate = (float)MSG_ReadByte( msg ) * 0.1f; |
|
life = (float)MSG_ReadByte( msg ) * 0.1f; |
|
width = (float)MSG_ReadByte( msg ) * 0.1f; |
|
noise = (float)MSG_ReadByte( msg ) * 0.01f; |
|
r = (float)MSG_ReadByte( msg ) / 255.0f; |
|
g = (float)MSG_ReadByte( msg ) / 255.0f; |
|
b = (float)MSG_ReadByte( msg ) / 255.0f; |
|
a = (float)MSG_ReadByte( msg ) / 255.0f; |
|
speed = (float)MSG_ReadByte( msg ) * 0.1f; |
|
R_BeamRing( startEnt, endEnt, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); |
|
break; |
|
case TE_BEAMHOSE: |
|
break; |
|
case TE_KILLBEAM: |
|
startEnt = MSG_ReadShort( msg ); |
|
R_BeamKill( startEnt ); |
|
break; |
|
} |
|
} |
|
|
|
|
|
/* |
|
============== |
|
R_BeamEnts |
|
|
|
Create beam between two ents |
|
============== |
|
*/ |
|
BEAM * GAME_EXPORT R_BeamEnts( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness, |
|
float speed, int startFrame, float framerate, float r, float g, float b ) |
|
{ |
|
cl_entity_t *start, *end; |
|
BEAM *pbeam; |
|
model_t *mod; |
|
|
|
mod = CL_ModelHandle( modelIndex ); |
|
|
|
// need a valid model. |
|
if( !mod || mod->type != mod_sprite ) |
|
return NULL; |
|
|
|
start = R_BeamGetEntity( startEnt ); |
|
end = R_BeamGetEntity( endEnt ); |
|
|
|
if( !start || !end ) |
|
return NULL; |
|
|
|
// don't start temporary beams out of the PVS |
|
if( life != 0 && ( !start->model || !end->model )) |
|
return NULL; |
|
|
|
pbeam = R_BeamLightning( vec3_origin, vec3_origin, modelIndex, life, width, amplitude, brightness, speed ); |
|
if( !pbeam ) return NULL; |
|
|
|
pbeam->type = TE_BEAMPOINTS; |
|
SetBits( pbeam->flags, FBEAM_STARTENTITY | FBEAM_ENDENTITY ); |
|
if( life == 0 ) SetBits( pbeam->flags, FBEAM_FOREVER ); |
|
|
|
pbeam->startEntity = startEnt; |
|
pbeam->endEntity = endEnt; |
|
|
|
R_BeamSetAttributes( pbeam, r, g, b, framerate, startFrame ); |
|
|
|
return pbeam; |
|
} |
|
|
|
/* |
|
============== |
|
R_BeamPoints |
|
|
|
Create beam between two points |
|
============== |
|
*/ |
|
BEAM * GAME_EXPORT R_BeamPoints( vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, |
|
float brightness, float speed, int startFrame, float framerate, float r, float g, float b ) |
|
{ |
|
BEAM *pbeam; |
|
|
|
if( life != 0 && ref.dllFuncs.R_BeamCull( start, end, true )) |
|
return NULL; |
|
|
|
pbeam = R_BeamAlloc(); |
|
if( !pbeam ) return NULL; |
|
|
|
pbeam->die = cl.time; |
|
|
|
if( modelIndex < 0 ) |
|
return NULL; |
|
|
|
R_BeamSetup( pbeam, start, end, modelIndex, life, width, amplitude, brightness, speed ); |
|
if( life == 0 ) SetBits( pbeam->flags, FBEAM_FOREVER ); |
|
|
|
R_BeamSetAttributes( pbeam, r, g, b, framerate, startFrame ); |
|
|
|
return pbeam; |
|
} |
|
|
|
/* |
|
============== |
|
R_BeamCirclePoints |
|
|
|
Create beam cicrle |
|
============== |
|
*/ |
|
BEAM * GAME_EXPORT R_BeamCirclePoints( int type, vec3_t start, vec3_t end, int modelIndex, float life, float width, |
|
float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ) |
|
{ |
|
BEAM *pbeam = R_BeamLightning( start, end, modelIndex, life, width, amplitude, brightness, speed ); |
|
|
|
if( !pbeam ) return NULL; |
|
pbeam->type = type; |
|
if( life == 0 ) SetBits( pbeam->flags, FBEAM_FOREVER ); |
|
R_BeamSetAttributes( pbeam, r, g, b, framerate, startFrame ); |
|
|
|
return pbeam; |
|
} |
|
|
|
|
|
/* |
|
============== |
|
R_BeamEntPoint |
|
|
|
Create beam between entity and point |
|
============== |
|
*/ |
|
BEAM *GAME_EXPORT R_BeamEntPoint( int startEnt, vec3_t end, int modelIndex, float life, float width, float amplitude, |
|
float brightness, float speed, int startFrame, float framerate, float r, float g, float b ) |
|
{ |
|
BEAM *pbeam; |
|
cl_entity_t *start; |
|
|
|
start = R_BeamGetEntity( startEnt ); |
|
|
|
if( !start ) return NULL; |
|
|
|
if( life == 0 && !start->model ) |
|
return NULL; |
|
|
|
pbeam = R_BeamAlloc(); |
|
if ( !pbeam ) return NULL; |
|
|
|
pbeam->die = cl.time; |
|
if( modelIndex < 0 ) |
|
return NULL; |
|
|
|
R_BeamSetup( pbeam, vec3_origin, end, modelIndex, life, width, amplitude, brightness, speed ); |
|
|
|
pbeam->type = TE_BEAMPOINTS; |
|
SetBits( pbeam->flags, FBEAM_STARTENTITY ); |
|
if( life == 0 ) SetBits( pbeam->flags, FBEAM_FOREVER ); |
|
pbeam->startEntity = startEnt; |
|
pbeam->endEntity = 0; |
|
|
|
R_BeamSetAttributes( pbeam, r, g, b, framerate, startFrame ); |
|
|
|
return pbeam; |
|
} |
|
|
|
/* |
|
============== |
|
R_BeamRing |
|
|
|
Create beam between two ents |
|
============== |
|
*/ |
|
BEAM * GAME_EXPORT R_BeamRing( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness, |
|
float speed, int startFrame, float framerate, float r, float g, float b ) |
|
{ |
|
BEAM *pbeam; |
|
cl_entity_t *start, *end; |
|
|
|
start = R_BeamGetEntity( startEnt ); |
|
end = R_BeamGetEntity( endEnt ); |
|
|
|
if( !start || !end ) |
|
return NULL; |
|
|
|
if( life != 0 && ( !start->model || !end->model )) |
|
return NULL; |
|
|
|
pbeam = R_BeamLightning( vec3_origin, vec3_origin, modelIndex, life, width, amplitude, brightness, speed ); |
|
if( !pbeam ) return NULL; |
|
|
|
pbeam->type = TE_BEAMRING; |
|
SetBits( pbeam->flags, FBEAM_STARTENTITY | FBEAM_ENDENTITY ); |
|
if( life == 0 ) SetBits( pbeam->flags, FBEAM_FOREVER ); |
|
pbeam->startEntity = startEnt; |
|
pbeam->endEntity = endEnt; |
|
|
|
R_BeamSetAttributes( pbeam, r, g, b, framerate, startFrame ); |
|
|
|
return pbeam; |
|
} |
|
|
|
/* |
|
============== |
|
R_BeamFollow |
|
|
|
Create beam following with entity |
|
============== |
|
*/ |
|
BEAM *GAME_EXPORT R_BeamFollow( int startEnt, int modelIndex, float life, float width, float r, float g, float b, float brightness ) |
|
{ |
|
BEAM *pbeam = R_BeamAlloc(); |
|
|
|
if( !pbeam ) return NULL; |
|
pbeam->die = cl.time; |
|
|
|
if( modelIndex < 0 ) |
|
return NULL; |
|
|
|
R_BeamSetup( pbeam, vec3_origin, vec3_origin, modelIndex, life, width, life, brightness, 1.0f ); |
|
|
|
pbeam->type = TE_BEAMFOLLOW; |
|
SetBits( pbeam->flags, FBEAM_STARTENTITY ); |
|
pbeam->startEntity = startEnt; |
|
|
|
R_BeamSetAttributes( pbeam, r, g, b, 1.0f, 0 ); |
|
|
|
return pbeam; |
|
} |
|
|
|
|
|
/* |
|
============== |
|
R_BeamLightning |
|
|
|
template for new beams |
|
============== |
|
*/ |
|
BEAM *GAME_EXPORT R_BeamLightning( vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed ) |
|
{ |
|
BEAM *pbeam = R_BeamAlloc(); |
|
|
|
if( !pbeam ) return NULL; |
|
pbeam->die = cl.time; |
|
|
|
if( modelIndex < 0 ) |
|
return NULL; |
|
|
|
R_BeamSetup( pbeam, start, end, modelIndex, life, width, amplitude, brightness, speed ); |
|
|
|
return pbeam; |
|
} |
|
|
|
|
|
|
|
/* |
|
=============== |
|
R_EntityParticles |
|
|
|
set EF_BRIGHTFIELD effect |
|
=============== |
|
*/ |
|
void GAME_EXPORT R_EntityParticles( cl_entity_t *ent ) |
|
{ |
|
float angle; |
|
float sr, sp, sy, cr, cp, cy; |
|
vec3_t forward; |
|
particle_t *p; |
|
int i; |
|
|
|
for( i = 0; i < NUMVERTEXNORMALS; i++ ) |
|
{ |
|
p = R_AllocParticle( NULL ); |
|
if( !p ) return; |
|
|
|
angle = cl.time * cl_avelocities[i][0]; |
|
SinCos( angle, &sy, &cy ); |
|
angle = cl.time * cl_avelocities[i][1]; |
|
SinCos( angle, &sp, &cp ); |
|
angle = cl.time * cl_avelocities[i][2]; |
|
SinCos( angle, &sr, &cr ); |
|
|
|
VectorSet( forward, cp * cy, cp * sy, -sp ); |
|
|
|
p->die = cl.time + 0.001f; |
|
p->color = 111; // yellow |
|
|
|
VectorMAMAM( 1.0f, ent->origin, 64.0f, m_bytenormals[i], 16.0f, forward, p->org ); |
|
} |
|
} |
|
|
|
/* |
|
=============== |
|
R_ParticleExplosion |
|
|
|
=============== |
|
*/ |
|
void GAME_EXPORT R_ParticleExplosion( const vec3_t org ) |
|
{ |
|
particle_t *p; |
|
int i, j; |
|
|
|
for( i = 0; i < 1024; i++ ) |
|
{ |
|
p = R_AllocParticle( NULL ); |
|
if( !p ) return; |
|
|
|
p->die = cl.time + 5.0f; |
|
p->ramp = COM_RandomLong( 0, 3 ); |
|
p->color = ramp1[0]; |
|
|
|
for( j = 0; j < 3; j++ ) |
|
{ |
|
p->org[j] = org[j] + COM_RandomFloat( -16.0f, 16.0f ); |
|
p->vel[j] = COM_RandomFloat( -256.0f, 256.0f ); |
|
} |
|
|
|
if( i & 1 ) p->type = pt_explode; |
|
else p->type = pt_explode2; |
|
} |
|
} |
|
|
|
/* |
|
=============== |
|
R_ParticleExplosion2 |
|
|
|
=============== |
|
*/ |
|
void GAME_EXPORT R_ParticleExplosion2( const vec3_t org, int colorStart, int colorLength ) |
|
{ |
|
int i, j; |
|
int colorMod = 0, packedColor; |
|
particle_t *p; |
|
|
|
if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) |
|
packedColor = 255; // use old code for blob particles |
|
else packedColor = 0; |
|
|
|
for( i = 0; i < 512; i++ ) |
|
{ |
|
p = R_AllocParticle( NULL ); |
|
if( !p ) return; |
|
|
|
p->die = cl.time + 0.3f; |
|
p->color = colorStart + ( colorMod % colorLength ); |
|
p->packedColor = packedColor; |
|
colorMod++; |
|
|
|
p->type = pt_blob; |
|
|
|
for( j = 0; j < 3; j++ ) |
|
{ |
|
p->org[j] = org[j] + COM_RandomFloat( -16.0f, 16.0f ); |
|
p->vel[j] = COM_RandomFloat( -256.0f, 256.0f ); |
|
} |
|
} |
|
} |
|
|
|
/* |
|
=============== |
|
R_BlobExplosion |
|
|
|
=============== |
|
*/ |
|
void GAME_EXPORT R_BlobExplosion( const vec3_t org ) |
|
{ |
|
particle_t *p; |
|
int i, j, packedColor; |
|
|
|
if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) |
|
packedColor = 255; // use old code for blob particles |
|
else packedColor = 0; |
|
|
|
for( i = 0; i < 1024; i++ ) |
|
{ |
|
p = R_AllocParticle( NULL ); |
|
if( !p ) return; |
|
|
|
p->die = cl.time + COM_RandomFloat( 1.0f, 1.4f ); |
|
p->packedColor = packedColor; |
|
|
|
if( i & 1 ) |
|
{ |
|
p->type = pt_blob; |
|
p->color = COM_RandomLong( 66, 71 ); |
|
} |
|
else |
|
{ |
|
p->type = pt_blob2; |
|
p->color = COM_RandomLong( 150, 155 ); |
|
} |
|
|
|
for( j = 0; j < 3; j++ ) |
|
{ |
|
p->org[j] = org[j] + COM_RandomFloat( -16.0f, 16.0f ); |
|
p->vel[j] = COM_RandomFloat( -256.0f, 256.0f ); |
|
} |
|
} |
|
} |
|
|
|
/* |
|
=============== |
|
ParticleEffect |
|
|
|
PARTICLE_EFFECT on server |
|
=============== |
|
*/ |
|
void GAME_EXPORT R_RunParticleEffect( const vec3_t org, const vec3_t dir, int color, int count ) |
|
{ |
|
particle_t *p; |
|
int i; |
|
|
|
if( count == 1024 ) |
|
{ |
|
// rocket explosion |
|
R_ParticleExplosion( org ); |
|
return; |
|
} |
|
|
|
for( i = 0; i < count; i++ ) |
|
{ |
|
p = R_AllocParticle( NULL ); |
|
if( !p ) return; |
|
|
|
p->color = (color & ~7) + COM_RandomLong( 0, 7 ); |
|
p->die = cl.time + COM_RandomFloat( 0.1f, 0.4f ); |
|
p->type = pt_slowgrav; |
|
|
|
VectorAddScalar( org, COM_RandomFloat( -8.0f, 8.0f ), p->org ); |
|
VectorScale( dir, 15.0f, p->vel ); |
|
} |
|
} |
|
|
|
/* |
|
=============== |
|
R_Blood |
|
|
|
particle spray |
|
=============== |
|
*/ |
|
void GAME_EXPORT R_Blood( const vec3_t org, const vec3_t ndir, int pcolor, int speed ) |
|
{ |
|
vec3_t pos, dir, vec; |
|
float pspeed = speed * 3.0f; |
|
int i, j; |
|
particle_t *p; |
|
|
|
VectorNormalize2( ndir, dir ); |
|
|
|
for( i = 0; i < (speed / 2); i++ ) |
|
{ |
|
VectorAddScalar( org, COM_RandomFloat( -3.0f, 3.0f ), pos ); |
|
VectorAddScalar( dir, COM_RandomFloat( -0.06f, 0.06f ), vec ); |
|
|
|
for( j = 0; j < 7; j++ ) |
|
{ |
|
p = R_AllocParticle( NULL ); |
|
if( !p ) return; |
|
|
|
p->die = cl.time + 1.5f; |
|
p->color = pcolor + COM_RandomLong( 0, 9 ); |
|
p->type = pt_vox_grav; |
|
|
|
VectorAddScalar( pos, COM_RandomFloat( -1.0f, 1.0f ), p->org ); |
|
VectorScale( vec, pspeed, p->vel ); |
|
} |
|
} |
|
} |
|
|
|
/* |
|
=============== |
|
R_BloodStream |
|
|
|
particle spray 2 |
|
=============== |
|
*/ |
|
void GAME_EXPORT R_BloodStream( const vec3_t org, const vec3_t dir, int pcolor, int speed ) |
|
{ |
|
particle_t *p; |
|
int i, j; |
|
float arc; |
|
int accel = speed; // must be integer due to bug in GoldSrc |
|
|
|
for( arc = 0.05f, i = 0; i < 100; i++ ) |
|
{ |
|
p = R_AllocParticle( NULL ); |
|
if( !p ) return; |
|
|
|
p->die = cl.time + 2.0f; |
|
p->type = pt_vox_grav; |
|
p->color = pcolor + COM_RandomLong( 0, 9 ); |
|
|
|
VectorCopy( org, p->org ); |
|
VectorCopy( dir, p->vel ); |
|
|
|
p->vel[2] -= arc; |
|
arc -= 0.005f; |
|
VectorScale( p->vel, accel, p->vel ); |
|
accel -= 0.00001f; // so last few will drip |
|
} |
|
|
|
for( arc = 0.075f, i = 0; i < ( speed / 5 ); i++ ) |
|
{ |
|
float num; |
|
|
|
p = R_AllocParticle( NULL ); |
|
if( !p ) return; |
|
|
|
p->die = cl.time + 3.0f; |
|
p->color = pcolor + COM_RandomLong( 0, 9 ); |
|
p->type = pt_vox_slowgrav; |
|
|
|
VectorCopy( org, p->org ); |
|
VectorCopy( dir, p->vel ); |
|
|
|
p->vel[2] -= arc; |
|
arc -= 0.005f; |
|
|
|
num = COM_RandomFloat( 0.0f, 1.0f ); |
|
accel = speed * num; |
|
num *= 1.7f; |
|
|
|
VectorScale( p->vel, num, p->vel ); |
|
VectorScale( p->vel, accel, p->vel ); |
|
|
|
for( j = 0; j < 2; j++ ) |
|
{ |
|
p = R_AllocParticle( NULL ); |
|
if( !p ) return; |
|
|
|
p->die = cl.time + 3.0f; |
|
p->color = pcolor + COM_RandomLong( 0, 9 ); |
|
p->type = pt_vox_slowgrav; |
|
|
|
p->org[0] = org[0] + COM_RandomFloat( -1.0f, 1.0f ); |
|
p->org[1] = org[1] + COM_RandomFloat( -1.0f, 1.0f ); |
|
p->org[2] = org[2] + COM_RandomFloat( -1.0f, 1.0f ); |
|
|
|
VectorCopy( dir, p->vel ); |
|
p->vel[2] -= arc; |
|
|
|
VectorScale( p->vel, num, p->vel ); |
|
VectorScale( p->vel, accel, p->vel ); |
|
} |
|
} |
|
} |
|
|
|
/* |
|
=============== |
|
R_LavaSplash |
|
|
|
=============== |
|
*/ |
|
void GAME_EXPORT R_LavaSplash( const vec3_t org ) |
|
{ |
|
particle_t *p; |
|
float vel; |
|
vec3_t dir; |
|
int i, j, k; |
|
|
|
for( i = -16; i < 16; i++ ) |
|
{ |
|
for( j = -16; j <16; j++ ) |
|
{ |
|
for( k = 0; k < 1; k++ ) |
|
{ |
|
p = R_AllocParticle( NULL ); |
|
if( !p ) return; |
|
|
|
p->die = cl.time + COM_RandomFloat( 2.0f, 2.62f ); |
|
p->color = COM_RandomLong( 224, 231 ); |
|
p->type = pt_slowgrav; |
|
|
|
dir[0] = j * 8.0f + COM_RandomFloat( 0.0f, 7.0f ); |
|
dir[1] = i * 8.0f + COM_RandomFloat( 0.0f, 7.0f ); |
|
dir[2] = 256.0f; |
|
|
|
p->org[0] = org[0] + dir[0]; |
|
p->org[1] = org[1] + dir[1]; |
|
p->org[2] = org[2] + COM_RandomFloat( 0.0f, 63.0f ); |
|
|
|
VectorNormalize( dir ); |
|
vel = COM_RandomFloat( 50.0f, 113.0f ); |
|
VectorScale( dir, vel, p->vel ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
/* |
|
=============== |
|
R_ParticleBurst |
|
|
|
=============== |
|
*/ |
|
void GAME_EXPORT R_ParticleBurst( const vec3_t org, int size, int color, float life ) |
|
{ |
|
particle_t *p; |
|
vec3_t dir, dest; |
|
int i, j; |
|
float dist; |
|
|
|
for( i = 0; i < 32; i++ ) |
|
{ |
|
for( j = 0; j < 32; j++ ) |
|
{ |
|
p = R_AllocParticle( NULL ); |
|
if( !p ) return; |
|
|
|
p->die = cl.time + life + COM_RandomFloat( -0.5f, 0.5f ); |
|
p->color = color + COM_RandomLong( 0, 10 ); |
|
p->ramp = 1.0f; |
|
|
|
VectorCopy( org, p->org ); |
|
VectorAddScalar( org, COM_RandomFloat( -size, size ), dest ); |
|
VectorSubtract( dest, p->org, dir ); |
|
dist = VectorNormalizeLength( dir ); |
|
VectorScale( dir, ( dist / life ), p->vel ); |
|
} |
|
} |
|
} |
|
|
|
/* |
|
=============== |
|
R_LargeFunnel |
|
|
|
=============== |
|
*/ |
|
void GAME_EXPORT R_LargeFunnel( const vec3_t org, int reverse ) |
|
{ |
|
particle_t *p; |
|
float vel, dist; |
|
vec3_t dir, dest; |
|
int i, j; |
|
|
|
for( i = -8; i < 8; i++ ) |
|
{ |
|
for( j = -8; j < 8; j++ ) |
|
{ |
|
p = R_AllocParticle( NULL ); |
|
if( !p ) return; |
|
|
|
dest[0] = (i * 32.0f) + org[0]; |
|
dest[1] = (j * 32.0f) + org[1]; |
|
dest[2] = org[2] + COM_RandomFloat( 100.0f, 800.0f ); |
|
|
|
if( reverse ) |
|
{ |
|
VectorCopy( org, p->org ); |
|
VectorSubtract( dest, p->org, dir ); |
|
} |
|
else |
|
{ |
|
VectorCopy( dest, p->org ); |
|
VectorSubtract( org, p->org, dir ); |
|
} |
|
|
|
vel = dest[2] / 8.0f; |
|
if( vel < 64.0f ) vel = 64.0f; |
|
|
|
dist = VectorNormalizeLength( dir ); |
|
vel += COM_RandomFloat( 64.0f, 128.0f ); |
|
VectorScale( dir, vel, p->vel ); |
|
p->die = cl.time + (dist / vel ); |
|
p->color = 244; // green color |
|
} |
|
} |
|
} |
|
|
|
/* |
|
=============== |
|
R_TeleportSplash |
|
|
|
=============== |
|
*/ |
|
void GAME_EXPORT R_TeleportSplash( const vec3_t org ) |
|
{ |
|
particle_t *p; |
|
vec3_t dir; |
|
float vel; |
|
int i, j, k; |
|
|
|
for( i = -16; i < 16; i += 4 ) |
|
{ |
|
for( j = -16; j < 16; j += 4 ) |
|
{ |
|
for( k = -24; k < 32; k += 4 ) |
|
{ |
|
p = R_AllocParticle( NULL ); |
|
if( !p ) return; |
|
|
|
p->die = cl.time + COM_RandomFloat( 0.2f, 0.34f ); |
|
p->color = COM_RandomLong( 7, 14 ); |
|
p->type = pt_slowgrav; |
|
|
|
dir[0] = j * 8.0f; |
|
dir[1] = i * 8.0f; |
|
dir[2] = k * 8.0f; |
|
|
|
p->org[0] = org[0] + i + COM_RandomFloat( 0.0f, 3.0f ); |
|
p->org[1] = org[1] + j + COM_RandomFloat( 0.0f, 3.0f ); |
|
p->org[2] = org[2] + k + COM_RandomFloat( 0.0f, 3.0f ); |
|
|
|
VectorNormalize( dir ); |
|
vel = COM_RandomFloat( 50.0f, 113.0f ); |
|
VectorScale( dir, vel, p->vel ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
/* |
|
=============== |
|
R_RocketTrail |
|
|
|
=============== |
|
*/ |
|
void GAME_EXPORT R_RocketTrail( vec3_t start, vec3_t end, int type ) |
|
{ |
|
vec3_t vec, right, up; |
|
static int tracercount; |
|
float s, c, x, y; |
|
float len, dec; |
|
particle_t *p; |
|
|
|
VectorSubtract( end, start, vec ); |
|
len = VectorNormalizeLength( vec ); |
|
|
|
if( type == 7 ) |
|
{ |
|
VectorVectors( vec, right, up ); |
|
} |
|
|
|
if( type < 128 ) |
|
{ |
|
dec = 3.0f; |
|
} |
|
else |
|
{ |
|
dec = 1.0f; |
|
type -= 128; |
|
} |
|
|
|
VectorScale( vec, dec, vec ); |
|
|
|
while( len > 0 ) |
|
{ |
|
len -= dec; |
|
|
|
p = R_AllocParticle( NULL ); |
|
if( !p ) return; |
|
|
|
p->die = cl.time + 2.0f; |
|
|
|
switch( type ) |
|
{ |
|
case 0: // rocket trail |
|
p->ramp = COM_RandomLong( 0, 3 ); |
|
p->color = ramp3[(int)p->ramp]; |
|
p->type = pt_fire; |
|
VectorAddScalar( start, COM_RandomFloat( -3.0f, 3.0f ), p->org ); |
|
break; |
|
case 1: // smoke smoke |
|
p->ramp = COM_RandomLong( 2, 5 ); |
|
p->color = ramp3[(int)p->ramp]; |
|
p->type = pt_fire; |
|
VectorAddScalar( start, COM_RandomFloat( -3.0f, 3.0f ), p->org ); |
|
break; |
|
case 2: // blood |
|
p->type = pt_grav; |
|
p->color = COM_RandomLong( 67, 74 ); |
|
VectorAddScalar( start, COM_RandomFloat( -3.0f, 3.0f ), p->org ); |
|
break; |
|
case 3: |
|
case 5: // tracer |
|
p->die = cl.time + 0.5f; |
|
|
|
if( type == 3 ) p->color = 52 + (( tracercount & 4 )<<1 ); |
|
else p->color = 230 + (( tracercount & 4 )<<1 ); |
|
|
|
VectorCopy( start, p->org ); |
|
tracercount++; |
|
|
|
if( FBitSet( tracercount, 1 )) |
|
{ |
|
p->vel[0] = 30.0f * vec[1]; |
|
p->vel[1] = 30.0f * -vec[0]; |
|
} |
|
else |
|
{ |
|
p->vel[0] = 30.0f * -vec[1]; |
|
p->vel[1] = 30.0f * vec[0]; |
|
} |
|
break; |
|
case 4: // slight blood |
|
p->type = pt_grav; |
|
p->color = COM_RandomLong( 67, 70 ); |
|
VectorAddScalar( start, COM_RandomFloat( -3.0f, 3.0f ), p->org ); |
|
len -= 3.0f; |
|
break; |
|
case 6: // voor trail |
|
p->color = COM_RandomLong( 152, 155 ); |
|
p->die += 0.3f; |
|
VectorAddScalar( start, COM_RandomFloat( -8.0f, 8.0f ), p->org ); |
|
break; |
|
case 7: // explosion tracer |
|
x = COM_RandomLong( 0, 65535 ); |
|
y = COM_RandomLong( 8, 16 ); |
|
SinCos( x, &s, &c ); |
|
s *= y; |
|
c *= y; |
|
|
|
VectorMAMAM( 1.0f, start, s, right, c, up, p->org ); |
|
VectorSubtract( start, p->org, p->vel ); |
|
VectorScale( p->vel, 2.0f, p->vel ); |
|
VectorMA( p->vel, COM_RandomFloat( 96.0f, 111.0f ), vec, p->vel ); |
|
p->ramp = COM_RandomLong( 0, 3 ); |
|
p->color = ramp3[(int)p->ramp]; |
|
p->type = pt_explode2; |
|
break; |
|
default: |
|
// just build line to show error |
|
VectorCopy( start, p->org ); |
|
break; |
|
} |
|
|
|
VectorAdd( start, vec, start ); |
|
} |
|
} |
|
|
|
/* |
|
================ |
|
R_ParticleLine |
|
|
|
================ |
|
*/ |
|
void GAME_EXPORT R_ParticleLine( const vec3_t start, const vec3_t end, byte r, byte g, byte b, float life ) |
|
{ |
|
int pcolor; |
|
|
|
pcolor = R_LookupColor( r, g, b ); |
|
PM_ParticleLine( start, end, pcolor, life, 0 ); |
|
} |
|
|
|
/* |
|
================ |
|
R_ParticleBox |
|
|
|
================ |
|
*/ |
|
void GAME_EXPORT R_ParticleBox( const vec3_t absmin, const vec3_t absmax, byte r, byte g, byte b, float life ) |
|
{ |
|
vec3_t mins, maxs; |
|
vec3_t origin; |
|
int pcolor; |
|
|
|
pcolor = R_LookupColor( r, g, b ); |
|
|
|
VectorAverage( absmax, absmin, origin ); |
|
VectorSubtract( absmax, origin, maxs ); |
|
VectorSubtract( absmin, origin, mins ); |
|
|
|
PM_DrawBBox( mins, maxs, origin, pcolor, life ); |
|
} |
|
|
|
/* |
|
================ |
|
R_ShowLine |
|
|
|
================ |
|
*/ |
|
void GAME_EXPORT R_ShowLine( const vec3_t start, const vec3_t end ) |
|
{ |
|
vec3_t dir, org; |
|
float len; |
|
particle_t *p; |
|
|
|
VectorSubtract( end, start, dir ); |
|
len = VectorNormalizeLength( dir ); |
|
VectorScale( dir, 5.0f, dir ); |
|
VectorCopy( start, org ); |
|
|
|
while( len > 0 ) |
|
{ |
|
len -= 5.0f; |
|
|
|
p = R_AllocParticle( NULL ); |
|
if( !p ) return; |
|
|
|
p->die = cl.time + 30; |
|
p->color = 75; |
|
|
|
VectorCopy( org, p->org ); |
|
VectorAdd( org, dir, org ); |
|
} |
|
} |
|
|
|
/* |
|
=============== |
|
R_BulletImpactParticles |
|
|
|
=============== |
|
*/ |
|
void GAME_EXPORT R_BulletImpactParticles( const vec3_t pos ) |
|
{ |
|
int i, quantity; |
|
int color; |
|
float dist; |
|
vec3_t dir; |
|
particle_t *p; |
|
|
|
VectorSubtract( pos, refState.vieworg, dir ); |
|
dist = VectorLength( dir ); |
|
if( dist > 1000.0f ) dist = 1000.0f; |
|
|
|
quantity = (1000.0f - dist) / 100.0f; |
|
if( quantity == 0 ) quantity = 1; |
|
|
|
color = 3 - ((30 * quantity) / 100 ); |
|
R_SparkStreaks( pos, 2, -200, 200 ); |
|
|
|
for( i = 0; i < quantity * 4; i++ ) |
|
{ |
|
p = R_AllocParticle( NULL ); |
|
if( !p ) return; |
|
|
|
VectorCopy( pos, p->org); |
|
|
|
p->vel[0] = COM_RandomFloat( -1.0f, 1.0f ); |
|
p->vel[1] = COM_RandomFloat( -1.0f, 1.0f ); |
|
p->vel[2] = COM_RandomFloat( -1.0f, 1.0f ); |
|
VectorScale( p->vel, COM_RandomFloat( 50.0f, 100.0f ), p->vel ); |
|
|
|
p->die = cl.time + 0.5; |
|
p->color = 3 - color; |
|
p->type = pt_grav; |
|
} |
|
} |
|
|
|
/* |
|
=============== |
|
R_FlickerParticles |
|
|
|
=============== |
|
*/ |
|
void GAME_EXPORT R_FlickerParticles( const vec3_t org ) |
|
{ |
|
particle_t *p; |
|
int i; |
|
|
|
for( i = 0; i < 15; i++ ) |
|
{ |
|
p = R_AllocParticle( NULL ); |
|
if( !p ) return; |
|
|
|
VectorCopy( org, p->org ); |
|
p->vel[0] = COM_RandomFloat( -32.0f, 32.0f ); |
|
p->vel[1] = COM_RandomFloat( -32.0f, 32.0f ); |
|
p->vel[2] = COM_RandomFloat( 80.0f, 143.0f ); |
|
|
|
p->die = cl.time + 2.0f; |
|
p->type = pt_blob2; |
|
p->color = 254; |
|
} |
|
} |
|
|
|
/* |
|
=============== |
|
R_StreakSplash |
|
|
|
create a splash of streaks |
|
=============== |
|
*/ |
|
void GAME_EXPORT R_StreakSplash( const vec3_t pos, const vec3_t dir, int color, int count, float speed, int velocityMin, int velocityMax ) |
|
{ |
|
vec3_t vel, vel2; |
|
particle_t *p; |
|
int i; |
|
|
|
VectorScale( dir, speed, vel ); |
|
|
|
for( i = 0; i < count; i++ ) |
|
{ |
|
VectorAddScalar( vel, COM_RandomFloat( velocityMin, velocityMax ), vel2 ); |
|
p = R_AllocTracer( pos, vel2, COM_RandomFloat( 0.1f, 0.5f )); |
|
if( !p ) return; |
|
|
|
p->type = pt_grav; |
|
p->color = color; |
|
p->ramp = 1.0f; |
|
} |
|
} |
|
|
|
/* |
|
=============== |
|
R_DebugParticle |
|
|
|
just for debug purposes |
|
=============== |
|
*/ |
|
void R_DebugParticle( const vec3_t pos, byte r, byte g, byte b ) |
|
{ |
|
particle_t *p; |
|
|
|
p = R_AllocParticle( NULL ); |
|
if( !p ) return; |
|
|
|
VectorCopy( pos, p->org ); |
|
p->color = R_LookupColor( r, g, b ); |
|
p->die = cl.time + 0.01f; |
|
} |
|
|
|
/* |
|
=============== |
|
CL_Particle |
|
|
|
pmove debugging particle |
|
=============== |
|
*/ |
|
void CL_Particle( const vec3_t org, int color, float life, int zpos, int zvel ) |
|
{ |
|
particle_t *p; |
|
|
|
p = R_AllocParticle( NULL ); |
|
if( !p ) return; |
|
|
|
if( org ) VectorCopy( org, p->org ); |
|
p->die = cl.time + life; |
|
p->vel[2] += zvel; // ??? |
|
p->color = color; |
|
} |
|
|
|
/* |
|
=============== |
|
R_TracerEffect |
|
|
|
=============== |
|
*/ |
|
void GAME_EXPORT R_TracerEffect( const vec3_t start, const vec3_t end ) |
|
{ |
|
vec3_t pos, vel, dir; |
|
float len, speed; |
|
float offset; |
|
|
|
speed = Q_max( tracerspeed->value, 3.0f ); |
|
|
|
VectorSubtract( end, start, dir ); |
|
len = VectorLength( dir ); |
|
if( len == 0.0f ) return; |
|
|
|
VectorScale( dir, 1.0f / len, dir ); // normalize |
|
offset = COM_RandomFloat( -10.0f, 9.0f ) + traceroffset->value; |
|
VectorScale( dir, offset, vel ); |
|
VectorAdd( start, vel, pos ); |
|
VectorScale( dir, speed, vel ); |
|
|
|
R_AllocTracer( pos, vel, len / speed ); |
|
} |
|
|
|
/* |
|
=============== |
|
R_UserTracerParticle |
|
|
|
=============== |
|
*/ |
|
void GAME_EXPORT R_UserTracerParticle( float *org, float *vel, float life, int colorIndex, float length, byte deathcontext, void (*deathfunc)( particle_t *p )) |
|
{ |
|
particle_t *p; |
|
|
|
if( colorIndex < 0 ) |
|
return; |
|
|
|
if(( p = R_AllocTracer( org, vel, life )) != NULL ) |
|
{ |
|
p->context = deathcontext; |
|
p->deathfunc = deathfunc; |
|
p->color = colorIndex; |
|
p->ramp = length; |
|
} |
|
} |
|
|
|
/* |
|
=============== |
|
R_TracerParticles |
|
|
|
allow more customization |
|
=============== |
|
*/ |
|
particle_t *R_TracerParticles( float *org, float *vel, float life ) |
|
{ |
|
return R_AllocTracer( org, vel, life ); |
|
} |
|
|
|
/* |
|
=============== |
|
R_SparkStreaks |
|
|
|
create a streak tracers |
|
=============== |
|
*/ |
|
void GAME_EXPORT R_SparkStreaks( const vec3_t pos, int count, int velocityMin, int velocityMax ) |
|
{ |
|
particle_t *p; |
|
vec3_t vel; |
|
int i; |
|
|
|
for( i = 0; i<count; i++ ) |
|
{ |
|
vel[0] = COM_RandomFloat( velocityMin, velocityMax ); |
|
vel[1] = COM_RandomFloat( velocityMin, velocityMax ); |
|
vel[2] = COM_RandomFloat( velocityMin, velocityMax ); |
|
|
|
p = R_AllocTracer( pos, vel, COM_RandomFloat( 0.1f, 0.5f )); |
|
if( !p ) return; |
|
|
|
p->color = 5; |
|
p->type = pt_grav; |
|
p->ramp = 0.5f; |
|
} |
|
} |
|
|
|
/* |
|
=============== |
|
R_Implosion |
|
|
|
make implosion tracers |
|
=============== |
|
*/ |
|
void GAME_EXPORT R_Implosion( const vec3_t end, float radius, int count, float life ) |
|
{ |
|
float dist = ( radius / 100.0f ); |
|
vec3_t start, temp, vel; |
|
float factor; |
|
particle_t *p; |
|
int i; |
|
|
|
if( life <= 0.0f ) life = 0.1f; // to avoid divide by zero |
|
factor = -1.0 / life; |
|
|
|
for ( i = 0; i < count; i++ ) |
|
{ |
|
temp[0] = dist * COM_RandomFloat( -100.0f, 100.0f ); |
|
temp[1] = dist * COM_RandomFloat( -100.0f, 100.0f ); |
|
temp[2] = dist * COM_RandomFloat( 0.0f, 100.0f ); |
|
VectorScale( temp, factor, vel ); |
|
VectorAdd( temp, end, start ); |
|
|
|
if(( p = R_AllocTracer( start, vel, life )) == NULL ) |
|
return; |
|
|
|
p->type = pt_explode; |
|
} |
|
} |
|
|
|
|
|
/* |
|
============== |
|
R_FreeDeadParticles |
|
|
|
Free particles that time has expired |
|
============== |
|
*/ |
|
void R_FreeDeadParticles( particle_t **ppparticles ) |
|
{ |
|
particle_t *p, *kill; |
|
|
|
// kill all the ones hanging direcly off the base pointer |
|
while( 1 ) |
|
{ |
|
kill = *ppparticles; |
|
if( kill && kill->die < cl.time ) |
|
{ |
|
if( kill->deathfunc ) |
|
kill->deathfunc( kill ); |
|
kill->deathfunc = NULL; |
|
*ppparticles = kill->next; |
|
kill->next = cl_free_particles; |
|
cl_free_particles = kill; |
|
continue; |
|
} |
|
break; |
|
} |
|
|
|
// kill off all the others |
|
for( p = *ppparticles; p; p = p->next ) |
|
{ |
|
while( 1 ) |
|
{ |
|
kill = p->next; |
|
if( kill && kill->die < cl.time ) |
|
{ |
|
if( kill->deathfunc ) |
|
kill->deathfunc( kill ); |
|
kill->deathfunc = NULL; |
|
p->next = kill->next; |
|
kill->next = cl_free_particles; |
|
cl_free_particles = kill; |
|
continue; |
|
} |
|
break; |
|
} |
|
} |
|
} |
|
|
|
/* |
|
=============== |
|
CL_ReadPointFile_f |
|
|
|
=============== |
|
*/ |
|
void CL_ReadPointFile_f( void ) |
|
{ |
|
byte *afile; |
|
char *pfile; |
|
vec3_t org; |
|
int count; |
|
particle_t *p; |
|
char filename[64]; |
|
string token; |
|
|
|
Q_snprintf( filename, sizeof( filename ), "maps/%s.pts", clgame.mapname ); |
|
afile = FS_LoadFile( filename, NULL, false ); |
|
|
|
if( !afile ) |
|
{ |
|
Con_Printf( S_ERROR "couldn't open %s\n", filename ); |
|
return; |
|
} |
|
|
|
Con_Printf( "Reading %s...\n", filename ); |
|
|
|
count = 0; |
|
pfile = (char *)afile; |
|
|
|
while( 1 ) |
|
{ |
|
pfile = COM_ParseFile( pfile, token, sizeof( token )); |
|
if( !pfile ) break; |
|
org[0] = Q_atof( token ); |
|
|
|
pfile = COM_ParseFile( pfile, token, sizeof( token )); |
|
if( !pfile ) break; |
|
org[1] = Q_atof( token ); |
|
|
|
pfile = COM_ParseFile( pfile, token, sizeof( token )); |
|
if( !pfile ) break; |
|
org[2] = Q_atof( token ); |
|
|
|
count++; |
|
|
|
if( !cl_free_particles ) |
|
{ |
|
Con_Printf( S_ERROR "not enough free particles!\n" ); |
|
break; |
|
} |
|
|
|
// NOTE: can't use R_AllocParticle because this command |
|
// may be executed from the console, while frametime is 0 |
|
p = cl_free_particles; |
|
cl_free_particles = p->next; |
|
p->next = cl_active_particles; |
|
cl_active_particles = p; |
|
|
|
p->ramp = 0; |
|
p->type = pt_static; |
|
p->die = cl.time + 99999; |
|
p->color = (-count) & 15; |
|
VectorCopy( org, p->org ); |
|
VectorClear( p->vel ); |
|
} |
|
|
|
Mem_Free( afile ); |
|
|
|
if( count ) Con_Printf( "%i points read\n", count ); |
|
else Con_Printf( "map %s has no leaks!\n", clgame.mapname ); |
|
} |
|
|
|
void CL_FreeDeadBeams( void ) |
|
{ |
|
BEAM *pBeam, *pNext, *pPrev = NULL; |
|
// draw temporary entity beams |
|
for( pBeam = cl_active_beams; pBeam; pBeam = pNext ) |
|
{ |
|
// need to store the next one since we may delete this one |
|
pNext = pBeam->next; |
|
|
|
// retire old beams |
|
if( CL_BeamAttemptToDie( pBeam )) |
|
{ |
|
// reset links |
|
if( pPrev ) pPrev->next = pNext; |
|
else cl_active_beams = pNext; |
|
|
|
// free the beam |
|
R_BeamFree( pBeam ); |
|
|
|
pBeam = NULL; |
|
continue; |
|
} |
|
|
|
pPrev = pBeam; |
|
} |
|
} |
|
|
|
void CL_DrawEFX( float time, qboolean fTrans ) |
|
{ |
|
CL_FreeDeadBeams(); |
|
if( CVAR_TO_BOOL( cl_draw_beams )) |
|
ref.dllFuncs.CL_DrawBeams( fTrans, cl_active_beams ); |
|
|
|
if( fTrans ) |
|
{ |
|
R_FreeDeadParticles( &cl_active_particles ); |
|
if( CVAR_TO_BOOL( cl_draw_particles )) |
|
ref.dllFuncs.CL_DrawParticles( time, cl_active_particles, PART_SIZE ); |
|
R_FreeDeadParticles( &cl_active_tracers ); |
|
if( CVAR_TO_BOOL( cl_draw_tracers )) |
|
ref.dllFuncs.CL_DrawTracers( time, cl_active_tracers ); |
|
} |
|
} |
|
|
|
void CL_ThinkParticle( double frametime, particle_t *p ) |
|
{ |
|
float time3 = 15.0f * frametime; |
|
float time2 = 10.0f * frametime; |
|
float time1 = 5.0f * frametime; |
|
float dvel = 4.0f * frametime; |
|
float grav = frametime * clgame.movevars.gravity * 0.05f; |
|
|
|
|
|
if( p->type != pt_clientcustom ) |
|
{ |
|
// update position. |
|
VectorMA( p->org, frametime, p->vel, p->org ); |
|
} |
|
|
|
switch( p->type ) |
|
{ |
|
case pt_static: |
|
break; |
|
case pt_fire: |
|
p->ramp += time1; |
|
if( p->ramp >= 6.0f ) p->die = -1.0f; |
|
else p->color = ramp3[(int)p->ramp]; |
|
p->vel[2] += grav; |
|
break; |
|
case pt_explode: |
|
p->ramp += time2; |
|
if( p->ramp >= 8.0f ) p->die = -1.0f; |
|
else p->color = ramp1[(int)p->ramp]; |
|
VectorMA( p->vel, dvel, p->vel, p->vel ); |
|
p->vel[2] -= grav; |
|
break; |
|
case pt_explode2: |
|
p->ramp += time3; |
|
if( p->ramp >= 8.0f ) p->die = -1.0f; |
|
else p->color = ramp2[(int)p->ramp]; |
|
VectorMA( p->vel,-frametime, p->vel, p->vel ); |
|
p->vel[2] -= grav; |
|
break; |
|
case pt_blob: |
|
if( p->packedColor == 255 ) |
|
{ |
|
// normal blob explosion |
|
VectorMA( p->vel, dvel, p->vel, p->vel ); |
|
p->vel[2] -= grav; |
|
break; |
|
} |
|
// intentionally fallthrough |
|
case pt_blob2: |
|
if( p->packedColor == 255 ) |
|
{ |
|
// normal blob explosion |
|
p->vel[0] -= p->vel[0] * dvel; |
|
p->vel[1] -= p->vel[1] * dvel; |
|
p->vel[2] -= grav; |
|
} |
|
else |
|
{ |
|
p->ramp += time2; |
|
if( p->ramp >= 9.0f ) p->ramp = 0.0f; |
|
p->color = gSparkRamp[(int)p->ramp]; |
|
VectorMA( p->vel, -frametime * 0.5f, p->vel, p->vel ); |
|
p->type = COM_RandomLong( 0, 3 ) ? pt_blob : pt_blob2; |
|
p->vel[2] -= grav * 5.0f; |
|
} |
|
break; |
|
case pt_grav: |
|
p->vel[2] -= grav * 20.0f; |
|
break; |
|
case pt_slowgrav: |
|
p->vel[2] -= grav; |
|
break; |
|
case pt_vox_grav: |
|
p->vel[2] -= grav * 8.0f; |
|
break; |
|
case pt_vox_slowgrav: |
|
p->vel[2] -= grav * 4.0f; |
|
break; |
|
case pt_clientcustom: |
|
if( p->callback ) |
|
p->callback( p, frametime ); |
|
break; |
|
} |
|
} |
|
|
|
|