Exemple #1
0
/*
============
idClip::TraceRenderModel
============
*/
void idClip::TraceRenderModel( trace_t &trace, const idVec3 &start, const idVec3 &end, const float radius, const idMat3 &axis, idClipModel *touch ) const {
	trace.fraction = 1.0f;

	// if the trace is passing through the bounds
	if ( touch->absBounds.Expand( radius ).LineIntersection( start, end ) ) {
		modelTrace_t modelTrace;

		// test with exact render model and modify trace_t structure accordingly
		if ( gameRenderWorld->ModelTrace( modelTrace, touch->renderModelHandle, start, end, radius ) ) {
			trace.fraction = modelTrace.fraction;
			trace.endAxis = axis;
			trace.endpos = modelTrace.point;
			trace.c.normal = modelTrace.normal;
			trace.c.dist = modelTrace.point * modelTrace.normal;
			trace.c.point = modelTrace.point;
			trace.c.type = CONTACT_TRMVERTEX;
			trace.c.modelFeature = 0;
			trace.c.trmFeature = 0;
			trace.c.contents = modelTrace.material->GetContentFlags();
			trace.c.material = modelTrace.material;
			// NOTE: trace.c.id will be the joint number
			touch->id = JOINT_HANDLE_TO_CLIPMODEL_ID( modelTrace.jointNumber );
		}
	}
}
void CMeleeWeapon::HandleValidCollision(trace_t &tr, idVec3 trOrigin, idVec3 PointVelDir)
{
	int location;

	idEntity *other = gameLocal.entities[ tr.c.entityNum ];
	DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeWeapon: Hit entity %s\r", other->name.c_str());
	// Show the initial trace collision point
	if( cv_melee_debug.GetBool() )
		gameRenderWorld->DebugArrow( colorBlue, trOrigin, tr.c.point, 3, 1000 );

	location = JOINT_HANDLE_TO_CLIPMODEL_ID( tr.c.id );

	// Secondary trace for when we hit the AF structure of an AI and want
	// to see where we would hit on the actual model
	if(	(tr.c.contents & CONTENTS_CORPSE)
		&& other->IsType(idAFEntity_Base::Type) )
	{
		idAFEntity_Base *otherAF = static_cast<idAFEntity_Base *>(other);
		trace_t tr2;

		// NOTE: Just extrapolating along the velocity can fail when the AF
		// extends outside of the rendermodel
		// Just using the AF face normal can fail when hit at a corner
		// So take an equal average of both
		idVec3 trDir = ( PointVelDir - tr.c.normal ) / 2.0f;
		trDir.Normalize();

		idVec3 start = tr.c.point - 8.0f * trDir;
		int contentsEnt = GetPhysics()->GetContents();
		GetPhysics()->SetContents( CONTENTS_FLASHLIGHT_TRIGGER );
		
		gameLocal.clip.TracePoint
			( 
				tr2, start, tr.c.point + 8.0f * trDir, 
				CONTENTS_RENDERMODEL, m_Owner.GetEntity() 
			);
		GetPhysics()->SetContents( contentsEnt );

		// Ishtvan: Ignore secondary trace if we hit a swapped-in head model on the first pass
		// we want to register a hit on that swapped in head body for gameplay reasons
		bool bHitSwappedHead = false;
		if( otherAF->IsType(idAI::Type) )
		{
			idAI *otherAI = static_cast<idAI *>(otherAF);
			if( otherAI->m_bHeadCMSwapped && (tr.c.id == otherAI->m_HeadBodyID) )
				bHitSwappedHead = true;
		}

		if( tr2.fraction < 1.0f && !bHitSwappedHead )
		{
			
			tr = tr2;
			other = gameLocal.entities[ tr.c.entityNum ];
			DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeWeapon: CONTENTS_CORPSE secondary trace hit entity %s\r", other->name.c_str());

			// Draw the new collision point
			if( cv_melee_debug.GetBool() )
			{
				gameRenderWorld->DebugArrow( colorRed, start, tr2.c.point, 3, 1000 );
			}

			other = gameLocal.entities[tr2.c.entityNum];
			// update location
			location = JOINT_HANDLE_TO_CLIPMODEL_ID( tr.c.id );
		}
		else
		{
			// failed to connect with the rendermodel.  
			// Last ditch effort to find the correct AF body
			location = otherAF->JointForBody(tr.c.id);

			// If we failed to find anything, draw the attempted trace in green
			if( cv_melee_debug.GetBool() )
				gameRenderWorld->DebugArrow( colorGreen, start, tr.c.point + 8.0f * PointVelDir, 3, 1000 );
		}

		// Uncomment for translational and angular velocity debugging
		if( cv_melee_debug.GetBool() )
		{
		// no longer works with new function format
		// if we really want to separate angular and linear,
		// we could do this same operation in outer function
		/*
			idVec3 vLinDir = vLinearTrans;
			idVec3 vSpinDir = vSpinTrans;
			vLinDir.Normalize();
			vSpinDir.Normalize();

			gameRenderWorld->DebugArrow
				(
					colorCyan, (tr.c.point - 8.0f * vLinDir), 
					(tr.c.point + 8.0f * vLinDir), 3, 1000
				);
			// Uncomment for translational and angular velocity debugging
			gameRenderWorld->DebugArrow
				(
					colorPurple, (tr.c.point - 8.0f * vSpinDir), 
					(tr.c.point + 8.0f * vSpinDir), 3, 1000
				);
		*/
		// instead, show combined velocity
			gameRenderWorld->DebugArrow
			(
				colorCyan, (tr.c.point - 8.0f * PointVelDir), 
				(tr.c.point + 8.0f * PointVelDir), 3, 1000
			);
		}
	}

	// Direction of the velocity of point that hit (renamed it for brevity)
	idVec3 dir = PointVelDir;

	idActor *AttachOwner(NULL);
	idEntity *OthBindMaster(NULL);
	if( other->IsType(idAFAttachment::Type) )
	{
		idEntity *othBody = static_cast<idAFAttachment *>(other)->GetBody();
		if( othBody->IsType(idActor::Type) )
			AttachOwner = static_cast<idActor *>(othBody);
	}
	// Also check for any object bound to an actor (helmets, etc)
	else if( (OthBindMaster = other->GetBindMaster()) != NULL
				&& OthBindMaster->IsType(idActor::Type) )
		AttachOwner = static_cast<idActor *>(OthBindMaster);

	CMeleeStatus *pStatus = &m_Owner.GetEntity()->m_MeleeStatus;

	// Check if we hit a melee parry or held object 
	// (for some reason tr.c.contents erroneously returns CONTENTS_MELEEWEAP for everything except the world)
	// if( (tr.c.contents & CONTENTS_MELEEWEAP) != 0 )
	// this doesn't always work either, it misses linked clipmodels on melee weapons
	// if( other->GetPhysics() && ( other->GetPhysics()->GetContents(tr.c.id) & CONTENTS_MELEEWEAP) )
	// have to do this to catch all cases:
	if
		( 
			(other->GetPhysics() && ( other->GetPhysics()->GetContents(tr.c.id) & CONTENTS_MELEEWEAP))
			|| ( other->IsType(CMeleeWeapon::Type) && static_cast<CMeleeWeapon *>(other)->GetOwner() )
		)
	{
		DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeWeapon: Hit someting with CONTENTS_MELEEWEAP\r");
		// hit a parry (make sure we don't hit our own other melee weapons)
		if( other->IsType(CMeleeWeapon::Type)
			&& static_cast<CMeleeWeapon *>(other)->GetOwner()
			&& static_cast<CMeleeWeapon *>(other)->GetOwner() != m_Owner.GetEntity() )
		{
			DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING
				("MeleeWeapon: Hit a melee parry put up by %s\r", 
				  static_cast<CMeleeWeapon *>(other)->GetOwner()->name.c_str() );
			// Test our attack against their parry
			TestParry( static_cast<CMeleeWeapon *>(other), dir, &tr );
		}
		// hit a held object
		else if( other == gameLocal.m_Grabber->GetSelected() )
		{
			DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeWeapon: Hit an object held by the player\r");
			
			MeleeCollision( other, dir, &tr, location );

			// TODO: Message the grabber that the grabbed object has been hit
			// So that it can fly out of player's hands if desired
			
			// update AI status (consider this a miss)
			pStatus->m_ActionResult = MELEERESULT_AT_MISSED;
			pStatus->m_ActionPhase = MELEEPHASE_RECOVERING;

			// TODO: Message the attacking AI to play a bounce off animation if appropriate

			DeactivateAttack();
		}
		else
		{
			DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeWeapon: Hit something with CONTENTS_MELEEWEAP that's not an active parry or a held object (this shouldn't normally happen).\r");
			MeleeCollision( other, dir, &tr, location );

			// update AI status (consider this a miss)
			pStatus->m_ActionResult = MELEERESULT_AT_MISSED;
			pStatus->m_ActionPhase = MELEEPHASE_RECOVERING;

			DeactivateAttack();
		}
	}
	// Hit an actor, or an AF attachment that is part of an actor
	else if( other->IsType(idActor::Type) || AttachOwner != NULL )
	{
		DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeWeapon: Hit actor or part of actor %s\r", other->name.c_str());
		
		idActor *owner = m_Owner.GetEntity();
		idActor *otherAct;

		if( AttachOwner )
			otherAct = AttachOwner;
		else
			otherAct = static_cast<idActor *>(other);

		// Don't do anything if we hit our own AF attachment
		// AI also don't do damage to friendlies/neutral
		if( AttachOwner != owner 
			&& !(owner->IsType(idAI::Type) && !(static_cast<idAI *>(owner)->IsEnemy(otherAct))) )
		{
			DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeWeapon: Hit AI other than ourselves.\r");
			// TODO: Scale damage with instantaneous velocity of the blade?
			MeleeCollision( other, dir, &tr, location );

			// update actor's melee status
			pStatus->m_ActionResult = MELEERESULT_AT_HIT;
			pStatus->m_ActionPhase = MELEEPHASE_RECOVERING;

			DeactivateAttack();
		}
		else
		{
			DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeWeapon: Hit yourself.  Stop hitting yourself!\r");
		}
	}
	// Hit something else in the world (only happens to the player)
	else
	{
		DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeWeapon: Hit a non-AI object: %s\r", other->name.c_str());
		MeleeCollision( other, dir, &tr, location );
		
		// TODO: Message the attacking actor to play a bounce off animation if appropriate

		// keep the attack going if the object hit is a moveable below a certain mass
		// TODO: Handle moveables bound to other things and use the total mass of the system?
		if( !( other->IsType(idMoveable::Type) && other->GetPhysics()->GetMass() < m_StopMass ) )
		{
			// message the AI, consider this a miss
			pStatus->m_ActionResult = MELEERESULT_AT_MISSED;
			pStatus->m_ActionPhase = MELEEPHASE_RECOVERING;

			DeactivateAttack();
		}
	}
}
bool idMoveable::Collide( const trace_t &collision, const idVec3 &velocity ) {
	// greebo: Check whether we are colliding with the nearly exact same point again
	bool sameCollisionAgain = ( lastCollision.fraction != -1 && lastCollision.c.point.Compare( collision.c.point, 0.05f ) );
	// greebo: Save the collision info for the next call
	lastCollision = collision;
	float v = -( velocity * collision.c.normal );
	if( !sameCollisionAgain ) {
		float bounceSoundMinVelocity = cv_bounce_sound_min_vel.GetFloat();
		float bounceSoundMaxVelocity = cv_bounce_sound_max_vel.GetFloat();
		if( ( v > bounceSoundMinVelocity ) && ( gameLocal.time > nextSoundTime ) ) {
			// grayman #3331 - some moveables should not bother with bouncing sounds
			if( !spawnArgs.GetBool( "no_bounce_sound", "0" ) ) {
				const idMaterial *material = collision.c.material;
				idStr sndNameLocal;
				idStr surfaceName; // "tile", "glass", etc.
				if( material != NULL ) {
					surfaceName = g_Global.GetSurfName( material );
					// Prepend the snd_bounce_ prefix to check for a surface-specific sound
					idStr sndNameWithSurface = "snd_bounce_" + surfaceName;
					if( spawnArgs.FindKey( sndNameWithSurface ) != NULL ) {
						sndNameLocal = sndNameWithSurface;
					} else {
						sndNameLocal = "snd_bounce";
					}
				}
				const char *sound = spawnArgs.GetString( sndNameLocal );
				const idSoundShader *sndShader = declManager->FindSound( sound );
				//f = v > BOUNCE_SOUND_MAX_VELOCITY ? 1.0f : idMath::Sqrt( v - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / idMath::Sqrt( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) );
				// angua: modify the volume set in the def instead of setting a fixed value.
				// At minimum velocity, the volume should be "min_velocity_volume_decrease" lower (in db) than the one specified in the def
				float f = ( v > bounceSoundMaxVelocity ) ? 0.0f : spawnArgs.GetFloat( "min_velocity_volume_decrease", "0" ) * ( idMath::Sqrt( v - bounceSoundMinVelocity ) * ( 1.0f / idMath::Sqrt( bounceSoundMaxVelocity - bounceSoundMinVelocity ) ) - 1 );
				float volume = sndShader->GetParms()->volume + f;
				if( cv_moveable_collision.GetBool() ) {
					gameRenderWorld->DrawText( va( "Velocity: %f", v ), ( physicsObj.GetOrigin() + idVec3( 0, 0, 20 ) ), 0.25f, colorGreen, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1, 100 * gameLocal.msec );
					gameRenderWorld->DrawText( va( "Volume: %f", volume ), ( physicsObj.GetOrigin() + idVec3( 0, 0, 10 ) ), 0.25f, colorGreen, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1, 100 * gameLocal.msec );
					gameRenderWorld->DebugArrow( colorMagenta, collision.c.point, ( collision.c.point + 30 * collision.c.normal ), 4.0f, 1 );
				}
				SetSoundVolume( volume );
				// greebo: We don't use StartSound() here, we want to do the sound propagation call manually
				StartSoundShader( sndShader, SND_CHANNEL_ANY, 0, false, NULL );
				// grayman #2603 - don't propagate a sound if this is a doused torch dropped by an AI
				if( !spawnArgs.GetBool( "is_torch", "0" ) ) {
					idStr sndPropName = GetSoundPropNameForMaterial( surfaceName );
					// Propagate a suspicious sound, using the "group" convention (soft, hard, small, med, etc.)
					PropSoundS( NULL, sndPropName, f, 0 ); // grayman #3355
				}
				SetSoundVolume( 0.0f );
				nextSoundTime = gameLocal.time + 500;
			}
		}
		// tels:
		//DM_LOG(LC_ENTITY, LT_INFO)LOGSTRING("Moveable %s might call script_collide %s because m_collideScriptCounter = %i and v = %f and time (%d) > m_nextCollideScriptTime (%d)\r",
		//		name.c_str(), m_scriptCollide.c_str(), m_collideScriptCounter, v, gameLocal.time, m_nextCollideScriptTime );
		if( ( m_collideScriptCounter != 0 ) && ( v > m_minScriptVelocity ) && ( gameLocal.time > m_nextCollideScriptTime ) ) {
			if( m_collideScriptCounter > 0 ) {
				// if positive, decrement it, so -1 stays as it is (for 0, we never come here)
				m_collideScriptCounter--;
			}
			// call the script
			const function_t *pScriptFun = scriptObject.GetFunction( m_scriptCollide.c_str() );
			if( pScriptFun == NULL ) {
				// Local function not found, check in global namespace
				pScriptFun = gameLocal.program.FindFunction( m_scriptCollide.c_str() );
			}
			if( pScriptFun != NULL ) {
				DM_LOG( LC_ENTITY, LT_INFO )LOGSTRING( "Moveable %s calling script_collide %s.\r",
													   name.c_str(), m_scriptCollide.c_str() );
				idThread *pThread = new idThread( pScriptFun );
				pThread->CallFunctionArgs( pScriptFun, true, "e", this );
				pThread->DelayedStart( 0 );
			} else {
				// script function not found!
				DM_LOG( LC_ENTITY, LT_ERROR )LOGSTRING( "Moveable %s could not find script_collide %s.\r",
														name.c_str(), m_scriptCollide.c_str() );
				m_collideScriptCounter = 0;
			}
			m_nextCollideScriptTime = gameLocal.time + 300;
		}
	}
	idEntity *ent = gameLocal.entities[collision.c.entityNum];
	trace_t newCollision = collision; // grayman #2816 - in case we need to modify collision
	// grayman #2816 - if we hit the world, skip all the damage work
	if( ent && ( ent != gameLocal.world ) ) {
		idActor *entActor = NULL;
		if( ent->IsType( idActor::Type ) ) {
			entActor = static_cast<idActor *>( ent ); // the object hit an actor directly
		} else if( ent->IsType( idAFAttachment::Type ) ) {
			newCollision.c.id = JOINT_HANDLE_TO_CLIPMODEL_ID( static_cast<idAFAttachment *>( ent )->GetAttachJoint() );
		}
		// go up the bindMaster chain to see if an Actor is lurking
		if( entActor == NULL ) { // no actor yet, so ent is an attachment or an attached moveable
			idEntity *bindMaster = ent->GetBindMaster();
			while( bindMaster != NULL ) {
				if( bindMaster->IsType( idActor::Type ) ) {
					entActor = static_cast<idActor *>( bindMaster ); // the object hit something attached to an actor
					// If ent is an idAFAttachment, we can leave ent alone
					// and pass the damage to it. It will, in turn, pass the
					// damage to the actor it's attached to. (helmets)
					// If ent is NOT an attachment, we have to change it to
					// be the actor we just found. Inventor goggles are an
					// example of when we have to do this, because they're
					// an idMoveable, and they DON'T transfer their damage
					// to the actor they're attached to.
					if( !ent->IsType( idAFAttachment::Type ) ) {
						ent = bindMaster;
					}
					break;
				}
				bindMaster = bindMaster->GetBindMaster(); // go up the chain
			}
		}
		// grayman #2816 - in order to allow knockouts from dropped objects,
		// we have to allow collisions where the velocity is < minDamageVelocity,
		// because dropped objects can have low velocity, while at the same time
		// carrying enough damage to warrant a KO possibility.
		if( canDamage && damage.Length() && ( gameLocal.time > nextDamageTime ) ) {
			if( !entActor || !entActor->AI_DEAD ) {
				float f;
				if( v < minDamageVelocity ) {
					f = 0.0f;
				} else if( v < maxDamageVelocity ) {
					f = idMath::Sqrt( ( v - minDamageVelocity ) / ( maxDamageVelocity - minDamageVelocity ) );
				} else {
					f = 1.0f; // capped when v >= maxDamageVelocity
				}
				// scale the damage by the surface type multiplier, if any
				idStr SurfTypeName;
				g_Global.GetSurfName( newCollision.c.material, SurfTypeName );
				SurfTypeName = "damage_mult_" + SurfTypeName;
				f *= spawnArgs.GetFloat( SurfTypeName.c_str(), "1.0" );
				idVec3 dir = velocity;
				dir.NormalizeFast();
				// Use a technique similar to what's used for a melee collision
				// to find a better joint (location), because when the head is
				// hit, the joint isn't identified correctly w/o it.
				int location = JOINT_HANDLE_TO_CLIPMODEL_ID( newCollision.c.id );
				// If this moveable is attached to an AI, identify that AI.
				// Otherwise, assume it was put in motion by someone.
				idEntity *attacker = GetPhysics()->GetClipModel()->GetOwner();
				if( attacker == NULL ) {
					attacker = m_SetInMotionByActor.GetEntity();
				}
				// grayman #3370 - if the entity being hit is the attacker, don't do damage
				if( attacker != ent ) {
					int preHealth = ent->health;
					ent->Damage( this, attacker, dir, damage, f, location, const_cast<trace_t *>( &newCollision ) );
					if( ent->health < preHealth ) { // only set the timer if there was damage
						nextDamageTime = gameLocal.time + 1000;
					}
				}
			}
		}
		// Darkmod: Collision stims and a tactile alert if it collides with an AI
		ProcCollisionStims( ent, newCollision.c.id ); // grayman #2816 - use new collision
		if( entActor && entActor->IsType( idAI::Type ) ) {
			static_cast<idAI *>( entActor )->TactileAlert( this );
		}
	}
	if( fxCollide.Length() && ( gameLocal.time > nextCollideFxTime ) ) {
		idEntityFx::StartFx( fxCollide, &collision.c.point, NULL, this, false );
		nextCollideFxTime = gameLocal.time + 3500;
	}
	return false;
}
Exemple #4
0
/*
==============
idDragEntity::Update
==============
*/
void idDragEntity::Update( idPlayer *player ) {
	idVec3 viewPoint, origin;
	idMat3 viewAxis, axis;
	trace_t trace;
	idEntity *newEnt;
	idAngles angles;
	jointHandle_t newJoint;
	idStr newBodyName;

	player->GetViewPos( viewPoint, viewAxis );

	// if no entity selected for dragging
	if ( !dragEnt.GetEntity() ) {

		if ( player->usercmd.buttons & BUTTON_ATTACK ) {

			gameLocal.clip.TracePoint( trace, viewPoint, viewPoint + viewAxis[0] * MAX_DRAG_TRACE_DISTANCE, (CONTENTS_SOLID|CONTENTS_RENDERMODEL|CONTENTS_BODY), player );
			if ( trace.fraction < 1.0f ) {

				newEnt = gameLocal.entities[ trace.c.entityNum ];
				if ( newEnt ) {

					if ( newEnt->GetBindMaster() ) {
						if ( newEnt->GetBindJoint() ) {
							trace.c.id = JOINT_HANDLE_TO_CLIPMODEL_ID( newEnt->GetBindJoint() );
						} else {
							trace.c.id = newEnt->GetBindBody();
						}
						newEnt = newEnt->GetBindMaster();
					}

					if ( newEnt->IsType( idAFEntity_Base::Type ) && static_cast<idAFEntity_Base *>(newEnt)->IsActiveAF() ) {
						idAFEntity_Base *af = static_cast<idAFEntity_Base *>(newEnt);

						// joint being dragged
						newJoint = CLIPMODEL_ID_TO_JOINT_HANDLE( trace.c.id );
						// get the body id from the trace model id which might be a joint handle
						trace.c.id = af->BodyForClipModelId( trace.c.id );
						// get the name of the body being dragged
						newBodyName = af->GetAFPhysics()->GetBody( trace.c.id )->GetName();

					} else if ( !newEnt->IsType( idWorldspawn::Type ) ) {

						if ( trace.c.id < 0 ) {
							newJoint = CLIPMODEL_ID_TO_JOINT_HANDLE( trace.c.id );
						} else {
							newJoint = INVALID_JOINT;
						}
						newBodyName = "";

					} else {

						newJoint = INVALID_JOINT;
						newEnt = NULL;
					}
				}
				if ( newEnt ) {
					dragEnt = newEnt;
					selected = newEnt;
					joint = newJoint;
					id = trace.c.id;
					bodyName = newBodyName;

					if ( !cursor ) {
						cursor = ( idCursor3D * )gameLocal.SpawnEntityType( idCursor3D::Type );
					}

					idPhysics *phys = dragEnt.GetEntity()->GetPhysics();
					localPlayerPoint = ( trace.c.point - viewPoint ) * viewAxis.Transpose();
					origin = phys->GetOrigin( id );
					axis = phys->GetAxis( id );
					localEntityPoint = ( trace.c.point - origin ) * axis.Transpose();

					cursor->drag.Init( g_dragDamping.GetFloat() );
					cursor->drag.SetPhysics( phys, id, localEntityPoint );
					cursor->Show();

					if ( phys->IsType( idPhysics_AF::Type ) ||
							phys->IsType( idPhysics_RigidBody::Type ) ||
								phys->IsType( idPhysics_Monster::Type ) ) {
						cursor->BecomeActive( TH_THINK );
					}
				}
			}
		}
	}

	// if there is an entity selected for dragging
	idEntity *drag = dragEnt.GetEntity();
	if ( drag ) {

		if ( !( player->usercmd.buttons & BUTTON_ATTACK ) ) {
			StopDrag();
			return;
		}

		cursor->SetOrigin( viewPoint + localPlayerPoint * viewAxis );
		cursor->SetAxis( viewAxis );

		cursor->drag.SetDragPosition( cursor->GetPhysics()->GetOrigin() );

		renderEntity_t *renderEntity = drag->GetRenderEntity();
		idAnimator *dragAnimator = drag->GetAnimator();

		if ( joint != INVALID_JOINT && renderEntity && dragAnimator ) {
			dragAnimator->GetJointTransform( joint, gameLocal.time, cursor->draggedPosition, axis );
			cursor->draggedPosition = renderEntity->origin + cursor->draggedPosition * renderEntity->axis;
			gameRenderWorld->DrawText( va( "%s\n%s\n%s, %s", drag->GetName(), drag->GetType()->classname, dragAnimator->GetJointName( joint ), bodyName.c_str() ), cursor->GetPhysics()->GetOrigin(), 0.1f, colorWhite, viewAxis, 1 );
		} else {
			cursor->draggedPosition = cursor->GetPhysics()->GetOrigin();
			gameRenderWorld->DrawText( va( "%s\n%s\n%s", drag->GetName(), drag->GetType()->classname, bodyName.c_str() ), cursor->GetPhysics()->GetOrigin(), 0.1f, colorWhite, viewAxis, 1 );
		}
	}

	// if there is a selected entity
	if ( selected.GetEntity() && g_dragShowSelection.GetBool() ) {
		// draw the bbox of the selected entity
		renderEntity_t *renderEntity = selected.GetEntity()->GetRenderEntity();
		if ( renderEntity ) {
			gameRenderWorld->DebugBox( colorYellow, idBox( renderEntity->bounds, renderEntity->origin, renderEntity->axis ) );
		}
	}
}