//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Client's CObjectSentrygun
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "bone_setup.h"
#include "CommanderOverlay.h"
#include "c_baseobject.h"
#include "C_Obj_SentryGun.h"
#include "tf_shareddefs.h"
#include "c_basetfplayer.h"
#include "ObjectControlPanel.h"
#include <vgui_controls/Button.h>

inline float UTIL_AngleMod(float a)
{
	return anglemod(a);
}

//-----------------------------------------------------------------------------
// Purpose: Base Sentrygun
//-----------------------------------------------------------------------------
BEGIN_RECV_TABLE_NOBASE(C_ObjectSentrygun, DT_SentrygunTeamOnlyVars)
	RecvPropInt(RECVINFO( m_iAmmo )),
END_RECV_TABLE()

IMPLEMENT_CLIENTCLASS_DT(C_ObjectSentrygun, DT_ObjectSentrygun, CObjectSentrygun)
	RecvPropInt( RECVINFO( m_iBaseTurnRate ) ),
	RecvPropEHandle(RECVINFO(m_hEnemy)),
	RecvPropDataTable( "teamonly", 0, 0, &REFERENCE_RECV_TABLE( DT_SentrygunTeamOnlyVars )),
	RecvPropInt(RECVINFO(m_bTurtled)),
 	RecvPropInt( RECVINFO( m_nAnimationParity ) ),
	RecvPropInt( RECVINFO( m_nOrientationParity ) ),
