//========= Copyright Valve Corporation, All rights reserved. ============//
//
//
//
//=============================================================================
#include "cbase.h"
#include "tf_player.h"
#include "tf_gamerules.h"
#include "tf_team.h"
#include "nav_mesh/tf_nav_area.h"
#include "NextBot/Path/NextBotChasePath.h"
#include "particle_parse.h"

#include "zombie.h"
#include "zombie_behavior/zombie_spawn.h"

#include "halloween/tf_weapon_spellbook.h"

#define SKELETON_MODEL "models/bots/skeleton_sniper/skeleton_sniper.mdl"
#define SKELETON_KING_MODEL "models/bots/skeleton_sniper_boss/skeleton_sniper_boss.mdl"
#define SKELETON_KING_CROWN_MODEL "models/player/items/demo/crown.mdl"

ConVar tf_max_active_zombie( "tf_max_active_zombie", "30", FCVAR_CHEAT );

#ifdef STAGING_ONLY
ConVar tf_halloween_skeleton_test_hat( "tf_halloween_skeleton_test_hat", "-1", FCVAR_CHEAT );
#endif // STAGING_ONLY

//-----------------------------------------------------------------------------------------------------
// NPC Zombie versions of the players
//-----------------------------------------------------------------------------------------------------
LINK_ENTITY_TO_CLASS( tf_zombie, CZombie );

IMPLEMENT_SERVERCLASS_ST( CZombie, DT_Zombie )
	SendPropFloat( SENDINFO( m_flHeadScale ) ),
END_SEND_TABLE()

IMPLEMENT_AUTO_LIST( IZombieAutoList );


static const char *s_skeletonHatModels[] =
{
	"models/player/items/all_class/skull_scout.mdl",
	"models/workshop/player/items/scout/hw2013_boston_bandy_mask/hw2013_boston_bandy_mask.mdl",
	"models/workshop/player/items/demo/hw2013_blackguards_bicorn/hw2013_blackguards_bicorn.mdl",
	"models/player/items/heavy/heavy_big_chief.mdl",
};


//-----------------------------------------------------------------------------------------------------
CZombie::CZombie()
{
	m_intention = new CZombieIntention( this );
	m_locomotor = new CZombieLocomotion( this );
	m_body = new CHeadlessHatmanBody( this );

	m_nType = SKELETON_NORMAL;

	m_flHeadScale = 1.f;

	m_flAttackRange = 50.f;
	m_flAttackDamage = 30.f;

	m_bSpy = false;
	m_bForceSuicide = false;
}


//-----------------------------------------------------------------------------------------------------
CZombie::~CZombie()
{
	if ( m_intention )
		delete m_intention;

	if ( m_locomotor )
		delete m_locomotor;

	if ( m_body )
		delete m_body;
}


void CZombie::PrecacheZombie()
{
	/*PrecacheModel( "models/player/items/scout/scout_zombie.mdl" );
	PrecacheModel( "models/player/items/sniper/sniper_zombie.mdl" );
	PrecacheModel( "models/player/items/soldier/soldier_zombie.mdl" );
	PrecacheModel( "models/player/items/demo/demo_zombie.mdl" );
	PrecacheModel( "models/player/items/medic/medic_zombie.mdl" );
	PrecacheModel( "models/player/items/heavy/heavy_zombie.mdl" );
	PrecacheModel( "models/player/items/pyro/pyro_zombie.mdl" );
	PrecacheModel( "models/player/items/spy/spy_zombie.mdl" );
	PrecacheModel( "models/player/items/engineer/engineer_zombie.mdl" );*/

	int nSkeletonModel = PrecacheModel( SKELETON_MODEL );
	PrecacheGibsForModel( nSkeletonModel );

	int nSkeletonKingModel = PrecacheModel( SKELETON_KING_MODEL );
	PrecacheGibsForModel( nSkeletonKingModel );

	PrecacheModel( SKELETON_KING_CROWN_MODEL );

	if( TFGameRules()->GetHalloweenScenario() == CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY )
	{
		for ( int i=0; i<ARRAYSIZE( s_skeletonHatModels ) ; ++i )
		{
			PrecacheModel( s_skeletonHatModels[i] );
		}
	}

	PrecacheParticleSystem( "bomibomicon_ring" );
	PrecacheParticleSystem( "spell_pumpkin_mirv_goop_red" );
	PrecacheParticleSystem( "spell_pumpkin_mirv_goop_blue" );
	PrecacheParticleSystem( "spell_skeleton_goop_green" );

	PrecacheScriptSound( "Halloween.skeleton_break" );
	PrecacheScriptSound( "Halloween.skeleton_laugh_small" );
	PrecacheScriptSound( "Halloween.skeleton_laugh_medium" );
	PrecacheScriptSound( "Halloween.skeleton_laugh_giant" );
}


