cstrike15_src/game/server/portal2/prop_floor_button.cpp
2025-06-04 03:22:50 +02:00

798 lines
22 KiB
C++

//===== Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose: button that can be pressed by a weighted cube or player standing on it
//
//===========================================================================//
#include "cbase.h"
#include "props.h"
#include "triggers.h"
#include "prop_weightedcube.h"
#include "portal_mp_gamerules.h"
#include "prop_monster_box.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define PROP_FLOOR_BUTTON_DEFAULT_MODEL_NAME "models/props/portal_button.mdl"
#define PROP_FLOOR_CUBE_BUTTON_MODEL_NAME "models/props/box_socket.mdl"
#define PROP_FLOOR_BALL_BUTTON_MODEL_NAME "models/props/ball_button.mdl"
#define PROP_UNDER_FLOOR_BUTTON_MODEL_NAME "models/props_underground/underground_floor_button.mdl"
static const char * s_pszPressingBoxHasSetteledThinkContext = "PressingBoxHasSetteledThinkContext";
class CPropFloorButton;
enum button_skins
{
button_off_skin,
button_on_skin
};
//
// Trigger for floor button
//
class CPortalButtonTrigger : public CBaseTrigger
{
public:
DECLARE_CLASS( CPortalButtonTrigger, CBaseTrigger );
DECLARE_DATADESC();
static CPortalButtonTrigger *Create( const Vector &vecOrigin, const QAngle &vecAngles, const Vector &vecMins, const Vector &vecMaxs, CPropFloorButton *pOwner );
virtual bool PassesTriggerFilters(CBaseEntity *pOther);
virtual void OnStartTouchAll( CBaseEntity *pOther );
virtual void EndTouch(CBaseEntity *pOther);
virtual void OnEndTouchAll( CBaseEntity *pOther );
virtual void StartTouch(CBaseEntity *pOther);
virtual void Spawn( void );
private:
CPropFloorButton *m_pOwnerButton;
};
LINK_ENTITY_TO_CLASS( trigger_portal_button, CPortalButtonTrigger );
BEGIN_DATADESC( CPortalButtonTrigger )
DEFINE_FIELD( m_pOwnerButton, FIELD_CLASSPTR ),
END_DATADESC()
//
// Floor Button
//
class CPropFloorButton : public CDynamicProp
{
public:
DECLARE_CLASS( CPropFloorButton, CDynamicProp );
DECLARE_DATADESC();
DECLARE_SERVERCLASS();
CPropFloorButton();
virtual void Precache( void );
virtual void Spawn( void );
virtual bool CreateVPhysics( void );
virtual void Activate( void );
virtual void AnimateThink( void );
virtual void UpdateOnRemove( void );
void PressingBoxHasSetteledThink( void );
virtual const char *GetButtonModelName();
virtual bool ShouldPlayerTouch();
virtual bool OnlyAcceptBall( void ) { return false; }
virtual bool AcceptsBall( void ) { return true; }
void SetSkin( int skinNum );
private:
void OnPressed( CBaseEntity* pActivator );
void OnUnPressed( CBaseEntity* pActivator );
COutputEvent m_OnPressed;
COutputEvent m_OnPressedOrange;
COutputEvent m_OnPressedBlue;
COutputEvent m_OnUnPressed;
protected:
CHandle<CPortalButtonTrigger> m_hButtonTrigger;
CNetworkVar( bool, m_bButtonState );
virtual void CreateTriggers( void );
void TriggerStartTouch( CBaseEntity *pOther );
void TriggerEndTouch( CBaseEntity *pOther );
virtual void Press( CBaseEntity *pActivator );
virtual void UnPress( CBaseEntity *pActivator );
void InputPressIn( inputdata_t &inputdata );
void InputPressOut( inputdata_t &inputdata );
virtual void LookUpAnimationSequences( void );
// animation sequences for the button
int m_UpSequence;
int m_DownSequence;
friend class CPortalButtonTrigger;
};
LINK_ENTITY_TO_CLASS( prop_floor_button, CPropFloorButton );
BEGIN_DATADESC( CPropFloorButton )
DEFINE_THINKFUNC( AnimateThink ),
DEFINE_THINKFUNC( PressingBoxHasSetteledThink ),
DEFINE_FIELD( m_UpSequence, FIELD_INTEGER ),
DEFINE_FIELD( m_DownSequence, FIELD_INTEGER ),
DEFINE_FIELD( m_hButtonTrigger, FIELD_EHANDLE ),
DEFINE_INPUTFUNC( FIELD_VOID, "PressIn", InputPressIn ),
DEFINE_INPUTFUNC( FIELD_VOID, "PressOut", InputPressOut ),
DEFINE_OUTPUT( m_OnPressed, "OnPressed" ),
DEFINE_OUTPUT( m_OnPressedOrange, "OnPressedOrange" ),
DEFINE_OUTPUT( m_OnPressedBlue, "OnPressedBlue" ),
DEFINE_OUTPUT( m_OnUnPressed, "OnUnPressed" ),
END_DATADESC()
IMPLEMENT_SERVERCLASS_ST(CPropFloorButton, DT_PropFloorButton)
SendPropBool( SENDINFO( m_bButtonState ) ),
END_SEND_TABLE()
//-----------------------------------------------------------------------------
// Purpose: constructor
//-----------------------------------------------------------------------------
CPropFloorButton::CPropFloorButton():
m_bButtonState( false ) // button is not pressed by default
{
RemoveEffects( EF_SHADOWDEPTH_NOCACHE );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPropFloorButton::Precache( void )
{
PrecacheModel( GetButtonModelName() );
BaseClass::Precache();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPropFloorButton::Spawn( void )
{
KeyValue( "model", GetButtonModelName() );
Precache();
BaseClass::Spawn();
SetSolid( SOLID_VPHYSICS );
LookUpAnimationSequences();
// Start in the up state
ResetSequence( m_UpSequence );
// Ensure the 'off' skin is set
SetSkin( button_off_skin );
//Buttons are unpaintable
AddFlag( FL_UNPAINTABLE );
CreateVPhysics();
CreateTriggers();
AddEffects( EF_MARKED_FOR_FAST_REFLECTION );
// Never let crucial game components fade out!
SetFadeDistance( -1.0f, 0.0f );
SetGlobalFadeScale( 0.0f );
}
void CPropFloorButton::LookUpAnimationSequences( void )
{
// look up animation sequences
m_UpSequence = LookupSequence( "up" );
m_DownSequence = LookupSequence( "down" );
}
bool CPropFloorButton::CreateVPhysics( void )
{
BaseClass::CreateVPhysics();
return true;
}
void CPropFloorButton::Activate( void )
{
BaseClass::Activate();
SetThink( &CPropFloorButton::AnimateThink );
SetNextThink( gpGlobals->curtime + 0.1f );
}
//-----------------------------------------------------------------------------
// Purpose: Animate and catch edge cases for us stopping / starting our animation
//-----------------------------------------------------------------------------
void CPropFloorButton::AnimateThink( void )
{
// Update our animation
StudioFrameAdvance();
DispatchAnimEvents( this );
m_BoneFollowerManager.UpdateBoneFollowers(this);
SetNextThink( gpGlobals->curtime + 0.1f );
RANDOM_CEG_TEST_SECRET();
// debug overlay of trigger displays if ent_bbox is used on entity
if ( m_debugOverlays & OVERLAY_BBOX_BIT )
{
if ( m_hButtonTrigger )
{
m_hButtonTrigger->m_debugOverlays |= OVERLAY_BBOX_BIT;
NDebugOverlay::Cross3D( GetAbsOrigin(), 4, 0, 255, 0, true, 0.1f );
}
}
else
{
if ( m_hButtonTrigger )
{
m_hButtonTrigger->m_debugOverlays &= ~OVERLAY_BBOX_BIT;
}
}
}
void CPropFloorButton::PressingBoxHasSetteledThink( void )
{
if ( gpGlobals->maxClients == 1 && (V_strcmp( gpGlobals->mapname.ToCStr(), "sp_a2_bts1" ) != 0)
&& (V_strcmp( gpGlobals->mapname.ToCStr(), "mp_coop_catapult_1" ) != 0) )
{
UTIL_RecordAchievementEvent( "ACH.BOX_HOLE_IN_ONE" );
}
SetContextThink( NULL, gpGlobals->curtime, s_pszPressingBoxHasSetteledThinkContext );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPropFloorButton::UpdateOnRemove( void )
{
if ( m_hButtonTrigger )
{
UTIL_Remove( m_hButtonTrigger );
m_hButtonTrigger = NULL;
}
BaseClass::UpdateOnRemove();
}
//-----------------------------------------------------------------------------
// Purpose: return floor button model name
//-----------------------------------------------------------------------------
const char *CPropFloorButton::GetButtonModelName()
{
if ( m_ModelName == NULL_STRING )
return PROP_FLOOR_BUTTON_DEFAULT_MODEL_NAME;
return STRING( m_ModelName );
}
//-----------------------------------------------------------------------------
// Purpose: Press the button
//-----------------------------------------------------------------------------
CEG_NOINLINE void CPropFloorButton::Press( CBaseEntity *pActivator )
{
// play the down sequence
ResetSequence( m_DownSequence );
// set the button state for the client
m_bButtonState = true;
// Change the skin
SetSkin( button_on_skin );
CEG_PROTECT_MEMBER_FUNCTION( CPropFloorButton_Press );
// call the function that fires the OnPressed output
OnPressed( pActivator );
}
//-----------------------------------------------------------------------------
// Purpose: UnPress the button
//-----------------------------------------------------------------------------
void CPropFloorButton::UnPress( CBaseEntity *pActivator )
{
// play the up sequence
ResetSequence( m_UpSequence );
// set the button state for the client
m_bButtonState = false;
// Change the skin
SetSkin( button_off_skin );
// call the function that fires the OnUnPressed output
OnUnPressed( pActivator );
}
void CPropFloorButton::InputPressIn( inputdata_t &inputdata )
{
Press( inputdata.pActivator );
}
void CPropFloorButton::InputPressOut( inputdata_t &inputdata )
{
UnPress( inputdata.pActivator );
}
//-----------------------------------------------------------------------------
// Purpose: Fire output for button being pressed
//-----------------------------------------------------------------------------
void CPropFloorButton::OnPressed( CBaseEntity* pActivator )
{
if ( pActivator != NULL )
{
CBaseEntity *pOther = dynamic_cast<CBaseEntity*>(pActivator);
if ( GameRules() && GameRules()->IsMultiplayer() && pOther && pOther->IsPlayer() )
{
if ( pOther->GetTeamNumber() == TEAM_RED )
{
m_OnPressedOrange.FireOutput( pOther, this );
}
else if ( pOther->GetTeamNumber() == TEAM_BLUE )
{
m_OnPressedBlue.FireOutput( pOther, this );
}
}
if( FClassnameIs( pActivator, "prop_monster_box" ) )
{
CPropMonsterBox* pMonsterBox = static_cast<CPropMonsterBox*>( pActivator );
pMonsterBox->BecomeBox( false );
}
}
// If this button was pressed without touching the player, fire the special output used for the 'hole in one' achievement.
if ( UTIL_IsWeightedCube( pActivator ) )
{
CPropWeightedCube *pCube = (CPropWeightedCube*)pActivator;
Assert( pCube );
if ( pCube )
{
if ( pCube->WasTouchedByPlayer() == false )
{
// HACK: this delay is a guess at how long it takes to be sure the box has setteled...
SetContextThink( &CPropFloorButton::PressingBoxHasSetteledThink, gpGlobals->curtime + 2.0f, s_pszPressingBoxHasSetteledThinkContext );
}
}
}
m_OnPressed.FireOutput( pActivator, this );
}
//-----------------------------------------------------------------------------
// Purpose: Fire output when button has reset after being pressed
//-----------------------------------------------------------------------------
void CPropFloorButton::OnUnPressed( CBaseEntity* pActivator )
{
if( pActivator && FClassnameIs( pActivator, "prop_monster_box" ) )
{
CPropMonsterBox* pMonsterBox = static_cast<CPropMonsterBox*>( pActivator );
pMonsterBox->BecomeMonster( false );
}
SetContextThink( NULL, gpGlobals->curtime, s_pszPressingBoxHasSetteledThinkContext );
// fire the OnUnPressed output
m_OnUnPressed.FireOutput( pActivator, this );
}
//-----------------------------------------------------------------------------
// Purpose: Create triggers for button
//-----------------------------------------------------------------------------
void CPropFloorButton::CreateTriggers( void )
{
Vector vecOrigin = GetAbsOrigin();
// trigger size
Vector vecMins( -20,-20,0 );
Vector vecMaxs( 20,20,14 );
// Create the button trigger
m_hButtonTrigger = CPortalButtonTrigger::Create( vecOrigin, GetAbsAngles(), vecMins, vecMaxs, this );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPropFloorButton::TriggerStartTouch( CBaseEntity *pOther )
{
Press( pOther );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPropFloorButton::TriggerEndTouch( CBaseEntity *pOther )
{
UnPress( pOther );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CPropFloorButton::ShouldPlayerTouch()
{
// Yes, players can touch the prop floor button
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Set the model skin
//-----------------------------------------------------------------------------
void CPropFloorButton::SetSkin( int skinNum )
{
m_nSkin = skinNum;
}
//-----------------------------------------------------------------------------
// Purpose: creates the button trigger
//-----------------------------------------------------------------------------
CPortalButtonTrigger *CPortalButtonTrigger::Create( const Vector &vecOrigin, const QAngle &vecAngles, const Vector &vecMins, const Vector &vecMaxs, CPropFloorButton *pOwner )
{
CPortalButtonTrigger *pTrigger = (CPortalButtonTrigger *) CreateEntityByName( "trigger_portal_button" );
if ( pTrigger == NULL )
return NULL;
UTIL_SetOrigin( pTrigger, vecOrigin );
pTrigger->SetAbsAngles( vecAngles );
UTIL_SetSize( pTrigger, vecMins, vecMaxs );
DispatchSpawn( pTrigger );
pTrigger->SetParent( (CBaseEntity *) pOwner );
pTrigger->m_pOwnerButton = pOwner;
return pTrigger;
}
void CPortalButtonTrigger::StartTouch(CBaseEntity *pOther)
{
if( !pOther->IsMarkedForDeletion() && FClassnameIs( pOther, "prop_weighted_cube") )
{
if ( PassesTriggerFilters( pOther ) )
{
//Set the cube to activate
CPropWeightedCube* pCube = assert_cast<CPropWeightedCube*>( pOther );
if( pCube )
{
pCube->SetActivated( true );
}
}
}
BaseClass::StartTouch( pOther );
}
void CPortalButtonTrigger::EndTouch(CBaseEntity *pOther)
{
if( !pOther->IsMarkedForDeletion() && FClassnameIs( pOther, "prop_weighted_cube") )
{
if ( PassesTriggerFilters( pOther ) )
{
//Set the cube to deactivate
CPropWeightedCube* pCube = assert_cast<CPropWeightedCube*>( pOther );
if( pCube )
{
pCube->SetActivated( false );
}
}
}
BaseClass::EndTouch( pOther );
}
//----------------------------------------------------------------------------------
// Purpose: checks filters on trigger in addition to specific filters (player, cube)
//----------------------------------------------------------------------------------
bool CPortalButtonTrigger::PassesTriggerFilters(CBaseEntity *pOther)
{
bool bPassedFilter = BaseClass::PassesTriggerFilters( pOther );
// did I fail the baseclass filter check?
if ( !bPassedFilter )
return false;
// are players allowed to touch me?
if ( m_pOwnerButton->ShouldPlayerTouch() )
{
// did a player touch me?
if ( pOther->IsPlayer() )
return true;
}
// did a cube touch me?
if ( FClassnameIs( pOther, "prop_weighted_cube") || FClassnameIs( pOther, "prop_monster_box") )
{
CPropWeightedCube *pCube = static_cast<CPropWeightedCube*>( pOther );
bool bIsBall = pCube && pCube->GetCubeType() == CUBE_SPHERE;
if ( ( bIsBall && m_pOwnerButton->AcceptsBall() ) || //If the button accepts balls and this is a ball ( floor, under and ball buttons )
( !bIsBall && !m_pOwnerButton->OnlyAcceptBall() ) ) //If the button doesn't only accept balls and this is not a ball ( cube buttons )
{
return true;
}
}
// failed filter check
return false;
}
//----------------------------------------------------------------------------------
// Purpose: called only when the trigger is touched for the first time
//----------------------------------------------------------------------------------
void CPortalButtonTrigger::OnStartTouchAll( CBaseEntity *pOther )
{
// call the button's start touch function
if ( m_pOwnerButton )
{
m_pOwnerButton->TriggerStartTouch( pOther );
}
BaseClass::OnStartTouchAll( pOther );
}
//----------------------------------------------------------------------------------
// Purpose: called when the last object stops touching the trigger
//----------------------------------------------------------------------------------
void CPortalButtonTrigger::OnEndTouchAll( CBaseEntity *pOther )
{
// call the button's end touch function
if ( m_pOwnerButton )
{
m_pOwnerButton->TriggerEndTouch( pOther );
}
BaseClass::OnEndTouchAll( pOther );
}
//----------------------------------------------------------------------------------
// Purpose: spawn the trigger
//----------------------------------------------------------------------------------
void CPortalButtonTrigger::Spawn( void )
{
// Setup our basic attributes
SetMoveType( MOVETYPE_NONE );
SetSolid( SOLID_OBB );
SetSolidFlags( FSOLID_NOT_SOLID|FSOLID_TRIGGER );
AddSpawnFlags( SF_TRIGGER_ALLOW_CLIENTS|SF_TRIGGER_ALLOW_PHYSICS );
BaseClass::Spawn();
}
//
// Floor Button
//
ConVar sv_slippery_cube_button( "sv_slippery_cube_button", "1", FCVAR_CHEAT );
class CPropFloorCubeButton : public CPropFloorButton
{
public:
DECLARE_CLASS( CPropFloorCubeButton, CPropFloorButton );
DECLARE_DATADESC();
CPropFloorCubeButton();
virtual const char *GetButtonModelName();
virtual bool ShouldPlayerTouch();
virtual bool OnlyAcceptBall( void ) { return false; }
virtual bool AcceptsBall( void ) { return m_bAcceptsBall; }
friend class CPortalButtonTrigger;
protected:
virtual void Press( CBaseEntity *pActivator );
virtual void UnPress( CBaseEntity *pActivator );
private:
int m_nStoredMaterialIndex;
bool m_bAcceptsBall;
};
LINK_ENTITY_TO_CLASS( prop_floor_cube_button, CPropFloorCubeButton );
BEGIN_DATADESC( CPropFloorCubeButton )
DEFINE_KEYFIELD( m_bAcceptsBall, FIELD_BOOLEAN, "AcceptsBall" ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CPropFloorCubeButton::CPropFloorCubeButton()
: m_bAcceptsBall( true )
{
}
//-----------------------------------------------------------------------------
// Purpose: get cube button model name
//-----------------------------------------------------------------------------
const char *CPropFloorCubeButton::GetButtonModelName()
{
return PROP_FLOOR_CUBE_BUTTON_MODEL_NAME;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CPropFloorCubeButton::ShouldPlayerTouch()
{
// Players may not touch the cube button
return false;
}
void CPropFloorCubeButton::Press( CBaseEntity *pActivator )
{
if ( sv_slippery_cube_button.GetBool() )
{
// set the object material type to be ice so it will slip into the cube button
IPhysicsObject* pPhysObject = pActivator->VPhysicsGetObject();
if( pPhysObject )
{
m_nStoredMaterialIndex = pPhysObject->GetMaterialIndex();
pPhysObject->SetMaterialIndex( physprops->GetSurfaceIndex( "ice" ) );
}
}
BaseClass::Press( pActivator );
}
void CPropFloorCubeButton::UnPress( CBaseEntity *pActivator )
{
if ( sv_slippery_cube_button.GetBool() )
{
IPhysicsObject* pPhysObject = pActivator->VPhysicsGetObject();
if( pPhysObject )
{
pPhysObject->SetMaterialIndex( m_nStoredMaterialIndex );
}
}
BaseClass::UnPress( pActivator );
}
class CPropFloorBallButton : public CPropFloorButton
{
public:
DECLARE_CLASS( CPropFloorBallButton, CPropFloorButton );
DECLARE_DATADESC();
virtual const char *GetButtonModelName();
virtual bool ShouldPlayerTouch();
virtual bool OnlyAcceptBall( void ) { return true; }
virtual bool AcceptsBall( void ) { return true; }
virtual void CreateTriggers( void );
friend class CPortalButtonTrigger;
private:
int m_nStoredMaterialIndex;
};
LINK_ENTITY_TO_CLASS( prop_floor_ball_button, CPropFloorBallButton );
BEGIN_DATADESC( CPropFloorBallButton )
END_DATADESC()
void CPropFloorBallButton::CreateTriggers( void )
{
Vector vecOrigin = GetAbsOrigin();
// trigger size
Vector vecMins( -5,-5,0 );
Vector vecMaxs( 5,5,14 );
// Create the button trigger
m_hButtonTrigger = CPortalButtonTrigger::Create( vecOrigin, GetAbsAngles(), vecMins, vecMaxs, this );
}
//-----------------------------------------------------------------------------
// Purpose: get cube button model name
//-----------------------------------------------------------------------------
const char *CPropFloorBallButton::GetButtonModelName()
{
return PROP_FLOOR_BALL_BUTTON_MODEL_NAME;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CPropFloorBallButton::ShouldPlayerTouch()
{
// Players may not touch the cube button
return false;
}
class CPropUnderFloorButton : public CPropFloorButton
{
public:
DECLARE_CLASS( CPropUnderFloorButton, CPropFloorButton );
DECLARE_DATADESC();
virtual const char *GetButtonModelName();
friend class CPortalButtonTrigger;
protected:
virtual void LookUpAnimationSequences( void );
virtual void CreateTriggers( void );
};
LINK_ENTITY_TO_CLASS( prop_under_floor_button, CPropUnderFloorButton );
BEGIN_DATADESC( CPropUnderFloorButton )
END_DATADESC()
const char *CPropUnderFloorButton::GetButtonModelName()
{
return PROP_UNDER_FLOOR_BUTTON_MODEL_NAME;
}
void CPropUnderFloorButton::LookUpAnimationSequences( void )
{
// look up animation sequences
m_UpSequence = LookupSequence( "release" );
m_DownSequence = LookupSequence( "press" );
}
void CPropUnderFloorButton::CreateTriggers( void )
{
Vector vecOrigin = GetAbsOrigin();
// trigger size
Vector vecMins( -30, -30, 0 );
Vector vecMaxs( 30, 30, 17 );
// Create the button trigger
m_hButtonTrigger = CPortalButtonTrigger::Create( vecOrigin, GetAbsAngles(), vecMins, vecMaxs, this );
}