1 /*
2 ===========================================================================
3
4 Return to Castle Wolfenstein single player GPL Source Code
5 Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
6
7 This file is part of the Return to Castle Wolfenstein single player GPL Source Code (RTCW SP Source Code).
8
9 RTCW SP Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 RTCW SP Source Code 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 RTCW SP Source Code. If not, see <http://www.gnu.org/licenses/>.
21
22 In addition, the RTCW SP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW SP Source Code. If not, please request a copy in writing from id Software at the address below.
23
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25
26 ===========================================================================
27 */
28
29
30 // cg_localents.c -- every frame, generate renderer commands for locally
31 // processed entities, like smoke puffs, gibs, shells, etc.
32
33 #include "cg_local.h"
34
35 // Ridah, increased this
36 //#define MAX_LOCAL_ENTITIES 512
37 #define MAX_LOCAL_ENTITIES 768 // renderer can only handle 1024 entities max, so we should avoid
38 // overwriting game entities
39 // done.
40
41 localEntity_t cg_localEntities[MAX_LOCAL_ENTITIES];
42 localEntity_t cg_activeLocalEntities; // double linked list
43 localEntity_t *cg_freeLocalEntities; // single linked list
44
45 // Ridah, debugging
46 int localEntCount = 0;
47
48 /*
49 ===================
50 CG_InitLocalEntities
51
52 This is called at startup and for tournement restarts
53 ===================
54 */
CG_InitLocalEntities(void)55 void CG_InitLocalEntities( void ) {
56 int i;
57
58 memset( cg_localEntities, 0, sizeof( cg_localEntities ) );
59 cg_activeLocalEntities.next = &cg_activeLocalEntities;
60 cg_activeLocalEntities.prev = &cg_activeLocalEntities;
61 cg_freeLocalEntities = cg_localEntities;
62 for ( i = 0 ; i < MAX_LOCAL_ENTITIES - 1 ; i++ ) {
63 cg_localEntities[i].next = &cg_localEntities[i + 1];
64 }
65
66 // Ridah, debugging
67 localEntCount = 0;
68 }
69
70
71 /*
72 ==================
73 CG_FreeLocalEntity
74 ==================
75 */
CG_FreeLocalEntity(localEntity_t * le)76 void CG_FreeLocalEntity( localEntity_t *le ) {
77 if ( !le->prev ) {
78 CG_Error( "CG_FreeLocalEntity: not active" );
79 }
80
81 // Ridah, debugging
82 localEntCount--;
83 // trap_Print( va("FreeLocalEntity: locelEntCount = %d\n", localEntCount) );
84 // done.
85
86 // remove from the doubly linked active list
87 le->prev->next = le->next;
88 le->next->prev = le->prev;
89
90 // the free list is only singly linked
91 le->next = cg_freeLocalEntities;
92 cg_freeLocalEntities = le;
93 }
94
95 /*
96 ===================
97 CG_AllocLocalEntity
98
99 Will always succeed, even if it requires freeing an old active entity
100 ===================
101 */
CG_AllocLocalEntity(void)102 localEntity_t *CG_AllocLocalEntity( void ) {
103 localEntity_t *le;
104
105 if ( !cg_freeLocalEntities ) {
106 // no free entities, so free the one at the end of the chain
107 // remove the oldest active entity
108 CG_FreeLocalEntity( cg_activeLocalEntities.prev );
109 }
110
111 // Ridah, debugging
112 localEntCount++;
113 // trap_Print( va("AllocLocalEntity: locelEntCount = %d\n", localEntCount) );
114 // done.
115
116 le = cg_freeLocalEntities;
117 cg_freeLocalEntities = cg_freeLocalEntities->next;
118
119 memset( le, 0, sizeof( *le ) );
120
121 // link into the active list
122 le->next = cg_activeLocalEntities.next;
123 le->prev = &cg_activeLocalEntities;
124 cg_activeLocalEntities.next->prev = le;
125 cg_activeLocalEntities.next = le;
126 return le;
127 }
128
129
130 /*
131 ====================================================================================
132
133 FRAGMENT PROCESSING
134
135 A fragment localentity interacts with the environment in some way (hitting walls),
136 or generates more localentities along a trail.
137
138 ====================================================================================
139 */
140
141 /*
142 ================
143 CG_BloodTrail
144
145 Leave expanding blood puffs behind gibs
146 ================
147 */
148 // use this to change between particle and trail code
149 //#define BLOOD_PARTICLE_TRAIL
CG_BloodTrail(localEntity_t * le)150 void CG_BloodTrail( localEntity_t *le ) {
151 int t;
152 int t2;
153 int step;
154 vec3_t newOrigin;
155
156 #ifndef BLOOD_PARTICLE_TRAIL
157 static vec3_t col = {1,1,1};
158 #endif
159
160 centity_t *cent;
161 cent = &cg_entities[le->ownerNum];
162
163 if ( !cg_blood.integer ) {
164 return;
165 }
166
167 // step = 150;
168 #ifdef BLOOD_PARTICLE_TRAIL
169 step = 10;
170 #else
171 // time it takes to move 3 units
172 step = ( 1000 * 3 ) / VectorLength( le->pos.trDelta );
173 #endif
174
175 if ( cent && cent->currentState.aiChar == AICHAR_ZOMBIE ) {
176 step = 30;
177 }
178
179 t = step * ( ( cg.time - cg.frametime + step ) / step );
180 t2 = step * ( cg.time / step );
181
182 for ( ; t <= t2; t += step ) {
183 BG_EvaluateTrajectory( &le->pos, t, newOrigin );
184
185 #ifdef BLOOD_PARTICLE_TRAIL
186 CG_Particle_Bleed( cgs.media.smokePuffShader, newOrigin, vec3_origin, 0, 500 + rand() % 200 );
187 #else
188
189
190 if ( cent && cent->currentState.aiChar == AICHAR_ZOMBIE ) {
191 CG_Particle_Bleed( cgs.media.smokePuffShader, newOrigin, vec3_origin, 1, 500 + rand() % 200 );
192 } else {
193 // Ridah, blood trail using trail code (should be faster since we don't have to spawn as many)
194 le->headJuncIndex = CG_AddTrailJunc( le->headJuncIndex,
195 cgs.media.bloodTrailShader,
196 t,
197 STYPE_STRETCH,
198 newOrigin,
199 180,
200 1.0, // start alpha
201 0.0, // end alpha
202 12.0,
203 12.0,
204 TJFL_NOCULL,
205 col, col,
206 0, 0 );
207 }
208 #endif
209
210 }
211 }
212
213
214 /*
215 ================
216 CG_FragmentBounceMark
217 ================
218 */
CG_FragmentBounceMark(localEntity_t * le,trace_t * trace)219 void CG_FragmentBounceMark( localEntity_t *le, trace_t *trace ) {
220 int radius;
221
222 if ( le->leMarkType == LEMT_BLOOD ) {
223 static int lastBloodMark;
224
225 // don't drop too many blood marks
226 if ( !( lastBloodMark > cg.time || lastBloodMark > cg.time - 100 ) ) {
227 radius = 16 + ( rand() & 31 );
228 CG_ImpactMark( cgs.media.bloodDotShaders[rand() % 5], trace->endpos, trace->plane.normal, random() * 360,
229 1,1,1,1, qtrue, radius, qfalse, cg_bloodTime.integer * 1000 );
230
231 lastBloodMark = cg.time;
232 }
233 }
234
235 // don't allow a fragment to make multiple marks, or they
236 // pile up while settling
237 le->leMarkType = LEMT_NONE;
238 }
239
240 /*
241 ================
242 CG_FragmentBounceSound
243 ================
244 */
CG_FragmentBounceSound(localEntity_t * le,trace_t * trace)245 void CG_FragmentBounceSound( localEntity_t *le, trace_t *trace ) {
246 if ( le->leBounceSoundType == LEBS_BLOOD ) {
247 // half the gibs will make splat sounds
248 if ( rand() & 1 ) {
249 int r = rand() & 3;
250 sfxHandle_t s;
251
252 if ( r < 2 ) {
253 s = cgs.media.gibBounce1Sound;
254 } else if ( r == 2 ) {
255 s = cgs.media.gibBounce2Sound;
256 } else {
257 s = cgs.media.gibBounce3Sound;
258 }
259 trap_S_StartSound( trace->endpos, ENTITYNUM_WORLD, CHAN_AUTO, s );
260 }
261 } else if ( le->leBounceSoundType == LEBS_BRASS ) {
262
263 //----(SA) added
264 } else if ( le->leBounceSoundType == LEBS_ROCK ) {
265 // half the hits will make thunk sounds (this is just to start since we don't even have the sound yet... (SA))
266 if ( rand() & 1 ) {
267 int r = rand() & 3;
268 sfxHandle_t s;
269
270 if ( r < 2 ) {
271 s = cgs.media.debBounce1Sound;
272 } else if ( r == 2 ) {
273 s = cgs.media.debBounce2Sound;
274 } else {
275 s = cgs.media.debBounce3Sound;
276 }
277 trap_S_StartSound( trace->endpos, ENTITYNUM_WORLD, CHAN_AUTO, s );
278 }
279 //----(SA) end
280
281 } else if ( le->leBounceSoundType == LEBS_BONE ) {
282
283 trap_S_StartSound( trace->endpos, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.boneBounceSound );
284
285 }
286
287 // don't allow a fragment to make multiple bounce sounds,
288 // or it gets too noisy as they settle
289 le->leBounceSoundType = LEBS_NONE;
290 }
291
292
293 /*
294 ================
295 CG_ReflectVelocity
296 ================
297 */
CG_ReflectVelocity(localEntity_t * le,trace_t * trace)298 void CG_ReflectVelocity( localEntity_t *le, trace_t *trace ) {
299 vec3_t velocity;
300 float dot;
301 int hitTime;
302
303 // reflect the velocity on the trace plane
304 hitTime = cg.time - cg.frametime + cg.frametime * trace->fraction;
305 BG_EvaluateTrajectoryDelta( &le->pos, hitTime, velocity );
306 dot = DotProduct( velocity, trace->plane.normal );
307 VectorMA( velocity, -2 * dot, trace->plane.normal, le->pos.trDelta );
308
309 VectorScale( le->pos.trDelta, le->bounceFactor, le->pos.trDelta );
310
311 VectorCopy( trace->endpos, le->pos.trBase );
312 le->pos.trTime = cg.time;
313
314
315 // check for stop, making sure that even on low FPS systems it doesn't bobble
316
317 if ( le->leMarkType == LEMT_BLOOD && trace->startsolid ) {
318 //centity_t *cent;
319 //cent = &cg_entities[trace->entityNum];
320 //if (cent && cent->currentState.apos.trType != TR_STATIONARY)
321 // le->pos.trType = TR_STATIONARY;
322 } else if ( trace->allsolid ||
323 ( trace->plane.normal[2] > 0 &&
324 ( le->pos.trDelta[2] < 40 || le->pos.trDelta[2] < -cg.frametime * le->pos.trDelta[2] ) ) ) {
325
326 //----(SA) if it's a fragment and it's not resting on the world...
327 // if(le->leType == LE_DEBRIS && trace->entityNum < (MAX_ENTITIES - 1))
328 if ( le->leType == LE_FRAGMENT && trace->entityNum < ( MAX_REFENTITIES - 1 ) ) {
329 le->pos.trType = TR_GRAVITY_PAUSED;
330 } else
331 {
332 le->pos.trType = TR_STATIONARY;
333 }
334 } else {
335
336 }
337 }
338
339
340 //----(SA) added
341
342 /*
343 ==============
344 CG_AddEmitter
345 ==============
346 */
CG_AddEmitter(localEntity_t * le)347 void CG_AddEmitter( localEntity_t *le ) {
348 vec3_t dir;
349 int nextTime = 50;
350
351 if ( le->breakCount > cg.time ) { // using 'breakCount' for 'wait'
352 return;
353 }
354
355 if ( cg_paused.integer ) { // don't add while paused
356 return;
357 }
358
359 // TODO: look up particle script and use proper effect rather than this check
360 //if(water){}
361 //else if(oil) {}
362 //else if(steam) {}
363 //else if(wine) {}
364
365 switch ( le->headJuncIndex ) {
366 case 1: // oil
367 VectorScale( le->angles.trBase, le->radius, dir );
368 CG_Particle_OilParticle( cgs.media.oilParticle, le->pos.trBase, dir, 10000, le->ownerNum );
369 break;
370 case 2: // water
371 VectorScale( le->angles.trBase, le->radius, dir );
372 CG_Particle_OilParticle( cgs.media.oilParticle, le->pos.trBase, dir, 10000, le->ownerNum );
373 break;
374 case 3: // steam
375 nextTime = 100;
376 CG_ParticleImpactSmokePuffExtended( cgs.media.smokeParticleShader, le->pos.trBase, le->angles.trBase, 8, 1000, 8, le->radius, 20, 0.25f );
377 break;
378 case 4: // wine
379 VectorScale( le->angles.trBase, le->radius, dir );
380 CG_Particle_OilParticle( cgs.media.oilParticle, le->pos.trBase, dir, 10000, le->ownerNum );
381 break;
382 case 5: // smoke
383 nextTime = 100;
384 CG_ParticleImpactSmokePuffExtended( cgs.media.smokeParticleShader, le->pos.trBase, dir, 8, 1000, 8, 20, 20, 0.25f );
385 break;
386 case 6: // electrical
387 nextTime = 100;
388 CG_AddBulletParticles( le->pos.trBase, dir, 2, 800, 4, 16.0f );
389 break;
390
391 case 0:
392 default:
393 nextTime = 100;
394 CG_ParticleImpactSmokePuffExtended( cgs.media.smokeParticleShader, le->pos.trBase, dir, 8, 1000, 8, 20, 20, 0.25f );
395 break;
396 }
397
398 le->breakCount = cg.time + nextTime;
399 }
400
401 //----(SA) end
402
403
404 /*
405 ================
406 CG_AddFragment
407 ================
408 */
CG_AddFragment(localEntity_t * le)409 void CG_AddFragment( localEntity_t *le ) {
410 vec3_t newOrigin;
411 trace_t trace;
412 refEntity_t *re;
413 float flameAlpha = 0.0f; // TTimo: init
414 vec3_t flameDir;
415 qboolean hasFlame = qfalse;
416 int i;
417 int contents;
418
419 // Ridah
420 re = &le->refEntity;
421 if ( !re->fadeStartTime || re->fadeEndTime < le->endTime ) {
422 if ( le->endTime - cg.time > 5000 ) {
423 re->fadeStartTime = le->endTime - 5000;
424 } else {
425 re->fadeStartTime = le->endTime - 1000;
426 }
427 re->fadeEndTime = le->endTime;
428 }
429
430 // Ridah, flaming gibs
431 if ( le->onFireStart && ( le->onFireStart < cg.time && le->onFireEnd > cg.time ) ) {
432 hasFlame = qtrue;
433 // calc the alpha
434 flameAlpha = 1.0 - ( (float)( cg.time - le->onFireStart ) / (float)( le->onFireEnd - le->onFireStart ) );
435 if ( flameAlpha < 0.0 ) {
436 flameAlpha = 0.0;
437 }
438 if ( flameAlpha > 1.0 ) {
439 flameAlpha = 1.0;
440 }
441 CG_S_AddLoopingSound( -1, le->refEntity.origin, vec3_origin, cgs.media.flameCrackSound, (int)( 5.0 * flameAlpha ) );
442 }
443
444 //----(SA) added
445 if ( le->leFlags & LEF_SMOKING ) {
446 float alpha;
447 refEntity_t flash;
448
449 // create a little less smoke
450
451 // TODO: FIXME: this is not quite right, because it'll become fps dependant - in a bad way.
452 // the slower the fps, the /more/ smoke there'll be, probably driving the fps lower.
453 if ( !cg_paused.integer ) { // don't add while paused
454 if ( !( rand() % 5 ) ) {
455 alpha = 1.0 - ( (float)( cg.time - le->startTime ) / (float)( le->endTime - le->startTime ) );
456 alpha *= 0.25f;
457 memset( &flash, 0, sizeof( flash ) );
458 CG_PositionEntityOnTag( &flash, &le->refEntity, "tag_flash", 0, NULL );
459 CG_ParticleImpactSmokePuffExtended( cgs.media.smokeParticleShader, flash.origin, tv( 0,0,1 ), 8, 1000, 8, 20, 20, alpha );
460 }
461 }
462 }
463 //----(SA) end
464
465 if ( le->pos.trType == TR_STATIONARY ) {
466 // Ridah, add the flame
467 if ( hasFlame ) {
468 refEntity_t backupEnt;
469
470 backupEnt = le->refEntity;
471
472 VectorClear( flameDir );
473 flameDir[2] = 1;
474
475 le->refEntity.shaderRGBA[3] = ( unsigned char )( 255.0 * flameAlpha );
476 VectorCopy( flameDir, le->refEntity.fireRiseDir );
477 le->refEntity.customShader = cgs.media.onFireShader;
478 trap_R_AddRefEntityToScene( &le->refEntity );
479 le->refEntity.customShader = cgs.media.onFireShader2;
480 trap_R_AddRefEntityToScene( &le->refEntity );
481
482 le->refEntity = backupEnt;
483 }
484
485 trap_R_AddRefEntityToScene( &le->refEntity );
486
487 return;
488
489 } else if ( le->pos.trType == TR_GRAVITY_PAUSED ) {
490 // Ridah, add the flame
491 if ( hasFlame ) {
492 refEntity_t backupEnt;
493
494 backupEnt = le->refEntity;
495
496 VectorClear( flameDir );
497 flameDir[2] = 1;
498
499 le->refEntity.shaderRGBA[3] = ( unsigned char )( 255.0 * flameAlpha );
500 VectorCopy( flameDir, le->refEntity.fireRiseDir );
501 le->refEntity.customShader = cgs.media.onFireShader;
502 trap_R_AddRefEntityToScene( &le->refEntity );
503 le->refEntity.customShader = cgs.media.onFireShader2;
504 trap_R_AddRefEntityToScene( &le->refEntity );
505
506 le->refEntity = backupEnt;
507 }
508
509 trap_R_AddRefEntityToScene( &le->refEntity );
510
511
512 // trace a line from previous position down, to see if I should start falling again
513
514 VectorCopy( le->refEntity.origin, newOrigin );
515 newOrigin [2] -= 5;
516 CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_MISSILECLIP );
517
518 if ( trace.fraction == 1.0 ) { // it's clear, start moving again
519 VectorClear( le->pos.trDelta );
520 VectorClear( le->angles.trDelta );
521 le->pos.trType = TR_GRAVITY; // nothing below me, start falling again
522 } else {
523 return;
524 }
525 }
526
527 // calculate new position
528 BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin );
529
530 if ( hasFlame ) {
531 // calc the flame dir
532 VectorSubtract( le->refEntity.origin, newOrigin, flameDir );
533 if ( VectorLength( flameDir ) == 0 ) {
534 flameDir[2] = 1;
535 // play a burning sound when not moving
536 CG_S_AddLoopingSound( 0, newOrigin, vec3_origin, cgs.media.flameSound, (int)( 0.3 * 255.0 * flameAlpha ) );
537 } else {
538 VectorNormalize( flameDir );
539 // play a flame blow sound when moving
540 CG_S_AddLoopingSound( 0, newOrigin, vec3_origin, cgs.media.flameBlowSound, (int)( 0.3 * 255.0 * flameAlpha ) );
541 }
542 }
543
544 contents = CONTENTS_SOLID;
545
546 // trace a line from previous position to new position
547 if ( ( le->leFlags & LEF_NOTOUCHPARENT ) && le->ownerNum ) {
548 CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, le->ownerNum, contents );
549 } else {
550 CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, contents );
551 }
552
553 // did we hit someone?
554 if ( le->leFlags & LEF_PLAYER_DAMAGE ) {
555 // only do damage if travelling at a fast enough velocity
556 if ( newOrigin[2] < le->refEntity.origin[2] ) {
557 vec3_t pmin, pmax, dmin = {-12,-12,-12}, dmax = {12,12,12};
558 // debris bounds
559 VectorAdd( dmin, newOrigin, dmin );
560 VectorAdd( dmax, newOrigin, dmax );
561 dmax[2] += le->refEntity.origin[2] - newOrigin[2]; // add falling distance to box
562 // are we touching the player?
563 VectorAdd( cg.snap->ps.mins, cg.snap->ps.origin, pmin );
564 VectorAdd( cg.snap->ps.maxs, cg.snap->ps.origin, pmax );
565 pmin[2] = pmax[2] - 32; // only hit on the head
566 for ( i = 0; i < 3; i++ ) {
567 if ( ( dmax[i] < pmin[i] ) || ( dmin[i] > pmax[i] ) ) {
568 break;
569 }
570 }
571 if ( i == 3 ) {
572 trap_S_StartSound( cg.snap->ps.origin, cg.snap->ps.clientNum, CHAN_VOICE, cgs.media.debrisHitSound );
573 CG_ClientDamage( cg.snap->ps.clientNum, ENTITYNUM_WORLD, CLDMG_DEBRIS );
574 // disable damage now for this debris
575 le->leFlags &= ~LEF_PLAYER_DAMAGE;
576 }
577 }
578 }
579
580 if ( trace.fraction == 1.0 ) {
581 // still in free fall
582 VectorCopy( newOrigin, le->refEntity.origin );
583
584 if ( le->leFlags & LEF_TUMBLE || le->angles.trType == TR_LINEAR ) {
585 vec3_t angles;
586
587 BG_EvaluateTrajectory( &le->angles, cg.time, angles );
588 AnglesToAxis( angles, le->refEntity.axis );
589 if ( le->sizeScale && le->sizeScale != 1.0 ) {
590 for ( i = 0; i < 3; i++ ) VectorScale( le->refEntity.axis[i], le->sizeScale, le->refEntity.axis[i] );
591 }
592 }
593
594 // Ridah, add the flame
595 if ( hasFlame ) {
596 refEntity_t backupEnt;
597
598 backupEnt = le->refEntity;
599
600 le->refEntity.shaderRGBA[3] = ( unsigned char )( 255.0 * flameAlpha );
601 VectorCopy( flameDir, le->refEntity.fireRiseDir );
602 le->refEntity.customShader = cgs.media.onFireShader;
603 trap_R_AddRefEntityToScene( &le->refEntity );
604 le->refEntity.customShader = cgs.media.onFireShader2;
605 trap_R_AddRefEntityToScene( &le->refEntity );
606
607 le->refEntity = backupEnt;
608 }
609
610 trap_R_AddRefEntityToScene( &le->refEntity );
611
612 // add a blood trail
613 if ( le->leBounceSoundType == LEBS_BLOOD ) {
614 CG_BloodTrail( le );
615 }
616
617 return;
618 }
619
620 // if it is in a nodrop zone, remove it
621 // this keeps gibs from waiting at the bottom of pits of death
622 // and floating levels
623 if ( CG_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) {
624 CG_FreeLocalEntity( le );
625 return;
626 }
627
628 // do a bouncy sound
629 CG_FragmentBounceSound( le, &trace );
630
631 // reflect the velocity on the trace plane
632 CG_ReflectVelocity( le, &trace );
633
634 // break on contact?
635 if ( le->breakCount ) {
636 clientInfo_t *ci;
637 int clientNum;
638 localEntity_t *nle;
639 vec3_t dir;
640
641 clientNum = le->ownerNum;
642 if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) {
643 CG_Error( "Bad clientNum on player entity" );
644 }
645 ci = &cgs.clientinfo[ clientNum ];
646
647 // spawn some new fragments
648 for ( i = 0; i <= le->breakCount; i++ ) {
649 nle = CG_AllocLocalEntity();
650 memcpy( &( nle->leType ), &( le->leType ), sizeof( localEntity_t ) - 2 * sizeof( localEntity_t * ) );
651 if ( nle->breakCount-- < 2 ) {
652 nle->refEntity.hModel = ci->gibModels[rand() % 2];
653 } else {
654 nle->refEntity.hModel = ci->gibModels[rand() % 4];
655 }
656 // make it smaller
657 nle->endTime = le->endTime + ( cg.time - le->startTime );
658 nle->sizeScale *= 0.8;
659 if ( nle->sizeScale < 0.7 ) {
660 nle->sizeScale = 0.7;
661 nle->leBounceSoundType = 0;
662 }
663 // move us a bit
664 VectorNormalize2( nle->pos.trDelta, dir );
665 VectorMA( trace.endpos, 4.0 * le->sizeScale * i, dir, nle->pos.trBase );
666 // randomize vel a bit
667 VectorMA( nle->pos.trDelta, VectorLength( nle->pos.trDelta ) * 0.3, bytedirs[rand() % NUMVERTEXNORMALS], nle->pos.trDelta );
668 }
669 // we're done
670 CG_FreeLocalEntity( le );
671 return;
672 }
673
674 if ( le->pos.trType == TR_STATIONARY && le->leMarkType == LEMT_BLOOD ) {
675 // RF, disabled for performance reasons in boss1
676 //if (le->leBounceSoundType)
677 // CG_BloodPool (le, cgs.media.bloodPool, &trace);
678
679 // leave a mark
680 if ( le->leMarkType ) {
681 CG_FragmentBounceMark( le, &trace );
682 }
683 }
684
685 // Ridah, add the flame
686 if ( hasFlame ) {
687 refEntity_t backupEnt;
688
689 backupEnt = le->refEntity;
690
691 le->refEntity.shaderRGBA[3] = ( unsigned char )( 255.0 * flameAlpha );
692 VectorCopy( flameDir, le->refEntity.fireRiseDir );
693 le->refEntity.customShader = cgs.media.onFireShader;
694 trap_R_AddRefEntityToScene( &le->refEntity );
695 le->refEntity.customShader = cgs.media.onFireShader2;
696 trap_R_AddRefEntityToScene( &le->refEntity );
697
698 le->refEntity = backupEnt;
699 }
700
701 trap_R_AddRefEntityToScene( &le->refEntity );
702 }
703
704 // Ridah
705 /*
706 ================
707 CG_AddMovingTracer
708 ================
709 */
CG_AddMovingTracer(localEntity_t * le)710 void CG_AddMovingTracer( localEntity_t *le ) {
711 vec3_t start, end, dir;
712
713 BG_EvaluateTrajectory( &le->pos, cg.time, start );
714 VectorNormalize2( le->pos.trDelta, dir );
715 VectorMA( start, cg_tracerLength.value, dir, end );
716
717 CG_DrawTracer( start, end );
718 }
719
720 /*
721 ================
722 CG_AddSparkElements
723 ================
724 */
CG_AddSparkElements(localEntity_t * le)725 void CG_AddSparkElements( localEntity_t *le ) {
726 vec3_t newOrigin;
727 trace_t trace;
728 float time;
729 float lifeFrac;
730
731 time = (float)( cg.time - cg.frametime );
732
733 while ( 1 ) {
734 // calculate new position
735 BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin );
736
737 // if ((le->endTime - le->startTime) > 500) {
738
739 // trace a line from previous position to new position
740 CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, MASK_SHOT );
741
742 // if stuck, kill it
743 if ( trace.startsolid ) {
744 // HACK, some walls screw up, so just pass through if starting in a solid
745 VectorCopy( newOrigin, trace.endpos );
746 trace.fraction = 1.0;
747 }
748
749 // moved some distance
750 VectorCopy( trace.endpos, le->refEntity.origin );
751 /*
752 } else
753 { // just move it there
754
755 VectorCopy( newOrigin, le->refEntity.origin );
756 trace.fraction = 1.0;
757
758 }
759 */
760 time += cg.frametime * trace.fraction;
761
762 lifeFrac = (float)( cg.time - le->startTime ) / (float)( le->endTime - le->startTime );
763
764 // add a trail
765 le->headJuncIndex = CG_AddSparkJunc( le->headJuncIndex,
766 le->refEntity.customShader,
767 le->refEntity.origin,
768 200,
769 1.0 - lifeFrac, // start alpha
770 0.0, //1.0 - lifeFrac, // end alpha
771 lifeFrac * 2.0 * ( ( ( le->endTime - le->startTime ) > 400 ) + 1 ) * 1.5,
772 lifeFrac * 2.0 * ( ( ( le->endTime - le->startTime ) > 400 ) + 1 ) * 1.5 );
773
774 // if it is in a nodrop zone, remove it
775 // this keeps gibs from waiting at the bottom of pits of death
776 // and floating levels
777 // for some reason SFM1.BSP is one big NODROP zone
778 // if ( trap_CM_PointContents( le->refEntity.origin, 0 ) & CONTENTS_NODROP ) {
779 // CG_FreeLocalEntity( le );
780 // return;
781 // }
782
783 if ( trace.fraction < 1.0 ) {
784 // just kill it
785 CG_FreeLocalEntity( le );
786 return;
787 /*
788 // reflect the velocity on the trace plane
789 CG_ReflectVelocity( le, &trace );
790 // the intersection is a fraction of the frametime
791 le->pos.trTime = (int)time;
792 */
793 }
794
795 if ( trace.fraction == 1.0 || time >= (float)cg.time ) {
796 return;
797 }
798 }
799
800 }
801
802 /*
803 ================
804 CG_AddFuseSparkElements
805 ================
806 */
CG_AddFuseSparkElements(localEntity_t * le)807 void CG_AddFuseSparkElements( localEntity_t *le ) {
808
809 float FUSE_SPARK_WIDTH = 1.0;
810
811 int step = 10;
812 float time;
813 float lifeFrac;
814 static vec3_t whiteColor = {1,1,1};
815
816 time = (float)( le->lastTrailTime );
817
818 while ( time < cg.time ) {
819
820 // calculate new position
821 BG_EvaluateTrajectory( &le->pos, time, le->refEntity.origin );
822
823 lifeFrac = (float)( time - le->startTime ) / (float)( le->endTime - le->startTime );
824
825 //if (lifeFrac > 0.2) {
826 // add a trail
827 le->headJuncIndex = CG_AddTrailJunc( le->headJuncIndex, cgs.media.sparkParticleShader, time, STYPE_STRETCH, le->refEntity.origin, (int)( lifeFrac * (float)( le->endTime - le->startTime ) / 2.0 ),
828 1.0 /*(1.0 - lifeFrac)*/, 0.0, FUSE_SPARK_WIDTH * ( 1.0 - lifeFrac ), FUSE_SPARK_WIDTH * ( 1.0 - lifeFrac ), TJFL_SPARKHEADFLARE, whiteColor, whiteColor, 0, 0 );
829 //}
830
831 time += step;
832
833 le->lastTrailTime = time;
834 }
835
836 }
837
838 /*
839 ================
840 CG_AddBloodElements
841 ================
842 */
CG_AddBloodElements(localEntity_t * le)843 void CG_AddBloodElements( localEntity_t *le ) {
844 vec3_t newOrigin;
845 trace_t trace;
846 float time;
847 float lifeFrac;
848
849 time = (float)( cg.time - cg.frametime );
850
851 while ( 1 ) {
852 // calculate new position
853 BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin );
854
855 // trace a line from previous position to new position
856 CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, MASK_SHOT );
857
858 // if stuck, kill it
859 if ( trace.startsolid ) {
860 // HACK, some walls screw up, so just pass through if starting in a solid
861 VectorCopy( newOrigin, trace.endpos );
862 trace.fraction = 1.0;
863 }
864
865 // moved some distance
866 VectorCopy( trace.endpos, le->refEntity.origin );
867 time += cg.frametime * trace.fraction;
868
869 lifeFrac = (float)( cg.time - le->startTime ) / (float)( le->endTime - le->startTime );
870
871 // add a trail
872 le->headJuncIndex = CG_AddSparkJunc( le->headJuncIndex,
873 cgs.media.bloodTrailShader,
874 le->refEntity.origin,
875 200,
876 1.0 - lifeFrac, // start alpha
877 1.0 - lifeFrac, // end alpha
878 3.0,
879 5.0 );
880
881 if ( trace.fraction < 1.0 ) {
882 // reflect the velocity on the trace plane
883 CG_ReflectVelocity( le, &trace );
884
885 // TODO: spawn a blood decal here?
886
887 // the intersection is a fraction of the frametime
888 le->pos.trTime = (int)time;
889 }
890
891 if ( trace.fraction == 1.0 || time >= (float)cg.time ) {
892 return;
893 }
894 }
895
896 }
897
898 /*
899 ===================
900 CG_AddSpiritViewflash
901 ===================
902 */
CG_AddSpiritViewflash(localEntity_t * le)903 void CG_AddSpiritViewflash( localEntity_t *le ) {
904 float alpha;
905 #define SPIRIT_FLASH_FADEIN 50
906 #define SPIRIT_FLASH_DURATION 400
907 #define SPIRIT_FLASH_FADEOUT 2000
908
909 if ( cg.viewFade > 1.0 ) {
910 return;
911 }
912
913 if ( cg.time < le->startTime + SPIRIT_FLASH_FADEIN ) {
914 alpha = (float)( cg.time - le->startTime ) / (float)SPIRIT_FLASH_FADEIN;
915 } else if ( cg.time < le->startTime + SPIRIT_FLASH_FADEIN + SPIRIT_FLASH_DURATION ) {
916 alpha = 1.0;
917 } else if ( cg.time < le->startTime + SPIRIT_FLASH_FADEIN + SPIRIT_FLASH_DURATION + SPIRIT_FLASH_FADEOUT ) {
918 alpha = 1.0 - (float)( cg.time - ( le->startTime + SPIRIT_FLASH_FADEIN + SPIRIT_FLASH_DURATION ) ) / (float)SPIRIT_FLASH_FADEOUT;
919 } else {
920 return;
921 }
922
923 if ( alpha < 0 ) {
924 return;
925 }
926
927 // only ever use the highest fade
928 if ( cg.viewFade < alpha ) {
929 cg.viewFade = alpha;
930 }
931 }
932
933 /*
934 ================
935 CG_AddClientCritter
936 ================
937 */
CG_AddClientCritter(localEntity_t * le)938 void CG_AddClientCritter( localEntity_t *le ) {
939 vec3_t newOrigin;
940 trace_t trace;
941 int time, step = 25, i;
942 vec3_t v, ang, v2, oDelta;
943 localEntity_t backup;
944 float oldSpeed, enemyDist, of;
945 vec3_t enemyPos;
946 float alpha = 0.0f, fadeRatio; // TTimo: init
947
948 if ( cg_entities[le->ownerNum].currentState.otherEntityNum2 == cg.snap->ps.clientNum ) {
949 VectorCopy( cg.snap->ps.origin, enemyPos );
950 enemyPos[2] += cg.snap->ps.viewheight;
951 } else {
952 VectorCopy( cg_entities[le->ownerNum].currentState.origin2, enemyPos );
953 }
954
955 VectorCopy( le->pos.trDelta, oDelta );
956
957 // vary the enemyPos to create a psuedo-randomness
958 of = (float)cg.time + le->startTime;
959 enemyPos[0] += 12 * ( sin( of / 100 ) * cos( of / 78 ) );
960 enemyPos[1] += 12 * ( sin( of / 70 ) * cos( of / 82 ) );
961 enemyPos[2] += 12 * ( sin( of / 67 ) * cos( of / 98 ) );
962
963 time = le->lastTrailTime + step;
964
965 fadeRatio = (float)( cg.time - le->startTime ) / 2000.0;
966 if ( fadeRatio < 0.0 ) {
967 fadeRatio = 0.0;
968 }
969 if ( fadeRatio > 1.0 ) {
970 fadeRatio = 1.0;
971 }
972
973 while ( time <= cg.time ) {
974 if ( time > le->refEntity.fadeStartTime ) {
975 alpha = (float)( time - le->refEntity.fadeStartTime ) / (float)( le->refEntity.fadeEndTime - le->refEntity.fadeStartTime );
976 if ( alpha < 0 ) {
977 alpha = 0;
978 } else if ( alpha > 1 ) {
979 alpha = 1;
980 }
981 } else {
982 alpha = fadeRatio;
983 }
984
985 // calculate new position
986 BG_EvaluateTrajectory( &le->pos, time, newOrigin );
987
988 // trace a line from previous position to new position
989 CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, le->ownerNum, MASK_SHOT );
990
991 // if stuck, kill it
992 if ( trace.startsolid || ( trace.fraction < 1.0 ) ) {
993 // let heinrich spirits pass through geometry
994 if ( !( le->leType == LE_HELGA_SPIRIT && ( le->refEntity.hModel == cgs.media.ssSpiritSkullModel ) ) ) {
995 // kill it
996 CG_FreeLocalEntity( le );
997 return;
998 } else {
999 VectorCopy( newOrigin, trace.endpos );
1000 }
1001 }
1002
1003 // moved some distance
1004 VectorCopy( trace.endpos, le->refEntity.origin );
1005
1006 // record this pos
1007 le->validOldPos[le->oldPosHead] = 1;
1008 VectorCopy( le->refEntity.origin, le->oldPos[le->oldPosHead] );
1009 if ( ++le->oldPosHead >= MAX_OLD_POS ) {
1010 le->oldPosHead = 0;
1011 }
1012
1013 if ( le->leType == LE_ZOMBIE_SPIRIT ) {
1014 le->headJuncIndex = CG_AddTrailJunc( le->headJuncIndex,
1015 cgs.media.zombieSpiritTrailShader,
1016 time,
1017 STYPE_STRETCH,
1018 le->refEntity.origin,
1019 (int)le->effectWidth, // trail life
1020 0.3 * alpha,
1021 0.0,
1022 le->radius,
1023 0,
1024 0, //TJFL_FIXDISTORT,
1025 colorWhite,
1026 colorWhite,
1027 1.0, 1 );
1028 }
1029
1030 if ( le->leType == LE_HELGA_SPIRIT && le->refEntity.hModel != cgs.media.ssSpiritSkullModel ) {
1031 le->headJuncIndex = CG_AddTrailJunc( le->headJuncIndex,
1032 cgs.media.helgaSpiritTrailShader,
1033 time,
1034 STYPE_STRETCH,
1035 le->refEntity.origin,
1036 (int)le->effectWidth, // trail life
1037 0.3 * alpha,
1038 0.0,
1039 le->radius,
1040 0,
1041 0, //TJFL_FIXDISTORT,
1042 colorWhite,
1043 colorWhite,
1044 1.0, 1 );
1045 }
1046
1047 // tracking factor
1048 if ( le->leType == LE_ZOMBIE_BAT ) {
1049 le->bounceFactor = 3.0 * (float)step / 1000.0;
1050 } else {
1051 le->bounceFactor = 5.0 * (float)step / 1000.0;
1052 }
1053 oldSpeed = VectorLength( le->pos.trDelta );
1054
1055 // track the enemy
1056 VectorSubtract( enemyPos, le->refEntity.origin, v );
1057 enemyDist = VectorNormalize( v );
1058
1059 if ( alpha > 0.5 && ( le->lastSpiritDmgTime < time - 100 ) && enemyDist < 24 ) {
1060 localEntity_t *fb;
1061
1062 // if dead, ignore
1063 if ( !( le->ownerNum != cg.snap->ps.clientNum ? cg_entities[le->ownerNum].currentState.eFlags & EF_DEAD : cg.snap->ps.pm_type == PM_DEAD ) ) {
1064 // inflict the damage!
1065 CG_ClientDamage( cg_entities[le->ownerNum].currentState.otherEntityNum2, le->ownerNum, CLDMG_SPIRIT );
1066 le->lastSpiritDmgTime = time;
1067
1068 if ( le->leType == LE_HELGA_SPIRIT ) {
1069 // spawn a "flashbang" thinker
1070 fb = CG_AllocLocalEntity();
1071 fb->leType = LE_SPIRIT_VIEWFLASH;
1072 fb->startTime = cg.time + 50;
1073 fb->endTime = fb->startTime + SPIRIT_FLASH_FADEIN + SPIRIT_FLASH_DURATION + SPIRIT_FLASH_FADEOUT;
1074 // gasp!
1075 CG_SoundPlayIndexedScript( cgs.media.helgaGaspSound, NULL, cg_entities[le->ownerNum].currentState.otherEntityNum2 );
1076 }
1077 }
1078 }
1079
1080 VectorMA( le->pos.trDelta, le->bounceFactor * oldSpeed, v, le->pos.trDelta );
1081 //VectorCopy( v, le->pos.trDelta );
1082 if ( VectorLength( le->pos.trDelta ) < 1 ) {
1083 CG_FreeLocalEntity( le );
1084 return;
1085 }
1086
1087 le->bounceFactor = 5.0 * (float)step / 1000.0; // avoidance factor
1088
1089 // the intersection is a fraction of the frametime
1090 le->pos.trTime = time;
1091 VectorCopy( le->refEntity.origin, le->pos.trBase );
1092 VectorNormalize( le->pos.trDelta );
1093 VectorScale( le->pos.trDelta, oldSpeed, le->pos.trDelta );
1094
1095 // now trace ahead of time, if we're going to hit something, then avoid it
1096 // only avoid dangers if we don't have direct sight to the enemy
1097 trap_CM_BoxTrace( &trace, le->refEntity.origin, enemyPos, NULL, NULL, 0, MASK_SOLID );
1098 if ( trace.fraction < 1.0 ) {
1099 BG_EvaluateTrajectory( &le->pos, time + 1000, newOrigin );
1100
1101 // if we would go passed the enemy, don't bother
1102 if ( VectorDistance( le->refEntity.origin, enemyPos ) > VectorDistance( le->refEntity.origin, newOrigin ) ) {
1103
1104 trap_CM_BoxTrace( &trace, le->refEntity.origin, newOrigin, NULL, NULL, 0, MASK_SOLID );
1105
1106 if ( trace.fraction < 1.0 ) {
1107 // make sure we are not heading away from the enemy too much
1108 VectorNormalize2( le->pos.trDelta, v2 );
1109 if ( DotProduct( v, v2 ) > 0.7 ) {
1110 // avoid world geometry
1111 backup = *le;
1112 le->bounceFactor = ( 1.0 - trace.fraction ) * 10.0 * (float)step / 1000.0; // tracking and avoidance factor
1113 // reflect the velocity on the trace plane
1114 VectorMA( le->pos.trDelta, le->bounceFactor * oldSpeed, trace.plane.normal, le->pos.trDelta );
1115 if ( VectorLength( le->pos.trDelta ) < 1 ) {
1116 CG_FreeLocalEntity( le );
1117 return;
1118 }
1119 // the intersection is a fraction of the frametime
1120 le->pos.trTime = time;
1121 VectorCopy( le->refEntity.origin, le->pos.trBase );
1122 VectorNormalize( le->pos.trDelta );
1123 VectorScale( le->pos.trDelta, oldSpeed, le->pos.trDelta );
1124 //
1125 // double check end velocity
1126 VectorNormalize2( le->pos.trDelta, v2 );
1127 if ( DotProduct( v, v2 ) <= 0.2 ) {
1128 // restore
1129 *le = backup;
1130 }
1131 }
1132 }
1133 }
1134 }
1135
1136 // set the angles
1137 VectorNormalize2( oDelta, v );
1138 // HACK!!! skull model is back-to-front, need to fix
1139 if ( le->leType == LE_ZOMBIE_SPIRIT ) {
1140 VectorInverse( v );
1141 }
1142 vectoangles( v, ang );
1143 AnglesToAxis( ang, le->refEntity.axis );
1144 // lean when turning
1145 if ( le->leType == LE_ZOMBIE_BAT || le->leType == LE_HELGA_SPIRIT ) {
1146 VectorSubtract( le->pos.trDelta, oDelta, v2 );
1147 ang[ROLL] = -0.5 * DotProduct( le->refEntity.axis[1], v2 );
1148 if ( fabs( ang[ROLL] ) < 20 ) {
1149 ang[ROLL] = 0;
1150 } else {
1151 if ( ang[ROLL] < 0 ) {
1152 ang[ROLL] += 20;
1153 } else {
1154 ang[ROLL] -= 20;
1155 }
1156 }
1157 if ( fabs( ang[ROLL] ) > 80 ) {
1158 if ( ang[ROLL] > 80 ) {
1159 ang[ROLL] = 80;
1160 } else { ang[ROLL] = -80;}
1161 }
1162 AnglesToAxis( ang, le->refEntity.axis );
1163 }
1164
1165 // HACK: the skull is slightly higher than the origin
1166 if ( le->leType == LE_ZOMBIE_SPIRIT ) {
1167 // set the size scale
1168 for ( i = 0; i < 3; i++ )
1169 VectorScale( le->refEntity.axis[i], 0.35, le->refEntity.axis[i] );
1170 VectorMA( le->refEntity.origin, -10, le->refEntity.axis[2], le->refEntity.origin );
1171 }
1172
1173 le->lastTrailTime = time;
1174 time += step;
1175 }
1176
1177 // Bats, set the frame
1178 if ( le->leType == LE_ZOMBIE_BAT ) {
1179 #define BAT_ANIM_FRAMETIME 30
1180 le->refEntity.frame = ( cg.time / BAT_ANIM_FRAMETIME + 1 ) % 19;
1181 le->refEntity.oldframe = ( cg.time / BAT_ANIM_FRAMETIME ) % 19;
1182 le->refEntity.backlerp = 1.0 - ( (float)( cg.time % BAT_ANIM_FRAMETIME ) / (float)BAT_ANIM_FRAMETIME );
1183 }
1184
1185 // add the sound
1186 if ( le->loopingSound ) {
1187 if ( cg.time > le->refEntity.fadeStartTime ) {
1188 CG_S_AddLoopingSound( 0, le->refEntity.origin, vec3_origin, le->loopingSound, 255 - (int)( 255.0 * (float)( cg.time - le->refEntity.fadeStartTime ) / (float)( le->refEntity.fadeEndTime - le->refEntity.fadeStartTime ) ) );
1189 } else {
1190 CG_S_AddLoopingSound( 0, le->refEntity.origin, vec3_origin, le->loopingSound, 255 - (int)( 255.0 * ( 1.0 - alpha ) ) );
1191 }
1192 }
1193
1194 trap_R_AddRefEntityToScene( &le->refEntity );
1195
1196 // Bats, add the flame
1197 if ( le->leType == LE_ZOMBIE_BAT ) {
1198 //
1199 le->refEntity.shaderRGBA[3] = 255;
1200 VectorNormalize2( le->pos.trDelta, v );
1201 VectorInverse( v );
1202 v[2] += 1;
1203 VectorNormalize2( v, le->refEntity.fireRiseDir );
1204
1205 le->refEntity.customShader = cgs.media.onFireShader2;
1206 trap_R_AddRefEntityToScene( &le->refEntity );
1207 le->refEntity.shaderTime = 1434;
1208 trap_R_AddRefEntityToScene( &le->refEntity );
1209
1210 le->refEntity.customShader = 0;
1211 le->refEntity.shaderTime = 0;
1212 }
1213 }
1214
1215 /*
1216 ================
1217 CG_AddDebrisElements
1218 ================
1219 */
CG_AddDebrisElements(localEntity_t * le)1220 void CG_AddDebrisElements( localEntity_t *le ) {
1221 vec3_t newOrigin;
1222 trace_t trace;
1223 float lifeFrac;
1224 int t, step = 50;
1225
1226 for ( t = le->lastTrailTime + step; t < cg.time; t += step ) {
1227 // calculate new position
1228 BG_EvaluateTrajectory( &le->pos, t, newOrigin );
1229
1230 // trace a line from previous position to new position
1231 CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, MASK_SHOT );
1232
1233 // if stuck, kill it
1234 if ( trace.startsolid ) {
1235 // HACK, some walls screw up, so just pass through if starting in a solid
1236 VectorCopy( newOrigin, trace.endpos );
1237 trace.fraction = 1.0;
1238 }
1239
1240 // moved some distance
1241 VectorCopy( trace.endpos, le->refEntity.origin );
1242
1243 // add a trail
1244 lifeFrac = (float)( t - le->startTime ) / (float)( le->endTime - le->startTime );
1245
1246 #if 0
1247 // fire
1248 #if 1 // flame
1249 if ( le->effectWidth > 0 ) {
1250 le->headJuncIndex = CG_AddSparkJunc( le->headJuncIndex,
1251 cgs.media.fireTrailShader,
1252 le->refEntity.origin,
1253 (int)( 500.0 * ( 0.5 + 0.5 * ( 1.0 - lifeFrac ) ) ), // trail life
1254 1.0, // alpha
1255 0.5, // end alpha
1256 3, // start width
1257 le->effectWidth ); // end width
1258 #else // spark line
1259 if ( le->effectWidth > 0 ) {
1260 le->headJuncIndex = CG_AddSparkJunc( le->headJuncIndex,
1261 cgs.media.sparkParticleShader,
1262 le->refEntity.origin,
1263 (int)( 600.0 * ( 0.5 + 0.5 * ( 0.5 - lifeFrac ) ) ), // trail life
1264 1.0 - lifeFrac * 2, // alpha
1265 0.5 * ( 1.0 - lifeFrac ), // end alpha
1266 5.0 * ( 1.0 - lifeFrac ), // start width
1267 5.0 * ( 1.0 - lifeFrac ) ); // end width
1268 #endif
1269 }
1270 #endif
1271
1272 // smoke
1273 if ( le->effectFlags & 1 ) {
1274 le->headJuncIndex2 = CG_AddSmokeJunc( le->headJuncIndex2,
1275 cgs.media.smokeTrailShader,
1276 le->refEntity.origin,
1277 (int)( 2000.0 * ( 0.5 + 0.5 * ( 1.0 - lifeFrac ) ) ), // trail life
1278 1.0 * ( trace.fraction == 1.0 ) * ( 0.5 + 0.5 * ( 1.0 - lifeFrac ) ), // alpha
1279 1, // start width
1280 (int)( 60.0 * ( 0.5 + 0.5 * ( 1.0 - lifeFrac ) ) ) ); // end width
1281 }
1282
1283 // if it is in a nodrop zone, remove it
1284 // this keeps gibs from waiting at the bottom of pits of death
1285 // and floating levels
1286 // if ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) {
1287 // CG_FreeLocalEntity( le );
1288 // return;
1289 // }
1290
1291 if ( trace.fraction < 1.0 ) {
1292 // reflect the velocity on the trace plane
1293 CG_ReflectVelocity( le, &trace );
1294 if ( VectorLength( le->pos.trDelta ) < 1 ) {
1295 CG_FreeLocalEntity( le );
1296 return;
1297 }
1298 // the intersection is a fraction of the frametime
1299 le->pos.trTime = t;
1300 }
1301
1302 le->lastTrailTime = t;
1303 }
1304
1305 }
1306
1307 // Rafael Shrapnel
1308 /*
1309 ===============
1310 CG_AddShrapnel
1311 ===============
1312 */
1313 void CG_AddShrapnel( localEntity_t *le ) {
1314 vec3_t newOrigin;
1315 trace_t trace;
1316
1317 if ( le->pos.trType == TR_STATIONARY ) {
1318 // sink into the ground if near the removal time
1319 int t;
1320 float oldZ;
1321
1322 t = le->endTime - cg.time;
1323 if ( t < SINK_TIME ) {
1324 // we must use an explicit lighting origin, otherwise the
1325 // lighting would be lost as soon as the origin went
1326 // into the ground
1327 VectorCopy( le->refEntity.origin, le->refEntity.lightingOrigin );
1328 le->refEntity.renderfx |= RF_LIGHTING_ORIGIN;
1329 oldZ = le->refEntity.origin[2];
1330 le->refEntity.origin[2] -= 16 * ( 1.0 - (float)t / SINK_TIME );
1331 trap_R_AddRefEntityToScene( &le->refEntity );
1332 le->refEntity.origin[2] = oldZ;
1333 } else {
1334 trap_R_AddRefEntityToScene( &le->refEntity );
1335 CG_AddParticleShrapnel( le );
1336 }
1337
1338 return;
1339 }
1340
1341 // calculate new position
1342 BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin );
1343
1344 // trace a line from previous position to new position
1345 CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, CONTENTS_SOLID );
1346 if ( trace.fraction == 1.0 ) {
1347 // still in free fall
1348 VectorCopy( newOrigin, le->refEntity.origin );
1349
1350 if ( le->leFlags & LEF_TUMBLE ) {
1351 vec3_t angles;
1352
1353 BG_EvaluateTrajectory( &le->angles, cg.time, angles );
1354 AnglesToAxis( angles, le->refEntity.axis );
1355 }
1356
1357 trap_R_AddRefEntityToScene( &le->refEntity );
1358 CG_AddParticleShrapnel( le );
1359 return;
1360 }
1361
1362 // if it is in a nodrop zone, remove it
1363 // this keeps gibs from waiting at the bottom of pits of death
1364 // and floating levels
1365 if ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) {
1366 CG_FreeLocalEntity( le );
1367 return;
1368 }
1369
1370 // leave a mark
1371 CG_FragmentBounceMark( le, &trace );
1372
1373 // do a bouncy sound
1374 CG_FragmentBounceSound( le, &trace );
1375
1376 // reflect the velocity on the trace plane
1377 CG_ReflectVelocity( le, &trace );
1378
1379 trap_R_AddRefEntityToScene( &le->refEntity );
1380 CG_AddParticleShrapnel( le );
1381 }
1382 // done.
1383
1384 /*
1385 =====================================================================
1386
1387 TRIVIAL LOCAL ENTITIES
1388
1389 These only do simple scaling or modulation before passing to the renderer
1390 =====================================================================
1391 */
1392
1393 /*
1394 ====================
1395 CG_AddFadeRGB
1396 ====================
1397 */
1398 void CG_AddFadeRGB( localEntity_t *le ) {
1399 refEntity_t *re;
1400 float c;
1401
1402 re = &le->refEntity;
1403
1404 c = ( le->endTime - cg.time ) * le->lifeRate;
1405 c *= 0xff;
1406
1407 re->shaderRGBA[0] = le->color[0] * c;
1408 re->shaderRGBA[1] = le->color[1] * c;
1409 re->shaderRGBA[2] = le->color[2] * c;
1410 re->shaderRGBA[3] = le->color[3] * c;
1411
1412 trap_R_AddRefEntityToScene( re );
1413 }
1414
1415 /*
1416 ==================
1417 CG_AddMoveScaleFade
1418 ==================
1419 */
1420 static void CG_AddMoveScaleFade( localEntity_t *le ) {
1421 refEntity_t *re;
1422 float c;
1423 vec3_t delta;
1424 float len;
1425
1426 re = &le->refEntity;
1427
1428 // fade / grow time
1429 // c = ( le->endTime - cg.time ) * le->lifeRate;
1430 if ( le->fadeInTime > le->startTime && cg.time < le->fadeInTime ) {
1431 // fade / grow time
1432 c = 1.0 - (float) ( le->fadeInTime - cg.time ) / ( le->fadeInTime - le->startTime );
1433 } else {
1434 // fade / grow time
1435 c = ( le->endTime - cg.time ) * le->lifeRate;
1436 }
1437
1438 // Ridah, spark
1439 if ( !( le->leFlags & LEF_NOFADEALPHA ) ) {
1440 // done.
1441 re->shaderRGBA[3] = 0xff * c * le->color[3];
1442 }
1443
1444 if ( !( le->leFlags & LEF_PUFF_DONT_SCALE ) ) {
1445 c = ( le->endTime - cg.time ) * le->lifeRate;
1446 re->radius = le->radius * ( 1.0 - c ) + 8;
1447 }
1448
1449 BG_EvaluateTrajectory( &le->pos, cg.time, re->origin );
1450
1451 // if the view would be "inside" the sprite, kill the sprite
1452 // so it doesn't add too much overdraw
1453 VectorSubtract( re->origin, cg.refdef.vieworg, delta );
1454 len = VectorLength( delta );
1455 if ( len < le->radius ) {
1456 CG_FreeLocalEntity( le );
1457 return;
1458 }
1459
1460 trap_R_AddRefEntityToScene( re );
1461 }
1462
1463
1464 /*
1465 ===================
1466 CG_AddScaleFade
1467
1468 For rocket smokes that hang in place, fade out, and are
1469 removed if the view passes through them.
1470 There are often many of these, so it needs to be simple.
1471 ===================
1472 */
1473 static void CG_AddScaleFade( localEntity_t *le ) {
1474 refEntity_t *re;
1475 float c;
1476 vec3_t delta;
1477 float len;
1478
1479 re = &le->refEntity;
1480
1481 // fade / grow time
1482 c = ( le->endTime - cg.time ) * le->lifeRate;
1483
1484 re->shaderRGBA[3] = 0xff * c * le->color[3];
1485 if ( !( le->leFlags & LEF_PUFF_DONT_SCALE ) ) {
1486 re->radius = le->radius * ( 1.0 - c ) + 8;
1487 }
1488
1489 // if the view would be "inside" the sprite, kill the sprite
1490 // so it doesn't add too much overdraw
1491 VectorSubtract( re->origin, cg.refdef.vieworg, delta );
1492 len = VectorLength( delta );
1493 if ( len < le->radius ) {
1494 CG_FreeLocalEntity( le );
1495 return;
1496 }
1497
1498 trap_R_AddRefEntityToScene( re );
1499 }
1500
1501
1502 /*
1503 =================
1504 CG_AddFallScaleFade
1505
1506 This is just an optimized CG_AddMoveScaleFade
1507 For blood mists that drift down, fade out, and are
1508 removed if the view passes through them.
1509 There are often 100+ of these, so it needs to be simple.
1510 =================
1511 */
1512 static void CG_AddFallScaleFade( localEntity_t *le ) {
1513 refEntity_t *re;
1514 float c;
1515 vec3_t delta;
1516 float len;
1517
1518 re = &le->refEntity;
1519
1520 // fade time
1521 c = ( le->endTime - cg.time ) * le->lifeRate;
1522
1523 re->shaderRGBA[3] = 0xff * c * le->color[3];
1524
1525 re->origin[2] = le->pos.trBase[2] - ( 1.0 - c ) * le->pos.trDelta[2];
1526
1527 re->radius = le->radius * ( 1.0 - c ) + 16;
1528
1529 // if the view would be "inside" the sprite, kill the sprite
1530 // so it doesn't add too much overdraw
1531 VectorSubtract( re->origin, cg.refdef.vieworg, delta );
1532 len = VectorLength( delta );
1533 if ( len < le->radius ) {
1534 CG_FreeLocalEntity( le );
1535 return;
1536 }
1537
1538 trap_R_AddRefEntityToScene( re );
1539 }
1540
1541
1542
1543 /*
1544 ================
1545 CG_AddExplosion
1546 ================
1547 */
1548 static void CG_AddExplosion( localEntity_t *ex ) {
1549 refEntity_t *ent;
1550
1551 ent = &ex->refEntity;
1552
1553 // add the entity
1554 // RF, don't add if shader is invalid
1555 if ( ent->customShader >= 0 ) {
1556 trap_R_AddRefEntityToScene( ent );
1557 }
1558
1559 // add the dlight
1560 if ( ex->light ) {
1561 float light;
1562
1563 light = (float)( cg.time - ex->startTime ) / ( ex->endTime - ex->startTime );
1564 if ( light < 0.5 ) {
1565 light = 1.0;
1566 } else {
1567 light = 1.0 - ( light - 0.5 ) * 2;
1568 }
1569 light = ex->light * light;
1570 trap_R_AddLightToScene( ent->origin, light, ex->lightColor[0], ex->lightColor[1], ex->lightColor[2], 0 );
1571 }
1572 }
1573
1574 /*
1575 ================
1576 CG_AddSpriteExplosion
1577 ================
1578 */
1579 static void CG_AddSpriteExplosion( localEntity_t *le ) {
1580 refEntity_t re;
1581 float c;
1582
1583 re = le->refEntity;
1584
1585 c = ( le->endTime - cg.time ) / ( float ) ( le->endTime - le->startTime );
1586 if ( c > 1 ) {
1587 c = 1.0; // can happen during connection problems
1588 }
1589
1590 re.shaderRGBA[0] = 0xff;
1591 re.shaderRGBA[1] = 0xff;
1592 re.shaderRGBA[2] = 0xff;
1593 re.shaderRGBA[3] = 0xff * c * 0.33;
1594
1595 re.reType = RT_SPRITE;
1596 re.radius = 42 * ( 1.0 - c ) + 30;
1597
1598 // Ridah, move away from surface
1599 VectorMA( le->pos.trBase, ( 1.0 - c ), le->pos.trDelta, re.origin );
1600 // done.
1601
1602 // RF, don't add if shader is invalid
1603 if ( re.customShader >= 0 ) {
1604 trap_R_AddRefEntityToScene( &re );
1605 }
1606
1607 // add the dlight
1608 if ( le->light ) {
1609 float light;
1610
1611 // Ridah, modified this so the light fades out rather than shrinking
1612 /*
1613 light = (float)( cg.time - le->startTime ) / ( le->endTime - le->startTime );
1614 if ( light < 0.5 ) {
1615 light = 1.0;
1616 } else {
1617 light = 1.0 - ( light - 0.5 ) * 2;
1618 }
1619 light = le->light * light;
1620 trap_R_AddLightToScene(re.origin, light, le->lightColor[0], le->lightColor[1], le->lightColor[2], 0 );
1621 */
1622 light = (float)( cg.time - le->startTime ) / ( le->endTime - le->startTime );
1623 if ( light < 0.5 ) {
1624 light = 1.0;
1625 } else {
1626 light = 1.0 - ( light - 0.5 ) * 2;
1627 }
1628 trap_R_AddLightToScene( re.origin, le->light, light * le->lightColor[0], light * le->lightColor[1], light * le->lightColor[2], 0 );
1629 // done.
1630 }
1631 }
1632
1633
1634 //==============================================================================
1635
1636 /*
1637 ===================
1638 CG_AddLocalEntities
1639
1640 ===================
1641 */
1642 void CG_AddLocalEntities( void ) {
1643 localEntity_t *le, *next;
1644
1645 cg.viewFade = 0.0;
1646
1647 // walk the list backwards, so any new local entities generated
1648 // (trails, marks, etc) will be present this frame
1649 le = cg_activeLocalEntities.prev;
1650 for ( ; le != &cg_activeLocalEntities ; le = next ) {
1651 // grab next now, so if the local entity is freed we
1652 // still have it
1653 next = le->prev;
1654
1655 if ( cg.time >= le->endTime ) {
1656 CG_FreeLocalEntity( le );
1657 continue;
1658 }
1659 switch ( le->leType ) {
1660 default:
1661 CG_Error( "Bad leType: %i", le->leType );
1662 break;
1663
1664 // Ridah
1665 case LE_MOVING_TRACER:
1666 CG_AddMovingTracer( le );
1667 break;
1668 case LE_SPARK:
1669 CG_AddSparkElements( le );
1670 break;
1671 case LE_FUSE_SPARK:
1672 CG_AddFuseSparkElements( le );
1673 break;
1674 case LE_DEBRIS:
1675 CG_AddDebrisElements( le );
1676 break;
1677 case LE_BLOOD:
1678 CG_AddBloodElements( le );
1679 break;
1680 case LE_HELGA_SPIRIT:
1681 case LE_ZOMBIE_SPIRIT:
1682 case LE_ZOMBIE_BAT:
1683 CG_AddClientCritter( le );
1684 break;
1685 case LE_SPIRIT_VIEWFLASH:
1686 CG_AddSpiritViewflash( le );
1687 // done.
1688
1689 case LE_MARK:
1690 break;
1691
1692 case LE_SPRITE_EXPLOSION:
1693 CG_AddSpriteExplosion( le );
1694 break;
1695
1696 case LE_EXPLOSION:
1697 CG_AddExplosion( le );
1698 break;
1699
1700 case LE_FRAGMENT: // gibs and brass
1701 CG_AddFragment( le );
1702 break;
1703
1704 case LE_MOVE_SCALE_FADE: // water bubbles
1705 CG_AddMoveScaleFade( le );
1706 break;
1707
1708 case LE_FADE_RGB: // teleporters, railtrails
1709 CG_AddFadeRGB( le );
1710 break;
1711
1712 case LE_FALL_SCALE_FADE: // gib blood trails
1713 CG_AddFallScaleFade( le );
1714 break;
1715
1716 case LE_SCALE_FADE: // rocket trails
1717 CG_AddScaleFade( le );
1718 break;
1719
1720 case LE_EMITTER:
1721 CG_AddEmitter( le );
1722 break;
1723
1724 }
1725 }
1726 }
1727
1728