//-----------------------------------------------------------------------------------------------------
void CZombie::Precache()
{
	BaseClass::Precache();

	// These are player models which are already precached...

	bool bAllowPrecache = CBaseEntity::IsPrecacheAllowed();
	CBaseEntity::SetAllowPrecache( true );

	PrecacheZombie();

	CBaseEntity::SetAllowPrecache( bAllowPrecache );
}


//-----------------------------------------------------------------------------------------------------
void CZombie::Spawn( void )
{
	Precache();

	/*int which = RandomInt( TF_CLASS_SCOUT, TF_CLASS_ENGINEER );
	const char *name = g_aRawPlayerClassNamesShort[ which ];

	if ( FStrEq( name, "spy" ) )
	{
		m_bSpy = true;
	}*/

	//SetModel( CFmtStr( "models/player/%s.mdl", name ) );

	SetModel( SKELETON_MODEL );

	BaseClass::Spawn();

	const int health = 50;
	SetHealth( health );
	SetMaxHealth( health );
	AddFlag( FL_NPC );
	
	QAngle qAngle = vec3_angle;
	qAngle[YAW] = RandomFloat( 0, 360 );
	SetAbsAngles( qAngle );

	// Spawn Pos
	GetBodyInterface()->StartActivity( ACT_TRANSITION );

	//int iSkinIndex = GetTeamNumber() == TF_TEAM_RED ? 0 : 1;

	//m_zombieParts = (CBaseAnimating *)CreateEntityByName( "prop_dynamic" );
	//if ( m_zombieParts )
	//{
	//	m_zombieParts->SetModel( CFmtStr( "models/player/items/%s/%s_zombie.mdl", name, name ) );
	//	m_zombieParts->m_nSkin = iSkinIndex;

	//	// bonemerge into our model
	//	m_zombieParts->FollowEntity( this, true );
	//}

	//if ( m_bSpy )
	//{
	//	// Spy has a bunch of extra skins used to adjust the mask
	//	iSkinIndex += 22;
	//}
	//else
	//{
	//	// 4: red zombie
	//	// 5: blue zombie
	//	// 6: red zombie invuln
	//	// 7: blue zombie invuln
	//	iSkinIndex += 4;
	//}

	switch ( GetTeamNumber() )
	{
	case TF_TEAM_RED:
		m_nSkin = 0;
		break;
	case TF_TEAM_BLUE:
		m_nSkin = 1;
		break;
	default:
		{
			m_nSkin = 2;
			// make sure I'm on TF_TEAM_HALLOWEEN
			ChangeTeam( TF_TEAM_HALLOWEEN );
		}
	}

	// force kill oldest skeletons in the level (except skeleton king) to keep the number of skeletons under the max active
	int nForceKill = IZombieAutoList::AutoList().Count() - tf_max_active_zombie.GetInt();
	for ( int i=0; i<IZombieAutoList::AutoList().Count() && nForceKill > 0; ++i )
	{
		CZombie *pZombie = static_cast< CZombie* >( IZombieAutoList::AutoList()[i] );
		if ( pZombie->GetSkeletonType() != SKELETON_KING )
		{
			pZombie->ForceSuicide();
			nForceKill--;
		}
	}
	Assert( nForceKill <= 0 );
}


//-----------------------------------------------------------------------------------------------------
int CZombie::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
	if ( info.GetAttacker() && info.GetAttacker()->GetTeamNumber() == GetTeamNumber() )
		return 0;

	if ( !IsPlayingGesture( ACT_MP_GESTURE_FLINCH_CHEST ) )
	{
		AddGesture( ACT_MP_GESTURE_FLINCH_CHEST );
	}

	const char* pszEffectName;
	if ( GetTeamNumber() == TF_TEAM_HALLOWEEN )
	{
		pszEffectName = "spell_skeleton_goop_green";
	}
	else
	{
		pszEffectName = GetTeamNumber() == TF_TEAM_RED ? "spell_pumpkin_mirv_goop_red" : "spell_pumpkin_mirv_goop_blue";
	}

	DispatchParticleEffect( pszEffectName, info.GetDamagePosition(), GetAbsAngles() );

	return BaseClass::OnTakeDamage_Alive( info );
}


