source-engine/game/server/tf2base/tf_weapon_builder.cpp
2022-08-10 19:52:28 +03:00

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;
}