/**
* Swoop
* @version 11-03-2003
* @author  Joseph 'Pcjoe' Florencio
*/

#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "monsters.h"
#include "weapons.h"
#include "nodes.h"
#include "effects.h"
#include "aura.h"
#include "classes.h"
#include "magicattack.h"
#include "explodingattack.h"
#include "player.h"
#include "gamerules.h"
#include "chargedattack.h"
#include "melee.h"
#include "playermelee.h"
#include "swoop.h"

#define FIRST_CLICK		0.4
#define MIDDLE_PAUSE	0.4

// swoop
extern cvar_t max_swoop_time;
extern cvar_t swoop_chain_delay;
extern cvar_t init_swoop_ki;
extern cvar_t chain_swoop_ki;
// dash
extern cvar_t max_dash_time;
extern cvar_t dash_chain_delay;
extern cvar_t init_dash_ki;
extern cvar_t chain_dash_ki;
// pause
extern cvar_t ki_cost_pause;

extern cvar_t charge_turbo_hold_dir_time;

// Constructor
CSwoop::CSwoop()
{
#ifndef CLIENT_DLL
	Reset();
#endif
}

/*
* Reset All
*/
void CSwoop::Reset( void )
{
#ifndef CLIENT_DLL
	m_iNullKeyset = 0;
	m_flStartSwoop = 0;
	m_iChainCount = 0;
	m_iSwoopDirection = -1;
	m_iPressDirection = -1;
	m_flPressDirectionTime = 0;
	m_fSwoop = FALSE;
	m_fDash = FALSE;
	m_fChainSwoop = FALSE;
	m_fPause = FALSE;

	ResetAllDT( );
	SetOwner( NULL );
#endif
}

#ifndef CLIENT_DLL
/*
* Reset Single double tap
*/
void CSwoop::ResetDT( double_tap_s *pDT )
{	pDT->flStartFirst = 0;
	pDT->flEndFirst = 0;
	pDT->flStartSecond = 0;
	pDT->fFirstTap = FALSE;
}

/*
* Reset All double taps
*/
void CSwoop::ResetAllDT( void )
{	for(int i=0;i<NUM_DIRECTIONS;i++)
	{	ResetDT( &m_DoubleTap[i] );
	}
}

/*
* Reset All double taps except specified one
*/
void CSwoop::ResetAllDTExcept( int iExcept )
{	for(int i=0;i<NUM_DIRECTIONS;i++)
	{	if(i == iExcept)
		{	continue;
		}
		ResetDT( &m_DoubleTap[i] );
	}
}

/*
* Get Swoop KI Cost
*/
int CSwoop::GetSwoopStartKi( BOOL fDash )
{	if(fDash)
		return init_dash_ki.value + m_iChainCount * chain_dash_ki.value;
	return init_swoop_ki.value + m_iChainCount * chain_swoop_ki.value;
}

