//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============
//
// Purpose: Auto Repair
//
// $NoKeywords: $
//=============================================================================
#include "cbase.h"
#include "tf_gamemovement_recon.h"
#include "tf_movedata.h"
#include "in_buttons.h"
			   
#define TIME_WALL_SUPPRESSION_JUMP	    100
#define TIME_WALL_SUPPRESSION_IMPACT	400
#define TIME_WALL_STICK					 50
#define TIME_STRAFE_STICK				 50
#define TIME_LEAP_STICK					300
#define TIME_WALL_ACTIVATE_JUMP			300
#define TIME_WALL_INVALID				-99999
#define MAX_VERTICAL_SPEED				400


//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CTFGameMovementRecon::CTFGameMovementRecon()
{
	m_pReconData = NULL;

	m_vStandMins = RECONCLASS_HULL_STAND_MIN;
	m_vStandMaxs = RECONCLASS_HULL_STAND_MAX;
	m_vStandViewOffset = RECONCLASS_VIEWOFFSET_STAND;

	m_vDuckMins = RECONCLASS_HULL_DUCK_MIN;
	m_vDuckMaxs = RECONCLASS_HULL_DUCK_MAX;
	m_vDuckViewOffset = RECONCLASS_VIEWOFFSET_DUCK;

	m_bPerformingAirMove = false;
}


//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFGameMovementRecon::ProcessClassMovement( CBaseTFPlayer *pPlayer, CTFMoveData *pTFMove )
{
	// Get the class specific data from the TFMoveData structure
	Assert( PlayerClassReconData_t::PLAYERCLASS_ID == pTFMove->m_nClassID );
	m_pReconData = &pTFMove->ReconData();

	// to test pass it through!!
	BaseClass::ProcessMovement( (CBasePlayer *)pPlayer, pTFMove );

	// Set the jump count appropriately...
	// If we've hit the ground, we can double jump again...
	if ((player->GetGroundEntity() != NULL) && (pTFMove->ReconData().m_flStickTime == TIME_WALL_INVALID))
	{
		m_pReconData->m_nJumpCount = 0;
		ResetWallImpact( (CTFMoveData*)mv );
	}
}

void CTFGameMovementRecon::PostPlayerMove( void )
{
	BaseClass::PostPlayerMove( );

	if (m_pReconData->m_flStickTime != TIME_WALL_INVALID)
	{
		// We're stuck, so stick!
		mv->m_vecVelocity.Init( 0.0f, 0.0f, 0.0f );
	}
}


//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFGameMovementRecon::UpdateTimers( void )
{
	BaseClass::UpdateTimers();

	CTFMoveData *pTFMove = TFMove();
	if ( !pTFMove )
		return;

	float frame_msec = 1000.0f * gpGlobals->frametime;

	// Decrement the recon timers.
	if ( pTFMove->ReconData().m_flSuppressionJumpTime != TIME_WALL_INVALID )
	{
		pTFMove->ReconData().m_flSuppressionJumpTime -= frame_msec;
		if ( pTFMove->ReconData().m_flSuppressionJumpTime <= 0.0f )
		{
			pTFMove->ReconData().m_flSuppressionJumpTime = TIME_WALL_INVALID;
		}
	}

	if ( pTFMove->ReconData().m_flSuppressionImpactTime != TIME_WALL_INVALID )
	{
		pTFMove->ReconData().m_flSuppressionImpactTime -= frame_msec;
		if ( pTFMove->ReconData().m_flSuppressionImpactTime <= 0.0f )
		{
			pTFMove->ReconData().m_flSuppressionImpactTime = TIME_WALL_INVALID;
		}
	}

	if ( pTFMove->ReconData().m_flActiveJumpTime != TIME_WALL_INVALID )
	{
		pTFMove->ReconData().m_flActiveJumpTime -= frame_msec;
		if ( pTFMove->ReconData().m_flActiveJumpTime <= 0.0f )
		{
			pTFMove->ReconData().m_flActiveJumpTime = TIME_WALL_INVALID;
		}
	}

	if ( pTFMove->ReconData().m_flStickTime != TIME_WALL_INVALID )
	{
		pTFMove->ReconData().m_flStickTime -= frame_msec;
		if ( pTFMove->ReconData().m_flStickTime <= 0.0f )
		{
			pTFMove->ReconData().m_flStickTime = TIME_WALL_INVALID;

			// Restore velocity at this time
			pTFMove->m_vecVelocity = pTFMove->ReconData().m_vecUnstickVelocity;
		}
	}
}

