Projectile.cpp (84164B)
1 /* 2 =========================================================================== 3 4 Doom 3 BFG Edition GPL Source Code 5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 6 7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). 8 9 Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify 10 it under the terms of the GNU General Public License as published by 11 the Free Software Foundation, either version 3 of the License, or 12 (at your option) any later version. 13 14 Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, 15 but WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 GNU General Public License for more details. 18 19 You should have received a copy of the GNU General Public License 20 along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>. 21 22 In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. 23 24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. 25 26 =========================================================================== 27 */ 28 29 #include "../idlib/precompiled.h" 30 #pragma hdrstop 31 32 #include "Game_local.h" 33 34 /* 35 =============================================================================== 36 37 idProjectile 38 39 =============================================================================== 40 */ 41 42 43 idCVar g_projectileDebug( "g_projectileDebug", "0", CVAR_BOOL, "Debug projectiles" ); 44 45 46 // This is used in MP to simulate frames to catchup a projectile's state. Similiar to how players work much lighter weight. 47 48 // This is used in MP to simulate frames to catchup a projectile's state. Similiar to how players work much lighter weight. 49 idArray< idProjectile::simulatedProjectile_t, idProjectile::MAX_SIMULATED_PROJECTILES > idProjectile::projectilesToSimulate; 50 51 static const int BFG_DAMAGE_FREQUENCY = 333; 52 static const float BOUNCE_SOUND_MIN_VELOCITY = 200.0f; 53 static const float BOUNCE_SOUND_MAX_VELOCITY = 400.0f; 54 55 const idEventDef EV_Explode( "<explode>", NULL ); 56 const idEventDef EV_Fizzle( "<fizzle>", NULL ); 57 const idEventDef EV_RadiusDamage( "<radiusdmg>", "e" ); 58 const idEventDef EV_GetProjectileState( "getProjectileState", NULL, 'd' ); 59 60 const idEventDef EV_CreateProjectile( "projectileCreateProjectile", "evv" ); 61 const idEventDef EV_LaunchProjectile( "projectileLaunchProjectile", "vvv" ); 62 const idEventDef EV_SetGravity( "setGravity", "f" ); 63 64 CLASS_DECLARATION( idEntity, idProjectile ) 65 EVENT( EV_Explode, idProjectile::Event_Explode ) 66 EVENT( EV_Fizzle, idProjectile::Event_Fizzle ) 67 EVENT( EV_Touch, idProjectile::Event_Touch ) 68 EVENT( EV_RadiusDamage, idProjectile::Event_RadiusDamage ) 69 EVENT( EV_GetProjectileState, idProjectile::Event_GetProjectileState ) 70 EVENT( EV_CreateProjectile, idProjectile::Event_CreateProjectile ) 71 EVENT( EV_LaunchProjectile, idProjectile::Event_LaunchProjectile ) 72 EVENT( EV_SetGravity, idProjectile::Event_SetGravity ) 73 END_CLASS 74 75 /* 76 ================ 77 idProjectile::idProjectile 78 ================ 79 */ 80 idProjectile::idProjectile() : 81 launchOrigin( 0.0f ), 82 launchAxis( mat3_identity ) { 83 owner = NULL; 84 lightDefHandle = -1; 85 thrust = 0.0f; 86 thrust_end = 0; 87 smokeFly = NULL; 88 smokeFlyTime = 0; 89 state = SPAWNED; 90 lightOffset = vec3_zero; 91 lightStartTime = 0; 92 lightEndTime = 0; 93 lightColor = vec3_zero; 94 damagePower = 1.0f; 95 launchedFromGrabber = false; 96 mTouchTriggers = false; 97 mNoExplodeDisappear = false; 98 memset( &projectileFlags, 0, sizeof( projectileFlags ) ); 99 memset( &renderLight, 0, sizeof( renderLight ) ); 100 101 // note: for net_instanthit projectiles, we will force this back to false at spawn time 102 fl.networkSync = true; 103 } 104 105 /* 106 ================ 107 idProjectile::Spawn 108 ================ 109 */ 110 void idProjectile::Spawn() { 111 physicsObj.SetSelf( this ); 112 physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_ENTITY) idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); 113 physicsObj.SetContents( 0 ); 114 physicsObj.SetClipMask( 0 ); 115 physicsObj.PutToRest(); 116 SetPhysics( &physicsObj ); 117 mNoExplodeDisappear = spawnArgs.GetBool( "no_explode_disappear", mNoExplodeDisappear ); 118 mTouchTriggers = spawnArgs.GetBool( "touch_triggers", mTouchTriggers ); 119 } 120 121 /* 122 ================ 123 idProjectile::Save 124 ================ 125 */ 126 void idProjectile::Save( idSaveGame *savefile ) const { 127 128 owner.Save( savefile ); 129 130 projectileFlags_s flags = projectileFlags; 131 LittleBitField( &flags, sizeof( flags ) ); 132 savefile->Write( &flags, sizeof( flags ) ); 133 134 savefile->WriteFloat( thrust ); 135 savefile->WriteInt( thrust_end ); 136 137 savefile->WriteRenderLight( renderLight ); 138 savefile->WriteInt( (int)lightDefHandle ); 139 savefile->WriteVec3( lightOffset ); 140 savefile->WriteInt( lightStartTime ); 141 savefile->WriteInt( lightEndTime ); 142 savefile->WriteVec3( lightColor ); 143 144 savefile->WriteParticle( smokeFly ); 145 savefile->WriteInt( smokeFlyTime ); 146 147 savefile->WriteInt( originalTimeGroup ); 148 149 savefile->WriteInt( (int)state ); 150 151 savefile->WriteFloat( damagePower ); 152 153 savefile->WriteStaticObject( physicsObj ); 154 savefile->WriteStaticObject( thruster ); 155 } 156 157 /* 158 ================ 159 idProjectile::Restore 160 ================ 161 */ 162 void idProjectile::Restore( idRestoreGame *savefile ) { 163 164 owner.Restore( savefile ); 165 166 savefile->Read( &projectileFlags, sizeof( projectileFlags ) ); 167 LittleBitField( &projectileFlags, sizeof( projectileFlags ) ); 168 169 savefile->ReadFloat( thrust ); 170 savefile->ReadInt( thrust_end ); 171 172 savefile->ReadRenderLight( renderLight ); 173 savefile->ReadInt( (int &)lightDefHandle ); 174 savefile->ReadVec3( lightOffset ); 175 savefile->ReadInt( lightStartTime ); 176 savefile->ReadInt( lightEndTime ); 177 savefile->ReadVec3( lightColor ); 178 179 savefile->ReadParticle( smokeFly ); 180 savefile->ReadInt( smokeFlyTime ); 181 182 savefile->ReadInt( originalTimeGroup ); 183 184 savefile->ReadInt( (int &)state ); 185 186 savefile->ReadFloat( damagePower ); 187 188 savefile->ReadStaticObject( physicsObj ); 189 RestorePhysics( &physicsObj ); 190 191 savefile->ReadStaticObject( thruster ); 192 thruster.SetPhysics( &physicsObj ); 193 194 if ( smokeFly != NULL ) { 195 idVec3 dir; 196 dir = physicsObj.GetLinearVelocity(); 197 dir.NormalizeFast(); 198 gameLocal.smokeParticles->EmitSmoke( smokeFly, gameLocal.time, gameLocal.random.RandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ ); 199 } 200 201 if ( lightDefHandle >= 0 ) { 202 lightDefHandle = gameRenderWorld->AddLightDef( &renderLight ); 203 } 204 } 205 206 /* 207 ================ 208 idProjectile::GetOwner 209 ================ 210 */ 211 idEntity *idProjectile::GetOwner() const { 212 return owner.GetEntity(); 213 } 214 215 /* 216 ================ 217 idProjectile::Create 218 ================ 219 */ 220 void idProjectile::Create( idEntity *owner, const idVec3 &start, const idVec3 &dir ) { 221 idDict args; 222 idStr shaderName; 223 idVec3 light_color; 224 idVec3 light_offset; 225 idVec3 tmp; 226 idMat3 axis; 227 228 Unbind(); 229 230 // align z-axis of model with the direction 231 axis = dir.ToMat3(); 232 tmp = axis[2]; 233 axis[2] = axis[0]; 234 axis[0] = -tmp; 235 236 physicsObj.SetOrigin( start ); 237 physicsObj.SetAxis( axis ); 238 239 physicsObj.GetClipModel()->SetOwner( owner ); 240 241 this->owner = owner; 242 243 memset( &renderLight, 0, sizeof( renderLight ) ); 244 shaderName = spawnArgs.GetString( "mtr_light_shader" ); 245 if ( *(const char *)shaderName ) { 246 renderLight.shader = declManager->FindMaterial( shaderName, false ); 247 renderLight.pointLight = true; 248 renderLight.lightRadius[0] = 249 renderLight.lightRadius[1] = 250 renderLight.lightRadius[2] = spawnArgs.GetFloat( "light_radius" ); 251 #ifdef ID_PC 252 renderLight.lightRadius *= 1.5f; 253 renderLight.forceShadows = true; 254 #endif 255 spawnArgs.GetVector( "light_color", "1 1 1", light_color ); 256 renderLight.shaderParms[0] = light_color[0]; 257 renderLight.shaderParms[1] = light_color[1]; 258 renderLight.shaderParms[2] = light_color[2]; 259 renderLight.shaderParms[3] = 1.0f; 260 } 261 262 spawnArgs.GetVector( "light_offset", "0 0 0", lightOffset ); 263 264 lightStartTime = 0; 265 lightEndTime = 0; 266 smokeFlyTime = 0; 267 268 damagePower = 1.0f; 269 270 if(spawnArgs.GetBool("reset_time_offset", "0")) { 271 renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); 272 } 273 274 UpdateVisuals(); 275 276 state = CREATED; 277 } 278 279 /* 280 ================= 281 idProjectile::~idProjectile 282 ================= 283 */ 284 idProjectile::~idProjectile() { 285 StopSound( SND_CHANNEL_ANY, false ); 286 FreeLightDef(); 287 } 288 289 /* 290 ================= 291 idProjectile::FreeLightDef 292 ================= 293 */ 294 void idProjectile::FreeLightDef() { 295 if ( lightDefHandle != -1 ) { 296 gameRenderWorld->FreeLightDef( lightDefHandle ); 297 lightDefHandle = -1; 298 } 299 } 300 301 /* 302 ================= 303 idProjectile::Launch 304 ================= 305 */ 306 void idProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) { 307 float fuse; 308 float startthrust; 309 float endthrust; 310 idVec3 velocity; 311 idAngles angular_velocity; 312 float linear_friction; 313 float angular_friction; 314 float contact_friction; 315 float bounce; 316 float mass; 317 float speed; 318 float gravity; 319 idVec3 gravVec; 320 idVec3 tmp; 321 idMat3 axis; 322 int thrust_start; 323 int contents; 324 int clipMask; 325 326 // allow characters to throw projectiles during cinematics, but not the player 327 if ( owner.GetEntity() && !owner.GetEntity()->IsType( idPlayer::Type ) ) { 328 cinematic = owner.GetEntity()->cinematic; 329 } else { 330 cinematic = false; 331 } 332 333 thrust = spawnArgs.GetFloat( "thrust" ); 334 startthrust = spawnArgs.GetFloat( "thrust_start" ); 335 endthrust = spawnArgs.GetFloat( "thrust_end" ); 336 337 spawnArgs.GetVector( "velocity", "0 0 0", velocity ); 338 339 speed = velocity.Length() * launchPower; 340 341 damagePower = dmgPower; 342 343 spawnArgs.GetAngles( "angular_velocity", "0 0 0", angular_velocity ); 344 345 linear_friction = spawnArgs.GetFloat( "linear_friction" ); 346 angular_friction = spawnArgs.GetFloat( "angular_friction" ); 347 contact_friction = spawnArgs.GetFloat( "contact_friction" ); 348 bounce = spawnArgs.GetFloat( "bounce" ); 349 mass = spawnArgs.GetFloat( "mass" ); 350 gravity = spawnArgs.GetFloat( "gravity" ); 351 fuse = spawnArgs.GetFloat( "fuse" ); 352 353 projectileFlags.detonate_on_world = spawnArgs.GetBool( "detonate_on_world" ); 354 projectileFlags.detonate_on_actor = spawnArgs.GetBool( "detonate_on_actor" ); 355 projectileFlags.randomShaderSpin = spawnArgs.GetBool( "random_shader_spin" ); 356 357 if ( mass <= 0 ) { 358 gameLocal.Error( "Invalid mass on '%s'\n", GetEntityDefName() ); 359 } 360 361 thrust *= mass; 362 thrust_start = SEC2MS( startthrust ) + gameLocal.time; 363 thrust_end = SEC2MS( endthrust ) + gameLocal.time; 364 365 lightStartTime = 0; 366 lightEndTime = 0; 367 368 if ( health ) { 369 fl.takedamage = true; 370 } 371 372 gravVec = gameLocal.GetGravity(); 373 gravVec.NormalizeFast(); 374 375 Unbind(); 376 377 // align z-axis of model with the direction 378 axis = dir.ToMat3(); 379 tmp = axis[2]; 380 axis[2] = axis[0]; 381 axis[0] = -tmp; 382 383 contents = 0; 384 clipMask = MASK_SHOT_RENDERMODEL; 385 if ( spawnArgs.GetBool( "detonate_on_trigger" ) ) { 386 contents |= CONTENTS_TRIGGER; 387 } 388 if ( !spawnArgs.GetBool( "no_contents" ) ) { 389 contents |= CONTENTS_PROJECTILE; 390 clipMask |= CONTENTS_PROJECTILE; 391 } 392 393 if ( !idStr::Cmp( this->GetEntityDefName(), "projectile_helltime_killer" ) ) { 394 contents = CONTENTS_MOVEABLECLIP; 395 clipMask = CONTENTS_MOVEABLECLIP; 396 fuse = 10.0f; 397 } 398 399 // don't do tracers on client, we don't know origin and direction 400 if ( spawnArgs.GetBool( "tracers" ) && gameLocal.random.RandomFloat() > 0.5f ) { 401 SetModel( spawnArgs.GetString( "model_tracer" ) ); 402 projectileFlags.isTracer = true; 403 } 404 405 physicsObj.SetMass( mass ); 406 physicsObj.SetFriction( linear_friction, angular_friction, contact_friction ); 407 if ( contact_friction == 0.0f ) { 408 physicsObj.NoContact(); 409 } 410 physicsObj.SetBouncyness( bounce ); 411 physicsObj.SetGravity( gravVec * gravity ); 412 physicsObj.SetContents( contents ); 413 physicsObj.SetClipMask( clipMask ); 414 physicsObj.SetLinearVelocity( axis[ 2 ] * speed + pushVelocity ); 415 physicsObj.SetAngularVelocity( angular_velocity.ToAngularVelocity() * axis ); 416 physicsObj.SetOrigin( start ); 417 physicsObj.SetAxis( axis ); 418 419 launchOrigin = start; 420 launchAxis = axis; 421 422 thruster.SetPosition( &physicsObj, 0, idVec3( GetPhysics()->GetBounds()[ 0 ].x, 0, 0 ) ); 423 424 if ( !common->IsClient() || fl.skipReplication ) { 425 if ( fuse <= 0 ) { 426 // run physics for 1 second 427 RunPhysics(); 428 PostEventMS( &EV_Remove, spawnArgs.GetInt( "remove_time", "1500" ) ); 429 } else if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) { 430 fuse -= timeSinceFire; 431 if ( fuse < 0.0f ) { 432 fuse = 0.0f; 433 } 434 PostEventSec( &EV_Explode, fuse ); 435 } else { 436 fuse -= timeSinceFire; 437 if ( fuse < 0.0f ) { 438 fuse = 0.0f; 439 } 440 PostEventSec( &EV_Fizzle, fuse ); 441 } 442 } 443 444 if ( projectileFlags.isTracer ) { 445 StartSound( "snd_tracer", SND_CHANNEL_BODY, 0, false, NULL ); 446 } else { 447 StartSound( "snd_fly", SND_CHANNEL_BODY, 0, false, NULL ); 448 } 449 450 smokeFlyTime = 0; 451 const char *smokeName = spawnArgs.GetString( "smoke_fly" ); 452 if ( *smokeName != '\0' ) { 453 smokeFly = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) ); 454 smokeFlyTime = gameLocal.time; 455 } 456 457 originalTimeGroup = timeGroup; 458 459 // used for the plasma bolts but may have other uses as well 460 if ( projectileFlags.randomShaderSpin ) { 461 float f = gameLocal.random.RandomFloat(); 462 f *= 0.5f; 463 renderEntity.shaderParms[SHADERPARM_DIVERSITY] = f; 464 } 465 466 UpdateVisuals(); 467 468 state = LAUNCHED; 469 } 470 471 /* 472 ================ 473 idProjectile::Think 474 ================ 475 */ 476 void idProjectile::Think() { 477 478 if ( thinkFlags & TH_THINK ) { 479 if ( thrust && ( gameLocal.time < thrust_end ) ) { 480 // evaluate force 481 thruster.SetForce( GetPhysics()->GetAxis()[ 0 ] * thrust ); 482 thruster.Evaluate( gameLocal.time ); 483 } 484 } 485 486 if ( mTouchTriggers ) { 487 TouchTriggers(); 488 } 489 490 // if the projectile owner is a player 491 if ( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::Type ) ) { 492 idPlayer *player = static_cast<idPlayer *>( owner.GetEntity() ); 493 494 // Remove any projectiles spectators threw. 495 if( player != NULL && player->spectating ) { 496 PostEventMS( &EV_Remove, 0 ); 497 } 498 } 499 500 // run physics 501 RunPhysics(); 502 503 DecayOriginAndAxisDelta(); 504 505 Present(); 506 507 AddParticlesAndLight(); 508 } 509 510 /* 511 ================= 512 idProjectile::AddParticlesAndLight 513 ================= 514 */ 515 void idProjectile::AddParticlesAndLight() { 516 // add the particles 517 if ( smokeFly != NULL && smokeFlyTime && !IsHidden() ) { 518 idVec3 dir = -GetPhysics()->GetLinearVelocity(); 519 dir.Normalize(); 520 SetTimeState ts(originalTimeGroup); 521 522 if ( !gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.RandomFloat(), GetPhysics()->GetOrigin(), dir.ToMat3(), timeGroup /*_D3XP*/ ) ) { 523 smokeFlyTime = gameLocal.time; 524 } 525 } 526 527 // add the light 528 if ( renderLight.lightRadius.x > 0.0f && g_projectileLights.GetBool() ) { 529 renderLight.origin = GetPhysics()->GetOrigin() + GetPhysics()->GetAxis() * lightOffset; 530 renderLight.axis = GetPhysics()->GetAxis(); 531 if ( ( lightDefHandle != -1 ) ) { 532 if ( lightEndTime > 0 && gameLocal.time <= lightEndTime ) { 533 idVec3 color( 0, 0, 0 ); 534 if ( gameLocal.time < lightEndTime ) { 535 float frac = ( float )( gameLocal.time - lightStartTime ) / ( float )( lightEndTime - lightStartTime ); 536 color.Lerp( lightColor, color, frac ); 537 } 538 renderLight.shaderParms[SHADERPARM_RED] = color.x; 539 renderLight.shaderParms[SHADERPARM_GREEN] = color.y; 540 renderLight.shaderParms[SHADERPARM_BLUE] = color.z; 541 } 542 gameRenderWorld->UpdateLightDef( lightDefHandle, &renderLight ); 543 } else { 544 lightDefHandle = gameRenderWorld->AddLightDef( &renderLight ); 545 } 546 } 547 } 548 549 /* 550 ================= 551 idProjectile::Collide 552 ================= 553 */ 554 bool idProjectile::Collide( const trace_t &collision, const idVec3 &velocity ) { 555 idEntity *ent; 556 idEntity *ignore; 557 const char *damageDefName; 558 idVec3 dir; 559 float push; 560 float damageScale; 561 562 if ( state == EXPLODED || state == FIZZLED ) { 563 return true; 564 } 565 566 const bool isHitscan = spawnArgs.GetBool( "net_instanthit" ); 567 568 // hanlde slow projectiles here. 569 if ( common->IsClient() && !isHitscan ) { 570 571 // This is a replicated slow projectile, predict the explosion. 572 if ( ClientPredictionCollide( this, spawnArgs, collision, velocity, !isHitscan ) ) { 573 Explode( collision, NULL ); 574 return true; 575 } 576 577 } 578 579 // remove projectile when a 'noimpact' surface is hit 580 if ( ( collision.c.material != NULL ) && ( collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) ) { 581 PostEventMS( &EV_Remove, 0 ); 582 common->DPrintf( "Projectile collision no impact\n" ); 583 return true; 584 } 585 586 // get the entity the projectile collided with 587 ent = gameLocal.entities[ collision.c.entityNum ]; 588 if ( ent == owner.GetEntity() ) { 589 assert( 0 ); 590 return true; 591 } 592 593 // just get rid of the projectile when it hits a player in noclip 594 if ( ent->IsType( idPlayer::Type ) && static_cast<idPlayer *>( ent )->noclip ) { 595 PostEventMS( &EV_Remove, 0 ); 596 return true; 597 } 598 599 // direction of projectile 600 dir = velocity; 601 dir.Normalize(); 602 603 // projectiles can apply an additional impulse next to the rigid body physics impulse 604 if ( spawnArgs.GetFloat( "push", "0", push ) && push > 0.0f ) { 605 if ( !common->IsClient() ) { 606 ent->ApplyImpulse( this, collision.c.id, collision.c.point, push * dir ); 607 } 608 } 609 610 // MP: projectiles open doors 611 if ( common->IsMultiplayer() && ent->IsType( idDoor::Type ) && !static_cast< idDoor * >(ent)->IsOpen() && !ent->spawnArgs.GetBool( "no_touch" ) ) { 612 if ( !common->IsClient() ) { 613 ent->ProcessEvent( &EV_Activate , this ); 614 } 615 } 616 617 if ( ent->IsType( idActor::Type ) || ( ent->IsType( idAFAttachment::Type ) && static_cast<const idAFAttachment*>(ent)->GetBody()->IsType( idActor::Type ) ) ) { 618 if ( !projectileFlags.detonate_on_actor ) { 619 return false; 620 } 621 } else { 622 if ( !projectileFlags.detonate_on_world ) { 623 if ( !StartSound( "snd_ricochet", SND_CHANNEL_ITEM, 0, true, NULL ) ) { 624 float len = velocity.Length(); 625 if ( len > BOUNCE_SOUND_MIN_VELOCITY ) { 626 SetSoundVolume( len > BOUNCE_SOUND_MAX_VELOCITY ? 1.0f : idMath::Sqrt( len - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / idMath::Sqrt( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) ) ); 627 StartSound( "snd_bounce", SND_CHANNEL_ANY, 0, true, NULL ); 628 } 629 } 630 return false; 631 } 632 } 633 634 SetOrigin( collision.endpos ); 635 SetAxis( collision.endAxis ); 636 637 // To see the explosion on the collision surface instead of 638 // at the muzzle, clear the deltas. 639 CreateDeltasFromOldOriginAndAxis( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); 640 641 // unlink the clip model because we no longer need it 642 GetPhysics()->UnlinkClip(); 643 644 damageDefName = spawnArgs.GetString( "def_damage" ); 645 646 ignore = NULL; 647 648 // if the projectile causes a damage effect 649 if ( spawnArgs.GetBool( "impact_damage_effect" ) ) { 650 // if the hit entity has a special damage effect 651 if ( ent->spawnArgs.GetBool( "bleed" ) ) { 652 ent->AddDamageEffect( collision, velocity, damageDefName ); 653 } else { 654 AddDefaultDamageEffect( collision, velocity ); 655 } 656 } 657 658 // if the hit entity takes damage 659 if ( ent->fl.takedamage ) { 660 if ( damagePower ) { 661 damageScale = damagePower; 662 } else { 663 damageScale = 1.0f; 664 } 665 666 // if the projectile owner is a player 667 if ( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::Type ) ) { 668 // if the projectile hit an actor 669 if ( ent->IsType( idActor::Type ) ) { 670 idPlayer *player = static_cast<idPlayer *>( owner.GetEntity() ); 671 player->AddProjectileHits( 1 ); 672 damageScale *= player->PowerUpModifier( PROJECTILE_DAMAGE ); 673 } 674 } 675 676 if ( damageDefName[0] != '\0' ) { 677 678 bool killedByImpact = true; 679 680 if( ent->health <= 0 ) { 681 killedByImpact = false; 682 } 683 684 // Only handle the server's own attacks here. Attacks by other players on the server occur through 685 // reliable messages. 686 if ( !common->IsMultiplayer() || common->IsClient() || ( common->IsServer() && owner.GetEntityNum() == gameLocal.GetLocalClientNum() ) || ( common->IsServer() && !isHitscan ) ) { 687 ent->Damage( this, owner.GetEntity(), dir, damageDefName, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) ); 688 } else { 689 if( ent->IsType( idPlayer::Type ) == false && common->IsServer() ) { 690 ent->Damage( this, owner.GetEntity(), dir, damageDefName, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) ); 691 } 692 } 693 694 // Check if we are hitting an actor. and see if we killed him. 695 if( !common->IsClient() && ent->health <= 0 && killedByImpact ) { 696 if ( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::Type ) ) { 697 if ( ent->IsType( idActor::Type ) && ent != owner.GetEntity() ) { 698 idPlayer *player = static_cast<idPlayer *>( owner.GetEntity() ); 699 player->AddProjectileKills(); 700 } 701 } 702 703 } 704 705 ignore = ent; 706 } 707 } 708 709 Explode( collision, ignore ); 710 711 if ( !common->IsClient() && owner.GetEntity() != NULL && owner.GetEntity()->IsType( idPlayer::Type ) ) { 712 idPlayer *player = static_cast<idPlayer *>( owner.GetEntity() ); 713 int kills = player->GetProjectileKills(); 714 715 if( kills >= 2 && common->IsMultiplayer() && strstr( GetName(), "projectile_rocket" ) != 0 ) { 716 player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_MP_KILL_2_GUYS_IN_ROOM_WITH_BFG ); 717 } 718 719 // projectile is done dealing damage. 720 player->ResetProjectileKills(); 721 } 722 723 return true; 724 } 725 726 /* 727 ================= 728 idProjectile::DefaultDamageEffect 729 ================= 730 */ 731 void idProjectile::DefaultDamageEffect( idEntity *soundEnt, const idDict &projectileDef, const trace_t &collision, const idVec3 &velocity ) { 732 const char *decal, *sound, *typeName; 733 surfTypes_t materialType; 734 735 if ( collision.c.material != NULL ) { 736 materialType = collision.c.material->GetSurfaceType(); 737 } else { 738 materialType = SURFTYPE_METAL; 739 } 740 741 // get material type name 742 typeName = gameLocal.sufaceTypeNames[ materialType ]; 743 744 // play impact sound 745 sound = projectileDef.GetString( va( "snd_%s", typeName ) ); 746 if ( *sound == '\0' ) { 747 sound = projectileDef.GetString( "snd_metal" ); 748 } 749 if ( *sound == '\0' ) { 750 sound = projectileDef.GetString( "snd_impact" ); 751 } 752 if ( *sound != '\0' ) { 753 soundEnt->StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, 0, false, NULL ); 754 } 755 756 // project decal 757 decal = projectileDef.GetString( va( "mtr_detonate_%s", typeName ) ); 758 if ( *decal == '\0' ) { 759 decal = projectileDef.GetString( "mtr_detonate" ); 760 } 761 if ( *decal != '\0' ) { 762 gameLocal.ProjectDecal( collision.c.point, -collision.c.normal, 8.0f, true, projectileDef.GetFloat( "decal_size", "6.0" ), decal ); 763 } 764 } 765 766 /* 767 ================= 768 idProjectile::AddDefaultDamageEffect 769 ================= 770 */ 771 void idProjectile::AddDefaultDamageEffect( const trace_t &collision, const idVec3 &velocity ) { 772 773 DefaultDamageEffect( this, spawnArgs, collision, velocity ); 774 775 if ( common->IsServer() && fl.networkSync ) { 776 idBitMsg msg; 777 byte msgBuf[MAX_EVENT_PARAM_SIZE]; 778 lobbyUserID_t excluding; 779 780 if ( spawnArgs.GetBool( "net_instanthit" ) && owner.GetEntityNum() < MAX_PLAYERS ) { 781 excluding = gameLocal.lobbyUserIDs[owner.GetEntityNum()]; 782 } 783 784 msg.InitWrite( msgBuf, sizeof( msgBuf ) ); 785 msg.BeginWriting(); 786 msg.WriteFloat( collision.c.point[0] ); 787 msg.WriteFloat( collision.c.point[1] ); 788 msg.WriteFloat( collision.c.point[2] ); 789 msg.WriteDir( collision.c.normal, 24 ); 790 msg.WriteLong( ( collision.c.material != NULL ) ? gameLocal.ServerRemapDecl( -1, DECL_MATERIAL, collision.c.material->Index() ) : -1 ); 791 msg.WriteFloat( velocity[0], 5, 10 ); 792 msg.WriteFloat( velocity[1], 5, 10 ); 793 msg.WriteFloat( velocity[2], 5, 10 ); 794 ServerSendEvent( EVENT_DAMAGE_EFFECT, &msg, false, excluding ); 795 } 796 } 797 798 /* 799 ================ 800 idProjectile::Killed 801 ================ 802 */ 803 void idProjectile::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { 804 if ( spawnArgs.GetBool( "detonate_on_death" ) ) { 805 trace_t collision; 806 807 memset( &collision, 0, sizeof( collision ) ); 808 collision.endAxis = GetPhysics()->GetAxis(); 809 collision.endpos = GetPhysics()->GetOrigin(); 810 collision.c.point = GetPhysics()->GetOrigin(); 811 collision.c.normal.Set( 0, 0, 1 ); 812 Explode( collision, NULL ); 813 physicsObj.ClearContacts(); 814 physicsObj.PutToRest(); 815 } else { 816 Fizzle(); 817 } 818 } 819 820 /* 821 ================ 822 idProjectile::Fizzle 823 ================ 824 */ 825 void idProjectile::Fizzle() { 826 827 if ( state == EXPLODED || state == FIZZLED ) { 828 return; 829 } 830 831 StopSound( SND_CHANNEL_BODY, false ); 832 StartSound( "snd_fizzle", SND_CHANNEL_BODY, 0, false, NULL ); 833 834 // fizzle FX 835 const char *psystem = spawnArgs.GetString( "smoke_fuse" ); 836 if ( psystem != NULL && *psystem != NULL ) { 837 //FIXME:SMOKE gameLocal.particles->SpawnParticles( GetPhysics()->GetOrigin(), vec3_origin, psystem ); 838 } 839 840 // we need to work out how long the effects last and then remove them at that time 841 // for example, bullets have no real effects 842 if ( smokeFly && smokeFlyTime ) { 843 smokeFlyTime = 0; 844 } 845 846 fl.takedamage = false; 847 physicsObj.SetContents( 0 ); 848 physicsObj.GetClipModel()->Unlink(); 849 physicsObj.PutToRest(); 850 851 Hide(); 852 FreeLightDef(); 853 854 state = FIZZLED; 855 856 if ( common->IsClient() && !fl.skipReplication ) { 857 return; 858 } 859 860 CancelEvents( &EV_Fizzle ); 861 PostEventMS( &EV_Remove, spawnArgs.GetInt( "remove_time", "1500" ) ); 862 } 863 864 /* 865 ================ 866 idProjectile::Event_RadiusDamage 867 ================ 868 */ 869 void idProjectile::Event_RadiusDamage( idEntity *ignore ) { 870 const char *splash_damage = spawnArgs.GetString( "def_splash_damage" ); 871 if ( splash_damage[0] != '\0' ) { 872 gameLocal.RadiusDamage( physicsObj.GetOrigin(), this, owner.GetEntity(), ignore, this, splash_damage, damagePower ); 873 } 874 } 875 876 /* 877 ================ 878 idProjectile::Event_RadiusDamage 879 ================ 880 */ 881 void idProjectile::Event_GetProjectileState() { 882 idThread::ReturnInt( state ); 883 } 884 885 /* 886 ================ 887 idProjectile::Explode 888 ================ 889 */ 890 void idProjectile::Explode( const trace_t &collision, idEntity *ignore ) { 891 const char *fxname, *light_shader, *sndExplode; 892 float light_fadetime; 893 idVec3 normal; 894 int removeTime; 895 896 if ( mNoExplodeDisappear ) { 897 PostEventMS( &EV_Remove, 0 ); 898 return; 899 } 900 901 if ( state == EXPLODED || state == FIZZLED ) { 902 return; 903 } 904 905 // activate rumble for player 906 idPlayer *player = gameLocal.GetLocalPlayer(); 907 const bool isHitscan = spawnArgs.GetBool( "net_instanthit" ); 908 if ( player != NULL && isHitscan == false ) { 909 910 // damage 911 const char *damageDefName = spawnArgs.GetString( "def_damage" ); 912 const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); 913 int damage; 914 if ( damageDef != NULL ) { 915 damage = damageDef->GetInt( "damage" ); 916 } else { 917 damage = 200; 918 } 919 float damageScale = idMath::ClampFloat( 0.25f, 1.0f, (float)damage * ( 1.0f / 200.0f ) ); // 50...200 -> min...max rumble 920 921 // distance 922 float dist = ( GetPhysics()->GetOrigin() - player->GetPhysics()->GetOrigin() ).LengthFast(); 923 float distScale = 1.0f - idMath::ClampFloat( 0.0f, 1.0f, ( dist * ( 1.0f / 4000.0f ) ) + 0.25f ); // 0...4000 -> max...min rumble 924 925 distScale *= damageScale; // apply damage scale here, weaker damage produces less rumble 926 927 // determine rumble 928 float highMag = distScale; 929 int highDuration = idMath::Ftoi( 300.0f * distScale ); 930 float lowMag = distScale * 0.75f; 931 int lowDuration = idMath::Ftoi( 500.0f * distScale ); 932 933 player->SetControllerShake( highMag, highDuration, lowMag, lowDuration ); 934 } 935 936 // stop sound 937 StopSound( SND_CHANNEL_BODY2, false ); 938 939 // play explode sound 940 switch ( ( int ) damagePower ) { 941 case 2: sndExplode = "snd_explode2"; break; 942 case 3: sndExplode = "snd_explode3"; break; 943 case 4: sndExplode = "snd_explode4"; break; 944 default: sndExplode = "snd_explode"; break; 945 } 946 StartSound( sndExplode, SND_CHANNEL_BODY, 0, true, NULL ); 947 948 // we need to work out how long the effects last and then remove them at that time 949 // for example, bullets have no real effects 950 if ( smokeFly && smokeFlyTime ) { 951 smokeFlyTime = 0; 952 } 953 954 Hide(); 955 FreeLightDef(); 956 957 if ( spawnArgs.GetVector( "detonation_axis", "", normal ) ) { 958 GetPhysics()->SetAxis( normal.ToMat3() ); 959 } 960 GetPhysics()->SetOrigin( collision.endpos + 2.0f * collision.c.normal ); 961 962 // default remove time 963 if( fl.skipReplication && !spawnArgs.GetBool( "net_instanthit" ) ) { 964 removeTime = spawnArgs.GetInt( "remove_time", "6000" ); 965 } else { 966 removeTime = spawnArgs.GetInt( "remove_time", "1500" ); 967 } 968 969 // change the model, usually to a PRT 970 fxname = NULL; 971 if ( g_testParticle.GetInteger() == TEST_PARTICLE_IMPACT ) { 972 fxname = g_testParticleName.GetString(); 973 } else { 974 fxname = spawnArgs.GetString( "model_detonate" ); 975 } 976 977 int surfaceType = collision.c.material != NULL ? collision.c.material->GetSurfaceType() : SURFTYPE_METAL; 978 if ( !( fxname != NULL && *fxname != NULL ) ) { 979 if ( ( surfaceType == SURFTYPE_NONE ) || ( surfaceType == SURFTYPE_METAL ) || ( surfaceType == SURFTYPE_STONE ) ) { 980 fxname = spawnArgs.GetString( "model_smokespark" ); 981 } else if ( surfaceType == SURFTYPE_RICOCHET ) { 982 fxname = spawnArgs.GetString( "model_ricochet" ); 983 } else { 984 fxname = spawnArgs.GetString( "model_smoke" ); 985 } 986 } 987 988 // If the explosion is in liquid, spawn a particle splash 989 idVec3 testOrg = GetPhysics()->GetOrigin(); 990 int testC = gameLocal.clip.Contents( testOrg, NULL, mat3_identity, CONTENTS_WATER, this ); 991 if ( testC & CONTENTS_WATER ) { 992 idFuncEmitter *splashEnt; 993 idDict splashArgs; 994 995 splashArgs.Set( "model", "sludgebulletimpact.prt" ); 996 splashArgs.Set( "start_off", "1" ); 997 splashEnt = static_cast<idFuncEmitter *>( gameLocal.SpawnEntityType( idFuncEmitter::Type, &splashArgs ) ); 998 999 splashEnt->GetPhysics()->SetOrigin( testOrg ); 1000 splashEnt->PostEventMS( &EV_Activate, 0, this ); 1001 splashEnt->PostEventMS( &EV_Remove, 1500 ); 1002 1003 // HACK - if this is a chaingun bullet, don't do the normal effect 1004 if ( !idStr::Cmp( spawnArgs.GetString( "def_damage" ), "damage_bullet_chaingun" ) ) { 1005 fxname = NULL; 1006 } 1007 } 1008 1009 if ( fxname && *fxname ) { 1010 SetModel( fxname ); 1011 renderEntity.shaderParms[SHADERPARM_RED] = 1012 renderEntity.shaderParms[SHADERPARM_GREEN] = 1013 renderEntity.shaderParms[SHADERPARM_BLUE] = 1014 renderEntity.shaderParms[SHADERPARM_ALPHA] = 1.0f; 1015 renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); 1016 renderEntity.shaderParms[SHADERPARM_DIVERSITY] = gameLocal.random.CRandomFloat(); 1017 Show(); 1018 removeTime = ( removeTime > 3000 ) ? removeTime : 3000; 1019 } 1020 1021 // explosion light 1022 light_shader = spawnArgs.GetString( "mtr_explode_light_shader" ); 1023 1024 if ( gameLocal.mpGame.IsGametypeFlagBased() && gameLocal.serverInfo.GetBool("si_midnight") ) 1025 { 1026 light_shader = "lights/midnight_grenade"; 1027 } 1028 1029 if ( *light_shader ) { 1030 renderLight.shader = declManager->FindMaterial( light_shader, false ); 1031 renderLight.pointLight = true; 1032 renderLight.lightRadius[0] = 1033 renderLight.lightRadius[1] = 1034 renderLight.lightRadius[2] = spawnArgs.GetFloat( "explode_light_radius" ); 1035 #ifdef ID_PC 1036 renderLight.lightRadius *= 2.0f; 1037 renderLight.forceShadows = true; 1038 #endif 1039 1040 // Midnight ctf 1041 if ( gameLocal.mpGame.IsGametypeFlagBased() && gameLocal.serverInfo.GetBool("si_midnight") ) { 1042 renderLight.lightRadius[0] = 1043 renderLight.lightRadius[1] = 1044 renderLight.lightRadius[2] = spawnArgs.GetFloat( "explode_light_radius" ) * 2; 1045 } 1046 1047 spawnArgs.GetVector( "explode_light_color", "1 1 1", lightColor ); 1048 renderLight.shaderParms[SHADERPARM_RED] = lightColor.x; 1049 renderLight.shaderParms[SHADERPARM_GREEN] = lightColor.y; 1050 renderLight.shaderParms[SHADERPARM_BLUE] = lightColor.z; 1051 renderLight.shaderParms[SHADERPARM_ALPHA] = 1.0f; 1052 renderLight.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); 1053 1054 // Midnight ctf 1055 if ( gameLocal.mpGame.IsGametypeFlagBased() && gameLocal.serverInfo.GetBool("si_midnight") ) { 1056 light_fadetime = 3.0f; 1057 } else { 1058 light_fadetime = spawnArgs.GetFloat( "explode_light_fadetime", "0.5" ); 1059 } 1060 1061 lightStartTime = gameLocal.time; 1062 lightEndTime = MSEC_ALIGN_TO_FRAME( gameLocal.time + SEC2MS( light_fadetime ) ); 1063 BecomeActive( TH_THINK ); 1064 } 1065 1066 fl.takedamage = false; 1067 physicsObj.SetContents( 0 ); 1068 physicsObj.PutToRest(); 1069 1070 state = EXPLODED; 1071 1072 if ( common->IsClient() && !fl.skipReplication ) { 1073 return; 1074 } 1075 1076 // alert the ai 1077 gameLocal.AlertAI( owner.GetEntity() ); 1078 1079 // bind the projectile to the impact entity if necesary 1080 if ( gameLocal.entities[collision.c.entityNum] && spawnArgs.GetBool( "bindOnImpact" ) ) { 1081 Bind( gameLocal.entities[collision.c.entityNum], true ); 1082 } 1083 1084 // splash damage 1085 if ( !projectileFlags.noSplashDamage ) { 1086 float delay = spawnArgs.GetFloat( "delay_splash" ); 1087 if ( delay ) { 1088 if ( removeTime < delay * 1000 ) { 1089 removeTime = ( delay + 0.10 ) * 1000; 1090 } 1091 PostEventSec( &EV_RadiusDamage, delay, ignore ); 1092 } else { 1093 Event_RadiusDamage( ignore ); 1094 } 1095 } 1096 1097 // spawn debris entities 1098 int fxdebris = spawnArgs.GetInt( "debris_count" ); 1099 if ( fxdebris ) { 1100 const idDict *debris = gameLocal.FindEntityDefDict( "projectile_debris", false ); 1101 if ( debris ) { 1102 int amount = gameLocal.random.RandomInt( fxdebris ); 1103 for ( int i = 0; i < amount; i++ ) { 1104 idEntity *ent; 1105 idVec3 dir; 1106 dir.x = gameLocal.random.CRandomFloat() * 4.0f; 1107 dir.y = gameLocal.random.CRandomFloat() * 4.0f; 1108 dir.z = gameLocal.random.RandomFloat() * 8.0f; 1109 dir.Normalize(); 1110 1111 gameLocal.SpawnEntityDef( *debris, &ent, false ); 1112 if ( ent == NULL || !ent->IsType( idDebris::Type ) ) { 1113 gameLocal.Error( "'projectile_debris' is not an idDebris" ); 1114 return; 1115 } 1116 1117 idDebris *debris = static_cast<idDebris *>(ent); 1118 debris->Create( owner.GetEntity(), physicsObj.GetOrigin(), dir.ToMat3() ); 1119 debris->Launch(); 1120 } 1121 } 1122 debris = gameLocal.FindEntityDefDict( "projectile_shrapnel", false ); 1123 if ( debris ) { 1124 int amount = gameLocal.random.RandomInt( fxdebris ); 1125 for ( int i = 0; i < amount; i++ ) { 1126 idEntity *ent; 1127 idVec3 dir; 1128 dir.x = gameLocal.random.CRandomFloat() * 8.0f; 1129 dir.y = gameLocal.random.CRandomFloat() * 8.0f; 1130 dir.z = gameLocal.random.RandomFloat() * 8.0f + 8.0f; 1131 dir.Normalize(); 1132 1133 gameLocal.SpawnEntityDef( *debris, &ent, false ); 1134 if ( ent == NULL || !ent->IsType( idDebris::Type ) ) { 1135 gameLocal.Error( "'projectile_shrapnel' is not an idDebris" ); 1136 break; 1137 } 1138 1139 idDebris *debris = static_cast<idDebris *>(ent); 1140 debris->Create( owner.GetEntity(), physicsObj.GetOrigin(), dir.ToMat3() ); 1141 debris->Launch(); 1142 } 1143 } 1144 } 1145 1146 CancelEvents( &EV_Explode ); 1147 PostEventMS( &EV_Remove, removeTime ); 1148 } 1149 1150 /* 1151 ================ 1152 idProjectile::GetVelocity 1153 ================ 1154 */ 1155 idVec3 idProjectile::GetVelocity( const idDict *projectile ) { 1156 idVec3 velocity; 1157 1158 projectile->GetVector( "velocity", "0 0 0", velocity ); 1159 return velocity; 1160 } 1161 1162 /* 1163 ================ 1164 idProjectile::GetGravity 1165 ================ 1166 */ 1167 idVec3 idProjectile::GetGravity( const idDict *projectile ) { 1168 float gravity; 1169 1170 gravity = projectile->GetFloat( "gravity" ); 1171 return idVec3( 0, 0, -gravity ); 1172 } 1173 1174 /* 1175 ================ 1176 idProjectile::Event_Explode 1177 ================ 1178 */ 1179 void idProjectile::Event_Explode() { 1180 trace_t collision; 1181 1182 memset( &collision, 0, sizeof( collision ) ); 1183 collision.endAxis = GetPhysics()->GetAxis(); 1184 collision.endpos = GetPhysics()->GetOrigin(); 1185 collision.c.point = GetPhysics()->GetOrigin(); 1186 collision.c.normal.Set( 0, 0, 1 ); 1187 AddDefaultDamageEffect( collision, collision.c.normal ); 1188 Explode( collision, NULL ); 1189 } 1190 1191 /* 1192 ================ 1193 idProjectile::Event_Fizzle 1194 ================ 1195 */ 1196 void idProjectile::Event_Fizzle() { 1197 Fizzle(); 1198 } 1199 1200 /* 1201 ================ 1202 idProjectile::Event_Touch 1203 ================ 1204 */ 1205 void idProjectile::Event_Touch( idEntity *other, trace_t *trace ) { 1206 if ( common->IsClient() ) { 1207 return; 1208 } 1209 1210 if ( IsHidden() ) { 1211 return; 1212 } 1213 1214 // Projectiles do not collide with flags 1215 if ( other->IsType( idItemTeam::Type ) ) { 1216 return; 1217 } 1218 1219 if ( other != owner.GetEntity() ) { 1220 trace_t collision; 1221 1222 memset( &collision, 0, sizeof( collision ) ); 1223 collision.endAxis = GetPhysics()->GetAxis(); 1224 collision.endpos = GetPhysics()->GetOrigin(); 1225 collision.c.point = GetPhysics()->GetOrigin(); 1226 collision.c.normal.Set( 0, 0, 1 ); 1227 AddDefaultDamageEffect( collision, collision.c.normal ); 1228 Explode( collision, NULL ); 1229 } 1230 } 1231 1232 /* 1233 ================ 1234 idProjectile::CatchProjectile 1235 ================ 1236 */ 1237 void idProjectile::CatchProjectile( idEntity* o, const char* reflectName ) { 1238 idEntity *prevowner = owner.GetEntity(); 1239 1240 owner = o; 1241 physicsObj.GetClipModel()->SetOwner( o ); 1242 1243 if ( this->IsType( idGuidedProjectile::Type ) ) { 1244 idGuidedProjectile *proj = static_cast<idGuidedProjectile*>(this); 1245 1246 proj->SetEnemy( prevowner ); 1247 } 1248 1249 idStr s = spawnArgs.GetString( "def_damage" ); 1250 s += reflectName; 1251 1252 const idDict *damageDef = gameLocal.FindEntityDefDict( s, false ); 1253 if ( damageDef ) { 1254 spawnArgs.Set( "def_damage", s ); 1255 } 1256 } 1257 1258 /* 1259 ================ 1260 idProjectile::GetProjectileState 1261 ================ 1262 */ 1263 int idProjectile::GetProjectileState() { 1264 1265 return (int)state; 1266 } 1267 1268 /* 1269 ================ 1270 idProjectile::Event_CreateProjectile 1271 ================ 1272 */ 1273 void idProjectile::Event_CreateProjectile( idEntity *owner, const idVec3 &start, const idVec3 &dir ) { 1274 Create(owner, start, dir); 1275 } 1276 1277 /* 1278 ================ 1279 idProjectile::Event_LaunchProjectile 1280 ================ 1281 */ 1282 void idProjectile::Event_LaunchProjectile( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity ) { 1283 Launch(start, dir, pushVelocity); 1284 } 1285 1286 /* 1287 ================ 1288 idProjectile::Event_SetGravity 1289 ================ 1290 */ 1291 void idProjectile::Event_SetGravity( float gravity ) { 1292 idVec3 gravVec; 1293 1294 gravVec = gameLocal.GetGravity(); 1295 gravVec.NormalizeFast(); 1296 physicsObj.SetGravity(gravVec * gravity); 1297 } 1298 1299 /* 1300 ================= 1301 idProjectile::ClientPredictionCollide 1302 ================= 1303 */ 1304 bool idProjectile::ClientPredictionCollide( idEntity *soundEnt, const idDict &projectileDef, const trace_t &collision, const idVec3 &velocity, bool addDamageEffect ) { 1305 idEntity *ent; 1306 1307 // remove projectile when a 'noimpact' surface is hit 1308 if ( collision.c.material && ( collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) ) { 1309 return false; 1310 } 1311 1312 // get the entity the projectile collided with 1313 ent = gameLocal.entities[ collision.c.entityNum ]; 1314 if ( ent == NULL ) { 1315 return false; 1316 } 1317 1318 // don't do anything if hitting a noclip player 1319 if ( ent->IsType( idPlayer::Type ) && static_cast<idPlayer *>( ent )->noclip ) { 1320 return false; 1321 } 1322 1323 if ( ent->IsType( idActor::Type ) || ( ent->IsType( idAFAttachment::Type ) && static_cast<const idAFAttachment*>(ent)->GetBody()->IsType( idActor::Type ) ) ) { 1324 if ( !projectileDef.GetBool( "detonate_on_actor" ) ) { 1325 return false; 1326 } 1327 } else { 1328 if ( !projectileDef.GetBool( "detonate_on_world" ) ) { 1329 return false; 1330 } 1331 } 1332 1333 // if the projectile causes a damage effect 1334 if ( addDamageEffect && projectileDef.GetBool( "impact_damage_effect" ) ) { 1335 // if the hit entity does not have a special damage effect 1336 if ( !ent->spawnArgs.GetBool( "bleed" ) ) { 1337 // predict damage effect 1338 DefaultDamageEffect( soundEnt, projectileDef, collision, velocity ); 1339 } 1340 } 1341 return true; 1342 } 1343 1344 /* 1345 ================ 1346 idProjectile::ClientThink 1347 ================ 1348 */ 1349 void idProjectile::ClientThink( const int curTime, const float fraction, const bool predict ) { 1350 if ( fl.skipReplication ) { 1351 Think(); 1352 } else { 1353 if ( !renderEntity.hModel ) { 1354 return; 1355 } 1356 InterpolatePhysicsOnly( fraction ); 1357 Present(); 1358 AddParticlesAndLight(); 1359 } 1360 } 1361 1362 /* 1363 ================ 1364 idProjectile::ClientPredictionThink 1365 ================ 1366 */ 1367 void idProjectile::ClientPredictionThink() { 1368 if ( !renderEntity.hModel ) { 1369 return; 1370 } 1371 Think(); 1372 } 1373 1374 /* 1375 ================ 1376 idProjectile::WriteToSnapshot 1377 ================ 1378 */ 1379 void idProjectile::WriteToSnapshot( idBitMsg &msg ) const { 1380 msg.WriteBits( owner.GetSpawnId(), 32 ); 1381 msg.WriteBits( state, 3 ); 1382 msg.WriteBits( fl.hidden, 1 ); 1383 1384 physicsObj.WriteToSnapshot( msg ); 1385 } 1386 1387 /* 1388 ================ 1389 idProjectile::ReadFromSnapshot 1390 ================ 1391 */ 1392 void idProjectile::ReadFromSnapshot( const idBitMsg &msg ) { 1393 projectileState_t newState; 1394 1395 owner.SetSpawnId( msg.ReadBits( 32 ) ); 1396 newState = (projectileState_t) msg.ReadBits( 3 ); 1397 1398 if ( msg.ReadBits( 1 ) ) { 1399 Hide(); 1400 } else { 1401 Show(); 1402 } 1403 1404 while( state != newState ) { 1405 switch( state ) { 1406 case SPAWNED: { 1407 Create( owner.GetEntity(), vec3_origin, idVec3( 1, 0, 0 ) ); 1408 break; 1409 } 1410 case CREATED: { 1411 // the right origin and direction are required if you want bullet traces 1412 Launch( vec3_origin, idVec3( 1, 0, 0 ), vec3_origin ); 1413 break; 1414 } 1415 case LAUNCHED: { 1416 if ( newState == FIZZLED ) { 1417 Fizzle(); 1418 } else { 1419 trace_t collision; 1420 memset( &collision, 0, sizeof( collision ) ); 1421 collision.endAxis = GetPhysics()->GetAxis(); 1422 collision.endpos = GetPhysics()->GetOrigin(); 1423 collision.c.point = GetPhysics()->GetOrigin(); 1424 collision.c.normal.Set( 0, 0, 1 ); 1425 Explode( collision, NULL ); 1426 } 1427 break; 1428 } 1429 case FIZZLED: 1430 case EXPLODED: { 1431 StopSound( SND_CHANNEL_BODY2, false ); 1432 gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &renderEntity ); 1433 state = SPAWNED; 1434 break; 1435 } 1436 } 1437 } 1438 1439 physicsObj.ReadFromSnapshot( msg ); 1440 1441 if ( msg.HasChanged() ) { 1442 UpdateVisuals(); 1443 } 1444 } 1445 1446 /* 1447 ================ 1448 idProjectile::ClientReceiveEvent 1449 ================ 1450 */ 1451 bool idProjectile::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { 1452 trace_t collision; 1453 idVec3 velocity; 1454 1455 switch( event ) { 1456 case EVENT_DAMAGE_EFFECT: { 1457 memset( &collision, 0, sizeof( collision ) ); 1458 collision.c.point[0] = msg.ReadFloat(); 1459 collision.c.point[1] = msg.ReadFloat(); 1460 collision.c.point[2] = msg.ReadFloat(); 1461 collision.c.normal = msg.ReadDir( 24 ); 1462 int index = gameLocal.ClientRemapDecl( DECL_MATERIAL, msg.ReadLong() ); 1463 collision.c.material = ( index != -1 ) ? static_cast<const idMaterial *>( declManager->DeclByIndex( DECL_MATERIAL, index ) ) : NULL; 1464 velocity[0] = msg.ReadFloat( 5, 10 ); 1465 velocity[1] = msg.ReadFloat( 5, 10 ); 1466 velocity[2] = msg.ReadFloat( 5, 10 ); 1467 DefaultDamageEffect( this, spawnArgs, collision, velocity ); 1468 return true; 1469 } 1470 default: { 1471 return idEntity::ClientReceiveEvent( event, time, msg ); 1472 } 1473 } 1474 } 1475 1476 1477 /* 1478 ======================== 1479 idProjectile::QueueToSimulate 1480 ======================== 1481 */ 1482 void idProjectile::QueueToSimulate( int startTime ) { 1483 assert( common->IsMultiplayer() && common->IsServer() ); 1484 1485 for ( int i = 0; i < MAX_SIMULATED_PROJECTILES; i++ ) { 1486 if ( projectilesToSimulate[i].projectile == NULL ) { 1487 projectilesToSimulate[i].projectile = this; 1488 projectilesToSimulate[i].startTime= startTime; 1489 if ( g_projectileDebug.GetBool() ) { 1490 int delta = gameLocal.GetServerGameTimeMs() - startTime; 1491 idLib::Printf( "Simulating projectile %d. Approx %d delay.\n", GetEntityNumber(), delta); 1492 } 1493 return; 1494 } 1495 } 1496 1497 idLib::Warning("Unable to simulate more projectiles this frame"); 1498 } 1499 1500 /* 1501 ======================== 1502 idProjectile::SimulateProjectileFrame 1503 ======================== 1504 */ 1505 void idProjectile::SimulateProjectileFrame( int msec, int endTime ) { 1506 idVec3 oldOrigin = GetPhysics()->GetOrigin(); 1507 1508 GetPhysics()->Evaluate( msec, endTime ); 1509 SetOrigin( GetPhysics()->GetOrigin() ); 1510 SetAxis( GetPhysics()->GetAxis() ); 1511 1512 if ( g_projectileDebug.GetBool() ) { 1513 float delta = ( GetPhysics()->GetOrigin() - oldOrigin ).Length(); 1514 idLib::Printf( "Simulated projectile %d. Delta: %.2f \n", GetEntityNumber(), delta ); 1515 //clientGame->renderWorld->DebugLine( idColor::colorYellow, oldOrigin, GetPhysics()->GetOrigin(), 5000 ); 1516 } 1517 } 1518 1519 /* 1520 ======================== 1521 idProjectile::PostSimulate 1522 ======================== 1523 */ 1524 void idProjectile::PostSimulate( int endTime ) { 1525 if ( state == EXPLODED || state == FIZZLED ) { 1526 // Already exploded. To see the explosion on the collision surface instead of 1527 // at the muzzle, don't set the deltas to the launch origin and axis. 1528 CreateDeltasFromOldOriginAndAxis( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); 1529 } else { 1530 CreateDeltasFromOldOriginAndAxis( launchOrigin, launchAxis ); 1531 } 1532 } 1533 1534 1535 1536 /* 1537 =============================================================================== 1538 1539 idGuidedProjectile 1540 1541 =============================================================================== 1542 */ 1543 1544 const idEventDef EV_SetEnemy( "setEnemy", "E" ); 1545 1546 CLASS_DECLARATION( idProjectile, idGuidedProjectile ) 1547 EVENT( EV_SetEnemy, idGuidedProjectile::Event_SetEnemy ) 1548 END_CLASS 1549 1550 /* 1551 ================ 1552 idGuidedProjectile::idGuidedProjectile 1553 ================ 1554 */ 1555 idGuidedProjectile::idGuidedProjectile() { 1556 enemy = NULL; 1557 speed = 0.0f; 1558 turn_max = 0.0f; 1559 clamp_dist = 0.0f; 1560 rndScale = ang_zero; 1561 rndAng = ang_zero; 1562 rndUpdateTime = 0; 1563 angles = ang_zero; 1564 burstMode = false; 1565 burstDist = 0; 1566 burstVelocity = 0.0f; 1567 unGuided = false; 1568 } 1569 1570 /* 1571 ================= 1572 idGuidedProjectile::~idGuidedProjectile 1573 ================= 1574 */ 1575 idGuidedProjectile::~idGuidedProjectile() { 1576 } 1577 1578 /* 1579 ================ 1580 idGuidedProjectile::Spawn 1581 ================ 1582 */ 1583 void idGuidedProjectile::Spawn() { 1584 } 1585 1586 /* 1587 ================ 1588 idGuidedProjectile::Save 1589 ================ 1590 */ 1591 void idGuidedProjectile::Save( idSaveGame *savefile ) const { 1592 enemy.Save( savefile ); 1593 savefile->WriteFloat( speed ); 1594 savefile->WriteAngles( rndScale ); 1595 savefile->WriteAngles( rndAng ); 1596 savefile->WriteInt( rndUpdateTime ); 1597 savefile->WriteFloat( turn_max ); 1598 savefile->WriteFloat( clamp_dist ); 1599 savefile->WriteAngles( angles ); 1600 savefile->WriteBool( burstMode ); 1601 savefile->WriteBool( unGuided ); 1602 savefile->WriteFloat( burstDist ); 1603 savefile->WriteFloat( burstVelocity ); 1604 } 1605 1606 /* 1607 ================ 1608 idGuidedProjectile::Restore 1609 ================ 1610 */ 1611 void idGuidedProjectile::Restore( idRestoreGame *savefile ) { 1612 enemy.Restore( savefile ); 1613 savefile->ReadFloat( speed ); 1614 savefile->ReadAngles( rndScale ); 1615 savefile->ReadAngles( rndAng ); 1616 savefile->ReadInt( rndUpdateTime ); 1617 savefile->ReadFloat( turn_max ); 1618 savefile->ReadFloat( clamp_dist ); 1619 savefile->ReadAngles( angles ); 1620 savefile->ReadBool( burstMode ); 1621 savefile->ReadBool( unGuided ); 1622 savefile->ReadFloat( burstDist ); 1623 savefile->ReadFloat( burstVelocity ); 1624 } 1625 1626 1627 /* 1628 ================ 1629 idGuidedProjectile::GetSeekPos 1630 ================ 1631 */ 1632 void idGuidedProjectile::GetSeekPos( idVec3 &out ) { 1633 idEntity *enemyEnt = enemy.GetEntity(); 1634 if ( enemyEnt ) { 1635 if ( enemyEnt->IsType( idActor::Type ) ) { 1636 out = static_cast<idActor *>(enemyEnt)->GetEyePosition(); 1637 out.z -= 12.0f; 1638 } else { 1639 out = enemyEnt->GetPhysics()->GetOrigin(); 1640 } 1641 } else { 1642 out = GetPhysics()->GetOrigin() + physicsObj.GetLinearVelocity() * 2.0f; 1643 } 1644 } 1645 1646 /* 1647 ================ 1648 idGuidedProjectile::Think 1649 ================ 1650 */ 1651 void idGuidedProjectile::Think() { 1652 idVec3 dir; 1653 idVec3 seekPos; 1654 idVec3 velocity; 1655 idVec3 nose; 1656 idVec3 tmp; 1657 idMat3 axis; 1658 idAngles dirAng; 1659 idAngles diff; 1660 float dist; 1661 float frac; 1662 int i; 1663 1664 if ( state == LAUNCHED && !unGuided ) { 1665 1666 GetSeekPos( seekPos ); 1667 1668 if ( rndUpdateTime < gameLocal.time ) { 1669 rndAng[ 0 ] = rndScale[ 0 ] * gameLocal.random.CRandomFloat(); 1670 rndAng[ 1 ] = rndScale[ 1 ] * gameLocal.random.CRandomFloat(); 1671 rndAng[ 2 ] = rndScale[ 2 ] * gameLocal.random.CRandomFloat(); 1672 rndUpdateTime = gameLocal.time + 200; 1673 } 1674 1675 nose = physicsObj.GetOrigin() + 10.0f * physicsObj.GetAxis()[0]; 1676 1677 dir = seekPos - nose; 1678 dist = dir.Normalize(); 1679 dirAng = dir.ToAngles(); 1680 1681 // make it more accurate as it gets closer 1682 frac = dist / clamp_dist; 1683 if ( frac > 1.0f ) { 1684 frac = 1.0f; 1685 } 1686 1687 diff = dirAng - angles + rndAng * frac; 1688 1689 // clamp the to the max turn rate 1690 diff.Normalize180(); 1691 for( i = 0; i < 3; i++ ) { 1692 if ( diff[ i ] > turn_max ) { 1693 diff[ i ] = turn_max; 1694 } else if ( diff[ i ] < -turn_max ) { 1695 diff[ i ] = -turn_max; 1696 } 1697 } 1698 angles += diff; 1699 1700 // make the visual model always points the dir we're traveling 1701 dir = angles.ToForward(); 1702 velocity = dir * speed; 1703 1704 if ( burstMode && dist < burstDist ) { 1705 unGuided = true; 1706 velocity *= burstVelocity; 1707 } 1708 1709 physicsObj.SetLinearVelocity( velocity ); 1710 1711 // align z-axis of model with the direction 1712 axis = dir.ToMat3(); 1713 tmp = axis[2]; 1714 axis[2] = axis[0]; 1715 axis[0] = -tmp; 1716 1717 GetPhysics()->SetAxis( axis ); 1718 } 1719 1720 idProjectile::Think(); 1721 } 1722 1723 /* 1724 ================= 1725 idGuidedProjectile::Launch 1726 ================= 1727 */ 1728 void idGuidedProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, float dmgPower ) { 1729 idProjectile::Launch( start, dir, pushVelocity, timeSinceFire, launchPower, dmgPower ); 1730 if ( owner.GetEntity() ) { 1731 if ( owner.GetEntity()->IsType( idAI::Type ) ) { 1732 enemy = static_cast<idAI *>( owner.GetEntity() )->GetEnemy(); 1733 } else if ( owner.GetEntity()->IsType( idPlayer::Type ) ) { 1734 trace_t tr; 1735 idPlayer *player = static_cast<idPlayer*>( owner.GetEntity() ); 1736 idVec3 start = player->GetEyePosition(); 1737 idVec3 end = start + player->viewAxis[0] * 1000.0f; 1738 gameLocal.clip.TracePoint( tr, start, end, MASK_SHOT_RENDERMODEL | CONTENTS_BODY, owner.GetEntity() ); 1739 if ( tr.fraction < 1.0f ) { 1740 enemy = gameLocal.GetTraceEntity( tr ); 1741 } 1742 // ignore actors on the player's team 1743 if ( enemy.GetEntity() == NULL || !enemy.GetEntity()->IsType( idActor::Type ) || ( static_cast<idActor *>( enemy.GetEntity() )->team == player->team ) ) { 1744 enemy = player->EnemyWithMostHealth(); 1745 } 1746 } 1747 } 1748 const idVec3 &vel = physicsObj.GetLinearVelocity(); 1749 angles = vel.ToAngles(); 1750 speed = vel.Length(); 1751 rndScale = spawnArgs.GetAngles( "random", "15 15 0" ); 1752 turn_max = spawnArgs.GetFloat( "turn_max", "180" ) / com_engineHz_latched; 1753 clamp_dist = spawnArgs.GetFloat( "clamp_dist", "256" ); 1754 burstMode = spawnArgs.GetBool( "burstMode" ); 1755 unGuided = false; 1756 burstDist = spawnArgs.GetFloat( "burstDist", "64" ); 1757 burstVelocity = spawnArgs.GetFloat( "burstVelocity", "1.25" ); 1758 UpdateVisuals(); 1759 } 1760 1761 void idGuidedProjectile::SetEnemy( idEntity *ent ) { 1762 enemy = ent; 1763 } 1764 void idGuidedProjectile::Event_SetEnemy(idEntity *ent) { 1765 SetEnemy(ent); 1766 } 1767 1768 /* 1769 =============================================================================== 1770 1771 idSoulCubeMissile 1772 1773 =============================================================================== 1774 */ 1775 1776 CLASS_DECLARATION( idGuidedProjectile, idSoulCubeMissile ) 1777 END_CLASS 1778 1779 /* 1780 ================ 1781 idSoulCubeMissile::Spawn() 1782 ================ 1783 */ 1784 void idSoulCubeMissile::Spawn() { 1785 startingVelocity.Zero(); 1786 endingVelocity.Zero(); 1787 accelTime = 0.0f; 1788 launchTime = 0; 1789 killPhase = false; 1790 returnPhase = false; 1791 smokeKillTime = 0; 1792 smokeKill = NULL; 1793 } 1794 1795 /* 1796 ================= 1797 idSoulCubeMissile::~idSoulCubeMissile 1798 ================= 1799 */ 1800 idSoulCubeMissile::~idSoulCubeMissile() { 1801 } 1802 1803 /* 1804 ================ 1805 idSoulCubeMissile::Save 1806 ================ 1807 */ 1808 void idSoulCubeMissile::Save( idSaveGame *savefile ) const { 1809 savefile->WriteVec3( startingVelocity ); 1810 savefile->WriteVec3( endingVelocity ); 1811 savefile->WriteFloat( accelTime ); 1812 savefile->WriteInt( launchTime ); 1813 savefile->WriteBool( killPhase ); 1814 savefile->WriteBool( returnPhase ); 1815 savefile->WriteVec3( destOrg); 1816 savefile->WriteInt( orbitTime ); 1817 savefile->WriteVec3( orbitOrg ); 1818 savefile->WriteInt( smokeKillTime ); 1819 savefile->WriteParticle( smokeKill ); 1820 } 1821 1822 /* 1823 ================ 1824 idSoulCubeMissile::Restore 1825 ================ 1826 */ 1827 void idSoulCubeMissile::Restore( idRestoreGame *savefile ) { 1828 savefile->ReadVec3( startingVelocity ); 1829 savefile->ReadVec3( endingVelocity ); 1830 savefile->ReadFloat( accelTime ); 1831 savefile->ReadInt( launchTime ); 1832 savefile->ReadBool( killPhase ); 1833 savefile->ReadBool( returnPhase ); 1834 savefile->ReadVec3( destOrg); 1835 savefile->ReadInt( orbitTime ); 1836 savefile->ReadVec3( orbitOrg ); 1837 savefile->ReadInt( smokeKillTime ); 1838 savefile->ReadParticle( smokeKill ); 1839 } 1840 1841 /* 1842 ================ 1843 idSoulCubeMissile::KillTarget 1844 ================ 1845 */ 1846 void idSoulCubeMissile::KillTarget( const idVec3 &dir ) { 1847 idEntity *ownerEnt; 1848 const char *smokeName; 1849 idActor *act; 1850 1851 ReturnToOwner(); 1852 if ( enemy.GetEntity() && enemy.GetEntity()->IsType( idActor::Type ) ) { 1853 act = static_cast<idActor*>( enemy.GetEntity() ); 1854 killPhase = true; 1855 orbitOrg = act->GetPhysics()->GetAbsBounds().GetCenter(); 1856 orbitTime = gameLocal.time; 1857 smokeKillTime = 0; 1858 smokeName = spawnArgs.GetString( "smoke_kill" ); 1859 if ( *smokeName != '\0' ) { 1860 smokeKill = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) ); 1861 smokeKillTime = gameLocal.time; 1862 } 1863 ownerEnt = owner.GetEntity(); 1864 if ( ( act->health > 0 ) && ownerEnt != NULL && ownerEnt->IsType( idPlayer::Type ) && ( ownerEnt->health > 0 ) && !act->spawnArgs.GetBool( "boss" ) ) { 1865 static_cast<idPlayer *>( ownerEnt )->GiveHealthPool( act->health ); 1866 } 1867 act->Damage( this, owner.GetEntity(), dir, spawnArgs.GetString( "def_damage" ), 1.0f, INVALID_JOINT ); 1868 act->GetAFPhysics()->SetTimeScale( 0.25 ); 1869 StartSound( "snd_explode", SND_CHANNEL_BODY, 0, false, NULL ); 1870 } 1871 } 1872 1873 /* 1874 ================ 1875 idSoulCubeMissile::Think 1876 ================ 1877 */ 1878 void idSoulCubeMissile::Think() { 1879 float pct; 1880 idVec3 seekPos; 1881 idEntity *ownerEnt; 1882 1883 if ( state == LAUNCHED ) { 1884 if ( killPhase ) { 1885 // orbit the mob, cascading down 1886 if ( gameLocal.time < orbitTime + 1500 ) { 1887 if ( !gameLocal.smokeParticles->EmitSmoke( smokeKill, smokeKillTime, gameLocal.random.CRandomFloat(), orbitOrg, mat3_identity, timeGroup /*_D3XP*/ ) ) { 1888 smokeKillTime = gameLocal.time; 1889 } 1890 } 1891 } else { 1892 if ( accelTime && gameLocal.time < launchTime + accelTime * 1000 ) { 1893 pct = ( gameLocal.time - launchTime ) / ( accelTime * 1000 ); 1894 speed = ( startingVelocity + ( startingVelocity + endingVelocity ) * pct ).Length(); 1895 } 1896 } 1897 idGuidedProjectile::Think(); 1898 GetSeekPos( seekPos ); 1899 if ( ( seekPos - physicsObj.GetOrigin() ).Length() < 32.0f ) { 1900 if ( returnPhase ) { 1901 StopSound( SND_CHANNEL_ANY, false ); 1902 StartSound( "snd_return", SND_CHANNEL_BODY2, 0, false, NULL ); 1903 Hide(); 1904 PostEventSec( &EV_Remove, 2.0f ); 1905 1906 ownerEnt = owner.GetEntity(); 1907 if ( ownerEnt != NULL && ownerEnt->IsType( idPlayer::Type ) ) { 1908 static_cast<idPlayer *>( ownerEnt )->SetSoulCubeProjectile( NULL ); 1909 } 1910 1911 state = FIZZLED; 1912 } else if ( !killPhase ){ 1913 KillTarget( physicsObj.GetAxis()[0] ); 1914 } 1915 } 1916 } 1917 } 1918 1919 /* 1920 ================ 1921 idSoulCubeMissile::GetSeekPos 1922 ================ 1923 */ 1924 void idSoulCubeMissile::GetSeekPos( idVec3 &out ) { 1925 if ( returnPhase && owner.GetEntity() && owner.GetEntity()->IsType( idActor::Type ) ) { 1926 idActor *act = static_cast<idActor*>( owner.GetEntity() ); 1927 out = act->GetEyePosition(); 1928 return; 1929 } 1930 if ( destOrg != vec3_zero ) { 1931 out = destOrg; 1932 return; 1933 } 1934 idGuidedProjectile::GetSeekPos( out ); 1935 } 1936 1937 1938 /* 1939 ================ 1940 idSoulCubeMissile::Event_ReturnToOwner 1941 ================ 1942 */ 1943 void idSoulCubeMissile::ReturnToOwner() { 1944 speed *= 0.65f; 1945 killPhase = false; 1946 returnPhase = true; 1947 smokeFlyTime = 0; 1948 } 1949 1950 1951 /* 1952 ================= 1953 idSoulCubeMissile::Launch 1954 ================= 1955 */ 1956 void idSoulCubeMissile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, float dmgPower ) { 1957 idVec3 newStart; 1958 idVec3 offs; 1959 idEntity *ownerEnt; 1960 1961 // push it out a little 1962 newStart = start + dir * spawnArgs.GetFloat( "launchDist" ); 1963 offs = spawnArgs.GetVector( "launchOffset", "0 0 -4" ); 1964 newStart += offs; 1965 idGuidedProjectile::Launch( newStart, dir, pushVelocity, timeSinceFire, launchPower, dmgPower ); 1966 if ( enemy.GetEntity() == NULL || !enemy.GetEntity()->IsType( idActor::Type ) ) { 1967 destOrg = start + dir * 256.0f; 1968 } else { 1969 destOrg.Zero(); 1970 } 1971 physicsObj.SetClipMask( 0 ); // never collide.. think routine will decide when to detonate 1972 startingVelocity = spawnArgs.GetVector( "startingVelocity", "15 0 0" ); 1973 endingVelocity = spawnArgs.GetVector( "endingVelocity", "1500 0 0" ); 1974 accelTime = spawnArgs.GetFloat( "accelTime", "5" ); 1975 physicsObj.SetLinearVelocity( startingVelocity.Length() * physicsObj.GetAxis()[2] ); 1976 launchTime = gameLocal.time; 1977 killPhase = false; 1978 UpdateVisuals(); 1979 1980 ownerEnt = owner.GetEntity(); 1981 if ( ownerEnt != NULL && ownerEnt->IsType( idPlayer::Type ) ) { 1982 static_cast<idPlayer *>( ownerEnt )->SetSoulCubeProjectile( this ); 1983 } 1984 1985 } 1986 1987 1988 /* 1989 =============================================================================== 1990 1991 idBFGProjectile 1992 1993 =============================================================================== 1994 */ 1995 const idEventDef EV_RemoveBeams( "<removeBeams>", NULL ); 1996 1997 CLASS_DECLARATION( idProjectile, idBFGProjectile ) 1998 EVENT( EV_RemoveBeams, idBFGProjectile::Event_RemoveBeams ) 1999 END_CLASS 2000 2001 2002 /* 2003 ================= 2004 idBFGProjectile::idBFGProjectile 2005 ================= 2006 */ 2007 idBFGProjectile::idBFGProjectile() { 2008 memset( &secondModel, 0, sizeof( secondModel ) ); 2009 secondModelDefHandle = -1; 2010 nextDamageTime = 0; 2011 } 2012 2013 /* 2014 ================= 2015 idBFGProjectile::~idBFGProjectile 2016 ================= 2017 */ 2018 idBFGProjectile::~idBFGProjectile() { 2019 FreeBeams(); 2020 2021 if ( secondModelDefHandle >= 0 ) { 2022 gameRenderWorld->FreeEntityDef( secondModelDefHandle ); 2023 secondModelDefHandle = -1; 2024 } 2025 } 2026 2027 /* 2028 ================ 2029 idBFGProjectile::Spawn 2030 ================ 2031 */ 2032 void idBFGProjectile::Spawn() { 2033 beamTargets.Clear(); 2034 memset( &secondModel, 0, sizeof( secondModel ) ); 2035 secondModelDefHandle = -1; 2036 const char *temp = spawnArgs.GetString( "model_two" ); 2037 if ( temp != NULL && *temp != NULL ) { 2038 secondModel.hModel = renderModelManager->FindModel( temp ); 2039 secondModel.bounds = secondModel.hModel->Bounds( &secondModel ); 2040 secondModel.shaderParms[ SHADERPARM_RED ] = 2041 secondModel.shaderParms[ SHADERPARM_GREEN ] = 2042 secondModel.shaderParms[ SHADERPARM_BLUE ] = 2043 secondModel.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; 2044 secondModel.noSelfShadow = true; 2045 secondModel.noShadow = true; 2046 } 2047 nextDamageTime = 0; 2048 damageFreq = NULL; 2049 } 2050 2051 /* 2052 ================ 2053 idBFGProjectile::Save 2054 ================ 2055 */ 2056 void idBFGProjectile::Save( idSaveGame *savefile ) const { 2057 int i; 2058 2059 savefile->WriteInt( beamTargets.Num() ); 2060 for ( i = 0; i < beamTargets.Num(); i++ ) { 2061 beamTargets[i].target.Save( savefile ); 2062 savefile->WriteRenderEntity( beamTargets[i].renderEntity ); 2063 savefile->WriteInt( beamTargets[i].modelDefHandle ); 2064 } 2065 2066 savefile->WriteRenderEntity( secondModel ); 2067 savefile->WriteInt( secondModelDefHandle ); 2068 savefile->WriteInt( nextDamageTime ); 2069 savefile->WriteString( damageFreq ); 2070 } 2071 2072 /* 2073 ================ 2074 idBFGProjectile::Restore 2075 ================ 2076 */ 2077 void idBFGProjectile::Restore( idRestoreGame *savefile ) { 2078 int i, num; 2079 2080 savefile->ReadInt( num ); 2081 beamTargets.SetNum( num ); 2082 for ( i = 0; i < num; i++ ) { 2083 beamTargets[i].target.Restore( savefile ); 2084 savefile->ReadRenderEntity( beamTargets[i].renderEntity ); 2085 savefile->ReadInt( beamTargets[i].modelDefHandle ); 2086 2087 if ( beamTargets[i].modelDefHandle >= 0 ) { 2088 beamTargets[i].modelDefHandle = gameRenderWorld->AddEntityDef( &beamTargets[i].renderEntity ); 2089 } 2090 } 2091 2092 savefile->ReadRenderEntity( secondModel ); 2093 savefile->ReadInt( secondModelDefHandle ); 2094 savefile->ReadInt( nextDamageTime ); 2095 savefile->ReadString( damageFreq ); 2096 2097 if ( secondModelDefHandle >= 0 ) { 2098 secondModelDefHandle = gameRenderWorld->AddEntityDef( &secondModel ); 2099 } 2100 } 2101 2102 /* 2103 ================= 2104 idBFGProjectile::FreeBeams 2105 ================= 2106 */ 2107 void idBFGProjectile::FreeBeams() { 2108 for ( int i = 0; i < beamTargets.Num(); i++ ) { 2109 if ( beamTargets[i].modelDefHandle >= 0 ) { 2110 gameRenderWorld->FreeEntityDef( beamTargets[i].modelDefHandle ); 2111 beamTargets[i].modelDefHandle = -1; 2112 } 2113 } 2114 2115 idPlayer *player = gameLocal.GetLocalPlayer(); 2116 if ( player ) { 2117 player->playerView.EnableBFGVision( false ); 2118 } 2119 } 2120 2121 /* 2122 ================ 2123 idBFGProjectile::Think 2124 ================ 2125 */ 2126 void idBFGProjectile::Think() { 2127 if ( state == LAUNCHED ) { 2128 2129 // update beam targets 2130 for ( int i = 0; i < beamTargets.Num(); i++ ) { 2131 if ( beamTargets[i].target.GetEntity() == NULL ) { 2132 continue; 2133 } 2134 idPlayer *player = ( beamTargets[i].target.GetEntity()->IsType( idPlayer::Type ) ) ? static_cast<idPlayer*>( beamTargets[i].target.GetEntity() ) : NULL; 2135 // Major hack for end boss. :( 2136 idAnimatedEntity *beamEnt; 2137 idVec3 org; 2138 bool forceDamage = false; 2139 2140 beamEnt = static_cast<idAnimatedEntity*>(beamTargets[i].target.GetEntity()); 2141 if ( !idStr::Cmp( beamEnt->GetEntityDefName(), "monster_boss_d3xp_maledict" ) ) { 2142 SetTimeState ts( beamEnt->timeGroup ); 2143 idMat3 temp; 2144 jointHandle_t bodyJoint; 2145 2146 bodyJoint = beamEnt->GetAnimator()->GetJointHandle( "Chest1" ); 2147 beamEnt->GetJointWorldTransform( bodyJoint, gameLocal.time, org, temp ); 2148 2149 forceDamage = true; 2150 } else { 2151 org = beamEnt->GetPhysics()->GetAbsBounds().GetCenter(); 2152 } 2153 beamTargets[i].renderEntity.origin = GetPhysics()->GetOrigin(); 2154 beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BEAM_END_X ] = org.x; 2155 beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BEAM_END_Y ] = org.y; 2156 beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BEAM_END_Z ] = org.z; 2157 beamTargets[i].renderEntity.shaderParms[ SHADERPARM_RED ] = 2158 beamTargets[i].renderEntity.shaderParms[ SHADERPARM_GREEN ] = 2159 beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BLUE ] = 2160 beamTargets[i].renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; 2161 if ( gameLocal.time > nextDamageTime ) { 2162 bool bfgVision = true; 2163 if ( damageFreq && *(const char *)damageFreq && beamTargets[i].target.GetEntity() && ( forceDamage || beamTargets[i].target.GetEntity()->CanDamage( GetPhysics()->GetOrigin(), org ) ) ) { 2164 org = beamTargets[i].target.GetEntity()->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); 2165 org.Normalize(); 2166 beamTargets[i].target.GetEntity()->Damage( this, owner.GetEntity(), org, damageFreq, ( damagePower ) ? damagePower : 1.0f, INVALID_JOINT ); 2167 } else { 2168 beamTargets[i].renderEntity.shaderParms[ SHADERPARM_RED ] = 2169 beamTargets[i].renderEntity.shaderParms[ SHADERPARM_GREEN ] = 2170 beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BLUE ] = 2171 beamTargets[i].renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 0.0f; 2172 bfgVision = false; 2173 } 2174 if ( player ) { 2175 player->playerView.EnableBFGVision( bfgVision ); 2176 } 2177 nextDamageTime = gameLocal.time + BFG_DAMAGE_FREQUENCY; 2178 } 2179 gameRenderWorld->UpdateEntityDef( beamTargets[i].modelDefHandle, &beamTargets[i].renderEntity ); 2180 } 2181 2182 if ( secondModelDefHandle >= 0 ) { 2183 secondModel.origin = GetPhysics()->GetOrigin(); 2184 gameRenderWorld->UpdateEntityDef( secondModelDefHandle, &secondModel ); 2185 } 2186 2187 idAngles ang; 2188 2189 ang.pitch = ( gameLocal.time & 4095 ) * 360.0f / -4096.0f; 2190 ang.yaw = ang.pitch; 2191 ang.roll = 0.0f; 2192 SetAngles( ang ); 2193 2194 ang.pitch = ( gameLocal.time & 2047 ) * 360.0f / -2048.0f; 2195 ang.yaw = ang.pitch; 2196 ang.roll = 0.0f; 2197 secondModel.axis = ang.ToMat3(); 2198 2199 UpdateVisuals(); 2200 } 2201 2202 idProjectile::Think(); 2203 } 2204 2205 /* 2206 ================= 2207 idBFGProjectile::Launch 2208 ================= 2209 */ 2210 void idBFGProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float power, const float dmgPower ) { 2211 idProjectile::Launch( start, dir, pushVelocity, 0.0f, power, dmgPower ); 2212 2213 // dmgPower * radius is the target acquisition area 2214 // acquisition should make sure that monsters are not dormant 2215 // which will cut down on hitting monsters not actively fighting 2216 // but saves on the traces making sure they are visible 2217 // damage is not applied until the projectile explodes 2218 2219 idEntity * ent; 2220 idEntity * entityList[ MAX_GENTITIES ]; 2221 int numListedEntities; 2222 idBounds bounds; 2223 idVec3 damagePoint; 2224 2225 float radius; 2226 spawnArgs.GetFloat( "damageRadius", "512", radius ); 2227 bounds = idBounds( GetPhysics()->GetOrigin() ).Expand( radius ); 2228 2229 float beamWidth = spawnArgs.GetFloat( "beam_WidthFly" ); 2230 const char *skin = spawnArgs.GetString( "skin_beam" ); 2231 2232 memset( &secondModel, 0, sizeof( secondModel ) ); 2233 secondModelDefHandle = -1; 2234 const char *temp = spawnArgs.GetString( "model_two" ); 2235 if ( temp != NULL && *temp != NULL ) { 2236 secondModel.hModel = renderModelManager->FindModel( temp ); 2237 secondModel.bounds = secondModel.hModel->Bounds( &secondModel ); 2238 secondModel.shaderParms[ SHADERPARM_RED ] = 2239 secondModel.shaderParms[ SHADERPARM_GREEN ] = 2240 secondModel.shaderParms[ SHADERPARM_BLUE ] = 2241 secondModel.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; 2242 secondModel.noSelfShadow = true; 2243 secondModel.noShadow = true; 2244 secondModel.origin = GetPhysics()->GetOrigin(); 2245 secondModel.axis = GetPhysics()->GetAxis(); 2246 secondModelDefHandle = gameRenderWorld->AddEntityDef( &secondModel ); 2247 } 2248 2249 idVec3 delta( 15.0f, 15.0f, 15.0f ); 2250 //physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, physicsObj.GetAxis().ToAngles(), delta, ang_zero ); 2251 2252 // get all entities touching the bounds 2253 numListedEntities = gameLocal.clip.EntitiesTouchingBounds( bounds, CONTENTS_BODY, entityList, MAX_GENTITIES ); 2254 for ( int e = 0; e < numListedEntities; e++ ) { 2255 ent = entityList[ e ]; 2256 assert( ent ); 2257 2258 if ( ent == this || ent == owner.GetEntity() || ent->IsHidden() || !ent->IsActive() || !ent->fl.takedamage || ent->health <= 0 || !ent->IsType( idActor::Type ) ) { 2259 continue; 2260 } 2261 2262 if ( !ent->CanDamage( GetPhysics()->GetOrigin(), damagePoint ) ) { 2263 continue; 2264 } 2265 2266 if ( ent->IsType( idPlayer::Type ) ) { 2267 idPlayer *player = static_cast<idPlayer*>( ent ); 2268 player->playerView.EnableBFGVision( true ); 2269 } 2270 2271 beamTarget_t bt; 2272 memset( &bt.renderEntity, 0, sizeof( renderEntity_t ) ); 2273 bt.renderEntity.origin = GetPhysics()->GetOrigin(); 2274 bt.renderEntity.axis = GetPhysics()->GetAxis(); 2275 bt.renderEntity.shaderParms[ SHADERPARM_BEAM_WIDTH ] = beamWidth; 2276 bt.renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f; 2277 bt.renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f; 2278 bt.renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f; 2279 bt.renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; 2280 bt.renderEntity.shaderParms[ SHADERPARM_DIVERSITY] = gameLocal.random.CRandomFloat() * 0.75; 2281 bt.renderEntity.hModel = renderModelManager->FindModel( "_beam" ); 2282 bt.renderEntity.callback = NULL; 2283 bt.renderEntity.numJoints = 0; 2284 bt.renderEntity.joints = NULL; 2285 bt.renderEntity.bounds.Clear(); 2286 bt.renderEntity.customSkin = declManager->FindSkin( skin ); 2287 bt.target = ent; 2288 bt.modelDefHandle = gameRenderWorld->AddEntityDef( &bt.renderEntity ); 2289 beamTargets.Append( bt ); 2290 } 2291 2292 // Major hack for end boss. :( 2293 idAnimatedEntity *maledict = static_cast<idAnimatedEntity*>(gameLocal.FindEntity( "monster_boss_d3xp_maledict_1" )); 2294 2295 if ( maledict ) { 2296 SetTimeState ts( maledict->timeGroup ); 2297 2298 idVec3 realPoint; 2299 idMat3 temp; 2300 float dist; 2301 jointHandle_t bodyJoint; 2302 2303 bodyJoint = maledict->GetAnimator()->GetJointHandle( "Chest1" ); 2304 maledict->GetJointWorldTransform( bodyJoint, gameLocal.time, realPoint, temp ); 2305 2306 dist = idVec3( realPoint - GetPhysics()->GetOrigin() ).Length(); 2307 2308 if ( dist < radius ) { 2309 beamTarget_t bt; 2310 memset( &bt.renderEntity, 0, sizeof( renderEntity_t ) ); 2311 bt.renderEntity.origin = GetPhysics()->GetOrigin(); 2312 bt.renderEntity.axis = GetPhysics()->GetAxis(); 2313 bt.renderEntity.shaderParms[ SHADERPARM_BEAM_WIDTH ] = beamWidth; 2314 bt.renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f; 2315 bt.renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f; 2316 bt.renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f; 2317 bt.renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; 2318 bt.renderEntity.shaderParms[ SHADERPARM_DIVERSITY] = gameLocal.random.CRandomFloat() * 0.75; 2319 bt.renderEntity.hModel = renderModelManager->FindModel( "_beam" ); 2320 bt.renderEntity.callback = NULL; 2321 bt.renderEntity.numJoints = 0; 2322 bt.renderEntity.joints = NULL; 2323 bt.renderEntity.bounds.Clear(); 2324 bt.renderEntity.customSkin = declManager->FindSkin( skin ); 2325 bt.target = maledict; 2326 bt.modelDefHandle = gameRenderWorld->AddEntityDef( &bt.renderEntity ); 2327 beamTargets.Append( bt ); 2328 2329 numListedEntities++; 2330 } 2331 } 2332 2333 if ( numListedEntities ) { 2334 StartSound( "snd_beam", SND_CHANNEL_BODY2, 0, false, NULL ); 2335 } 2336 damageFreq = spawnArgs.GetString( "def_damageFreq" ); 2337 nextDamageTime = gameLocal.time + BFG_DAMAGE_FREQUENCY; 2338 UpdateVisuals(); 2339 } 2340 2341 /* 2342 ================ 2343 idProjectile::Event_RemoveBeams 2344 ================ 2345 */ 2346 void idBFGProjectile::Event_RemoveBeams() { 2347 FreeBeams(); 2348 UpdateVisuals(); 2349 } 2350 2351 /* 2352 ================ 2353 idProjectile::Explode 2354 ================ 2355 */ 2356 void idBFGProjectile::Explode( const trace_t &collision, idEntity *ignore ) { 2357 int i; 2358 idVec3 dmgPoint; 2359 idVec3 dir; 2360 float beamWidth; 2361 float damageScale; 2362 const char *damage; 2363 idPlayer * player; 2364 idEntity * ownerEnt; 2365 2366 ownerEnt = owner.GetEntity(); 2367 if ( ownerEnt != NULL && ownerEnt->IsType( idPlayer::Type ) ) { 2368 player = static_cast< idPlayer * >( ownerEnt ); 2369 } else { 2370 player = NULL; 2371 } 2372 2373 beamWidth = spawnArgs.GetFloat( "beam_WidthExplode" ); 2374 damage = spawnArgs.GetString( "def_damage" ); 2375 2376 for ( i = 0; i < beamTargets.Num(); i++ ) { 2377 if ( ( beamTargets[i].target.GetEntity() == NULL ) || ( ownerEnt == NULL ) ) { 2378 continue; 2379 } 2380 2381 if ( !beamTargets[i].target.GetEntity()->CanDamage( GetPhysics()->GetOrigin(), dmgPoint ) ) { 2382 continue; 2383 } 2384 2385 beamTargets[i].renderEntity.shaderParms[SHADERPARM_BEAM_WIDTH] = beamWidth; 2386 2387 // if the hit entity takes damage 2388 if ( damagePower ) { 2389 damageScale = damagePower; 2390 } else { 2391 damageScale = 1.0f; 2392 } 2393 2394 // if the projectile owner is a player 2395 if ( player ) { 2396 // if the projectile hit an actor 2397 if ( beamTargets[i].target.GetEntity()->IsType( idActor::Type ) ) { 2398 player->SetLastHitTime( gameLocal.time ); 2399 player->AddProjectileHits( 1 ); 2400 damageScale *= player->PowerUpModifier( PROJECTILE_DAMAGE ); 2401 } 2402 } 2403 2404 if ( damage[0] && ( beamTargets[i].target.GetEntity()->entityNumber > gameLocal.numClients - 1 ) ) { 2405 dir = beamTargets[i].target.GetEntity()->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); 2406 dir.Normalize(); 2407 beamTargets[i].target.GetEntity()->Damage( this, ownerEnt, dir, damage, damageScale, ( collision.c.id < 0 ) ? CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) : INVALID_JOINT ); 2408 } 2409 } 2410 2411 if ( secondModelDefHandle >= 0 ) { 2412 gameRenderWorld->FreeEntityDef( secondModelDefHandle ); 2413 secondModelDefHandle = -1; 2414 } 2415 2416 if ( ignore == NULL ) { 2417 projectileFlags.noSplashDamage = true; 2418 } 2419 2420 if ( !common->IsClient() || fl.skipReplication ) { 2421 if ( ignore != NULL ) { 2422 PostEventMS( &EV_RemoveBeams, 750 ); 2423 } else { 2424 PostEventMS( &EV_RemoveBeams, 0 ); 2425 } 2426 } 2427 2428 return idProjectile::Explode( collision, ignore ); 2429 } 2430 2431 2432 /* 2433 =============================================================================== 2434 2435 idDebris 2436 2437 =============================================================================== 2438 */ 2439 2440 CLASS_DECLARATION( idEntity, idDebris ) 2441 EVENT( EV_Explode, idDebris::Event_Explode ) 2442 EVENT( EV_Fizzle, idDebris::Event_Fizzle ) 2443 END_CLASS 2444 2445 /* 2446 ================ 2447 idDebris::Spawn 2448 ================ 2449 */ 2450 void idDebris::Spawn() { 2451 owner = NULL; 2452 smokeFly = NULL; 2453 smokeFlyTime = 0; 2454 } 2455 2456 /* 2457 ================ 2458 idDebris::Create 2459 ================ 2460 */ 2461 void idDebris::Create( idEntity *owner, const idVec3 &start, const idMat3 &axis ) { 2462 Unbind(); 2463 GetPhysics()->SetOrigin( start ); 2464 GetPhysics()->SetAxis( axis ); 2465 GetPhysics()->SetContents( 0 ); 2466 this->owner = owner; 2467 smokeFly = NULL; 2468 smokeFlyTime = 0; 2469 sndBounce = NULL; 2470 noGrab = true; 2471 UpdateVisuals(); 2472 } 2473 2474 /* 2475 ================= 2476 idDebris::idDebris 2477 ================= 2478 */ 2479 idDebris::idDebris() { 2480 owner = NULL; 2481 smokeFly = NULL; 2482 smokeFlyTime = 0; 2483 sndBounce = NULL; 2484 } 2485 2486 /* 2487 ================= 2488 idDebris::~idDebris 2489 ================= 2490 */ 2491 idDebris::~idDebris() { 2492 } 2493 2494 /* 2495 ================= 2496 idDebris::Save 2497 ================= 2498 */ 2499 void idDebris::Save( idSaveGame *savefile ) const { 2500 owner.Save( savefile ); 2501 2502 savefile->WriteStaticObject( physicsObj ); 2503 2504 savefile->WriteParticle( smokeFly ); 2505 savefile->WriteInt( smokeFlyTime ); 2506 savefile->WriteSoundShader( sndBounce ); 2507 } 2508 2509 /* 2510 ================= 2511 idDebris::Restore 2512 ================= 2513 */ 2514 void idDebris::Restore( idRestoreGame *savefile ) { 2515 owner.Restore( savefile ); 2516 2517 savefile->ReadStaticObject( physicsObj ); 2518 RestorePhysics( &physicsObj ); 2519 2520 savefile->ReadParticle( smokeFly ); 2521 savefile->ReadInt( smokeFlyTime ); 2522 savefile->ReadSoundShader( sndBounce ); 2523 } 2524 2525 /* 2526 ================= 2527 idDebris::Launch 2528 ================= 2529 */ 2530 void idDebris::Launch() { 2531 float fuse; 2532 idVec3 velocity; 2533 idAngles angular_velocity; 2534 float linear_friction; 2535 float angular_friction; 2536 float contact_friction; 2537 float bounce; 2538 float mass; 2539 float gravity; 2540 idVec3 gravVec; 2541 bool randomVelocity; 2542 idMat3 axis; 2543 2544 renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); 2545 2546 spawnArgs.GetVector( "velocity", "0 0 0", velocity ); 2547 spawnArgs.GetAngles( "angular_velocity", "0 0 0", angular_velocity ); 2548 2549 linear_friction = spawnArgs.GetFloat( "linear_friction" ); 2550 angular_friction = spawnArgs.GetFloat( "angular_friction" ); 2551 contact_friction = spawnArgs.GetFloat( "contact_friction" ); 2552 bounce = spawnArgs.GetFloat( "bounce" ); 2553 mass = spawnArgs.GetFloat( "mass" ); 2554 gravity = spawnArgs.GetFloat( "gravity" ); 2555 fuse = spawnArgs.GetFloat( "fuse" ); 2556 randomVelocity = spawnArgs.GetBool ( "random_velocity" ); 2557 2558 if ( mass <= 0 ) { 2559 gameLocal.Error( "Invalid mass on '%s'\n", GetEntityDefName() ); 2560 } 2561 2562 if ( randomVelocity ) { 2563 velocity.x *= gameLocal.random.RandomFloat() + 0.5f; 2564 velocity.y *= gameLocal.random.RandomFloat() + 0.5f; 2565 velocity.z *= gameLocal.random.RandomFloat() + 0.5f; 2566 } 2567 2568 if ( health ) { 2569 fl.takedamage = true; 2570 } 2571 2572 gravVec = gameLocal.GetGravity(); 2573 gravVec.NormalizeFast(); 2574 axis = GetPhysics()->GetAxis(); 2575 2576 Unbind(); 2577 2578 physicsObj.SetSelf( this ); 2579 2580 // check if a clip model is set 2581 const char *clipModelName; 2582 idTraceModel trm; 2583 spawnArgs.GetString( "clipmodel", "", &clipModelName ); 2584 if ( !clipModelName[0] ) { 2585 clipModelName = spawnArgs.GetString( "model" ); // use the visual model 2586 } 2587 2588 // load the trace model 2589 if ( !collisionModelManager->TrmFromModel( clipModelName, trm ) ) { 2590 // default to a box 2591 physicsObj.SetClipBox( renderEntity.bounds, 1.0f ); 2592 } else { 2593 physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_ENTITY) idClipModel( trm ), 1.0f ); 2594 } 2595 2596 physicsObj.GetClipModel()->SetOwner( owner.GetEntity() ); 2597 physicsObj.SetMass( mass ); 2598 physicsObj.SetFriction( linear_friction, angular_friction, contact_friction ); 2599 if ( contact_friction == 0.0f ) { 2600 physicsObj.NoContact(); 2601 } 2602 physicsObj.SetBouncyness( bounce ); 2603 physicsObj.SetGravity( gravVec * gravity ); 2604 physicsObj.SetContents( 0 ); 2605 physicsObj.SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP ); 2606 physicsObj.SetLinearVelocity( axis[ 0 ] * velocity[ 0 ] + axis[ 1 ] * velocity[ 1 ] + axis[ 2 ] * velocity[ 2 ] ); 2607 physicsObj.SetAngularVelocity( angular_velocity.ToAngularVelocity() * axis ); 2608 physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); 2609 physicsObj.SetAxis( axis ); 2610 SetPhysics( &physicsObj ); 2611 2612 if ( !common->IsClient() ) { 2613 if ( fuse <= 0 ) { 2614 // run physics for 1 second 2615 RunPhysics(); 2616 PostEventMS( &EV_Remove, 0 ); 2617 } else if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) { 2618 if ( fuse < 0.0f ) { 2619 fuse = 0.0f; 2620 } 2621 RunPhysics(); 2622 PostEventSec( &EV_Explode, fuse ); 2623 } else { 2624 if ( fuse < 0.0f ) { 2625 fuse = 0.0f; 2626 } 2627 PostEventSec( &EV_Fizzle, fuse ); 2628 } 2629 } 2630 2631 StartSound( "snd_fly", SND_CHANNEL_BODY, 0, false, NULL ); 2632 2633 smokeFly = NULL; 2634 smokeFlyTime = 0; 2635 const char *smokeName = spawnArgs.GetString( "smoke_fly" ); 2636 if ( *smokeName != '\0' ) { 2637 smokeFly = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) ); 2638 smokeFlyTime = gameLocal.time; 2639 gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ ); 2640 } 2641 2642 const char *sndName = spawnArgs.GetString( "snd_bounce" ); 2643 if ( *sndName != '\0' ) { 2644 sndBounce = declManager->FindSound( sndName ); 2645 } 2646 2647 UpdateVisuals(); 2648 } 2649 2650 /* 2651 ================ 2652 idDebris::Think 2653 ================ 2654 */ 2655 void idDebris::Think() { 2656 2657 // run physics 2658 RunPhysics(); 2659 Present(); 2660 2661 if ( smokeFly && smokeFlyTime ) { 2662 if ( !gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ ) ) { 2663 smokeFlyTime = 0; 2664 } 2665 } 2666 } 2667 2668 /* 2669 ================ 2670 idDebris::Killed 2671 ================ 2672 */ 2673 void idDebris::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { 2674 if ( spawnArgs.GetBool( "detonate_on_death" ) ) { 2675 Explode(); 2676 } else { 2677 Fizzle(); 2678 } 2679 } 2680 2681 /* 2682 ================= 2683 idDebris::Collide 2684 ================= 2685 */ 2686 bool idDebris::Collide( const trace_t &collision, const idVec3 &velocity ) { 2687 if ( sndBounce != NULL ) { 2688 StartSoundShader( sndBounce, SND_CHANNEL_BODY, 0, false, NULL ); 2689 } 2690 sndBounce = NULL; 2691 return false; 2692 } 2693 2694 2695 /* 2696 ================ 2697 idDebris::Fizzle 2698 ================ 2699 */ 2700 void idDebris::Fizzle() { 2701 if ( IsHidden() ) { 2702 // already exploded 2703 return; 2704 } 2705 2706 StopSound( SND_CHANNEL_ANY, false ); 2707 StartSound( "snd_fizzle", SND_CHANNEL_BODY, 0, false, NULL ); 2708 2709 // fizzle FX 2710 const char *smokeName = spawnArgs.GetString( "smoke_fuse" ); 2711 if ( *smokeName != '\0' ) { 2712 smokeFly = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) ); 2713 smokeFlyTime = gameLocal.time; 2714 gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ ); 2715 } 2716 2717 fl.takedamage = false; 2718 physicsObj.SetContents( 0 ); 2719 physicsObj.PutToRest(); 2720 2721 Hide(); 2722 2723 if ( common->IsClient() && !fl.skipReplication ) { 2724 return; 2725 } 2726 2727 CancelEvents( &EV_Fizzle ); 2728 PostEventMS( &EV_Remove, 0 ); 2729 } 2730 2731 /* 2732 ================ 2733 idDebris::Explode 2734 ================ 2735 */ 2736 void idDebris::Explode() { 2737 if ( IsHidden() ) { 2738 // already exploded 2739 return; 2740 } 2741 2742 StopSound( SND_CHANNEL_ANY, false ); 2743 StartSound( "snd_explode", SND_CHANNEL_BODY, 0, false, NULL ); 2744 2745 Hide(); 2746 2747 // these must not be "live forever" particle systems 2748 smokeFly = NULL; 2749 smokeFlyTime = 0; 2750 const char *smokeName = spawnArgs.GetString( "smoke_detonate" ); 2751 if ( *smokeName != '\0' ) { 2752 smokeFly = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) ); 2753 smokeFlyTime = gameLocal.time; 2754 gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ ); 2755 } 2756 2757 fl.takedamage = false; 2758 physicsObj.SetContents( 0 ); 2759 physicsObj.PutToRest(); 2760 2761 CancelEvents( &EV_Explode ); 2762 PostEventMS( &EV_Remove, 0 ); 2763 } 2764 2765 /* 2766 ================ 2767 idDebris::Event_Explode 2768 ================ 2769 */ 2770 void idDebris::Event_Explode() { 2771 Explode(); 2772 } 2773 2774 /* 2775 ================ 2776 idDebris::Event_Fizzle 2777 ================ 2778 */ 2779 void idDebris::Event_Fizzle() { 2780 Fizzle(); 2781 } 2782 2783 /* 2784 =============================================================================== 2785 2786 idHomingProjectile 2787 2788 =============================================================================== 2789 */ 2790 2791 CLASS_DECLARATION( idProjectile, idHomingProjectile ) 2792 EVENT( EV_SetEnemy, idHomingProjectile::Event_SetEnemy ) 2793 END_CLASS 2794 2795 /* 2796 ================ 2797 idHomingProjectile::idHomingProjectile 2798 ================ 2799 */ 2800 idHomingProjectile::idHomingProjectile() { 2801 enemy = NULL; 2802 speed = 0.0f; 2803 turn_max = 0.0f; 2804 clamp_dist = 0.0f; 2805 rndScale = ang_zero; 2806 rndAng = ang_zero; 2807 angles = ang_zero; 2808 burstMode = false; 2809 burstDist = 0; 2810 burstVelocity = 0.0f; 2811 unGuided = false; 2812 seekPos = vec3_origin; 2813 } 2814 2815 /* 2816 ================= 2817 idHomingProjectile::~idHomingProjectile 2818 ================= 2819 */ 2820 idHomingProjectile::~idHomingProjectile() { 2821 } 2822 2823 /* 2824 ================ 2825 idHomingProjectile::Spawn 2826 ================ 2827 */ 2828 void idHomingProjectile::Spawn() { 2829 } 2830 2831 /* 2832 ================ 2833 idHomingProjectile::Save 2834 ================ 2835 */ 2836 void idHomingProjectile::Save( idSaveGame *savefile ) const { 2837 enemy.Save( savefile ); 2838 savefile->WriteFloat( speed ); 2839 savefile->WriteAngles( rndScale ); 2840 savefile->WriteAngles( rndAng ); 2841 savefile->WriteFloat( turn_max ); 2842 savefile->WriteFloat( clamp_dist ); 2843 savefile->WriteAngles( angles ); 2844 savefile->WriteBool( burstMode ); 2845 savefile->WriteBool( unGuided ); 2846 savefile->WriteFloat( burstDist ); 2847 savefile->WriteFloat( burstVelocity ); 2848 savefile->WriteVec3( seekPos ); 2849 } 2850 2851 /* 2852 ================ 2853 idHomingProjectile::Restore 2854 ================ 2855 */ 2856 void idHomingProjectile::Restore( idRestoreGame *savefile ) { 2857 enemy.Restore( savefile ); 2858 savefile->ReadFloat( speed ); 2859 savefile->ReadAngles( rndScale ); 2860 savefile->ReadAngles( rndAng ); 2861 savefile->ReadFloat( turn_max ); 2862 savefile->ReadFloat( clamp_dist ); 2863 savefile->ReadAngles( angles ); 2864 savefile->ReadBool( burstMode ); 2865 savefile->ReadBool( unGuided ); 2866 savefile->ReadFloat( burstDist ); 2867 savefile->ReadFloat( burstVelocity ); 2868 savefile->ReadVec3( seekPos ); 2869 } 2870 2871 2872 /* 2873 ================ 2874 idHomingProjectile::Think 2875 ================ 2876 */ 2877 void idHomingProjectile::Think() { 2878 if ( seekPos == vec3_zero ) { 2879 // ai def uses a single def_projectile .. guardian has two projectile types so when seekPos is zero, just run regular projectile 2880 idProjectile::Think(); 2881 return; 2882 } 2883 2884 idVec3 dir; 2885 idVec3 velocity; 2886 idVec3 nose; 2887 idVec3 tmp; 2888 idMat3 axis; 2889 idAngles dirAng; 2890 idAngles diff; 2891 float dist; 2892 float frac; 2893 int i; 2894 2895 2896 nose = physicsObj.GetOrigin() + 10.0f * physicsObj.GetAxis()[0]; 2897 2898 dir = seekPos - nose; 2899 dist = dir.Normalize(); 2900 dirAng = dir.ToAngles(); 2901 2902 // make it more accurate as it gets closer 2903 frac = ( dist * 2.0f ) / clamp_dist; 2904 if ( frac > 1.0f ) { 2905 frac = 1.0f; 2906 } 2907 2908 diff = dirAng - angles * frac; 2909 2910 // clamp the to the max turn rate 2911 diff.Normalize180(); 2912 for( i = 0; i < 3; i++ ) { 2913 if ( diff[ i ] > turn_max ) { 2914 diff[ i ] = turn_max; 2915 } else if ( diff[ i ] < -turn_max ) { 2916 diff[ i ] = -turn_max; 2917 } 2918 } 2919 angles += diff; 2920 2921 // make the visual model always points the dir we're traveling 2922 dir = angles.ToForward(); 2923 velocity = dir * speed; 2924 2925 if ( burstMode && dist < burstDist ) { 2926 unGuided = true; 2927 velocity *= burstVelocity; 2928 } 2929 2930 physicsObj.SetLinearVelocity( velocity ); 2931 2932 // align z-axis of model with the direction 2933 axis = dir.ToMat3(); 2934 tmp = axis[2]; 2935 axis[2] = axis[0]; 2936 axis[0] = -tmp; 2937 2938 GetPhysics()->SetAxis( axis ); 2939 2940 idProjectile::Think(); 2941 } 2942 2943 /* 2944 ================= 2945 idHomingProjectile::Launch 2946 ================= 2947 */ 2948 void idHomingProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, float dmgPower ) { 2949 idProjectile::Launch( start, dir, pushVelocity, timeSinceFire, launchPower, dmgPower ); 2950 if ( owner.GetEntity() ) { 2951 if ( owner.GetEntity()->IsType( idAI::Type ) ) { 2952 enemy = static_cast<idAI *>( owner.GetEntity() )->GetEnemy(); 2953 } else if ( owner.GetEntity()->IsType( idPlayer::Type ) ) { 2954 trace_t tr; 2955 idPlayer *player = static_cast<idPlayer*>( owner.GetEntity() ); 2956 idVec3 start = player->GetEyePosition(); 2957 idVec3 end = start + player->viewAxis[0] * 1000.0f; 2958 gameLocal.clip.TracePoint( tr, start, end, MASK_SHOT_RENDERMODEL | CONTENTS_BODY, owner.GetEntity() ); 2959 if ( tr.fraction < 1.0f ) { 2960 enemy = gameLocal.GetTraceEntity( tr ); 2961 } 2962 // ignore actors on the player's team 2963 if ( enemy.GetEntity() == NULL || !enemy.GetEntity()->IsType( idActor::Type ) || ( static_cast<idActor *>( enemy.GetEntity() )->team == player->team ) ) { 2964 enemy = player->EnemyWithMostHealth(); 2965 } 2966 } 2967 } 2968 const idVec3 &vel = physicsObj.GetLinearVelocity(); 2969 angles = vel.ToAngles(); 2970 speed = vel.Length(); 2971 rndScale = spawnArgs.GetAngles( "random", "15 15 0" ); 2972 turn_max = spawnArgs.GetFloat( "turn_max", "180" ) / com_engineHz_latched; 2973 clamp_dist = spawnArgs.GetFloat( "clamp_dist", "256" ); 2974 burstMode = spawnArgs.GetBool( "burstMode" ); 2975 unGuided = false; 2976 burstDist = spawnArgs.GetFloat( "burstDist", "64" ); 2977 burstVelocity = spawnArgs.GetFloat( "burstVelocity", "1.25" ); 2978 UpdateVisuals(); 2979 } 2980 2981 void idHomingProjectile::SetEnemy( idEntity *ent ) { 2982 enemy = ent; 2983 } 2984 void idHomingProjectile::SetSeekPos( idVec3 pos ) { 2985 seekPos = pos; 2986 } 2987 void idHomingProjectile::Event_SetEnemy(idEntity *ent) { 2988 SetEnemy(ent); 2989 }