//-----------------------------------------------------------------------------------------------------
void CZombie::Event_Killed( const CTakeDamageInfo &info )
{
	EmitSound( "Halloween.skeleton_break" );

	if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_HIGHTOWER ) )
	{
		CTFPlayer *pPlayerAttacker = NULL;
		if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() )
		{
			pPlayerAttacker = ToTFPlayer( info.GetAttacker() );
			if ( pPlayerAttacker )
			{
				pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_HALLOWEEN_HELLTOWER_SKELETON_GRIND );

				IGameEvent *pEvent = gameeventmanager->CreateEvent( "halloween_skeleton_killed" );
				if ( pEvent )
				{
					pEvent->SetInt( "player", pPlayerAttacker->GetUserID() );
					gameeventmanager->FireEvent( pEvent, true );
				}
			}
		}
	}
	
	BaseClass::Event_Killed( info );
}


//-----------------------------------------------------------------------------------------------------
void CZombie::UpdateOnRemove()
{
	CPVSFilter filter( GetAbsOrigin() );
	UserMessageBegin( filter, "BreakModel" );
		WRITE_SHORT( GetModelIndex() );
		WRITE_VEC3COORD( GetAbsOrigin() );
		WRITE_ANGLES( GetAbsAngles() );
		WRITE_SHORT( m_nSkin );
	MessageEnd();

	UTIL_Remove( m_hHat );

	BaseClass::UpdateOnRemove();
}


//---------------------------------------------------------------------------------------------
/*static*/ CZombie* CZombie::SpawnAtPos( const Vector& vSpawnPos, float flLifeTime /*= 0.f*/, int nTeam /*= TF_TEAM_HALLOWEEN*/, CBaseEntity *pOwner /*= NULL*/, SkeletonType_t nSkeletonType /*= SKELETON_NORMAL*/ )
{
	CZombie *pZombie = (CZombie *)CreateEntityByName( "tf_zombie" );
	if ( pZombie )
	{
		pZombie->ChangeTeam( nTeam );

		DispatchSpawn( pZombie );

		pZombie->SetAbsOrigin( vSpawnPos );
		pZombie->SetOwnerEntity( pOwner );

		if ( flLifeTime > 0.f )
		{
			pZombie->StartLifeTimer( flLifeTime );
		}

		pZombie->SetSkeletonType( nSkeletonType );
	}

	return pZombie;
}


bool CZombie::ShouldSuicide() const
{
	// out of life time
	if ( m_lifeTimer.HasStarted() && m_lifeTimer.IsElapsed() )
		return true;

	// owner changed team
	if ( GetOwnerEntity() && GetOwnerEntity()->GetTeamNumber() != GetTeamNumber() )
		return true;

	return m_bForceSuicide;
}


//-----------------------------------------------------------------------------------------------------
void CZombie::SetSkeletonType( SkeletonType_t nType )
{
	m_nType = nType;
	// Skeleton King?
	if ( nType == SKELETON_KING )
	{
		SetModel( SKELETON_KING_MODEL );
		SetModelScale( 2.f );

		const int health = 1000;
		SetHealth( health );
		SetMaxHealth( health );

		m_flAttackRange = 100.f;
		m_flAttackDamage = 100.f;

		AddHat( SKELETON_KING_CROWN_MODEL );
	}
	else if ( nType == SKELETON_MINI )
	{
		SetModel( SKELETON_MODEL );
		SetModelScale( 0.5f );
		m_flHeadScale = 3.f;

		if( TFGameRules()->GetHalloweenScenario() == CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY )
		{
			int iModelIndex = RandomInt( 0, ARRAYSIZE( s_skeletonHatModels ) - 1 );
#ifdef STAGING_ONLY
			iModelIndex = tf_halloween_skeleton_test_hat.GetInt() > 0 ? tf_halloween_skeleton_test_hat.GetInt() : iModelIndex;
#endif // STAGING_ONLY
			const char *pszHat = s_skeletonHatModels[ iModelIndex ];
			AddHat( pszHat );
		}

		m_flAttackRange = 40.f;
		m_flAttackDamage = 20.f;
	}
}


//-----------------------------------------------------------------------------------------------------
void CZombie::AddHat( const char *pszModel )
{
	if ( !m_hHat )
	{
		int iHead = LookupBone( "bip_head" );
		Assert( iHead != -1 );
		if ( iHead != -1 )
		{
			m_hHat = (CBaseAnimating *)CreateEntityByName( "prop_dynamic" );
			if ( m_hHat )
			{
				m_hHat->SetModel( pszModel );

				Vector pos;
				QAngle angles;
				GetBonePosition( iHead, pos, angles );
				m_hHat->SetAbsOrigin( pos );
				m_hHat->SetAbsAngles( angles );
				m_hHat->FollowEntity( this, true );
			}
		}
	}
}


//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
class CZombieBehavior : public Action< CZombie >
{
public:
	virtual Action< CZombie > *InitialContainedAction( CZombie *me )	
	{
		return new CZombieSpawn;
	}

