//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:			The "weapon" used to build objects
//					
//
// $Workfile:     $
// $Date:         $
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "tf_player.h"
#include "entitylist.h"
#include "in_buttons.h"
#include "SoundEmitterSystem/isoundemittersystembase.h"
#include "engine/IEngineSound.h"
#include "tf_obj.h"
#include "sendproxy.h"
#include "tf_weapon_builder.h"
#include "vguiscreen.h"
#include "tf_gamerules.h"
#include "tf_obj_teleporter.h"
#include "tf_obj_sapper.h"

extern ISoundEmitterSystemBase *soundemitterbase;

extern ConVar tf2_object_hard_limits;
extern ConVar tf_fastbuild;


EXTERN_SEND_TABLE(DT_BaseCombatWeapon)

BEGIN_NETWORK_TABLE_NOBASE( CTFWeaponBuilder, DT_BuilderLocalData )
	SendPropInt( SENDINFO( m_iObjectType ), BUILDER_OBJECT_BITS, SPROP_UNSIGNED ),
	SendPropEHandle( SENDINFO( m_hObjectBeingBuilt ) ),
	SendPropArray3( SENDINFO_ARRAY3( m_aBuildableObjectTypes ), SendPropBool( SENDINFO_ARRAY( m_aBuildableObjectTypes ) ) ),
END_NETWORK_TABLE()

IMPLEMENT_SERVERCLASS_ST(CTFWeaponBuilder, DT_TFWeaponBuilder)
	SendPropInt( SENDINFO( m_iBuildState ), 4, SPROP_UNSIGNED ),
	SendPropDataTable( "BuilderLocalData", 0, &REFERENCE_SEND_TABLE( DT_BuilderLocalData ), SendProxy_SendLocalWeaponDataTable ),
	SendPropInt( SENDINFO( m_iObjectMode ) , 4, SPROP_UNSIGNED ),
	SendPropFloat( SENDINFO( m_flWheatleyTalkingUntil) ),
END_SEND_TABLE()

LINK_ENTITY_TO_CLASS( tf_weapon_builder, CTFWeaponBuilder );
PRECACHE_WEAPON_REGISTER( tf_weapon_builder );

//

IMPLEMENT_SERVERCLASS_ST( CTFWeaponSapper, DT_TFWeaponSapper )
	SendPropFloat( SENDINFO( m_flChargeBeginTime ) ),
END_SEND_TABLE()

