//========= 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( 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( 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 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( pEnts[i] ); if ( !pProp ) { CBaseProjectile *pProjectile = dynamic_cast( 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; iGetObject(i); if ( pObj && (pObj != this) && (iObjType == pObj->GetType()) ) { CObjectTeleporter *pTele = dynamic_cast(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 ); } } }