END_RECV_TABLE()

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
C_ObjectSentrygun::C_ObjectSentrygun()
{
	m_fBoneXRotator = 0;
	m_fBoneYRotator = 0;
	m_iAmmo = 0;
	m_bTurtled = false;
	m_flStartedTurtlingAt = 0;
	m_flStartedUnTurtlingAt = 0;

	SetViewOffset( Vector(0,0,22) );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_ObjectSentrygun::SetDormant( bool bDormant )
{
	BaseClass::SetDormant( bDormant );
	ENTITY_PANEL_ACTIVATE( "sentrygun", !bDormant );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int C_ObjectSentrygun::DrawModel( int flags )
{
	float flRealOriginZ = GetLocalOrigin().z;

	// If we're turtling, slide the model into the ground
	if ( m_bTurtled )
	{
		// How far down are we?
		float flTime = MIN( gpGlobals->curtime - m_flStartedTurtlingAt, SENTRY_TURTLE_TIME );
		float flPercent = 1 - (SENTRY_TURTLE_TIME - flTime) / SENTRY_TURTLE_TIME;

		// FIXME: This is totally wrong!!!
		Vector vNewOrigin = GetLocalOrigin();
		vNewOrigin.z -= (CollisionProp()->OBBSize().z * flPercent);
		SetLocalOrigin( vNewOrigin );
		InvalidateBoneCache();
	}
	else if ( !m_bTurtled )
	{
		if ( m_flStartedUnTurtlingAt )
		{
			float flTime = MIN( gpGlobals->curtime - m_flStartedUnTurtlingAt, SENTRY_TURTLE_TIME );
			float flPercent = (SENTRY_TURTLE_TIME - flTime) / SENTRY_TURTLE_TIME;
			
		// FIXME: This is totally wrong!!!
			Vector vNewOrigin = GetLocalOrigin();
			vNewOrigin.z -= (CollisionProp()->OBBSize().z * flPercent);
			SetLocalOrigin( vNewOrigin );
			InvalidateBoneCache();

			// Fully unturtled?
			if ( flTime >= SENTRY_TURTLE_TIME )
			{
				m_flStartedUnTurtlingAt = 0;
			}
		}
	}

	int drawn = BaseClass::DrawModel( flags );
	SetLocalOriginDim( Z_INDEX, flRealOriginZ );

	return drawn;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_ObjectSentrygun::PreDataUpdate( DataUpdateType_t updateType )
{
	BaseClass::PreDataUpdate( updateType );

	m_bLastTurtled = m_bTurtled;
	m_nLastAnimationParity = m_nAnimationParity;
	m_angPrevLocalAngles = GetLocalAngles();
	m_nPrevOrientationParity = 	m_nOrientationParity;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_ObjectSentrygun::PostDataUpdate( DataUpdateType_t updateType )
{
	BaseClass::PostDataUpdate( updateType );

	if ( m_bLastTurtled != m_bTurtled )
	{
		if ( m_bTurtled )
		{
			m_flStartedTurtlingAt = gpGlobals->curtime;
			m_flStartedUnTurtlingAt = 0;
		}
		else
		{
			m_flStartedUnTurtlingAt = gpGlobals->curtime;
			m_flStartedTurtlingAt = 0;
		}
	}

	if ( m_nLastAnimationParity != m_nAnimationParity )
 	{
 		SetCycle( 0.0f );
 	}

	bool changed = false;
	QAngle angleDiff;
	angleDiff = ( GetAbsAngles() - m_angPrevLocalAngles );
	for (int i = 0;i < 3; i++ )
	{
		angleDiff[i] = UTIL_AngleMod( angleDiff[ i ] );
	}

	if ( angleDiff.Length() > 0.1f )
	{
		changed = true;
	}
	if ( updateType == DATA_UPDATE_CREATED || changed )
	{
		// Orient it
		m_vecCurAngles.y = UTIL_AngleMod( GetLocalAngles().y );
		RecomputeOrientation();
	}
	else if ( m_nPrevOrientationParity != m_nOrientationParity )
	{
		if ( changed )
		{
			RecomputeOrientation();
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_ObjectSentrygun::OnDataChanged( DataUpdateType_t updateType )
{
	BaseClass::OnDataChanged( updateType );

	if ( updateType == DATA_UPDATE_CREATED )
	{
		// Start thinking (Baseclass stops it)
		SetNextClientThink( CLIENT_THINK_ALWAYS );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_ObjectSentrygun::FinishedBuilding( void )
{
	BaseClass::FinishedBuilding();

	EmitSound( "ObjectSentrygun.Activate" );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_ObjectSentrygun::GetBoneControllers(float controllers[MAXSTUDIOBONECTRLS])
{
	studiohdr_t *pModel = modelinfo->GetStudiomodel( GetModel() );

	// When yaw preview is on,
	if (!IsPreviewingYaw())
	{
		Studio_SetController(pModel, 0, m_fBoneXRotator, controllers[0]);
	}
	else
	{
		// Bone rotation == 0 here to make it exactly match the preview
		Studio_SetController(pModel, 0, 0, controllers[0]);
	}

	Studio_SetController(pModel, 1, m_fBoneYRotator, controllers[1]);
	Studio_SetController(pModel, 2, m_fBoneYRotator, controllers[2]);
	Studio_SetController(pModel, 3, m_fBoneYRotator, controllers[3]);
}

//-----------------------------------------------------------------------------
// Purpose: This is called to get the initial builder yaw...
//-----------------------------------------------------------------------------
float C_ObjectSentrygun::GetInitialBuilderYaw()
{
	// Take the current rotation into account
	return GetAbsAngles().y + m_fBoneXRotator;
}

//-----------------------------------------------------------------------------
// Called when a rotation happens
//-----------------------------------------------------------------------------
void C_ObjectSentrygun::RecomputeOrientation( )
{
	m_iRightBound = UTIL_AngleMod( m_vecCurAngles.y - 50);
	m_iLeftBound = UTIL_AngleMod( m_vecCurAngles.y + 50);
	if ( m_iRightBound > m_iLeftBound )
	{
		m_iRightBound = m_iLeftBound;
		m_iLeftBound = UTIL_AngleMod( m_vecCurAngles.y - 50);
	}

	// Start it rotating
	m_vecGoalAngles.y = m_iRightBound;;
	m_vecGoalAngles.x = m_vecCurAngles.x = 0;

	m_fBoneXRotator = 0.0f;
	m_fBoneYRotator = 0.0f;

	m_bTurningRight = true;
}


//-----------------------------------------------------------------------------
// Purpose: Handle movement of the turret
//-----------------------------------------------------------------------------
bool C_ObjectSentrygun::MoveTurret(void)
{
	bool bMoved = 0;

	float turnrate = (float)(m_iBaseTurnRate) * 10.0f;
	turnrate *= gpGlobals->frametime;

	// any x movement?
	if ( m_vecCurAngles.x != m_vecGoalAngles.x )
	{
		float flDir = m_vecGoalAngles.x > m_vecCurAngles.x ? 1 : -1 ;

		m_vecCurAngles.x += 0.1 * (turnrate * 5) * flDir;

		// if we started below the goal, and now we're past, peg to goal
		if (flDir == 1)
		{
			if (m_vecCurAngles.x > m_vecGoalAngles.x)
				m_vecCurAngles.x = m_vecGoalAngles.x;
		} 
		else
		{
			if (m_vecCurAngles.x < m_vecGoalAngles.x)
				m_vecCurAngles.x = m_vecGoalAngles.x;
		}

		m_fBoneYRotator = m_vecCurAngles.x;

		bMoved = 1;
	}

	if ( m_vecCurAngles.y != m_vecGoalAngles.y )
	{
		float flDir = m_vecGoalAngles.y > m_vecCurAngles.y ? 1 : -1 ;
		float flDist = fabs(m_vecGoalAngles.y - m_vecCurAngles.y);
		bool bReversed = false;
		
		if (flDist > 180)
		{
			flDist = 360 - flDist;
			flDir = -flDir;
			bReversed = true;
		}

		if (m_hEnemy == NULL )
		{
			if (flDist > 30)
			{
				if (m_fTurnRate < turnrate * 20)
				{
					m_fTurnRate += turnrate;
				}
			}
			else
			{
				// Slow down
				if ( m_fTurnRate > (turnrate * 5) )
					m_fTurnRate -= turnrate;
			}
		}
		else
		{
			// When tracking enemies, move faster and don't slow
			if (flDist > 30)
			{
				if (m_fTurnRate < turnrate * 30)
				{
					m_fTurnRate += turnrate * 3;
				}
			}
		}

		m_vecCurAngles.y += 0.1 * m_fTurnRate * flDir;

		// if we passed over the goal, peg right to it now
		if (flDir == -1)
		{
			if ( (bReversed == false && m_vecGoalAngles.y > m_vecCurAngles.y) || (bReversed == true && m_vecGoalAngles.y < m_vecCurAngles.y) )
				m_vecCurAngles.y = m_vecGoalAngles.y;
		} 
		else
		{
			if ( (bReversed == false && m_vecGoalAngles.y < m_vecCurAngles.y) || (bReversed == true && m_vecGoalAngles.y > m_vecCurAngles.y) )
				m_vecCurAngles.y = m_vecGoalAngles.y;
		}

		if (m_vecCurAngles.y < 0)
			m_vecCurAngles.y += 360;
		else if (m_vecCurAngles.y >= 360)
			m_vecCurAngles.y -= 360;

		if (flDist < (0.05 * turnrate))
			m_vecCurAngles.y = m_vecGoalAngles.y;

		m_fBoneXRotator = m_vecCurAngles.y - UTIL_AngleMod( GetAbsAngles().y );

		bMoved = 1;
	}

	if ( !bMoved || !m_fTurnRate )
		m_fTurnRate = turnrate;

	if ( bMoved )
	{
		NetworkStateChanged();
	}

	return bMoved;
}

void C_ObjectSentrygun::ClientThink( void )
{
	// Turtling sentryguns don't think
	if ( IsTurtled() )
		return;

	if ( IsPlacing() || IsBuilding() )
		return;


	if ( m_hEnemy != NULL )
	{
		// Figure out where we're firing at
		Vector vecMid = EyePosition();
		Vector vecFireTarget = m_hEnemy->WorldSpaceCenter(); //  + vecMid; // BodyTarget( vecMid );
		Vector vecDirToEnemy = vecFireTarget - vecMid;
		QAngle angToTarget;
		VectorAngles(vecDirToEnemy, angToTarget);
		
		angToTarget.y = UTIL_AngleMod( angToTarget.y );
		if (angToTarget.x < -180)
			angToTarget.x += 360;
		if (angToTarget.x > 180)
			angToTarget.x -= 360;

		// now all numbers should be in [1...360]
		// pin to turret limitations to [-50...50]
		if (angToTarget.x > 50)
			angToTarget.x = 50;
		else if (angToTarget.x < -50)
			angToTarget.x = -50;

		m_vecGoalAngles.y = angToTarget.y;
		m_vecGoalAngles.x = angToTarget.x;

		MoveTurret();
		return;
	}

	// Rotate
	if ( !MoveTurret() )
	{
		// Play a sound occasionally
		if ( random->RandomFloat(0, 1) < 0.02 )
		{
			EmitSound( "ObjectSentrygun.Idle" );
		}

		// Switch rotation direction
		if (m_bTurningRight)
		{
			m_bTurningRight = false;
			m_vecGoalAngles.y = m_iLeftBound;
		}
		else
		{
			m_bTurningRight = true;
			m_vecGoalAngles.y = m_iRightBound;
		}

		// Randomly look up and down a bit
		if ( random->RandomFloat(0, 1) < 0.3 )
		{
			m_vecGoalAngles.x = (int)random->RandomFloat(-10,10);
		}
	}
}


//-----------------------------------------------------------------------------
// Control screen 
//-----------------------------------------------------------------------------
class CSentrygunControlPanel : public CRotatingObjectControlPanel
{
	DECLARE_CLASS( CSentrygunControlPanel, CRotatingObjectControlPanel );

public:
	CSentrygunControlPanel( vgui::Panel *parent, const char *panelName );
	virtual bool Init( KeyValues* pKeyValues, VGuiScreenInitData_t* pInitData );
	virtual void OnTick();
	virtual void OnCommand( const char *command );

	void		 AddAmmo( void );

private:
	vgui::Label		*m_pAmmoLabel;
};


DECLARE_VGUI_SCREEN_FACTORY( CSentrygunControlPanel, "sentrygun_control_panel" );


//-----------------------------------------------------------------------------
// Constructor: 
//-----------------------------------------------------------------------------
CSentrygunControlPanel::CSentrygunControlPanel( vgui::Panel *parent, const char *panelName )
	: BaseClass( parent, "CSentrygunControlPanel" ) 
{
}

//-----------------------------------------------------------------------------
// Initialization 
//-----------------------------------------------------------------------------
bool CSentrygunControlPanel::Init( KeyValues* pKeyValues, VGuiScreenInitData_t* pInitData )
{
	m_pAmmoLabel = new vgui::Label( this, "AmmoReadout", "" );

	if (!BaseClass::Init(pKeyValues, pInitData))
		return false;

	return true;
}

//-----------------------------------------------------------------------------
// Frame-based update
//-----------------------------------------------------------------------------
void CSentrygunControlPanel::OnTick()
{
	BaseClass::OnTick();
	
	C_BaseObject *pObj = GetOwningObject();
	if (!pObj)
		return;

	Assert( dynamic_cast<C_ObjectSentrygun*>(pObj) );
	C_ObjectSentrygun *pSentrygun = static_cast<C_ObjectSentrygun*>(pObj);

	char buf[256];
	int iAmmo = pSentrygun->GetAmmoLeft();
	if (iAmmo > 0)
	{
		Q_snprintf( buf, sizeof( buf ), "%d rounds left", iAmmo );
	}
	else
	{
		Q_snprintf( buf, sizeof( buf ), "OUT OF AMMO" );
	}
	m_pAmmoLabel->SetText( buf );
}

//-----------------------------------------------------------------------------
// Purpose: Handle ammo input to the sentrygun
//-----------------------------------------------------------------------------
void CSentrygunControlPanel::AddAmmo( void )
{
	C_BaseObject *pObj = GetOwningObject();
	if (pObj)
	{
		pObj->SendClientCommand( "addammo" );
	}
}

//-----------------------------------------------------------------------------
// Button click handlers
//-----------------------------------------------------------------------------
void CSentrygunControlPanel::OnCommand( const char *command )
{
	if (!Q_strnicmp(command, "AddAmmo", 7))
	{
		AddAmmo();
		return;
	}

	BaseClass::OnCommand(command);
}



//======================================================================================================
// SENTRYGUN TYPES
//======================================================================================================
// Purpose: Plasma sentrygun
//-----------------------------------------------------------------------------
IMPLEMENT_CLIENTCLASS_DT(C_ObjectSentrygunPlasma, DT_ObjectSentrygunPlasma, CObjectSentrygunPlasma)
END_RECV_TABLE()

C_ObjectSentrygunPlasma::C_ObjectSentrygunPlasma()
{
}

//-----------------------------------------------------------------------------
// Purpose: Rocket launcher sentrygun
//-----------------------------------------------------------------------------
IMPLEMENT_CLIENTCLASS_DT(C_ObjectSentrygunRocketlauncher, DT_ObjectSentrygunRocketlauncher, CObjectSentrygunRocketlauncher)
END_RECV_TABLE()

C_ObjectSentrygunRocketlauncher::C_ObjectSentrygunRocketlauncher()
{
}