//======================================================================= // Copyright XashXT Group 2009 © // cl_tent.c - temp entity effects management //======================================================================= #include "common.h" #include "client.h" #include "effects_api.h" #include "const.h" /* ============================================================== TEMPENTS MANAGEMENT ============================================================== */ #define MAX_TEMPENTS 1024 TEMPENTITY *cl_active_tents, *cl_free_tents; static TEMPENTITY cl_tents_list[MAX_TEMPENTS]; /* ================= CL_FreeTempEntity ================= */ static void CL_FreeTempEnt( TEMPENTITY *pTemp, TEMPENTITY *pPrev ) { // remove from the active list. if( pPrev ) { pPrev->next = pTemp->next; } else { cl_active_tents = pTemp->next; } // cleanup its data. if( pTemp->pvEngineData ) Mem_Free( pTemp->pvEngineData ); Mem_Set( pTemp, 0, sizeof( TEMPENTITY )); // add to the free list. pTemp->next = cl_free_tents; cl_free_tents = pTemp; } // free the first low priority tempent it finds. static bool CL_FreeLowPriorityTempEnt( void ) { TEMPENTITY *pActive = cl_active_tents; TEMPENTITY *pPrev = NULL; while( pActive ) { if( pActive->priority == TENTPRIORITY_LOW ) { CL_FreeTempEnt( pActive, pPrev ); return true; } pPrev = pActive; pActive = pActive->next; } return false; } static void CL_PrepareTempEnt( TEMPENTITY *pTemp, int modelIndex ) { // Use these to set per-frame and termination conditions / actions pTemp->flags = FTENT_NONE; pTemp->die = clgame.globals->time + 0.75f; if( modelIndex != 0 ) // allocate prevframe only for tents with valid model pTemp->pvEngineData = Mem_Alloc( cls.mempool, sizeof( studioframe_t )); else pTemp->flags |= FTENT_NOMODEL; pTemp->modelindex = modelIndex; pTemp->renderMode = kRenderNormal; pTemp->renderFX = kRenderFxNone; pTemp->renderAmt = 255; pTemp->body = 0; pTemp->skin = 0; pTemp->fadeSpeed = 0.5f; pTemp->hitSound = 0; pTemp->clientIndex = -1; pTemp->bounceFactor = 1; pTemp->m_flSpriteScale = 1.0f; } /* ================= CL_AllocTempEntity ================= */ TEMPENTITY *CL_TempEntAlloc( float *org, int modelIndex ) { TEMPENTITY *pTemp; if( !cl_free_tents ) { MsgDev( D_WARN, "Overflow %d temporary ents!\n", MAX_TEMPENTS ); return NULL; } pTemp = cl_free_tents; cl_free_tents = pTemp->next; Com_Assert( pTemp->pvEngineData ); CL_PrepareTempEnt( pTemp, modelIndex ); pTemp->priority = TENTPRIORITY_LOW; if( org ) VectorCopy( org, pTemp->origin ); pTemp->next = cl_active_tents; cl_active_tents = pTemp; return pTemp; } TEMPENTITY *CL_TempEntAllocNoModel( float *org ) { return CL_TempEntAlloc( org, 0 ); } TEMPENTITY *CL_TempEntAllocHigh( float *org, int modelIndex ) { 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. MsgDev( D_ERROR, "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; Com_Assert( pTemp->pvEngineData ); CL_PrepareTempEnt( pTemp, modelIndex ); pTemp->priority = TENTPRIORITY_HIGH; if( org ) VectorCopy( org, pTemp->origin ); pTemp->next = cl_active_tents; cl_active_tents = pTemp; return pTemp; } TEMPENTITY *CL_TentEntAllocCustom( float *org, int modelIndex, int high, ENTCALLBACK pfnCallback ) { TEMPENTITY *pTemp; if( high ) pTemp = CL_TempEntAllocHigh( org, modelIndex ); else CL_TempEntAlloc( org, modelIndex ); if( pTemp && pfnCallback ) { pTemp->callback = pfnCallback; pTemp->flags |= FTENT_CLIENTCUSTOM; } return pTemp; } /* =============== CL_ClearTempEnts =============== */ void CL_ClearTempEnts( void ) { int i; cl_active_tents = NULL; cl_free_tents = cl_tents_list; for( i = 0; i < MAX_TEMPENTS; i++ ) { cl_tents_list[i].next = &cl_tents_list[i+1]; if( cl_tents_list[i].pvEngineData ) Mem_Free( cl_tents_list[i].pvEngineData ); cl_tents_list[i].pvEngineData = NULL; } cl_tents_list[MAX_TEMPENTS-1].next = NULL; } /* =============== CL_IsActiveTempEnt =============== */ bool CL_IsActiveTempEnt( TEMPENTITY *pTemp ) { bool active = true; float life; if( !pTemp ) return false; life = pTemp->die - clgame.globals->time; if( life < 0.0f ) { if( pTemp->flags & FTENT_FADEOUT ) { int alpha; if( pTemp->renderMode == kRenderNormal ) pTemp->renderMode = kRenderTransTexture; alpha = pTemp->startAlpha * ( 1.0f + life * pTemp->fadeSpeed ); if( alpha <= 0 ) { active = false; alpha = 0; } pTemp->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; } bool CL_UpdateTempEnt( TEMPENTITY *pTemp, int framenumber ) { if( !pTemp ) return false; return clgame.dllFuncs.pfnUpdateEntity( pTemp, framenumber ); } /* =============== CL_AddTempEnts =============== */ void CL_AddTempEnts( void ) { static int gTempEntFrame = 0; TEMPENTITY *current, *pnext, *pprev; // nothing to simulate ? if( !cl_active_tents ) return; // !!!BUGBUG -- This needs to be time based // g-cont. it's used only for flickering dlights, what difference ? gTempEntFrame = (gTempEntFrame + 1) & 31; current = cl_active_tents; pprev = NULL; while( current ) { bool fTempEntFreed = false; pnext = current->next; // Kill it if( !CL_IsActiveTempEnt( current ) || !CL_UpdateTempEnt( current, gTempEntFrame )) { CL_FreeTempEnt( current, pprev ); fTempEntFreed = true; } else { // renderer rejected entity for some reasons... if( !re->AddTmpEntity( current, ED_TEMPENTITY )) { if(!( current->flags & FTENT_PERSIST )) { // If we can't draw it this frame, just dump it. current->die = clgame.globals->time; // don't fade out, just die current->flags &= ~FTENT_FADEOUT; CL_FreeTempEnt( current, pprev ); fTempEntFreed = true; } } } if( !fTempEntFreed ) pprev = current; current = pnext; } } /* =============== CL_TempModel =============== */ TEMPENTITY *CL_TempModel( float *pos, float *dir, float *ang, float life, int modelIndex, int type ) { // alloc a new tempent TEMPENTITY *pTemp = CL_TempEntAlloc( pos, modelIndex ); if( !pTemp ) return NULL; // keep track of shell type switch( type ) { case 1: pTemp->hitSound = TE_BOUNCE_SHELL; break; case 2: pTemp->hitSound = TE_BOUNCE_SHOTSHELL; break; } if( pos ) VectorCopy( pos, pTemp->origin ); if( dir ) VectorCopy( dir, pTemp->m_vecVelocity ); if( ang ) VectorCopy( ang, pTemp->angles ); pTemp->body = 0; pTemp->flags = (FTENT_COLLIDEWORLD|FTENT_FADEOUT|FTENT_GRAVITY|FTENT_ROTATE); pTemp->m_vecAvelocity[0] = Com_RandomFloat( -255, 255 ); pTemp->m_vecAvelocity[1] = Com_RandomFloat( -255, 255 ); pTemp->m_vecAvelocity[2] = Com_RandomFloat( -255, 255 ); pTemp->renderMode = kRenderNormal; pTemp->startAlpha = 255; pTemp->die = clgame.globals->time + life; return pTemp; } /* =============== CL_DefaultSprite =============== */ TEMPENTITY *CL_DefaultSprite( float *pos, int spriteIndex, float framerate ) { TEMPENTITY *pTemp; int frameCount; if( !spriteIndex || CM_GetModelType( spriteIndex ) != mod_sprite ) { MsgDev( D_ERROR, "No Sprite %d!\n", spriteIndex ); return NULL; } Mod_GetFrames( spriteIndex, &frameCount ); pTemp = CL_TempEntAlloc( pos, spriteIndex ); if( !pTemp ) return NULL; pTemp->m_flFrameMax = frameCount - 1; pTemp->m_flSpriteScale = 1.0f; pTemp->flags |= FTENT_SPRANIMATE; if( framerate == 0 ) framerate = 10; pTemp->m_flFrameRate = framerate; pTemp->die = clgame.globals->time + (float)frameCount / framerate; pTemp->m_flFrame = 0; return pTemp; } /* =============== CL_TempSprite =============== */ TEMPENTITY *CL_TempSprite( float *pos, float *dir, float scale, int modelIndex, int rendermode, int renderfx, float a, float life, int flags ) { TEMPENTITY *pTemp; int frameCount; if( !modelIndex ) return NULL; if( CM_GetModelType( modelIndex ) == mod_bad ) { MsgDev( D_ERROR, "No model %d!\n", modelIndex ); return NULL; } Mod_GetFrames( modelIndex, &frameCount ); pTemp = CL_TempEntAlloc( pos, modelIndex ); if( !pTemp ) return NULL; pTemp->m_flFrameMax = frameCount - 1; pTemp->m_flFrameRate = 10; pTemp->renderMode = rendermode; pTemp->renderFX = renderfx; pTemp->m_flSpriteScale = scale; pTemp->startAlpha = a * 255; pTemp->renderAmt = a * 255; pTemp->flags |= flags; if( pos ) VectorCopy( pos, pTemp->origin ); if( dir ) VectorCopy( dir, pTemp->m_vecVelocity ); if( life ) pTemp->die = clgame.globals->time + life; else pTemp->die = clgame.globals->time + (frameCount * 0.1f) + 1.0f; pTemp->m_flFrame = 0; return pTemp; } /* =============== CL_MuzzleFlash NOTE: this temp entity live once one frame so we wan't add it to tent list =============== */ void CL_MuzzleFlash( float *pos, int modelIndex, int type ) { TEMPENTITY *pTemp; int frameCount; float scale; int index; if( CM_GetModelType( modelIndex ) == mod_bad ) { MsgDev( D_ERROR, "Invalid muzzleflash %d!\n", modelIndex ); return; } Mod_GetFrames( modelIndex, &frameCount ); pTemp = CL_TempEntAlloc( pos, modelIndex ); if( !pTemp ) return; index = type % 10; scale = (type / 10) * 0.1f; if( scale == 0.0f ) scale = 0.5f; pTemp->renderMode = kRenderTransAdd; pTemp->renderAmt = 255; pTemp->renderFX = 0; VectorCopy( pos, pTemp->origin ); pTemp->die = clgame.globals->time + 0.02f; pTemp->m_flFrame = Com_RandomLong( 0, frameCount - 1 ); pTemp->m_flFrameMax = frameCount - 1; if( index == 0 ) { // Rifle flash pTemp->m_flSpriteScale = scale * Com_RandomFloat( 0.5f, 0.6f ); pTemp->angles[2] = 90 * Com_RandomLong( 0, 3 ); } else { pTemp->m_flSpriteScale = scale; pTemp->angles[2] = Com_RandomLong( 0, 359 ); } re->AddTmpEntity( pTemp, ED_TEMPENTITY ); }