Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1679 lines
47 KiB

//========= 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 );
}
}
}