//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Client's CObjectTeleporter
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "c_baseobject.h"
#include "c_tf_player.h"
#include "vgui/ILocalize.h"
#include "c_obj_teleporter.h"
#include "soundenvelope.h"
#include "vgui/ILocalize.h"
#include "tf_gamerules.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

using namespace vgui;

#define TELEPORTER_MINS			Vector( -24, -24, 0)
#define TELEPORTER_MAXS			Vector( 24, 24, 12)	

//-----------------------------------------------------------------------------
// Purpose: Teleporter object
//-----------------------------------------------------------------------------

IMPLEMENT_CLIENTCLASS_DT(C_ObjectTeleporter, DT_ObjectTeleporter, CObjectTeleporter)
	RecvPropInt( RECVINFO(m_iState) ),
	RecvPropTime( RECVINFO(m_flRechargeTime) ),
	RecvPropTime( RECVINFO(m_flCurrentRechargeDuration) ),
	RecvPropInt( RECVINFO(m_iTimesUsed) ),
	RecvPropFloat( RECVINFO(m_flYawToExit) ),
	RecvPropBool( RECVINFO(m_bMatchBuilding) ),
END_RECV_TABLE()

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
C_ObjectTeleporter::C_ObjectTeleporter()
{
	m_hChargedEffect = NULL;
	m_hDirectionEffect = NULL;
	m_hChargedLeftArmEffect = NULL;
	m_hChargedRightArmEffect = NULL;

	m_iDirectionArrowPoseParam = 0;

	m_pSpinSound = NULL;

	m_bMatchBuilding = false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_ObjectTeleporter::UpdateOnRemove( void )
{
	StopActiveEffects();
	StopChargedEffects();

	if ( m_pSpinSound )
	{
		CSoundEnvelopeController::GetController().SoundDestroy( m_pSpinSound );
	}

	BaseClass::UpdateOnRemove();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_ObjectTeleporter::OnPreDataChanged( DataUpdateType_t updateType )
{
	BaseClass::OnPreDataChanged( updateType );

	m_iOldState = m_iState;
	m_bOldMatchBuilding = m_bMatchBuilding;
}

void C_ObjectTeleporter::StartBuildingEffects()
{
	StopBuildingEffects();
	char szEffect[128];

	// arm glow effects
	Q_snprintf( szEffect, sizeof(szEffect), "teleporter_arms_circle_%s_blink", ( GetTeamNumber() == TF_TEAM_RED ) ? "red" : "blue" );

	Assert( m_hBuildingLeftArmEffect.m_pObject == NULL );
	m_hBuildingLeftArmEffect = ParticleProp()->Create( szEffect, PATTACH_POINT_FOLLOW, 1 );

	Assert( m_hBuildingRightArmEffect.m_pObject == NULL );
	m_hBuildingRightArmEffect = ParticleProp()->Create( szEffect, PATTACH_POINT_FOLLOW, 3 );
}

void C_ObjectTeleporter::StartChargedEffects()
{
	StopChargedEffects();
	char szEffect[128];

	Q_snprintf( szEffect, sizeof(szEffect), "teleporter_%s_charged_level%d", 
		( GetTeamNumber() == TF_TEAM_RED ) ? "red" : "blue", GetUpgradeLevel() );

	Assert( m_hChargedEffect.m_pObject == NULL );
	m_hChargedEffect = ParticleProp()->Create( szEffect, PATTACH_ABSORIGIN );
}

void C_ObjectTeleporter::StartActiveEffects()
{
	StopActiveEffects();
	char szEffect[128];

	Q_snprintf( szEffect, sizeof(szEffect), "teleporter_%s_%s_level%d", 
		( GetTeamNumber() == TF_TEAM_RED ) ? "red" : "blue",
		GetObjectMode() == MODE_TELEPORTER_ENTRANCE ? "entrance" : "exit",
		GetUpgradeLevel() );

	Assert( m_hDirectionEffect.m_pObject == NULL );
	m_hDirectionEffect = ParticleProp()->Create( szEffect, PATTACH_ABSORIGIN );

	// arm glow effects
	Q_snprintf( szEffect, sizeof(szEffect), "teleporter_arms_circle_%s",
		( GetTeamNumber() == TF_TEAM_RED ) ? "red" : "blue" );

	Assert( m_hChargedLeftArmEffect.m_pObject == NULL );
	m_hChargedLeftArmEffect = ParticleProp()->Create( szEffect, PATTACH_POINT_FOLLOW, 1 );

	Assert( m_hChargedRightArmEffect.m_pObject == NULL );
	m_hChargedRightArmEffect = ParticleProp()->Create( szEffect, PATTACH_POINT_FOLLOW, 3 );

	// always reinitializes sound since this only gets called when the sound needs to start or change
	CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
	if ( m_pSpinSound )
	{
		controller.SoundDestroy( m_pSpinSound );
		m_pSpinSound = NULL;
	}
	char szSound[128];
	Q_snprintf( szSound, sizeof(szSound), "Building_Teleporter.SpinLevel%d", GetUpgradeLevel());

	CLocalPlayerFilter filter;
	m_pSpinSound = controller.SoundCreate( filter, entindex(), szSound );
	controller.Play( m_pSpinSound, 1.0, 100 );
}

void C_ObjectTeleporter::StopBuildingEffects()
{
	if ( m_hBuildingLeftArmEffect )
	{
		ParticleProp()->StopEmission( m_hBuildingLeftArmEffect );
		m_hBuildingLeftArmEffect = NULL;
	}

	if ( m_hBuildingRightArmEffect )
	{
		ParticleProp()->StopEmission( m_hBuildingRightArmEffect );
		m_hBuildingRightArmEffect = NULL;
	}
}

void C_ObjectTeleporter::StopChargedEffects()
{
	if ( m_hChargedEffect )
	{
		ParticleProp()->StopEmission( m_hChargedEffect );
		m_hChargedEffect = NULL;
	}
}

void C_ObjectTeleporter::StopActiveEffects()
{
	if ( m_hDirectionEffect )
	{
		ParticleProp()->StopEmission( m_hDirectionEffect );
		m_hDirectionEffect = NULL;
	}

	if ( m_hChargedLeftArmEffect )
	{
		ParticleProp()->StopEmission( m_hChargedLeftArmEffect );
		m_hChargedLeftArmEffect = NULL;
	}

	if ( m_hChargedRightArmEffect )
	{
		ParticleProp()->StopEmission( m_hChargedRightArmEffect );
		m_hChargedRightArmEffect = NULL;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_ObjectTeleporter::SetInvisibilityLevel( float flValue )
{
	if ( IsEnteringOrExitingFullyInvisible( flValue ) )
	{
		UpdateTeleporterEffects();
	}

	BaseClass::SetInvisibilityLevel( flValue );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_ObjectTeleporter::UpdateTeleporterEffects( void )
{
#ifdef STAGING_ONLY
	C_TFPlayer *pTFOwner = GetOwner();
	if ( ( pTFOwner && pTFOwner->m_Shared.IsEnteringOrExitingFullyInvisible() ) || GetInvisibilityLevel() == 1.f  )
	{
		StopActiveEffects();
		StopBuildingEffects();
		StopChargedEffects();
		return;
	}
#endif // STAGING_ONLY

	if ( m_bMatchBuilding )
	{
		StartBuildingEffects();
	}
	else
	{
		StopBuildingEffects();
	}

	// In MVM, teleporter from invaders act as spawn point. Always play active effect
	if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
	{
		if ( m_iState != TELEPORTER_STATE_BUILDING && GetTeamNumber() == TF_TEAM_PVE_INVADERS )
		{
			StartChargedEffects();
			StartActiveEffects();
			return;
		}
	}

	if ( m_iState == TELEPORTER_STATE_READY )
	{
		StartChargedEffects();
	}
	else
	{
		StopChargedEffects();
	}

	if ( m_iState > TELEPORTER_STATE_IDLE && m_iOldState <= TELEPORTER_STATE_IDLE )
	{
		StartActiveEffects();
	}
	else if ( ( m_iState <= TELEPORTER_STATE_IDLE || m_iState == TELEPORTER_STATE_UPGRADING ) && m_iOldState > TELEPORTER_STATE_IDLE )
	{
		StopActiveEffects();
	}
}

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

	if ( m_bOldMatchBuilding != m_bMatchBuilding )
	{
		m_bOldMatchBuilding = m_bMatchBuilding;
		UpdateTeleporterEffects();
	}

	if ( m_iOldState != m_iState )
	{
		UpdateTeleporterEffects();
		m_iOldState = m_iState;
	}

	// update the pitch based on our playback rate
	if ( m_pSpinSound )
	{
		CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();

		controller.SoundChangePitch( m_pSpinSound, GetPlaybackRate() * 100.0f, 0.1 );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
float C_ObjectTeleporter::GetChargeTime( void )
{
	float flTime = m_flRechargeTime - gpGlobals->curtime;

	if ( flTime < 0 )
		return 0;

	return flTime;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int C_ObjectTeleporter::GetTimesUsed( void )
{
	return m_iTimesUsed;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CStudioHdr *C_ObjectTeleporter::OnNewModel( void )
{
	CStudioHdr *hdr = BaseClass::OnNewModel();

	m_iDirectionArrowPoseParam = LookupPoseParameter( "direction" );

	SetNextClientThink( CLIENT_THINK_ALWAYS );

	return hdr;
}

//-----------------------------------------------------------------------------
// Purpose: Update the direction arrow
//-----------------------------------------------------------------------------
void C_ObjectTeleporter::ClientThink( void )
{
	if ( m_iState >= TELEPORTER_STATE_READY )
	{
		SetPoseParameter( m_iDirectionArrowPoseParam, m_flYawToExit);
	}

#ifdef STAGING_ONLY
	C_TFPlayer *pTFOwner = GetOwner();
	if ( pTFOwner && pTFOwner->m_Shared.IsEnteringOrExitingFullyInvisible() )
	{
		UpdateTeleporterEffects();
	}
#endif // STAGING_ONLY
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_ObjectTeleporter::GetTargetIDDataString( OUT_Z_BYTECAP(iMaxLenInBytes) wchar_t *sDataString, int iMaxLenInBytes )
{
	Assert( iMaxLenInBytes >= sizeof(sDataString[0]) );
	wchar_t wzBaseString[MAX_ID_STRING];
	BaseClass::GetTargetIDDataString( wzBaseString, sizeof( wzBaseString ) );

	sDataString[0] = '\0';
	if ( m_iState == TELEPORTER_STATE_RECHARGING && gpGlobals->curtime < m_flRechargeTime )
	{
		float flPercent = clamp( ( m_flRechargeTime - gpGlobals->curtime ) / m_flCurrentRechargeDuration, 0.0f, 1.0f );

		wchar_t wszRecharging[ 32 ];
		_snwprintf( wszRecharging, ARRAYSIZE(wszRecharging) - 1, L"%.0f", 100 - (flPercent * 100) );
		wszRecharging[ ARRAYSIZE(wszRecharging)-1 ] = '\0';

		const char *printFormatString = "#TF_playerid_object_recharging";

		g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find(printFormatString),
			1,
			wszRecharging );
	}	
	else if ( m_iState == TELEPORTER_STATE_IDLE )
	{
		g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find("#TF_playerid_teleporter_nomatch" ), 0 );
	}

	// Concatenate the base level string
	V_wcsncat( sDataString, L"   ", iMaxLenInBytes / sizeof( wchar_t ) );
	V_wcsncat( sDataString, wzBaseString, iMaxLenInBytes / sizeof( wchar_t ) );
}

//-----------------------------------------------------------------------------
// Purpose: Damage level has changed, update our effects
//-----------------------------------------------------------------------------
void C_ObjectTeleporter::UpdateDamageEffects( BuildingDamageLevel_t damageLevel )
{
	if ( m_hDamageEffects )
	{
		m_hDamageEffects->StopEmission( false, false );
		m_hDamageEffects = NULL;
	}

	const char *pszEffect = "";

	switch( damageLevel )
	{
	case BUILDING_DAMAGE_LEVEL_LIGHT:
		pszEffect = "tpdamage_1";
		break;
	case BUILDING_DAMAGE_LEVEL_MEDIUM:
		pszEffect = "tpdamage_2";
		break;
	case BUILDING_DAMAGE_LEVEL_HEAVY:
		pszEffect = "tpdamage_3";
		break;
	case BUILDING_DAMAGE_LEVEL_CRITICAL:
		pszEffect = "tpdamage_4";
		break;

	default:
		break;
	}

	if ( Q_strlen(pszEffect) > 0 )
	{
		m_hDamageEffects = ParticleProp()->Create( pszEffect, PATTACH_ABSORIGIN );
	}
}

//-----------------------------------------------------------------------------
// 
//-----------------------------------------------------------------------------
bool C_ObjectTeleporter::IsPlacementPosValid( void )
{
	bool bResult = BaseClass::IsPlacementPosValid();

	if ( !bResult )
	{
		return false;
	}

	// m_vecBuildOrigin is the proposed build origin

	// start above the teleporter position
	Vector vecTestPos = m_vecBuildOrigin;
	vecTestPos.z += TELEPORTER_MAXS.z;

	// make sure we can fit a player on top in this pos
	trace_t tr;
	UTIL_TraceHull( vecTestPos, vecTestPos, VEC_HULL_MIN, VEC_HULL_MAX, MASK_SOLID | CONTENTS_PLAYERCLIP, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );

	return ( tr.fraction >= 1.0 );
}
//-----------------------------------------------------------------------------
// 
//-----------------------------------------------------------------------------
void C_ObjectTeleporter::UpgradeLevelChanged( void )
{
	StopActiveEffects();
	StopChargedEffects();

	if ( m_iState >= TELEPORTER_STATE_READY && m_iState != TELEPORTER_STATE_UPGRADING )
	{
		StartActiveEffects();
		if ( m_iState != TELEPORTER_STATE_RECHARGING )
		{
			StartChargedEffects();
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_ObjectTeleporter::OnGoInactive( void )
{
	StopActiveEffects();
	StopBuildingEffects();
	StopChargedEffects();

	BaseClass::OnGoInactive();
}