#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 }; static CVAR_DEFINE_AUTO( tracerspeed, "6000", 0, "tracer speed" ); static CVAR_DEFINE_AUTO( tracerlength, "0.8", 0, "tracer length factor" ); static CVAR_DEFINE_AUTO( traceroffset, "30", 0, "tracer starting offset" ); 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 ); } Cvar_RegisterVariable( &tracerspeed ); Cvar_RegisterVariable( &tracerlength ); Cvar_RegisterVariable( &traceroffset ); } /* ================ 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_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( cl_clientframetime() == 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 ================ */ static 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( cl_clientframetime() == 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 ============== */ static 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 ============== */ static 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 ============== */ static 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 ndir, int pcolor, int speed ) { particle_t *p; int i, j; float arc; int accel = speed; // must be integer due to bug in GoldSrc vec3_t dir; VectorNormalize2( ndir, dir ); 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; icolor = 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 ); } static 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( cl_draw_beams.value ) ref.dllFuncs.CL_DrawBeams( fTrans, cl_active_beams ); if( fTrans ) { R_FreeDeadParticles( &cl_active_particles ); if( cl_draw_particles.value ) ref.dllFuncs.CL_DrawParticles( time, cl_active_particles, PART_SIZE ); R_FreeDeadParticles( &cl_active_tracers ); if( cl_draw_tracers.value ) 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; } }