mirror of
https://github.com/nillerusr/source-engine.git
synced 2025-04-08 01:14:33 +00:00
589 lines
16 KiB
C++
589 lines
16 KiB
C++
//========= Copyright © 1996-2005, 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 "tf_obj.h"
|
|
#include "sendproxy.h"
|
|
#include "tf_weapon_builder.h"
|
|
#include "vguiscreen.h"
|
|
#include "tf_gamerules.h"
|
|
|
|
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 ) ),
|
|
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 ),
|
|
END_SEND_TABLE()
|
|
|
|
LINK_ENTITY_TO_CLASS( tf_weapon_builder, CTFWeaponBuilder );
|
|
PRECACHE_WEAPON_REGISTER( tf_weapon_builder );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CTFWeaponBuilder::CTFWeaponBuilder()
|
|
{
|
|
m_iObjectType.Set( BUILDER_INVALID_OBJECT );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CTFWeaponBuilder::~CTFWeaponBuilder()
|
|
{
|
|
StopPlacement();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CTFWeaponBuilder::SetSubType( int iSubType )
|
|
{
|
|
m_iObjectType = iSubType;
|
|
|
|
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->CanBuild( m_iObjectType ) != CB_CAN_BUILD )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return BaseClass::CanDeploy();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CTFWeaponBuilder::Deploy( void )
|
|
{
|
|
m_iViewModelIndex = modelinfo->GetModelIndex( GetViewModel( 0 ) );
|
|
m_iWorldModelIndex = modelinfo->GetModelIndex( GetWorldModel() );
|
|
|
|
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_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 );
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
else
|
|
{
|
|
return BaseClass::GetDrawActivity();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Stop placement when holstering
|
|
//-----------------------------------------------------------------------------
|
|
bool CTFWeaponBuilder::Holster( CBaseCombatWeapon *pSwitchingTo )
|
|
{
|
|
if ( m_iBuildState == BS_PLACING || m_iBuildState == BS_PLACING_INVALID )
|
|
{
|
|
SetCurrentState( BS_IDLE );
|
|
}
|
|
StopPlacement();
|
|
|
|
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 ) != 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;
|
|
}
|
|
|
|
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 );
|
|
}
|
|
|
|
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;
|
|
|
|
if ( pOwner->DoClassSpecialSkill() )
|
|
{
|
|
// intentionally blank
|
|
}
|
|
else if ( m_iBuildState == BS_PLACING )
|
|
{
|
|
if ( m_hObjectBeingBuilt )
|
|
{
|
|
pOwner->StopHintTimer( HINT_ALTFIRE_ROTATE_BUILDING );
|
|
m_hObjectBeingBuilt->RotateBuildAngles();
|
|
}
|
|
}
|
|
|
|
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
|
|
//-----------------------------------------------------------------------------
|
|
void CTFWeaponBuilder::WeaponIdle( void )
|
|
{
|
|
CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
|
|
if ( !pOwner )
|
|
return;
|
|
|
|
if ( HasWeaponIdleTimeElapsed() )
|
|
{
|
|
SendWeaponAnim( ACT_VM_IDLE );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Start placing the object
|
|
//-----------------------------------------------------------------------------
|
|
void CTFWeaponBuilder::StartPlacement( void )
|
|
{
|
|
StopPlacement();
|
|
|
|
// Create the slab
|
|
m_hObjectBeingBuilt = (CBaseObject*)CreateEntityByName( GetObjectInfo( m_iObjectType )->m_pClassName );
|
|
if ( m_hObjectBeingBuilt )
|
|
{
|
|
m_hObjectBeingBuilt->Spawn();
|
|
m_hObjectBeingBuilt->StartPlacement( ToTFPlayer( GetOwner() ) );
|
|
|
|
// Stomp this here in the same frame we make the object, so prevent clientside warnings that it's under attack
|
|
m_hObjectBeingBuilt->m_iHealth = OBJECT_CONSTRUCTION_STARTINGHEALTH;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CTFWeaponBuilder::StopPlacement( void )
|
|
{
|
|
if ( m_hObjectBeingBuilt )
|
|
{
|
|
m_hObjectBeingBuilt->StopPlacement();
|
|
m_hObjectBeingBuilt = NULL;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CTFWeaponBuilder::WeaponReset( void )
|
|
{
|
|
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 *pPlayer = ToTFPlayer( GetOwner() );
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->RemoveInvisibility();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Return true if this weapon has some ammo
|
|
//-----------------------------------------------------------------------------
|
|
bool CTFWeaponBuilder::HasAmmo( void )
|
|
{
|
|
CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
|
|
if ( !pOwner )
|
|
return false;
|
|
|
|
int iCost = CalculateObjectCost( 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:
|
|
// -----------------------------------------------------------------------------
|
|
const char *CTFWeaponBuilder::GetViewModel( int iViewModel ) const
|
|
{
|
|
if ( GetPlayerOwner() == NULL )
|
|
{
|
|
return BaseClass::GetViewModel();
|
|
}
|
|
|
|
if ( m_iObjectType != BUILDER_INVALID_OBJECT )
|
|
{
|
|
return GetObjectInfo( m_iObjectType )->m_pViewModel;
|
|
}
|
|
|
|
return BaseClass::GetViewModel();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
const char *CTFWeaponBuilder::GetWorldModel( void ) const
|
|
{
|
|
if ( GetPlayerOwner() == NULL )
|
|
{
|
|
return BaseClass::GetWorldModel();
|
|
}
|
|
|
|
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;
|
|
}
|