summaryrefslogtreecommitdiff
path: root/game/server/tf/tf_obj_teleporter.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/tf/tf_obj_teleporter.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/server/tf/tf_obj_teleporter.cpp')
-rw-r--r--game/server/tf/tf_obj_teleporter.cpp1679
1 files changed, 1679 insertions, 0 deletions
diff --git a/game/server/tf/tf_obj_teleporter.cpp b/game/server/tf/tf_obj_teleporter.cpp
new file mode 100644
index 0000000..8b576c9
--- /dev/null
+++ b/game/server/tf/tf_obj_teleporter.cpp
@@ -0,0 +1,1679 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Teleporter Object
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+
+#include "tf_obj_teleporter.h"
+#include "engine/IEngineSound.h"
+#include "tf_player.h"
+#include "tf_team.h"
+#include "tf_gamerules.h"
+#include "world.h"
+#include "explode.h"
+#include "particle_parse.h"
+#include "tf_gamestats.h"
+#include "tf_weapon_sniperrifle.h"
+#include "tf_fx.h"
+#include "props.h"
+#include "tf_objective_resource.h"
+#include "rtime.h"
+#include "tf_logic_player_destruction.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+// Ground placed version
+#define TELEPORTER_MODEL_ENTRANCE_PLACEMENT "models/buildables/teleporter_blueprint_enter.mdl"
+#define TELEPORTER_MODEL_EXIT_PLACEMENT "models/buildables/teleporter_blueprint_exit.mdl"
+#define TELEPORTER_MODEL_BUILDING "models/buildables/teleporter.mdl"
+#define TELEPORTER_MODEL_LIGHT "models/buildables/teleporter_light.mdl"
+
+#define TELEPORTER_MINS Vector( -24, -24, 0)
+#define TELEPORTER_MAXS Vector( 24, 24, 12)
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+// Seconds it takes a teleporter to recharge
+int g_iTeleporterRechargeTimes[4] =
+{
+ 0,
+ 10,
+ 5,
+ 3
+};
+
+IMPLEMENT_SERVERCLASS_ST( CObjectTeleporter, DT_ObjectTeleporter )
+ SendPropInt( SENDINFO(m_iState), 5 ),
+ SendPropTime( SENDINFO(m_flRechargeTime) ),
+ SendPropTime( SENDINFO(m_flCurrentRechargeDuration) ),
+ SendPropInt( SENDINFO(m_iTimesUsed), 10, SPROP_UNSIGNED ),
+ SendPropFloat( SENDINFO(m_flYawToExit), 8, 0, 0.0, 360.0f ),
+ SendPropBool( SENDINFO(m_bMatchBuilding) ),
+END_SEND_TABLE()
+
+BEGIN_DATADESC( CObjectTeleporter )
+ // keys
+ DEFINE_KEYFIELD( m_iTeleportType, FIELD_INTEGER, "teleporterType" ),
+ DEFINE_KEYFIELD( m_iszMatchingMapPlacedTeleporter, FIELD_STRING, "matchingTeleporter" ),
+ // other
+ DEFINE_THINKFUNC( TeleporterThink ),
+ DEFINE_ENTITYFUNC( TeleporterTouch ),
+END_DATADESC()
+
+PRECACHE_REGISTER( obj_teleporter );
+
+#define TELEPORTER_THINK_CONTEXT "TeleporterContext"
+
+#define BUILD_TELEPORTER_DAMAGE 25 // how much damage an exploding teleporter can do
+
+#define BUILD_TELEPORTER_FADEOUT_TIME 0.25 // time to teleport a player out (teleporter with full health)
+#define BUILD_TELEPORTER_FADEIN_TIME 0.25 // time to teleport a player in (teleporter with full health)
+
+#define BUILD_TELEPORTER_NEXT_THINK 0.05
+
+#define BUILD_TELEPORTER_PLAYER_OFFSET 20 // how far above the origin of the teleporter to place a player
+
+#define BUILD_TELEPORTER_EFFECT_TIME 12.0 // seconds that player glows after teleporting
+
+ConVar tf_teleporter_fov_start( "tf_teleporter_fov_start", "120", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Starting FOV for teleporter zoom.", true, 1, false, 0 );
+ConVar tf_teleporter_fov_time( "tf_teleporter_fov_time", "0.5", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "How quickly to restore FOV after teleport.", true, 0.0, false, 0 );
+
+LINK_ENTITY_TO_CLASS( obj_teleporter, CObjectTeleporter );
+
+//-----------------------------------------------------------------------------
+// Purpose: Teleport the passed player to our destination
+//-----------------------------------------------------------------------------
+void CObjectTeleporter::TeleporterSend( CTFPlayer *pPlayer )
+{
+ if ( !pPlayer )
+ return;
+
+ SetTeleportingPlayer( pPlayer );
+ pPlayer->m_Shared.AddCond( TF_COND_SELECTED_TO_TELEPORT );
+
+ Vector origin = GetAbsOrigin();
+ CPVSFilter filter( origin );
+
+ int iTeam = pPlayer->GetTeamNumber();
+ if ( pPlayer->IsPlayerClass( TF_CLASS_SPY ) && pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) )
+ {
+ if ( GetBuilder() && iTeam != GetBuilder()->GetTeamNumber() )
+ {
+ iTeam = GetBuilder()->GetTeamNumber();
+ }
+ }
+
+ switch( iTeam )
+ {
+ case TF_TEAM_RED:
+ TE_TFParticleEffect( filter, 0.0, "teleported_red", origin, vec3_angle );
+ TE_TFParticleEffect( filter, 0.0, "player_sparkles_red", origin, vec3_angle, pPlayer, PATTACH_POINT );
+ break;
+ case TF_TEAM_BLUE:
+ TE_TFParticleEffect( filter, 0.0, "teleported_blue", origin, vec3_angle );
+ TE_TFParticleEffect( filter, 0.0, "player_sparkles_blue", origin, vec3_angle, pPlayer, PATTACH_POINT );
+ break;
+ default:
+ break;
+ }
+
+ EmitSound( "Building_Teleporter.Send" );
+
+ SetState( TELEPORTER_STATE_SENDING );
+ m_flMyNextThink = gpGlobals->curtime + 0.1;
+
+ m_iTimesUsed++;
+
+ m_hReservedForPlayer = NULL;
+
+ // Strange - Teleports Provided to Allies
+ if ( GetBuilder() && GetBuilder()->GetTeam() == pPlayer->GetTeam() )
+ {
+ // Strange Health Provided to Allies
+ EconEntity_OnOwnerKillEaterEvent(
+ dynamic_cast<CEconEntity *>( GetBuilder()->GetEntityForLoadoutSlot( LOADOUT_POSITION_PDA ) ),
+ GetBuilder(),
+ pPlayer,
+ kKillEaterEvent_TeleportsProvided
+ );
+
+ if ( GetBuilder() != pPlayer &&
+ TFGameRules() &&
+ TFGameRules()->GameModeUsesUpgrades() &&
+ TFGameRules()->State_Get() == GR_STATE_RND_RUNNING )
+ {
+ CTF_GameStats.Event_PlayerAwardBonusPoints( GetBuilder(), pPlayer, 10 );
+ }
+ }
+
+ int iSpeedBoost = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( GetBuilder(), iSpeedBoost, mod_teleporter_speed_boost );
+ if ( iSpeedBoost )
+ {
+ pPlayer->m_Shared.AddCond( TF_COND_SPEED_BOOST, 4.f );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Receive a teleporting player
+//-----------------------------------------------------------------------------
+void CObjectTeleporter::TeleporterReceive( CTFPlayer *pPlayer, float flDelay )
+{
+ if ( !pPlayer )
+ return;
+
+ SetTeleportingPlayer( pPlayer );
+
+ Vector origin = GetAbsOrigin();
+ CPVSFilter filter( origin );
+
+ int iTeam = pPlayer->GetTeamNumber();
+ if ( pPlayer->IsPlayerClass( TF_CLASS_SPY ) && pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) )
+ {
+ if ( GetBuilder() && iTeam != GetBuilder()->GetTeamNumber() )
+ {
+ iTeam = GetBuilder()->GetTeamNumber();
+ }
+ }
+
+ if ( GetBuilder() )
+ {
+ pPlayer->m_Shared.SetTeamTeleporterUsed( GetBuilder()->GetTeamNumber() );
+ }
+
+ switch( iTeam )
+ {
+ case TF_TEAM_RED:
+ TE_TFParticleEffect( filter, 0.0, "teleportedin_red", origin, vec3_angle );
+ break;
+ case TF_TEAM_BLUE:
+ TE_TFParticleEffect( filter, 0.0, "teleportedin_blue", origin, vec3_angle );
+ break;
+ default:
+ break;
+ }
+
+ EmitSound( "Building_Teleporter.Receive" );
+
+ SetState( TELEPORTER_STATE_RECEIVING );
+ m_flMyNextThink = gpGlobals->curtime + BUILD_TELEPORTER_FADEOUT_TIME;
+
+ m_iTimesUsed++;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectTeleporter::CObjectTeleporter()
+{
+ int iHealth = GetMaxHealthForCurrentLevel();
+
+ SetMaxHealth( iHealth );
+ SetHealth( iHealth );
+ UseClientSideAnimation();
+
+ SetType( OBJ_TELEPORTER );
+
+ m_bMatchBuilding.Set( false );
+
+ m_iTeleportType = TTYPE_NONE;
+
+ m_flCurrentRechargeDuration = 0.0f;
+ m_flRechargeTime = 0.0f;
+
+ ListenForGameEvent( "player_spawn" );
+ ListenForGameEvent( "player_team" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectTeleporter::Spawn()
+{
+ SetSolid( SOLID_BBOX );
+
+ m_takedamage = DAMAGE_NO;
+
+ SetState( TELEPORTER_STATE_BUILDING );
+
+ m_flNextEnemyTouchHint = gpGlobals->curtime;
+
+ m_flYawToExit = 0;
+
+ if ( IsEntrance() )
+ {
+ SetModel( TELEPORTER_MODEL_ENTRANCE_PLACEMENT );
+ }
+ else
+ {
+ SetModel( TELEPORTER_MODEL_EXIT_PLACEMENT );
+ }
+
+ m_iUpgradeLevel = 1;
+
+ BaseClass::Spawn();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectTeleporter::UpdateOnRemove()
+{
+ if ( GetTeamNumber() == TF_TEAM_PVE_INVADERS )
+ {
+ TFObjectiveResource()->DecrementTeleporterCount();
+ }
+
+ BaseClass::UpdateOnRemove();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectTeleporter::FirstSpawn()
+{
+ int iHealth = GetMaxHealthForCurrentLevel();
+
+ SetMaxHealth( iHealth );
+ SetHealth( iHealth );
+
+ BaseClass::FirstSpawn();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectTeleporter::SetObjectMode( int iVal )
+{
+#ifdef STAGING_ONLY
+ int iSpeedPad = 0;
+ if ( GetBuilder() )
+ {
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( GetBuilder(), iSpeedPad, teleporter_is_speedpad );
+ if ( iSpeedPad )
+ {
+ SetTeleporterType( TTYPE_SPEEDPAD );
+ }
+ }
+
+ if ( !iSpeedPad )
+ {
+ switch ( iVal )
+ {
+ case MODE_TELEPORTER_ENTRANCE:
+ SetTeleporterType( TTYPE_ENTRANCE );
+ break;
+ case MODE_TELEPORTER_EXIT:
+ SetTeleporterType( TTYPE_EXIT );
+ break;
+ }
+ }
+#else
+ if ( iVal == MODE_TELEPORTER_ENTRANCE )
+ {
+ SetTeleporterType( TTYPE_ENTRANCE );
+ }
+ else
+ {
+ SetTeleporterType( TTYPE_EXIT );
+ }
+#endif
+
+ BaseClass::SetObjectMode( iVal );
+}
+
+//-----------------------------------------------------------------------------
+int CObjectTeleporter::GetUpgradeMetalRequired()
+{
+#ifdef STAGING_ONLY
+ // STAGING_ENGY
+ int iSpeedPad = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( GetBuilder(), iSpeedPad, teleporter_is_speedpad )
+ if ( iSpeedPad )
+ {
+ return 100;
+ }
+#endif
+
+ int nCost = GetObjectInfo( GetType() )->m_UpgradeCost;
+
+ float flCostMod = 1.f;
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetBuilder(), flCostMod, mod_teleporter_cost );
+ if ( flCostMod != 1.f )
+ {
+ nCost *= flCostMod;
+ }
+
+ return nCost;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectTeleporter::SetModel( const char *pModel )
+{
+ BaseClass::SetModel( pModel );
+
+ // Reset this after model change
+ UTIL_SetSize( this, TELEPORTER_MINS, TELEPORTER_MAXS );
+
+ CreateBuildPoints();
+
+ ReattachChildren();
+
+ m_iDirectionBodygroup = FindBodygroupByName( "teleporter_direction" );
+ m_iBlurBodygroup = FindBodygroupByName( "teleporter_blur" );
+
+ if ( m_iBlurBodygroup >= 0 )
+ {
+ SetBodygroup( m_iBlurBodygroup, 0 );
+ }
+}
+
+void CObjectTeleporter::InitializeMapPlacedObject( void )
+{
+ BaseClass::InitializeMapPlacedObject();
+
+ SetObjectMode( IsEntrance() ? MODE_TELEPORTER_ENTRANCE : MODE_TELEPORTER_EXIT );
+
+#ifdef STAGING_ONLY
+ if ( GetTeleporterType() == TTYPE_SPEEDPAD )
+ return;
+#endif
+
+ m_hMatchingTeleporter = dynamic_cast<CObjectTeleporter*>( gEntList.FindEntityByName( NULL, m_iszMatchingMapPlacedTeleporter.ToCStr() ) );
+
+ // Select the teleporter with the most upgrade
+ if ( m_hMatchingTeleporter.Get() )
+ {
+ bool bFrom = (m_hMatchingTeleporter->GetUpgradeLevel() > GetUpgradeLevel() || m_hMatchingTeleporter->GetUpgradeMetal() > GetUpgradeMetal() );
+ CopyUpgradeStateToMatch( m_hMatchingTeleporter, bFrom );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Start building the object
+//-----------------------------------------------------------------------------
+bool CObjectTeleporter::StartBuilding( CBaseEntity *pBuilder )
+{
+ SetStartBuildingModel();
+
+ if ( GetTeleporterType() == TTYPE_NONE )
+ {
+ if ( GetObjectMode() == MODE_TELEPORTER_ENTRANCE )
+ {
+ SetTeleporterType( TTYPE_ENTRANCE );
+ }
+ else
+ {
+ SetTeleporterType( TTYPE_EXIT );
+ }
+ }
+
+ return BaseClass::StartBuilding( pBuilder );
+}
+
+void CObjectTeleporter::SetStartBuildingModel( void )
+{
+ SetState( TELEPORTER_STATE_BUILDING );
+
+ SetModel( TELEPORTER_MODEL_BUILDING );
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+bool CObjectTeleporter::IsPlacementPosValid( void )
+{
+ bool bResult = BaseClass::IsPlacementPosValid();
+
+ if ( !bResult )
+ {
+ return false;
+ }
+
+ // m_vecBuildOrigin is the proposed build origin
+
+ // start above the teleporter position
+ Vector vecTestPos = m_vecBuildOrigin;
+ vecTestPos.z += TELEPORTER_MAXS.z;
+
+ // make sure we can fit a player on top in this pos
+ trace_t tr;
+ UTIL_TraceHull( vecTestPos, vecTestPos, VEC_HULL_MIN, VEC_HULL_MAX, MASK_SOLID | CONTENTS_PLAYERCLIP, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
+
+ return ( tr.fraction >= 1.0 );
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+void CObjectTeleporter::OnGoActive( void )
+{
+ Assert( GetBuilder() || m_bWasMapPlaced );
+
+ SetModel( TELEPORTER_MODEL_LIGHT );
+ SetActivity( ACT_OBJ_IDLE );
+
+ SetContextThink( &CObjectTeleporter::TeleporterThink, gpGlobals->curtime + 0.1, TELEPORTER_THINK_CONTEXT );
+ SetTouch( &CObjectTeleporter::TeleporterTouch );
+
+ SetState( TELEPORTER_STATE_IDLE );
+
+ BaseClass::OnGoActive();
+
+ SetPlaybackRate( 0.0f );
+ m_flLastStateChangeTime = 0.0f; // used as a flag to initialize the playback rate to 0 in the first DeterminePlaybackRate
+
+ // match our partner's maxhealth
+ if ( IsMatchingTeleporterReady() )
+ {
+ CObjectTeleporter *pMatch = GetMatchingTeleporter();
+ if ( pMatch )
+ {
+ UpdateMaxHealth( pMatch->GetMaxHealth() );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectTeleporter::Precache()
+{
+ BaseClass::Precache();
+
+ // Precache Object Models
+ int iModelIndex;
+
+ PrecacheModel( TELEPORTER_MODEL_ENTRANCE_PLACEMENT );
+ PrecacheModel( TELEPORTER_MODEL_EXIT_PLACEMENT );
+
+ iModelIndex = PrecacheModel( TELEPORTER_MODEL_BUILDING );
+ PrecacheGibsForModel( iModelIndex );
+
+ iModelIndex = PrecacheModel( TELEPORTER_MODEL_LIGHT );
+ PrecacheGibsForModel( iModelIndex );
+
+ // Bread models
+ int nRange = TF_LAST_NORMAL_CLASS - TF_FIRST_NORMAL_CLASS;
+ for( int i = 0; i < nRange; ++i )
+ {
+ if ( g_pszBreadModels[i] && *g_pszBreadModels[i] )
+ {
+ PrecacheModel( g_pszBreadModels[i] );
+ }
+ }
+
+ // Precache Sounds
+ PrecacheScriptSound( "Building_Teleporter.Ready" );
+ PrecacheScriptSound( "Building_Teleporter.Send" );
+ PrecacheScriptSound( "Building_Teleporter.Receive" );
+ PrecacheScriptSound( "Building_Teleporter.SpinLevel1" );
+ PrecacheScriptSound( "Building_Teleporter.SpinLevel2" );
+ PrecacheScriptSound( "Building_Teleporter.SpinLevel3" );
+
+ PrecacheParticleSystem( "teleporter_red_charged" );
+ PrecacheParticleSystem( "teleporter_blue_charged" );
+ PrecacheParticleSystem( "teleporter_red_entrance" );
+ PrecacheParticleSystem( "teleporter_blue_entrance" );
+ PrecacheParticleSystem( "teleporter_red_exit" );
+ PrecacheParticleSystem( "teleporter_blue_exit" );
+ PrecacheParticleSystem( "teleporter_arms_circle_red" );
+ PrecacheParticleSystem( "teleporter_arms_circle_blue" );
+ PrecacheParticleSystem( "tpdamage_1" );
+ PrecacheParticleSystem( "tpdamage_2" );
+ PrecacheParticleSystem( "tpdamage_3" );
+ PrecacheParticleSystem( "tpdamage_4" );
+ PrecacheParticleSystem( "teleported_red" );
+ PrecacheParticleSystem( "player_sparkles_red" );
+ PrecacheParticleSystem( "teleported_blue" );
+ PrecacheParticleSystem( "player_sparkles_blue" );
+ PrecacheParticleSystem( "teleportedin_red" );
+ PrecacheParticleSystem( "teleportedin_blue" );
+
+ PrecacheParticleSystem( "teleporter_arms_circle_red_blink" );
+ PrecacheParticleSystem( "teleporter_arms_circle_blue_blink" );
+
+#ifdef STAGING_ONLY
+ // STAGING ENGY
+ PrecacheScriptSound( "Building_Speedpad.BoostStart" );
+ PrecacheScriptSound( "Building_Speedpad.BoostStop" );
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CObjectTeleporter::PlayerCanBeTeleported( CTFPlayer *pPlayer )
+{
+ if ( !pPlayer )
+ return false;
+
+ if ( pPlayer->HasTheFlag() )
+ {
+ if ( !CTFPlayerDestructionLogic::GetRobotDestructionLogic() || ( CTFPlayerDestructionLogic::GetRobotDestructionLogic()->GetType() != CTFPlayerDestructionLogic::TYPE_PLAYER_DESTRUCTION ) )
+ return false;
+ }
+
+ CTFPlayer *pBuilder = GetBuilder();
+ if ( !pBuilder && m_bWasMapPlaced == false )
+ return false;
+
+ if ( pPlayer->IsPlayerClass( TF_CLASS_SPY ) )
+ return true;
+
+ if ( pBuilder && pBuilder->GetTeamNumber() != pPlayer->GetTeamNumber() )
+ return false;
+
+ if ( m_bWasMapPlaced && GetTeamNumber() != pPlayer->GetTeamNumber() )
+ return false;
+
+ if ( TFGameRules() && TFGameRules()->IsPasstimeMode() && pPlayer->m_Shared.HasPasstimeBall() )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectTeleporter::StartTouch( CBaseEntity *pOther )
+{
+ BaseClass::StartTouch(pOther);
+
+ if ( m_hReservedForPlayer == pOther )
+ {
+ m_flReserveAfterTouchUntil = 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectTeleporter::EndTouch( CBaseEntity *pOther )
+{
+ BaseClass::EndTouch(pOther);
+
+ if ( m_hReservedForPlayer == pOther )
+ {
+ // Players can push the reserved player off the teleporter. So after the player falls off the teleporter
+ // we allow him to continue reserving it for a short time.
+ m_flReserveAfterTouchUntil = gpGlobals->curtime + 2.0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectTeleporter::TeleporterTouch( CBaseEntity *pOther )
+{
+ if ( IsDisabled() )
+ {
+ return;
+ }
+
+ // if it's not a player, ignore
+ if ( !pOther->IsPlayer() )
+ return;
+
+ CTFPlayer *pPlayer = ToTFPlayer( pOther );
+
+ if ( !PlayerCanBeTeleported( pPlayer ) )
+ {
+ // are we able to teleport?
+ if ( pPlayer->HasTheFlag() )
+ {
+ // If they have the flag, print a warning that you can't tele with the flag
+ CSingleUserRecipientFilter filter( pPlayer );
+ TFGameRules()->SendHudNotification( filter, HUD_NOTIFY_NO_TELE_WITH_FLAG );
+ }
+ else if ( pPlayer->m_Shared.HasPasstimeBall() )
+ {
+ CSingleUserRecipientFilter filter( pPlayer );
+ TFGameRules()->SendHudNotification( filter, HUD_NOTIFY_PASSTIME_NO_TELE );
+ }
+
+ if ( m_hReservedForPlayer == pPlayer )
+ {
+ m_hReservedForPlayer = NULL;
+ }
+
+ return;
+ }
+
+#ifdef STAGING_ONLY
+ // STAGING_ENGY
+ // For Speed Teleporters
+ if ( IsSpeedPad() )
+ {
+ ApplySpeedBoost( pPlayer );
+ return;
+ }
+#endif
+
+ int iBiDirectional = 0;
+ if ( GetOwner() )
+ {
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwner(), iBiDirectional, bidirectional_teleport );
+ }
+
+ if ( IsEntrance() || iBiDirectional == 1 )
+ {
+ // Reserve ourselves for the first player who touches us.
+ // Players can push the reserved player off the teleporter. So after the player falls off the teleporter
+ // we allow him to continue reserving it for a short time.
+ bool bSetReserved = !m_hReservedForPlayer;
+ if ( !bSetReserved )
+ {
+ bSetReserved = ( !PlayerCanBeTeleported(m_hReservedForPlayer) || !m_hReservedForPlayer->IsAlive() ||
+ (m_flReserveAfterTouchUntil != 0 && m_flReserveAfterTouchUntil < gpGlobals->curtime) );
+ }
+
+ if ( bSetReserved )
+ {
+ m_hReservedForPlayer = pPlayer;
+ m_flReserveAfterTouchUntil = 0;
+ }
+
+ // If we're reserved for another player, ignore me
+ if ( m_hReservedForPlayer != pPlayer )
+ return;
+
+ if ( ( m_iState == TELEPORTER_STATE_READY ) )
+ {
+ // get the velocity of the player touching the teleporter
+ if ( pPlayer->GetAbsVelocity().LengthSqr() < (5.0*5.0) )
+ {
+ CObjectTeleporter *pDest = GetMatchingTeleporter();
+
+ if ( pDest )
+ {
+ TeleporterSend( pPlayer );
+ }
+ }
+ else
+ {
+ // If it's been some time since we went active, and the reserved player still
+ // hasn't teleported, we clear his reservation to prevent griefing.
+ if ( gpGlobals->curtime - m_flLastStateChangeTime > 3.0 )
+ {
+ m_hReservedForPlayer = NULL;
+ }
+ }
+ }
+ }
+}
+
+#ifdef STAGING_ONLY
+//STAGING_ENGY
+//-----------------------------------------------------------------------------
+void CObjectTeleporter::ApplySpeedBoost( CTFPlayer *pPlayer )
+{
+ if ( m_iState != TELEPORTER_STATE_READY )
+ return;
+
+ Vector origin = GetAbsOrigin();
+ CPVSFilter filter( origin );
+ int iTeam = pPlayer->GetTeamNumber();
+ if ( pPlayer->IsPlayerClass( TF_CLASS_SPY ) && pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) )
+ {
+ if ( GetBuilder() && iTeam != GetBuilder()->GetTeamNumber() )
+ {
+ iTeam = GetBuilder()->GetTeamNumber();
+ }
+ }
+
+ switch ( iTeam )
+ {
+ case TF_TEAM_RED:
+ TE_TFParticleEffect( filter, 0.0, "teleported_red", origin, vec3_angle );
+ TE_TFParticleEffect( filter, 0.0, "player_sparkles_red", origin, vec3_angle, pPlayer, PATTACH_POINT );
+ break;
+ case TF_TEAM_BLUE:
+ TE_TFParticleEffect( filter, 0.0, "teleported_blue", origin, vec3_angle );
+ TE_TFParticleEffect( filter, 0.0, "player_sparkles_blue", origin, vec3_angle, pPlayer, PATTACH_POINT );
+ break;
+ default:
+ break;
+ }
+
+ float flUpgrade = (float)GetUpgradeLevel();
+ pPlayer->m_Shared.AddCond( TF_COND_NO_COMBAT_SPEED_BOOST, 3.0f + flUpgrade );
+
+ SetState( TELEPORTER_STATE_RECHARGING );
+
+ EmitSound( "Building_Speedpad.BoostStart" );
+
+ m_flCurrentRechargeDuration = 2.0f - ( flUpgrade / 3.0f );
+ m_flRechargeTime = gpGlobals->curtime + ( BUILD_TELEPORTER_FADEOUT_TIME + BUILD_TELEPORTER_FADEIN_TIME + m_flCurrentRechargeDuration );
+ m_flMyNextThink = gpGlobals->curtime + m_flCurrentRechargeDuration;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CObjectTeleporter::Command_Repair( CTFPlayer *pActivator, float flRepairMod )
+{
+ float flTargetHeal = 100.0f * flRepairMod;
+ int iAmountToHeal = MIN( flTargetHeal, GetMaxHealth() - GetHealth() );
+
+ // repair the building
+ int iRepairCost = ceil( (float)( iAmountToHeal ) * 0.2f );
+
+ TRACE_OBJECT( UTIL_VarArgs( "%0.2f CObjectTeleporter::Command_Repair ( %f / %d ) - cost = %d\n", gpGlobals->curtime,
+ GetHealth(),
+ GetMaxHealth(),
+ iRepairCost ) );
+
+ if ( iRepairCost > 0 )
+ {
+ if ( iRepairCost > pActivator->GetBuildResources() )
+ {
+ iRepairCost = pActivator->GetBuildResources();
+ }
+
+ pActivator->RemoveBuildResources( iRepairCost );
+
+ int nHealthToAdd = iRepairCost * 5;
+ float flNewHealth = MIN( GetMaxHealth(), GetHealth() + nHealthToAdd );
+ SetHealth( flNewHealth );
+
+ // add the same amount of health to our match
+ CObjectTeleporter *pMatch = GetMatchingTeleporter();
+ if ( pMatch )
+ {
+ pMatch->AddHealth( nHealthToAdd );
+ }
+
+ return ( iRepairCost > 0 );
+ }
+ else
+ {
+ // see if our match needs repairing
+ CObjectTeleporter *pMatch = GetMatchingTeleporter();
+ if ( pMatch && !pMatch->IsBuilding() )
+ {
+ iAmountToHeal = MIN( flTargetHeal, pMatch->GetMaxHealth() - pMatch->GetHealth() );
+
+ // repair the building
+ iRepairCost = ceil( (float)( iAmountToHeal ) * 0.2f );
+
+ TRACE_OBJECT( UTIL_VarArgs( "%0.2f CObjectTeleporter::Command_Repair ( %f / %d ) - cost = %d\n", gpGlobals->curtime,
+ pMatch->GetHealth(),
+ pMatch->GetMaxHealth(),
+ iRepairCost ) );
+
+ if ( iRepairCost > 0 )
+ {
+ if ( iRepairCost > pActivator->GetBuildResources() )
+ {
+ iRepairCost = pActivator->GetBuildResources();
+ }
+
+ pActivator->RemoveBuildResources( iRepairCost );
+
+ int nHealthToAdd = iRepairCost * 5;
+ float flNewHealth = MIN( pMatch->GetMaxHealth(), pMatch->GetHealth() + nHealthToAdd );
+ pMatch->SetHealth( flNewHealth );
+
+ return ( iRepairCost > 0 );
+ }
+ }
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Is this teleporter connected and functional? (ie: not sapped, disabled, upgrading, unconnected, etc)
+//-----------------------------------------------------------------------------
+bool CObjectTeleporter::IsReady( void )
+{
+#ifdef STAGING_ONLY
+ if ( !IsMatchingTeleporterReady() && !IsSpeedPad() )
+#else
+ if ( !IsMatchingTeleporterReady() )
+#endif
+ return false;
+
+ return GetState() != TELEPORTER_STATE_BUILDING && !IsUpgrading() && !IsDisabled();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CObjectTeleporter::IsMatchingTeleporterReady( void )
+{
+ if ( m_hMatchingTeleporter.Get() == NULL )
+ {
+ m_hMatchingTeleporter = FindMatch();
+ }
+
+ if ( m_hMatchingTeleporter &&
+ m_hMatchingTeleporter->GetState() != TELEPORTER_STATE_BUILDING &&
+ !m_hMatchingTeleporter->IsUpgrading() &&
+ !m_hMatchingTeleporter->IsDisabled() )
+ return true;
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if we are in the process of teleporting the given player
+//-----------------------------------------------------------------------------
+bool CObjectTeleporter::IsSendingPlayer( CTFPlayer *pPlayer )
+{
+ return ( GetState() == TELEPORTER_STATE_SENDING && m_hTeleportingPlayer == pPlayer );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CObjectTeleporter::CheckUpgradeOnHit( CTFPlayer *pPlayer )
+{
+ if ( BaseClass::CheckUpgradeOnHit( pPlayer ) )
+ {
+ CopyUpgradeStateToMatch( GetMatchingTeleporter(), false );
+ return true;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectTeleporter::CopyUpgradeStateToMatch( CObjectTeleporter *pMatch, bool bFrom )
+{
+ // Copy our upgrade state to the matching teleporter
+ if ( pMatch )
+ {
+ if ( bFrom )
+ {
+ pMatch->CopyUpgradeStateToMatch( pMatch, false );
+ }
+ else
+ {
+ pMatch->m_iHighestUpgradeLevel = m_iHighestUpgradeLevel;
+ pMatch->m_iUpgradeLevel = m_iUpgradeLevel;
+ pMatch->m_iUpgradeMetal = m_iUpgradeMetal;
+ pMatch->m_iUpgradeMetalRequired = m_iUpgradeMetalRequired;
+ pMatch->m_nDefaultUpgradeLevel = m_nDefaultUpgradeLevel;
+ pMatch->m_flUpgradeCompleteTime = m_flUpgradeCompleteTime;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectTeleporter *CObjectTeleporter::GetMatchingTeleporter( void )
+{
+#ifdef STAGING_ONLY
+ if ( GetTeleporterType() == TTYPE_SPEEDPAD )
+ return NULL;
+#endif
+ return m_hMatchingTeleporter.Get();
+}
+
+void CObjectTeleporter::DeterminePlaybackRate( void )
+{
+ float flPlaybackRate = GetPlaybackRate();
+
+ bool bWasBelowFullSpeed = ( flPlaybackRate < 1.0f );
+
+ if ( IsBuilding() )
+ {
+ // Fall back to standard object building to handle reverse sappers without duplicating code
+ BaseClass::DeterminePlaybackRate();
+ return;
+ }
+ else if ( IsPlacing() )
+ {
+ SetPlaybackRate( 1.0f );
+ }
+ else
+ {
+ float flFrameTime = 0.1; // BaseObjectThink delay
+
+ switch( m_iState )
+ {
+ case TELEPORTER_STATE_READY:
+ {
+ // spin up to 1.0 from whatever we're at, at some high rate
+ flPlaybackRate = Approach( 1.0f, flPlaybackRate, 0.5f * flFrameTime );
+ }
+ break;
+
+ case TELEPORTER_STATE_RECHARGING:
+ {
+ // Recharge - spin down to low and back up to full speed over the recharge time
+
+ float flTotalTime = m_flCurrentRechargeDuration;
+ float flFirstStage = flTotalTime * 0.4;
+ float flSecondStage = flTotalTime * 0.6;
+
+ // 0 -> 4, spin to low
+ // 4 -> 6, stay at low
+ // 6 -> 10, spin to 1.0
+
+ float flTimeSinceChange = gpGlobals->curtime - m_flLastStateChangeTime;
+
+ float flLowSpinSpeed = 0.15f;
+
+ if ( flTimeSinceChange <= flFirstStage )
+ {
+ flPlaybackRate = RemapVal( gpGlobals->curtime,
+ m_flLastStateChangeTime,
+ m_flLastStateChangeTime + flFirstStage,
+ 1.0f,
+ flLowSpinSpeed );
+ }
+ else if ( flTimeSinceChange > flFirstStage && flTimeSinceChange <= flSecondStage )
+ {
+ flPlaybackRate = flLowSpinSpeed;
+ }
+ else
+ {
+ flPlaybackRate = RemapVal( gpGlobals->curtime,
+ m_flLastStateChangeTime + flSecondStage,
+ m_flLastStateChangeTime + flTotalTime,
+ flLowSpinSpeed,
+ 1.0f );
+ }
+ }
+ break;
+
+ default:
+ {
+ if ( m_flLastStateChangeTime <= 0.0f )
+ {
+ flPlaybackRate = 0.0f;
+ }
+ else
+ {
+ // lost connect - spin down to 0.0 from whatever we're at, slowish rate
+ flPlaybackRate = Approach( 0.0f, flPlaybackRate, 0.25f * flFrameTime );
+ }
+ }
+ break;
+ }
+
+ // Always spin when the teleporter is done building
+ if ( TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS )
+ {
+ flPlaybackRate = 1.f;
+ }
+
+ SetPlaybackRate( flPlaybackRate );
+ }
+
+ bool bBelowFullSpeed = ( GetPlaybackRate() < 1.0f );
+
+ if ( m_iBlurBodygroup >= 0 && bBelowFullSpeed != bWasBelowFullSpeed )
+ {
+ if ( bBelowFullSpeed )
+ {
+ SetBodygroup( m_iBlurBodygroup, 0 ); // turn off blur bodygroup
+ }
+ else
+ {
+ SetBodygroup( m_iBlurBodygroup, 1 ); // turn on blur bodygroup
+ }
+ }
+
+ StudioFrameAdvance();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Teleport a player to us
+//-----------------------------------------------------------------------------
+void CObjectTeleporter::RecieveTeleportingPlayer( CTFPlayer* pTeleportingPlayer )
+{
+ if ( !pTeleportingPlayer || IsMarkedForDeletion() )
+ return;
+
+ // get the position we'll move the player to
+ Vector newPosition = GetAbsOrigin();
+ newPosition.z += TELEPORTER_MAXS.z + 1;
+
+ // Telefrag anyone in the way
+ CBaseEntity *pEnts[256];
+ Vector mins, maxs;
+ Vector expand( 4, 4, 4 );
+
+ mins = newPosition + VEC_HULL_MIN - expand;
+ maxs = newPosition + VEC_HULL_MAX + expand;
+
+ // move the player
+ if ( pTeleportingPlayer )
+ {
+ CUtlVector<CBaseEntity*> hPlayersToKill;
+ bool bClear = true;
+
+ // Telefrag any players in the way
+ int numEnts = UTIL_EntitiesInBox( pEnts, 256, mins, maxs, 0 );
+ if ( numEnts )
+ {
+ //Iterate through the list and check the results
+ for ( int i = 0; i < numEnts && bClear; i++ )
+ {
+ if ( pEnts[i] == NULL )
+ continue;
+
+ if ( pEnts[i] == this )
+ continue;
+
+ // kill players
+ if ( pEnts[i]->IsPlayer() && ( pEnts[i]->GetTeamNumber() >= FIRST_GAME_TEAM ) )
+ {
+ if ( !pTeleportingPlayer->InSameTeam( pEnts[i] ) && ( pTeleportingPlayer->GetTeamNumber() >= FIRST_GAME_TEAM ) )
+ {
+ hPlayersToKill.AddToTail( pEnts[i] );
+ }
+ continue;
+ }
+
+ if ( pEnts[i]->IsBaseObject() )
+ continue;
+
+ // Solid entities will prevent a teleport
+ if ( pEnts[i]->IsSolid() && pEnts[i]->ShouldCollide( pTeleportingPlayer->GetCollisionGroup(), MASK_SOLID ) &&
+ g_pGameRules->ShouldCollide( pTeleportingPlayer->GetCollisionGroup(), pEnts[i]->GetCollisionGroup() ) )
+ {
+ // HACK to solve the problem of building teleporter exits in CDynamicProp entities at
+ // the end of maps like Badwater that have the VPhysics explosions when the point is capped
+ CDynamicProp *pProp = dynamic_cast<CDynamicProp *>( pEnts[i] );
+ if ( !pProp )
+ {
+ CBaseProjectile *pProjectile = dynamic_cast<CBaseProjectile *>( pEnts[i] );
+ if ( !pProjectile )
+ {
+ bClear = false;
+ }
+ }
+ else
+ {
+ if ( !pProp->IsEffectActive( EF_NODRAW ) )
+ {
+ // We're going to teleport into something solid. Abort & destroy this exit.
+ bClear = false;
+ }
+ }
+
+ // need to make sure we're really overlapping geometry and not just overlapping the bounding boxes
+ if ( !bClear )
+ {
+ Ray_t ray;
+ ray.Init( newPosition, newPosition, VEC_HULL_MIN - expand, VEC_HULL_MAX + expand );
+
+ trace_t trace;
+ enginetrace->ClipRayToEntity( ray, MASK_PLAYERSOLID, pEnts[i], &trace );
+ if ( trace.fraction >= 1.0f )
+ {
+ // not overlapping geometry so reset our check
+ bClear = true;
+ }
+ }
+ }
+ }
+ }
+
+ if ( bClear )
+ {
+ // Telefrag all enemy players we've found
+ for ( int player = 0; player < hPlayersToKill.Count(); player++ )
+ {
+ hPlayersToKill[player]->TakeDamage( CTakeDamageInfo( pTeleportingPlayer, pTeleportingPlayer, 1000, DMG_CRUSH, TF_DMG_CUSTOM_TELEFRAG ) );
+ }
+
+ pTeleportingPlayer->Teleport( &newPosition, &(GetAbsAngles()), &vec3_origin );
+
+ // Unzoom if we are a sniper zoomed!
+ pTeleportingPlayer->m_Shared.InstantlySniperUnzoom();
+
+ pTeleportingPlayer->SetFOV( pTeleportingPlayer, 0, tf_teleporter_fov_time.GetFloat(), tf_teleporter_fov_start.GetInt() );
+
+ color32 fadeColor = {255,255,255,100};
+ UTIL_ScreenFade( pTeleportingPlayer, fadeColor, 0.25, 0.4, FFADE_IN );
+
+ // 1/20 of te time teleport bread -- except for Soldier who does it 1/3 of the time.
+ int nMax = pTeleportingPlayer->GetPlayerClass()->GetClassIndex() == TF_CLASS_SOLDIER ? 2 : 19;
+ if ( RandomInt( 0, nMax ) == 0 )
+ {
+ SpawnBread( pTeleportingPlayer );
+ }
+ }
+ else
+ {
+ DetonateObject();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectTeleporter::TeleporterThink( void )
+{
+ if ( IsCarried() )
+ return;
+
+ SetContextThink( &CObjectTeleporter::TeleporterThink, gpGlobals->curtime + BUILD_TELEPORTER_NEXT_THINK, TELEPORTER_THINK_CONTEXT );
+
+ // At any point, if our match is not ready, revert to IDLE
+#ifdef STAGING_ONLY
+ if ( IsDisabled() || ( IsMatchingTeleporterReady() == false && !IsSpeedPad() ))
+#else
+ if ( IsDisabled() || IsMatchingTeleporterReady() == false )
+#endif
+ {
+ if ( GetState() != TELEPORTER_STATE_IDLE && GetState() != TELEPORTER_STATE_UPGRADING )
+ {
+ SetState( TELEPORTER_STATE_IDLE );
+ ShowDirectionArrow( false );
+ }
+ return;
+ }
+
+ if ( m_flMyNextThink && m_flMyNextThink > gpGlobals->curtime )
+ return;
+
+ // pMatch is not NULL and is not building
+#ifdef STAGING_ONLY
+ CObjectTeleporter *pMatch = NULL;
+
+ if ( !IsSpeedPad() )
+ {
+ pMatch = GetMatchingTeleporter();
+ Assert( pMatch );
+ Assert( pMatch->m_iState != TELEPORTER_STATE_BUILDING );
+ }
+#else
+ CObjectTeleporter *pMatch = GetMatchingTeleporter();
+#endif
+
+ int iBiDirectional = 0;
+
+ if ( GetOwner() )
+ {
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwner(), iBiDirectional, bidirectional_teleport );
+ }
+
+ switch ( m_iState )
+ {
+ // Teleporter is not yet active, do nothing
+ case TELEPORTER_STATE_BUILDING:
+ case TELEPORTER_STATE_UPGRADING:
+ ShowDirectionArrow( false );
+ break;
+
+ default:
+ case TELEPORTER_STATE_IDLE:
+ // Do we have a match that is active?
+#ifdef STAGING_ONLY
+ if ( IsMatchingTeleporterReady() || IsSpeedPad() )
+#else
+ if ( IsMatchingTeleporterReady() )
+#endif
+ {
+ SetState( TELEPORTER_STATE_READY );
+ EmitSound( "Building_Teleporter.Ready" );
+
+ if ( IsEntrance() || iBiDirectional == 1 )
+ {
+ ShowDirectionArrow( true );
+ }
+ }
+ break;
+
+ case TELEPORTER_STATE_READY:
+ if ( IsEntrance() || iBiDirectional == 1 )
+ {
+ ShowDirectionArrow( true );
+ }
+ break;
+
+ case TELEPORTER_STATE_SENDING:
+ {
+ pMatch->TeleporterReceive( m_hTeleportingPlayer, 1.0 );
+
+ m_flCurrentRechargeDuration = (float)g_iTeleporterRechargeTimes[GetUpgradeLevel()];
+
+ if ( !m_bWasMapPlaced )
+ {
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetBuilder(), m_flCurrentRechargeDuration, mult_teleporter_recharge_rate );
+ }
+
+ m_flRechargeTime = gpGlobals->curtime + ( BUILD_TELEPORTER_FADEOUT_TIME + BUILD_TELEPORTER_FADEIN_TIME + m_flCurrentRechargeDuration );
+
+ // change state to recharging...
+ SetState( TELEPORTER_STATE_RECHARGING );
+ }
+ break;
+
+ case TELEPORTER_STATE_RECEIVING:
+ {
+ RecieveTeleportingPlayer( m_hTeleportingPlayer.Get() );
+
+ SetState( TELEPORTER_STATE_RECEIVING_RELEASE );
+
+ m_flMyNextThink = gpGlobals->curtime + ( BUILD_TELEPORTER_FADEIN_TIME );
+ }
+ break;
+
+ case TELEPORTER_STATE_RECEIVING_RELEASE:
+ {
+ CTFPlayer *pTeleportingPlayer = m_hTeleportingPlayer.Get();
+
+ if ( pTeleportingPlayer )
+ {
+ pTeleportingPlayer->TeleportEffect();
+ pTeleportingPlayer->m_Shared.RemoveCond( TF_COND_SELECTED_TO_TELEPORT );
+ CTF_GameStats.Event_PlayerUsedTeleport( GetBuilder(), pTeleportingPlayer );
+
+ pTeleportingPlayer->SpeakConceptIfAllowed( MP_CONCEPT_TELEPORTED );
+
+ IGameEvent * event = gameeventmanager->CreateEvent( "player_teleported" );
+ if ( event )
+ {
+ event->SetInt( "userid", pTeleportingPlayer->GetUserID() );
+ event->SetInt( "builderid", GetBuilder() ? GetBuilder()->GetUserID() : 0 );
+ if ( GetMatchingTeleporter() )
+ {
+ event->SetFloat( "dist", GetMatchingTeleporter()->GetAbsOrigin().DistTo( GetAbsOrigin() ) );
+ }
+ else
+ {
+ event->SetFloat( "dist", 0 );
+ }
+ gameeventmanager->FireEvent( event );
+ }
+ }
+
+ // reset the pointers to the player now that we're done teleporting
+ SetTeleportingPlayer( NULL );
+ pMatch->SetTeleportingPlayer( NULL );
+
+ SetState( TELEPORTER_STATE_RECHARGING );
+
+ m_flCurrentRechargeDuration = (float)g_iTeleporterRechargeTimes[GetUpgradeLevel()];
+ m_flMyNextThink = gpGlobals->curtime + m_flCurrentRechargeDuration;
+ }
+ break;
+
+ case TELEPORTER_STATE_RECHARGING:
+ // If we are finished recharging, go active
+ if ( gpGlobals->curtime > m_flRechargeTime )
+ {
+ SetState( TELEPORTER_STATE_READY );
+ EmitSound( "Building_Teleporter.Ready" );
+ }
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectTeleporter::FinishedBuilding( void )
+{
+ BaseClass::FinishedBuilding();
+
+ if ( GetTeamNumber() == TF_TEAM_PVE_INVADERS )
+ {
+ TFObjectiveResource()->IncrementTeleporterCount();
+ }
+
+ SetActivity( ACT_OBJ_RUNNING );
+ SetPlaybackRate( 0.0f );
+}
+
+void CObjectTeleporter::SetState( int state )
+{
+ if ( state != m_iState )
+ {
+ m_iState = state;
+ m_flLastStateChangeTime = gpGlobals->curtime;
+ }
+}
+
+void CObjectTeleporter::ShowDirectionArrow( bool bShow )
+{
+ if ( bShow != m_bShowDirectionArrow )
+ {
+ if ( m_iDirectionBodygroup >= 0 )
+ {
+ SetBodygroup( m_iDirectionBodygroup, bShow ? 1 : 0 );
+ }
+
+ m_bShowDirectionArrow = bShow;
+
+ if ( bShow )
+ {
+ CObjectTeleporter *pMatch = GetMatchingTeleporter();
+
+ Assert( pMatch );
+
+ Vector vecToOwner = pMatch->GetAbsOrigin() - GetAbsOrigin();
+ QAngle angleToExit;
+ VectorAngles( vecToOwner, Vector(0,0,1), angleToExit );
+ angleToExit -= GetAbsAngles();
+
+ // pose param is flipped and backwards, adjust.
+ m_flYawToExit = anglemod( -angleToExit.y + 180 );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CObjectTeleporter::DrawDebugTextOverlays(void)
+{
+ int text_offset = BaseClass::DrawDebugTextOverlays();
+
+ if (m_debugOverlays & OVERLAY_TEXT_BIT)
+ {
+ CObjectTeleporter *pMatch = GetMatchingTeleporter();
+
+ char tempstr[512];
+
+ // match
+ Q_snprintf( tempstr, sizeof( tempstr ), "Match Found: %s", ( pMatch != NULL ) ? "Yes" : "No" );
+ EntityText(text_offset,tempstr,0);
+ text_offset++;
+
+ // state
+ Q_snprintf( tempstr, sizeof( tempstr ), "State: %d", m_iState.Get() );
+ EntityText(text_offset,tempstr,0);
+ text_offset++;
+
+ // recharge time
+ if ( gpGlobals->curtime < m_flRechargeTime )
+ {
+ float flPercent = ( m_flRechargeTime - gpGlobals->curtime ) / m_flCurrentRechargeDuration;
+
+ Q_snprintf( tempstr, sizeof( tempstr ), "Recharging: %.1f", flPercent );
+ EntityText(text_offset,tempstr,0);
+ text_offset++;
+ }
+ }
+ return text_offset;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CObjectTeleporter* CObjectTeleporter::FindMatch( void )
+{
+ int iObjType = GetType();
+ CObjectTeleporter *pMatch = NULL;
+
+ CTFPlayer *pBuilder = GetBuilder();
+ Assert( pBuilder || m_bWasMapPlaced );
+ if ( !pBuilder )
+ {
+ return NULL;
+ }
+
+ int i;
+ int iNumObjects = pBuilder->GetObjectCount();
+ for ( i=0; i<iNumObjects; i++ )
+ {
+ CBaseObject *pObj = pBuilder->GetObject(i);
+
+ if ( pObj && (pObj != this) && (iObjType == pObj->GetType()) )
+ {
+ CObjectTeleporter *pTele = dynamic_cast<CObjectTeleporter*>(pObj);
+ if ( pTele && (( IsEntrance() && pTele->IsExit() ) ||
+ ( IsExit() && pTele->IsEntrance() )) )
+ {
+ pMatch = pTele;
+ CObjectTeleporter* pOtherMatch = pMatch->GetMatchingTeleporter();
+ if ( pOtherMatch && pOtherMatch != this )
+ {
+ pMatch = NULL;
+ continue;
+ }
+ break;
+ }
+ }
+ }
+
+ if ( pMatch )
+ {
+ // Select the teleporter with the most upgrade
+ bool bFrom = (pMatch->GetUpgradeLevel() > GetUpgradeLevel() || pMatch->GetUpgradeMetal() > GetUpgradeMetal() );
+ CopyUpgradeStateToMatch( pMatch, bFrom );
+ }
+
+ return pMatch;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectTeleporter::Explode( void )
+{
+ CObjectTeleporter *pMatch = GetMatchingTeleporter();
+ if ( pMatch )
+ {
+ pMatch->m_iHighestUpgradeLevel = 1;
+ pMatch->m_iUpgradeLevel = 1;
+ pMatch->m_iUpgradeMetal = 0;
+
+ int iHealth = pMatch->GetMaxHealthForCurrentLevel();
+ pMatch->UpdateMaxHealth( iHealth, true );
+
+ if ( pMatch->GetTeleportingPlayer() )
+ {
+ pMatch->GetTeleportingPlayer()->m_Shared.RemoveCond( TF_COND_SELECTED_TO_TELEPORT );
+ }
+ pMatch->SetTeleportingPlayer( NULL );
+ }
+
+ if ( m_hTeleportingPlayer.Get() )
+ {
+ m_hTeleportingPlayer.Get()->m_Shared.RemoveCond( TF_COND_SELECTED_TO_TELEPORT );
+ }
+ SetTeleportingPlayer( NULL );
+
+ BaseClass::Explode();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Update the max health value and scale the health value to match
+//-----------------------------------------------------------------------------
+void CObjectTeleporter::UpdateMaxHealth( int nHealth, bool bForce /* = false */ )
+{
+ if ( m_bCarryDeploy && !bForce )
+ return;
+
+ float flPercentageHealth = (float)GetHealth()/(float)GetMaxHealth();
+
+ SetMaxHealth( nHealth );
+ SetHealth( nHealth * flPercentageHealth );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Raises the Teleporter one level
+//-----------------------------------------------------------------------------
+void CObjectTeleporter::StartUpgrading( void )
+{
+ // Call our base class upgrading first to update our health and maxhealth
+ BaseClass::StartUpgrading();
+
+ // Tell our partner to match his maxhealth to ours
+ CObjectTeleporter *pMatch = GetMatchingTeleporter();
+ if ( pMatch && !m_bCarryDeploy && !pMatch->m_bCarryDeploy )
+ {
+ pMatch->UpdateMaxHealth( GetMaxHealth() );
+ }
+
+ SetState( TELEPORTER_STATE_UPGRADING );
+}
+
+void CObjectTeleporter::FinishUpgrading( void )
+{
+ SetState( TELEPORTER_STATE_IDLE );
+
+ if ( ShouldQuickBuild() )
+ {
+ // See if we have a lower level match and upgrade them
+ if ( m_hMatchingTeleporter.Get() && m_hMatchingTeleporter->GetUpgradeLevel() < GetUpgradeLevel() )
+ {
+ CopyUpgradeStateToMatch( m_hMatchingTeleporter, false );
+ }
+ }
+
+ BaseClass::FinishUpgrading();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CObjectTeleporter::InputWrenchHit( CTFPlayer *pPlayer, CTFWrench *pWrench, Vector hitLoc )
+{
+ return BaseClass::InputWrenchHit( pPlayer, pWrench, hitLoc );
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+void CObjectTeleporter::MakeCarriedObject( CTFPlayer *pCarrier )
+{
+ ShowDirectionArrow( false );
+
+ BaseClass::MakeCarriedObject( pCarrier );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectTeleporter::InputEnable( inputdata_t &inputdata )
+{
+ BaseClass::InputEnable( inputdata );
+
+ if ( !IsDisabled() )
+ {
+ if ( m_hMatchingTeleporter && m_hMatchingTeleporter->IsDisabled() )
+ {
+ m_hMatchingTeleporter->UpdateDisabledState();
+ if ( !m_hMatchingTeleporter->IsDisabled() )
+ {
+ m_hMatchingTeleporter->OnGoActive();
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CObjectTeleporter::InputDisable( inputdata_t &inputdata )
+{
+ BaseClass::InputDisable( inputdata );
+
+ if ( m_hMatchingTeleporter && !m_hMatchingTeleporter->IsDisabled() )
+ {
+ m_hMatchingTeleporter->SetDisabled( true );
+ m_hMatchingTeleporter->OnGoInactive();
+ }
+}
+
+void CObjectTeleporter::SpawnBread( const CTFPlayer* pTeleportingPlayer )
+{
+ if( !pTeleportingPlayer )
+ return;
+
+ const char* pszModelName = g_pszBreadModels[ RandomInt( 0, TF_LAST_NORMAL_CLASS - TF_FIRST_NORMAL_CLASS - 1 ) ];
+ CPhysicsProp *pProp = NULL;
+
+ MDLHandle_t h = mdlcache->FindMDL( pszModelName );
+ if ( h != MDLHANDLE_INVALID )
+ {
+ // Must have vphysics to place as a physics prop
+ studiohdr_t *pStudioHdr = mdlcache->GetStudioHdr( h );
+ if ( pStudioHdr && mdlcache->GetVCollide( h ) )
+ {
+ // Try to create entity
+ pProp = dynamic_cast< CPhysicsProp * >( CreateEntityByName( "prop_physics_override" ) );
+ if ( pProp )
+ {
+ Vector vecSpawn = GetAbsOrigin();
+ vecSpawn.z += TELEPORTER_MAXS.z + 50;
+ QAngle qSpawnAngles = GetAbsAngles();
+ pProp->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
+ // so it can be pushed by airblast
+ pProp->AddFlag( FL_GRENADE );
+ // so that it will always be interactable with the player
+ char buf[512];
+ // Pass in standard key values
+ Q_snprintf( buf, sizeof(buf), "%.10f %.10f %.10f", vecSpawn.x, vecSpawn.y, vecSpawn.z );
+ pProp->KeyValue( "origin", buf );
+ Q_snprintf( buf, sizeof(buf), "%.10f %.10f %.10f", qSpawnAngles.x, qSpawnAngles.y, qSpawnAngles.z );
+ pProp->KeyValue( "angles", buf );
+ pProp->KeyValue( "model", pszModelName );
+ pProp->KeyValue( "fademindist", "-1" );
+ pProp->KeyValue( "fademaxdist", "0" );
+ pProp->KeyValue( "fadescale", "1" );
+ pProp->KeyValue( "inertiaScale", "1.0" );
+ pProp->KeyValue( "physdamagescale", "0.1" );
+ pProp->Precache();
+ DispatchSpawn( pProp );
+ pProp->m_takedamage = DAMAGE_YES; // Take damage, otherwise this can block trains
+ pProp->SetHealth( 5000 );
+ pProp->Activate();
+ IPhysicsObject *pPhysicsObj = pProp->VPhysicsGetObject();
+ if ( pPhysicsObj )
+ {
+ AngularImpulse angImpulse( RandomFloat( -100, 100 ), RandomFloat( -100, 100 ), RandomFloat( -100, 100 ) );
+ Vector vForward;
+ AngleVectors( qSpawnAngles, &vForward );
+ Vector vecVel = ( vForward * 100 ) + Vector( 0, 0, 200 ) + RandomVector( -50, 50 );
+ pPhysicsObj->SetVelocityInstantaneous( &vecVel, &angImpulse );
+ }
+
+ // Die in 10 seconds
+ pProp->ThinkSet( &CBaseEntity::SUB_Remove, gpGlobals->curtime + 10, "DieContext" );
+ }
+ }
+
+ mdlcache->Release( h ); // counterbalance addref from within FindMDL
+ }
+}
+
+void CObjectTeleporter::FireGameEvent( IGameEvent *event )
+{
+ if ( FStrEq( event->GetName(), "player_spawn" ) ||
+ FStrEq( event->GetName(), "player_team" ) )
+ {
+ // On instant-spawn servers, players can change teams just as the teleporter
+ // queues them for a teleport and will still teleport them even if they respawn / change team.
+ //
+ // If we hear a spawn or team-change event for our queued player, clear them from the queue
+ if ( !m_hTeleportingPlayer.Get() )
+ return;
+
+ const int iUserID = event->GetInt( "userid" );
+ if ( iUserID == m_hTeleportingPlayer->GetUserID() )
+ {
+ SetTeleportingPlayer( NULL );
+ }
+ }
+} \ No newline at end of file