/*
* Manage Swoop
*/
void CSwoop::ManageSwoop( void )
{	// Make sure we have an owner
	if(!m_pOwner)
	{	return;
	}

	// Make sure he's alive
	if(!m_pOwner->IsAlive())
	{	return;
	}

	// Manage null keyset
	ManageNullKeyset();

	// Reset pressed direction every run
	m_iPressDirection = -1;

	// If direction is non-usable, reset it completly
	if(!CanUseDirections())
	{	for(int i=0;i<NUM_DIRECTIONS;i++)
		{	if(m_DoubleTap[i].flStartFirst)
			{	ResetDT( &m_DoubleTap[i] );
			}
		}
	}
	else
	{	// Charged turbo swoop uses single tap, single taps for successive dashes as well
		if(m_pOwner->m_ChargedTurbo.isChargeTurboActive() || IsSwooping() || IsDashing())
		{	for(int i=0;i<NUM_DIRECTIONS;i++)
			{	if(IsHolding(i))
				{	// Dash logic
					if(IsDashing())
					{	if(CanDash() && m_pOwner->UseKi(GetSwoopStartKi(TRUE)))
						{	// Add to null keyset
							AddToNullKeyset(DirectionsToFlags(i));

							// Start dash
							StartSwoop(i, TRUE);
						}
					}
					// Swoop logic
					else if(CanSwoop(i))
					{	// First time swoop
						if(!IsSwooping())
						{	m_iPressDirection = i;
							
							// Only let us hold down the key for so many seconds
							if(m_flPressDirectionTime)
							{	if((gpGlobals->time-m_flPressDirectionTime) > charge_turbo_hold_dir_time.value)
								{	AddToNullKeyset(DirectionsToFlags(m_iPressDirection));

									// Disable active charge, if active
									if(m_pOwner->m_ChargedTurbo.isChargeTurboActive())
										m_pOwner->m_ChargedTurbo.stopActiveCharge();

									// Start swoop
									StartSwoop(m_iPressDirection, FALSE);
								}
							}
						}
						// Chain swoop
						else if(m_pOwner->UseKi(GetSwoopStartKi(FALSE)))
						{	AddToNullKeyset(DirectionsToFlags(m_iSwoopDirection));

							// Start swoop
							StartSwoop(i, FALSE);
						}
					}
				}
			}

			if(m_iPressDirection != -1)
			{	if(m_flPressDirectionTime == 0)
					m_flPressDirectionTime = gpGlobals->time;
			}
			else if(m_flPressDirectionTime != 0)
				m_flPressDirectionTime = 0;
		}
		// Double tap dash
		else
		{	int iKey = UpdateDoubleTap();
			// Start Dash
			if(iKey != -1 && CanDash())
			{	if(m_pOwner->UseKi(GetSwoopStartKi(TRUE)))
				{	// Add to null keyset
					AddToNullKeyset(DirectionsToFlags(iKey));

					// Start dash
					StartSwoop(iKey, TRUE);
				}
			}
		}
	}

	// Handle swoop
	if(m_fSwoop)
	{	HandleSwoop();
	}
}

/*
* Update Double Tap
*/
int CSwoop::UpdateDoubleTap( void )
{	int iPossible = -1;
	for(int i=0;i<NUM_DIRECTIONS;i++)
	{	// Used for "pressing" of keys
		if(IsHolding(i))
		{	// We already did the first tap
			if(m_DoubleTap[i].fFirstTap)
			{	// Holding second tap
				if(m_DoubleTap[i].flStartSecond)
				{	iPossible = i;
					ResetAllDTExcept(iPossible);
					break;
				}
				// Just starting second tap
				else
				{	if((gpGlobals->time - m_DoubleTap[i].flEndFirst) < MIDDLE_PAUSE)
					{	m_DoubleTap[i].flStartSecond = gpGlobals->time;
					}
					// We made too much of a space between first + second tap, delete
					else
					{	AddToNullKeyset( DirectionsToFlags( i ) );
						ResetDT( &m_DoubleTap[i] );
					}
				}
			}
			else
			{	// Start first tap
				if(!m_DoubleTap[i].flStartFirst)
				{	m_DoubleTap[i].flStartFirst = gpGlobals->time;
				}
			}
			continue;
		}
				
		// Used for "letting go" of keys
		// Ending first tap
		if(m_DoubleTap[i].flStartFirst && !m_DoubleTap[i].flEndFirst)
		{	// First Tap came out good
			if((gpGlobals->time - m_DoubleTap[i].flStartFirst) < FIRST_CLICK)
			{	m_DoubleTap[i].flEndFirst = gpGlobals->time;
				m_DoubleTap[i].fFirstTap = TRUE;
			}
			// First tap was too long, reset
			else
			{	AddToNullKeyset( DirectionsToFlags( i ) );
				ResetDT( &m_DoubleTap[i] );
				continue;
			}
		}

		// Checking second tap
		if(m_DoubleTap[i].flStartSecond)
		{	// Reset double tap
			ResetDT(&m_DoubleTap[i]);
		}
	}
	return iPossible;
}

