mirror of
https://github.com/ReactionQuake3/reaction.git
synced 2024-11-25 05:31:21 +00:00
1209 lines
30 KiB
C
1209 lines
30 KiB
C
//-----------------------------------------------------------------------------
|
|
//
|
|
// $Id$
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// $Log$
|
|
// Revision 1.43 2004/01/26 21:26:08 makro
|
|
// no message
|
|
//
|
|
// Revision 1.42 2003/08/02 22:29:07 makro
|
|
// Made underwater bubbles 3 times smaller
|
|
//
|
|
// Revision 1.41 2002/08/25 07:09:20 niceass
|
|
// added "life" setting to func_pressure
|
|
//
|
|
// Revision 1.40 2002/08/21 07:09:54 jbravo
|
|
// Removed an extra definition of the vtos call
|
|
//
|
|
// Revision 1.39 2002/07/22 07:27:21 niceass
|
|
// better fog laser support
|
|
//
|
|
// Revision 1.38 2002/06/23 04:36:27 niceass
|
|
// change to foglaser
|
|
//
|
|
// Revision 1.37 2002/06/21 21:06:56 niceass
|
|
// laserfog stuff
|
|
//
|
|
// Revision 1.36 2002/06/21 04:11:34 niceass
|
|
// fog laser
|
|
//
|
|
// Revision 1.35 2002/06/16 20:06:13 jbravo
|
|
// Reindented all the source files with "indent -kr -ut -i8 -l120 -lc120 -sob -bad -bap"
|
|
//
|
|
// Revision 1.34 2002/06/16 19:12:52 jbravo
|
|
// Removed the MISSIONPACK ifdefs and missionpack only code.
|
|
//
|
|
// Revision 1.33 2002/06/09 05:16:33 niceass
|
|
// pressure change
|
|
//
|
|
// Revision 1.32 2002/06/03 00:39:29 blaze
|
|
// dont make a sound when bouncing on the ground
|
|
//
|
|
// Revision 1.31 2002/05/26 05:16:12 niceass
|
|
// pressure
|
|
//
|
|
// Revision 1.30 2002/05/19 21:27:51 blaze
|
|
// added force and buoyancy to breakables
|
|
//
|
|
// Revision 1.29 2002/04/29 06:14:10 niceass
|
|
// pressure
|
|
//
|
|
// Revision 1.28 2002/04/26 03:39:34 jbravo
|
|
// added tkok, fixed players always leaving zcam modes when player thats
|
|
// beeing tracked dies
|
|
//
|
|
// Revision 1.27 2002/04/23 06:10:10 niceass
|
|
// some good tabbing
|
|
//
|
|
// Revision 1.26 2002/04/06 21:43:58 makro
|
|
// New surfaceparm system
|
|
//
|
|
// Revision 1.25 2002/04/03 15:51:01 jbravo
|
|
// Small warning fixes
|
|
//
|
|
// Revision 1.24 2002/04/03 03:13:48 blaze
|
|
// NEW BREAKABLE CODE - will break all old breakables(wont appear in maps)
|
|
//
|
|
// Revision 1.23 2002/03/31 03:31:24 jbravo
|
|
// Compiler warning cleanups
|
|
//
|
|
// Revision 1.22 2002/03/21 02:17:39 blaze
|
|
// more func_explosive goodness
|
|
//
|
|
// Revision 1.21 2002/03/21 00:26:46 blaze
|
|
// some fixing of func_explosive
|
|
//
|
|
// Revision 1.20 2002/03/04 20:50:59 jbravo
|
|
// No floating scores over dead bodies, triangles disabled, and no viewing
|
|
// names of enemys just of teammates.
|
|
//
|
|
// Revision 1.19 2002/01/24 14:20:53 jbravo
|
|
// Adding func_explosive and a few new surfaceparms
|
|
//
|
|
// Revision 1.18 2002/01/11 19:48:29 jbravo
|
|
// Formatted the source in non DOS format.
|
|
//
|
|
// Revision 1.17 2001/12/31 16:28:41 jbravo
|
|
// I made a Booboo with the Log tag.
|
|
//
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 1999-2000 Id Software, Inc.
|
|
//
|
|
// cg_effects.c -- these functions generate localentities, usually as a result
|
|
// of event processing
|
|
|
|
#include "cg_local.h"
|
|
|
|
extern void CG_Particle_Bleed(qhandle_t pshader, vec3_t start, vec3_t dir, int fleshEntityNum, int duration);
|
|
|
|
/*
|
|
===============
|
|
CG_ShrapnelSpark
|
|
|
|
Moved from cg_weapons
|
|
Added by Elder
|
|
Modified tracer code
|
|
I really don't know what's going on half the time here :)
|
|
===============
|
|
*/
|
|
|
|
void CG_CreateTracer(int entity, vec3_t start, vec3_t end);
|
|
|
|
void CG_ShrapnelSpark(vec3_t source, vec3_t dest, float width, float length)
|
|
{
|
|
vec3_t forward, right;
|
|
// polyVert_t verts[4];
|
|
vec3_t line;
|
|
float len, begin, end;
|
|
// vec3_t start, finish;
|
|
|
|
//vec3_t midpoint;
|
|
|
|
// tracer
|
|
VectorSubtract(dest, source, forward);
|
|
len = VectorNormalize(forward);
|
|
|
|
// start at least a little ways from the muzzle
|
|
//if ( len < 10 ) {
|
|
//return;
|
|
//}
|
|
|
|
begin = crandom() * 8;
|
|
end = begin + length;
|
|
if (end > len) {
|
|
end = len;
|
|
}
|
|
// VectorMA(source, begin, forward, start);
|
|
// VectorMA(source, end, forward, finish);
|
|
|
|
line[0] = DotProduct(forward, cg.refdef.viewaxis[1]);
|
|
line[1] = DotProduct(forward, cg.refdef.viewaxis[2]);
|
|
|
|
VectorScale(cg.refdef.viewaxis[1], line[1], right);
|
|
VectorMA(right, -line[0], cg.refdef.viewaxis[2], right);
|
|
VectorNormalize(right);
|
|
|
|
/* VectorMA(finish, width, right, verts[0].xyz);
|
|
verts[0].st[0] = 0;
|
|
verts[0].st[1] = 1;
|
|
verts[0].modulate[0] = 255;
|
|
verts[0].modulate[1] = 255;
|
|
verts[0].modulate[2] = 255;
|
|
verts[0].modulate[3] = 255;
|
|
|
|
VectorMA(finish, -width, right, verts[1].xyz);
|
|
verts[1].st[0] = 1;
|
|
verts[1].st[1] = 0;
|
|
verts[1].modulate[0] = 255;
|
|
verts[1].modulate[1] = 255;
|
|
verts[1].modulate[2] = 255;
|
|
verts[1].modulate[3] = 255;
|
|
|
|
VectorMA(start, -width, right, verts[2].xyz);
|
|
verts[2].st[0] = 1;
|
|
verts[2].st[1] = 1;
|
|
verts[2].modulate[0] = 255;
|
|
verts[2].modulate[1] = 255;
|
|
verts[2].modulate[2] = 255;
|
|
verts[2].modulate[3] = 255;
|
|
|
|
VectorMA(start, width, right, verts[3].xyz);
|
|
verts[3].st[0] = 0;
|
|
verts[3].st[1] = 0;
|
|
verts[3].modulate[0] = 255;
|
|
verts[3].modulate[1] = 255;
|
|
verts[3].modulate[2] = 255;
|
|
verts[3].modulate[3] = 255; */
|
|
|
|
//trap_R_AddPolyToScene(cgs.media.tracerShader, 4, verts);
|
|
CG_CreateTracer(ENTITYNUM_WORLD, source, dest);
|
|
|
|
//midpoint[0] = ( start[0] + finish[0] ) * 0.5;
|
|
//midpoint[1] = ( start[1] + finish[1] ) * 0.5;
|
|
//midpoint[2] = ( start[2] + finish[2] ) * 0.5;
|
|
|
|
// add the tracer sound
|
|
//trap_S_StartSound( midpoint, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.tracerSound );
|
|
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CG_BubbleTrail
|
|
|
|
Bullets shot underwater
|
|
==================
|
|
*/
|
|
void CG_BubbleTrail(vec3_t start, vec3_t end, float spacing)
|
|
{
|
|
vec3_t move;
|
|
vec3_t vec;
|
|
float len;
|
|
int i;
|
|
|
|
if (cg_noProjectileTrail.integer) {
|
|
return;
|
|
}
|
|
|
|
VectorCopy(start, move);
|
|
VectorSubtract(end, start, vec);
|
|
len = VectorNormalize(vec);
|
|
|
|
// advance a random amount first
|
|
i = rand() % (int) spacing;
|
|
VectorMA(move, i, vec, move);
|
|
|
|
VectorScale(vec, spacing, vec);
|
|
|
|
for (; i < len; i += spacing) {
|
|
localEntity_t *le;
|
|
refEntity_t *re;
|
|
|
|
le = CG_AllocLocalEntity();
|
|
le->leFlags = LEF_PUFF_DONT_SCALE;
|
|
le->leType = LE_MOVE_SCALE_FADE;
|
|
le->startTime = cg.time;
|
|
le->endTime = cg.time + 1000 + random() * 250;
|
|
le->lifeRate = 1.0 / (le->endTime - le->startTime);
|
|
|
|
re = &le->refEntity;
|
|
re->shaderTime = cg.time / 1000.0f;
|
|
|
|
re->reType = RT_SPRITE;
|
|
re->rotation = 0;
|
|
//Makro - changed from 3
|
|
re->radius = 1;
|
|
re->customShader = cgs.media.waterBubbleShader;
|
|
re->shaderRGBA[0] = 0xff;
|
|
re->shaderRGBA[1] = 0xff;
|
|
re->shaderRGBA[2] = 0xff;
|
|
re->shaderRGBA[3] = 0xff;
|
|
|
|
le->color[3] = 1.0;
|
|
|
|
le->pos.trType = TR_LINEAR;
|
|
le->pos.trTime = cg.time;
|
|
VectorCopy(move, le->pos.trBase);
|
|
le->pos.trDelta[0] = crandom() * 5;
|
|
le->pos.trDelta[1] = crandom() * 5;
|
|
le->pos.trDelta[2] = crandom() * 5 + 6;
|
|
|
|
VectorAdd(move, vec, move);
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
CG_SmokePuff
|
|
|
|
Adds a smoke puff or blood trail localEntity.
|
|
=====================
|
|
*/
|
|
localEntity_t *CG_SmokePuff(const vec3_t p, const vec3_t vel,
|
|
float radius,
|
|
float r, float g, float b, float a,
|
|
float duration, int startTime, int fadeInTime, int leFlags, qhandle_t hShader)
|
|
{
|
|
static int seed = 0x92;
|
|
localEntity_t *le;
|
|
refEntity_t *re;
|
|
|
|
// int fadeInTime = startTime + duration / 2;
|
|
|
|
le = CG_AllocLocalEntity();
|
|
le->leFlags = leFlags;
|
|
le->radius = radius;
|
|
|
|
re = &le->refEntity;
|
|
re->rotation = Q_random(&seed) * 360;
|
|
re->radius = radius;
|
|
re->shaderTime = startTime / 1000.0f;
|
|
|
|
le->leType = LE_MOVE_SCALE_FADE;
|
|
le->startTime = startTime;
|
|
le->fadeInTime = fadeInTime;
|
|
le->endTime = startTime + duration;
|
|
if (fadeInTime > startTime) {
|
|
le->lifeRate = 1.0 / (le->endTime - le->fadeInTime);
|
|
} else {
|
|
le->lifeRate = 1.0 / (le->endTime - le->startTime);
|
|
}
|
|
le->color[0] = r;
|
|
le->color[1] = g;
|
|
le->color[2] = b;
|
|
le->color[3] = a;
|
|
|
|
le->pos.trType = TR_LINEAR;
|
|
le->pos.trTime = startTime;
|
|
VectorCopy(vel, le->pos.trDelta);
|
|
VectorCopy(p, le->pos.trBase);
|
|
|
|
VectorCopy(p, re->origin);
|
|
re->customShader = hShader;
|
|
|
|
// rage pro can't alpha fade, so use a different shader
|
|
if (cgs.glconfig.hardwareType == GLHW_RAGEPRO) {
|
|
re->customShader = cgs.media.smokePuffRageProShader;
|
|
re->shaderRGBA[0] = 0xff;
|
|
re->shaderRGBA[1] = 0xff;
|
|
re->shaderRGBA[2] = 0xff;
|
|
re->shaderRGBA[3] = 0xff;
|
|
} else {
|
|
re->shaderRGBA[0] = le->color[0] * 0xff;
|
|
re->shaderRGBA[1] = le->color[1] * 0xff;
|
|
re->shaderRGBA[2] = le->color[2] * 0xff;
|
|
re->shaderRGBA[3] = 0xff;
|
|
}
|
|
|
|
re->reType = RT_SPRITE;
|
|
re->radius = le->radius;
|
|
|
|
return le;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CG_ScorePlum
|
|
==================
|
|
*/
|
|
void CG_ScorePlum(int client, vec3_t org, int score)
|
|
{
|
|
localEntity_t *le;
|
|
refEntity_t *re;
|
|
vec3_t angles;
|
|
static vec3_t lastPos;
|
|
|
|
// only visualize for the client that scored
|
|
if (client != cg.predictedPlayerState.clientNum || cg_scorePlum.integer == 0) {
|
|
return;
|
|
}
|
|
// JBravo: and not in team based games
|
|
if (cgs.gametype >= GT_TEAM) {
|
|
return;
|
|
}
|
|
|
|
le = CG_AllocLocalEntity();
|
|
le->leFlags = 0;
|
|
le->leType = LE_SCOREPLUM;
|
|
le->startTime = cg.time;
|
|
le->endTime = cg.time + 4000;
|
|
le->lifeRate = 1.0 / (le->endTime - le->startTime);
|
|
|
|
le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0;
|
|
le->radius = score;
|
|
|
|
VectorCopy(org, le->pos.trBase);
|
|
if (org[2] >= lastPos[2] - 20 && org[2] <= lastPos[2] + 20) {
|
|
le->pos.trBase[2] -= 20;
|
|
}
|
|
//CG_Printf( "Plum origin %i %i %i -- %i\n", (int)org[0], (int)org[1], (int)org[2], (int)Distance(org, lastPos));
|
|
VectorCopy(org, lastPos);
|
|
|
|
re = &le->refEntity;
|
|
|
|
re->reType = RT_SPRITE;
|
|
re->radius = 16;
|
|
|
|
VectorClear(angles);
|
|
AnglesToAxis(angles, re->axis);
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CG_MakeExplosion
|
|
====================
|
|
*/
|
|
localEntity_t *CG_MakeExplosion(vec3_t origin, vec3_t dir,
|
|
qhandle_t hModel, qhandle_t shader, int msec, qboolean isSprite)
|
|
{
|
|
float ang;
|
|
localEntity_t *ex;
|
|
int offset;
|
|
vec3_t tmpVec, newOrigin;
|
|
|
|
if (msec <= 0) {
|
|
CG_Error("CG_MakeExplosion: msec = %i", msec);
|
|
}
|
|
// skew the time a bit so they aren't all in sync
|
|
offset = rand() & 63;
|
|
|
|
ex = CG_AllocLocalEntity();
|
|
if (isSprite) {
|
|
ex->leType = LE_SPRITE_EXPLOSION;
|
|
|
|
// randomly rotate sprite orientation
|
|
ex->refEntity.rotation = rand() % 360;
|
|
VectorScale(dir, 16, tmpVec);
|
|
VectorAdd(tmpVec, origin, newOrigin);
|
|
} else {
|
|
ex->leType = LE_EXPLOSION;
|
|
VectorCopy(origin, newOrigin);
|
|
|
|
// set axis with random rotate
|
|
if (!dir) {
|
|
AxisClear(ex->refEntity.axis);
|
|
} else {
|
|
ang = rand() % 360;
|
|
VectorCopy(dir, ex->refEntity.axis[0]);
|
|
RotateAroundDirection(ex->refEntity.axis, ang);
|
|
}
|
|
}
|
|
|
|
ex->startTime = cg.time - offset;
|
|
ex->endTime = ex->startTime + msec;
|
|
|
|
// bias the time so all shader effects start correctly
|
|
ex->refEntity.shaderTime = ex->startTime / 1000.0f;
|
|
|
|
ex->refEntity.hModel = hModel;
|
|
ex->refEntity.customShader = shader;
|
|
|
|
// set origin
|
|
VectorCopy(newOrigin, ex->refEntity.origin);
|
|
VectorCopy(newOrigin, ex->refEntity.oldorigin);
|
|
|
|
ex->color[0] = ex->color[1] = ex->color[2] = 1.0;
|
|
|
|
return ex;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_BleedSpray
|
|
|
|
Elder: This is a super blood spray for SSG hits
|
|
Based on bubble trail code + other stuff
|
|
=================
|
|
*/
|
|
#define MAX_SPRAY_BURSTS 16
|
|
void CG_BleedSpray(vec3_t start, vec3_t end, int entityNum, int numBursts)
|
|
{
|
|
//vec3_t dir;
|
|
vec3_t trueEnd;
|
|
vec3_t move;
|
|
vec3_t vec;
|
|
vec3_t velocity;
|
|
|
|
localEntity_t *blood;
|
|
float len;
|
|
int i;
|
|
int spacing = 30;
|
|
int bloodCount = 0;
|
|
trace_t tr;
|
|
|
|
if (!cg_blood.integer || cg_RQ3_bloodStyle.integer == 0) {
|
|
return;
|
|
}
|
|
//Clamp number so we don't generate too many blood entities
|
|
if (numBursts > MAX_SPRAY_BURSTS)
|
|
numBursts = MAX_SPRAY_BURSTS;
|
|
|
|
VectorCopy(end, move);
|
|
VectorSubtract(end, start, vec);
|
|
|
|
//Calculate true length via start/end points
|
|
VectorCopy(vec, trueEnd);
|
|
VectorNormalize(trueEnd);
|
|
|
|
//VectorScale (trueEnd, 300 + rand() % 100, trueEnd);
|
|
//VectorAdd (end, trueEnd, trueEnd);
|
|
VectorMA(end, 300 + rand() % 100, trueEnd, trueEnd);
|
|
|
|
// Check end point validity so it doesn't go through walls
|
|
// If it does go through wall, take the trace's endpoint
|
|
CG_Trace(&tr, start, NULL, NULL, trueEnd, entityNum, CONTENTS_SOLID);
|
|
if (tr.fraction != 1.0)
|
|
VectorCopy(tr.endpos, trueEnd);
|
|
|
|
VectorSubtract(trueEnd, start, vec);
|
|
|
|
len = VectorNormalize(vec);
|
|
|
|
//Set velocity
|
|
VectorScale(vec, 10, velocity);
|
|
if (cg_RQ3_bloodStyle.integer == 1)
|
|
velocity[2] += 30;
|
|
else
|
|
velocity[2] -= 10;
|
|
|
|
// advance a random amount first
|
|
i = rand() % (int) spacing;
|
|
VectorMA(move, i, vec, move);
|
|
VectorScale(vec, spacing, vec);
|
|
|
|
for (; i < len; i += spacing) {
|
|
//restrict amount of spurts
|
|
if (bloodCount++ > numBursts)
|
|
break;
|
|
|
|
blood = CG_SmokePuff(move, velocity, 8,
|
|
1, 1, 1, 1,
|
|
1500 + rand() % 250,
|
|
cg.time, 0, LEF_TUMBLE | LEF_PUFF_DONT_SCALE, cgs.media.bloodTrailShader);
|
|
|
|
blood->refEntity.rotation = rand() % 360;
|
|
//Check blood style
|
|
if (cg_RQ3_bloodStyle.integer == 1) {
|
|
blood->leType = LE_FRAGMENT;
|
|
blood->leMarkType = LEMT_BLOOD;
|
|
blood->pos.trType = TR_GRAVITY;
|
|
blood->bounceFactor = 0.4f;
|
|
}
|
|
|
|
VectorAdd(move, vec, move);
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_EjectBloodSplat
|
|
|
|
Drop a splat
|
|
=================
|
|
*/
|
|
|
|
void CG_EjectBloodSplat(vec3_t origin, vec3_t velocity, int amount, int duration)
|
|
{
|
|
int i;
|
|
localEntity_t *blood;
|
|
vec3_t bOrigin;
|
|
vec3_t bVelocity;
|
|
|
|
if (!cg_blood.integer)
|
|
return;
|
|
|
|
for (i = 0; i < amount; i++) {
|
|
VectorCopy(origin, bOrigin);
|
|
VectorCopy(velocity, bVelocity);
|
|
bOrigin[0] += rand() % 6 - 3;
|
|
bOrigin[1] += rand() % 6 - 3;
|
|
bVelocity[0] += rand() % 6 - 3;
|
|
bVelocity[1] += rand() % 6 - 3;
|
|
|
|
blood = CG_SmokePuff(bOrigin, bVelocity, 4,
|
|
1, 1, 1, 0.6f,
|
|
duration + rand() % 250, cg.time, 0,
|
|
LEF_TUMBLE | LEF_PUFF_DONT_SCALE, cgs.media.bloodTrailShader);
|
|
blood->refEntity.rotation = rand() % 360;
|
|
blood->leType = LE_FRAGMENT;
|
|
blood->leType = LE_FRAGMENT;
|
|
blood->leMarkType = LEMT_BLOOD;
|
|
blood->pos.trType = TR_GRAVITY;
|
|
blood->bounceFactor = 0.4f;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_BleedParticleSpray
|
|
|
|
This is a particle blood spray not unlike Quake 2's
|
|
Err, it's not working well right now :/
|
|
=================
|
|
*/
|
|
|
|
void CG_BleedParticleSpray(vec3_t start, vec3_t dir, int fleshEntityNum, int amount, int duration)
|
|
{
|
|
int i;
|
|
|
|
if (!cg_RQ3_bloodStyle.integer || !cg_blood.integer)
|
|
return;
|
|
|
|
for (i = 0; i < amount; i++) {
|
|
CG_Particle_Bleed(cgs.media.bloodExplosionShader, start, dir, fleshEntityNum, duration + rand() % 250);
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_Bleed
|
|
|
|
This is the spurt of blood when a character gets hit
|
|
=================
|
|
*/
|
|
void CG_Bleed(vec3_t origin, int entityNum)
|
|
{
|
|
localEntity_t *ex;
|
|
|
|
if (!cg_blood.integer) {
|
|
return;
|
|
}
|
|
|
|
ex = CG_AllocLocalEntity();
|
|
ex->leType = LE_EXPLOSION;
|
|
|
|
ex->startTime = cg.time;
|
|
ex->endTime = ex->startTime + 500;
|
|
|
|
VectorCopy(origin, ex->refEntity.origin);
|
|
ex->refEntity.reType = RT_SPRITE;
|
|
ex->refEntity.rotation = rand() % 360;
|
|
ex->refEntity.radius = 24;
|
|
|
|
ex->refEntity.customShader = cgs.media.bloodExplosionShader;
|
|
|
|
// don't show player's own blood in view
|
|
if (entityNum == cg.snap->ps.clientNum) {
|
|
ex->refEntity.renderfx |= RF_THIRD_PERSON;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CG_LaunchExplode
|
|
==================
|
|
*/
|
|
void CG_LaunchGib(vec3_t origin, vec3_t velocity, qhandle_t hModel)
|
|
{
|
|
localEntity_t *le;
|
|
refEntity_t *re;
|
|
|
|
le = CG_AllocLocalEntity();
|
|
re = &le->refEntity;
|
|
|
|
le->leType = LE_FRAGMENT;
|
|
le->startTime = cg.time;
|
|
le->endTime = le->startTime + 5000 + random() * 3000;
|
|
|
|
VectorCopy(origin, re->origin);
|
|
AxisCopy(axisDefault, re->axis);
|
|
re->hModel = hModel;
|
|
|
|
le->pos.trType = TR_GRAVITY;
|
|
VectorCopy(origin, le->pos.trBase);
|
|
VectorCopy(velocity, le->pos.trDelta);
|
|
le->pos.trTime = cg.time;
|
|
|
|
le->bounceFactor = 0.6f;
|
|
|
|
le->leBounceSoundType = LEBS_BLOOD;
|
|
le->leMarkType = LEMT_BLOOD;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
CG_GibPlayer
|
|
|
|
Generated a bunch of gibs launching out from the bodies location
|
|
===================
|
|
*/
|
|
#define GIB_VELOCITY 250
|
|
#define GIB_JUMP 250
|
|
void CG_GibPlayer(vec3_t playerOrigin)
|
|
{
|
|
vec3_t origin, velocity;
|
|
|
|
if (!cg_blood.integer) {
|
|
return;
|
|
}
|
|
|
|
VectorCopy(playerOrigin, origin);
|
|
velocity[0] = crandom() * GIB_VELOCITY;
|
|
velocity[1] = crandom() * GIB_VELOCITY;
|
|
velocity[2] = GIB_JUMP + crandom() * GIB_VELOCITY;
|
|
if (rand() & 1) {
|
|
CG_LaunchGib(origin, velocity, cgs.media.gibSkull);
|
|
} else {
|
|
CG_LaunchGib(origin, velocity, cgs.media.gibBrain);
|
|
}
|
|
|
|
// allow gibs to be turned off for speed
|
|
if (!cg_gibs.integer) {
|
|
return;
|
|
}
|
|
|
|
VectorCopy(playerOrigin, origin);
|
|
velocity[0] = crandom() * GIB_VELOCITY;
|
|
velocity[1] = crandom() * GIB_VELOCITY;
|
|
velocity[2] = GIB_JUMP + crandom() * GIB_VELOCITY;
|
|
CG_LaunchGib(origin, velocity, cgs.media.gibAbdomen);
|
|
|
|
VectorCopy(playerOrigin, origin);
|
|
velocity[0] = crandom() * GIB_VELOCITY;
|
|
velocity[1] = crandom() * GIB_VELOCITY;
|
|
velocity[2] = GIB_JUMP + crandom() * GIB_VELOCITY;
|
|
CG_LaunchGib(origin, velocity, cgs.media.gibArm);
|
|
|
|
VectorCopy(playerOrigin, origin);
|
|
velocity[0] = crandom() * GIB_VELOCITY;
|
|
velocity[1] = crandom() * GIB_VELOCITY;
|
|
velocity[2] = GIB_JUMP + crandom() * GIB_VELOCITY;
|
|
CG_LaunchGib(origin, velocity, cgs.media.gibChest);
|
|
|
|
VectorCopy(playerOrigin, origin);
|
|
velocity[0] = crandom() * GIB_VELOCITY;
|
|
velocity[1] = crandom() * GIB_VELOCITY;
|
|
velocity[2] = GIB_JUMP + crandom() * GIB_VELOCITY;
|
|
CG_LaunchGib(origin, velocity, cgs.media.gibFist);
|
|
|
|
VectorCopy(playerOrigin, origin);
|
|
velocity[0] = crandom() * GIB_VELOCITY;
|
|
velocity[1] = crandom() * GIB_VELOCITY;
|
|
velocity[2] = GIB_JUMP + crandom() * GIB_VELOCITY;
|
|
CG_LaunchGib(origin, velocity, cgs.media.gibFoot);
|
|
|
|
VectorCopy(playerOrigin, origin);
|
|
velocity[0] = crandom() * GIB_VELOCITY;
|
|
velocity[1] = crandom() * GIB_VELOCITY;
|
|
velocity[2] = GIB_JUMP + crandom() * GIB_VELOCITY;
|
|
CG_LaunchGib(origin, velocity, cgs.media.gibForearm);
|
|
|
|
VectorCopy(playerOrigin, origin);
|
|
velocity[0] = crandom() * GIB_VELOCITY;
|
|
velocity[1] = crandom() * GIB_VELOCITY;
|
|
velocity[2] = GIB_JUMP + crandom() * GIB_VELOCITY;
|
|
CG_LaunchGib(origin, velocity, cgs.media.gibIntestine);
|
|
|
|
VectorCopy(playerOrigin, origin);
|
|
velocity[0] = crandom() * GIB_VELOCITY;
|
|
velocity[1] = crandom() * GIB_VELOCITY;
|
|
velocity[2] = GIB_JUMP + crandom() * GIB_VELOCITY;
|
|
CG_LaunchGib(origin, velocity, cgs.media.gibLeg);
|
|
|
|
VectorCopy(playerOrigin, origin);
|
|
velocity[0] = crandom() * GIB_VELOCITY;
|
|
velocity[1] = crandom() * GIB_VELOCITY;
|
|
velocity[2] = GIB_JUMP + crandom() * GIB_VELOCITY;
|
|
CG_LaunchGib(origin, velocity, cgs.media.gibLeg);
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CG_LaunchGib
|
|
==================
|
|
*/
|
|
void CG_LaunchExplode(vec3_t origin, vec3_t velocity, qhandle_t hModel)
|
|
{
|
|
localEntity_t *le;
|
|
refEntity_t *re;
|
|
|
|
le = CG_AllocLocalEntity();
|
|
re = &le->refEntity;
|
|
|
|
le->leType = LE_FRAGMENT;
|
|
le->startTime = cg.time;
|
|
le->endTime = le->startTime + 10000 + random() * 6000;
|
|
|
|
VectorCopy(origin, re->origin);
|
|
AxisCopy(axisDefault, re->axis);
|
|
re->hModel = hModel;
|
|
|
|
le->pos.trType = TR_GRAVITY;
|
|
VectorCopy(origin, le->pos.trBase);
|
|
VectorCopy(velocity, le->pos.trDelta);
|
|
le->pos.trTime = cg.time;
|
|
|
|
le->bounceFactor = 0.1f;
|
|
|
|
le->leBounceSoundType = LEBS_BRASS;
|
|
le->leMarkType = LEMT_NONE;
|
|
}
|
|
|
|
#define EXP_VELOCITY 100
|
|
#define EXP_JUMP 150
|
|
/*
|
|
===================
|
|
CG_BigExplode
|
|
|
|
Generated a bunch of gibs launching out from the bodies location
|
|
===================
|
|
*/
|
|
void CG_BigExplode(vec3_t playerOrigin)
|
|
{
|
|
vec3_t origin, velocity;
|
|
|
|
if (!cg_blood.integer) {
|
|
return;
|
|
}
|
|
|
|
VectorCopy(playerOrigin, origin);
|
|
velocity[0] = crandom() * EXP_VELOCITY;
|
|
velocity[1] = crandom() * EXP_VELOCITY;
|
|
velocity[2] = EXP_JUMP + crandom() * EXP_VELOCITY;
|
|
CG_LaunchExplode(origin, velocity, cgs.media.smoke2);
|
|
|
|
VectorCopy(playerOrigin, origin);
|
|
velocity[0] = crandom() * EXP_VELOCITY;
|
|
velocity[1] = crandom() * EXP_VELOCITY;
|
|
velocity[2] = EXP_JUMP + crandom() * EXP_VELOCITY;
|
|
CG_LaunchExplode(origin, velocity, cgs.media.smoke2);
|
|
|
|
VectorCopy(playerOrigin, origin);
|
|
velocity[0] = crandom() * EXP_VELOCITY * 1.5;
|
|
velocity[1] = crandom() * EXP_VELOCITY * 1.5;
|
|
velocity[2] = EXP_JUMP + crandom() * EXP_VELOCITY;
|
|
CG_LaunchExplode(origin, velocity, cgs.media.smoke2);
|
|
|
|
VectorCopy(playerOrigin, origin);
|
|
velocity[0] = crandom() * EXP_VELOCITY * 2.0;
|
|
velocity[1] = crandom() * EXP_VELOCITY * 2.0;
|
|
velocity[2] = EXP_JUMP + crandom() * EXP_VELOCITY;
|
|
CG_LaunchExplode(origin, velocity, cgs.media.smoke2);
|
|
|
|
VectorCopy(playerOrigin, origin);
|
|
velocity[0] = crandom() * EXP_VELOCITY * 2.5;
|
|
velocity[1] = crandom() * EXP_VELOCITY * 2.5;
|
|
velocity[2] = EXP_JUMP + crandom() * EXP_VELOCITY;
|
|
CG_LaunchExplode(origin, velocity, cgs.media.smoke2);
|
|
}
|
|
|
|
#define GLASS_VELOCITY 175
|
|
#define GLASS_JUMP 125
|
|
/*
|
|
==================
|
|
CG_LaunchGlass
|
|
==================
|
|
*/
|
|
void CG_LaunchGlass(vec3_t origin, vec3_t velocity, vec3_t rotation, float bounce, qhandle_t hModel) //, qhandle_t altSkin )
|
|
{
|
|
localEntity_t *le;
|
|
refEntity_t *re;
|
|
|
|
le = CG_AllocLocalEntity();
|
|
re = &le->refEntity;
|
|
|
|
le->leType = LE_FRAGMENT;
|
|
le->startTime = cg.time;
|
|
le->endTime = le->startTime + (random() * 3000) + cg_RQ3_glasstime.integer; // + 30000;
|
|
|
|
VectorCopy(origin, re->origin);
|
|
AxisCopy(axisDefault, re->axis);
|
|
re->hModel = hModel;
|
|
|
|
//Elder: custom shaders for debris?
|
|
//if (altSkin)
|
|
//re->customSkin = altSkin;
|
|
|
|
le->pos.trType = TR_GRAVITY;
|
|
VectorCopy(origin, le->pos.trBase);
|
|
VectorCopy(velocity, le->pos.trDelta);
|
|
le->pos.trTime = cg.time;
|
|
|
|
//Elder: added
|
|
//VectorCopy( origin, le->angles.trBase );
|
|
VectorCopy(velocity, le->angles.trBase);
|
|
le->angles.trBase[2] = le->angles.trBase[2] - GLASS_JUMP;
|
|
VectorCopy(rotation, le->angles.trDelta);
|
|
le->angles.trTime = cg.time;
|
|
|
|
le->bounceFactor = bounce;
|
|
|
|
le->leFlags = LEF_TUMBLE;
|
|
le->leBounceSoundType = LEBS_NONE;
|
|
le->leMarkType = LEMT_NONE;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
CG_BreakGlass
|
|
|
|
Generated a bunch of glass shards launching out from the glass location
|
|
Elder: don't be mislead by the name - this breaks more than glass
|
|
===================
|
|
*/
|
|
|
|
void CG_BreakGlass(vec3_t playerOrigin, int glassParm, int number, int type, int isChip)
|
|
{
|
|
vec3_t origin, velocity, rotation;
|
|
int value;
|
|
int count;
|
|
int states[] = { 1, 2, 3 }; // Select model variations
|
|
|
|
// Get the size of the array
|
|
int numstates = sizeof(states) / sizeof(states[0]);
|
|
|
|
// Elder: debris model handles
|
|
qhandle_t debris1;
|
|
qhandle_t debris2;
|
|
qhandle_t debris3;
|
|
float bounceFactor;
|
|
int newParm;
|
|
int id;
|
|
qhandle_t sound;
|
|
|
|
id = (glassParm & 63);
|
|
// Com_Printf("ID is %d\n",id);
|
|
glassParm = glassParm >> 6;
|
|
sound = cgs.media.breakables[id].sound[rand() % 3];
|
|
trap_S_StartSound(NULL, number, CHAN_BODY, sound);
|
|
bounceFactor = (float) 0.3;
|
|
|
|
if ((glassParm & RQ3_DEBRIS_MEDIUM) == RQ3_DEBRIS_MEDIUM && (glassParm & RQ3_DEBRIS_HIGH) == RQ3_DEBRIS_HIGH) {
|
|
//Tons
|
|
count = 65 + rand() % 16;
|
|
} else if ((glassParm & RQ3_DEBRIS_HIGH) == RQ3_DEBRIS_HIGH) {
|
|
//Large
|
|
count = 40 + rand() % 11;
|
|
} else if ((glassParm & RQ3_DEBRIS_MEDIUM) == RQ3_DEBRIS_MEDIUM) {
|
|
//Medium
|
|
count = 20 + rand() % 6;
|
|
} else {
|
|
//Small
|
|
count = 8 + rand() % 6;
|
|
}
|
|
//If it's just a chip, dont make so many
|
|
if (isChip == 1) {
|
|
count /= 8;
|
|
}
|
|
//Strip off amount info and revert eParm back to server-side size
|
|
newParm = glassParm & 15;
|
|
glassParm &= ~newParm;
|
|
glassParm = glassParm << (type * 4);
|
|
|
|
debris1 = cgs.media.breakables[id].model[0];
|
|
debris2 = cgs.media.breakables[id].model[1];
|
|
debris3 = cgs.media.breakables[id].model[2];
|
|
|
|
//launch loop
|
|
while (count--) {
|
|
// Generate the random number every count so every shard is a
|
|
// of the three. If this is placed above it only gets a random
|
|
// number every time a piece of glass is broken.
|
|
value = states[rand() % numstates];
|
|
|
|
VectorCopy(playerOrigin, origin);
|
|
velocity[0] = crandom() * 25 * cgs.media.breakables[id].velocity;
|
|
velocity[1] = crandom() * 25 * cgs.media.breakables[id].velocity;
|
|
velocity[2] = 25 * cgs.media.breakables[id].jump + crandom() * 25 * cgs.media.breakables[id].velocity;
|
|
//Elder: added
|
|
rotation[0] = crandom() * GLASS_VELOCITY;
|
|
rotation[1] = crandom() * GLASS_VELOCITY;
|
|
rotation[2] = crandom() * GLASS_VELOCITY;
|
|
|
|
switch (value) {
|
|
case 1:
|
|
CG_LaunchGlass(origin, velocity, rotation, bounceFactor, debris1);
|
|
break;
|
|
case 2:
|
|
CG_LaunchGlass(origin, velocity, rotation, bounceFactor, debris2);
|
|
break;
|
|
case 3:
|
|
CG_LaunchGlass(origin, velocity, rotation, bounceFactor, debris3);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// JBravo: For func_explosive
|
|
void CG_LaunchBreakableFrag(vec3_t origin, vec3_t velocity, qhandle_t hModel, float bouncyness, float size)
|
|
{
|
|
localEntity_t *le;
|
|
refEntity_t *re;
|
|
|
|
le = CG_AllocLocalEntity();
|
|
re = &le->refEntity;
|
|
|
|
le->leType = LE_FRAGMENT;
|
|
le->startTime = cg.time - (rand() & 63);
|
|
le->endTime = le->startTime + 5000 + random() * 3000;
|
|
|
|
VectorCopy(origin, re->origin);
|
|
AxisCopy(axisDefault, re->axis);
|
|
re->hModel = hModel;
|
|
// re->customShader = hShader;
|
|
|
|
le->pos.trType = TR_GRAVITY;
|
|
VectorCopy(origin, le->pos.trBase);
|
|
VectorCopy(velocity, le->pos.trDelta);
|
|
le->pos.trTime = cg.time;
|
|
|
|
le->size = size;
|
|
VectorScale(re->axis[0], size, re->axis[0]);
|
|
VectorScale(re->axis[1], size, re->axis[1]);
|
|
VectorScale(re->axis[2], size, re->axis[2]);
|
|
re->nonNormalizedAxes = qtrue;
|
|
|
|
le->angles.trType = TR_LINEAR;
|
|
le->angles.trTime = cg.time;
|
|
le->angles.trBase[0] = rand() & 63;
|
|
le->angles.trBase[1] = rand() & 63;
|
|
le->angles.trBase[2] = rand() & 63;
|
|
le->angles.trDelta[0] = rand() & 127;
|
|
le->angles.trDelta[1] = rand() & 127;
|
|
le->angles.trDelta[2] = rand() & 127;
|
|
le->leFlags = LEF_TUMBLE;
|
|
|
|
le->bounceFactor = bouncyness;
|
|
|
|
le->leBounceSoundType = LEBS_NONE;
|
|
le->leMarkType = LEMT_NONE;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
VectorToString
|
|
|
|
This is just a convenience function
|
|
for printing vectors
|
|
=============
|
|
*/
|
|
/*
|
|
* JBravo: this is unneccesary and causes compiler warnings for two definitions
|
|
char *vtos(const vec3_t v)
|
|
{
|
|
static int index;
|
|
static char str[8][32];
|
|
char *s;
|
|
|
|
// use an array so that multiple vtos won't collide
|
|
s = str[index];
|
|
index = (index + 1) & 7;
|
|
|
|
Com_sprintf(s, 32, "(%i %i %i)", (int) v[0], (int) v[1], (int) v[2]);
|
|
|
|
return s;
|
|
}
|
|
*/
|
|
// JBravo: also for func_explosive
|
|
/*
|
|
===================
|
|
CG_BreakBreakable
|
|
|
|
Generated a bunch of gibs launching out from the breakables location
|
|
===================
|
|
*/
|
|
#define BREAK_VELOCITY 550
|
|
#define BREAK_JUMP 1500
|
|
|
|
void CG_BreakBreakable(centity_t * cent, int eParam, int number)
|
|
{
|
|
localEntity_t *le;
|
|
vec3_t origin, velocity;
|
|
qhandle_t mod;
|
|
qhandle_t shader;
|
|
vec3_t shrapnelDest;
|
|
// localEntity_t *smokePuff;
|
|
vec3_t puffDir;
|
|
|
|
float light;
|
|
vec3_t lightColor;
|
|
int duration;
|
|
int sparkCount;
|
|
|
|
int i;
|
|
|
|
// JBravo: Unused variable
|
|
// int modelbias[10] = { 0, 0, 0, 0, 1, 1, 1, 2, 2 };
|
|
int id;
|
|
// int count;
|
|
|
|
id = (eParam & 63);
|
|
eParam = eParam >> 6;
|
|
|
|
trap_S_StartSound(NULL, number, CHAN_BODY, cgs.media.breakables[id].exp_sound);
|
|
|
|
/* JBravo: After all that setting of 'count' it is never used.
|
|
if ((eParam & RQ3_DEBRIS_MEDIUM) == RQ3_DEBRIS_MEDIUM && (eParam & RQ3_DEBRIS_HIGH) == RQ3_DEBRIS_HIGH) {
|
|
//Tons
|
|
count = 65 + rand() % 16;
|
|
} else if ((eParam & RQ3_DEBRIS_HIGH) == RQ3_DEBRIS_HIGH) {
|
|
//Large
|
|
count = 40 + rand() % 11;
|
|
} else if ((eParam & RQ3_DEBRIS_MEDIUM) == RQ3_DEBRIS_MEDIUM) {
|
|
//Medium
|
|
count = 20 + rand() % 6;
|
|
} else {
|
|
//Small
|
|
count = 8 + rand() % 6;
|
|
}
|
|
*/
|
|
//if (material) material--;
|
|
VectorCopy(cent->lerpOrigin, origin);
|
|
/*
|
|
sound = cgs.media.breakables[id].sound;
|
|
trap_S_StartSound( origin, cent->currentState.number, CHAN_BODY, sound ); */
|
|
|
|
// create an explosion
|
|
mod = cgs.media.dishFlashModel;
|
|
shader = cgs.media.breakables[id].shader;
|
|
|
|
//Com_Printf("Explosion, %d, breakableshader %d at %s ep %d\n",shader,cgs.media.breakables[id].shader, vtos(origin), eParam);
|
|
light = 550;
|
|
lightColor[0] = 1;
|
|
lightColor[1] = 1;
|
|
lightColor[2] = 0;
|
|
duration = 1000;
|
|
|
|
velocity[0] = 1;
|
|
velocity[1] = 1;
|
|
velocity[2] = 1;
|
|
|
|
le = CG_MakeExplosion(origin, velocity, mod, shader, duration, qtrue);
|
|
le->light = light;
|
|
VectorCopy(lightColor, le->lightColor);
|
|
|
|
sparkCount = 60 + rand() % 10;
|
|
origin[2] += 32;
|
|
|
|
for (i = 0; i < sparkCount; i++) {
|
|
VectorScale(velocity, rand() % 200, velocity);
|
|
velocity[0] += rand() % 200 - 100;
|
|
velocity[1] += rand() % 200 - 100;
|
|
if (i % 8 == 7) {
|
|
// Add shrapnel trace effect
|
|
VectorMA(origin, 0.7f, velocity, shrapnelDest);
|
|
CG_ShrapnelSpark(origin, shrapnelDest, 10, 280);
|
|
}
|
|
// Add sparks
|
|
CG_ParticleSparks(origin, velocity, 900 + rand() % 200, 5, 5, -2.5f, 3.5f);
|
|
}
|
|
|
|
// Add smoke puff
|
|
puffDir[0] = 0;
|
|
puffDir[1] = 0;
|
|
puffDir[2] = 20;
|
|
origin[2] -= 16;
|
|
CG_SmokePuff(origin, puffDir,
|
|
rand() % 12 + 100, 1, 1, 1, 0.6f, 3000, cg.time, 0, 0, cgs.media.smokePuffShader);
|
|
|
|
}
|
|
|
|
void CG_Pressure(vec3_t origin, vec3_t dir, int type, int speed, int life)
|
|
{
|
|
localEntity_t *le;
|
|
// refEntity_t *re;
|
|
|
|
le = CG_AllocLocalEntity();
|
|
// re = &le->refEntity;
|
|
le->leType = LE_PRESSURE;
|
|
|
|
if (type == 1)
|
|
le->leFlags = LEF_AIR;
|
|
else if (type == 2)
|
|
le->leFlags = LEF_FLAME;
|
|
else if (type == 3)
|
|
le->leFlags = LEF_WATER;
|
|
else
|
|
le->leFlags = LEF_STEAM;
|
|
|
|
le->size = (float) speed; // Size holds the speed.... yes...
|
|
le->life = life;
|
|
|
|
VectorCopy(origin, le->pos.trBase);
|
|
VectorCopy(dir, le->pos.trDelta);
|
|
le->startTime = cg.time;
|
|
le->endTime = le->startTime + 10000;
|
|
}
|
|
|
|
static void CG_VisibleLaser( vec3_t start, vec3_t finish ) {
|
|
refEntity_t re;
|
|
|
|
// re.shaderTime = cg.time / 1000.0f;
|
|
re.reType = RT_RAIL_CORE;
|
|
re.customShader = cgs.media.railCoreShader;
|
|
|
|
VectorCopy( start, re.origin );
|
|
VectorCopy( finish, re.oldorigin );
|
|
|
|
re.shaderRGBA[0] = 255;
|
|
re.shaderRGBA[1] = 0;
|
|
re.shaderRGBA[2] = 0;
|
|
re.shaderRGBA[3] = 128;
|
|
|
|
AxisClear( re.axis );
|
|
|
|
trap_R_AddRefEntityToScene( &re );
|
|
}
|
|
|
|
|
|
void CG_DrawVisibleLaser( vec3_t origin, int clientNum, vec3_t dir) {
|
|
int sourceContentType, destContentType;
|
|
vec3_t start, end;
|
|
trace_t trace;
|
|
|
|
if ( !cg_enableLaserFog.integer )
|
|
return;
|
|
|
|
// NiceAss: This user does not have a lasersight
|
|
if (!( cg_entities[clientNum].currentState.powerups & (1 << PW_LASERSIGHT) ))
|
|
return;
|
|
|
|
VectorCopy(origin, start);
|
|
|
|
VectorMA(origin, 8192 * 16, dir, end);
|
|
//trap_CM_BoxTrace(&trace, start, end, NULL, NULL, clientNum, MASK_ALL); //MASK_SHOT);
|
|
CG_Trace(&trace, start, NULL, NULL, end, clientNum, MASK_ALL);
|
|
VectorCopy(trace.endpos, end);
|
|
|
|
sourceContentType = trap_CM_PointContents(start, 0);
|
|
destContentType = trap_CM_PointContents(end, 0);
|
|
|
|
// do a complete laser if necessary
|
|
if ((sourceContentType == destContentType) && (sourceContentType & CONTENTS_FOG)) {
|
|
CG_VisibleLaser(start, end);
|
|
}
|
|
// laser from fog into air
|
|
else if ((sourceContentType & CONTENTS_FOG)) {
|
|
trap_CM_BoxTrace(&trace, end, start, NULL, NULL, 0, CONTENTS_FOG);
|
|
CG_VisibleLaser(start, trace.endpos);
|
|
}
|
|
// laser from air into fog
|
|
else if ((destContentType & CONTENTS_FOG)) {
|
|
trap_CM_BoxTrace(&trace, start, end, NULL, NULL, 0, CONTENTS_FOG);
|
|
CG_VisibleLaser(trace.endpos, end);
|
|
}
|
|
}
|