cg_ents.c (27281B)
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 Foobar; 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 */ 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 MatrixMultiply( 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 */ 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 MatrixMultiply( entity->axis, lerped.axis, tempAxis ); 84 MatrixMultiply( 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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