1727 lines
47 KiB
C++
1727 lines
47 KiB
C++
//=======================================================================
|
|
// Copyright XashXT Group 2010 ©
|
|
// r_tempents.cpp - tempentity management
|
|
//=======================================================================
|
|
|
|
#include "extdll.h"
|
|
#include "utils.h"
|
|
#include "studio_event.h"
|
|
#include "triangle_api.h"
|
|
#include "entity_types.h"
|
|
#include "pm_movevars.h"
|
|
#include "r_tempents.h"
|
|
#include "r_particle.h"
|
|
#include "pm_defs.h"
|
|
#include "ev_hldm.h"
|
|
#include "r_beams.h"
|
|
#include "hud.h"
|
|
|
|
extern TEMPENTITY *m_pEndFlare;
|
|
extern TEMPENTITY *m_pLaserSpot;
|
|
|
|
CTempEnts *g_pTempEnts;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CTempEnts::CTempEnts( void )
|
|
{
|
|
memset( m_TempEnts, 0, sizeof( TEMPENTITY ) * MAX_TEMP_ENTITIES );
|
|
|
|
m_pFreeTempEnts = m_TempEnts;
|
|
m_pActiveTempEnts = NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CTempEnts::~CTempEnts( void )
|
|
{
|
|
// free pvEngineData pointers
|
|
Clear();
|
|
}
|
|
|
|
void CTempEnts::TE_Prepare( TEMPENTITY *pTemp, int modelIndex )
|
|
{
|
|
int frameCount;
|
|
|
|
// Use these to set per-frame and termination conditions / actions
|
|
pTemp->flags = FTENT_NONE;
|
|
pTemp->die = GetClientTime() + 0.75f;
|
|
|
|
frameCount = Mod_GetFrames( modelIndex );
|
|
|
|
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 = max( 0, frameCount - 1 );
|
|
pTemp->entity.curstate.renderamt = 255;
|
|
pTemp->entity.curstate.body = 0;
|
|
pTemp->entity.curstate.skin = 0;
|
|
pTemp->fadeSpeed = 0.5f;
|
|
pTemp->hitSound = 0;
|
|
pTemp->clientIndex = 0;
|
|
pTemp->bounceFactor = 1;
|
|
pTemp->entity.curstate.scale = 1.0f;
|
|
}
|
|
|
|
int CTempEnts::TE_Active( TEMPENTITY *pTemp )
|
|
{
|
|
bool active = true;
|
|
float life;
|
|
|
|
if( !pTemp ) return false;
|
|
life = pTemp->die - GetClientTime();
|
|
|
|
if( life < 0.0f )
|
|
{
|
|
if( pTemp->flags & FTENT_FADEOUT )
|
|
{
|
|
int alpha;
|
|
|
|
if( pTemp->entity.curstate.rendermode == kRenderNormal )
|
|
pTemp->entity.curstate.rendermode = kRenderTransTexture;
|
|
|
|
alpha = pTemp->entity.baseline.renderamt * ( 1.0f + life * pTemp->fadeSpeed );
|
|
|
|
if( alpha <= 0 )
|
|
{
|
|
active = false;
|
|
alpha = 0;
|
|
}
|
|
pTemp->entity.curstate.renderamt = alpha;
|
|
}
|
|
else
|
|
{
|
|
active = false;
|
|
}
|
|
}
|
|
|
|
// never die tempents only die when their die is cleared
|
|
if( pTemp->flags & FTENT_NEVERDIE )
|
|
{
|
|
active = (pTemp->die != 0.0f);
|
|
}
|
|
return active;
|
|
}
|
|
|
|
int CTempEnts::TE_Update( TEMPENTITY *pTemp, float frametime )
|
|
{
|
|
// before first frame when movevars not initialized
|
|
if( !gpMovevars )
|
|
{
|
|
gEngfuncs.Con_Printf( "ERROR: TempEntUpdate: no movevars!!!\n" );
|
|
return true;
|
|
}
|
|
|
|
float gravity, gravitySlow, fastFreq;
|
|
|
|
fastFreq = GetClientTime() * 5.5;
|
|
gravity = -frametime * gpMovevars->gravity;
|
|
gravitySlow = gravity * 0.5;
|
|
|
|
// in order to have tents collide with players, we have to run the player prediction code so
|
|
// that the client has the player list. We run this code once when we detect any COLLIDEALL
|
|
// tent, then set this BOOL to true so the code doesn't get run again if there's more than
|
|
// one COLLIDEALL ent for this update. (often are).
|
|
gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true );
|
|
|
|
// Store off the old count
|
|
gEngfuncs.pEventAPI->EV_PushPMStates();
|
|
|
|
// Now add in all of the players.
|
|
gEngfuncs.pEventAPI->EV_SetSolidPlayers ( -1 );
|
|
|
|
// save oldorigin
|
|
pTemp->entity.prevstate.origin = pTemp->entity.origin;
|
|
|
|
if( pTemp->flags & FTENT_SPARKSHOWER )
|
|
{
|
|
// adjust speed if it's time
|
|
// scale is next think time
|
|
if( GetClientTime() > pTemp->entity.baseline.scale )
|
|
{
|
|
// show Sparks
|
|
SparkEffect( pTemp->entity.origin, 8, -200, 200 );
|
|
|
|
// reduce life
|
|
pTemp->entity.baseline.framerate -= 0.1f;
|
|
|
|
if( pTemp->entity.baseline.framerate <= 0.0f )
|
|
{
|
|
pTemp->die = GetClientTime();
|
|
}
|
|
else
|
|
{
|
|
// so it will die no matter what
|
|
pTemp->die = GetClientTime() + 0.5f;
|
|
|
|
// next think
|
|
pTemp->entity.baseline.scale = GetClientTime() + 0.1f;
|
|
}
|
|
}
|
|
}
|
|
else if( pTemp->flags & FTENT_PLYRATTACHMENT )
|
|
{
|
|
cl_entity_t *pClient = GetEntityByIndex( pTemp->clientIndex );
|
|
|
|
if( pClient )
|
|
{
|
|
if( EV_IsLocal( pClient->index ))
|
|
{
|
|
// NOTE: if this a local client ?
|
|
// relink attachment with her viewmodel
|
|
pClient = GetViewEntity();
|
|
}
|
|
pTemp->entity.origin = pClient->origin + pTemp->tentOffset;
|
|
}
|
|
}
|
|
// same as FTENT_PLYRATTACHMENT but offset will be updated every frame
|
|
else if( pTemp->flags & FTENT_ATTACHMENT )
|
|
{
|
|
cl_entity_t *pClient = GetEntityByIndex( pTemp->clientIndex );
|
|
|
|
if( pClient )
|
|
{
|
|
if( EV_IsLocal( pClient->index ))
|
|
{
|
|
// NOTE: if this a local client ?
|
|
// relink attachment with her viewmodel
|
|
pClient = GetViewEntity();
|
|
}
|
|
|
|
pTemp->entity.origin = pClient->origin;
|
|
|
|
if( pTemp->entity.baseline.body > 0 )
|
|
pTemp->entity.origin += pClient->attachment[pTemp->entity.baseline.body - 1];
|
|
}
|
|
}
|
|
else if( pTemp->flags & FTENT_SINEWAVE )
|
|
{
|
|
pTemp->x += pTemp->entity.baseline.origin.x * frametime;
|
|
pTemp->y += pTemp->entity.baseline.origin.y * frametime;
|
|
|
|
pTemp->entity.origin.x = pTemp->x + sin( pTemp->entity.baseline.origin.z + GetClientTime() ) * ( 10 * pTemp->entity.curstate.scale );
|
|
pTemp->entity.origin.y = pTemp->y + sin( pTemp->entity.baseline.origin.z + fastFreq + 0.7f ) * ( 8 * pTemp->entity.curstate.scale);
|
|
pTemp->entity.origin.z = pTemp->entity.origin.z + pTemp->entity.baseline.origin[2] * frametime;
|
|
}
|
|
else if( pTemp->flags & FTENT_SPIRAL )
|
|
{
|
|
float s, c;
|
|
s = sin( pTemp->entity.baseline.origin.z + fastFreq );
|
|
c = cos( pTemp->entity.baseline.origin.z + fastFreq );
|
|
|
|
pTemp->entity.origin.x = pTemp->entity.origin.x + pTemp->entity.baseline.origin.x * frametime + 8 * sin( GetClientTime() * 20 );
|
|
pTemp->entity.origin.y = pTemp->entity.origin.y + pTemp->entity.baseline.origin.y * frametime + 4 * sin( GetClientTime() * 30 );
|
|
pTemp->entity.origin.z = pTemp->entity.origin.z + pTemp->entity.baseline.origin.z * frametime;
|
|
}
|
|
else
|
|
{
|
|
// just add linear velocity
|
|
pTemp->entity.origin = pTemp->entity.origin + pTemp->entity.baseline.origin * frametime;
|
|
}
|
|
|
|
if( pTemp->flags & FTENT_SPRANIMATE )
|
|
{
|
|
pTemp->entity.curstate.frame += frametime * pTemp->entity.curstate.framerate;
|
|
|
|
if( pTemp->entity.curstate.frame >= pTemp->frameMax )
|
|
{
|
|
pTemp->entity.curstate.frame = pTemp->entity.curstate.frame - (int)(pTemp->entity.curstate.frame);
|
|
|
|
if(!( pTemp->flags & FTENT_SPRANIMATELOOP ))
|
|
{
|
|
// this animating sprite isn't set to loop, so destroy it.
|
|
pTemp->die = 0.0f;
|
|
|
|
// restore state info
|
|
gEngfuncs.pEventAPI->EV_PopPMStates();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else if( pTemp->flags & FTENT_SPRCYCLE )
|
|
{
|
|
pTemp->entity.curstate.frame += frametime * 10;
|
|
if( pTemp->entity.curstate.frame >= pTemp->frameMax )
|
|
{
|
|
pTemp->entity.curstate.frame = pTemp->entity.curstate.frame - (int)(pTemp->entity.curstate.frame);
|
|
}
|
|
}
|
|
|
|
if( pTemp->flags & FTENT_SCALE )
|
|
{
|
|
// this only used for Egon effect
|
|
pTemp->entity.curstate.scale += frametime * 10;
|
|
}
|
|
|
|
if( pTemp->flags & FTENT_ROTATE )
|
|
{
|
|
// just add angular velocity
|
|
pTemp->entity.angles += pTemp->entity.baseline.angles * frametime;
|
|
pTemp->entity.latched.prevangles = pTemp->entity.angles;
|
|
}
|
|
|
|
if( pTemp->flags & ( FTENT_COLLIDEALL|FTENT_COLLIDEWORLD ))
|
|
{
|
|
Vector traceNormal;
|
|
float traceFraction = 1.0f;
|
|
|
|
traceNormal.Init();
|
|
|
|
if( pTemp->flags & FTENT_COLLIDEALL )
|
|
{
|
|
pmtrace_t pmtrace;
|
|
physent_t *pe;
|
|
|
|
gEngfuncs.pEventAPI->EV_SetTraceHull( 2 );
|
|
gEngfuncs.pEventAPI->EV_PlayerTrace( pTemp->entity.prevstate.origin, pTemp->entity.origin, PM_STUDIO_BOX, -1, &pmtrace );
|
|
|
|
if ( pmtrace.fraction != 1 )
|
|
{
|
|
pe = gEngfuncs.pEventAPI->EV_GetPhysent( pmtrace.ent );
|
|
|
|
if( !pmtrace.ent || ( pe->info != pTemp->clientIndex ))
|
|
{
|
|
traceFraction = pmtrace.fraction;
|
|
traceNormal = pmtrace.plane.normal;
|
|
|
|
if ( pTemp->hitcallback )
|
|
(*pTemp->hitcallback)( pTemp, &pmtrace );
|
|
}
|
|
}
|
|
}
|
|
else if( pTemp->flags & FTENT_COLLIDEWORLD )
|
|
{
|
|
pmtrace_t pmtrace;
|
|
|
|
gEngfuncs.pEventAPI->EV_SetTraceHull( 2 );
|
|
gEngfuncs.pEventAPI->EV_PlayerTrace( pTemp->entity.prevstate.origin, pTemp->entity.origin, PM_STUDIO_BOX|PM_WORLD_ONLY, -1, &pmtrace );
|
|
|
|
if( pmtrace.fraction != 1.0f )
|
|
{
|
|
traceFraction = pmtrace.fraction;
|
|
traceNormal = pmtrace.plane.normal;
|
|
|
|
if ( pTemp->flags & FTENT_SPARKSHOWER )
|
|
{
|
|
// chop spark speeds a bit more
|
|
pTemp->entity.baseline.origin *= 0.6f;
|
|
|
|
if( pTemp->entity.baseline.origin.Length() < 10.0f )
|
|
pTemp->entity.baseline.framerate = 0.0f;
|
|
}
|
|
|
|
if( pTemp->hitcallback )
|
|
(*pTemp->hitcallback)( pTemp, &pmtrace );
|
|
}
|
|
}
|
|
|
|
if( traceFraction != 1.0f ) // Decent collision now, and damping works
|
|
{
|
|
float proj, damp;
|
|
|
|
// Place at contact point
|
|
pTemp->entity.origin = pTemp->entity.prevstate.origin + (traceFraction * frametime) * pTemp->entity.baseline.origin;
|
|
|
|
// Damp velocity
|
|
damp = pTemp->bounceFactor;
|
|
if( pTemp->flags & ( FTENT_GRAVITY|FTENT_SLOWGRAVITY ))
|
|
{
|
|
damp *= 0.5f;
|
|
if( traceNormal[2] > 0.9f ) // Hit floor?
|
|
{
|
|
if( pTemp->entity.baseline.origin[2] <= 0 && pTemp->entity.baseline.origin[2] >= gravity * 3 )
|
|
{
|
|
pTemp->flags &= ~(FTENT_SLOWGRAVITY|FTENT_ROTATE|FTENT_GRAVITY);
|
|
pTemp->flags &= ~(FTENT_COLLIDEWORLD|FTENT_SMOKETRAIL);
|
|
pTemp->entity.angles.x = pTemp->entity.angles.z = 0;
|
|
damp = 0; // stop
|
|
}
|
|
}
|
|
}
|
|
|
|
if( pTemp->hitSound )
|
|
{
|
|
PlaySound( pTemp, damp );
|
|
}
|
|
|
|
if( pTemp->flags & FTENT_COLLIDEKILL )
|
|
{
|
|
// die on impact
|
|
pTemp->flags &= ~FTENT_FADEOUT;
|
|
pTemp->die = GetClientTime();
|
|
}
|
|
else
|
|
{
|
|
// reflect velocity
|
|
if( damp != 0 )
|
|
{
|
|
proj = DotProduct( pTemp->entity.baseline.origin, traceNormal );
|
|
pTemp->entity.baseline.origin += (-proj * 2) * traceNormal;
|
|
|
|
// reflect rotation (fake)
|
|
pTemp->entity.angles.y = -pTemp->entity.angles.y;
|
|
}
|
|
|
|
if( damp != 1.0f )
|
|
{
|
|
pTemp->entity.baseline.origin *= damp;
|
|
pTemp->entity.angles *= 0.9f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// FIXME: this code is right ???
|
|
if(( pTemp->flags & FTENT_FLICKER ) && m_iTempEntFrame == pTemp->entity.curstate.effects )
|
|
{
|
|
dlight_t *dl = gEngfuncs.pEfxAPI->CL_AllocDLight( 0 );
|
|
|
|
dl->origin = pTemp->entity.origin;
|
|
dl->radius = 60;
|
|
dl->color.r = 255;
|
|
dl->color.g = 120;
|
|
dl->color.b = 0;
|
|
dl->die = GetClientTime() + 0.01f;
|
|
}
|
|
|
|
if( pTemp->flags & FTENT_SMOKETRAIL )
|
|
{
|
|
g_pParticles->RocketTrail( pTemp->entity.prevstate.origin, pTemp->entity.origin, 1 );
|
|
}
|
|
|
|
if( pTemp->flags & FTENT_GRAVITY )
|
|
pTemp->entity.baseline.origin.z += gravity;
|
|
else if( pTemp->flags & FTENT_SLOWGRAVITY )
|
|
pTemp->entity.baseline.origin.z += gravitySlow;
|
|
|
|
if( pTemp->flags & FTENT_CLIENTCUSTOM )
|
|
{
|
|
if( pTemp->callback )
|
|
(*pTemp->callback)( pTemp, frametime, GetClientTime( ));
|
|
}
|
|
|
|
if( pTemp->flags & FTENT_WINDBLOWN )
|
|
{
|
|
Vector vecWind = gHUD.m_vecWindVelocity;
|
|
|
|
for( int i = 0 ; i < 2 ; i++ )
|
|
{
|
|
if( pTemp->entity.baseline.origin[i] < vecWind[i] )
|
|
{
|
|
pTemp->entity.baseline.origin[i] += ( frametime * TENT_WIND_ACCEL );
|
|
|
|
// clamp
|
|
if( pTemp->entity.baseline.origin[i] > vecWind[i] )
|
|
pTemp->entity.baseline.origin[i] = vecWind[i];
|
|
}
|
|
else if( pTemp->entity.baseline.origin[i] > vecWind[i] )
|
|
{
|
|
pTemp->entity.baseline.origin[i] -= ( frametime * TENT_WIND_ACCEL );
|
|
|
|
// clamp
|
|
if( pTemp->entity.baseline.origin[i] < vecWind[i] )
|
|
pTemp->entity.baseline.origin[i] = vecWind[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
// restore state info
|
|
gEngfuncs.pEventAPI->EV_PopPMStates();
|
|
|
|
return true;
|
|
}
|
|
|
|
void CTempEnts :: Update( void )
|
|
{
|
|
TEMPENTITY *current, *pnext, *pprev;
|
|
|
|
m_fOldTime = m_flTime;
|
|
m_flTime = GetClientTime();
|
|
float frametime = m_flTime - m_fOldTime;
|
|
|
|
if ( !m_pActiveTempEnts )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// !!!BUGBUG -- This needs to be time based
|
|
// g-cont. it's used only for flickering dlights, what difference ?
|
|
m_iTempEntFrame = ( m_iTempEntFrame + 1 ) & 31;
|
|
|
|
current = m_pActiveTempEnts;
|
|
|
|
// !!! Don't simulate while paused.... This is sort of a hack, revisit.
|
|
if( IN_GAME() == 0 )
|
|
{
|
|
while( current )
|
|
{
|
|
gEngfuncs.CL_CreateVisibleEntity( ET_TEMPENTITY, ¤t->entity, -1 );
|
|
current = current->next;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pprev = NULL;
|
|
|
|
while( current )
|
|
{
|
|
bool fTempEntFreed = false;
|
|
|
|
pnext = current->next;
|
|
|
|
// Kill it
|
|
if( !TE_Active( current ) || !TE_Update( current, frametime ))
|
|
{
|
|
TempEntFree( current, pprev );
|
|
fTempEntFreed = true;
|
|
}
|
|
else
|
|
{
|
|
// renderer rejected entity for some reasons...
|
|
if( !gEngfuncs.CL_CreateVisibleEntity( ET_TEMPENTITY, ¤t->entity, -1 ))
|
|
{
|
|
if(!( current->flags & FTENT_PERSIST ))
|
|
{
|
|
// If we can't draw it this frame, just dump it.
|
|
current->die = GetClientTime();
|
|
// don't fade out, just die
|
|
current->flags &= ~FTENT_FADEOUT;
|
|
|
|
TempEntFree( current, pprev );
|
|
fTempEntFreed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !fTempEntFreed )
|
|
pprev = current;
|
|
current = pnext;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Clear existing temp entities
|
|
//-----------------------------------------------------------------------------
|
|
void CTempEnts::Clear( void )
|
|
{
|
|
m_iTempEntFrame = 0;
|
|
|
|
// update muzzleflash indexes
|
|
m_iMuzzleFlash[0] = gEngfuncs.pEventAPI->EV_FindModelIndex( "sprites/muzzleflash1.spr" );
|
|
m_iMuzzleFlash[1] = gEngfuncs.pEventAPI->EV_FindModelIndex( "sprites/muzzleflash2.spr" );
|
|
m_iMuzzleFlash[2] = gEngfuncs.pEventAPI->EV_FindModelIndex( "sprites/muzzleflash3.spr" );
|
|
m_iMuzzleFlash[3] = gEngfuncs.pEventAPI->EV_FindModelIndex( "sprites/muzzleflash.spr" );
|
|
|
|
hSprGlowShell = TEX_Load( "renderfx/glowshell" );
|
|
|
|
for ( int i = 0; i < MAX_TEMP_ENTITIES-1; i++ )
|
|
{
|
|
m_TempEnts[i].next = &m_TempEnts[i+1];
|
|
}
|
|
|
|
m_TempEnts[MAX_TEMP_ENTITIES-1].next = NULL;
|
|
m_pFreeTempEnts = m_TempEnts;
|
|
m_pActiveTempEnts = NULL;
|
|
|
|
// clearing user pointers
|
|
m_pLaserSpot = NULL;
|
|
m_pEndFlare = NULL;
|
|
}
|
|
|
|
void CTempEnts::TempEntFree( TEMPENTITY *pTemp, TEMPENTITY *pPrev )
|
|
{
|
|
// Remove from the active list.
|
|
if( pPrev )
|
|
{
|
|
pPrev->next = pTemp->next;
|
|
}
|
|
else
|
|
{
|
|
m_pActiveTempEnts = pTemp->next;
|
|
}
|
|
|
|
memset( pTemp, 0, sizeof( TEMPENTITY ));
|
|
|
|
// Add to the free list.
|
|
pTemp->next = m_pFreeTempEnts;
|
|
m_pFreeTempEnts = pTemp;
|
|
}
|
|
|
|
// free the first low priority tempent it finds.
|
|
bool CTempEnts::FreeLowPriorityTempEnt( void )
|
|
{
|
|
TEMPENTITY *pActive = m_pActiveTempEnts;
|
|
TEMPENTITY *pPrev = NULL;
|
|
|
|
while( pActive )
|
|
{
|
|
if( pActive->priority == TENTPRIORITY_LOW )
|
|
{
|
|
TempEntFree( pActive, pPrev );
|
|
return true;
|
|
}
|
|
|
|
pPrev = pActive;
|
|
pActive = pActive->next;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Allocate temp entity ( normal/low priority )
|
|
// Input : *org -
|
|
// *model -
|
|
// Output : C_LocalTempEntity
|
|
//-----------------------------------------------------------------------------
|
|
TEMPENTITY *CTempEnts::TempEntAlloc( const Vector& org, int modelIndex )
|
|
{
|
|
TEMPENTITY *pTemp;
|
|
|
|
if ( !m_pFreeTempEnts )
|
|
{
|
|
gEngfuncs.Con_Printf( "Overflow %d temporary ents!\n", MAX_TEMP_ENTITIES );
|
|
return NULL;
|
|
}
|
|
|
|
pTemp = m_pFreeTempEnts;
|
|
m_pFreeTempEnts = pTemp->next;
|
|
|
|
TE_Prepare( pTemp, modelIndex );
|
|
|
|
pTemp->priority = TENTPRIORITY_LOW;
|
|
if( org ) pTemp->entity.origin = org;
|
|
|
|
pTemp->next = m_pActiveTempEnts;
|
|
m_pActiveTempEnts = pTemp;
|
|
|
|
return pTemp;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Allocate a temp entity, if there are no slots, kick out a low priority
|
|
// one if possible
|
|
// Input : *org -
|
|
// *model -
|
|
// Output : C_LocalTempEntity
|
|
//-----------------------------------------------------------------------------
|
|
TEMPENTITY *CTempEnts::TempEntAllocHigh( const Vector& org, int modelIndex )
|
|
{
|
|
TEMPENTITY *pTemp;
|
|
|
|
if ( !m_pFreeTempEnts )
|
|
{
|
|
// no temporary ents free, so find the first active low-priority temp ent
|
|
// and overwrite it.
|
|
FreeLowPriorityTempEnt();
|
|
}
|
|
|
|
if ( !m_pFreeTempEnts )
|
|
{
|
|
// 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.
|
|
gEngfuncs.Con_Printf( "Couldn't alloc a high priority TENT!\n" );
|
|
return NULL;
|
|
}
|
|
|
|
// Move out of the free list and into the active list.
|
|
pTemp = m_pFreeTempEnts;
|
|
m_pFreeTempEnts = pTemp->next;
|
|
|
|
pTemp->next = m_pActiveTempEnts;
|
|
m_pActiveTempEnts = pTemp;
|
|
|
|
TE_Prepare( pTemp, modelIndex );
|
|
|
|
pTemp->priority = TENTPRIORITY_HIGH;
|
|
if( org ) pTemp->entity.origin = org;
|
|
|
|
return pTemp;
|
|
}
|
|
|
|
|
|
TEMPENTITY *CTempEnts::TempEntAllocNoModel( const Vector& org )
|
|
{
|
|
return TempEntAlloc( org, 0 );
|
|
}
|
|
|
|
TEMPENTITY *CTempEnts::TempEntAllocCustom( const Vector& org, int modelIndex, int high, void ( *callback )( struct tempent_s *ent, float frametime, float currenttime ))
|
|
{
|
|
TEMPENTITY *pTemp;
|
|
|
|
if( high )
|
|
{
|
|
pTemp = TempEntAllocHigh( org, modelIndex );
|
|
}
|
|
else
|
|
{
|
|
pTemp = TempEntAlloc( org, modelIndex );
|
|
}
|
|
|
|
if( pTemp && callback )
|
|
{
|
|
pTemp->callback = callback;
|
|
pTemp->flags |= FTENT_CLIENTCUSTOM;
|
|
}
|
|
|
|
return pTemp;
|
|
}
|
|
|
|
/*
|
|
==============================================================
|
|
|
|
BASE TEMPENTS CODE
|
|
|
|
==============================================================
|
|
*/
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Create a fizz effect
|
|
// Input : *pent -
|
|
// modelIndex -
|
|
// density -
|
|
//-----------------------------------------------------------------------------
|
|
void CTempEnts::FizzEffect( cl_entity_t *pent, int modelIndex, int density )
|
|
{
|
|
TEMPENTITY *pTemp;
|
|
int i, width, depth, count, frameCount;
|
|
float angle, maxHeight, speed, xspeed, yspeed;
|
|
Vector origin;
|
|
Vector mins, maxs;
|
|
|
|
if( !pent || Mod_GetModelType( modelIndex ) == mod_bad )
|
|
return;
|
|
|
|
count = density + 1;
|
|
density = count * 3 + 6;
|
|
|
|
Mod_GetBounds( pent->curstate.modelindex, mins, maxs );
|
|
|
|
maxHeight = maxs[2] - mins[2];
|
|
width = maxs[0] - mins[0];
|
|
depth = maxs[1] - mins[1];
|
|
speed = ( pent->curstate.rendercolor.r<<8 | pent->curstate.rendercolor.g );
|
|
if( pent->curstate.rendercolor.b ) speed = -speed;
|
|
if( speed == 0.0f ) speed = 100.0f; // apply default value
|
|
|
|
if( pent->angles[YAW] != 0.0f )
|
|
{
|
|
angle = pent->angles[YAW] * M_PI / 180;
|
|
yspeed = sin( angle );
|
|
xspeed = cos( angle );
|
|
|
|
xspeed *= speed;
|
|
yspeed *= speed;
|
|
}
|
|
else xspeed = yspeed = 0.0f; // zonly
|
|
|
|
frameCount = Mod_GetFrames( modelIndex );
|
|
|
|
for ( i = 0; i < count; i++ )
|
|
{
|
|
origin[0] = mins[0] + RANDOM_LONG( 0, width - 1 );
|
|
origin[1] = mins[1] + RANDOM_LONG( 0, depth - 1 );
|
|
origin[2] = mins[2];
|
|
pTemp = TempEntAlloc( origin, modelIndex );
|
|
|
|
if ( !pTemp ) return;
|
|
|
|
pTemp->flags |= FTENT_SINEWAVE;
|
|
|
|
pTemp->x = origin[0];
|
|
pTemp->y = origin[1];
|
|
|
|
float zspeed = RANDOM_LONG( 80, 140 );
|
|
pTemp->entity.baseline.origin = Vector( xspeed, yspeed, zspeed );
|
|
pTemp->die = GetClientTime() + ( maxHeight / zspeed ) - 0.1f;
|
|
pTemp->entity.curstate.frame = RANDOM_LONG( 0, frameCount - 1 );
|
|
// Set sprite scale
|
|
pTemp->entity.curstate.scale = 1.0f / RANDOM_FLOAT( 2, 5 );
|
|
pTemp->entity.curstate.rendermode = kRenderTransAlpha;
|
|
pTemp->entity.curstate.renderamt = pTemp->entity.baseline.renderamt = 255;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Create bubbles
|
|
// Input : *mins -
|
|
// *maxs -
|
|
// height -
|
|
// modelIndex -
|
|
// count -
|
|
// speed -
|
|
//-----------------------------------------------------------------------------
|
|
void CTempEnts::Bubbles( const Vector &mins, const Vector &maxs, float height, int modelIndex, int count, float speed )
|
|
{
|
|
TEMPENTITY *pTemp;
|
|
int i, frameCount;
|
|
float sine, cosine;
|
|
Vector origin;
|
|
|
|
if( Mod_GetModelType( modelIndex ) == mod_bad )
|
|
return;
|
|
|
|
frameCount = Mod_GetFrames( modelIndex );
|
|
|
|
for ( i = 0; i < count; i++ )
|
|
{
|
|
origin[0] = RANDOM_LONG( mins[0], maxs[0] );
|
|
origin[1] = RANDOM_LONG( mins[1], maxs[1] );
|
|
origin[2] = RANDOM_LONG( mins[2], maxs[2] );
|
|
pTemp = TempEntAlloc( origin, modelIndex );
|
|
if ( !pTemp ) return;
|
|
|
|
pTemp->flags |= FTENT_SINEWAVE;
|
|
|
|
pTemp->x = origin[0];
|
|
pTemp->y = origin[1];
|
|
float angle = RANDOM_LONG( -M_PI, M_PI );
|
|
sine = sin( angle );
|
|
cosine = cos( angle );
|
|
|
|
float zspeed = RANDOM_LONG( 80, 140 );
|
|
pTemp->entity.baseline.origin = Vector( speed * cosine, speed * sine, zspeed );
|
|
pTemp->die = GetClientTime() + ((height - (origin[2] - mins[2])) / zspeed) - 0.1f;
|
|
pTemp->entity.curstate.frame = RANDOM_LONG( 0, frameCount - 1 );
|
|
|
|
// Set sprite scale
|
|
pTemp->entity.curstate.scale = 1.0 / RANDOM_FLOAT( 4, 16 );
|
|
pTemp->entity.curstate.rendermode = kRenderTransAlpha;
|
|
pTemp->entity.curstate.renderamt = pTemp->entity.baseline.renderamt = 192; // g-cont. why difference with FizzEffect ???
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Create bubble trail
|
|
// Input : *start -
|
|
// *end -
|
|
// height -
|
|
// modelIndex -
|
|
// count -
|
|
// speed -
|
|
//-----------------------------------------------------------------------------
|
|
void CTempEnts::BubbleTrail( const Vector &start, const Vector &end, float flWaterZ, int modelIndex, int count, float speed )
|
|
{
|
|
TEMPENTITY *pTemp;
|
|
int i, frameCount;
|
|
float dist, angle;
|
|
Vector origin;
|
|
|
|
if( Mod_GetModelType( modelIndex ) == mod_bad )
|
|
return;
|
|
|
|
frameCount = Mod_GetFrames( modelIndex );
|
|
|
|
for ( i = 0; i < count; i++ )
|
|
{
|
|
dist = RANDOM_FLOAT( 0, 1.0 );
|
|
origin = LerpPoint( start, end, dist );
|
|
pTemp = TempEntAlloc( origin, modelIndex );
|
|
if ( !pTemp ) return;
|
|
|
|
pTemp->flags |= FTENT_SINEWAVE;
|
|
|
|
pTemp->x = origin[0];
|
|
pTemp->y = origin[1];
|
|
angle = RANDOM_LONG( -M_PI, M_PI );
|
|
|
|
float zspeed = RANDOM_LONG( 80, 140 );
|
|
pTemp->entity.baseline.origin = Vector( speed * cos( angle ), speed * sin( angle ), zspeed );
|
|
pTemp->die = GetClientTime() + (( flWaterZ - origin[2]) / zspeed ) - 0.1f;
|
|
pTemp->entity.curstate.frame = RANDOM_LONG( 0, frameCount - 1 );
|
|
// Set sprite scale
|
|
pTemp->entity.curstate.scale = 1.0 / RANDOM_FLOAT( 4, 8 );
|
|
pTemp->entity.curstate.rendermode = kRenderTransAlpha;
|
|
pTemp->entity.curstate.renderamt = pTemp->entity.baseline.renderamt = 192;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Attaches entity to player
|
|
// Input : client -
|
|
// modelIndex -
|
|
// zoffset -
|
|
// life -
|
|
//-----------------------------------------------------------------------------
|
|
void CTempEnts::AttachTentToPlayer( int client, int modelIndex, float zoffset, float life )
|
|
{
|
|
TEMPENTITY *pTemp;
|
|
Vector position;
|
|
int frameCount;
|
|
|
|
if ( client <= 0 || client > gEngfuncs.GetMaxClients() )
|
|
{
|
|
gEngfuncs.Con_Printf( "Bad client in AttachTentToPlayer()!\n" );
|
|
return;
|
|
}
|
|
|
|
cl_entity_t *pClient = GetEntityByIndex( client );
|
|
if ( !pClient )
|
|
{
|
|
gEngfuncs.Con_Printf( "Couldn't get ClientEntity for %i\n", client );
|
|
return;
|
|
}
|
|
|
|
if( Mod_GetModelType( modelIndex ) == mod_bad )
|
|
{
|
|
gEngfuncs.Con_Printf( "No model %d!\n", modelIndex );
|
|
return;
|
|
}
|
|
|
|
position = pClient->origin;
|
|
position[2] += zoffset;
|
|
|
|
pTemp = TempEntAllocHigh( position, modelIndex );
|
|
if ( !pTemp )
|
|
{
|
|
gEngfuncs.Con_Printf( "No temp ent.\n" );
|
|
return;
|
|
}
|
|
|
|
pTemp->entity.curstate.rendermode = kRenderNormal;
|
|
pTemp->entity.curstate.renderamt = pTemp->entity.baseline.renderamt = 192;
|
|
pTemp->entity.curstate.renderfx = kRenderFxNoDissipation;
|
|
|
|
pTemp->clientIndex = client;
|
|
pTemp->tentOffset[0] = 0;
|
|
pTemp->tentOffset[1] = 0;
|
|
pTemp->tentOffset[2] = zoffset;
|
|
pTemp->die = GetClientTime() + life;
|
|
pTemp->flags |= FTENT_PLYRATTACHMENT|FTENT_PERSIST;
|
|
|
|
// is the model a sprite?
|
|
if ( Mod_GetModelType( pTemp->entity.curstate.modelindex ) == mod_sprite )
|
|
{
|
|
frameCount = Mod_GetFrames( pTemp->entity.curstate.modelindex );
|
|
pTemp->frameMax = frameCount - 1;
|
|
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;
|
|
}
|
|
#define FOR_EACH_LL( listName, iteratorName ) \
|
|
for( int iteratorName=listName.Head(); iteratorName != listName.InvalidIndex(); iteratorName = listName.Next( iteratorName ) )
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Detach entity from player
|
|
//-----------------------------------------------------------------------------
|
|
void CTempEnts::KillAttachedTents( int client )
|
|
{
|
|
if ( client <= 0 || client > gEngfuncs.GetMaxClients() )
|
|
{
|
|
gEngfuncs.Con_Printf( "Bad client in KillAttachedTents()!\n" );
|
|
return;
|
|
}
|
|
|
|
for( int i = 0; i < MAX_TEMP_ENTITIES; i++ )
|
|
{
|
|
TEMPENTITY *pTemp = &m_TempEnts[i];
|
|
|
|
if ( pTemp->flags & FTENT_PLYRATTACHMENT )
|
|
{
|
|
// this TENT is player attached.
|
|
// if it is attached to this client, set it to die instantly.
|
|
if ( pTemp->clientIndex == client )
|
|
{
|
|
pTemp->die = GetClientTime(); // good enough, it will die on next tent update.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Create ricochet sprite
|
|
// Input : *pos -
|
|
// *pmodel -
|
|
// duration -
|
|
// scale -
|
|
//-----------------------------------------------------------------------------
|
|
void CTempEnts::RicochetSprite( const Vector &pos, int modelIndex, float scale )
|
|
{
|
|
TEMPENTITY *pTemp;
|
|
|
|
pTemp = TempEntAlloc( pos, modelIndex );
|
|
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->flags = FTENT_FADEOUT;
|
|
pTemp->fadeSpeed = 8;
|
|
pTemp->die = GetClientTime();
|
|
|
|
pTemp->entity.curstate.frame = 0;
|
|
pTemp->entity.angles[ROLL] = 45 * RANDOM_LONG( 0, 7 );
|
|
}
|
|
|
|
void CTempEnts::PlaySound( TEMPENTITY *pTemp, float damp )
|
|
{
|
|
float fvol;
|
|
char soundname[32];
|
|
bool isshellcasing = false;
|
|
int zvel;
|
|
|
|
switch ( pTemp->hitSound )
|
|
{
|
|
default:
|
|
return; // null sound
|
|
case BOUNCE_GLASS:
|
|
{
|
|
sprintf( soundname, "debris/glass%i.wav", RANDOM_LONG( 1, 4 ));
|
|
}
|
|
break;
|
|
case BOUNCE_METAL:
|
|
{
|
|
sprintf( soundname, "debris/metal%i.wav", RANDOM_LONG( 1, 6 ));
|
|
}
|
|
break;
|
|
case BOUNCE_FLESH:
|
|
{
|
|
sprintf( soundname, "debris/flesh%i.wav", RANDOM_LONG( 1, 7 ));
|
|
}
|
|
break;
|
|
case BOUNCE_WOOD:
|
|
{
|
|
sprintf( soundname, "debris/wood%i.wav", RANDOM_LONG( 1, 4 ));
|
|
}
|
|
break;
|
|
case BOUNCE_SHRAP:
|
|
{
|
|
sprintf( soundname, "weapons/ric%i.wav", RANDOM_LONG( 1, 5 ));
|
|
}
|
|
break;
|
|
case BOUNCE_SHOTSHELL:
|
|
{
|
|
sprintf( soundname, "weapons/sshell%i.wav", RANDOM_LONG( 1, 3 ));
|
|
isshellcasing = true; // shell casings have different playback parameters
|
|
}
|
|
break;
|
|
case BOUNCE_SHELL:
|
|
{
|
|
sprintf( soundname, "player/pl_shell%i.wav", RANDOM_LONG( 1, 3 ));
|
|
isshellcasing = true; // shell casings have different playback parameters
|
|
}
|
|
break;
|
|
case BOUNCE_CONCRETE:
|
|
{
|
|
sprintf( soundname, "debris/concrete%i.wav", RANDOM_LONG( 1, 3 ));
|
|
}
|
|
break;
|
|
}
|
|
|
|
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 && RANDOM_LONG( 0, 3 ))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if ( RANDOM_LONG( 0, 5 ))
|
|
return;
|
|
}
|
|
|
|
fvol = 1.0f;
|
|
|
|
if ( damp > 0.0 )
|
|
{
|
|
int pitch;
|
|
|
|
if ( isshellcasing )
|
|
{
|
|
fvol *= min ( 1.0f, ((float)zvel) / 350.0 );
|
|
}
|
|
else
|
|
{
|
|
fvol *= min ( 1.0f, ((float)zvel) / 450.0 );
|
|
}
|
|
|
|
if ( !RANDOM_LONG( 0, 3 ) && !isshellcasing )
|
|
{
|
|
pitch = RANDOM_LONG( 95, 105 );
|
|
}
|
|
else
|
|
{
|
|
pitch = PITCH_NORM;
|
|
}
|
|
|
|
gEngfuncs.pEventAPI->EV_PlaySound( 0, pTemp->entity.origin, CHAN_AUTO, soundname, fvol, ATTN_NORM, 0, pitch );
|
|
}
|
|
}
|
|
|
|
void CTempEnts::RocketFlare( const Vector& pos )
|
|
{
|
|
TEMPENTITY *pTemp;
|
|
int modelIndex;
|
|
int nframeCount;
|
|
|
|
modelIndex = gEngfuncs.pEventAPI->EV_FindModelIndex( "sprites/animglow01.spr" );
|
|
if( !modelIndex ) return;
|
|
|
|
nframeCount = Mod_GetFrames( modelIndex );
|
|
|
|
pTemp = TempEntAlloc( pos, modelIndex );
|
|
if ( !pTemp ) return;
|
|
|
|
pTemp->frameMax = nframeCount - 1;
|
|
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 = RANDOM_LONG( 0, nframeCount - 1 );
|
|
pTemp->entity.curstate.scale = 1.0;
|
|
pTemp->die = GetClientTime() + 0.01f; // when 100 fps die at next frame
|
|
pTemp->flags |= FTENT_SPRANIMATE;
|
|
}
|
|
|
|
void CTempEnts::MuzzleFlash( cl_entity_t *pEnt, int iAttachment, int type )
|
|
{
|
|
Vector pos;
|
|
TEMPENTITY *pTemp;
|
|
int index, modelIndex, frameCount;
|
|
float scale;
|
|
|
|
index = bound( 0, type % 10, MAX_MUZZLEFLASH - 1 );
|
|
scale = (type / 10) * 0.1f;
|
|
if( scale == 0.0f ) scale = 0.5f;
|
|
|
|
modelIndex = m_iMuzzleFlash[index];
|
|
if( !modelIndex ) return;
|
|
|
|
frameCount = Mod_GetFrames( modelIndex );
|
|
|
|
if( iAttachment > 0 && pEnt->attachment[iAttachment - 1] == g_vecZero )
|
|
{
|
|
gEngfuncs.Con_Printf( "Invalid muzzleflash entity!\n" );
|
|
return;
|
|
}
|
|
|
|
// must set position for right culling on render
|
|
pTemp = TempEntAlloc( pEnt->origin, modelIndex );
|
|
if( !pTemp ) return;
|
|
|
|
pTemp->entity.curstate.rendermode = kRenderTransAdd;
|
|
pTemp->entity.curstate.renderamt = 255;
|
|
pTemp->entity.curstate.renderfx = 0;
|
|
pTemp->die = GetClientTime() + 0.05; // die at next frame
|
|
pTemp->entity.curstate.frame = RANDOM_LONG( 0, frameCount - 1 );
|
|
pTemp->frameMax = frameCount - 1;
|
|
|
|
// because viewentity doesn't have a valid index
|
|
if( pEnt->curstate.entityType == ET_VIEWENTITY )
|
|
pTemp->clientIndex = GetLocalPlayer()->index;
|
|
else pTemp->clientIndex = pEnt->index;
|
|
|
|
if( iAttachment > 0 )
|
|
{
|
|
// store attachment as baseline->body
|
|
pTemp->entity.baseline.body = iAttachment;
|
|
pTemp->flags |= FTENT_ATTACHMENT;
|
|
}
|
|
|
|
if( index == 0 )
|
|
{
|
|
// Rifle flash
|
|
pTemp->entity.curstate.scale = scale * RANDOM_FLOAT( 0.5f, 0.6f );
|
|
pTemp->entity.angles[2] = 90 * RANDOM_LONG( 0, 3 );
|
|
}
|
|
else
|
|
{
|
|
pTemp->entity.curstate.scale = scale;
|
|
pTemp->entity.angles[2] = RANDOM_LONG( 0, 359 );
|
|
}
|
|
}
|
|
|
|
void CTempEnts::BloodSprite( const Vector &org, int colorIndex, int modelIndex, int modelIndex2, float size )
|
|
{
|
|
TEMPENTITY *pTemp;
|
|
|
|
if( Mod_GetModelType( modelIndex ) == mod_bad )
|
|
return;
|
|
|
|
// Large, single blood sprite is a high-priority tent
|
|
if( pTemp = TempEntAllocHigh( org, modelIndex ))
|
|
{
|
|
int frameCount;
|
|
Vector color;
|
|
|
|
frameCount = Mod_GetFrames( modelIndex );
|
|
pTemp->entity.curstate.rendermode = kRenderTransTexture;
|
|
pTemp->entity.curstate.renderfx = kRenderFxClampMinScale;
|
|
pTemp->entity.curstate.scale = RANDOM_FLOAT(( size / 25.0f), ( size / 35.0f ));
|
|
pTemp->flags = FTENT_SPRANIMATE;
|
|
|
|
CL_GetPaletteColor( colorIndex, color );
|
|
pTemp->entity.curstate.rendercolor.r = color[0];
|
|
pTemp->entity.curstate.rendercolor.g = color[1];
|
|
pTemp->entity.curstate.rendercolor.b = color[2];
|
|
pTemp->entity.curstate.framerate = frameCount * 4; // Finish in 0.250 seconds
|
|
// play the whole thing once
|
|
pTemp->die = GetClientTime() + (frameCount / pTemp->entity.curstate.framerate);
|
|
|
|
pTemp->entity.angles[2] = RANDOM_LONG( 0, 360 );
|
|
pTemp->bounceFactor = 0;
|
|
}
|
|
}
|
|
|
|
void CTempEnts::BreakModel( const Vector &pos, const Vector &size, const Vector &dir, float random, float life, int count, int modelIndex, char flags )
|
|
{
|
|
int i, frameCount;
|
|
TEMPENTITY *pTemp;
|
|
char type;
|
|
|
|
if ( !modelIndex ) return;
|
|
|
|
type = flags & BREAK_TYPEMASK;
|
|
|
|
if ( Mod_GetModelType( modelIndex ) == mod_bad )
|
|
return;
|
|
|
|
frameCount = Mod_GetFrames( modelIndex );
|
|
|
|
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;
|
|
|
|
// fill up the box with stuff
|
|
vecSpot[0] = pos[0] + RANDOM_FLOAT( -0.5f, 0.5f ) * size[0];
|
|
vecSpot[1] = pos[1] + RANDOM_FLOAT( -0.5f, 0.5f ) * size[1];
|
|
vecSpot[2] = pos[2] + RANDOM_FLOAT( -0.5f, 0.5f ) * size[2];
|
|
|
|
pTemp = TempEntAlloc( vecSpot, modelIndex );
|
|
if( !pTemp ) return;
|
|
|
|
// keep track of break_type, so we know how to play sound on collision
|
|
pTemp->hitSound = type;
|
|
|
|
if( Mod_GetModelType( modelIndex ) == mod_sprite )
|
|
pTemp->entity.curstate.frame = RANDOM_LONG( 0, frameCount - 1 );
|
|
else if( Mod_GetModelType( modelIndex ) == mod_studio )
|
|
pTemp->entity.curstate.body = RANDOM_LONG( 0, frameCount - 1 );
|
|
|
|
pTemp->flags |= FTENT_COLLIDEWORLD | FTENT_FADEOUT | FTENT_SLOWGRAVITY;
|
|
|
|
if ( RANDOM_LONG( 0, 255 ) < 200 )
|
|
{
|
|
pTemp->flags |= FTENT_ROTATE;
|
|
pTemp->entity.baseline.angles[0] = RANDOM_FLOAT( -256, 255 );
|
|
pTemp->entity.baseline.angles[1] = RANDOM_FLOAT( -256, 255 );
|
|
pTemp->entity.baseline.angles[2] = RANDOM_FLOAT( -256, 255 );
|
|
}
|
|
|
|
if (( RANDOM_LONG( 0, 255 ) < 100 ) && ( flags & BREAK_SMOKE ))
|
|
pTemp->flags |= FTENT_SMOKETRAIL;
|
|
|
|
if (( type == BREAK_GLASS ) || ( 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] + RANDOM_FLOAT( -random, random );
|
|
pTemp->entity.baseline.origin[1] = dir[1] + RANDOM_FLOAT( -random, random );
|
|
pTemp->entity.baseline.origin[2] = dir[2] + RANDOM_FLOAT( 0, random );
|
|
|
|
pTemp->die = GetClientTime() + life + RANDOM_FLOAT( 0, 1 ); // Add an extra 0-1 secs of life
|
|
}
|
|
}
|
|
|
|
TEMPENTITY *CTempEnts::TempModel( const Vector &pos, const Vector &dir, const Vector &ang, float life, int modelIndex, int soundtype )
|
|
{
|
|
// alloc a new tempent
|
|
TEMPENTITY *pTemp = TempEntAlloc( pos, modelIndex );
|
|
if( !pTemp ) return NULL;
|
|
|
|
// keep track of shell type
|
|
switch( soundtype )
|
|
{
|
|
case TE_BOUNCE_SHELL: pTemp->hitSound = BOUNCE_SHELL; break;
|
|
case TE_BOUNCE_SHOTSHELL: pTemp->hitSound = BOUNCE_SHOTSHELL; break;
|
|
}
|
|
|
|
pTemp->entity.origin = pos;
|
|
pTemp->entity.baseline.origin = dir;
|
|
pTemp->entity.angles = ang;
|
|
|
|
pTemp->entity.curstate.body = 0;
|
|
pTemp->flags = (FTENT_COLLIDEWORLD|FTENT_FADEOUT|FTENT_GRAVITY|FTENT_ROTATE);
|
|
pTemp->entity.baseline.angles[0] = RANDOM_FLOAT( -255, 255 );
|
|
pTemp->entity.baseline.angles[1] = RANDOM_FLOAT( -255, 255 );
|
|
pTemp->entity.baseline.angles[2] = RANDOM_FLOAT( -255, 255 );
|
|
pTemp->entity.curstate.rendermode = kRenderNormal;
|
|
pTemp->entity.baseline.renderamt = 255;
|
|
pTemp->die = GetClientTime() + life;
|
|
|
|
return pTemp;
|
|
}
|
|
|
|
TEMPENTITY *CTempEnts::DefaultSprite( const Vector &pos, int spriteIndex, float framerate )
|
|
{
|
|
TEMPENTITY *pTemp;
|
|
int frameCount;
|
|
|
|
if( !spriteIndex || Mod_GetModelType( spriteIndex ) != mod_sprite )
|
|
{
|
|
gEngfuncs.Con_Printf( "No Sprite %d!\n", spriteIndex );
|
|
return NULL;
|
|
}
|
|
|
|
frameCount = Mod_GetFrames( spriteIndex );
|
|
|
|
pTemp = TempEntAlloc( pos, spriteIndex );
|
|
if( !pTemp ) return NULL;
|
|
|
|
pTemp->frameMax = frameCount - 1;
|
|
pTemp->entity.curstate.scale = 1.0f;
|
|
pTemp->flags |= FTENT_SPRANIMATE;
|
|
if( framerate == 0 ) framerate = 10;
|
|
|
|
pTemp->entity.curstate.framerate = framerate;
|
|
pTemp->die = GetClientTime() + (float)frameCount / framerate;
|
|
pTemp->entity.curstate.frame = 0;
|
|
|
|
return pTemp;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CL_TempSprite
|
|
===============
|
|
*/
|
|
TEMPENTITY *CTempEnts::TempSprite( const Vector &pos, const Vector &dir, float scale, int modelIndex, int rendermode, int renderfx, float a, float life, int flags )
|
|
{
|
|
TEMPENTITY *pTemp;
|
|
int frameCount;
|
|
|
|
if( !modelIndex )
|
|
return NULL;
|
|
|
|
if( Mod_GetModelType( modelIndex ) == mod_bad )
|
|
{
|
|
gEngfuncs.Con_Printf( "No model %d!\n", modelIndex );
|
|
return NULL;
|
|
}
|
|
|
|
frameCount = Mod_GetFrames( modelIndex );
|
|
|
|
pTemp = TempEntAlloc( pos, modelIndex );
|
|
if( !pTemp ) return NULL;
|
|
|
|
pTemp->frameMax = frameCount - 1;
|
|
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;
|
|
|
|
pTemp->entity.origin = pos;
|
|
pTemp->entity.baseline.origin = dir;
|
|
|
|
if( life ) pTemp->die = GetClientTime() + life;
|
|
else pTemp->die = GetClientTime() + (frameCount * 0.1f) + 1.0f;
|
|
pTemp->entity.curstate.frame = 0;
|
|
|
|
return pTemp;
|
|
}
|
|
|
|
void CTempEnts::Sprite_Explode( TEMPENTITY *pTemp, float scale, int flags )
|
|
{
|
|
if( !pTemp ) return;
|
|
|
|
// NOTE: Xash3D doesn't needs this stuff, because sprites
|
|
// have right rendermodes already at loading point
|
|
// but i'm leave it for backward compatibility
|
|
if( flags & TE_EXPLFLAG_NOADDITIVE )
|
|
{
|
|
// solid sprite
|
|
pTemp->entity.curstate.rendermode = kRenderNormal;
|
|
pTemp->entity.curstate.renderamt = 255;
|
|
}
|
|
else if( flags & TE_EXPLFLAG_DRAWALPHA )
|
|
{
|
|
// alpha sprite
|
|
pTemp->entity.curstate.rendermode = kRenderTransAlpha;
|
|
pTemp->entity.curstate.renderamt = 180;
|
|
}
|
|
else
|
|
{
|
|
// additive sprite
|
|
pTemp->entity.curstate.rendermode = kRenderTransAdd;
|
|
pTemp->entity.curstate.renderamt = 120;
|
|
}
|
|
|
|
if( flags & TE_EXPLFLAG_ROTATE )
|
|
{
|
|
pTemp->entity.angles[2] = RANDOM_LONG( 0, 360 );
|
|
}
|
|
|
|
pTemp->entity.curstate.renderfx = kRenderFxNone;
|
|
pTemp->entity.baseline.origin[2] = 8;
|
|
pTemp->entity.origin[2] += 10;
|
|
pTemp->entity.curstate.scale = scale;
|
|
}
|
|
|
|
void CTempEnts::Sprite_Smoke( TEMPENTITY *pTemp, float scale )
|
|
{
|
|
int iColor;
|
|
|
|
if( !pTemp ) return;
|
|
|
|
iColor = RANDOM_LONG( 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;
|
|
pTemp->flags |= FTENT_WINDBLOWN;
|
|
}
|
|
|
|
void CTempEnts::Sprite_Spray( const Vector &pos, const Vector &dir, int modelIndex, int count, int speed, int iRand, int renderMode )
|
|
{
|
|
TEMPENTITY *pTemp;
|
|
float noise;
|
|
float znoise;
|
|
int i, frameCount;
|
|
|
|
noise = (float)iRand / 100;
|
|
|
|
// more vertical displacement
|
|
znoise = noise * 1.5f;
|
|
|
|
if( znoise > 1 ) znoise = 1;
|
|
|
|
if( Mod_GetModelType( modelIndex ) == mod_bad )
|
|
{
|
|
gEngfuncs.Con_Printf( "No model %d!\n", modelIndex );
|
|
return;
|
|
}
|
|
|
|
frameCount = Mod_GetFrames( modelIndex );
|
|
|
|
for( i = 0; i < count; i++ )
|
|
{
|
|
vec3_t velocity;
|
|
float scale;
|
|
|
|
pTemp = TempEntAlloc( pos, modelIndex );
|
|
if( !pTemp ) return;
|
|
|
|
pTemp->entity.curstate.rendermode = renderMode;
|
|
pTemp->entity.curstate.renderfx = kRenderFxNoDissipation;
|
|
pTemp->entity.curstate.scale = 0.5f;
|
|
pTemp->flags |= FTENT_FADEOUT|FTENT_SLOWGRAVITY;
|
|
pTemp->fadeSpeed = 2.0f;
|
|
|
|
// make the spittle fly the direction indicated, but mix in some noise.
|
|
velocity[0] = dir[0] + RANDOM_FLOAT( -noise, noise );
|
|
velocity[1] = dir[1] + RANDOM_FLOAT( -noise, noise );
|
|
velocity[2] = dir[2] + RANDOM_FLOAT( 0, znoise );
|
|
scale = RANDOM_FLOAT(( speed * 0.8f ), ( speed * 1.2f ));
|
|
pTemp->entity.baseline.origin = velocity * scale;
|
|
pTemp->die = GetClientTime() + 0.35f;
|
|
pTemp->entity.curstate.frame = RANDOM_LONG( 0, frameCount - 1 );
|
|
}
|
|
}
|
|
|
|
void CTempEnts::Sprite_Trail( int type, const Vector &vecStart, const Vector &vecEnd, int modelIndex, int nCount, float flLife, float flSize, float flAmplitude, int nRenderamt, float flSpeed )
|
|
{
|
|
TEMPENTITY *pTemp;
|
|
vec3_t vecDelta, vecDir;
|
|
int i, flFrameCount;
|
|
|
|
if( Mod_GetModelType( modelIndex ) == mod_bad )
|
|
{
|
|
gEngfuncs.Con_Printf( "No model %d!\n", modelIndex );
|
|
return;
|
|
}
|
|
|
|
flFrameCount = Mod_GetFrames( modelIndex );
|
|
|
|
vecDelta = vecEnd - vecStart;
|
|
vecDir = vecDelta.Normalize();
|
|
|
|
flAmplitude /= 256.0;
|
|
|
|
for ( i = 0; i < nCount; i++ )
|
|
{
|
|
Vector vecPos, vecVel;
|
|
|
|
// Be careful of divide by 0 when using 'count' here...
|
|
if ( i == 0 )
|
|
{
|
|
vecPos = vecStart;
|
|
}
|
|
else
|
|
{
|
|
vecPos = vecStart + vecDelta * ( i / ( nCount - 1.0f ));
|
|
}
|
|
|
|
pTemp = TempEntAlloc( vecPos, modelIndex );
|
|
if( !pTemp ) return;
|
|
|
|
pTemp->flags |= FTENT_COLLIDEWORLD | FTENT_SPRCYCLE | FTENT_FADEOUT | FTENT_SLOWGRAVITY;
|
|
|
|
vecVel = vecDir * flSpeed;
|
|
vecVel[0] += RANDOM_FLOAT( -127, 128 ) * flAmplitude;
|
|
vecVel[1] += RANDOM_FLOAT( -127, 128 ) * flAmplitude;
|
|
vecVel[2] += RANDOM_FLOAT( -127, 128 ) * flAmplitude;
|
|
pTemp->entity.baseline.origin = vecVel;
|
|
pTemp->entity.origin = vecPos;
|
|
|
|
pTemp->entity.curstate.scale = flSize;
|
|
pTemp->entity.curstate.rendermode = kRenderGlow;
|
|
pTemp->entity.curstate.renderfx = kRenderFxNoDissipation;
|
|
pTemp->entity.curstate.renderamt = pTemp->entity.baseline.renderamt = nRenderamt;
|
|
|
|
pTemp->entity.curstate.frame = RANDOM_LONG( 0, flFrameCount - 1 );
|
|
pTemp->frameMax = flFrameCount - 1;
|
|
pTemp->die = GetClientTime() + flLife + RANDOM_FLOAT( 0, 4 );
|
|
}
|
|
}
|
|
|
|
void CTempEnts::Large_Funnel( Vector pos, int spriteIndex, int flags )
|
|
{
|
|
TEMPENTITY *pTemp = NULL;
|
|
CBaseParticle *pPart = NULL;
|
|
float vel;
|
|
Vector dir;
|
|
Vector dest;
|
|
Vector m_vecPos;
|
|
float flDist;
|
|
|
|
for ( int i = -256 ; i <= 256 ; i += 32 )
|
|
{
|
|
for ( int j = -256 ; j <= 256 ; j += 32 )
|
|
{
|
|
if( pTemp )
|
|
{
|
|
pPart = g_pParticles->AllocParticle ();
|
|
pTemp = NULL;
|
|
}
|
|
else
|
|
{
|
|
pTemp = TempEntAlloc( pos, spriteIndex );
|
|
pPart = NULL;
|
|
}
|
|
|
|
if( pTemp || pPart )
|
|
{
|
|
if ( flags & 1 )
|
|
{
|
|
m_vecPos = pos;
|
|
|
|
dest[0] = pos[0] + i;
|
|
dest[1] = pos[1] + j;
|
|
dest[2] = pos[2] + RANDOM_FLOAT( 100, 800 );
|
|
|
|
// send particle heading to dest at a random speed
|
|
dir = dest - m_vecPos;
|
|
|
|
vel = dest[2] / 8;// velocity based on how far particle has to travel away from org
|
|
}
|
|
else
|
|
{
|
|
m_vecPos[0] = pos[0] + i;
|
|
m_vecPos[1] = pos[1] + j;
|
|
m_vecPos[2] = pos[2] + RANDOM_FLOAT( 100, 800 );
|
|
|
|
// send particle heading to org at a random speed
|
|
dir = pos - m_vecPos;
|
|
|
|
vel = m_vecPos[2] / 8;// velocity based on how far particle starts from org
|
|
}
|
|
|
|
flDist = dir.Length(); // save the distance
|
|
dir = dir.Normalize();
|
|
|
|
if ( vel < 64 )
|
|
{
|
|
vel = 64;
|
|
}
|
|
|
|
vel += RANDOM_FLOAT( 64, 128 );
|
|
float life = (flDist / vel);
|
|
|
|
if( pTemp )
|
|
{
|
|
pTemp->entity.origin = m_vecPos;
|
|
pTemp->entity.baseline.origin = dir * vel;
|
|
pTemp->entity.curstate.rendermode = kRenderTransAdd;
|
|
pTemp->flags |= FTENT_FADEOUT;
|
|
pTemp->fadeSpeed = 2.0f;
|
|
pTemp->die = GetClientTime() + RANDOM_FLOAT( life * 0.5, life );
|
|
pTemp->entity.curstate.renderamt = pTemp->entity.baseline.renderamt = 255;
|
|
}
|
|
|
|
if( pPart )
|
|
{
|
|
pPart->m_Pos = m_vecPos;
|
|
pPart->SetColor( 0.0f, 1.0f, 0.0f );
|
|
pPart->SetType( pt_static );
|
|
pPart->SetAlpha( 1.0f );
|
|
|
|
pPart->m_Velocity = dir * vel;
|
|
// die right when you get there
|
|
pPart->SetLifetime( RANDOM_FLOAT( life * 0.5, life ));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CTempEnts::SparkEffect( const Vector& pos, int count, int velocityMin, int velocityMax )
|
|
{
|
|
Vector m_vecDir;
|
|
|
|
int modelIndex = gEngfuncs.pEventAPI->EV_FindModelIndex( "sprites/richo1.spr" );
|
|
RicochetSprite( pos, modelIndex, RANDOM_FLOAT( 0.4, 0.6f ));
|
|
|
|
for ( int i = 0; i < RANDOM_LONG( 1, count ); i++ )
|
|
{
|
|
m_vecDir.x = RANDOM_FLOAT( velocityMin, velocityMax );
|
|
m_vecDir.y = RANDOM_FLOAT( velocityMin, velocityMax );
|
|
m_vecDir.z = RANDOM_FLOAT( velocityMin, velocityMax );
|
|
m_vecDir = m_vecDir.Normalize();
|
|
|
|
g_pParticles->SparkleTracer( pos, m_vecDir );
|
|
}
|
|
}
|
|
|
|
void CTempEnts::SparkShower( const Vector& pos )
|
|
{
|
|
Vector m_vecPos, m_vecDir;
|
|
|
|
// randomize position
|
|
m_vecPos.x = pos.x + RANDOM_FLOAT( -2, 2 );
|
|
m_vecPos.y = pos.y + RANDOM_FLOAT( -2, 2 );
|
|
m_vecPos.z = pos.z + RANDOM_FLOAT( -2, 2 );
|
|
|
|
int modelIndex = gEngfuncs.pEventAPI->EV_FindModelIndex( "sprites/richo1.spr" );
|
|
RicochetSprite( m_vecPos, modelIndex, RANDOM_FLOAT( 0.4, 0.6f ));
|
|
|
|
// create a 8 random spakle tracers
|
|
for ( int i = 0; i < 8; i++ )
|
|
{
|
|
m_vecDir.x = RANDOM_FLOAT( -1.0f, 1.0f );
|
|
m_vecDir.y = RANDOM_FLOAT( -1.0f, 1.0f );
|
|
m_vecDir.z = RANDOM_FLOAT( -1.0f, 1.0f );
|
|
|
|
g_pParticles->SparkleTracer( m_vecPos, m_vecDir );
|
|
}
|
|
}
|
|
|
|
void CTempEnts::TracerEffect( const Vector &start, const Vector &end )
|
|
{
|
|
g_pParticles->BulletTracer( start, end );
|
|
}
|
|
|
|
void CTempEnts::StreakSplash( const Vector &pos, const Vector &dir, int color, int count, int speed, int velMin, int velMax )
|
|
{
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
Vector vel;
|
|
|
|
vel.x = (dir.x * speed) + RANDOM_FLOAT( velMin, velMax );
|
|
vel.y = (dir.y * speed) + RANDOM_FLOAT( velMin, velMax );
|
|
vel.z = (dir.z * speed) + RANDOM_FLOAT( velMin, velMax );
|
|
|
|
g_pParticles->StreakTracer( pos, vel, color );
|
|
}
|
|
}
|
|
|
|
void CTempEnts::WeaponFlash( cl_entity_t *pEnt, int iAttachment )
|
|
{
|
|
Vector pos = pEnt->origin;
|
|
|
|
if( iAttachment > 0 )
|
|
pos += pEnt->attachment[iAttachment - 1];
|
|
if( pos == pEnt->origin ) return; // missing attachment
|
|
|
|
AllocDLight( pos, 255, 180, 64, 100, 0.05f );
|
|
}
|
|
|
|
void CTempEnts::PlaceDecal( Vector pos, int entityIndex, int decalIndex )
|
|
{
|
|
HSPRITE hDecal;
|
|
cl_entity_t *pEnt;
|
|
int modelIndex = 0;
|
|
|
|
pEnt = GetEntityByIndex( entityIndex );
|
|
if( pEnt ) modelIndex = pEnt->curstate.modelindex;
|
|
hDecal = gEngfuncs.pEfxAPI->CL_DecalIndex( decalIndex );
|
|
gEngfuncs.pEfxAPI->R_DecalShoot( hDecal, entityIndex, modelIndex, pos, 0 );
|
|
}
|
|
|
|
void CTempEnts::PlaceDecal( Vector pos, int entityIndex, const char *decalname )
|
|
{
|
|
HSPRITE hDecal;
|
|
cl_entity_t *pEnt;
|
|
int modelIndex = 0;
|
|
|
|
pEnt = GetEntityByIndex( entityIndex );
|
|
if( pEnt ) modelIndex = pEnt->curstate.modelindex;
|
|
hDecal = gEngfuncs.pEfxAPI->CL_DecalIndexFromName( decalname );
|
|
gEngfuncs.pEfxAPI->R_DecalShoot( hDecal, entityIndex, modelIndex, pos, 0 );
|
|
}
|
|
|
|
void CTempEnts::AllocDLight( Vector pos, byte r, byte g, byte b, float radius, float time, float decay )
|
|
{
|
|
if( radius <= 0 ) return;
|
|
|
|
dlight_t *dl;
|
|
|
|
dl = gEngfuncs.pEfxAPI->CL_AllocDLight( 0 );
|
|
|
|
dl->origin = pos;
|
|
dl->die = GetClientTime() + time;
|
|
dl->color.r = r;
|
|
dl->color.g = g;
|
|
dl->color.b = b;
|
|
dl->radius = radius;
|
|
dl->decay = decay;
|
|
}
|
|
|
|
void CTempEnts::AllocDLight( Vector pos, float radius, float time, float decay )
|
|
{
|
|
AllocDLight( pos, 255, 255, 255, radius, time, decay );
|
|
}
|
|
|
|
void CTempEnts::RocketTrail( Vector start, Vector end, int type )
|
|
{
|
|
g_pParticles->RocketTrail( start, end, type );
|
|
} |