DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

Grabber.cpp (20267B)


      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 #include "../idlib/precompiled.h"
     29 #pragma hdrstop
     30 
     31 
     32 #include "Game_local.h"
     33 #include "Misc.h"
     34 
     35 #define MAX_DRAG_TRACE_DISTANCE			384.0f
     36 #define TRACE_BOUNDS_SIZE				3.f
     37 #define HOLD_DISTANCE					72.f
     38 #define FIRING_DELAY					1000.0f
     39 #define DRAG_FAIL_LEN					64.f
     40 #define	THROW_SCALE						1000
     41 #define MAX_PICKUP_VELOCITY				1500 * 1500
     42 #define MAX_PICKUP_SIZE					96
     43 
     44 /*
     45 ===============================================================================
     46 
     47 	Allows entities to be dragged through the world with physics.
     48 
     49 ===============================================================================
     50 */
     51 
     52 CLASS_DECLARATION( idEntity, idGrabber )
     53 END_CLASS
     54 
     55 /*
     56 ==============
     57 idGrabber::idGrabber
     58 ==============
     59 */
     60 idGrabber::idGrabber() {
     61 	dragEnt = NULL;
     62 	owner = NULL;
     63 	beam = NULL;
     64 	beamTarget = NULL;
     65 	oldImpulseSequence = 0;
     66 	shakeForceFlip = false;
     67 	holdingAF = false;
     68 	endTime = 0;
     69 	lastFiredTime = -FIRING_DELAY;
     70 	dragFailTime = 0;
     71 	startDragTime = 0;
     72 	warpId = -1;
     73 	dragTraceDist = MAX_DRAG_TRACE_DISTANCE;
     74 }
     75 
     76 /*
     77 ==============
     78 idGrabber::~idGrabber
     79 ==============
     80 */
     81 idGrabber::~idGrabber() {
     82 	StopDrag( true );
     83 	if ( beam ) {
     84 		delete beam;
     85 	}
     86 	if ( beamTarget ) {
     87 		delete beamTarget;
     88 	}
     89 }
     90 
     91 /*
     92 ==============
     93 idGrabber::Save
     94 ==============
     95 */
     96 void idGrabber::Save( idSaveGame *savefile ) const {
     97 
     98 	dragEnt.Save( savefile );
     99 	savefile->WriteStaticObject( drag );
    100 
    101 	savefile->WriteVec3( saveGravity );
    102 	savefile->WriteInt( id );
    103 
    104 	savefile->WriteVec3( localPlayerPoint );
    105 
    106 	owner.Save( savefile );
    107 
    108 	savefile->WriteBool( holdingAF );
    109 	savefile->WriteBool( shakeForceFlip );
    110 
    111 	savefile->WriteInt( endTime );
    112 	savefile->WriteInt( lastFiredTime );
    113 	savefile->WriteInt( dragFailTime );
    114 	savefile->WriteInt( startDragTime );
    115 	savefile->WriteFloat( dragTraceDist );
    116 	savefile->WriteInt( savedContents );
    117 	savefile->WriteInt( savedClipmask );
    118 
    119 	savefile->WriteObject( beam );
    120 	savefile->WriteObject( beamTarget );
    121 
    122 	savefile->WriteInt( warpId );
    123 }
    124 
    125 /*
    126 ==============
    127 idGrabber::Restore
    128 ==============
    129 */
    130 void idGrabber::Restore( idRestoreGame *savefile ) {
    131 	//Spawn the beams
    132 	Initialize();
    133 
    134 	dragEnt.Restore( savefile );
    135 	savefile->ReadStaticObject( drag );
    136 
    137 	savefile->ReadVec3( saveGravity );
    138 	savefile->ReadInt( id );
    139 
    140 	// Restore the drag force's physics object
    141 	if ( dragEnt.IsValid() ) {
    142 		drag.SetPhysics( dragEnt.GetEntity()->GetPhysics(), id, dragEnt.GetEntity()->GetPhysics()->GetOrigin() );
    143 	}
    144 
    145 	savefile->ReadVec3( localPlayerPoint );
    146 
    147 	owner.Restore( savefile );
    148 
    149 	savefile->ReadBool( holdingAF );
    150 	savefile->ReadBool( shakeForceFlip );
    151 
    152 	savefile->ReadInt( endTime );
    153 	savefile->ReadInt( lastFiredTime );
    154 	savefile->ReadInt( dragFailTime );
    155 	savefile->ReadInt( startDragTime );
    156 	savefile->ReadFloat( dragTraceDist );
    157 	savefile->ReadInt( savedContents );
    158 	savefile->ReadInt( savedClipmask );
    159 
    160 	savefile->ReadObject( reinterpret_cast<idClass *&>(beam) );
    161 	savefile->ReadObject( reinterpret_cast<idClass *&>(beamTarget) );
    162 
    163 	savefile->ReadInt( warpId );
    164 }
    165 
    166 /*
    167 ==============
    168 idGrabber::Initialize
    169 ==============
    170 */
    171 void idGrabber::Initialize() {
    172 	if ( !common->IsMultiplayer() ) {
    173 		idDict args;
    174 
    175 		if ( !beamTarget ) {
    176 			args.SetVector( "origin", vec3_origin );
    177 			args.SetBool( "start_off", true );
    178 			beamTarget = ( idBeam * )gameLocal.SpawnEntityType( idBeam::Type, &args );
    179 		}
    180 
    181 		if ( !beam ) {
    182 			args.Clear();
    183 			args.Set( "target", beamTarget->name.c_str() );
    184 			args.SetVector( "origin", vec3_origin );
    185 			args.SetBool( "start_off", true );
    186 			args.Set( "width", "6" );
    187 			args.Set( "skin", "textures/smf/flareSizeable" );
    188 			args.Set( "_color", "0.0235 0.843 0.969 0.2" );
    189 			beam = ( idBeam * )gameLocal.SpawnEntityType( idBeam::Type, &args );
    190 			beam->SetShaderParm( 6, 1.0f );
    191 		}
    192 
    193 		endTime = 0;
    194 		dragTraceDist = MAX_DRAG_TRACE_DISTANCE;
    195 	}
    196 	else {
    197 		beam = NULL;
    198 		beamTarget = NULL;
    199 		endTime = 0;
    200 		dragTraceDist = MAX_DRAG_TRACE_DISTANCE;
    201 	};
    202 }
    203 
    204 /*
    205 ==============
    206 idGrabber::SetDragDistance
    207 ==============
    208 */
    209 void idGrabber::SetDragDistance( float dist ) {
    210 	dragTraceDist = dist;
    211 }
    212 
    213 /*
    214 ==============
    215 idGrabber::StartDrag
    216 ==============
    217 */
    218 void idGrabber::StartDrag( idEntity *grabEnt, int id ) {
    219 	int clipModelId = id;
    220 	idPlayer *thePlayer = owner.GetEntity();
    221 
    222 	holdingAF = false;
    223 	dragFailTime = gameLocal.slow.time;
    224 	startDragTime = gameLocal.slow.time;
    225 
    226 	oldImpulseSequence = thePlayer->usercmd.impulseSequence;
    227 
    228 	// set grabbed state for networking
    229 	grabEnt->SetGrabbedState( true );
    230 
    231 	// This is the new object to drag around
    232 	dragEnt = grabEnt;
    233 
    234 	// Show the beams!
    235 	UpdateBeams();
    236 	if ( beam ) {
    237 		beam->Show();
    238 	}
    239 	if ( beamTarget ) {
    240 		beamTarget->Show();
    241 	}
    242 
    243 	// Move the object to the fast group (helltime)
    244 	grabEnt->timeGroup = TIME_GROUP2;
    245 
    246 	// Handle specific class types
    247 	if ( grabEnt->IsType( idProjectile::Type ) ) {
    248 		idProjectile* p = (idProjectile*)grabEnt;
    249 
    250 		p->CatchProjectile( thePlayer, "_catch" );
    251 
    252 		// Make the projectile non-solid to other projectiles/enemies (special hack for helltime hunter)
    253 		if ( !idStr::Cmp( grabEnt->GetEntityDefName(), "projectile_helltime_killer" ) ) {
    254 			savedContents = CONTENTS_PROJECTILE;
    255 			savedClipmask = MASK_SHOT_RENDERMODEL|CONTENTS_PROJECTILE;
    256 		} else {
    257 			savedContents = grabEnt->GetPhysics()->GetContents();
    258 			savedClipmask = grabEnt->GetPhysics()->GetClipMask();
    259 		}
    260 		grabEnt->GetPhysics()->SetContents( 0 );
    261 		grabEnt->GetPhysics()->SetClipMask( CONTENTS_SOLID|CONTENTS_BODY );
    262 
    263 	} else if ( grabEnt->IsType( idExplodingBarrel::Type ) ) {
    264 		idExplodingBarrel *ebarrel = static_cast<idExplodingBarrel*>(grabEnt);
    265 
    266 		ebarrel->StartBurning();
    267 
    268 	} else if ( grabEnt->IsType( idAFEntity_Gibbable::Type ) ) {
    269 		holdingAF = true;
    270 		clipModelId = 0;
    271 
    272 		if ( grabbableAI( grabEnt->spawnArgs.GetString( "classname" ) ) ) {
    273 			idAI *aiEnt = static_cast<idAI*>(grabEnt);
    274 
    275 			aiEnt->StartRagdoll();
    276 		}
    277 	} else if ( grabEnt->IsType( idMoveableItem::Type ) ) {
    278 		grabEnt->PostEventMS( &EV_Touch, 250, thePlayer, NULL );
    279 	}
    280 
    281 	// Get the current physics object to manipulate
    282 	idPhysics *phys = grabEnt->GetPhysics();
    283 
    284 	// Turn off gravity on object
    285 	saveGravity = phys->GetGravity();
    286 	phys->SetGravity( vec3_origin );
    287 
    288 	// hold it directly in front of player
    289 	localPlayerPoint = ( thePlayer->firstPersonViewAxis[0] * HOLD_DISTANCE ) * thePlayer->firstPersonViewAxis.Transpose();
    290 
    291 	// Set the ending time for the hold
    292 	endTime = gameLocal.time + g_grabberHoldSeconds.GetFloat() * 1000;
    293 
    294 	// Start up the Force_Drag to bring it in
    295 	drag.Init( g_grabberDamping.GetFloat() );
    296 	drag.SetPhysics( phys, clipModelId, thePlayer->firstPersonViewOrigin + localPlayerPoint * thePlayer->firstPersonViewAxis);
    297 
    298 	// start the screen warp
    299 	warpId = thePlayer->playerView.AddWarp( phys->GetOrigin(), SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 160, 2000 );
    300 }
    301 
    302 /*
    303 ==============
    304 idGrabber::StopDrag
    305 ==============
    306 */
    307 void idGrabber::StopDrag( bool dropOnly ) {
    308 	idPlayer *thePlayer = owner.GetEntity();
    309 
    310 	if ( beam ) {
    311 		beam->Hide();
    312 	}
    313 	if ( beamTarget ) {
    314 		beamTarget->Hide();
    315 	}
    316 
    317 	if ( dragEnt.IsValid() ) {
    318 		idEntity *ent = dragEnt.GetEntity();
    319 
    320 		// set grabbed state for networking
    321 		ent->SetGrabbedState( false );
    322 
    323 		// If a cinematic has started, allow dropped object to think in cinematics
    324 		if ( gameLocal.inCinematic ) {
    325 			ent->cinematic = true;
    326 		}
    327 
    328 		// Restore Gravity
    329 		ent->GetPhysics()->SetGravity( saveGravity );
    330 
    331 		// Move the object back to the slow group (helltime)
    332 		ent->timeGroup = TIME_GROUP1;
    333 
    334 		if ( holdingAF ) {
    335 			idAFEntity_Gibbable *af = static_cast<idAFEntity_Gibbable *>(ent);
    336 			idPhysics_AF	*af_Phys = static_cast<idPhysics_AF*>(af->GetPhysics());
    337 
    338 			if ( grabbableAI( ent->spawnArgs.GetString( "classname" ) ) ) {
    339 				idAI *aiEnt = static_cast<idAI*>(ent);
    340 
    341 				aiEnt->Damage( thePlayer, thePlayer, vec3_origin, "damage_suicide", 1.0f, INVALID_JOINT );
    342 			}
    343 			
    344 			af->SetThrown( !dropOnly );
    345 
    346 			// Reset timers so that it isn't forcibly put to rest in mid-air
    347 			af_Phys->PutToRest();
    348 			af_Phys->Activate();
    349 
    350 			af_Phys->SetTimeScaleRamp( MS2SEC(gameLocal.slow.time) - 1.5f, MS2SEC(gameLocal.slow.time) + 1.0f );
    351 		}
    352 
    353 		// If the object isn't near its goal, just drop it in place.
    354 		if ( !ent->IsType( idProjectile::Type ) && ( dropOnly || drag.GetDistanceToGoal() > DRAG_FAIL_LEN ) ) {
    355 			ent->GetPhysics()->SetLinearVelocity( vec3_origin );
    356 			thePlayer->StartSoundShader( declManager->FindSound( "grabber_maindrop" ), SND_CHANNEL_WEAPON, 0, false, NULL );
    357 
    358 			if ( ent->IsType( idExplodingBarrel::Type ) ) {
    359 				idExplodingBarrel *ebarrel = static_cast<idExplodingBarrel*>(ent);
    360 
    361 				ebarrel->SetStability( true );
    362 				ebarrel->StopBurning();
    363 			}
    364 		} else {
    365 			// Shoot the object forward
    366 			ent->ApplyImpulse( thePlayer, 0, ent->GetPhysics()->GetOrigin(), thePlayer->firstPersonViewAxis[0] * THROW_SCALE * ent->GetPhysics()->GetMass() );
    367 			thePlayer->StartSoundShader( declManager->FindSound( "grabber_release" ), SND_CHANNEL_WEAPON, 0, false, NULL );
    368 
    369 			// Orient projectiles away from the player
    370 			if ( ent->IsType( idProjectile::Type ) ) {
    371 				idPlayer *player = owner.GetEntity();
    372 				idAngles ang = player->firstPersonViewAxis[0].ToAngles();
    373 
    374 				ang.pitch += 90.f;
    375 				ent->GetPhysics()->SetAxis( ang.ToMat3() );
    376 				ent->GetPhysics()->SetAngularVelocity( vec3_origin );
    377 
    378 				// Restore projectile contents
    379 				ent->GetPhysics()->SetContents( savedContents );
    380 				ent->GetPhysics()->SetClipMask( savedClipmask );
    381 
    382 				idProjectile *projectile = static_cast< idProjectile* >( ent );
    383 				if ( projectile != NULL ) {
    384 					projectile->SetLaunchedFromGrabber( true );
    385 				}
    386 
    387 			} else if ( ent->IsType( idMoveable::Type ) ) {
    388 				// Turn on damage for this object
    389 				idMoveable *obj = static_cast<idMoveable*>(ent);
    390 				obj->EnableDamage( true, 2.5f );
    391 				obj->SetAttacker( thePlayer );
    392 
    393 				if ( ent->IsType( idExplodingBarrel::Type ) ) {
    394 					idExplodingBarrel *ebarrel = static_cast<idExplodingBarrel*>(ent);
    395 					ebarrel->SetStability( false );
    396 				}
    397 
    398 			} else if ( ent->IsType( idMoveableItem::Type ) ) {
    399 				ent->GetPhysics()->SetClipMask( MASK_MONSTERSOLID );
    400 			}
    401 		}
    402 
    403 		// Remove the Force_Drag's control of the entity
    404 		drag.RemovePhysics( ent->GetPhysics() );
    405 	}
    406 
    407 	if ( warpId != -1 ) {
    408 		thePlayer->playerView.FreeWarp( warpId );
    409 		warpId = -1;
    410 	}
    411 
    412 	lastFiredTime = gameLocal.time;
    413 	dragEnt = NULL;
    414 	endTime = 0;
    415 }
    416 
    417 /*
    418 ==============
    419 idGrabber::Update
    420 ==============
    421 */
    422 int idGrabber::Update( idPlayer *player, bool hide ) {
    423 	trace_t trace;
    424 	idEntity *newEnt;
    425 
    426 	// pause before allowing refire
    427 	if ( lastFiredTime + FIRING_DELAY > gameLocal.time ) {
    428 		return 3;
    429 	}
    430 
    431 	// Dead players release the trigger
    432 	if ( hide || player->health <= 0 ) {
    433 		StopDrag( true );
    434 		if ( hide ) {
    435 			lastFiredTime = gameLocal.time - FIRING_DELAY + 250;
    436 		}
    437 		return 3;
    438 	}
    439 
    440 	// Check if object being held has been removed (dead demon, projectile, etc.)
    441 	if ( endTime > gameLocal.time ) {
    442 		bool abort = !dragEnt.IsValid();
    443 
    444 		if ( !abort && dragEnt.GetEntity()->IsType( idProjectile::Type ) ) {
    445 			idProjectile *proj = (idProjectile *)dragEnt.GetEntity();
    446 
    447 			if ( proj->GetProjectileState() >= 3 ) {
    448 				abort = true;
    449 			}
    450 		}
    451 		if ( !abort && dragEnt.GetEntity() && dragEnt.GetEntity()->IsHidden() ) {
    452 			abort = true;
    453 		}
    454 		// Not in multiplayer :: Pressing "reload" lets you carefully drop an item
    455 		if ( !common->IsMultiplayer() && !abort && ( player->usercmd.impulseSequence != oldImpulseSequence ) && (player->usercmd.impulse == IMPULSE_13) ) {
    456 			abort = true;
    457 		}
    458         
    459 		if ( abort ) {
    460 			StopDrag( true );
    461 			return 3;
    462 		}
    463 	}
    464 
    465 	owner = player;
    466 
    467 	// if no entity selected for dragging
    468     if ( !dragEnt.GetEntity() ) {
    469 		idBounds bounds;
    470 		idVec3 end = player->firstPersonViewOrigin + player->firstPersonViewAxis[0] * dragTraceDist;
    471 
    472 		bounds.Zero();
    473 		bounds.ExpandSelf( TRACE_BOUNDS_SIZE );
    474 
    475 		gameLocal.clip.TraceBounds( trace, player->firstPersonViewOrigin, end, bounds, MASK_SHOT_RENDERMODEL|CONTENTS_PROJECTILE|CONTENTS_MOVEABLECLIP, player );
    476 		// If the trace hit something
    477 		if ( trace.fraction < 1.0f ) {
    478 			newEnt = gameLocal.entities[ trace.c.entityNum ];
    479 
    480 			// if entity is already being grabbed then bypass
    481 			if ( common->IsMultiplayer() && newEnt && newEnt->IsGrabbed() ) {
    482 				return 0;
    483 			}
    484 
    485 			// Check if this is a valid entity to hold
    486 			if ( newEnt && ( newEnt->IsType( idMoveable::Type ) ||
    487 					newEnt->IsType( idMoveableItem::Type ) ||
    488 					newEnt->IsType( idProjectile::Type ) ||
    489 					newEnt->IsType( idAFEntity_Gibbable::Type )
    490 					) &&
    491 					newEnt->noGrab == false &&
    492 					newEnt->GetPhysics()->GetBounds().GetRadius() < MAX_PICKUP_SIZE &&
    493 					newEnt->GetPhysics()->GetLinearVelocity().LengthSqr() < MAX_PICKUP_VELOCITY ) {
    494 
    495 				bool validAF = true;
    496 
    497 				if ( newEnt->IsType( idAFEntity_Gibbable::Type ) ) {
    498 					idAFEntity_Gibbable *afEnt = static_cast<idAFEntity_Gibbable*>(newEnt);
    499 
    500 					if ( grabbableAI( newEnt->spawnArgs.GetString( "classname" ) ) ) {
    501 						// Make sure it's also active
    502 						if ( !afEnt->IsActive() ) {
    503 							validAF = false;
    504 						}
    505 					} else if ( !afEnt->IsActiveAF() ) {
    506 						validAF = false;
    507 					}
    508 				}
    509 
    510 				if ( validAF && player->usercmd.buttons & BUTTON_ATTACK ) {
    511 					// Grab this entity and start dragging it around
    512 					StartDrag( newEnt, trace.c.id );
    513 				} else if ( validAF ) {
    514 					// A holdable object is ready to be grabbed
    515 					return 1;
    516 				}
    517 			}
    518 		}
    519 	}
    520 
    521 	// check backwards server time in multiplayer
    522 	bool allow = true;
    523 
    524 	if ( common->IsMultiplayer() ) {
    525 
    526 		// if we've marched backwards
    527 		if ( gameLocal.slow.time < startDragTime ) {
    528 			allow = false;
    529 		}
    530 	}
    531 
    532 
    533 	// if there is an entity selected for dragging
    534 	if ( dragEnt.GetEntity() && allow ) {
    535 		idPhysics *entPhys = dragEnt.GetEntity()->GetPhysics();
    536 		idVec3 goalPos;
    537 
    538 		// If the player lets go of attack, or time is up
    539 		if ( !( player->usercmd.buttons & BUTTON_ATTACK ) ) {
    540 			StopDrag( false );
    541 			return 3;
    542 		}
    543 		if ( gameLocal.time > endTime ) {
    544 			StopDrag( true );
    545 			return 3;
    546 		}
    547 
    548 		// Check if the player is standing on the object
    549 		if ( !holdingAF ) {
    550 			idBounds	playerBounds;
    551 			idBounds	objectBounds = entPhys->GetAbsBounds();
    552 			idVec3		newPoint = player->GetPhysics()->GetOrigin();
    553 
    554 			// create a bounds at the players feet
    555 			playerBounds.Clear();
    556 			playerBounds.AddPoint( newPoint );
    557 			newPoint.z -= 1.f;
    558 			playerBounds.AddPoint( newPoint );
    559 			playerBounds.ExpandSelf( 8.f );
    560 
    561 			// If it intersects the object bounds, then drop it
    562 			if ( playerBounds.IntersectsBounds( objectBounds ) ) {
    563 				StopDrag( true );
    564 				return 3;
    565 			}
    566 		}
    567 
    568 		// Shake the object at the end of the hold
    569 		if ( g_grabberEnableShake.GetBool() && !common->IsMultiplayer() ) {
    570 			ApplyShake();
    571 		}
    572 
    573 		// Set and evaluate drag force
    574 		goalPos = player->firstPersonViewOrigin + localPlayerPoint * player->firstPersonViewAxis;
    575 
    576 		drag.SetGoalPosition( goalPos );
    577 		drag.Evaluate( gameLocal.time );
    578 
    579 		// If an object is flying too fast toward the player, stop it hard
    580 		if ( g_grabberHardStop.GetBool() ) {
    581 			idPlane theWall;
    582 			idVec3 toPlayerVelocity, objectCenter;
    583 			float toPlayerSpeed;
    584 
    585 			toPlayerVelocity = -player->firstPersonViewAxis[0];
    586 			toPlayerSpeed = entPhys->GetLinearVelocity() * toPlayerVelocity;
    587 
    588 			if ( toPlayerSpeed > 64.f ) {
    589 				objectCenter = entPhys->GetAbsBounds().GetCenter();
    590 
    591 				theWall.SetNormal( player->firstPersonViewAxis[0] );
    592 				theWall.FitThroughPoint( goalPos );
    593 
    594 				if ( theWall.Side( objectCenter, 0.1f ) == PLANESIDE_BACK ) {
    595 					int i, num;
    596 
    597 					num = entPhys->GetNumClipModels();
    598 					for ( i=0; i<num; i++ ) {
    599 						entPhys->SetLinearVelocity( vec3_origin, i );
    600 					}
    601 				}
    602 			}
    603 
    604 			// Make sure the object isn't spinning too fast
    605 			const float MAX_ROTATION_SPEED = 12.f;
    606 
    607 			idVec3	angVel = entPhys->GetAngularVelocity();
    608 			float	rotationSpeed = angVel.LengthFast();
    609 
    610 			if ( rotationSpeed > MAX_ROTATION_SPEED ) {
    611 				angVel.NormalizeFast();
    612 				angVel *= MAX_ROTATION_SPEED;
    613 				entPhys->SetAngularVelocity( angVel );
    614 			}
    615 		}
    616 
    617 		// Orient projectiles away from the player
    618 		if ( dragEnt.GetEntity()->IsType( idProjectile::Type ) ) {
    619 			idAngles ang = player->firstPersonViewAxis[0].ToAngles();
    620 			ang.pitch += 90.f;
    621 			entPhys->SetAxis( ang.ToMat3() );
    622 		}
    623 
    624 		// Some kind of effect from gun to object?
    625 		UpdateBeams();
    626 
    627 		// If the object is stuck away from its intended position for more than 500ms, let it go.
    628 		if ( drag.GetDistanceToGoal() > DRAG_FAIL_LEN ) {
    629 			if ( dragFailTime < (gameLocal.slow.time - 500) ) {
    630 				StopDrag( true );
    631 				return 3;
    632 			}
    633 		} else {
    634 			dragFailTime = gameLocal.slow.time;
    635 		}
    636 
    637 		// Currently holding an object
    638 		return 2;
    639 	}
    640 
    641 	// Not holding, nothing to hold
    642 	return 0;
    643 }
    644 
    645 /*
    646 ======================
    647 idGrabber::UpdateBeams
    648 ======================
    649 */
    650 void idGrabber::UpdateBeams() {
    651 	jointHandle_t	muzzle_joint;
    652 	idVec3	muzzle_origin;
    653 	idMat3	muzzle_axis;
    654 	renderEntity_t *re;
    655 
    656 	if ( !beam ) {
    657 		return;
    658 	}
    659 
    660 	if ( dragEnt.IsValid() ) {
    661 		idPlayer *thePlayer = owner.GetEntity();
    662 
    663 		if ( beamTarget ) {
    664 			beamTarget->SetOrigin( dragEnt.GetEntity()->GetPhysics()->GetAbsBounds().GetCenter() );
    665 		}
    666 
    667 		muzzle_joint = thePlayer->weapon.GetEntity()->GetAnimator()->GetJointHandle( "particle_upper" );
    668 		if ( muzzle_joint != INVALID_JOINT ) {
    669 			thePlayer->weapon.GetEntity()->GetJointWorldTransform( muzzle_joint, gameLocal.time, muzzle_origin, muzzle_axis );
    670 		} else {
    671 			muzzle_origin = thePlayer->GetPhysics()->GetOrigin();
    672 		}
    673 
    674 		beam->SetOrigin( muzzle_origin );
    675 		re = beam->GetRenderEntity();
    676 		re->origin = muzzle_origin;
    677 
    678 		beam->UpdateVisuals();
    679 		beam->Present();
    680 	}
    681 }
    682 
    683 /*
    684 ==============
    685 idGrabber::ApplyShake
    686 ==============
    687 */
    688 void idGrabber::ApplyShake() {
    689 	float u = 1 - (float)( endTime - gameLocal.time ) / ( g_grabberHoldSeconds.GetFloat() * 1000 );
    690 
    691 	if ( u >= 0.8f ) {
    692 		idVec3 point, impulse;
    693 		float shakeForceMagnitude = 450.f;
    694 		float mass = dragEnt.GetEntity()->GetPhysics()->GetMass();
    695 
    696 		shakeForceFlip = !shakeForceFlip;
    697 
    698 		// get point to rotate around
    699 		point = dragEnt.GetEntity()->GetPhysics()->GetOrigin();
    700 		point.y += 1;
    701 
    702 		// Articulated figures get less violent shake
    703 		if ( holdingAF ) {
    704 			shakeForceMagnitude = 120.f;
    705 		}
    706 
    707 		// calc impulse
    708 		if ( shakeForceFlip ) {
    709 			impulse.Set( 0, 0, shakeForceMagnitude * u * mass );
    710 		}
    711 		else {
    712 			impulse.Set( 0, 0, -shakeForceMagnitude * u * mass );
    713 		}
    714 
    715 		dragEnt.GetEntity()->ApplyImpulse( NULL, 0, point, impulse );
    716 	}
    717 }
    718 
    719 /*
    720 ==============
    721 idGrabber::grabbableAI
    722 ==============
    723 */
    724 bool idGrabber::grabbableAI( const char *aiName ) {
    725 	// skip "monster_"
    726 	aiName += 8;
    727 
    728 	if (!idStr::Cmpn( aiName, "flying_lostsoul", 15 ) ||
    729 		!idStr::Cmpn( aiName, "demon_trite", 11 ) ||
    730 		!idStr::Cmp( aiName, "flying_forgotten" ) ||
    731 		!idStr::Cmp( aiName, "demon_cherub" ) ||
    732 		!idStr::Cmp( aiName, "demon_tick" )) {
    733 
    734 		return true;
    735 	}
    736 
    737 	return false;
    738 }
    739