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 = &cent->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 = &cent->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 = &cent->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( &cent->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 = &cent->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 = &cent->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 = &cent->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 = &cent->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( &cent->currentState.pos, fromTime, oldOrigin );
682 	BG_EvaluateTrajectory( &cent->currentState.apos, fromTime, oldAngles );
683 
684 	BG_EvaluateTrajectory( &cent->currentState.pos, toTime, origin );
685 	BG_EvaluateTrajectory( &cent->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( &cent->currentState.pos, cg.snap->serverTime, current );
716 	BG_EvaluateTrajectory( &cent->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( &cent->currentState.apos, cg.snap->serverTime, current );
723 	BG_EvaluateTrajectory( &cent->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( &cent->currentState.pos, cg.time, cent->lerpOrigin );
763 	BG_EvaluateTrajectory( &cent->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