mirror of
https://github.com/w23/xash3d-fwgs
synced 2024-12-16 06:00:33 +01:00
5e0a0765ce
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:]]\+$//' {} \+ ```
3051 lines
76 KiB
C
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 ();
|
|
}
|
|
|