	virtual ActionResult< CZombie >	OnStart( CZombie *me, Action< CZombie > *priorAction )
	{
		return Continue();
	}

	virtual ActionResult< CZombie >	Update( CZombie *me, float interval )
	{
		if ( !me->IsAlive() || me->ShouldSuicide() )
		{
			UTIL_Remove( me );
			return Done();
		}

		if ( ShouldLaugh( me ) )
		{
			Laugh( me );
		}

		return Continue();
	}

	virtual EventDesiredResult< CZombie > OnKilled( CZombie *me, const CTakeDamageInfo &info )
	{
		// bonemerged models don't ragdoll
		//UTIL_Remove( me->m_zombieParts );

		if ( info.GetAttacker() && dynamic_cast< CBaseCombatCharacter* >( info.GetAttacker() ) )
		{
			if ( me->GetSkeletonType() == CZombie::SKELETON_NORMAL )
			{
				// normal skeleton spawns 3 mini skeletons
				CBaseCombatCharacter* pOwner = dynamic_cast< CBaseCombatCharacter* >( me->GetOwnerEntity() );
				pOwner = pOwner ? pOwner : me;
				for ( int i=0; i<3; ++i )
				{
					CreateSpellSpawnZombie( pOwner, me->GetAbsOrigin(), 2 );
				}
			}
			else if ( me->GetSkeletonType() == CZombie::SKELETON_KING )
			{
				// skeleton king drops rare spell
				TFGameRules()->DropSpellPickup( me->GetAbsOrigin(), 1 );
			}
		}

		UTIL_Remove( me );

		return TryDone();
	}

	virtual const char *GetName( void ) const	{ return "ZombieBehavior"; }		// return name of this action

private:

	bool ShouldLaugh( CZombie *me )
	{
		if ( !m_laughTimer.HasStarted() )
		{
			switch ( me->GetSkeletonType() )
			{
			case CZombie::SKELETON_KING:
				{
					m_laughTimer.Start( RandomFloat( 6.f, 7.f ) );
					break;
				}
			case CZombie::SKELETON_MINI:
				{
					m_laughTimer.Start( RandomFloat( 2.f, 3.f ) );
					break;
				}
			default:
				{
					m_laughTimer.Start( RandomFloat( 4.f, 5.f ) );
				}
			}

			return false;
		}

		if ( m_laughTimer.HasStarted() && m_laughTimer.IsElapsed() )
		{
			m_laughTimer.Invalidate();
			return true;
		}

		return false;
	}

	void Laugh( CZombie *me )
	{
		const char *pszSoundName;
		switch ( me->GetSkeletonType() )
		{
		case CZombie::SKELETON_KING:
			{
				pszSoundName = "Halloween.skeleton_laugh_giant";
				break;
			}
		case CZombie::SKELETON_MINI:
			{
				pszSoundName = "Halloween.skeleton_laugh_small";
				break;
			}
		default:
			{
				pszSoundName = "Halloween.skeleton_laugh_medium";
			}
		}

		me->EmitSound( pszSoundName );
	}

	CountdownTimer m_laughTimer;
};


//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
CZombieIntention::CZombieIntention( CZombie *me ) : IIntention( me )
{ 
	m_behavior = new Behavior< CZombie >( new CZombieBehavior ); 
}

CZombieIntention::~CZombieIntention()
{
	delete m_behavior;
}

void CZombieIntention::Reset( void )
{ 
	delete m_behavior; 
	m_behavior = new Behavior< CZombie >( new CZombieBehavior );
}

void CZombieIntention::Update( void )
{
	m_behavior->Update( static_cast< CZombie * >( GetBot() ), GetUpdateInterval() ); 
}

// is this a place we can be?
QueryResultType CZombieIntention::IsPositionAllowed( const INextBot *meBot, const Vector &pos ) const
{
	return ANSWER_YES;
}



//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
float CZombieLocomotion::GetRunSpeed( void ) const
{
	return 300.f;
}


//---------------------------------------------------------------------------------------------
// if delta Z is greater than this, we have to jump to get up
float CZombieLocomotion::GetStepHeight( void ) const
{
	return 18.0f;
}


//---------------------------------------------------------------------------------------------
// return maximum height of a jump
float CZombieLocomotion::GetMaxJumpHeight( void ) const
{
	return 18.0f;
}


//---------------------------------------------------------------------------------------------
// Return max rate of yaw rotation
float CZombieLocomotion::GetMaxYawRate( void ) const
{
	return 200.0f;
}


//---------------------------------------------------------------------------------------------
bool CZombieLocomotion::ShouldCollideWith( const CBaseEntity *object ) const
{
	return false;
}
