Permalink
Fetching contributors…
Cannot retrieve contributors at this time
1717 lines (1479 sloc) 44.4 KB
/*
* Wolfenstein: Enemy Territory GPL Source Code
* Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
*
* ET: Legacy
* Copyright (C) 2012-2018 ET:Legacy team <mail@etlegacy.com>
*
* This file is part of ET: Legacy - http://www.etlegacy.com
*
* ET: Legacy 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.
*
* ET: Legacy 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.
*
* You should have received a copy of the GNU General Public License
* along with ET: Legacy. If not, see <http://www.gnu.org/licenses/>.
*
* In addition, Wolfenstein: Enemy Territory GPL Source Code is also
* subject to certain additional terms. You should have received a copy
* of these additional terms immediately following the terms and conditions
* of the GNU General Public License which accompanied the source code.
* If not, please request a copy in writing from id Software at the address below.
*
* id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
*/
/**
* @file cg_localents.c
* @brief Every frame, generate renderer commands for locally processed
* entities, like smoke puffs, gibs, shells, etc.
*/
#include "cg_local.h"
// increased this
//#define MAX_LOCAL_ENTITIES 512
#define MAX_LOCAL_ENTITIES 768 // renderer can only handle 1024 entities max, so we should avoid
// overwriting game entities
localEntity_t cg_localEntities[MAX_LOCAL_ENTITIES];
localEntity_t cg_activeLocalEntities; // double linked list
localEntity_t *cg_freeLocalEntities; // single linked list
// debugging
int localEntCount = 0;
/**
* @brief CG_GetLocation
* @param[in] client
* @param[in] origin
* @return
*/
location_t *CG_GetLocation(int client, vec3_t origin)
{
location_t *curLoc;
location_t *bestLoc = NULL;
float bestdist = 200000000.f;
float len;
int i;
if (ISVALIDCLIENTNUM(client) && cgs.clientLocation[client].lastLocation)
{
if ((cgs.clientLocation[client].lastX == origin[0]
&& cgs.clientLocation[client].lastY == origin[1]
&& cgs.clientLocation[client].lastZ == origin[2])
&& &cgs.location[cgs.clientLocation[client].lastLocation])
{
return &cgs.location[cgs.clientLocation[client].lastLocation];
}
}
for (i = 0; i < cgs.numLocations; ++i)
{
curLoc = &cgs.location[i];
len = VectorDistance(origin, curLoc->origin);
if (len > bestdist || !trap_R_inPVS(origin, curLoc->origin))
{
//CG_Printf("^6PVS %i \n", trap_R_inPVS(origin, curLoc->origin));
//CG_Printf("^6OR %i %i -- %i %i\n", origin[0], origin[1], curLoc->origin[0], curLoc->origin[1]);
continue;
}
bestdist = len;
bestLoc = curLoc;
}
// store new information
if (ISVALIDCLIENTNUM(client) && bestLoc != NULL)
{
cgs.clientLocation[client].lastX = origin[0];
cgs.clientLocation[client].lastY = origin[1];
cgs.clientLocation[client].lastZ = origin[2];
cgs.clientLocation[client].lastLocation = bestLoc->index;
}
return bestLoc;
}
/**
* @brief CG_GetLocationMsg
* @param[in] clientNum
* @param[in] origin
* @return
*/
char *CG_GetLocationMsg(int clientNum, vec3_t origin)
{
location_t *bestLoc = CG_GetLocation(clientNum, origin);
// Fast way out, there are no locations for the map
if (cgs.numLocations < 1)
{
return "Unknown";
}
if (bestLoc != NULL && strlen(bestLoc->message) > 1)
{
return va("%s", bestLoc->message);
}
return "Unknown";
}
/**
* @brief CG_BuildLocationString
* @param[in] clientNum
* @param[in] origin
* @param[in] flag
* @return
*/
char *CG_BuildLocationString(int clientNum, vec3_t origin, int flag)
{
char *locStr = NULL;
if (cg_locations.integer & flag)
{
qboolean locValid = qtrue;
if (cg_locations.integer & LOC_SHOWDISTANCE)
{
if (clientNum == cg.clientNum)
{
locStr = va("^3 ");
//CG_Printf("same client\n");
}
else
{
//locStr = va("^3%7.2f", VectorDistance(origin, cgs.clientinfo[cg.clientNum].location) / 40.f); // meter
locStr = va("^3%7.2f", VectorDistance(origin, cgs.clientinfo[cg.clientNum].location)); // game units
}
}
else
{
locStr = va("^3%s", CG_GetLocationMsg(clientNum, origin));
if (!(cg_locations.integer & LOC_KEEPUNKNOWN))
{
if (!Q_stricmp(locStr, "Unknown"))
{
locStr = va("^3(%s)", BG_GetLocationString(origin[0], origin[1]));
locValid = qfalse; // don't draw it twice..
}
}
}
if ((cg_locations.integer & LOC_SHOWCOORDS) && locValid)
{
Q_strcat(locStr, 64, va(" ^3(%s)", BG_GetLocationString(origin[0], origin[1]))); // append a location
}
}
else
{
locStr = va("^3%s", BG_GetLocationString(origin[0], origin[1]));
}
return locStr;
}
#define MAX_BUFFER 32768
/**
* @brief Report a location for the player. Uses placed nearby target_location entities
*/
void CG_LoadLocations(void)
{
fileHandle_t f; // handle of file on disk
int fLen; // length of the file
char fBuffer[MAX_BUFFER]; // buffer to read the file into
char message[128] = "\0"; // location description
char temp[128] = "\0"; // temporary buffer
int x = 0; // x-coord of the location
int y = 0; // y-coord of the location
int z = 0; // z-coord of the location
int p = 0; // current location in the file buffer
int t = 0; // current location in the temp buffer
fLen = trap_FS_FOpenFile(va("maps/%s_loc_override.dat", cgs.rawmapname), &f, FS_READ);
if (fLen <= 0)
{
// open the location .dat file that matches the map's name
fLen = trap_FS_FOpenFile(va("maps/%s_loc.dat", cgs.rawmapname), &f, FS_READ);
if (fLen <= 0)
{
CG_Printf("^dLoadLocations: ^3Warning: ^9No location data found for map ^2%s^9.\n", cgs.rawmapname);
return;
}
}
if (fLen >= MAX_BUFFER)
{
trap_FS_FCloseFile(f);
CG_Error("Location file is too big, make it smaller (max = %i bytes)\n", MAX_BUFFER);
}
trap_FS_Read(&fBuffer, fLen, f); // read the file into the buffer
fBuffer[fLen] = '\0'; // make sure it's null-terminated
trap_FS_FCloseFile(f); // close the file, we're done with it
CG_Printf("^dLoadLocations: ^9location data for map ^2%s ^9loaded\n", cgs.rawmapname);
// start parsing!
while (p < fLen)
{
// check for the beginning of a comment
if (fBuffer[p++] == '/')
{
//check for single line comment
if (fBuffer[p] == '/')
{
while (p < fLen && (fBuffer[p] != '\n' && fBuffer[p] != '\r'))
{
p++;
}
}
// check for multiline comment
else if (fBuffer[p] == '*')
{
while (p < fLen && (fBuffer[p] != '*' && fBuffer[p + 1] != '/'))
{
p++;
}
}
}
// parse the next line
while (p < fLen && (fBuffer[p] != '\n' && fBuffer[p] != '\r'))
{
// grab the x-coord
while (p < fLen && fBuffer[p] != ' ')
{
temp[t++] = fBuffer[p++];
}
temp[t] = '\0';
x = atoi(temp);
t = 0;
Com_Memset(&temp, 0, sizeof(temp));
if (p > fLen)
{
break;
}
p++;
// grab the y-coord
while (p < fLen && fBuffer[p] != ' ')
{
temp[t++] = fBuffer[p++];
}
temp[t] = '\0';
y = atoi(temp);
t = 0;
Com_Memset(&temp, 0, sizeof(temp));
if (p > fLen)
{
break;
}
p++;
// grab the z-coord
while (p < fLen && fBuffer[p] != ' ')
{
temp[t++] = fBuffer[p++];
}
temp[t] = '\0';
z = atoi(temp);
t = 0;
Com_Memset(&temp, 0, sizeof(temp));
if (p > fLen)
{
break;
}
p++;
// grab the description
while (p < fLen && fBuffer[p] != '\n' && fBuffer[p] != '\r')
{
// ignore quotation marks
if (fBuffer[p] != '\"')
{
temp[t++] = fBuffer[p++];
}
else
{
p++;
}
}
temp[t] = '\0';
t = 0;
// if @, then keep the previous location name, otherwise, update message
if (Q_stricmp(temp, "@"))
{
strcpy(message, temp);
}
if (p > fLen)
{
break;
}
if ((x != 0 || y != 0 || z != 0) && strlen(message) > 0)
{
location_t *loc = &cgs.location[cgs.numLocations];
loc->index = cgs.numLocations;
strcpy(loc->message, message);
loc->origin[0] = x;
loc->origin[1] = y;
loc->origin[2] = z;
cgs.numLocations++;
if (cgs.numLocations == MAX_C_LOCATIONS)
{
CG_Printf("^9Too many locations specifed.\n");
break;
}
}
}
}
// ok we are succesfull
CG_Printf("^2%i ^9locations loaded.\n", cgs.numLocations);
cgs.locationsLoaded = qtrue;
}
/**
* @brief This is called at startup and for tournement restarts
*/
void CG_InitLocalEntities(void)
{
int i;
Com_Memset(cg_localEntities, 0, sizeof(cg_localEntities));
cg_activeLocalEntities.next = &cg_activeLocalEntities;
cg_activeLocalEntities.prev = &cg_activeLocalEntities;
cg_freeLocalEntities = cg_localEntities;
for (i = 0 ; i < MAX_LOCAL_ENTITIES - 1 ; i++)
{
cg_localEntities[i].next = &cg_localEntities[i + 1];
}
// debugging
localEntCount = 0;
}
/**
* @brief CG_FreeLocalEntity
* @param le
*/
void CG_FreeLocalEntity(localEntity_t *le)
{
if (!le->prev)
{
CG_Error("CG_FreeLocalEntity: not active\n");
}
// debugging
localEntCount--;
//trap_Print(va("FreeLocalEntity: locelEntCount = %d type = %i\n", localEntCount, le->leType));
// remove from the doubly linked active list
le->prev->next = le->next;
le->next->prev = le->prev;
// the free list is only singly linked
le->next = cg_freeLocalEntities;
cg_freeLocalEntities = le;
}
/**
* @brief CG_FindLocalEntity
* @details Will allways succeed, even if it requires freeing an old active entity
* @param[in] index
* @param[in] sideNum
* @return
*/
localEntity_t *CG_FindLocalEntity(int index, int sideNum)
{
int i;
for (i = 0 ; i < localEntCount ; i++)
{
if (cg_localEntities[i].data1 == index)
{
if (cg_localEntities[i].data2 == sideNum)
{
return &cg_localEntities[i];
}
}
}
return NULL;
}
/**
* @brief CG_AllocLocalEntity
* @details Will allways succeed, even if it requires freeing an old active entity
* @return
*/
localEntity_t *CG_AllocLocalEntity(void)
{
localEntity_t *le;
if (!cg_freeLocalEntities)
{
// no free entities, so free the one at the end of the chain
// remove the oldest active entity
CG_FreeLocalEntity(cg_activeLocalEntities.prev);
}
// debugging
localEntCount++;
//trap_Print(va("AllocLocalEntity: locelEntCount = %d\n", localEntCount));
le = cg_freeLocalEntities;
cg_freeLocalEntities = cg_freeLocalEntities->next;
Com_Memset(le, 0, sizeof(*le));
// link into the active list
le->next = cg_activeLocalEntities.next;
le->prev = &cg_activeLocalEntities;
cg_activeLocalEntities.next->prev = le;
cg_activeLocalEntities.next = le;
return le;
}
/*
====================================================================================
FRAGMENT PROCESSING
A fragment localentity interacts with the environment in some way (hitting walls),
or generates more localentities along a trail.
====================================================================================
*/
// use this to change between particle and trail code
//#define BLOOD_PARTICLE_TRAIL
/**
* @brief Leave expanding blood puffs behind gibs
* @param le
*/
void CG_BloodTrail(localEntity_t *le)
{
int t;
int t2;
int step;
vec3_t newOrigin;
float vl; //bani
#ifndef BLOOD_PARTICLE_TRAIL
static vec3_t col = { 1, 1, 1 };
#endif
if (!cg_blood.integer)
{
return;
}
// step = 150;
#ifdef BLOOD_PARTICLE_TRAIL
step = 10;
#else
// time it takes to move 3 units
vl = VectorLength(le->pos.trDelta);
// rain - need to check FLT_EPSILON because floating-point math sucks <3
if (vl < FLT_EPSILON)
{
return;
}
step = (int)((1000 * 3) / vl);
// avoid another div by 0
// check against <= 0 instead of == 0, because it can still wrap; (3000 / (FLT_EPSILON*11.7f)) < 0
if (step <= 0)
{
return;
}
#endif
t = step * ((cg.time - cg.frametime + step) / step);
t2 = step * (cg.time / step);
for ( ; t <= t2; t += step)
{
BG_EvaluateTrajectory(&le->pos, t, newOrigin, qfalse, -1);
#ifdef BLOOD_PARTICLE_TRAIL
CG_Particle_Bleed(cgs.media.smokePuffShader, newOrigin, vec3_origin, 0, 500 + rand() % 200);
#else
// blood trail using trail code (should be faster since we don't have to spawn as many)
le->headJuncIndex = CG_AddTrailJunc(le->headJuncIndex,
le, // rain - zinx's trail fix
cgs.media.bloodTrailShader,
t,
STYPE_STRETCH,
newOrigin,
180,
1.0, // start alpha
0.0, // end alpha
12.0,
12.0,
TJFL_NOCULL,
col, col,
0, 0);
#endif
}
}
/**
* @brief CG_FragmentBounceMark
* @param[in,out] le
* @param[in] trace
*/
void CG_FragmentBounceMark(localEntity_t *le, trace_t *trace)
{
if (le->leMarkType == LEMT_BLOOD && cg_bloodTime.integer)
{
static int lastBloodMark;
// don't drop too many blood marks
if (!(lastBloodMark > cg.time || lastBloodMark > cg.time - 100))
{
vec4_t projection, color;
int radius = 16 + (rand() & 31);
//CG_ImpactMark( cgs.media.bloodDotShaders[rand()%5], trace->endpos, trace->plane.normal, random()*360,
// 1,1,1,1, qtrue, radius, qfalse, cg_bloodTime.integer * 1000 );
#if 0
VectorSubtract(vec3_origin, trace->plane.normal, projection);
projection[3] = radius * 2.0f;
VectorMA(trace->endpos, -8.0f, projection, markOrigin);
CG_ImpactMark(cgs.media.bloodDotShaders[rand() % 5], markOrigin, projection, radius, random() * 360.0f, 1.0f, 1.0f, 1.0f, 1.0f, cg_bloodTime.integer * 1000);
#else
VectorSet(projection, 0, 0, -1);
projection[3] = radius;
Vector4Set(color, 1.0f, 1.0f, 1.0f, 1.0f);
trap_R_ProjectDecal(cgs.media.bloodDotShaders[rand() % 5], 1, (vec3_t *) trace->endpos, projection, color,
cg_bloodTime.integer * 1000, (cg_bloodTime.integer * 1000) >> 4);
#endif
lastBloodMark = cg.time;
}
}
// don't allow a fragment to make multiple marks, or they
// pile up while settling
le->leMarkType = LEMT_NONE;
}
/**
* @brief CG_FragmentBounceSound
* @param[in] le
* @param[in] trace
*/
void CG_FragmentBounceSound(localEntity_t *le, trace_t *trace)
{
switch (le->leBounceSoundType)
{
// adding machinegun brass bouncy sound for tk
case LEBS_BRASS:
{
int rnd = rand() % 3;
if (trace->surfaceFlags & SURF_METAL)
{
trap_S_StartSoundVControl(trace->endpos, -1, CHAN_AUTO, cgs.media.sfx_brassSound[BRASSSOUND_METAL][rnd][0], 64);
}
else if (trace->surfaceFlags & SURF_WOOD)
{
trap_S_StartSoundVControl(trace->endpos, -1, CHAN_AUTO, cgs.media.sfx_brassSound[BRASSSOUND_WOOD][rnd][0], 64);
}
else if (trace->surfaceFlags & (SURF_GRAVEL | SURF_SNOW | SURF_CARPET | SURF_GRASS))
{
trap_S_StartSoundVControl(trace->endpos, -1, CHAN_AUTO, cgs.media.sfx_brassSound[BRASSSOUND_SOFT][rnd][0], 64);
}
else
{
trap_S_StartSoundVControl(trace->endpos, -1, CHAN_AUTO, cgs.media.sfx_brassSound[BRASSSOUND_STONE][rnd][0], 64);
}
break;
}
case LEBS_SG_BRASS:
{
int rnd = rand() % 3;
if (trace->surfaceFlags & SURF_METAL)
{
trap_S_StartSoundVControl(trace->endpos, -1, CHAN_AUTO, cgs.media.sfx_brassSound[BRASSSOUND_METAL][rnd][1], 96);
}
else if (trace->surfaceFlags & SURF_WOOD)
{
trap_S_StartSoundVControl(trace->endpos, -1, CHAN_AUTO, cgs.media.sfx_brassSound[BRASSSOUND_WOOD][rnd][1], 96);
}
else if (trace->surfaceFlags & (SURF_GRAVEL | SURF_SNOW | SURF_CARPET | SURF_GRASS))
{
trap_S_StartSoundVControl(trace->endpos, -1, CHAN_AUTO, cgs.media.sfx_brassSound[BRASSSOUND_SOFT][rnd][1], 96);
}
else
{
trap_S_StartSoundVControl(trace->endpos, -1, CHAN_AUTO, cgs.media.sfx_brassSound[BRASSSOUND_STONE][rnd][1], 96);
}
}
break;
case LEBS_ROCK:
trap_S_StartSound(trace->endpos, -1, CHAN_AUTO, cgs.media.sfx_rubbleBounce[(rand() % 3)]);
break;
case LEBS_BONE:
trap_S_StartSound(trace->endpos, -1, CHAN_AUTO, cgs.media.boneBounceSound);
break;
default:
return;
}
// don't allow a fragment to make multiple bounce sounds,
// or it gets too noisy as they settle
le->leBounceSoundType = LEBS_NONE;
}
/**
* @brief CG_ReflectVelocity
* @param[in,out] le
* @param[in] trace
*/
void CG_ReflectVelocity(localEntity_t *le, trace_t *trace)
{
vec3_t velocity;
float dot;
int hitTime = (int)(cg.time - cg.frametime + cg.frametime * trace->fraction);
// reflect the velocity on the trace plane
BG_EvaluateTrajectoryDelta(&le->pos, hitTime, velocity, qfalse, -1);
dot = DotProduct(velocity, trace->plane.normal);
VectorMA(velocity, -2 * dot, trace->plane.normal, le->pos.trDelta);
VectorScale(le->pos.trDelta, le->bounceFactor, le->pos.trDelta);
VectorCopy(trace->endpos, le->pos.trBase);
le->pos.trTime = cg.time;
// check for stop, making sure that even on low FPS systems it doesn't bobble
if (le->leMarkType == LEMT_BLOOD && trace->startsolid)
{
//centity_t *cent;
//cent = &cg_entities[trace->entityNum];
//if (cent && cent->currentState.apos.trType != TR_STATIONARY)
// le->pos.trType = TR_STATIONARY;
}
else if (trace->allsolid || (trace->plane.normal[2] > 0 && (le->pos.trDelta[2] < 40 || le->pos.trDelta[2] < -cg.frametime * le->pos.trDelta[2])))
{
// if it's a fragment and it's not resting on the world...
//if(le->leType == LE_DEBRIS && trace->entityNum < (MAX_ENTITIES - 1))
if (le->leType == LE_FRAGMENT && trace->entityNum < (MAX_ENTITIES - 1))
{
le->pos.trType = TR_GRAVITY_PAUSED;
}
else
{
le->pos.trType = TR_STATIONARY;
}
}
}
/**
* @brief CG_AddEmitter
* @param[in,out] le
*/
void CG_AddEmitter(localEntity_t *le)
{
vec3_t dir;
if (le->breakCount > cg.time) // using 'breakCount' for 'wait'
{
return;
}
VectorScale(le->angles.trBase, 30, dir);
CG_Particle_OilParticle(cgs.media.oilParticle, le->pos.trBase, dir, 15000, le->ownerNum);
le->breakCount = cg.time + 50;
}
void CG_Explodef(vec3_t origin, vec3_t dir, int mass, int type, qhandle_t sound, int forceLowGrav, qhandle_t shader);
/**
* @brief CG_AddFragment
* @param[in,out] le
*/
void CG_AddFragment(localEntity_t *le)
{
vec3_t newOrigin;
trace_t trace;
refEntity_t *re = &le->refEntity;
float flameAlpha = 0.0;
vec3_t flameDir;
qboolean hasFlame = qfalse;
if (!re->fadeStartTime || re->fadeEndTime < le->endTime)
{
if (le->endTime - cg.time > 5000)
{
re->fadeStartTime = le->endTime - 5000;
}
else
{
re->fadeStartTime = le->endTime - 1000;
}
re->fadeEndTime = le->endTime;
}
// flaming gibs
if (le->onFireStart && (le->onFireStart < cg.time && le->onFireEnd > cg.time))
{
hasFlame = qtrue;
// calc the alpha
flameAlpha = 1.0f - ((float)(cg.time - le->onFireStart) / (float)(le->onFireEnd - le->onFireStart));
if (flameAlpha < 0.0f)
{
flameAlpha = 0.0f;
}
if (flameAlpha > 1.0f)
{
flameAlpha = 1.0f;
}
}
if (le->leFlags & LEF_SMOKING)
{
// create a little less smoke
// TODO: FIXME: this is not quite right, because it'll become fps dependant - in a bad way.
// the slower the fps, the /more/ smoke there'll be, probably driving the fps lower.
if (!(rand() % 5))
{
refEntity_t flash;
float alpha = 1.0f - ((float)(cg.time - le->startTime) / (float)(le->endTime - le->startTime));
alpha *= 0.25f;
Com_Memset(&flash, 0, sizeof(flash));
CG_PositionEntityOnTag(&flash, &le->refEntity, "tag_flash", 0, NULL);
CG_ParticleImpactSmokePuffExtended(cgs.media.smokeParticleShader, flash.origin, 1000, 8, 20, 20, alpha, 8.f);
}
}
if (le->pos.trType == TR_STATIONARY)
{
// add the flame
if (hasFlame)
{
refEntity_t backupEnt = le->refEntity;
VectorClear(flameDir);
flameDir[2] = 1;
le->refEntity.shaderRGBA[3] = ( unsigned char )(255.0f * flameAlpha);
VectorCopy(flameDir, le->refEntity.fireRiseDir);
le->refEntity.customShader = cgs.media.onFireShader;
trap_R_AddRefEntityToScene(&le->refEntity);
le->refEntity.customShader = cgs.media.onFireShader2;
trap_R_AddRefEntityToScene(&le->refEntity);
le->refEntity = backupEnt;
}
trap_R_AddRefEntityToScene(&le->refEntity);
return;
}
else if (le->pos.trType == TR_GRAVITY_PAUSED)
{
// add the flame
if (hasFlame)
{
refEntity_t backupEnt = le->refEntity;
VectorClear(flameDir);
flameDir[2] = 1;
le->refEntity.shaderRGBA[3] = ( unsigned char )(255.0f * flameAlpha);
VectorCopy(flameDir, le->refEntity.fireRiseDir);
le->refEntity.customShader = cgs.media.onFireShader;
trap_R_AddRefEntityToScene(&le->refEntity);
le->refEntity.customShader = cgs.media.onFireShader2;
trap_R_AddRefEntityToScene(&le->refEntity);
le->refEntity = backupEnt;
}
trap_R_AddRefEntityToScene(&le->refEntity);
// trace a line from previous position down, to see if I should start falling again
VectorCopy(le->refEntity.origin, newOrigin);
newOrigin[2] -= 5;
CG_Trace(&trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_MISSILECLIP);
if (trace.fraction == 1.0f) // it's clear, start moving again
{
VectorClear(le->pos.trDelta);
VectorClear(le->angles.trDelta);
le->pos.trType = TR_GRAVITY; // nothing below me, start falling again
}
else
{
return;
}
}
// calculate new position
BG_EvaluateTrajectory(&le->pos, cg.time, newOrigin, qfalse, -1);
if (hasFlame)
{
// calc the flame dir
VectorSubtract(le->refEntity.origin, newOrigin, flameDir);
if (VectorLengthSquared(flameDir) == 0.f)
{
flameDir[2] = 1;
// play a burning sound when not moving
trap_S_AddLoopingSound(newOrigin, vec3_origin, cgs.media.flameSound, (int)(0.3f * 255.0f * flameAlpha), 0);
}
else
{
VectorNormalize(flameDir);
// play a flame blow sound when moving
trap_S_AddLoopingSound(newOrigin, vec3_origin, cgs.media.flameBlowSound, (int)(0.3f * 255.0f * flameAlpha), 0);
}
}
// trace a line from previous position to new position
CG_Trace(&trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, CONTENTS_SOLID);
if (trace.fraction == 1.0f)
{
int i;
// still in free fall
VectorCopy(newOrigin, le->refEntity.origin);
if ((le->leFlags & LEF_TUMBLE) || le->angles.trType == TR_LINEAR)
{
vec3_t angles;
BG_EvaluateTrajectory(&le->angles, cg.time, angles, qtrue, -1);
AnglesToAxis(angles, le->refEntity.axis);
if (le->sizeScale != 0.f && le->sizeScale != 1.0f)
{
for (i = 0; i < 3; i++)
{
VectorScale(le->refEntity.axis[i], le->sizeScale, le->refEntity.axis[i]);
}
le->refEntity.nonNormalizedAxes = qtrue;
}
}
else
{
AnglesToAxis(le->angles.trBase, le->refEntity.axis);
if (le->sizeScale != 0.f && le->sizeScale != 1.0f)
{
for (i = 0; i < 3; i++)
{
VectorScale(le->refEntity.axis[i], le->sizeScale, le->refEntity.axis[i]);
}
le->refEntity.nonNormalizedAxes = qtrue;
}
}
// add the flame
if (hasFlame)
{
refEntity_t backupEnt = le->refEntity;
le->refEntity.shaderRGBA[3] = ( unsigned char )(255.0f * flameAlpha);
VectorCopy(flameDir, le->refEntity.fireRiseDir);
le->refEntity.customShader = cgs.media.onFireShader;
trap_R_AddRefEntityToScene(&le->refEntity);
le->refEntity.customShader = cgs.media.onFireShader2;
trap_R_AddRefEntityToScene(&le->refEntity);
le->refEntity = backupEnt;
}
trap_R_AddRefEntityToScene(&le->refEntity);
// add a blood trail
if (le->leBounceSoundType == LEBS_BLOOD)
{
CG_BloodTrail(le);
}
return;
}
// if it is in a nodrop zone, remove it
// this keeps gibs from waiting at the bottom of pits of death
// and floating levels
if (CG_PointContents(trace.endpos, 0) & CONTENTS_NODROP)
{
CG_FreeLocalEntity(le);
return;
}
// do a bouncy sound
CG_FragmentBounceSound(le, &trace);
// reflect the velocity on the trace plane
CG_ReflectVelocity(le, &trace);
if (le->leFlags & LEF_TUMBLE_SLOW)
{
VectorScale(le->angles.trDelta, 0.8f, le->angles.trDelta);
}
// break on contact?
if (le->breakCount)
{
if (le->leFlags & LEF_TUMBLE_SLOW) // HACK HACK x_X
{
vec3_t org, dir;
float sizeScale = le->sizeScale * 0.8f;
// make it smaller
if (sizeScale < 0.7f)
{
sizeScale = 0.7f;
}
// move us a bit
VectorNormalize2(le->pos.trDelta, dir);
VectorMA(trace.endpos, 4.0f * sizeScale, dir, org);
// randomize vel a bit
VectorMA(le->pos.trDelta, VectorLength(le->pos.trDelta) * 0.3f, bytedirs[rand() % NUMVERTEXNORMALS], dir);
CG_Explodef(org, dir, (int)(sizeScale * 50), 0, 0, qfalse, trap_R_GetShaderFromModel(le->refEntity.hModel, 0, 0));
CG_FreeLocalEntity(le);
return;
}
else
{
// re-added gibmodel support
// FIXME: limit local ent usage and cap this at N gib model ents in total for client? cvar? 0 = off, N = value
clientInfo_t *ci;
int i, clientNum = le->ownerNum;
localEntity_t *nle;
vec3_t dir;
bg_character_t *character;
if (clientNum < 0 || clientNum >= MAX_CLIENTS)
{
CG_Error("Bad clientNum on player entity\n");
}
ci = &cgs.clientinfo[clientNum];
character = CG_CharacterForClientinfo(ci, NULL);
// spawn some new fragments
for (i = 0; i <= le->breakCount; i++)
{
nle = CG_AllocLocalEntity();
Com_Memcpy(&(nle->leType), &(le->leType), sizeof(localEntity_t) - 2 * sizeof(localEntity_t *));
if (nle->breakCount-- < 2)
{
nle->refEntity.hModel = character->gibModels[rand() % 2];
}
else
{
nle->refEntity.hModel = character->gibModels[rand() % 4];
}
// make it smaller
nle->endTime = cg.time + 4000 + rand() % 2000;
nle->sizeScale *= 0.8;
if (nle->sizeScale < 0.7f)
{
nle->sizeScale = 0.7f;
nle->leBounceSoundType = 0;
}
// move us a bit
VectorNormalize2(nle->pos.trDelta, dir);
VectorMA(trace.endpos, 4.0f * le->sizeScale * i, dir, nle->pos.trBase);
// randomize vel a bit
VectorMA(nle->pos.trDelta, VectorLength(nle->pos.trDelta) * 0.3f, bytedirs[rand() % NUMVERTEXNORMALS], nle->pos.trDelta);
}
// we're done
CG_FreeLocalEntity(le);
// end gib model support
return;
}
}
if (le->pos.trType == TR_STATIONARY && le->leMarkType == LEMT_BLOOD)
{
// leave a mark
if (le->leMarkType)
{
CG_FragmentBounceMark(le, &trace);
}
}
// add the flame
if (hasFlame)
{
refEntity_t backupEnt = le->refEntity;
le->refEntity.shaderRGBA[3] = ( unsigned char )(255.0f * flameAlpha);
VectorCopy(flameDir, le->refEntity.fireRiseDir);
le->refEntity.customShader = cgs.media.onFireShader;
trap_R_AddRefEntityToScene(&le->refEntity);
le->refEntity.customShader = cgs.media.onFireShader2;
trap_R_AddRefEntityToScene(&le->refEntity);
le->refEntity = backupEnt;
}
trap_R_AddRefEntityToScene(&le->refEntity);
}
/**
* @brief CG_AddMovingTracer
* @param[in] le
*/
void CG_AddMovingTracer(localEntity_t *le)
{
vec3_t start, end, dir;
BG_EvaluateTrajectory(&le->pos, cg.time, start, qfalse, -1);
VectorNormalize2(le->pos.trDelta, dir);
VectorMA(start, cg_tracerLength.value, dir, end);
CG_DrawTracer(start, end);
}
/**
* @brief CG_AddSparkElements
* @param[in,out] le
*/
void CG_AddSparkElements(localEntity_t *le)
{
vec3_t newOrigin;
trace_t trace;
float lifeFrac;
float time = (float)(cg.time - cg.frametime);
while (1)
{
// calculate new position
BG_EvaluateTrajectory(&le->pos, cg.time, newOrigin, qfalse, -1);
//if ((le->endTime - le->startTime) > 500) {
// trace a line from previous position to new position
CG_Trace(&trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, MASK_SHOT);
// if stuck, kill it
if (trace.startsolid)
{
// HACK, some walls screw up, so just pass through if starting in a solid
VectorCopy(newOrigin, trace.endpos);
trace.fraction = 1.0;
}
// moved some distance
VectorCopy(trace.endpos, le->refEntity.origin);
//} else
//{ // just move it there
// VectorCopy( newOrigin, le->refEntity.origin );
// trace.fraction = 1.0;
//}
time += cg.frametime * trace.fraction;
lifeFrac = (float)(cg.time - le->startTime) / (float)(le->endTime - le->startTime);
// add a trail
le->headJuncIndex = CG_AddSparkJunc(le->headJuncIndex,
le, // rain - zinx's trail fix
le->refEntity.customShader,
le->refEntity.origin,
200,
1.0f - lifeFrac, // start alpha
0.0, //1.0 - lifeFrac, // end alpha
lifeFrac * 2.0f * (((le->endTime - le->startTime) > 400) + 1) * 1.5f,
lifeFrac * 2.0f * (((le->endTime - le->startTime) > 400) + 1) * 1.5f);
// if it is in a nodrop zone, remove it
// this keeps gibs from waiting at the bottom of pits of death
// and floating levels
// for some reason SFM1.BSP is one big NODROP zone
//if ( CG_PointContents( le->refEntity.origin, 0 ) & CONTENTS_NODROP ) {
// CG_FreeLocalEntity( le );
//return;
//}
if (trace.fraction < 1.0f)
{
// just kill it
CG_FreeLocalEntity(le);
return;
/*
// reflect the velocity on the trace plane
CG_ReflectVelocity( le, &trace );
// the intersection is a fraction of the frametime
le->pos.trTime = (int)time;
*/
}
if (trace.fraction == 1.0f || time >= (float)cg.time)
{
return;
}
}
}
/**
* @brief CG_AddFuseSparkElements
* @param[in,out] le
*/
void CG_AddFuseSparkElements(localEntity_t *le)
{
float FUSE_SPARK_WIDTH = 1.0;
int step = 10;
float lifeFrac;
static vec3_t whiteColor = { 1, 1, 1 };
int time = le->lastTrailTime;
while (time < cg.time)
{
// calculate new position
BG_EvaluateTrajectory(&le->pos, time, le->refEntity.origin, qfalse, -1);
lifeFrac = (time - le->startTime) / (float)(le->endTime - le->startTime);
//if (lifeFrac > 0.2) {
// add a trail
// rain - added le for zinx's trail fix
le->headJuncIndex = CG_AddTrailJunc(le->headJuncIndex, le, cgs.media.sparkParticleShader, time, STYPE_STRETCH, le->refEntity.origin, (int)(lifeFrac * (float)(le->endTime - le->startTime) / 2.0f),
1.0 /*(1.0 - lifeFrac)*/, 0.0, FUSE_SPARK_WIDTH * (1.0f - lifeFrac), FUSE_SPARK_WIDTH * (1.0f - lifeFrac), TJFL_SPARKHEADFLARE, whiteColor, whiteColor, 0, 0);
//}
time += step;
le->lastTrailTime = time;
}
}
/**
* @brief CG_AddBloodElements
* @param[in,out] le
*/
void CG_AddBloodElements(localEntity_t *le)
{
vec3_t newOrigin;
trace_t trace;
float lifeFrac;
float time = (float)(cg.time - cg.frametime);
int numbounces;
for (numbounces = 0; numbounces < 5; numbounces++)
{
// calculate new position
BG_EvaluateTrajectory(&le->pos, cg.time, newOrigin, qfalse, -1);
// trace a line from previous position to new position
CG_Trace(&trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, MASK_SHOT);
// if stuck, kill it
if (trace.startsolid)
{
// HACK, some walls screw up, so just pass through if starting in a solid
VectorCopy(newOrigin, trace.endpos);
trace.fraction = 1.0;
}
// moved some distance
VectorCopy(trace.endpos, le->refEntity.origin);
time += cg.frametime * trace.fraction;
lifeFrac = (float)(cg.time - le->startTime) / (float)(le->endTime - le->startTime);
// add a trail
le->headJuncIndex = CG_AddSparkJunc(le->headJuncIndex,
le, // rain - zinx's trail fix
cgs.media.bloodTrailShader,
le->refEntity.origin,
200,
1.0f - lifeFrac, // start alpha
1.0f - lifeFrac, // end alpha
3.0,
5.0);
if (trace.fraction < 1.0f)
{
// reflect the velocity on the trace plane
CG_ReflectVelocity(le, &trace);
// TODO: spawn a blood decal here?
// the intersection is a fraction of the frametime
le->pos.trTime = (int)time;
}
if (trace.fraction == 1.0f || time >= (float)cg.time)
{
return;
}
}
}
/**
* @brief CG_AddDebrisElements
* @param[in,out] le
*/
void CG_AddDebrisElements(localEntity_t *le)
{
vec3_t newOrigin;
trace_t trace;
float lifeFrac;
int t, step = 50;
for (t = le->lastTrailTime + step; t < cg.time; t += step)
{
// calculate new position
BG_EvaluateTrajectory(&le->pos, t, newOrigin, qfalse, -1);
// trace a line from previous position to new position
CG_Trace(&trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, MASK_SHOT);
// if stuck, kill it
if (trace.startsolid)
{
// HACK, some walls screw up, so just pass through if starting in a solid
VectorCopy(newOrigin, trace.endpos);
trace.fraction = 1.0f;
}
// moved some distance
VectorCopy(trace.endpos, le->refEntity.origin);
// add a trail
lifeFrac = (float)(t - le->startTime) / (float)(le->endTime - le->startTime);
#if 0 // spark line
if (le->effectWidth > 0)
{
le->headJuncIndex = CG_AddSparkJunc(le->headJuncIndex,
le, // rain - zinx's trail fix
cgs.media.sparkParticleShader,
le->refEntity.origin,
(int)(600.0 * (0.5 + 0.5 * (0.5 - lifeFrac))), // trail life
1.0 - lifeFrac * 2, // alpha
0.5 * (1.0 - lifeFrac), // end alpha
5.0 * (1.0 - lifeFrac), // start width
5.0 * (1.0 - lifeFrac)); // end width
}
#endif
// smoke
if (le->effectFlags & 1)
{
le->headJuncIndex2 = CG_AddSmokeJunc(le->headJuncIndex2,
le, // rain - zinx's trail fix
cgs.media.smokeTrailShader,
le->refEntity.origin,
(int)(2000.0f * (0.5f + 0.5f * (1.0f - lifeFrac))), // trail life
1.0f * (trace.fraction == 1.0f) * (0.5f + 0.5f * (1.0f - lifeFrac)), // alpha
1, // start width
(int)(60.0f * (0.5f + 0.5f * (1.0f - lifeFrac)))); // end width
}
// if it is in a nodrop zone, remove it
// this keeps gibs from waiting at the bottom of pits of death
// and floating levels
//if ( CG_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) {
//CG_FreeLocalEntity( le );
//return;
//}
if (trace.fraction < 1.0f)
{
// reflect the velocity on the trace plane
CG_ReflectVelocity(le, &trace);
if (VectorLengthSquared(le->pos.trDelta) < Square(1))
{
CG_FreeLocalEntity(le);
return;
}
// the intersection is a fraction of the frametime
le->pos.trTime = t;
}
le->lastTrailTime = t;
}
}
/**
* @brief CG_AddDebrisElementsExtended
* @param[in,out] le
*/
void CG_AddDebrisElementsExtended(localEntity_t *le)
{
vec3_t newOrigin;
trace_t trace;
if (le->pos.trType == TR_STATIONARY)
{
// sink into the ground if near the removal time
int t = le->endTime - cg.time;
if (t < SINK_TIME)
{
float oldZ;
// we must use an explicit lighting origin, otherwise the
// lighting would be lost as soon as the origin went
// into the ground
VectorCopy(le->refEntity.origin, le->refEntity.lightingOrigin);
le->refEntity.renderfx |= RF_LIGHTING_ORIGIN;
oldZ = le->refEntity.origin[2];
le->refEntity.origin[2] -= 16 * (1.0f - (float)t / SINK_TIME);
trap_R_AddRefEntityToScene(&le->refEntity);
le->refEntity.origin[2] = oldZ;
}
else
{
trap_R_AddRefEntityToScene(&le->refEntity);
}
return;
}
// calculate new position
BG_EvaluateTrajectory(&le->pos, cg.time, newOrigin, qfalse, -1);
// trace a line from previous position to new position
// FIXME: don't bounce at sky?
CG_Trace(&trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, CONTENTS_SOLID);
if (trace.fraction == 1.0f)
{
// still in free fall
VectorCopy(newOrigin, le->refEntity.origin);
if (le->leFlags & LEF_TUMBLE)
{
vec3_t angles;
BG_EvaluateTrajectory(&le->angles, cg.time, angles, qtrue, -1);
AnglesToAxis(angles, le->refEntity.axis);
}
trap_R_AddRefEntityToScene(&le->refEntity);
return;
}
// if it is in a nodrop zone, remove it
// this keeps gibs from waiting at the bottom of pits of death
// and floating levels
if (CG_PointContents(trace.endpos, 0) & CONTENTS_NODROP)
{
CG_FreeLocalEntity(le);
return;
}
// do a bouncy sound
CG_FragmentBounceSound(le, &trace);
// reflect the velocity on the trace plane
CG_ReflectVelocity(le, &trace);
trap_R_AddRefEntityToScene(&le->refEntity);
}
/*
=====================================================================
TRIVIAL LOCAL ENTITIES
These only do simple scaling or modulation before passing to the renderer
=====================================================================
*/
/**
* @brief CG_AddFadeRGB
* @param[in] le
*/
void CG_AddFadeRGB(localEntity_t *le)
{
refEntity_t *re = &le->refEntity;
float c = (le->endTime - cg.time) * le->lifeRate;
c *= 0xff;
re->shaderRGBA[0] = (byte)(le->color[0] * c);
re->shaderRGBA[1] = (byte)(le->color[1] * c);
re->shaderRGBA[2] = (byte)(le->color[2] * c);
re->shaderRGBA[3] = (byte)(le->color[3] * c);
trap_R_AddRefEntityToScene(re);
}
/**
* @brief CG_AddConstRGB
* @param[in] le
*/
void CG_AddConstRGB(localEntity_t *le)
{
refEntity_t *re = &le->refEntity;
re->shaderRGBA[0] = (byte)(le->color[0] * 255);
re->shaderRGBA[1] = (byte)(le->color[1] * 255);
re->shaderRGBA[2] = (byte)(le->color[2] * 255);
re->shaderRGBA[3] = (byte)(le->color[3] * 255);
trap_R_AddRefEntityToScene(re);
}
/**
* @brief CG_AddMoveScaleFade
* @param[in] le
*/
static void CG_AddMoveScaleFade(localEntity_t *le)
{
refEntity_t *re = &le->refEntity;
float c;
float len;
// fade / grow time
if (le->fadeInTime > le->startTime && cg.time < le->fadeInTime)
{
// fade / grow time
c = 1.0f - (float) (le->fadeInTime - cg.time) / (le->fadeInTime - le->startTime);
}
else
{
// fade / grow time
c = (le->endTime - cg.time) * le->lifeRate;
}
// spark
if (!(le->leFlags & LEF_NOFADEALPHA))
{
re->shaderRGBA[3] = (byte)(0xff * c * le->color[3]);
}
if (!(le->leFlags & LEF_PUFF_DONT_SCALE))
{
c = (le->endTime - cg.time) * le->lifeRate;
re->radius = le->radius * (1.0f - c) + 8;
}
BG_EvaluateTrajectory(&le->pos, cg.time, re->origin, qfalse, -1);
// if the view would be "inside" the sprite, kill the sprite
// so it doesn't add too much overdraw
len = VectorDistance(re->origin, cg.refdef_current->vieworg);
if (len < le->radius)
{
CG_FreeLocalEntity(le);
return;
}
trap_R_AddRefEntityToScene(re);
}
/**
* @brief CG_AddScaleFade
* @details For rocket smokes that hang in place, fade out, and are
* removed if the view passes through them.
* There are often many of these, so it needs to be simple.
* @param[in] le
*/
static void CG_AddScaleFade(localEntity_t *le)
{
refEntity_t *re = &le->refEntity;
float c = (le->endTime - cg.time) * le->lifeRate; // fade / grow time
float len;
re->shaderRGBA[3] = (byte)(0xff * c * le->color[3]);
if (!(le->leFlags & LEF_PUFF_DONT_SCALE))
{
re->radius = le->radius * (1.0f - c) + 8;
}
// if the view would be "inside" the sprite, kill the sprite
// so it doesn't add too much overdraw
len = VectorDistance(re->origin, cg.refdef_current->vieworg);
if (len < le->radius)
{
CG_FreeLocalEntity(le);
return;
}
trap_R_AddRefEntityToScene(re);
}
/**
* @brief CG_AddFallScaleFade
* @details This is just an optimized CG_AddMoveScaleFade
* For blood mists that drift down, fade out, and are
* removed if the view passes through them.
* There are often 100+ of these, so it needs to be simple.
* @param[in] le
*/
static void CG_AddFallScaleFade(localEntity_t *le)
{
refEntity_t *re = &le->refEntity;
float c = (le->endTime - cg.time) * le->lifeRate; // fade time
float len;
re->shaderRGBA[3] = (byte)(0xff * c * le->color[3]);
re->origin[2] = le->pos.trBase[2] - (1.0f - c) * le->pos.trDelta[2];
re->radius = le->radius * (1.0f - c) + 16;
// if the view would be "inside" the sprite, kill the sprite
// so it doesn't add too much overdraw
len = VectorDistance(re->origin, cg.refdef_current->vieworg);
if (len < le->radius)
{
CG_FreeLocalEntity(le);
return;
}
trap_R_AddRefEntityToScene(re);
}
/**
* @brief CG_AddExplosion
* @param[in] ex
*/
static void CG_AddExplosion(localEntity_t *ex)
{
refEntity_t *ent = &ex->refEntity;
// add the entity
// don't add if shader is invalid
if (ent->customShader >= 0)
{
trap_R_AddRefEntityToScene(ent);
}
// add the dlight
if (ex->light != 0.f)
{
float light = (float)(cg.time - ex->startTime) / (ex->endTime - ex->startTime);
if (light < 0.5f)
{
light = 1.0f;
}
else
{
light = 1.0f - (light - 0.5f) * 2;
}
light = ex->light * light;
trap_R_AddLightToScene(ent->origin, 512, light, ex->lightColor[0], ex->lightColor[1], ex->lightColor[2], 0, 0);
}
}
/**
* @brief CG_AddSpriteExplosion
* @param[in] le
*/
static void CG_AddSpriteExplosion(localEntity_t *le)
{
refEntity_t re = le->refEntity;
float c = (le->endTime - cg.time) / ( float ) (le->endTime - le->startTime);
if (c > 1)
{
c = 1.0; // can happen during connection problems
}
re.shaderRGBA[0] = 0xff;
re.shaderRGBA[1] = 0xff;
re.shaderRGBA[2] = 0xff;
re.shaderRGBA[3] = (byte)(0xff * c * 0.33f);
re.reType = RT_SPRITE;
re.radius = 42 * (1.0f - c) + 30;
// move away from surface
VectorMA(le->pos.trBase, (1.0f - c), le->pos.trDelta, re.origin);
// done.
// don't add if shader is invalid
if (re.customShader >= 0)
{
trap_R_AddRefEntityToScene(&re);
}
// add the dlight
//if (le->light || 1)
{
float light = (float)(cg.time - le->startTime) / (le->endTime - le->startTime);
if (light < 0.5f)
{
light = 1.0f;
}
else
{
light = 1.0f - (light - 0.5f) * 2;
}
trap_R_AddLightToScene(re.origin, 320, light, le->lightColor[0], le->lightColor[1], le->lightColor[2], 0, 0);
}
}
//==============================================================================
/**
* @brief CG_AddLocalEntities
*/
void CG_AddLocalEntities(void)
{
localEntity_t *le, *next;
// walk the list backwards, so any new local entities generated
// (trails, marks, etc) will be present this frame
le = cg_activeLocalEntities.prev;
for ( ; le != &cg_activeLocalEntities ; le = next)
{
// grab next now, so if the local entity is freed we
// still have it
next = le->prev;
if (cg.time >= le->endTime)
{
CG_FreeLocalEntity(le);
continue;
}
switch (le->leType)
{
default:
CG_Error("Bad leType: %i\n", le->leType);
break;
case LE_MOVING_TRACER:
CG_AddMovingTracer(le);
break;
case LE_SPARK:
CG_AddSparkElements(le);
break;
case LE_FUSE_SPARK:
CG_AddFuseSparkElements(le);
break;
case LE_DEBRIS:
{
CG_AddDebrisElements(le);
// reuses debris le for more debris - we don't allocate extra local ents for this
// setup is done in CG_AddDebris
CG_AddDebrisElementsExtended(le); // TODO merge with CG_AddDebrisElements
}
break;
case LE_BLOOD:
CG_AddBloodElements(le);
break;
case LE_MARK:
break;
case LE_SPRITE_EXPLOSION:
CG_AddSpriteExplosion(le);
break;
case LE_EXPLOSION:
CG_AddExplosion(le);
break;
case LE_FRAGMENT: // gibs and brass
CG_AddFragment(le);
break;
case LE_MOVE_SCALE_FADE: // water bubbles
CG_AddMoveScaleFade(le);
break;
case LE_FADE_RGB: // teleporters, railtrails
CG_AddFadeRGB(le);
break;
case LE_CONST_RGB:
CG_AddConstRGB(le); // debug lines
break;
case LE_FALL_SCALE_FADE: // gib blood trails
CG_AddFallScaleFade(le);
break;
case LE_SCALE_FADE: // rocket trails
CG_AddScaleFade(le);
break;
case LE_EMITTER:
CG_AddEmitter(le);
break;
}
}
}