/*
* Get Swoop Speed Start
*/
float CSwoop::GetSwoopSpeedStart( void )
{	if(m_fDash)
		return m_pOwner->m_pClass->GetDashSpeedStart();
	return m_pOwner->m_pClass->GetSwoopSpeedStart();
}

/*
* Get Swoop Speed End
*/
float CSwoop::GetSwoopSpeedEnd( void )
{	if(m_fDash)
		return m_pOwner->m_pClass->GetDashSpeedEnd();
	return m_pOwner->m_pClass->GetSwoopSpeedEnd();
}

/*
* Did we double tap correctly so it's a valid dash?
*/
BOOL CSwoop::CanDash( void )
{	// Can't dash when blinded
	if(m_pOwner->m_fBlinded)
		return FALSE;

	// Make sure we're not trying to do too many dashes in a row
	if(IsDashing() && (gpGlobals->time-m_flStartSwoop) < dash_chain_delay.value)
		return FALSE;
	
	return TRUE;
}

/*
* Can Swoop?
*/
BOOL CSwoop::CanSwoop( int iDirection )
{	// Can't dash when blinded
	if(m_pOwner->m_fBlinded)
		return FALSE;

	// Make sure we're not trying to chain too many swoops
	if(IsSwooping())
	{	if((gpGlobals->time-m_flStartSwoop) < swoop_chain_delay.value)
			return FALSE;

		if(m_fSwoop && (iDirection == m_iSwoopDirection))
			return FALSE;
	}

	return TRUE;
}

/*
* Can Pickup direction
*/
BOOL CSwoop::CanUseDirections( void )
{	if(!m_pOwner->m_fStrangled && !m_pOwner->m_fBecomeCandy && !m_pOwner->m_fPowerUp && !m_pOwner->m_fCharging &&
	   !m_pOwner->m_fControl && m_pOwner->IsAlive() && !m_pOwner->m_fObserverFlag &&
	   !m_pOwner->m_fHoldDisc && !m_pOwner->m_fAscending && !m_pOwner->m_fShield &&
	   !m_pOwner->m_Melee.m_fThrown && !m_pOwner->m_fBlock )
	{	return TRUE;
	}

	return FALSE;
}


/*
* Specific swoop direction multiplier
*/
float CSwoop::SwoopSlowdown( void )
{	if(m_iSwoopDirection == BACK)
	{	return 0.85;	// Slow down backwards swoop by 15%
	}
	else
	{	return 1.0;
	}
}

/*
* Handle Swoop (Player actually swooping)
*/
void CSwoop::HandleSwoop( void )
{	// In a intermediate pause stage, stop speed
	if(m_fPause)
	{	m_pOwner->pev->vuser4.y = m_flSwoopSpeed = 0;
		return;
	}
	
	float flRatio = 0;
	// Over time limit, stop swoop
	if(m_fDash)
	{
		if((m_flStartSwoop + max_dash_time.value) < gpGlobals->time)
		{	StopSwoop();
			return;
		}

		flRatio = (gpGlobals->time - m_flStartSwoop)/max_dash_time.value;
	}
	else
	{
		if((m_flStartSwoop + max_swoop_time.value) < gpGlobals->time)
		{	StopSwoop();
			return;
		}

		flRatio = (gpGlobals->time - m_flStartSwoop)/max_swoop_time.value;
	}

	// added by harSens: burn slowdown
	if(m_pOwner->m_fBurning)
	{
		float flDist = (m_pOwner->pev->origin-m_pOwner->m_pBurner->pev->origin).Length();
		m_flSwoopSpeed = -0.02*(flDist*flDist);
		if(flDist>FLAMETHROWER_MAXRADIUS*2)
		{
			m_pOwner->m_fBurning=false;
		}
	}
	else
	{
		// Calculate and apply
		m_flSwoopSpeed = (GetSwoopSpeedStart() + (GetSwoopSpeedEnd()-GetSwoopSpeedStart()) * flRatio) * SwoopSlowdown();
	}
	m_pOwner->pev->vuser4.y = m_flSwoopSpeed;
}

