xash3d-fwgs/engine/client/cl_tent.c
Gleb Mazovetskiy 5e0a0765ce Trim all trailing whitespace
The `.editorconfig` file in this repo is configured to trim all trailing
whitespace regardless of whether the line is modified.

Trims all trailing whitespace in the repository to make the codebase easier
to work with in editors that respect `.editorconfig`.

`git blame` becomes less useful on these lines but it already isn't very useful.

Commands:

```
find . -type f -name '*.h' -exec sed --in-place 's/[[:space:]]\+$//' {} \+
find . -type f -name '*.c' -exec sed --in-place 's/[[:space:]]\+$//' {} \+
```
2021-01-04 20:55:10 +03:00

3051 lines
76 KiB
C

/*
cl_tent.c - temp entity effects management
Copyright (C) 2009 Uncle Mike
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#include "common.h"
#include "client.h"
#include "r_efx.h"
#include "entity_types.h"
#include "triangleapi.h"
#include "cl_tent.h"
#include "pm_local.h"
#include "studio.h"
#include "wadfile.h" // acess decal size
#include "sound.h"
/*
==============================================================
TEMPENTS MANAGEMENT
==============================================================
*/
#define FLASHLIGHT_DISTANCE 2000 // in units
#define SHARD_VOLUME 12.0f // on shard ever n^3 units
#define MAX_MUZZLEFLASH 3
TEMPENTITY *cl_active_tents;
TEMPENTITY *cl_free_tents;
TEMPENTITY *cl_tempents = NULL; // entities pool
model_t *cl_sprite_muzzleflash[MAX_MUZZLEFLASH]; // muzzle flashes
model_t *cl_sprite_dot = NULL;
model_t *cl_sprite_ricochet = NULL;
model_t *cl_sprite_shell = NULL;
model_t *cl_sprite_glow = NULL;
const char *cl_default_sprites[] =
{
// built-in sprites
"sprites/muzzleflash1.spr",
"sprites/muzzleflash2.spr",
"sprites/muzzleflash3.spr",
"sprites/dot.spr",
"sprites/animglow01.spr",
"sprites/richo1.spr",
"sprites/shellchrome.spr",
};
const char *cl_player_shell_sounds[] =
{
"player/pl_shell1.wav",
"player/pl_shell2.wav",
"player/pl_shell3.wav",
};
const char *cl_weapon_shell_sounds[] =
{
"weapons/sshell1.wav",
"weapons/sshell2.wav",
"weapons/sshell3.wav",
};
const char *cl_ricochet_sounds[] =
{
"weapons/ric1.wav",
"weapons/ric2.wav",
"weapons/ric3.wav",
"weapons/ric4.wav",
"weapons/ric5.wav",
};
const char *cl_explode_sounds[] =
{
"weapons/explode3.wav",
"weapons/explode4.wav",
"weapons/explode5.wav",
};
/*
================
CL_LoadClientSprites
INTERNAL RESOURCE
================
*/
void CL_LoadClientSprites( void )
{
cl_sprite_muzzleflash[0] = CL_LoadClientSprite( cl_default_sprites[0] );
cl_sprite_muzzleflash[1] = CL_LoadClientSprite( cl_default_sprites[1] );
cl_sprite_muzzleflash[2] = CL_LoadClientSprite( cl_default_sprites[2] );
cl_sprite_dot = CL_LoadClientSprite( cl_default_sprites[3] );
cl_sprite_glow = CL_LoadClientSprite( cl_default_sprites[4] );
cl_sprite_ricochet = CL_LoadClientSprite( cl_default_sprites[5] );
cl_sprite_shell = CL_LoadClientSprite( cl_default_sprites[6] );
}
/*
================
CL_AddClientResource
add client-side resource to list
================
*/
void CL_AddClientResource( const char *filename, int type )
{
resource_t *p, *pResource;
for( p = cl.resourcesneeded.pNext; p != &cl.resourcesneeded; p = p->pNext )
{
if( !Q_stricmp( p->szFileName, filename ))
break;
}
if( p != &cl.resourcesneeded )
return; // already in list?
pResource = Mem_Calloc( cls.mempool, sizeof( resource_t ));
Q_strncpy( pResource->szFileName, filename, sizeof( pResource->szFileName ));
pResource->type = type;
pResource->nIndex = -1; // client resource marker
pResource->nDownloadSize = 1;
pResource->ucFlags |= RES_WASMISSING;
CL_AddToResourceList( pResource, &cl.resourcesneeded );
}
/*
================
CL_AddClientResources
client resources not precached by server
================
*/
void CL_AddClientResources( void )
{
char filepath[MAX_QPATH];
int i;
// don't request resources from localhost or in quake-compatibility mode
if( cl.maxclients <= 1 || Host_IsQuakeCompatible( ))
return;
// check sprites first
for( i = 0; i < ARRAYSIZE( cl_default_sprites ); i++ )
{
if( !FS_FileExists( cl_default_sprites[i], false ))
CL_AddClientResource( cl_default_sprites[i], t_model );
}
// then check sounds
for( i = 0; i < ARRAYSIZE( cl_player_shell_sounds ); i++ )
{
Q_snprintf( filepath, sizeof( filepath ), DEFAULT_SOUNDPATH "%s", cl_player_shell_sounds[i] );
if( !FS_FileExists( filepath, false ))
CL_AddClientResource( cl_player_shell_sounds[i], t_sound );
}
for( i = 0; i < ARRAYSIZE( cl_weapon_shell_sounds ); i++ )
{
Q_snprintf( filepath, sizeof( filepath ), DEFAULT_SOUNDPATH "%s", cl_weapon_shell_sounds[i] );
if( !FS_FileExists( filepath, false ))
CL_AddClientResource( cl_weapon_shell_sounds[i], t_sound );
}
for( i = 0; i < ARRAYSIZE( cl_explode_sounds ); i++ )
{
Q_snprintf( filepath, sizeof( filepath ), DEFAULT_SOUNDPATH "%s", cl_explode_sounds[i] );
if( !FS_FileExists( filepath, false ))
CL_AddClientResource( cl_explode_sounds[i], t_sound );
}
#if 0 // ric sounds was precached by server-side
for( i = 0; i < ARRAYSIZE( cl_ricochet_sounds ); i++ )
{
Q_snprintf( filepath, sizeof( filepath ), DEFAULT_SOUNDPATH "%s", cl_ricochet_sounds[i] );
if( !FS_FileExists( filepath, false ))
CL_AddClientResource( cl_ricochet_sounds[i], t_sound );
}
#endif
}
/*
================
CL_InitTempents
================
*/
void CL_InitTempEnts( void )
{
cl_tempents = Mem_Calloc( cls.mempool, sizeof( TEMPENTITY ) * GI->max_tents );
CL_ClearTempEnts();
// load tempent sprites (glowshell, muzzleflashes etc)
CL_LoadClientSprites ();
}
/*
================
CL_ClearTempEnts
================
*/
void CL_ClearTempEnts( void )
{
int i;
if( !cl_tempents ) return;
for( i = 0; i < GI->max_tents - 1; i++ )
{
cl_tempents[i].next = &cl_tempents[i+1];
cl_tempents[i].entity.trivial_accept = INVALID_HANDLE;
}
cl_tempents[GI->max_tents-1].next = NULL;
cl_free_tents = cl_tempents;
cl_active_tents = NULL;
}
/*
================
CL_FreeTempEnts
================
*/
void CL_FreeTempEnts( void )
{
if( cl_tempents )
Mem_Free( cl_tempents );
cl_tempents = NULL;
}
/*
==============
CL_PrepareTEnt
set default values
==============
*/
void CL_PrepareTEnt( TEMPENTITY *pTemp, model_t *pmodel )
{
int frameCount = 0;
int modelIndex = 0;
int modelHandle = pTemp->entity.trivial_accept;
memset( pTemp, 0, sizeof( *pTemp ));
// use these to set per-frame and termination conditions / actions
pTemp->entity.trivial_accept = modelHandle; // keep unchanged
pTemp->flags = FTENT_NONE;
pTemp->die = cl.time + 0.75f;
if( pmodel ) frameCount = pmodel->numframes;
else pTemp->flags |= FTENT_NOMODEL;
pTemp->entity.curstate.modelindex = modelIndex;
pTemp->entity.curstate.rendermode = kRenderNormal;
pTemp->entity.curstate.renderfx = kRenderFxNone;
pTemp->entity.curstate.rendercolor.r = 255;
pTemp->entity.curstate.rendercolor.g = 255;
pTemp->entity.curstate.rendercolor.b = 255;
pTemp->frameMax = Q_max( 0, frameCount - 1 );
pTemp->entity.curstate.renderamt = 255;
pTemp->entity.curstate.body = 0;
pTemp->entity.curstate.skin = 0;
pTemp->entity.model = pmodel;
pTemp->fadeSpeed = 0.5f;
pTemp->hitSound = 0;
pTemp->clientIndex = 0;
pTemp->bounceFactor = 1;
pTemp->entity.curstate.scale = 1.0f;
}
/*
==============
CL_TempEntPlaySound
play collide sound
==============
*/
void CL_TempEntPlaySound( TEMPENTITY *pTemp, float damp )
{
float fvol;
char soundname[32];
qboolean isshellcasing = false;
int zvel;
Assert( pTemp != NULL );
fvol = 0.8f;
switch( pTemp->hitSound )
{
case BOUNCE_GLASS:
Q_snprintf( soundname, sizeof( soundname ), "debris/glass%i.wav", COM_RandomLong( 1, 4 ));
break;
case BOUNCE_METAL:
Q_snprintf( soundname, sizeof( soundname ), "debris/metal%i.wav", COM_RandomLong( 1, 6 ));
break;
case BOUNCE_FLESH:
Q_snprintf( soundname, sizeof( soundname ), "debris/flesh%i.wav", COM_RandomLong( 1, 7 ));
break;
case BOUNCE_WOOD:
Q_snprintf( soundname, sizeof( soundname ), "debris/wood%i.wav", COM_RandomLong( 1, 4 ));
break;
case BOUNCE_SHRAP:
Q_strncpy( soundname, cl_ricochet_sounds[COM_RandomLong( 0, 4 )], sizeof( soundname ) );
break;
case BOUNCE_SHOTSHELL:
Q_strncpy( soundname, cl_weapon_shell_sounds[COM_RandomLong( 0, 2 )], sizeof( soundname ) );
isshellcasing = true; // shell casings have different playback parameters
fvol = 0.5f;
break;
case BOUNCE_SHELL:
Q_strncpy( soundname, cl_player_shell_sounds[COM_RandomLong( 0, 2 )], sizeof( soundname ) );
isshellcasing = true; // shell casings have different playback parameters
break;
case BOUNCE_CONCRETE:
Q_snprintf( soundname, sizeof( soundname ), "debris/concrete%i.wav", COM_RandomLong( 1, 3 ));
break;
default: // null sound
return;
}
zvel = abs( pTemp->entity.baseline.origin[2] );
// only play one out of every n
if( isshellcasing )
{
// play first bounce, then 1 out of 3
if( zvel < 200 && COM_RandomLong( 0, 3 ))
return;
}
else
{
if( COM_RandomLong( 0, 5 ))
return;
}
if( damp > 0.0f )
{
int pitch;
sound_t handle;
if( isshellcasing )
fvol *= min ( 1.0f, ((float)zvel) / 350.0f );
else fvol *= min ( 1.0f, ((float)zvel) / 450.0f );
if( !COM_RandomLong( 0, 3 ) && !isshellcasing )
pitch = COM_RandomLong( 95, 105 );
else pitch = PITCH_NORM;
handle = S_RegisterSound( soundname );
S_StartSound( pTemp->entity.origin, -(pTemp - cl_tempents), CHAN_BODY, handle, fvol, ATTN_NORM, pitch, SND_STOP_LOOPING );
}
}
/*
==============
CL_TEntAddEntity
add entity to renderlist
==============
*/
int CL_TempEntAddEntity( cl_entity_t *pEntity )
{
vec3_t mins, maxs;
Assert( pEntity != NULL );
if( !pEntity->model )
return 0;
VectorAdd( pEntity->origin, pEntity->model->mins, mins );
VectorAdd( pEntity->origin, pEntity->model->maxs, maxs );
// g-cont. just use PVS from previous frame
if( TriBoxInPVS( mins, maxs ))
{
VectorCopy( pEntity->angles, pEntity->curstate.angles );
VectorCopy( pEntity->origin, pEntity->curstate.origin );
VectorCopy( pEntity->angles, pEntity->latched.prevangles );
VectorCopy( pEntity->origin, pEntity->latched.prevorigin );
// add to list
CL_AddVisibleEntity( pEntity, ET_TEMPENTITY );
return 1;
}
return 0;
}
/*
==============
CL_AddTempEnts
temp-entities will be added on a user-side
setup client callback
==============
*/
void CL_TempEntUpdate( void )
{
double ft = cl.time - cl.oldtime;
float gravity = clgame.movevars.gravity;
clgame.dllFuncs.pfnTempEntUpdate( ft, cl.time, gravity, &cl_free_tents, &cl_active_tents, CL_TempEntAddEntity, CL_TempEntPlaySound );
}
/*
==============
CL_TEntAddEntity
free the first low priority tempent it finds.
==============
*/
qboolean CL_FreeLowPriorityTempEnt( void )
{
TEMPENTITY *pActive = cl_active_tents;
TEMPENTITY *pPrev = NULL;
while( pActive )
{
if( pActive->priority == TENTPRIORITY_LOW )
{
// remove from the active list.
if( pPrev ) pPrev->next = pActive->next;
else cl_active_tents = pActive->next;
// add to the free list.
pActive->next = cl_free_tents;
cl_free_tents = pActive;
return true;
}
pPrev = pActive;
pActive = pActive->next;
}
return false;
}
/*
==============
CL_TempEntAlloc
alloc normal\low priority tempentity
==============
*/
TEMPENTITY *CL_TempEntAlloc( const vec3_t org, model_t *pmodel )
{
TEMPENTITY *pTemp;
if( !cl_free_tents )
{
Con_DPrintf( "Overflow %d temporary ents!\n", GI->max_tents );
return NULL;
}
pTemp = cl_free_tents;
cl_free_tents = pTemp->next;
CL_PrepareTEnt( pTemp, pmodel );
pTemp->priority = TENTPRIORITY_LOW;
if( org ) VectorCopy( org, pTemp->entity.origin );
pTemp->next = cl_active_tents;
cl_active_tents = pTemp;
return pTemp;
}
/*
==============
CL_TempEntAllocHigh
alloc high priority tempentity
==============
*/
TEMPENTITY *CL_TempEntAllocHigh( const vec3_t org, model_t *pmodel )
{
TEMPENTITY *pTemp;
if( !cl_free_tents )
{
// no temporary ents free, so find the first active low-priority temp ent
// and overwrite it.
CL_FreeLowPriorityTempEnt();
}
if( !cl_free_tents )
{
// didn't find anything? The tent list is either full of high-priority tents
// or all tents in the list are still due to live for > 10 seconds.
Con_DPrintf( "Couldn't alloc a high priority TENT!\n" );
return NULL;
}
// Move out of the free list and into the active list.
pTemp = cl_free_tents;
cl_free_tents = pTemp->next;
CL_PrepareTEnt( pTemp, pmodel );
pTemp->priority = TENTPRIORITY_HIGH;
if( org ) VectorCopy( org, pTemp->entity.origin );
pTemp->next = cl_active_tents;
cl_active_tents = pTemp;
return pTemp;
}
/*
==============
CL_TempEntAlloc
alloc normal priority tempentity with no model
==============
*/
TEMPENTITY *CL_TempEntAllocNoModel( const vec3_t org )
{
return CL_TempEntAlloc( org, NULL );
}
/*
==============
CL_TempEntAlloc
custom tempentity allocation
==============
*/
TEMPENTITY * GAME_EXPORT CL_TempEntAllocCustom( const vec3_t org, model_t *model, int high, void (*pfn)( TEMPENTITY*, float, float ))
{
TEMPENTITY *pTemp;
if( high )
{
pTemp = CL_TempEntAllocHigh( org, model );
}
else
{
pTemp = CL_TempEntAlloc( org, model );
}
if( pTemp && pfn )
{
pTemp->flags |= FTENT_CLIENTCUSTOM;
pTemp->callback = pfn;
pTemp->die = cl.time;
}
return pTemp;
}
/*
==============================================================
EFFECTS BASED ON TEMPENTS (presets)
==============================================================
*/
/*
==============
R_FizzEffect
Create a fizz effect
==============
*/
void GAME_EXPORT R_FizzEffect( cl_entity_t *pent, int modelIndex, int density )
{
TEMPENTITY *pTemp;
int i, width, depth, count;
float angle, maxHeight, speed;
float xspeed, yspeed, zspeed;
vec3_t origin;
model_t *mod;
if( !pent || pent->curstate.modelindex <= 0 )
return;
if(( mod = CL_ModelHandle( pent->curstate.modelindex )) == NULL )
return;
count = density + 1;
density = count * 3 + 6;
maxHeight = mod->maxs[2] - mod->mins[2];
width = mod->maxs[0] - mod->mins[0];
depth = mod->maxs[1] - mod->mins[1];
speed = ( pent->curstate.rendercolor.r<<8 | pent->curstate.rendercolor.g );
if( pent->curstate.rendercolor.b )
speed = -speed;
angle = DEG2RAD( pent->angles[YAW] );
SinCos( angle, &yspeed, &xspeed );
xspeed *= speed;
yspeed *= speed;
for( i = 0; i < count; i++ )
{
origin[0] = mod->mins[0] + COM_RandomLong( 0, width - 1 );
origin[1] = mod->mins[1] + COM_RandomLong( 0, depth - 1 );
origin[2] = mod->mins[2];
pTemp = CL_TempEntAlloc( origin, CL_ModelHandle( modelIndex ));
if ( !pTemp ) return;
pTemp->flags |= FTENT_SINEWAVE;
pTemp->x = origin[0];
pTemp->y = origin[1];
zspeed = COM_RandomLong( 80, 140 );
VectorSet( pTemp->entity.baseline.origin, xspeed, yspeed, zspeed );
pTemp->die = cl.time + ( maxHeight / zspeed ) - 0.1f;
pTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax );
// Set sprite scale
pTemp->entity.curstate.scale = 1.0f / COM_RandomFloat( 2.0f, 5.0f );
pTemp->entity.curstate.rendermode = kRenderTransAlpha;
pTemp->entity.curstate.renderamt = 255;
}
}
/*
==============
R_Bubbles
Create bubbles
==============
*/
void GAME_EXPORT R_Bubbles( const vec3_t mins, const vec3_t maxs, float height, int modelIndex, int count, float speed )
{
TEMPENTITY *pTemp;
float sine, cosine;
float angle, zspeed;
vec3_t origin;
model_t *mod;
int i;
if(( mod = CL_ModelHandle( modelIndex )) == NULL )
return;
for ( i = 0; i < count; i++ )
{
origin[0] = COM_RandomLong( mins[0], maxs[0] );
origin[1] = COM_RandomLong( mins[1], maxs[1] );
origin[2] = COM_RandomLong( mins[2], maxs[2] );
pTemp = CL_TempEntAlloc( origin, mod );
if( !pTemp ) return;
pTemp->flags |= FTENT_SINEWAVE;
pTemp->x = origin[0];
pTemp->y = origin[1];
angle = COM_RandomFloat( -M_PI, M_PI );
SinCos( angle, &sine, &cosine );
zspeed = COM_RandomLong( 80, 140 );
VectorSet( pTemp->entity.baseline.origin, speed * cosine, speed * sine, zspeed );
pTemp->die = cl.time + ((height - (origin[2] - mins[2])) / zspeed) - 0.1f;
pTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax );
// Set sprite scale
pTemp->entity.curstate.scale = 1.0f / COM_RandomFloat( 2.0f, 5.0f );
pTemp->entity.curstate.rendermode = kRenderTransAlpha;
pTemp->entity.curstate.renderamt = 255;
}
}
/*
==============
R_BubbleTrail
Create bubble trail
==============
*/
void GAME_EXPORT R_BubbleTrail( const vec3_t start, const vec3_t end, float height, int modelIndex, int count, float speed )
{
TEMPENTITY *pTemp;
float sine, cosine, zspeed;
float dist, angle;
vec3_t origin;
model_t *mod;
int i;
if(( mod = CL_ModelHandle( modelIndex )) == NULL )
return;
for( i = 0; i < count; i++ )
{
dist = COM_RandomFloat( 0, 1.0 );
VectorLerp( start, dist, end, origin );
pTemp = CL_TempEntAlloc( origin, mod );
if( !pTemp ) return;
pTemp->flags |= FTENT_SINEWAVE;
pTemp->x = origin[0];
pTemp->y = origin[1];
angle = COM_RandomFloat( -M_PI, M_PI );
SinCos( angle, &sine, &cosine );
zspeed = COM_RandomLong( 80, 140 );
VectorSet( pTemp->entity.baseline.origin, speed * cosine, speed * sine, zspeed );
pTemp->die = cl.time + ((height - (origin[2] - start[2])) / zspeed) - 0.1f;
pTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax );
// Set sprite scale
pTemp->entity.curstate.scale = 1.0f / COM_RandomFloat( 2.0f, 5.0f );
pTemp->entity.curstate.rendermode = kRenderTransAlpha;
pTemp->entity.curstate.renderamt = 255;
}
}
/*
==============
R_AttachTentToPlayer
Attaches entity to player
==============
*/
void GAME_EXPORT R_AttachTentToPlayer( int client, int modelIndex, float zoffset, float life )
{
TEMPENTITY *pTemp;
vec3_t position;
cl_entity_t *pClient;
model_t *pModel;
if( client <= 0 || client > cl.maxclients )
return;
pClient = CL_GetEntityByIndex( client );
if( !pClient || pClient->curstate.messagenum != cl.parsecount )
return;
if(( pModel = CL_ModelHandle( modelIndex )) == NULL )
return;
VectorCopy( pClient->origin, position );
position[2] += zoffset;
pTemp = CL_TempEntAllocHigh( position, pModel );
if( !pTemp ) return;
pTemp->entity.curstate.renderfx = kRenderFxNoDissipation;
pTemp->entity.curstate.framerate = 1;
pTemp->clientIndex = client;
pTemp->tentOffset[0] = 0;
pTemp->tentOffset[1] = 0;
pTemp->tentOffset[2] = zoffset;
pTemp->die = cl.time + life;
pTemp->flags |= FTENT_PLYRATTACHMENT|FTENT_PERSIST;
// is the model a sprite?
if( pModel->type == mod_sprite )
{
pTemp->flags |= FTENT_SPRANIMATE|FTENT_SPRANIMATELOOP;
pTemp->entity.curstate.framerate = 10;
}
else
{
// no animation support for attached clientside studio models.
pTemp->frameMax = 0;
}
pTemp->entity.curstate.frame = 0;
}
/*
==============
R_KillAttachedTents
Detach entity from player
==============
*/
void GAME_EXPORT R_KillAttachedTents( int client )
{
int i;
if( client <= 0 || client > cl.maxclients )
return;
for( i = 0; i < GI->max_tents; i++ )
{
TEMPENTITY *pTemp = &cl_tempents[i];
if( !FBitSet( pTemp->flags, FTENT_PLYRATTACHMENT ))
continue;
// this TEMPENTITY is player attached.
// if it is attached to this client, set it to die instantly.
if( pTemp->clientIndex == client )
{
// good enough, it will die on next tent update.
pTemp->die = cl.time;
}
}
}
/*
==============
R_RicochetSprite
Create ricochet sprite
==============
*/
void GAME_EXPORT R_RicochetSprite( const vec3_t pos, model_t *pmodel, float duration, float scale )
{
TEMPENTITY *pTemp;
pTemp = CL_TempEntAlloc( pos, pmodel );
if( !pTemp ) return;
pTemp->entity.curstate.rendermode = kRenderGlow;
pTemp->entity.curstate.renderamt = pTemp->entity.baseline.renderamt = 200;
pTemp->entity.curstate.renderfx = kRenderFxNoDissipation;
pTemp->entity.curstate.scale = scale;
pTemp->die = cl.time + duration;
pTemp->flags = FTENT_FADEOUT;
pTemp->fadeSpeed = 8;
pTemp->entity.curstate.frame = 0;
pTemp->entity.angles[ROLL] = 45.0f * COM_RandomLong( 0, 7 );
}
/*
==============
R_RocketFlare
Create rocket flare
==============
*/
void GAME_EXPORT R_RocketFlare( const vec3_t pos )
{
TEMPENTITY *pTemp;
if( !cl_sprite_glow ) return;
pTemp = CL_TempEntAlloc( pos, cl_sprite_glow );
if ( !pTemp ) return;
pTemp->entity.curstate.rendermode = kRenderGlow;
pTemp->entity.curstate.renderfx = kRenderFxNoDissipation;
pTemp->entity.curstate.renderamt = 200;
pTemp->entity.curstate.framerate = 1.0;
pTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax );
pTemp->entity.curstate.scale = 1.0;
pTemp->die = cl.time + 0.01f; // when 100 fps die at next frame
pTemp->entity.curstate.effects = EF_NOINTERP;
}
/*
==============
R_MuzzleFlash
Do muzzleflash
==============
*/
void GAME_EXPORT R_MuzzleFlash( const vec3_t pos, int type )
{
TEMPENTITY *pTemp;
int index;
float scale;
index = ( type % 10 ) % MAX_MUZZLEFLASH;
scale = ( type / 10 ) * 0.1f;
if( scale == 0.0f ) scale = 0.5f;
if( !cl_sprite_muzzleflash[index] )
return;
// must set position for right culling on render
pTemp = CL_TempEntAllocHigh( pos, cl_sprite_muzzleflash[index] );
if( !pTemp ) return;
pTemp->entity.curstate.rendermode = kRenderTransAdd;
pTemp->entity.curstate.renderamt = 255;
pTemp->entity.curstate.framerate = 10;
pTemp->entity.curstate.renderfx = 0;
pTemp->die = cl.time + 0.01; // die at next frame
pTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax );
pTemp->flags |= FTENT_SPRANIMATE|FTENT_SPRANIMATELOOP;
pTemp->entity.curstate.scale = scale;
if( index == 0 ) pTemp->entity.angles[2] = COM_RandomLong( 0, 20 ); // rifle flash
else pTemp->entity.angles[2] = COM_RandomLong( 0, 359 );
CL_TempEntAddEntity( &pTemp->entity );
}
/*
==============
R_BloodSprite
Create a high priority blood sprite
and some blood drops. This is high-priority tent
==============
*/
void GAME_EXPORT R_BloodSprite( const vec3_t org, int colorIndex, int modelIndex, int modelIndex2, float size )
{
model_t *pModel, *pModel2;
int impactindex;
int spatterindex;
int i, splatter;
TEMPENTITY *pTemp;
vec3_t pos;
colorIndex += COM_RandomLong( 1, 3 );
impactindex = colorIndex;
spatterindex = colorIndex - 1;
// validate the model first
if(( pModel = CL_ModelHandle( modelIndex )) != NULL )
{
VectorCopy( org, pos );
pos[2] += COM_RandomFloat( 2.0f, 4.0f ); // make offset from ground (snarks issues)
// large, single blood sprite is a high-priority tent
if(( pTemp = CL_TempEntAllocHigh( pos, pModel )) != NULL )
{
pTemp->entity.curstate.rendermode = kRenderTransTexture;
pTemp->entity.curstate.renderfx = kRenderFxClampMinScale;
pTemp->entity.curstate.scale = COM_RandomFloat( size / 25.0f, size / 35.0f );
pTemp->flags = FTENT_SPRANIMATE;
pTemp->entity.curstate.rendercolor = clgame.palette[impactindex];
pTemp->entity.baseline.renderamt = pTemp->entity.curstate.renderamt = 250;
pTemp->entity.curstate.framerate = pTemp->frameMax * 4.0f; // Finish in 0.250 seconds
pTemp->die = cl.time + (pTemp->frameMax / pTemp->entity.curstate.framerate ); // play the whole thing once
pTemp->entity.curstate.frame = 0;
pTemp->bounceFactor = 0;
pTemp->entity.angles[2] = COM_RandomLong( 0, 360 );
}
}
// validate the model first
if(( pModel2 = CL_ModelHandle( modelIndex2 )) != NULL )
{
splatter = size + ( COM_RandomLong( 1, 8 ) + COM_RandomLong( 1, 8 ));
for( i = 0; i < splatter; i++ )
{
// create blood drips
if(( pTemp = CL_TempEntAlloc( org, pModel2 )) != NULL )
{
pTemp->entity.curstate.rendermode = kRenderTransTexture;
pTemp->entity.curstate.renderfx = kRenderFxClampMinScale;
pTemp->entity.curstate.scale = COM_RandomFloat( size / 15.0f, size / 25.0f );
pTemp->flags = FTENT_ROTATE | FTENT_SLOWGRAVITY | FTENT_COLLIDEWORLD;
pTemp->entity.curstate.rendercolor = clgame.palette[spatterindex];
pTemp->entity.baseline.renderamt = pTemp->entity.curstate.renderamt = 250;
pTemp->entity.baseline.origin[0] = COM_RandomFloat( -96.0f, 95.0f );
pTemp->entity.baseline.origin[1] = COM_RandomFloat( -96.0f, 95.0f );
pTemp->entity.baseline.origin[2] = COM_RandomFloat( -32.0f, 95.0f );
pTemp->entity.baseline.angles[0] = COM_RandomFloat( -256.0f, -255.0f );
pTemp->entity.baseline.angles[1] = COM_RandomFloat( -256.0f, -255.0f );
pTemp->entity.baseline.angles[2] = COM_RandomFloat( -256.0f, -255.0f );
pTemp->die = cl.time + COM_RandomFloat( 1.0f, 3.0f );
pTemp->entity.curstate.frame = COM_RandomLong( 1, pTemp->frameMax );
if( pTemp->entity.curstate.frame > 8.0f )
pTemp->entity.curstate.frame = pTemp->frameMax;
pTemp->entity.baseline.origin[2] += COM_RandomFloat( 4.0f, 16.0f ) * size;
pTemp->entity.angles[2] = COM_RandomFloat( 0.0f, 360.0f );
pTemp->bounceFactor = 0.0f;
}
}
}
}
/*
==============
R_BreakModel
Create a shards
==============
*/
void GAME_EXPORT R_BreakModel( const vec3_t pos, const vec3_t size, const vec3_t dir, float random, float life, int count, int modelIndex, char flags )
{
TEMPENTITY *pTemp;
model_t *pmodel;
char type;
int i, j;
if(( pmodel = CL_ModelHandle( modelIndex )) == NULL )
return;
type = flags & BREAK_TYPEMASK;
if( count == 0 )
{
// assume surface (not volume)
count = (size[0] * size[1] + size[1] * size[2] + size[2] * size[0]) / (3 * SHARD_VOLUME * SHARD_VOLUME);
}
// limit to 100 pieces
if( count > 100 ) count = 100;
for( i = 0; i < count; i++ )
{
vec3_t vecSpot;
for( j = 0; j < 32; j++ )
{
// fill up the box with stuff
vecSpot[0] = pos[0] + COM_RandomFloat( -0.5f, 0.5f ) * size[0];
vecSpot[1] = pos[1] + COM_RandomFloat( -0.5f, 0.5f ) * size[1];
vecSpot[2] = pos[2] + COM_RandomFloat( -0.5f, 0.5f ) * size[2];
if( CL_PointContents( vecSpot ) != CONTENTS_SOLID )
break; // valid spot
}
if( j == 32 ) continue; // a piece completely stuck in the wall, ignore it
pTemp = CL_TempEntAlloc( vecSpot, pmodel );
if( !pTemp ) return;
// keep track of break_type, so we know how to play sound on collision
pTemp->hitSound = type;
if( pmodel->type == mod_sprite )
pTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax );
else if( pmodel->type == mod_studio )
pTemp->entity.curstate.body = COM_RandomLong( 0, pTemp->frameMax );
pTemp->flags |= FTENT_COLLIDEWORLD | FTENT_FADEOUT | FTENT_SLOWGRAVITY;
if( COM_RandomLong( 0, 255 ) < 200 )
{
pTemp->flags |= FTENT_ROTATE;
pTemp->entity.baseline.angles[0] = COM_RandomFloat( -256, 255 );
pTemp->entity.baseline.angles[1] = COM_RandomFloat( -256, 255 );
pTemp->entity.baseline.angles[2] = COM_RandomFloat( -256, 255 );
}
if (( COM_RandomLong( 0, 255 ) < 100 ) && FBitSet( flags, BREAK_SMOKE ))
pTemp->flags |= FTENT_SMOKETRAIL;
if(( type == BREAK_GLASS ) || FBitSet( flags, BREAK_TRANS ))
{
pTemp->entity.curstate.rendermode = kRenderTransTexture;
pTemp->entity.curstate.renderamt = pTemp->entity.baseline.renderamt = 128;
}
else
{
pTemp->entity.curstate.rendermode = kRenderNormal;
pTemp->entity.curstate.renderamt = pTemp->entity.baseline.renderamt = 255; // set this for fadeout
}
pTemp->entity.baseline.origin[0] = dir[0] + COM_RandomFloat( -random, random );
pTemp->entity.baseline.origin[1] = dir[1] + COM_RandomFloat( -random, random );
pTemp->entity.baseline.origin[2] = dir[2] + COM_RandomFloat( 0, random );
pTemp->die = cl.time + life + COM_RandomFloat( 0.0f, 1.0f ); // Add an extra 0-1 secs of life
}
}
/*
==============
R_TempModel
Create a temp model with gravity, sounds and fadeout
==============
*/
TEMPENTITY *R_TempModel( const vec3_t pos, const vec3_t dir, const vec3_t angles, float life, int modelIndex, int soundtype )
{
// alloc a new tempent
TEMPENTITY *pTemp;
model_t *pmodel;
if(( pmodel = CL_ModelHandle( modelIndex )) == NULL )
return NULL;
pTemp = CL_TempEntAlloc( pos, pmodel );
if( !pTemp ) return NULL;
pTemp->flags = (FTENT_COLLIDEWORLD|FTENT_GRAVITY);
VectorCopy( dir, pTemp->entity.baseline.origin );
VectorCopy( angles, pTemp->entity.angles );
// keep track of shell type
switch( soundtype )
{
case TE_BOUNCE_SHELL:
pTemp->hitSound = BOUNCE_SHELL;
pTemp->entity.baseline.angles[0] = COM_RandomFloat( -512, 511 );
pTemp->entity.baseline.angles[1] = COM_RandomFloat( -255, 255 );
pTemp->entity.baseline.angles[2] = COM_RandomFloat( -255, 255 );
pTemp->flags |= FTENT_ROTATE;
break;
case TE_BOUNCE_SHOTSHELL:
pTemp->hitSound = BOUNCE_SHOTSHELL;
pTemp->entity.baseline.angles[0] = COM_RandomFloat( -512, 511 );
pTemp->entity.baseline.angles[1] = COM_RandomFloat( -255, 255 );
pTemp->entity.baseline.angles[2] = COM_RandomFloat( -255, 255 );
pTemp->flags |= FTENT_ROTATE|FTENT_SLOWGRAVITY;
break;
}
if( pmodel->type == mod_sprite )
pTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax );
else pTemp->entity.curstate.body = COM_RandomLong( 0, pTemp->frameMax );
pTemp->die = cl.time + life;
return pTemp;
}
/*
==============
R_DefaultSprite
Create an animated sprite
==============
*/
TEMPENTITY *R_DefaultSprite( const vec3_t pos, int spriteIndex, float framerate )
{
TEMPENTITY *pTemp;
model_t *psprite;
// don't spawn while paused
if( cl.time == cl.oldtime )
return NULL;
if(( psprite = CL_ModelHandle( spriteIndex )) == NULL || psprite->type != mod_sprite )
{
Con_Reportf( "No Sprite %d!\n", spriteIndex );
return NULL;
}
pTemp = CL_TempEntAlloc( pos, psprite );
if( !pTemp ) return NULL;
pTemp->entity.curstate.scale = 1.0f;
pTemp->flags |= FTENT_SPRANIMATE;
if( framerate == 0 ) framerate = 10;
pTemp->entity.curstate.framerate = framerate;
pTemp->die = cl.time + (float)pTemp->frameMax / framerate;
pTemp->entity.curstate.frame = 0;
return pTemp;
}
/*
===============
R_SparkShower
Create an animated moving sprite
===============
*/
void GAME_EXPORT R_SparkShower( const vec3_t pos )
{
TEMPENTITY *pTemp;
pTemp = CL_TempEntAllocNoModel( pos );
if( !pTemp ) return;
pTemp->entity.baseline.origin[0] = COM_RandomFloat( -300.0f, 300.0f );
pTemp->entity.baseline.origin[1] = COM_RandomFloat( -300.0f, 300.0f );
pTemp->entity.baseline.origin[2] = COM_RandomFloat( -200.0f, 200.0f );
pTemp->flags |= FTENT_SLOWGRAVITY | FTENT_COLLIDEWORLD | FTENT_SPARKSHOWER;
pTemp->entity.curstate.framerate = COM_RandomFloat( 0.5f, 1.5f );
pTemp->entity.curstate.scale = cl.time;
pTemp->die = cl.time + 0.5;
}
/*
===============
R_TempSprite
Create an animated moving sprite
===============
*/
TEMPENTITY *R_TempSprite( vec3_t pos, const vec3_t dir, float scale, int modelIndex, int rendermode, int renderfx, float a, float life, int flags )
{
TEMPENTITY *pTemp;
model_t *pmodel;
if(( pmodel = CL_ModelHandle( modelIndex )) == NULL )
{
Con_Reportf( S_ERROR "No model %d!\n", modelIndex );
return NULL;
}
pTemp = CL_TempEntAlloc( pos, pmodel );
if( !pTemp ) return NULL;
pTemp->entity.curstate.framerate = 10;
pTemp->entity.curstate.rendermode = rendermode;
pTemp->entity.curstate.renderfx = renderfx;
pTemp->entity.curstate.scale = scale;
pTemp->entity.baseline.renderamt = a * 255;
pTemp->entity.curstate.renderamt = a * 255;
pTemp->flags |= flags;
VectorCopy( dir, pTemp->entity.baseline.origin );
if( life ) pTemp->die = cl.time + life;
else pTemp->die = cl.time + ( pTemp->frameMax * 0.1f ) + 1.0f;
pTemp->entity.curstate.frame = 0;
return pTemp;
}
/*
===============
R_Sprite_Explode
apply params for exploding sprite
===============
*/
void GAME_EXPORT R_Sprite_Explode( TEMPENTITY *pTemp, float scale, int flags )
{
if( !pTemp ) return;
if( FBitSet( flags, TE_EXPLFLAG_NOADDITIVE ))
{
// solid sprite
pTemp->entity.curstate.rendermode = kRenderNormal;
pTemp->entity.curstate.renderamt = 255;
}
else if( FBitSet( flags, TE_EXPLFLAG_DRAWALPHA ))
{
// alpha sprite (came from hl2)
pTemp->entity.curstate.rendermode = kRenderTransAlpha;
pTemp->entity.curstate.renderamt = 180;
}
else
{
// additive sprite
pTemp->entity.curstate.rendermode = kRenderTransAdd;
pTemp->entity.curstate.renderamt = 180;
}
if( FBitSet( flags, TE_EXPLFLAG_ROTATE ))
{
// came from hl2
pTemp->entity.angles[2] = COM_RandomLong( 0, 360 );
}
pTemp->entity.curstate.renderfx = kRenderFxNone;
pTemp->entity.baseline.origin[2] = 8;
pTemp->entity.origin[2] += 10;
pTemp->entity.curstate.scale = scale;
}
/*
===============
R_Sprite_Smoke
apply params for smoke sprite
===============
*/
void GAME_EXPORT R_Sprite_Smoke( TEMPENTITY *pTemp, float scale )
{
int iColor;
if( !pTemp ) return;
iColor = COM_RandomLong( 20, 35 );
pTemp->entity.curstate.rendermode = kRenderTransAlpha;
pTemp->entity.curstate.renderfx = kRenderFxNone;
pTemp->entity.baseline.origin[2] = 30;
pTemp->entity.curstate.rendercolor.r = iColor;
pTemp->entity.curstate.rendercolor.g = iColor;
pTemp->entity.curstate.rendercolor.b = iColor;
pTemp->entity.origin[2] += 20;
pTemp->entity.curstate.scale = scale;
}
/*
===============
R_Spray
Throws a shower of sprites or models
===============
*/
void GAME_EXPORT R_Spray( const vec3_t pos, const vec3_t dir, int modelIndex, int count, int speed, int spread, int rendermode )
{
TEMPENTITY *pTemp;
float noise;
float znoise;
model_t *pmodel;
int i;
if(( pmodel = CL_ModelHandle( modelIndex )) == NULL )
{
Con_Reportf( "No model %d!\n", modelIndex );
return;
}
noise = (float)spread / 100.0f;
// more vertical displacement
znoise = Q_min( 1.0f, noise * 1.5f );
for( i = 0; i < count; i++ )
{
pTemp = CL_TempEntAlloc( pos, pmodel );
if( !pTemp ) return;
pTemp->entity.curstate.rendermode = rendermode;
pTemp->entity.baseline.renderamt = pTemp->entity.curstate.renderamt = 255;
pTemp->entity.curstate.renderfx = kRenderFxNoDissipation;
if( rendermode != kRenderGlow )
{
// spray
pTemp->flags |= FTENT_COLLIDEWORLD | FTENT_SLOWGRAVITY;
if( pTemp->frameMax > 1 )
{
pTemp->flags |= FTENT_COLLIDEWORLD | FTENT_SLOWGRAVITY | FTENT_SPRANIMATE;
pTemp->die = cl.time + (pTemp->frameMax * 0.1f);
pTemp->entity.curstate.framerate = 10;
}
else pTemp->die = cl.time + 0.35f;
}
else
{
// sprite spray
pTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax );
pTemp->flags |= FTENT_FADEOUT | FTENT_SLOWGRAVITY;
pTemp->entity.curstate.framerate = 0.5;
pTemp->die = cl.time + 0.35f;
pTemp->fadeSpeed = 2.0;
}
// make the spittle fly the direction indicated, but mix in some noise.
pTemp->entity.baseline.origin[0] = dir[0] + COM_RandomFloat( -noise, noise );
pTemp->entity.baseline.origin[1] = dir[1] + COM_RandomFloat( -noise, noise );
pTemp->entity.baseline.origin[2] = dir[2] + COM_RandomFloat( 0, znoise );
VectorScale( pTemp->entity.baseline.origin, COM_RandomFloat(( speed * 0.8f ), ( speed * 1.2f )), pTemp->entity.baseline.origin );
}
}
/*
===============
R_Sprite_Spray
Spray of alpha sprites
===============
*/
void GAME_EXPORT R_Sprite_Spray( const vec3_t pos, const vec3_t dir, int modelIndex, int count, int speed, int spread )
{
R_Spray( pos, dir, modelIndex, count, speed, spread, kRenderGlow );
}
/*
===============
R_Sprite_Trail
Line of moving glow sprites with gravity,
fadeout, and collisions
===============
*/
void GAME_EXPORT R_Sprite_Trail( int type, vec3_t start, vec3_t end, int modelIndex, int count, float life, float size, float amp, int renderamt, float speed )
{
TEMPENTITY *pTemp;
vec3_t delta, dir;
model_t *pmodel;
int i;
if(( pmodel = CL_ModelHandle( modelIndex )) == NULL )
return;
VectorSubtract( end, start, delta );
VectorNormalize2( delta, dir );
amp /= 256.0f;
for( i = 0; i < count; i++ )
{
vec3_t pos, vel;
// Be careful of divide by 0 when using 'count' here...
if( i == 0 ) VectorCopy( start, pos );
else VectorMA( start, ( i / ( count - 1.0f )), delta, pos );
pTemp = CL_TempEntAlloc( pos, pmodel );
if( !pTemp ) return;
pTemp->flags = (FTENT_COLLIDEWORLD|FTENT_SPRCYCLE|FTENT_FADEOUT|FTENT_SLOWGRAVITY);
VectorScale( dir, speed, vel );
vel[0] += COM_RandomFloat( -127.0f, 128.0f ) * amp;
vel[1] += COM_RandomFloat( -127.0f, 128.0f ) * amp;
vel[2] += COM_RandomFloat( -127.0f, 128.0f ) * amp;
VectorCopy( vel, pTemp->entity.baseline.origin );
VectorCopy( pos, pTemp->entity.origin );
pTemp->entity.curstate.scale = size;
pTemp->entity.curstate.rendermode = kRenderGlow;
pTemp->entity.curstate.renderfx = kRenderFxNoDissipation;
pTemp->entity.curstate.renderamt = pTemp->entity.baseline.renderamt = renderamt;
pTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax );
pTemp->die = cl.time + life + COM_RandomFloat( 0.0f, 4.0f );
}
}
/*
===============
R_FunnelSprite
Create a funnel effect with custom sprite
===============
*/
void GAME_EXPORT R_FunnelSprite( const vec3_t org, int modelIndex, int reverse )
{
TEMPENTITY *pTemp;
vec3_t dir, dest;
float dist, vel;
model_t *pmodel;
int i, j;
if(( pmodel = CL_ModelHandle( modelIndex )) == NULL )
{
Con_Reportf( S_ERROR "no model %d!\n", modelIndex );
return;
}
for( i = -8; i < 8; i++ )
{
for( j = -8; j < 8; j++ )
{
pTemp = CL_TempEntAlloc( org, pmodel );
if( !pTemp ) 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, pTemp->entity.origin );
VectorSubtract( dest, pTemp->entity.origin, dir );
}
else
{
VectorCopy( dest, pTemp->entity.origin );
VectorSubtract( org, pTemp->entity.origin, dir );
}
pTemp->entity.curstate.rendermode = kRenderGlow;
pTemp->entity.curstate.renderfx = kRenderFxNoDissipation;
pTemp->entity.baseline.renderamt = pTemp->entity.curstate.renderamt = 200;
pTemp->entity.baseline.angles[2] = COM_RandomFloat( -100.0f, 100.0f );
pTemp->entity.curstate.framerate = COM_RandomFloat( 0.1f, 0.4f );
pTemp->flags = FTENT_ROTATE|FTENT_FADEOUT;
pTemp->entity.curstate.framerate = 10;
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, pTemp->entity.baseline.origin );
pTemp->die = cl.time + (dist / vel) - 0.5f;
pTemp->fadeSpeed = 2.0f;
}
}
}
/*
===============
R_SparkEffect
Create a streaks + richochet sprite
===============
*/
void GAME_EXPORT R_SparkEffect( const vec3_t pos, int count, int velocityMin, int velocityMax )
{
R_RicochetSprite( pos, cl_sprite_ricochet, 0.1f, COM_RandomFloat( 0.5f, 1.0f ));
R_SparkStreaks( pos, count, velocityMin, velocityMax );
}
/*
==============
R_RicochetSound
Make a random ricochet sound
==============
*/
void GAME_EXPORT R_RicochetSound( const vec3_t pos )
{
int iPitch = COM_RandomLong( 90, 105 );
float fvol = COM_RandomFloat( 0.7f, 0.9f );
char soundpath[32];
sound_t handle;
Q_strncpy( soundpath, cl_ricochet_sounds[COM_RandomLong( 0, 4 )], sizeof( soundpath ) );
handle = S_RegisterSound( soundpath );
S_StartSound( pos, 0, CHAN_AUTO, handle, fvol, ATTN_NORM, iPitch, 0 );
}
/*
==============
R_Projectile
Create an projectile entity
==============
*/
void GAME_EXPORT R_Projectile( const vec3_t origin, const vec3_t velocity, int modelIndex, int life, int owner, void (*hitcallback)( TEMPENTITY*, pmtrace_t* ))
{
TEMPENTITY *pTemp;
model_t *pmodel;
vec3_t dir;
if(( pmodel = CL_ModelHandle( modelIndex )) == NULL )
return;
pTemp = CL_TempEntAllocHigh( origin, pmodel );
if( !pTemp ) return;
VectorCopy( velocity, pTemp->entity.baseline.origin );
if( pmodel->type == mod_sprite )
{
SetBits( pTemp->flags, FTENT_SPRANIMATE );
if( pTemp->frameMax < 10 )
{
SetBits( pTemp->flags, FTENT_SPRANIMATE|FTENT_SPRANIMATELOOP );
pTemp->entity.curstate.framerate = 10;
}
else
{
pTemp->entity.curstate.framerate = pTemp->frameMax / life;
}
}
else
{
pTemp->frameMax = 0;
VectorNormalize2( velocity, dir );
VectorAngles( dir, pTemp->entity.angles );
}
pTemp->flags |= FTENT_COLLIDEALL|FTENT_PERSIST|FTENT_COLLIDEKILL;
pTemp->clientIndex = bound( 1, owner, cl.maxclients );
pTemp->entity.baseline.renderamt = 255;
pTemp->hitcallback = hitcallback;
pTemp->die = cl.time + life;
}
/*
==============
R_TempSphereModel
Spherical shower of models, picks from set
==============
*/
void GAME_EXPORT R_TempSphereModel( const vec3_t pos, float speed, float life, int count, int modelIndex )
{
TEMPENTITY *pTemp;
int i;
// create temp models
for( i = 0; i < count; i++ )
{
pTemp = CL_TempEntAlloc( pos, CL_ModelHandle( modelIndex ));
if( !pTemp ) return;
pTemp->entity.curstate.body = COM_RandomLong( 0, pTemp->frameMax );
if( COM_RandomLong( 0, 255 ) < 10 )
pTemp->flags |= FTENT_SLOWGRAVITY;
else pTemp->flags |= FTENT_GRAVITY;
if( COM_RandomLong( 0, 255 ) < 200 )
{
pTemp->flags |= FTENT_ROTATE;
pTemp->entity.baseline.angles[0] = COM_RandomFloat( -256.0f, -255.0f );
pTemp->entity.baseline.angles[1] = COM_RandomFloat( -256.0f, -255.0f );
pTemp->entity.baseline.angles[2] = COM_RandomFloat( -256.0f, -255.0f );
}
if( COM_RandomLong( 0, 255 ) < 100 )
pTemp->flags |= FTENT_SMOKETRAIL;
pTemp->flags |= FTENT_FLICKER | FTENT_COLLIDEWORLD;
pTemp->entity.curstate.rendermode = kRenderNormal;
pTemp->entity.curstate.effects = i & 31;
pTemp->entity.baseline.origin[0] = COM_RandomFloat( -1.0f, 1.0f );
pTemp->entity.baseline.origin[1] = COM_RandomFloat( -1.0f, 1.0f );
pTemp->entity.baseline.origin[2] = COM_RandomFloat( -1.0f, 1.0f );
VectorNormalize( pTemp->entity.baseline.origin );
VectorScale( pTemp->entity.baseline.origin, speed, pTemp->entity.baseline.origin );
pTemp->die = cl.time + life;
}
}
/*
==============
R_Explosion
Create an explosion (scale is magnitude)
==============
*/
void GAME_EXPORT R_Explosion( vec3_t pos, int model, float scale, float framerate, int flags )
{
sound_t hSound;
if( scale != 0.0f )
{
// create explosion sprite
R_Sprite_Explode( R_DefaultSprite( pos, model, framerate ), scale, flags );
if( !FBitSet( flags, TE_EXPLFLAG_NOPARTICLES ))
R_FlickerParticles( pos );
if( !FBitSet( flags, TE_EXPLFLAG_NODLIGHTS ))
{
dlight_t *dl;
// big flash
dl = CL_AllocDlight( 0 );
VectorCopy( pos, dl->origin );
dl->radius = 200;
dl->color.r = 250;
dl->color.g = 250;
dl->color.b = 150;
dl->die = cl.time + 0.01f;
dl->decay = 80;
// red glow
dl = CL_AllocDlight( 0 );
VectorCopy( pos, dl->origin );
dl->radius = 150;
dl->color.r = 255;
dl->color.g = 190;
dl->color.b = 40;
dl->die = cl.time + 1.0f;
dl->decay = 200;
}
}
if( !FBitSet( flags, TE_EXPLFLAG_NOSOUND ))
{
hSound = S_RegisterSound( cl_explode_sounds[COM_RandomLong( 0, 2 )] );
S_StartSound( pos, 0, CHAN_STATIC, hSound, VOL_NORM, 0.3f, PITCH_NORM, 0 );
}
}
/*
==============
R_PlayerSprites
Create a particle smoke around player
==============
*/
void GAME_EXPORT R_PlayerSprites( int client, int modelIndex, int count, int size )
{
TEMPENTITY *pTemp;
cl_entity_t *pEnt;
vec3_t position;
vec3_t dir;
float vel;
int i;
pEnt = CL_GetEntityByIndex( client );
if( !pEnt || !pEnt->player )
return;
vel = 128;
for( i = 0; i < count; i++ )
{
VectorCopy( pEnt->origin, position );
position[0] += COM_RandomFloat( -10.0f, 10.0f );
position[1] += COM_RandomFloat( -10.0f, 10.0f );
position[2] += COM_RandomFloat( -20.0f, 36.0f );
pTemp = CL_TempEntAlloc( position, CL_ModelHandle( modelIndex ));
if( !pTemp ) return;
VectorSubtract( pTemp->entity.origin, pEnt->origin, pTemp->tentOffset );
if ( i != 0 )
{
pTemp->flags |= FTENT_PLYRATTACHMENT;
pTemp->clientIndex = client;
}
else
{
VectorSubtract( position, pEnt->origin, dir );
VectorNormalize( dir );
VectorScale( dir, 60, dir );
VectorCopy( dir, pTemp->entity.baseline.origin );
pTemp->entity.baseline.origin[1] = COM_RandomFloat( 20.0f, 60.0f );
}
pTemp->entity.curstate.renderfx = kRenderFxNoDissipation;
pTemp->entity.curstate.framerate = COM_RandomFloat( 1.0f - (size / 100.0f ), 1.0f );
if( pTemp->frameMax > 1 )
{
pTemp->flags |= FTENT_SPRANIMATE;
pTemp->entity.curstate.framerate = 20.0f;
pTemp->die = cl.time + (pTemp->frameMax * 0.05f);
}
else
{
pTemp->die = cl.time + 0.35f;
}
}
}
/*
==============
R_FireField
Makes a field of fire
==============
*/
void GAME_EXPORT R_FireField( float *org, int radius, int modelIndex, int count, int flags, float life )
{
TEMPENTITY *pTemp;
model_t *pmodel;
float time;
vec3_t pos;
int i;
if(( pmodel = CL_ModelHandle( modelIndex )) == NULL )
return;
for( i = 0; i < count; i++ )
{
VectorCopy( org, pos );
pos[0] += COM_RandomFloat( -radius, radius );
pos[1] += COM_RandomFloat( -radius, radius );
if( !FBitSet( flags, TEFIRE_FLAG_PLANAR ))
pos[2] += COM_RandomFloat( -radius, radius );
pTemp = CL_TempEntAlloc( pos, pmodel );
if( !pTemp ) return;
if( FBitSet( flags, TEFIRE_FLAG_ALPHA ))
{
pTemp->entity.curstate.rendermode = kRenderTransAlpha;
pTemp->entity.curstate.renderfx = kRenderFxNoDissipation;
pTemp->entity.baseline.renderamt = pTemp->entity.curstate.renderamt = 128;
}
else if( FBitSet( flags, TEFIRE_FLAG_ADDITIVE ))
{
pTemp->entity.curstate.rendermode = kRenderTransAdd;
pTemp->entity.curstate.renderamt = 80;
}
else
{
pTemp->entity.curstate.rendermode = kRenderNormal;
pTemp->entity.curstate.renderfx = kRenderFxNoDissipation;
pTemp->entity.baseline.renderamt = pTemp->entity.curstate.renderamt = 255;
}
pTemp->entity.curstate.framerate = COM_RandomFloat( 0.75f, 1.25f );
time = life + COM_RandomFloat( -0.25f, 0.5f );
pTemp->die = cl.time + time;
if( pTemp->frameMax > 1 )
{
pTemp->flags |= FTENT_SPRANIMATE;
if( FBitSet( flags, TEFIRE_FLAG_LOOP ))
{
pTemp->entity.curstate.framerate = 15.0f;
pTemp->flags |= FTENT_SPRANIMATELOOP;
}
else
{
pTemp->entity.curstate.framerate = pTemp->frameMax / time;
}
}
if( FBitSet( flags, TEFIRE_FLAG_ALLFLOAT ) || ( FBitSet( flags, TEFIRE_FLAG_SOMEFLOAT ) && !COM_RandomLong( 0, 1 )))
{
// drift sprite upward
pTemp->entity.baseline.origin[2] = COM_RandomFloat( 10.0f, 30.0f );
}
}
}
/*
==============
R_MultiGunshot
Client version of shotgun shot
==============
*/
void GAME_EXPORT R_MultiGunshot( const vec3_t org, const vec3_t dir, const vec3_t noise, int count, int decalCount, int *decalIndices )
{
pmtrace_t trace;
vec3_t right, up;
vec3_t vecSrc, vecDir, vecEnd;
int i, j, decalIndex;
VectorVectors( dir, right, up );
VectorCopy( org, vecSrc );
for( i = 0; i < count; i++ )
{
// get circular gaussian spread
float x, y, z;
do {
x = COM_RandomFloat( -0.5f, 0.5f ) + COM_RandomFloat( -0.5f, 0.5f );
y = COM_RandomFloat( -0.5f, 0.5f ) + COM_RandomFloat( -0.5f, 0.5f );
z = x * x + y * y;
} while( z > 1.0f );
for( j = 0; j < 3; j++ )
{
vecDir[j] = dir[j] + x * noise[0] * right[j] + y * noise[1] * up[j];
vecEnd[j] = vecSrc[j] + 4096.0f * vecDir[j];
}
trace = CL_TraceLine( vecSrc, vecEnd, PM_STUDIO_IGNORE );
// paint decals
if( trace.fraction != 1.0f )
{
physent_t *pe = NULL;
if( i & 2 ) R_RicochetSound( trace.endpos );
R_BulletImpactParticles( trace.endpos );
if( trace.ent >= 0 && trace.ent < clgame.pmove->numphysent )
pe = &clgame.pmove->physents[trace.ent];
if( pe && ( pe->solid == SOLID_BSP || pe->movetype == MOVETYPE_PUSHSTEP ))
{
cl_entity_t *e = CL_GetEntityByIndex( pe->info );
decalIndex = CL_DecalIndex( decalIndices[COM_RandomLong( 0, decalCount-1 )] );
CL_DecalShoot( decalIndex, e->index, 0, trace.endpos, 0 );
}
}
}
}
/*
==============
R_Sprite_WallPuff
Create a wallpuff
==============
*/
void GAME_EXPORT R_Sprite_WallPuff( TEMPENTITY *pTemp, float scale )
{
if( !pTemp ) return;
pTemp->entity.curstate.renderamt = 255;
pTemp->entity.curstate.rendermode = kRenderTransAlpha;
pTemp->entity.angles[ROLL] = COM_RandomLong( 0, 359 );
pTemp->entity.baseline.origin[2] = 30;
pTemp->entity.curstate.scale = scale;
pTemp->die = cl.time + 0.01f;
}
/*
==============
CL_ParseTempEntity
handle temp-entity messages
==============
*/
void CL_ParseTempEntity( sizebuf_t *msg )
{
sizebuf_t buf;
byte pbuf[256];
int iSize;
int type, color, count, flags;
int decalIndex, modelIndex, entityIndex;
float scale, life, frameRate, vel, random;
float brightness, r, g, b;
vec3_t pos, pos2, ang;
int decalIndices[1]; // just stub
TEMPENTITY *pTemp;
cl_entity_t *pEnt;
dlight_t *dl;
if( cls.legacymode )
iSize = MSG_ReadByte( msg );
else iSize = MSG_ReadWord( msg );
decalIndex = modelIndex = entityIndex = 0;
// parse user message into buffer
MSG_ReadBytes( msg, pbuf, iSize );
// init a safe tempbuffer
MSG_Init( &buf, "TempEntity", pbuf, iSize );
type = MSG_ReadByte( &buf );
switch( type )
{
case TE_BEAMPOINTS:
case TE_BEAMENTPOINT:
case TE_LIGHTNING:
case TE_BEAMENTS:
case TE_BEAM:
case TE_BEAMSPRITE:
case TE_BEAMTORUS:
case TE_BEAMDISK:
case TE_BEAMCYLINDER:
case TE_BEAMFOLLOW:
case TE_BEAMRING:
case TE_BEAMHOSE:
case TE_KILLBEAM:
CL_ParseViewBeam( &buf, type );
break;
case TE_GUNSHOT:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
R_RicochetSound( pos );
R_RunParticleEffect( pos, vec3_origin, 0, 20 );
break;
case TE_EXPLOSION:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
modelIndex = MSG_ReadShort( &buf );
scale = (float)(MSG_ReadByte( &buf ) * 0.1f);
frameRate = MSG_ReadByte( &buf );
flags = MSG_ReadByte( &buf );
R_Explosion( pos, modelIndex, scale, frameRate, flags );
break;
case TE_TAREXPLOSION:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
R_BlobExplosion( pos );
break;
case TE_SMOKE:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
modelIndex = MSG_ReadShort( &buf );
scale = (float)(MSG_ReadByte( &buf ) * 0.1f);
frameRate = MSG_ReadByte( &buf );
pTemp = R_DefaultSprite( pos, modelIndex, frameRate );
R_Sprite_Smoke( pTemp, scale );
break;
case TE_TRACER:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
pos2[0] = MSG_ReadCoord( &buf );
pos2[1] = MSG_ReadCoord( &buf );
pos2[2] = MSG_ReadCoord( &buf );
R_TracerEffect( pos, pos2 );
break;
case TE_SPARKS:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
R_SparkShower( pos );
break;
case TE_LAVASPLASH:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
R_LavaSplash( pos );
break;
case TE_TELEPORT:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
R_TeleportSplash( pos );
break;
case TE_EXPLOSION2:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
color = MSG_ReadByte( &buf );
count = MSG_ReadByte( &buf );
R_ParticleExplosion2( pos, color, count );
break;
case TE_BSPDECAL:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
decalIndex = MSG_ReadShort( &buf );
entityIndex = MSG_ReadShort( &buf );
if( entityIndex ) modelIndex = MSG_ReadShort( &buf );
CL_DecalShoot( CL_DecalIndex( decalIndex ), entityIndex, modelIndex, pos, FDECAL_PERMANENT );
break;
case TE_IMPLOSION:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
scale = MSG_ReadByte( &buf );
count = MSG_ReadByte( &buf );
life = (float)(MSG_ReadByte( &buf ) * 0.1f);
R_Implosion( pos, scale, count, life );
break;
case TE_SPRITETRAIL:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
pos2[0] = MSG_ReadCoord( &buf );
pos2[1] = MSG_ReadCoord( &buf );
pos2[2] = MSG_ReadCoord( &buf );
modelIndex = MSG_ReadShort( &buf );
count = MSG_ReadByte( &buf );
life = (float)(MSG_ReadByte( &buf ) * 0.1f);
scale = (float)(MSG_ReadByte( &buf ) * 0.1f);
vel = (float)MSG_ReadByte( &buf );
random = (float)MSG_ReadByte( &buf );
R_Sprite_Trail( type, pos, pos2, modelIndex, count, life, scale, random, 255, vel );
break;
case TE_SPRITE:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
modelIndex = MSG_ReadShort( &buf );
scale = (float)(MSG_ReadByte( &buf ) * 0.1f);
brightness = (float)MSG_ReadByte( &buf );
if(( pTemp = R_DefaultSprite( pos, modelIndex, 0 )) != NULL )
{
pTemp->entity.curstate.scale = scale;
pTemp->entity.baseline.renderamt = brightness;
pTemp->entity.curstate.renderamt = brightness;
pTemp->entity.curstate.rendermode = kRenderTransAdd;
}
break;
case TE_GLOWSPRITE:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
modelIndex = MSG_ReadShort( &buf );
life = (float)(MSG_ReadByte( &buf ) * 0.1f);
scale = (float)(MSG_ReadByte( &buf ) * 0.1f);
brightness = (float)MSG_ReadByte( &buf );
if(( pTemp = R_DefaultSprite( pos, modelIndex, 0 )) != NULL )
{
pTemp->entity.curstate.scale = scale;
pTemp->entity.curstate.rendermode = kRenderGlow;
pTemp->entity.curstate.renderfx = kRenderFxNoDissipation;
pTemp->entity.baseline.renderamt = brightness;
pTemp->entity.curstate.renderamt = brightness;
pTemp->flags = FTENT_FADEOUT;
pTemp->die = cl.time + life;
}
break;
case TE_STREAK_SPLASH:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
pos2[0] = MSG_ReadCoord( &buf );
pos2[1] = MSG_ReadCoord( &buf );
pos2[2] = MSG_ReadCoord( &buf );
color = MSG_ReadByte( &buf );
count = MSG_ReadShort( &buf );
vel = (float)MSG_ReadShort( &buf );
random = (float)MSG_ReadShort( &buf );
R_StreakSplash( pos, pos2, color, count, vel, -random, random );
break;
case TE_DLIGHT:
dl = CL_AllocDlight( 0 );
dl->origin[0] = MSG_ReadCoord( &buf );
dl->origin[1] = MSG_ReadCoord( &buf );
dl->origin[2] = MSG_ReadCoord( &buf );
dl->radius = (float)(MSG_ReadByte( &buf ) * 10.0f);
dl->color.r = MSG_ReadByte( &buf );
dl->color.g = MSG_ReadByte( &buf );
dl->color.b = MSG_ReadByte( &buf );
dl->die = cl.time + (float)(MSG_ReadByte( &buf ) * 0.1f);
dl->decay = (float)(MSG_ReadByte( &buf ) * 10.0f);
break;
case TE_ELIGHT:
dl = CL_AllocElight( MSG_ReadShort( &buf ));
dl->origin[0] = MSG_ReadCoord( &buf );
dl->origin[1] = MSG_ReadCoord( &buf );
dl->origin[2] = MSG_ReadCoord( &buf );
dl->radius = MSG_ReadCoord( &buf );
dl->color.r = MSG_ReadByte( &buf );
dl->color.g = MSG_ReadByte( &buf );
dl->color.b = MSG_ReadByte( &buf );
life = (float)MSG_ReadByte( &buf ) * 0.1f;
dl->die = cl.time + life;
dl->decay = MSG_ReadCoord( &buf );
if( life != 0 ) dl->decay /= life;
break;
case TE_TEXTMESSAGE:
CL_ParseTextMessage( &buf );
break;
case TE_LINE:
case TE_BOX:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
pos2[0] = MSG_ReadCoord( &buf );
pos2[1] = MSG_ReadCoord( &buf );
pos2[2] = MSG_ReadCoord( &buf );
life = (float)(MSG_ReadShort( &buf ) * 0.1f);
r = MSG_ReadByte( &buf );
g = MSG_ReadByte( &buf );
b = MSG_ReadByte( &buf );
if( type == TE_LINE ) R_ParticleLine( pos, pos2, r, g, b, life );
else R_ParticleBox( pos, pos2, r, g, b, life );
break;
case TE_LARGEFUNNEL:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
modelIndex = MSG_ReadShort( &buf );
flags = MSG_ReadShort( &buf );
R_LargeFunnel( pos, flags );
R_FunnelSprite( pos, modelIndex, flags );
break;
case TE_BLOODSTREAM:
case TE_BLOOD:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
pos2[0] = MSG_ReadCoord( &buf );
pos2[1] = MSG_ReadCoord( &buf );
pos2[2] = MSG_ReadCoord( &buf );
color = MSG_ReadByte( &buf );
count = MSG_ReadByte( &buf );
if( type == TE_BLOOD ) R_Blood( pos, pos2, color, count );
else R_BloodStream( pos, pos2, color, count );
break;
case TE_SHOWLINE:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
pos2[0] = MSG_ReadCoord( &buf );
pos2[1] = MSG_ReadCoord( &buf );
pos2[2] = MSG_ReadCoord( &buf );
R_ShowLine( pos, pos2 );
break;
case TE_DECAL:
case TE_DECALHIGH:
case TE_WORLDDECAL:
case TE_WORLDDECALHIGH:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
decalIndex = MSG_ReadByte( &buf );
if( type == TE_DECAL || type == TE_DECALHIGH )
entityIndex = MSG_ReadShort( &buf );
else entityIndex = 0;
if( type == TE_DECALHIGH || type == TE_WORLDDECALHIGH )
decalIndex += 256;
pEnt = CL_GetEntityByIndex( entityIndex );
if( pEnt ) modelIndex = pEnt->curstate.modelindex;
CL_DecalShoot( CL_DecalIndex( decalIndex ), entityIndex, modelIndex, pos, 0 );
break;
case TE_FIZZ:
entityIndex = MSG_ReadShort( &buf );
modelIndex = MSG_ReadShort( &buf );
scale = MSG_ReadByte( &buf ); // same as density
pEnt = CL_GetEntityByIndex( entityIndex );
R_FizzEffect( pEnt, modelIndex, scale );
break;
case TE_MODEL:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
pos2[0] = MSG_ReadCoord( &buf );
pos2[1] = MSG_ReadCoord( &buf );
pos2[2] = MSG_ReadCoord( &buf );
ang[0] = 0.0f;
ang[1] = MSG_ReadAngle( &buf ); // yaw angle
ang[2] = 0.0f;
modelIndex = MSG_ReadShort( &buf );
flags = MSG_ReadByte( &buf ); // sound flags
life = (float)(MSG_ReadByte( &buf ) * 0.1f);
R_TempModel( pos, pos2, ang, life, modelIndex, flags );
break;
case TE_EXPLODEMODEL:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
vel = MSG_ReadCoord( &buf );
modelIndex = MSG_ReadShort( &buf );
count = MSG_ReadShort( &buf );
life = (float)(MSG_ReadByte( &buf ) * 0.1f);
R_TempSphereModel( pos, vel, life, count, modelIndex );
break;
case TE_BREAKMODEL:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
pos2[0] = MSG_ReadCoord( &buf );
pos2[1] = MSG_ReadCoord( &buf );
pos2[2] = MSG_ReadCoord( &buf );
ang[0] = MSG_ReadCoord( &buf );
ang[1] = MSG_ReadCoord( &buf );
ang[2] = MSG_ReadCoord( &buf );
random = (float)MSG_ReadByte( &buf ) * 10.0f;
modelIndex = MSG_ReadShort( &buf );
count = MSG_ReadByte( &buf );
life = (float)(MSG_ReadByte( &buf ) * 0.1f);
flags = MSG_ReadByte( &buf );
R_BreakModel( pos, pos2, ang, random, life, count, modelIndex, (char)flags );
break;
case TE_GUNSHOTDECAL:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
entityIndex = MSG_ReadShort( &buf );
decalIndex = MSG_ReadByte( &buf );
pEnt = CL_GetEntityByIndex( entityIndex );
CL_DecalShoot( CL_DecalIndex( decalIndex ), entityIndex, 0, pos, 0 );
R_BulletImpactParticles( pos );
R_RicochetSound( pos );
break;
case TE_SPRAY:
case TE_SPRITE_SPRAY:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
pos2[0] = MSG_ReadCoord( &buf );
pos2[1] = MSG_ReadCoord( &buf );
pos2[2] = MSG_ReadCoord( &buf );
modelIndex = MSG_ReadShort( &buf );
count = MSG_ReadByte( &buf );
vel = (float)MSG_ReadByte( &buf );
random = (float)MSG_ReadByte( &buf );
if( type == TE_SPRAY )
{
flags = MSG_ReadByte( &buf ); // rendermode
R_Spray( pos, pos2, modelIndex, count, vel, random, flags );
}
else R_Sprite_Spray( pos, pos2, modelIndex, count, vel * 2.0f, random );
break;
case TE_ARMOR_RICOCHET:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
scale = (float)(MSG_ReadByte( &buf ) * 0.1f);
R_RicochetSprite( pos, cl_sprite_ricochet, 0.1f, scale );
R_RicochetSound( pos );
break;
case TE_PLAYERDECAL:
color = MSG_ReadByte( &buf ) - 1; // playernum
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
entityIndex = MSG_ReadShort( &buf );
decalIndex = MSG_ReadByte( &buf );
CL_PlayerDecal( color, decalIndex, entityIndex, pos );
break;
case TE_BUBBLES:
case TE_BUBBLETRAIL:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
pos2[0] = MSG_ReadCoord( &buf );
pos2[1] = MSG_ReadCoord( &buf );
pos2[2] = MSG_ReadCoord( &buf );
scale = MSG_ReadCoord( &buf ); // water height
modelIndex = MSG_ReadShort( &buf );
count = MSG_ReadByte( &buf );
vel = MSG_ReadCoord( &buf );
if( type == TE_BUBBLES ) R_Bubbles( pos, pos2, scale, modelIndex, count, vel );
else R_BubbleTrail( pos, pos2, scale, modelIndex, count, vel );
break;
case TE_BLOODSPRITE:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
modelIndex = MSG_ReadShort( &buf ); // sprite #1
decalIndex = MSG_ReadShort( &buf ); // sprite #2
color = MSG_ReadByte( &buf );
scale = (float)MSG_ReadByte( &buf );
R_BloodSprite( pos, color, modelIndex, decalIndex, scale );
break;
case TE_PROJECTILE:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
pos2[0] = MSG_ReadCoord( &buf );
pos2[1] = MSG_ReadCoord( &buf );
pos2[2] = MSG_ReadCoord( &buf );
modelIndex = MSG_ReadShort( &buf );
life = MSG_ReadByte( &buf );
color = MSG_ReadByte( &buf ); // playernum
R_Projectile( pos, pos2, modelIndex, life, color, NULL );
break;
case TE_PLAYERSPRITES:
color = MSG_ReadShort( &buf ); // entitynum
modelIndex = MSG_ReadShort( &buf );
count = MSG_ReadByte( &buf );
random = (float)MSG_ReadByte( &buf );
R_PlayerSprites( color, modelIndex, count, random );
break;
case TE_PARTICLEBURST:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
scale = (float)MSG_ReadShort( &buf );
color = MSG_ReadByte( &buf );
life = (float)(MSG_ReadByte( &buf ) * 0.1f);
R_ParticleBurst( pos, scale, color, life );
break;
case TE_FIREFIELD:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
scale = (float)MSG_ReadShort( &buf );
modelIndex = MSG_ReadShort( &buf );
count = MSG_ReadByte( &buf );
flags = MSG_ReadByte( &buf );
life = (float)(MSG_ReadByte( &buf ) * 0.1f);
R_FireField( pos, scale, modelIndex, count, flags, life );
break;
case TE_PLAYERATTACHMENT:
color = MSG_ReadByte( &buf ); // playernum
scale = MSG_ReadCoord( &buf ); // height
modelIndex = MSG_ReadShort( &buf );
life = (float)(MSG_ReadShort( &buf ) * 0.1f);
R_AttachTentToPlayer( color, modelIndex, scale, life );
break;
case TE_KILLPLAYERATTACHMENTS:
color = MSG_ReadByte( &buf ); // playernum
R_KillAttachedTents( color );
break;
case TE_MULTIGUNSHOT:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
pos2[0] = MSG_ReadCoord( &buf ) * 0.1f;
pos2[1] = MSG_ReadCoord( &buf ) * 0.1f;
pos2[2] = MSG_ReadCoord( &buf ) * 0.1f;
ang[0] = MSG_ReadCoord( &buf ) * 0.01f;
ang[1] = MSG_ReadCoord( &buf ) * 0.01f;
ang[2] = 0.0f;
count = MSG_ReadByte( &buf );
decalIndices[0] = MSG_ReadByte( &buf );
R_MultiGunshot( pos, pos2, ang, count, 1, decalIndices );
break;
case TE_USERTRACER:
pos[0] = MSG_ReadCoord( &buf );
pos[1] = MSG_ReadCoord( &buf );
pos[2] = MSG_ReadCoord( &buf );
pos2[0] = MSG_ReadCoord( &buf );
pos2[1] = MSG_ReadCoord( &buf );
pos2[2] = MSG_ReadCoord( &buf );
life = (float)(MSG_ReadByte( &buf ) * 0.1f);
color = MSG_ReadByte( &buf );
scale = (float)(MSG_ReadByte( &buf ) * 0.1f);
R_UserTracerParticle( pos, pos2, life, color, scale, 0, NULL );
break;
default:
Con_DPrintf( S_ERROR "ParseTempEntity: illegible TE message %i\n", type );
break;
}
// throw warning
if( MSG_CheckOverflow( &buf ))
Con_DPrintf( S_WARN "ParseTempEntity: overflow TE message\n" );
}
/*
==============================================================
LIGHT STYLE MANAGEMENT
==============================================================
*/
#define STYLE_LERPING_THRESHOLD 3.0f // because we wan't interpolate fast sequences (like on\off)
/*
================
CL_ClearLightStyles
================
*/
void CL_ClearLightStyles( void )
{
memset( cl.lightstyles, 0, sizeof( cl.lightstyles ));
}
void CL_SetLightstyle( int style, const char *s, float f )
{
int i, k;
lightstyle_t *ls;
float val1, val2;
Assert( s != NULL );
Assert( style >= 0 && style < MAX_LIGHTSTYLES );
ls = &cl.lightstyles[style];
Q_strncpy( ls->pattern, s, sizeof( ls->pattern ));
ls->length = Q_strlen( s );
ls->time = f; // set local time
for( i = 0; i < ls->length; i++ )
ls->map[i] = (float)(s[i] - 'a');
ls->interp = (ls->length <= 1) ? false : true;
// check for allow interpolate
// NOTE: fast flickering styles looks ugly when interpolation is running
for( k = 0; k < (ls->length - 1); k++ )
{
val1 = ls->map[(k+0) % ls->length];
val2 = ls->map[(k+1) % ls->length];
if( fabs( val1 - val2 ) > STYLE_LERPING_THRESHOLD )
{
ls->interp = false;
break;
}
}
Con_Reportf( "Lightstyle %i (%s), interp %s\n", style, ls->pattern, ls->interp ? "Yes" : "No" );
}
/*
==============================================================
DLIGHT MANAGEMENT
==============================================================
*/
dlight_t cl_dlights[MAX_DLIGHTS];
dlight_t cl_elights[MAX_ELIGHTS];
/*
================
CL_ClearDlights
================
*/
void CL_ClearDlights( void )
{
memset( cl_dlights, 0, sizeof( cl_dlights ));
memset( cl_elights, 0, sizeof( cl_elights ));
}
/*
===============
CL_AllocDlight
===============
*/
dlight_t *CL_AllocDlight( int key )
{
dlight_t *dl;
int i;
// first look for an exact key match
if( key )
{
for( i = 0, dl = cl_dlights; i < MAX_DLIGHTS; i++, dl++ )
{
if( dl->key == key )
{
// reuse this light
memset( dl, 0, sizeof( *dl ));
dl->key = key;
return dl;
}
}
}
// then look for anything else
for( i = 0, dl = cl_dlights; i < MAX_DLIGHTS; i++, dl++ )
{
if( dl->die < cl.time && dl->key == 0 )
{
memset( dl, 0, sizeof( *dl ));
dl->key = key;
return dl;
}
}
// otherwise grab first dlight
dl = &cl_dlights[0];
memset( dl, 0, sizeof( *dl ));
dl->key = key;
return dl;
}
/*
===============
CL_AllocElight
===============
*/
dlight_t *CL_AllocElight( int key )
{
dlight_t *dl;
int i;
// first look for an exact key match
if( key )
{
for( i = 0, dl = cl_elights; i < MAX_ELIGHTS; i++, dl++ )
{
if( dl->key == key )
{
// reuse this light
memset( dl, 0, sizeof( *dl ));
dl->key = key;
return dl;
}
}
}
// then look for anything else
for( i = 0, dl = cl_elights; i < MAX_ELIGHTS; i++, dl++ )
{
if( dl->die < cl.time && dl->key == 0 )
{
memset( dl, 0, sizeof( *dl ));
dl->key = key;
return dl;
}
}
// otherwise grab first dlight
dl = &cl_elights[0];
memset( dl, 0, sizeof( *dl ));
dl->key = key;
return dl;
}
/*
===============
CL_DecayLights
===============
*/
void CL_DecayLights( void )
{
dlight_t *dl;
float time;
int i;
time = cl.time - cl.oldtime;
for( i = 0, dl = cl_dlights; i < MAX_DLIGHTS; i++, dl++ )
{
if( !dl->radius ) continue;
dl->radius -= time * dl->decay;
if( dl->radius < 0 ) dl->radius = 0;
if( dl->die < cl.time || !dl->radius )
memset( dl, 0, sizeof( *dl ));
}
for( i = 0, dl = cl_elights; i < MAX_ELIGHTS; i++, dl++ )
{
if( !dl->radius ) continue;
dl->radius -= time * dl->decay;
if( dl->radius < 0 ) dl->radius = 0;
if( dl->die < cl.time || !dl->radius )
memset( dl, 0, sizeof( *dl ));
}
}
dlight_t *CL_GetDynamicLight( int number )
{
Assert( number >= 0 && number < MAX_DLIGHTS );
return &cl_dlights[number];
}
dlight_t *CL_GetEntityLight( int number )
{
Assert( number >= 0 && number < MAX_ELIGHTS );
return &cl_elights[number];
}
/*
================
CL_UpdateFlashlight
update client flashlight
================
*/
void CL_UpdateFlashlight( cl_entity_t *ent )
{
vec3_t forward, view_ofs;
vec3_t vecSrc, vecEnd;
float falloff;
pmtrace_t *trace;
cl_entity_t *hit;
dlight_t *dl;
if( ent->index == ( cl.playernum + 1 ))
{
// local player case
AngleVectors( cl.viewangles, forward, NULL, NULL );
VectorCopy( cl.viewheight, view_ofs );
}
else // non-local player case
{
vec3_t v_angle;
// NOTE: pitch divided by 3.0 twice. So we need apply 3^2 = 9
v_angle[PITCH] = ent->curstate.angles[PITCH] * 9.0f;
v_angle[YAW] = ent->angles[YAW];
v_angle[ROLL] = 0.0f; // roll not used
AngleVectors( v_angle, forward, NULL, NULL );
view_ofs[0] = view_ofs[1] = 0.0f;
// FIXME: these values are hardcoded ...
if( ent->curstate.usehull == 1 )
view_ofs[2] = 12.0f; // VEC_DUCK_VIEW;
else view_ofs[2] = 28.0f; // DEFAULT_VIEWHEIGHT
}
VectorAdd( ent->origin, view_ofs, vecSrc );
VectorMA( vecSrc, FLASHLIGHT_DISTANCE, forward, vecEnd );
trace = CL_VisTraceLine( vecSrc, vecEnd, PM_STUDIO_BOX );
// update flashlight endpos
dl = CL_AllocDlight( ent->index );
#if 1
hit = CL_GetEntityByIndex( clgame.pmove->visents[trace->ent].info );
if( hit && hit->model && ( hit->model->type == mod_alias || hit->model->type == mod_studio ))
VectorCopy( hit->origin, dl->origin );
else VectorCopy( trace->endpos, dl->origin );
#else
VectorCopy( trace->endpos, dl->origin );
#endif
// compute falloff
falloff = trace->fraction * FLASHLIGHT_DISTANCE;
if( falloff < 500.0f ) falloff = 1.0f;
else falloff = 500.0f / falloff;
falloff *= falloff;
// apply brigthness to dlight
dl->color.r = bound( 0, falloff * 255, 255 );
dl->color.g = bound( 0, falloff * 255, 255 );
dl->color.b = bound( 0, falloff * 255, 255 );
dl->die = cl.time + 0.01f; // die on next frame
dl->radius = 80;
}
/*
================
CL_AddEntityEffects
apply various effects to entity origin or attachment
================
*/
void CL_AddEntityEffects( cl_entity_t *ent )
{
// yellow flies effect 'monster stuck in the wall'
if( FBitSet( ent->curstate.effects, EF_BRIGHTFIELD ))
R_EntityParticles( ent );
if( FBitSet( ent->curstate.effects, EF_DIMLIGHT ))
{
if( ent->player && !Host_IsQuakeCompatible( ))
{
CL_UpdateFlashlight( ent );
}
else
{
dlight_t *dl = CL_AllocDlight( ent->index );
dl->color.r = dl->color.g = dl->color.b = 100;
dl->radius = COM_RandomFloat( 200, 231 );
VectorCopy( ent->origin, dl->origin );
dl->die = cl.time + 0.001;
}
}
if( FBitSet( ent->curstate.effects, EF_BRIGHTLIGHT ))
{
dlight_t *dl = CL_AllocDlight( ent->index );
dl->color.r = dl->color.g = dl->color.b = 250;
if( ent->player ) dl->radius = 400; // don't flickering
else dl->radius = COM_RandomFloat( 400, 431 );
VectorCopy( ent->origin, dl->origin );
dl->die = cl.time + 0.001;
dl->origin[2] += 16.0f;
}
// add light effect
if( FBitSet( ent->curstate.effects, EF_LIGHT ))
{
dlight_t *dl = CL_AllocDlight( ent->index );
dl->color.r = dl->color.g = dl->color.b = 100;
VectorCopy( ent->origin, dl->origin );
R_RocketFlare( ent->origin );
dl->die = cl.time + 0.001;
dl->radius = 200;
}
// studio models are handle muzzleflashes difference
if( FBitSet( ent->curstate.effects, EF_MUZZLEFLASH ) && Mod_AliasExtradata( ent->model ))
{
dlight_t *dl = CL_AllocDlight( ent->index );
vec3_t fv;
ClearBits( ent->curstate.effects, EF_MUZZLEFLASH );
dl->color.r = dl->color.g = dl->color.b = 100;
VectorCopy( ent->origin, dl->origin );
AngleVectors( ent->angles, fv, NULL, NULL );
dl->origin[2] += 16.0f;
VectorMA( dl->origin, 18, fv, dl->origin );
dl->radius = COM_RandomFloat( 200, 231 );
dl->die = cl.time + 0.1;
dl->minlight = 32;
}
}
/*
================
CL_AddModelEffects
these effects will be enable by flag in model header
================
*/
void CL_AddModelEffects( cl_entity_t *ent )
{
vec3_t neworigin;
vec3_t oldorigin;
if( !ent->model ) return;
switch( ent->model->type )
{
case mod_alias:
case mod_studio:
break;
default: return;
}
if( cls.demoplayback == DEMO_QUAKE1 )
{
VectorCopy( ent->baseline.vuser1, oldorigin );
VectorCopy( ent->origin, ent->baseline.vuser1 );
VectorCopy( ent->origin, neworigin );
}
else
{
VectorCopy( ent->prevstate.origin, oldorigin );
VectorCopy( ent->curstate.origin, neworigin );
}
// NOTE: this completely over control about angles and don't broke interpolation
if( FBitSet( ent->model->flags, STUDIO_ROTATE ))
ent->angles[1] = anglemod( 100.0f * cl.time );
if( FBitSet( ent->model->flags, STUDIO_GIB ))
R_RocketTrail( oldorigin, neworigin, 2 );
if( FBitSet( ent->model->flags, STUDIO_ZOMGIB ))
R_RocketTrail( oldorigin, neworigin, 4 );
if( FBitSet( ent->model->flags, STUDIO_TRACER ))
R_RocketTrail( oldorigin, neworigin, 3 );
if( FBitSet( ent->model->flags, STUDIO_TRACER2 ))
R_RocketTrail( oldorigin, neworigin, 5 );
if( FBitSet( ent->model->flags, STUDIO_ROCKET ))
{
dlight_t *dl = CL_AllocDlight( ent->index );
dl->color.r = dl->color.g = dl->color.b = 200;
VectorCopy( ent->origin, dl->origin );
// XASH SPECIFIC: get radius from head entity
if( ent->curstate.rendermode != kRenderNormal )
dl->radius = Q_max( 0, ent->curstate.renderamt - 55 );
else dl->radius = 200;
dl->die = cl.time + 0.01f;
R_RocketTrail( oldorigin, neworigin, 0 );
}
if( FBitSet( ent->model->flags, STUDIO_GRENADE ))
R_RocketTrail( oldorigin, neworigin, 1 );
if( FBitSet( ent->model->flags, STUDIO_TRACER3 ))
R_RocketTrail( oldorigin, neworigin, 6 );
}
/*
================
CL_TestLights
if cl_testlights is set, create 32 lights models
================
*/
void CL_TestLights( void )
{
int i, j, numLights;
vec3_t forward, right;
float f, r;
dlight_t *dl;
if( !CVAR_TO_BOOL( cl_testlights ))
return;
numLights = bound( 1, cl_testlights->value, MAX_DLIGHTS );
AngleVectors( cl.viewangles, forward, right, NULL );
for( i = 0; i < numLights; i++ )
{
dl = &cl_dlights[i];
r = 64 * ((i % 4) - 1.5f );
f = 64 * ( i / 4) + 128;
for( j = 0; j < 3; j++ )
dl->origin[j] = cl.simorg[j] + forward[j] * f + right[j] * r;
dl->color.r = ((((i % 6) + 1) & 1)>>0) * 255;
dl->color.g = ((((i % 6) + 1) & 2)>>1) * 255;
dl->color.b = ((((i % 6) + 1) & 4)>>2) * 255;
dl->radius = Q_max( 64, 200 - 5 * numLights );
dl->die = cl.time + host.frametime;
}
}
/*
==============================================================
DECAL MANAGEMENT
==============================================================
*/
/*
===============
CL_FireCustomDecal
custom temporary decal
===============
*/
void GAME_EXPORT CL_FireCustomDecal( int textureIndex, int entityIndex, int modelIndex, float *pos, int flags, float scale )
{
ref.dllFuncs.R_DecalShoot( textureIndex, entityIndex, modelIndex, pos, flags, scale );
}
/*
===============
CL_DecalShoot
normal temporary decal
===============
*/
void GAME_EXPORT CL_DecalShoot( int textureIndex, int entityIndex, int modelIndex, float *pos, int flags )
{
CL_FireCustomDecal( textureIndex, entityIndex, modelIndex, pos, flags, 1.0f );
}
/*
===============
CL_PlayerDecal
spray custom colored decal (clan logo etc)
===============
*/
void CL_PlayerDecal( int playernum, int customIndex, int entityIndex, float *pos )
{
int textureIndex = 0;
customization_t *pCust = NULL;
if( playernum < MAX_CLIENTS )
pCust = cl.players[playernum].customdata.pNext;
if( pCust != NULL && pCust->pBuffer != NULL && pCust->pInfo != NULL )
{
if( FBitSet( pCust->resource.ucFlags, RES_CUSTOM ) && pCust->resource.type == t_decal && pCust->bTranslated )
{
if( !pCust->nUserData1 && pCust->pInfo != NULL )
{
const char *decalname = va( "player%dlogo%d", playernum, customIndex );
pCust->nUserData1 = GL_LoadTextureInternal( decalname, pCust->pInfo, TF_DECAL );
}
textureIndex = pCust->nUserData1;
}
}
CL_DecalShoot( textureIndex, entityIndex, 0, pos, FDECAL_CUSTOM );
}
/*
===============
CL_DecalIndexFromName
get decal global index from decalname
===============
*/
int GAME_EXPORT CL_DecalIndexFromName( const char *name )
{
int i;
if( !COM_CheckString( name ))
return 0;
// look through the loaded sprite name list for SpriteName
for( i = 1; i < MAX_DECALS && host.draw_decals[i][0]; i++ )
{
if( !Q_stricmp( name, host.draw_decals[i] ))
return i;
}
return 0; // invalid decal
}
/*
===============
CL_DecalIndex
get texture index from decal global index
===============
*/
int GAME_EXPORT CL_DecalIndex( int id )
{
id = bound( 0, id, MAX_DECALS - 1 );
if( cl.decal_index[id] == 0 )
{
Image_SetForceFlags( IL_LOAD_DECAL );
cl.decal_index[id] = ref.dllFuncs.GL_LoadTexture( host.draw_decals[id], NULL, 0, TF_DECAL );
Image_ClearForceFlags();
}
return cl.decal_index[id];
}
/*
===============
CL_DecalRemoveAll
remove all decals with specified texture
===============
*/
void GAME_EXPORT CL_DecalRemoveAll( int textureIndex )
{
int id = bound( 0, textureIndex, MAX_DECALS - 1 );
ref.dllFuncs.R_DecalRemoveAll( cl.decal_index[id] );
}
/*
==============================================================
EFRAGS MANAGEMENT
==============================================================
*/
efrag_t cl_efrags[MAX_EFRAGS];
/*
==============
CL_ClearEfrags
==============
*/
void CL_ClearEfrags( void )
{
int i;
memset( cl_efrags, 0, sizeof( cl_efrags ));
// allocate the efrags and chain together into a free list
clgame.free_efrags = cl_efrags;
for( i = 0; i < MAX_EFRAGS - 1; i++ )
clgame.free_efrags[i].entnext = &clgame.free_efrags[i+1];
clgame.free_efrags[i].entnext = NULL;
}
/*
=======================
R_ClearStaticEntities
e.g. by demo request
=======================
*/
void CL_ClearStaticEntities( void )
{
int i;
if( host.type == HOST_DEDICATED )
return;
// clear out efrags in case the level hasn't been reloaded
for( i = 0; i < cl.worldmodel->numleafs; i++ )
cl.worldmodel->leafs[i+1].efrags = NULL;
clgame.numStatics = 0;
CL_ClearEfrags ();
}
/*
==============
CL_ClearEffects
==============
*/
void CL_ClearEffects( void )
{
CL_ClearEfrags ();
CL_ClearDlights ();
CL_ClearTempEnts ();
CL_ClearViewBeams ();
CL_ClearParticles ();
CL_ClearLightStyles ();
}