1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4
5 This file is part of Quake III Arena source code.
6
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Quake III Arena source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 ===========================================================================
21 */
22 //
23 // cg_ents.c -- present snapshot entities, happens every single frame
24
25 #include "cg_local.h"
26
27
28 /*
29 ======================
30 CG_PositionEntityOnTag
31
32 Modifies the entities position and axis by the given
33 tag location
34 ======================
35 */
CG_PositionEntityOnTag(refEntity_t * entity,const refEntity_t * parent,qhandle_t parentModel,char * tagName)36 void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent,
37 qhandle_t parentModel, char *tagName ) {
38 int i;
39 orientation_t lerped;
40
41 // lerp the tag
42 trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
43 1.0 - parent->backlerp, tagName );
44
45 // FIXME: allow origin offsets along tag?
46 VectorCopy( parent->origin, entity->origin );
47 for ( i = 0 ; i < 3 ; i++ ) {
48 VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin );
49 }
50
51 // had to cast away the const to avoid compiler problems...
52 AxisMultiply( lerped.axis, ((refEntity_t *)parent)->axis, entity->axis );
53 entity->backlerp = parent->backlerp;
54 }
55
56
57 /*
58 ======================
59 CG_PositionRotatedEntityOnTag
60
61 Modifies the entities position and axis by the given
62 tag location
63 ======================
64 */
CG_PositionRotatedEntityOnTag(refEntity_t * entity,const refEntity_t * parent,qhandle_t parentModel,char * tagName)65 void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent,
66 qhandle_t parentModel, char *tagName ) {
67 int i;
68 orientation_t lerped;
69 vec3_t tempAxis[3];
70
71 //AxisClear( entity->axis );
72 // lerp the tag
73 trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
74 1.0 - parent->backlerp, tagName );
75
76 // FIXME: allow origin offsets along tag?
77 VectorCopy( parent->origin, entity->origin );
78 for ( i = 0 ; i < 3 ; i++ ) {
79 VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin );
80 }
81
82 // had to cast away the const to avoid compiler problems...
83 AxisMultiply( entity->axis, lerped.axis, tempAxis );
84 AxisMultiply( tempAxis, ((refEntity_t *)parent)->axis, entity->axis );
85 }
86
87
88
89 /*
90 ==========================================================================
91
92 FUNCTIONS CALLED EACH FRAME
93
94 ==========================================================================
95 */
96
97 /*
98 ======================
99 CG_SetEntitySoundPosition
100
101 Also called by event processing code
102 ======================
103 */
CG_SetEntitySoundPosition(centity_t * cent)104 void CG_SetEntitySoundPosition( centity_t *cent ) {
105 if ( cent->currentState.solid == SOLID_BMODEL ) {
106 vec3_t origin;
107 float *v;
108
109 v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ];
110 VectorAdd( cent->lerpOrigin, v, origin );
111 trap_S_UpdateEntityPosition( cent->currentState.number, origin );
112 } else {
113 trap_S_UpdateEntityPosition( cent->currentState.number, cent->lerpOrigin );
114 }
115 }
116
117 /*
118 ==================
119 CG_EntityEffects
120
121 Add continuous entity effects, like local entity emission and lighting
122 ==================
123 */
CG_EntityEffects(centity_t * cent)124 static void CG_EntityEffects( centity_t *cent ) {
125
126 // update sound origins
127 CG_SetEntitySoundPosition( cent );
128
129 // add loop sound
130 if ( cent->currentState.loopSound ) {
131 if (cent->currentState.eType != ET_SPEAKER) {
132 trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin,
133 cgs.gameSounds[ cent->currentState.loopSound ] );
134 } else {
135 trap_S_AddRealLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin,
136 cgs.gameSounds[ cent->currentState.loopSound ] );
137 }
138 }
139
140
141 // constant light glow
142 if ( cent->currentState.constantLight ) {
143 int cl;
144 int i, r, g, b;
145
146 cl = cent->currentState.constantLight;
147 r = cl & 255;
148 g = ( cl >> 8 ) & 255;
149 b = ( cl >> 16 ) & 255;
150 i = ( ( cl >> 24 ) & 255 ) * 4;
151 trap_R_AddLightToScene( cent->lerpOrigin, i, r, g, b );
152 }
153
154 }
155
156
157 /*
158 ==================
159 CG_General
160 ==================
161 */
CG_General(centity_t * cent)162 static void CG_General( centity_t *cent ) {
163 refEntity_t ent;
164 entityState_t *s1;
165
166 s1 = ¢->currentState;
167
168 // if set to invisible, skip
169 if (!s1->modelindex) {
170 return;
171 }
172
173 memset (&ent, 0, sizeof(ent));
174
175 // set frame
176
177 ent.frame = s1->frame;
178 ent.oldframe = ent.frame;
179 ent.backlerp = 0;
180
181 VectorCopy( cent->lerpOrigin, ent.origin);
182 VectorCopy( cent->lerpOrigin, ent.oldorigin);
183
184 ent.hModel = cgs.gameModels[s1->modelindex];
185
186 // player model
187 if (s1->number == cg.snap->ps.clientNum) {
188 ent.renderfx |= RF_THIRD_PERSON; // only draw from mirrors
189 }
190
191 // convert angles to axis
192 AnglesToAxis( cent->lerpAngles, ent.axis );
193
194 // add to refresh list
195 trap_R_AddRefEntityToScene (&ent);
196 }
197
198 /*
199 ==================
200 CG_Speaker
201
202 Speaker entities can automatically play sounds
203 ==================
204 */
CG_Speaker(centity_t * cent)205 static void CG_Speaker( centity_t *cent ) {
206 if ( ! cent->currentState.clientNum ) { // FIXME: use something other than clientNum...
207 return; // not auto triggering
208 }
209
210 if ( cg.time < cent->miscTime ) {
211 return;
212 }
213
214 trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.gameSounds[cent->currentState.eventParm] );
215
216 // ent->s.frame = ent->wait * 10;
217 // ent->s.clientNum = ent->random * 10;
218 cent->miscTime = cg.time + cent->currentState.frame * 100 + cent->currentState.clientNum * 100 * crandom();
219 }
220
221 /*
222 ==================
223 CG_Item
224 ==================
225 */
CG_Item(centity_t * cent)226 static void CG_Item( centity_t *cent ) {
227 refEntity_t ent;
228 entityState_t *es;
229 gitem_t *item;
230 int msec;
231 float frac;
232 float scale;
233 weaponInfo_t *wi;
234
235 es = ¢->currentState;
236 if ( es->modelindex >= bg_numItems ) {
237 CG_Error( "Bad item index %i on entity", es->modelindex );
238 }
239
240 // if set to invisible, skip
241 if ( !es->modelindex || ( es->eFlags & EF_NODRAW ) ) {
242 return;
243 }
244
245 item = &bg_itemlist[ es->modelindex ];
246 if ( cg_simpleItems.integer && item->giType != IT_TEAM ) {
247 memset( &ent, 0, sizeof( ent ) );
248 ent.reType = RT_SPRITE;
249 VectorCopy( cent->lerpOrigin, ent.origin );
250 ent.radius = 14;
251 ent.customShader = cg_items[es->modelindex].icon;
252 ent.shaderRGBA[0] = 255;
253 ent.shaderRGBA[1] = 255;
254 ent.shaderRGBA[2] = 255;
255 ent.shaderRGBA[3] = 255;
256 trap_R_AddRefEntityToScene(&ent);
257 return;
258 }
259
260 // items bob up and down continuously
261 scale = 0.005 + cent->currentState.number * 0.00001;
262 cent->lerpOrigin[2] += 4 + cos( ( cg.time + 1000 ) * scale ) * 4;
263
264 memset (&ent, 0, sizeof(ent));
265
266 // autorotate at one of two speeds
267 if ( item->giType == IT_HEALTH ) {
268 VectorCopy( cg.autoAnglesFast, cent->lerpAngles );
269 AxisCopy( cg.autoAxisFast, ent.axis );
270 } else {
271 VectorCopy( cg.autoAngles, cent->lerpAngles );
272 AxisCopy( cg.autoAxis, ent.axis );
273 }
274
275 wi = NULL;
276 // the weapons have their origin where they attatch to player
277 // models, so we need to offset them or they will rotate
278 // eccentricly
279 if ( item->giType == IT_WEAPON ) {
280 wi = &cg_weapons[item->giTag];
281 cent->lerpOrigin[0] -=
282 wi->weaponMidpoint[0] * ent.axis[0][0] +
283 wi->weaponMidpoint[1] * ent.axis[1][0] +
284 wi->weaponMidpoint[2] * ent.axis[2][0];
285 cent->lerpOrigin[1] -=
286 wi->weaponMidpoint[0] * ent.axis[0][1] +
287 wi->weaponMidpoint[1] * ent.axis[1][1] +
288 wi->weaponMidpoint[2] * ent.axis[2][1];
289 cent->lerpOrigin[2] -=
290 wi->weaponMidpoint[0] * ent.axis[0][2] +
291 wi->weaponMidpoint[1] * ent.axis[1][2] +
292 wi->weaponMidpoint[2] * ent.axis[2][2];
293
294 cent->lerpOrigin[2] += 8; // an extra height boost
295 }
296
297 ent.hModel = cg_items[es->modelindex].models[0];
298
299 VectorCopy( cent->lerpOrigin, ent.origin);
300 VectorCopy( cent->lerpOrigin, ent.oldorigin);
301
302 ent.nonNormalizedAxes = qfalse;
303
304 // if just respawned, slowly scale up
305 msec = cg.time - cent->miscTime;
306 if ( msec >= 0 && msec < ITEM_SCALEUP_TIME ) {
307 frac = (float)msec / ITEM_SCALEUP_TIME;
308 VectorScale( ent.axis[0], frac, ent.axis[0] );
309 VectorScale( ent.axis[1], frac, ent.axis[1] );
310 VectorScale( ent.axis[2], frac, ent.axis[2] );
311 ent.nonNormalizedAxes = qtrue;
312 } else {
313 frac = 1.0;
314 }
315
316 // items without glow textures need to keep a minimum light value
317 // so they are always visible
318 if ( ( item->giType == IT_WEAPON ) ||
319 ( item->giType == IT_ARMOR ) ) {
320 ent.renderfx |= RF_MINLIGHT;
321 }
322
323 // increase the size of the weapons when they are presented as items
324 if ( item->giType == IT_WEAPON ) {
325 VectorScale( ent.axis[0], 1.5, ent.axis[0] );
326 VectorScale( ent.axis[1], 1.5, ent.axis[1] );
327 VectorScale( ent.axis[2], 1.5, ent.axis[2] );
328 ent.nonNormalizedAxes = qtrue;
329 #ifdef MISSIONPACK
330 trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.weaponHoverSound );
331 #endif
332 }
333
334 #ifdef MISSIONPACK
335 if ( item->giType == IT_HOLDABLE && item->giTag == HI_KAMIKAZE ) {
336 VectorScale( ent.axis[0], 2, ent.axis[0] );
337 VectorScale( ent.axis[1], 2, ent.axis[1] );
338 VectorScale( ent.axis[2], 2, ent.axis[2] );
339 ent.nonNormalizedAxes = qtrue;
340 }
341 #endif
342
343 // add to refresh list
344 trap_R_AddRefEntityToScene(&ent);
345
346 #ifdef MISSIONPACK
347 if ( item->giType == IT_WEAPON && wi->barrelModel ) {
348 refEntity_t barrel;
349
350 memset( &barrel, 0, sizeof( barrel ) );
351
352 barrel.hModel = wi->barrelModel;
353
354 VectorCopy( ent.lightingOrigin, barrel.lightingOrigin );
355 barrel.shadowPlane = ent.shadowPlane;
356 barrel.renderfx = ent.renderfx;
357
358 CG_PositionRotatedEntityOnTag( &barrel, &ent, wi->weaponModel, "tag_barrel" );
359
360 AxisCopy( ent.axis, barrel.axis );
361 barrel.nonNormalizedAxes = ent.nonNormalizedAxes;
362
363 trap_R_AddRefEntityToScene( &barrel );
364 }
365 #endif
366
367 // accompanying rings / spheres for powerups
368 if ( !cg_simpleItems.integer )
369 {
370 vec3_t spinAngles;
371
372 VectorClear( spinAngles );
373
374 if ( item->giType == IT_HEALTH || item->giType == IT_POWERUP )
375 {
376 if ( ( ent.hModel = cg_items[es->modelindex].models[1] ) != 0 )
377 {
378 if ( item->giType == IT_POWERUP )
379 {
380 ent.origin[2] += 12;
381 spinAngles[1] = ( cg.time & 1023 ) * 360 / -1024.0f;
382 }
383 AnglesToAxis( spinAngles, ent.axis );
384
385 // scale up if respawning
386 if ( frac != 1.0 ) {
387 VectorScale( ent.axis[0], frac, ent.axis[0] );
388 VectorScale( ent.axis[1], frac, ent.axis[1] );
389 VectorScale( ent.axis[2], frac, ent.axis[2] );
390 ent.nonNormalizedAxes = qtrue;
391 }
392 trap_R_AddRefEntityToScene( &ent );
393 }
394 }
395 }
396 }
397
398 //============================================================================
399
400 /*
401 ===============
402 CG_Missile
403 ===============
404 */
CG_Missile(centity_t * cent)405 static void CG_Missile( centity_t *cent ) {
406 refEntity_t ent;
407 entityState_t *s1;
408 const weaponInfo_t *weapon;
409 // int col;
410
411 s1 = ¢->currentState;
412 if ( s1->weapon > WP_NUM_WEAPONS ) {
413 s1->weapon = 0;
414 }
415 weapon = &cg_weapons[s1->weapon];
416
417 // calculate the axis
418 VectorCopy( s1->angles, cent->lerpAngles);
419
420 // add trails
421 if ( weapon->missileTrailFunc )
422 {
423 weapon->missileTrailFunc( cent, weapon );
424 }
425 /*
426 if ( cent->currentState.modelindex == TEAM_RED ) {
427 col = 1;
428 }
429 else if ( cent->currentState.modelindex == TEAM_BLUE ) {
430 col = 2;
431 }
432 else {
433 col = 0;
434 }
435
436 // add dynamic light
437 if ( weapon->missileDlight ) {
438 trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight,
439 weapon->missileDlightColor[col][0], weapon->missileDlightColor[col][1], weapon->missileDlightColor[col][2] );
440 }
441 */
442 // add dynamic light
443 if ( weapon->missileDlight ) {
444 trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight,
445 weapon->missileDlightColor[0], weapon->missileDlightColor[1], weapon->missileDlightColor[2] );
446 }
447
448 // add missile sound
449 if ( weapon->missileSound ) {
450 vec3_t velocity;
451
452 BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity );
453
454 trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, weapon->missileSound );
455 }
456
457 // create the render entity
458 memset (&ent, 0, sizeof(ent));
459 VectorCopy( cent->lerpOrigin, ent.origin);
460 VectorCopy( cent->lerpOrigin, ent.oldorigin);
461
462 if ( cent->currentState.weapon == WP_PLASMAGUN ) {
463 ent.reType = RT_SPRITE;
464 ent.radius = 16;
465 ent.rotation = 0;
466 ent.customShader = cgs.media.plasmaBallShader;
467 trap_R_AddRefEntityToScene( &ent );
468 return;
469 }
470
471 // flicker between two skins
472 ent.skinNum = cg.clientFrame & 1;
473 ent.hModel = weapon->missileModel;
474 ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW;
475
476 #ifdef MISSIONPACK
477 if ( cent->currentState.weapon == WP_PROX_LAUNCHER ) {
478 if (s1->generic1 == TEAM_BLUE) {
479 ent.hModel = cgs.media.blueProxMine;
480 }
481 }
482 #endif
483
484 // convert direction of travel into axis
485 if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) {
486 ent.axis[0][2] = 1;
487 }
488
489 // spin as it moves
490 if ( s1->pos.trType != TR_STATIONARY ) {
491 RotateAroundDirection( ent.axis, cg.time / 4 );
492 } else {
493 #ifdef MISSIONPACK
494 if ( s1->weapon == WP_PROX_LAUNCHER ) {
495 AnglesToAxis( cent->lerpAngles, ent.axis );
496 }
497 else
498 #endif
499 {
500 RotateAroundDirection( ent.axis, s1->time );
501 }
502 }
503
504 // add to refresh list, possibly with quad glow
505 CG_AddRefEntityWithPowerups( &ent, s1, TEAM_FREE );
506 }
507
508 /*
509 ===============
510 CG_Grapple
511
512 This is called when the grapple is sitting up against the wall
513 ===============
514 */
CG_Grapple(centity_t * cent)515 static void CG_Grapple( centity_t *cent ) {
516 refEntity_t ent;
517 entityState_t *s1;
518 const weaponInfo_t *weapon;
519
520 s1 = ¢->currentState;
521 if ( s1->weapon > WP_NUM_WEAPONS ) {
522 s1->weapon = 0;
523 }
524 weapon = &cg_weapons[s1->weapon];
525
526 // calculate the axis
527 VectorCopy( s1->angles, cent->lerpAngles);
528
529 #if 0 // FIXME add grapple pull sound here..?
530 // add missile sound
531 if ( weapon->missileSound ) {
532 trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->missileSound );
533 }
534 #endif
535
536 // Will draw cable if needed
537 CG_GrappleTrail ( cent, weapon );
538
539 // create the render entity
540 memset (&ent, 0, sizeof(ent));
541 VectorCopy( cent->lerpOrigin, ent.origin);
542 VectorCopy( cent->lerpOrigin, ent.oldorigin);
543
544 // flicker between two skins
545 ent.skinNum = cg.clientFrame & 1;
546 ent.hModel = weapon->missileModel;
547 ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW;
548
549 // convert direction of travel into axis
550 if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) {
551 ent.axis[0][2] = 1;
552 }
553
554 trap_R_AddRefEntityToScene( &ent );
555 }
556
557 /*
558 ===============
559 CG_Mover
560 ===============
561 */
CG_Mover(centity_t * cent)562 static void CG_Mover( centity_t *cent ) {
563 refEntity_t ent;
564 entityState_t *s1;
565
566 s1 = ¢->currentState;
567
568 // create the render entity
569 memset (&ent, 0, sizeof(ent));
570 VectorCopy( cent->lerpOrigin, ent.origin);
571 VectorCopy( cent->lerpOrigin, ent.oldorigin);
572 AnglesToAxis( cent->lerpAngles, ent.axis );
573
574 ent.renderfx = RF_NOSHADOW;
575
576 // flicker between two skins (FIXME?)
577 ent.skinNum = ( cg.time >> 6 ) & 1;
578
579 // get the model, either as a bmodel or a modelindex
580 if ( s1->solid == SOLID_BMODEL ) {
581 ent.hModel = cgs.inlineDrawModel[s1->modelindex];
582 } else {
583 ent.hModel = cgs.gameModels[s1->modelindex];
584 }
585
586 // add to refresh list
587 trap_R_AddRefEntityToScene(&ent);
588
589 // add the secondary model
590 if ( s1->modelindex2 ) {
591 ent.skinNum = 0;
592 ent.hModel = cgs.gameModels[s1->modelindex2];
593 trap_R_AddRefEntityToScene(&ent);
594 }
595
596 }
597
598 /*
599 ===============
600 CG_Beam
601
602 Also called as an event
603 ===============
604 */
CG_Beam(centity_t * cent)605 void CG_Beam( centity_t *cent ) {
606 refEntity_t ent;
607 entityState_t *s1;
608
609 s1 = ¢->currentState;
610
611 // create the render entity
612 memset (&ent, 0, sizeof(ent));
613 VectorCopy( s1->pos.trBase, ent.origin );
614 VectorCopy( s1->origin2, ent.oldorigin );
615 AxisClear( ent.axis );
616 ent.reType = RT_BEAM;
617
618 ent.renderfx = RF_NOSHADOW;
619
620 // add to refresh list
621 trap_R_AddRefEntityToScene(&ent);
622 }
623
624
625 /*
626 ===============
627 CG_Portal
628 ===============
629 */
CG_Portal(centity_t * cent)630 static void CG_Portal( centity_t *cent ) {
631 refEntity_t ent;
632 entityState_t *s1;
633
634 s1 = ¢->currentState;
635
636 // create the render entity
637 memset (&ent, 0, sizeof(ent));
638 VectorCopy( cent->lerpOrigin, ent.origin );
639 VectorCopy( s1->origin2, ent.oldorigin );
640 ByteToDir( s1->eventParm, ent.axis[0] );
641 PerpendicularVector( ent.axis[1], ent.axis[0] );
642
643 // negating this tends to get the directions like they want
644 // we really should have a camera roll value
645 VectorSubtract( vec3_origin, ent.axis[1], ent.axis[1] );
646
647 CrossProduct( ent.axis[0], ent.axis[1], ent.axis[2] );
648 ent.reType = RT_PORTALSURFACE;
649 ent.oldframe = s1->powerups;
650 ent.frame = s1->frame; // rotation speed
651 ent.skinNum = s1->clientNum/256.0 * 360; // roll offset
652
653 // add to refresh list
654 trap_R_AddRefEntityToScene(&ent);
655 }
656
657
658 /*
659 =========================
660 CG_AdjustPositionForMover
661
662 Also called by client movement prediction code
663 =========================
664 */
CG_AdjustPositionForMover(const vec3_t in,int moverNum,int fromTime,int toTime,vec3_t out)665 void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ) {
666 centity_t *cent;
667 vec3_t oldOrigin, origin, deltaOrigin;
668 vec3_t oldAngles, angles, deltaAngles;
669
670 if ( moverNum <= 0 || moverNum >= ENTITYNUM_MAX_NORMAL ) {
671 VectorCopy( in, out );
672 return;
673 }
674
675 cent = &cg_entities[ moverNum ];
676 if ( cent->currentState.eType != ET_MOVER ) {
677 VectorCopy( in, out );
678 return;
679 }
680
681 BG_EvaluateTrajectory( ¢->currentState.pos, fromTime, oldOrigin );
682 BG_EvaluateTrajectory( ¢->currentState.apos, fromTime, oldAngles );
683
684 BG_EvaluateTrajectory( ¢->currentState.pos, toTime, origin );
685 BG_EvaluateTrajectory( ¢->currentState.apos, toTime, angles );
686
687 VectorSubtract( origin, oldOrigin, deltaOrigin );
688 VectorSubtract( angles, oldAngles, deltaAngles );
689
690 VectorAdd( in, deltaOrigin, out );
691
692 // FIXME: origin change when on a rotating object
693 }
694
695
696 /*
697 =============================
698 CG_InterpolateEntityPosition
699 =============================
700 */
CG_InterpolateEntityPosition(centity_t * cent)701 static void CG_InterpolateEntityPosition( centity_t *cent ) {
702 vec3_t current, next;
703 float f;
704
705 // it would be an internal error to find an entity that interpolates without
706 // a snapshot ahead of the current one
707 if ( cg.nextSnap == NULL ) {
708 CG_Error( "CG_InterpoateEntityPosition: cg.nextSnap == NULL" );
709 }
710
711 f = cg.frameInterpolation;
712
713 // this will linearize a sine or parabolic curve, but it is important
714 // to not extrapolate player positions if more recent data is available
715 BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, current );
716 BG_EvaluateTrajectory( ¢->nextState.pos, cg.nextSnap->serverTime, next );
717
718 cent->lerpOrigin[0] = current[0] + f * ( next[0] - current[0] );
719 cent->lerpOrigin[1] = current[1] + f * ( next[1] - current[1] );
720 cent->lerpOrigin[2] = current[2] + f * ( next[2] - current[2] );
721
722 BG_EvaluateTrajectory( ¢->currentState.apos, cg.snap->serverTime, current );
723 BG_EvaluateTrajectory( ¢->nextState.apos, cg.nextSnap->serverTime, next );
724
725 cent->lerpAngles[0] = LerpAngle( current[0], next[0], f );
726 cent->lerpAngles[1] = LerpAngle( current[1], next[1], f );
727 cent->lerpAngles[2] = LerpAngle( current[2], next[2], f );
728
729 }
730
731 /*
732 ===============
733 CG_CalcEntityLerpPositions
734
735 ===============
736 */
CG_CalcEntityLerpPositions(centity_t * cent)737 static void CG_CalcEntityLerpPositions( centity_t *cent ) {
738
739 // if this player does not want to see extrapolated players
740 if ( !cg_smoothClients.integer ) {
741 // make sure the clients use TR_INTERPOLATE
742 if ( cent->currentState.number < MAX_CLIENTS ) {
743 cent->currentState.pos.trType = TR_INTERPOLATE;
744 cent->nextState.pos.trType = TR_INTERPOLATE;
745 }
746 }
747
748 if ( cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE ) {
749 CG_InterpolateEntityPosition( cent );
750 return;
751 }
752
753 // first see if we can interpolate between two snaps for
754 // linear extrapolated clients
755 if ( cent->interpolate && cent->currentState.pos.trType == TR_LINEAR_STOP &&
756 cent->currentState.number < MAX_CLIENTS) {
757 CG_InterpolateEntityPosition( cent );
758 return;
759 }
760
761 // just use the current frame and evaluate as best we can
762 BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin );
763 BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles );
764
765 // adjust for riding a mover if it wasn't rolled into the predicted
766 // player state
767 if ( cent != &cg.predictedPlayerEntity ) {
768 CG_AdjustPositionForMover( cent->lerpOrigin, cent->currentState.groundEntityNum,
769 cg.snap->serverTime, cg.time, cent->lerpOrigin );
770 }
771 }
772
773 /*
774 ===============
775 CG_TeamBase
776 ===============
777 */
CG_TeamBase(centity_t * cent)778 static void CG_TeamBase( centity_t *cent ) {
779 refEntity_t model;
780 #ifdef MISSIONPACK
781 vec3_t angles;
782 int t, h;
783 float c;
784
785 if ( cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF ) {
786 #else
787 if ( cgs.gametype == GT_CTF) {
788 #endif
789 // show the flag base
790 memset(&model, 0, sizeof(model));
791 model.reType = RT_MODEL;
792 VectorCopy( cent->lerpOrigin, model.lightingOrigin );
793 VectorCopy( cent->lerpOrigin, model.origin );
794 AnglesToAxis( cent->currentState.angles, model.axis );
795 if ( cent->currentState.modelindex == TEAM_RED ) {
796 model.hModel = cgs.media.redFlagBaseModel;
797 }
798 else if ( cent->currentState.modelindex == TEAM_BLUE ) {
799 model.hModel = cgs.media.blueFlagBaseModel;
800 }
801 else {
802 model.hModel = cgs.media.neutralFlagBaseModel;
803 }
804 trap_R_AddRefEntityToScene( &model );
805 }
806 #ifdef MISSIONPACK
807 else if ( cgs.gametype == GT_OBELISK ) {
808 // show the obelisk
809 memset(&model, 0, sizeof(model));
810 model.reType = RT_MODEL;
811 VectorCopy( cent->lerpOrigin, model.lightingOrigin );
812 VectorCopy( cent->lerpOrigin, model.origin );
813 AnglesToAxis( cent->currentState.angles, model.axis );
814
815 model.hModel = cgs.media.overloadBaseModel;
816 trap_R_AddRefEntityToScene( &model );
817 // if hit
818 if ( cent->currentState.frame == 1) {
819 // show hit model
820 // modelindex2 is the health value of the obelisk
821 c = cent->currentState.modelindex2;
822 model.shaderRGBA[0] = 0xff;
823 model.shaderRGBA[1] = c;
824 model.shaderRGBA[2] = c;
825 model.shaderRGBA[3] = 0xff;
826 //
827 model.hModel = cgs.media.overloadEnergyModel;
828 trap_R_AddRefEntityToScene( &model );
829 }
830 // if respawning
831 if ( cent->currentState.frame == 2) {
832 if ( !cent->miscTime ) {
833 cent->miscTime = cg.time;
834 }
835 t = cg.time - cent->miscTime;
836 h = (cg_obeliskRespawnDelay.integer - 5) * 1000;
837 //
838 if (t > h) {
839 c = (float) (t - h) / h;
840 if (c > 1)
841 c = 1;
842 }
843 else {
844 c = 0;
845 }
846 // show the lights
847 AnglesToAxis( cent->currentState.angles, model.axis );
848 //
849 model.shaderRGBA[0] = c * 0xff;
850 model.shaderRGBA[1] = c * 0xff;
851 model.shaderRGBA[2] = c * 0xff;
852 model.shaderRGBA[3] = c * 0xff;
853
854 model.hModel = cgs.media.overloadLightsModel;
855 trap_R_AddRefEntityToScene( &model );
856 // show the target
857 if (t > h) {
858 if ( !cent->muzzleFlashTime ) {
859 trap_S_StartSound (cent->lerpOrigin, ENTITYNUM_NONE, CHAN_BODY, cgs.media.obeliskRespawnSound);
860 cent->muzzleFlashTime = 1;
861 }
862 VectorCopy(cent->currentState.angles, angles);
863 angles[YAW] += (float) 16 * acos(1-c) * 180 / M_PI;
864 AnglesToAxis( angles, model.axis );
865
866 VectorScale( model.axis[0], c, model.axis[0]);
867 VectorScale( model.axis[1], c, model.axis[1]);
868 VectorScale( model.axis[2], c, model.axis[2]);
869
870 model.shaderRGBA[0] = 0xff;
871 model.shaderRGBA[1] = 0xff;
872 model.shaderRGBA[2] = 0xff;
873 model.shaderRGBA[3] = 0xff;
874 //
875 model.origin[2] += 56;
876 model.hModel = cgs.media.overloadTargetModel;
877 trap_R_AddRefEntityToScene( &model );
878 }
879 else {
880 //FIXME: show animated smoke
881 }
882 }
883 else {
884 cent->miscTime = 0;
885 cent->muzzleFlashTime = 0;
886 // modelindex2 is the health value of the obelisk
887 c = cent->currentState.modelindex2;
888 model.shaderRGBA[0] = 0xff;
889 model.shaderRGBA[1] = c;
890 model.shaderRGBA[2] = c;
891 model.shaderRGBA[3] = 0xff;
892 // show the lights
893 model.hModel = cgs.media.overloadLightsModel;
894 trap_R_AddRefEntityToScene( &model );
895 // show the target
896 model.origin[2] += 56;
897 model.hModel = cgs.media.overloadTargetModel;
898 trap_R_AddRefEntityToScene( &model );
899 }
900 }
901 else if ( cgs.gametype == GT_HARVESTER ) {
902 // show harvester model
903 memset(&model, 0, sizeof(model));
904 model.reType = RT_MODEL;
905 VectorCopy( cent->lerpOrigin, model.lightingOrigin );
906 VectorCopy( cent->lerpOrigin, model.origin );
907 AnglesToAxis( cent->currentState.angles, model.axis );
908
909 if ( cent->currentState.modelindex == TEAM_RED ) {
910 model.hModel = cgs.media.harvesterModel;
911 model.customSkin = cgs.media.harvesterRedSkin;
912 }
913 else if ( cent->currentState.modelindex == TEAM_BLUE ) {
914 model.hModel = cgs.media.harvesterModel;
915 model.customSkin = cgs.media.harvesterBlueSkin;
916 }
917 else {
918 model.hModel = cgs.media.harvesterNeutralModel;
919 model.customSkin = 0;
920 }
921 trap_R_AddRefEntityToScene( &model );
922 }
923 #endif
924 }
925
926 /*
927 ===============
928 CG_AddCEntity
929
930 ===============
931 */
932 static void CG_AddCEntity( centity_t *cent ) {
933 // event-only entities will have been dealt with already
934 if ( cent->currentState.eType >= ET_EVENTS ) {
935 return;
936 }
937
938 // calculate the current origin
939 CG_CalcEntityLerpPositions( cent );
940
941 // add automatic effects
942 CG_EntityEffects( cent );
943
944 switch ( cent->currentState.eType ) {
945 default:
946 CG_Error( "Bad entity type: %i\n", cent->currentState.eType );
947 break;
948 case ET_INVISIBLE:
949 case ET_PUSH_TRIGGER:
950 case ET_TELEPORT_TRIGGER:
951 break;
952 case ET_GENERAL:
953 CG_General( cent );
954 break;
955 case ET_PLAYER:
956 CG_Player( cent );
957 break;
958 case ET_ITEM:
959 CG_Item( cent );
960 break;
961 case ET_MISSILE:
962 CG_Missile( cent );
963 break;
964 case ET_MOVER:
965 CG_Mover( cent );
966 break;
967 case ET_BEAM:
968 CG_Beam( cent );
969 break;
970 case ET_PORTAL:
971 CG_Portal( cent );
972 break;
973 case ET_SPEAKER:
974 CG_Speaker( cent );
975 break;
976 case ET_GRAPPLE:
977 CG_Grapple( cent );
978 break;
979 case ET_TEAM:
980 CG_TeamBase( cent );
981 break;
982 }
983 }
984
985 /*
986 ===============
987 CG_AddPacketEntities
988
989 ===============
990 */
991 void CG_AddPacketEntities( void ) {
992 int num;
993 centity_t *cent;
994 playerState_t *ps;
995
996 // set cg.frameInterpolation
997 if ( cg.nextSnap ) {
998 int delta;
999
1000 delta = (cg.nextSnap->serverTime - cg.snap->serverTime);
1001 if ( delta == 0 ) {
1002 cg.frameInterpolation = 0;
1003 } else {
1004 cg.frameInterpolation = (float)( cg.time - cg.snap->serverTime ) / delta;
1005 }
1006 } else {
1007 cg.frameInterpolation = 0; // actually, it should never be used, because
1008 // no entities should be marked as interpolating
1009 }
1010
1011 // the auto-rotating items will all have the same axis
1012 cg.autoAngles[0] = 0;
1013 cg.autoAngles[1] = ( cg.time & 2047 ) * 360 / 2048.0;
1014 cg.autoAngles[2] = 0;
1015
1016 cg.autoAnglesFast[0] = 0;
1017 cg.autoAnglesFast[1] = ( cg.time & 1023 ) * 360 / 1024.0f;
1018 cg.autoAnglesFast[2] = 0;
1019
1020 AnglesToAxis( cg.autoAngles, cg.autoAxis );
1021 AnglesToAxis( cg.autoAnglesFast, cg.autoAxisFast );
1022
1023 // generate and add the entity from the playerstate
1024 ps = &cg.predictedPlayerState;
1025 BG_PlayerStateToEntityState( ps, &cg.predictedPlayerEntity.currentState, qfalse );
1026 CG_AddCEntity( &cg.predictedPlayerEntity );
1027
1028 // lerp the non-predicted value for lightning gun origins
1029 CG_CalcEntityLerpPositions( &cg_entities[ cg.snap->ps.clientNum ] );
1030
1031 // add each entity sent over by the server
1032 for ( num = 0 ; num < cg.snap->numEntities ; num++ ) {
1033 cent = &cg_entities[ cg.snap->entities[ num ].number ];
1034 CG_AddCEntity( cent );
1035 }
1036 }
1037
1038