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