/*
* Start Swoop
*/
void CSwoop::StartSwoop( int iDirection, BOOL fDash )
{	// Disable Flying
	if(m_pOwner->m_fFlying)
	{	m_pOwner->StopFly();
	}
	BOOL fSwoop = m_fSwoop;

	// Enable Swooping
	m_fSwoop = TRUE;
	m_fDash = fDash;
	m_fPause = FALSE;
	m_iPressDirection = -1;
	m_flPressDirectionTime = 0;
	m_iSwoopDirection = iDirection;
	m_flSwoopSpeed = GetSwoopSpeedStart();
	m_flStartSwoop = gpGlobals->time;
	m_iChainCount++;
	
	m_pOwner->m_fMelee = true;
	m_pOwner->pev->gravity = 0.0000001;
	m_pOwner->pev->movetype = MOVETYPE_PLAYERSWOOP;
	// Disable all anims
	m_pOwner->m_Melee.RemoveAllAnims();

	// Set Vectors
	m_pOwner->pev->vuser4.x = iDirection;
	m_pOwner->pev->vuser4.y = m_flSwoopSpeed;

	// Reset animation progress (double animation in same direction)
	m_pOwner->pev->frame = 0;
	m_pOwner->ResetSequenceInfo();

	if(fSwoop)
		m_fChainSwoop = TRUE;
	
	// Use current aura and reset sound and anim
	if(m_pOwner->m_pAura)
	{	if(fSwoop)
		{	EMIT_SOUND(ENT(m_pOwner->pev), CHAN_ITEM, "weapons/chainswoop.wav", 1, ATTN_NORM);
			// Animation needs to be reset if we're comming from a pause
			m_pOwner->m_pAura->pev->sequence = m_pOwner->m_pAura->LookupSequence("swoop");
		}
		else
		{	EMIT_SOUND(ENT(m_pOwner->pev), CHAN_ITEM, "weapons/swoop.wav", 1, ATTN_NORM);
			// Start swooping animation
			m_pOwner->m_pAura->pev->sequence = m_pOwner->m_pAura->LookupSequence("swoop");
			m_pOwner->m_pAura->pev->frame = 0;
			m_pOwner->m_pAura->ResetSequenceInfo( );
		}
	}
	// Create aura
	else if (!m_pOwner->m_pAura)
	{	m_pOwner->m_pAura = CAura::Create(m_pOwner, m_fDash);
	}
}

/*
* Stop Swoop
*/
void CSwoop::StopSwoop( void )
{	// Disable woop
	m_fSwoop = FALSE;
	m_fDash = FALSE;
	m_fChainSwoop = FALSE;
	m_iSwoopDirection = -1;
	m_flSwoopSpeed = 0;
	m_flStartSwoop = 0;
	m_iChainCount = 0;
	m_pOwner->m_fMelee = false;
	// Set Vectors
	m_pOwner->pev->vuser4 = Vector(0,0,0);
	// Delete double taps
	ResetAllDT();

	if(m_pOwner->m_pAura)
	{	// Just reset the sound if the aura already exist
		if(m_pOwner->m_fTurbo)
		{	if(!m_fPause)
			{	EMIT_SOUND(ENT(m_pOwner->pev), CHAN_ITEM, "weapons/aura.wav", 1, ATTN_NORM);
				// Resume idle animation
				m_pOwner->m_pAura->pev->sequence = m_pOwner->m_pAura->LookupSequence("idle");
				m_pOwner->m_pAura->pev->frame = 0;
				m_pOwner->m_pAura->ResetSequenceInfo( );
			}
		}
		// remove it, no turbo was active
		else
		{	m_pOwner->m_pAura->Destroy();
			m_pOwner->m_pAura = NULL;
		}
	}
	m_fPause = FALSE;

	// Stay on Ground/Flight
	m_pOwner->m_fFlying = false;
	if(FBitSet(m_pOwner->pev->flags, FL_ONGROUND))
	{	m_pOwner->pev->gravity = 1;
		m_pOwner->pev->movetype = MOVETYPE_WALK;
	}
	else
	{	m_pOwner->StartFly();
	}

	// set Null Keyset
	m_pOwner->m_Melee.SetMovementFlags(m_iNullKeyset);
	if(m_pOwner->pev->button & IN_JUMP)
	{	AddToNullKeyset(IN_JUMP);
	}
	if(m_pOwner->pev->button & IN_DUCK)
	{	AddToNullKeyset(IN_DUCK);
	}

}