//-----------------------------------------------------------------------------
// Implement this if you want to know when the player collides during OnPlayerMove
//-----------------------------------------------------------------------------
void CTFGameMovementRecon::OnTryPlayerMoveCollision( trace_t &tr )
{
	if ( !m_bPerformingAirMove )
		return;
	
	// Only keep track of world collisions
	if ( tr.DidHitWorld() )
	{
		CTFMoveData *pTFMove = TFMove();
		if ( pTFMove )
		{
			if ( ( pTFMove->ReconData().m_flSuppressionJumpTime == TIME_WALL_INVALID ) &&
				 ( pTFMove->ReconData().m_flSuppressionImpactTime == TIME_WALL_INVALID ) )
			{
				// No walljumps off of mostly horizontal surfaces...
				if ( fabs( tr.plane.normal.z ) > 0.9f )
					return;
			
				// No walljumps off of the same plane as the last one...
				if ( (pTFMove->ReconData().m_flImpactDist == tr.plane.dist) && 
					(VectorsAreEqual(pTFMove->ReconData().m_vecImpactNormal, tr.plane.normal, 1e-2) ) )
				{
					return;
				}

				// If you hit a wall, no double jumps for you
				pTFMove->ReconData().m_nJumpCount = 2;
				
				// Play an impact sound
				MoveHelper()->StartSound( pTFMove->m_vecOrigin, "Recon.WallJump" );

				pTFMove->ReconData().m_vecImpactNormal = tr.plane.normal;
				pTFMove->ReconData().m_flImpactDist = tr.plane.dist;

				pTFMove->ReconData().m_flActiveJumpTime = TIME_WALL_ACTIVATE_JUMP;
				pTFMove->ReconData().m_flSuppressionImpactTime = TIME_WALL_SUPPRESSION_IMPACT;
			}
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFGameMovementRecon::AirMove()
{
	m_bPerformingAirMove = true;

	// When in the air, recon travels ballistically
	if ( TFMove()->ReconData().m_nJumpCount )
	{
		// Add in any base velocity to the current velocity.
		VectorAdd( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity );

		TryPlayerMove();
	}
	else
	{
		// But if we're falling (or coming up off ladders), treat it normally
		BaseClass::AirMove();
	}

	m_bPerformingAirMove = false;
}

//-----------------------------------------------------------------------------
// Purpose: Check the jump button to make various jumps
//-----------------------------------------------------------------------------
bool CTFGameMovementRecon::CheckWaterJump()
{
	// See if we are waterjumping.  If so, decrement count and return.
	if (player->m_flWaterJumpTime)
	{
		player->m_flWaterJumpTime -= gpGlobals->frametime;
		if (player->m_flWaterJumpTime < 0)
			player->m_flWaterJumpTime = 0;
		
		return true;
	}

	// If we are in the water most of the way...
	if ( player->GetWaterLevel() >= 2 )
	{	
		// swimming, not jumping
		player->SetGroundEntity( (CBaseEntity *)NULL );

		if(player->GetWaterType() == CONTENTS_WATER)    // We move up a certain amount
			mv->m_vecVelocity[2] = 100;
		else if (player->GetWaterType() == CONTENTS_SLIME)
			mv->m_vecVelocity[2] = 80;
		
		// play swiming sound
		if ( player->m_flSwimSoundTime <= 0 )
		{
			// Don't play sound again for 1 second
			player->m_flSwimSoundTime = 1000;
			PlaySwimSound();
		}
		return true;
	}

	return false;
}


//-----------------------------------------------------------------------------
// Resets the impact time
//-----------------------------------------------------------------------------
void CTFGameMovementRecon::ResetWallImpact( CTFMoveData *pTFMove )
{
	if ( pTFMove->ReconData().m_flActiveJumpTime != TIME_WALL_INVALID )
	{
		pTFMove->ReconData().m_flActiveJumpTime = TIME_WALL_INVALID;
		pTFMove->ReconData().m_flSuppressionImpactTime = TIME_WALL_INVALID;
		pTFMove->ReconData().m_vecImpactNormal.Init( 9999, 9999, 9999 );
		pTFMove->ReconData().m_flImpactDist = -9999.0f;
	}
}


//-----------------------------------------------------------------------------
// Purpose: Check the jump button to make various jumps
//-----------------------------------------------------------------------------
bool CTFGameMovementRecon::CheckWallJump( CTFMoveData *pTFMove )
{
	if ( player->GetGroundEntity() != NULL )
		return false;

	if ( pTFMove->ReconData().m_flActiveJumpTime == TIME_WALL_INVALID )
		return false;

	// Play a jump sound
	PlayStepSound( m_pSurfaceData, 1.0, true );

	Vector jumpDir;
	if ( ( pTFMove->m_nButtons & ( IN_MOVELEFT | IN_MOVERIGHT ) ) )
	{
		AngleVectors( pTFMove->m_vecViewAngles, NULL, &jumpDir, NULL );

		// Apply strafe jump...
		jumpDir *= ( pTFMove->m_nButtons & IN_MOVELEFT ) ? -1.0f : 1.0f;
		jumpDir.z = 0.0f;

		if ( pTFMove->m_nButtons & ( IN_FORWARD | IN_BACK ) )
		{
			Vector forward;
			AngleVectors( pTFMove->m_vecViewAngles, &forward, NULL, NULL );
			forward *= 0.5f;
			forward *= ( pTFMove->m_nButtons & IN_BACK ) ? -1.0f : 1.0f;
			forward.z = 0.0;
			jumpDir += forward;
		}

		VectorNormalize( jumpDir );
		jumpDir *= 400;
	}
	else
	{
		AngleVectors( pTFMove->m_vecViewAngles, &jumpDir, NULL, NULL );
		jumpDir *= ( pTFMove->m_nButtons & IN_BACK ) ? -1.0f : 1.0f;
		jumpDir.z = 0.0;

		VectorNormalize( jumpDir );
		jumpDir *= 400;
	}

	pTFMove->ReconData().m_flStickTime = TIME_WALL_STICK;
	pTFMove->ReconData().m_vecUnstickVelocity.Init( jumpDir.x, jumpDir.y, 
		pTFMove->m_vecVelocity[2] + 1.5 * sqrt(2 * 800 * 45.0) );
	if (pTFMove->ReconData().m_vecUnstickVelocity.GetZ() > MAX_VERTICAL_SPEED)
		pTFMove->ReconData().m_vecUnstickVelocity.SetZ( MAX_VERTICAL_SPEED );

	pTFMove->m_vecVelocity.Init( 0, 0, 0 );

	// Don't allow jump into wall
	float normalComponent = DotProduct( pTFMove->ReconData().m_vecUnstickVelocity, pTFMove->ReconData().m_vecImpactNormal );
	if ( normalComponent < 0 )
	{
		Vector vUnstickVel;
		VectorMA( pTFMove->ReconData().m_vecUnstickVelocity, -normalComponent, 
			pTFMove->ReconData().m_vecImpactNormal, vUnstickVel );
		pTFMove->ReconData().m_vecUnstickVelocity = vUnstickVel;
	}

	return true;
}


//-----------------------------------------------------------------------------
// Purpose: Check the jump button to make various jumps
//-----------------------------------------------------------------------------
bool CTFGameMovementRecon::CheckBackJump( bool bWasInAir )
{
	if ((mv->m_nButtons & IN_BACK) == 0)
		return false;

	Vector jumpDir, right;
	AngleVectors( mv->m_vecViewAngles, &jumpDir, &right, NULL );
	jumpDir.z = 0.0f;
	jumpDir *= -1.0f;

	if (mv->m_nButtons & (IN_MOVELEFT | IN_MOVERIGHT))
	{
		// Apply strafe jump...
		right *= (mv->m_nButtons & IN_MOVELEFT) ? -1.0f : 1.0f;
		right.z = 0.0f;

		// Make us not jump quite at a 45% angle if both are selected
		right *= 0.5f;
		jumpDir += right;
	}

	VectorNormalize( jumpDir );

	float flGroundFactor = 1.0f;
	if ((m_pSurfaceData) /*&& (!bWasInAir)*/ )
	{
		flGroundFactor = m_pSurfaceData->jumpFactor;
	}

	jumpDir *= 150 * flGroundFactor;

	// Dampen current motion
	mv->m_vecVelocity[0] *= 0.5f;
	mv->m_vecVelocity[1] *= 0.5f;

	float flSideFactor = (bWasInAir) ? 2.0f : 1.0f;
	float flUpFactor = (bWasInAir) ? 0.5f : 1.5f;
	flSideFactor *= flGroundFactor;
	flUpFactor *= flGroundFactor;

	mv->m_vecVelocity[0] += flSideFactor * jumpDir.x;
	mv->m_vecVelocity[1] += flSideFactor * jumpDir.y;
	mv->m_vecVelocity[2] += flUpFactor * sqrt(2 * 800 * 45.0); 

	mv->m_vecVelocity[0] = clamp( mv->m_vecVelocity[0], -200, 200 );
	mv->m_vecVelocity[1] = clamp( mv->m_vecVelocity[1], -200, 200 );
	if (mv->m_vecVelocity[2] > MAX_VERTICAL_SPEED)
		mv->m_vecVelocity[2] = MAX_VERTICAL_SPEED;

	return true;
}


//-----------------------------------------------------------------------------
// Purpose: Check the jump button to make various jumps
//-----------------------------------------------------------------------------
bool CTFGameMovementRecon::CheckStrafeJump( bool bWasInAir )
{
	if ( (mv->m_nButtons & (IN_MOVELEFT | IN_MOVERIGHT)) == 0 )
		return false;

	if (mv->m_nButtons & IN_FORWARD)
		return false;

	Vector jumpDir;
	AngleVectors( mv->m_vecViewAngles, NULL, &jumpDir, NULL );

	// Apply strafe jump...
	jumpDir *= (mv->m_nButtons & IN_MOVELEFT) ? -1.0f : 1.0f;
	jumpDir.z = 0.0f;
	VectorNormalize( jumpDir );

	float flGroundFactor = 1.0f;
	if ((m_pSurfaceData) /*&& (!bWasInAir)*/ )
	{
		flGroundFactor = m_pSurfaceData->jumpFactor;
	}

	jumpDir *= 300 * flGroundFactor;

	// Dampen current motion
	mv->m_vecVelocity[0] *= 0.5f;
	mv->m_vecVelocity[1] *= 0.5f;
	mv->m_vecVelocity[0] += jumpDir.x;
	mv->m_vecVelocity[1] += jumpDir.y;

	if (!bWasInAir)
		mv->m_vecVelocity[2] += flGroundFactor * sqrt(2 * 800 * 45.0);  // 2 * gravity * height
	else
		mv->m_vecVelocity[2] += 0.5f * sqrt(2 * 800 * 45.0);  // 2 * gravity * height

	mv->m_vecVelocity[0] = clamp( mv->m_vecVelocity[0], -400, 400 );
	mv->m_vecVelocity[1] = clamp( mv->m_vecVelocity[1], -400, 400 );
	if (mv->m_vecVelocity[2] > MAX_VERTICAL_SPEED)
		mv->m_vecVelocity[2] = MAX_VERTICAL_SPEED;

	return true;
}


//-----------------------------------------------------------------------------
// Purpose: Check the jump button to make various jumps
//-----------------------------------------------------------------------------
bool CTFGameMovementRecon::CheckForwardJump( bool bWasInAir )
{
	// If we are ducking...
	if ( ( player->m_Local.m_bDucking ) || ( player->GetFlags() & FL_DUCKING ) )
	{
		// d = 0.5 * g * t^2		- distance traveled with linear accel
		// t = sqrt(2.0 * 45 / g)	- how long to fall 45 units
		// v = g * t				- velocity at the end (just invert it to jump up that high)
		// v = g * sqrt(2.0 * 45 / g )
		// v^2 = g * g * 2.0 * 45 / g
		// v = sqrt( g * 2.0 * 45 )
		mv->m_vecVelocity[2] = sqrt(2 * 800 * 45.0);  // 2 * gravity * height
	}
	else
	{
		Vector forward, right;
		AngleVectors( mv->m_vecViewAngles, &forward, &right, NULL );
 		forward.z = 0.0;

		if ((mv->m_nButtons & IN_FORWARD) == 0)
		{
			forward.x = forward.y = 0.0f;
		}

		if (mv->m_nButtons & (IN_MOVELEFT | IN_MOVERIGHT))
		{
			// Apply strafe jump...
			right *= (mv->m_nButtons & IN_MOVELEFT) ? -1.0f : 1.0f;
			right.z = 0.0f;

			// Make us not jump quite at a 45% angle if both are selected
			right *= 0.5f;
			forward += right;
		}

		VectorNormalize( forward );

		// Slow down by the speed factor
		float flGroundFactor = 1.0f;
		if ((m_pSurfaceData) /* && (!bWasInAir) */ )
		{
			flGroundFactor = m_pSurfaceData->jumpFactor;
		}

		forward *= 400 * flGroundFactor;	  

		// Dampen current motion
		mv->m_vecVelocity[0] *= 0.5f;
		mv->m_vecVelocity[1] *= 0.5f;
		mv->m_vecVelocity[0] += forward.x;
		mv->m_vecVelocity[1] += forward.y;

		float flUpFactor = (bWasInAir) ? 0.7f : 1.0f;
		flUpFactor *= flGroundFactor;

		mv->m_vecVelocity[2] += flUpFactor * MAX_VERTICAL_SPEED;

		// Limit their velocity in X and Y. We don't want to just clamp because that will change the
		// direction we're moving in.		
		for ( int i=0; i < 2; i++ )
		{
			float flAbs = fabs( mv->m_vecVelocity[i] );
			if ( flAbs > 400 )
			{
				mv->m_vecVelocity[0] *= (400.0f / flAbs);
				mv->m_vecVelocity[1] *= (400.0f / flAbs);
			}
		}

		if (mv->m_vecVelocity[2] > MAX_VERTICAL_SPEED)
 			mv->m_vecVelocity[2] = MAX_VERTICAL_SPEED;
	}

	return true;
}


//-----------------------------------------------------------------------------
// Purpose: Check the jump button to make various jumps
//-----------------------------------------------------------------------------
void CTFGameMovementRecon::CheckJumpButton()
{
	// FIXME: Refactor this so we don't have this complicated duplicate
	// code here + in gamemovement.cpp

	if ( player->pl.deadflag )
	{
		mv->m_nOldButtons |= IN_JUMP ;	// don't jump again until released
		return;
	}

	// Water jumps!
	if ( CheckWaterJump() )
		return;

	if ( mv->m_nOldButtons & IN_JUMP )
		return;		// don't pogo stick

	CTFMoveData *pTFMove = static_cast<CTFMoveData*>( mv );

	// Check for wall jump...
	if ( !CheckWallJump( pTFMove ) )
	{
		// If we already did one air jump, can't do another 
 		if ( (player->GetGroundEntity() == NULL ) && ( pTFMove->ReconData().m_nJumpCount > 1) )
		{
			mv->m_nOldButtons |= IN_JUMP;
			return;		// in air, so no effect
		}

		pTFMove->ReconData().m_nJumpCount += 1;

		// Am I doing a double-jump?
		bool bWasInAir = (player->GetGroundEntity() == NULL);

		// In the air now.
		player->SetGroundEntity( (CBaseEntity *)NULL );
		
		PlayStepSound( m_pSurfaceData, 1.0, true );

		if (!CheckBackJump(bWasInAir))
		{
			if (CheckStrafeJump(bWasInAir))
			{
				// Can't double jump out of a roll....
				pTFMove->ReconData().m_nJumpCount += 1;
			}
			else
			{
				CheckForwardJump(bWasInAir);
			}
		}
	}

	pTFMove->ReconData().m_flSuppressionJumpTime = TIME_WALL_SUPPRESSION_JUMP;	

	FinishGravity();

	mv->m_outWishVel = mv->m_vecVelocity;
	mv->m_outStepHeight += 0.1f;

	// Flag that we jumped.
	mv->m_nOldButtons |= IN_JUMP;	// don't jump again until released
}


//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
const Vector &CTFGameMovementRecon::GetPlayerMins( bool bDucked ) const
{
	return bDucked ? m_vDuckMins : m_vStandMins; 
}
	
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
const Vector &CTFGameMovementRecon::GetPlayerMaxs( bool bDucked ) const
{
	return bDucked ? m_vDuckMaxs : m_vStandMaxs;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
const Vector &CTFGameMovementRecon::GetPlayerViewOffset( bool bDucked ) const
{
	return bDucked ? m_vDuckViewOffset : m_vStandViewOffset;
}
