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