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