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