// sikk---> Object Manipulation


#include "Game_local.h"
#include "Grab.h"
#include "Misc.h"
#include "Player.h"
#include "gamesys/SysCvar.h"

#define MAX_DRAG_TRACE_DISTANCE			80.0f
#define TRACE_BOUNDS_SIZE				3.0f
#define MAX_HOLD_DISTANCE				96.0f
#define MIN_HOLD_DISTANCE				48.0f
#define DRAG_FAIL_LEN					64.0f
#define FAIL_TIME						1000
#define THROW_TIME						500
#define	THROW_SCALE						20000
#define MAX_PICKUP_VELOCITY				1000 * 1000
#define MAX_PICKUP_SIZE					96.0f

/*
===============================================================================

	Allows entities to be dragged through the world with physics.

===============================================================================
*/

CLASS_DECLARATION( idEntity, idGrabEntity )
END_CLASS

/*
==============
idGrabEntity::idGrabEntity
==============
*/
idGrabEntity::idGrabEntity( void ) {
	Clear();
}

/*
==============
idGrabEntity::~idGrabEntity
==============
*/
idGrabEntity::~idGrabEntity( void ) {
	StopDrag( owner, true );
}

/*
==============
idGrabEntity::Clear
==============
*/
void idGrabEntity::Clear() {
	dragEnt			= NULL;
	owner			= NULL;
	id				= 0;
	oldUcmdFlags	= 0;
	dragFailTime	= 0;
	lastThrownTime	= 0;
	localPlayerPoint.Zero();
	saveGravity.Zero();
}
/*
==============
idGrabEntity::StartDrag
==============
*/
void idGrabEntity::StartDrag( idPlayer *player, idEntity *grabEnt, int id ) {
	if ( grabEnt && grabEnt->GetPhysics()->GetBounds().GetRadius() < MAX_PICKUP_SIZE &&
		grabEnt->GetPhysics()->GetLinearVelocity().LengthSqr() < MAX_PICKUP_VELOCITY ) {

		dragFailTime = gameLocal.time + FAIL_TIME;

		oldUcmdFlags = player->usercmd.flags;

		// This is the new object to drag around
		dragEnt = grabEnt;

		// Get the current physics object to manipulate
		idPhysics *phys = grabEnt->GetPhysics();

		// Turn off gravity on object
		saveGravity = phys->GetGravity();
		phys->SetGravity( vec3_origin );
		
		// hold it directly in front of player, distance depends on object size
		float lerp = 1.0f - ( grabEnt->GetPhysics()->GetBounds().GetRadius() / MAX_HOLD_DISTANCE );
		lerp *= lerp;
		float holdDist = lerp * MIN_HOLD_DISTANCE + ( 1.0f - lerp ) * MAX_HOLD_DISTANCE;
		localPlayerPoint = ( player->firstPersonViewAxis[0] * holdDist ) * player->firstPersonViewAxis.Transpose();

		// Start up the Force_Drag to bring it in
		drag.Init( g_dragDamping.GetFloat() );
		drag.SetPhysics( phys, id, player->firstPersonViewOrigin + localPlayerPoint * player->firstPersonViewAxis );

		player->StartSoundShader( declManager->FindSound( "use_grab" ), SND_CHANNEL_VOICE, 0, false, NULL );
	}
}

/*
==============
idGrabEntity::StopDrag
==============
*/
void idGrabEntity::StopDrag( idPlayer *player, bool drop ) {
	if ( dragEnt.IsValid() ) {
		idEntity *ent = dragEnt.GetEntity();

		// If a cinematic has started, allow dropped object to think in cinematics
		if ( gameLocal.inCinematic )
			ent->cinematic = true;

		// Restore Gravity
		ent->GetPhysics()->SetGravity( saveGravity );

		// If the object isn't near its goal, just drop it in place.
		if ( drop || drag.GetDistanceToGoal() > DRAG_FAIL_LEN ) {
			ent->GetPhysics()->SetLinearVelocity( vec3_origin );
		} else {	// throw the object forward
			ent->ApplyImpulse( player, 0, ent->GetPhysics()->GetOrigin(), player->firstPersonViewAxis[0] * THROW_SCALE );
			player->StartSoundShader( declManager->FindSound( "use_throw" ), SND_CHANNEL_VOICE, 0, false, NULL );
		}
		// Remove the Force_Drag's control of the entity
		drag.RemovePhysics( ent->GetPhysics() );

		lastThrownTime = gameLocal.time + THROW_TIME;
	}

	dragEnt = NULL;
}

/*
==============
idGrabEntity::Update
==============
*/
void idGrabEntity::Update( idPlayer *player ) {
	trace_t trace;
//	idEntity *newEnt;

	owner = player;

	if ( lastThrownTime > gameLocal.time ) {
		prevViewAngles = player->viewAngles;
		return;
	}

	bool valid = dragEnt.IsValid();

	// Check if object being held has been removed or player is dead
	if ( valid && dragEnt.GetEntity()->IsHidden() || player->health <= 0 ) {
		StopDrag( player, true );
		prevViewAngles = player->viewAngles;
		return;
	}

	// attack throws object 
	if ( valid && player->usercmd.buttons & BUTTON_ATTACK ) {
		StopDrag( player, false );
		prevViewAngles = player->viewAngles;
		return;
	}

	// if there is an entity selected for dragging
	if ( dragEnt.GetEntity() ) {
		idPhysics *entPhys = dragEnt.GetEntity()->GetPhysics();
		idVec3 goalPos;

		// Check if the player is standing on the object
		idBounds	playerBounds;
		idBounds	objectBounds = entPhys->GetAbsBounds();
		idVec3		newPoint = player->GetPhysics()->GetOrigin();

		// create a bounds at the players feet
		playerBounds.Clear();
		playerBounds.AddPoint( newPoint );
		newPoint.z -= 1.0f;
		playerBounds.AddPoint( newPoint );
		playerBounds.ExpandSelf( 8.0f );

		// If it intersects the object bounds, then drop it
		if ( playerBounds.IntersectsBounds( objectBounds ) ) {
			StopDrag( player, true );
			prevViewAngles = player->viewAngles;
			return;
		}

		idAngles ang = entPhys->GetAxis().ToAngles();
		ang.yaw += player->viewAngles.yaw - prevViewAngles.yaw;
		if ( ang.yaw > 180.0f )
			ang.yaw -= 360.0f;
		else if ( ang.yaw < -180.0f )
			ang.yaw += 360.0f;
		entPhys->SetAxis( ang.ToMat3() );

		// Set and evaluate drag force
		goalPos = player->firstPersonViewOrigin + localPlayerPoint * player->firstPersonViewAxis;

		drag.SetGoalPosition( goalPos );
		drag.Evaluate( gameLocal.time );

		// If the object is stuck away from its intended position for more than 500ms, let it go.
		if ( drag.GetDistanceToGoal() > DRAG_FAIL_LEN ) {
			if ( dragFailTime < gameLocal.time ) {
				StopDrag( player, true );
				prevViewAngles = player->viewAngles;
				return;
			}
		} else {
			dragFailTime = gameLocal.time + 1000;
		}
	}
	prevViewAngles = player->viewAngles;
}
// <---sikk