/*
* Get Current Direction ( Vector direction )
*/
Vector CSwoop::GetCurrentDirection( void )
{	UTIL_MakeVectors( m_pOwner->pev->v_angle );

	if(!m_fSwoop)
	{	return gpGlobals->v_forward;
	}

	switch(m_iSwoopDirection)
	{
	case UP:
		{	return gpGlobals->v_up;
		}
	case DOWN:
		{	return -gpGlobals->v_up;
		}
	case LEFT:
		{	return -gpGlobals->v_right;
		}
	case RIGHT:
		{	return gpGlobals->v_right;
		}
	default:
	case FRONT:
		{	return gpGlobals->v_forward;
		}
	case BACK:
		{	return -gpGlobals->v_forward;
		}
	}

	return Vector(0,0,0);
}

/*
* Manage Null Keyset
*/
void CSwoop::ManageNullKeyset( void )
{	// Go through all directional flags, unset directions that are no longer held down
	for( int i = UP; i <= BACK; i++ )
	{	// Special exception for lefts/rights below
		if(i==LEFT||i==RIGHT)
			continue;
		
		if(m_iNullKeyset & DirectionsToFlags(i))
		{	if(!(m_pOwner->pev->button & DirectionsToFlags(i)))
			{	RemoveFromNullKeyset(DirectionsToFlags(i));
			}
		}
	}

	// HACK HACK: Special exceptions for left/right
	if(m_iNullKeyset & IN_LEFT)
	{	if(!(m_pOwner->pev->button & IN_MOVELEFT))
		{	m_iNullKeyset &= ~IN_LEFT;
		}
	}
	if(m_iNullKeyset & IN_RIGHT)
	{	if(!(m_pOwner->pev->button & IN_MOVERIGHT))
		{	m_iNullKeyset &= ~IN_RIGHT;
		}
	}
}

/*
* Add to NullKeyset
*/
void CSwoop::AddToNullKeyset( int iFlags )
{	m_iNullKeyset |= iFlags;
}

/*
* Remove from NullKeyset
*/
void CSwoop::RemoveFromNullKeyset( int iFlags )
{	m_iNullKeyset &= ~iFlags;
}

/*
* Add All To NullKeyset
*/
void CSwoop::AddAllToNullKeyset( void )
{	for( int i = UP; i <= BACK; i++ )
	{	AddToNullKeyset( DirectionsToFlags( i ) );
	}
}

/*
* Reset Null Keyset
*/
void CSwoop::ResetNullKeyset( void )
{	m_iNullKeyset = 0;
}

/*
* Is this direction held down?
*/
BOOL CSwoop::IsHolding( int direction )
{	switch(direction)
	{
	case UP:
		{	return ((m_iNullKeyset & IN_JUMP) ? FALSE : (m_pOwner->pev->button & IN_JUMP));
		}
	case DOWN:
		{	return ((m_iNullKeyset & IN_DUCK) ? FALSE : (m_pOwner->pev->button & IN_DUCK));
		}
	case LEFT:
		{	return ((m_iNullKeyset & IN_LEFT) ? FALSE : ((m_pOwner->pev->button & IN_LEFT) || (m_pOwner->pev->button & IN_MOVELEFT)));
		}
	case RIGHT:
		{	return ((m_iNullKeyset & IN_RIGHT) ? FALSE : ((m_pOwner->pev->button & IN_RIGHT) || (m_pOwner->pev->button & IN_MOVERIGHT)));
		}
	case FRONT:
		{	return ((m_iNullKeyset & IN_FORWARD) ? FALSE : (m_pOwner->pev->button & IN_FORWARD));
		}
	case BACK:
		{	return ((m_iNullKeyset & IN_BACK) ? FALSE : (m_pOwner->pev->button & IN_BACK));
		}
	}

	return FALSE;
}

