1 /*
2 ===========================================================================
3 Copyright (C) 1999 - 2005, Id Software, Inc.
4 Copyright (C) 2000 - 2013, Raven Software, Inc.
5 Copyright (C) 2001 - 2013, Activision, Inc.
6 Copyright (C) 2013 - 2015, OpenJK contributors
7 
8 This file is part of the OpenJK source code.
9 
10 OpenJK is free software; you can redistribute it and/or modify it
11 under the terms of the GNU General Public License version 2 as
12 published by the Free Software Foundation.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, see <http://www.gnu.org/licenses/>.
21 ===========================================================================
22 */
23 
24 // cg_effects.c -- these functions generate localentities, usually as a result
25 // of event processing
26 
27 #include "cg_local.h"
28 
29 /*
30 ==================
31 CG_BubbleTrail
32 
33 Bullets shot underwater
34 ==================
35 */
CG_BubbleTrail(vec3_t start,vec3_t end,float spacing)36 void CG_BubbleTrail( vec3_t start, vec3_t end, float spacing ) {
37 	vec3_t		move;
38 	vec3_t		vec;
39 	float		len;
40 	int			i;
41 
42 	if ( cg_noProjectileTrail.integer ) {
43 		return;
44 	}
45 
46 	VectorCopy (start, move);
47 	VectorSubtract (end, start, vec);
48 	len = VectorNormalize (vec);
49 
50 	// advance a random amount first
51 	i = rand() % (int)spacing;
52 	VectorMA( move, i, vec, move );
53 
54 	VectorScale (vec, spacing, vec);
55 
56 	for ( ; i < len; i += spacing ) {
57 		localEntity_t	*le;
58 		refEntity_t		*re;
59 
60 		le = CG_AllocLocalEntity();
61 		le->leFlags = LEF_PUFF_DONT_SCALE;
62 		le->leType = LE_MOVE_SCALE_FADE;
63 		le->startTime = cg.time;
64 		le->endTime = cg.time + 1000 + Q_flrand(-250.0f, 250.0f);
65 		le->lifeRate = 1.0 / ( le->endTime - le->startTime );
66 
67 		re = &le->refEntity;
68 		re->shaderTime = cg.time / 1000.0f;
69 
70 		re->reType = RT_SPRITE;
71 		re->rotation = 0;
72 		re->radius = 3;
73 		re->customShader = 0;//cgs.media.waterBubbleShader;
74 		re->shaderRGBA[0] = 0xff;
75 		re->shaderRGBA[1] = 0xff;
76 		re->shaderRGBA[2] = 0xff;
77 		re->shaderRGBA[3] = 0xff;
78 
79 		le->color[3] = 1.0;
80 
81 		le->pos.trType = TR_LINEAR;
82 		le->pos.trTime = cg.time;
83 		VectorCopy( move, le->pos.trBase );
84 		le->pos.trDelta[0] = Q_flrand(-5.0f, 5.0f);
85 		le->pos.trDelta[1] = Q_flrand(-5.0f, 5.0f);
86 		le->pos.trDelta[2] = Q_flrand(-5.0f, 5.0f) + 6;
87 
88 		VectorAdd (move, vec, move);
89 	}
90 }
91 
92 /*
93 =====================
94 CG_SmokePuff
95 
96 Adds a smoke puff or blood trail localEntity.
97 =====================
98 */
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)99 localEntity_t *CG_SmokePuff( const vec3_t p, const vec3_t vel,
100 				   float radius,
101 				   float r, float g, float b, float a,
102 				   float duration,
103 				   int startTime,
104 				   int fadeInTime,
105 				   int leFlags,
106 				   qhandle_t hShader ) {
107 	static int	seed = 0x92;
108 	localEntity_t	*le;
109 	refEntity_t		*re;
110 //	int fadeInTime = startTime + duration / 2;
111 
112 	le = CG_AllocLocalEntity();
113 	le->leFlags = leFlags;
114 	le->radius = radius;
115 
116 	re = &le->refEntity;
117 	re->rotation = Q_random( &seed ) * 360;
118 	re->radius = radius;
119 	re->shaderTime = startTime / 1000.0f;
120 
121 	le->leType = LE_MOVE_SCALE_FADE;
122 	le->startTime = startTime;
123 	le->fadeInTime = fadeInTime;
124 	le->endTime = startTime + duration;
125 	if ( fadeInTime > startTime ) {
126 		le->lifeRate = 1.0 / ( le->endTime - le->fadeInTime );
127 	}
128 	else {
129 		le->lifeRate = 1.0 / ( le->endTime - le->startTime );
130 	}
131 	le->color[0] = r;
132 	le->color[1] = g;
133 	le->color[2] = b;
134 	le->color[3] = a;
135 
136 
137 	le->pos.trType = TR_LINEAR;
138 	le->pos.trTime = startTime;
139 	VectorCopy( vel, le->pos.trDelta );
140 	VectorCopy( p, le->pos.trBase );
141 
142 	VectorCopy( p, re->origin );
143 	re->customShader = hShader;
144 
145 	re->shaderRGBA[0] = le->color[0] * 0xff;
146 	re->shaderRGBA[1] = le->color[1] * 0xff;
147 	re->shaderRGBA[2] = le->color[2] * 0xff;
148 	re->shaderRGBA[3] = 0xff;
149 
150 	re->reType = RT_SPRITE;
151 	re->radius = le->radius;
152 
153 	return le;
154 }
155 
CGDEBUG_SaberColor(int saberColor)156 int CGDEBUG_SaberColor( int saberColor )
157 {
158 	switch( (int)(saberColor) )
159 	{
160 		case SABER_RED:
161 			return 0x000000ff;
162 			break;
163 		case SABER_ORANGE:
164 			return 0x000088ff;
165 			break;
166 		case SABER_YELLOW:
167 			return 0x0000ffff;
168 			break;
169 		case SABER_GREEN:
170 			return 0x0000ff00;
171 			break;
172 		case SABER_BLUE:
173 			return 0x00ff0000;
174 			break;
175 		case SABER_PURPLE:
176 			return 0x00ff00ff;
177 			break;
178 		default:
179 			return saberColor;
180 			break;
181 	}
182 }
183 
CG_TestLine(vec3_t start,vec3_t end,int time,unsigned int color,int radius)184 void CG_TestLine( vec3_t start, vec3_t end, int time, unsigned int color, int radius) {
185 	localEntity_t	*le;
186 	refEntity_t		*re;
187 
188 	le = CG_AllocLocalEntity();
189 	le->leType = LE_LINE;
190 	le->startTime = cg.time;
191 	le->endTime = cg.time + time;
192 	le->lifeRate = 1.0 / ( le->endTime - le->startTime );
193 
194 	re = &le->refEntity;
195 	VectorCopy( start, re->origin );
196 	VectorCopy( end, re->oldorigin);
197 	re->shaderTime = cg.time / 1000.0f;
198 
199 	re->reType = RT_LINE;
200 	re->radius = 0.5*radius;
201 	re->customShader = cgs.media.whiteShader; //trap->R_RegisterShaderNoMip("textures/colombia/canvas_doublesided");
202 
203 	re->shaderTexCoord[0] = re->shaderTexCoord[1] = 1.0f;
204 
205 	if (color==0)
206 	{
207 		re->shaderRGBA[0] = re->shaderRGBA[1] = re->shaderRGBA[2] = re->shaderRGBA[3] = 0xff;
208 	}
209 	else
210 	{
211 		color = CGDEBUG_SaberColor( color );
212 		re->shaderRGBA[0] = color & 0xff;
213 		color >>= 8;
214 		re->shaderRGBA[1] = color & 0xff;
215 		color >>= 8;
216 		re->shaderRGBA[2] = color & 0xff;
217 //		color >>= 8;
218 //		re->shaderRGBA[3] = color & 0xff;
219 		re->shaderRGBA[3] = 0xff;
220 	}
221 
222 	le->color[3] = 1.0;
223 
224 	//re->renderfx |= RF_DEPTHHACK;
225 }
226 
227 //----------------------------
228 //
229 // Breaking Glass Technology
230 //
231 //----------------------------
232 
233 // Since we have shared verts when we tesselate the glass sheet, it helps to have a
234 //	random offset table set up up front.
235 
236 static float offX[20][20],
237 			offZ[20][20];
238 
239 #define	FX_ALPHA_NONLINEAR	0x00000004
240 #define FX_APPLY_PHYSICS	0x02000000
241 #define FX_USE_ALPHA		0x08000000
242 
CG_DoGlassQuad(vec3_t p[4],vec2_t uv[4],qboolean stick,int time,vec3_t dmgDir)243 static void CG_DoGlassQuad( vec3_t p[4], vec2_t uv[4], qboolean stick, int time, vec3_t dmgDir )
244 {
245 	float	bounce;
246 	vec3_t	rotDelta;
247 	vec3_t	vel, accel;
248 	vec3_t	rgb1;
249 	addpolyArgStruct_t apArgs;
250 	int		i, i_2;
251 
252 	VectorSet( vel, Q_flrand(-12.0f, 12.0f), Q_flrand(-12.0f, 12.0f), -1 );
253 
254 	if ( !stick )
255 	{
256 		// We aren't a motion delayed chunk, so let us move quickly
257 		VectorMA( vel, 0.3f, dmgDir, vel );
258 	}
259 
260 	// Set up acceleration due to gravity, 800 is standard QuakeIII gravity, so let's use something close
261 	VectorSet( accel, 0.0f, 0.0f, -(600.0f + Q_flrand(0.0f, 1.0f) * 100.0f ) );
262 
263 	// We are using an additive shader, so let's set the RGB low so we look more like transparent glass
264 //	VectorSet( rgb1, 0.1f, 0.1f, 0.1f );
265 	VectorSet( rgb1, 1.0f, 1.0f, 1.0f );
266 
267 	// Being glass, we don't want to bounce much
268 	bounce = Q_flrand(0.0f, 1.0f) * 0.2f + 0.15f;
269 
270 	// Set up our random rotate, we only do PITCH and YAW, not ROLL.  This is something like degrees per second
271 	VectorSet( rotDelta, Q_flrand(-40.0f, 40.0f), Q_flrand(-40.0f, 40.0f), 0.0f );
272 
273 	//In an ideal world, this might actually work.
274 	/*
275 	CPoly *pol = FX_AddPoly(p, uv, 4,			// verts, ST, vertCount
276 			vel, accel,				// motion
277 			0.15f, 0.0f, 85.0f,		// alpha start, alpha end, alpha parm ( begin alpha fade when 85% of life is complete )
278 			rgb1, rgb1, 0.0f,		// rgb start, rgb end, rgb parm ( not used )
279 			rotDelta, bounce, time,	// rotation amount, bounce, and time to delay motion for ( zero if no delay );
280 			6000,					// life
281 			cgi_R_RegisterShader( "gfx/misc/test_crackle" ),
282 			FX_APPLY_PHYSICS | FX_ALPHA_NONLINEAR | FX_USE_ALPHA );
283 
284 	if ( Q_flrand(0.0f, 1.0f) > 0.95f && pol )
285 	{
286 		pol->AddFlags( FX_IMPACT_RUNS_FX | FX_KILL_ON_IMPACT );
287 		pol->SetImpactFxID( theFxScheduler.RegisterEffect( "glass_impact" ));
288 	}
289 	*/
290 
291 	//rww - this is dirty.
292 
293 	i = 0;
294 	i_2 = 0;
295 
296 	while (i < 4)
297 	{
298 		while (i_2 < 3)
299 		{
300 			apArgs.p[i][i_2] = p[i][i_2];
301 
302 			i_2++;
303 		}
304 
305 		i_2 = 0;
306 		i++;
307 	}
308 
309 	i = 0;
310 	i_2 = 0;
311 
312 	while (i < 4)
313 	{
314 		while (i_2 < 2)
315 		{
316 			apArgs.ev[i][i_2] = uv[i][i_2];
317 
318 			i_2++;
319 		}
320 
321 		i_2 = 0;
322 		i++;
323 	}
324 
325 	apArgs.numVerts = 4;
326 	VectorCopy(vel, apArgs.vel);
327 	VectorCopy(accel, apArgs.accel);
328 
329 	apArgs.alpha1 = 0.15f;
330 	apArgs.alpha2 = 0.0f;
331 	apArgs.alphaParm = 85.0f;
332 
333 	VectorCopy(rgb1, apArgs.rgb1);
334 	VectorCopy(rgb1, apArgs.rgb2);
335 
336 	apArgs.rgbParm = 0.0f;
337 
338 	VectorCopy(rotDelta, apArgs.rotationDelta);
339 
340 	apArgs.bounce = bounce;
341 	apArgs.motionDelay = time;
342 	apArgs.killTime = 6000;
343 	apArgs.shader = cgs.media.glassShardShader;
344 	apArgs.flags = (FX_APPLY_PHYSICS | FX_ALPHA_NONLINEAR | FX_USE_ALPHA);
345 
346 	trap->FX_AddPoly(&apArgs);
347 }
348 
CG_CalcBiLerp(vec3_t verts[4],vec3_t subVerts[4],vec2_t uv[4])349 static void CG_CalcBiLerp( vec3_t verts[4], vec3_t subVerts[4], vec2_t uv[4] )
350 {
351 	vec3_t	temp;
352 
353 	// Nasty crap
354 	VectorScale( verts[0],	1.0f - uv[0][0],	subVerts[0] );
355 	VectorMA( subVerts[0],	uv[0][0],			verts[1], subVerts[0] );
356 	VectorScale( subVerts[0], 1.0f - uv[0][1],	temp );
357 	VectorScale( verts[3],	1.0f - uv[0][0],	subVerts[0] );
358 	VectorMA( subVerts[0],	uv[0][0],			verts[2], subVerts[0] );
359 	VectorMA( temp,			uv[0][1],			subVerts[0], subVerts[0] );
360 
361 	VectorScale( verts[0],	1.0f - uv[1][0],	subVerts[1] );
362 	VectorMA( subVerts[1],	uv[1][0],			verts[1], subVerts[1] );
363 	VectorScale( subVerts[1], 1.0f - uv[1][1],	temp );
364 	VectorScale( verts[3],	1.0f - uv[1][0],	subVerts[1] );
365 	VectorMA( subVerts[1],	uv[1][0],			verts[2], subVerts[1] );
366 	VectorMA( temp,			uv[1][1],			subVerts[1], subVerts[1] );
367 
368 	VectorScale( verts[0],	1.0f - uv[2][0],	subVerts[2] );
369 	VectorMA( subVerts[2],	uv[2][0],			verts[1], subVerts[2] );
370 	VectorScale( subVerts[2], 1.0f - uv[2][1],		temp );
371 	VectorScale( verts[3],	1.0f - uv[2][0],	subVerts[2] );
372 	VectorMA( subVerts[2],	uv[2][0],			verts[2], subVerts[2] );
373 	VectorMA( temp,			uv[2][1],			subVerts[2], subVerts[2] );
374 
375 	VectorScale( verts[0],	1.0f - uv[3][0],	subVerts[3] );
376 	VectorMA( subVerts[3],	uv[3][0],			verts[1], subVerts[3] );
377 	VectorScale( subVerts[3], 1.0f - uv[3][1],	temp );
378 	VectorScale( verts[3],	1.0f - uv[3][0],	subVerts[3] );
379 	VectorMA( subVerts[3],	uv[3][0],			verts[2], subVerts[3] );
380 	VectorMA( temp,			uv[3][1],			subVerts[3], subVerts[3] );
381 }
382 // bilinear
383 //f(p',q') = (1 - y) � {[(1 - x) � f(p,q)] + [x � f(p,q+1)]} + y � {[(1 - x) � f(p+1,q)] + [x � f(p+1,q+1)]}.
384 
385 
CG_CalcHeightWidth(vec3_t verts[4],float * height,float * width)386 static void CG_CalcHeightWidth( vec3_t verts[4], float *height, float *width )
387 {
388 	vec3_t	dir1, dir2, cross;
389 
390 	VectorSubtract( verts[3], verts[0], dir1 ); // v
391 	VectorSubtract( verts[1], verts[0], dir2 ); // p-a
392 	CrossProduct( dir1, dir2, cross );
393 	*width = VectorNormalize( cross ) / VectorNormalize( dir1 ); // v
394 	VectorSubtract( verts[2], verts[0], dir2 ); // p-a
395 	CrossProduct( dir1, dir2, cross );
396 	*width += VectorNormalize( cross ) / VectorNormalize( dir1 ); // v
397 	*width *= 0.5f;
398 
399 	VectorSubtract( verts[1], verts[0], dir1 ); // v
400 	VectorSubtract( verts[2], verts[0], dir2 ); // p-a
401 	CrossProduct( dir1, dir2, cross );
402 	*height = VectorNormalize( cross ) / VectorNormalize( dir1 ); // v
403 	VectorSubtract( verts[3], verts[0], dir2 ); // p-a
404 	CrossProduct( dir1, dir2, cross );
405 	*height += VectorNormalize( cross ) / VectorNormalize( dir1 ); // v
406 	*height *= 0.5f;
407 }
408 //Consider a line in 3D with position vector "a" and direction vector "v" and
409 // let "p" be the position vector of an arbitrary point in 3D
410 //dist = len( crossprod(p-a,v) ) / len(v);
411 
CG_InitGlass(void)412 void CG_InitGlass( void )
413 {
414 	int i, t;
415 
416 	// Build a table first, so that we can do a more unpredictable crack scheme
417 	//	do it once, up front to save a bit of time.
418 	for ( i = 0; i < 20; i++ )
419 	{
420 		for ( t = 0; t < 20; t++ )
421 		{
422 			offX[t][i] = Q_flrand(-1.0f, 1.0f) * 0.03f;
423 			offZ[i][t] = Q_flrand(-1.0f, 1.0f) * 0.03f;
424 		}
425 	}
426 }
427 
428 #define TIME_DECAY_SLOW		0.1f
429 #define TIME_DECAY_MED		0.04f
430 #define TIME_DECAY_FAST		0.009f
431 
CG_DoGlass(vec3_t verts[4],vec3_t normal,vec3_t dmgPt,vec3_t dmgDir,float dmgRadius,int maxShards)432 void CG_DoGlass( vec3_t verts[4], vec3_t normal, vec3_t dmgPt, vec3_t dmgDir, float dmgRadius, int maxShards )
433 {
434 	int			i, t;
435 	int			mxHeight, mxWidth;
436 	float		height, width;
437 	float		stepWidth, stepHeight;
438 	float		timeDecay;
439 	float		x, z;
440 	float		xx, zz;
441 	float		dif;
442 	int			time = 0;
443 	int			glassShards = 0;
444 	qboolean	stick = qtrue;
445 	vec3_t		subVerts[4];
446 	vec2_t		biPoints[4];
447 
448 	// To do a smarter tesselation, we should figure out the relative height and width of the brush face,
449 	//	then use this to pick a lod value from 1-3 in each axis.  This will give us 1-9 lod levels, which will
450 	//	hopefully be sufficient.
451 	CG_CalcHeightWidth( verts, &height, &width );
452 
453 	trap->S_StartSound( dmgPt, -1, CHAN_AUTO, trap->S_RegisterSound("sound/effects/glassbreak1.wav"));
454 
455 	// Pick "LOD" for height
456 	if ( height < 100 )
457 	{
458 		stepHeight = 0.2f;
459 		mxHeight = 5;
460 		timeDecay = TIME_DECAY_SLOW;
461 	}
462 	else if ( height > 220 )
463 	{
464 		stepHeight = 0.05f;
465 		mxHeight = 20;
466 		timeDecay = TIME_DECAY_FAST;
467 	}
468 	else
469 	{
470 		stepHeight = 0.1f;
471 		mxHeight = 10;
472 		timeDecay = TIME_DECAY_MED;
473 	}
474 
475 	// Pick "LOD" for width
476 	/*
477 	if ( width < 100 )
478 	{
479 		stepWidth = 0.2f;
480 		mxWidth = 5;
481 		timeDecay = ( timeDecay + TIME_DECAY_SLOW ) * 0.5f;
482 	}
483 	else if ( width > 220 )
484 	{
485 		stepWidth = 0.05f;
486 		mxWidth = 20;
487 		timeDecay = ( timeDecay + TIME_DECAY_FAST ) * 0.5f;
488 	}
489 	else
490 	{
491 		stepWidth = 0.1f;
492 		mxWidth = 10;
493 		timeDecay = ( timeDecay + TIME_DECAY_MED ) * 0.5f;
494 	}
495 	*/
496 
497 	//Attempt to scale the glass directly to the size of the window
498 
499 	stepWidth = (0.25f - (width*0.0002)); //(width*0.0005));
500 	mxWidth = width*0.2;
501 	timeDecay = ( timeDecay + TIME_DECAY_FAST ) * 0.5f;
502 
503 	if (stepWidth < 0.01f)
504 	{
505 		stepWidth = 0.01f;
506 	}
507 	if (mxWidth < 5)
508 	{
509 		mxWidth = 5;
510 	}
511 
512 	for ( z = 0.0f, i = 0; z < 1.0f; z += stepHeight, i++ )
513 	{
514 		for ( x = 0.0f, t = 0; x < 1.0f; x += stepWidth, t++ )
515 		{
516 			// This is nasty..
517 			if ( t > 0 && t < mxWidth )
518 			{
519 				xx = x - offX[i][t];
520 			}
521 			else
522 			{
523 				xx = x;
524 			}
525 
526 			if ( i > 0 && i < mxHeight )
527 			{
528 				zz = z - offZ[t][i];
529 			}
530 			else
531 			{
532 				zz = z;
533 			}
534 
535 			VectorSet2( biPoints[0], xx, zz );
536 
537 			if ( t + 1 > 0 && t + 1 < mxWidth )
538 			{
539 				xx = x - offX[i][t + 1];
540 			}
541 			else
542 			{
543 				xx = x;
544 			}
545 
546 			if ( i > 0 && i < mxHeight )
547 			{
548 				zz = z - offZ[t + 1][i];
549 			}
550 			else
551 			{
552 				zz = z;
553 			}
554 
555 			VectorSet2( biPoints[1], xx + stepWidth, zz );
556 
557 			if ( t + 1 > 0 && t + 1 < mxWidth )
558 			{
559 				xx = x - offX[i + 1][t + 1];
560 			}
561 			else
562 			{
563 				xx = x;
564 			}
565 
566 			if ( i + 1 > 0 && i + 1 < mxHeight )
567 			{
568 				zz = z - offZ[t + 1][i + 1];
569 			}
570 			else
571 			{
572 				zz = z;
573 			}
574 
575 			VectorSet2( biPoints[2], xx + stepWidth, zz + stepHeight);
576 
577 			if ( t > 0 && t < mxWidth )
578 			{
579 				xx = x - offX[i + 1][t];
580 			}
581 			else
582 			{
583 				xx = x;
584 			}
585 
586 			if ( i + 1 > 0 && i + 1 < mxHeight )
587 			{
588 				zz = z - offZ[t][i + 1];
589 			}
590 			else
591 			{
592 				zz = z;
593 			}
594 
595 			VectorSet2( biPoints[3], xx, zz + stepHeight );
596 
597 			CG_CalcBiLerp( verts, subVerts, biPoints );
598 
599 			dif = DistanceSquared( subVerts[0], dmgPt ) * timeDecay - Q_flrand(0.0f, 1.0f) * 32;
600 
601 			// If we decrease dif, we are increasing the impact area, making it more likely to blow out large holes
602 			dif -= dmgRadius * dmgRadius;
603 
604 			if ( dif > 1 )
605 			{
606 				stick = qtrue;
607 				time = dif + Q_flrand(0.0f, 1.0f) * 200;
608 			}
609 			else
610 			{
611 				stick = qfalse;
612 				time = 0;
613 			}
614 
615 			CG_DoGlassQuad( subVerts, biPoints, stick, time, dmgDir );
616 			glassShards++;
617 
618 			if (maxShards && glassShards >= maxShards)
619 			{
620 				return;
621 			}
622 		}
623 	}
624 }
625 
626 /*
627 ==================
628 CG_GlassShatter
629 Break glass with fancy method
630 ==================
631 */
CG_GlassShatter(int entnum,vec3_t dmgPt,vec3_t dmgDir,float dmgRadius,int maxShards)632 void CG_GlassShatter(int entnum, vec3_t dmgPt, vec3_t dmgDir, float dmgRadius, int maxShards)
633 {
634 	vec3_t		verts[4], normal;
635 
636 	if (cgs.inlineDrawModel[cg_entities[entnum].currentState.modelindex])
637 	{
638 		trap->R_GetBModelVerts(cgs.inlineDrawModel[cg_entities[entnum].currentState.modelindex], verts, normal);
639 		CG_DoGlass(verts, normal, dmgPt, dmgDir, dmgRadius, maxShards);
640 	}
641 	//otherwise something awful has happened.
642 }
643 
644 //==========================================================
645 //SP-style chunks
646 //==========================================================
647 
648 /*
649 -------------------------
650 CG_ExplosionEffects
651 
652 Used to find the player and shake the camera if close enough
653 intensity ranges from 1 (minor tremble) to 16 (major quake)
654 -------------------------
655 */
656 
CG_ExplosionEffects(vec3_t origin,float intensity,int radius,int time)657 void CG_ExplosionEffects( vec3_t origin, float intensity, int radius, int time )
658 {
659 	//FIXME: When exactly is the vieworg calculated in relation to the rest of the frame?s
660 
661 	vec3_t	dir;
662 	float	dist, intensityScale;
663 	float	realIntensity;
664 
665 	VectorSubtract( cg.refdef.vieworg, origin, dir );
666 	dist = VectorNormalize( dir );
667 
668 	//Use the dir to add kick to the explosion
669 
670 	if ( dist > radius )
671 		return;
672 
673 	intensityScale = 1 - ( dist / (float) radius );
674 	realIntensity = intensity * intensityScale;
675 
676 	CGCam_Shake( realIntensity, time );
677 }
678 
679 /*
680 -------------------------
681 CG_MiscModelExplosion
682 
683 Adds an explosion to a misc model breakables
684 -------------------------
685 */
686 
CG_MiscModelExplosion(vec3_t mins,vec3_t maxs,int size,material_t chunkType)687 void CG_MiscModelExplosion( vec3_t mins, vec3_t maxs, int size, material_t chunkType )
688 {
689 	int		ct = 13;
690 	float	r;
691 	vec3_t	org, mid, dir;
692 	char	*effect = NULL, *effect2 = NULL;
693 	int		eID1, eID2 = 0;
694 	int		i;
695 
696 	VectorAdd( mins, maxs, mid );
697 	VectorScale( mid, 0.5f, mid );
698 
699 	switch( chunkType )
700 	{
701 	default:
702 		break;
703 	case MAT_GLASS:
704 		effect = "chunks/glassbreak";
705 		ct = 5;
706 		break;
707 	case MAT_GLASS_METAL:
708 		effect = "chunks/glassbreak";
709 		effect2 = "chunks/metalexplode";
710 		ct = 5;
711 		break;
712 	case MAT_ELECTRICAL:
713 	case MAT_ELEC_METAL:
714 		effect = "chunks/sparkexplode";
715 		ct = 5;
716 		break;
717 	case MAT_METAL:
718 	case MAT_METAL2:
719 	case MAT_METAL3:
720 	case MAT_CRATE1:
721 	case MAT_CRATE2:
722 		effect = "chunks/metalexplode";
723 		ct = 2;
724 		break;
725 	case MAT_GRATE1:
726 		effect = "chunks/grateexplode";
727 		ct = 8;
728 		break;
729 	case MAT_ROPE:
730 		ct = 20;
731 		effect = "chunks/ropebreak";
732 		break;
733 	case MAT_WHITE_METAL: //not sure what this crap is really supposed to be..
734 	case MAT_DRK_STONE:
735 	case MAT_LT_STONE:
736 	case MAT_GREY_STONE:
737 	case MAT_SNOWY_ROCK:
738 		switch( size )
739 		{
740 		case 2:
741 			effect = "chunks/rockbreaklg";
742 			break;
743 		case 1:
744 		default:
745 			effect = "chunks/rockbreakmed";
746 			break;
747 		}
748 	}
749 
750 	if ( !effect )
751 	{
752 		return;
753 	}
754 
755 	ct += 7 * size;
756 
757 	// FIXME: real precache .. VERify that these need to be here...don't think they would because the effects should be registered in g_breakable
758 	//rww - No they don't.. indexed effects gameside get precached on load clientside, as server objects are setup before client asset load time.
759 	//However, we need to index them, so..
760 	eID1 = trap->FX_RegisterEffect( effect );
761 
762 	if ( effect2 && effect2[0] )
763 	{
764 		// FIXME: real precache
765 		eID2 = trap->FX_RegisterEffect( effect2 );
766 	}
767 
768 	// spawn chunk roughly in the bbox of the thing..
769 	for ( i = 0; i < ct; i++ )
770 	{
771 		int j;
772 		for( j = 0; j < 3; j++ )
773 		{
774 			r = Q_flrand(0.0f, 1.0f) * 0.8f + 0.1f;
775 			org[j] = ( r * mins[j] + ( 1 - r ) * maxs[j] );
776 		}
777 
778 		// shoot effect away from center
779 		VectorSubtract( org, mid, dir );
780 		VectorNormalize( dir );
781 
782 		if ( effect2 && effect2[0] && ( rand() & 1 ))
783 		{
784 			trap->FX_PlayEffectID( eID2, org, dir, -1, -1, qfalse );
785 		}
786 		else
787 		{
788 			trap->FX_PlayEffectID( eID1, org, dir, -1, -1, qfalse );
789 		}
790 	}
791 }
792 
793 /*
794 -------------------------
795 CG_Chunks
796 
797 Fun chunk spewer
798 -------------------------
799 */
800 
CG_Chunks(int owner,vec3_t origin,const vec3_t normal,const vec3_t mins,const vec3_t maxs,float speed,int numChunks,material_t chunkType,int customChunk,float baseScale)801 void CG_Chunks( int owner, vec3_t origin, const vec3_t normal, const vec3_t mins, const vec3_t maxs,
802 						float speed, int numChunks, material_t chunkType, int customChunk, float baseScale )
803 {
804 	localEntity_t	*le;
805 	refEntity_t		*re;
806 	vec3_t			dir;
807 	int				i, j, k;
808 	int				chunkModel = 0;
809 	leBounceSoundType_t	bounce = LEBS_NONE;
810 	float			r, speedMod = 1.0f;
811 	qboolean		chunk = qfalse;
812 
813 	if ( chunkType == MAT_NONE )
814 	{
815 		// Well, we should do nothing
816 		return;
817 	}
818 
819 	// Set up our chunk sound info...breaking sounds are done here so they are done once on breaking..some return instantly because the chunks are done with effects instead of models
820 	switch( chunkType )
821 	{
822 	default:
823 		break;
824 	case MAT_GLASS:
825 		trap->S_StartSound( NULL, owner, CHAN_BODY, cgs.media.glassChunkSound );
826 		return;
827 		break;
828 	case MAT_GRATE1:
829 		trap->S_StartSound( NULL, owner, CHAN_BODY, cgs.media.grateSound );
830 		return;
831 		break;
832 	case MAT_ELECTRICAL:// (sparks)
833 		trap->S_StartSound( NULL, owner, CHAN_BODY, trap->S_RegisterSound (va("sound/ambience/spark%d.wav", Q_irand(1, 6))) );
834 		return;
835 		break;
836 	case MAT_DRK_STONE:
837 	case MAT_LT_STONE:
838 	case MAT_GREY_STONE:
839 	case MAT_WHITE_METAL:  // not quite sure what this stuff is supposed to be...it's for Stu
840 	case MAT_SNOWY_ROCK:
841 		trap->S_StartSound( NULL, owner, CHAN_BODY, cgs.media.rockBreakSound );
842 		bounce = LEBS_ROCK;
843 		speedMod = 0.5f; // rock blows up less
844 		break;
845 	case MAT_GLASS_METAL:
846 		trap->S_StartSound( NULL, owner, CHAN_BODY, cgs.media.glassChunkSound ); // FIXME: should probably have a custom sound
847 		bounce = LEBS_METAL;
848 		break;
849 	case MAT_CRATE1:
850 	case MAT_CRATE2:
851 		trap->S_StartSound( NULL, owner, CHAN_BODY, cgs.media.crateBreakSound[Q_irand(0,1)] );
852 		break;
853 	case MAT_METAL:
854 	case MAT_METAL2:
855 	case MAT_METAL3:
856 	case MAT_ELEC_METAL:// FIXME: maybe have its own sound?
857 		trap->S_StartSound( NULL, owner, CHAN_BODY, cgs.media.chunkSound );
858 		bounce = LEBS_METAL;
859 		speedMod = 0.8f; // metal blows up a bit more
860 		break;
861 	case MAT_ROPE:
862 //		trap->S_StartSound( NULL, owner, CHAN_BODY, cgi_S_RegisterSound( "" ));  FIXME:  needs a sound
863 		return;
864 		break;
865 	}
866 
867 	if ( baseScale <= 0.0f )
868 	{
869 		baseScale = 1.0f;
870 	}
871 
872 	// Chunks
873 	for( i = 0; i < numChunks; i++ )
874 	{
875 		if ( customChunk > 0 )
876 		{
877 			// Try to use a custom chunk.
878 			if ( cgs.gameModels[customChunk] )
879 			{
880 				chunk = qtrue;
881 				chunkModel = cgs.gameModels[customChunk];
882 			}
883 		}
884 
885 		if ( !chunk )
886 		{
887 			// No custom chunk.  Pick a random chunk type at run-time so we don't get the same chunks
888 			switch( chunkType )
889 			{
890 			default:
891 				break;
892 			case MAT_METAL2: //bluegrey
893 				chunkModel = cgs.media.chunkModels[CHUNK_METAL2][Q_irand(0, 3)];
894 				break;
895 			case MAT_GREY_STONE://gray
896 				chunkModel = cgs.media.chunkModels[CHUNK_ROCK1][Q_irand(0, 3)];
897 				break;
898 			case MAT_LT_STONE: //tan
899 				chunkModel = cgs.media.chunkModels[CHUNK_ROCK2][Q_irand(0, 3)];
900 				break;
901 			case MAT_DRK_STONE://brown
902 				chunkModel = cgs.media.chunkModels[CHUNK_ROCK3][Q_irand(0, 3)];
903 				break;
904 			case MAT_SNOWY_ROCK://gray & brown
905 				if ( Q_irand( 0, 1 ) )
906 				{
907 					chunkModel = cgs.media.chunkModels[CHUNK_ROCK1][Q_irand(0, 3)];
908 				}
909 				else
910 				{
911 					chunkModel = cgs.media.chunkModels[CHUNK_ROCK3][Q_irand(0, 3)];
912 				}
913 				break;
914 			case MAT_WHITE_METAL:
915 				chunkModel = cgs.media.chunkModels[CHUNK_WHITE_METAL][Q_irand(0, 3)];
916 				break;
917 			case MAT_CRATE1://yellow multi-colored crate chunks
918 				chunkModel = cgs.media.chunkModels[CHUNK_CRATE1][Q_irand(0, 3)];
919 				break;
920 			case MAT_CRATE2://red multi-colored crate chunks
921 				chunkModel = cgs.media.chunkModels[CHUNK_CRATE2][Q_irand(0, 3)];
922 				break;
923 			case MAT_ELEC_METAL:
924 			case MAT_GLASS_METAL:
925 			case MAT_METAL://grey
926 				chunkModel = cgs.media.chunkModels[CHUNK_METAL1][Q_irand(0, 3)];
927 				break;
928 			case MAT_METAL3:
929 				if ( rand() & 1 )
930 				{
931 					chunkModel = cgs.media.chunkModels[CHUNK_METAL1][Q_irand(0, 3)];
932 				}
933 				else
934 				{
935 					chunkModel = cgs.media.chunkModels[CHUNK_METAL2][Q_irand(0, 3)];
936 				}
937 				break;
938 			}
939 		}
940 
941 		// It wouldn't look good to throw a bunch of RGB axis models...so make sure we have something to work with.
942 		if ( chunkModel )
943 		{
944 			le = CG_AllocLocalEntity();
945 			re = &le->refEntity;
946 
947 			re->hModel = chunkModel;
948 			le->leType = LE_FRAGMENT;
949 			le->endTime = cg.time + 1300 + Q_flrand(0.0f, 1.0f) * 900;
950 
951 			// spawn chunk roughly in the bbox of the thing...bias towards center in case thing blowing up doesn't complete fill its bbox.
952 			for( j = 0; j < 3; j++ )
953 			{
954 				r = Q_flrand(0.0f, 1.0f) * 0.8f + 0.1f;
955 				re->origin[j] = ( r * mins[j] + ( 1 - r ) * maxs[j] );
956 			}
957 			VectorCopy( re->origin, le->pos.trBase );
958 
959 			// Move out from center of thing, otherwise you can end up things moving across the brush in an undesirable direction.  Visually looks wrong
960 			VectorSubtract( re->origin, origin, dir );
961 			VectorNormalize( dir );
962 			VectorScale( dir, flrand( speed * 0.5f, speed * 1.25f ) * speedMod, le->pos.trDelta );
963 
964 			// Angular Velocity
965 			VectorSet( le->angles.trBase, Q_flrand(0.0f, 1.0f) * 360, Q_flrand(0.0f, 1.0f) * 360, Q_flrand(0.0f, 1.0f) * 360 );
966 
967 			le->angles.trDelta[0] = Q_flrand(-1.0f, 1.0f);
968 			le->angles.trDelta[1] = Q_flrand(-1.0f, 1.0f);
969 			le->angles.trDelta[2] = 0; // don't do roll
970 
971 			VectorScale( le->angles.trDelta, Q_flrand(0.0f, 1.0f) * 600.0f + 200.0f, le->angles.trDelta );
972 
973 			le->pos.trType = TR_GRAVITY;
974 			le->angles.trType = TR_LINEAR;
975 			le->pos.trTime = le->angles.trTime = cg.time;
976 			le->bounceFactor = 0.2f + Q_flrand(0.0f, 1.0f) * 0.2f;
977 			le->leFlags |= LEF_TUMBLE;
978 			//le->ownerGentNum = owner;
979 			le->leBounceSoundType = bounce;
980 
981 			// Make sure that we have the desired start size set
982 			le->radius = flrand( baseScale * 0.75f, baseScale * 1.25f );
983 			re->nonNormalizedAxes = qtrue;
984 			AxisCopy( axisDefault, re->axis ); // could do an angles to axis, but this is cheaper and works ok
985 			for( k = 0; k < 3; k++ )
986 			{
987 				re->modelScale[k] = le->radius;
988 			}
989 			ScaleModelAxis(re);
990 			/*
991 			for( k = 0; k < 3; k++ )
992 			{
993 				VectorScale( re->axis[k], le->radius, re->axis[k] );
994 			}
995 			*/
996 		}
997 	}
998 }
999 
1000 /*
1001 ==================
1002 CG_ScorePlum
1003 ==================
1004 */
CG_ScorePlum(int client,vec3_t org,int score)1005 void CG_ScorePlum( int client, vec3_t org, int score ) {
1006 	localEntity_t	*le;
1007 	refEntity_t		*re;
1008 	vec3_t			angles;
1009 	static vec3_t lastPos;
1010 
1011 	// only visualize for the client that scored
1012 	if (client != cg.predictedPlayerState.clientNum || cg_scorePlums.integer == 0) {
1013 		return;
1014 	}
1015 
1016 	le = CG_AllocLocalEntity();
1017 	le->leFlags = 0;
1018 	le->leType = LE_SCOREPLUM;
1019 	le->startTime = cg.time;
1020 	le->endTime = cg.time + 4000;
1021 	le->lifeRate = 1.0 / ( le->endTime - le->startTime );
1022 
1023 
1024 	le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0;
1025 	le->radius = score;
1026 
1027 	VectorCopy( org, le->pos.trBase );
1028 	if (org[2] >= lastPos[2] - 20 && org[2] <= lastPos[2] + 20) {
1029 		le->pos.trBase[2] -= 20;
1030 	}
1031 
1032 	//trap->Print( "Plum origin %i %i %i -- %i\n", (int)org[0], (int)org[1], (int)org[2], (int)Distance(org, lastPos));
1033 	VectorCopy(org, lastPos);
1034 
1035 
1036 	re = &le->refEntity;
1037 
1038 	re->reType = RT_SPRITE;
1039 	re->radius = 16;
1040 
1041 	VectorClear(angles);
1042 	AnglesToAxis( angles, re->axis );
1043 }
1044 
1045 /*
1046 ====================
1047 CG_MakeExplosion
1048 ====================
1049 */
CG_MakeExplosion(vec3_t origin,vec3_t dir,qhandle_t hModel,int numFrames,qhandle_t shader,int msec,qboolean isSprite,float scale,int flags)1050 localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir,
1051 								qhandle_t hModel, int numFrames, qhandle_t shader,
1052 								int msec, qboolean isSprite, float scale, int flags )
1053 {
1054 	float			ang = 0;
1055 	localEntity_t	*ex;
1056 	int				offset;
1057 	vec3_t			tmpVec, newOrigin;
1058 
1059 	if ( msec <= 0 ) {
1060 		trap->Error( ERR_DROP, "CG_MakeExplosion: msec = %i", msec );
1061 	}
1062 
1063 	// skew the time a bit so they aren't all in sync
1064 	offset = rand() & 63;
1065 
1066 	ex = CG_AllocLocalEntity();
1067 	if ( isSprite ) {
1068 		ex->leType = LE_SPRITE_EXPLOSION;
1069 		ex->refEntity.rotation = rand() % 360;
1070 		ex->radius = scale;
1071 		VectorScale( dir, 16, tmpVec );
1072 		VectorAdd( tmpVec, origin, newOrigin );
1073 	} else {
1074 		ex->leType = LE_EXPLOSION;
1075 		VectorCopy( origin, newOrigin );
1076 
1077 		// set axis with random rotate when necessary
1078 		if ( !dir )
1079 		{
1080 			AxisClear( ex->refEntity.axis );
1081 		}
1082 		else
1083 		{
1084 			if ( !(flags & LEF_NO_RANDOM_ROTATE) )
1085 				ang = rand() % 360;
1086 			VectorCopy( dir, ex->refEntity.axis[0] );
1087 			RotateAroundDirection( ex->refEntity.axis, ang );
1088 		}
1089 	}
1090 
1091 	ex->startTime = cg.time - offset;
1092 	ex->endTime = ex->startTime + msec;
1093 
1094 	// bias the time so all shader effects start correctly
1095 	ex->refEntity.shaderTime = ex->startTime / 1000.0f;
1096 
1097 	ex->refEntity.hModel = hModel;
1098 	ex->refEntity.customShader = shader;
1099 	ex->lifeRate = (float)numFrames / msec;
1100 	ex->leFlags = flags;
1101 
1102 	//Scale the explosion
1103 	if (scale != 1) {
1104 		ex->refEntity.nonNormalizedAxes = qtrue;
1105 
1106 		VectorScale( ex->refEntity.axis[0], scale, ex->refEntity.axis[0] );
1107 		VectorScale( ex->refEntity.axis[1], scale, ex->refEntity.axis[1] );
1108 		VectorScale( ex->refEntity.axis[2], scale, ex->refEntity.axis[2] );
1109 	}
1110 	// set origin
1111 	VectorCopy ( newOrigin, ex->refEntity.origin);
1112 	VectorCopy ( newOrigin, ex->refEntity.oldorigin );
1113 
1114 	ex->color[0] = ex->color[1] = ex->color[2] = 1.0;
1115 
1116 	return ex;
1117 }
1118 
1119 
1120 /*
1121 -------------------------
1122 CG_SurfaceExplosion
1123 
1124 Adds an explosion to a surface
1125 -------------------------
1126 */
1127 
1128 #define NUM_SPARKS		12
1129 #define NUM_PUFFS		1
1130 #define NUM_EXPLOSIONS	4
1131 
CG_SurfaceExplosion(vec3_t origin,vec3_t normal,float radius,float shake_speed,qboolean smoke)1132 void CG_SurfaceExplosion( vec3_t origin, vec3_t normal, float radius, float shake_speed, qboolean smoke )
1133 {
1134 	localEntity_t	*le;
1135 	//FXTrail			*particle;
1136 	vec3_t			direction, new_org;
1137 	vec3_t			velocity		= { 0, 0, 0 };
1138 	vec3_t			temp_org, temp_vel;
1139 //	float			scale;
1140 	int				i, numSparks;
1141 
1142 	//Sparks
1143 	numSparks = 16 + (Q_flrand(0.0f, 1.0f) * 16.0f);
1144 
1145 	for ( i = 0; i < numSparks; i++ )
1146 	{
1147 	//	scale = 0.25f + (Q_flrand(0.0f, 1.0f) * 2.0f);
1148 
1149 /*		particle = FX_AddTrail( origin,
1150 								NULL,
1151 								NULL,
1152 								32.0f,
1153 								-64.0f,
1154 								scale,
1155 								-scale,
1156 								1.0f,
1157 								0.0f,
1158 								0.25f,
1159 								4000.0f,
1160 								cgs.media.sparkShader,
1161 								rand() & FXF_BOUNCE);
1162 		if ( particle == NULL )
1163 			return;
1164 
1165 		FXE_Spray( normal, 500, 150, 1.0f, 768 + (rand() & 255), (FXPrimitive *) particle );*/
1166 	}
1167 
1168 	//Smoke
1169 	//Move this out a little from the impact surface
1170 	VectorMA( origin, 4, normal, new_org );
1171 	VectorSet( velocity, 0.0f, 0.0f, 16.0f );
1172 
1173 	for ( i = 0; i < 4; i++ )
1174 	{
1175 		VectorSet( temp_org, new_org[0] + (Q_flrand(-1.0f, 1.0f) * 16.0f), new_org[1] + (Q_flrand(-1.0f, 1.0f) * 16.0f), new_org[2] + (Q_flrand(0.0f, 1.0f) * 4.0f) );
1176 		VectorSet( temp_vel, velocity[0] + (Q_flrand(-1.0f, 1.0f) * 8.0f), velocity[1] + (Q_flrand(-1.0f, 1.0f) * 8.0f), velocity[2] + (Q_flrand(-1.0f, 1.0f) * 8.0f) );
1177 
1178 /*		FX_AddSprite(	temp_org,
1179 						temp_vel,
1180 						NULL,
1181 						64.0f + (Q_flrand(0.0f, 1.0f) * 32.0f),
1182 						16.0f,
1183 						1.0f,
1184 						0.0f,
1185 						20.0f + (Q_flrand(-1.0f, 1.0f) * 90.0f),
1186 						0.5f,
1187 						1500.0f,
1188 						cgs.media.smokeShader, FXF_USE_ALPHA_CHAN );*/
1189 	}
1190 
1191 	//Core of the explosion
1192 
1193 	//Orient the explosions to face the camera
1194 	VectorSubtract( cg.refdef.vieworg, origin, direction );
1195 	VectorNormalize( direction );
1196 
1197 	//Tag the last one with a light
1198 	le = CG_MakeExplosion( origin, direction, cgs.media.explosionModel, 6, cgs.media.surfaceExplosionShader, 500, qfalse, radius * 0.02f + (Q_flrand(0.0f, 1.0f) * 0.3f), 0);
1199 	le->light = 150;
1200 	VectorSet( le->lightColor, 0.9f, 0.8f, 0.5f );
1201 
1202 	for ( i = 0; i < NUM_EXPLOSIONS-1; i ++)
1203 	{
1204 		VectorSet( new_org, (origin[0] + (16 + (Q_flrand(-1.0f, 1.0f) * 8))*Q_flrand(-1.0f, 1.0f)), (origin[1] + (16 + (Q_flrand(-1.0f, 1.0f) * 8))*Q_flrand(-1.0f, 1.0f)), (origin[2] + (16 + (Q_flrand(-1.0f, 1.0f) * 8))*Q_flrand(-1.0f, 1.0f)) );
1205 		le = CG_MakeExplosion( new_org, direction, cgs.media.explosionModel, 6, cgs.media.surfaceExplosionShader, 300 + (rand() & 99), qfalse, radius * 0.05f + (Q_flrand(-1.0f, 1.0f) *0.3f), 0);
1206 	}
1207 
1208 	//Shake the camera
1209 	CG_ExplosionEffects( origin, shake_speed, 350, 750 );
1210 
1211 	// The level designers wanted to be able to turn the smoke spawners off.  The rationale is that they
1212 	//	want to blow up catwalks and such that fall down...when that happens, it shouldn't really leave a mark
1213 	//	and a smoke spewer at the explosion point...
1214 	if ( smoke )
1215 	{
1216 		VectorMA( origin, -8, normal, temp_org );
1217 //		FX_AddSpawner( temp_org, normal, NULL, NULL, 100, Q_flrand(0.0f, 1.0f)*25.0f, 5000.0f, (void *) CG_SmokeSpawn );
1218 
1219 		//Impact mark
1220 		//FIXME: Replace mark
1221 		//CG_ImpactMark( cgs.media.burnMarkShader, origin, normal, Q_flrand(0.0f, 1.0f)*360, 1,1,1,1, qfalse, 8, qfalse );
1222 	}
1223 }
1224 
1225 /*
1226 ==================
1227 CG_LaunchGib
1228 ==================
1229 */
CG_LaunchGib(vec3_t origin,vec3_t velocity,qhandle_t hModel)1230 void CG_LaunchGib( vec3_t origin, vec3_t velocity, qhandle_t hModel ) {
1231 	localEntity_t	*le;
1232 	refEntity_t		*re;
1233 
1234 	le = CG_AllocLocalEntity();
1235 	re = &le->refEntity;
1236 
1237 	le->leType = LE_FRAGMENT;
1238 	le->startTime = cg.time;
1239 	le->endTime = le->startTime + 5000 + Q_flrand(0.0f, 1.0f) * 3000;
1240 
1241 	VectorCopy( origin, re->origin );
1242 	AxisCopy( axisDefault, re->axis );
1243 	re->hModel = hModel;
1244 
1245 	le->pos.trType = TR_GRAVITY;
1246 	VectorCopy( origin, le->pos.trBase );
1247 	VectorCopy( velocity, le->pos.trDelta );
1248 	le->pos.trTime = cg.time;
1249 
1250 	le->bounceFactor = 0.6f;
1251 
1252 	le->leBounceSoundType = LEBS_BLOOD;
1253 	le->leMarkType = LEMT_BLOOD;
1254 }
1255