LINK_ENTITY_TO_CLASS( tf_weapon_sapper, CTFWeaponSapper );
PRECACHE_WEAPON_REGISTER( tf_weapon_sapper );


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CTFWeaponBuilder::CTFWeaponBuilder()
{
	m_iObjectType.Set( BUILDER_INVALID_OBJECT );
	m_iObjectMode = 0;
	m_bAttack3Down = false;

	//Sapper VO Pack stuff
	WheatleyReset( true );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CTFWeaponBuilder::~CTFWeaponBuilder()
{
	StopPlacement();
	if (m_pkvWavList)
	{
		m_pkvWavList->deleteThis();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFWeaponBuilder::SetSubType( int iSubType )
{
	m_iObjectType = iSubType;
	
	// m_iViewModelIndex is set by the base Precache(), which didn't know what
	// type of object we built, so it didn't get the right viewmodel index.
	// Now that our data is filled in, go and get the right index.
	const char *pszViewModel = GetViewModel(0);
	if ( pszViewModel && pszViewModel[0] )
	{
		m_iViewModelIndex = CBaseEntity::PrecacheModel( pszViewModel );
	}

	if ( m_iObjectType == OBJ_ATTACHMENT_SAPPER )
	{
		if ( IsWheatleySapper() )
		{
			if (m_pkvWavList)
			{
				m_pkvWavList->deleteThis();
			}
			m_pkvWavList = new KeyValues("sappervo");
		}
	}

	BaseClass::SetSubType( iSubType );
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFWeaponBuilder::Precache( void )
{
	BaseClass::Precache();

	// Precache all the viewmodels we could possibly be building
	for ( int iObj=0; iObj < OBJ_LAST; iObj++ )
	{
		const CObjectInfo *pInfo = GetObjectInfo( iObj );

		if ( pInfo )
		{
			if ( pInfo->m_pViewModel )
			{
				PrecacheModel( pInfo->m_pViewModel );
			}

			if ( pInfo->m_pPlayerModel )
			{
				PrecacheModel( pInfo->m_pPlayerModel );
			}
		}
	}

}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTFWeaponBuilder::CanDeploy( void )
{
	CTFPlayer *pPlayer = ToTFPlayer( GetOwner() );
	if (!pPlayer)
		return false;

	if ( pPlayer->m_Shared.IsCarryingObject() )
		return BaseClass::CanDeploy();

	if ( pPlayer->CanBuild( m_iObjectType, m_iObjectMode ) != CB_CAN_BUILD )
	{
		return false;
	}

	return BaseClass::CanDeploy();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTFWeaponBuilder::Deploy( void )
{
	bool bDeploy = BaseClass::Deploy();

	if ( bDeploy )
	{
		SetCurrentState( BS_PLACING );
		StartPlacement(); 
		m_flNextPrimaryAttack = gpGlobals->curtime + 0.35f;
		m_flNextSecondaryAttack = gpGlobals->curtime;		// asap

		CTFPlayer *pPlayer = ToTFPlayer( GetOwner() );
		if (!pPlayer)
			return false;

		pPlayer->SetNextAttack( gpGlobals->curtime );

		m_iWorldModelIndex = modelinfo->GetModelIndex( GetWorldModel() );

		m_flNextDenySound = 0;

		// Set off the hint here, because we don't know until now if our building
		// is rotate-able or not.
		if ( m_hObjectBeingBuilt && !m_hObjectBeingBuilt->MustBeBuiltOnAttachmentPoint() )
		{
			// set the alt-fire hint so it gets removed when we holster
			m_iAltFireHint = HINT_ALTFIRE_ROTATE_BUILDING;
			pPlayer->StartHintTimer( m_iAltFireHint );
		}

		pPlayer->PlayWearableAnimsForPlaybackEvent( WAP_START_BUILDING );
	}

	return bDeploy;
}

Activity CTFWeaponBuilder::GetDrawActivity( void )
{
	// sapper used to call different draw animations , one when invis and one when not.
	// now you can go invis *while* deploying, so let's always use the one-handed deploy.
	if ( GetType() == OBJ_ATTACHMENT_SAPPER )
	{
		return ACT_VM_DRAW_DEPLOYED;
	}

	return BaseClass::GetDrawActivity();
}

//-----------------------------------------------------------------------------
// Purpose: Stop placement when holstering
//-----------------------------------------------------------------------------
bool CTFWeaponBuilder::Holster( CBaseCombatWeapon *pSwitchingTo )
{
	CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
	if ( !pOwner )
		return false; 

	if ( pOwner->m_Shared.IsCarryingObject() )
		return false;

	if ( m_iObjectType == OBJ_ATTACHMENT_SAPPER )
	{
		if( IsWheatleySapper() )
		{
			pOwner->ClearSappingTracking();
			if ( pOwner->m_Shared.GetState() == TF_STATE_DYING)
			{
				if ( RandomInt( 0, 4) == 0 )
				{
					WheatleyEmitSound( "PSap.DeathLong", true );
				}
				else
				{
					WheatleyEmitSound( "PSap.Death", true );
				}
			}
			else
			{
				float flSoundDuration;
				if ( gpGlobals->curtime - m_flWheatleyLastDeploy < 1.5 && gpGlobals->curtime - m_flWheatleyLastDeploy > -1.0 )
				{
					flSoundDuration = WheatleyEmitSound( "PSap.HolsterFast");
				}
				else
				{
					flSoundDuration = WheatleyEmitSound( "PSap.Holster");
				}
				m_flWheatleyLastHolster = gpGlobals->curtime + flSoundDuration;
			}
		}
	}

	m_flNextVoicePakIdleStartTime = -1.0f;

	if ( m_iBuildState == BS_PLACING || m_iBuildState == BS_PLACING_INVALID )
	{
		SetCurrentState( BS_IDLE );
	}
	StopPlacement();

	pOwner->PlayWearableAnimsForPlaybackEvent( WAP_STOP_BUILDING );

	return BaseClass::Holster(pSwitchingTo);
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFWeaponBuilder::ItemPostFrame( void )
{
	CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
	if ( !pOwner )
		return;

	// If we're building, and our team has lost, stop placing the object
	if ( m_hObjectBeingBuilt.Get() && 
		TFGameRules()->State_Get() == GR_STATE_TEAM_WIN && 
		pOwner->GetTeamNumber() != TFGameRules()->GetWinningTeam() )
	{
		StopPlacement();
		return;
	}

	// Check that I still have enough resources to build this item
	if ( pOwner->CanBuild( m_iObjectType, m_iObjectMode ) != CB_CAN_BUILD )
	{
		SwitchOwnersWeaponToLast();
	}

	if ( ( pOwner->m_nButtons & IN_ATTACK ) && ( m_flNextPrimaryAttack <= gpGlobals->curtime ) )
	{
		PrimaryAttack();
	}

	if ( pOwner->m_nButtons & IN_ATTACK2 )
	{
		if ( m_flNextSecondaryAttack <= gpGlobals->curtime )
		{
			SecondaryAttack();
		}
	}
	else
	{
		m_bInAttack2 = false;
	}

	// Attrib
	int iMarkForDeathOnPickup = 0;
	if ( pOwner->m_Shared.IsCarryingObject () )
	{
		CALL_ATTRIB_HOOK_INT_ON_OTHER( pOwner, iMarkForDeathOnPickup, mark_for_death_on_building_pickup );
		if ( iMarkForDeathOnPickup )
		{
			pOwner->m_Shared.AddCond( TF_COND_MARKEDFORDEATH_SILENT, 3.f );
		}
	}

	WeaponIdle();
}

//-----------------------------------------------------------------------------
// Purpose: Start placing or building the currently selected object
//-----------------------------------------------------------------------------
void CTFWeaponBuilder::PrimaryAttack( void )
{
	CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
	if ( !pOwner )
		return;

	if ( !CanAttack() )
		return;

	// Necessary so that we get the latest building position for the test, otherwise
	// we are one frame behind.
	UpdatePlacementState();

	// What state should we move to?
	switch( m_iBuildState )
	{
	case BS_IDLE:
		{
			// Idle state starts selection
			SetCurrentState( BS_SELECTING );
		}
		break;

	case BS_SELECTING:
		{
			// Do nothing, client handles selection
			return;
		}
		break;

	case BS_PLACING:
		{
			if ( m_hObjectBeingBuilt )
			{
				int iFlags = m_hObjectBeingBuilt->GetObjectFlags();

				// Tricky, because this can re-calc the object position and change whether its a valid 
				// pos or not. Best not to do this only in debug, but we can be pretty sure that this
				// will give the same result as was calculated in UpdatePlacementState() above.
				Assert( IsValidPlacement() );

				// If we're placing an attachment, like a sapper, play a placement animation on the owner
				if ( m_hObjectBeingBuilt->MustBeBuiltOnAttachmentPoint() )
				{
					pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_GRENADE );
				}

				CBaseEntity *pBuiltOnObject = m_hObjectBeingBuilt->GetBuiltOnObject();
				
				if ( pBuiltOnObject && m_iObjectType == OBJ_ATTACHMENT_SAPPER )
				{
					m_vLastKnownSapPos = pBuiltOnObject->GetAbsOrigin();
					m_hLastSappedBuilding = pBuiltOnObject;
				}
				StartBuilding();

				if ( m_iObjectType == OBJ_ATTACHMENT_SAPPER )
				{
					// tell players a sapper was just placed (so bots can react)
					CUtlVector< CTFPlayer * > playerVector;
					CollectPlayers( &playerVector, TEAM_ANY, COLLECT_ONLY_LIVING_PLAYERS );

					for( int i=0; i<playerVector.Count(); ++i )
						playerVector[i]->OnSapperPlaced( pBuiltOnObject );

					// if we just placed a sapper on a teleporter...try to sap the match, too?
					if ( pBuiltOnObject )
					{
						CObjectTeleporter *pTeleporter = dynamic_cast<CObjectTeleporter*>( pBuiltOnObject );
						if ( pTeleporter && pTeleporter->GetMatchingTeleporter() && !pTeleporter->GetMatchingTeleporter()->HasSapper() )
						{
							// Start placing another
							SetCurrentState( BS_PLACING );
							StartPlacement(); 
	
							if ( m_hObjectBeingBuilt.Get() )
							{
								m_hObjectBeingBuilt->UpdateAttachmentPlacement( pTeleporter->GetMatchingTeleporter() );
								StartBuilding();
							}
						}
					}
				}

				// Should we switch away?
				if ( iFlags & OF_ALLOW_REPEAT_PLACEMENT )
				{
					// Start placing another
					SetCurrentState( BS_PLACING );
					StartPlacement(); 
				}
				else
				{
					SwitchOwnersWeaponToLast();
				}
			}
		}
		break;

	case BS_PLACING_INVALID:
		{
			if ( m_flNextDenySound < gpGlobals->curtime )
			{
				CSingleUserRecipientFilter filter( pOwner );
				EmitSound( filter, entindex(), "Player.DenyWeaponSelection" );

				m_flNextDenySound = gpGlobals->curtime + 0.5;
			}
		}
		break;
	}

	m_flNextPrimaryAttack = gpGlobals->curtime + 0.2f;
}

void CTFWeaponBuilder::SecondaryAttack( void )
{
	if ( m_bInAttack2 )
		return;

	// require a re-press
	m_bInAttack2 = true;

	CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
	if ( !pOwner )
		return;

	UpdatePlacementState();

	if ( !pOwner->IsPlayerClass( TF_CLASS_ENGINEER ) && pOwner->DoClassSpecialSkill() )
	{
		// Spies do the special skill first.
	}
	else if ( m_iBuildState == BS_PLACING || m_iBuildState == BS_PLACING_INVALID )
	{
		if ( m_hObjectBeingBuilt )
		{
			pOwner->StopHintTimer( HINT_ALTFIRE_ROTATE_BUILDING );
			m_hObjectBeingBuilt->RotateBuildAngles();
		}
	}
	else if ( pOwner->DoClassSpecialSkill() )
	{
		// Engineers do the special skill last.
	}

	m_flNextSecondaryAttack = gpGlobals->curtime + 0.2f;
}

//-----------------------------------------------------------------------------
// Purpose: Set the builder to the specified state
//-----------------------------------------------------------------------------
void CTFWeaponBuilder::SetCurrentState( int iState )
{
	m_iBuildState = iState;
}

//-----------------------------------------------------------------------------
// Purpose: Set the owner's weapon and last weapon appropriately when we need to
//			switch away from the builder weapon.  
//-----------------------------------------------------------------------------
void CTFWeaponBuilder::SwitchOwnersWeaponToLast()
{
	CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
	if ( !pOwner )
		return;

	// for engineer, switch to wrench and set last weapon appropriately
	if ( pOwner->IsPlayerClass( TF_CLASS_ENGINEER ) )
	{
		// Switch to wrench if possible. if not, then best weapon
		CBaseCombatWeapon *pWpn = pOwner->Weapon_GetSlot( 2 );

		// Don't store last weapon when we autoswitch off builder
		CBaseCombatWeapon *pLastWpn = pOwner->GetLastWeapon();

		if ( pWpn )
		{
			pOwner->Weapon_Switch( pWpn );
		}
		else
		{
			pOwner->SwitchToNextBestWeapon( NULL );
		}

		if ( pWpn == pLastWpn )
		{
			// We had the wrench out before we started building. Go ahead and set out last
			// weapon to our primary weapon.
			pWpn = pOwner->Weapon_GetSlot( 0 );
			pOwner->Weapon_SetLast( pWpn );
		}
		else
		{
			pOwner->Weapon_SetLast( pLastWpn );
		}
	}
	else
	{
		// for all other classes, just switch to last weapon used
		pOwner->Weapon_Switch( pOwner->GetLastWeapon() );
	}
}

//-----------------------------------------------------------------------------
// Purpose: updates the building postion and checks the new postion
//-----------------------------------------------------------------------------
void CTFWeaponBuilder::UpdatePlacementState( void )
{
	// This updates the building position
	bool bValidPos = IsValidPlacement();

	// If we're in placement mode, update the placement model
	switch( m_iBuildState )
	{
	case BS_PLACING:
	case BS_PLACING_INVALID:
		{
			if ( bValidPos )
			{
				SetCurrentState( BS_PLACING );
			}
			else
			{
				SetCurrentState( BS_PLACING_INVALID );
			}
		}
		break;

	default:
		break;
	}
}

//-----------------------------------------------------------------------------
// Purpose: Idle updates the position of the build placement model
//-----------------------------------------------------------------------------
/*#define SAPPER_VOPAK_DEFAULT_WAIT 0.0f*/
void CTFWeaponBuilder::WeaponIdle( void )
{
	CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
	if ( !pOwner )
		return;

	WheatleySapperIdle( pOwner );

	if ( HasWeaponIdleTimeElapsed() )
	{
		SendWeaponAnim( ACT_VM_IDLE );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Special Item Idle
//-----------------------------------------------------------------------------
bool CTFWeaponBuilder::IsWheatleySapper( void )
{
	float flVoicePak = 0.0;
	CALL_ATTRIB_HOOK_FLOAT( flVoicePak, sapper_voice_pak );
	return (flVoicePak == 1.0);
}

//-----------------------------------------------------------------------------
// Purpose: Special Item Reset
//-----------------------------------------------------------------------------
void CTFWeaponBuilder::WheatleyReset( bool bResetIntro )
{
	if ( IsWheatleySapper() )
	{
		WheatleyEmitSound( "PSap.null" );
	}
	if ( bResetIntro )
	{
		m_bWheatleyIntroPlayed = false;
	}
	m_flNextVoicePakIdleStartTime = -1.0f;
	SetWheatleyState( TF_PSAPSTATE_IDLE );
	m_flWheatleyTalkingUntil = 0.00;
	m_flWheatleyLastDamage = 0.00;
	m_iWheatleyVOSequenceOffset = 0;
	m_flWheatleyLastDeploy = 0.00;
	m_flWheatleyLastHolster = 0.00;
}

bool CTFWeaponBuilder::IsWheatleyTalking( void )
{
	return gpGlobals->curtime <= m_flWheatleyTalkingUntil;
}

float CTFWeaponBuilder::WheatleyEmitSound( const char *snd, bool bEmitToAll /*= false*/, bool bNoRepeats /*= false */ )
{
	CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
	CSoundParameters params;
	if ( !soundemitterbase->GetParametersForSound( snd, params, GENDER_NONE ) )
	{
		return 0.00;
	}
	//Should we check to see if it's already been played?
	if ( bNoRepeats && m_pkvWavList )
	{
		if ( m_pkvWavList->GetInt( params.soundname , 0 ) )
		{
			return 0;
		}
		else
		{
			m_pkvWavList->SetInt( params.soundname , 1);
		}
	}
	//Look for special cases that require us to pick the next lines from a sequential list
	if ( Q_strcmp( params.soundname, "vo/items/wheatley_sapper/wheatley_sapper_idle38.mp3") == NULL )
	{
		SetWheatleyState( TF_PSAPSTATE_SPECIALIDLE_HARMLESS );
		m_iWheatleyVOSequenceOffset = 0;
	}
	else if ( Q_strcmp( params.soundname, "vo/items/wheatley_sapper/wheatley_sapper_idle41.mp3") == NULL )
	{
		SetWheatleyState( TF_PSAPSTATE_SPECIALIDLE_HACK );
		m_iWheatleyVOSequenceOffset = 0;
	}
	else if ( Q_strcmp( params.soundname, "vo/items/wheatley_sapper/wheatley_sapper_idle35.mp3") == NULL )
	{
		SetWheatleyState( TF_PSAPSTATE_SPECIALIDLE_KNIFE );
		m_iWheatleyVOSequenceOffset = 0;
	}
	
	//Play the sound
	// When playing a sound to all players, do it at the last known sapper location
	// This is not played on the object itself (building or sapper) cause it may not exist in the case of death and follow up audio
	// Also having it played on this entity itself prevents multiple VO playing in the case of mass sapping
	if ( bEmitToAll )
	{
		//int entIndex = 0;
		CBroadcastNonOwnerRecipientFilter filter( pOwner );
		EmitSound( filter, entindex(), snd, &m_vLastKnownSapPos );
	}

	// GetSoundDuration is not supported on Linux or for MP3s.  So lets just put in a good number
	//float flSoundDuration = enginesound->GetSoundDuration( params.soundname );
	float flSoundDuration = 3.0f;
	CSingleUserRecipientFilter filter( pOwner );
	EmitSound( filter, entindex(), params );
	m_flWheatleyTalkingUntil = gpGlobals->curtime + flSoundDuration;
	return flSoundDuration;
}

//-----------------------------------------------------------------------------
// Purpose: Set Wheatley Sapper State
//-----------------------------------------------------------------------------
void CTFWeaponBuilder::SetWheatleyState( int iNewState )
{
	m_iSapState = iNewState;
}

int CTFWeaponBuilder::GetWheatleyIdleWait()
{
	return RandomInt( WHEATLEY_IDLE_WAIT_SECS_MIN, WHEATLEY_IDLE_WAIT_SECS_MAX );
}


//-----------------------------------------------------------------------------
// Purpose: Set Wheatley Sapper State
//-----------------------------------------------------------------------------
void CTFWeaponBuilder::WheatleyDamage( void )
{
	if ( (gpGlobals->curtime - m_flWheatleyLastDamage) > 10.0)
	{
		if ( RandomInt(0,2) == 0 )
		{
			CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
			if (pOwner)
			{
				pOwner->ClearSappingEvent();
			}
			SetWheatleyState( TF_PSAPSTATE_IDLE );
			m_flWheatleyLastDamage = gpGlobals->curtime;
			WheatleyEmitSound( "PSap.Damage" );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Special Item Idle
//-----------------------------------------------------------------------------
void CTFWeaponBuilder::WheatleySapperIdle( CTFPlayer *pOwner )
{
	if ( pOwner && m_iObjectType == OBJ_ATTACHMENT_SAPPER && IsWheatleySapper())
	{
		//Is Wheatley coming out of the player's pocket?
		if( m_flNextVoicePakIdleStartTime < 0.0f )
		{			
			pOwner->ClearSappingTracking();
			float flSoundDuration;
			if ( gpGlobals->curtime - m_flWheatleyLastHolster < 2.0 && gpGlobals->curtime - m_flWheatleyLastHolster >= -1.00 )
			{
				flSoundDuration = WheatleyEmitSound( "Psap.DeployAgain" );
			}
			else
			{
				flSoundDuration = WheatleyEmitSound( (m_bWheatleyIntroPlayed) ? "Psap.Deploy" : "Psap.DeployIntro" );
			}
			m_flWheatleyLastDeploy = gpGlobals->curtime + flSoundDuration;
			if ( (!m_bWheatleyIntroPlayed) && (RandomInt(0,2) == 0) )
			{
				SetWheatleyState( TF_PSAPSTATE_INTRO );
				m_bWheatleyIntroPlayed = true;
				m_iWheatleyVOSequenceOffset = 0;
				m_flNextVoicePakIdleStartTime = gpGlobals->curtime + flSoundDuration + 3.0;
			}
			else
			{
				m_bWheatleyIntroPlayed = true;
				SetWheatleyState( TF_PSAPSTATE_IDLE );
				m_flNextVoicePakIdleStartTime = gpGlobals->curtime + flSoundDuration + GetWheatleyIdleWait();
			}
		}
		//Is there a sapper event? (sapper placed / sapper finished)
		else if( pOwner->GetSappingEvent() != TF_SAPEVENT_NONE)
		{
			char *pVoicePakString = NULL;
			switch ( pOwner->GetSappingEvent() )
			{
			case TF_SAPEVENT_PLACED:
				if (RandomInt(0,1) == 0)
				{
					if ( RandomInt(0,3) == 0 )
					{
						pVoicePakString = "PSap.AttachedPW";
						SetWheatleyState( TF_PSAPSTATE_WAITINGHACK );
					}
					else 
					{
						pVoicePakString = "PSap.Attached";
						SetWheatleyState( TF_PSAPSTATE_WAITINGHACKPW );
					}
					m_flNextVoicePakIdleStartTime = gpGlobals->curtime + 0.2;
				}
				else
				{
					pVoicePakString = "PSap.Hacking";
					SetWheatleyState( TF_PSAPSTATE_IDLE );
					m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
				}
				break;
			case TF_SAPEVENT_DONE:
				if ( IsWheatleyTalking() )
				{
					if ( m_hLastSappedBuilding && m_hLastSappedBuilding.Get() )
					{
						//Building Alive, Sapper died
						pVoicePakString = "PSap.Death";
					}
					else
					{
						pVoicePakString = "PSap.HackedLoud";
					}
					
					if ( RandomInt( 0, 3 ) == 0 )
					{
						SetWheatleyState( TF_PSAPSTATE_WAITINGFOLLOWUP);
						m_flNextVoicePakIdleStartTime = gpGlobals->curtime + 1.3;
					}
					else
					{
						m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
					}
				}
				else
				{
					SetWheatleyState( TF_PSAPSTATE_WAITINGHACKED );
					m_flNextVoicePakIdleStartTime = gpGlobals->curtime + 0.5;
				}
				break;
			default:
				break;
			}
			pOwner->ClearSappingEvent();
			if ( pVoicePakString )
			{
				float flSoundDuration = WheatleyEmitSound( pVoicePakString, true );
				m_flNextVoicePakIdleStartTime += flSoundDuration;
			}
		}
		//Are we in the intro sequence?
		else if ( m_iSapState == TF_PSAPSTATE_INTRO && gpGlobals->curtime > m_flNextVoicePakIdleStartTime )
		{
			if ( !IsWheatleyTalking() )
			{
				char szVoicePakString[128];
				szVoicePakString[0] = '\0';
				if ( m_iWheatleyVOSequenceOffset >= 0 && m_iWheatleyVOSequenceOffset <=3 )
				{
					V_sprintf_safe( szVoicePakString, "PSap.IdleIntro0%i", ++m_iWheatleyVOSequenceOffset);
					float flSoundDuration = WheatleyEmitSound( szVoicePakString );
					if ( m_iWheatleyVOSequenceOffset == 4 )
					{
						m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait() + flSoundDuration;
					}
					else
					{
						m_flNextVoicePakIdleStartTime = gpGlobals->curtime + 1.0 + flSoundDuration;
					}
				}
				else
				{
					SetWheatleyState( TF_PSAPSTATE_IDLE );
					m_iWheatleyVOSequenceOffset = 0;
				}
			}
		}
		//Does a generic timed event need to be serviced?
		else if( gpGlobals->curtime > m_flNextVoicePakIdleStartTime )
		{
			bool bNoRepeats = false;
			bool bEmitAll = false;
			char *pVoicePakString = NULL;
			//Sapped! vo
			if ( m_iSapState == TF_PSAPSTATE_WAITINGHACKED )
			{
				bEmitAll = true;
				SetWheatleyState( TF_PSAPSTATE_IDLE );
				if ( IsWheatleyTalking() )
				{
					pVoicePakString = "PSap.HackedLoud";
				}
				else
				{
					pVoicePakString = "PSap.Hacked";
				}
				if ( RandomInt( 0, 3 ) == 0 )
				{
					SetWheatleyState( TF_PSAPSTATE_WAITINGFOLLOWUP);
					m_flNextVoicePakIdleStartTime = gpGlobals->curtime + 1.3;
				}
				else
				{
					SetWheatleyState( TF_PSAPSTATE_IDLE );
					m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
				}
			}
			//Waiting to start the password guessing vo
			else if ( m_iSapState == TF_PSAPSTATE_WAITINGHACKPW )
			{
				bEmitAll = true;
				SetWheatleyState( TF_PSAPSTATE_IDLE );
				pVoicePakString = "PSap.HackingPW";
				m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
			}
			//Waiting to start regular hacking vo
			else if ( m_iSapState == TF_PSAPSTATE_WAITINGHACK )
			{
				bEmitAll = true;
				SetWheatleyState( TF_PSAPSTATE_IDLE );
				if ( RandomInt( 0, 2 ) == 0 )
				{
					pVoicePakString = "PSap.HackingShort";
				}
				else
				{
					pVoicePakString = "PSap.Hacking";
				}
				m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
			}
			//Waiting to start successful hack followup vo
			else if ( m_iSapState == TF_PSAPSTATE_WAITINGFOLLOWUP )
			{
				bEmitAll = true;
				SetWheatleyState( TF_PSAPSTATE_IDLE );
				pVoicePakString = "PSap.HackedFollowup";
				m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
			}
			//If Wheatley's talking, skip & check again later
			else if ( IsWheatleyTalking() )
			{
				pVoicePakString = NULL;
				m_flNextVoicePakIdleStartTime = gpGlobals->curtime + 5.0;
			}
			//Are we in the SPECIAL IDLE SEQUENCE "HACK"?
			else if ( m_iSapState == TF_PSAPSTATE_SPECIALIDLE_HACK )
			{
				switch ( m_iWheatleyVOSequenceOffset )
				{
				case 0:
					pVoicePakString = "PSap.IdleHack02";
					m_iWheatleyVOSequenceOffset++;
					break;
				default:
					SetWheatleyState( TF_PSAPSTATE_IDLE );
					m_iWheatleyVOSequenceOffset = 0;
					break;
				}
				m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
			}
			//Are we in the SPECIAL IDLE SEQUENCE "KNIFE"?
			else if ( m_iSapState == TF_PSAPSTATE_SPECIALIDLE_KNIFE )
			{
				switch ( m_iWheatleyVOSequenceOffset )
				{
				case 0:
					pVoicePakString = "PSap.IdleKnife02";
					m_iWheatleyVOSequenceOffset++;
					m_flNextVoicePakIdleStartTime = gpGlobals->curtime + 0.3;
					break;
				case 1:
					pVoicePakString = "PSap.IdleKnife03";
					m_iWheatleyVOSequenceOffset++;
					m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
					break;
				default:
					SetWheatleyState( TF_PSAPSTATE_IDLE );
					m_iWheatleyVOSequenceOffset = 0;
					m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
					break;
				}
			}
			//Are we in the SPECIAL IDLE SEQUENCE "HARMLESS"?
			else if ( m_iSapState == TF_PSAPSTATE_SPECIALIDLE_HARMLESS )
			{
				switch ( m_iWheatleyVOSequenceOffset )
				{
				case 0:
					pVoicePakString = "PSap.IdleHarmless02";
					m_iWheatleyVOSequenceOffset++;
					m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
					break;
				default:
					SetWheatleyState( TF_PSAPSTATE_IDLE );
					m_iWheatleyVOSequenceOffset = 0;
					m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
					break;
				}
			}
			//Is the player stealthed?
			else if ( pOwner->m_Shared.IsStealthed() )
			{
				if ( RandomInt(0,1) == 0 )
				{
					pVoicePakString = "PSap.Sneak";
				}
				m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
			}
			else if ( m_iSapState == TF_PSAPSTATE_IDLE )
			{
				pVoicePakString = "PSap.Idle";
				bNoRepeats = true;
				m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
			}
			else
			{
				pVoicePakString = NULL;
			}
			if (!pVoicePakString)
			{
				m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
				return;
			}
			float flSoundDuration = WheatleyEmitSound( pVoicePakString, bEmitAll, bNoRepeats );
			m_flNextVoicePakIdleStartTime += flSoundDuration;
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: Start placing the object
//-----------------------------------------------------------------------------
void CTFWeaponBuilder::StartPlacement( void )
{
	StopPlacement();

	CTFPlayer *pTFPlayer = ToTFPlayer( GetOwner() );
	if ( !pTFPlayer )
		return;

	if ( pTFPlayer->m_Shared.IsCarryingObject() )
	{
		m_hObjectBeingBuilt = pTFPlayer->m_Shared.GetCarriedObject();
		m_hObjectBeingBuilt->StopFollowingEntity();
	}
	else
	{
		m_hObjectBeingBuilt = (CBaseObject*)CreateEntityByName( GetObjectInfo( m_iObjectType )->m_pClassName );
	}

	if ( m_hObjectBeingBuilt )
	{
		// Set the builder before Spawn() so attributes can hook correctly
		m_hObjectBeingBuilt->SetBuilder( pTFPlayer );

		bool bIsCarried = m_hObjectBeingBuilt->IsCarried();

		// split this off from the block at the bottom because we need to know what type of building
		// this is before we spawn so things like the teleporters have the correct placement models
		// but we need to set the starting construction health after we've called spawn
		if ( !bIsCarried )
		{
			m_hObjectBeingBuilt->SetObjectMode( m_iObjectMode );
		}

		m_hObjectBeingBuilt->Spawn();
		m_hObjectBeingBuilt->StartPlacement( pTFPlayer );

		if ( !bIsCarried )
		{
			m_hObjectBeingBuilt->m_iHealth = OBJECT_CONSTRUCTION_STARTINGHEALTH;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFWeaponBuilder::StopPlacement( void )
{
	if ( m_hObjectBeingBuilt )
	{
		if ( m_hObjectBeingBuilt->IsCarried() )
		{
			m_hObjectBeingBuilt->MakeCarriedObject( ToTFPlayer( GetOwner() ) );
		}
		else
		{
			m_hObjectBeingBuilt->StopPlacement();
		}
		m_hObjectBeingBuilt = NULL;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFWeaponBuilder::WeaponReset( void )
{
	//Check to see if the active weapon is the wheatley sapper, and, if so, reset him
	if ( m_iObjectType == OBJ_ATTACHMENT_SAPPER )
	{
		if ( IsWheatleySapper() )
		{
			CTFPlayer *pPlayer = ToTFPlayer( GetOwner() );
			if ( pPlayer )
			{
				pPlayer->ClearSappingTracking();
			}
			WheatleyReset();
		}
	}

	BaseClass::WeaponReset();

	StopPlacement();
}


//-----------------------------------------------------------------------------
// Purpose: Move the placement model to the current position. Return false if it's an invalid position
//-----------------------------------------------------------------------------
bool CTFWeaponBuilder::IsValidPlacement( void )
{
	if ( !m_hObjectBeingBuilt )
		return false;

	CBaseObject *pObj = m_hObjectBeingBuilt.Get();

	pObj->UpdatePlacement();

	return m_hObjectBeingBuilt->IsValidPlacement();
}

//-----------------------------------------------------------------------------
// Purpose: Player holding this weapon has started building something
// Assumes we are in a valid build position
//-----------------------------------------------------------------------------
void CTFWeaponBuilder::StartBuilding( void )
{
	CBaseObject *pObj = m_hObjectBeingBuilt.Get();

	Assert( pObj );

	pObj->StartBuilding( GetOwner() );

	m_hObjectBeingBuilt = NULL;

	CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
	if ( pOwner )
	{
		pOwner->RemoveInvisibility();
		pOwner->m_Shared.SetCarriedObject( NULL );

		if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() )
		{
			if ( pObj->ObjectType() == OBJ_ATTACHMENT_SAPPER )
			{
				// Let human players place player-targeted sappers in modes that allow upgrades
				if ( !pOwner->IsBot() && pObj->GetBuiltOnObject() && pObj->GetBuiltOnObject()->IsPlayer() )
				{
					int iRoboSapper = 0;
					CALL_ATTRIB_HOOK_INT_ON_OTHER( pOwner, iRoboSapper, robo_sapper );

					int nMode = iRoboSapper ? MODE_SAPPER_ANTI_ROBOT_RADIUS : MODE_SAPPER_ANTI_ROBOT;
					pObj->SetObjectMode( nMode );

					pOwner->RemoveAmmo( 1, TF_AMMO_GRENADES2 );
					StartEffectBarRegen();
				}
			}
#ifdef STAGING_ONLY
			// Traps use TF_AMMO_GRENADES1
			else if ( pObj->GetType() == OBJ_SPY_TRAP )
			{
				pOwner->RemoveAmmo( 1, TF_AMMO_GRENADES1 );
			}
#endif // STAGING_ONLY
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Return true if this weapon has some ammo
//-----------------------------------------------------------------------------
bool CTFWeaponBuilder::HasAmmo( void )
{
	CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
	if ( !pOwner )
		return false;

	int iCost = pOwner->m_Shared.CalculateObjectCost( pOwner, m_iObjectType );
	return ( pOwner->GetBuildResources() >= iCost );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CTFWeaponBuilder::GetSlot( void ) const
{
	return GetObjectInfo( m_iObjectType )->m_SelectionSlot;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CTFWeaponBuilder::GetPosition( void ) const
{
	return GetObjectInfo( m_iObjectType )->m_SelectionPosition;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : char const
//-----------------------------------------------------------------------------
const char *CTFWeaponBuilder::GetPrintName( void ) const
{
	return GetObjectInfo( m_iObjectType )->m_pStatusName;
}

// -----------------------------------------------------------------------------
// Purpose:
// -----------------------------------------------------------------------------
bool CTFWeaponBuilder::CanBuildObjectType( int iObjectType )
{
	if ( iObjectType < 0 || iObjectType >= OBJ_LAST )
		return false;

	return m_aBuildableObjectTypes.Get( iObjectType );
}

// -----------------------------------------------------------------------------
// Purpose:
// -----------------------------------------------------------------------------
void CTFWeaponBuilder::SetObjectTypeAsBuildable( int iObjectType )
{
	if ( iObjectType < 0 || iObjectType >= OBJ_LAST )
		return;

	m_aBuildableObjectTypes.Set( iObjectType, true );
	SetSubType( iObjectType );
}

// -----------------------------------------------------------------------------
// Purpose:
// -----------------------------------------------------------------------------
Activity CTFWeaponBuilder::TranslateViewmodelHandActivity( Activity actBase )
{
	if ( GetObjectInfo( m_iObjectType )->m_bUseItemInfo )
	{
		return BaseClass::TranslateViewmodelHandActivity( actBase );
	}
	else
	{
		return actBase;
	}
}

// -----------------------------------------------------------------------------
// Purpose:
// -----------------------------------------------------------------------------
const char *CTFWeaponBuilder::GetViewModel( int iViewModel ) const
{
	if ( m_iObjectType != BUILDER_INVALID_OBJECT )
	{
		if ( GetObjectInfo( m_iObjectType )->m_bUseItemInfo )
			return BaseClass::GetViewModel();

		return GetObjectInfo( m_iObjectType )->m_pViewModel;
	}

	return BaseClass::GetViewModel();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
const char *CTFWeaponBuilder::GetWorldModel( void ) const	
{
	if ( m_iObjectType != BUILDER_INVALID_OBJECT )
	{
		return GetObjectInfo( m_iObjectType )->m_pPlayerModel;
	}

	return BaseClass::GetWorldModel();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTFWeaponBuilder::AllowsAutoSwitchTo( void ) const
{
	// ask the object we're building
	return GetObjectInfo( m_iObjectType )->m_bAutoSwitchTo;
}

// ****************************************************************************
// SAPPER
// ****************************************************************************
CTFWeaponSapper::CTFWeaponSapper()
{
	m_flChargeBeginTime = 0;
	m_bAttackDown = false;
}
//-----------------------------------------------------------------------------
void CTFWeaponSapper::ItemPostFrame( void )
{
#ifdef STAGING_ONLY
	CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() );
	if ( pPlayer )
	{
		float flSapperDeployTime = 0;
		CALL_ATTRIB_HOOK_FLOAT( flSapperDeployTime, sapper_deploy_time );
		if ( flSapperDeployTime )
		{
			//IsValidPlacement
			if ( !( pPlayer->m_nButtons & IN_ATTACK ) || !IsValidPlacement() )
			{
				m_bAttackDown = false;
				m_flChargeBeginTime = 0;
			}
			else if ( m_bAttackDown == false && ( pPlayer->m_nButtons & IN_ATTACK ) )
			{
				m_bAttackDown = true;
				m_flChargeBeginTime = gpGlobals->curtime;
			}

			if ( ( m_bAttackDown == true && m_flChargeBeginTime + flSapperDeployTime < gpGlobals->curtime ) || !( pPlayer->m_nButtons & IN_ATTACK ) )
			{
				BaseClass::ItemPostFrame();
			}
			return;
		}
	}
#endif // STAGING_ONLY
	BaseClass::ItemPostFrame();
}
//-----------------------------------------------------------------------------
const char *CTFWeaponSapper::GetViewModel( int iViewModel ) const
{
	// Skip over Builder's version
	return CTFWeaponBase::GetViewModel();
}
//-----------------------------------------------------------------------------
const char *CTFWeaponSapper::GetWorldModel( void ) const	
{
	// Skip over Builder's version
	return CTFWeaponBase::GetWorldModel();
}
//-----------------------------------------------------------------------------
Activity CTFWeaponSapper::TranslateViewmodelHandActivity( Activity actBase )
{
	return BaseClass::TranslateViewmodelHandActivity( actBase );

	// Skip over Builder's version
	//return CTFWeaponBase::TranslateViewmodelHandActivity( actBase );
}