/*
* Directions To Flags
*/
int CSwoop::DirectionsToFlags( int iDirections )
{	switch(iDirections)
	{
	case UP:
		{	return IN_JUMP;
		}
	case DOWN:
		{	return IN_DUCK;
		}
	case LEFT:
		{	return IN_LEFT;
		}
	case RIGHT:
		{	return IN_RIGHT;
		}
	case FRONT:
		{	return IN_FORWARD;
		}
	case BACK:
		{	return IN_BACK;
		}
	}
	return 0;
}

/*
* Get Swoop time
*/
float CSwoop::GetSwoopTime( void )
{	if(m_flStartSwoop == 0)
	{	return 0;
	}
	else
	{	return (gpGlobals->time - m_flStartSwoop);
	}
}

/*
* Is Paused (paused in mid turbo/dash)
*/
BOOL CSwoop::IsPaused( void )
{	return m_fPause;
}

/*
* Can Pause
*/
BOOL CSwoop::CanPause( void )
{	if(m_fSwoop && m_pOwner->UseKi(ki_cost_pause.value))
	{	return TRUE;
	}
	return FALSE;
}

/*
* Pause Player (perform pause)
*/
void CSwoop::PausePlayer( void )
{	m_fPause = TRUE;
	// Set aura to idle anim
	if(m_pOwner->m_pAura && m_pOwner->m_fTurbo)
	{	EMIT_SOUND(ENT(m_pOwner->pev), CHAN_ITEM, "weapons/aura.wav", 1, ATTN_NORM);
		m_pOwner->m_pAura->pev->sequence = m_pOwner->m_pAura->LookupSequence("idle");
		m_pOwner->m_pAura->pev->frame = 0;
		m_pOwner->m_pAura->ResetSequenceInfo( );
	}
}

/*
* Set Swoop Anim
*/
void CSwoop::SetSwoopAnim( char *pAnim, Activity &IdealActivity )
{	// Seperate animation for pause
	if(m_fPause)
	{	strcpy(pAnim, "fly_powerup_charge");
		IdealActivity = ACT_POWERUPCHARGE;
		return;
	}
	
	if(m_fDash)
		strcpy( pAnim, "dash_" );
	else
		strcpy( pAnim, "swoop_" );

	switch(m_iSwoopDirection)
	{
	case UP:
		{	strcat(pAnim, "up");
			IdealActivity = ACT_SWOOP_UP;
			break;
		}
	case DOWN:
		{	strcat(pAnim, "down");
			IdealActivity = ACT_SWOOP_DOWN;
			break;
		}
	case LEFT:
		{	strcat(pAnim, "left");
			IdealActivity = ACT_SWOOP_LEFT;
			break;
		}
	case RIGHT:
		{	strcat(pAnim, "right");
			IdealActivity = ACT_SWOOP_RIGHT;
			break;
		}
	default:
	case FRONT:
		{	strcat(pAnim, "forward");
			IdealActivity = ACT_SWOOP_FORWARD;
			break;
		}
	case BACK:
		{	strcat(pAnim, "backward");
			IdealActivity = ACT_SWOOP_BACK;
			break;
		}
	}
}

/*
* Get active direction
*/
int CSwoop::GetActiveDirection( void )
{	return m_iPressDirection;
}

/*
* Is Swoopig
*/
BOOL CSwoop::IsSwooping( void )
{	return (m_fSwoop && !m_fDash);
}

/*
* Is Dashing
*/
BOOL CSwoop::IsDashing( void )
{	return (m_fSwoop && m_fDash);
}


#endif